携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情
前言
C语言中,数组很重要也比较基础,很多地方都要用到。
本文就来分享一波作者对数组的学习心得与见解。本篇属于第一篇,主要介绍二维数组、数组传参、变长数组和复合字面量的一些内容。
笔者水平有限,难免存在纰漏,欢迎指正交流。
二维数组
二维数组的创建
二维数组可以看成是一维数组的一维数组,如图:
(这里为了方便理解才画成矩阵样式,实际上是一行上连续排列的而非多行的)
可以把诸如arr[0],arr[1],arr[2]看成一维数组名,每一行都是一个带有三个元素的一维数组,而每一行同时又作为一个整体的元素共同构成一个一维数组。
int B[2][3]也就是创建了一个装了两个带有三个int变量的一维数组的数组,如图:
实际上更高维度的数组可以以此类推,比如三维数组就是一个装有若干个二维数组的一维数组。如图:
二维数组的初始化
int arr[3][4] = {1,2,3,4};//未初始化的全部置为0
int arr[3][4] = {{1,2},{4,5}};//里面的一个花括号{ }里的是一个子数组的元素
int arr[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略
更高维的数组以此类推。
二维数组的使用
二维数组的使用也是通过下标的方式。
看代码:
#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;
}
一般来说,二维数组的第一维度看作行,第二维度看作列,整个看成一个矩阵。
比如int arr[3][3],不过实际上并不是以矩阵样式存放到内存的,只是使用的时候模拟成矩阵。
二维数组在内存中的存储
像一维数组一样,这里我们尝试打印二维数组的每个元素的地址看看。
#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;
}
说明什么?说明即使是高维数组,在内存里存放时也是连续存放在一整块内存上的。
实际上在内存空间中是这样存放的:
你会发现arr[3][3]和arr[9]在内存的布局基本一致。
拓展——多维数组空间布局
所有的数组都可以看成“一维数组”。
数组的定义是:具有相同数据元素类型的集合,特征是数组中可以保存任意类型。
那么数组中可以保存数组吗?答案是可以!
在理解上,我们甚至可以理解所有的数组都可以当成"一维数组"!
就二维数组来说,我们认为二维数组,可以被看做“一维数组”,只不过内部“元素”也是一维数组。
那么内部一维数组是在内存中布局是“线性连续且递增”的,多个该一维数组构成另一个“一维数组”,那么整体便也是线性连续且递增的。
所以我们认为:二维数组可以被看作内部元素是一维数组的一维数组。
那么,三维呢?n维呢?以此类推。
n维数组可以被看作内部元素是n-1维数组的一维数组。
比如:
三维数组x可以看成是具有两个二维数组元素的“一维数组”,而二维数组c又可以看成是具有四个一维数组元素的“一维数组”。
二维数组的边界
数组的下标是有范围限制的。
数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。
所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,所以程序员写代码时,最好自己做越界的检查。
(自由的代价是责任)
不确定数组元素个数时可以sizeof(arr) / sizeof(arr[0][0])
二维数组的行和列也可能存在越界,可能是在自己的范围内越界。
我们前面也讲了,二维数组可以看成一维数组的一维数组,那现在就看看arr[0][i]这个一维数组,只有三个元素,要是我们访问arr[0][3]会怎样?会访问到arr[1][0],相当于一维数组arr[0][i]越界了,只不过二维数组在内存中也是连续分布的,所以越界范围还在二维数组之内。
数组传参与数组名
数组传参
数组作为函数参数,本质上传递的是首元素地址,是指针,函数形参是一个对应类型的指针变量。
正是因为数组可以由指针来表示,才有了这么一出。相比于传递一整个数组(形参就要创建相同大小的数组),传递指针显然更加高效和节省空间,所以实参为数组时,只需要写数组名即可,形参可以写成int arr[ ],[ ]里面无须数字,因为不是真的要再创建一个数组,而是接收数组首元素地址,通过指针偏移来访问数组元素,所以写成这样只是能清楚地说明实参是一个数组罢了,还可以直接把形参写成指针如int*arr,都没有问题。
基于上述论断,我们可以发现:
在非main函数内使用sizeof(arr) / sizeof(arr[0])想要求取数组大小就会出现问题,为什么呢?传进函数的arr是指针(在32位平台下是4字节,在64位平台下是8字节)而非数组。
数组名
数组名一般都被认为是数组首元素的地址,但也有两个例外。
sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
&数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
地址就是指针,存放地址(指针)的变量就叫指针变量,而指针有类型,比如&arr[0]和arr都是int型指针,对指针+-整数会使得地址(指针)偏移,注意其单位不是1字节,而是对应类型的字节数,比如&arr[0]+1实际上地址向高位移动了四个字节。
更多请看指针章节:[深入浅出C语言]深析指针(篇二) - 掘金 (juejin.cn)
&数组名与数组名
&arr在数值上与arr和&arr[0]并无差异,但是发生偏移后的结果截然不同,比如&arr+1是跳过一整个数组的空间,而&arr[0]+1和arr+1只跳过了一个整型的空间,其实也跟它们本身的类型有关,arr和&arr[0]是int指针类型,而&arr是int数组指针类型。
二维数组的数组名也表示首元素地址,但是要弄清楚这里的首元素是什么。
前面讲过,二维数组是一维数组的一维数组,它的首元素就是第一个一维数组。
arr[0]是一个含有三个元素的一维数组,arr[1]和arr[2]也是,同时arr是以arr[0],arr[1]和arr[2]为元素的一维数组。
所以二维数组名是包含的第一个一维数组的地址,是数组指针。
如何求取一个二维数组有几行和几列呢?
sizeof(arr) /sizeof(arr[0]);//行数,也就是二维数组里有几个一维数组
sizeof(arr[0]) / sizeof(arr[0][0]); //列数,也就是每一个一维数组里有几个元素
注意:数组名是首元素地址,是指针,但不是变量,不能够自增或自减,有点像标识符常量,不是可修改的值,当然也不可以赋值。
变长数组
C99新增了变长数组(VLA),允许使用变量表示数组的维度。
比如:
但是,不能在声明中初始化它们。
注意:变长数组不能改变大小
变长数组中的“变”并不是指可以修改已创建数组的大小。一旦创建了变长数组,它的大小则保持不变。这里的“变”指的是:在创建数组时,可以使用变量指定数组的维度。
然而,目前完全支持这一特性的编译器不多,所以很多时候都不用变长数组。
C99/C11标准允许在声明变长数组时使用const变量。
复合字面量
C99新增了针对数组的复合字面量。字面量是除了符号常量外的常量。例如,5是int类型字面量,81.3是double类型字面量,"elephant"是字符串字面量等等。
对于数组,复合字面量类似于数组初始化列表,前面是用括号括起来的类型名。
例如:
int diva[2] = {10, 20};
(int [2]){10, 20}//复合字面量
注意,去掉声明中的数组名,留下的int [2]就是复合字面量的类型名。
因为复合字面量是匿名的常量,所以不能先创建后使用它,必须在创建的同时使用它。
为什么这么说?
实际上常量保存在内存里的一个只读区域(有地址),而且字面量没有标识符,也就是没有名字,除了刚创建出来的时候可以直接使用以外,就无法再次找到并使用。
与变量的区别其实主要是存储位置,读写权限以及有无标识符(名字)的差异。
使用指针记录地址就是一种用法:
复合字面量的类型名也代表首元素地址,所以可以把它赋值给指向int的指针。
还可以把复合字面量作为实参传递给函数。
这种用法的好处是,把信息传入函数前不必先创建数组,这也是复合字面量的典型用法。
冒泡排序
基本思想:两两比较相邻的元素,如果逆序则交换,直到没有逆序为止。
顺序一般分为两种,一是升序,指的是从小到大排序;二是降序,指的是从大到小排序。
所谓逆序就是违反顺序,比如想要升序,而相邻两元素左边的大于右边的。
两个两个比较,一个循环就是一轮,一轮过后根据升序或降序,排出一个数,如果是升序,则排在右边,如果是降序,则排在左边。
如图:
#include<stdio.h>
void BubbleSort(int arr[], int sz)
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
//一轮,每轮排好一个数
for (j = 0; j < sz - 1 - i; j++)//要减i,因为排好序的不用再排
{
if (arr[j] > arr[j + 1])//大于为升序排列,小于的话为降序排列
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[10] = {0};
int sz = sizeof(arr) / sizeof(arr[0]);
printf("请输入十个整数:\n");
for (int i = 0; i < sz; i++)
{
scanf("%d", &arr[i]);
}
BubbleSort(arr, sz);
printf("经过冒泡排序后的数组:\n");
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
以上就是本文全部内容了,感谢观看,你的支持就是对我最大的鼓励~