数组详解
1. 前言🔶
2. 一维数组🔶
2.1 一维数组的创建🔷
type_t arr_name [const_n];
//type_t 是指数组的元素类型
//arr_name是数组名,是自己取的
//const_n 是一个常量表达式
比如我们可以依次定义一个整型数组,一个浮点型数组,一个字符型数组:
int a[10];//可以存放十个整型的数组
float b[20];//可以存放二十个整型的数组
char c[10];
值得注意的是,方括号 [ ] 里面必须是常量表达式,假如我们这样初始化数组就会出问题:
int size=10;
int a[size];
- 注:数组创建,在C99标准之前, [] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念,数组的大小可以使用变量指定,但是数组不能初始化。
特别的:
vs2022和2019编译器中不支持C99中变长数字组.
2.2 数组的初始化🔷
以下是几种初始化的方式:
int main()
{
int arr1[10] = {1,2,3,4,5,6,7,8,9,10};//完全初始化
int arr2[10] = { 1,2,3 };//不完全初始化,剩余的元素默认都是0
int arr3[10] = { 0 };//不完全初始化,剩余的元素默认都是0
int arr4[] = { 0 };//省略数组的大小,数组必须初始化,数组的大小是根据初始化的内容来确定
int arr5[] = { 1,2,3 };//前三个元素为1 2 3,后面两个元素默认为0
int arr6[];//错误的写法
char arr1[] = "abc";
char arr2[] = {'a', 'b', 'c'};
char arr3[] = { 'a', 98, 'c' };
return 0;
}
我们要区分下面这两种初始化数组的方式的区别:
char a[]="abc";
char b[]={'a','b','c'};
画个内存图理解一下:
2.3 一维数组的使用🔷
对于数组的使用我们之前介绍了一个操作符: [] ,下标引用操作符。它其实就数组访问的操作符。我们来看代码:
(下标从0开始!)
#include <stdio.h>
int main()
{
int arr[10] = {0};//数组的不完全初始化
//计算数组的元素个数
int sz = sizeof(arr)/sizeof(arr[0]);
//对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以:
int i = 0;//做下标
for(i=0; i<10; i++)
{
arr[i] = i;
}
//输出数组的内容
for(i=0; i<10; ++i)
{
printf("%d ", arr[i]);
}
return 0;
}
值得注意的是,数组初始化时,方括号[ ]内不允许使用变量只能用常量,但是对数组的使用中,方括号[ ]内是可以为变量的
比如我们来举个例子:
#include<stdio.h>
int main()
{
int a[10] = { 1,2,3,4,5,6,7,8,9,0 };
int x = sizeof(a);//等于40,4*10,一个整型元素四个字节,共10个
printf("%d\n", x);
int y = sizeof(a[0]);//等于4,大小为一个整型的大小.这里你也可以写成a[1].a[2].随便一个数组中的元素就行
printf("%d", y);
return 0;
}
2.4 一维数组在内存中的存储🔷
我们先定义一个整型数组并将它所有元素的地址打印出来:(%p是打印地址)
#include<stdio.h>
int main()
{
int a[10] = { 1,2,3,4,5,6,7,8,9,0 };
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
printf("%p \n", &a[i]);
}
return 0;
}
我们来研究一下数组中元素在内存中是怎么存放的:
我们知道数组在内存中存储时用的是16进制,所以这里我们来找一下这些地址的规律:(只看最后四个)
可以发现,每一个元素之间都相差四个字节,并且我们这里定义的数组是整型数组,每一个元素所占空间刚好也是四个字节,这刚好能说明数组中元素是连续存放的!
我们按照这个编译器打印出的地址再来画一个内存图理解一下:
值得注意的是,不同电脑不同编译器在不同时刻为数组开辟的空间是不一样的,所以你的电脑的地址中不必和我一模一样,只需要查看每个元素之间是不是紧挨着的.
3. 二维数组🔶
3.1 二维数组的创建🔷
//数组创建
int arr[3][4];
char arr[3][5];
double arr[2][4];
和一维数组相似,只不过要多写一个方括号
3.2 二维数组的初始化🔷
有几种初始化的方式:
//数组初始化
int arr[3][4] = {1,2,3,4};//这样初始化代表第一行为1 2 3 4,而2,3行所有元素默认为0
int arr[3][4] = {{1,2},{4,5}};//这种加上大括号的表示第一行为1 2 0 0,第二行为4 5 0 0,第三行默认全部为0.
int arr[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略
- 一种不加大括号的初始化方式就是挨着往后初始化,没有被初始化到的位置默认为0
- 一种是加了大括号的初始化方式就是第一个大括号内代表第一行元素,没有被初始化到的位置,默认为0
- 并且行可以省略但是列不行!
3.3 二维数组的使用🔷
假如我们想挨个打印二维数组中的内容,我们可以这样写:(二维数组的下标也是从0开始)
#include<stdio.h>
int main()
{
int arr[4][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7},{5,6,7,8,9} };
printf("%d\n", arr[2][3]);//打印第三行第四列元素
int i = 0;
//行号
for (i = 0; i < 4; i++)//最外层for循环代表行,i等于0就是第一行,进入第二个循环将第一行所有列打印出来再到第二行
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");//每打印完一列就换一次行
}
return 0;
}
我们就把每一个元素很好的打印出来了:
这里二维数组的使用与一维数组大同小异,直接往下走!
3.4 二维数组在内存中的存储🔷
这里和一维数组一样,我们先创建一个二维数组再将所有元素的地址打印出来:
#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. 数组的越界访问🔶
比如像下面这段代码,当i等于10时数组就越界了,但是你的编译器可能不会给你警告提醒你
#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++)
{
printf("%d\n", arr[i]);//当i等于10的时候,越界访问了
}
return 0;
}
5. 数组作为函数参数🔶
- 一维数组:
void test(int a[])
void test(int a[10])
void test(int* a)//使用方法和上面一样
- 二维数组
void test(int a[3][5])
void test(int a[][5])
void test(int (*a)[5])
void test(int** a)
我们举一个例子来说明一下数组传参有什么坑!
5.1 冒泡排序中数组传参的问题🔷
相信大家对冒泡排序已经不陌生了,我们下面就设计一个冒泡排序函数来讲数组中元素排成升序
#include <stdio.h>
void bubble_sort(int arr[])
{
int sz = sizeof(arr)/sizeof(arr[0]);//这样对吗?
int i = 0;
for(i=0; i<sz-1; i++)
{
int j = 0;
for(j=0; j<sz-i-1; j++)
{
if(arr[j] > arr[j+1])
{
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
int main()
{
int arr[] = {3,1,7,5,8,9,0,2,4,6};
bubble_sort(arr);//是否可以正常排序?
int i = 0;
for(i=0; i<sizeof(arr)/sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
return 0;
}
当我们运行查看结果时,会发现我们的升序并没有排好,当我们去调试程序时会发现:
所以这个地方是错误的写法,我们将它修改一下:
void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{
int i = 0;
for(i=0; i<sz-1; i++)
{
int j = 0;
for(j=0; j<sz-i-1; j++)
{
if(arr[j] > arr[j+1])
{
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
int main()
{
int arr[] = {3,1,7,5,8,9,0,2,4,6};
int sz = sizeof(arr)/sizeof(arr[0]);
bubble_sort(arr, sz);//是否可以正常排序?
for(i=0; i<sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
将数组的大小做为函数的参数传入函数中就可以解决这个问题了🫵🫵
5.2 数组名到底是什么?🔷
我们用一段打印地址的代码来为大家阐述:
#include <stdio.h>
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;
}
但是,这里有两个特例中,数组名不是首元素地址:
-
当数组名放在sizeof当中时,这时数组名代表整个数组的地址
-
当数组名前面加一个取地址符号的,这时代表整个数组的地址