内容提要
-
数组
-
排序算法:冒泡排序
-
二维数组
-
字符数组
-
数组
冒泡排序
-
排序思想(向前冒泡)
-
一次只排好一个数,针对n个数,最差情况需要n-1次就可以排好
-
每次排序假定第一个元素是最大或者最小,用第一个元素后面的元素一一与第一个元素比较,遇到较大或较小的和第一个元素交换,访问完数组的最后一个元素,就排好了一个数
-
在余下的数中,再次应用第2步的操作,直到只剩下1个数
-
-
动图演示:
-
推理:
例如:将
5,4,3,2,1
冒泡排序为1,2,3,4,5
排序演示:
-
第0轮:5,4,3,2,1 → 4,3,2,1,5 比较4次 = 数组长度5 - 轮数0 - 1
-
第1轮:4,3,2,1,5 → 3,2,1,4,5 比较3次 = 数组长度5 - 轮数1 - 1
-
第2轮:3,2,1,4,5 → 2,1,3,4,5 比较2次 = 数组长度5 - 轮数2 - 1
-
第3轮:2,1,3,4,5 → 1,2,3,4,5 比较1次 = 数组长度5 - 轮数3 - 1
总结:
-
上面案例涉及到5个数的排序,排序了4轮,得到:轮数 = 元素个数(数组长度) - 1,我们可以通过一个外层for循环实现轮数的遍历
-
案例涉及的每一轮中数列的排序次数,计算规则:次数 = 元素个数 - 轮数 - 1,我们可以通过一个内层for循环实现每一轮次数的遍历
-
每一次比较过程中,两个数涉及到位置交换,比如a = 3, b = 4,交换ab的数据变为a = 4, b = 3,应该如何实现:
-
引入一个临时变量temp,将a的值赋值给temp,int temp = a;
-
将b的值赋值给a,a = b;
-
将temp的值赋值给a,a = temp;
-
-
-
代码:
// 创建一个数组,用来存放排序用的数列int arr[10];// 定义循环变量和临时变量int i,j,temp;printf("请输入10个整数:\n");// 计算数组的长度int len = sizeof(arr) / sizeof(arr[0]); // 等价于 sizeof(arr) / sizeof(int);// 通过循环录入for (i = 0; i < len; i++) scanf("%d",&arr[i]);printf("\n");// 冒泡排序// 外层循环:实现轮数的遍历,轮数 = 数组长度 - 1for (i = 0; i < len - 1; i++){// 内层循环:实现每一轮的比较次数,比较次数 = 数组长度 - 轮数 - 1for (j = 0; j < len - i - 1; j++){// 相邻两个数比较后交换位置// if (arr[j] < arr[j+1]) // 此时,实现降序排列:从大到小if (arr[j] > arr[j+1]) // 此时,实现升序排列:从小到大{temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}}prinf("冒泡排序后的数列:\n");for (i = 0; i < len; i++) printf("%3d",arr[i]);printf("\n");
-
衍生:
冒泡排序 → 鸡尾酒排序 → 摇床排序
二维数组
定义
二维数组本质上是一个行列式的组合,也就是说二维数组由行和列两部分组成,属于多维数组。二维数组数据通过行列进行解读
二维数组可被视为一个特殊的一维数组,相当于二维数组又是一个特殊的一维数组,只不过它的元素是一维数组。(数组的元素的类型可以是数组类型)
语法
数据类型 数组名[行数][列数]
行数:外层数组的数组容量
列数:内层数组的数组容量
说明
-
二维数组在初始化的时候可以省略行数,系统会通过初始化后的数据自动推断行数
-
二维数组和一维数组一样,也可以部分初始化,未初始化的数据默认用
0
或者\0(\0对应的ASCII是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},{31}}; // 正确,等价于下面的写法int arr[3][3] = {{11,12,0},{21,22,0},{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}}; // 错误,这种写法,编译报错,不能省略列数
注意:在C语言中,二维数组在计算机的存储顺序是按行进行的,即第一维的(行)下标变化慢,第二维的(列)下标变化快
内存存储
应用场合
主要是应用于对行列有要求的情况。比如我们现在要存储西安粤嵌所有在班学生的成绩
还有就是字符数组的应用,比如用数组存储学生的姓名
特殊写法
-
下标可以是整型表达式,如:
a[2-1][2*2-1] → a[1][3]
-
下标可以是已经有值的变量或数组元素,如:
a[2*x-1][b[3][1]]
-
数组元素可以出现在表达式中,如:
b[1][2] = a[2][3]/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 | 字符型默认值-\0
int arr[3][4] = {{11},{21,22},{31}};
-
若对全部元素赋初值,自定义数组时可以省略第1维数组的长度,第2维数组的长度必须指明
int a[][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循环,外层循环控制行,内层循环控制列
-
取数据:
arr[行号][列号]
-
-
代码:
// 创建一个二维数组 int arr[][3] = {{11},{21,22},{31,32,33}};// 获取行数组容量和列数组容量 int row = sizeof(arr) / sizeof(arr[0]); int col = sizeof(arr[0]) / sizeof(arr[0][0]);// 遍历数组 // 外层循环控制行 for (int i = 0; i < row; i++) {// 内层循环控制列(也可以在这里计算列的大小)// int col = sizeof(arr[i]) / sizeof(arr[i][0]);for (int j = 0; j < col; j++){// 输出元素printf("%-3d",arr[i][j]);} } printf("\n");
案例2:
-
需求:矩阵的转置
-
分析:
-
所谓的转置,就是原本的列变行,行变列
-
-
代码:
#define ROW 2 #define COL 2// 定义循环变量 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"); 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");
运行结果:
课堂练习
-
需求:求一个3行3列的矩阵对角线上的元素之和
-
分析:
-
总结:等行等列的矩阵,转置前后,对角线上的数据相等
字符数组
在C语言中,支持常量字符串,不支持变量字符串,如果想要实现类似的变量字符串,C语言中提供了两种实现方式
-
字符数组
char name[] = "哪吒";
-
字符指针
char *name = "哪吒";
概念
元素类型为char字符型的数组,字符数组往往是用来存储字符串数据的.需要注意的是,我们C语言中的字符是字节字符
测试题:
char a = 'A'; // 正确 char b = '1'; // 正确 char c = 65; // 正确,这里的65是ASCII码,char的值有两种形式,一种是字符,一种是字符对应的ASCII码 char d = "A"; // 错误,char字符不能用双引号 char e = '王'; // 错误,因为一个中文汉字占两个字节 char f = "王"; // 正确,占三个字节,一个汉字+\n
语法:
// 一维数组 char 数组名[数组容量];// 二维数组 char 数组名[行容量][列容量];
字符数组的语法就是我们前面所学的一维数组和二维数组的语法,只不过数据类型是char而已
注意:
如果我们的char数组初始化的时候,没有完全初始化值的时候,使用'\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)
-
代码:
// 创建一个数组,用来存储 I LOVE YOU,ASCII中对应空格为:' ',其对应的ASCII值为 32 char arr[] = {'I',' ','L','O','V','E',32,'Y','O','U'}; // 计算数组的长度 int len = sizeof(arr) / sizeof(arr[0]);// 通过for循环遍历数组 for (int i = 0; i < len; i++) printf("%c",arr[i]);printf("\n");
案例2:
-
需求:输出一个用字符*组成的空菱形图案
-
代码:
// 创建一个二维数组,存放空菱形 char arr[5][5] = {{' ',' ','*',' ',' '},{' ','*',' ','*',' '},{'*',' ',' ',' ','*'},{' ','*',' ','*',' '},{' ',' ','*',' ',' '} }; // 计算行数和列数 int row = sizeof(arr) / sizeof(arr[0]); int col = sizeof(arr[0]) / sizeof(arr[0][0]); // 遍历数组 for (int i = 0; i < row; i++) {for (int j = 0; j < col; j++){printf("%c",arr[i][j]);}printf("\n"); // 当一行所有列数据输出完毕,需要换行 } printf("\n");
注意:
①如果定义时,不初始化,元素值不确定(针对定义在函数中的数组)
char arr1[2]; // 此时这个值是随机的,不固定的 char arr2[5] = {'a','b','c'}; // 此时处于不完全初始化,未初始化的元素使用\0进行填充②如果提供的字符个数大于数组长度,则按照语法错误处理(会报警告,但是能编译通过);如果字符个数小于数组长度,后面的元素自动补充\0
char arr1[2] = {'h','e','e'}; // 编译能通过,但是会报警告;不建议写 char arr2[3] = {'a'}; // 正确,未初始化的元素使用\0填充③如果提供的字符个数与数组长度相同,可以省略数组长度,系统会自动确定元素个数,适合字符较多时
char arr1[] == {'b','u'}; // 正确,根据初始化元素,由系统自动计算元素个数
字符串结束标志
说明
-
C语言规定,字符串以字符
\0
作为结束标志 -
编译系统对字符串常量自动加一个
\0
作为结束标志。比如"hello"
实际上的存储{'h','e','l','l','o','\0'}
-
程序中往往通过判断
\0
来检测字符串是否结束 -
\0
的ASCII码是0,不是一个可显示可输出的字符,是“空操作符”,它什么都不做,不会增加有效字符,仅仅用作一个工程判别的标志或者在字符数组中占位char a[] = {'h','i'}; // hi char a[] = {'h','i','\0'}; // hi char c[] = "hello"; // 实际的存储形式为 hello\0
字符数组的多样表示
我们的char数组可以以数组的形式一个一个输出每个字符;也可以以字符串的形式整体进行输出
案例:
// 字符串的第1种表示: char s1[] = {'h','e','l','l','o',' ','w','o','r','l','d','\0'}; // 字符串的第2种表示: char s2[] = {"hello world"}; // ""包裹的字符串自带\0 // 字符串的第3种表示: char s3[] = "hello world";// 字符串输出第1种方式: // 计算数组的长度 int len = sizeof(s3) / sizeof(s3[0]);for (int i = 0; i < len; i++) {// 过滤\0if (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");
注意:
-
字符串的长度与字符数组的长度不一定相同
char c[] = {'h','\0','i','\0'}; // c作为数组,长度为:4 // c作为字符串,长度为:1
-
利用字符串常量可以对字符数组进行初始化,但不能用字符串常量对字符数组赋值
// 正确演示:利用字符串常量给字符数组初始化 char arr1[6] = "hello";// 错误演示:用字符串常量给字符数组赋值 char arr2[6]; arr2 = "hello";
字符串的基础操作
在用格式化说明符%s进行输入输出时,其输入输出项均为数组名,但在输入时,相邻两个字符串之间要用空格分隔,系统将自动在字符串后加上\0
,在输出时,遇到结束符\0
作为输出结束标志
对于字符串的操作,我们需要使用到一些系统提供的API函数
字符串输入
scanf
语法:
scanf("%s",数组名);
注意:数组名对应的数组只能是char类型
案例:
// 创建一个字符数组,用来存储姓名 char name[20];printf("请输入您的名字:\n"); scanf("%s",name); // 数组本身没有空间,它的内存空间其实就是其元素空间,C语言规定数组名指向的就是首地址 printf("您的姓名是%s\n",name);
注意:采用scanf进行字符串输入,要求字符串中不能有空格,否则字符串遇到空格就会结束
fgets
语法:
fgets(数组名,数组容量,stdin);
功能:
从键盘录入一个字符串常量到字符数组,返回字符数组的地址(首地址,默认返回的地址,一般用12位16进制数表示)
说明:
采用fgets进行字符串输入,可获取所有输入的字符串,包含\n,在实际的字符串处理时,我们可能需要手动处理\n
案例:
// 创建一个字符数组,用来存储姓名 char name[20]; // 计算数组的大小 int len = sizeof(name) / sizeof(name[0]);printf("请输入您的名字:\n"); fgets(name,len,stdin); // 数组本身没有空间,它的内存空间其实就是其元素空间,C语言规定数组名指向的就是首地址printf("您的名字是%s\n",name);
注意:
①如果输入的字符串不包括空格或换行,可以使用scanf或者fgets
②如果输入的字符串包括空格或换行,只能使用fgets
gets 危险的
语法:
gets(数组名);
功能:
从键盘录入一个字符串常量到字符数组,返回字符数组的地址(首地址,默认返回的地址,一般用12位16进制数表示)
说明:
采用gets进行字符串输入,可获取所有输入的字符串,包含\n,在实际的字符串处理时,我们可能需要手动处理\n
案例:
// 创建一个字符数组,用来存储姓名 char name[20]; // 计算数组的大小 int len = sizeof(name) / sizeof(name[0]);printf("请输入您的名字:\n"); gets(name); // 数组本身没有空间,它的内存空间其实就是其元素空间,C语言规定数组名指向的就是首地址printf("您的名字是%s\n",name);
字符串输出
printf
语法:
printf("%s",数组名);
案例:
// 创建一个字符数组,用来存储姓名 char name[20];printf("请输入您的名字:\n"); scanf("%s",name); // 数组本身没有空间,它的内存空间其实就是其元素空间,C语言规定数组名指向的就是首地址printf("您的名字是%s\n",name);
fputs
语法:
fputs(数组名,stdout);
功能:
输出一个字符串
说明:
字符串可以包含转义字符(可以识别转义字符)
案例:
char arr[] = "hi lucy\teat\n";// 第1种输出 printf("%s\n",arr);// 第2种输出 fputs(arr,stdout);
puts
语法:
puts(数组名);
功能:
输出一个字符串
说明:
字符串可以包含转义字符(可以识别转义字符)
案例:
char name[20];printf("请输入您的名字:\n");// gets、fgets和scanf只能多选一 gets(name);// 输出 puts(name); // 标准的输出
字符串转数值【扩展】
-
strtol
long strtol(const char *str, char **endptr, int base);
将字符串转换为长整型数
参数说明:
-
str
:指向要转换的字符串的指针 -
endptr
:一个指向字符指针的指针。如果提供了这个参数,并且转换成功,endptr
将被设置为指向第一个为转换字符的指针。如果endptr
是NULL
,则不使用它 -
base
:用于指定转换的基数。它可以是 2 到 36 之间的值,或者是特殊值 0。如果base
是 0,则函数会根据字符串的前缀(如 "0x" 或 "0X" 表示十六进制,"0" 表示八进制,否则默认为十进制)来自动确定基数。
-
-
strtoul
unsigned long strtoul(const char *str, char **endptr, int base);
将字符串转换为无符号长整型数
-
strtod
double strtod(const char *str, char **endptr);
将字符串转换为双精度浮点数
-
atol
long atol(const char *str);
将字符串转换为长整型数(不推荐使用,建议使用
strtol
) -
atof
double atof(const char *str);
将字符串转换为双精度浮点数(不推荐使用,建议使用
strtod
)
案例:
printf("%lo,%ld,%lx\n",strtol("12",NULL,8),strtol("12",NULL,10),strtol("12",NULL,16)); printf("%lo,%ld,%lx\n",strtol("012",NULL,0),strtol("12",NULL,10),strtol("0x12",NULL,0));int a = 10;printf("%p,%lx\n",&a,&a);main() {int a=2,b=-1,c=2;if(a<b)if(b<0) c=0;else c+=1;printf("%d\n",c); }
关于单字符的输入:
scanf("%c",.....);
getchar();
关于单字符的输出:
printf("%c",变量);
putchar();