从零开始学习嵌入式P9----C语言数组(二维字符数组)和函数基础

发布时间:2026/7/30 5:38:36
从零开始学习嵌入式P9----C语言数组(二维字符数组)和函数基础 从零开始学习嵌入式P9----C语言数组二维字符数组和函数基础本篇学习二维字符数组以及函数的定义、调用、声明、变量的作用域与生存周期、存储类型和值传递。示例默认在 Linux GCC 环境中运行。本篇目标学完这一篇我们应该能够理解二维字符数组为什么适合保存一组字符串正确定义、初始化和访问二维字符数组区分单个字符、一行字符数组和整个二维数组计算二维字符数组的行数、每行容量和总空间使用循环逐行处理字符串理解函数在模块化程序设计中的作用正确定义、声明和调用函数区分形参、实参、主调函数和被调函数正确处理函数返回值理解局部变量、全局变量的作用域和生存周期了解auto、register、extern和static理解 C 语言函数参数的值传递规则。一、二维字符数组二维字符数组可以理解为“由多个一维字符数组组成的数组”。例如charwords[5][32];它表示整个数组共有 5 行每一行都是一个包含 32 个char元素的一维字符数组每行最多保存 31 个普通字符和末尾的一个\0整个数组一共包含5 * 32 160个char元素。由于每一行都能保存一个 C 字符串所以二维字符数组常用于保存字符串数组例如设备名称、命令列表或菜单文本。words[0] - 第 1 个字符串 words[1] - 第 2 个字符串 words[2] - 第 3 个字符串 words[3] - 第 4 个字符串 words[4] - 第 5 个字符串二、定义二维字符数组基本格式char数组名[行数][列数];例如charwords[5][32];这里的两个长度含义不同行数 5 - 最多保存 5 个字符串 列数 32 - 每个字符串占用 32 个 char 的空间如果每行保存的是普通 C 字符串那么每行能够保存的最大有效字符串长度为列数 - 1因为最后一个元素要留给\0。可以使用常量统一描述数组大小enum{WORD_COUNT5,WORD_CAPACITY32};charwords[WORD_COUNT][WORD_CAPACITY];这种写法比在代码各处直接使用数字更容易维护。三、访问二维字符数组二维字符数组可以按单个字符访问也可以按整行访问。对于charwords[5][32];1. 访问单个字符words[0][2]它表示第 1 行、第 3 列的一个char元素。例如words[0][0]h;words[0][1]i;words[0][2]\0;此时第一行words[0]保存字符串hi。有效下标范围为行下标0 到 4 列下标0 到 312. 访问一整行words[0]words[0]表示第一行这个char[32]类型的一维字符数组。在传给printf、puts、strlen等函数时它通常会转换为指向首字符的指针因此可以把一整行当作字符串使用printf(%s\n,words[0]);puts(words[0]);前提是这一行在容量范围内包含\0。3. 访问整个数组wordswords表示整个二维数组。它不是一个普通的一维字符串不能直接使用%s输出printf(%s\n,words);/* 类型不匹配不要这样写 */应该逐行处理for(introw0;row5;row){puts(words[row]);}四、函数的作用函数是具有特定功能的一段代码。把重复操作或独立功能封装成函数可以避免重复编写相同代码让程序结构更加清晰让程序实现模块化便于分别测试、维护和复用不同功能让函数名直接表达一段操作的目的。例如程序多次需要计算两个整数中的较大值时可以把这项操作封装为函数intmax(intleft,intright){if(leftright){returnleft;}returnright;}其他代码只需要调用max不必重复写判断逻辑。五、函数的三个基本环节学习一个函数时通常需要掌握函数的定义函数的声明函数的调用。它们的作用不同环节作用定义写出函数真正要执行的代码声明提前告诉编译器函数的名称、返回类型和参数类型调用使用函数完成一次具体操作六、函数的定义函数定义的基本格式返回值类型 函数名(参数类型1形参1,参数类型2形参2,...){函数体;return返回值;}例如intadd(intleft,intright){intresultleftright;returnresult;}这个函数包含四个重要部分。1. 函数名add函数名的命名规则与变量名相同只能由字母、数字和下划线组成不能以数字开头不能使用 C 语言关键字区分大小写。建议使用能够表达功能的名称sum max menu2. 形参intleft,intright形参是形式参数用来描述函数接收什么数据以及如何处理这些数据。形参属于函数内部的局部变量只有函数被调用时才会得到本次调用传入的值。3. 返回值类型int返回值类型说明函数运行结果的类型。如果函数不需要返回结果应明确写成voidvoidprint_separator(void){puts(----------------);}4. 返回值returnresult;return用于结束函数并把结果返回给主调函数。返回表达式的值会转换为函数声明的返回值类型。例如intget_value(void){return3.8;}返回值会转换为int结果为3。实际代码应尽量让返回表达式类型与函数返回值类型保持一致避免精度丢失。七、函数的调用基本形式函数名(实参1,实参2,...);例如intresultadd(10,20);这里add是被调用的函数10和20是实参left和right是形参main是主调函数add是被调函数函数调用表达式add(10, 20)的类型为int函数调用表达式的值为add返回的30。完整示例#includestdio.hintadd(intleft,intright){returnleftright;}intmain(void){intresultadd(10,20);printf(result %d\n,result);return0;}实参与形参应相互对应调用函数时实参的个数和顺序必须与函数原型一致intadd(intleft,intright);add(10,20);/* 正确 */add(10);/* 错误参数太少 */add(10,20,30);/* 错误参数太多 */如果实参类型和形参类型不同编译器会按照函数原型进行必要的类型转换intresultadd(3.8,2.6);两个double实参会先转换为int相当于传入3和2。这种隐式转换可能丢失数据应避免。八、函数的声明函数声明也称为函数原型。它提前告诉编译器函数叫什么函数返回什么类型函数需要几个参数每个参数是什么类型。基本格式返回值类型 函数名(参数类型1,参数类型2,...);例如intadd(intleft,intright);声明中的形参名可以省略intadd(int,int);但保留有意义的形参名通常更容易阅读。1. 先定义后调用如果函数定义出现在调用位置之前编译器已经知道函数信息可以不再单独声明#includestdio.hintadd(intleft,intright){returnleftright;}intmain(void){printf(%d\n,add(10,20));return0;}2. 先调用后定义如果函数定义写在main后面应在调用之前提供声明#includestdio.hintadd(intleft,intright);intmain(void){printf(%d\n,add(10,20));return0;}intadd(intleft,intright){returnleftright;}在多文件项目中函数声明通常放在头文件中函数定义放在对应的.c源文件中。九、变量的作用域变量的作用域是“程序中能够使用这个变量的范围”。1. 局部变量在函数或代码块内部定义的变量通常是局部变量voidexample(void){intvalue10;if(value0){intdoubledvalue*2;printf(%d\n,doubled);}}这里value的作用域是example的函数体doubled的作用域只在if的大括号内部离开对应作用域后变量名不能再被使用。局部变量的作用域从声明位置开始到包含它的最近一层大括号结束。2. 全局变量在所有函数之外定义的变量是全局变量intsystem_state0;voidset_state(void){system_state1;}intmain(void){set_state();printf(%d\n,system_state);return0;}全局变量从声明位置开始可以被本源文件后续的函数使用。需要跨源文件使用时还涉及extern声明。3. 同名变量的遮蔽内层作用域可以定义与外层同名的变量intvalue10;intmain(void){intvalue20;printf(%d\n,value);return0;}这里输出局部变量的值20。局部变量遮蔽了同名全局变量。虽然语法允许这样做但同名变量容易造成混淆应谨慎使用。十、变量的生存周期变量的生存周期是变量从获得存储空间到存储空间被释放的过程。作用域和生存周期不是同一个概念概念关注的问题作用域在代码的哪些位置可以使用这个变量名生存周期变量保存的数据在运行期间存在多久普通局部变量通常在程序执行到其定义所在代码块时获得存储空间离开代码块后结束生存周期。全局变量和静态变量在程序的整个运行期间都存在。十一、变量的存储类型存储类型会影响变量的存储期、链接属性或编译器处理方式。基础阶段常见的关键字包括autoregisterexternstatic1.auto代码块中的普通局部变量默认具有自动存储期voidexample(void){intvalue10;}也可以显式写成voidexample(void){autointvalue10;}但通常省略auto。自动局部变量具有以下特点执行进入相应代码块时开始其生存周期离开代码块后结束生存周期未显式初始化时值不确定不能依赖未初始化的自动变量。intvalue;printf(%d\n,value);/* 错误读取未初始化变量 */2.registerregister表示建议编译器频繁访问这个局部变量registerintindex;编译器可以选择把它放入寄存器也可以忽略这个建议。现代优化编译器通常能自行决定变量是否适合使用寄存器因此实际项目中很少需要手动添加register。不能对声明为register的对象使用取地址运算符registerintvalue10;/* int *pointer value; *//* 不允许 */3.externextern通常用于声明变量或函数在其他位置定义externintsystem_state;它表示这里先声明system_state真正的定义和存储空间位于其他位置。多文件程序可以这样组织/* state.c */intsystem_state0;/* main.c */externintsystem_state;一个具有外部链接的全局变量通常只能有一个定义但可以在需要它的多个源文件中进行extern声明。在实际项目中通常把extern声明放进头文件再由需要使用该变量的源文件包含这个头文件。4.staticstatic的作用与它出现的位置有关。静态局部变量intnext_count(void){staticintcount0;count;returncount;}count的作用域仍然只在next_count函数内部但它的生存周期贯穿整个程序。函数每次调用时会保留上一次的值。连续调用printf(%d\n,next_count());printf(%d\n,next_count());printf(%d\n,next_count());输出1 2 3静态变量如果没有显式初始化会自动初始化为0。文件作用域的静态变量staticintsystem_state0;在函数外部使用static会把该变量的可见性限制在当前源文件中避免与其他源文件中的同名标识符冲突。静态函数staticvoidupdate_state(void){}文件作用域函数加上static后只能在当前源文件中使用。这是隐藏模块内部实现、减少命名冲突的常用方式。十二、函数参数的值传递C 语言的函数参数使用值传递。主调函数调用被调函数时会把实参的值复制给形参。形参是新的局部变量对形参的修改不会直接改变原来的实参变量。#includestdio.hvoidchange_value(intvalue){value100;printf(函数内部 value %d\n,value);}intmain(void){intnumber10;change_value(number);printf(函数外部 number %d\n,number);return0;}输出函数内部 value 100 函数外部 number 10调用过程可以理解为number 的值 10 | | 复制 v 形参 value 的初值 10value 100只修改了形参自己的副本因此number仍然为10。交换函数为什么不能直接生效下面的函数只能交换两个形参副本voidswap(intleft,intright){inttemporaryleft;leftright;righttemporary;}调用intfirst10;intsecond20;swap(first,second);不会改变first和second。如果函数需要修改主调函数中的变量后续要使用指针把变量地址传给函数。即使传入的是地址C 语言本质上仍然是把“地址值”复制给形参。小结二维字符数组是由多个一维字符数组构成的数组常用于保存一组字符串。定义char words[5][32]时5 表示最多保存 5 行字符串32 表示每行有 32 个字符的容量。访问words[row][column]得到单个字符访问words[row]得到一整行字符数组。初始化时可以省略行数但不能省略列数。函数可以减少重复代码并帮助程序实现模块化。一个完整的函数使用过程包含定义、声明和调用。函数定义需要明确返回值类型、函数名、形参和函数体调用时实参与形参必须相互对应如果函数定义位于调用位置之后就必须提前提供函数原型。变量的作用域描述变量名在哪里可用生存周期描述变量保存的数据存在多久。普通局部变量默认具有自动存储期extern用于声明在其他位置定义的对象static可以让局部变量保留值也可以把文件作用域变量或函数限制在当前源文件中。C 语言的函数参数采用值传递。形参获得的是实参值的副本修改普通形参不会直接修改调用者中的变量。理解这一点是后续学习指针、数组传参和模块化嵌入式程序设计的重要基础。