数组是一种数据格式,能够存储多个同类型的值。
一维数组可用来实现线性表的顺序存储、哈希表、堆(堆排序部分)等;
二位数组可用来保存图的邻接矩阵等。
1.1.1 一维数组的声明与初始化
应指出以下三点:
1)存储在每个元素中的值的类型;
2)数组名;
3)数组中的元素数,数组的元素数必须用值大于等于1的常量表达式定义。
需要引起注意的地方: 数组定义中类型不能使引用,因为引用是不能赋值的,而数组中元素必须可以被赋值。
如下面的定义就是错误的。
int& a[10]; // 错误的
虽然没有引用数组,但数组可以有引用。
int a[6] = {0, 2, 4, 6, 8, 10}; int (&p)[6] = a; // p是数组a的引用
注:数组的引用可以用于函数实参传递。此时可确保传递过来的数组长度合乎要求。
2.一维数组的初始化
1)函数体 外 定义的内置类型数组(即内置类型的全局数组),元素初始化为0;
2)函数体 内 定义的内置类型数组,元素无初始化(注意,若只初始化部分元素,其后的元素此时也会被初始化为0);
3)如果不是内置类型,则不管其在哪定义,自动调用其默认构造器函数为其初始化,若该类型无默认构造函数则会报错。
注:当数组的大小未知时,需要动态声明一维数组,声明格式如下:
int* a = new int[n]; //.... delete []a; // 当数组使用完毕,需要释放内存空间
1.1.2 字符数组
字符数组的初始化既可以用字符常量也可以用常量字符串(末尾有空字符)记性初始化:
方式1:char ca1[] = {'C', '+', '+'}; // 末尾没有null字符 char ca1[] = {'C', '+', '+', '/0'}; // 末尾显示添加null字符 方式2:char ca1[] = "C++"; // 末尾自动添加null字符
注:ca1的长度为3,ca2和ca3的长度为4。
const char ch4[5] = "Hello"; // 编译错误 数组长度应为6
另外,C/C++很多字符串处理函数(strcpy,strcat等),传递给这些标准库函数的指针必须是非零值并且是 指向以null结束的字符数组 的第一个元素。
1.2.1 二位数组的声明与初始化
初始化方式分为两种:
1)按行初始化:
int a[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}};
2)顺序初始化:
int a[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
注:如果只是初始化部分,则其余的规则按照一维数组的初始化规则。
C++规定,在声明和初始化一个二维数组时,如果对二维数组的所有元素都赋值,则第一维(行数)可以省略,但第二维一定不能省略。
1.2.2 行优先存储和列优先存储
本质上讲,所有数组在内存中都是一维线性的,C/C++中,二维数组按照行优先顺序连续存储, 因此按照行优先读取可以顺序读取,速度更快。
注:二维数组a[x][y]转化为一维数组b:a[x][y] = b[x*列数 + y]。
1.2.3 二维数组的动态声明
int **a = new int* [m]; for(int i = 0 ; i < m ; i++) a[i] = new int[n];
动态声明的数组任意的a[k]都是一个int*类型,即一个地址,所以只能a[i][j]或者*(*(a+i)+j)来访问数组元素。
此外动态声明的数组,使用后需要释放内存。
for(int i = 0 ; i < m ; i++) delete []a[i]; delete []a;
1.3.1 指针运算---算数运算、关系运算
C/C++规定的合法运算包括:指针与整数的加减、同类型指针间的比较、同类型的两个指针相减。
当一个指针和一个整数进行算数运算的时候,整数在执行加法运算前始终会根据合适的大小进行调整,这个“合适的大小”就是 指针所指向类型的大小 。
例如:float占据4个字节,在计算float型指针加3的表达式时,这个3将根据float的类型大小(即此例中的4)进行调整为12。
减法运算的值是两个指针在内存中的距离(以数组元素长度为单位,不是以字节为单位)。
进行<、<=、>、>=运算,前提是它们都指向同一个数组中的元素。根据所使用的操作符,比较表达式的值表示那个指针指向数组更靠前或靠后。
下面的代码用于清楚一个数组中所有的元素:
float values[5]; float *vp; vp = &values[0] ; while(vp < &values[5]) { *vp = 0; *vp++; // 等价于 *vp ++= 0 }
1.3.2 指针数组与数组指针
指针数组 :一个数组里面装着指针,即指针数组为一个数组。
例如一个有10个指针的数组,其中每个指针是指向一个整数型,那么此数组的定义为:
int *a[10];
数组指针:一个指向数组的指针,它其实还是指针,只不过它指向整个数组。
例如一个指向有10个整型元素数组的指针定义为:
int (*p)[10]; // 由于[]的优先级高于*,所以必须添加()
注意:二维数组的 数组名是一个数组指针 ,若有:
int a[4][10]; int (*)p[10]; p = a; // a的类型是 int(*)[10]
对应关系如下图:
若有如下代码:
int a[10]; int (*p)[10] = &a; // 注意此处是&a,不是a,a的类型是int*,&a的类型是int(*)[10] int *q = a;
对应关系如下图:
可见,p与q虽然都指向数组的一个元素,但由于p的类型与q的类型不同,p是指向有10个元素整型数组的指针,*p的大小是40字节,故p+1跳过40字节;而q是指向整型的指针,*q的大小是4字节,故q+1跳过4字节。
1.3.3 指针在数组中的应用
指针和数组密切相关,特别是在表达式中使用数组名时,该名字会自动转换为指向数组首元素(第0元素)的指针。
int ia[] = {0, 2, 4, 6, 8}; int *ip = ia; // 指针ip指向了数组ia的首地址 ip = &ia[4]; // ip指向了ia[4] ip = ia; // 重新指向ip[0] int *ip2 = ip + 4 // ip2指向ia[4]
注:对于int a[10],可以有以下几种方式表示a[1]:
&a[0] + 1; // &a[0]等价于a,为首元素的地址 (int*)&a + 1; // &a为指向数组的指针,与a类型不同(&a类型为int(*)[10]),但指向的单元相同,强制转化为int*,加1跳过一个int大小 (int*)((char*)&a + sizeof(int)) ; // 强制转化为char*,加1跳过char的大小,跳了4次(int的大小), 再强制转化为int*(指向a[1]的指针类型为int*)
&、*总结:
例如这样的声明 int a[4][5];
&a : 类型为 int(*)[4][5];
a+i :类型为 int(*)[5];
*(a+i) :类型为 int*;
*(*(a+i) + j) :类型为int;
*(a+i) = a[i];
*(*(a+i) + j) = *(a[i] + j) = a[i][j];
数组就暂时告一段落,最后在强调一点,在遇到指针问题的时候,首先要明确指针指向的是什么,才能知道跳跃的长度。