在C和C++语言中指针之所以复杂多变,我认为主要原因在于指针与数组错综复杂的关系。如果数组不能转为指针那么就不存在指针的数学运算,也不存在指针的比较。因此,深入的理解指针与数组的关系是发挥指针强大作用的必经之路。
一维数组
一维数组的定义
int a1[5];
int a2[] = {1, 2, 3, 4, 5};
char b1[6];
char b2[] = {'a', 'b', 'c', 'd', 'e', '\0'};
char b3[] = "abcde"; //是b2初始化方式的简化版本
- 数组定义的时候可以初始化,也可以不初始化,但必须指明数组元素的个数;
- 数据元素的个数可以通过初始化隐身确定,上面代码中a1和a2的元素个数相同,b1,b2,b3的元素个数相同,而且b2和b3存储的数据是相同的;
数组名一般表示,指向数组第一个元素的常量指针,对数组其他的理解都要基于这个认识之上。只有两种特殊情况,对于数组名用于sizeof返回整个数组占的字节,&数组名返回的是数组的首地址,而不是指向数组首地址的地址;
对数组元素的访问
对数组元素的访问有两种方式,一种是指针的间接访问,另一种是下标访问。下标访问的效率绝不可能比指针高,但是下标访问的程序可读性更高。没有必要为了几微秒的效率而牺牲可读性。
int array[10];
int *ap = array + 2;
ap[0] == *ap == array[2] == *(array + 2)
ap[2] == *(ap + 2) = array[4] == *(array + 4)
一维数组做形参
- 数组不能进行拷贝,在作为参数传入函数的时候,也是采用传值的方式,只不过这个值是一个地址;
- 使用数组的地方,自动转换为指针进行运算
- 以下三种形式做函数形参的结果是一样的,都是转化为指针进行运算的,该指针就是指向数组首地址的常量指针,而数组的个数需要通过额外的参数传入;
void fun(int * array){}
void fun(int array[]){}
void fun(int array[10]){}
字符数组与字符串常量
char a[] = "abcde";
char b = "abcde";
其中a是字符数组,b是字符串常量。两者的区别在于:a存储在堆栈上,b存储在常量区;a所指向的对象可以更改,而b所指的对象一旦创建就不能更改;a在运行过程中会被销毁,b会一直存在知道程序结束。
所以使用字符串处理函数时,要格外小心字符串常量,字符串常量只能做const char *的参数,例如用strcat函数处理两个常量字符串时就是出错。
多维数组
多维数组实际上是数组中存放数组
图是直接百度上找的,但是足以说明问题了,下图是个int a[3][4]的数组在内存中存放格式。
多维数组的定义
int a[3]; //一维数组
int b[2][3]; //二维数组
int c[1][2][3]; //三维数组
通过大量的分析总结出一句话:多维数组的数组名是指向比定义的数组低一维的数组的指针,话说起很绕口,举例子就明白了。上面的定义中,a是一维数组,数组名a是指向数组第一个元素的指针,该指针是int*类型的;b是二维数组,数组名b是指向第一行数组的指针,即int (*p)[3];c是三维数组,数组名c是指向一个两行三列的数组的指针,即int (*p)[2][3]。对于其他更高维数的指针类似分析即可。
int p1[3]与int(*p2)[3]的区别:int *p1[3]定义的p1是一个一维数组,该数组中存放了三个int类型的指针,而int(*p2)[3]定义的p2是一个指针,该指针指向是一个一维数组,该数组中存放了三个int类型的数据。因为,数组名就是指针,因此p1和p2都是指针。但是最大的区别在哪里展现出来呢?考虑++p1和++p2,指针会分别往后移动多少个字节?++p1是int类型指针,因此会移动四个字节,而++p2会移动3*4 = 12,移动12个字节。
还有一点需要注意的是:
int array[3][4];
int (*p)[4] = array;
注意:int **p = array, 这是错误的。
只有在另一种情况下使用指针数组:
char ss[] = {"abc", "def", "hij"};
char** str = ss;
char* strarray[3] = ss;
看一个问题:下面这段代码的输出结果应该是多少?
#include <iostream>
using namespace std;
int _tmain(int args, char* argv[])
{
int a1[3][2] = {11, 12, 21, 22, 31, 32}; //二维数组的初始化
int (*p2)[2];
p2 = a1; //注意这里的复制,说明二维数组的数组名是一个指向数组的指针
p2++;
cout << p2[1][1] << endl; //p2[1][1] = *(*(p2 + 1) + 1)
return 0;
}
应该是32,因为用a1初始化p2后,p2指向数组的第一行数组,p2++后指向第二行数组。
p2进行下标访问与指针访问相同,等同于((p2 + 1) + 1),把p2指针加1后指向了第三行数组,然后解引用得到该数组的地址,因为p2是指向数组的指针,而数组又是用一个指针来表达的,因此解引用后得到了第三行数组的首地址,然后加1指向第二个元素,最后解引用得到的就是第三行的第二个元素,也就是32了。
另一种设计多维数组的方法,在堆上分配内存,注意销毁
#include <tchar.h>
#include <iostream>
using namespace std;
int _tmain(int args, TCHAR* argv[])
{
int **p1;
p1 = new int*[3];
for (int i = 0; i < 3; i++)
{
p1[i] = new int[2];
}
p1[0][0] = 1;
p1[0][1] = 2;
p1[1][0] = 4;
p1[1][1] = 5;
p1[2][0] = 7;
p1[2][1] = 8;
return 0;
}
- 二维数组做函数形参
规定:如果将二维数组作为参数传递给函数,那么在函数的参数声明中必须指明数组的列数,数组的行数没有太大关系,可以指定也可以不指定。因为函数调用时传递的是一个指针,它指向由行向量构成的一维数组。
因此二维数组作为函数参数正确写法如下所示:
fun(int array[3][10]);
fun(int array[][10]);
fun(int (*array)[10]);
void main()
{
int a[3][10];
fun(a);
}
错误的传参方法:
//指针错误
fun(int *array[10]);
//未指明列数
fun(int array[][]);
指针与数组的区别
写了这么多,似乎指针与数组的区别越来越模糊了。在数组做函数形参时,指针和数组是完全等同的。但是,指针与数组最大的区别在于:定义一个指针,只会给其分配一个4字节(为了便于描述,定为4个字节)的空间,用于存放地址。而定义一个指针,会给其分配数组中所有元素所需要的空间。定义一个指针的初始化,要么指向已经分配内存的空间,要么动态分配内存。