数组与字符串
7. 数组与字符串
数组和字符串是 C 语言中用于存储和处理数据的重要数据结构。掌握数组和字符串的定义、使用以及相关操作函数,是编写高效和功能丰富的 C 程序的基础。
7.1 一维数组的定义与使用
一维数组是一组相同数据类型元素的集合,每个元素可以通过索引访问。数组在内存中是连续存储的,便于高效地访问和操作数据。
数组的定义
语法:
数据类型 数组名[数组大小];
- 数据类型:数组中元素的类型,如
int、float等。 - 数组名:数组的名称,用于引用数组。
- 数组大小:数组中元素的个数,必须是一个常量表达式。
示例:
#include <stdio.h>
int main() {
int numbers[5]; // 定义一个包含5个整数的数组
return 0;
}
数组的初始化
数组可以在定义时进行初始化,赋予每个元素初始值。如果未完全初始化,未赋值的元素会被自动初始化为零。
语法:
数据类型 数组名[数组大小] = {元素1, 元素2, ..., 元素n};
示例:
#include <stdio.h>
int main() {
int numbers[5] = {1, 2, 3, 4, 5}; // 完全初始化
int scores[5] = {90, 85}; // 部分初始化,剩余元素自动为0
// 输出数组元素
for (int i = 0; i < 5; i++) {
printf("scores[%d] = %d\n", i, scores[i]);
}
return 0;
}
输出:
scores[0] = 90
scores[1] = 85
scores[2] = 0
scores[3] = 0
scores[4] = 0
访问和修改数组元素
数组元素通过索引访问,索引从0开始。可以通过索引读取或修改特定位置的元素。
示例:
#include <stdio.h>
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
// 访问数组元素
printf("第一个元素: %d\n", numbers[0]); // 输出 10
printf("第三个元素: %d\n", numbers[2]); // 输出 30
// 修改数组元素
numbers[1] = 25; // 将第二个元素修改为25
printf("修改后的第二个元素: %d\n", numbers[1]); // 输出 25
return 0;
}
输出:
第一个元素: 10
第三个元素: 30
修改后的第二个元素: 25
数组的遍历
遍历数组意味着依次访问数组中的每个元素,通常使用for循环实现。
示例:
#include <stdio.h>
int main() {
int numbers[5] = {1, 2, 3, 4, 5};
int sum = 0;
// 使用for循环遍历数组
for (int i = 0; i < 5; i++) {
printf("numbers[%d] = %d\n", i, numbers[i]);
sum += numbers[i]; // 累加元素值
}
printf("数组元素之和: %d\n", sum); // 输出 15
return 0;
}
输出:
numbers[0] = 1
numbers[1] = 2
numbers[2] = 3
numbers[3] = 4
numbers[4] = 5
数组元素之和: 15
多种数组初始化方式
-
部分初始化
:
int numbers[5] = {1, 2}; // numbers = {1, 2, 0, 0, 0} -
不指定大小,由初始化列表决定
:
int numbers[] = {1, 2, 3, 4, 5}; // 自动推断数组大小为5 -
全部元素初始化为零
:
int numbers[5] = {0}; // numbers = {0, 0, 0, 0, 0}
示例:
#include <stdio.h>
int main() {
int a[5] = {1, 2}; // 部分初始化
int b[] = {3, 4, 5}; // 自动推断大小为3
int c[5] = {0}; // 全部初始化为0
// 输出数组a
printf("数组a: ");
for (int i = 0; i < 5; i++) {
printf("%d ", a[i]);
}
printf("\n");
// 输出数组b
printf("数组b: ");
for (int i = 0; i < 3; i++) {
printf("%d ", b[i]);
}
printf("\n");
// 输出数组c
printf("数组c: ");
for (int i = 0; i < 5; i++) {
printf("%d ", c[i]);
}
printf("\n");
return 0;
}
输出:
数组a: 1 2 0 0 0
数组b: 3 4 5
数组c: 0 0 0 0 0
注意事项
-
数组越界:访问数组时,索引必须在
0到数组大小-1之间。越界访问会导致未定义行为,可能引发程序崩溃或数据损坏。示例:
#include <stdio.h> int main() { int numbers[3] = {1, 2, 3}; printf("第四个元素: %d\n", numbers[3]); // 未定义行为 return 0; }输出:
第四个元素: [随机值或程序崩溃] -
数组大小固定:一旦定义,数组的大小不能动态改变。如果需要动态数组,请使用指针和动态内存分配函数。
7.2 多维数组的定义与使用
多维数组是多个一维数组的集合,最常见的是二维数组。多维数组用于表示表格、矩阵等复杂数据结构。
二维数组的定义
语法:
数据类型 数组名[行数][列数];
- 行数:二维数组的行数。
- 列数:二维数组每行的元素个数。
示例:
#include <stdio.h>
int main() {
int matrix[3][4]; // 定义一个3行4列的二维数组
return 0;
}
二维数组的初始化
二维数组可以在定义时进行初始化,每行用一对花括号{}包围,元素用逗号分隔。
语法:
数据类型 数组名[行数][列数] = {
{元素11, 元素12, ..., 元素1n},
{元素21, 元素22, ..., 元素2n},
...
{元素m1, 元素m2, ..., 元素mn}
};
示例:
#include <stdio.h>
int main() {
// 定义并初始化一个3行3列的矩阵
int matrix[3][3] = {
{1, 2, 3}, // 第一行
{4, 5, 6}, // 第二行
{7, 8, 9} // 第三行
};
// 输出二维数组元素
for (int i = 0; i < 3; i++) { // 行循环
for (int j = 0; j < 3; j++) { // 列循环
printf("%d ", matrix[i][j]);
}
printf("\n"); // 换行
}
return 0;
}
输出:
1 2 3
4 5 6
7 8 9
访问和修改二维数组元素
通过行索引和列索引访问特定的元素,语法为数组名[行][列]。
示例:
#include <stdio.h>
int main() {
int matrix[2][3] = {
{10, 20, 30},
{40, 50, 60}
};
// 访问元素
printf("matrix[0][1] = %d\n", matrix[0][1]); // 输出 20
printf("matrix[1][2] = %d\n", matrix[1][2]); // 输出 60
// 修改元素
matrix[0][1] = 25; // 将第1行第2列的元素修改为25
printf("修改后的 matrix[0][1] = %d\n", matrix[0][1]); // 输出 25
return 0;
}
输出:
matrix[0][1] = 20
matrix[1][2] = 60
修改后的 matrix[0][1] = 25
二维数组的遍历
遍历二维数组需要嵌套循环,外层循环遍历行,内层循环遍历列。
示例:
#include <stdio.h>
int main() {
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
int sum = 0;
// 遍历二维数组并计算元素之和
for (int i = 0; i < 2; i++) { // 行循环
for (int j = 0; j < 3; j++) { // 列循环
printf("matrix[%d][%d] = %d\n", i, j, matrix[i][j]);
sum += matrix[i][j];
}
}
printf("二维数组元素之和: %d\n", sum); // 输出 21
return 0;
}
输出:
matrix[0][0] = 1
matrix[0][1] = 2
matrix[0][2] = 3
matrix[1][0] = 4
matrix[1][1] = 5
matrix[1][2] = 6
二维数组元素之和: 21
三维数组的定义与使用(扩展)
除了二维数组,C 语言还支持多维数组,如三维数组。三维数组可用于表示立体结构的数据,如 3D 图形中的坐标点。
示例:
#include <stdio.h>
int main() {
int threeD[2][3][4] = {
{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
},
{
{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24}
}
};
// 输出三维数组元素
for (int i = 0; i < 2; i++) { // 第一维
for (int j = 0; j < 3; j++) { // 第二维
for (int k = 0; k < 4; k++) { // 第三维
printf("threeD[%d][%d][%d] = %d\n", i, j, k, threeD[i][j][k]);
}
printf("\n"); // 换行
}
printf("\n"); // 换行
}
return 0;
}
输出:
threeD[0][0][0] = 1
threeD[0][0][1] = 2
threeD[0][0][2] = 3
threeD[0][0][3] = 4
threeD[0][1][0] = 5
threeD[0][1][1] = 6
threeD[0][1][2] = 7
threeD[0][1][3] = 8
threeD[0][2][0] = 9
threeD[0][2][1] = 10
threeD[0][2][2] = 11
threeD[0][2][3] = 12
threeD[1][0][0] = 13
threeD[1][0][1] = 14
threeD[1][0][2] = 15
threeD[1][0][3] = 16
threeD[1][1][0] = 17
threeD[1][1][1] = 18
threeD[1][1][2] = 19
threeD[1][1][3] = 20
threeD[1][2][0] = 21
threeD[1][2][1] = 22
threeD[1][2][2] = 23
threeD[1][2][3] = 24
注意事项
- 数组索引从 0 开始:第一个元素的索引为
0,最后一个元素的索引为数组大小-1。 - 内存连续:数组在内存中是连续存储的,便于快速访问,但也意味着一次性分配较大的内存可能导致内存浪费。
- 不可变长度:数组大小在编译时必须确定,不能在运行时动态改变。如果需要动态数组,请使用指针和动态内存分配函数(如
malloc、calloc等)。
7.3 字符数组与字符串的区别
字符数组和字符串在 C 语言中紧密相关,但它们并不完全相同。理解它们的区别有助于正确地处理文本数据。
字符数组
字符数组是一个数组,其元素类型为char,用于存储一组字符。
定义与初始化:
char chars[5] = {'H', 'e', 'l', 'l', 'o'};
特点:
- 可以存储任意字符,包括不以
'\0'结尾的字符序列。 - 不具备字符串的特殊性质,不能直接作为字符串函数的参数使用,除非以
'\0'结尾。
示例:
#include <stdio.h>
int main() {
char chars[5] = {'A', 'B', 'C', 'D', 'E'};
// 输出字符数组
for (int i = 0; i < 5; i++) {
printf("chars[%d] = %c\n", i, chars[i]);
}
return 0;
}
输出:
chars[0] = A
chars[1] = B
chars[2] = C
chars[3] = D
chars[4] = E
字符串
字符串在 C 语言中是一种特殊的字符数组,用于存储文本数据。字符串以空字符'\0'结尾,标志着字符串的结束。
定义与初始化:
char str1[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
char str2[] = "Hello"; // 自动添加'\0'
特点:
- 以
'\0'结尾,表示字符串的结束。 - 可以直接作为字符串函数(如
printf、strlen等)的参数使用。 - 字符串常量(如
"Hello")实际上是一个字符数组,包含'\0'。
示例:
#include <stdio.h>
int main() {
char str1[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
char str2[] = "World"; // 自动添加'\0'
// 输出字符串
printf("str1: %s\n", str1); // 使用%s格式说明符
printf("str2: %s\n", str2);
return 0;
}
输出:
str1: Hello
str2: World
字符数组与字符串的区别
| 特性 | 字符数组 | 字符串 |
|---|---|---|
| 定义方式 | char arr[size] | char str[size] = "text" 或 char str[] = "text" |
| 结束标志 | 无(除非手动添加'\0') | 自动包含'\0'作为结束标志 |
| 使用场景 | 存储单个字符或固定长度的字符序列 | 存储文本数据,并与字符串函数配合使用 |
| 函数兼容性 | 需要手动添加'\0'后才能作为字符串使用 | 直接兼容字符串函数 |
示例比较:
#include <stdio.h>
int main() {
char charArray[5] = {'T', 'e', 's', 't', '1'};
char string1[6] = {'T', 'e', 's', 't', '2', '\0'};
char string2[] = "Test3";
// 尝试使用字符串函数
printf("charArray as string: %s\n", charArray); // 未定义行为,缺少'\0'
printf("string1: %s\n", string1); // 正常输出
printf("string2: %s\n", string2); // 正常输出
return 0;
}
输出(charArray as string 可能导致未定义行为):
charArray as string: Test1
string1: Test2
string2: Test3
注意:
- 当使用字符串函数(如
printf的%s)时,必须确保字符数组以'\0'结尾,否则可能导致内存泄漏或程序崩溃。 - 字符数组用于存储不需要以
'\0'结尾的字符序列,而字符串用于存储以'\0'结尾的文本数据。
7.4 字符串的常用操作函数
C 语言通过标准库提供了一系列函数,用于处理和操作字符串。这些函数定义在<string.h>头文件中。以下是一些常用的字符串操作函数,包括strlen、strcpy、strcat和strcmp。
7.4.1 strlen
功能:计算字符串的长度(不包括终止的空字符'\0')。
原型:
size_t strlen(const char *str);
示例与详细说明:
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello, World!";
size_t length;
// 使用strlen函数计算字符串长度
length = strlen(str);
printf("字符串 \"%s\" 的长度是: %zu\n", str, length); // 输出 13
return 0;
}
输出:
字符串 "Hello, World!" 的长度是: 13
详细解释:
strlen函数从字符串的开始位置依次计数,直到遇到'\0'为止,返回计数值。size_t是无符号整数类型,适用于表示大小和长度。
更多示例:
#include <stdio.h>
#include <string.h>
int main() {
char emptyStr[] = ""; // 空字符串
char singleChar[] = "A"; // 单字符字符串
char sentence[] = "C语言学习笔记";
printf("emptyStr 的长度: %zu\n", strlen(emptyStr)); // 输出 0
printf("singleChar 的长度: %zu\n", strlen(singleChar)); // 输出 1
printf("sentence 的长度: %zu\n", strlen(sentence)); // 输出 7
return 0;
}
输出:
emptyStr 的长度: 0
singleChar 的长度: 1
sentence 的长度: 7
7.4.2 strcpy
功能:将一个字符串复制到另一个字符串。
原型:
char *strcpy(char *dest, const char *src);
dest:目标字符串,必须有足够的空间存放源字符串及其终止符。src:源字符串。
示例与详细说明:
#include <stdio.h>
#include <string.h>
int main() {
char source[] = "Hello, C!";
char destination[20]; // 确保有足够的空间
// 使用strcpy函数复制字符串
strcpy(destination, source);
printf("源字符串: %s\n", source);
printf("目标字符串: %s\n", destination);
return 0;
}
输出:
源字符串: Hello, C!
目标字符串: Hello, C!
详细解释:
strcpy函数将source字符串的内容复制到destination字符串,包括终止的'\0'。- 注意:目标数组必须有足够的空间容纳源字符串,否则会导致缓冲区溢出,产生安全漏洞。
更多示例:
#include <stdio.h>
#include <string.h>
int main() {
char src1[] = "First String";
char src2[] = "Second String";
char dest1[20];
char dest2[20];
// 复制不同的源字符串到不同的目标字符串
strcpy(dest1, src1);
strcpy(dest2, src2);
printf("dest1: %s\n", dest1); // 输出 "First String"
printf("dest2: %s\n", dest2); // 输出 "Second String"
return 0;
}
输出:
dest1: First String
dest2: Second String
7.4.3 strcat
功能:将一个字符串连接到另一个字符串的末尾。
原型:
char *strcat(char *dest, const char *src);
dest:目标字符串,必须有足够的空间存放源字符串的内容。src:源字符串。
示例与详细说明:
#include <stdio.h>
#include <string.h>
int main() {
char dest[20] = "Hello, ";
char src[] = "World!";
// 使用strcat函数连接字符串
strcat(dest, src);
printf("连接后的字符串: %s\n", dest); // 输出 "Hello, World!"
return 0;
}
输出:
连接后的字符串: Hello, World!
详细解释:
strcat函数将src字符串的内容追加到dest字符串的末尾,并添加终止的'\0'。- 注意:目标数组必须有足够的空间容纳追加的字符串,否则会导致缓冲区溢出。
更多示例:
#include <stdio.h>
#include <string.h>
int main() {
char greeting[50] = "Good ";
char timeOfDay[] = "Morning";
char name[] = ", Alice!";
// 连接多个字符串
strcat(greeting, timeOfDay); // "Good Morning"
strcat(greeting, name); // "Good Morning, Alice!"
printf("完整的问候语: %s\n", greeting); // 输出 "Good Morning, Alice!"
return 0;
}
输出:
完整的问候语: Good Morning, Alice!
7.4.4 strcmp
功能:比较两个字符串的大小关系。
原型:
int strcmp(const char *str1, const char *str2);
str1和str2:要比较的两个字符串。
返回值:
- 0:两个字符串相等。
- 正数:
str1大于str2。 - 负数:
str1小于str2。
示例与详细说明:
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "Apple";
char str2[] = "Banana";
char str3[] = "Apple";
// 比较str1和str2
int result1 = strcmp(str1, str2);
if (result1 < 0) {
printf("\"%s\" 小于 \"%s\"\n", str1, str2);
} else if (result1 > 0) {
printf("\"%s\" 大于 \"%s\"\n", str1, str2);
} else {
printf("\"%s\" 等于 \"%s\"\n", str1, str2);
}
// 比较str1和str3
int result2 = strcmp(str1, str3);
if (result2 < 0) {
printf("\"%s\" 小于 \"%s\"\n", str1, str3);
} else if (result2 > 0) {
printf("\"%s\" 大于 \"%s\"\n", str1, str3);
} else {
printf("\"%s\" 等于 \"%s\"\n", str1, str3);
}
return 0;
}
输出:
"Apple" 小于 "Banana"
"Apple" 等于 "Apple"
详细解释:
strcmp函数逐字符比较两个字符串,直到找到不同的字符或到达字符串末尾。- 比较是基于字符的 ASCII 值,字母的大小写会影响比较结果。
更多示例:
#include <stdio.h>
#include <string.h>
int main() {
char a[] = "hello";
char b[] = "hello";
char c[] = "world";
char d[] = "Hello"; // 注意大写 'H'
// 比较相同字符串
printf("strcmp(a, b) = %d\n", strcmp(a, b)); // 输出 0
// 比较不同字符串
printf("strcmp(a, c) = %d\n", strcmp(a, c)); // 输出 <0 因为 'h' < 'w'
// 比较大小写不同的字符串
printf("strcmp(a, d) = %d\n", strcmp(a, d)); // 输出 >0 因为 'h' > 'H'
return 0;
}
输出:
strcmp(a, b) = 0
strcmp(a, c) = -15
strcmp(a, d) = 32
注意事项:
strcmp比较的是字符的 ASCII 值,大小写不同会影响结果(例如,'A'<'a')。- 为了进行不区分大小写的比较,可以使用
strcasecmp函数(POSIX 标准,不在 C 标准库中)。
7.5 字符数组与指针的关系(扩展)
虽然用户没有明确请求此小节,但理解字符数组与指针的关系对于深入学习字符串处理非常有帮助。
字符数组和字符指针在很多情况下可以互换使用,但它们在内存分配和操作方式上存在差异。
字符数组
- 内存分配:字符数组在栈上分配固定的内存空间。
- 不可变长度:数组大小在定义时确定,无法动态改变。
- 独立存储:字符数组存储自己的数据,复制或修改不影响其他数组。
示例:
#include <stdio.h>
int main() {
char arr1[] = "Hello";
char arr2[] = "Hello";
// 修改arr1
arr1[0] = 'h';
printf("arr1: %s\n", arr1); // 输出 "hello"
printf("arr2: %s\n", arr2); // 输出 "Hello"
return 0;
}
输出:
arr1: hello
arr2: Hello
字符指针
- 内存分配:字符指针可以指向字符串常量或动态分配的内存。
- 可变指向:指针可以指向不同的字符串或内存位置。
- 共享存储:多个指针可以指向同一字符串,修改一个指针指向的内容会影响所有指向该内容的指针(如果内容可修改)。
示例:
#include <stdio.h>
int main() {
char *ptr1 = "Hello";
char *ptr2 = "Hello";
// 修改ptr1指向的内容(未定义行为,字符串常量通常存储在只读内存)
// ptr1[0] = 'h'; // 错误:尝试修改只读内存
// 指针指向不同的字符串
ptr1 = "Hi";
printf("ptr1: %s\n", ptr1); // 输出 "Hi"
printf("ptr2: %s\n", ptr2); // 输出 "Hello"
return 0;
}
输出:
ptr1: Hi
ptr2: Hello
注意事项:
- 只读存储:字符串常量(如
"Hello")通常存储在只读内存中,尝试修改会导致未定义行为。 - 动态分配:如果需要修改字符串内容,应使用字符数组或动态内存分配函数(如
malloc)分配可写内存。
示例(使用动态内存分配):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
// 分配内存并复制字符串
char *ptr = (char *)malloc(6 * sizeof(char)); // 分配6个字节
if (ptr == NULL) {
printf("内存分配失败\n");
return 1;
}
strcpy(ptr, "Hello"); // 复制字符串到动态分配的内存
printf("ptr: %s\n", ptr); // 输出 "Hello"
// 修改内容
ptr[0] = 'h';
printf("修改后的ptr: %s\n", ptr); // 输出 "hello"
// 释放内存
free(ptr);
return 0;
}
输出:
ptr: Hello
修改后的ptr: hello
7.6 总结
本节详细介绍了一维数组和多维数组的定义、初始化、访问和遍历方法,阐述了字符数组与字符串的区别,并深入讲解了字符串的常用操作函数,包括strlen、strcpy、strcat和strcmp。通过多个实例和详细的注释,帮助你理解和掌握数组与字符串在 C 语言中的使用方法。这些知识对于处理数据、构建复杂的数据结构和开发功能丰富的应用程序至关重要。
- 一维数组:用于存储同类型数据的线性集合,通过索引访问和修改元素。
- 多维数组:扩展了一维数组的概念,适用于表示表格、矩阵等多维数据结构。
- 字符数组与字符串:了解它们的区别和联系,有助于正确处理文本数据。
- 字符串操作函数:掌握常用的字符串函数,提高字符串处理的效率和准确性。