C语言小课堂:字符数组

同学们,今天我们将一起深入学习C语言中一个非常基础且重要的数据类型——字符数组。我们将从它的定义、使用场景到具体操作进行系统讲解,并辅以习题帮助大家巩固理解。

  1. 什么是字符数组?

在C语言中,字符数组是一种特殊的数组,它的每个元素都用来存储一个字符(char类型)。你可以把字符数组想象成一串字符的集合,它们在内存中是连续存放的。

最常见的字符数组用途是存储字符串。在C语言中,字符串实际上就是以空字符 \0 (null character)结尾的字符数组。这个空字符标志着字符串的结束,\0本身不属于字符串的可见内容,但它对于字符串处理函数(如 printfstrlen 等)来说至关重要,因为它告诉函数字符串在哪里结束。

总结一下:

本质:由 char 类型元素组成的数组。

用途:主要用于存储字符串。

关键特征:字符串以空字符 \0 结束。

  1. 为什么要使用字符数组?

当你需要在程序中处理文本信息,例如:

存储用户的姓名、地址。

读取和显示文件内容。

处理命令行参数。

进行文本数据的查找、替换、拼接等操作。

这时候,字符数组就成为了你的首选工具。

字符数组在编程学习中的地位和作用:

文本处理的基础:字符数组是C语言中处理字符串的唯一直接方式。理解它,你就掌握了C语言文本处理的基石。

内存管理实践:字符数组的声明、初始化以及字符串操作涉及到内存的连续存储和空字符终止符的概念,这有助于你更好地理解C语言的底层内存管理。

与标准库函数结合:C语言标准库 <string.h> 中提供了大量用于操作字符串的函数(如 strlen , strcpy, strcat , strcmp 等),这些函数都是基于字符数组和空字符终止符的约定来工作的。学习字符数组,就意味着你将能够有效利用这些强大的工具。

指针的引申:字符数组名在很多情况下可以被视为指向其第一个元素的常量指针,这为后续学习C语言指针提供了非常好的铺垫和实践场景。

  1. 怎么使用字符数组?

声明和初始化:

你可以通过以下几种方式声明和初始化字符数组:

逐个字符初始化(不推荐用于字符串):

char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 包含空字符

这种方式需要你手动添加 \0。如果数组大小刚好等于字符数,没有位置放 \0,则它不是一个合法的C字符串。

使用字符串字面量初始化(最常用):

char name[20] = "Alice"; // 数组大小应足够容纳字符串及空字符
char city[] = "New York"; // 编译器会自动计算数组大小并添加\0

当使用字符串字面量初始化时,编译器会自动在字符串末尾添加 \0。因此,数组的大小至少需要比字符串的可见字符数多1。如果声明时未指定大小(如 city[]),编译器会根据字符串字面量的长度自动分配空间。

未初始化(不推荐):

char buffer[100]; // 包含垃圾值,不是字符串

未初始化的字符数组包含的是垃圾值,不是有效的字符串,必须在使用前进行赋值。

访问和操作:

你可以像访问普通数组一样访问字符数组的单个元素:

char str[] = "World";
printf("%c\n", str[0]); // 输出 'W'
str[0] = 'w'; // 修改第一个字符
printf("%s\n", str); // 输出 "world"

字符串输入输出:

输入:

scanf("%s", charArrayName);:会读取一个单词(遇到空格、制表符或换行符停止),不安全,可能导致缓冲区溢出。

fgets(charArrayName, sizeof(charArrayName), stdin);:推荐使用,可以读取一行(包括空格),并指定最大读取字节数,防止溢出。它会读取换行符 \n

输出:

printf("%s", charArrayName);:最常用,会一直输出直到遇到 \0

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>  // 包含标准输入输出库
#include <string.h> // 包含字符串处理库

int main() {
// 声明并初始化一个字符数组
char greeting[] = "Hello, C!"; // 编译器自动添加 '\0'

// 打印整个字符串
printf("初始问候语: %s\n", greeting);

// 访问单个字符
printf("第一个字符: %c\n", greeting[0]);

// 修改字符数组中的字符
greeting[7] = 'W'; // 将 'C' 改为 'W'
printf("修改后的问候语: %s\n", greeting);

// 获取字符串长度 (不包括空字符)
size_t length = strlen(greeting); // strlen 返回 size_t 类型
printf("问候语长度: %zu\n", length); // %zu 用于打印 size_t 类型

// 复制字符串
char anotherGreeting[20]; // 确保有足够空间
strcpy(anotherGreeting, greeting); // 将 greeting 复制到 anotherGreeting
printf("复制的问候语: %s\n", anotherGreeting);

// 拼接字符串
char firstName[10] = "John";
char lastName[10] = "Doe";
char fullName[25]; // 确保足够空间存储拼接后的字符串和空字符

strcpy(fullName, firstName); // 先复制第一个名字
strcat(fullName, " "); // 拼接一个空格
strcat(fullName, lastName); // 拼接第二个名字
printf("全名: %s\n", fullName);

// 从用户输入读取字符串 (安全方式)
char userInput[50];
printf("请输入你的爱好: ");
fgets(userInput, sizeof(userInput), stdin); // 读取一行
// fgets 会读取换行符,如果不需要,可以去除它
// if (userInput[strlen(userInput) - 1] == '\n') {
// userInput[strlen(userInput) - 1] = '\0';
// }
printf("你的爱好是: %s", userInput); // 注意这里userInput可能包含\n,所以printf格式串末尾不需要\n了

return 0;
}

运行上述代码,你将看到类似以下的输出:

初始问候语: Hello, C!
第一个字符: H
修改后的问候语: Hello, W!
问候语长度: 9
复制的问候语: Hello, W!
全名: John Doe
请输入你的爱好: [用户输入例如:reading books]
你的爱好是: reading books

通过以上讲解和示例,你应该对C语言中的字符数组有了比较全面的认识。接下来,让我们通过一些习题来检验你的掌握情况。

💻 配套习题

第一题 (选择题)

以下关于C语言字符数组的描述中,哪一项是不正确的?

A. 字符数组可以用来存储字符串。

B. C语言中的字符串通常以空字符 \0 结尾。

C. 声明 char str[] = "Hello"; 时,数组 str 的大小是5。

D. 可以通过下标 str[i] 访问字符数组的单个字符。

第二题 (填空题)

要声明一个能够存储字符串 “Programming” 的字符数组 word,并且让编译器自动确定数组的大小,应该使用以下声明方式:char word[] = "___________";

第三题 (代码输出题)

阅读下面的C代码,并写出其输出结果:

#include <stdio.h>
#include <string.h> // 包含 strlen 函数

int main() {
char message[] = "C Language";
message[1] = 'x';
printf("%s - Length: %zu\n", message, strlen(message));
return 0;
}

第四题 (概念辨析题)

判断题:使用 scanf("%s", charArray); 读取字符串时,如果用户输入的字符串长度超过了 charArray 的声明大小,可能会导致缓冲区溢出。

A. 正确 B. 错误

第五题 (编程题)

编写一个C程序,声明两个字符数组 str1str2 。将 str1 初始化为 “Hello”,将 str2 初始化为 “World”。然后,将 str2 的内容连接到 str1 的后面,并在两者之间添加一个空格。最后,打印连接后的 str1 的内容。

📝 习题讲解

第一题 (选择题) 解析

A. 字符数组可以用来存储字符串。正确。这是字符数组最主要的用途。

B. C语言中的字符串通常以空字符 \0 结尾。 正确。空字符是字符串的终止符,没有它就不是一个合法的C字符串。

C. 声明 char str[] = "Hello"; 时,数组 str 的大小是5。不正确。字符串 “Hello” 包含5个可见字符,但编译器会自动在末尾添加一个空字符 \0 。所以,数组 str 的实际大小是 5 + 1 = 6。

D. 可以通过下标 str[i] 访问字符数组的单个字符。正确。字符数组和普通数组一样,可以通过下标来访问和修改其元素。

第二题 (填空题) 解析

题目要求编译器自动确定数组大小,这正是使用字符串字面量初始化且不指定数组大小的特点。因此,直接将字符串 “Programming” 放入双引号中即可。编译器会计算 “Programming” 的长度(11个字符)并自动为其加上 \0 ,使得数组 word 的大小为12。

第三题 (代码输出题) 解析

char message[] = "C Language";:声明并初始化一个字符数组 message。它的内容是 “C Language”,末尾自动带 \0。数组大小为11(10个可见字符 + 1个 \0 )。

message[1] = 'x'; :将 message 数组索引为1的字符(即第二个字符)从 ‘ ‘(空格)修改为 ‘x’。

原始字符串: C L a n g u a g e \0

修改后: C x L a n g u a g e \0

printf("%s - Length: %zu\n", message, strlen(message));

%s 会打印 message 数组作为字符串,直到遇到 \0。所以会打印 Cx Language

strlen(message) 会计算字符串的长度,不包括 \0。原始字符串 “C Language” 长度为10,修改一个字符并不会改变它的长度。所以长度是10。

%zusize_t 类型的格式说明符,strlen 函数返回的就是 size_t 类型。 最终输出为 Cx Language - Length: 10

第四题 (概念辨析题) 解析

A. 正确 (即原命题正确)。

scanf("%s", charArray); 的工作方式是读取非空白字符序列,直到遇到空白字符(空格、制表符、换行符)或文件结束。它不会检查目标字符数组的大小是否足够容纳输入的字符串。

如果用户输入的字符串长度(包括自动添加的 \0 )超出了 charArray 所能提供的内存空间,那么超出的部分就会写入到数组后面的内存区域,这可能覆盖掉其他重要的数据,导致程序崩溃、数据损坏,甚至被恶意利用,这就是缓冲区溢出(Buffer Overflow)。

为了避免这种情况,推荐使用 fgets 函数,因为它允许你指定最大读取长度,从而防止溢出。或者,如果必须使用 scanf ,可以使用格式控制符 %s 结合最大宽度限制,例如 scanf("%9s", charArray); (为 \0 留出1个字节),但这只对单个单词有效。

第五题 (编程题) 解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>  // 用于printf函数
#include <string.h> // 用于strcpy和strcat函数

int main() {
// 声明并初始化str1和str2
char str1[20] = "Hello"; // 确保str1有足够大的空间来容纳拼接后的字符串
char str2[] = "World"; // 编译器自动确定大小,此处为6 (World + \\0)

printf("原始str1: %s\n", str1);
printf("原始str2: %s\n", str2);

// 在str1和str2之间添加一个空格
// 先将空格拼接到str1后面
strcat(str1, " "); // str1现在是 "Hello "

// 再将str2的内容拼接到str1后面
strcat(str1, str2); // str1现在是 "Hello World"

// 打印连接后的str1
printf("连接后的str1: %s\n", str1);

return 0;
}

讲解:

头文件: stdio.h 用于 printf 函数, string.h 包含了 strcpystrcat 等字符串操作函数。

str1 的大小:char str1[20] = "Hello"; 这里我们特意将 str1 的大小设置为20。这是非常重要的,因为 strcat 函数会修改 str1 的内容,它需要 str1 所在的内存区域有足够的空间来容纳原字符串、新拼接的字符串以及空字符 \0。如果 str1 的大小只够存 “Hello”(即 char str1[6]),那么再拼接其他内容就会导致缓冲区溢出。

str2 的大小:char str2[] = "World"; 编译器会自动为 str2 分配 5 + 1 = 6 个字节的空间。

strcat(str1, " ");:这个函数用于将第二个参数(源字符串)的内容追加到第一个参数(目标字符串)的末尾。它会找到目标字符串的 \0,然后从那里开始复制源字符串,并自动添加新的 \0。这里,我们首先将一个空格字符串 " " 拼接到 str1 后面,此时 str1 变为 “Hello “。

strcat(str1, str2);:接着,我们将 str2(”World”)的内容拼接到已经变为 “Hello “ 的 str1 后面。最终 str1 的内容就变为了 “Hello World”。

printf("连接后的str1: %s\n", str1);:打印最终拼接好的字符串。

这个题目考察了字符数组的声明、初始化以及 strcat 函数的使用,强调了在进行字符串拼接操作时,目标数组必须有足够的预留空间以避免缓冲区溢出。

希望这些讲解和习题能帮助你更好地理解和掌握C语言的 字符数组 数据类型!继续努力,编程的世界充满乐趣!🚀