天天看点

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);
}