📝 今日学习主题:C语言数组核心用法(嵌入式开发基础必备)

💡 学习目标:掌握数组存储结构、字符数组与字符串的区别、多维数组原理,能独立完成杨辉三角编程练习,夯实嵌入式编程基础。

数组是嵌入式开发中最常用的数据结构之一,无论是存储传感器数据、缓冲区数据,还是实现简单算法,都离不开数组的灵活运用。今天系统梳理数组相关知识点,结合实例拆解难点,方便后续复习和项目调用。

一、数组的存储结构(核心重点)

数组的核心特性的是“连续存储”,这也是它高效访问的关键,尤其适合嵌入式中对内存效率和访问速度有要求的场景。

1.1 数组的存储本质

  • 核心结论:数组中的所有元素地址一定是连续的,编译器会为数组分配一块连续的内存空间,元素按下标顺序依次存储(下标从0开始)。

  • 补充说明:连续存储的优势的是可以通过“首地址+偏移量”快速访问任意元素,无需遍历,这也是嵌入式开发中数组比链表更常用的原因之一。

1.2 数组名的核心作用(重中之重)

数组名是数组的“灵魂”,理解数组名的特性,能轻松搞定数组的地址访问、元素操作,避免嵌入式编程中常见的内存访问错误。

❓ 常见问题拆解(嵌入式高频考点)
  1. 数组名是什么?

    1. 核心答案:数组名本质是数组首元素的地址(注意:数组名≠指针,但在大多数表达式中会退化为首元素指针)。

    2. 补充:数组名并非普通指针,它是编译器识别数组的符号,没有独立的存储空间,与指针常量(T* const)行为相似但类型不同。

  2. 数组名有类型吗?

    1. 有,数组名的类型是“数组元素的指针类型”,由数组元素的类型决定:

    2. 若元素是int类型(如int arr[5]),则数组名的类型是int*(指向int的指针);

    3. 若元素是char类型(如char str[10]),则数组名的类型是char*(指向char的指针)。

  3. 数组名的运算能力(偏移量计算)

    1. 数组名的运算能力由其类型决定,本质是“每偏移1个单位,跳过的字节数”(即元素的大小):

    2. int*类型(数组元素为int):运算能力为4(32位系统中,int占4个字节);

    3. char*类型(数组元素为char):运算能力为1(char占1个字节)。

    4. 示例:int arr[5]; arr+1 表示跳过第一个int元素,指向arr[1]的地址(偏移4个字节)。

  4. 数组名是变量吗?

    1. 不是变量,是一个常量值(地址常量)。

    2. 注意:不能对数组名进行赋值操作(如arr = &arr[1]; 是非法的),因为数组名的地址是编译时确定的,无法修改,这一点与指针变量有本质区别。

  5. 取值运算符(*)与数组访问

    1. *arr:从数组名(首元素地址)开始,取出对应类型大小的字节数,具体取多少字节由数组元素类型决定;

    2. 核心等价式:arr[n] == *(arr + n)(嵌入式编程中常用*(arr + n)访问元素,尤其在循环中更灵活)。

    3. 示例:int arr[3] = {1,2,3}; arr[2] 与 *(arr+2) 均表示访问第三个元素(值为3)。

二、字符数组和字符串(嵌入式字符串操作基础)

嵌入式开发中,字符串常用于串口通信、日志打印、配置信息存储等场景,而C语言中没有专门的字符串类型,需通过字符数组实现,这是高频易错点。

2.1 字符数组

  • 定义:专门用于存储char类型变量的数组(char是C语言中最小的字符存储单位,占1字节);

  • 核心用途:存储单个字符,或存储字符串(是C语言中唯一能存储字符串的数据类型)。

2.2 字符串的定义与特性

  • 定义:由0个或多个字符组成,必须以'\0'(空字符,ASCII值为0)结尾的一组字符,称为字符串;

  • 关键细节:

    • 空串:"" 表示空串,内部仅包含一个'\0'(无其他字符);

    • 字符串常量:如"hello",编译器会自动在末尾添加'\0'(我们编写时可省略,但编译器会自动补充);

    • 字符串变量:C语言中没有“字符串”数据类型,若需使用字符串变量,必须借助字符数组实现。

2.3 存储字符串的数组(易错点解析)

并非所有字符数组都是字符串,只有以'\0'结尾的字符数组,才能被当作字符串使用,以下3个示例对比,彻底分清区别:

  1. 示例1:char str1[10] = "hello";

    1. 解析:数组str1有10个字节空间,"hello"共5个字符,编译器自动在末尾添加'\0',因此str1是字符数组,也可看作字符串变量;

    2. 内存分布:h e l l o \0 (剩余4个空间默认为'\0')。

  2. 示例2:char str2[2] = {'h', 'i'};

    1. 解析:str2是字符数组,但不是字符串变量

    2. 原因:数组空间仅2个字节,刚好存储'h'和'i',没有多余空间存储'\0',不符合字符串“以'\0'结尾”的要求,无法作为字符串使用(如用strlen计算长度会出错)。

  3. 示例3:char str3[2] = "hi";

    1. 解析:数组越界(嵌入式编程中致命错误!);

    2. 原因:"hi"作为字符串,包含'h'、'i'和'\0'共3个成员,而数组str3仅分配2个字节空间,无法容纳'\0',会导致内存越界,可能破坏其他数据。

2.4 字符串的常用操作(嵌入式必备)

  • 长度:字符串的长度是“不包含'\0'的字符个数”,可通过strlen()函数计算(需包含头文件<string.h>),strlen()的实现原理是遍历字符直到'\0',返回计数器值;

  • 复制:将一个字符串复制到另一个字符数组中,常用函数strcpy()、strncpy()(推荐用strncpy(),可限制复制长度,避免缓冲区溢出);

  • 拼接:将一个字符串追加到另一个字符串末尾,常用函数strcat()、strncat(),注意目标数组需有足够空间容纳拼接后的结果;

  • 比较:逐字符比较两个字符串的ASCII值,第一个不相等的字符,ASCII码值大的字符串整体更大(常用函数strcmp(),返回值为负数、0、正数,分别表示小于、等于、大于);

    • 示例1:"hello" > "hi" → 错误!实际是"hello" < "hi",因为第一个不同字符'e'(ASCII=101)< 'i'(ASCII=105);

    • 示例2:"hello" == "hello" → 正确,所有字符完全一致;

    • 示例3:"hello" < "helloa" → 正确,"hello"长度为5,"helloa"长度为6,前5个字符一致,短字符串更小。

三、多维数组(以二维数组为例)

嵌入式开发中,多维数组常用于存储矩阵数据、坐标信息等(如LCD屏幕像素矩阵、传感器采集的二维数据),核心是理解“多维数组的本质是数组的数组”。

3.1 多维数组的定义与理解

以int类型为例,通过对比一维数组和二维数组,理解多维数组的存储逻辑:

  1. 一维数组的“多维表示”

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

    2. 若每两个成员表示一个坐标(如(1,2)、(3,4)、(5,6)),用一维数组表示不够直观,此时可使用二维数组。

  2. 二维数组的正确定义

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

    2. 解析:

      • arr是一个二维数组,包含3个成员(即3个一维数组);

      • 每个成员都是一个包含2个int类型变量的一维数组;

      • 存储结构:仍为连续存储,顺序为arr[0][0]、arr[0][1]、arr[1][0]、arr[1][1]、arr[2][0]、arr[2][1],地址依次连续。

3.2 二维数组名的深层解析(难点)

  1. arr是什么?

    1. arr表示二维数组的首地址,本质是&arr[0](即第一个一维数组的地址);

    2. 补充:&arr[0]表示整个一维数组的地址,其类型是int(*)[2](指向包含2个int的数组的指针),运算能力为8字节(32位系统中,2个int共8字节),因此arr的运算能力也为8字节。

  2. arr[0]是什么?

    1. arr[0]是二维数组的第一个成员,本质是一个包含2个int类型变量的一维数组;

    2. arr[0]的作用:表示该一维数组中首元素的地址(即&arr[0][0]);

    3. 访问示例:(arr[0])[0] 等价于 arr[0][0],表示第一个一维数组的第一个元素(值为1)。

  3. arr[0]的类型:int*(因为arr[0]是一维数组的首地址,该一维数组的元素是int类型)。

  4. 补充:整个数组的大小

    1. 对于int arr[3][2],整个数组的大小 = 3(一维数组个数) × 2(每个一维数组的元素个数) × 4(int字节数) = 24字节;

    2. 单个一维数组(如arr[0])的大小 = 2 × 4 = 8字节,与&arr[0]的运算能力一致。

四、实战练习:打印10行杨辉三角(二维数组应用)

杨辉三角是二维数组的经典实战案例,嵌入式开发中可通过该练习掌握二维数组的初始化、元素赋值和格式输出,同时理解循环逻辑的优化技巧。

4.1 杨辉三角核心规律

  • 每行首尾元素固定为1;

  • 中间元素 = 上一行相邻两个元素之和(如第i行第j列元素 = 第i-1行第j-1列 + 第i-1行第j列);

  • 第i行(从0开始计数)有i+1个元素。

4.2 10行杨辉三角预期输出

1
1   1
1   2   1
1   3   3   1
1   4   6   4   1
1   5   10  10  5   1
1   6   15  20  15  6   1
1   7   21  35  35  21  7   1
1   8   28  56  70  56  28  8   1
1   9   36  84  126 126 84  36  9   1

4.3 规范实现代码(嵌入式风格,带注释)

/******************************
* 功能:打印10行杨辉三角(二维数组实现)
* 适用场景:嵌入式C语言基础练习、数组操作实战
* 核心思路:1. 边界初始化(每行首尾为1);2. 中间元素递推;3. 格式输出
*****************************/
#include <stdio.h>
#define ROW 10 // 宏定义行数,便于修改(嵌入式开发常用技巧)

int main() {
    int yanghui[ROW][ROW] = {0}; // 初始化二维数组为0,避免随机值干扰
    int i, j;

    // 步骤1:生成杨辉三角数据
    for (i = 0; i < ROW; i++) {
        yanghui[i][0] = 1;          // 每行首元素为1(边界条件)
        yanghui[i][i] = 1;          // 每行尾元素为1(边界条件)
        if (i > 1) {                // 前两行无中间元素,无需计算
            // 中间元素:j从1到i-1,避免越界
            for (j = 1; j < i; j++) {
                yanghui[i][j] = yanghui[i-1][j-1] + yanghui[i-1][j];
            }
        }
    }

    // 步骤2:按三角格式输出(嵌入式常用格式优化)
    printf("杨辉三角(前10行):\n");
    for (i = 0; i < ROW; i++) {
        for (j = 0; j <= i; j++) {  // 第i行仅输出i+1个元素
            printf("%4d", yanghui[i][j]); // 占4位输出,保证三角对齐
        }
        printf("\n"); // 每行结束换行
    }

    return 0;
}

4.4 代码解析(嵌入式重点)

  • 宏定义ROW=10:嵌入式开发中常用宏定义统一常量,后续修改行数只需修改宏定义,无需逐行调整;

  • 数组初始化:yanghui[ROW][ROW] = {0},避免未赋值元素为随机值,防止打印异常;

  • 循环优化:j < i 避免无效循环(如i=3时,j无需遍历到9),提升代码效率(嵌入式开发对效率要求较高);

  • 格式输出:%4d 保证每个元素占4位,使杨辉三角呈现标准的三角形态,提升可读性。

五、今日学习总结

  • 数组核心:连续存储、数组名是首元素地址(常量),掌握arr[n]与*(arr+n)的等价关系;

  • 字符数组与字符串:重点区分“字符数组”和“字符串”(是否以'\0'结尾),避免数组越界;

  • 多维数组:本质是“数组的数组”,理解数组名的类型和运算能力,避免地址访问错误;

  • 实战重点:杨辉三角练习,掌握二维数组的初始化、元素递推和格式输出,贴合嵌入式编程规范。

📌 备注:数组是嵌入式开发的基础,后续学习指针、缓冲区、算法时都会用到,建议多敲代码练习,重点掌握地址访问和边界判断,避免内存越界(嵌入式开发中内存越界会导致程序崩溃)。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐