二维字符数组与函数(声明、作用域、生存周期、存储)
1. 二维字符型数组
二维字符数组是C语言中处理字符串数组的常用数据结构,可以看作是一个字符串列表。
1.1 定义与初始化
// 方式1:指定行数和列数 char names[3][10] = {"Alice", "Bob", "Charlie"}; // 方式2:省略行数,由初始化列表决定 char fruits[][8] = {"apple", "banana", "orange"}; // 方式3:逐个字符初始化 char matrix[2][3] = {{'a', 'b', 'c'}, {'d', 'e', 'f'}};1.2 内存布局与访问
二维字符数组在内存中是连续存储的。对于char arr[3][10],它占用30个连续字节,每行10个字符。
#include <stdio.h> int main() { char cities[3][20] = {"Beijing", "Shanghai", "Guangzhou"}; // 访问单个字符 printf("第一个城市的第一个字符: %c\n", cities[0][0]); // B // 访问整个字符串 printf("第二个城市: %s\n", cities[1]); // Shanghai // 遍历所有字符串 for (int i = 0; i < 3; i++) { printf("城市%d: %s\n", i + 1, cities[i]); } return 0; }1.3 与指针数组的区别
二维字符数组存储的是实际字符数据,而字符指针数组存储的是字符串常量的地址。
// 二维字符数组 - 可修改内容 char arr[2][10] = {"hello", "world"}; arr[0][0] = 'H'; // 允许修改 // 字符指针数组 - 指向字符串常量 char *ptr_arr[] = {"hello", "world"}; // ptr_arr[0][0] = 'H'; // 错误!试图修改字符串常量2. 函数的声明与定义
2.1 函数声明(原型)
函数声明告诉编译器函数的名称、返回类型和参数列表,但不包含函数体。
// 函数声明(通常在头文件或文件开头) int add(int a, int b); void print_array(char arr[][10], int rows); double calculate_average(double numbers[], int count);2.2 函数定义
函数定义包含函数的具体实现。
// 函数定义 int add(int a, int b) { return a + b; } // 处理二维字符数组的函数 void print_strings(char strings[][20], int count) { for (int i = 0; i < count; i++) { printf("字符串%d: %s\n", i + 1, strings[i]); } }3. 作用域(Scope)
作用域决定了标识符(变量、函数名)在程序中的可见范围。
3.1 局部作用域
void function1() { int x = 10; // x只在function1中可见 printf("x = %d\n", x); } void function2() { // printf("x = %d\n", x); // 错误!x在function2中不可见 int x = 20; // 这是另一个x,与function1中的x无关 }3.2 全局作用域
#include <stdio.h> int global_var = 100; // 全局变量,从声明处到文件末尾都可见 void func_a() { printf("func_a: global_var = %d\n", global_var); // 可以访问 global_var = 200; // 可以修改 } void func_b() { printf("func_b: global_var = %d\n", global_var); // 可以访问,值为200 } int main() { func_a(); func_b(); return 0; }3.3 块作用域
int main() { int outer = 5; { int inner = 10; // inner只在这个块内可见 printf("inner = %d\n", inner); printf("outer = %d\n", outer); // 可以访问外层变量 } // printf("inner = %d\n", inner); // 错误!inner已不可见 for (int i = 0; i < 3; i++) { int loop_var = i * 2; // loop_var只在循环体内可见 printf("循环%d: loop_var = %d\n", i, loop_var); } // printf("loop_var = %d\n", loop_var); // 错误! return 0; }4. 生存周期(Lifetime)
生存周期指变量在内存中存在的时间。
4.1 自动存储期(局部变量)
void demo_auto() { int auto_var = 10; // 进入函数时创建,函数结束时销毁 auto_var++; printf("auto_var = %d\n", auto_var); } // auto_var在这里被销毁 int main() { demo_auto(); demo_auto(); // 每次调用都创建新的auto_var return 0; }4.2 静态存储期(static变量)
void counter() { static int count = 0; // 只初始化一次,函数结束后仍存在 count++; printf("调用次数: %d\n", count); } int main() { counter(); // 输出: 调用次数: 1 counter(); // 输出: 调用次数: 2 counter(); // 输出: 调用次数: 3 return 0; }4.3 动态存储期(malloc分配)
#include <stdlib.h> int main() { int *ptr = (int*)malloc(5 * sizeof(int)); // 动态分配内存 if (ptr != NULL) { for (int i = 0; i < 5; i++) { ptr[i] = i * 10; } // 使用完毕后必须手动释放 free(ptr); ptr = NULL; // 避免野指针 } return 0; }5. 存储类别
5.1 auto(默认)
void func() { auto int x = 10; // auto可省略,默认就是auto int y = 20; // 等价于 auto int y = 20 }5.2 register
void optimize_loop() { register int i; // 建议编译器将i放入寄存器(编译器可能忽略) for (i = 0; i < 1000000; i++) { // 频繁使用的循环变量 } }5.3 static
// 文件作用域的static变量 static int file_static = 100; // 只在当前文件可见 void preserve_state() { static char buffer[100]; // 函数调用间保持值 static int initialized = 0; if (!initialized) { strcpy(buffer, "初始值"); initialized = 1; } printf("buffer: %s\n", buffer); }5.4 extern
// file1.c int global_counter = 0; // file2.c extern int global_counter; // 声明在其他文件中定义的变量 void increment_counter() { global_counter++; }6. 综合示例:二维字符数组与函数
#include <stdio.h> #include <string.h> // 函数声明 void print_string_array(char arr[][20], int size); void sort_strings(char arr[][20], int size); int find_string(char arr[][20], int size, const char *target); // 全局变量 static int call_count = 0; int main() { char students[][20] = {"ZhangSan", "LiSi", "WangWu", "ZhaoLiu"}; int count = 4; call_count++; printf("=== 程序第%d次运行 ===\n", call_count); printf("原始数组:\n"); print_string_array(students, count); sort_strings(students, count); printf("\n排序后数组:\n"); print_string_array(students, count); int pos = find_string(students, count, "LiSi"); if (pos != -1) { printf("\n找到 LiSi 在位置: %d\n", pos); } return 0; } // 函数定义:打印字符串数组 void print_string_array(char arr[][20], int size) { for (int i = 0; i < size; i++) { printf(" [%d] %s\n", i, arr[i]); } } // 函数定义:冒泡排序字符串数组 void sort_strings(char arr[][20], int size) { char temp[20]; for (int i = 0; i < size - 1; i++) { for (int j = 0; j < size - 1 - i; j++) { if (strcmp(arr[j], arr[j + 1]) > 0) { strcpy(temp, arr[j]); strcpy(arr[j], arr[j + 1]); strcpy(arr[j + 1], temp); } } } } // 函数定义:查找字符串 int find_string(char arr[][20], int size, const char *target) { for (int i = 0; i < size; i++) { if (strcmp(arr[i], target) == 0) { return i; } } return -1; }7. 关键要点总结
- 二维字符数组:用于存储多个字符串,内存连续,可修改内容
- 函数声明:告诉编译器函数接口,便于类型检查
- 作用域:决定标识符的可见范围(局部、全局、块作用域)
- 生存周期:决定变量的存在时间(自动、静态、动态)
- 存储类别:auto(默认)、register、static、extern
- 最佳实践:合理使用static保持状态,及时释放动态内存
理解这些概念对于编写模块化、可维护的C程序至关重要。二维字符数组结合函数的使用,是处理字符串集合的常见模式。
