← 返回文章列表

C 语言(一)|数组:从一维到二维、存储与越界

详细介绍C语言数组的基础概念、一维数组、二维数组的定义、初始化、访问和内存布局。

版权声明:本文为作者原创,版权归作者所有。如需转载或引用,请联系作者获得授权,并在使用时注明出处。

大家好呀,好久不见~转眼假期已经到啦,大一上半程的校园生活伴着期末复习备考的忙碌告一段落,我也总算对大学的学习节奏慢慢熟悉了。这段时间跟着课程啃完了 C 语言指针的内容,也刷了不少对应的练习题,虽说能跟着做题目,但总觉得学编程不能一直往前赶,适时停下来回顾总结,把基础打牢才更重要。思来想去,就从数组这个 C 语言的核心基础开始,重新梳理一遍之前的知识点,再走一趟 C 语言的学习旅程~

一、C语言数组的基础认知

1.1 什么是数组(核心定义)

在 C 语言里,数组是一组相同数据类型元素的连续存储集合,核心作用是批量管理同类数据,比如用一个数组存 10 个学生的成绩,比定义 10 个单独变量更简洁。

1.2 核心特点

  • 类型严格统一:数组里所有元素必须是同一种 C 语言基础类型(如 int、char、float),不能混存(比如不能在 int 数组里放字符串)
  • 内存连续:数组在内存中是"挨着放"的
  • 索引从 0 开始:访问数组元素靠"索引(下标)",第一个元素是 arr[0],最后一个是 arr[长度-1],越界访问(如 arr[10])会导致程序出错

1.3 数组的使用价值(解决什么问题)

批量处理同类型数据,替代大量单独定义的变量,让代码更简洁、易维护,是处理表格、成绩、序列等批量数据的核心工具。

二、一维数组(核心重点,C语言数组基础)

2.1 一维数组的定义与初始化

2.1.1 标准定义格式

type arr_name[常量值];

type是数组中存放数据的类型(如 int、char、float 等),也可以是自定义类型;arr_name是数组的名字;[]内是数组的大小,代表元素个数。

2.1.2 3种常见初始化方式

// 1. 完全初始化:给所有元素赋值
int num1[3] = {1, 2, 3};  // 数组num1:[1, 2, 3]

// 2. 部分初始化:未赋值元素默认是0
int num2[3] = {1};        // 数组num2:[1, 0, 0]

// 3. 省略长度:编译器按初始化元素个数自动算长度(此时长度是2)
int num3[] = {5, 6};      // 数组num3:[5, 6]

2.2 一维数组的访问与遍历

2.2.1 元素访问方式

1. 基础方式:索引(下标)访问(最常用)

C 语言数组通过"索引(下标)"定位元素,索引从 0 开始(对应数组第一个元素),最大值为"数组长度-1"。

2. 进阶方式:指针偏移访问

C 语言中,数组名本质是"指向数组第一个元素的常量指针"(即数组名 == &数组[0])。

#include<stdio.h>
int main()
{
    int num1[3] = { 1, 2, 3 };
    printf("%d", num1[0]);  // 输出:1
    return 0;
}

2.2.2 2种遍历方法(for循环 + sizeof通用求长)

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
    int num1[3] = { 0 };
    int sum = sizeof(num1) / sizeof(num1[0]);
    printf("请输入元素的值:");
    for(int i = 0; i < sum; i++)  // 数组元素的输入
    {
        scanf("%d", &num1[i]);
    }
    printf("数组元素的打印\n");
    for (int i = 0; i < sum; i++)  // 数组元素的打印
    {
        printf("%d ", num1[i]);
    }
    return 0;
}

2.2.3 数组越界问题(核心风险)

int arr[5] = {1, 2, 3, 4, 5};
arr[5] = 10;  // 越界!合法下标仅0-4
for(int i=0; i<=5; i++){}  // 越界!条件应写i<5

2.3 一维数组的内存布局

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
    int num1[3] = { 1, 2, 3 };
    for (int i = 0; i < 3; i++)
    {
        printf("%d ", num1[i]);
        printf("&arr[%d] = %p\n", i, &num1[i]);
    }
    return 0;
}

运行结果:

1 &arr[0] = 000000A0F54FF828
2 &arr[1] = 000000A0F54FF82C
3 &arr[2] = 000000A0F54FF830

内存存储特征:

  • 数组元素在内存中连续不间断存储,同类型元素占字节数一致,地址随下标依次递增(递增步长 = 单个元素字节数)
  • 数组名是数组首元素的地址常量,不可修改(如 arr++ 属于语法错误)
  • sizeof(数组名) 计算的是整个数组的总字节数

下标访问的底层逻辑:

  • 数组下标访问 arr[i] 的本质是指针偏移解引用,等价于 *(arr + i)
  • 以首元素地址为基准,向后偏移 i 个元素的字节数,再取对应内存的值

三、二维数组(一维数组的延伸,多维数组基础)

3.1 二维数组的核心理解(本质是"一维数组的数组")

二维数组可以看作是由多个相同长度的一维数组组成的数组,C 语言中按"行优先"规则存储,本质上还是一维数组的嵌套,用于处理表格、矩阵等二维结构数据。

3.2 二维数组的定义与初始化

3.2.1 标准定义格式

type arr_name[常量值1][常量值2];
int num1[3][6];   // 3行6列的二维数组
double num2[3][6];

解释:

  • 3 表示数组有 3 行
  • 6 表示每一行有 6 个元素

3.2.2 4种灵活初始化方式

1. 按行完全初始化(最清晰,推荐)

把每一行的元素用 {} 包裹,结构一目了然。

int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};

2. 按行部分初始化

未初始化的元素会自动补 0。

int arr[2][3] = {{1}, {4, 5}};
// 结果:arr[0] = {1, 0, 0},arr[1] = {4, 5, 0}

3. 省略行长度初始化

可以省略行数,编译器会根据初始化的元素个数自动计算行数,但列长度不能省略。

int arr[][3] = {1, 2, 3, 4, 5, 6};
// 编译器自动推断行数为 2,等价于 int arr[2][3]

4. 紧凑初始化

不分行包裹,按"行优先"顺序依次填充元素,可读性稍差。

int arr[2][3] = {1, 2, 3, 4, 5, 6};
// 效果与方式 1 完全一致

3.2.3 关键注意事项

  • 列长度不可省略:无论是否省略行数,二维数组的列长度必须显式指定,否则编译器无法确定每行的元素个数
  • 默认补 0 规则:如果初始化元素不足,未赋值的元素会被默认初始化为 0
  • 全局 vs 局部数组:全局/静态二维数组默认所有元素为 0;局部二维数组未初始化时,元素为随机值

3.3 二维数组的访问与遍历

3.3.1 元素访问方式

索引(下标)访问:这是最常用的访问方式,行、列索引均从 0 开始,最大值为"对应维度长度-1"。

进阶方式:指针偏移访问:二维数组名本质是"指向第一行一维数组的常量指针",即 数组名 == &数组[0]。

3.3.2 双层 for 循环遍历

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
    int num2[2][3] = {0};
    for (int i = 0; i < 2; i++)  // 二维数组的输入
    {
        for (int j = 0; j < 3; j++)
        {
            scanf("%d", &num2[i][j]);
        }
    }
    printf("二维数组的输出如下\n");
    for (int i = 0; i < 2; i++)  // 二维数组的输出
    {
        for (int j = 0; j < 3; j++)
        {
            printf("%d ", num2[i][j]);
        }
        printf("\n");
    }
    return 0;
}

3.4 二维数组的内存布局

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
    int num2[2][3] = { 0 };
    int num = 1;
    for (int i = 0; i < 2; i++)  // 遍历赋值并打印地址
    {
        for (int j = 0; j < 3; j++)
        {
            printf("第%d个元素的地址为:%p \n", num, &num2[i][j]);
            num++;
        }
    }
    return 0;
}

物理存储核心特征(打破"二维"误区):

  • 我们主观认为的存储方式:按行、列的二维表格形式分散存储
  • 实际的存储方式:所有元素在内存中连续不间断存储,按"行优先"顺序依次排列,无行、列的物理分隔

四、C99中的变长数组

4.1 核心定义

变长数组(Variable Length Array,简称 VLA)是 C99 标准新增的特性,允许用变量指定数组的长度,数组长度可在运行时根据变量值动态确定,打破了 C99 前数组长度必须是「编译期常量」的限制。

4.2 示例

#include <stdio.h>
int main() {
    int n;
    printf("请输入数组长度:");
    scanf("%d", &n);
    int arr[n];  // 用变量n定义数组长度,这就是变长数组
    // 遍历赋值+打印
    for(int i=0; i<n; i++){
        arr[i] = i+1;
        printf("arr[%d] = %d\n", i, arr[i]);
    }
    return 0;
}

4.3 注意事项(避坑重点)

  • 编译器支持问题:GCC、Clang 完全支持 VLA,但微软 MSVC(Visual Studio 默认编译器)不支持 C99 VLA
  • 不可初始化:定义变长数组时不能直接初始化(int arr[n] = {1, 2, 3}; 语法错误)
  • 栈溢出风险:栈空间较小,若变量值过大(如 int arr[1000000];),会直接导致栈溢出,程序崩溃
  • sizeof 适配 VLA:sizeof(变长数组) 在运行时计算(普通数组 sizeof 在编译期计算),可正常获取 VLA 的实际总字节数

五、C语言数组高频坑点与避坑技巧

坑点说明避坑技巧
下标从 1 开始新手最易犯,牢记数组下标从 0 开始最后一个元素下标为「长度 - 1」
数组越界C 语言不检查,后果严重拒绝硬编码长度,用 sizeof 通用求长公式,循环条件严格写「i < 长度」
定义数组时长度用变量C99 前编译器不支持如需动态长度可使用堆内存分配或 C99 变长数组
未初始化局部数组直接使用局部数组未初始化元素为随机值使用前必须手动初始化或赋值
字符数组初始化的 '\0' 遗漏字符数组作为字符串使用时,末尾必须有 '\0'确保字符串末尾有终止符
二维数组初始化省略列长度语法错误二维数组列长度不可省略

六、二分查找练习

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
    int arr[] = { 1, 2, 3, 5, 6, 7, 8, 9, 11, 13 };
    int right = sizeof(arr) / sizeof(arr[0]) - 1;  // 右下标
    int left = 0;  // 左下标
    int m = 0;  // 中间下标
    int k = 7;  // 目标值
    int flag = 0;  // 标记判断能否找到,0为找不到,1为找到
    
    while (left <= right)
    {
        m = (right + left) / 2;
        if (arr[m] > k)
        {
            right = m - 1;
        }
        if (arr[m] < k)
        {
            left = m + 1;
        }
        else
        {
            flag = 1;
            break;
        }
    }
    
    if (flag == 1)
    {
        printf("下标为%d\n", m);
    }
    else
        printf("找不到");
    
    return 0;
}

七、补充内容

7.1 sizeof求长原理再梳理

sizeof(数组名)      // 计算整个数组占的总字节数
sizeof(数组名[0])   // 计算数组中单个元素的字节数

相除结果就是数组实际的元素个数。

7.2 C语言数组进阶用法

  • 数组与指针的关联(核心等价公式)
  • 数组作为函数参数(传参本质 + 长度丢失问题)
  • 字符数组与字符串的区别(C 语言无字符串类型)
  • 一维数组的经典操作(逆序、查找、去重)

7.3 C语言数组实战案例

  • 一维数组:统计班级学生成绩(平均分/最高分/最低分)
  • 一维数组:实现简单冒泡排序(排序基础)
  • 二维数组:存储并打印矩阵(行/列求和)
  • 字符数组:简单字符串遍历与统计(字符个数/数字个数)

八、总结

本篇我们学习了 C 语言数组的核心知识:

  1. 数组的基础认知:数组是一组相同类型元素的连续存储集合
  2. 一维数组:定义、初始化、访问、遍历、内存布局
  3. 二维数组:本质是"一维数组的数组",按行优先存储
  4. 变长数组:C99 新增特性,支持运行时动态指定长度
  5. 常见易错点:数组越界、下标边界、数组名本质
  6. 实战应用:二分查找、成绩统计、排序算法

数组是后续学习数据结构的基础,掌握这些核心知识点,为指针和更复杂的数据结构学习打下坚实基础!

感谢大家对我文章的支持,也很高兴与各位共同努力学习,我会继续坚持分享总结,共同奔赴下一个山海!