数组
引言
本章逻辑结构如下:
- 先介绍一维数组基础。
- 然后讲解多维数组。
- 接着讨论字符串作为特殊数组。
- 最后探讨数组在函数中的使用、常见错误和最佳实践。
第一节:一维数组基础
一维数组是最简单的形式,像一个线性列表,存储固定数量的相同类型元素。元素通过下标(索引)访问,从0开始。在下一章中,你会学到数组名实际上是一个指向首元素的指针,这解释了数组的许多行为。
1.1 声明数组
- 语法:类型 数组名[大小];
- 大小必须是正整数常量(C99前严格要求常量,C99+支持变量作为大小,称为VLA - 可变长度数组)。
- 示例:int scores[10]; // 10个int元素,未初始化时包含垃圾值。
- 注意:声明时不指定大小会编译错误,除非初始化。
1.2 初始化数组
- 完整初始化:int arr[5] = {1, 2, 3, 4, 5};
- 部分初始化:int arr[5] = {1, 2}; // 剩余元素自动为0(对int、float等)。
- 自动推断大小:int arr[] = {1, 2, 3, 4, 5}; // 大小为5
- 指定位置初始化(C99+):int arr[5] = {[0]=10, [4]=50}; // 其他为0
- 用循环初始化:for (int i = 0; i < 5; i++) arr[i] = i * 10;
1.3 访问和修改元素
- 访问:arr[索引],如 arr[0] 获取第一个元素。这等价于指针解引用*(arr + 索引),详见指针章节。
- 修改:arr[2] = 30;
- 下标范围:0 到 大小-1。越界访问导致未定义行为(如崩溃或错数据)。
示例代码:求数组最大值。
#include <stdio.h>
int main() {
int arr[5] = {10, 50, 30, 40, 20};
int max = arr[0];
for (int i = 1; i < 5; i++) {
if (arr[i] > max) max = arr[i];
}
printf("最大值: %d\n", max); // 输出: 50
return 0;
}
1.4 数组的内存布局
- 数组元素在内存中连续存储。这使得指针运算高效,如用指针遍历数组(指针章节会详细说明)。
- sizeof(arr) 返回整个数组字节大小:sizeof(int) * 5 = 20(假设int 4字节)。
- 求元素数:sizeof(arr) / sizeof(arr[0])。注意,在函数中传递数组时,这个技巧失效,因为数组退化为指针。
注意事项:
- 数组不可整体赋值:arr = {1,2,3}; 只在声明时有效。需用循环或memcpy。
- VLA示例:int n=5; int arr[n]; // C99+
第二节:多维数组
多维数组扩展一维,像表格或立方体,用于矩阵、图像等。同样,多维数组在内存中连续存储,可用指针访问整个块。
2.1 二维数组
- 语法:类型 数组名[行数][列数];
- 示例:int matrix[3][4]; // 3行4列,12个int
- 初始化:int matrix[2][3] = {{1,2,3}, {4,5,6}}; 或扁平{1,2,3,4,5,6}
- 访问:matrix[行][列],如 matrix[1][2] = 6; 这可视为指针运算*(matrix[行] + 列)。
示例:矩阵加法。
int a[2][2] = {{1,2}, {3,4}};
int b[2][2] = {{5,6}, {7,8}};
int sum[2][2];
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
sum[i][j] = a[i][j] + b[i][j];
}
}
2.2 高维数组
- 三维:int cube[2][3][4]; // 2层、3行、4列
- 初始化类似嵌套。
- 内存布局:行优先(C是row-major),连续存储。这允许用单指针遍历整个数组,如将二维数组视为一维(指针章节示例)。
2.3 多维数组的应用
- 图像处理:char img[高度][宽度][通道]; // RGB
- 游戏板:char board[8][8]; // 棋盘
注意事项:
- 大数组可能栈溢出(栈大小有限),考虑动态分配(指针章节中的malloc)。
- 部分初始化:剩余填充0。
第三节:字符串作为数组
在C中,字符串是char数组,以null终止符'\0'结束。字符串常与指针结合使用,如char*指向字符串。
3.1 声明字符串
- char str[6] = {'h','e','l','l','o','\0'}; 或 char str[] = "hello"; // 自动加'\0',大小6
- 未加'\0'不是标准字符串。
3.2 操作字符串
- 访问:str[0] = 'H'; // 修改为"Hello"
- 打印:printf("%s\n", str); // %s读到'\0'止
- 长度:手动循环或用strlen(<string.h>)
示例:字符串连接(简单版)。
#include <string.h>
#include <stdio.h>
int main() {
char s1[10] = "hello";
char s2[] = " world";
strcat(s1, s2); // s1够大
printf("%s\n", s1); // hello world
return 0;
}
3.3 字符串库函数
- <string.h>:strcpy(复制), strcat(连接), strcmp(比较), strlen(长度), strchr(找字符)等。
- 安全版(C11+):strcpy_s 等,防溢出。
注意事项:
- "hello" 是字符串常量,不可修改(指针章节会解释常量字符串指针)。
- 输入:scanf("%s", str); // 读到空格止,gets危险(用fgets)。
- 数组溢出:strcpy不检查大小,用strncpy。
第四节:数组在函数中的使用
- 传递数组:void func(int arr[5]) 或 int arr[] // 大小可选
- 实际退化为指针(详见指针章节),丢失大小:需传长度参数。这意味着函数内sizeof(arr)返回指针大小,不是数组大小。
- 示例:void printArr(int arr[], int size) { for(int i=0; i<size; i++) printf("%d ", arr[i]); }
修改数组:函数内改arr[i]影响原数组(传地址)。
返回数组:不能直接返回局部数组(栈释放),用指针或全局(指针章节用malloc动态返回)。
多维数组传递:void func(int matrix[][4], int rows); // 指定内维大小。
第五节:常见错误和最佳实践
- 越界访问:用条件检查i < size。指针章节会介绍用指针哨兵避免。
- 未初始化:总初始化为0或值。
- 字符串无'\0':导致读越界。
- 大数组栈溢出:用static或全局,或指针动态分配。
- 函数中sizeof失效:因为退化为指针。
- 最佳实践:用const int SIZE=10; 定义大小。循环用sizeof求长度(栈数组)。避免硬编码索引。考虑用指针增强灵活性。
总结与进阶
这一章我们全面掌握了数组,从一维到多维,再到字符串应用。数组是静态高效的集合基础,但其与指针的内在联系(如数组名是指针常量)将在下一章《指针》中揭示,那里你会学到更灵活的内存操作。
进阶阅读:数组与算法(如搜索、排序)、VLA局限。