字符串 - string
这节课,我们终于来到了字符串的学习。在课程开始的时候,我们已经简单的介绍过字符串(string)了,字符串其实就是用双引号 ""
引起来的一串字符,像名字、爱好、地址等文本信息都需要用到字符串。但是 C 语言中没有字符串这种变量类型,在程序中,如果要存储字符串,可以通过以下 2 种方式:
- 通过 char 数组保存字符串,例如:
char str[] = "abc_123"
- 通过 char* 指针保存字符串的首地址,例如:
char* str = "abc_123"
通过 char 数组保存字符串
字符串由多个字符组成,可用 1 个 char 型的一维数组来保存 1 个字符串,注意 1 个一维数组只能保存 1 个字符串。保存字符串只是 char 数组的一种应用,但 char 型数组不一定都要保存字符串。
字符 \0
是字符串的结束标志,因此,如果用 char 数组保存字符串,数组中必须有 \0
元素,否则它只是一个数组而已,数组可以做它用,但是不能保存字符串。例如:
char array_1[] = {'a', 'b', '_', '1', '2'};
char array_2[] = {'a', 'b', '_', '1', '2', '\0'};
char array_3[6] = {'a', 'b', '_', '1', '2'};
printf("%s\n", array_1);
printf("%s\n", array_2);
运行上述代码,可以发现 array_1 无法正常显示,而 array_2 与 array_3 可以正常显示。
数组 array_1 有 5 个元素,由于其中没有 \0
元素,printf 函数会从数组的首地址开始逐个输出其中的字符,直到输出完 2
还没有遇到 \0
元素,所以会继续输出下去,而内存中元素 2
后面的内容不是我们程序中的内容,因此,打印的内容与长度都是随机的,直到碰巧在后面的内存中遇到了 \0
而终止输出,这就是字符串中没有 \0
的后果;
array_2 最后一个元素是 \0
,因此,可以保存字符串;
尽管 array_3 没有 \0
元素,但是,array_3 的数组长度为 6,而初始值长度仅为 5,最后一个元素 array_3[5] 由于没有初始值,于是被自动补了 \0
。
注意
array_3 最后一位元素实际上被自动补充的是数字 0,但是 \0
的 ASCII 码是 0。
以上定义 char 型数组赋初值是一个一个地将字符串中的每个字符写出,C 语言还允许通过如下方式给 char 型数组赋初值,这是当 char 型数组保存字符串时的特殊用法:
char array_4[] = {"ab_12"};
char array_5[] = "ab_12";
char array_6[6] = "ab_12";
char array_7[100] = "ab_12";
printf("array_4: %s\n", array_4);
printf("array_5: %s\n", array_5);
printf("array_6: %s\n", array_6);
需要注意的是,双引号中字符的长度虽然是 5,但是末尾还有一个 \0
,因此,数组长度应该为 6,array_6 与 array_7 都是因为初始值不足而自动补了 0,从而存储了字符串,因此,array_6 与 array_7 本质上没有区别,除了存储数组长度不同。
通过 char* 指针保存字符串的首地址
我们也可以将字符串的首地址赋值给一个 char* 类型的指针变量。
根据 char 数组保存字符串与数组指针的概念,我们可以让数组指针指向一个 char 数组,代码如下:
char array_5[] = "abc_123"; // 定义字符串数组
char* ptr = array_5; // ptr 指向 array_5
printf("%s", ptr);
请牢记规定:双引号 "" 引起来的字符串常量可被看做是表达式,表达式的值就是字符串常量的首地址
。我们可以来打印验证一下:
printf("%p", "abc_123");
运行该行代码,我们可以得到一个地址的值,因此,我们可以直接将该地址值传递给指针,而不需要先创建一个新的数组,再让指针指向该数组,代码如下:
char* ptr = "abc_123";
printf("%s", ptr);
两种存储方式的异同点
二者在实际存储时,程序都会把字符串转换成字符数组进行保存,并在末尾加上 \0
。
区别是通过 char 数组定义的字符串是可修改的;而通过 char* 指针定义的字符串不可修改,代码如下:
#include <stdio.h>
int main() {
// char 数组定义的字符串可修改
char str_array[] = "ab_12";
printf("修改前 str_array:%s\n", str_array);
str_array[1] = '0';
*(str_array + 2) = '+';
printf("修改后 str_array:%s\n", str_array);
// char* 指针定义的字符串不可修改
char* str_ptr = "ab_12";
printf("修改前 str_ptr:%s\n", str_ptr);
*(str_ptr + 2) = '+';
printf("修改后 str_ptr:%s\n", str_ptr);
return 0;
}
之所以指针定义的字符串不可修改,是因为该字符串被放在了只读常量区。只读常量区中的内容是不可修改的,并且其中定义的字符串是可以复用的。比如,我们使用指针创建两个相同的字符串,然后打印他们的地址,代码如下:
#include <stdio.h>
int main() {
char* str_1 = "罗大富";
char* str_2 = "罗大富";
printf("str_1 的地址为:%p\n", str_1);
printf("str_2 的地址为:%p\n", str_2);
return 0;
}
运行这段代码后,我们可以发现 str_1 的地址与 str_2 的地址是一致的,这就表示 str_2 复用了 str_1 的地址。也就是说,通过指针创建字符串时,会检查只读常量区里面有没有相同的字符串,如果有则会进行复用,如果没有才会创建新的地址。
最后就是关于字符串输入需要注意的事项,因为通过指针创建的字符串无法修改,所以,我们在输入字符串时要先创建一个用来存放字符串的数组,代码如下:
#include <stdio.h>
int main() {
char name[50];
char province[50];
printf("请输入你的名字:");
scanf("%s", name); // 数组名就是首地址,因此,不需要使用取地址符
printf("你是哪里人:");
scanf("%s", &province[0]); // 也可以对数组中
printf("我叫%s,来自%s", name, province);
return 0;
}
字符串数组
字符串本身就需要一个 char 型的一维数组保存,那么多个字符串就需要用到多个 char 类型的一维数组,而字符串数组则可以使用一个 char 类型的二维数组进行保存,char 型的二维数组每行保存一个字符串,例如:定义一个数组存储 4 个学生的名字,代码如下:
char student_array[4][20] = {"Alice", "Bob", "Cathy", "David"};
虽然,本质上字符串数组是一个 char 类型的二维数组,但是我们在遍历时,依然可以把他看作是一维字符串数组进行使用,代码如下:
#include <stdio.h>
int main() {
char student_array[4][20] = {"Alice", "Bob", "Cathy", "David"};
for (int i = 0; i < 4; ++i) {
printf("%s\n", student_array[i]);
}
return 0;
}
字符串处理函数
C 语言提供了丰富的字符串处理函数,可以对字符串进行输入、输出、合并、修改、比较、转换、复制、搜索等操作,使用这些现成的函数可以大大减轻我们的编程负担。在使用字符串处理函数的时候需要包含头文件 <string.h>
。
string.h
是一个专门用来处理字符串的头文件,它包含了很多字符串处理函数,由于篇幅限制,本节只能讲解几个常用的,也可以点击此处查阅所有函数。
strlen - 获取字符串长度
strlen
是 string length
的缩写,意思是计算字符串的长度,语法格式为:
size_t strlen ( const char * str );
字符串以 ‘\0'
作为结束标志,strlen 函数返回的是在字符串中 '\0'
前面出现的字符个数(不包含 '\0'
)。参数指向的字符串必须要以 '\0'
结束。注意函数的返回值类型为 size_t,对应的占位符为 %zu
。
示例如下:
#include <stdio.h>
#include <string.h>
int main()
{
char str_1[] = "abcdef";
printf("使用 strlen 获取 str_1 长度:%zu\n", strlen(str_1));
return 0;
}
运行以上代码,我们可以获得数组长度为 6,如果使用 sizeof 来获取字符串的长度,得到的结果是 7。还有一点注意事项,当我们使用 char 数组定义字符串时,如果给数组设置了长度,无论字符串长度是多少,sizeof 都只会打印数组的长度代码如下:
#include <stdio.h>
#include <string.h>
int main()
{
char str_1[] = "abcdef";
printf("使用 strlen 获取 str_1 长度:%zu\n", strlen(str_1));
printf("使用 sizeof 获取 str_1 长度:%zu\n", sizeof(str_1));
char str_2[100] = "abcdef";
printf("使用 strlen 获取 str_2 长度:%zu\n", strlen(str_2));
printf("使用 sizeof 获取 str_2 长度:%zu\n", sizeof(str_2));
return 0;
}
strcmp - 字符串比较函数
strcmp
是 string compare
的缩写,意思是字符串比较,语法格式为:
int strcmp ( const char * str1, const char * str2 );
str1 与 str2 是需要比较的两个字符串。字符本身没有大小之分,strcmp()
以各个字符对应的 ASCII 码值进行比较。strcmp()
从两个字符串的第 1 个字符开始比较,如果它们相等,就继续比较下一个字符,直到遇见不同的字符,或者到字符串的末尾。
返回值:若 str1 和 str2 相同,则返回 0;若 str1 大于 str2,则返回大于 0 的值;若 str1 小于 str2,则返回小于 0 的值。
#include <stdio.h>
#include <string.h>
int main() {
char str_1[] = "apple";
char str_2[] = "banana";
int result = strcmp(str_1, str_2);
if (result == 0) {
printf("str1 == str2\n");
}
return 0;
}
strcat - 拼接字符串
strcat
是 string catenate
的缩写,意思是把两个字符串拼接在一起,语法格式为:
char * strcat( char * destination, const char * source );
该函数需要传递两个字符串 destination
与 source
,并将第 2 个字符串 source
的内容拼接在第 1 个字符串 destination
后面,并删除原来 destination
最后的结束标志 \0
。这意味着,destination
必须足够长,要能够同时容纳 destination
和 source
,否则会越界(超出范围)。
strcat()
的返回值为 destination
的地址。 代码如下:
#include <stdio.h>
#include <string.h>
int main() {
char last_name[100] = "罗";
char first_name[] = "大富";
char* ptr = strcat(last_name, first_name);
printf("姓名:%s\n", last_name);
printf("last_name 的地址:%p\n", last_name);
printf("ptr:%s\n", ptr);
printf("ptr 指向的地址:%p\n", ptr);
return 0;
}
需要注意的是,strcat
函数的第 1 个参数 destination
必须是可以修改的字符串,如果是指针定义的字符串则无法实现拼接效果,示例如下:
#include <stdio.h>
#include <string.h>
int main() {
char* last_name = "罗";
char first_name[] = "大富";
char* ptr = strcat(last_name, first_name);
printf("姓名:%s\n", last_name);
printf("last_name 的地址:%p\n", last_name);
printf("ptr:%s\n", ptr);
printf("ptr 指向的地址:%p\n", ptr);
return 0;
}
strcpy - 字符串复制函数
strcpy
是 string copy
的缩写,意思是字符串复制,将字符串从一个地方复制到另外一个地方,语法格式为:
char * strcpy ( char * destination, const char * source );
strcpy
函数与 strcat
函数类似,也需要传递两个字符串 destination
与 source
,并将第 2 个字符串 source
的内容转移到第 1 个字符串 destination
中,与 strcat
不同的是,strcat
是将第 2 个参数拼接在第 1 个参数的后面,而 strcpy
则会把第 2 个字符串的内容全部复制到第 1 个字符串中,并将第 1 个字符串原本的内容覆盖掉。
因此,destination
字符串空间的长度必须 >= source
字符串的长度,以确保能存放 source
字符串。同样地,destination
也必须是可以修改的字符串。返回值为第 1 个字符串 destination
的地址。
示例如下:
#include <stdio.h>
#include <string.h>
int main() {
char str_6[20] = "Hello, ";
char str_7[] = "world";
char* ptr = strcpy(str_6, str_7);
printf("str_6:%s\n", str_6);
printf("str_6 的地址:%p\n", str_6);
printf("r:%s\n", ptr);
printf("r 的地址:%p\n", ptr);
return 0;
}