跳到主要内容

数组与字符串

数组是存储相同类型元素的连续内存空间。字符串是字符数组的一种特殊形式,以空字符 \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 语言最核心也是最重要的概念。