为什么需要数组?
我们创建少量相同类型变量时,通常会逐个创建,但是当我们创建的相同类型变量越来越多时,逐个创建变量就不太实用了,我们最好一口气创建许多变量,这时引入数组,可以快速方便地创建多个相同类型的变量,大大提升了我们的效率。
一.一维数组
1.概念
什么是数组?
数组是一组相同类型的元素集合
相同类型:全是整形,全是字符型,全是浮点型……
2.创建
数组创建的方式:
type_t arr_name [const_n]
//type_t 是指数组的元素类型
//const_n 是一个常量表达式用来指定数组大小
一般创建方式:
int arr[10];
注意[ ]中是常量表达式
可以用define定义变量n来创建数组:
#define n 10
int main(){
int arr[n] ;
return 0;
}
以下这样的就不行:
int n = 10;
int arr[n];
注:在c99中引入了变长数组的概念,允许数组的大小用变量来指定,若编译器不支持c99中的变长数组,那就不能使用,如:VS2019是不支持变长数组的。
还有个例外:
const int n = 0; //这样是错误的,用const修饰的n不能创建数组
int arr[n] ;
在c语言中,const修饰的变量叫常变量,还是变量。(c++中是常量)
3.初始化
创建的同时赋值
int arr[10]={1,2,3,4,5};//不完全初始化,剩下的元素补为零
int arr[10]={1,2,3 4,5,6,7,8,9,0};//完全初始化
//int arr[10];不初始化数组中赋予随机值,有时编译器会报错不让我们使用
注:变长数组不能初始化。
再来对比这两种创建方式:
int arr1[10] = { 1,2,3,4 };
int arr2[] = { 1,2,3,4 };
调试后,我们发现初始化时[ ]中不给值,数组创建的元素个数会按照我们给的值进行创建。
而我们注明元素个数后,即使我们给的数不够10个,它也会将我们剩下的元素赋为零
int型:(以上都以int为例)
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
char型:
char arr[3] = { 'a','b','c' };
char arr[3] = { 'a',98 ,'c' };//b的阿斯特码值是98,所以这个地方也可以用98
char arr[ ] = "abc";//注意此处数组中有4个元素 a b c \0
4.使用
我们想要精确使用每个元素时,要用[ ]下标引用操作符
每个元素已被标号,注意标号是从0开始,如下图:
我们想打印5时,应引用第4号元素
#include<stdio.h>
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
printf("%d", arr[4]); //arr和4是[ ]的两个操作数
}
数组的元素个数可以通过计算获得 :
#include<stdio.h>
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("%d", sz);
}
这里注意strlen和sizeof求字符串长度
#include<stdio.h>
#include<string.h>
int main() {
char str[] = "abcd";
printf("%d ", sizeof(str));
printf("%d ", strlen(str));
return 0;
}
结果是5和4
原因:
str数组中存放的有'a' 'b' 'c' 'd' '\0'
strlen函数的原理是每检测到一个字符+1,直到检测到'\0'停止,所以不包括‘\0’,结果是4
sizeof计算的是数组中元素的总是,不管是不是'\0',所以结果是5
5.在内存中的存储
接下来,我们要从基层更深入的了解数组每个元素存储的方式
看代码:
#include<stdio.h>
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0;i < sz;i++) {
printf("&arr[%d] = %p\n",i, &arr[i]); //%p--打印地址(16进制)
}
}
结果差4,原因是int型的数组中元素都是int型,元素大小为4个字节,导致他们地址差4,刚好差出一个整形元素
由上我们可以得出:
- 结论1:一维数组在内存中连续存放。
- 结论2:随着数组下标增长,地址是由低到高变化的。
所以,我们也可以应用连续存放的性质,
#include<stdio.h>
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0;i < sz;i++) {
printf("arr[%d] = %d\n",i, *(arr+i));//指针+1跳过1个整形,因为数组是连续存放的
} //所以直接指引到下一个元素
}
同样可以使用每一个元素
二.二维数组
1.创建
创建方式与一维数组大同小异
int arr[3][5];//三行五列的数组
2.初始化
我们先将它们完全初始化:
int main() {
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
return 0;
}
我们发现了二维数组的赋值规律。
不完全初始化:
第一种情况:如果赋6个值,是否将第6个数赋到第二行?
int main() {
int arr[3][5] = { 1,2,3,4,5,6 };
return 0;
}
我们发现,6确实被赋值到第二行,且没有赋值被初始化为0
第二种情况:括号中的括号代表的是什么?
int main() {
int arr[3][5] = { {1,2},{4,5},{5,6} };
return 0;
}
我们发现第一个被括起来的被初始化在第一行,第二个被括起来的被初始化在第二行,第三个被括起来的被初始化在第三行,没被初始化的元素默认为零。
第三种情况:可以省略行,但不能省略列
int main() {
int arr[][5] = { 1,2,3,4,5,6 };
return 0;
}
此处行被省略,但是有列,当满五个数时,第六个数自动被初始化到第二行
这时int arr[ ][5]中[ ]被默认为2
3.使用
与一维数组的使用基本相同,要注意首元素a[0][0],序号是从0开始。
打印二维数组
#include<stdio.h>
int main() {
int arr[3][5] = { 1,2,3,4,5,6 };
int i = 0;
int j = 0;
for (i = 0;i < 3;i++) {
for (j = 0;j < 5;j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
也可改进一下:
#include<stdio.h>
int main() {
int arr[3][5] = { 1,2,3,4,5,6 };
int i = 0;
int j = 0;
for (i = 0;i < sizeof(arr)/sizeof(arr[0]);i++) {//arr[0]将第一行看成一个数组,用总大小除以第一行大小就是行数
for (j = 0;j < sizeof(arr[0])/sizeof(arr[0][0]);j++) {//arr[0]——第一行大小,用第一行大小除以第一个元素大小就是列数
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
这里我们可以将数组的行和列计算出来
4.在内存中的储存
我们将每个数组元素地址打印出来
#include<stdio.h>
int main() {
int arr[3][5] = { 1,2,3,4,5,6 };
int i = 0;
int j = 0;
for (i = 0;i < 3;i++) {
for (j = 0;j < 5;j++) {
printf("&arr[%d][%d] = %p\n",i,j, &arr[i][j]);
}
printf("\n");
}
return 0;
}
观察结果我们发现:像一维数组那样,每个地址相差4,但是第二行的第一个地址与第一行末尾元素地址相差4,说明二维数组在内存中也是连续存放的,其中第一行与第二行也是连续的
二维数组也可以想象成一维数组,第一行数组名arr[0],第二行数组名arr[1],第三行数组名arr[3]
三.数组越界
数组的下标是有范围限制的
数组下标规定从0开始,若有n个元素,那么最后一个元素的下标就是n-1。
如果数组下标小于0,或者大于n-1,就代表数组越界了,但编译器不一定报错,但使用越界的值是导致的后果是不可知的,很可能引起程序错误。
四.数组作为函数参数时的一些问题
数组名就是数组首元素的地址
有2个例外:
- sizeof(数组名),数组名不是数组首元素的地址,数组名表示整个数组,计算的是整个数组的大小
- &数组名,数组名不是数组首元素的地址,数组名表示整个数组,取出的是整个数组的地址
然而,数组名在函数传参给sizeof后时,数组名表示首元素地址
我们来设计一个函数计算数组元素个数
#include<stdio.h>
void arr_size(int arr[]) {
int sz = sizeof(arr) / sizeof(arr[0]);
printf("%d", sz);
}
int main() {
int arr[] = { 3,1,5,2,4,9,8,6,0,7 };
arr_size(arr);
}
结果为1,而不是10
为什么呢?
原因是arr_size传过去的是arr数组的首地址并不是将整个数组的地址传过去,所以sizeof(arr)计算结果为arr首元素地址大小(32位,8个16进制数组,4byte),不是整个数组的大小