一、一维数组
1.1 一维数组的创建与初始化
1.1.1 数组的创建
数组是一组相同类型元素的集合。数组的创建形式为:
type_t arr_name [const_n]
//type_t 数组的元素类型
//const_n常量表达式,指定数组的大小
基于此,我们可以创建相关数组:
//形式1
int arr1[3];
int arr2[2+4]
char arr3[5];
int arr[];//err 没有指定数组的大小,换句话说,数组怎么能不指定大小呢?
//在创建数组的时候,括号内必须指定大小,除非进行初始化。
//此外,在数组传参时,作为形参也可以不指定,但在形参部分,已经不再是数组,而是指针。
//形式2
#define X 4
int arr4[X];
针对形式2,如果我们不采用宏定义,而是采用赋值的方法,在函数中以局部变量或全局变量的形式令int X = 4,该方法是否正确呢?
C99之前,数组只能是常量指定大小;
C99之后,引入了变长数组的概念,数组的大小是可以用变量指定的;
但是VS2022、2019是不支持C99的变长数组的。
当我们在使用变长数组时,需要注意,变长数组是不能初始化的。
#include<stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
int arr[n];//不能设置为int arr[n]={0};
int i = 0;
for (i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}
for (i = 0; i < n; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
1.1.2 数组的初始化
(1)完全初始化
定义数组时,所有元素都被赋初值。
int arr1[5] = { 1,2,3,4,5 };
(2)不完全初始化
int arr2[5] = { 1,2,3 };
值得注意的是,“不完全初始化”和“不初始化”,以上式为例,不完全初始化表示arr[0]、arr[1]和arr[2]分别被初始化为1,2,3,而后边七个元素没有被初始化,未被初始化的元素自动赋值为0;不初始化,即仅定义为“int arr[10];”,数组的各个元素就不再是0。
(3)不指定数组的长度
如果数组中所有元素均被初始化,那么就可以不指定数组的长度,此时,数组的长度就是元素的个数。
int arr3[] = { 1,2,3 };
(4)字符数组
char arr1[] = { 'a','b','c' };
char arr2[] = "abc";
char arr3[3] = "abc";
char arr4[10] = "abc";
由图可知,系统为arr1和arr2分别分配了3个和4个空间,arr2相较于arr1,字符串最后是有“\0”的。同样的,arr3的字符串中本应该是有“\0”的,但是由于已经指定了arr3的长度,因此“\0”被丢掉了。我们采用strlen函数计算上述四个字符数组的长度。
1.2 一维数组的使用
[ ],下标引用操作符,用来访问数组的元素。例如:
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
//0 1 2 3 4 5 6 7 8 9
//printf("%d", arr[6]);
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
sizeof用来计算数据类型和表达式长度的计算符,返回一个变量或类型的大小,单位是byte。
sizeof(arr)计算的是整个数组的大小;sizeof(arr[0])计算的是首元素的大小。
1.3 一维数组在内存中的储存
数组在内存中是连续存放的。我们可以通过代码证明:
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = 0;
int i = 0;
sz = sizeof(arr)/sizeof(arr[0]);
for(i = 0; i < sz; i++)
{
printf("&arr[%d] = %p", &arr[i]);
}
return 0;
}
数组在内存中是连续存放的;
随着下标的增长,地址是由低到高变化的;
这就意味着,只要拿到首元素地址,就可以通过地址偏移找到其他元素,为指针访问数组提供很大便利。
二、二维数组
2.1 二维数组的创建与初始化
对于二维数组,如果初始化了,对于行数是可以省略的,但是列不能省略。
int main()
{
int arr[3][5] = { 0 };//3行5列
int arr2[4][10] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };//没有大括号,肯定是把一行放满,再去存放下一行
int arr2[4][10] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
int arr3[4][10] = { {1,2},{2,3,4},{5,5,5} };//第四行全是0
int arr4[][10] = { {1,2},{2,3,4},{5,5,5} };//行省略,只有3行
return 0;
}
2.2 二维数组的使用
与一维数组相同,二维数组也是通过下标的形式进行访问。证明代码如下:
#include<stdio.h>
int main()
{
int arr[3][5] = { {1,2},{4,5},{6,7,8} };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
2.3 二维数组在内存中的储存
#include<stdio.h>
int main()
{
int arr[3][5] = { {1,2},{4,5},{6,7,8} };//1 2 0 0 0 4 5 0 0 0 6 7 8 0 0
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("&arr[%d][%d] = %p\n",i,j, &arr[i][j]);
}
printf("\n");
}
return 0;
}
由此可知:
二位数组在内存中也是连续存放的;
二维数组其实是一维数组的数组;
arr[0]是第一组数组的数组名,arr[1]、arr[2]是第二、三组数组的数组名。
三、数组越界
由1.1.2可知,数组首元素的下标为0,并且,数组的下标规定了数组的大小。所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。例如:
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5 };//0~9
int i = 0;
for (i = 0; i <= 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
因此,数组越界时,编译器没发现不报错,但并不代表代码是正确的,因此要自行检查下标有没有越界。
四、数组作为函数参数
数组也会作为参数传递给函数。例如,冒泡排序就是对一个数组进行多次的循环与遍历,从而实现数组内各个元素的有效排序。
代码如下:
#include<stdio.h>
void sort(int arr[])//形参和实参格式要一样
{
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (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[9] = { 9,8,7,6,5,4,3,2,1 };
//写个函数对arr进行排序,排序为升序
sort(arr);//数组传参
//冒泡排序
//两两相邻的元素进行比较
int i = 0;
for (i = 0; i < 9; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
运行代码我们发现,数组并没有完成排序工作。可是我们确实是按照逻辑思路,一步一步的向下写代码的呀。
我们需要对照成程序代码进行逐步调试。经过调试发现,sz的值是1。导致了程序进入sort函数后并没有继续进入循环语句。难道我们在数组传参时没有把整个数组都传递过去?因此,我们需要知道,数组传参,传递的是什么?
数组名就是地址,所以之前就经常讲,数组名就是地址,不需要对它取地址。
数组名是地址,那数组名是谁的地址?
——通常说,是数组首元素的地址。
原来,数组传参传递的时首元素的地址,所以在sort函数中计算sizeof(arr),其实是计算指针的大小,而不是整个数组的大小。这里,sizeof(arr)的大小为4或者8个字节。因此就有了sz=1。
综上,我们可以总结:所有遇到的数组名,都是数组首元素的地址,但有两个情况例外。
- sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
对此,我们通过代码进一步验证如下:
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);//数组名
printf("%p\n", &arr[0]);//数组首元素 //两个一样
printf("%p\n", &arr);
printf("%d\n", sizeof(arr));//40
printf("%p\n", arr+1);
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr+1);//70变成了98,相当于加了16进制的28,8*16的0次方+2*16的1次方=40
//所以,&arr[0]+1只是跳过1个元素,&arr+1是跳过整个数组
return 0;
}
所以,我们再次回到冒泡排序,arr这里不是特殊的2种情况,就是数组首元素的地址,那么传过去的就不是数组,代码修正如下:
#include<stdio.h>
void sort(int arr[], int sz)
{
int tmp = 0;
int i = 0;
int j = 0;
for(i = 0; i < sz - 1; i++)
{
for(j = 0; j < sz - 1 - i; j++)
{
if(arr[j] > arr[j+1])
{
tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
int main()
{
int arr[9] = { 9,8,7,6,5,4,3,2,1 };
int sz = 0;
sz = sizeof(arr)/sizeof(arr[0]);
sort(arr, sz);
int i = 0;
for(i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
通过运行,验证了代码的正确性。
最后还有一个小尾巴,我们看这里:void sort(int arr[], int sz)。
既然我们说传递过去的是地址,那么sort函数为什么要用int arr[]来接收呢?
原来,这里看着像数组,但本质上是指针。那此处为什么要写成数组?
因为为了方便理解,数组传参,拿数组接收,传什么形参就写什么。当了解数组就是指针后,改为int*arr也是正确的。即 void sort(int *arr, int sz)。