一、指针的概念
本质上讲指针也是一种变量,普通的变量包含的是实际的数据,而指针变量包含的是内存中的一块地址,这块地址指向某个变量或者函数,指针就是地址。指针是一个指示器,它告诉程序在内存的哪块区域可以找到数据。
二、指针的内容
指针的内容包含4部分:指针的类型,指针所指向的类型,指针的值,指针本身所占有的内存区。在初学指针时,指针的类型和指针所指向的类型是极容易搞混淆的,弄清楚这些概念,有助于我们正确的使用指针。
1.指针的类型
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。
<span style="font-family:Microsoft YaHei;">int *ptr;<span style="white-space: pre;"> </span> <span style="white-space: pre;"> </span>//指针的类型是int *
char *ptr; <span style="white-space: pre;"> </span>//指针的类型是char *
int **ptr; <span style="white-space: pre;"> </span>//指针的类型是 int **
int (*ptr)[3]; <span style="white-space: pre;"> </span>//指针的类型是 int(*)[3]
int *(*ptr)[4]; <span style="white-space: pre;"> </span>//指针的类型是 int *(*)[4] </span>
2.指针所指向的类型
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
<span style="font-family:Microsoft YaHei;font-size:14px;">int *ptr; <span style="white-space: pre;"> </span>//指针所指向的类型是int
char *ptr; <span style="white-space: pre;"> </span>//指针所指向的的类型是char
int **ptr; <span style="white-space: pre;"> </span>//指针所指向的的类型是 int *
int (*ptr)[3]; <span style="white-space: pre;"> </span>//指针所指向的的类型是 int()[3]
int *(*ptr)[4];<span style="white-space: pre;"> </span>//指针所指向的的类型是 int *()[4] </span>
3.指针的值
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。
指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。
指针所指向的内存区和指针所指向的类型是两个完全不同的概念。如int*p,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指向的类型是什么?该指针指向了哪里?
4.指针本身所占有的内存区
指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。
指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。
三、看待指针类型和指针所指向的类型的方法。(我自己的理解)
int*p来说,它可以写成int* p,也可以写成int *p。第一种的理解偏向于地址,就是p是一个地址变量,p表示一个十六进制地址;第二种的写法偏向于值,*p是一个整型变量,它能够表示一个整型值。
这两种写法都正确,只是理解上不同,但是我认为,在理解指针类型和指针所指向的类型这两个概念时,完全可以把它们两个结合起来。
想想我们使用指针的步骤:声明指针,为指针赋值,然后使用指针所指向的值。
我们都知道指针是一种复合数据类型,它必须和基本的类型结合才能构成指针类型。
那么int*就是一种复合的数据类型——整型指针类型。
这样就好解释第一种写法了,在声明时,int* p,直接声明p变量为整型指针类型,这是第一步。
第二步就是为指针赋值了,p是指针类型,它存放的是地址,这里假设我这样为它赋值:p = &普通变量;(比如int a = 5;p=&a;)。
第三步使用指针,在C++ Primer中详细的解释了*是解除引用的运算符(我的理解是地址解析运算符),如果p是地址,那么*p就是实际的值(比如上面对应的*p = 5)。
对于初学者来说,在理解它的含义时,完全可以跨过这一步,上面说了在声明指针时int* p和int *p这两种写法都可以,在声明时我偏向第一种理解,在使用时我偏向第二种理解:毕竟我们使用的是值,而*p就是这个值。
我的结论:对于int* p和int *p的理解(也是对于指针类型和指针所指向的类型的理解),一个指针包含两部分,地址和值,指针声明时声明的是一个地址变量(指针就是地址),在使用时使用的是指针所指向的值。或者说指针包含两个类型:指针类型和指针所指向的类型,声明时是声明指针类型,使用时是使用指针所指向的类型。
<span style="font-family:Microsoft YaHei;font-size:14px;">int p[3];<span style="white-space:pre"> </span>//p先和[]结合,说明p是一个数组,再和int结合,所以p是一个int型数组</span>
<span style="font-family:Microsoft YaHei;font-size:14px;">
int* p[3];<span style="white-space:pre"> </span>//优先级[]比*高,p是数组,再加上int*,可以称它为指针数组,数组的每一个元素的值都为指针(地址)</span>
<span style="font-family:Microsoft YaHei;font-size:14px;">
int (*p)[3];<span style="white-space:pre"> </span>//*p可以看作是普通变量,就回到第三种情况(int p[3]),但是这里p是指针变量,它指向的是一个包含3个整型<span style="white-space:pre"> </span>数值的数组。可以称它为数组指针,数组中每一个元素的值为普通整型值。</span>
<span style="font-family:Microsoft YaHei;font-size:14px;">
int** p;<span style="white-space:pre"> </span>//int*代表指针类型,*是指针类型,所以p是指向int型指针的指针,p指向的类型是int*类型(int型指针)</span>
<span style="font-family:Microsoft YaHei;font-size:14px;">
int p(int);<span style="white-space:pre"> </span>//这很明显是一个返回类型为int,并且带一个int型参数的函数</span>
<span style="font-family:Microsoft YaHei;font-size:14px;">
int (*p)(int);<span style="white-space:pre"> </span>//p是函数指针,指向的是返回值为int并且带一个int参数的函数。这个声明包含两部分:函数变量+函数地址变量<span style="white-space:pre"> </span>(姑且把函数也看做是变量)</span>
<span style="font-family:Microsoft YaHei;font-size:14px;">
int* (*p(int))[3];//这个有点复杂,它仍然是一个函数指针。从*p(int)看,它是函数指针,带一个int参数;然后看[],说明函数返回值为数组,然后返回类型为int*。所以p是一个指向返回值为int*型指针数组,并且带一个int型参数的函数的指针</span>
四、几个Demo
1.指针的算术运算
对一个指针加1或者减1,实际上是加上或减去它所指向的数据的长度,所计算的结果是一个新的地址值。
两个相同类型的指针还可以做减法运算,得到的结果是一个表示这两个地址间该类型数据个数的整数值。
2.指针与数组
<span style="font-family:Microsoft YaHei;font-size:14px;">int* p = &a[0];<span style="white-space:pre"> </span>//第一个元素的地址
int* p1 = a;<span style="white-space:pre"> </span>//数组地址(相同地址)</span>
<span style="font-family:Microsoft YaHei;font-size:14px;">
</span>
<span style="font-family:Microsoft YaHei;font-size:14px;"></span><pre name="code" class="cpp"><span style="font-family:Microsoft YaHei;">int main()
{
double wages[3] = {10000.0,20000.0,30000.0};
short stacks[3] = {3,2,1};
//两种方式去获取数组的地址——数组名或对数组首元素进行求址运算
double* pw = wages;
short* ps = &stacks[0];
cout<<"pw = "<<pw<<",*pw = "<<*pw<<endl;//打印出数组第一个元素的值和地址
pw = pw + 1;
cout<<"add 1 to the pw pointer:\n";
cout<<"pw = "<<pw<<",*pw = "<<*pw<<"\n\n";
cout<<"ps = "<<ps<<",*ps = "<<*ps<<endl;//打印出数组第一个元素的值和地址
ps = ps + 1;
cout<<"add 1 to the ps pointer:\n";
cout<<"pw = "<<ps<<",*ps = "<<*ps<<"\n\n";
//stacks[1] 等同于 *(stacks + 1)
cout<<"access two elements with array notation\n";
cout<<" stacks[0] = "<<stacks[0]<<", stack[1] = "<<stacks[1]<<endl;
cout<<"access two elements with pointer notation\n";
cout<<"*stacks = "<<*stacks<<",*(stacks + 1) = "<<*(stacks + 1)<<endl;
cout<<sizeof(wages)<<" = size of wages array\n"; //24字节
cout<<sizeof(pw)<<" = size of pw pointer\n"; //4字节
return 0;
}</span>
<span style="font-family:Microsoft YaHei;font-size:14px;"></span><pre name="code" class="cpp"><span style="font-family: 'Microsoft YaHei'; background-color: rgb(255, 255, 255);">
</span>
<span style="font-family: 'Microsoft YaHei'; background-color: rgb(255, 255, 255);"><strong>3.结构指针</strong></span>
<span style="font-family:Microsoft YaHei;font-size:14px;">struct MyStruct
{
<span style="white-space:pre"> </span>int a;
<span style="white-space:pre"> </span>int b;
<span style="white-space:pre"> </span>int c;
};</span>
<span style="font-family:Microsoft YaHei;font-size:14px;">
</span>
<span style="font-family:Microsoft YaHei;font-size:14px;">MyStruct ss={20,30,40};<span style="white-space:pre"> </span>//声明了结构对象ss,并把ss的三个成员初始化为20,30和40。
MyStruct *ptr=&ss;<span style="white-space:pre"> </span>//声明了一个指向结构对象ss的指针。它的类型是MyStruct*,它指向的类型是MyStruct。
int main(){
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>cout<<</span><span style="font-family: 'Microsoft YaHei';"><span style="font-size:14px;color:#3333ff;">ptr->a</span></span><span style="font-family:Microsoft YaHei;font-size:14px;"><<endl;<span style="white-space:pre"> </span>//<span style="line-height: 19.5px;">通过指针ptr来访问ss的三个成员变量</span>
<span style="white-space:pre"> </span>cout<<ptr->b<<endl;
<span style="white-space:pre"> </span>cout<<ptr->c<<endl;
<span style="white-space:pre"> </span>return 0;
} </span>
4.函数指针
可以把一个指针声明成为一个指向函数的指针。
<span style="font-family:Microsoft YaHei;">int fun1(int); </span>
<span style="font-family:Microsoft YaHei;">int main</span>
<span style="font-family:Microsoft YaHei;">{
<span style="white-space:pre"> </span>int (*pfun1)(int);
<span style="white-space:pre"> </span>pfun1=fun1;
<span style="white-space:pre"> </span>int a=(*pfun1)(7);<span style="white-space:pre"> </span>//通过函数指针调用函数。 </span>
<span style="font-family:Microsoft YaHei;">}</span>
<span style="font-family:Microsoft YaHei;">...</span>