)
从零开始学习嵌入式P8----C语言数组字符数组本篇将学习一维字符数组、C 字符串的存储和输入输出、常用字符串库函数并初步学习二维整型数组的定义、访问、存储和初始化。示例默认在 Linux GCC 环境中运行。本篇目标学完这一篇我们应该能够理解字符数组与 C 字符串的关系正确为字符串末尾的\0预留空间区分0、\0和0正确定义、初始化和访问字符数组使用%s、puts和fgets安全地输入输出字符串区分strlen得到的字符串长度与sizeof得到的数组容量正确使用strcpy、strcat和strcmp理解二维整型数组的存储结构和下标正确初始化并遍历二维数组一、字符数组与字符串C 语言没有独立的字符串类型。因此想保存字符串通常使用char类型的数组保存chartext[16];但“字符数组”和“字符串”并不等同。1. 字符数组字符数组只要求数组元素是charchardata[5]{h,e,l,l,o};这个数组保存了 5 个字符但没有字符串结束标志因此不能把它直接当作 C 字符串交给%s、puts等。2. 字符串字符串是以空字符\0\0不是字符0结尾的字符序列chartext[6]{h,e,l,l,o,\0};内存内容为下标 0 1 2 3 4 5 内容 h e l l o \0字符串函数从数组开头逐个读取字符直到遇到第一个\0。3. 必须包含\0文本hello包含 5 个可见字符但作为字符串保存时至少需要 6 个char5 个可见字符 1 个 \0 6 个数组元素正确chartext[6]hello;下面的数组没有空间保存字符串结束标志chartext[5]hello;在 C 中这种初始化可以形成一个正好包含 5 个字符的字符数组但它不是以\0结尾的有效字符串系统会报错。二、定义一维字符数组基本格式与其他一维数组相同char数组名[元素个数];例如charcommand[8];chardevice_name[32];charrx_buffer[128];1. 元素个数固定长度数组通常是能够确定的正整数charname[number];2. 最大字符串长度如果数组容量为N普通以\0结尾的字符串最多保存N - 1 个非空字符例如charname[20];最多安全保存 19 个普通字符和末尾的一个\0。三、访问字符数组元素字符数组使用从0开始的下标chartext[20];text[0]h;text[1]i;text[2]\0;此时text保存字符串hi,其余字符为\0。有效下标范围为0 到 元素个数 - 1下标可以是整数常量、变量或表达式for(inti0;i3;i){printf(text[%d] %d\n,i,text[i]);}逐字符输出字符数组同样可以逐字符的输出。#includestdio.hintmain(void){chartext[]hello;for(inti0;text[i]!\0;i){putchar(text[i]);}putchar(\n);return0;}循环条件是否遇到字符串结束标志。四、字符数组的存储大小C 语言规定sizeof(char)1因此字符数组所占字节数与数组元素个数相同chartext[20];故sizeof(text)20五、字符数组的初始化初始化发生在数组定义时不等于定义完成后的元素赋值。1. 逐个字符全部初始化chartext[6]{h,e,l,l,o,\0};2. 使用字符串字面量初始化下面两种写法等价chartext[6]{hello};chartext[6]hello;字符数组会自动保存字符串。3. 部分初始化chartext[6]{h,e,l};没有提供初值的元素会被初始化为0所以数组内容为h e l \0 \0 \0它包含有效字符串hel。不使用大括号初始化也可以得到相同的字符串chartext[6]hel;后面的未使用元素同样为0。4. 整个数组初始化为0chartext[32]{0};这会把所有元素初始化为0因此数组一开始保存的是空字符串。5. 省略数组长度使用初始化列表时C语言允许省略字符长度chartext1[]{h,e,l,\0};chartext2[]{hel};chartext3[]hel;这三个数组的长度都是 4h e l \06. 未初始化的局部字符数组chartext[32];如果text是局部自动数组它的元素值是不确定的不能直接交给%s、puts或字符串函数。建议在填入字符串之前先初始化或者确保输入函数成功写入了完整的内容chartext[32]{0};六、区分0、\0和0前文提到的\0与数字0和字符‘0’这三个写法外观相似但含义不同。写法含义常见数值0整数常量零0\0空字符字符串结束标志00字符0ASCII 中为 48因此0\0//即0等价于 \0但0!\0示例#includestdio.hintmain(void){printf(0 %d\n,0);printf(\\0 %d\n,\0);printf(0 %d\n,0);return0;}在 ASCII 环境中的输出为0 0 \0 0 0 48\0表示字符串结束0则是可以显示的数字字符。七、字符串的长度与数组容量字符串长度是从开头到第一个\0之前的字符个数不包含\0。chartext[32]hello;这里字符串长度 5 数组容量 321.strlen想要知道一个字符数组中字符段长度可以使用strlen。strlen声明在string.h中#includestring.h用法lengthstrlen(text);完整示例#includestdio.h#includestring.hintmain(void){chartext[32]hello;printf(strlen(text) %s\n,strlen);printf(sizeof(text) %s\n,sizeof(text));return0;}输出strlen(text) 5 sizeof(text) 322.strlen与sizeof的区别对比项strlen(text)sizeof(text)得到什么字符串长度数组占用空间是否包含\0不包含包含数组全部元素何时确定运行时扫描到\0对固定数组通常在编译期确定是否要求有效字符串是否数组传入函数后仍可统计字符串通常只能得到指针大小八、输出字符串1. 使用printf(%s)#includestdio.hintmain(void){chartext[20]hello world;printf(text %s\n,text);return0;}%s会从数组开头持续输出直到遇到\0。2. 使用puts#includestdio.hintmain(void){chartext[20]hello world;puts(text);return0;}puts输出字符串后会自动添加一个换行符。大致相当于printf(%s\n,text);3. 字符串必须有效终止printf(%s)和puts都依赖末尾\0。如果字符数组没有结束标志它们可能读取数组之外的内存。九、输入字符串1. 使用scanf(%s)chartext[32]{0};scanf(%s,text);这里不写text的原因等后面学习指针时再细讲。2.getsgets(text);gets无法知道目标数组容量输入过长时会直接越界。但该函数存在危险程序编译时可能警告或报错。十、复制字符串strcpystrcpy声明在string.h中#includestring.h基本形式strcpy(目标数组,源字符串);示例#includestdio.h#includestring.hintmain(void){chardestination[32]{0};constcharsource[]hello world;strcpy(destination,source);printf(destination %s\n,destination);return0;}strcpy会复制源字符串中的所有字符包括末尾的\0。目标容量必须足够必须满足目标数组容量 strlen(源字符串) 1源和目标内存区域也不能重叠。十一、拼接字符串strcatstrcat把源字符串追加到目标字符串末尾strcat(目标字符串,源字符串);示例#includestdio.h#includestring.hintmain(void){chardestination[32]hello ;constcharsource[]world;strcat(destination,source);printf(%s\n,destination);return0;}输出hello world目标必须先是有效字符串strcat会先在目标数组中寻找\0再从该位置开始追加。因此目标数组必须已经包含有效字符串chardestination[32]{0};strcat(destination,hello);目标容量必须容纳拼接结果必须满足sizeof(destination) strlen(destination) strlen(source) 1十二、比较字符串strcmp字符串不能使用比较内容if(text1text2){/* 这里比较的不是两个字符串的全部字符 */}比较字符串内容应使用strcmpintresultstrcmp(text1,text2);strcmp的返回值有三类返回结果含义0两个字符串内容相等小于0第一个字符串排在第二个字符串之前大于0第一个字符串排在第二个字符串之后不要假设非零结果一定恰好是-1或1程序只应判断其符号。1. 基本示例#includestdio.h#includestring.hintmain(void){chartext1[]apple;chartext2[]banana;intresultstrcmp(text1,text2);return0;}2. 比较规则strcmp从左到右逐个比较字符对应字符相同就继续比较下一个字符遇到第一对不同字符时根据它们的无符号字符值决定结果如果一方先遇到\0较短的相同前缀字符串排在前面。十三、二维整型数组二维数组可以理解为“由一维数组构成的一维数组”。例如intmatrix[2][3];可以理解为matrix 是一个包含 2 个元素的数组 每个元素又是一个包含 3 个 int 的一维数组因此它包含2 x 3 6 个 int 元素十四、定义二维数组基本格式数据类型 数组名[行数][列数];例如intmatrix[2][3];表示 2 行、3 列。在固定长度二维数组中行数和列数通常使用可确定的正整数十五、访问二维数组元素基本形式数组名[行下标][列下标]对于intmatrix[2][3];有效元素为matrix[0][0] matrix[0][1] matrix[0][2] matrix[1][0] matrix[1][1] matrix[1][2]有效下标范围行下标0 到 行数 - 1 列下标0 到 列数 - 1赋值示例matrix[0][0]10;matrix[1][2]60;十六、二维数组的存储二维数组同样占用连续内存并按照逐行、从左到右的顺序存储这称为行优先顺序。对于intmatrix[2][3]{{1,2,3},{4,5,6}};内存中的逻辑顺序为matrix[0][0] matrix[0][1] matrix[0][2] matrix[1][0] matrix[1][1] matrix[1][2]也就是1 2 3 4 5 61. 总空间二维数组总大小元素类型大小 x 行数 x 列数2. 计算行数和列数在二维数组定义所在的作用域中rowssizeof(matrix)/sizeof(matrix[0]);columnssizeof(matrix[0])/sizeof(matrix[0][0]);这里matrix是整个二维数组matrix[0]是第一行这个一维数组matrix[0][0]是单个int元素。十七、遍历二维数组二维数组通常使用双层循环#includestdio.hintmain(void){intmatrix[2][3]{{1,2,3},{4,5,6}};rowssizeof(matrix)/sizeof(matrix[0]);columnssizeof(matrix[0])/sizeof(matrix[0][0]);for(row0;rowrows;row){for(column0;columncolumns;column){printf(%d ,matrix[row][column]);}putchar(\n);}return0;}输出1 2 3 4 5 6外层循环控制行内层循环控制当前行中的列。十八、二维数组的初始化1. 全部初始化可以按连续顺序提供全部元素intmatrix[2][3]{1,2,3,4,5,6};也可以按行分组结构更直观intmatrix[2][3]{{1,2,3},{4,5,6}};这两种写法得到相同内容。2. 部分初始化intmatrix[2][3]{1,2,3,4};按照行优先顺序填充剩余元素初始化为01 2 3 4 0 0按行分组intmatrix[2][3]{{1,2},{3}};结果1 2 0 3 0 03. 全部初始化为0intmatrix[2][3]{0};或者intmatrix[2][3]{{0}};所有元素都会初始化为0。4. 省略行数提供初始化列表时可以省略最外层的行数intmatrix[][3]{{1,2,3},{4,5,6}};编译器根据初始值推断为 2 行。也可以写成intmatrix[][3]{1,2,3,4};编译器需要容纳 4 个元素因此推断为 2 行结果为1 2 3 4 0 05. 列数不能省略普通二维数组初始化时最外层行数可以省略但列数不能省略intmatrix[][3]{1,2,3,4,5,6};/* 正确 */下面的写法错误intmatrix[2][]{1,2,3,4,5,6};编译器必须知道每一行包含多少个元素才能计算 在内存中的位置。十九、N 维数组的理解方式二维数组可以理解为由一维数组构成的一维数组二维数组 一维数组的数组同理N 维数组可以理解为由 N-1 维数组构成的一维数组三维数组 二维数组的数组 四维数组 三维数组的数组例如intdata[2][3][4];可以理解为最外层有 2 个元素每个元素是一个3 x 4的二维数组总元素个数为2 x 3 x 4 24。维数越多下标和循环越复杂。小结C 语言使用以\0结尾的字符数组表示字符串。字符数组容量必须包含末尾结束标志0和\0数值相同而0是普通可见字符。printf(%s)和puts可以输出字符串scanf使用%s时必须限制输入宽度。strlen统计字符串长度sizeof获取数组占用空间。strcpy用于复制字符串strcat用于拼接字符串strcmp用于比较字符串内容。使用这些函数前必须保证字符串有效并确保目标数组空间足够。二维数组可以理解为由一维数组组成的一维数组元素按照逐行、从左到右的顺序连续存储。初始化时可以全部赋值、部分赋值或省略最外层行数但列数不能省略。掌握字符数组和二维数组是后续学习的重要基础。