思维导图


一、 一维数组
1.1 语法结构:声明与初始化
声明语法:
c
// 类型 数组名[常量表达式];
int arr[10]; // 声明一个包含10个整型的数组
float scores[5]; // 声明一个包含5个浮点型的数组
初始化语法:
c
// 1. 完全初始化
int a[5] = {1, 2, 3, 4, 5};
// 2. 部分初始化(剩余补0)
int b[5] = {10, 20}; // {10, 20, 0, 0, 0}
// 3. 自动推断长度(省略长度)
int c[] = {1, 2, 3}; // 编译器自动算出长度为3
// 4. 指定初始化(C99标准)
int d[10] = {[2] = 5, [8] = 9}; // 只有下标2和8赋值,其余为0
1.2 内存图景与寻址公式
数组在内存中是严格连续 的。
如果 int arr[5] 的首地址是 1000,且 int 占 4 字节,那么:
arr[0]@ 1000
arr[1]@ 1004
arr[2]@ 1008...
CPU 寻址公式:
bash
Target_Addr = Base_Addr + index * sizeof(Type)
这也是为什么下标必须从 0 开始------为了省去一次减法运算,追求极致效率。
1.3 动态计算长度
代码示例:
c
int scores[] = {85, 92, 78, 90, 88};
// 标准写法:总字节数 / 单个元素字节数
int len = sizeof(scores) / sizeof(scores[0]);
for (int i = 0; i < len; i++) {
printf("%d ", scores[i]);
}
二、 数组越界
2.1 越界的本质
访问语法: 数组名[下标]
合法的下标范围:0 到 N-1。
危险示例:
c
int arr[3] = {10, 20, 30};
// 越界读取:
// 可能读到之前的函数栈帧数据,或者垃圾值
int val = arr[3];
// 越界写入:
// 极度危险!可能覆盖了循环变量 i,导致死循环
// 或者覆盖了函数的返回地址,导致程序跳转到恶意代码(缓冲区溢出攻击)
arr[3] = 99;
2.2 为什么不报错?
C 语言编译器不检查数组越界。
原因: 信任程序员 + 追求极致性能。每次访问都检查边界会消耗指令周期。C 语言选择裸奔,把速度交给你,把风险也交给你。
三、 常见数组算法模板
3.1 线性查找
代码模板:
c
int find(int arr[], int len, int target) {
for (int i = 0; i < len; i++) {
if (arr[i] == target) {
return i; // 找到了,返回下标
}
}
return -1; // 没找到
}
3.2 数组反转
代码模板:
c
void reverse(int arr[], int len) {
int left = 0;
int right = len - 1;
while (left < right) {
// 交换两端
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
// 向中间逼近
left++;
right--;
}
}
四、 二维数组:矩阵与表格
4.1 语法结构
声明语法: 类型 数组名[行数][列数];
初始化语法:
c
// 3行4列
int matrix[3][4] = {
{1, 2, 3, 4}, // 第0行
{5, 6, 7, 8}, // 第1行
{9, 10, 11, 12} // 第2行
};
// 省略行数(列数绝对不能省!)
int auto_rows[][3] = {
{1, 2, 3},
{4, 5, 6}
}; // 编译器推断为 2 行
4.2 内存布局与遍历
内存是平 的。matrix[0][3] 后面紧挨着 matrix[1][0]。
C 语言采用行主序。
遍历模板(缓存友好):
c
// 外层循环行,内层循环列 ------ 顺着内存跑,速度快
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
五、 数组传参:退化的真相
5.1 数组参数的本质
语法伪装:
c
void func(int arr[]) { ... }
// 或者
void func(int arr[10]) { ... }
以上两种写法,在编译器眼中,都会被无情地翻译成:
c
void func(int *arr) { ... }
这就是数组退化 (Decay) 。传递的仅仅是首元素的地址。
5.2 带来的后果
1.
sizeof(arr)测不到数组长度了(变成了指针大小)。2.必须显式传递长度。
最佳实践模板:
c
// 这里的 const 表示函数承诺不修改数组内容
int sum_array(const int *arr, int len) {
int sum = 0;
for (int i = 0; i < len; i++) {
sum += arr[i];
}
return sum;
}
int main() {
int data[] = {1, 3, 5, 7, 9};
// 在外部算好长度,传进去
int len = sizeof(data) / sizeof(data[0]);
int s = sum_array(data, len);
}
六、 练习题
题目 1: int a[5] = {1, 2}; 数组中 a[3] 的值是多少?
题目 2: 定义 int arr[10];,arr 和 &arr 的值相同吗?
题目 3: 以下代码为什么危险?
c
int arr[5];
for(int i = 0; i <= 5; i++) arr[i] = 0;
题目 4: 在 64 位系统下,void func(char a[100]) { printf("%d", sizeof(a)); } 输出多少?
题目 5: 若 int a[3][4] 的首地址是 0x1000,a[1][0] 的地址是多少?(int 占 4 字节)
题目 6: 解释为什么 int a[][] = {``{1,2}, {3,4}}; 会编译错误?
题目 7: 使用 const 修饰函数参数中的数组有什么好处?
题目 8: 数组下标可以使用负数吗?例如 p[-1]?
题目 9: arr[i] 和 i[arr] 等价吗?为什么?
题目 10: 如何快速将一个大数组全部置为 0?(除了循环)
题目 11: 编写一个函数,判断字符串是否回文(如 "aba" 是,"abc" 不是)。
题目 12: 在二维数组中,a[i][j] 的地址计算公式是什么?(假设列数为 COLS)
题目 13: 字符数组 char s[] = "Hi"; 占用了几个字节?
题目 14: 为什么不建议在栈上定义 int huge_arr[1000000];?
题目 15: 柔性数组 (Flexible Array Member) 是什么?(选做)
七、 解析
题 1 解析
答案: 0。
详解:
只要使用了初始化列表
{...},未显式赋值的元素自动补 0 。如果写int a[5];不初始化,那就是垃圾值。
题 2 解析
答案: 值相同,类型不同。
详解:
arr类型是int*(首元素地址)。
&arr类型是int (*)[10](整个数组指针)。
arr+1增加 4 字节,&arr+1增加 40 字节。
题 3 解析
答案: 越界写入。
详解:
循环条件
i <= 5会访问arr[5],这是第 6 个元素,属于非法内存。
题 4 解析
答案: 8。
详解:
退化!
char a[100]在参数里等同于char *a。64 位指针大小为 8 字节。
题 5 解析
答案: 0x1010 (即 1000 + 16)。
详解:
a[1][0]是第二行的开头。跳过第一行(4个int),偏移4 * 4 = 16字节。
题 6 解析
答案: 二维数组列数不可省略 。
详解:
编译器需要列数来计算地址跳转。它得知道一行有几个,才能知道何时换行。
题 7 解析
答案:
- 防止函数内部意外修改数组。
- 告诉调用者:放心传进来,我是只读的。
题 8 解析
答案: 可以。
详解:
p[-1]等价于*(p - 1)。只要p指向数组中间,向前访问是合法的。
题 9 解析
答案: 等价。
详解:
这是一个 C 语言的奇葩特性。
arr[i]编译为*(arr + i),而i[arr]编译为*(i + arr)。加法满足交换律。
题 10 解析
答案: memset(arr, 0, sizeof(arr));。
详解:
<string.h>提供的库函数,直接操作内存,速度最快。
题 11 解析
答案:
c
int is_palindrome(char str[]) {
int l = 0, r = strlen(str) - 1;
while (l < r) {
if (str[l++] != str[r--]) return 0;
}
return 1;
}
题 12 解析
答案: Base + (i * COLS + j) * ElementSize。
详解:
先跳过
i行,每行COLS个元素。
题 13 解析
答案: 3 字节。
详解:
'H', 'i', '\0'。别忘了隐藏的结束符。
题 14 解析
答案: 栈溢出 (Stack Overflow)。
详解:
栈空间通常很小(如 8MB)。百万个 int 需要 4MB,稍有不慎就撑爆。大数组应定义为全局变量(静态区)或使用
malloc(堆)。
题 15 解析
答案: 结构体中最后一个元素大小未定。
详解:
struct S { int len; char data[]; };常用于网络协议包,数据变长。

日期:2025年2月11日
专栏:C语言