一维数组
数组的创建:
数组是一系列相同类型元素的集合,C语言中数组的创建都遵循以下原则:
type_t arr_name[const_n]
//type_t是数组的元素类型
//arr_name是数组名
//const_n是一个常量表达式,用于指定数组大小
数组创建的例子:
int arr1[10];
int count=10;
int arr2[count]; //可否正常创建?
char arr3[10];
float arr4[1];
double arr5[10];
其中的arr2用了count变量作为数组的长度,在C99标准之前,这是不被允许的。但C99之后的C语言标准允许变长数组的存在,数组的大小可以使用变量指定,但变长数组不允许初始化。
数组的初始化
初始化即是指在数组创建的同时给定一些合理的值。
int arr1[10]={1,2,3,4,5,6,7,8,9,10};//完全初始化
int arr2[10]={1,2,3};//不完全初始化,剩余的元素默认为零
int arr3[]={1,2,3,4,5};//没有指定元素个数,会依据初始化内容来确定数组的长度
对于下面的代码,要有区分:
char arr1[]="abc";
char arr2[]={'a','b','c'};
在初识阶段已经理解过,这里不解释了。
一维数组的使用
访问数组元素的时候,要用到单目操作符[ ],即下标引用操作符,就是数组访问的操作符。看代码:
#include <stdio.h>
int main()
{
int arr[10] = {0};//数组的不完全初始化
//计算数组元素个数
int sz = sizeof(arr)/sizeof(arr[0]);
for(int i=0;i<sz;i++)
{
arr[i]=i;
}
for(int i=0;i<sz;i++)
{
printf("%d ",arr[i]);
}
return 0;
总结:
1、数组使用下标和下标操作符[]来访问,下标从0开始
2、数组大小可以通过操作符sizeof计算得到
一维数组在内存中的存储
先看代码:
#include <stdio.h>
int main()
{
int arr[10]={0};
int sz=sizeof arr / sizeof arr[0];
for(int i=0;i<sz;i++)
{
printf("&arr[%d]=%p\n",i,&arr[i]);
}
return 0;
}
运行结果是:
可以发现,每相邻的两个元素的地址相差了4个字节,刚好就是数组存储数据的类型的大小,这是一个规律。且随着下标增长,地址也在变高。
于是我们可以得出结论:数组在内存中是连续存放的!
二维数组
二维数组的创建与初始化:
int arr[3][4]={1,2,3,4};
int arr[3][4]={{1,2},{3,4}};
int arr[][4]={{2,3},{4,5}};//在初始化二维数组的时候,行可以省略,列绝对不能省略
二维数组的使用
同一维数组一样,二维数组在访问的时候也是通过下标和操作符[]来访问的,但是由于二维数组的特殊性,其比一维数组更复杂,需要通过嵌套的循环来访问其中的值:
#include <stdio.h>
int main()
{
int arr[3][4]={0};
for(int i=0;i<3;i++)
{
for(int j=0;j<4;j++)
{
arr[i][j]=i*4+j;
}
}
for(int i=0;i<3;i++)
{
for(int j=0;j<4;j++)
{
printf("%d",arr[i][j]);
}
}
return 0;
}
二维数组在内存中的存储
二维数组是否像我们想的这样在内存中存储的呢:
我们尝试打印每一个元素的地址:
#include <stdio.h>
int main()
{
int arr[3][4];
for(int i=0;i<3;i++)
{
for(int j=0;j<4;j++)
{
printf("&arr[%d][%d]=%p\n",&arr[i][j]);
}
}
return 0;
}
输出结果是:
通过结果可以看到,其实二维数组在内存中也是连续存储的。
另外,对于二维数组可以有另一种理解方式,将其每一行看作一个一维数组,数组名[行表]就是这个对应的一维数组的数组名。
于是也就能理解了为什么在初始化二维数组的时候行可以省略,列不可以省略。
数组越界
数组的下标最大只能是数组长度-1,在使用数组时,下标越界了编译器也不一定报错,但不报错并不意味着程序完全正确。写代码时,要自己做好越界的检查。
数组作为函数参数
很多情况下,我们都会需要将一个数组作为参数传递给函数帮助我们完成一些操作。但数组是一个很特殊的数据结构,其在作为实际参数给函数传参时也于其他的数据类型有很大的不同。
这里初步接触一下算法,写一个最简单的排序算法,冒泡排序
冒泡排序是将当前元素与与之相邻的元素作比较,如果符合情况,则两个值调换位置,一趟冒泡排序即可将当前元素移动至它应当在的位置,因此n个元素的冒泡排序只需比较n-1次,因为最后一个元素在其他所有元素排完序后其就已经在它应当在的位置了。
#include <stdio.h>
void bubble_sort(int arr[]) //int *arr也没问题甚至更容易理解
{
int sz = sizeof arr / sizeof arr[0]; //是否正确?
for(int i=0;i<sz;i++)
{
for(int j=0;j<sz-1-i;j++)
{
if(arr[j]>arr[j+1])
{
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=tmp;
}
}
}
}
该种可以方法明确给出不正确,进入到函数内部发现他的局部变量sz是1,我们期望sz是数组的长度。
数组名是什么?
#include <stdio.h>
int main()
{
int arr[10]={1,2,3,4,5};
printf("%p\n",arr);
printf("%p\n",&arr[0]);
printf("%d\n",*arr);
return 0;
}
结论:数组名是数组首元素的地址。(但有例外)
int arr[10]={0};
printf("%d\n",sizeof(arr));
为什么会输出40?
补充:
1、sizeof 数组名 计算整个数组的大小,这里的数组名代表整个数组
2、&数组名,取出的是整个数组的地址,其值与arr以及&arr[0]相等,但意义完全不同。
除上述两种情况外的所有情况,数组名都表示首元素的地址。
&arr、 arr、 &arr[0]要注意区别,虽然&arr和arr的值相同,&arr+1,但arr+1完全不同,&arr是将整个数组视作整体,+1后取到的是整个数组后面的第一个地址,而arr+1则是取到了数组第二个元素的地址。
于是将上述冒泡排序算法修改成如下,将数组长度在函数体外部求好并将其当作参数传给函数就可以实现功能了:
void bubble_sort(int *arr,int sz) //int *arr也没问题甚至更容易理解
{
for(int i=0;i<sz;i++)
{
for(int j=0;j<sz-1-i;j++)
{
if(arr[j]>arr[j+1])
{
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=tmp;
}
}
}
}