指针的深入学习(函数指针,数组指针,typedef与指针)

何谓指针?

指针仅仅表示一个内存中的某个地址?

非也,注意到,我们在定义指针的时候,都关联了一个类型,如int,char,或者是string等等,如果说指针仅仅表示一个内存中的地址,那何必要关联这么多变化的东西呢?完全可以DWORD p=0;这样解决问题。

关联了的数据类型是作何用的呢?

它可以指示编译器怎样解释特定地址上内存的内容,以及该内存区域应该跨越多少内存单元。如 int *p;

编译器可以从这个定义中获得信息:1、p指向的内存存放的是整型数据,2、由于该内存区域只存放了一个数据,跨越的内存区域为4个字节,即p+1的效果是跳过了四个字节。

另一个复杂一点的例子,如

struct a

{int x1;

short x2;

a *next;

扫描二维码关注公众号,回复: 2550144 查看本文章

}

定义指针 a *p;那么编译器对这个指针又作何解释呢?

1、p指向的内存区域依次存放了三种类型的数据,分别是int,short和一个指针型数据。

2、p指向的内存区域跨越了12个字节,即p+1的效果是跳过了12个字节。(为何不是10?对齐的原因)

但是,C++中定义了一种特殊的指针,它去处了一般指针中对内存区域内容以及大小的解释,以满足特定定的需要,如我们只需要某块内存的首地址,不需要考虑其中的数据类型以及大小。这种形式为 void *; 这种类型的指针可以被任意数据类型的指针赋值,如上面的a* 型,void *q = p; 唯一例外的是,不能把函数指针赋给它。

函数与指针

指向函数的指针:可以利用它代替函数名来调用函数。

如何定义函数指针,由于一个程序中可以用多个函数名相同的情形(即函数的重载),因而,定义函数指针的时候,必须包含函数的参数,这样才能准确地将指针指向某函数。

定义:int (*p)(const char*, int); 表示p是一个指向函数的指针,该函数的两个参数为const char* 和int,另外该函数返回int型值。

容易混淆的是:int *p(const char *, int); 缺少了一个括号,此时编译器的解释是 int* p(const char*, int);即其含义是一个函数的声明,函数名为p,返回一个指向int型的指针。那么 int* (*p)(const char*, int);则是定义了一个函数指针p,它指向一个函数,该函数的两个参数为const char*和int,该函数返回一个指向int型的指针

函数指针的初始化与初始化:

函数名如同数组名,编译器将其解释为指向该类型函数的指针,故而,可以领用函数名,或者&函数名对函数指针进行初始化或者赋值,另外,可以用另一个函数指针对该指针进行初始化以及赋值。重要的一点是指针与函数名,指针与指针必须具有完全相同的参数表和返回类型(必须完全完全一样,任何一点不同都不可以)。不存在隐式的类型转换,用户必须保证完全的一致性。

初始化或者赋值为0,表示不指向任何函数。

利用函数指针调用函数是可以p(x,y)这样调用,也可以(*p)(x,y)这样调用,前提是p已经正确的赋值或者初始化。

函数返回指针:可以返回一个非基本类型的对象。

数组与指针

int a[3] = {1,2,3};

考虑 a,a[0], &a, 以及 &a[0]这三个表达式的含义:

首先这三个表达式的数值结果是一样的--数组的首地址(即数组中第0个元素的地址),但是编译器对三者的解释不同:

对于a,编译器将其解释为一个指针,指向的是一个整型数据,因而利用a+1即指向数组中的第一的元素,a+2指向第二个元素。

对于a这个指针有些特殊的性质:

a不是一个普通的指针,它同时是一个数组名,即关联了一个数组,因而某些性质上与普通的指针不同。

普通的指针可以被赋值,即可以用一个地址或者另一个指针修改当前指针的指向,然而对于a这种关联了一个数组的指针,如果允许这样赋值的话,那么数组中的元素将无法被访问,所以不允许对数组名代表的指针进行赋值。在这一点上a相当于指针常量,即只能被初始化,不可以进行赋值。

   虽然a不可以被赋值,但是将a赋给其他的元素是完全可以的,这一点同普通的指针没有不同。

综上,a相当于一个指针常量。(type* const型的)

本质上a[i]操作被编译器解释为*(a+i)操作,即[]运算符是通过数组名指针实现的,因而&a[0]的含义即&(*a),显然对一个指针先*(解引用),再&(引用),等价于什么都没做,还是这个指针本身,因而a完全等价于&a[o],--(&a[0])[i]等价于a[i],形式有点诡异,呵呵。而对于&a这个表达式,奇怪的是这个也是数组的首地址,那么就是说,这个数组的首地址中存放了一个指针常量(即数组名),但是数组的首地址中不是存放的一个int型的数字吗?这是怎么回事呢?难道一个地址能存放两个东西?

暂时无法解释,可以这样认为编译器发现这种&和数组名的结合运算时,即返回数组首地址,只不过,这是,这仅仅是个纯粹的地址,它不再具有指针的特性,即编译器不再将其解释为指针,用户不可以通过+1运算来访问下一个数组元素。它的+1就是数学上的+1。

当数组变为多维,问题变成怎么样了呢?

考虑二维数组 int b[4][3] = {{1,2,3}, {4,5,6}, {7,8,9}, {10,11,12}}; b,&b, b[0], &b[0], &b[0][0]几个表达式的含义:

首先,c++中数组元素的存放是以行序为主序,即第一段存放第一行的数据,第二段存放第二行的数据,....,如此。

首先考虑数组名b,编译器同样将数组名b解释为一个指针,但是显然这个指针不是普通的指针。这个b指向数组所有元素的首地址,这一点是勿庸置疑的,那么这个指针一步跨越的内存是多大呢?在本例中,b一步跨越12个字节,即b一步跨越数组中的一行元素,实际上b是一个指针的指针,或者说指向指针的指针,即b所指向的内容是一个指针,(同样对于b+1,b+2),b[i][j]这种访问方式本质上即:先通过+i,将指针跳跃到第i行,从而获得了指向第i行首地址的指针b[i],然后通过这个指针,再通过+j,跳跃j步,到达了第j个元素,即找到第i行,第j列的元素。

所以b是指针的指针,b[i]是指针,这儿,b[i]类似于一维中的a。

那么&b呢?&b仍然是数组的首地址,但是跟一维类似的是,这是个纯粹的地址,不再具有指针的特性,它的+1就是数学上的+1,不可以利用+1来访问下一个元素。同样的道理对于&b[i],&运算符加上之后,本来是作为指针的b[i]被剥夺的指针的资格,返回一个纯粹的地址。

实际上,由于[]本质上是对指针的解引用,那么我们访问数组元素时可以不拘于a[i][j]这种方式,可以这样:(*(a+i))[j], 或者*(a[i]+j),或者 *(*(a+i)+j),这几种写法是等价的。

对于&b[i][j]呢?我们把b[i][j]换一种写法,写成*(*(b+i)+j),这样问题就容易看清楚了,原来的*b[i][j]就等价于&(*(*(b+i)+j)),我们可以把最外层的括号脱掉,就成了*(b+i)+j,即b[i]+j,显然这是一个指针,指向第i行,第j列元素的指针,对该指针的解释是一次跨越一个int型的数据。

让我们再变态一点,考虑三维的情形,虽然三维的数组不多见,还是考虑一下吧,毕竟空间的坐标是用三维表示的。

int c[2][3][4] = {{{1,2,3,4},{5,6,7,8},{9,10,11,12}}, {{13,14,15,16},{17,18,19,20},{21,22,23,24}}};

首先,数组名c,编译器将c解释为一个指针,指向数组的首地址,由于行序是主序,所以,该指针一步跨越12个整型数,共48个字节,实际上即跨越了一个二维数组。

对于&c,跟一维二维的情形类似,是一个纯粹的地址.

c[i]呢?可以推测,c[i]与二维中的b类似,即指向指针的指针,c[i]一步跨越4个整数,16个字节。c[i]是指向指针的指针,那么c便是指向指向指针的指针的指针(晕~)。

c[i]亦等价于*(c+i)

至于c[i][j],这才是真正的int型的指针,即指向真实数据的指针,一步跨越一个int型,4个字节。跟二维类似,对于&c[i][j],编译器返回一个地址,虽然跟c[i][j]的值一样,但是只是一个纯粹的地址,跨越单元为一个字节。

对于c[i][j][k],不需要废话,对于&c[i][j][k],这是一个地址吗?这是一个指针吗?我们还是要借助[]的另一种表示方法:c[i][j][k]等价于*(*(*(c+i)+j)+k),那么&c[i][j][k]就等价于*(*(c+i)+j)+k,即c[i][j]+k,即指向第(i,j,k)个元素的指针,一步跨越单元为一个int型。

让我们来看一看,寻找第(i,j,k)个元素有哪些写法:

1、c[i][j][k]

2、*(c[i][j]+k)

3、*(*(c[i]+j)+k)

4、*(*(*(c+i)+j)+k)

5、(*(c+i))[j][k]

6、(*(*(c+i)+j))[k]

7、*((*(c+i))[j]+k)

8、(*(c[i]+j))[k]

可见,共八种写法,实际上就是一共有三个解引用,选择用[]还是用*,这样,总共有8个组合。(那么二维的就是4种,一维的2种)

typedef与指针

typedef似乎很简单,如typedef int integer;然而,这些简单的typedef语句容易让人产生一种误解,typedef就是一种宏替换,把后面的自定义类型替换成前面的已知类型,事实是这样的吗?显然不是!

考虑这样的问题:如何定义一个指向整型的指针类型?如何定义一个函数指针类型?

第一个问题很简单:typedef int* int_pointer;即可,对于第二个问题,似乎就没有那么简单了,首先,看函数指针的定义方法:int (*p)(const&, int); 这个p指向的函数必须返回int,形参必须是const&和int。现在要将这种指针类型命名为func_pointer,其定义的方法如下:

typedef int (*func_pointer)(const&, int);

可以这样来理解:typedef int integer;将typedef去掉,那就是个变量的定义,这儿即定义了一个int型的变量integer,考虑这个integer是什么类型的,那么这个typedef语句就是将integer定义为这个类型的。将typedef int (*func_pointer)(const&, int);中的typedef去掉,就成了一个函数指针定义,即func_pointer被定义为函数指针类型变量,那么原来的typedef即将func_pointer定义为函数指针类型

函数,数组与指针

int (*testCases[10])();

这个表达式是什么意思?指针,数组,函数糅合在了一起问题变得复杂起来。它定义了数组,testCases[10],数组中的元素是函数指针,函数指针的类型是 int (*)();

怎么来理解这种定义呢?首先考虑数组的定义,数组的定义一般模式是:

类型 数组名[大小];

考虑这个表达式,似乎是定义了一个数组,但是数组名[大小]被夹在了中间,那么类型是什么呢,发现类型并不是简单的数据类型,而是一个函数指针类型int (*p)(),这个函数没有参数,返回int型。从而这个表达式的含义是:定义了一个函数指针型的数组,大小是10。

可以利用typedef来简化这种定义:

typedef int (*PFV)();

PFV testCases[10];

其实int (*testCases[10])();这儿我们定义了一个函数指针数组,数组是主体。

下面考虑这样的问题:如何定义一个指向数组的指针?

指向数组的指针,好像比较新鲜,所谓指向数组的指针,即指针的一步跨越是一个数组,跟指向整型的指针一步跨越一个整型一个道理。事实上前面已经碰到了指向数组的指针,如二维数组名,实际上就是一个指向数组的指针,它一次跨越一行的数据,实际上即是跨越了一个一维数组,而三维数组名呢,也是一个指向数组的指针,它一次跨越的是低维组成的一个二维数组。

数组指针(即指向数组的指针)的定义:

int (*ptr)[3];  这个表达式定义了一个数组指针ptr,ptr一次跨越一个由3个int型组成的一维数组。发现其定义的方式与函数指针定义的方式很相似,只是把()换作了[]。

更进一步,如果要定义一个指向数组的指针,而数组中的元素不是简单的int型,而是比较复杂的类型,那该如何定义呢?事实上数组指针这种东西就已经够稀有的了,一般编程绝对不会用到,我们只需要能读懂一些比较复杂的东西就行了,自己没有必要构造这么复杂的类型。

比较复杂的表达式:

      1、int (*(*(*p())[])())[];

首先,根据p()判断p是一个函数,再根据p()前面的*号判断该函数返回一个指针,下面就看这个指针指向的是什么类新了,我们可以把*p()替换成一个*pointer,这个pointer就是函数p返回的指针,那么就成了int (*(*(*pointer)[])())[];再根据(*pointer)[],这说明了指针pointer是指向的一个数组,那么这个数组中的元素是什么类型呢?由于数组名实际上就是个指针,我们把(*pointer)[](即(*p())[])替换成一个array,这样就成了 int (*(*array)())[];发现array是一个函数指针,从而数组中的每个元素是函数指针,而这个函数呢,又返回一个指针类型,把(*array)()用func代替,就成了int (*func)[];这说明了func函数返回的是指向数组的指针,数组中的元素是int型。

这个表达式够酷!!!

2、p = (int( * (*)[20])[10])q;

这是一个强制类型转换,q被强制类型转换成一个这样的指针类型,这个指针呢直线一个具有20个元素的数组,这个数组中的元素也是指针,是指向另外一种数组,这种数组是含有10个int型数据的一维数组。

可见,分析复杂的表达式时(所谓复杂,即糅合了指针,数组,函数三样,缺少了一样就不会复杂了),从括号的最里层做起,最里层的东西是复杂表达式的“根节点”,然后一层一层脱,脱的时候,是这样的,比如里层是个数组,那么就是说这个数组的元素是什么呢,那就是外层的东西,如果里层是个有返回值的函数,那么就是说这个函数返回什么值呢?那就是外层的东西,就这样一层一层地把表达式解析清楚。

关于typedef还有一些要说的:

typedef  int (*PFV)(); 这是定义了一个函数指针,那么PFV p;就可以定义了一个指向函数的指针。

typedef int (*p[10])(); 这是把p定义为函数指针数组,那么 p array;语句就可以定义了一个函数指针数组,数组名即为array,array这个数组含10个元素。

typedef int (*parray)[3];这是定义了一个指向整型数组的指针,那么 parray ptr;就定义了一个指向数组的指针。如何对这个ptr赋值或者初始化呢?事实上,是通过二维数组名来对其进行赋值(初始化)的,因为二维数组名作为指针来讲,就是一个指向数组的指针,一次跨越一个数组。

typedef int a[3][3]; 这个语句什么意思呢?这是把a定义为一个3*3的整型数组类型。当a b = {1}时就完成了一个3×3的整型数组的定义初始化的工作。

同样,简单一点 typedef int a[3];这个语句是把a定义为一个一维数组类型。

typedef  void func(int); 这个语句定义了一个函数类型。通过这个typedef,我们可以比较清晰地定义出函数指针,func* p;即可。

typedef char* string;

const string str;

这个str是什么类型的呢?const char * str,即指向常量的指针类型?事实上,答案有些不可思议,str是一个常量指针,而不是指针常量,即const修饰符针对的是指针,而不是char

原文地址:https://blog.csdn.net/chuanshaoke/article/details/7681377

猜你喜欢

转载自blog.csdn.net/weixin_39953289/article/details/81383205