从来没有好好理解过指针,这次花了点时间大概看了一下,把学到的一些东西记录下。
1.指针
1.1 初步理解指针
指针是C语言的一个重要特征,它是一个特殊的变量,里面存储的数值被解释成为内存里的一个地址
在讲指针之前,先搞清楚两个运算符:&和*
&是取地址符,&a的运算结果是一个指针,指针的类型是a的类型加个*,指针所指向的类型是a的类型,指针所指向的地址就是a的地址。
*用于指针的间接引用,运算结果随着指针指向的内容而改变,比如可能是一个数值,也有可能是一个地址。*p的类型是p所指向的类型,它占用的地址是p所指向的地址。
通过一个例子来解释下&,*
在讲数组的时候经常会讲到a和&a的区别,比如给你一个数组 int a[4]。
a指向的是数组首元素的首地址,即a=&a[0], 所占内存区长度为sizeof(int),a+1是数组下一个元素的首地址
&a是数组的首地址,所占内存区长度为4*sizeof(int),&a+1是下一个数组的首地址
要获得数组中保存的内容时,可以使用*运算符。例如要获得a[1]的内容,即*(a+1)。
言归正传:
搞清一个指针,我们从四个方面的内容来认识指针:
- 指针的类型
- 指针所指向的类型
- 指针的值(是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。)
- 指针本身所占据的内存区(从指针所代表的的那个内存地址开始,长度为指针所指向的类型长度的一片内存区,注意指针未初始化时,内存区是不存在的)
通过以下几个例子来说明:
指针类型 | 指针所指向的类型 | |
int *p | int * | int |
int *p[5] | int *[5] | int [5] |
int (*p)[5] | int (*)[5] | int ()[5] |
int **p | int ** | int * |
int *(*p)[5] | int *(*)[5] | int *()[5] |
1.int *p p是一个指向int类型的指针
int a[5];
int *p=a;
指针的值为数组元素首地址
指针本身所占据的内存区为sizeof(int)
2.int *p[5] p是一个指向数组首元素的指针,该数组含有5个int *类型的元素
int *p[5];
指针的值为数组元素首地址
指针本身所占据的内存区为sizeof(int *)
数组中的每个元素可以看成是指向int类型的指针,此时对数组中元素的赋值可如下:
int b=0;
p[0]=&b;
3.int (*p)[5] p是一个数组的指针,该数组含有5个int类型的元素,p是一个行指针
int a[5];
int (*p)[5]=&a;
指针的值为数组首地址
指针本身所占据的内存区为sizeof(int)*5
4.int **p p是一个指向int类型指针的指针
int *a[5];
int **p=a;
指针的值为数组元素首地址
指针本身所占据的内存区为sizeof(int *)
5.int *(*p)[5] p是一个指向数组的指针,该数组含有5个int *类型的元素,p是一个行指针
int *a[5];
int *(*p)[5]=&a;
指针的值为数组首地址
指针本身所占据的内存区为sizeof(int *)*5
所以,当碰到指针时,先想想以上四个方面(指针的类型,指针所指向的类型....),这样可以更好的理解指针。
1.2 const修饰符和typedef
const修饰符:
在给一个变量加上const修饰符的同时,通常需要对它进行初始化,因为之后将不能对该变量进行修改。
const int n=5;
n=6;将会报错,因为n是const型的,不允许被修改。
const和指针在一起使用时,要注意是给谁加上了const。例如:
(1) const int *p; p是一个指向const int类型的指针,因此*p的值不可以改变。
const int a=5;
const int *p=&a;
*p=6;// 错误error: assignment of read-only location
(2) int * const p; p是一个指向int类型const指针,因此p不可以改变,但是*p可以改变。
int a=5,b=6;
int * const p=&a;
p=&b;//错误 error: assignment of read-only variable `p'
(3) const int * const p; p是一个指向const int 类型的const 指针。p和*p的值都不能被改变。
const int a=5,b=6;
const int * const p=&a;
*p=6;//错误error: assignment of read-only location
p=&b;// 错误error: assignment of read-only variable `p'
(4) const int **p; p是一个指向const int类型的指针的指针
const int a=5;
const int *q=&a;
const int **p=&q;
*q=6;//错误,a的值不能被改变
(5) int * const * p; p是一个指向int类型的const指针的指针,*p的值不能改变,p的值可以改变
int a=5,b=6;
int *q=&a,*r=&b;
int * const *p=&q;
p=&r;//正确,p可以被改变
*p=r;//错误,*p不能被改变
*q=6;//正确,a,b的值可以被改变
(6) const int * const * p; p是一个指向const int类型的const指针的指针,*p的值不能变
const int a=5,b=6;
const int *q=&a,*r=&b;
const int * const *p=&q;
p=&r;//正确,p可以被改变
*p=r;//错误
*q=6;//错误,a,b的值也是不能改变的
(7) int ** const p; p是一个指向int类型的指针的const指针,p的值不能改变
int a=5,b=6;
int *q=&a,*r=&b;
int ** const p=&q;
p=&r;//错误,p的值不能改变
*p=r;//正确,*p可以被改变
*q=6;//正确,a,b的值可以被改变
(8) const int ** const p; p是一个指向const int类型的指针的const指针,p不能被改变
const int a=5,b=6;
const int *q=&a,*r=&b;
const int * *const p=&q;
p=&r;//错误,p不能改变
*p=r;//正确,*p可以被改变
*q=6;//错误,a,b的值也不能被改变
(9) int * const * const p; p是一个指向int类型的const指针的const指针
int a=5,b=6;
int *q=&a,*r=&b;
int * const * const p=&q;
p=&r;//错误,p的值不能被改变
*p=r;//错误,*p的值也不能被改变
*q=5;//正确,a,b可以被改变
(10) const int * const * const p; p是一个指向const int 类型的const指针的const指针
const int a=5,b=6;
const int *q=&a,*r=&b;
const int * const * const p=&q;
p=&r;//错误,p不能被改变
*p=r;//错误,*p不能被改变
*q=5;//错误,a,b的值也不能被改变
typedef的用法:
typedef给出了一种方式来克服 “ * 只使用与变量而不适用与类型”的弊端。例如:
typedef int * PINT;
PINT p,q;
p,q都声明为指向int类型的指针。
typedef还可以定义返回值类型。比如:
typedef int * b(),定义b的类型为int * ();
typedef int * b();
int * f()
{
int a=5;
int *p=&a;
return p;
}
int main()
{
b *c;
c=f;
cout<<c<<endl;
return 0;
}
对于复杂的声明,例如 int * (* (*fp1) (int) ) [10];我们可以使用“右左法则”来理解这个声明。这个法则运用如下:从最内部的括号开始阅读声明,向右看,然后向左看。当你碰到一个括号时就调转阅读的方向。括号内的所有内容都分析完毕就跳出括号的范围。这样继续,直到整个声明都被分析完毕。
以int * (* (*fp1) (int) ) [10];为例,阅读步骤:
(1) 从变量名开始——fp1
(2) 往右看,声明也没有了,碰到了),因此往左看,碰到一个*——一个指针
(3) 跳出括号,碰到了(int)——一个带一个int参数的函数
(4) 向左看,发现一个*——(函数)返回一个指针
(5) 跳出括号,向右看,碰到[10]——一个10元素的数组
(6) 向左看,发现一个*——指针
(7) 向左看,发现int——int类型
总结:fp1被声明成为一个函数的指针,该函数返回指向指针数组的指针.
2.函数指针
函数指针可能是最容易引起理解上的困惑的声明。先来看一个例子:
int (*p)(int);
这里p被声明为一个函数指针,该函数带一个int类型的参数,并且返回值为int类型。
直接定义一个函数指针显得冗长,我们可以用typedef进行简化。
typedef int(*func)(int);
此时,func定义为一个函数指针类型,可以用这个类型来定义变量,例如 func p;
下面举几个例子来了解一下到底什么是函数指针
(1) int * (*(*fp1)(int))[10];
对于fp1:
我们从里向外一点一点分析,首先(*fp1)(int),这说明fp1是一个函数指针,它有一个int类型的参数;然后我们来找这个函数指针类型的返回值,注意到*(*fp1)(int),所以我们可以断定它的返回值是一个指针,指针指向什么呢?
我们可以看到最外层剩余的部分是int * [10],因此这个函数的返回值是一个指针,这个指针指向一个包含十个int *类型数据的数组。
综上:fp1是一个函数指针,它所指向的函数有一个int类型的参数,并且这个函数的返回值是一个指针,这个指针指向一个包含10个int *元素的数组。
(2) int (*(*fp2)(int, int, float))(int);
对于fp2
就不再赘述了。fp2是一个函数指针,它所指向的函数有三个参数,参数类型分别为int,int,float;它的返回值是一个函数指针,这个函数指针所指向的函数具有一个int类型的参数,且返回类型为int。
(3) double (*(*(*fp3)())[10])();
对于fp3
fp3被定义为一个函数指针类型,这种函数指针所指向的函数的参数为空;它的返回值是一个指针,这个指针指向一个包含10元素的函数指针数组,这些函数指针所指向的函数的参数为空,返回值为double。
(4) int (*(*fp4)[10])();
对于fp4
fp4是一个指针,这个指针指向一个包含10元素的函数指针数组,这些函数指针所指向的函数的参数为空,返回值为int。
本文参考的一些资料:
如何理解c和c ++的复杂类型声明 (其中有些错误的地方,文中已修改)