天天看點

C指針之五:指針和字元串

指針和字元串

目标:

    1、深入了解指針及指針與字元串結合的用法

    2、掌握聲明和初始化字元串的不同方法,研究C程式中字面量池的使用及其影響

    3、函數傳參的時候,可以用字元指針傳遞字元串,也可以用字元字面量傳遞參數

    4、函數傳回字元串

        <1>、字元串從外部傳遞函數并由函數修改

        <2>、函數内部配置設定

        <3>、傳回靜态配置設定的字元串

一、字元串基礎

    NULL和NUL不同。NULL用來表示特殊的指針,定義為((void*)0),而

NUL是一個char,定義為\0,兩者不能混用

    另外還有一個坑:字元常量是單引号引起來的字元序列,字元常量通常由一個字元組成,

也可以包含多個字元,在C中,他們的類型是int,

    int iSize = 0;

    iSize = sizeof(char);

    iASize = sizeof('a');

    注意在c中,sizeof('a') = 4,在C++中sizeof('a') = 1,這個是語言設計者故意這麼做的

    (一)、字元串聲明

        三種方式:字面量,字元數組,字元指針

        字元串字面量是用雙引号引起來的字元序列

        字元字面量是用單引号引起來的字元序列

    (二)、字元串字面量池

        字元串字面量一般配置設定在隻讀記憶體中,是以不可變。

        為了避免意外修改字元串字面量,可以把變量聲明為常量

        const char* pszName = "Sound";

    (三)、字元串初始化

        初始化字元串的方法取決于變量是被聲明為字元數組還是字元指針,

字元串所用的記憶體要麼是數組要麼是指針指向的一塊記憶體

        1、初始化char數組

        <1>、可以用初始化操作符初始化char數組

        char header[] = "Media Player"

        <2>、用strcpy函數初始化數組

        char header[13];

        strcpy(header,"Media Player");

        <3>、更Low的版畫是吧字元串逐個指派給數組元素

        header[0] = 'M';

        header[1] = 'e';

        header[2] = '\0'

        注意:下面的指派方式是不合法的,不能把字元串字面量的位址指派給數組名字

        char header2[];

        header2 = "Media Player";

        2、初始化char指針

        動态記憶體配置設定可以提供更多的靈活性,當然也可以讓記憶體存在的更久

        <1>

        char* header = (char*)malloc(strlen("Media Player")+1);

        需要注意的時候,一定要記得算上終結符NUL

        一定不要使用sizeof操作符,因為sizeof計算的是數組或者指針的長度,strlen函數确定的是

已有字元串的長度

        <2>、可以直接将字元字面量的位址直接指派給字元指針

        char* header = "Media Player";

        注意,試圖用字元字面量來初始化char指針不會起作用,因為字元字面量是int類型,這其實是

嘗試在把整數賦給字元指針,這樣經常會造成應用程式在接引指針的時候終止

        char* prefix = '+'; // 不合法

        正确做法

        prefix = (char*)malloc(2);

        *prefix = '+';

        *(prefix+1) = 0;

        3、從标準輸入初始化字元串

        最容易出錯的地方,在于沒有給變量配置設定記憶體

        char * command;

        scanf("%s",command);

        修改之後

        char  command[20];

        scanf("%s", command);

        4、字元串位置小結

        static 修飾過的,或者在主函數外定義的都在全局記憶體區

二、标準字元串操作

    (一)、比較字元串

    int strcmp(const char* s1,const char* s2);

        <0: s1<s2

        =0: s1=s2

        >0:

    分析一段有問題的代碼

    char command[16];

    printf("Enter a Command");

    scanf("%s",command);

    if(command == "Quit")

    上述代碼之是以有問題,在于上面比較的是位址,而不是位址中的内容,用數字名字或者

字元串字面量就會傳回位址

   (二)、複制字元串

   兩個指針可以引用同一個字元串,兩個指針引用同一個位址成為别名。

一個指針指派給另一個指針不會複制字元串,隻是複制了字元串的位址

    (三)、拼接字元串

    1、做法

    char* strcat(char* s1,char* s2);

   第一個字元串必須足夠長,否則函數會越界寫入,導緻不可預期的問題

    下面是标準做法

char *error = "ERROR:";
  char*errorMessage = "Not enough memory";

  char*buffer = (char*)malloc(strlen(error)+strlen(errorMessage)+1);

  strcpy(buffer, error);
  strcat(buffer, errorMessage);

  printf("%s\n", buffer);
  printf("%s\n", error);
  printf("%s\n", errorMessage);      

錯誤做法:

    沒有為拼接後的字元串配置設定足夠的記憶體,就可能會覆寫第一個字元串

char *error = "ERROR:";
  char*errorMessage = "Not enough memory";

  strcat(error, errorMessage);

  printf("%s\n", error);
  printf("%s\n", errorMessage);      

 這樣做輸出的結果可能會是

    ERROR:Not enough memory

    ot enough memory

    還有可能崩潰

    2、注意的地方

    拼接字元串容易犯錯的另一個地方是使用字元字面量而不是字元串字面量

    正确做法

char *path = "C:";
  char * currentPath = (char*)malloc(strlen(path) + 2);
  strcpy(currentPath, path);
  currentPath = strcat(currentPath, "\\");

  printf("%s\n",currentPath);      

  錯誤做法:

    currentPath = strcat(currentPath, ‘\\’);

    這裡第二個參數會被錯誤地解釋為char類型變量的位址

三、傳遞字元串

    (一)、傳遞簡單字元串

    把字元串位址傳遞給函數

    比如模拟下strlen

size_t stringLength(char* pszString)
    {
        size_t length = 0;
        while(*(pszString++))
        {
            length++;
        }

        return length;
    }      

總結一下,字元串實際上以char常量的指針的形式傳遞

    (二)、傳遞字元常量的指針

    很常見也很有用,在用指針傳遞字元串的同時,也能夠防止傳遞的字元串被修改

size_t stringLength(const char* pszString)
    {
        size_t length = 0;
        while(*(pszString++))
        {
            length++;
        }

        return length;
    }      

(三)、傳遞需要初始化的字元串

    需要考慮:給函數傳遞一個空的緩沖區讓它填充傳回,還是讓函數動态配置設定緩沖區并傳回

    1、要傳遞緩沖區:

    <1>、必須傳遞緩沖區位址和長度

    <2>、調用者負責釋放緩沖區

    <3>、函數通常傳回緩沖區的指針

char* format(char* pszBuffer,size_t iSize,const char* pszName,size_t iQuality,size_t iWeight)
    {
        snprintf(pszBuffer, iSize, "Item: %s Quality: %u Weight: %u",pszName,iQuality,iWeight);
        
        return pszBuffer;
    }

    char buffer[64];
    const char* pszOutput = format(buffer,sizeof(buffer),"Alex",25,45);      

 2、傳遞NULL給緩沖區(主要是這種寫法需要注意記憶體釋放的問題)

char * format(char* pszBuffer,  const char* pszName, size_t iQuality, size_t iWeight)
    {
        char* pszFormatString = "Item: %s Quality: %u Weight: %u";
        size_t iFormatStringLength = strlen(pszFormatString) - 6;
        int iNameLength = strlen(pszName);
        size_t iLength = iFormatStringLength + iNameLength;

        if(NULL == pszBuffer)
        {
            pszBuffer = (char *)malloc(iLength);
        }

        snprintf(pszBuffer, iLength, "Item: %s Quality: %u Weight: %u", pszName, iQuality, iWeight);

        return pszBuffer;
    }

    const char* pszUseful = format(NULL, "KAKA", 23, 33);
    cout <<"pszUseful = " << pszUseful<< endl;      

  第二種寫法的主要缺點在于需要手動申請,手動釋放記憶體

    (四)、給應用程式傳遞參數

int main(int argc,char** argv)
    {
        return 0;
    }      

 第一個參數argc,是一個指定傳遞參數數量的整數

    第二個桉樹argv,是傳遞的參數

四、傳回字元串

    (一)、傳回字面量的位址

    函數傳回字元串的時候,傳回的實際是字元串的位址,這個應該關注的是

如何傳回合法的位址,要做到這一點,可以傳回以下三種對象之一的引用

    <1>、字面量

    <2>、動态配置設定的記憶體

    <3>、本地字元串變量

    注意:針對多個不同目的傳回同一個靜态字元串的指針可能會有問題

char *staticFormat(const char* pszName, size_t iQuality, size_t iWeight)
    {
        static char szbuffer[64]; // 
        sprintf(szbuffer, "Item: %s Quality: %u Weight:%u",pszName,iQuality,iWeight);

        return szbuffer;
    }

    char* szPart1 = staticFormat("Alex", 25, 45);
    char* szPart2 = staticFormat("Poston", 25, 55);

    cout <<"szPart1 = " << szPart1 << endl;
    cout << "szPart2 = " << szPart2 << endl;      

問題的原因,在于staticFormat兩次調用都使用同一個靜态緩沖區,後一次調用會覆寫

前一次調用的結果

    (二)、傳回動态記憶體配置設定的位址

    這個函數用完之後要釋放記憶體,例如

char *blanks(int iNumber)
    {
        char* szSpaces = (char*)malloc(iNumber + 1);
        for(int i = 0;i<iNumber;i++)
        {
            //szSpaces[i] = "12";
            strcpy(&szSpaces[i],"g");
        }

        return  szSpaces;
    }
    char *tmp = blanks(5);
    cout <<"tmp--" << tmp<< endl;
    free(tmp);      

 傳回局部字元串的位址

    傳回局部字元串的位址可能會出問題,如果記憶體被别的棧幀覆寫,就會損壞,應該避免這種寫法

下面是一個錯誤示範

char *blanks(int numer)
    {
        char spaces[MAX_TAB_LENGTH];
        int i = 0;
        for(i = 0;i<numer&&i <MAX_TAB_LENGTH;i++)
        {
            spaces[i] = '';
        }
        spaces[i] = '\0';
        return spaces;
    }      

五、函數指針和字元串

// 大寫字元轉小寫
char *stringToLower(const char* pszString)
{
    char* pszTemp = (char*)malloc(strlen(pszString));
    char *start = pszTemp;

    while(*pszString != 0)
    {
        *pszTemp++ = tolower(*pszString++);
    }
    *pszTemp = 0;

    return start;
}

// 比較
int Compare(const char*pszS1,const char* pszS2)
{
    return strcmp(pszS1, pszS2);
}

// 忽略大小的比較
int compareIgnoreCase(const char*pszS1, const char* pszS2)
{
    char *pszStr1 = stringToLower(pszS1);
    char *pszStr2 = stringToLower(pszS2);

    int iResult = strcmp(pszStr1, pszStr2);

    free(pszStr1);
    free(pszStr2);

    return iResult;

    return 0;
}

// 定義函數指針
typedef int (fptrOperation)(const char* pszStr1, const char* pszStr2);

// sort函數
void sort(char *array[], int iSize, fptrOperation opration)
{
    int iSwap = 1;
    while(iSwap)
    {
        iSwap = 0;
        for(int i = 0;i<iSize -1;i++)
        {
            if(opration(array[i],array[i+1])>0)
            {
                iSwap = 1;
                char * tmp = array[i];
                array[i] = array[i + 1];
                array[i + 1] = tmp;
            }
        }
    }
}

// 輸出名字
void displayNames(char*names[],int iSize)
{
    for(int i = 0;i<iSize;i++)
    {
        printf("%s", names[i]);
        printf(" ");
    }

    printf("\n");
}

int main(int argc,int **argv)
{
    char *names[] = {"Bob","Ted","Color","Alice","alice"};
    sort(names, 5, Compare);
    displayNames(names, 5);
}