7.【C语言详解】数组
一维数组的创建和初始化
数组的创建
数组是一组相同类型元素的集合。
数组的创建方式:
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[20];
注:数组创建,在C99标准之前, [] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念。
数组的初始化
数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
看代码:
int arr1[10] = {1,2,3};
int arr2[] = {1,2,3,4};
int arr3[5] = {1,2,3,4,5};
char arr4[3] = {'a',98, 'c'};
char arr5[] = {'a','b','c'};
char arr6[] = "abcdef";
初始化值一般用大括号括起来并且用逗号隔开,并且初始化给定数组具体的初始化值,编译器会自动计算数组大小,并不需要手动指定数组的大小。
但是对于char类型的数组,稍微有些不同.
char arr1[] = "abc";
char arr2[3] = {'a','b','c'};
第一种方式:char[]类型可以直接使用字符串初始化,会自动拷贝字符串的每个字符放入到数组中并且会加上末尾隐藏的'\0'。
第二种则是常规情况下数组的初始化方式。
一维数组的使用
对于数组的使用我们之前介绍了一个操作符: [] ,下标引用操作符。它其实就数组访问的操作符。
我们来看代码:
int main()
{
int arr[10] = {0};//数组的不完全初始化
//计算数组的元素个数
int sz = sizeof(arr)/sizeof(arr[0]);
//对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以:
int i = 0;//做下标
for(i=0; i<10; i++)//这里写10,好不好?
{
arr[i] = i;
}
//输出数组的内容
for(i=0; i<10; ++i)
{
printf("%d ", arr[i]);
}
return 0;
}
总结:
- 数组是使用下标来访问的,下标是从0开始。
- 数组的大小可以通过计算得到。
- 一般情况下遍历数组我们使用左闭右开区间。
计算数组元素个数
int arr[19];
int size = sizeof(arr) / sizeof(arr[0]);
一维数组在内存中的存储
接下来我们探讨数组在内存中的存储。
int main()
{
int arr[10] = { 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]);
}
return 0;
}
仔细观察输出的结果,我们知道,随着数组下标的增长,元素的地址,也在有规律的递增。
由此可以得出结论:数组在内存中是连续存放的。
注意:下标是从0开始。
二维数组的创建和初始化
二维数组的创建
//数组创建
int arr[3][4];
char arr[3][5];
double arr[2][4];
元素类型 + 行数 + 列数
二维数组的初始化
//数组初始化
int arr[3][4] = {1,2,3,4};
int arr[3][4] = {{1,2},{4,5}};
int arr[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略
- 可以直接按照一维数组的方式去初始化,因为我们指定了列数,则每一行都多少个元素,第一行元素放满后会自动往第二行放元素,当前行不足的补0.
- 把每一行看作一个一维数组,可以分别指定每一行的元素数据,每一行的元素使用大括号括起来,每一行不足的补0.
- 可以不指定行数,但一定要指定列数,仅仅知道行数无法推断出二维数组的列数,但是通过二维数组的列数和元素个数可以推导出元素的行数。
行可以省略,列不能!
二维数组的使用
二维数组的使用也是通过下标的方式。
#include <stdio.h>
int main()
{
int arr[3][4] = { 0 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
arr[i][j] = i * 4 + j;
}
}
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
}
return 0;
}
二维数组在内存中的存储
一维数组在内存中是线性存储的那么二维呢?
这里我们尝试打印出每个元素的地址。
#include <stdio.h>
int main()
{
int arr[3][4];
int i = 0;
for(i=0; i<3; i++)
{
int j = 0;
for(j=0; j<4; j++)
{
printf("&arr[%d][%d] = %p\n", i, j,&arr[i][j]);
}
}
return 0;
}
输出:
可以看到每个元素的地址都是相差4个字节,即一个整形的大小,很显然虽说逻辑结构叫二维数组,但是其的物理结构仍然是线性的。

因此我们可以使用另一个方式遍历二维数组
int main()
{
int arr[3][4] = {0};
int* p = &arr[0][0];
for(int i = 0; i < 3 * 4; i++)
{
printf("%d ", p[i]);
}
return 0;
}
数组越界
顾名思义,超出这个数组权限范围就叫数组越界。
数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,通常对于数组的越界访问,编译器是抽查,像下标为 -1 , n , n + 1,这样的位置才会被编译器检测到,所以一定要做好自己数组越界的检查。
一维数组的越界:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
for (i = 0; i <= 10; i++)//等于10时实际访问的是第11个元素,即越界了。
{
printf("%d\n", *(arr + i));
}
return 0;
}
当然,与一维数组一样,二维数组也存在越界问题,注意行和列的个数和下标关系。
数组作为函数参数
写代码的时候我们常常需要对一个数组进行操作,会对函数进行数组的传参,那是如何进行的?
例如:我们将一个数组排序。
//错误例子
void bubble_sort(int arr[])
{
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz - 1; ++i)
{
int flag = 0;
int j = 0;
for (j = 0; j < sz - 1 - i; ++j)
{
if (arr[j] > arr[j + 1])
{
flag = 1;
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
if (flag == 0) //说明每一个元素都是前一个小于后一个,直接退出。
break;
}
}
int main()
{
int arr[10] = { 9,4,6,2,6,1,3,7,8 ,11 };
bubble_sort(arr,10);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
这样显然不能排序,因为形参虽然写的是数组的形式,但是本质上是一个int的指针,通过sizeof(arr)/ sizeof(arr[0])方法计算元素个数是行不通的。
数组名是什么
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;
}
上面三个值中arr 和 &arr[0]的值是相同的,并且*arr的值与arr[0]相同。
结论:数组名是首元素地址。
如果数组名是首元素地址,那么:
int arr[10] = {0};
printf("%d\n", sizeof(arr));
为什么输出的结果是:40?
补充特别说明:
- sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
- &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
除此1,2两种情况之外,所有的数组名都表示数组首元素的地址。
当数组传参的时候,实际上只是把数组的首元素的地址传递过去了。
所以即使在函数形式参数部分写成数组的形式: int arr[] 表示的依然是一个指针: int *arr 。
那么,函数内部的 sizeof(arr) 结果是4。
知道错误的原因了,进行一下小小修改,就可以成功排序了
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; ++i)
{
int flag = 0;
int j = 0;
for (j = 0; j < sz - 1 - i; ++j)
{
if (arr[j] > arr[j + 1])
{
flag = 1;
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
if (flag == 0)
break;
}
}
int main()
{
int arr[10] = { 9,4,6,2,6,1,3,7,8 ,11 };
bubble_sort(arr,10);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
输出:

二维数组和一维数组的关系
二维数组中,例如:
int main()
{
int arr[][3] = { {1},{3},{5,6},{2,3} };
printf("%p %p %p", arr, arr[0], arr[0][0]);
return 0;
}
假如我们想访问第一行第一列的元素我们通常会使用arr[0] [0]去访问。
而我们访问一维数组的第一个元素通常也是 arr1[0]
都是通过下标去访问元素,如果我们将黄标的部分去掉,则剩下的就是数组名,那么也就可以认为arr[0]和arr1一样都是一个数组的名字,然后通过下标访问实现访问元素。
因此,arr[0]可以看作二维数组的第一行数组名。
输出:

可以看到三者的值是一样的
分析如下:
- arr是二维数组的首元素地址,而arr的每个元素都是一个数组,那么首元素的地址就是第一行数组的地址。
- 第一行数组的地址应该是第一行的首地址,其值等于第一个元素的地址。
- arr[0]是第一行数组。
- 数组名是首元素地址,即第一个元素的地址。
- &arr[0] [0]是二维数组中第一个元素的地址。
- 第一个元素的地址。
当然,1 和 2.3 只是数值上的相同,其意义是不一样的,一个是指向整个数组,另一个指向一个整形。

那么arr既然是一维数组的指针,那么它的类型是什么呢?
通常我们定义一个指针都是:
int* p;
p为变量名,*则表示p是一个指针,剩下的就是指针指向的类型(即解引用后的类型)。
我们照猫画虎试着写一写数组的指针。
定义变量名p
*p表示为一个指针
之前已经讲过,一个元素为int的数组为int []类型
而p指向的就是int []
那么数组指针就是 int(*p)[];
总结:数组注意不要越界访问,数组传参时还要传入元素个数。
补充:在数组作为函数形式参数时,若用const修饰指针变量,则只能写成指针的形式。
像const int arr[] 或者 int const arr[]这里的const修饰的都是数组元素,表示数组中元素的内容不可变。
牢记:const修饰整个数组元素。
技巧:
arr[]还差一个元素类型来修饰。
arr[]的每个元素类型是 const int --》const int arr[];
const修饰指针:
const int* p;//修饰*p
int const* p;//修饰*p
int* const p;//修饰p
const int** p;//修饰**p
int const ** p;//修饰**p
int *const* p;//修饰*p
int ** const p;//修饰p

浙公网安备 33010602011771号