数组与字符串
数组是存储相同类型元素的连续内存空间。字符串是字符数组的一种特殊形式,以空字符 \0 结尾。
一维数组
数组声明与初始化
数组是固定大小的同类型元素集合:
int numbers[5]; // 声明一个包含 5 个整数的数组
int arr1[5] = {1, 2, 3, 4, 5}; // 完全初始化
int arr2[5] = {1, 2}; // 部分初始化,其余为 0
int arr3[5] = {0}; // 全部初始化为 0
int arr4[] = {1, 2, 3, 4, 5}; // 自动推断大小为 5
C99 支持指定初始化器:
int arr[10] = {[0] = 1, [5] = 2, [9] = 3}; // 指定位置初始化
// arr: {1, 0, 0, 0, 0, 2, 0, 0, 0, 3}
数组访问
使用下标访问数组元素,下标从 0 开始:
int arr[5] = {10, 20, 30, 40, 50};
printf("%d\n", arr[0]); // 10(第一个元素)
printf("%d\n", arr[4]); // 50(最后一个元素)
arr[2] = 100; // 修改第三个元素
数组越界访问是未定义行为,不会自动检查:
int arr[5] = {1, 2, 3, 4, 5};
printf("%d\n", arr[5]); // 越界!未定义行为
printf("%d\n", arr[-1]); // 越界!未定义行为
数组大小
使用 sizeof 获取数组大小:
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]); // 5
printf("数组字节数: %zu\n", sizeof(arr)); // 20(5 * 4)
printf("元素大小: %zu\n", sizeof(arr[0])); // 4
printf("元素个数: %d\n", size); // 5
数组遍历
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < size; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
C99 支持范围 for 循环的类似写法(仅限指针):
for (int* p = arr; p < arr + size; p++) {
printf("%d ", *p);
}
数组与函数
数组作为参数传递时退化为指针:
void print_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void) {
int arr[] = {1, 2, 3, 4, 5};
print_array(arr, 5);
return 0;
}
在函数内无法用 sizeof 获取数组大小,必须额外传递。
多维数组
二维数组
二维数组是"数组的数组":
int matrix[3][4]; // 3 行 4 列的矩阵
int m1[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
int m2[2][3] = {1, 2, 3, 4, 5, 6}; // 按行顺序初始化
int m3[2][3] = {{1, 2}, {4}}; // 部分初始化
// m3: {{1, 2, 0}, {4, 0, 0}}
二维数组访问
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
printf("%d\n", matrix[0][0]); // 1(第一行第一列)
printf("%d\n", matrix[1][2]); // 6(第二行第三列)
matrix[0][1] = 20; // 修改元素
二维数组遍历
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
int rows = 2, cols = 3;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
二维数组与函数
void print_matrix(int rows, int cols, int matrix[rows][cols]) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
}
int main(void) {
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
print_matrix(2, 3, matrix);
return 0;
}
C99 支持变长数组(VLA)作为参数,第一维可以省略:
void print_matrix(int matrix[][3], int rows); // 固定列数
void print_matrix_vla(int rows, int cols, int matrix[rows][cols]); // 变长
内存布局
二维数组在内存中按行连续存储:
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
内存布局:
地址: [0] [1] [2] [3] [4] [5]
值: 1 2 3 4 5 6
└─第一行─┘ └─第二行─┘
字符数组与字符串
字符数组
字符数组存储单个字符:
char chars[5] = {'H', 'e', 'l', 'l', 'o'};
字符串
字符串是以空字符 \0 结尾的字符数组:
char str1[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 显式结尾
char str2[6] = "Hello"; // 字符串字面量,自动添加 \0
char str3[] = "Hello"; // 自动推断大小为 6
char str4[] = "Hello"; // 包含 \0,大小为 6
字符串字面量自动在末尾添加 \0,声明数组时要预留空间。
字符串输入输出
char name[50];
printf("请输入姓名: ");
scanf("%s", name); // 遇到空白符停止
printf("你好, %s\n", name);
scanf 读取字符串的问题:
- 遇到空格、制表符、换行符停止
- 不检查缓冲区大小,可能溢出
安全的输入方式:
char name[50];
printf("请输入姓名: ");
fgets(name, sizeof(name), stdin); // 安全,读取一行
// 去除末尾的换行符
name[strcspn(name, "\n")] = '\0';
printf("你好, %s\n", name);
字符串长度
strlen 返回字符串的实际长度(不含 \0):
#include <string.h>
char str[] = "Hello";
printf("长度: %zu\n", strlen(str)); // 5
printf("数组大小: %zu\n", sizeof(str)); // 6(包含 \0)
字符串函数
<string.h> 提供了丰富的字符串操作函数:
#include <string.h>
char s1[20] = "Hello";
char s2[20] = "World";
char s3[20];
// 字符串长度
size_t len = strlen(s1); // 5
// 字符串复制
strcpy(s3, s1); // s3 = "Hello"
strncpy(s3, s1, sizeof(s3) - 1); // 安全版本
s3[sizeof(s3) - 1] = '\0'; // 确保以 \0 结尾
// 字符串连接
strcat(s1, " "); // s1 = "Hello "
strcat(s1, s2); // s1 = "Hello World"
strncat(s1, s2, sizeof(s1) - strlen(s1) - 1); // 安全版本
// 字符串比较
int result = strcmp("apple", "banana"); // 返回负数(apple < banana)
result = strcmp("apple", "apple"); // 返回 0(相等)
result = strcmp("banana", "apple"); // 返回正数(banana > apple)
// 字符串查找
char* p = strchr("Hello", 'l'); // 查找字符,返回 "llo"
char* q = strstr("Hello", "ll"); // 查找子串,返回 "llo"
字符串比较
strcmp 按字典序比较字符串:
int compare(const char* s1, const char* s2) {
int result = strcmp(s1, s2);
if (result < 0) {
printf("%s < %s\n", s1, s2);
} else if (result > 0) {
printf("%s > %s\n", s1, s2);
} else {
printf("%s == %s\n", s1, s2);
}
return result;
}
注意:不能直接用 == 比较字符串内容:
char s1[] = "Hello";
char s2[] = "Hello";
if (s1 == s2) { // 错误!比较的是地址
// ...
}
if (strcmp(s1, s2) == 0) { // 正确!比较内容
// ...
}
字符串转换
<stdlib.h> 提供字符串与数值的转换函数:
#include <stdlib.h>
// 字符串转整数
int num1 = atoi("123"); // 123
long num2 = atol("123456"); // 123456
// 字符串转浮点数
double d1 = atof("3.14"); // 3.14
// 更安全的转换函数
char* endptr;
long num3 = strtol("123abc", &endptr, 10); // 123, endptr 指向 "abc"
double d2 = strtod("3.14abc", &endptr); // 3.14
// 数值转字符串
char buffer[20];
sprintf(buffer, "%d", 123); // buffer = "123"
snprintf(buffer, sizeof(buffer), "%f", 3.14); // 安全版本
字符串数组
字符串数组
存储多个字符串:
char fruits[3][10] = {
"Apple",
"Banana",
"Orange"
};
for (int i = 0; i < 3; i++) {
printf("%s\n", fruits[i]);
}
指针数组
更灵活的方式是使用指针数组:
const char* fruits[] = {
"Apple",
"Banana",
"Orange"
};
for (int i = 0; i < 3; i++) {
printf("%s\n", fruits[i]);
}
区别:
char arr[3][10]:每个字符串占用固定 10 字节char* arr[3]:每个指针指向不同长度的字符串
常见操作示例
字符串反转
void reverse_string(char* str) {
int len = strlen(str);
for (int i = 0; i < len / 2; i++) {
char temp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = temp;
}
}
int main(void) {
char str[] = "Hello";
reverse_string(str);
printf("%s\n", str); // olleH
return 0;
}
统计字符出现次数
int count_char(const char* str, char ch) {
int count = 0;
for (int i = 0; str[i] != '\0'; i++) {
if (str[i] == ch) {
count++;
}
}
return count;
}
int main(void) {
char str[] = "Hello, World!";
printf("'l' 出现 %d 次\n", count_char(str, 'l')); // 3
return 0;
}
字符串分割
#include <string.h>
int main(void) {
char str[] = "apple,banana,orange";
char* token = strtok(str, ",");
while (token != NULL) {
printf("%s\n", token);
token = strtok(NULL, ","); // 后续调用传 NULL
}
return 0;
}
输出:
apple
banana
orange
注意:strtok 会修改原字符串,将分隔符替换为 \0。
删除字符串中的字符
void remove_char(char* str, char ch) {
int j = 0;
for (int i = 0; str[i] != '\0'; i++) {
if (str[i] != ch) {
str[j++] = str[i];
}
}
str[j] = '\0';
}
int main(void) {
char str[] = "Hello, World!";
remove_char(str, 'l');
printf("%s\n", str); // Heo, Word!
return 0;
}
小结
本章介绍了 C 语言的数组与字符串:
- 一维数组:声明、初始化、访问、遍历
- 多维数组:二维数组的声明、初始化、遍历
- 字符数组与字符串:字符串的本质、输入输出
- 字符串函数:strlen、strcpy、strcat、strcmp、strstr 等
- 字符串转换:atoi、atof、sprintf 等
- 常见字符串操作:反转、统计、分割、删除
下一章将学习 指针,这是 C 语言最核心也是最重要的概念。