数组
数组是最简单的数据结构,是多个同类型数据的集合体。《C Prime Plus》先介绍了指针,然后紧接着介绍数组,是因为数组与指针有千丝万缕的关系。
数组基础要点
声明和使用
声明一个数组格式为“数据类型 变量名[数组长度]
”这样,开头的类型表明数组存储的元素是什么类型。数组声明的核心符号是方口号[]
。在使用数组前必须先把数组初始化!
只读数组
只读数组:只需要读取,不需要改变数组的元素内容的数组。需要在声明前加const
。只读数组在C语言当中非常常用,起到保护程序、加快编译等作用。
数组边界
声明数组时,会指定数组的长度,但数组是从下标[0]
开始为第一个元素,实际上最后一个元素是长度减1的下标。C语言是不检查边界的,所以一定要注意数组的长度和边界。
数组大小
在C99之前,数组的长度只能是整型常量,在C99之后,引入变长数组特性,数组的长度可以是变量了,但不代表数组可以动态变化长度。
多维数组
多维数组是数组的数组,元素是数组,每个元素的长度都一致。
多维数组可以用下标法去访问到具体数据类型的基本元素。
数组与指针
数组名
数组名本身是一个指针常量。数组名储存着数组首元素的地址,对数组名解引用,得到的是数组首个元素的值。
int ai[3]=[1,2,3];
*ai = 1;
由于是指针常量,所以数组名不能被赋值,但是可以把它所储存的地址赋给其他指针。
数组中的内存地址
计算机系统中,地址是按字节编址的,也就是说,地址是以字节为间隔,而非最小的数据单位bit。
指针+整数,得到的是指针所储存地址加上整数乘以指针指向对象的数据类型字节大小后的内存地址,比如int是4字节,指向int的指针加上1得到的则是原本内存地址加上4字节的那个地址。
数组的表示法
我们可以简单地通过下标来访问元素的值,而另一种方法则是利用数组名存储着首元素地址的指针常量来实现,数组名加上整数n,就可以访问这个数组的第n个元素的值:
int ai[5]=[0,1,2,3,4];
*(ai+4)=4;
在这里,*(ai+4)
等效于ai[4]
。
数组的这两种表示法,一种可读性强,一种编译效率更高。
数组作为形参
当数组作为形参在函数原型里时,可以用以下形式编写:
int sum(int [],int);
int sum(int *, int);
int []
和int *
均是接收数组的形参,一种用到了数组表示法,一种是指针表示法,因为数组名本身是指针,所以接收数组可以用接收指针来等效替换。
数组末尾
C语言在分配内存空间时,对数组的末元素的后一个内存地址也给予了定义,编译器不会对这样一个内存地址报错,虽然这个内存地址上没有任何值。
int ai[3]=[0,1,2];
int *pi=ai+3;//不会报错,但无法取值。
指针的基础操作
指针赋值
指针只能被赋值内存地址,而且指针不具备数据类型转换的功能,只有相同指向对象类型的指针才能相互赋值(对应的则是浮点数和整数可以允许相互转换)
解引用
*
+指针变量名,是为访问指针所储存的内存地址上的值。
取址
取地址符&
+变量名,这个表达式的值为变量的内存地址。
指针与整数的加减
前面提到,指针加减整数,本质上是所储存的内存地址前移或者后移数据字节大小与整数的乘积。
指针递增递减
指针的值的变化,只会改变指针所指的内存地址,或者说是改变了指针所指的对象,不会改变原指针所指对象的值。也就是只会从指向a变为指向b,而不会影响a。
指针求差
指针与指针之间求差,得到的是整数。为什么呢?因为指针的内存地址求差得到的是内存地址的差距,这个差距除以指针所指数据类型的字节大小,也就得到了一个整数。
注意不要使用野指针
未被初始化的指针是野指针,它可能指向内存中任意地址,这是很危险的。
数组与函数
const数组
数组有两种使用情况,一种是不需要改变数组的各元素值,只需要用数组的数据来做处理;另一种是需要改变数组的值。而对于第一种情况,最好将数组声明为const
,这样就可以避免后续编程以及程序运行时改变这些数组的值,让不理想的情况尽量少发生。
const指针
前面的学习中,最开始接触的是#define
字符常量,而另一种是const
,const
更加灵活,可以声明更多的类型,包括const
指针。
(重点)多维数组与指针
既然一维数组的数组名是数组首元素地址,那多维数组呢?也是一样,只不过多维数组名存储的是首个数组元素的地址,然后首个数组元素的数组下标表示法这个名字也是其首元素的地址,堪称套娃。简单地说,多维数组名是地址的地址,要解引用两次及以上才能访问到具体数据类型元素的值。
指向数组的指针
在C语言中,[]
比*
优先级更高,所以要声明一个指向数组元素的指针,要先用圆括号括住指针名和*
:
int (*pi)[5]; //一个指向长度为5的数组的指针
如果没有圆括号,就会变成:
int * pi[5]; //一个存储了五个int指针的数组,指针的数组。
多维数组与函数
如果要让函数可以接收多维数组,首先可以让形参是指向数组的指针:
void function(int (*pi)[5]); //函数接收一个元素为长度5的整数数组的多维数组
也可以用数组表示法(编译器会自动把这样的形参转换为指针表示法):
void function(int [][5], int n); //函数接收一个元素为长度5的整数数组的多维数组,多一个形参n是要告诉函数这个多维数组有n个数组元素。
变长数组
某些编译器在C99之后支持变长数组,最大的作用是允许了函数接收任意大小的数组来处理,但不意味着数组可以动态改变维度大小。