欢迎━(*`∀´*)ノ亻!,这里是王_哈_哈 Jw~!!!笑一笑呐!!!
目录
✍数组的"前言"
当我们需要对一个数据比如说一个学生的成绩进行计算或存储的时候可以用过创建一个变量来实现,但是如果要对100个学生的成绩进行处理,且不说定义100个变量真的很麻烦,每个数据的位置毫无关联,处理起来也像是理一团乱糟糟的毛线...让人头大。而C语言恰巧提供了一种叫做数组的数据结构,让数据处理起来变得更加方便清晰。数组也是C语言中很重要的一部分,以下是我学习后的笔记,主要记录了一维数组和二维数组的创建,初始化,使用以及在内存中的存储。
✍关于数组
-
❀数组概念
- 数组是一组相同类型的数据的集合。
- 里面是什么类型就可以叫做什么数组。eg.如果里面全部存放整型数据,那就叫做整型数组,类似的还有字符数组,指针数组(存放指针的数组)...
-
❀数组元素和下标
- 构成该数组的数据成员被称为数组元素。
- 因为数组里的数据是有序存放且连续的,所以每一个元素都有属于自己的下标,下标从0开始依次递增。第一个元素的下标为0!
-
❀数组维数
- 数组可以有很多个维度,比较常见的是一维数组和二维数组。
- 变量,一维数组,二维数组,三维数组分别对应着几何空间里的点,线,面,体(一个比喻)。
- 一维数组的定义int arr[3];二维数组的定义int arr[3][4];(表示一个三行四列的数组)。(可以简单地认为数组元素有几个下标就是几维数组)。
![e97354472c46460583d205d1062cedb9.png](https://img-blog.csdnimg.cn/e97354472c46460583d205d1062cedb9.png)
一二维数组的下标
✍一维数组
✎一维数组的定义
❀如何定义
type_t a rr_name [const_n];
数据类型 数组名[常量表达式];
❀定义示例
- int arr[10]; (int表示该数组元素类型为整型,arr为其数组名称,该数组存储10个元素,元素下标从0到9)
- char name[5];
- int arr1[ ] = {1,2,3}; (定义的时候可以省略数组大小,但必须初始化,数组大小就会根据初始化的内容来确定)
❀注意事项
- 常量表达式代表[ ]内只能用常量(整型常量或符号常量,不能是0或浮点数常量),表示数组的长度,不能用变量。
虽然,但是,其实...
C99标准之后,引入了变长数组的概念,即[ ]内可以用变量,但是不能初始化(定义完后还是可以赋值的),不初始化所以就都是随机值,存放在栈区。即使有这个概念还是有很多编译器不支持这个概念。(还是建议不要使用它,因为很多编译器不支持,如果后面内存被占了就会很麻烦,况且可以用动态内存)
比如VS:
gcc是支持的(第一张图片倒数第二行输出10个随机值,第二张图是对数组进行赋值后输出):
如果对变长数组进行初始化就会报错:
✎ 一维数组的初始化
数据类型 数组名[长度] = {常量1,常量2,常量3...}
如果没有初始化里面的值是随机的,且会被警告
❀没有省略数组大小的数组初始化
初始化示例
- int arr[4] = {1,2,3,4};
- int arr[4] = {1,2};
- int arr[4] = {0};
注意点
- 可以只初始化一部分,不完全初始化后面自动默认为0,所以示例三就是把所有元素初始化为0
- int arr[4] = {0,1,2,3,4};是非法的,会报错
❀省略数组大小的数组初始化
初始化示例
- int arr[ ] = {1,2,3,4}; 相当于int arr[4] = {1,2,3,4};
- int arr[ ] = {1,2}; int arr[2] = {1,2};
注意点
- 这就相当于你输入几个数,这个数组就多长,之后是不能再追加的
- 如果被定义数组的长度和初值个数不同,则数组长度就不能省略
❀字符数组的初始化
字符数组与其他数组最大的不同就是可以用字符串初始化。
初始化示例
- char arr1[5] = {'c','h','i','n','a'};
- char arr2[ ] = {'c','h','i','n','a'};
- char arr3[5] = {'c','h','i','n',97};
- char arr4[ ] = ''china'';
- char arr4[6] = ''china'';
以上示例在内存中的存储be like :
注意点
- 字符是以ascall值的形式在内存中存储的,所以可以用整型(示例3)
- 字符串内部默认以'\0'结尾,所以用它给字符数组赋值的时候,比它在外观上的长度+1
✎一维数组的使用
❀用sizeof计算数组大小
int sz = sizeof(arr_name)/sizeof(arr_name[0]);
- sizeof(arr)求出整个数组的大小(arr是数组名)
- sizeof(arr[0])求出第一个元素的大小(每个元素大小相同)
❀利用循环语句对数组进行简单处理
[ ] 下标引用操作符,在处理的时候里面是可以用变量的
逐个赋值
int arr[4];
for (int i = 0; i < 4; i++)
{
scanf("%d", &arr[i]);
}
逐个打印(输出)
for (int i = 0; i < 4; i++)
{
printf("%d ", arr[i]);
}
✎一维数组在内存中的存储
利用代码查看每个元素的地址
for (int i = 0; i < 4; i++)
{
printf("%p\n", &arr[i]);
}
可以看出每个地址相差四个字节(一个int的大小),由此可见:一维数组中的数据在内存中是连续存放的,随着下标的增长,地址也递增,且由低到高存放。
二维数组不论是在定义初始化使用还是存储方式上与一维数组都很相似,so...next
✍二维数组
- 可以将二维数组看做是一个矩阵,第一个下标代表行数,第二个下标代表列数
- 可以将二维数组看做是一维数组的数组,
✎二维数组的创建
- int arr1[3][4]; (定义了一个三行四列的二维数组)
- char arr2[5][8];
- double arr3[3][4];
如果没有初始化,两个下标都不能省略。
✎二维数组的初始化
❀初始化示例,以及初始化后数组元素的变化
- int arr1[3][4]= {1,2,3,4,5,6,7,8,9,10,11,12};
- int arr2[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
- int arr3[3][4]={{1,2,3},{0},{7,8,3,4}} ;
- int arr4[3][4] = {0};
- int arr5[ ][4] = {1,2,3,4,5,6,{7,8},9};
❀注意点
- 如果有初始化,行标可以省略,列表不可以省略
- 可以通过在外层{}里添加{},初始化各行数据
- 不完全初始化时,没有初始化的元素自动赋值为0
✎二维数组的使用
❀用sizeof计算元素个数
- sizeof(arr_name)/sizeof(arr_name[0][0]) 可以计算二维数组的元素个数
- sizeof(arr_name)/sizeof(arr_name[0]) 计算的是二维数组的行数
❀利用循环语句对数组进行简单处理
逐个赋值
需要用两层循环,先处理第一行,再处理第二行...
int arr[3][4] = { 0 };
for (int i = 0; i < 3; i++) //遍历行
{
for(int j = 0;j < 4;j++) //遍历列
{
scanf("%d", &arr[i][j]);
}
}
逐个打印(输出)
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%4d ",arr[i][j]);
}
printf("\n"); //每行打印完换行
}
✎二维数组在内存中的存储
二维数组在内存中的存储和一维数组相似,也是连续的,逐个打印出每个元素的地址发现都相差一个int也就是四个字节的大小,也就证明了:二维数组在内存中的存储是连续的。
✍常见错误—数组越界
因为数组的每个元素都有对应的下标,而下标是从0开始的,如果一个数组里有n个元素,那么最后一个元素的下标为n-1,所以很容易导致数组下标越界。
然而C语言本身不做数组下标越界的检查,编译器也不一定报错,所以在写代码的时候一定要对是否越界做好检查。
![f6e3e017288f4b8baba1a069517a85da.png](https://img-blog.csdnimg.cn/f6e3e017288f4b8baba1a069517a85da.png)
数组越界访问了,但还是被打印出来,越界访问的数是随机值
✍关于数组名
通常情况下,数组名是首元素的地址。
例如,当我们在程序中输入:
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
结果是这样的,数组名的地址和首元素地址一样:
不过,值得注意的是,数组的地址和数组首元素的地址也是一样的,那我们要怎么区分呢?
可以这样(因为首元素的地址+1会跳过一个元素,而数组的地址+1则会跳过一个数组)
int arr[10] = { 0 };
printf("%p\n", arr); //数组名
printf("%p\n", &arr[0]); //首元素地址
printf("%p\n", &arr); //数组名地址
printf("%p\n", arr + 1); //数组名+1
printf("%p\n", &arr[0] + 1); //首元素地址+1
printf("%p\n", &arr+1); //数组名地址+1
上图可以证明:
- 数组首元素地址和整个数组的地址相同
- 数组名就是首元素地址(因为数组名+1跳过的是一个元素)
- 整个数组的地址+1跳过一个数组的大小(该数组为40字节)
不过数组名有两个例外情况不是数组首元素的地址:
- sizeof(arr) (表示的是整个数组的大小,而不是首元素的大小)
- &arr (由上图可知,在数组名前加上&表示的是取出整个数组的地址)
✍数组的两个应用
✎冒泡排序
数组存储的通常是一列数,排序是其一个重要应用。以下就通过设计一个冒泡排序的函数,更加深入地理解数组的应用以及数组名在传参过程中的实际应用.
void bubble_sort(int* arr, int sz) //冒泡排序函数
{
for (int i = 0; i < sz - 1; i++) //控制趟数
{
int flag = 0;
for (int j = 0; j < sz - i - 1; j++) //进行比较
{
if (arr[j] > arr[j + 1]) //交换
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = 1;
}
}
if (flag == 0) //优化
break;
}
}
int main()
{
int arr[7] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]); //计算数组的大小
for (int i = 0; i < sz ;i++) //输入数据
{
scanf("%d", &arr[i]);
}
bubble_sort(arr, sz); //冒泡排序函数
for (int i = 0; i < sz; i++) //输出
{
printf("%d ", arr[i]);
}
return 0;
}
以上就是一整个升序冒泡排序,有几个需要注意的地方:
- 计算数组大小要在主函数计算,并且进行传参,数组作为函数的参数代表的是首元素的地址,如果把它放在冒泡函数里就会出错,如果只传一个数组名(数组名是首元素的地址,相当于一个指针),再在函数里计算sizeof(arr)代表的已经是一个指针变量的大小了,最后sz=1,会失去函数的意义。
- 函数内部可以用flag进行标记,当一趟遍历都没有进行交换时说明此时数组已经有序,不需要进行比较,可以直接退出,完成了优化。
- 一定要注意检查数组的下标是否越界。
✎三子棋
参考博文:戳这里yeah!
内容还有很多不足之处还请多多指正~
Thanks♪(・ω・)ノ u~ bye ~bye~