一串以NUL字节结尾的零个或多个字符。C语言不存在显式的字符串类型,字符串通常存储在字符数组中。
字符串常量
在程序中使用字符串常量会生成一个“指向字符的常量指针”,当一个字符串常量出现于一个表达式中时,表达式所使用的值就是这些字符所存储的位置,而不是这些字符本身。因此,可以把字符串常量赋值给一个“指向字符的指针”,该指针的值就是字符串常量所存储的地址。
void test()
{
char *p = NULL;
p = "Hello World"; //p[0] = 'H';
p = "abc"; //p[0] = 'a';
}
“Hello World”字符串常量存储于静态数据区的某个位置,该部分内存在程序运行过程中一直存在,不会被清空。
指针p是一个局部指针变量,其值等于字符串常量的存储地址,指针p的值可以改变,即p可以指向不同的位置,但字符串常量的存储位置始终不变。
头文件string.h包含了使用字符串函数所需的原型和声明。
字符串长度
字符串的长度就是它所包含的字符个数。字符串所包含的字符内部不能出现NUL字符,NUL字符是字符串的终止符,但它本身并不是字符串的一部分,所以字符串的长度不包括NUL字节。
库函数strlen原型如下:
size_t strlen(char const *string);
该函数返回一个类型为size_t的值,size_t是一个无符号整数类型。在表达式中使用无符号数可能导致不可预料的后果,例如:
if(strlen(x) >= strlen(y)) ...
if(strlen(x) - strlen(y) >= 0) ...
事实上并不等价,第1条语句将按照预期工作,而第2条语句中,无符号数绝不可能是负的,当其与0比较时,结果将始终是真。
char *strcpy(char *dst, char const *src);
char *strcat(char *dst, char const *src);
int strcmp(char const *s1, char const *s2);
不受限制是指,上述函数只是通过寻找字符串结尾的NUL字节来判断它的长度,当使用这些函数时,程序员必须保证结果字符串不会溢出为其指定的内存。
复制字符串
char message[] = "Hello World";
strcpy(message, "abc");
复制操作之后,数组的内容为:['a', 'b', 'c', 0, 'o', ' ', 'W', 'o', 'r', 'l', 'd', 0]
字符'c'后面的第一个NUL字节后面的几个字符将无法被字符串访问到。
char message[] = "abc";
strcpy(message, "Hello World");
目标字符数组的空间不足以容纳第2个字符串,因此数组后面的部分内存空间将被侵占,原来存储在那里的值将被改写。
连接字符串
strcpy(message, "Hello ");
strcat(message, name); //name是一个char指针,指向内容为"Jim"
strcat(message, ", How are you?");
message结果将是"Hello Jim, How are you?"
和前面一样,程序员必须保证目标数组的剩余空间足以容纳新连接的字符串。
strcpy和strcat都返回它们第一个参数的一份拷贝,因此可以嵌套调用这些函数,例如:
strcat(strcpy(dst, a), b);
但这种嵌套调用的风格相对于分开调用来说在功能上并无优势,事实上,在这些函数的大多数调用中,它们的返回值只是被简单忽略。
字符串比较
strcmp(a, b);
strcmp需要注意该函数的返回值,a > b则返回一个大于0的值,a < b则返回一个小于0的值,如果a == b则返回0。
首先,if(strcmp(a, b))并不表示两个字符串相等,其结果为真,并执行if里面的语句
其次,标准并没有规定a和b不相等时返回1或-1,因此将函数返回值与1或-1做比较,其得到的结果未必是正确的。
注意:strcmp并不存在溢出字符数组的危险,但该函数在查找字符串时也以NUL作为结尾,因此,如果传入函数的参数没有NUL结束符号,则strcmp将对字符串后面的字节进行比较,得到没有意义的比较结果。
为了防止难以预料的数组溢出,标准库包含了一些函数,这些函数接受一个显示长度的参数,用于限定进行复制或比较的字符数。len以字节为单位。
char *strncpy(char *dst, char const *src, size_t len);
char *strncat(char *dst, char const *src, size_t len);
int strncmp(char const *s1, char const *s2, size_t len);
strncpy向dst写入len个字符,如果源字符串的长度小于len, 则用额外的NUL填充到len长度;如果源字符串的长度大于或等于len,只有len个字符被复制到dst,但,dst将不会以NUL字节结尾!
为了确定字符串实际以NUL字节结尾,可以考虑如下的代码段:
char buffer[BSIZE];
strncpy(buffer, name, BSIZE);
buffer[BSIZE - 1] = '\0';
strncat与strncpy不同,它不会对目标数组用NUL字节填充,而且总会在结果字符串后面添加一个NUL字节。
strncmp只比较len个字符。
查找一个字符
char *strchr(char const *str, int ch);
char *strrchr(char const *str, int ch);
上述函数的第二个参数是整型值,但它包含了一个字符值。strchr函数在字符串str中找到字符ch第一次出现的位置,找到后返回一个指向该位置的指针,找不到则返回NULL。strrchr的功能和strchr基本一致,只不过它返回最后一个指向字符最后一次出现的位置的指针。
查找一个子串
char *strstr(char const *s1, char const *s2);
函数在s1中查找整个s2第一次出现的位置,并返回一个指向该位置的指针,否则返回NULL。如果第二个参数是一个空字符串,函数将返回s1。
标准库并不存在strrstr函数,可以自己实现,参考《C和指针》9.5节。
字符串函数只能处理内部不包含NUL的字符串数据,另外一组相关的函数操作与字符串函数类似,能够处理任意的字节序列:
void *memcpy(void *dst, void const *src, size_t length);
void *memmove(void *dst, void const *src, size_t length);
void *memcmp(void const *a, void const *b, size_t length);
void *memchr(void const *a, int ch, size_t length);
void *memset(void *a, int ch, size_t length);
每个原型都包含一个显示的参数说明需要处理的字节数,且不会因为遇到NUL而终止操作。length以字节为单位。
memcpy和memmove都从src的起始位置复制length个字节到dst的内存起始位置。不同的是,如果src和dst以任何形式出现了重叠,前者的结果是未定义的,而后者则允许源操作数和目标操作数重叠。
char dst[SIZE], src[SIZE];
memcpy(dst, src, SIZE);
如果数组是整型数组,则有:
memcpy(dst, src, sizeof(src)); //sizeof(数组名)表示整个数组所占字节数
如果数组元素是大于一个字节的数据,如int,要确保数量和数据类型的长度相乘:
memcpy(dst, src, count * sizeof(src[0])); //sizeof(数组元素)表示数组元素所占字节数
如果源和目标操作数可能存在重叠,就应该使用memmove:
memmove(x, x+1, (count - 1) * sizeof(x[0]));
memcmp对两段内存内容进行比较,函数的返回值与strcmp相同。由于memcmp是根据一串无符号字节进行比较的,所以如果该函数用于比较不是单字节的数据,如整数或浮点数时,可能会出现不可预料的后果。
memchr从a的起始位置查找字符ch第一次出现的位置,并返回一个指向该位置的指针,共查找length个字节,未找到则返回NULL。
memset函数把从a开始的length个字节都设置为字符ch:
memset(buffer, 0, SIZE);
memset可以对数组、结构体等进行初始化:
int arr[100];
memset(arr, 0, sizeof(arr)); //sizeof(数组名)表示整个数组所占字节数
typedef struct _Person{
int age;
char name[10];
}Person;//typedef 定义Person类型
Person P;//声明Person类型的变量P
memset(&P, 0, sizeof(Person)); //将变量P中的所有数据初始化为0