第五章 字符串与指针

字符串基本

  • 字符串有两种定义形式
    • 用字符数组来定义,如char string[]="Hello Wordl!"
    • 用指针指向字符串常量来定义char *string="Hello Wordl!"
  • 不管是用上述那种方法,在字符串的最后都有\0以代表字符串结束。
  • 计算字符串长度时是不包括\0字符的
  • 注意NULNULL是不同的,NUL代表\0NULL的定义是((void*)0)
  • 不能使用sizeof对字符串求取长度
  • 字符串字面量不是位于堆区域,也不是栈区域。对于字符串字面量来讲,是没有作用域概念的。
  • GCC中,字符串字面量可以更改,除非将其限制为字符串常量

字符串的初始化

目前初始化字符串常用的方法有四种

  • 第一种,初学者最常用的,使用字符数组初始化字符串。 char string[]="Hello World!" 每个数组元素保存的就是字符串的每个字符

注意,这里字符数组的长度是13,而字符串长度是12.不要忘了最后的\0

  • 第二种,使用空数组配合strcpy函数将字符串字面量复制到数组
#include <string.h>
char string[13];
strcpy(string,"Hello World!");

注意,这里定义数组时不能定义成char string[],这样程序无法为数组分配内存

  • 第三种,使用字符指针,在堆中动态分配字符串
#include <string.h>
char *header = (char *)malloc(strlen("Hello World!")+1);
// 或者char *header = (char *)malloc(13);
strcpy(string,"Hello World!");

这种是在堆中创建了字面量的副本

  • 第四种,使用字符指针,直接指向字符串字面量 char *header="Hello World!" 这是最简单的方式,推荐

  • 第五种,通过标准输入获取字符串

char *command;
printf("Enter a Command: ");
scanf("%s",command);

字符串的内存分布

字符串很有意思的地方就是,它可以以很多种内存形式存在。以下的代码和图片能清晰的概括出字符串的内存分布情况

为了更好地理解书中的图片,本人自己重新绘制了图片

代码

//全局变量,位于全局内存
#include<string.h>
char    *globalHeader = "HELLO WORLD!";
char    globalArray[] = "HELLO WORLD!";
void displayString()
{
    //静态变量,位于全局内存
    static char     *staticHeader = "HELLO WORLD!";
    static char     staticArray[] = "HELLO WORLD!";
    //局部变量,位于自动内存
    char    *localHeader          = "HELLO WORLD!";
    char    localArray[]          = "HELLO WORLD!";
    //动态变量,位于动态内存(堆)
    char *heapHeader              = (char *)malloc(strlen("HELLO WORLD!")+1);
    strcpy(heapHeader,""HELLO WORLD!"")
}

说明图 字符串内存分布

标准字符串操作函数

在C标准库中,定义了对字符串基本操作的函数,而且这些函数经常被使用。这里不再赘述每个函数的意义,函数原型很简单。有什么疑问可以查看标准手册。具体的内容将在代码中体现。

strcmp()

  • memset()函数可以用来清空数组,第一个参数是指向数组的指针,第二个是打算使用的值,第三个参数是长度
  • 比较字符不用包括\0
  • 在代码
char cmd[20];
scanf("%s", cmd);
if (!strcmp(cmd, "Quit"))

中,不能使用if(cmd == "Quit"),这是在比较cmd的地址!

strcpy()

  • 拼接函数的原型是char *strcat(char *, const char *),此函数返回拼接后字符串的地址。
  • 与上面两个不同时的是,我们必须先为拼接后的字符串分配好内存,然后再进行拼接,否则会出现内存越界等错误。

传递字符串作为函数的参数

  • 传递字符串时,最好以常量的形式进行传递,以防止对实参的修改
  • 传递需要初始化的字符串,即想通过一函数来初始化字符串内容,那么需要将字符串指针传递给函数,并返回处理后缓冲区的指针,这里要注意三点:
    • 必须传递缓冲区的地址和长度
    • 调用者负责释放缓冲区
    • 函数通常返回缓冲区的指针
  • 在C语言中,数字0\0,或者说NUL是不同的.ASIC中真的的0是NUL,而数字0是有值的。
  • snprintf()函数是printf()函数的变体,第一和第二个参数是指定一个分配好了的区域地址和长度,以后的参数与printf()就相同了

sizeofstrlen的区别

  • sizeof是运算符,strlen是函数
  • sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以'\0'结尾的。
  • 数组做sizeof的参数不退化,传递给strlen就退化为指针了。
  • 实际上,用sizeof来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。具体而言,当参数分别如下时,sizeof返回的值表示的含义如下:
    • 数组——编译时分配的数组空间大小;
    • 指针——存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,应该为4);
    • 类型——该类型所占的空间大小;
    • 对象——对象的实际占用空间大小;
    • 函数——函数的返回类型所占的空间大小。函数的返回类型不能是void。
  • 对于静态声明char str[20]="0123456789"
    • int a=strlen(str); //a=10strlen计算字符串的长度,以结束符\0 为字符串结束。
    • int b=sizeof(str); //b=20;sizeof计算的则是分配的数组str[20] 所占的内存空间的大小,不受里面存储的内容改变。
  • 对于静态声明char str[20]
    • int a = strlen(str); //a=0strlen计算字符串的长度,以结束符\0 为字符串结束,然后没有字符串,所以为0。
    • int b=sizeof(str); //b=20;sizeof计算的则是分配的数组str[20] 所占的内存空间的大小,不受里面存储的内容改变。
  • 对声明char *str="Hello World!"
    • int a=strlen(str); //a=12strlen计算字符串的长度,以结束符\0 为字符串结束。
    • int b=sizeof(str); //b=4;sizeof这里计算的是str指针所占的空间,所以为4

请查看代码diffStrlenSizeof.c

返回字符串

返回字符串实际是返回字符串的地址,有三种方式可以返回字符串地址:

  • 字面量
    • 字面量没有作用域的概念,所以在函数内将字符串分配于字符串字面量池内,字符串地址不会随着函数结束而变动。
  • 动态分配的内存
    • 只要将动态分配在堆内的地址返回给调用者,那么字符串的地址也不会丢失。
  • 本地字符串变量 - 不要使用!

字符串与函数指针

字符串与函数指针相结合可以实现强大的功能,详细请查看代码。

results matching ""

    No results matching ""