C语言
主要内容
- 数组
- 一维数组
- 数组的典型应用:冒泡排序
数组
数组的概念
什么是数组
数组是相同类型,有序数据的集合。

数组的特征
- 数组中的数据被称之为数组的元素(数组中的每一个匿名的变量空间),是同构的。
- 数组中的元素存放在内存空间键(char player_name[6]:申请在内存中开辟6块连续的基于char类型的变量空间)
衍生概念:下标(索引)
- 下标或者索引代表了数组中元素距离第一个元素的(首地址所在元素)偏移量。举例:第一个元素距离第一个元素的偏移量为0,所以数组的下标是从0开始。
- 数组中元素的地址值,下标越大,地址值越大。(每一块内存空间都有一个独有的内存地址,内存中每8个bit也就是1个字节编一个号,这个号就是我们所说内存地址)
- 数组的下标是从0开始。
一维数组
数组的定义
c
类型说明符或数据类型 数组名[数组容量]
-
语法:
-
数组的类型说明符由数组中的元素来决定,类型说明符也就是数据类型,元素是什么类型,数组就是什么类型。同一个数组中,所有元素的类型都是一致的。
-
数组名也是标识符,我们所说的数组(名),大家可以理解为数据类型是数组的变量( 名)。命名规则与变量名相同,遵循标识符命名规则(标识符命名规则:不能以数字开头,只能包含数字、字母、下划线)。
-
数组容量也可以叫常量表达式或者元素个数,其值必须为整型,可以包含常量和符号常量,但不能是变量。
cint size = 10; int arr[size];// 这里永远是10,因此此时数组的内存已将申请,此时数组的大小就是10,并不会因为后面对size重新赋值而改变 size = 22; printf("%d",size);// 22
- 举例:
c#define SIZE 4;// 符号常量 写法1,符号常量:int arr[SIZE]; 写法2,常量:int size = 4; int arr[size]; // 重新给size赋值,并不会影响到数组的大小 写法3,常量:int arr[4]; int lcd[800*480];
-
类型:
- 代表了数组中元素的类型
-
容量:
- 数组中能存储多少个元素,数组容量可以是一个常量、常量表达式、还可以是符号常量,但必须是整型。
-
深入理解:
- ①定义一个数组,相当于申请了一个可以容纳所指定元素数量的内存单元。所申请的内存单元是连续。
- ②定义一个数组,相当于定义了多个匿名的变量,这些变量可以通过
数组名[下标]
来访问。
-
范例:
c// 定义一个数组 int arr[10];// 此时只是在内存中申请了10个元素所对应的空间,此时里面的值是随机值,大概率是0 // 上面数组中,最小下标是0,最大下标是9
-
经过上面的案例,分析得到:
c数组的最大下标 = 数组元素个数(数组容量)- 1;
-
数组元素的访问
-
原则:数组中的元素不能一次性访问所有,只能一个一个的访问。
-
访问方式:
c数组名[下标];
-
举例:
c// 定义一个容纳10个元素的int数组 int arr[10]; // 给数组的第一个元素进行赋值(存数据) arr[0] = 89; // 访问数组中的第一个元素(取数据) int a = arr[0]; int c = arr[9]; // 0 int b = arr[10];// error,报下标越界异常,所以使用数组的过程中,一定要进行下标越界校验,否则报错
- 注意:数组元素的访问一定不能越界
-
案例:
-
需求:利用循环给数组元素a[0]~a[9]赋值 0~9 ,并且逆序输出。
-
代码:
c#include <stdio.h> int main(int argc,char *argv[]) { // 创建一个数组,用来存放0~9的数字 int a[10]; // 使用for循环给数组元素赋值(一般数组配套的都是for循环) // C语言中,没有提供数组的大小,需要我们自己计算,数组大小 = 数组总字节数 / 1个元素的字节数 int len = sizeof(a) / sizeof(a[0]); // 等价与 int len = sizeof(a) / sizeof(int) for(int i = 0;i < len; i++) { a[i] = i; } // 逆序输出 // 遍历:通过循环将数组中的元素一个一个取出来 for(int j = len -1; j>= 0; j--) { printf("%4d",a[j]); } printf("\n");// 纯粹换行 return 0; }
-
数组初始化
-
定义数组的同时,用指定数据来给对应的元素赋值。
-
简化数组定义后,需要对元素一一赋值操作。
-
语法规则:
c数据类型 数组名[数组容量] = {常量1,常量2,常量3...};
-
注意事项:
-
数组可以部分初始化,也就是可以给数组中前几个元素初始化,未被初始化的元素系统将自动初始化,大概率是0;如果定义数组时未指定数据容量,则系统会根据初始化元素的个数来决定数组容量。
c// 1. 如果定义数组时只给数组前几个初始化,后续剩余元素会自动完成初始化,大概率赋0 int arr[10] = {11,12,13,14,15}; // 推荐写法,等价于下面写法 int arr[10] = {11,12,13,14,15,0,0,0,0,0}; // 2. 如果定义数组时,未指定数据容量,系统根据初始化元素个数来决定容量 int arr[] = {11,12,13,14,15}; // 推荐写法,等价于下面写法 int arr[5] = {11,12,13,14,15};
-
柔性数组
- 专业理解:柔性数组的概念是在C99标准,针对结构体的最后一个成员可以是一个未指定大小的数组;
- 广义理解:数组容量待定或者待确定的数组,举例:
int arr[] = {1,2,3,4,5}
-
扩展
-
在不知道数组类型的情况下,如何确定数组元素的个数
cint length = sizeof(arr) / sizeof(arr[0])
-
说明:
①arr就是我们计算的数组本身,
sizeof(arr)
用来计算该数组中总的字节大小。②
sizeof(arr[0])
用来计算数组中一个元素所占的字节大小,因为数组中的元素类型相同,所以计算哪一个都行。③
sizeof(arr)/sizeof(arr[0])
就是用数组中总的字节数除以每一个元素所占的字节数,从而得到元素的个数。
-
一维数组的案例
案例1:
-
需求:斐波拉契数列
-
分析:1+1+2+3+5+...就是斐波拉契数列
-
代码:
c#include <stdio.h> int main(int argc,char *argv[]) { int i;// 循环变量 // 定义一个数组,用来存储数列,默认存储的第1和第2的值是1 int f[20] = {1,1}; // 计算数组的大小 int len = sizeof(f) / sizeof(f[0]); // 使用for循环,将生成的数据存入数组 for(i = 2;i < len; i++)// i=3 { f[i] = f[i-2]+f[i-1];// 给数组元素赋值,从数组的第3个元素开始 1,1,2,3,5.. } // 遍历数组 for(i = 0; i < len; i++) { // 遍历的时候,要求每5个换一行,也就是1行显示5个 if(i % 5 == 0) { printf("\n"); } printf("%8d",f[i]); } printf("\n"); return 0; }
案例2:
-
需求:从键盘输入年、月、日,计算并输出该日是该年的第几天
-
分析:
- 首先创建一个数组,用来存放每一个月的天数,因为二月比较特殊,初始化的时候设置为0,int t[] = {31,0,31...}
- 从控制台输入年、月、日
- 闰年判断,修改数组中二月对应的天数(闰年:29,平年:28)
- 定义一个变量,用来记录天数,默认值是我们输入的天数:int sum = 14
- 将当前月之前所有的天数,从数组中取出来进行相加 sum += t[0]+t[1]...
- 最后打印输出sum
-
代码:
c#include <stdio.h> int main(int argc,char *argv[]) { // 定义一个数组,用来存放1~12月的天数,二月比较特殊,暂时不初始化 int t[] = {31,0,31,30,31,30,31,31,30,31,30,31}; // 定义三个变量,用来接收控制台输入的年,月,日 int year,month,day; printf("请输入年份、月份、天:\n"); scanf("%d-%d-%d",&year,&month,&day); // 润年的判断,给数组中的二月份赋值天数 if((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)) t[1] = 29;// 润年 else t[1] = 28;// 平年 // 创建一个变量,用来记录当前是第几天,默认位当前输入的天数 int sum = day; // 将对应月份之前的所有月份天数加起来 for(int k = 0; k < month - 1; k++) { sum += t[k]; // 叠加前几个月的天数 } printf("%d月%d日是%d年第%d天。\n",month,day,year,sum); return 0; }
数组的典型应用:冒泡排序
向后冒泡
-
思想:
- ①一次只排好一个数,针对n个数,最差情况需要n-1次就可以排好
- ②每次排序将相邻数据两两比较,将较大或者较小的数据向后交换,等所有数据比较完成,较大或者较小的数就会出现在最后,这也是该数应该有的位置。
- ③在余下的数中,再次应用第②步的操作,直到只剩下1个数。
-
分析:
原始数列:1 2 3 4 5 → 5 4 3 2 1
排序轮数: 5 1 2 3 4、 5 4 1 2 3、 5 4 3 1 2、 5 4 3 2 1 得到:
轮数 = 元素个数 - 1 = 4
比较次数:
① 1 2 3 4 5 → 5 1 2 3 4(第1轮比4次)5 -1 -0= 4
② 1 2 3 4 5 → 5 4 1 2 3(第2轮比3次)5 - 1 -1 = 3
③ 1 2 3 4 5 → 5 4 3 1 2 (第3轮比2次)5 -1 -2 = 2
④ 1 2 3 4 5 → 5 4 3 2 1 (第4轮比1次)5 - 1 -3 = 1
得到每一轮的比较次数:
每一轮比较次数 = 元素个数 - 1 - 轮数
,轮数从0开始 -
代码:
c#include <stdio.h> int main(int argc,char *argv[]) { // 创建一个数组,用来存储排序的序列 int arr[10]; // 定义三个变量 i:比较的轮数(0~len-1)j:每一轮比较的次数(0~len-1-i)temp:临时变量,用来实现两个变量值的交换 int i,j,temp; printf("请输入10个整数:\n"); // 计算数组的大小 int len = sizeof(arr) / sizeof(arr[0]); // 通过循环录入数据 for(i = 0; i < len; i++) { scanf("%d",&arr[i]); } printf("\n"); // 冒泡排序 // 第一次循环:控制比较的轮数:轮数 = len -1; for(i = 0; i < len - 1; i++) { // 第二层循环:控制每一轮的比较次数:次数 = len - 1 - i for(j = 0; j < len - 1 - i; j++) { // 相邻两个数进行比较,满足条件交换位置 if(arr[j] > arr[j+1]) // 1 2 { temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } printf("冒泡排序后的数列:\n"); for(i = 0; i < len; i++) { printf("%4d",arr[i]); } printf("\n"); return 0; }
向前冒泡
- 思想:
- ①一次只排好一个数,针对n个数,最差情况需要n-1次就可以排好
- ②每次排序假定第一个元素是最大或者最小的,用第一个元素的后面的元素一一与第一个元素比较, 遇到较大或者较小的和第一个元素交换,访问完数组的最后一个元素,就排好了一个数。
- ③在余下的数中,再次应用第②步的操作,直到只剩下1个数。