数组
在程序中如何保存一个人的年龄
创建一个基于int类型的变量即可,如:int age = 12;
在程序中保存一个人三门课的成绩
创建三个基于float类型的变量,如:float score1,score2,score3;
保存一个人15门课程的成绩
数组!
数组的概念
什么是数组
定义:数组是相同类型,有序数据的集合
数组的特征
- 数组中的数据被称之为数组的**元素**,(==所谓的元素,其实就是数组的每一个匿名的变量空间==),是同构
- 数组中的元素存放在内存空间(char player_name[6]:申请在内存中开辟6块连续的基于char型的变量空间)
衍生:下标(索引)
- 下标或者索引代表了数组中元素距离第1个元素(首地址所在的元素)的偏移量。如:第1个元素距离第1个元素的偏移量为0,所以数组中的下标是从0开始的
数组的下标是从0开始的
数组的最大下标 = 数组的元素个数(数组的大小或容量)- 1
int a:在内存中开辟1块空间,该空间的大小是4个字节。
int a1,a2,a3,a4,a5:在内存中开辟连续的5块空间,每一块空间的大小是4个字节。但是不如数组方便。
int arr[5]:在内存中开辟连续的5块空间,每一块空间的大小是4个字节。
一维数组
数组的定义
语法:
数据类型 数组名[数组容量];
注意:数据类型又被叫做类型说明符,数组容量又被称作数组元素个数或者数组的大小/长度
说明:
数组的数据类型由数组中的元素来决定。也就是说数据类型,由元素的类型来决定,元素是什么类型,数组就是什么类型。同一个数组中,所有元素的类型都是一致的。
数组名也是标识符,我们所说的数组(名),可以理解为数据类型是数组的变量(名)。命名规则与变量的规则一样,唯一的区别是变量使用单数,数组使用复数。也就是以字母或者下划线开头,后面只能跟字母、数字、下划线。
数组容量还可以叫做常量表达式。其值必须是整数。关于数组容量的类型:
C89标准:只支持常量和符号常量,不支持变量。
#define SIZE 5 // 符号常量,使用宏定义
int lenght = 5; // 变量
int arr1[5]; // 常量(字面量)正确
int arr2[SIZE]; // 符号常量,正确
int arr3[length];// 变量,C89错误
C99标准(大部分环境支持):引入变长数组(VLA)的概念,就是可以使用变量,数组在运行的时候决定大小。举例:
int length = 5; // length = 5; 创建一个变量length,赋值5
int arr[length]; // 创建一个容量为5的数组,C99标准下是正确的(执行这句的代码的时候,已经在内存申请空间)
length = 10; // 此时虽然变量length的值变成了10,但是已经创建的数组的大小是不会改变的。数组大小依然是5
类型:
代表了数组中元素的类型,数组的空间大小 = 数组中所有元素空间之和
容量:
数组中能存储多少个元素,数组容量一定是一个整型
深入理解:
①定义一个数组,相当于申请了一个可以容纳所指定元素个数的内存单元,所申请的内存单元是连续的
②定义一个数组,相当于定义了多个匿名的变量,这些变量可以通过数组名[下标]
来访问
范例:
//定义一个数组
int arr[10]; //定义一个存放10个int类型元素的数组
关于数组中元素默认值问题:
全局变量和static修饰的变量:元素的默认值是0
局部变量:元素的默认值是随机值,此时强烈建议用户初始化
#include int g_age; //全局变量,定义在函数的外面,默认值为0 int g_age[5]; //全局数组,等同于全局变量,元素默认值是0 int main() { int p_age; //局部变量,函数中定义的所有变量都可称作广义上局部的变量,使用前需要初始化 int p_age[5]; //局部作用域的数组,类似局部变量,元素的值需要初始化 return 0; }
注意:关于默认值,整型和浮点型的默认值是0,字符型的默认值是\0,\0对应的ASCII码为0
数组元素的访问
原则:
数组中的元素不能一次性访问所有,只能一个一个的访问
语法:
取值:
数组名[下标]
赋值
数组名[下标] = 值
举例:
//定义一个存储10个元素的int数组
int arr[10];
//给数组的第一个元素赋值
arr[0] = 88;
//访问数组的第一个元素
int a = arr[0];
//修改数组中第一个元素的值
arr[0] = 66; //使用66覆盖88
int c = arr[9]; //如果是一个局部作用域的数组,此时访问的元素的值是随机值,如果是全局作用域,值是0
int b = arr[10]; //error,报错,下标越界异常,因为访问了一个未知的存储空间
案例
需求:利用循环给数组元素a[0]~a[9]赋值0 ~ 9,并要求逆序输出。
代码
#include <stdio.h>
int main(int argc,char *argv[])
{
// 创建一个数组,用来存放0~9
int arr[10];
// 计算数组的大小:数组大小 = 数组所有元素的总字节数 / 每一个元素的字节数 需要使用到sizeof运算符
int len = sizeof(arr) / sizeof(arr[0]);
// 通过for循环给数组赋予0~9
for (int i = 0; i <= 9; i++) arr[i] = i;
// 逆序输出数组中的元素:数组最大下标 = 数组大小 - 1
// 使用for循环获取数组每一个元素称之为数组的遍历
for (int j = len -1; j >= 0; j--) printf("%4d", arr[j]);
printf("\n");
return 0;
}
数组的初始化
说明:所谓的初始化,就是定义数组的时候,用指定的数据给对应的元素赋值
读法:
数据类型 数组名[数组容量] = {...};
注意:
数组可以部分初始化:也就是可以给数组中的前几个元素初始化,未被初始化的元素系统将自动初始化,初始化值为0(也就是一个数组中,一旦有元素被初始化,剩余元素将自动初始化为0)
//数组的部分初始化 int arr[10] = {11,12,13,14,15}; //推荐写法,只初始化前5个元素,剩余元素系统默认为0,等价于下面写法 int arr[10] = {11,12,13,14,15,0,0,0}; char arr1[5] = {'a','b','c'}; //推荐写法,等价于下面写法 char arr1[5] = {'a','b','c','\0','\0'}; char arr1[5] = {'a','b','c',0,0}; //'\0'对应的ASCII码是0 char a = 'A' 等价于char a = 65 int arr2[5] = {}; //等价于下面三种 int arr2[5] = {0}; int arr2[5] = {0,0,0,0,0};
数组根据初始化的元素自动分配大小:如果定义数组时未指定数组容量,则系统会根据初始化的元素的个数来决定容量。
//由初始化的元素决定数组的容量 int arr[] = {11,12,13,14,15}; //推荐写法,等价于下面写法 int arr[5] = {11,12,13,14,15};
案例
案例1
需求:求斐波那契数列,限制在20个。
代码:
#include <stdio.h> int main(int argc,char *argv[]) { // 定义循环变量 int i; // 定义一个数组,用来存储20个数列 int f[20] = {1,1}; // 计算数组的大小 int len = sizeof(f) / sizeof(f[0]); // 通过一个for循环完成数列 for (i = 2; i < len; i++) f[i] = f[i-1] + f[i- 2]; // {1,1,2,3} // 遍历数组 for (i = 0; i < len; i++) { // 1行显示5个数 if (i > 0 && i % 5 == 0) printf("\n"); printf("%8d",f[i]); } printf("\n"); return 0; }
案例2
需求:从键盘输入年、月、日,计算并输出该日是该年多少天
分析
先创建一个数组,用来存放每一个月的天数,由于二月特殊,初始化时默认为平年的天数
从控制台输入年,月,日
闰年校验:如果是闰年,就修改数组中二月份对应的天数(平年:28天,闰年:29天)
定义一个变量,用来记录天数,默认就是我们输入的天数
将输入的月份之前的每一个月的天数取出来加到记录天数的变量中
将统计后的天数打印输出
代码
#include <stdio.h>
int main(int argc,char *argv[])
{
// 首先创建一个数组,用来存放每一个月的天数,因为二月特殊,初始化的时候,默认为平年天数。
int t[] ={31,28,31,30,31,30,31,31,30,31,30,31};
// 从控制台输入年,月,日
int year,month,day;
printf("请输入年份、月份、天(yyyy-MM-dd):");
scanf("%d-%d-%d", &year, &month, &day);
// 闰年校验:如果是闰年,就修改数组中二月份对应的天 数(平年:28天,闰年:29天)。
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) t[1] = 29;
// 定义一个变量,用来记录天数,默认就是我们输入的天数。
int sum = day;
// 遍历数组,将输入月份之前的每一个月的天数取出来加到记录天数的变量中。
for (int i = 0; i < month - 1; i++) sum += t[i];
// 将统计后的天数打印输出。
printf("%d月%d日是%d年的第%d天!\n", month, day, year, sum);
return 0;
}
《冒泡排序》
排序思想(向前冒泡)
一次只排好一个元素,针对n个元素,最差情况需要n-1次就能排好,最好情况需要0次(交换位置次数)
每次排序假定第一个元素时最大或者最小的,然后相邻的两个元素依次进行比较,遇到较大或者较小的元素进行交换,访问完最后一个元素,就排好了一个数
在余下的元素中,再次进行第二步,直到只剩下一个数
推理:
例如:将 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的值赋值给b, b = temp;
#include <stdio.h>
int main(int argc,char *argv[])
{
//创建数组存储输入的数字
int arr[10];
//创建变量
int i, j, temp, desc = 1;
printf("请输入10个整数:\n");
//计算数组大小
int len = sizeof(arr) / sizeof(arr[0]);
//接收并打印所有输入的数字
for(int i = 0;i <= 10;i++) scanf("%d",&arr[i]);
printf("\n排序前:");
for(i = 0;i <= 10; i++) printf("%-4d",arr[i]);
printf("\n");
//开始冒泡排序,外部循环,排序轮次 = 数组大小 - 1
for(i = 0;i < len-1;i++)
{
//设置一个信号位,排除数字已经排好序的情况
int flag = 0;
//内部循环,实现高低排序(交换位置),交换次数最大 = 数组大小 - 本轮轮次(第几轮排序) - 1
for(j = 0;j <= len - i -1;j++)
{
if(desc)
{
//降序排列
if(arr[j] < arr[j+1])
{
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = 1;
}
//升序排列
if(arr[j] > arr[j+1])
{
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = 1;
}
}
}
//如果排序提前完成,直接跳出循环,简化已经排好顺序数字的数组排序过程
if(!flag) break;
}
printf("\n排序后:");
//打印排序后的数字
for(i = 0;i <= 10;i++) printf("%-4d",arr[i]);
printf("\n");
return 0;
}
衍生:
冒泡排序 → 鸡尾酒排序、摇坠排序、摇床排序、搅拌排序...