第五章 字符串与指针
字符串基本
- 字符串有两种定义形式
- 用字符数组来定义,如
char string[]="Hello Wordl!"
- 用指针指向字符串常量来定义
char *string="Hello Wordl!"
- 用字符数组来定义,如
- 不管是用上述那种方法,在字符串的最后都有
\0
以代表字符串结束。 - 计算字符串长度时是不包括
\0
字符的 - 注意
NUL
和NULL
是不同的,NUL
代表\0
,NULL
的定义是((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()
就相同了
sizeof
与strlen
的区别
sizeof
是运算符,strlen
是函数sizeof
可以用类型做参数,strlen
只能用char*
做参数,且必须是以'\0'
结尾的。- 数组做
sizeof
的参数不退化,传递给strlen
就退化为指针了。 - 实际上,用
sizeof
来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。具体而言,当参数分别如下时,sizeof
返回的值表示的含义如下:- 数组——编译时分配的数组空间大小;
- 指针——存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,应该为4);
- 类型——该类型所占的空间大小;
- 对象——对象的实际占用空间大小;
- 函数——函数的返回类型所占的空间大小。函数的返回类型不能是void。
- 对于静态声明
char str[20]="0123456789"
int a=strlen(str); //a=10
,strlen
计算字符串的长度,以结束符\0
为字符串结束。int b=sizeof(str); //b=20;
,sizeof
计算的则是分配的数组str[20]
所占的内存空间的大小,不受里面存储的内容改变。
- 对于静态声明
char str[20]
int a = strlen(str); //a=0
,strlen
计算字符串的长度,以结束符\0
为字符串结束,然后没有字符串,所以为0。int b=sizeof(str); //b=20;
,sizeof
计算的则是分配的数组str[20]
所占的内存空间的大小,不受里面存储的内容改变。
- 对声明
char *str="Hello World!"
int a=strlen(str); //a=12
,strlen
计算字符串的长度,以结束符\0
为字符串结束。int b=sizeof(str); //b=4;
,sizeof
这里计算的是str
指针所占的空间,所以为4
请查看代码diffStrlenSizeof.c
返回字符串
返回字符串实际是返回字符串的地址,有三种方式可以返回字符串地址:
- 字面量
- 字面量没有作用域的概念,所以在函数内将字符串分配于字符串字面量池内,字符串地址不会随着函数结束而变动。
- 动态分配的内存
- 只要将动态分配在堆内的地址返回给调用者,那么字符串的地址也不会丢失。
- 本地字符串变量 - 不要使用!
字符串与函数指针
字符串与函数指针相结合可以实现强大的功能,详细请查看代码。