#数组的基本概念
##1.数组的基本概念
- 数组,从字面上看,就是一组数据的意思,是用来存储一组数据的
- 在C语言中,数组属于构造数据类型。一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。
>+ 注意:只能存放一种类型的数据
- 数组的几个名词
+数组:一组具有相同数据类型的数据的有序的集合
+数组元素:构成数组的数据。数组中的每一个数组元素具有相同的名称,不同的下标,可以作为单个变量使用,所以也称为下标变量。
+数组的下标:是数组元素的位置的一个索引或指示。(从0开始)
+数组的维数:数组元素下标的个数。根据数组的维数可以将数组分为一维、二维、三维、多维数组。
- 数组的应用场景
+一个int类型的变量能保存一个人的年龄,如果想保存整个班的年龄呢?
* 第一种方法是定义很多个int类型的变量来存储
* 第二种方法是只需要定义一个int类型的数组来存储
##2.数组的分类
- 按存储的内容分类
+数值数组:用来存储数值得
+字符数组:用来存储字符‘a’
+指针数组:用来存放指针(地址)的
+结构数组:用来存放一个结构体类型的数据
- 按维度分类
+一维数组
+二维数组
+ 多维数组
# 数组的定义、初始化、使用
##1.定义数组
- 元素类型 数组名[元素个数];
- 示例:
int ages[10];
##2.初始化数组
- 一般会在数组定义的同时进行初始化
+其中在{ }中的各数据值即为各元素的初值,各值之间用逗号间隔
int ages[3] = {4, 6, 9};
- 指定数组的元素个数,对数组进行部分显式初始化
+定义的同时对数组进行初始化,没有显式初始化的元素,那么系统会自动将其初始化为0
int nums[10] = {1,2};
- 不指定元素个数,定义的同时初始化,它是根据大括号中的元素的个数来确定数组的元素个数
int nums[] = {1,2,3,5,6};
- 指定元素个数,同时给指定元素进行初始化
int nums[5] = {[4] = 3,[1] = 2};
- 先定义,后初始化
int nums[3];
nums[0] = 1;
nums[1] = 2;
nums[2] = 3;
- 没有初始化会怎样?
+如果定义数组后,没有初始化,数
组中是有值的,是随机的垃圾数,所以如
果想要正确使用数组应该要进行初始化。
int nums[5];
printf("%d\n", nums[0]);
printf("%d\n", nums[1]);
printf("%d\n", nums[2]);
printf("%d\n", nums[3]);
printf("%d\n", nums[4]);
输出结果:
0
0
1606416312
0
1606416414
>+ 注意:对于数组来说,一旦有元素被初始化,其他元素都被赋值0
##3.数组的使用
- 通过下标(索引)访问:
ages[0]=10;
int a = ages[2];
printf("a = %d", a);
# 数组注意事项
##1.数组注意事项
- 在定义数组的时候[]里面只能写整型常量或者是返回整型常量的表达式
int ages4['A'] = {19, 22, 33};
printf("ages4[0] = %d\n", ages4[0])
intages5[5 + 5] = {19, 22, 33};
printf("ages5[0] = %d\n", ages5[0]);
intages5['A' + 5] = {19, 22, 33};
printf("ages5[0] = %d\n", ages5[0]);
- 错误写法
// 没有指定元素个数,错误
int a[];
[]中不能放变量
int number = 10;
int ages7[number]; // 不报错, 但是没有初始化, 里面是随机值
printf("%d\n", ages7[4]);
int number = 10;
int ages7[number] = {19, 22, 33} // 直接报错
int ages8[5];
// 只能在定义数组的时候进行一次性(全部赋值)的初始化
int ages10[5];
ages10 = {19, 22, 33};
// 一个长度为n的数组,最大下标为n-1, 下标范围:0~n-1
int ages11[4] = {19, 22, 33}
ages[8]; // 数组角标越界
# 数组遍历
##1.数组的遍历
- 数组的遍历:遍历的意思就是有序地查看数组的每一个元素
- 示例
int ages[4] = {19, 22, 33, 13};
for (int i = 0; i < 4; i++) {
printf("ages[%d] = %d\n", i, ages[i]);
}
##2.数组长度计算方法
- 因为数组在内存中占用的字节数取决于其存储的数据类型和数据的个数
数组在内存中占用的总字节数:sizeof(数组名);
- 数组所占用存储空间 = 一个元素所占用存储空间 *元素个数(数组长度)
- 所以计算数组长度可以使用如下方法
数组的长度 = 数组占用的总字节数 / 数组元素占用的字节数
int ages[4] = {19, 22, 33, 13};
int length = sizeof(ages)/sizeof(int);
printf("length = %d", length);
输出结果: 4
##3.练习
- 正序输出(遍历)数组
int ages[4] = {19, 22, 33, 13};
for (int i = 0; i < 4; i++) {
printf("ages[%d] = %d\n", i, ages[i]);
}
- 逆序输出(遍历)数组
int ages[4] = {19, 22, 33, 13};
for (int i = 3; i >=0; i--) {
printf("ages[%d] = %d\n", i, ages[i]);
}
- 从键盘输入数组长度,构建一个数组,然后再通过for循环从键 盘接收数字给数组初始化。并使用for循环输出查看
# 数组的内存分配
##1.数组内部存储细节
- 存储方式:
+1)计算机会给数组分配一块连续的存储空间
+2)数组名代表数组的首地址,从首地址位置,依次存入数组的第1个、第2个....、第n个元素
+3)每个元素占用相同的字节数(取决于数组类型)
+4)并且数组中元素之间的地址是连续。
- 示例
模拟该数组的内存存储细节如下: int x[2]={1,2};
int ca[5]={'a','A','B','C','D'};
>+ 注意:字符在内存中是以对应ASCII值的二进制形式存储的,而非上表的形式。在这个例子中,数组x的地址为它的首元素的地址0x08,数组ca的地址为0x03。
##2.数组的地址
- 在内存中,内存从大到小进行寻址,为数组分配了存储空间后,数组的元素自然的从上往下排列 存储,整个数组的地址为首元素的地址。
![](./images/sznc.png)
+数组a的地址是ffc1,a[0]的地址是ffc1,a[1]的地址是ffc5
+因此a == &a[0],即第一个元素的地址就是整个数组的地址
##3.数组的越界问题
- 数组越界导致的问题
+约错对象
+程序崩溃
char cs1[2] = {1, 2};
char cs2[3] = {3, 4, 5};
cs2[3] = 88; // 注意:这句访问到了不属于cs1的内存
printf("cs1[0] = %d\n", cs1[0] );
输出结果: 88
# 数组练习
##1. 从键盘录入当天出售BTC的价格并计算出售的BTC的总价和平均价(比如说一天出售了10个比特币)
// 1.定义数组用于保存所有BTC的价格
int prices[3];
// 2.动态计算数组长度
int length = sizeof(prices)/ sizeof(prices[0]);
int sum = 0;// 保存总价
// 3.通过循环录入价格
for (int i = 0; i < length; i++) {
printf("请输入第%d个BTC的价格\n", i + 1);
scanf("%d", &prices[i]);
sum = sum + prices[i];
}
// int sum = 0;
// for (int i = 0; i < length; i++) {
// sum = sum + prices[i];
// }
// 4.计算平均价
int average = sum / length;
printf("sum = %d, average = %d\n", sum, average);
##1.设计一个函数intarrayMax(int a[], int count)找出数组元素的最大值
int getMax(int ages[], int length)
{
// 注意:不要假设数组以外的值位最大值,会出现意想不到的问题
// int max = 0;
// 假设数组中的第0个元素是最大
int max = ages[0];
for (int i = 0; i < length; i++) {
// 判断从数组中取出的值是否大于max
if (max < ages[i]) {
// 如果大于max就把当前索引对应的元素设置成最大值
max = ages[i];
}
}
return max;
}
int getMax(int ages[], int length)
{
// 把数组中的第0个索引作为最大值
int max = 0;// 是一个索引
for (int i = 1; i < length; i++) {
if (ages[max] < ages[i]) {
max = i;
}
}
return ages[max];
}
##2.从键盘输入3个0~9的数字,然后输出0~9中哪些数字没有出现过
例如: 输入:1,3,5
输出:0,2,4,6,7,8,9
// 1.定义变量保存用户输入的值
int num1;
int num2;
int num3;
// 2.接收用户输入的值
printf("请输入3个整数,用逗号隔开\n");
scanf("%d,%d,%d", &num1, &num2, &num3);
for (int i = 0; i < 10 ; i++) {
if ((num1 != i) &&
(num2 != i) &&
(num3 != i)) {
printf("%d\n", i);
}
}
// 1.定义数组,数组可以保存10个数字,正好可以保存0~9
int numbers[10] = {0};
// 2.接收用户的输入,讲用户输入的值保存到数组中
// 1 ,3 ,5将用户输入值,作为数组的索引取数组中修改对应索引的值
int index = -1;// 定义变量用于接收用户输入的值
for (int i = 0; i < 3; i++) {
printf("请输入%d个整数\n", i + 1);
scanf("%d", &index);
// 把用户输入的值,作为数组的索引取修改数组中的值为1
numbers[index] = 1;
}
int length = sizeof(numbers) / sizeof(numbers[0]);
// 遍历数组,查看哪些元素没有被修改过,没有被修改过就是没有出现过
for (int i = 0; i < length; i++) {
if (1 != numbers[i]) {
printf("%d\n", i);
}
}
##3.要求从键盘输入6个0~9的数字,排序后输出
// 空间换时间, 适合数据比较少
// 1.定义数组,保存用户输入的整数
// 一定要给数组初始化, 否则有可能是一些随机值
int numbers[10] = {0};
// 2.接收用户输入的整数
// 2.1定义变量接收用户输入的整数
int index = -1;
for (int i = 0; i < 6; i++) {
printf("请输入第%d个整数\n", i + 1);
scanf("%d", &index);
// 将用户输入的值作为索引取修改数组中对应的元素的值为1
// 指针的时候回来演示刚才的问题
numbers[index] = 1 ;
}
int length = sizeof(numbers) / sizeof(numbers[0]);
for (int i = 0; i < length; i++) {
if (1 == numbers[i]) {
// 输出索引
printf("%d", i);
}
}
// 1.定义数组,保存用户输入的整数
int numbers[10] = {0};
// 2.接收用户输入的整数
// 2.1定义变量接收用户输入的整数
int index = -1;
for (int i = 0; i < 6; i++) {
printf("请输入第%d个整数\n", i + 1);
scanf("%d", &index);
// 将用户输入的值作为索引取修改数组中对应的元素的值为1
// 假设用户输入的是 1,1,1,2,2,2
numbers[index] = numbers[index] +1 ;
}
int length = sizeof(numbers) / sizeof(numbers[0]);
for (int i = 0; i < length; i++) {
// j = 1 因为如果数组元素中存储的值是0不用输出
// 将i对应存储空间中的元素取出,判断需要输出几次
for (int j = 1; j <= numbers[i]; j++) {
printf("%d", i);// 1 1 1 2 2 2
}
}
# 数组元素作为函数参数
- 数组可以作为函数的参数使用,进行数据传送。数组用作函数参数有两种形式:
+一种是把数组元素(下标变量)作为实参使用
+一种是把数组名作为函数的形参和实参使用
##1.数组元素作为函数参数
- 数组元素就是下标变量,它与普通变量并无区别。 因此它作为函数实参使用与普通变量是完全相
同的,在发生函数调用时,把作为实参的数组元素的值传送给形参,实现单向的值传送。
- 数组的元素作为函数实参,与同类型的简单变量作为实参一样,是单向的值传递,即数组元素的值传给形参,形参的改变不影响实参
```
void change(int val)// int val = number
{
val = 55;
}
int main(int argc, const char * argv[])
{
int ages[3] = {1, 5, 8};
printf("ages[0] = %d", ages[0]);// 1
change(ages[0]);
printf("ages[0] = %d", ages[0]);// 1
}
>+ 用数组元素作函数参数不要求形参也必须是数组元素
##2.数组名作为函数参数
- 在C语言中,数组名除作为变量的标识符之外,数组名还代表了该数组在内存中的起始地址, 因此,当数组名作函数参数时,实参与形参之间不是"值传递",而是"地址传递",实参数组名将该数组的起始地址传递给形参数组,两个数组共享一段内存单元,编译系统不再为形参数组分配存储单元。
- 数组的名字作为函数实参,传递的是整个数组,即形参数组和实参数组完全等同,是存放在同一存储空间的同一个数组。这样形参数组修改时,实参数组也同时被修改了。形参数组的元素个数可以省略
void change2(int array[3])// int array =0ffd1
{
array[0] = 88;
}
int main(int argc, const char * argv[])
{
int ages[3] = {1, 5, 8};
printf("ages[0] = %d", ages[0]);// 1
change(ages[0]);
printf("ages[0] = %d", ages[0]);// 88
}
##3.数组名作函数参数的注意点
- 在函数形参表中,允许不给出形参数组的长度
void change2(int array[])
{
array[0] = 88;
}
- 形参数组和实参数组的类型必须一致,否则将引起错误。
void prtArray(double array[3]) // 错误写法
{
for (int i = 0; i < 3; i++) {
printf("array[%d], %f", i, array[i]);
}
}
int main(int argc, const char * argv[])
{
int ages[3] = {1, 5, 8};
prtArray(ages[0]);
}
- 当数组名作为函数参数时, 因为自动转换为了指针类型,所以在函数中无法动态计算除数组的元素个数
void printArray(int array[])
{
printf("printArray size = %lu\n", sizeof(array)); // 8
int length = sizeof(array)/ sizeof(int); // 2
printf("length = %d", length);
}
# 冒泡排序
##1.冒泡排序
- 冒泡排序(Bubble Sort,台湾译为:泡沫排序或气泡排序)是一种简单的排序算法。它重复 地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
- 冒泡排序 分为: 大数下沉 小数上浮
##2.冒泡排序
- 1)比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 3)针对所有的元素重复以上的步骤,除了最后一个。
- 4)持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
# 选择排序
##1.选择排序
- 选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此类推,直到所有元素均排序完毕。
##2.选择排序的基本思想
- 第一趟排序在所有待排序的n个记录中选出关键字最小的记录,将它与数据表中的第一个记录交换位置,使关键字最小的记录处于数据表的最前端;第二趟在剩下的n-1个记录中再选出关键字最小的记录,将其与数据表中的第二个记录交换位置,使关键字次小的记录处于数据表的第二个位置;重复这样的操作,依次选出数据表中关键字第三小、第四小...的元素,将它们分别换到数据表的第三、第四...个位置上。排序共进行n-1趟,最终可实现数据表的升序排列。
# 折半查找
##1.基本思路
- 在有序表中,取中间元素作为比较对象,若给定值与中间元素的要查找的数相等,则查找成功;
若给定值小于中间元素的要查找的数,则在中间元素的左半区继续查找;
- 若给定值大于中间元素的要查找的数,则在中间元素的右半区继续查找。不断重复上述查找过 程,直到查找成功,或所查找的区域无数据元素,查找失败。
##2.实现步骤
- 在有序表中,取中间元素作为比较对象,若给定值与中间元素的要查找的数相等,则查找成功;
- 若给定值小于中间元素的要查找的数,则在中间元素的左半区继续查找;
- 若给定值大于中间元素的要查找的数,则在中间元素的右半区继续查找。不断重复上述查找过 程,直到查找成功,或所查找的区域无数据元素,查找失败。
# 进制转换查表法
void toBinary2(int num)
{
total(num, 1, 1);
}
void toOct2(int num)
{
total(num, 7, 3);
}
void toHex2(int num)
{
total(num, 15, 4);
}
void total(int num , int base, int offset)
{
// 1.定义表用于查询结果
char cs[] = {
'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b',
'c', 'd', 'e', 'f'
};
// 2.定义保存结果的数组
char rs[32];
// 计算最大的角标位置
int length = sizeof(rs)/sizeof(char);
int pos = length;//8
while (num != 0) {
int index = num & base;
rs[--pos] = cs[index];
num = num >> offset;
}
for (int i = pos; i < length; i++) {
printf("%c", rs[i]);
}
printf("\n");
}
void toBinary(int num)
{
// 1.定义表用于查询结果
char cs[] = {
'0', '1'
};
// 2.定义保存结果的数组
char rs[32];
// 计算最大的角标位置
int length = sizeof(rs)/sizeof(char);
int pos = length;//8
while (num != 0) {
int result = num & 1;
rs[--pos] = cs[result];
num = num >> 1;
}
for (int i = pos; i < length; i++) {
printf("%c", rs[i]);
}
}
void toOct(int num)
{
// 1.定义表用于查询结果
char cs[] = {
'0', '1', '2', '3', '4', '5',
'6', '7'
};
// 2.定义保存结果的数组
char rs[11];
// 计算最大的角标位置
int length = sizeof(rs)/sizeof(char);
int pos = length;//8
while (num != 0) {
int result = num & 7;
rs[--pos] = cs[result];
num = num >> 3;
}
for (int i = pos; i < length; i++) {
printf("%c", rs[i]);
}
}
void toHex(int num)
{
// 1.定义表用于查询结果
char cs[] = {
'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b',
'c', 'd', 'e', 'f'
};
// 2.定义保存结果的数组
char rs[8];
// 3.定义存储结果的索引
// 计算保存结果数组的元素个数
int length = sizeof(rs)/sizeof(char);
int pos = length;//8
while (num != 0) {
int result = num & 15;//12
//7 6
rs[--pos] = cs[result];//c 3
// pos--;
num = num >> 4;
}
for (int i = pos; i < length; i++) {
printf("%c", rs[i]);
}
}
void printHex(int num)
{
// 9 a b c d e f
for (int i = 0; i < 8; i++) {
int result = num & 15;
if (result > 9) {//10 11 1+
printf("%c", result - 10 + 'a');
}else{
printf("%d", result);
}
num = num >> 4;
}
}
void printOct(int num)
{
for (int i = 0; i < 11; i++) {
int result = num & 7;
printf("%d", result);
num = num >> 3;
}
}
void printBinary(int num)
{
// 要左移的位数
int temp = (sizeof(int) << 3) - 1;
while (temp >= 0) {
int result = (num >> temp) & 1;
printf("%d", result);
if (temp % 4 == 0) {
printf(" ");
}
temp--;
}
}