3 `5 {! n# a0 [/ B+ a' V" R 操作方面有着速度快,节约内存等优点,又使许多C++程序员的深爱不以。那么指针究竟是怎么样一个概念呢?/ ?! b* G) y# r$ R+ o4 I! c! d
+ O( F/ |/ r7 M9 N
其实, 指针就是一类变量,是一类包含了其他变量或函数的地址的变量。与其他变量所不同的是,一般的变量包含的是实际的真实的数据,而指针是一个指示器,它告诉程序在内存的哪块区域可以找到数据。 ' Q0 h/ k* T( H/ s: d9 S+ r; Z' V Q d3 n1 x$ I
好了,在这里我们可以这样定义指针:指针是一类包含了其他变量或函数的地址的变量,它里面存储的数值被解释成为内存的地址。) f- n$ z' X+ h% [
3 w6 S/ Y9 f4 l( P7 F6 P8 H( g+ y% Y4 O 1.2指针的内容, Y9 v, _$ M2 R5 h7 K- @7 O7 g: X
+ f& g& ^/ ^' c. l
简单讲,指针有四个方面的内容:即指针的类型,指针所指向的类型,指针的值,指针本身所 , x! ^7 K: M4 T; F; ~, u% U4 U, {8 @0 n
占有的内存区。下面我们将分别阐述这些内容。 Q5 E' ^# V# G# J/ o6 y7 m" K1 e" U, y6 e* f6 U
1.2.1指针的类型 * i: N M/ [, s u7 _" S& t$ S3 k3 v) `% D& i" F5 m
从语法的角度看,指针的类型是指把指针声明语句中的指针名字去掉所剩下的部分。这是指针本身所具有的类型。例如: & \5 P9 u3 n% g/ m8 |9 L. v: c, m- ~( {# ^* v' \
int*ip; //指针的类型是int* 0 e, X5 u- w: ?2 [3 ` R- c( i, \/ {/ O# ?' Z
char*ip; //指针的类型是char* % s& ]3 M: l) l2 B) N( `& e7 g2 w, Z+ M5 M, c$ E
int**ip; //指针的类型是int** - c+ z5 N2 C, y' U3 R5 S9 J3 c % ~0 k! Z3 e; P \% X, Z }int(*ip)[5]; //指针的类型是int(*)[5]" M! F1 C. z% f! ?
9 w2 V O: C5 k' k* D' m( r' y( y2 Z5 h 4 j0 Z' |) M2 w) m' Y$ i( f 1.2.2指针所指向的类型" X* W7 s: V3 e# g
; @/ y/ I, y E" Y I7 {# E+ j
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么类型来看待。从语法的角度看,指针所指向的类型是指针声明语句中的指针名字和名字左边的指针声明符*去掉所剩下的部分。例如:) c2 F: X c$ ^0 Z0 ?
, Q7 j$ u _7 C6 o% l
int*ip; //指针所指向的类型是int* t& }: }3 B. B# T, M+ Y5 q3 z
. l3 m9 S1 u4 g. s5 d9 g5 l 1.3. 5杜绝“野指针”" y( J; ` C) ~8 Q$ D/ C
8 D8 c' Q7 ]# O$ ^! u0 A0 ] Q “野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。 “野指针”的原因主要有如下几种:! K8 Q5 q! b' q1 M$ B; g) s
' R4 C! S7 D5 Q. F
(1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如 - O" y j8 o& l% j8 e% f% v 7 C& a0 W( y! n0 @" _1 J, d" J char *ip = NULL;1 Z- G7 d" z9 g' k. K O
4 P+ a7 L- {* h. V, x
char *ip = new char;; f3 f, {* Z( C6 q$ Y& k
9 g: g9 s# x! O! r/ [. L: @0 ^ (2)指针ip被free或者delete之后,没有置为NULL,让人误以为ip是个合法的指针。& z' ^/ w& K# N/ f' Z% m) Q
2 S6 E' [9 | K% U" _6 G
(3)指针操作超越了变量的作用范围。这种情况让人防不胜防,示例程序如下: 0 o: s* n. a3 w+ f' j4 g, v0 K* w8 p# t0 E2 K/ o3 l; x
class A9 g2 X- ]( v( d7 V# g7 Z8 V. z
& C! m5 p$ u+ s5 J2 }; y+ E
{6 f' d' o1 ^" V3 l0 n% F: ^* v
2 o* N0 c: S: p' ?4 w, W% e( F
public:/ F; e- Y0 |% ~) v8 a! R. P
/ I/ T+ S) P4 b2 x/ o- ~
void Func(void){ std::cout << “Func of class A” << std::endl; }% p' z" r: o$ `% e5 d6 g
, c+ _; e! Y x5 p; Y* |- E
};) G+ r3 v8 C- m- ~
5 w7 J1 ?2 v, ~' O* {void Test(void)# f7 Z) o+ Q7 j& d
+ T5 L( ?& Q' y9 S3 X3 a5 o
{ 3 C" ~ e2 S4 t, z9 V. q+ B& k. G$ U- x' Y0 v
A *p; . f6 M1 Q) X0 s/ {* o+ i; C9 u# z3 B
{+ s B# L' z& _) a
: h. m) R$ { l" l A a; 7 G9 N9 I: m) t & n: U$ l! P: d N' J/ b p = &a; // 注意 a 的生命期1 v3 D, W. A2 ]' ?
" s1 l) B* E0 f; F
} 7 W" }; i9 ~% U5 _* N c, t) y& Z/ j3 h: [3 ]. |4 S1 n+ k8 E
p->Func(); // p是“野指针” 3 p$ ] L2 x' Q( H% s0 u + S3 I2 y+ M2 a, w} 6 Z0 g" _; a, [1 ]; K, z # } F1 M, c) Y, t
- R1 J3 S% K1 S; M; {4 W- k
函数Test在执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了“野指针”。但奇怪的是有些编译器运行这个程序时居然没有出错,这可能与编译器有关。 3 P6 n/ f* G' U- v4 m - e# A7 w2 r% {& l7 ^: z, E6 e+ h& \3 y2 } 1.3.6指针参数是如何传递内存的? 3 @$ N, f. L. I; z) ~$ k5 L, X9 i% d% ~( Q, s M
如果函数的参数是一个指针,不要指望用该指针去申请动态内存。见如下例子:# |6 x4 g- I- I% F& G- @' u
$ g8 t+ O d" k' \, J. ^" r0 }9 D void GetMemory(char *ip, int num)* E/ f/ z4 O1 l) K+ x7 l% X3 U
. M* d9 `) A/ e8 P
{9 U- W! |2 X* p8 V" f! F; b
4 @9 M- L Y2 V( c7 Q
ip = (char *)malloc(sizeof(char) * num); ( o* l% X$ i. b0 k' K: U$ o9 E , e6 _: K/ i2 F l( n# \3 ?9 t. x}8 \' n1 a3 i( e& W: s
( ?7 U, L% E; `3 S! s% r- U 指针变量的赋值运算有以下几种形式: " G; h; g/ x9 T( h5 n' D n" n2 k% W2 M+ I7 W. Y/ q9 x6 Z( l
1.4.1.1指针变量初始化赋值如下: / e5 b' u. N; S8 x4 {: s/ q; K* F8 [ 5 {( q1 k% R9 p( L8 ^3 J int a;, N- F& N9 d& h4 e0 K1 w" ~
- W( ~& R" R6 z8 t4 B o int *ip=&a; 6 A& Y3 ^- N W, S) a) Q2 K' _$ B W4 t8 d& k7 m
1.4.1.2 把一个变量的地址赋予指向相同数据类型的指针变量。例如:: \5 I6 Q: D- Q& k- |# U
; \5 E2 H2 m J) X9 J1 g
int a;9 J# Z" ~! q! j
9 Q+ O6 }5 k% c, b int *ip;+ `3 a* [+ T+ t) e& g, Z4 v! }6 E
- y- ~3 e3 I; R4 Z7 R2 ]' l/ y ip=&a; //把整型变量a的地址赋予整型指针变量ip e9 C H; X6 T, N! n& f- u+ \8 M! a0 ^
1.4.1.3把一个指针变量的值赋予指向相同类型变量的另一个指针变量。例如: 0 L8 p$ A1 s" C$ N+ g& ~* Z3 b$ e: N, ~ c0 d, |& ?% e. E4 m0 Z- s: M' X
int a;4 q* Y5 v# _# b4 f! p5 X, E- D
! O" r9 c$ G! I1 Z
int *pa=&a; ; K0 U8 U5 h: s5 V8 k* H9 m7 B. e; F7 r7 d7 L2 o
int *PB;1 S3 t( s; t( V- n; d4 r
# }" }5 M& t: ]; v8 o
pb=pa; //把a的地址赋予指针变量pb * q1 w- g+ U: V' K : g( M$ i/ v* c6 Y* D' w2 @ 由于pa,pb均为指向整型变量的指针变量,因此可以相互赋值。 . ~6 L0 ^. k; T: M: L# d ( c; q8 e6 ~+ x: |6 \" u 1.4.1.4把数组的首地址赋予指向数组的指针变量。例如:! `% T# t1 m9 P: n$ t* p9 v
8 W; e" p+ _4 T O' m5 ]
int a[5],*pa;* C! v% Y, R8 F! [# x( j
! `* m3 F0 C. N6 b
pa=a; //数组名表示数组的首地址,故可赋予指向数组的指针变量pa8 C3 V/ c- l4 ^. y
$ N7 v( N3 \8 w2 {2 r 也可写为:$ b; n+ W4 }6 A) m
, \& I7 T: v+ E5 A# N pa=&a[0]; //数组第一个元素的地址也是整个数组的首地址也可赋予pa 7 i+ ^, j5 ^6 W% v/ k+ f* O+ i 4 U. T6 k5 U. c- `6 w1 `( N 当然也可采取初始化赋值的方法:+ m) l& ?: @) c( C
0 I) b2 P3 g! C& l/ A5 g5 R int a[5],*pa=a; ! |# d4 @$ O, U7 a; Q4 I4 O & j: J) J+ Z$ @/ v 以上是一些基本的数组赋值方法,后面我们会详细讨论指针在数组中的使用。 6 g' Q& J9 \6 D1 I: {/ d( d/ P, t2 B6 o! ^1 c- _
1.4.1.5把字符串的首地址赋予指向字符类型的指针变量。例如:/ Y% e, I* g1 R. I/ v* e7 k* k/ ?7 U
) ^8 p2 [, y6 w H- l char *pc;; `; t& u; m3 a- C& n+ I+ x, C; D D
7 Z& w% h% D5 z& P9 v) d+ `) w pc="c language";( Z7 H( d1 f, V8 V# n. z
* @3 P6 k( N, {, [' T5 i 或用初始化赋值的方法写为: # { K3 j% n7 w2 y; [+ b$ j0 }' b2 g2 u2 B
char *pc=" c language "; ) h# R& `* j$ y, [1 {# A* c! f. ^* s! g: b* m% h- z G& R9 a( h
这里应说明的是并不是把整个字符串装入指针变量, 而是把存放该字符串的字符数组的首地址装入指针变量。' E2 v1 L# L1 U& @/ N4 |) H8 [
% e/ P, p9 f$ P0 f( R! a9 } 1.4.1.6把函数的入口地址赋予指向函数的指针变量。例如: 7 \9 r3 X0 D, `6 i4 `( l e, @7 L3 v' L2 Y: X5 @$ ^
int (*pf)();2 p, M0 u4 @2 n! n. r/ x
: ]% q, S+ e& O pf=f; //f为函数名; i6 f8 U, k- z
3 J2 }; T$ f* ?/ A4 Q( f2 J: [3 L
1.4.2加减运算 0 Y! }! e# ?9 x+ ]; `/ e8 I6 g) P/ H3 e
对于指向数组的指针变量,可以加上或减去一个整数n.设ip是指向数组a的指针变量,则ip+n,ip-n,ip++,++ip,ip——,——ip 运算都是合法的。指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置。应该注意,数组指针变量向前或向后移动一个位置和地址加1或减1 在概念上是不同的。因为数组可以有不同的类型, 各种类型的数组元素所占的字节长度是不同的。如指针变量加1,即向后移动1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1.看如下例子: 5 g) S/ W$ f8 A2 [6 E% r : A& @( E) |7 `6 x4 N L/ Z6 n8 a4 |/ c- i3 k/ q+ w
char a[20]; # U( V* M5 A9 L6 B9 ~5 j' } int*ip=a; : K4 \- e+ U6 ^6 n6 }0 S ... # q( P' ^: L- g" u7 q4 X% X' _
ip++; : v8 p5 P6 q; x& n; F5 B# b % r, a7 h _! N3 F
3 C; i, s+ O* n7 C! @# X: @* T 在上例中,指针ip的类型是int*,它指向的类型是int,它被初始化为指向整形变量a.接下来的第3句中,指针ip被加了1,编译器是这样处理的:它把指针ip的值加上了sizeof(int),在32位程序中,是被加上了4.由于地址是用字节做单位的,故ip所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。 ( L. U8 ?2 V# m( A* r% Y0 ~ * {, ?! E5 @- P) \5 l% `# i 由于char类型的长度是一个字节,所以,原来ptr是指向数组a的第0号单元开始的四个字节,此时指向了数组a中从第4号单元开始的四个字节。再看如下例子: 2 d+ W( A' ~# V. O3 f# F: s- J$ _6 k1 A
+ b3 L( ]0 c5 e5 y/ u
char a[20]; # r# } s* M7 h
int*ip=a; ! g4 z, }6 q. G$ L- J ... 8 v0 v! n8 G3 p* ?; L% n" W ip+=5; ) g$ v. |0 e e3 p ( D0 d7 q6 v h- b5 b 3 S( C* \/ @9 r 在这个例子中,ip被加上了5,编译器是这样处理的:将指针ip的值加上5乘sizeof(int),在32位程序中就是加上了5乘4=20.由于地址的单位是字节,故现在的ip所指向的地址比起加5后的ip所指向的地址来说,向高地址方向移动了20个字节。在这个例子中,没加5前的ip指向数组a的第0号单元开始的四个字节,加5后,ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。/ w+ P7 {# N \$ I. ~. L# C
! Q! u& o; x, @) g9 l& Q! T- ]) r 如果上例中,ip是被减去5,那么处理过程大同小异,只不过ip的值是被减去5乘sizeof(int),新的ip指向的地址将比原来的ip所指向的地址向低地址方向移动了20个字节。 * {3 S( w/ j* C+ a2 x1 P ! l, K! O9 K; J2 G K1 o5 m$ p3 V 总结一下,一个指针ipold加上一个整数n后,结果是一个新的指针ipnew,ipnew的类型和ipold的类型相同,ipnew所指向的类型和ipold所指向的类型也相同。ipnew的值将比ipold的值增加了n乘sizeof(ipold所指向的类型)个字节。就是说,ipnew所指向的内存区将比ipold所指向的内存区向高地址方向移动了n乘sizeof(ipold所指向的类型)个字节。+ f4 L: A" y" R m1 T/ T# L
! K( r( a) N2 d8 _+ [) }
一个指针ipold减去一个整数n后,结果是一个新的指针ipnew,ipnew的类型和ipold的类型相同,ipnew所指向的类型和ipold所指向的类型也相同。ipnew的值将比ipold的值减少了n乘sizeof(ipold所指向的类型)个字节,就是说,ipnew所指向的内存区将比ipold所指向的内存区向低地址方向移动了n乘sizeof(ipold所指向的类型)个字节。 ( P' m1 y( f; v; W' |1 W
, T, {; ^: t, \3 S$ u( e! ]1.4.3关系运算 4 f. A$ J8 C0 c5 ^) Q m9 a2 B7 h( p W) J/ c) s
指向同一个数组中的不同元素的两个指针可以进行各种关系运算。例如:* ^, z, S6 G1 u7 B: G1 p$ w