数学建模社区-数学中国

标题: C++数组指针、函数指针、成员函数指针 [打印本页]

作者: 杨利霞    时间: 2022-9-12 18:49
标题: C++数组指针、函数指针、成员函数指针
C++数组指针、函数指针、成员函数指针
) n- v" [- u- l& iC++数组指针、函数指针、成员函数指针* u. s) a! [2 j3 I4 s$ D
' b3 {; o. x; O7 p' y. l

4 F9 N# X$ R# \' v4 t1 B操作符名称. x( t- b+ m6 G9 p
& 取地址符(Address-Of operator)$ {* {* L: E3 z) K$ r  A8 x
* 间接寻址运算符(Indirection operator)+ e# \! S" {% Y. G
.和-> 成员访问运算符(Member-Access operators),用于取对象的成员。
4 Y$ b/ C# r7 Z7 q.*和->* 指向成员的指针运算符(Pointer-To-Member operators), 用于成员函数指针和成员变量指针的取对象。# _6 i* a8 b( b6 m3 L$ ~! I$ i
() 函数调用运算符(Function-Call operator)
7 `0 u, B7 }* L9 N* Q3 \3 U0 H:: 范围解析运算符(Scope-Resolution operator)9 e: B. p2 W: n8 V% s( `) l% h0 d0 A
如何定义一个指针变量7 \, T# z% ?' l0 D0 j# I
假设类型T, 变量名称name, 指针的定义如下:: V. ^% P4 ^7 x/ f  z+ C
, @% t4 b0 b/ z5 r
T* name;; d% u2 [# y: s0 k7 o  x8 P8 U/ `
标识变量名字name, 它是T类型的指针。例如
2 @# o+ R9 v: A  F9 V2 E  Y- {' q) g' B! N6 U  g
int n = 0;" l5 G+ @! V: l% z0 ^$ a
int* p_n = &n;% n$ Q3 F/ A0 h% _
p_n是int指针类型, 指向某个int型的对象。
' O) F1 P$ Y8 O2 }
8 ?7 U2 A0 s( J! G7 H指针变量的修饰
! f! x8 F7 R% E: V' W( C1 c  @指针实际上也是一种变量类型, 只是它保存的内容有些特别, 是指定类型的地址值,通过间接寻址运算符(indirection operator)*, 可以访问到指针指向地址上的指定类型。- d  c) {2 }; c3 M0 U4 ~

9 A) o' [( U% ]4 g- Q6 ~1 @! k指针也可以用const, volatile修饰。 const int或int const均表示一个变量类型是int, 且该变量不能修改。以下两种写法都可以:" m' G) q" E2 b8 c5 T
# ?% N% `% O% }
const int a = 1;
: I0 q2 m* K8 p/ T; Sint const b = 2;7 R8 Y; R7 B7 k0 j
既然指针是也一种变量类型,同样支持被const修饰, 表示指针的值/指针的指向不允许修改, 指针所指向的那个变量是否允许修改, 那是另外修饰。写法如下:$ U, F" Z7 v: O* x; c8 Q! C
3 }3 C1 o! n4 \; `. W( h) R
int a = 1;% z" q/ J3 L& W/ I9 N! ^
int b = 2;- w: W5 l) Y. j2 B; w5 E8 t2 ?
int* const cp_a = &a; // 指针的修饰词,放在*号后面。9 t5 o  L( k/ w) O$ G/ Y9 J7 I
*cp_a = 10; // 指针指向的值可以修改
; x* o: I0 N! e' s4 d' w# d1 `$ @  p0 [cp_a = &b; // 指针不能被修改,报错!
! w7 y: m2 u/ n8 u3 T总结带修饰的指针的格式:, {" V" s: p' l0 g6 w
只要记住修饰词总是放在被修饰的内容后面。
6 s% @  j& z8 s* O% ]8 J  t% ]7 W
cv表示const / volatile修饰词。指针定义形式如下:
" a! O: U% T# {+ T0 t; p
! J9 k; h! m/ S0 j* C( `+ jT [cv for T] * [cv for pointer] name/ @2 v( a% F3 I  L. ?, R7 Q
注意对T的修饰放在T的前面也是合法的写法。7 V* K& m  N8 `1 M

* }  r$ t# u- G7 C5 X! q) F3 Uconst int const c = 2;, W. n1 r- n9 h- D4 O5 R0 m$ N
在mscv编译器下也不会报错。' T. J) B5 l4 r! x/ G0 R4 S4 I" i5 Q0 L

* w, E! L9 j4 c  F2 d' @* ?5 e完整的格式:" o( A. C% O! ]' D
[cv for T] T [cv for T] * [cv for pointer] name/ H; X- {$ ]9 w/ X" @3 i
3 F) T& Y% D! P/ R* i9 y
Syntax        meaning+ j3 t- e' a9 ?0 r, Q$ l4 P( Y
const T*       
( z0 h! m; r. l8 P; d2 P1 ]' Tpointer to constant object
9 k3 @) \9 _6 i
! V) s8 {) ^8 o. H7 ~' R. HT const*        pointer to constant object. k5 T) Z5 @9 |; K! G6 ^
T* const        constant pointer to object3 D* K+ |; |8 v- R/ E
const T* const        constant pointer to constant object$ H8 X/ U8 [3 k2 x' w
T const* const        constant pointer to constant object8 ^+ h% F, {0 M1 {$ t, l* [/ h
上面格式中T还可以是一种指针, 指针的指针仍然是按照修饰词总是修饰前面的标识(T或者*)来确定修饰的意图。
5 Z/ u3 ?- m4 v7 Z" w, @) P3 d4 g. a7 B9 E  Q
int a = 1;
2 t1 t6 N# ^. K2 }2 zint b = 2;* }9 [) _3 D5 @
6 B2 F& P+ a* `( k! N: V2 t1 t
int* p_a = &a;
& ~+ c. O( s# J( [% d; T2 h*p_a = 10; // 合法# X3 u! s8 ]4 ]# O7 X- l) S' I& P
p_a = &b; //合法
4 ?. x* J( w+ P) j1 h% @" @" u( o
const int* cp_a = &a; // const修饰int类型, 并非修饰指针
' q5 X- k8 f" X* D& P*cp_a = 11; //报错! const int类型不能修改
3 e% ~) j0 g) b! W3 O! S; ocp_a = &b; // 合法, 指针没有const修饰,指针可以修改。
0 I# i: ~6 c1 j6 I# t+ E
# f. I* `* t* v) K" }% M, {int* const pc_a = &a; // const修饰指针。类型没有const修饰
$ \. E6 E2 y! G" A- D*pc_a = 12; // 合法, 因为类型没有const修饰,可以修改。( f7 {- X! d+ F7 f8 ^* S
pc_a = &b; //报错! 指针被const修饰, 不能修改指针。
6 ]" B2 ~7 ^1 Y- e1 }1 D3 m5 @, O7 A! l3 q+ T7 s# z$ Y
int const* const cpc_a = &a; // int类型被它后面的const修饰, 指针符号*后面也有const修饰
0 ?; Z; P: H4 i/ ]' n*cpc_a = 13; // 报错! 类型被const修饰,不能修改。
( V5 L6 X, P$ h& D+ k* A5 @! jcpc_a = &b; // 报错! 指针被const修饰,不能修改。
9 c( \  q# s) t2 O1 x8 }" @+ U1 Q  v( b
更复杂的指针的指针
' l/ F  g' b8 s$ g8 u9 @/ r/ |' X- ~2 A
int a = 1;% I0 e5 e7 s# A! r4 r9 J
int b = 2;6 T( H5 z$ P9 z+ b# |
int* p1 = &a;
# F) y$ f' ~7 s0 v4 tint* p2 = &b;5 T; Q6 ]: K' I" H. Y, U
const int* ct_p1 = &a; // ct for const type
3 u+ q* b: w; n) `$ p3 ?const int* ct_p2 = &b; // ct for const type
, n) K6 w* s# [2 a- h2 {! v; l8 N" ^$ O1 R
// int * * pp1; 指向(int*)类型的指针4 u9 T, e2 E$ P+ g
int** pp1 = &p1;  
6 f0 Y8 \) f2 V2 \4 |pp1 = &p2; // 合法, ) f- J' a) q% K- ~1 Q
pp1 = &ct_p1; // 报错! 类型不匹配。 (int*)不能指向(const int*)9 K1 \5 G) w1 ~! e0 G. g

5 Y7 e% X/ y& C# R. x// (const int) * * pp1; 指向((const int) *)类型的指针$ n- J# N( Y! `
const int** ct_pp1 = &ct_p1;  $ m& u& L# A1 F# J3 F8 M) ]* t
ct_pp1 = &ct_p2; // 合法7 w# D7 P# n  v
ct_pp1 = &p1; // 合法!(const int*) 可以指向(int*)类型。& A& m: l9 X$ Z1 m! f! T8 n
  U) @( M. n, k2 O) B
// (const int) (*const)& G! z" l$ N: `, u( X9 a- f
const int * const ct_cp1 = &a; // 指针也不能修改# a# t0 R" [0 I5 j0 C9 M7 y( V& d
const int * const ct_cp2 = &b; // 指针也不能修改/ o/ i* e6 B, Z" Z. Y
ct_cp1 = &b; // 报错!指针有const修饰7 @8 b( \4 ^! B/ o5 S
! s# p; w3 b3 t. c4 L9 t2 T7 F' j1 L
// (const int) (* const) *  指向((const int) (*const))的指针; U5 \5 u6 W% G# Q# R7 O, R
const int* const * ct_cp_p1 = &ct_p1;  ' P9 r' d. u& K: u! {, \
ct_cp_p1 = &ct_cp2; // 合法, 指针的指针并没有const修饰, 指向的指针有const修饰' F: o  K8 U3 S: q5 e3 G3 F
*ct_cp_p1 = &a; // 报错!等价于操作ct_cp2,  指向的指针是带const修饰的不能修改* Z( S% w: Z& }  E; Q9 m9 X
6 ]4 m  s6 U* _( d
// (const int) (* const) (*const)  
0 n- a$ M1 y) z// 指向((const int) (*const))的指针,且该指针被const修饰
4 i, o6 N- V7 o3 h5 Mconst int* const * const ct_cp_cp1 = &ct_cp1;
. y' J0 K% S( m  E3 m  Y1 [  q* b( {ct_cp_cp1 = &ct_cp2; // 报错! 指针的指针被const修饰, 不能修改指针指向。; b3 K# I# ]  f* i7 m% T8 c$ c

- Y5 I6 ~0 a9 T+ E( V. g; H2 Q一行声明多个变量. n. `2 R: S! E0 X% Q; W
类型 + 名称定义一个变量。; Y8 Z+ w# F7 R% M
变量的前面可以加*号修饰, 表示指针, 一个星号代表一层间接。**表示指针的指针。
: F. u( P8 A. i0 c7 H& B) s0 y( j0 ^4 h; Z
int a, *b, *c, d, **e;
2 G0 |' P# h7 ^5 r$ W3 `a = 0;
: r8 ]" U7 Z& T% V& Fd = 1;
5 b3 N0 m( Z2 b* {6 Mb = &a;
: N! o- x. X7 X3 j: Gc = &d;
% C) {' m5 F9 [! N& j$ ^5 K" l9 ee = &b; // e为int**类型 指针的指针# D3 f, H" C' }: t, H5 O
e = &c; // e为int**类型 指针的指针
! W3 @7 L& P- f! H- ?! A也可以用括号包围变量和*号。
$ k  }8 r6 Z0 k3 Q
0 D; M6 f& J1 X: I, M/ T/ A+ S9 Hint (a), (*b), (*c), (d), (**e); // 合法定义。
: b- P" r; D  c& P$ S7 _) U4 g, {括号可以省略,某些情况, 个人感觉加上括号更清晰一些。例如
  @; T4 C5 Z6 \2 O$ S& R* \% M9 a, s- P5 ^- q
int (a), (const *b), (*const c) = &a, (const d), (const* const* const e) = &c;
3 P' Q: ?; Z( N  R写成, a) U2 M5 ^: c0 [: n

2 [8 O% m8 J$ Z5 T7 Iint a, const *b, *const c = &a, const d, const* const* const e = &c;
" w7 I5 Q+ h: T更重要的是, 后面我们表达数组指针,以及函数指针时,括号是不可缺少的, 带括号的表达更加统一。% Y4 I- v/ J; V

9 i4 X; K& |. B& r7 ~5 |! q4 t数组指针1 U" V$ `& z$ R
数组基本表达
9 o& Q+ h* L1 b3 z" |4 vint a[10];  // 定义了类型是int, 元素个数是10的一个数组。
3 a. F& P8 i0 m9 h+ k: o由于c++要支持一行定义一个类型的多个变量。 所以数组的[]时放在名称后面的。虽然我觉得7 ?1 @( @; ?1 b1 J" e

" M$ f- v5 `3 s+ c+ ?' Q5 kint[10] a;$ e* `% Y* U" H9 o' u" l. T
这样的写法更符合类型 名称的思维, 但是如果类型都这么写的话, 没法兼容以下的写法:4 ]3 G9 {* ]& b- J6 X/ b% d: V
+ A- i) ]1 C9 g* n' }: V- q4 u
int a = 0, *b = nullptr, c[20], **d = nullptr;5 U: |8 h  c. P* y! K
c++标准规定如此,但我们可以通过每一行只定义一个变量的写法, 类型会更加清晰。/ [1 S, C7 z8 @; `3 Q3 g

/ [4 }+ ?+ R1 s! e1 Fint a = 0;! k  H" X4 N+ Z4 l" {. V! u: H/ V% ~
int* b = nullptr; // 指针int*
8 T- T3 O( {: zint c[20];+ C' L3 g6 k& X0 D+ u+ l; e
int** d = nullptr; // 指针的指针int**
1 g: c0 A# s: ?, x数组的名称是什么类型
' \7 t  l, Q1 B+ h! }数组元素类型的指针,可以直接指向数组。 并且数组跟指针一样,可以通过下标去访问元素。4 {. w) Z: K/ J4 H
' p5 I; _8 U( S3 ?
int a[10];4 H+ o* d# y- A  b" M: G2 s
int* p = a; // 指向a数组的第一个元素
6 k' Q0 W! g. W+ s% g5 t. U5 Ra[1] = 1;
. J. _. _, J2 ap[1] = 1; // 效果与a[1] = 1一样。
3 R- v3 |5 N8 ]6 ^" o, }数组可以当作T* const来使用, 但是又与T* const有些不同。sizeof()的结果不一样。$ Z( c1 e$ o! m. l3 E+ e; K: E/ n
8 n' ~5 \$ Z9 b% A' k" X, _6 m
int a[10];
: }0 N- y& p1 J1 `6 g% k4 t$ N9 ?int b[10];
5 f: _: Z1 D0 L( \/ ^. ~int* const p_a = a;/ Z: X$ L5 ^  @' c/ T1 t+ K4 K
a[0] = 1; // 合法。 数组的元素可以修改。& l/ t0 t( w5 X) @7 z" ^) Z" C
p_a[0] = 1; // 效果与a[0] = 1一样。
6 `6 Q) x+ N( o! o& E6 F! `: f! J& i# Q9 O8 j& D* M
a = b; // 报错! 数组本身的指向不能修改。$ l. C, j/ I4 p3 i: f
/ V/ p% }8 H' J# _) l/ q0 Y
// 所以数组a可以当作int* const来使用" b1 c5 r3 P4 K3 s
int *const& ref1 = a; //正确。
& }6 S; D$ z* t& p8 W! |. wint *& ref2 = a; // 报错!
% q( J5 @( D! C$ J3 P8 `+ @/ h
  N+ a5 r& S4 L% v5 a// 但是又跟int* const有些区别。
# p* ~$ F6 G( w9 f" [assert(sizeof(p_a) == 4); // 32bit程序。+ p& _: ?- v# N! {; E4 q
assert(sizeof(a) == 4*10); // 32bit程序
( l$ \; F9 I* K- p) ~4 Z5 [) O' t
: [- r7 G3 `6 t9 z: |' q1 k6 b数组跟元素指针的作用很相似,都可以通过下标去访问元素, 但调用sizeof()函数的结果不一样。元素指针的sizeof()返回值是4(32-bit应用)或者8(64-bit应用), 数组的sizeof()返回值是数组实际占用的空间。数组可以当作指向第一个元素地址的T* const来用其实就是我们常说的数组到指针的隐式转换。当数组作为函数参数传递后,会自动退化成T* const, 在被调用的函数内部调用sizeof()的返回值跟T* const指针大小一样。 数组传递作为函数参数后, 在被调用函数的内部与T* const是没有任何区别,只有在数组定义的可见范围内sizeof()才有获取数组占用空间大小的效果。
. d3 @) H; ~' J' y
$ _: G% z  {* E7 ^以下3个函数翻译成汇编以后,汇编代码是一样的。
3 L4 m2 Y, q/ m% E% h, i/ P9 v6 ]- j/ G& h  s$ _3 F3 }1 \
void Func1(int* p_ary)6 B5 v9 t9 _# a- K  c- X0 f
{0 N! |5 q6 X1 z* U3 B% t- n
    assert(sizeof(p_ary) == 4); // 32-bit7 i7 F& j  `0 L9 Y, N& q
    p_ary[1] = 1;
3 `$ V0 y6 ]& z  ]& G' {}
, ]  x" p9 \0 M5 `* ~8 Z8 O) ?! v  n& s
void Func2(int ary[])7 W5 ]- k' \2 q2 ^4 a  D2 [
{- m! N! l7 D- N8 {$ |
    assert(sizeof(ary) == 4); // 32-bit
0 v: l$ g* a2 I# Y    ary[1] = 1;
' F  H* M) t" M7 o. t# }7 H}
6 ~$ u' g$ V$ L! m) l$ k( u" @0 J& o2 U( F
void Func3(int ary[10])$ P4 J( f1 o$ X
{
& U( W. a7 M. d0 r    assert(sizeof(ary) == 4); // 32-bit
1 e3 b: U" d3 t4 L5 |1 ^. Z    ary[1] = 1;& G. `8 U' s) j6 H
}& R- A- X1 x, S- x5 ^

0 ]8 N/ @9 o% l2 O/ ^8 f6 ]int main(int argc, char** argv)
# K, Q8 W- ^: Z% P4 S{
5 ]8 B; G/ Y% z+ F. P1 ?  o" V# A    int a[10];
; M8 P5 L3 D9 K  V3 I8 Z+ B    int b[20];% A9 d9 a) p- q/ u
    Func1(a);. J+ a) v4 s- |3 S
    Func2(a);2 v  O, H! O' R4 n, p
    Func3(a);
, ~" f( u) {1 G% W- w    Func3(b); // 退化成int* const了, 即使数组长度不匹配也不会报错。$ y* |% F4 E( I8 m5 J& O: y0 b0 h& E
    return 0;$ [* L4 g: b3 m2 q% r
}
- ?# S! Z9 o6 _* a- \0 d: [; }
6 P* c1 {* b0 U% u
" n, w- [8 U/ a9 F
; r5 b# {/ y, @3 Y& F多维数组
* v  w- `1 a, n1 j1 [- g一个3行,4列的数组, 结构如下:
* P  w8 M4 J1 r( ~
' N  s) M+ _& Z$ cint a[3][4];
2 }! Y& k- I/ j! M" b, h) I$ ?column 0        column 1        column 2        column 3
1 U8 s7 I' M" Brow 0        a[0][0]        a[0][1]        a[0][2]        a[0][3]
3 c: k6 D8 l" `) Brow 1        a[1][0]        a[1][1]        a[1][2]        a[1][3]0 V& j% i. S$ @
row 2        a[2][0]        a[2][1]        a[2][2]        a[2][3]3 C: z/ ~3 I$ o. T
数组初始化
5 s& ?- ]) z: h  s1 ?# s4 ]# O: q2 H+ Z* z$ a7 j
int a[3][4] = {* m+ g4 t2 q4 g' y. K, C$ i
    {0, 1, 2, 3},) M9 L' g7 m, n$ D/ P' B: z' e9 m; o
    {4, 5, 6, 7},! F; q9 j' {4 f
    {8, 9, 10, 11}, ^& }, M3 l* i( s
};, a6 r, E  T5 y2 o, O! v9 P" p7 Q
实际上多维数组和1维数组在开启速度优化后,翻译成汇编代码是一样的。. G1 Y% o; D" |- |- ]

* G. [; k( ^; L# [! Z) Ivoid Print(int* p_ary);  |. _: Z5 r" D9 ^6 u

* _% ?2 }1 b- E% }0 evoid Test1()5 f/ B* h- q5 ]9 w
{/ z( h1 Q- [. N! N8 @7 j- ^( D
    int a[10];6 q/ A, A- @7 k! x
    a[3] = 3;
6 g- A) g" @" F    a[7] = 7;" z6 B7 T9 {0 n& Z5 ^
    Print(&a[0]);
+ i. ?# c# z' c& y7 w}& M* Y: ~7 Y* J* s

9 g% \" D2 ?/ O6 p' yvoid Test2()8 v/ S" M5 p. `- m; @
{
  E; X: v+ n1 E    int a[2][5];  Y6 j/ p) q' D0 h1 P' s1 Z
    a[0][3] = 3;9 l7 W5 q) Q% J0 ~, C
    a[1][2] = 7;& D1 T2 Z/ a: s; I
    Print(&a[0][0]);
0 e+ z! Y1 W% i! |! c}
! s2 D  y0 J% g  Z
5 e" }- u( f% V$ o3 K5 Z. ]8 c5 \' I/ U. T

( h8 \, j9 e& A; s0 x很自然地,多维数组也支持用1维数组的方式去初始化。) ^9 A& I& c% ]* |: T1 l. F7 i
+ c# J5 B$ M5 W. T3 B. U2 Q
int a[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
8 o0 V. f) \+ J, E0 k! g# U. w1 ~既然多维数组与1维数组没什么区别, 为什么还需要多维数组?, e4 M0 b4 y, ~- \3 q9 g8 g5 m* t9 h

( }. \7 E8 ~( V+ W假设有一幅RGB图像720*576个像素,每个像素有RGB三个通道,每个通道的值是8bit大小。给出图像的首地址p_rgb_image, 我们要取第40行,第50个像素的R,G,B值。代码如下:
: e; L/ u5 z; Z; B' G3 {7 o% T" g
unsigned char* p_rgb_image;
' r0 m# V; p+ \unsigned char r = p_rgb_image[40*720*3 + 50 + 0];- P9 }: p/ i; p1 ?* z: F
unsigned char g = p_rgb_image[40*720*3 + 50 + 1];0 |5 t1 P$ o, L8 ~, H1 L" |
unsigned char b = p_rgb_image[40*720*3 + 50 + 2];
' H# A% k* l! o8 g7 X( v类似这样的场景, 采用多维数组的写法, 有点类似以索引为参数,可读性更高。相当于程序员和编译器打了一个配合。; v% I' z/ b& U

& H& Z2 f- I: K+ z5 Y8 E) ]enum/ G3 S+ T8 y1 |& a3 w
{
2 Z# ^9 G/ u( ^# a# g( @6 h- I Red = 0,. y/ I0 p" g5 S/ k: T6 v
Green = 1,
; m  `' Q: Y" Y/ `% r Blue = 2: g% h' Y$ K3 |. L0 B
};
% M# f: a& [, R5 M) }" R& a: }0 Xunsigned char rgb_image[576][720][3];
+ ]. h  ~; o" a4 G) u( gint row = 40;
. k* [% [( \8 v; I8 |int col = 50;
2 O" ?, l  H; xunsigned char r = rgb_image[row][col][Red];4 w( |5 Q7 B' v3 A4 E
unsigned char g = rgb_image[row][col][Green];
! `+ s' p5 H! V$ m8 xunsigned char b = rgb_image[row][col][Blue];3 E  b# Y3 z: Y
数组指针以及与指针数组的区别
: w% h! g% H% k5 t! N/ C1 ]数组指针,是一个指针, 指向的对象是数组。 数组指针的赋值,要求数组的长度匹配,否则会报错。当指向1维数组时, 需用用*取得数组对象的引用,再用下标来访问数组元素。
4 X9 S8 J3 r' S' B2 t2 M& w指针数组,是一个数组, 数组保存的元素的类型是指针。+ ?) b: z! ]* T
数组指针的定义8 G! H' S: ^& i# F
数组指针定义先定义一个数组。2 R$ T8 c2 r, _+ @2 \) q( }3 Y
int a[10];" h8 Z( z. O' A5 S* H( {% m$ S
然后对数组里的名称用括号括起来后再在变量名称前面加个*号
, E' ^' Z; X+ z% [' Y/ Tint (*a)[10];1 X# _" b) i% }3 N6 @; ]5 t- B
后面你会发现函数指针定义类似。# f& K- U5 c( [) b/ E1 G# r( Q- B
// 各类定义对比2 o4 s! j/ G. i2 ]; N" y
int a, *b, **c, d[10], e[10][20], *f[10], (*g)[10], *(*h)[10];
* T4 c0 b, L0 q! e" a
6 J6 k  j) j% K. K" \* O% w4 x; Qint *f[10]; // 指针数组, f是包含10个元素的数组, 数组里每一个元素的类型都是int*
- C+ K# s7 N" S) u- s5 Aint *(f2[10]); // 指针数组。另外一种定义方式。
" f; K1 o- D! j& s$ [/ b, u0 ~int(*f3[10]); // 指针数组。另外一种定义方式。
& K7 G1 G2 [+ Yint(f4)[10]; // int数组
" j# n7 V+ B3 Yint(*g)[10]; // 数组指针, g是一个指针, 这个指针可以指向类型是int,元素个数是10的数组
5 w3 j7 c* W. Q, }9 K( ^0 D5 ]4 Nint* (*h)[10]; // 数组指针, h是一个指针, 这个指针可以指向类型是int*,元素个数是10的指针数组8 C" X% B, ~9 P$ U$ B4 t
% [: y- W$ b3 e1 ]" ~, Q5 E
int d[10];& @# A. {/ l  U% S
g = &d;( @/ f! _& P* O9 K1 ?* t

  |7 Y. r! o; l# g6 k; @6 |int* e[10];
9 g! `- y: ]3 qh = &e;
5 K, q$ f# M6 O9 z数组指针的使用7 S& t6 `. r% ]  O8 F# ^2 }* I
数组指针一般先通过*号取得指针指向的数组对象, 然后再用下标操作访问元素。$ r- A+ m7 o7 O' M! _3 i& j/ M
, h1 f4 b7 R& }6 Z5 K! x7 {& }
int a[10];6 {5 y" X3 m3 F+ O. c$ U4 r
int(*p_ary)[10] = &a; // p_ary是一个指针, 指向"int (*)[10]"类型的数组
+ [5 E) f- V1 E2 lfor (int i = 0; i < 10; i++) {" V3 u% c8 r3 V2 v/ F
// p_ary是一个指向数组的指针, 需要先通过间接寻址运算符*(indirection operator)取得数组对象
, ]! t6 S# p2 @& H5 h3 |" N; s // 再通过下标操作访问元素。6 B8 d9 K( l7 S5 M* [( M) D
(*p_ary) = i;
8 k  F, m& u1 r$ u. G8 x7 y}
, f2 ]! [8 x" T$ j, `7 X
8 X: G% S( l; K5 nint b[10];1 o3 t  P5 w7 d/ R
int c[20];
1 L% ^8 I8 m& U; q! l# Ip_ary = &b; // 合法, T. q. w5 C6 e6 _# O/ ?6 C" l8 o
p_ary = &c; // 报错! 不能将 "int (*)[20]" 类型的值分配到 "int (*)[10]" 类型的实体; Y1 R  ]6 h$ }% Z
数组指针指向多维数组的子数组- U# ~8 j: M: `: i8 L/ b/ S. f4 |
int a[10];
, j, {6 r% U, C0 Vint b[4][10];& V  P' t* H. ]% C/ s
int(*p_ary)[10] = &a;0 P- s! P# A, q
for (int i = 0; i < 10; i++) {
" k5 `  R9 K5 E* ]6 c. J (*p_ary) = 1;
% |, L8 @  s+ a" B9 M  i}0 m% Z) X5 w- ^/ M( a
. G3 A: e0 a9 H* O- ^
p_ary = &b[2]; // 多维数组,可以看作数组的数组,; ~1 u+ S& k; }# A/ S- Q; Q% M
// b[2][0] ~ b[2][9]的值都被改成2了0 P. X) J; _- i: k& O
for (int i = 0; i < 10; i++) {8 _1 ?5 O, c* m3 k
(*p_ary) = 2;$ s' |& }% f( T8 o% h9 k: c4 f
}
1 W0 y4 h6 T8 A5 W; Z多维数组指针
8 z& Y# r& t: G2 J  {多维数组指针,是一种指针,指向的对象是个多维数组,支持多个下标操作。+ ]/ J/ `+ K0 c  \
4 E/ Q, ]$ c% I8 Y" L5 c# Y( T
int a[2][5][10];
/ j% ^: E: {+ {* W2 qint(*p_ary1)[10] = &a[1][2]; // 1维数组指针
' n* p% I! N/ |# `+ c" K. C" Fint(*p_ary2)[5][10] = &a[1]; // 2维数组指针7 @& Q3 ^5 r( n$ u, h9 j
for (int row = 0; row < 5; row++) {
, u/ r  v# k0 a8 P; I    for (int col = 0; col < 10; col++) {
- b# [/ \" W$ ^- m8 ^  o, D        (*p_ary2)[row][col] = row * col;
: J, c9 j' u5 m/ @( D8 A8 {    }
" I# B5 K/ ~& t( J* a$ S( N$ a  c}  O% O4 T. Z  j: A/ M3 R$ W
数组指针和指针数组对比实例
5 L$ ]& K% S0 w9 T. F3 r' r3 G数组指针还是记住两步法即可, l& t/ w9 m8 ~0 H
- W; I4 K) Y/ B7 U0 L
定义一个数组, H5 z1 M' f' m5 i# z6 m2 d$ |
括号包围1中定义的名称,再在名称前加个*号。
4 g+ T( W; G8 t( N) z" Hint a[10];
$ \( l6 q4 q3 t/ Yint(*ary_pointer1)[10] = &a; // 数组指针1 f, l( f! [) h7 `: w* Z+ m8 ~; @1 }8 \
int* pointer_ary1[10]; // 指针数组。元素类型是int*& A# [* f' m' y7 x$ E- M9 P; e
int *(ponter_ary2[10]); // 指针数组。另外一种定义方式。
0 W$ P0 \! T  j6 i" wint (*ponter_ary3[10]); // 指针数组。另外一种定义方式。  `- ^) K* p. {; G  s9 f
int c, *d, (*ary_pointer2)[10], *pointer_ary3[10]; // 排列定义比较。+ r8 l9 T, |& D

2 y# j+ t' s6 i$ X/ u! i$ b% I// 指针数组可以把每个元素指向数组对应位置的地址。2 H5 U  \7 l/ b8 e. Y/ i
// 这样遍历指针数组, 可以达到遍历数组元素的效果,但是注意每个元素都是指针,
) `/ |  Q5 V% m5 W" w$ G  c# @// 需要访问原数组的值的话, 需要对指针用*间接寻址运算符。
. H1 T" S) f4 n) N; L1 U- y. Xint* pointer_ary[10];   D; N, n! x% d1 Q% }$ |$ ]
for (int i = 0; i < 10; i++) {
2 [1 G/ O" t  s% U    pointer_ary = &a;; s% s6 V8 E. _. q4 H6 p& L
}3 l4 u9 p1 V* T' e) L
// 类似遍历原数组效果。% @6 a& p0 P5 w9 t% x; u
for (int i = 0; i < 10; i++) {
3 h8 r( U- t" s' o1 J& k    *pointer_ary = i; // 修改原数组。+ N% B9 f" m: ^  n
}
- ^* K" y( x5 G* m+ Z+ b
5 ~" E1 w* w3 K7 S- T9 r! P1 n) S3 h函数指针
- q  [7 q. u$ Y5 ?7 {/ q取得函数地址
) ^0 V7 B+ G  W9 E$ C函数的名称作为参数被传递时,会隐式转换成函数指针, 和在函数名称前加取地址符&等价。建议带上更加统一和清晰。
6 ?" m% L: W# y: M8 y2 i6 o( d5 i. @- [1 V% C6 a0 S
void f(int);  l, M' {& G9 t* |0 R3 Q5 g. P: g1 o
int main()1 p( b" F8 L: _1 A
{
8 z% n4 `/ I; G; L( H    void (*p1)(int) = &f;
- k- n( x. E( r* ~/ |  K    void (*p2)(int) = f; // same as &f
; b( w5 t5 g  A2 s    return 0;
! `7 h5 |9 ^6 T4 Y/ v}9 j; ^. _; }" b9 o( ?
翻译成汇编代码, p1和p2的赋值是一样的。4 e" r, E! ?) p) {9 ~( j: M

! @+ U+ o3 l+ _5 \! t& F' {
4 _8 w2 h3 u5 W. D
2 T% d3 [, ^4 B5 v* o# \) f. y函数指针的声明1 [1 n6 R6 M% s
单个函数指针变量定义步骤: A$ u2 m9 g/ n' @3 O" z4 @/ h
定义一个函数。void fun1(int a, int b); int fun2(double a);
* O) Z, L/ c, s6 t, ^. P用括号把函数名称包围起来,然后在名称前面加*号。void (*fun1)(int a, int b); int (*fun2)(double a);/ q8 [+ K* m" E0 P2 I' [
如果要定义函数指针数组,在定义单个函数指针的基础上,在名称后面加上[数组长度]void (*fun1[2])(int a, int b); int (*fun2[10])(double a);2 L" A0 i" b/ c
typedef定义函数指针" o2 Z/ `; L4 E2 c$ `$ J
可读性高比单个定义要高,特别是声明多个同类型的函数指针,或者函数指针数组。$ s* i+ C3 b( a& T$ s2 A' v3 Q. b8 k3 X) l

) I% N/ ^  o/ j$ J. x" k/ Wtypedef定义函数指针的语法% Q8 c2 l* y/ p- p' w. e6 @
typedef有两种做法, 一种就是定义一种函数对象,另外一种就是定义函数指针。用法稍稍不同,效果是一样。其中函数对象不支持赋值, 但是支持引用。/ |$ C# _9 {8 ~/ g# f! y' Z

' Y0 ^+ @# n" W! x: p6 u( gtypedef int FuncObject(int a, int b); // FuncObject类型是函数对象
! E" t2 o( l# R) ltypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针
( k' _2 N2 D& y. b1 i6 IFuncObject* f1 = &Add;
0 e- y0 x: V/ O" `! O3 r. ]FuncPointer f2;$ _( z: |3 i8 r4 Y9 N  u* S: P4 ]
f2 = f1; // f1, f2类型一样, 都是形式为int(int, int)的函数的指针。+ I% s9 W* q+ S, R
FuncObject f3 = Add; // 报错! 函数对象不支持拷贝. W% t2 I" [. w$ Q6 r
FuncObject f4 = &Add; // 报错!&Add是函数指针,与函数对象类型不匹配
. Q  {9 y) \, KFuncObject& f5 = Add; // 正确) l8 Q$ w/ t8 B4 L
int ret = f5(2, 3); // 正确
- J+ ~" s1 }6 z* mFuncObject& f6 = &Add; // 报错!&Add是函数指针,与函数对象引用类型不匹配
" o) b( X+ t& ^6 {1 |如何记住typedef定义函数指针的步骤
0 m. i! U0 q2 i$ A3 R' `6 ?. q像定义一个函数指针那样, 指定一个名称。int (*CalFun)(int a, int b);7 j( ]$ ~- J0 N1 @+ e! O
在这个函数指针变量声明前面加上typedef。typedef int (*CalFun)(int a, int b);7 e  ^0 U- i/ M! a
完整例子
7 g; J+ j: i$ L+ Mtypedef int(*CalFun)(int a, int b);
7 k7 j6 `$ ?3 W; B) y- v' \% d
. c& R+ ^) q7 K- O7 _int Add(int a, int b)
7 B9 i7 W  |# i{7 E' s  R) }' T! Z5 v
    return (a + b);
% ]! B$ m) f8 a; d$ J) O- A- j* F: ~) e! o}
* ^8 c" `8 X4 c3 y
' b7 V, Y+ L/ F, Tint Sub(int a, int b)# z& S7 |4 q  E3 {1 A9 j* R
{
# o5 d) W9 u# Z) Y1 M7 x    return (a - b);  M  _, ?" W8 B& {1 @' f' {" V/ z  M
}
7 [% n  e  ~: M$ r& s. Z4 F( F2 [6 F3 O* \, S, @# w. ^
int main(int argc, char** argv)
$ \: t6 S$ R9 z6 f3 t; B{9 R6 C2 y9 z7 B# y$ c1 }! j
    CalFun f1 = Add;
1 [- J3 ]( L7 p3 t  J$ }    CalFun f2 = Sub;
) s1 i  }* O6 T    int a = f1(2, 3);
0 f3 `+ S' E* q% w+ }+ }: |    int b = f2(10, 5);! J" U% S" g8 ?4 ?

2 _# P/ x* r7 T  `8 m    // typedef定义的函数指针数组。4 J9 Q$ O% i# ]5 h+ h$ G$ j, |
    CalFun f_ary[2];
, a! j' \( a& m( F$ E: i2 B    f_ary[0] = Add;  @% n8 e! t1 a1 y0 d3 |: \
    f_ary[1] = Sub;
" z! J3 |3 ?) V& {1 K: _
* R# B* }0 f% x* v9 f! B    // 单个定义的函数指针数组。
) l# i9 g3 C) p    int(*f_ary2[2])(int a, int b);
+ ^6 X, M: U% ~3 W4 m- g    f_ary2[0] = Add;
1 G7 J% W0 s/ x6 k9 ~3 O    f_ary2[1] = Sub;
; d, G- O) v4 w$ o: U# F: p# K+ R0 g) \) W  P1 _3 u
    return 0;
% I, j9 O% m1 |% R4 |# s  U}
7 M* o7 r) U3 `8 N2 E5 f1 G8 a9 D* N* L! m$ }
using别名定义函数指针) p- y, _; S) y& s# X( i
c++11以后的类型别名定义--using也可以用于定义函数指针, typedef的好处它都有,个人感觉比typedef更直观。using类型别名同样分函数对象和函数指针两种方式。
1 O! H; p7 `6 m7 q' W# C6 Q' p- g
- c) @" R" L5 Y6 r! `" utypedef int FuncObject(int a, int b); // FuncObject类型是函数对象
0 Y8 \7 K& `' b' Gusing FuncObject = int(int a, int b);+ Y0 o( t5 J3 O4 \
typedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针# x. z# j9 Z/ d" S) A
using FuncPointer = int(*)(int a, int b);# t9 R9 r, ^% \/ l' H) n# @
函数指针的调用$ J2 d5 O5 ^  T8 ^* a
函数指针和函数对象都可以直接后加括号调用9 K$ @! q( D0 ]& L$ N$ F
int f();/ I+ O7 E0 t! r# E% W0 g1 h; i- l, y
int (*p)() = f;  // pointer p is pointing to f
7 m7 M. Y4 L2 e9 [int (&r)() = *p; // the lvalue that identifies f is bound to a reference
0 A) N/ |, `7 A0 U6 C" w0 Fr();             // function f invoked through lvalue reference6 D& l$ o* d1 s8 H$ S) Q' w* x) E* U
(*p)();          // function f invoked through the function lvalue
- Q0 N; L( O6 x; ?1 z  i" Rp();             // function f invoked directly through the pointer
1 V- t" V* X9 l/ p7 r: t如果函数有重载, 函数指针会指向匹配的那个版本。
+ U1 [, e' r0 V  k: |. u6 J/ f9 ytemplate<typename T>* J( N9 a4 }" Q7 q' o, g, @  K8 E
T f(T n) { return n; }/ _" G% `3 x+ d$ i
: ]8 d9 W5 K7 m) M9 L
double f(double n) { return n; }
3 g0 P- [* L6 b! Q$ L
" Y4 ~- G$ A+ g3 b- jint main()' w8 C- C$ X2 C. j' o! I
{4 |$ `; e" l3 g3 ]+ q: R) A: r! V
    int (*p)(int) = f; // instantiates and selects f<int>
# y4 R! }1 P6 a3 B  d8 j}3 }7 Y8 H7 Y0 K( W4 P7 ^
成员函数指针
, R. V: T' ^8 M9 b% b1 B5 O" d) R静态成员函数,除了增加了访问控制以外,跟普通的函数指针没什么区别,所以普通函数指针可以直接指向类的静态成员函数。但非静态的成员函数与普通函数指针不太一样,声明时需要指定函数归属的类名,并且调用需要指定对象实例。
! B$ `1 T5 i# ^0 k( R0 u
: `& T4 ]) ?' q& g( S, |9 @8 ]成员函数指针定义。
: t( a: [* t3 L  K0 r% h2 y像定义类成员函数实现那样写, 并任意指定名称,这里作func。void ClassName::func(int);
; @) \) B0 r) u! w2 V括号把类名、范围解析运算符::、名称包围起来。void (ClassName::func)(int);
0 h; @' M+ N# {5 u在名称的前面加个*号void (ClassName::*func)(int);
; z7 n# n# z6 T7 O9 ]( v9 i7 D! V  o成员函数也支持typedef和using的定义方式。typedef void(C::* MemberFunc)(int); using MemberFunc = void(C::*)(int);
, b  P5 m1 V$ I  D) G2 w6 B/ v成员函数指针如何调用。9 `$ q& F% a8 E0 }1 y
假设成员函数指针名字为func
1 ~, ^  H0 R4 a0 I1 Z6 P. O# e8 C* Z3 ^9 k- V5 K1 T$ {4 E. I1 F
void (ClassName::*func)(int);
+ T7 w/ @% N$ y% \5 I+ P8 y  s对象式调用。( O' ?% S; C. u8 A, ?7 D2 i
ClassName c; // 被调用的对象5 D, P! M1 `& j0 V5 y5 |6 q. Z
成员函数指针名字当作正常函数那样写。
, k( L; r! M9 mc.func(3);
: i4 t- I2 n6 c; |* k! U+ _  ?成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。
: q5 u; `& }$ [9 n3 q# [c.*func(3);
5 B' W5 a0 n: C' b' n, M) l最后用括号把调用对象、成员访问运算符.、间接寻址运算符*、和成员函数指针的名称包围起来。( ~/ Z+ l1 \' {8 R( _
(c.*func)(3);
7 z* l6 B& X+ S6 n为何要加上括号? 根据c++的优先级标准,取成员运算符. > 函数调用() > 间接引用符*。 *号优先级比函数调用要低, 成员函数指针还没取得对象就被调用了,自然报错。 另外
9 e0 |7 h. l5 u% L& L; o(c.(*func))(3);
, ?$ x# m- g: m! k- Z: `1 k" b7 T$ J这样的写法也不行。 .*和->*是整体作为一个运算符的,中间不能用括号隔开。! `# k% n6 U* S& G
指针式调用; M- \, S* l+ \: @& g9 o  Y
ClassName* p; // 被调用的对象的指针" _" b. m( b& W' A
成员函数指针名字当作正常函数那样写。
! o4 N( d7 w0 s7 F8 _( A% Zp->func(3);
7 r3 P$ U* r% w8 r9 j成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。; M: y0 \9 i0 E. p+ w1 T; `. q) b, N
p->*func(3);
" \. B* e/ x% K7 \# j8 l最后用括号把调用对象、成员访问运算符->、间接寻址运算符*、和成员函数指针的名称包围起来。; b0 ~/ m' J/ ]. ?* ~% e* k
(p->*func)(3);: m: z/ L9 n# D
函数指针使用完整例子
- r+ P, i. d! tstruct Cal 8 ^$ j7 Q0 T8 Y) s
{
+ R. R  n6 }4 O9 V    int add(int a, int b); 0 q7 g- }/ X( P- }" H1 W8 a
    int sub(int a, int b);
8 a- {9 m. m. L  ]0 P};
! i5 F5 _! H+ w* Z  H( b, d
4 G, H2 v, M8 v$ mint main()
% e0 n" u; P& k+ r) C4 U7 V1 @{
9 Y9 T+ P% i% x0 x    int (Cal::*fun)(int, int) = &Cal::add;0 s5 d( n% r! |2 W9 d
    fun = &Cal::sub;, ~+ X' H  }! @: p/ c0 y$ F$ [
2 |* a& ~2 T2 _# @
    Cal* p_cal = new Cal();  `2 n1 q& O7 x! S' O4 o
    int r1 = (p_cal->*fun)(2, 3);
" N' \. m* Y. X9 k+ a% k' f0 |    delete p_cal;
! b" Y* |/ H) v, k3 Z! c& T: T# f' `; d  [
    Cal local_cal;
2 Z) x: y+ V7 _1 j6 e. W+ j    int r2 = (local_cal.*fun)(8, 6);" t9 M4 z3 `2 X! z8 M
}
+ k+ W- g# _) ~/ [7 u8 `+ M4 v3 n" |0 P2 I3 O
成员变量指针
9 Y+ Y* m6 k$ o( `成员变量指针比成员函数指针还要简单些,没有函数调用, 无需考虑函数调用和间接引用符*的优先级问题。
# k! G5 ~% W% L1 G4 L! `  T$ }  A2 `
成员变量指针的定义3 ^& N2 |7 W( V; v) V. k
假如以下结构体C。9 y5 a( e: p( g: l9 l- T

' g# q5 I8 f6 o2 Zstruct C
, d8 S+ u1 R. O; h' d4 w8 u8 a{ / T( K. q7 _" h2 x; ]/ T
    int m;
* l8 \* N! F6 W9 i. M};2 m, l2 N$ F' Y- q7 F- N/ T5 C
单个成员变量指针定义
5 J# k0 M+ B" W' P2 Y假设名称为p, 类似静态成员变量定义那样声明$ R7 m: D0 h4 e. v$ R
int C::p;0 v) }* o! m; I9 O/ E
在名称前面加上指针标识号* # y* M4 ?2 g0 M5 @0 c* l9 I3 E
int C::*p;
; j/ C- r3 c+ m1 d4 A  U' T- K5 ?% stypedef或using方式定义9 G$ m( l3 f# B7 x7 U- H" O
typedef int C::*MemberPointer;3 p2 w, n$ T7 J$ D4 {8 ?
using MemberPointer = int C::*;
1 l' R; d/ s  h  D: z+ d成员变量指针的使用。6 H$ r5 J2 r) Z* [9 {4 A! a; a6 [: T
类似成员函数指针那样,直接使用指向成员的指针运算符:.* 和->*即可。
8 I) T2 U% t& U; H7 x& K1 M  T& e成员变量指针, 能让我们实现一些遍历成员的动态功能。 例如把一个类/结构体的多个同类型的成员变量放进一个容器里,然后遍历访问这些成员变量。
% ?* R8 j& y; i1 ?" k! e: a) u! a
% Q' C  _. [  K, ?; z. U 完整例子
5 {: }- U! t- `& w7 o8 Mstruct C { int m; };: W5 [2 R& c* R
int main()
% r% y! w& f6 {5 l5 E{' L: v: J4 q7 r( X" e. r3 h: q' {
    int C::* p = &C::m;          // pointer to data member m of class C. l# G/ L! S- M: U& k4 t9 W
    C c = {7};
) c; p% [/ x" C5 V% J4 O1 s% ]0 t    std::cout << c.*p << '\n';   // prints 7
1 a0 _  Y1 O! ^! ]! Z6 k    C* cp = &c;
- H+ _- ~/ {6 i    cp->m = 10;
1 {( S4 D5 x- |    std::cout << cp->*p << '\n'; // prints 10
4 B9 Y) x' a- B: q6 S1 ^
9 Q  [' A( u/ b8 x: Q7 q————————————————1 Q  {8 _" u; m7 r! W
版权声明:本文为CSDN博主「南风fahaxiki」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。1 P, ^4 W4 J+ W# u) a0 C
原文链接:https://blog.csdn.net/m0_64407685/article/details/1267881150 I5 e" \5 t0 b, c! a( C
6 q9 r, b. f6 I2 [3 i. S
' J# m4 {' q! d, ]  \' D7 |





欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5