这里记录本文的大概内容:
提示:以下是本篇文章正文内容,下面案例可供参考
type_t arr_name [const_n];
//type_t 是指数组的元素类型
//const_n 是一个常量表达式,用来指定数组的大小
举个例子:
//代码1
int arr1[10];
//代码2
int count = 10;
int arr2[count];//数组时候可以正常创建?
//代码3
char arr3[10];
float arr4[1];
double arr5[12];
ps:数组创建时,[ ]里面最好使用常量,C99之后才允许使用变量,
有些编译器对C99标准支持不是很好,你在[ ]里面使用变量可能会报错
没有初始化之前,数组里面放的是一些随机值
int main()
{
//初始化
int arr1[10] = { 1,2,3 };
//不完全初始化,开头三个初始化为1、2、3,其他的默认为0
int arr2[] = { 1,2,3 };
//没有指定大小,根据初始化内容来默认指定大小,
//比如这里初始化3个数,那么数组大小就是3
char arr3[] = "abc";//abc后默认有一个\0,arr3大小为4字节
char arr4[] = { 'a','b','c' };//abc后没有\0,arr4大小为3字节
char arr5[] = { 'a',98,'c' };//如果用数字初始化字符数组,是对应的ASCII码的字母
//比如这里的98对应的是'b'
return 0;
}
对于数组的使用我们之前介绍了一个操作符: [ ] ,下标引用操作符。
而我们数组就是使用下标来访问元素的,比如arr[下标],示例如下:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };//数组下标从0开始,往后依次+1
//printf("%d", arr[4]);//如果想打印数组第5个元素,那么你访问的应该是下标4
int i = 0;
for (i = 0;i < 10;i++)
{
printf("%d ", arr[i]);
}
return 0;
}
然而,上面代码也是有一定缺陷的,我们的for循环的判断条件i<10,是我们提前知道数组一共10个元素的情况下,将来如果有人修改了数组元素个数,我们这里还用i<10就容易出错了。
所以我们这里建议使用sizeof函数把数组元素个数计算出来,然后替换掉for循环里面的10
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };//数组下标从0开始,往后依次+1
//printf("%d", arr[4]);//如果想打印数组第5个元素,那么你访问的应该是下标4
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
//sizeof(arr)是数组总大小,sizeof(arr[0])是数组一个元素大小
//两者相除就是数组元素个数
for (i = 0;i < sz;i++)
{
printf("%d ", arr[i]);
}
return 0;
}
我们一维数组在内存中是连续开辟的一块空间,
要想知道它的布局,只要把它每个元素的地址打印下来就可以了
int main()
{
int arr[] = { 1,2,3,4,5 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0;i < sz;i++)
{
printf("%p\n", &arr[i]);//打印地址用%p,&arr[i]取出arr[i]的地址
}
return 0;
}
可以看到,我们这里每隔一个数组元素地址差4,
为什么差4?因为一个int型是4字节啊(我们这里是整形数组)
相应的,如果是其他类型的数组,每个数组元素地址相差一个类型的大小
仔细观察输出的结果,我们知道,随着数组下标的增长,
元素的地址,也在有规律的递增。 由此可以得出结论:数组在内存中是连续存放的。
int main()
{
int arr[3][5];
char brr[2][4];
double crr[4][2];
return 0;
}
示意图如下:
int main()
{
int arr1[3][5] = {1,2,3,4,5,6,7,8,9,10,11};
//3行5列,前两行放满,第3行放11,剩余的用0补
int arr2[3][5] = { {1,2},{3,4},{5,6} };
//第1行放1,2剩下的用0补;第2行放3,4剩下的用0补;第3行放5,6剩下的用0补
int arr3[][5] = { 1,2,3,4,5,6 };
//对二维数组来说,可以省略掉行数,但不能省略掉列数
//有了列数,可以根据初始化内容,推出行数
//比如列是5,那么arr3的第一行就要放5个元素,剩下一个6放第二行
//6后面没有数了那么就用0补齐第二行,
//也就是推出了行数为2
//为什么不能没有列号呢?
//答:没有列号我怎么知道你一行放多少个?
return 0;
}
其他类型是数组也是差不多的形式:
int main()
{
char ch1[2][3] = { 'a','b' };
char ch2[2][3] = { {'a'},{'b'} };
char ch3[4][6] = { "abc","def","qwe" };
return 0;
}
二维数组的使用也是通过下标的方式。
行号列号都是从0开始,往后依次+1
int main()
{
int arr[3][5] = { {1,2,3},{4,5},{6,7,8,9,10} };
int i = 0;//行号
for (int i = 0;i < 3;i++)//行号范围0-2
{
int j = 0;//列号
for (j = 0;j < 5;j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
我们通过上面的讲解,可以把二维数组理解为一个n*m的列表
但是它在内存里存储真的是这样吗?
我们通过和一维数组一样的方法“看地址”来看它在内存中到底是怎么存储的
int main()
{
int arr[3][5] = { {1,2,3},{4,5},{6,7,8,9,10} };
int i = 0;//行号
for (int i = 0;i < 3;i++)//行号范围0-2
{
int j = 0;//列号
for (j = 0;j < 5;j++)
{
printf("&arr[%d][%d]=%p\n", i,j,&arr[i][j]);
}
}
return 0;
}
也就是说,我们的二维数组在内存中也是连续存放的
你可以把二维数组的每个元素理解为一个一维数组,
比如第一行是一个一维数组;第二行是一个一维数组…
数组下标是有范围限制的,比如arr[3],这个数组下标范围是[0,2]
数组下标规定是从0开始,如果数组有n个元素,那么最后一个元素下标n-1
所有的数组,如果下标小于0,或者大于n-1,就构成了数组越界访问
C语言本身不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错不代表程序就是正确的,所以程序员写代码的时候,应该自己做一下越界检查。
int main()
{
int arr[5] = { 1,2,3,4,5 };
int i = 0;
for (i = 0;i <= 5;i++)//越界访问了下标5
{
printf("%d ", arr[i]);
}
return 0;
}
如上图,最后一个数就是我们越界访问的。虽然编译器没有报错,但是越界访问是很严重的问题。
举个例子:电脑一些程序正在用一块空间,你突然越界访问了那块空间,可能访问,看一下没有啥问题,但是一旦你代码中有一些赋值或者改变了那块空间的某些东西,你电脑很容易就崩溃了!
往往我们在写代码的时候,会将数组作为参数传个函数,比如:我要实现一个冒泡排序(这里要讲算法思想)函数将一个整形数组排序。 那我们将会这样使用该函数:
//我们以冒泡排序(升序)为例
//冒泡排序核心思想:两两相邻的元素进行比较
void sort(int arr[],int sz)//这里参数int arr[]本质上和int* arr是一样的,都是指针
{//所以这里参数也可以写int* arr,只不过int arr[]更容易让c语言新手理解
int i = 0;//冒泡排序的趟数
for (int i = 0;i < sz - 1;i++)
{
//一趟冒泡排序的过程
int j = 0;
for (j = 0;j < sz-1-i;j++)
{
if (arr[j] > arr[j + 1])
{
//交换
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 1,321,32,2,6,8,65,2,77 };
int sz = sizeof(arr) / sizeof(arr[0]);
//数组名单独放sizeof内部时,这时表示的是整个数组,而不是数组首元素地址
sort(arr,sz);
//数组名传参本质传的是首元素地址(是一个指针)
for (int i = 0;i < sz;i++)
{
printf("%d ", arr[i]);
}
return 0;
}
特别注意:数组名在进行函数传参的时候,就是传的数组首元素地址(指针)
但是sizeof(数组名),传的是完整数组,不是首元素地址。
注:如果数组名作为函数参数传递,我们也可以通过数组名(指针)来打印数组元素
比如我们这里把main函数里面的打印部分抽离出来,做成一个函数
//我们以冒泡排序(升序)为例
//冒泡排序核心思想:两两相邻的元素进行比较
void sort(int arr[],int sz)//这里参数int arr[]本质上和int* arr是一样的,都是指针
{
int i = 0;//冒泡排序的趟数
for (int i = 0;i < sz - 1;i++)
{
//一趟冒泡排序的过程
int j = 0;
for (j = 0;j < sz-1-i;j++)
{
if (arr[j] > arr[j + 1])
{
//交换
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
void print1(int* p,int sz)
{
//打印法1
for (int i = 0;i < sz;i++)
{
printf("%d ", *p);
p++;
//上面两行也可以简写成printf("%d ", *(p+i));
}
printf("\n");
}
void print2(int* p, int sz)
{
//打印法2
//我们之前说过数组名arr作为参数,int arr[]=int *p
//那么我们也可以把p看成数组名
for (int i = 0;i < sz;i++)
{
printf("%d ", p[i]);
}
}
int main()
{
int arr[] = { 1,321,32,2,6,8,65,2,77 };
int sz = sizeof(arr) / sizeof(arr[0]);
sort(arr,sz);//数组名传参本质传的是首元素地址(是一个指针)
print1(arr, sz);
print2(arr, sz);
return 0;
}
注:冒泡排序的一些改进(非数组知识点)
我们上面的冒泡排序假如遇到了1 2 3 4 5 6这样原本就是升序的数组,你还要对他一趟一趟的测试?
我们可以置一个flag,如果跑了一趟,数组元素一个也没有发生交换,说明是已经有序了,后续就不用再来排序了。改进代码如下:
void sort(int arr[],int sz)//这里参数int arr[]本质上和int* arr是一样的,都是指针
{
int i = 0;//冒泡排序的趟数
for (int i = 0;i < sz - 1;i++)
{
//一趟冒泡排序的过程
int j = 0;
int flag = 1;//假设已经有序
for (j = 0;j < sz-1-i;j++)
{
if (arr[j] > arr[j + 1])
{
//交换
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
另外还有数组名作为函数参数:是首元素地址(有两个例外)
int main()
{
int arr[10] = { 1,2,3,4,5,6 };
printf("%p\n", arr);//数组首元素地址
printf("%p\n", &arr[0]);//数组首元素地址
printf("%p\n", &arr);//数组地址
return 0;
}
数组首元素地址和数组地址打印出来是一样的,但是本质是不同的,我们来看下面的代码示例
int main()
{
int arr[10] = { 1,2,3,4,5,6 };
printf("%p\n", arr);//数组首元素地址
printf("%p\n", &arr[0]);//数组首元素地址
printf("%p\n", &arr);//数组地址
printf("\n");
printf("%p\n", arr+1);//数组首元素地址+1
printf("%p\n", &arr[0]+1);//数组首元素地址+1
printf("%p\n", &arr+1);//数组地址+1
return 0;
}
我们知道数组首元素地址+1,跳过4字节(一个整形),那数组地址+1呢?
我们用计算机可以算一下十六进制的CC-A4=28,28转换成十进制是40,为啥是40呢?
因为我们int arr[10],数组总大小为40字节,我们数组地址+1跳到的是下一个数组地址
数组名作为参数的两个例外
1. sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。 2. &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。本文介绍了一维数组、二维数组的使用,及在内存空间中的存储,数组越界相关知识点
另外还有数组名作为函数参数:是首元素地址(有两个例外)
本文的冒泡排序改进读者们也可以研究一下,对将来的数据结构排序也是一种铺垫