C语言浅理解——数组
数组
学生的学籍号码,球场上运动员身上的号码······在生活中我们经常遇到相同类型的事物聚集在一起,比起逐个去叫他们的名字,我想大家更喜欢简单点统称为“号码”,更加简单明了。
为了提高效率而把具有相同相同类型的数据有序地组织起来的一种形式就称为——数组。
简单来说,数组是一组相同类型的元素的集合。
ps:笔者在本文中所有程序都使用VS2019进行编写。
ps:新人的第一篇长博客,排版可能稍许乱,文字表达可能不是那么清晰,欢迎各位哥哥姐姐指点,非常感谢!
一维数组
数组的声明
数组的声明是通过指定元素类型、数组名、数组大小来进行的。
注意:[]
中的元素个数必须是常量。
语法格式:
//type_t 元素类型
//arr_name 数组名
//const_size 常量表达式,用于指定数组的大小
type_t arr_name[const_size];
实例:
//这里声明的数组arr,是一个元素类型为int类型、元素个数为5的一维数组。
int arr[5];
数组初始化
我们知道在声明变量的时候,除了的确没有必要的情况下,都需要对变量进行初始化。同样的对数组也是。
数组的初始化是指,在创建数组的同时给数组中的元素赋以一些合理的初始值。
初始化
数组的初始化的方式分为很多种,下面将一一列举:
- 完全初始化
//为数组中所有元素的都进行初始化。
int arr[5] = { 1,2,3,4,5 };
char arr1[3] = { 'a','b','c' };
char arr2[4] = "abc";
注意:上述代码块中,arr1和arr2在内存中的存储是不一样的。
arr1在内存中存储为:a b c
arr2在内存中存储为:a b c
- 不完全初始化
//为数组的部分元素进行初始化
//未主动进行初始化赋值的元素默认初始化为0
int arr[5] = { 1,2 };// 1,2,0,0,0
- 不指定数组元素个数
//数组在创建时,如果未指定数组元素个数,数组会根据初始值的个数自动进行设定。
int arr[ ] = { 1,2,3,4,5 }; //元素个数省略不写,自动认为是5
- 数组元素全初始为0
int arr[5] = { 0 }; // 0,0,0,0,0,
注意
- 初始值过多
//当数组进行初始化,初始值的个数超过数组的元素个数的时候,程序会发生错误。
int arr[5] = { 1,2,3,4,5,6 }; // 错误:初始值设定项值太多
- 赋值语句进行初始化
//不可以通过赋值语句进行初始化
int arr[3];
arr = { 1,2,3 }; //这种写法是不能出现的,并且是错误的
数组的使用
下标操作符
在学习对数组的使用之前,我们需要先了解一个操作符:[]
,下标引用操作符,或者称为数组访问操作符都是可以的。
[]
中的操作数称为 下标。说到这里我想我们需要先讲解一下下标。
下标
数组中的每个元素是有下标的,下标从0开始向后依次加一。
下标表示的是该元素是首元素之后的第几个元素而不是数组中的第几个元素。
0下标对应数组的第一个元素,也就是数组首元素。
数组中第二个元素的下标为1,最后一个元素的下标为n-1(元素个数-1)。
我们可以通过下标和下标引用操作符访问(读取)数组中的某个元素。
arr[2]; //下标为2,表示arr[2]是首元素之后的第2个元素,也就是数组的第3个元素。而不是数组的第二个元素。
在数组声明过程中使用的`[]`仅仅是一个分隔符,而防问各个元素时使用的`[]`则是运算符——下标引用操作符。
访问数组
数组内的各个元素都是可以自由访问(读取)的。通过下标引用操作符和下标,我们可以读取或者修改数组的某个元素。
实例:
#include<stdio.h>
int main()
{
int arr[10] = { 0 }; //把数组arr中的十个元素全部初始化为0
int i = 0;
//for循环,对数组内容进行赋值,数组使用下标进行访问
//arr数组,下标从0开始,最后一个元素的下标为9
for (i = 0; i < 10; i++)
{
scanf("%d", &arr[i]);
}
//输出数组内容
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
//换行
printf("n");
return 0;
}
在上述实例中我们可以一眼看出数组大小与数组最后一个元素下标,当然我们也可以通过计算的方法来得出数组的大小。
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7 };
//sizeof(arr) 求得数组所占内存空间大小(单位字节)
//sizeof(arr[0]) 求得数组一个元素所占内存空间大小
//总大小 / 单个元素大小 = 总元素个数
//总元素个数 - 1 = 数组最后一个元素下标
int size = sizeof(arr) / sizeof(arr[0]);
printf("%dn", size);
return 0;
}
一维数组在内存中的存储
- 同一类型的对象集中在一起,在内存上排列成一条直线这就是数组。
随着数组下标的增长地址是从低地址到高地址变化的。
我们可以通过一段程序来证明这一事实。
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int i = 0;
for (i = 0; i < 10; i++)
{
// %p 以十六进制的形式
// & 取地址操作符
printf("&arr[%d] = %pn", i, &arr[i]);
}
return 0;
}
上述代码的输出为:
//&arr[0] = 005CFAD4
//&arr[1] = 005CFAD8
//&arr[2] = 005CFADC
//&arr[3] = 005CFAE0
//&arr[4] = 005CFAE4
//&arr[5] = 005CFAE8
//&arr[6] = 005CFAEC
//&arr[7] = 005CFAF0
//&arr[8] = 005CFAF4
//&arr[9] = 005CFAF8
不难看出,每个地址相差4,int类型占4个字节大小,所以可以得出数组在内存中的存储是连续的。在int类型数组下标中体现为,下标每次加一就跳过四个字节。
-
数组名为数组首元素地址
- 由下述结果可以知道,数组名为数组首元素地址。
- 特例:
-
sizeof(数组名)
,这里的数组名是表示整个数组,计算的是整个数组的大小,单位为字节。 -
&
数组名,这里的数组名表示整个数组,&(数组名)
取出的是数组的地址。
-
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
printf("&arr = %pn", arr);//arr数组名
printf("&arr[0] = %pn", &arr[0]);//arr[0] 数组首元素
//计算整个arr数组的大小,单位为字节
//一个int类型所占字节大小为4,数组有十个int类型元素,所以为40
printf("sizeof(arr) = %dn", sizeof(arr));
//&arr,取的是整个数组的地址
printf("&arr = %pn", &arr);
return 0;
}
输出结果为:
&arr = 009BFA24
&arr[0] = 009BFA24
sizeof(arr) = 40
&arr = 009BFA24
多维数组
多维数组,就是多个数组集合在一起形成的数组,即元素本身就是数组的数组。
多维数组
在一维数组中,一维数组的元素都是int类型,double类型等单一的数据类型。实际上数组的元素也可以是数组本身。
以一维数组作为元素的数组称为二维数组,以二维数组作为元素的数组是三维数组,以此类推,也可以形成更高维数的数组。我们把二维数组以上的数组,称为多维数组。
二维数组
二维数组的声明
二维数组的声明和一维数组相似,只是相对一维数组多了一个参数。
语法格式:
type_t arr_name[Row][Line];
实例:
//这是一个数组名为 demo ,数组类型为int,具有三行四列数据的数组。
int demo[3][4];
相信大家都知道Excel表格,在Excel表格中分为行和列,在二维数组中,我们也可以把一个二维数组看作是一个Excel表格,二维数组的第一个参数看为行数,第二个参数,看为列数。
行标/列标 | 0 | 1 | 2 | 3 |
---|---|---|---|---|
0 | 1 | 2 | 3 | 4 |
1 | 5 | 6 | 7 | 8 |
2 | 10 | 11 | 12 | 13 |
//arr[0][0] = 1
//arr[0][1] = 2
//arr[0][2] = 3
//arr[0][3] = 4
//arr[1][0] = 5
//arr[1][1] = 6
//arr[1][2] = 7
//arr[1][3] = 8
//arr[2][0] = 9
//arr[2][1] = 10
//arr[2][2] = 11
//arr[2][3] = 12
二维数组初始化
- 完全初始化
//类似于一维数组初始化,数组进行初始化时,编译器会自动识别数组列参数,例如下例代码三行四列,编译器会把前一个数1234放在第一行,5678放在第二行
int demo[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
//下面这种初始化方式,我们可以想象成先创建好三个数组,把这三个数组依次放在demo1数组中。
int demo1[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
//所有元素初始化为0
int demo2[3][4] = { 0 };
- 不完全初始化
//运行之后,数组中存放的数值为:1,2,3,4,5,6,0,0,0,0,0,0
int demo[3][4] = { 1,2,3,4,5,6 };
//运行之后,数组中存放的数值为:1,2,3,4,5,6,7,8,0,0,0,0
int demo1[3][4] = { {1,2,3,4},{5,6,7,8}};
- 省略行初始化
int demo[][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
当我们省略行参初始化时,编译器会根据所初始化的值的个数以及所声明数组的列参,自动进行换行,当满足列参限定个数时,如果还有初始值未分配会自动行数加一。
可以省略行参数进行数组初始化,但是一定不可以省略列进行初始化。因为如果没有声明数组列参,编译器不知道一行可以存放几个值,会产生错误。
二维数组使用
二维数组的使用与一维数组基本相同,也是通过下标和下标引用操作符进行使用。只是相当于多了一行一维数组而已。我们可以直接通过实例来了解。
#include<stdio.h>
int main()
{
int arr[3][4] = { 1,2,3,4,5,6,7,8,9,6,6,6 };
int i = 0;
int j = 0;
//arr[i][j]
//i 表示行标,0 <= i < 3
//j 表示列标,0 <= i < 4
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
printf("n");
}
return 0;
}
输出结果:
//1 2 3 4
//5 6 7 8
//9 6 6 6
二维数组在内存中的存储
和一维数组相同,二维数组中的每个元素也都是连续的,即使他们不在“同一行”。
#include<stdio.h>
int main()
{
int arr[3][4] = { 1,2,3,4,5,6,7,8,9,6,6,6 };
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("arr[%d][%d] = %p n", i, j, &arr[i][j]);
}
printf("n");
}
return 0;
}
输出结果:
//arr[0][0] = 0073F710
//arr[0][1] = 0073F714
//arr[0][2] = 0073F718
//arr[0][3] = 0073F71C
//
//arr[1][0] = 0073F720
//arr[1][1] = 0073F724
//arr[1][2] = 0073F728
//arr[1][3] = 0073F72C
//
//arr[2][0] = 0073F730
//arr[2][1] = 0073F734
//arr[2][2] = 0073F738
//arr[2][3] = 0073F73C
数组越界
数组的下标具有范围限制。
例如一维数组`arr[3];`,数组下标的范围是 0 <= n < 3
数组的下标规定是从0开始,假设数组有n个元素,那么最后一个元素的下标是n-1。所以访问数组时如果下标小于0,或者大于n-1,就会造成数组越界,超出规定的数组合法空间。会出现不可预料的结果。
C语言本身
C语言本身是不对程序中的数组下标做越界检查的,我们所使用的编译器也不一定会报错(笔者使用的vs2019版本并没有对下标越界进行报错)。我们要知道,虽然编译器没有报错,但是并不能说这个程序就是正确的,最后结果可能并不是我们的预期。
为此,在编写代码时,我们应该留意是否越界。
#include<stdio.h>
int main()
{
int arr[3][4] = { 1,2,3,4,5,6,7,8,9,6,6,6 };
int i = 0;
int j = 0;
printf("%dn", arr[4][4]);//越界访问 此程序在vs2019编译器中,不会报错,并且有输出结果
return 0;
}
输出结果:
//22398480
数组作为函数参数
我们在调用函数时,往往需要将一个变量作为参数传给函数,那么将数组作为参数传给函数又是什么样子的呢?下面我们通过一个简单的实例来进行讲解。
实例 求数组元素个数:
猜一下,下面程序中sum1和sum2的结果是多少?
#include<stdio.h>
int Length(int arr[])
{
return sizeof(arr)/sizeof(arr[0]);
}
int main()
{
int arr[] = { 2,1,2,4,5,6,5,6 };
int sum1 = sizeof(arr) / sizeof(arr[0]);
int sum2 = Length(arr);
printf("sum1 = %dn", sum1); //输出结果为 sum1 = 8
printf("sum2 = %dn", sum2); //输出结果为 sum2 = 1
}
会不会有点惊讶?sum1和sum2的结果不一样。
其实当我们在把数组作为参数传给函数时传的是指针而不是数组本身,而这个指针存储的是数组的首地址,也就是数组首元素地址。
所以函数接收的只是一个元素的地址,当用sizeof运算符求个数的时候,结果就是1。
所以在传参时,我们通常在函数外部把数组的元素个数求出再传给函数。
实例 冒泡排序:
#include<stdio.h>
void bubble_sort(int arr[], int size)
{
int i = 0;
int j = 0;
int tmp = 0;
for (i = 0; i < size; i++)
{
for (j = 0; j < size - 1 - i; j++)
{
if (arr[j] > arr[j+1])
{
tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
int main()
{
int i = 0;
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int size = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, size);
for (i = 0; i < size; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
输出结果
//0 1 2 3 4 5 6 7 8 9
ps:新人的第一篇长博客,排版可能稍许乱,文字表达可能不是那么清晰,欢迎各位哥哥姐姐指点,非常感谢!