一、一维数组
1. 一维数组的定义与使用
-
(1)数组的简单概念:一组具有相同类型的元素的集合
-
(2)数组的创建:
格式:类型名+数组名+[数组大小]
需要注意的是:对多数情况而言,在指定的数组大小时,需要用常量表达式。
(PS:若编译器支持C99,则可使用变量来指定数组的大小(即变长数组);但变长数组有一些限制:变长数组必须是自动存储类的,意味着它们必须在函数内部或作为函数参数声明,而且声明时不可以进行初始化。)
数组的大小可以省略,但若想省略则必须在创建时就对其进行初始化。 -
(3)数组的初始化:
和变量的初始化类似,在创建它的时候赋予它一些合理的值
一般的初始化形式:数组名[数组大小]={…}
数组的下标是从0开始的,故需根据实际需求正确指定数组的大小
若数组的大小省略,则数组会根据初始化的内容来确定数组的大小
如:int arr[]={1,2,3};
数组arr的大小就为3个整型变量的大小
若数组的初始化部分即{ }中的元素达不到数组的大小时,相应没有指定初始化的地方自动初始化为零,称为数组的不完全初始化
如:int arr[3] = {1};
此时数组中对应的元素为 arr[0] = 1; arr[1] = 0; arr[2] = 0
故常通过如下面这段代码来实现对数组每个元素赋0的初始化:
int arr[10] = {0};
对于作为全局变量的数组 ,其元素默认初始化为0
- (4)一维数组的使用:通过下标引用操作符[ ]来实现对每个元素的访问
arr[2] = 2;
//将数组的第三个元素赋值为2
- (5) 可通过sizeof来计算数组的长度:
int len = sizeof(arr)/sizeof(arr[0]);
- (6)易踩坑点:用数组存储字符串的初始化
时刻要在脑海中的一个知识点:字符串的最后一个字符是‘\0’,这也是函数对字符串进行操作的标志,如strlen strcmp等。故在用数组进行字符串的存储时,一定要考虑到 \0 而正确指定数组的大小
如下代码:
char arr[3] = { "abc"};
将其打印出来的结果是:
解释:此时数组的情况是arr[0] = ‘a’; arr[1] = ‘b’; arr[2] = ‘c’; 而‘\0’ 并没有被随着字符串存储到数组当中,故在对其进行打印操作时,找不到结束的标志, 在打印完abc后又打印了乱码
结论:对字符串来说,其长度就为组成它的字符个数,如上字符串“abc”的长度就是3,而对数组来说,还要另将字符串的结束标志 \0考虑进去。通过如下代码加深印象:
int main()
{
char arr1[3] = { "abc"};
char arr2[3] = { 'a','b','c' };
char arr3[4] = { 'a','b','c','\0' };
printf("%s\n", arr1);
printf("%s\n", arr2);
printf("%s\n", arr3);
return 0;
}
打印结果:
2. 一维数组在内存中的存储
先说结论:数组在内存中是连续存放的,且是从低地址到高地址,即随着下标的增长,其每个元素的地址也在有规律地递增。
如下代码展示:
#include<stdio.h>
int main()
{
int arr[5] = { 0 };
int len = sizeof(arr) / sizeof(arr[0]);
//上面说的计算数组长度的方法
int i = 0;
for (i = 0; i < len; i++)
printf("%p\n", &arr[i]);
return 0;
}
运行结果:
二、二维数组
1. 二维数组的创建和使用
- (1)格式:
int arr [rows] [cols];
同一维数组, [ ] 中为常量表达式
- (2)初始化:
int arr[2][2] = {1,2};
//数组的第一行的一二列的初始化
int arr[2][2] = {{1},{2}};
//数组的第一列的一二行的初始化
int arr[][2] = {1,2};
//数组的省略初始化,但要注意行可以省略,但列不能省略
- (3)使用:同一维数组,使用 [ ] 操作符进行访问。
2. 二维数组在内存中的存储
也和一维数组一样,按地址由高到低连续存放
(PS:其实所谓行和列只是我们的一种理解,其实二维数组的存放和一维数组一样是一整行的),如下图:
所以这里可以有一种理解方法:把对应行(如arr[0])看作是对应一维数组的数组名,而再通过操作符 [ ] 中的数字来访问这个一维数组的每一个元素。即把二维数组当一维数组来使用。如arr[0][1]就可以理解为访问的是一维数组arr[0]的第二个元素。
这里我们同样可以通过sizeof来对二维数组进行一些运算:
//计算二维数组的行数:
sizeof(arr) / sizeof(arr[0])
//计算二维数组每行元素个数:
sizeof(arr[0]) / sizeof(arr[0][0])
三、补充知识
1.数组越界问题
所谓越界就是指访问的下标超过了数组的长度。需要注意的是C语言本身不做数组下标的越界检查,编译器也不一定报错,编译器不报错并不代表程序就本身是正确的。所以我们在写代码的时候一定要细心,写完后最好做一遍越界的检查。
2.数组作为函数参数
先说知识点:数组传参,本质上传的是数组首元素的地址。这里就不得不谈到数组名的问题。
数组名其实就代表着该数组首元素的地址,如下代码:
int arr[5] = { 1,2,3 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%d", *arr);
运行结果:
当然也有两个例外情况:
(1)用sizeof计算数组大小时,()中的数组名,代表的时整个数组。
(2)&数组名,其中的数组名也表示整个数组,这个操作指的是取出整个数组的地址(到指针总结时会更详细说明)
3. 对数组类型的理解:
int num = 10;
对于变量num而言, int 就是它的类型(即去掉变量名)
可以得到 sizeof(int) == sizeof(num)
对于数组而言其实是同样的道理:
int arr[10];
对于上面这个数组arr而言呢,int[10] 就是它的类型(即去掉数组名)
同理也有:sizeof(int[10]) == sizeof(arr)
4. 数组作为函数参数的重要实例:冒泡排序
补充:冒泡排序的主要思想是:相邻两数进行对比,若不符合想要实现的序列(升序或降序),则两数进行交换。
先来个错误的函数例子,好涨记性:
void bubble_sort(int arr[])
{
int len = sizeof(arr)/sizeof(arr[0]);
//这里想求数组长度,但调试发现结果不符合预期
int i = 0;
for(i=0; i<len-1; i++)
{
int j = 0;
for(j=0; j<len-i-1; j++)
{
if(arr[j] > arr[j+1])
{
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
结果为1的原因:上面说过,用sizeof计算数组长度时()中的数组名表示整个数组,或着说应当为整个数组的地址,而数组传参本质上传的是数组首元素的地址,而地址的大小,在32位机上位4个字节,在64位机上为8个字节,又因为该数组为整型数组,其元素的类型大小为4个字节,故最终的len的值只可能是2或1。
解决办法:数组长度在外面算好后,再传进去
改正并通过flag优化后的完成冒泡排序程序如下:
void bubble_sort(int arr[], int n)
{
int i, j;
int tmp = 0;
for (i = 0; i < n - 1; i++) //趟数,即有多少个数需要被处理
{
int flag = 1;
for (j = 0; j < n - 1 - i; j++) //对换次数,即一趟需对换多少次
{
if (arr[j] > arr[j + 1]) //本质上是想让不满足既定有序条件的两个数对换
{
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = 0; //当i=1时,5和4就已发生对换,整个数组已经有序
}
}
if (flag == 1)
break;
}
}
对flag优化的解释:利用flag可以减少趟数,优化代码(用来解决可能提前排序完成的情况)(即已经有序了)。因为n-1趟的规律是在完全倒序情况下总结的,即数组为9,8,7…,但实际上数组可能只需一趟就完成了排序
如1,2,3,5,4。
如果没有flag的话,代码依然会按照n-1趟来判断,一些过程就重复了。
flag可以理解为是一个检测有序的标志
结合代码理解:如上例{1,2,3,5,4},第一趟4和5发生对换,对换后数组已经有序,此时进行下一趟排序时,内循环中的交换将一次都不进行,也就是flag的值不会被置为0,内循环结束时,flag的值仍为1,这就代表着数组已经有序,就可以跳出循环了。
以上就是我对数组这一部分知识的总结啦。
看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或有误地方的地方还恳请过路的朋友们留个评论,多多指点,谢谢朋友们🌹🌹🌹!