C语言第 9 天学习笔记:数组(二维数组与字符数组)
C语言第09天学习笔记:数组(二维数组与字符数组)
内容提要
- 数组
- 二维数组
- 字符数组
二维数组
定义
二维数组本质上是一个行列式组合,由行和列两部分组成,属于多维数组,通过行和列解读(先行后列)。
二维数组可被视为一个特殊的一维数组,即当一个数组中的每一个元素是一维数组的时候,这个数组就是二维数组。
语法
数据类型 数组名[行容量][列容量];
- 行容量:外层数组的数组容量
- 列容量:内层数组的数组容量
说明
- 二维数组在初始化的时候,可以省略行数,系统会通过初始化后的数据自动推断行数。
- 二维数组和一维数组一样,也可以部分初始化,未初始化的元素使用0。
- 二维数组在初始化的时候,不能省略列数,否则编译报错。
举例
int arr[3][3] = {{11,12,13},{21,22,23},{31,32,33}}; // 正确int arr[][3] = {{11,12,13},{21,22,23},{31,32,33}}; // 正确,可省略行容量,推荐int arr[3][3] = {{11,12},{21,22,23},{31}}; // 正确,未初始化部分补0int arr[3][3] = {{11,12,0},{21,22,23},{31,0,0}}; // 正确,支持部分初始化int arr[3][3] = {0}; // 正确,所有位置使用0补齐,推荐int arr[3][3] = {}; // 正确,所有位置使用0补齐int arr[3][3] = {11}; // 正确,除了0行0列是11外,其他都用0补齐int arr[][] = {{11,12,13},{21,22,23},{31,32,33}}; // 错误,不能省略列容量int arr[3][] = {{11,12,13},{21,22,23},{31,32,33}}; // 错误,不能省略列容量int arr[][3] = {11,12,13,21,22,23,31,32,33}; // 正确,{}中不一定嵌套int arr[][3] = {11,12,13,21}; // 正确,{}中不一定嵌套
内存存储
在C语言中,二维数组在计算机的存储顺序是按行进行的,即第一维(行)下标变化慢,第二维(列)下标变化快。
例如:
注意:地址这里只是为了区分,实际的地址表示为十六进制。
应用场合
主要应用于对行列有要求的情况,例如:
double scores[35] = {..};
:一维数组初始化,存放1个班所有学生的成绩double scores[5][40] = {{..}..}
:二维数组初始化,存放5个班的学生成绩,每个班最多40人double scores[6][10][40] = {{{..}..}..}
:三维数组初始化,存放6个校区、每校区最多10个班,每班最多40人
特殊写法
-
下标可以是整型表达式,如
a[2-1][2*2-1]
等价于a[1][3]
-
下标可以是已经有值的变量或者数组元素,如
a[2*x-1][b[3][1]]
,[]
中最终需要的是一个大于0的整数 -
数组元素可以出现在表达式中,如
b[1][2] = a[2][3]/2
-
演示:
数组:arr 列-0 列-1 列-2 举例 说明 行-0 11 12 13 arr[0][1]
数组arr的0行1列对应的元素 行-1 21 22 23 arr[1][2]
数组arr的1行2列对应的元素
注意:使用数组元素的下标应在已定义数组的大小范围内;应注意区别定义数组大小和引用数组元素的区别。
初始化
- 分行给二维数组赋初值
int arr[3][4] = {{11,12,13,14},{21,22,23,24},{31,32,33,34}};
- 可将所有数据写在一个
{}
内,按照排列顺序赋值int arr[3][4] = {11,12,13,14,21,22,23,24,31,32,33,34};
- 可对部分元素赋初值,其余未初始化部分自动填充0
int arr[3][4] = {{11},{21,22},{31,32,33}};
- 若对全部元素赋初值,自定义数组时可以省略第一维数组容量(行容量),第二维数组容量(列容量)必须指明
int arr[][4] = {{11,12,13,14},{21,22,23,24},{31,32,33,34}};int arr[][4] = {11,12,13,14,21,22,23,24,31,32,33,34};
- 在分行赋初值时,也可以省略第1维的长度(行容量)
int arr[][4] = {{11,12,13},{0},{0,10}};
案例
案例1:二维数组的遍历
分析:
- 二维数组本质上属于行列式,遍历的时候需要借助于嵌套的for循环,外层for负责行的遍历,内层for负责列的遍历。
- 行和列的大小计算:
// 计算行的大小int row_length = sizeof(数组名) / sizeof(数组名[行下标0]);// 计算列的大小(每一行的列数是相同)int col_length = sizeof(数组名[行下标0]) / sizeof(数组名[行下标0][列下标0]);```代码:```c#include int main(int argc,char *argv[]){ // 创建一个二维数组 int arr[][3] = {{11},{21,22},{31,32,33}}; // 获取行和列的大小 int row_len = sizeof(arr) / sizeof(arr[0]);// 外层数组大小 int col_len = sizeof(arr[0]) / sizeof(arr[0][0]);// 内层数组大小 // 遍历数组 // 外层循环:遍历行 for (int i = 0; i < row_len; i++) { // 内层循环:遍历列 for (int j = 0; j < col_len; j++) { // 输出元素 printf(\"%-4d\", arr[i][j]); } printf(\"\\n\"); } return 0;}
运行结果:
11 0 021 22 031 32 33
案例2:矩阵的转置
分析:所谓的转置,就是原本的列变行,行变列。
例如,2行3列矩阵转置为3行2列矩阵:
转置后:
代码:
#include #define ROW 2#define COL 3int main(int argc,char *argv[]){ // 定义循环变量 int i,j; // 准备2个数组,用来存放转置前后的数列 int arr_before[ROW][COL] = {11,12,13,21,22,23}; int arr_after[COL][ROW] = {0}; // 计算数组的大小 int arr_before_row = sizeof(arr_before) / sizeof(arr_before[0]); int arr_before_col = sizeof(arr_before[0]) / sizeof(arr_before[0][0]); int arr_after_row = sizeof(arr_after) / sizeof(arr_after[0]); int arr_after_col = sizeof(arr_after[0]) / sizeof(arr_after[0][0]); // 循环遍历二维数组 printf(\"\\n转置前:\\n\"); for (i = 0; i < arr_before_row; i++) { for (j = 0; j < arr_before_col; j++) { // 打印转置前的数据 printf(\"%-4d\",arr_before[i][j]); // 转置:行变列,列变行 arr_after[j][i] = arr_before[i][j]; } printf(\"\\n\"); } printf(\"\\n\"); printf(\"转置后:\\n\"); for (i = 0; i < arr_after_row; i++) { for (j = 0; j < arr_after_col; j++) { printf(\"%-4d\",arr_after[i][j]); } printf(\"\\n\"); } printf(\"\\n\"); return 0;}
字符数组
概念
元素类型为char
(字符型)的数组叫做字符数组,往往用来存储字符串数据。C语言中的字符是字节字符(1字符=1字节,1char=8bit)。
硬件中存放数据以bit(位)为单位,系统对于内存的操作以char(字节)为单位,系统为内存以1个字节为单位进行编号。
实验:
char a = \'A\'; // 正确char b = \'1\'; // 正确,ASCII码:49char c = 1; // 正确,ASCII码:1char d = \'\\n\'; // 正确,只要其对应的ASCII码的范围在0~127之间,都属于字符char e = \"A\"; // 错误,双引号括起来的内容叫做字符串常量char f = \'冯\'; // 错误,中文字符不在0~127这个范围内
语法
- 一维数组:
char 数组名[数组容量];
- 二维数组:
char 数组名[行容量][列容量];
字符数组的语法与之前所学的一维数组和二维数组的语法类似,只不过数据类型是char
。
注意:如果char数组初始化的时候,没有完全初始化值,未初始化部分使用\\0
(\\0
对应的ASCII值是0)进行填充,\\0
无法通过printf
等打印输出到控制台。
例如:
char c[8] = {\'h\',\'e\',\'l\',\'l\',\'o\'}; // 等价于 char c[8] = {\'h\',\'e\',\'l\',\'l\',\'o\',\'\\0\',\'\\0\',\'\\0\'};
案例
案例1:输出一个字符序列(I LOVE YOU)
代码:
#include int main(int argc,char *argv[]){ // 创建一个数组,用来存储I LOVE YOU,空格\' \'也是字符,对应的ASCII为32 char arr[] = {\'I\',\' \',\'L\',\'O\',\'V\',\'E\',32,\'Y\',\'O\',\'U\'}; // 计算数组的大小 int len = sizeof(arr) / sizeof(arr[0]); // 遍历数组 for (int i = 0; i < len; i ++) printf(\"%c\",arr[i]); printf(\"\\n\"); return 0;}
案例2:输出一个用字符*
组成的空菱形图案
代码:
#include int main(int argc,char *argv[]){ // 创建一个二维数组,存放菱形 char arr[5][5] = { {\' \', \' \', \'*\', \' \', \' \'}, {\' \', \'*\', \' \', \'*\', \' \'}, {\'*\', \' \', \' \', \' \', \'*\'}, {\' \', \'*\', \' \', \'*\', \' \'}, {\' \', \' \', \'*\', \' \', \' \'} }; // 计算行和列的大小 int row_len = sizeof(arr) / sizeof(arr[0]); int col_len = sizeof(arr[0]) / sizeof(arr[0][0]); // 遍历数组 for (int i = 0; i < row_len; i++) { for (int j = 0; j < col_len; j++) printf(\"%c\",arr[i][j]); printf(\"\\n\"); } printf(\"\\n\"); return 0;}
注意
- 如果定义时,不初始化,元素值不确定(局部作用域)
char arr1[2]; // 此时,这个数组中元素的值是随机值char arr2[3] = {\'a\',\'b\',\'c\'}; // 完全初始化char arr3[3] = {}; // 此时所有的元素使用 \\0 填充char arr4[3] = {0}; // 此时所有的元素使用 \\0 填充
- 如果提供的字符个数大于数组长度,则按照语法错误处理(会报警告,但是能编译通过);如果字符个数小于数组长度,后面的元素自动补
\\0
char arr1[2] = {\'h\',\'e\',\'e\'}; // 编译通过,但是会报警告(warning),不建议写,实际存放的是hechar arr2[3] = {\'a\'}; // 正确写法,部分初始化,未初始化部分补 \\0
- 如果提供的字符个数与数组长度相同,可以省略数组长度,系统会自动确定元素的个数,适合字符较多时
char arr1[] = {\'b\',\'u\'}; // 正确,根据初始化元素,由系统自动计算元素个数
字符串结束标志
- C语言规定,字符串常量以字符
\\0
作为结束标志。 - 编译系统对字符串常量自动加一个
\\0
作为结束标志,例如char *name = \"tom\"
,实际存储为{\'t\',\'o\',\'m\',\'\\0\'}
。 - 程序中往往通过判断
\\0
来检测字符串是否结束,例如while(arr[i] != \'\\0\') {..}
。 \\0
的ASCII码是0,不是一个可显示可输出的字符,是“空操作符”,仅仅用作一个工程判别的标志或者在数组中占位。
例如:
char a[] = {\'h\',\'i\'}; // 输出:hichar a[] = {\'h\',\'i\',\'\\0\'}; // 输出:hichar c[] = \"hello\"; // 输出:hello,实际存储:hello\\0,将字符串常量赋值给字符数组
字符数组的多样表示
char数组可以以数组的形式一个一个输出每个字符,也可以以字符串的形式整体输出。
演示:
#include int main(int argc,char *argv[]){ // 字符串的第1种表示: char s1[] = {\'h\',\'e\',\'l\',\'l\',\'o\',\'w\',\'o\',\'r\',\'l\',\'d\',\'\\0\'};// 字符个数:12 // 字符串的第2种表示: char s2[] = {\"hello world\"};// \"\"包裹的内容是字符串常量,字符串常量默认末尾有一个\\0,字符个数:12 // 字符串的第3种表示: char s3[] = \"hello world\"; // 字符个数:12 // 字符串的第1种输出: // 计算字符串所占字节数 printf(\"s1=%lu,s2=%lu,s3=%lu\\n\", sizeof(s1), sizeof(s2), sizeof(s3)); // s1=12,s2=12,s3=12 // 计算数组大小 int len = sizeof(s3) / sizeof(s3[0]); // 遍历 for (int i = 0; i < len; i++) { // 过滤\\0 if (s1[i] == 0 || s2[i] == \'\\0\' || s3[i] == 0) continue; printf(\"%c,%c,%c\\n\", s1[i], s2[i], s3[i]); } printf(\"\\n\"); // 字符串的第2种输出: printf(\"%s,%s,%s\\n\",s1,s2,s3); printf(\"\\n\"); return 0;}
注意
- 字符串的长度与字符数组的长度不一定相同。
char name[] = \"hello\"; // 数组长度:6,字符串长度:5
- 利用字符串常量可以对字符数组进行初始化,但不能用字符串常量对字符数组赋值。
// 正确演示:利用字符串常量给字符数组初始化char arr1[] = \"hello\";// 错误演示:利用字符串常量给字符数组赋值char arr2[6];arr2 = \"hello\"; // 错误,数组名是常量,不能被赋值
一维数组练习题
- 键盘录入一组数列,利用冒泡排序将数据由大到小排序
- 从键盘输入年、月、日,计算并输出该日是该年第几天
- 键盘录入一组数列,求最大数、最小数、均值
- 从键盘录入一组数列,判断是否是回文,举例:12321,abba,121
- 用数组存储10个整型数,通过键盘输入一个数,找出该数在数组中的下标值
- 通过键盘输入 10 个学员成绩:
1)输出不及格学员的成绩和下标。
2)求最高分的下标值
3)求最低成绩的下标值
4)求总成绩及平均成绩
二维数组练习题
- 一个二维数组赋了初值,用户输入一个数,在该二维数组中查找。找到则返回行列位置,没找到则提示。
- 二维整型数组,求所有元素平均值,求每行最大值,求每列最小值。
- 在行列相等数组计算主对角线元素的和
- 计算一个矩阵下三角元素的和
- 电影院为了答谢影迷的支持,在某一排的某一列座位上放置了一个大礼包,放置礼物的位置具有这样的规则(行和列的平方和为开店日期 512(5月12日)); 请设计程序找出大礼包的位置,(假定电影院有20排,每排25个座位)
字符数组练习题
- 编写一个程序,读取用户输入的字符串,并将其反转输出。
- 编写一个程序,判断用户输入的字符串是否为回文(即正反读都一样的字符串)。
思考题【选做】
- 求出一个矩阵的鞍点。鞍点的含义为行上最大同时列上也最大。
注::后续笔记将围绕C语言知识展开,建议每日实操时长不少于3小时