数组
存放相同数据类型元素的集合
数据类型 数组名[数组大小]
创建数组,数组元素个数需要是常量,C99标准才支持变长数组,可以使用变量定义数组元素个数(变长数组不能初始化)。
创建数组
char arr1[3]; // 字符数组,有3个元素,元素的类型是char
int arr2[4]; // 整型数组,有4个元素,元素的类型是int
int* arr3[5]; // 整型指针数组,有5个元素,元素的类型是int*
数组的初始化
// 初始化内容为数组个数,数组元素个数是4个,字符串还有一个'\0'
char arr1[] = "abc";
// 不完全初始化,只初始化了前面3个,剩下的默认为0
char arr2[10] = { 'a', 'b', 'c' };
// 初始化内容为数组个数,数组元素个数是3个
int arr3[] = { 1,2,3 };
// 不初始化,数组元素都是随机值
int arr4[4];
// 不完全初始化,值初始化了前面5个,剩下的默认为0
int arr4[10] = { 1,2,3,4,5 };
数组的使用
[]是下标引用操作符,有两个操作数,数组名和下标,用来访问数组元素的。
不建议用第二种方式访问数组元素数据。
下标是从0开始的,一般通过for循环遍历数组。
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3 };
for (int i = 0; i < 3; ++i)
printf("%d ", arr[i]);
return 0;
}
数组的大小能够通过sizeof(数组名)计算。
sizeof(数组名) 计算整个数组大小
sizeof(数组名[0]) 计算数组元素大小
sizeof(数组名) / sizeof(数组名[0]) 计算数组元素个数
一般计算数组元素个数作为边界。
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3 };
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < sz; ++i)
printf("%d ", arr[i]);
return 0;
}
数组的存储
元素的地址
发现每个元素的地址相差4个字节,因为元素是int类型的,int类型的大小是4个字节,数组在内存中是连续存放的,随着数组下标的增长,元素的地址也在变大。
当数组是char类型的时候,每个元素的地址相差1个字节。
指针访问数组元素
将首元素的地址存放在一个指针变量中,直接打印数组的地址和通过指针打印数组的地址是一样的,p+i就是跳过i个元素,因为p指向的对象是int类型的,所以跳过i*4个字节。
可以通过解引用指针遍历访问数组元素。
二维数组
数据类型 数组名[行][列]
创建二维数组
char arr1[3][4]; //3行4列的二维数组,数组元素是char类型的
int arr2[2][3]; //2行3列的二维数组,数组元素是int类型的
二维数组不初始化也跟一维数组一样,元素都是随机值。
二维数组的初始化
int arr1[2][3] = { 1,2,3,4 };
int arr2[2][3] = { {1,2}, {3,4} };
arr1按顺序初始化二维数组,第一行初始完,初始化第二行第一个,剩下的默认为0。
arr2每行初始化两个,剩下的默认为0。
int arr1[][3] = { 1,2 };
int arr2[][3] = { {1,2}, {3,4} };
二维数组可以根据初始化内容判断有多少行。但是不能根据初始化内容判断有多少列。
二维数组的使用
行和列的下标都是从0开始的。遍历打印二维数组。
sizeof(arr2)计算的是整个二维数组的大小,sizeof(arr2[0])计算的是一行的大小,sizeof(arr2[0][0])计算的是二维数组元素的大小,可以算出行和列。
arr2[0]是找到二维数组第一行的地址,相当于找到的是数组名。想要访问这一行的元素,还需要使用下标继续访问,arr[0][0]访问第一行第一个元素。
二维数组的存储
打印二维数组每个元素的地址。
每个元素的地址相差4,二维数组在内存中也是连续存储的。本质是一块连续的空间模拟成二维数组。
前面三个存储第一行的数据,后面三个存储第二行的数据。
二维数组的数组名+1,跳过第一行3*4个字节,到第二行的地址。
数组越界
数组的下标是有范围的,n个元素的数组,下标是从0开始,到n-1。小于0,大于n-1,去访问数组元素,都是数组越界访问数据,这是错误的行为。
冒泡排序
排升序,依次比较两个相邻的元素,如果当前的元素比后面一个大,交换数据,每一趟排序,都会正确排好一个位置(最后一个,排一趟少一个),直到没有相邻元素需要交换,排序完成。核心思想是交换数据。
排升序
5 4 3 2 1 比较第1个数和第2个数,第1个数大,两数交换
4 5 3 2 1 比较第2个数和第3个数,第2个数大,两数交换
4 3 5 2 1 比较第3个数和第4个数,第3个数大,两数交换
4 3 2 5 1 比较第4个数和第5个数,第4个数大,两数交换
4 3 2 1 5 一趟排序结束,排好一个数,下一趟就少排序一个数。
...
1 2 3 4 5 结果
#include <stdio.h>
int main()
{
int arr[] = { 5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
// sz(5)个元素只需要排序sz-1(4)趟,每一趟排序好一个位置
// 最后剩下的一个就是有序的
for (int i = 0; i < sz - 1; ++i)
{
// 定义一个flag判断是否有交换数据
bool flag = true;
// sz(5)个数排序,第一趟需要排序sz-1(4)次
// 剩下4个数,第二趟就需要排序sz-1-1(3)次
// i是变化的数,如果写成sz-1-i满足需求
for (int j = 0; j < sz - 1 - i; ++j)
{
if (arr[j] > arr[j + 1])
{
flag = false;
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
// 如果没有进行交换数据,证明该数组已经有序,结束循环
if (flag)
break;
}
// 打印数据
for (int i = 0; i < sz; ++i)
printf("%d ", arr[i]);
return 0;
}
如果数组本身就是有序的,可以定义一个判断条件,如果没有进行交换数据,结束循环。
函数
如果想实现一个功,最好能写成函数,不要在main函数直接写。
#include <stdio.h>
#include <stdbool.h>
// 需要传数组元素个数
void bubble_sort(int arr[], int sz)
{
// sz个元素需要排序sz-1趟
for (int i = 0; i < sz - 1; ++i)
{
bool flag = true;
// sz个数,排序sz-1次,每次排序一个数,下一趟少排一个数
for (int j = 0; j < sz - 1 - i; ++j)
{
// 依次比较
if (arr[j] > arr[j + 1])
{
flag = false;
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
if (flag)
break;
}
}
int main()
{
int arr[] = { 5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
// 打印数据
for (int i = 0; i < sz; ++i)
printf("%d ", arr[i]);
return 0;
}
注: 数组名本质上就是地址,数组名作为实参,并不会将整个数组传过去,传的是首元素的地址,形参得到的是地址。不能通过指针(地址)计算数组元素个数,所以需要将数组元素个数传给冒泡排序函数。
数组名
数组名是数组首元素的地址,但是有两个列外。
1.sizeof(数组名) 这里的数组名表示的是整个数组,计算的是整个数组的大小。
2.&数组名 这里的数组名表示的是整个数组,取出的是整个数组的地址,对该地址加1,跳过的是整个数组大小。
打印的地址是16进制数。
arr和arr+1相差一个整型大小,4个字节;&arr[0]和&arr[0]+1相差一个整型大小,4个字节。
&arr和&arr+1相差五个整型大小,20个字节,&arr+1跳过的整个数组。