6 F0 M3 b6 _: l' J) @& n# N 指针的值或者叫指针所指向的内存区或地址,是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。 指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。 - T- x: u4 Y$ E; V: k# A5 N/ U7 R : x# P0 j$ ]7 H+ M 指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在上例中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。 6 v1 ^# j0 M* z. g1 f4 t* ?0 O1 a8 |& Y$ [1 ?( D, s
以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里? 3 X6 p( Z, k- o$ i; I# {2 n) E e( E, A- L; e% Q
1.2.4指针本身所占有的内存区 ( K& V9 R. h3 C% P1 f, m. T/ V+ t$ ]
指针本身所占有的内存区是指针本身占内存的大小,这个你只要用函数sizeof(指针的* F/ \( @! r1 T: M) m2 _ g
% E3 ?" Z2 {/ Y3 D5 H
类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。 7 | J: w! w4 F, ?. K6 m! N" } . P/ i) Q5 P# u% H 指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。; U. G H# \5 E# o! h0 p
5 |6 t# i1 N% m' `& N: R
C/C++指针精髓(二)5 _0 o$ `2 g/ H& k# O1 f9 {. p! h1 `
1.3指针与内存管理: m1 S, m0 U. o2 |: }# u2 F
( @# u% v/ c+ T J7 L7 o6 U
利用指针你可以将数据写入内存中的任意位置,但是,一旦你的程序中有一个野指针("wild“pointer),即指向一个错误位置的指针,你的数据就危险了—存放在堆中的数据可能会被破坏,用来管理堆的数据结构也可能会被破坏,甚至操作系统的数据也可能会被修改,有时,上述三种破坏情况会同时发生。所以合理的正确的分配指针的地址是非常重要的。 1 y2 ~- B$ p! M8 H8 l5 S; R2 O2 J* J. F' p0 u: f6 b+ S
1.3.1内存分配的方式 $ H7 }9 Q4 ^, P7 ^ ! G% c! S' w$ ~; C 内存分配方式有三种:& P% {% D/ P i
% M Z+ b0 V5 @6 c
(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。7 \) r7 f1 ^$ w; j. ]* n3 v; o
7 O( B8 @" \$ ^2 F4 N
(2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 2 q- c9 L( X# z2 o% f2 v: ^5 ?4 T0 m2 w5 {3 B6 f u
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多,以下我们重点讲解动态内存分配。 3 f2 I* v7 w( H% R, K- R7 p, g3 C F- A; u6 |( W" D1 ]5 j4 i8 G: k 1.3.2 malloc/free 的使用要点* l+ Z3 G/ {0 E! i, M. i- Y
6 u% Z; J* j J4 o8 I; H' D. Z
malloc与free是C/C++语言的标准库函数,它用于申请动态内存和释放内存。" r' G% @* W5 i
- h1 y6 a# L4 G6 ~/ W6 g% \* p
函数malloc的原型如下:& u1 P$ q; r8 R+ N+ K1 u. w U; \. D+ {
7 Y% }: W1 i9 {, g
void * malloc(size_t size); ' @% b5 @2 @7 d* ^* M! J0 G0 B, d" w8 d- t1 ^# z
用malloc申请一块长度为length的整数类型的内存,程序如下:" [- p7 ]- W9 b2 _, u1 O& @9 @
1 u8 n4 g2 \/ B0 w6 w
int *ip = (int *) malloc(sizeof(int) * length);. z8 a; z6 C* l/ n$ D3 ?( x3 L M
" _. m- ]0 K. c& Y5 m 我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。 7 r: d- o3 x0 z- u2 V7 K* j a- Q. P9 R
malloc函数返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。5 ~: q! _2 L5 e
5 G6 T6 X" L5 z malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。例如int变量在16位系统下是2个字节,在32位下是4个字节;而float变量在16位系统下是4个字节,在32位下也是4个字节。这个你可以用sizeof(类型)去测试。 & G( a& K7 c x E, Z1 g( t" Q/ X- A" \3 d( W
在malloc的“()”中使用sizeof运算符是良好的风格,但要当心有时我们会昏了头,写出 ip = malloc(sizeof(ip))这样的程序来。 , q) G: L8 [& t$ L 5 Q; O0 e N; F$ y2 {) v 函数free的原型如下:3 B, e) C( d* j3 B$ x7 t% g& j
3 C5 C8 g2 [) _: e+ q void free( void * memblock );2 s A7 m* X7 q- H
3 Q( C# B5 n- \, f% Q% _6 _ |; X
为什么free函数不象malloc函数那样复杂呢?这是因为指针p的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。如果p是NULL指针,那么free对p无论操作多少次都不会出问题。如果p不是NULL指针,那么free对p连续操作两次就会导致程序运行错误。7 `( O j4 O+ @
' i( B- ^9 @9 k v 1.3.3 new/delete 的使用要点2 ]& W; |" ^0 G
: t3 ~# y$ X! C7 b& I+ X; L
对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free./ b b- ?. x/ A& t/ l' m
; W# k- C$ Y. ]1 l (3)指针操作超越了变量的作用范围。这种情况让人防不胜防,示例程序如下:% E: q7 a" R- `
5 }0 V8 I& N, l6 D! h class A , [% c4 W8 q, r+ A) [9 B6 ^' A0 @, x1 N# p
{ ! Y% g- l, a, i" q; o$ k4 t, l! X6 k0 h
public: , C& Z8 H5 W2 n7 \( b$ x + W) L* y7 i2 s' W void Func(void){ std::cout << “Func of class A” << std::endl; }4 c) R0 z" [2 Z
6 m4 F/ v8 _1 J( ~1 M4 j};* X# I" _4 X# t5 R
/ |# G! H- l* Z
void Test(void) 4 i6 a3 `0 M9 O7 G 5 w2 Y/ z$ X o{ 7 q" w) L3 U% n! Z! B. a, ~" O& q, D, I1 ]' b$ k' ?1 f3 N
A *p; + b5 V* X# D+ L K, f 6 G# f3 A& X9 T. ], R! _( _ {+ S# V* t2 B8 h8 J
) y$ t! ^8 G0 z; X A a; 1 y/ o" v( D9 r2 k c! K& e5 F7 ]/ L& c* y2 p9 \5 R+ h0 G1 }, Z
p = &a; // 注意 a 的生命期 : y! r/ a( j" ^* u+ S1 l9 w - l( u" j* E+ G, l1 m$ D8 P } 5 G9 A! i- B/ A1 \# }' S $ j7 l* a# i* ~( g8 @ p->Func(); // p是“野指针” % ?4 n2 j9 p4 R, {# N) }& h: @' m V2 _/ k* I: \. _* b
}+ t, P5 Y7 @" t4 a
& b/ P4 L; B$ K! F* B strcpy(str, "hello"); // 运行错误: i( ]( V: M. E0 ^7 C) p% B' g
6 O6 s! T( Y! m2 F' ?6 d0 b* K
} $ B( b) W& N# X+ D4 E5 v % V" J, U, A. {1 ~1 d0 }# ^
2 G& ~ ] C/ G5 `0 ~ 毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数ip的副本是 _ip,编译器使 _ip = ip.如果函数体内的程序修改了_ip的内容,就导致参数ip的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_ip申请了新的内存,只是把_ip所指的内存地址改变了,但是ip丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。 ( f' x" a0 M+ ]. T) M: G N v% m& q4 L# q, n% m
如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针(二级指针)”,见如下示例: & q7 \6 S a, X4 e; b& W! U) F* r. ~+ q& J$ d
void GetMemory(char **p, int num)% c8 A6 P9 } V t% d' {
6 u9 {* v- u/ S& _2 ?1 L. l% S{ 3 | ?- A4 F5 w2 k# z2 v - {0 N( k' t3 l4 k/ e j" _ *ip = (char *)malloc(sizeof(char) * num);& ~. m/ k l- A6 Q: l ]9 c0 y( e8 n
1 h' {3 i- E7 ?5 ^- S& ]* \$ W
} $ T% @( w# c6 _6 W, w* u- j, j9 r. x: R {0 S; B
void Test(void) 5 ]- n3 b& J. r+ Y! I, G! [4 X5 C- _- h* Y
{2 w* F" I3 w9 ^( ?. Z' ~$ ]
: J% l6 d, V, h7 m; |9 m; _4 i ) e; V1 [4 T( i- T( e, w0 TC/C++指针精髓(三) ! L' ]7 h( y* Y! N0 _1 K7 H, P1.4指针的运算 ( U4 \6 I$ _) A1 R n& f$ `* h' i9 A+ q- ]" o. K! y& E
1.4.1赋值运算2 c$ K, p9 I- W: e
2 \; ^7 j! X. J1 F
指针变量的赋值运算有以下几种形式:2 u, s' G6 d D" _ E6 I" u
+ E3 h# f& _& |5 N# j4 \3 ?# K- e
1.4.1.1指针变量初始化赋值如下:- m- y F& h) `* @! u. D' O" |( j# D
. ?8 Q1 ~* F/ M% m, r" ^+ u int a; , y8 I1 |) O3 }9 f3 H6 l, l8 f ) {* Z* c) j0 f u int *ip=&a;5 y# q; w% h7 j ~- \, f+ | I) n
4 e) ?! [. [) G6 O5 j
1.4.1.2 把一个变量的地址赋予指向相同数据类型的指针变量。例如: & D4 V7 Z9 V; |% P4 a 6 |$ Y# L$ H5 _' o" c5 ] int a; 7 c6 Y r G2 f% }8 I9 Y1 `( |% d1 I - }$ ~9 I9 z6 l+ I int *ip; 4 {0 z' W' d3 @# p B ( s p; x+ ?% P: O$ f4 r/ l ip=&a; //把整型变量a的地址赋予整型指针变量ip e; e5 z0 G' s7 g' v$ ?
0 i2 F7 m$ U' }) a5 v7 Z 1.4.1.3把一个指针变量的值赋予指向相同类型变量的另一个指针变量。例如:) B0 h+ O; u9 r `& q a
, G" k3 ^- W) `7 f6 ]/ B- K+ C" c L
int a; 6 ~1 b3 V4 x# M/ n, E4 y$ @ 3 R* N% q0 d8 v$ }1 _+ o( G int *pa=&a;; @% V( W5 L+ L1 M( `
+ B) @% R/ e# U8 s- { int *PB;5 t; h- x# v3 s* _7 i- N$ d& q. j+ e
4 P, I; F r8 X
pb=pa; //把a的地址赋予指针变量pb : K8 _4 |# J3 z, S/ w7 r! h( n- Z' \ E! Y
由于pa,pb均为指向整型变量的指针变量,因此可以相互赋值。 ( e5 W- B4 y6 M' A) a7 J \) e, m* I( G( U7 D/ M 1.4.1.4把数组的首地址赋予指向数组的指针变量。例如: + l( I9 h5 ?, F3 i/ ]$ l4 y/ Q ]" w* g8 |' k- G
int a[5],*pa; 2 c e6 w' p. v) d) j2 Q, `' d; L3 g' _
pa=a; //数组名表示数组的首地址,故可赋予指向数组的指针变量pa& l- n/ ~# h( Z3 Z# Q; V& p- {
( f6 R$ K9 }, Q2 K; c
也可写为: 5 `; O! a' }) l- l2 h( Z% K% U0 l" B# F9 M; m$ Z1 ]5 {$ A
pa=&a[0]; //数组第一个元素的地址也是整个数组的首地址也可赋予pa ' ], `9 o, ~2 F/ ^ 1 i2 ]) h5 E0 i) V4 b 当然也可采取初始化赋值的方法:8 M2 ^: Y: e. r3 w7 h
" |/ C" X& J3 Y5 ?8 i2 k
int a[5],*pa=a;, ?. G4 [. @; V' U. _
: P! g! t s+ w) r$ z6 f% n9 N
以上是一些基本的数组赋值方法,后面我们会详细讨论指针在数组中的使用。 3 e/ t/ ^4 Z: a5 w7 }6 o) T# R( c 1 W2 z2 l: l* m7 q9 d 1.4.1.5把字符串的首地址赋予指向字符类型的指针变量。例如:, G- C6 e7 s. [) M2 a& S8 z* }
2 u9 t! k8 y* n& `, F char *pc; . T5 }- j* o) [$ x6 m* S ?. w7 h, |4 x pc="c language"; * m3 X: x$ {/ \) q; k, Z8 r( [! J0 u P$ K% {
或用初始化赋值的方法写为:( ?0 ~7 u" {2 }( z( i p
2 V2 s# q5 O+ t- ~: }( V' | char *pc=" c language ";7 I# v! }. O, t$ |7 |' }
, W- R7 N* a- h# d# A
这里应说明的是并不是把整个字符串装入指针变量, 而是把存放该字符串的字符数组的首地址装入指针变量。 ' d% Y) g- v4 B , Z; Q2 F, P% I$ O2 ] 1.4.1.6把函数的入口地址赋予指向函数的指针变量。例如:: }" ?# m# L( G) X% A
. ?" H' u5 Q. X y- C# D
int (*pf)(); ) [ E0 z& G: @9 J/ }, }: K: } % n; }8 k" Q4 F' r p' u' y! c pf=f; //f为函数名0 a2 E1 [8 X$ K4 |, M& n
0 `( U y; y2 U' C& Q+ @1 S& j
1.4.2加减运算 . s; y' p+ y" J& F3 v F ' F7 Q } h' ?" u! G" Y 对于指向数组的指针变量,可以加上或减去一个整数n.设ip是指向数组a的指针变量,则ip+n,ip-n,ip++,++ip,ip——,——ip 运算都是合法的。指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置。应该注意,数组指针变量向前或向后移动一个位置和地址加1或减1 在概念上是不同的。因为数组可以有不同的类型, 各种类型的数组元素所占的字节长度是不同的。如指针变量加1,即向后移动1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1.看如下例子:. U3 X2 ]6 r7 l5 t
; m) T$ P7 @' D, F z) q6 l! F; |3 F; ~" c2 m
char a[20]; % L: `( f9 G/ |5 T! S int*ip=a; 3 O% O# D$ [" J( u( K
... 3 I' Z8 I! m1 Y) J/ ^ ip++;; U; ^! c" M. q( |
4 b9 k; @- ]/ S% j" e, D) B' }- A* \* {
在上例中,指针ip的类型是int*,它指向的类型是int,它被初始化为指向整形变量a.接下来的第3句中,指针ip被加了1,编译器是这样处理的:它把指针ip的值加上了sizeof(int),在32位程序中,是被加上了4.由于地址是用字节做单位的,故ip所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。 # v1 b1 M9 p) L# F n! V# _: |5 ^9 b. b2 c$ s3 S! A
由于char类型的长度是一个字节,所以,原来ptr是指向数组a的第0号单元开始的四个字节,此时指向了数组a中从第4号单元开始的四个字节。再看如下例子:) t% l+ b5 f5 k7 U' H! f0 Q
$ l$ |, B2 J* C! Y6 M
2 b- B8 X n8 D1 b' { b char a[20]; + Y4 M/ e/ K8 j2 Q3 W
int*ip=a; 0 E; L4 {% T& V" G; Q& w ... 2 j$ A5 b. X( B# h
ip+=5;: P! L! O9 P! \2 L9 J
1 b1 z: Q8 m$ ^
; o# Y6 m" i) s# Y0 u
在这个例子中,ip被加上了5,编译器是这样处理的:将指针ip的值加上5乘sizeof(int),在32位程序中就是加上了5乘4=20.由于地址的单位是字节,故现在的ip所指向的地址比起加5后的ip所指向的地址来说,向高地址方向移动了20个字节。在这个例子中,没加5前的ip指向数组a的第0号单元开始的四个字节,加5后,ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。 3 t5 T7 Q/ @" n" _6 T6 t 9 K+ P0 b* t0 q! Q: U1 D 如果上例中,ip是被减去5,那么处理过程大同小异,只不过ip的值是被减去5乘sizeof(int),新的ip指向的地址将比原来的ip所指向的地址向低地址方向移动了20个字节。 ; C; V+ A! m! m' x5 E # m; u3 S L; [ 总结一下,一个指针ipold加上一个整数n后,结果是一个新的指针ipnew,ipnew的类型和ipold的类型相同,ipnew所指向的类型和ipold所指向的类型也相同。ipnew的值将比ipold的值增加了n乘sizeof(ipold所指向的类型)个字节。就是说,ipnew所指向的内存区将比ipold所指向的内存区向高地址方向移动了n乘sizeof(ipold所指向的类型)个字节。 ; }7 P+ W' {. T7 L$ m/ C 1 V7 m8 x" h3 N% f6 ^) d 一个指针ipold减去一个整数n后,结果是一个新的指针ipnew,ipnew的类型和ipold的类型相同,ipnew所指向的类型和ipold所指向的类型也相同。ipnew的值将比ipold的值减少了n乘sizeof(ipold所指向的类型)个字节,就是说,ipnew所指向的内存区将比ipold所指向的内存区向低地址方向移动了n乘sizeof(ipold所指向的类型)个字节。 4 a9 T( M/ e# [$ R) l0 s# g r
4 \3 y) Q4 L$ o. W U: i7 }9 Z1.4.3关系运算 6 ^( w5 Q& ]0 ]2 r# | 2 [' B( ^0 Y' ~- q5 c. X0 ^" P4 q7 [ 指向同一个数组中的不同元素的两个指针可以进行各种关系运算。例如:( V7 \+ B- d6 ?! b
, p) C0 [/ { p& I2 q
ip1==ip2表示ip1和ip2指向同一数组元素 0 l6 R* Y4 X9 ` p3 A& u8 M h. v/ T6 U' J ip1>ip2表示ip1处于高地址位置* {3 G2 G. }: z, x2 H$ R
% {3 Z4 W' k' ?: x. g( Q" q, g
ip1<IP2表示IP2处于低地址位置< p> 0 t9 k# C6 s, Z $ j$ B; b0 }- v1 J9 G- r 指针变量还可以与0比较。设ip为指针变量,则ip==0表明ip是空指针,它不指向任何变量;ip!=0表示ip不是空指针。空指针是由对指针变量赋予0值而得到的。例如: 7 k% A- E8 R: w# N- y ( Q6 o/ @; \3 Y3 a0 o7 f C L #define NULL 0/ y2 g9 Z: h" d9 H
( |8 P( O- R# n( f
int *ip=NULL;3 h- G6 ^6 N2 u& B3 S
0 Q! ?& S. }# Z! m, a; q
对指针变量赋0值和不赋值是不同的。指针变量未赋值时,可以是任意值,是不能使用的。否则将造成意外错误。而指针变量赋0值后,则可以使用,只是它不指向具体的变量而已。7 \, y" a% t1 D& @: j
! S- H% R. Q5 V6 A* ~# | 1.4.4取地址运算符‘&’和取内容运算符‘*’# I L. f! \: ~% G. v
5 N( E& K. t9 K 取地址运算符&是单目运算符,其结合性为自右至左,其功能是取变量的地址。 4 [7 ?* h' j. Z: t- T) P " P0 {/ K3 e0 M$ y+ N5 s" { 取内容运算符*是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在*运算符之后跟的变量必须是指针变量。需要注意的是指针运算符*和指针变量说明中的指针说明符* 不是一回事。在指针变量说明中,‘*’是类型说明符,表示其后的变量是指针类型。而表达式中出现的‘*’则是一个运算符用以表示指针变量所指的变量。如下例子: / u# t! K! ~/ x: d' r , A# D6 a) O! X8 w* u) V' j- p) ]$ s3 K# i2 O
int a=12; 7 B" V, O' M3 O8 i- [! Z int b; - Z( F" b' I& b7 q$ o% H1 I1 l
int *p; ) k8 C+ q; g6 G6 i7 }/ V
int **ptr; + l& O9 s/ a" j6 \: v' K! e& p7 R p=&a; //&a的结果是一个指针,类型是int*,指向的类型是int,指向的地址是a的 ( h/ L- X z2 A0 q
//地址。 0 r: K1 ?2 T. q) o2 m0 ^: {' j *p=24; //*p的结果,在这里它的类型是int,它所占用的地址是p所指向的地址。 5 e' _5 \9 e r" p
ptr=&p; //&p的结果是个指针,该指针的类型是p的类型加个*,在这里是int **。该 - D8 Z5 o" {5 Q6 } //指针所指向的类型是p的类型,这里是int*。该指针所指向的地址就是指针 ; R# P2 F* R% L/ v0 N" u //p自己的地址。 * l7 o9 o8 V2 s *ptr=&b;//*ptr是个指针,&b的结果也是个指针,且这两个指针的类型和所指向的类型//是一样的,所以用&b来给*ptr赋值就是毫无问题的了。 , P& l9 d7 p/ r$ v$ A& u **ptr=34;//*ptr的结果是ptr所指向的东西,在这里是一个指针,对这个指针再做一次* " s6 n$ P- C: M) i( w7 k3 z9 l, A1 S+ I //运算,结果就是一个int类型的变量。7 g: \3 o5 ]: R& t2 x
* V# v, s3 t7 E8 t * i# c- ^5 H0 B. v/ v 1.4.5关于括号组合 Y; W4 m5 f5 \( L/ x ' t. ]; o _: v 在解释组合说明符时, 标识符右边的方括号和圆括号优先于标识符左边的“*”号,而方括号和圆括号以相同的优先级从左到右结合。但可以用圆括号改变约定的结合顺序。1 w# M1 q, V* y! F2 p! a7 C
. j) a1 t3 G# v* d! f( y& Y 阅读组合说明符的规则是“从里向外”。从标识符开始,先看它右边有无方括号或园括号,如有则先作出解释,再看左边有无*号。如果在任何时候遇到了闭括号,则在继续之前必须用相同的规则处理括号内的内容。 * }( V- D3 S1 z% @8 }; b) G0 Q# \) H+ F" W, J, s7 O
1.5指针表达式 2 @( u% M6 B6 K' h. d( ^" R6 T ; e7 l7 i. ^# M# L- ^. F# g2 `& v+ h) O 一个表达式的最后结果如果是一个指针,那么这个表达式就叫指针表式。所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。3 U4 t4 s: u, ~, a; V
1 D. ]2 y1 U/ S5 q
5 E2 L ^+ ?1 ] p) w) F: N: i ' W- ~% C9 T9 S6 q m4 W. s; r T# {8 b1 k