11.1 基础
1.指针是一个变量,它存储着另一个变量或函数的地址,也就是说可以通过指针间接地引用变量。指针变量包含一个地
址,而且可以存储任何数据类型的内存地址,但指针变量却被声明为特定的数据类型,一个指向整型数据类型的指针
不能存储一个浮点型的变量地址。
2 .指针声明的形式为,数据类型 *指针变量名;其中*星号是指针运算符,例如 int *x;声明x 为int 型指针.
11.2 指针运算符*和&地址运算符
1.&地址运算符是一元运算符,能反回它的操作数的内存地址.如y=&x ;把变量x 的地址输入到y 中,它与x 的值无
关,比如x 的值为1000,而x 的地址为55 则,y 将接收到地址55.
2 .*指针运算符是一元运算符,它是&运算符的相反形式,*运算符能反回位于其操作数所指定的地址的变量的值.例
如y = &x;z = *y;假设x 的值为1000,地址为55,则第二条语句说明z 的值为1000,*y 把由y 所指向的内存的地
址的变量x 的值赋给z 。*运算符可理解为 “在地址中”,则z=*y 可描术为 “z 接收了在址址y 中的值。”,
3.其实可以把*y 当成一个变量来使用,即可以为*y 赋值等,例如*y=100;(*y)++;等,但要注意的是对*y 的操作相当
于是对此指针指向的地址中的变量的操作,即对*y=100 的赋值语句,相当于是x=100,而(*y)++则相当于x++ 。
11.3 指针的运算
0.指针只支持4 种算术运算符:++,――,+,-.指针只能与整数加减.指针运算的原则是:每当指针的值增加时,
它将指向其基本类型的下一个元素的存储单元.减少时则指向上一个元素的存储单元.
1.++,――运算符,假设int 型x 的地址为200,且int 型占4 个字节,定义int *p;p=&x ;则p++ 的地址将是204,而
不是201,因为当指针p 的值增加时,它都将指向下一个int 型数据.减少时也是这样,如p ――则,p 的地址将是196.
2 .+,-,运算符,注意两个指针不能相加.例int *p;p=&x ;假设x 的地址为200,则p+9 将的指针地址将是200+4*9=236,
即p 指向了从当前正指向的元素向下的第9 个元素.
3.两指针相减,同类型的一个指针减去另一个指针的值将是两个指针分开的基本类型的元素的个数.
11.4 指针和数组
1.在C++语言中使用没有下标的数组名会产生一个指向数组中第一个元素的指针.如char x[20] ;char *p;p=x ;此语句
说明将x 数组的第一个元素的地址赋给指针p .
2 .*(p+4)和x[4]两句都可以访问数组中第5 个元素,这里假设int x[33] ;int *p;p=x ;因为p 是指向数组x 的第一个元
素地址的指针,而p+4 就是指向第五个元素的指针,而*(p+4)就是第五的个元素了.
3.p[i]语句相当于*(p+i)或x[i] 即数组中第i+1 个元素的值,假设char x[20] ;char *p;p=x ;
11.5 字符串常量
在 中字符串常量会被存储到程序的串表中,所以语句 ;”” ;是合法的,该语句将字符串常量存储
在串表中的地址赋给指针变量 .
11.6 指针数组
声明形式 ;该语句声明了 个指针数组,每个数组中存储一个整数值地址. ;语句为指针变量的第
三个元素赋予 变量的地址现在要访问 变量的值需要编写.即访问指针数组第三个元素的地址指向的变量的值.
11.7 空指针
如果指针包含了空值,就假定其未指向任何存储,这样能避免意外使用未初始化的指针.例: ;初始化为空
11.9 const 指针
. const int *p=&x; 声明了一个指向 型常量的指针,也就是说不能用 来修改 的值,也就是说 的值不能
被修改。即语句 将是错误的。虽然 不能修改 的值,但可以通过变量 来修改 的值。 的声明并不代表 指
向的值实际上是一个常量,而只是对 而言这个值是常量。可以让 指向另一个地址,如 这时 指向的
值为,但指针 同样不能修改他所指向的变量 的值。
. 表明既不能用变量 来改变 的值,也不能用指针 来改变变量 的值。
这样做将发生错误,因为如果把 的地址给了,而指针 又修改了 的值时,这时 就违反
了是常量的规定,所以不允许这样做。
. int * const p=&x;这种方式使得指针 只能指向 的地址,即指针 的地址不能改变,但可以通过指针 来修
改 所指向的变量 的值。
. const int * const p=&x; 这种方式使得指针 既不能修改变量 的值,也不能改变 所指向的地址。
11.10 指针与二维数组(对指针的透彻理解)
1、两条基本准则:
、首先要明白,指针运算符的作用,我用一言以概之,你在哪里使用都不会错。指针运算符的作用是求出后面所指地
址里的值。因此只要后面的变量表示的是一个地址就可以使用运算符,来求出这个地址中的值,你不用管这个地址
的表示形式是怎样的,只要是地址就可以使用来求出地址中的值。
、 这个运算符的的运算法则是,把左侧的地址加上 内的偏移量然后再求指针运算,注意有 运算符的地方就有个隐
含的指针,比如表示的就是将指针 偏移 个单位量后再求指针运算。也就说与是相等的。
2、对二维数组的讲解:
a、对一维数组地址的详细讲解:
比如一维数组,众所周知,一维数组的数组名 表示的是第一个元素的地址,也就是说数组名与是等价
的,因此数组名 的地址,并不是这个一维数组的数组的地址。那么,表示的又是什么呢?因为, 是一个一维数
组,所以 表示的就是这个一维数组的地址,也就是说 中的地址是一个包含有 个元素的一维数组的地址。就好
比 中的,才表示的是这个变量 的地址一样。
b、对二维数组地址的讲解:
比如二维数组 ,我们首先从一维开始分析,众所周知 ,分别表示的是二维数组中第一行第一个元素和
第二行第二个元素的地址,也就是说 是与等价的,是与等介的。因此数组名就与等介
的,也就是说数组名表示的是二维数组中第一行所包含的一维数组的地址,说简单一点,就是说二维数组名是二维数
组中第一行的行地址。因此二维数组名 所包含的地址中包含有 二维数组中第二维中元素的个数的一维数组,也就是
的地址中包含一个含有 个元素的一维数组的地址也就是所谓的数组的数组了 。
c、对二维数组中地址的相加运算的讲解:
同样以b[3][4]为例来讲解,在上面讲到b[0]表示的是&b[0][0],因此对b[0]进行相加运算,比如b[0]+1 ,那么就将使
地址偏移一个单位,也就是地址被偏移到了&b[0][1]处,也就是b[0]+1 表示的是b[0][1] 的地址。上面也讲到数组名b ,
表示的是一个一维数组的地址,因此对数组名进行偏移,比如b+1,则将使指针偏移一个一维数组的长度,也就是b+1,
将是&b[1]的地址,因此b+1 的地址,表示的是二维数组中第二行所包含的一维数组的地址,简单点就是第二行的行
地址。
d、对二维数组的指针运算:
*b
在上面讲解过,因为b 表示的是二维数组中第一行所包含的一维数组的地址,因此*b=*(b+0)=*(&b[0])=b[0],所以*b
表示的是二维数组中第一行第一个元素的地址,即*b=&b[0][0],用语言来描术*(b+0)就是,把数组名的地址偏移0 个
单位,然后再求这个地址所包含的值。在二维数组中,这个值就是指的b[0] ,因此这个值是与b[0][0] 的地址相等的,
结果就是*(b+0)与b 是相等的,这在多维数组中是个怪现象,至少本人无法理解这是为什么。因为对同一个地址,进
行指针运算得到了不同的结果,比如*b 和*b[0],因为b 和b[0]都是相同的地址,但对他们进行指针运算却得到了不
同的结果,*b 得到了&b[0][0]的地址,而*b[0]得到了b[0][0] 的值,这是对同一个地址 进行指针运算却得到了不同的值,
对于这个问题,无法理解。
*(b+1)和*(b+0)
对于*(b+1)也和*(b+0)是同样的道理,*(b+1)=b[1]。我们再来看*b[1],因为*b[1]=*(b[1]+0)=*(&b[1][0])=b[1][0],可以
看出,这就是二维数组中第二行第一个元素的地址。
*(*(b+1)+1)
因为*(*(b+1)+1)=*(*(&b[1])+1)=*(b[1]+1)=*(&b[1][0]+1)=*(&b[1][1])=b[1][1],语言描术就是,b+1 使地址偏移到了二
维数组中第二行所包含的一维数组的地址(或第二行的行地址),然后再对这个行地址求指针(或求值)运算,因此就得
到第二行第一个元素的地址,然后再对这个地址偏移一个单位,就得到第二行第二个元素的地址,再对这个地址进行
指针运算,就得到了这个元素的值,即b[1][1] ,其他的内容可以以止类推。
e、对二维数组的指针和[ ]的混合运算
在下面的指针和[ ]的混合计算中,要记住两点关键法则,记住了这两点在哪里计算都不会出错
a、对于像b[1]这样的地址,最好应表示为&b[1][0]再进行偏移计算,比如对于b[1]+1,这不是直接在对b[1]加 1,也
就是b[1]+1 不等于b[2] ,因为b[1]表示的是第二行行1 个元素的地址,对其加 1,应该表示的是第二行第二个元
素的地址,也就是&b[1][1],而b[2]则表示的是第二行第一个元素的地址,因此错误,所以在计算时应把b[1]转换
为&b[1][0]之后,才能直接进行地址的偏移,也就是说 b[1]+1=&b[1][0]+1=&b[1][1] ,这样才能得到正确的结果,
并且不会出错。
b 、对于有小括号的地方,一定不要省略小括号。比如(&b[1])[1]与&b[1][1]将表示的是不同的结果,第二个是显然的,
对于第一个(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],可以看到,表示的是第3 行第 1 个元素的地址,
因此这两个 的结果是显然不一样的。因此对于(b+1)[1] 这样 的运算,不 能省略小括号,即
(b+1)[1]=(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],如果省略了小括号,则是(b+1)[1]=&b[1][1],这将是
不易发现的错误。因此这是两个 全全不同的符案。
c、总结,指针和[ ]混合运算2 点关键,
第 1:应把是地址的[ ]运算,转换为地址的形式,比如b[1]应转换为&b[1][0] 。因为只有这样才能进行直接的地址
相加运算,即&b[1][0]+1=&b[1][1],而b[1]+1 不等于b[2] 。
第2:有小括号的地方不能省略小括号,如(b+1)[1]=(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],也&b[1][1]
是完全不同的。
(*(b+1))[1] ,(*b)[1]
最简单的理解方法为*(b+1)和语句b[1]等价,即(*(b+1))[1]和语句b[1][1]是相同的,也就是二组数组第2 行第2 个元
素的值b[1][1],理解方法2 逐条解释,如上面的解释*(b+1)表示的是二维数组b 的第二行第一个元素的地址,也就是
b[1] ,然后再与后面的[1]进行运算,就得到b[1][1] 。或者(*(b+1))[1]=*((*(b+1))+1)=*(*(b+1)+1)这个语句上面解释过。
同理(*b)[1]=b[0][1]
*(b+1)[1]:
计算方法1 把[ ]分解为指针来计算:因为[ ]运算符高于指针,因此应先计算[ ]再计算指针,因为[1]表示的是把左侧的
地址偏移 1 个单位,再求指针, 因此(b+1)[1]=*((b+1)+1) ,最后再计算一次指针运算,也就 是
*(b+1)[1]=**((b+1)+1)=**(b+2)=*(*(b+2))=*b[2]=b[2][0],可以看到,最后表示的是b 中第3 行第一个元素的值。
计算方法2 把指针化为[ ]来计算:*(b+1)[1]=*(&b[1])[1]=*(*(&b[1]+1))=**(&b[2])=*b[2]=b[2][0],注意*((&b[1])[1])表
达式中应把(&b[1])括起来,若不括起来,则[ ]运算符的优先级高于&运算符,因此(&b[1])[1]与&b[1][1]是不一样的,
后一个表示的是第二行第二个元素的地址,而头一个(&b[1])[1]则表示的是,对 b 的第二行的行地址偏移1 个单位后
再求指针的结果,也就是*(&b[1]+1)=*(&b[2])=b[2],所以性质是不一样的。
(*b+1)[2]
计算方法1 化简[ ]运算符:(*b+1)[2]=*((*b+1)+2)=*(*b+3)=*(&b[0][0]+3)=*(&b[0][3])=b[0][3],这里要注意*b 表示的
是b[0]=&b[0][0] ,在计算时最好不要代入b[0]来计算,而应把b[0]转换为&b[0][0]后再计算,因为b[0]+3,很容易被
错误的计算为b[3] ,而实际上b[0]指向的是第一行第一个元素的地址,对其偏移3 个单位应该是指向第一行第4 个元
素的地址,即&b[0][3],而b[3],则指向了第3 行第1 个元素的地址,这是不同的。
计算方法2 化简*运算符:(*b+1)[2]=(b[0]+1)[2]=(&b[0][0]+1)[2]=(&b[0][1])[2]=*(&b[0][1]+2)=*(&b[0][3])=b[0][3],注
意,在计算过程中小括号最好不要省略,省略了容易出错,因为[ ]运算符的优先给更高,如果省略了,某些地方将无
法计算。比如(&b[0][0]+1)[2]=(&b[0][1])[2],如果省略掉括号,则成为&b[0][1][2],这对于二维数组来讲,是无法计
算的。
(*(b+1))[5]
计算方法:(*(b+1))[5]=(*(&b[1]))[5]=(b[1])[5]=*(b[1]+5)=*(&b[1][0]+5)=*(&b[1][5])=b[1][5],结果等于第二行第 6 个
元素的值。
f、注意,在二维或者多维数组中有个怪现象,比如对于多维数组a[n][m][i][j],那么这些地址是相同的,即数组名a, a[0],
a[0][0], a[0][0][0], &a[0][0][0][0],都是相同的地址。而且对数组名依次求指针运算将得到,比如*a=a[0],*a[0]=a[0][0],
*a[0][0]=a[0][0][0], *a[0][0][0]=a[0][0][0][0],可以看到,只有对最后这个地址求指针运算才真正得到了数组中的值,
因此对数组名求指针运算,要得到第一个元素的值,应该****a,也就是对4 维数组需要求4 次指针运算。同样可以
看到,对数组名进行的前三次指针运算的值都是相同的,即*a, **a, ***a 和a 的值都是&a[0][0][0][0]的值,这就是这
个怪问题,按理说对地址求指针应该得到一个值,但对多维数组求指针,却得到的是同一个地址,只是这些地址所包
含的内容不一样。
3、数组指针与二维数组讲解:
下面我们将以y[4]={1,2,3,4}这个一维数组为例来层层讲解,指针和数组的关系。
1、数组指针:
定义形式为:int (*p)[4];表示定义了一个指向多维数组的指针,即指针p 指向的是一个数组,这个数组有4 个元素,
对这个指针p 的赋值必须是有4 个 int 元素的数组的地址,即只要包含有 四个元素的数组的地址都能赋给指针p,不
管这个数组的行数是多少,但列数必须为4 。即int y[4],x[22][4];都可以赋给指针p 。赋值方式为p=&y 或p=x ,对于
&y 和二维数组数组名前面已讲过,&y 中的地址是一个包含有4 个元素的一维数组的地址,二维数组名x 也是一个含
有4 个元素的一维数组的地址,因此可以这样赋值。而这样赋值将是错误的p=y,或者p=x[0]; 因为y 和x[0] 的地址
只包含一个元素,他们不包含一个数组,因此出错。
2 .注意()必须有,如果没有的话则,int *p[4];则是定义了一个指针数组,表示每个数组元素都是一个指针,即p[2]=&i;
指向一个int 型的地址,而*p[2]则表示p[2]所指向的元素的值。
3.初始化数组指针p:
a、当把int y[4]赋给指针p 时p=y 将是错误的,正确的方式为p=&y 因为这时编译器会检查赋给指针p 的元素是否是
含有四个元素的数组,如果是就能正确的赋值,但语句p=y 中的y 代表的是数组y[4]第一行第一列的元素的地址
也就是&y[0]的地址,因此y 指向的地址只有一个元素,而指针p 要求的是有4 个元素的数组的地址,因此语句
p=y 将出错。而&y 也表示的是一个地址,因为数组名y 表示的是&y[0]的地址,因此&y=&(&y[0]) 。可以把&y 理
解为是数组y[4] 的第一行的行地址,即&y 包含了数组y[4] 的第一行的所有元素,在这里&y 包含有4 个元素,因
则p=&y 才是正确的赋值方法,在这里要注意,数组的某行的行地址是第本行的第一个元素的地址是相同的。
b 、把x[22][4]赋给指针p 有几种方法,方法一:p=x ;我们这样来理解该条语句,首先x[0]表示的是二维数组x[22][4]
的第1 行第一列元素的地址,这个地址包含一个元素,这是显而易见的,而数组名x 也表示一个地址,但这个地
址包含的是一个数组(即数组的数组),这个数组是包含4 个元素的数组,这4 个元素就是数组x 第一行的4 个元
素,也就是说x 表示的是数组x[22][4] 的第1 行的行地址,即数组名x 就包含了数组x[22][4]第 1 行的4 个元素,
因此这种赋值方式是正确的。这时指针p 就相当于是数组名一样,比如p[2][1]访问的就是数组x 的第3 行的第2
个元素。
c、方法二:p=x+1 或者p=&x[1];注意必须要有地址运算符&,同理语句&x[1]表示的是数组x[22][4]第2 行的行地址,
因为x[1]表示的是数组x[22][4]第的第2 行第1 列的元素的地址,因此&x[1]表示的就是数组x 的第2 行的行地址,
因为&x[1]这个地址包含了一个数组,这个数组的起始地址是从x[1]这个地址开始的,这,即数组x[22][4] 中x[1]
这一行的4 个元素。在这一行中包含了4 个元素。而 x+1 本身就是指的x[1] 的地址。这时指针p 的起始地址是
&x[1],所以p[0][1]不再是访问的x 的第一行第二个元素,而是访问的x 的第二行第二个元素。
d、注意,再次提示,数组的某行的行地址是与本行的第一个元素的地址是相同的。
4 .访问成员:
a . 可以把数组指针p 当成数组名来理解,即可以像使用数组一样使用指针,比如p[1][2]访问数组x 中的第二行第
三个元素7。这种方法容易理解。
b. 访问数组元素的方法:记住二条法则*(p+1)与语句p[1]或者x[1]等价。
法则二:[ ]这个运算符的的运算法则是,把左侧的地址加上[ ] 内的偏移量然后再求指针运算,注意有[ ]运算符
的地方就有个隐含的指针,比如x[2]表示的就是将指针x 偏移2 个单位量后再求指针运算。
5.int (*p)[4]与语句int x[][4]等价。
int (&p)[4]引用和指针有一些差别因为对引用赋值必须是变量的名字,所以int y[4] ;int (&p)[4]=y;是正确的而用&y 将
得到一个错误的结果,同样int x[22][4]; int (&p)[4]=x[1];才是正确的,不能用x+1 或者&x[1]来初始化引用。int (&p)[4]=x
也将得到一个错误的结果,只能是int (&p)[4]=x[0];对于引用来说语句p[0][1]将是错误的,只能是p[0] ,p[1] ,依次
访问p 所引用的对象的第一个元素的值和第二个元素的值,int (&p)[4]=x[1];p[1]将访问的是p 所引用的第二个元素
的值也就是数组x 第二行第2 个元素的值。
例:指针与二维数组int (*p)[4].