深入详解C语言数组:承上启下——从C语言数组基础到数据结构衔接

发布于:2025-08-15 ⋅ 阅读:(17) ⋅ 点赞:(0)


🔥个人主页艾莉丝努力练剑

❄专栏传送门:《C语言》《数据结构与算法》C语言刷题12天IO强训LeetCode代码强化刷题洛谷刷题C/C++基础知识知识强化补充C/C++干货分享&学习过程记录

🍉学习方向:C/C++方向

⭐️人生格言:为天地立心,为生民立命,为往圣继绝学,为万世开太平

前言:数组是C语言中最基础也是最最重要的数据结构,理解其内存布局和操作特性是学习更复杂数据结构的基础。从一维数组到二维数组,再到变长数组,C语言提供了不同层次的数组支持。虽然数组有局限性,但其简单高效的特性和顺序存储的特点,使其在适合的场景下仍然是首选的数据组织方式。



目录

一、一维数组:最基础的数据容器

1.1  定义与初始化

1.2  内存布局

1.3  数组与指针的关系

二、二维数组:表格型数据存储

2.1  定义与初始化

2.2  内存布局

2.3  访问方式

三、变长数组(VLA):C99引入的灵活特性

3.0  变长数组(VLA)到来前的C语言创建数组BeLike:

3.1  什么是变长数组(VLA)

3.2  变长数组(VLA)的代码演示

3.3  主要的注意事项

3.4  总结一下变长数组(VLA)

四、数组应用:二分查找算法

4.1  算法特性

4.2  代码演示

五、承上启下:衔接C语言数组与数据结构

5.1  顺序表(动态数组)

5.2  栈(数组实现,链表用的比较少)

5.3  顺序存储的二叉树

六、数组的局限性


 C++的两个参考文档:

老朋友(非官方文档):cplusplus

官方文档(同步更新):cppreference


掌握数组:一维数组、二维数组、变长数组及简单的二分查找

一、一维数组:最基础的数据容器

一维数组概念:一维数组是C语言中最简单的数据结构,是一组相同类型元素的线性集合。

1.1  定义与初始化

关键点

(1)数组长度必须是编译期常量(C99标准之前);

(2)数组下标从0开始;

(3)数组名在大多数情况下代表数组首元素的地址。

代码演示:

// 定义方式:类型 数组名[元素个数];
int scores[5]; // 声明一个包含5个整数的数组

// 初始化方式
int primes[5] = {2, 3, 5, 7, 11}; // 完全初始化
int evens[10] = {2, 4, 6}; // 部分初始化,剩余元素自动初始化为0
int arr[] = {1, 2, 3}; // 省略长度,编译器自动计算

1.2  内存布局

内存布局:一维数组在内存中是连续存储的,每个元素占用相同大小的内存空间。

例如 int arr[3] 在32位系统(x86环境)中占用连续的12字节(3×4字节)。

1.3  数组与指针的关系

数组与指针关系的重要区别:

sizeof(arr) 返回整个数组的字节大小;

sizeof(p) 返回指针变量的大小(4个字节或8个字节)。

数组与指针的关系相关的代码演示:

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 等价于 &arr[0]

// 以下表达式等价
arr[2] == *(arr + 2) == *(p + 2) == p[2]

二、二维数组:表格型数据存储

引言:二维数组可以看作是"数组的数组",常用于表示矩阵或表格数据。

2.1  定义与初始化

初始化代码演示:

// 定义方式:类型 数组名[行数][列数];
int matrix[3][4]; // 3行4列的矩阵

// 初始化方式
int grid[2][3] = 
{
    {1, 2, 3}, // 第一行
    {4, 5, 6}  // 第二行
};

// 也可以连续初始化
int grid2[2][3] = {1, 2, 3, 4, 5, 6};

2.2  内存布局

内存布局:二维数组在内存中仍然是线性连续存储的,按行优先顺序排列。

比如 grid[2][3] 的内存布局为:

grid[0][0], grid[0][1], grid[0][2], grid[1][0], grid[1][1], grid[1][2]

如下图所示:

2.3  访问方式

代码演示:

// 常规访问
int val = grid[1][2]; // 获取第2行第3列元素

// 指针方式访问
int val = *(*(grid + 1) + 2); // 等价于grid[1][2]

三、变长数组(VLA):C99引入的灵活特性

3.0  变长数组(VLA)到来前的C语言创建数组BeLike:

        在C99标准之前,C语言在创建数组的时候,数组大小的指定只能使用常量、常量表达式,或者如果我们初始化数据的话,可以省略数组大小。

        这样的语法限制,让我们创建数组就不够灵活,有时候数组大了浪费空间,有时候数组又小了不够用的。 这样能爽吗?不爽!如此受限制,跟这群虫豸语法在一起,怎么能搞好数组呢?

3.1  什么是变长数组(VLA)

概念:变长数组(Variable Length Array)允许使用变量指定数组长度。

3.2  变长数组(VLA)的代码演示

代码演示:

int n;
printf("请输入数组大小:");
scanf("%d", &n);

int vla[n]; // 变长数组

3.3  主要的注意事项

这里我们应当注意以下几点:

1、VLA(变长数组)只能是局部变量,不能是全局变量或static变量;

2、VLA(变长数组)不能初始化;

3、内存分配在栈上,大数组可能导致栈溢出;

4、C11标准中VLA(变长数组)变为可选特性

3.4  总结一下变长数组(VLA)

四、数组应用:二分查找算法

二分查找算法:二分查找是数组的经典应用,要求数组必须有序。

4.1  算法特性

二分查找算法的算法特性——

(1)时间复杂度:O(log n)

(2)空间复杂度:O(1)

(3)仅适用于有序数组

4.2  代码演示

既然我们已经知道了二分查找算法的算法特性,就来通过一个实际的例子来感受一下二分查找吧!

int binary_search(int arr[], int size, int target) 
{
    int left = 0;
    int right = size - 1;
    
    while (left <= right) 
    {
        int mid = left + (right - left) / 2; // 防止溢出
        
        if (arr[mid] == target) 
        {
            return mid;
        }     
        else if (arr[mid] < target) 
        {
            left = mid + 1;
        } 
        else 
        {
            right = mid - 1;
        }
    }
    
    return -1; // 未找到
}

五、承上启下:衔接C语言数组与数据结构

5.1  顺序表(动态数组)

顺序表是数组的扩展,可以动态增长——

typedef struct {
    int *data;     // 指向动态数组的指针
    int capacity;  // 当前容量
    int size;      // 当前元素数量
} SeqList;

void init(SeqList *list, int cap) {
    list->data = (int *)malloc(cap * sizeof(int));
    list->capacity = cap;
    list->size = 0;
}

void append(SeqList *list, int value) {
    if (list->size >= list->capacity) {
        // 扩容操作
        list->capacity *= 2;
        list->data = (int *)realloc(list->data, list->capacity * sizeof(int));
    }
    list->data[list->size++] = value;
}

5.2  栈(数组实现,链表用的比较少)

栈的数组实现利用数组的尾部作为栈顶:

#define MAX_SIZE 100

typedef struct {
    int data[MAX_SIZE];
    int top; // 栈顶指针
} ArrayStack;

void push(ArrayStack *stack, int value) {
    if (stack->top < MAX_SIZE - 1) {
        stack->data[++stack->top] = value;
    }
}

int pop(ArrayStack *stack) {
    if (stack->top >= 0) {
        return stack->data[stack->top--];
    }
    return -1; // 栈空
}

5.3  顺序存储的二叉树

完全二叉树可以用数组顺序存储——

// 对于索引为i的节点:
// 父节点:(i-1)/2
// 左孩子:2*i + 1
// 右孩子:2*i + 2

int tree[7] = {1, 2, 3, 4, 5, 6, 7}; // 表示一个完全二叉树

六、数组的局限性

数组不可能是万能的,它主要存在以下几点不足——

(1)固定大小:传统数组大小在编译时确定(VLA【变长数组】除外);

(2)插入删除效率低:平均需要移动O(n)个元素;

(3)内存浪费或不足:静态分配难以精确控制。

正是这些局限性促使我们学习更高级的一些数据结构,比如链表、树等。


往期回顾:

深入详解C语言的循环结构:while循环、do-while循环、for循环,结合实例,讲透C语言的循环结构

掌握数组:一维数组、二维数组、变长数组及简单的二分查找

结语:本文内容到这里就全部结束了,非常感谢大家的阅读!不要忘记给博主“一键四连”哦!


网站公告

今日签到

点亮在社区的每一天
去签到