五、C语言数组

思维导图


一、 一维数组

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 越界的本质

访问语法: 数组名[下标]

合法的下标范围:0N-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] 的首地址是 0x1000a[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 解析
答案:

  1. 防止函数内部意外修改数组。
  2. 告诉调用者:放心传进来,我是只读的。

题 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语言

相关推荐
寻寻觅觅☆5 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
YJlio5 小时前
1.7 通过 Sysinternals Live 在线运行工具:不下载也能用的“云端工具箱”
c语言·网络·python·数码相机·ios·django·iphone
l1t5 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
赶路人儿6 小时前
Jsoniter(java版本)使用介绍
java·开发语言
ceclar1236 小时前
C++使用format
开发语言·c++·算法
码说AI7 小时前
python快速绘制走势图对比曲线
开发语言·python
Gofarlic_OMS7 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
星空下的月光影子7 小时前
易语言开发从入门到精通:补充篇·网络爬虫与自动化采集分析系统深度实战·HTTP/HTTPS请求·HTML/JSON解析·反爬策略·电商价格监控·新闻资讯采集
开发语言
老约家的可汗7 小时前
初识C++
开发语言·c++
wait_luky7 小时前
python作业3
开发语言·python