当有非常多个数据时,我们不能为每一个数据定义一个变量。这时,我们就想能不能有一种方法-----创建一个空间,能够存放多个数据。这种方法就是使用数组
一组相同类型数据的集合称为数组
数组又分为很多种,有一维数组,二维数组,三维数组,多维数组等等。我们用到最多的就是一维数组和二维数组。
一维数组的创建和初始化
创建
Type arr_name[const_n];
- Type - 数组元素类型
- arr_name - 数组名
- const_n - 数组中的元素序号,为常量表达式,指定数组大小,称为下标(切记!数组下标从0开始,而不是从我们熟悉的1开始)
这是一个非常常见的错误。数组元素只能是常量表达式。 不过,有些编译器对于此种情况是不会报错的(比如一些初学者刚开始会使用的devc++)为什么呢?是因为它是C99语法支持的变长数组,即数组大小可以是变量。但是,VS2019是不支持C99的。 最好不用变量
初始化
int a = 1; // 初始化
int a[4] = { 1, 2, 3, 4 }; //完全初始化
int a[10] = { 1, 2, 3, 4, 5 }; //不完全初始化
int a1[ ] = { 1, 2, 3, 4, 5 };
//相当于 int a1[5] = { 1, 2, 3, 4, 5 };
(根据初始化内容来确定数组个数)
//1.
char ch[5] = { 'a','b','c' };
char ch[ ] = { 'a','b','c' };
//2.字符串初始化
char ch3[5] = "abc"; //a b c \0 0
char ch4[ ] = "abc"; //a b c \0
//3.
char ch5[] = "abc";
char ch6[] = { 'a','b','c' };
printf("%s\n", ch5);
printf("%s\n", ch6);
//4.
printf("%d\n", strlen(ch5));
printf("%d\n", strlen(ch6));
- 数组元素的值由
{ }
包围,各个值之间以,
分隔。
1.
2.
3.
4.
// 15为随机值(由第3点可知ch6的\0不知道在哪出现,故打印出随机值)
注意
-
赋值的元素少于数组总体元素的时候,剩余的元素自动初始化为 0
-
对于short、int、long,就是整数 0;
对于char,就是字符 ‘\0’;
对于float、double,就是小数 0.0。 -
ch6数组长度还是3,只是求字符串长度是不确定的。要明确这一点。数组长度和字符串长度是两个不同的概念。
-
给全部元素赋值时,在定义数组时可以不给出数组长度。即
int a1[ ]
-
数组大小可以通过计算得到。
int sz = sizeof(arr) / sizeof(arr[0]);
一维数组在内存中连续存储
补充知识 -> 敲黑板!(〃‘▽’〃)
1.%p - 按地址的格式打印 - 十六进制的打印
2.数组名是首元素的地址
打印int a[4]
数组
下面画出int a[4]
在内存中存储情形
- 数组是一个整体,它的内存是连续的;也就是说,数组元素之间是相互挨着的,彼此之间没有一点点缝隙。
- 随着数组下标的增长,数组地址是从低到高变化的。
- 连续的内存为指针操作(通过指针来访问数组元素)提供了便利
二维数组的创建和初始化
创建
dataType arrayName[length1][length2];
- 和一维数组形式相似,比如
int arr[3][4]
我们可以将int arr[3][4]看作一个三行四列的矩阵, 一共有 3×4=12 个元素。每个元素的类型都是整型。 - 和一维数组一样,二维数组行,列下标也是从0开始的。
初始化
int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };//完全初始化
int arr[3][4] = { 1, 2, 3, 4, 5, 6, 7 };//不完全初始化
int arr[3][4] = { {1,2},{3,4},{5,6} };//按行分段赋值
int arr[ ][4] = { {1,2},{3,4},{5,6} };//
- 完全初始化
- 不完全初始化 - 未赋值的元素会自动补0(如果是字符数组,其实是补\0)
- 按行分段赋值 ,赋值后各元素的值为
- 二维数组下标中,有几行是可以省略的,但是一行有几个元素是不可以省略的。为什么呢?我们等下来揭晓ヽ(^_−)ノ
注意
观察下面这张图片,你发现了什么?
没错!你说对了!
- 二维数组在内存中是连续存放的。即使是跨行,也是连续的。为什么呢?因为二维数组的形式是我们假想出来的。它真正的形式其实也是按行排列的。
- 这也解答了上面为什么一行有几个元素是不能省略的这一问题。因为如果元素个数不能确定,第一行无法确定,那么我们就无法确定第二行从哪里开始,最终导致数组长度无法确定。
- 二维数组可以看作由几个一维数组组成,还是int arr[3][4],我们可以用arr[0]表示第一行,arr[1]表示第二行,arr[2]表示第三行。
数组作为函数参数
有时候,我们在写代码时要将数组作为参数传给函数。相信大家都听说过冒泡排序,在冒泡排序中,我们就要用到传参。
下面给出一张动图来介绍一下冒泡排序
(该图片来源于网络)
冒泡排序的思想
- 两两相邻,元素比较,交换。
- 假如有n个数字,那么进行比较的趟数就有n-1趟。
下面是代码实现
void bubble_sort(int arr[],int sz)
{
int i = 0;
int t = 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])
{
t = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = t;
}
}
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//升序 - 冒泡排序
bubble_sort(arr,sz);
return 0;
}
有些同学可能会把数组长度的计算放在bubble_aort 函数里面实现,这是不正确的。我们要明白,数组名传参只是把首元素的地址传过去。bubble_aort 函数接收到的只是4个字节,因此在bubble_aort 函数内求到的数组长度是错误的。
那么我们平时所说“数组名传参只是把首元素的地址传过去”这一说法是否正确?,接下来我们验证一下。
int main()
{
int arr[10] = { 0 };
printf("%p\n", &arr[0]);
printf("%p\n", arr);
return 0;
}
很显然,这一说法是正确的。但是,有两个例外
- sizeof(数组名) - 数组名表示整个数组 - 计算的是整个数组的大小 - 单位是字节
- &数组名 - 数组名表示整个数组 - 取出的是整个数组的地址
当然,它们的意义肯定是不相同的。接下来我们来验证一下
int main()
{
int arr[10] = { 0 };
printf("%p\n", &arr);
printf("%p\n", &arr+1);//数组加1
printf("%p\n", arr);
printf("%p\n", arr+1);//数组首元素加1
return 0;
}
可见,数组加1和数组首元素加1其意义是不同的。
这样的代码显然不够高效,我们还可以再优化一下。当我们发现已经完成排序时,我们不必再进行后面的排序,避免浪费时间。
优化后:
void bubble_sort(int arr[],int sz)
{
int i = 0;
int t = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
int flag = 1;
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
t = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = t;
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//升序 - 冒泡排序
bubble_sort(arr,sz);
return 0;
}
形参的两种形式
- 数组形式
- 指针形式
//一维数组
//数组形式
void test1(int arr[10] {}
void test1(int arr[]) {}
//指针形式
void test1(int *arr) {}
//二维数组
//数组形式
void test2(char arr[3][5] {}
void test2(char arr[][5]) {}
//指针形式 - 暂不介绍
数组的应用
编写一个实现三子棋的小程序。内容详见下一篇。