C 字符串简介
在 C 语言中,字符串指的就是一串字符,它本质上是一个以 '\0' 结尾的字符数组。在 C 语言中,我们可以使用字符数组的方式来定义一个字符串。
语法:
char 字符串名[] = 值;说明:
当编译器遇到使用双引号括起来的字符串时,默认情况下会自动在其末尾追加一个 '\0' 字符。比如 char str[] = "lvye"; 的存储结构如下图所示。

实际上,定义字符串有以下 3 种方式。
// 方式 1
char str[] = "lvye";
// 方式 2
char* str = "lvye";
// 方式 3
char str[] = {'l', 'v', 'y', 'e', '\0'};方式 2 使用了指针的语法。关于指针的使用,我们会在后面 “C 指针” 一节中详细介绍,这里简单了解即可。
方式 3 是使用了数组的语法。在这种语法中,'\0' 是必需的。'\0' 是一个终止字符,以表示字符串的结束。如果 '\0' 省略,此时 str 代表的就不再是一个字符串,而是一个普通字符数组。
提示: 如果想要使用 printf() 函数打印一个字符串,应该使用 “%s” 格式。其中 s 是 “string” 的缩写。
示例 1:C 定义字符串
#include <stdio.h>
int main(void)
{
char str[] = "lvye";
printf("%s", str); // 打印字符串
return 0;
}运行结果如下。
lvye分析:
如果小伙伴们接触过其他编程语言(比如 Python、JavaScript),会发现它们都可以使用一个普通变量来保存一个字符串。但 C 语言是没有字符串类型的变量的,而必须使用数组的方式来保存一个字符串。
注意: 字符串字面量(如 "lvye")通常存储在只读内存区域。
示例 2:C 获取字符串中某个字符
#include <stdio.h>
int main(void)
{
char str[] = "lvye";
char ch1 = str[0];
char ch2 = str[1];
printf("%c\n", ch1); // 打印第 1 个字符
printf("%c\n", ch2); // 打印第 2 个字符
return 0;
}运行结果如下。
l
v分析:
字符串本身就是一个数组,所以可以使用下标的方式来获取某一个字符。需要注意的是,下标是从 0 开始的。
示例 3:C 修改字符串中某个字符
#include <stdio.h>
int main(void)
{
char str[] = "lvye";
str[0] = 'L';
printf("%s", str);
return 0;
}运行结果如下。
LvyeC 字符串常见问题
1. 'a' 和 "a" 这两个是等价的吗?如果不是,那么它们之间的区别是什么?
'a' 和 "a" 并不是一个东西,'a' 是一个字符,而 "a" 是一个字符串。对于字符串来说,编译器会在后面自动加上一个结束符 “\0”,所以 "a" 的存储结构如下图所示。

我们一定要记住,凡是使用单引号的就是一个字符,凡是使用双引号的就是一个字符串。
C 输入字符串
从之前的学习可以知道,字符的输入输出我们可以使用 getchar() 和 putchar() 这 2 个函数。而对于字符串的输入输出,有没有类似的函数呢?其实是有的。
1. gets() 函数
在 C 语言中,我们可以使用 gets() 函数来快速输入一个字符串。其中,gets 是 “get string” 的缩写。
语法:
gets(字符串)说明:
gets() 本质上是 scanf() 的一种简写方式,下面 2 种方式是等价的。
// 方式 1
gets(str);
// 方式 2
scanf("%s", &str);注意:
- gets() 是属于 <stdio.h> 标准库的函数,而不是属于 <string.h> 标准库。
- gets() 函数存在严重的安全性问题,因为它不会检查输入的字符串是否会超出目标缓冲区的大小,可能会导致缓冲区溢出。因此,我们强烈建议避免使用 gets() 函数,而应该使用更安全的替代方案(如使用 fgets() 函数)。
示例 4:使用 gets() 输入字符串
#include <stdio.h>
int main(void)
{
char str[10];
printf("请输入字符串:");
gets(str);
printf("%s", str);
return 0;
}运行之后,我们输入 “lvye”,然后输出结果如下。
lvye分析:
特别注意,数组长度必须 “至少” 要比字符串实际长度大 1。这是因为字符串最后自带了一个结束符 '\0',而该结束符本身也占了一个字节。一般情况下,我们只是笼统给一个大于实际长度值的数就可以了,比如 10、20、100 之类的。
2. fgets() 函数
gets() 函数存在严重的安全性问题,因此我们更推荐使用 fgets() 函数来输入一个字符串。fgets() 函数可以指定读取的最大字符数,从而防止缓冲区溢出。
语法:
fgets(str, size, stdin)说明:
fgets() 函数接收以下 3 个参数。
str:是一个字符数组。size:是要读取的最大字节数(包括 '\0')。stdin表示从标准输入读取。
示例 5:使用 fgets() 输入字符串(推荐)
#include <stdio.h>
int main(void)
{
char str[10];
printf("请输入字符串:");
fgets(str, sizeof(str), stdin);
printf("%s", str);
return 0;
}运行之后,我们输入 “lvye”,然后输出结果如下。
lvye分析:
特别注意,使用 fgets() 函数时,数组长度必须足够存储输入的字符串加上结尾的 '\0'。fgets() 会读取最多 size - 1 个字符,并在末尾添加 '\0'。另外,fgets() 还会将输入中的换行符(如果存在且未超出读取长度)也读取到字符串中,我们可能需要手动去除结尾的换行符。
C 输出字符串
在 C 语言中,我们可以使用 puts() 函数来输出(打印)一个字符串。puts,是 “put string” 的缩写。
语法:
puts(字符串)说明:
puts(str) 会自动在末尾添加一个换行符 \n,而 printf("%s", str) 不会。下面 2 种方式是等价的。
// 方式 1
puts(str);
// 方式 2
printf("%s\n", str);注意: puts() 是属于 <stdio.h> 标准库的函数,而不是属于 <string.h> 标准库。
示例 6:使用 puts() 打印字符串
#include <stdio.h>
int main(void)
{
char str[] = "lvye";
puts(str);
return 0;
}运行结果如下。
lvye分析:
在实际开发中,对于字符串的输入输出,我们推荐使用 fgets() 来输入字符串(安全),以及使用 puts() 或 printf() 来输出字符串。
C 字符串函数
在 C 语言中,与字符串相关的函数都存放在 <string.h> 标准库中,常用的函数如下表所示。
| 字符串操作 | |
| strlen() | 计算字符串的长度(不包含结束符) |
| strcat() | 将一个字符串接到另一个字符串后面 |
| strncat() | 将一个字符串的前 n 个字符接到另一个字符串后面 |
| strcmp() | 比较两个字符串的大小 |
| strncmp() | 比较两个字符串的前 n 个字符的大小 |
| strcpy() | 将一个字符串完全复制到另一个地方 |
| strncpy() | 将一个字符串的前 n 个字符复制到另一个地方 |
| strtok() | 将一个字符串按分隔符切分成多个小段(标记) |
| strchr() | 从字符串 “头部” 开始找某个字符第一次出现的位置 |
| strrchr() | 从字符串 “尾部” 开始找某个字符第一次出现的位置(也就是最后一次出现的位置) |
| strstr() | 在字符串中查找某个子串第一次出现的位置 |
| strpbrk() | 在字符串中查找第一个属于某个特定字符集合的字符。 |
| strspn() | 计算字符串开头连续 “属于” 某个特定字符集合的字符有多长 |
| strcspn() | 计算字符串开头连续 “不属于” 某个特定字符集合的字符有多长 |
| 内存块操作 | |
| memcpy() | 将一块内存区域的数据复制到另一块区域(不处理重叠) |
| memmove() | 将一块内存区域的数据复制到另一块区域(能正确处理区域重叠) |
| memset() | 使用一个特定的字节值填充一块内存区域 |
| memcmp() | 比较两个内存区域的内容大小 |
| memchr() | 在一块内存区域中查找某个字节第一次出现的位置 |
| 非标准库函数 | |
| strupr() | 将字符串转换为纯大写 |
| strlwr() | 将字符串转换为纯小写 |
| strrev() | 将字符串反转 |
