数学建模社区-数学中国

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

作者: 杨利霞    时间: 2022-9-12 18:49
标题: C++数组指针、函数指针、成员函数指针
C++数组指针、函数指针、成员函数指针
* E' o4 `5 ^/ Y, k7 ]C++数组指针、函数指针、成员函数指针
8 q0 l& c% a, w: t* O, N! O3 E  Z4 m6 L+ K1 C9 [& W

/ H3 y( c" E6 ]2 R" M4 n操作符名称- L" H- g' `7 c2 [5 d( u8 O
& 取地址符(Address-Of operator)
- l8 B8 i- V6 r: k* 间接寻址运算符(Indirection operator)6 l, Y6 J6 _6 e! h, a* }" N' H
.和-> 成员访问运算符(Member-Access operators),用于取对象的成员。
3 e" u& O: B1 O0 e  w% S: j.*和->* 指向成员的指针运算符(Pointer-To-Member operators), 用于成员函数指针和成员变量指针的取对象。% @5 ^% Q& B: o9 }' ^
() 函数调用运算符(Function-Call operator)
$ ^( l2 F! k  a* o' Z% _" ^:: 范围解析运算符(Scope-Resolution operator)+ A& G: C6 E2 N6 Q7 Y7 c
如何定义一个指针变量, e- v4 Q! _( R6 @6 v. }. O$ X  t. I
假设类型T, 变量名称name, 指针的定义如下:# [- c6 V, \1 _$ t! ]
. V0 E' T' d( v2 C% Z
T* name;
: W6 Y0 \. {$ E& e& H9 Z, c* _标识变量名字name, 它是T类型的指针。例如
5 U( u; W/ e9 y- K) i% l3 Y" r" B, d# l$ O3 `5 N2 a
int n = 0;1 v. g1 y. I' T
int* p_n = &n;9 H, W/ ]4 U& \$ ]" P6 @( F
p_n是int指针类型, 指向某个int型的对象。' z% I" q  u& r6 ]. ]

/ `; Y1 z7 ?+ `指针变量的修饰
" J5 U1 f3 ^! }3 y8 Y7 x6 m指针实际上也是一种变量类型, 只是它保存的内容有些特别, 是指定类型的地址值,通过间接寻址运算符(indirection operator)*, 可以访问到指针指向地址上的指定类型。
/ D/ e$ k9 ]$ w" z' C. M$ m: k+ \# s
指针也可以用const, volatile修饰。 const int或int const均表示一个变量类型是int, 且该变量不能修改。以下两种写法都可以:
' b0 F2 Z' L! R% W% H+ p% l% \: h0 T2 T2 _$ u$ f: e3 H
const int a = 1;
9 s3 p% B: d7 c. g! `! d# P2 T5 c: Y0 [int const b = 2;
0 d. o% P9 U7 O& j0 E既然指针是也一种变量类型,同样支持被const修饰, 表示指针的值/指针的指向不允许修改, 指针所指向的那个变量是否允许修改, 那是另外修饰。写法如下:1 K7 Z' N. p. i; Z* w: T7 n- F: ]0 ^
- l. ]9 u" x3 h/ ~
int a = 1;: ^( e3 }; c3 O5 T
int b = 2;
; D- h' l6 X6 f0 O4 E1 q1 nint* const cp_a = &a; // 指针的修饰词,放在*号后面。9 m2 z2 f, f# n: i" v
*cp_a = 10; // 指针指向的值可以修改+ X8 {- h' v; p' Q0 g
cp_a = &b; // 指针不能被修改,报错!
: b- L5 {5 n3 w+ ^4 s! @) i总结带修饰的指针的格式:
* K! w0 e) A! R# a只要记住修饰词总是放在被修饰的内容后面。
: e* \, ?9 `  t0 j0 A! U0 }- b- T. A2 y, M  b
cv表示const / volatile修饰词。指针定义形式如下:
% G: C3 C7 d% b, L" X3 X: Q6 D# u: J- L" X
T [cv for T] * [cv for pointer] name
+ K) E5 B* B$ }$ _& a( {注意对T的修饰放在T的前面也是合法的写法。8 j; x  ^% o5 J& A  L: k7 q+ c
. Q& z# @% d2 p& @2 S% T% S  L
const int const c = 2;" l/ v4 e+ T# l
在mscv编译器下也不会报错。$ O" G; f4 w( P* M

0 M+ z$ h. @" S$ ]" U5 J& _完整的格式:
$ x8 e3 Y9 G2 u; \  k9 L[cv for T] T [cv for T] * [cv for pointer] name5 w* ~* r) J7 n* {  F& t; a

& M3 M$ `- g; e+ K  g% y! Q+ b# y5 pSyntax        meaning
/ R+ Y4 }, w% k) a' G: B" p! @' Oconst T*        1 w  d9 b" `  d* q6 p) ?
pointer to constant object
3 N- T1 n! {' R# x0 z  t  O
" g. w5 q! V2 F& b9 [$ MT const*        pointer to constant object6 a0 O% G/ z+ F
T* const        constant pointer to object
/ s+ X% l4 {. Tconst T* const        constant pointer to constant object
  Z9 Z# w0 Y9 {9 l5 \2 l) q& ?T const* const        constant pointer to constant object
# z3 ]1 v3 M  l上面格式中T还可以是一种指针, 指针的指针仍然是按照修饰词总是修饰前面的标识(T或者*)来确定修饰的意图。( E7 r5 ^  j$ n# n# @1 a. d& C

5 I' n$ z1 X/ ?int a = 1;  a/ ~7 ^  s" d- a# ], y* Z
int b = 2;
6 H7 c! m1 r8 Z, P0 p2 o8 [5 J3 U; T" @, T* `2 c
int* p_a = &a;* e! }2 I* C6 h9 j& p
*p_a = 10; // 合法/ |5 b$ c# I& ~# B2 a# `. O
p_a = &b; //合法1 p: |, I; |1 J% i& }; s: W9 m' ~

- i+ T( p9 {0 s$ m. ]) }3 Jconst int* cp_a = &a; // const修饰int类型, 并非修饰指针4 S( l3 A9 I$ \( a: f) J4 v5 }& y
*cp_a = 11; //报错! const int类型不能修改
. b% w5 L) r! L) N0 {' Z" dcp_a = &b; // 合法, 指针没有const修饰,指针可以修改。
+ e. c+ o' J( @. _% }" G/ @( q" L6 x1 v$ Z6 s' {" C! v% @
int* const pc_a = &a; // const修饰指针。类型没有const修饰
2 ^* Q( @5 f. y$ L- t( H. Q, Q5 f0 R*pc_a = 12; // 合法, 因为类型没有const修饰,可以修改。
7 k* |" ?# I( c% Mpc_a = &b; //报错! 指针被const修饰, 不能修改指针。
5 n) C+ p& ?  x# n- g4 b' V5 c) \
' i2 O  ^% g: b" M6 ~% lint const* const cpc_a = &a; // int类型被它后面的const修饰, 指针符号*后面也有const修饰3 R, d$ Q7 d7 L( z! h7 w
*cpc_a = 13; // 报错! 类型被const修饰,不能修改。
" Y* i* F7 [2 e2 lcpc_a = &b; // 报错! 指针被const修饰,不能修改。
6 i7 W$ ~/ p1 d4 Z* {, K
2 b1 y9 M4 i7 q0 s- |! v$ ? 更复杂的指针的指针, E3 U0 z6 v& b2 e7 s6 O7 u
8 n  E7 E, p9 M' F! f& Y
int a = 1;& ~6 V% J+ n$ h. ]
int b = 2;
  R( w2 i7 N9 lint* p1 = &a;
( I1 J$ \( v4 t+ zint* p2 = &b;$ C) x1 r* M# i2 z
const int* ct_p1 = &a; // ct for const type. }8 v0 C: j2 |! p
const int* ct_p2 = &b; // ct for const type9 E+ T+ T+ j, |* x1 j

! N8 O  p5 q3 P  |9 y0 V// int * * pp1; 指向(int*)类型的指针4 M( h  s- g0 U) W0 ^5 I
int** pp1 = &p1;  
" [  L3 r' {7 s8 z) \4 \1 \$ L$ kpp1 = &p2; // 合法, 6 v9 u$ @) X" K7 r9 m. W
pp1 = &ct_p1; // 报错! 类型不匹配。 (int*)不能指向(const int*)
$ U3 {) A2 V7 P0 l, L* z0 q8 H  \, _. b+ @- u+ Q
// (const int) * * pp1; 指向((const int) *)类型的指针
' k3 f% j' Y. ^3 Dconst int** ct_pp1 = &ct_p1;  1 q. x+ g+ O& ~
ct_pp1 = &ct_p2; // 合法
% s0 ^7 C; m+ V& E8 x6 mct_pp1 = &p1; // 合法!(const int*) 可以指向(int*)类型。
4 g1 O0 {2 k3 E! n) r- ~1 A6 x& v1 z: B. r9 l: \
// (const int) (*const)1 j/ t( V# y& Q5 n0 ?$ j; X0 [4 K
const int * const ct_cp1 = &a; // 指针也不能修改9 Y- q: ~; B4 s
const int * const ct_cp2 = &b; // 指针也不能修改( _! T' h, G& O
ct_cp1 = &b; // 报错!指针有const修饰4 U9 w( U1 v8 v, N- f8 v
8 R. ^  ^8 u, L( t- A0 g$ s3 x1 F
// (const int) (* const) *  指向((const int) (*const))的指针
: H/ n) C4 Y# ?const int* const * ct_cp_p1 = &ct_p1;  
( l4 t  O" C5 ?! I8 J; hct_cp_p1 = &ct_cp2; // 合法, 指针的指针并没有const修饰, 指向的指针有const修饰+ E) s7 C0 D% k4 t7 j& z. @
*ct_cp_p1 = &a; // 报错!等价于操作ct_cp2,  指向的指针是带const修饰的不能修改
/ u0 ~- @% E4 l  j6 l: q- V4 }2 E: ^5 g$ ]# k! A! |
// (const int) (* const) (*const)  8 G& t, A; L. M$ }5 V8 I; A1 s
// 指向((const int) (*const))的指针,且该指针被const修饰
0 B2 U/ ?; r$ A6 ?9 a! ?  mconst int* const * const ct_cp_cp1 = &ct_cp1;
  W. Z( f# j" Sct_cp_cp1 = &ct_cp2; // 报错! 指针的指针被const修饰, 不能修改指针指向。4 t! @$ ]# Z! k" t& P: r
% k# T# o( ]$ g1 c* _3 a! c, m
一行声明多个变量
: M! m" h# r% }/ g2 O类型 + 名称定义一个变量。
' K' d; n0 {+ v; }3 u  h  i- N变量的前面可以加*号修饰, 表示指针, 一个星号代表一层间接。**表示指针的指针。) V1 s8 o5 Z9 k- j: H
+ V* u  G  w7 Q6 G' P
int a, *b, *c, d, **e;4 D* @! n  ^, p5 `8 g
a = 0;" `* J5 x7 J$ D: e
d = 1;; g; S) c& G7 S. S- Z1 j8 D
b = &a;/ P* j9 e* ]5 ^
c = &d;6 {* e. p; p4 ~( m! [( Q
e = &b; // e为int**类型 指针的指针
# I5 Q* R0 T, l  qe = &c; // e为int**类型 指针的指针
) X8 A$ s9 T$ M. }  u1 j: ~也可以用括号包围变量和*号。# H* Z+ r4 b1 T4 c, s% s4 q

1 E$ {3 w( \. M* h( A2 {! s4 ^' P  V9 mint (a), (*b), (*c), (d), (**e); // 合法定义。
4 {( k4 S- P, o# T& _4 X括号可以省略,某些情况, 个人感觉加上括号更清晰一些。例如
+ d" n! k) w& q% h9 T( o8 u) [' N7 ?+ V) t
int (a), (const *b), (*const c) = &a, (const d), (const* const* const e) = &c;
5 c# I, Z+ v' y; ^6 W写成
- a3 Y% Y! t4 |9 M5 f# L% \) ^9 z4 m/ F+ U
int a, const *b, *const c = &a, const d, const* const* const e = &c;
; k# C$ u5 i" |更重要的是, 后面我们表达数组指针,以及函数指针时,括号是不可缺少的, 带括号的表达更加统一。
. Z+ V5 x' l- @1 u- o$ t9 y- o5 j/ G; o2 i3 S, K  D& B! O
数组指针2 G% Y$ T1 I8 v9 s" b2 Y/ r
数组基本表达; g6 E4 N& P/ a# X; |
int a[10];  // 定义了类型是int, 元素个数是10的一个数组。
4 C5 y( V* y) ~* Q, d! J/ u5 t由于c++要支持一行定义一个类型的多个变量。 所以数组的[]时放在名称后面的。虽然我觉得
' W* C8 k, W$ B
6 z1 ?# g1 x" l( u+ [* ~/ l) Aint[10] a;
3 ?. A& }1 K# |' r% n这样的写法更符合类型 名称的思维, 但是如果类型都这么写的话, 没法兼容以下的写法:
$ k; w: U3 d" ]9 ]* n! X2 X7 A! _" C9 C
int a = 0, *b = nullptr, c[20], **d = nullptr;
) W7 Z$ x/ E4 c0 ]c++标准规定如此,但我们可以通过每一行只定义一个变量的写法, 类型会更加清晰。" R8 O- p5 A7 I6 C
9 n/ M6 C2 E0 l# r. k, x
int a = 0;7 y4 v" e6 `; o* @1 s
int* b = nullptr; // 指针int*
8 J! S. v9 t( ^# B8 `int c[20];1 o' u  k9 b( ]! X
int** d = nullptr; // 指针的指针int**
2 o+ f6 c% |, ]3 @数组的名称是什么类型
' S9 \" Z2 P/ D' I* D4 m数组元素类型的指针,可以直接指向数组。 并且数组跟指针一样,可以通过下标去访问元素。
4 A- }, d) ]0 F. I4 X* j( I2 Q4 @. V, v
int a[10];* ]% v) |# ?0 X$ V
int* p = a; // 指向a数组的第一个元素# W6 `' e5 y2 p0 N
a[1] = 1;
: D! d, f8 X( o  c4 t. p0 ?p[1] = 1; // 效果与a[1] = 1一样。
7 W2 M8 M7 w: N' c: e数组可以当作T* const来使用, 但是又与T* const有些不同。sizeof()的结果不一样。
0 Q8 B9 Z5 I5 Z4 C0 U3 _' J! s5 `8 Y+ u/ f: |8 f# B  D
int a[10];2 e4 f3 S$ [; {4 G! ^- Q
int b[10];
9 v( w* U# {6 ?; k8 r  b% `int* const p_a = a;1 I8 I3 g: W! b0 B/ Z
a[0] = 1; // 合法。 数组的元素可以修改。3 r: m/ n. V; I5 Y7 N! Q, J
p_a[0] = 1; // 效果与a[0] = 1一样。7 L* H1 L# @; S9 k0 K

4 F4 R$ h3 v- y( J* fa = b; // 报错! 数组本身的指向不能修改。
& q4 A8 u& V' `* I0 D, S1 ^
4 @5 V2 b8 F) I/ |// 所以数组a可以当作int* const来使用  X( x. e& W% ?6 v. K
int *const& ref1 = a; //正确。
, h" x( ~$ n: _) |' mint *& ref2 = a; // 报错!
  Z1 q" |8 b9 Z* A7 `. p" [: v8 Q' f
4 x& V6 ^1 k2 N& y- R. I& G; g// 但是又跟int* const有些区别。! q/ y) i& T# r$ R1 H
assert(sizeof(p_a) == 4); // 32bit程序。
' \8 T; T# h# a2 a' j# iassert(sizeof(a) == 4*10); // 32bit程序- u  }: Z: l' L) ], R: }

; @, U0 y2 j9 A" G$ K" J数组跟元素指针的作用很相似,都可以通过下标去访问元素, 但调用sizeof()函数的结果不一样。元素指针的sizeof()返回值是4(32-bit应用)或者8(64-bit应用), 数组的sizeof()返回值是数组实际占用的空间。数组可以当作指向第一个元素地址的T* const来用其实就是我们常说的数组到指针的隐式转换。当数组作为函数参数传递后,会自动退化成T* const, 在被调用的函数内部调用sizeof()的返回值跟T* const指针大小一样。 数组传递作为函数参数后, 在被调用函数的内部与T* const是没有任何区别,只有在数组定义的可见范围内sizeof()才有获取数组占用空间大小的效果。
5 l: z+ C* l+ r* z2 z) c) G
# t$ v4 D# E2 H6 A8 @  V$ o0 H以下3个函数翻译成汇编以后,汇编代码是一样的。' E' c& W' x# T" ^  Q) x/ y) i2 j6 K

9 T6 O( k* P6 T3 K, \2 \. p& ivoid Func1(int* p_ary): s7 V9 `9 n) B+ [
{
, U0 U  L' b& ^" b    assert(sizeof(p_ary) == 4); // 32-bit7 K& P9 _9 k7 m9 E- }+ D
    p_ary[1] = 1;
3 J$ F8 @# u8 i) A. c}
  P% D4 Z  W) k: g0 ?" P" B
+ V- b! ~7 i( K, Yvoid Func2(int ary[])' ^: S8 G0 y9 |+ s. s* u
{
$ s1 v9 G; Q) b6 y4 |9 J0 G    assert(sizeof(ary) == 4); // 32-bit
# ^6 }  M8 O; s' v8 c! [( a    ary[1] = 1;' H# E  }% s) |  h
}
' P2 Y3 y; e6 j$ e* {9 I: y, N* m% {, Z4 H/ p0 A* v' l# e3 a  r" _
void Func3(int ary[10])
! |0 d9 g3 w$ H5 V( q/ L, D8 i{
0 z( U) D. r' d/ J( \# Y    assert(sizeof(ary) == 4); // 32-bit
% v0 w9 p  k' A9 s4 D  }6 {$ Q    ary[1] = 1;, @  h; ?; ^* N+ l1 O5 Q& k  @: @
}
; \' l# p1 J9 p+ A. H) Y$ y! i3 Q0 }& x
int main(int argc, char** argv), B  C% F: S5 N" L. l" _& Q
{" }/ \; ]' P* M3 }
    int a[10];
& Q& h7 m1 @1 b, u% S5 C# c    int b[20];
% F- @3 b. k7 i- Z    Func1(a);0 o# U5 L. _" ?8 T1 ~+ v% J. E
    Func2(a);
  c7 r! _$ Z3 ^& p- X9 a    Func3(a);
* ^# `: @3 q# u+ g" g1 [# x    Func3(b); // 退化成int* const了, 即使数组长度不匹配也不会报错。
+ V/ A+ z1 l. \# ~/ t8 R+ [    return 0;
7 ^* t' N$ [6 p2 B! E}. P4 _2 s; e! U: @) w
6 R, ]2 x5 N8 \, ?% V

4 a* h7 N9 i$ X+ R3 F! `) B# e
; x1 F" N, `8 F) y. E- W# L多维数组" Q% j2 N5 @  o$ y2 f  L
一个3行,4列的数组, 结构如下:. O( J7 j# E1 A- |, P( {# y, I5 G

. |! G1 L# u. z# y* @+ aint a[3][4];
% |" ]5 y1 z7 Ecolumn 0        column 1        column 2        column 3: H% g: l( L* m. e+ q* d
row 0        a[0][0]        a[0][1]        a[0][2]        a[0][3]$ l8 g/ [! y  e: s
row 1        a[1][0]        a[1][1]        a[1][2]        a[1][3]
. E5 s" ?7 D+ K) Q7 N3 G5 T' h( Yrow 2        a[2][0]        a[2][1]        a[2][2]        a[2][3]2 p, j# `3 v( {, ?
数组初始化
! `3 Z; C# b2 Z* N. g( ?& V- b) I& ]! d
int a[3][4] = {
; t/ s# c6 k/ P6 h% a4 Z, I    {0, 1, 2, 3},
# g7 a3 i) P, q0 W# y    {4, 5, 6, 7},
, |# C" Y" d7 s" Y    {8, 9, 10, 11}
& U: Q% S" `6 ]};
4 F; h8 m' W4 z/ m9 G0 f 实际上多维数组和1维数组在开启速度优化后,翻译成汇编代码是一样的。
, x4 o! K) ]( v8 R+ ~7 j' k2 \. V% e
5 @" e- r! |1 e& ^; \/ kvoid Print(int* p_ary);
. X  \, s2 C! u3 ^6 r- Q2 S& B
/ w3 e  Q$ H& G) ]* ?void Test1()1 q* I2 O3 W  s% Z: v& N8 @
{
" U, c% g; }& b: ]# B5 A  ^( m, Z    int a[10];5 c& P1 Z) l6 A8 x1 T/ c/ {
    a[3] = 3;
( R2 e( z' j1 g/ B3 Y6 k    a[7] = 7;
( ?% _* h# l# }, p2 _  q    Print(&a[0]);; e2 P  |! r; S& ]3 z) ^
}' P, T: f4 Z& \: D% F: G4 ?

# ~' p+ P& J) j2 K& S* Yvoid Test2()
& J* b3 ?  q2 u7 w& w. R{* D) V$ O; V& J' X, u( o6 \/ u
    int a[2][5];
9 b1 o# p$ d' R8 \, A5 e, |    a[0][3] = 3;
+ e% |5 J. O7 f; R    a[1][2] = 7;
9 K9 k7 m4 u& t2 u2 N    Print(&a[0][0]);4 E% a0 f' {% n# G# p$ M4 ~. b
}5 v0 m" p9 M+ {9 [5 Q1 ?) L" }3 ?

; k5 F7 K. h* {' b& J* n/ h& `2 t! h4 p1 M4 a
5 _( U4 O# ]  T" ]
很自然地,多维数组也支持用1维数组的方式去初始化。
* p6 A) a9 p3 G: d' u  n# Q. M& P" v9 o& ~
int a[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
# y* m, |3 B% @  t9 b6 \既然多维数组与1维数组没什么区别, 为什么还需要多维数组?
: d: _/ j# t8 L, m$ l; q
" }" V6 I) k7 w0 ?假设有一幅RGB图像720*576个像素,每个像素有RGB三个通道,每个通道的值是8bit大小。给出图像的首地址p_rgb_image, 我们要取第40行,第50个像素的R,G,B值。代码如下:
6 l# H- D: m" o% O/ [3 b- L3 s3 o# z% ~
unsigned char* p_rgb_image;; M: L* S7 K2 ^% l; F; A3 S. Y, s
unsigned char r = p_rgb_image[40*720*3 + 50 + 0];& z! e+ K6 G, H+ K% g2 z2 }2 g
unsigned char g = p_rgb_image[40*720*3 + 50 + 1];
. v, G; d0 H$ i( Dunsigned char b = p_rgb_image[40*720*3 + 50 + 2];
& P7 Y1 X5 g. E7 |0 j* k6 M& d类似这样的场景, 采用多维数组的写法, 有点类似以索引为参数,可读性更高。相当于程序员和编译器打了一个配合。4 {1 A& f- f; |% P

# |4 ~5 e* J7 t  m' D" Xenum
1 y( L; Z) h! W0 t- C1 @  Y{
) {1 X2 J9 e; ^0 c Red = 0,
& G5 z4 c+ E% K" W/ S) x Green = 1,
4 h' s7 g; M; D) S' B3 A Blue = 2
2 ]  |( z+ k' A) B};
: U2 Q+ N$ P7 `. s" F% \1 eunsigned char rgb_image[576][720][3];1 b4 Y0 p# n7 f1 g" T+ X" ^
int row = 40;
( k5 t- N$ c7 y- jint col = 50;
' c+ V& p8 U# p1 s0 E$ Ounsigned char r = rgb_image[row][col][Red];( z# r2 p9 w2 |3 o
unsigned char g = rgb_image[row][col][Green];1 b6 O9 Y' l& m! c" ^
unsigned char b = rgb_image[row][col][Blue];: b- _* t" g. M$ n% I$ m
数组指针以及与指针数组的区别: ~: I! S+ a1 }1 P* Q: S3 [
数组指针,是一个指针, 指向的对象是数组。 数组指针的赋值,要求数组的长度匹配,否则会报错。当指向1维数组时, 需用用*取得数组对象的引用,再用下标来访问数组元素。2 \# g* L  O7 O4 {* Q/ z3 F
指针数组,是一个数组, 数组保存的元素的类型是指针。' I; x& x2 n4 L3 H3 e
数组指针的定义
) h9 k" v0 Z7 X/ H+ c8 \数组指针定义先定义一个数组。) g! b5 P# Q, [8 U' T" K1 ~/ v
int a[10];
! n# S4 O" {4 j) f然后对数组里的名称用括号括起来后再在变量名称前面加个*号
  C: B& C9 Q1 Iint (*a)[10];
- ?2 w; z. w& N# {& j6 b1 B* }; X6 q后面你会发现函数指针定义类似。
- O" Z: V% J% n- w0 W+ U7 y// 各类定义对比
" N  h) J! N, w: gint a, *b, **c, d[10], e[10][20], *f[10], (*g)[10], *(*h)[10];1 t  }: ]# A1 t5 [0 j) [$ h! W

0 X. ^. N% m1 @/ h+ A9 ^int *f[10]; // 指针数组, f是包含10个元素的数组, 数组里每一个元素的类型都是int*  ?4 a6 y! x6 w  {+ ]# w- x: ^
int *(f2[10]); // 指针数组。另外一种定义方式。
3 p8 b% Q3 I: R) K: d0 u* Xint(*f3[10]); // 指针数组。另外一种定义方式。- ^+ J( E7 u5 _2 O5 D9 G# u- A
int(f4)[10]; // int数组) p/ ?1 l- P' h& K
int(*g)[10]; // 数组指针, g是一个指针, 这个指针可以指向类型是int,元素个数是10的数组4 D# I6 O) Z5 F  L- ?: \9 v) M
int* (*h)[10]; // 数组指针, h是一个指针, 这个指针可以指向类型是int*,元素个数是10的指针数组, g( Z. ]  h2 n2 u; w1 @' ~
0 X) X0 Z1 {" [4 p1 D. k' c% S3 Q
int d[10];9 J$ Q3 }1 z  f, G- f) i& R9 Z/ p
g = &d;& k* R. p- d4 p7 D  \
6 o5 y' s% ?- G% y+ l; }8 x
int* e[10];
, x* d1 g  E' v; j3 o/ O& Kh = &e;
1 ]# @" P: _4 F+ G8 |1 K数组指针的使用
0 J- j1 h+ E5 N% v7 J* a数组指针一般先通过*号取得指针指向的数组对象, 然后再用下标操作访问元素。
) O6 u1 P6 s5 u$ v8 j) ]5 {6 D# @  G( _4 n8 x' L* c
int a[10];4 U! D: M& L1 @  d/ _$ |
int(*p_ary)[10] = &a; // p_ary是一个指针, 指向"int (*)[10]"类型的数组
$ w5 L: k6 o- Yfor (int i = 0; i < 10; i++) {" s& O6 `4 m4 F4 x% @& V
// p_ary是一个指向数组的指针, 需要先通过间接寻址运算符*(indirection operator)取得数组对象
6 v0 {& s1 D: e  W // 再通过下标操作访问元素。7 {' n& B+ \6 @% c1 G
(*p_ary) = i;
# N% m8 k5 b- G7 f5 Z& J6 [2 L: a}- z" x& G% T+ z0 {3 l3 w( C

# K  S- ?9 T$ m6 ^/ j' nint b[10];
+ n" p' k0 m& s2 yint c[20];& Z* b% `8 V2 n! |5 b* ^
p_ary = &b; // 合法
3 t, x8 `, h9 G4 s  Lp_ary = &c; // 报错! 不能将 "int (*)[20]" 类型的值分配到 "int (*)[10]" 类型的实体
; i. j9 ~9 a( `% v! F: n数组指针指向多维数组的子数组2 q) Y% E# I4 X
int a[10];
8 A# H" [; G- a6 n/ F' pint b[4][10];0 s; k0 |3 W" x: R, S6 p7 S' ]6 a0 E9 n
int(*p_ary)[10] = &a;2 s9 [/ D6 l1 N2 A  k  c
for (int i = 0; i < 10; i++) {6 T$ S6 i' G: b/ X$ w8 n: W5 |
(*p_ary) = 1; ! h* Z0 F4 p" @+ }: l
}! G! e4 a! y, N6 ], q
) R" P! v' T$ Q+ N8 I; `
p_ary = &b[2]; // 多维数组,可以看作数组的数组,
; T" C6 @% Q2 E: Y, [, e) n// b[2][0] ~ b[2][9]的值都被改成2了
: M3 i+ q  G) Dfor (int i = 0; i < 10; i++) {
  l  w1 b- f5 ]0 b (*p_ary) = 2;$ S7 B4 S* c- ~' A4 n( G2 I
}3 O. F; ^0 j  t/ p2 H, c% o
多维数组指针8 p3 I( X# K  W
多维数组指针,是一种指针,指向的对象是个多维数组,支持多个下标操作。) `, I$ c; _. Y9 R2 X- \
8 T! }5 W) j3 i% k
int a[2][5][10];
3 d' t# t$ c* X* p4 S! U' @int(*p_ary1)[10] = &a[1][2]; // 1维数组指针
8 d7 b9 [; {- v" T# f  F' lint(*p_ary2)[5][10] = &a[1]; // 2维数组指针
( X/ M' Q2 G9 A1 dfor (int row = 0; row < 5; row++) {5 c4 r& e5 N  h8 j+ |8 L! D
    for (int col = 0; col < 10; col++) {3 s. G6 C0 |) d% }, z* w" V
        (*p_ary2)[row][col] = row * col;
3 T6 K" @, @6 R9 H    }% n5 W1 ]2 o) g  |
}
5 l# \8 f0 p+ Z: e4 i! J& t数组指针和指针数组对比实例
. |; q$ D$ M/ G; @% ~' G数组指针还是记住两步法即可
/ e, ^3 ?% @( ~+ h3 q1 Q3 x7 G9 q2 n9 z
定义一个数组1 g+ K1 e* }5 ]
括号包围1中定义的名称,再在名称前加个*号。
, W' |( b$ h+ K, Pint a[10];
6 d) ]) }6 ]' yint(*ary_pointer1)[10] = &a; // 数组指针5 |! ]6 q2 P" P  \5 y% Y% I
int* pointer_ary1[10]; // 指针数组。元素类型是int*
  e: b& |3 a& wint *(ponter_ary2[10]); // 指针数组。另外一种定义方式。- r/ X% d7 Q5 ]% t5 }1 c; V" B
int (*ponter_ary3[10]); // 指针数组。另外一种定义方式。3 b/ F1 U: k* i8 ?* X, e$ `
int c, *d, (*ary_pointer2)[10], *pointer_ary3[10]; // 排列定义比较。7 }, O: M& g# W; V& u" B  _8 N4 J) D1 l

& m* E5 Q+ d/ L// 指针数组可以把每个元素指向数组对应位置的地址。6 q9 i. H' W1 E. a& U- g
// 这样遍历指针数组, 可以达到遍历数组元素的效果,但是注意每个元素都是指针,: d, T. |2 I6 o
// 需要访问原数组的值的话, 需要对指针用*间接寻址运算符。
( G8 E( N+ X- aint* pointer_ary[10]; 0 s$ @" n* Z/ G
for (int i = 0; i < 10; i++) {! ]4 j6 r+ E' k2 B2 B
    pointer_ary = &a;
! K) i( ?; D5 R4 }$ d3 e& G+ q2 {& q- I- v0 ^}
% @+ v5 s+ }: q/ T// 类似遍历原数组效果。
" E" S1 G7 E! @% V: Y, L) g3 C1 zfor (int i = 0; i < 10; i++) {
, k& R4 l7 l& C2 w# Z( o    *pointer_ary = i; // 修改原数组。0 t3 o( Z7 |) l2 @9 ]1 Z
}
' m5 A3 f  E6 ^1 Y% i
, A2 S2 r' U* A  Z' v函数指针; t: a2 P5 M+ S! }/ S
取得函数地址* K$ J# ?. h3 z# E5 t
函数的名称作为参数被传递时,会隐式转换成函数指针, 和在函数名称前加取地址符&等价。建议带上更加统一和清晰。
6 b1 b% e5 Y* e; A( v! E7 h
- w6 D7 u. M4 b; T( y% Svoid f(int);& f; I+ q2 I$ f) @% N
int main()
: t) j7 ]( I2 F! `$ ]4 |3 Y. l{6 `2 z1 [, ~4 K" R" y6 L$ m! J
    void (*p1)(int) = &f;
9 k7 r( _" G- b    void (*p2)(int) = f; // same as &f8 u8 b$ [) x3 b  U4 t+ n
    return 0;$ Q7 i5 V, s6 E# d! A
}4 H$ U3 T7 j2 i2 p
翻译成汇编代码, p1和p2的赋值是一样的。8 E/ P  Q7 R+ W% H4 B1 K2 z% T

& r+ L# F) D9 n% V' R) `5 T+ V/ b( p# Y' ^* P2 P7 U  N
" `7 R# x# H0 R; `, Z2 d& @! t4 \
函数指针的声明
8 O# q3 X; q1 k% X单个函数指针变量定义步骤9 M; D  o/ o+ v" U
定义一个函数。void fun1(int a, int b); int fun2(double a);1 K) }/ W: x8 C
用括号把函数名称包围起来,然后在名称前面加*号。void (*fun1)(int a, int b); int (*fun2)(double a);) G- B1 t7 x6 F# R4 W1 p
如果要定义函数指针数组,在定义单个函数指针的基础上,在名称后面加上[数组长度]void (*fun1[2])(int a, int b); int (*fun2[10])(double a);
: l3 y7 O' G7 Otypedef定义函数指针& h+ K1 D  J2 H. G: d' i
可读性高比单个定义要高,特别是声明多个同类型的函数指针,或者函数指针数组。
' E9 P5 k7 P- k" k9 J7 K! M' u5 a' ]4 r6 _
typedef定义函数指针的语法6 J" ^3 W4 D+ o5 M! ?* R
typedef有两种做法, 一种就是定义一种函数对象,另外一种就是定义函数指针。用法稍稍不同,效果是一样。其中函数对象不支持赋值, 但是支持引用。
) F3 L# |; w% c! ^% R' ]8 \3 e, A7 r5 O1 B. U- z
typedef int FuncObject(int a, int b); // FuncObject类型是函数对象7 O* C7 b9 Z2 H+ j! A
typedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针
" R4 s6 j6 G% R: D8 UFuncObject* f1 = &Add;/ @6 [) x" j% P
FuncPointer f2;
( r& [# R$ y8 S5 cf2 = f1; // f1, f2类型一样, 都是形式为int(int, int)的函数的指针。+ z* S; q' ^+ q: n
FuncObject f3 = Add; // 报错! 函数对象不支持拷贝
/ q1 N9 h' }3 a% E2 E: g  w. PFuncObject f4 = &Add; // 报错!&Add是函数指针,与函数对象类型不匹配% x& e9 q. u/ o" f0 q: S
FuncObject& f5 = Add; // 正确
" @+ u+ g7 z- A3 G( ]% sint ret = f5(2, 3); // 正确
. d" j" p; m3 }  Q8 P* wFuncObject& f6 = &Add; // 报错!&Add是函数指针,与函数对象引用类型不匹配: O9 s' ^: S2 @$ Y6 q: W
如何记住typedef定义函数指针的步骤. C( z- N. s9 T, q; A" e
像定义一个函数指针那样, 指定一个名称。int (*CalFun)(int a, int b);
7 b* h# y; w1 N/ I0 K在这个函数指针变量声明前面加上typedef。typedef int (*CalFun)(int a, int b);5 I& X5 L, a' j! ^: B2 U* D
完整例子' `9 X+ M: ?5 }- h- M( ~7 H
typedef int(*CalFun)(int a, int b);
% ~9 N' ^3 g! @
9 F/ R& P9 I8 ]. \$ B& T; A; {int Add(int a, int b)
( q- }( C' S7 O, Q7 h{
6 Y: X% ?$ `) ~. V    return (a + b);
! e; k( ^# U! A+ c+ L  d}* D* I* V) ]3 }$ w" O4 J
7 C1 w8 {& P; h$ H. j
int Sub(int a, int b)4 @( Q4 O9 O0 h; `1 t
{
" x  f+ g' s1 ~& v- ]5 [, u- R8 A    return (a - b);
, D3 X. i+ S9 q) Y) P}
# c8 t9 [% z+ O9 K8 Y+ C! ^; `. h
4 @$ Y* E* G. n5 Z4 ]int main(int argc, char** argv)
5 `( H) Z5 G1 \  T{
9 L) P9 E; v! p2 y3 {    CalFun f1 = Add;4 W  c6 D; E" q2 Z
    CalFun f2 = Sub;/ t' p) S6 B1 w6 F
    int a = f1(2, 3);  G$ u" X- z" {5 D7 L
    int b = f2(10, 5);
& s" E" C! {: h# }) T; c) E# w
( g6 W6 a6 H9 u    // typedef定义的函数指针数组。
' W0 s  y/ A- O% x  l. Q" h3 T, Z    CalFun f_ary[2];
% \" S* N8 i! G/ k& H    f_ary[0] = Add;' W5 w' K2 O2 T# u7 m8 B& b
    f_ary[1] = Sub;
" G& G, |) i$ s' ~- N: }1 c8 p4 L5 z2 [) P
    // 单个定义的函数指针数组。( I! v9 z6 N# V8 {8 W, @
    int(*f_ary2[2])(int a, int b);
, K* F  q; l- W/ Q    f_ary2[0] = Add;
: u1 i* g' u2 u    f_ary2[1] = Sub;
  D* c" a4 z( U# u
1 m. z" m6 m4 \# W4 z    return 0;
/ I1 P) v) N* p}
0 p7 t, N/ I2 c- y; s8 N
4 F5 S* A7 b" Q# N  U0 l3 Susing别名定义函数指针9 [/ [6 _$ G2 j, c1 B" ~
c++11以后的类型别名定义--using也可以用于定义函数指针, typedef的好处它都有,个人感觉比typedef更直观。using类型别名同样分函数对象和函数指针两种方式。
$ I7 p4 |+ a% `1 h! M) x7 E' W* X
- Y1 ~, p. f; g2 p, Ntypedef int FuncObject(int a, int b); // FuncObject类型是函数对象5 C  `: |- u0 w7 u
using FuncObject = int(int a, int b);! G( ^1 Z, G" {# k
typedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针
7 d1 C5 [2 x  d1 Cusing FuncPointer = int(*)(int a, int b);1 X. j. d, C, }5 \
函数指针的调用
4 [* ?1 m& E5 r* I" l! T: q5 K函数指针和函数对象都可以直接后加括号调用
- B5 u7 s& u4 t3 C+ ?) T$ o' G( dint f();+ f( i; u3 U( X$ N& @
int (*p)() = f;  // pointer p is pointing to f* ~- x$ p0 L0 w" \4 b7 }3 r
int (&r)() = *p; // the lvalue that identifies f is bound to a reference
5 {4 c7 c$ G- |' g5 _r();             // function f invoked through lvalue reference
# b: r2 x% ]" a5 G/ S(*p)();          // function f invoked through the function lvalue
) s9 ^2 F8 G% B1 |" [p();             // function f invoked directly through the pointer# _5 p. c) X, I
如果函数有重载, 函数指针会指向匹配的那个版本。
0 `2 r6 J3 G5 l& [+ z; Etemplate<typename T>) I# [7 q: C3 u; J6 J( H
T f(T n) { return n; }
$ i1 [$ [9 q$ t, d* Q6 i: p4 z5 |$ W7 g, R
double f(double n) { return n; }
+ b  Q% L( ~( q
: m* C# H' O9 r3 y+ q: mint main()& H- s- `4 \4 y0 p
{
  }5 Y3 I8 O+ T# f. u* E1 {    int (*p)(int) = f; // instantiates and selects f<int># n) f$ v2 c& W, v9 v6 ~2 f
}+ j) P+ u8 f4 ?
成员函数指针
5 t, c$ `' ^: D9 f静态成员函数,除了增加了访问控制以外,跟普通的函数指针没什么区别,所以普通函数指针可以直接指向类的静态成员函数。但非静态的成员函数与普通函数指针不太一样,声明时需要指定函数归属的类名,并且调用需要指定对象实例。4 j* U( W% x& a6 o
1 v) [% n3 m  L! h7 H( S
成员函数指针定义。2 ~" p/ e" V; p% Y
像定义类成员函数实现那样写, 并任意指定名称,这里作func。void ClassName::func(int);* a2 x, G! _/ \4 Z0 f' y
括号把类名、范围解析运算符::、名称包围起来。void (ClassName::func)(int);
: T- }# Y. D$ l! t4 [2 v在名称的前面加个*号void (ClassName::*func)(int);6 O4 x5 g0 E, i
成员函数也支持typedef和using的定义方式。typedef void(C::* MemberFunc)(int); using MemberFunc = void(C::*)(int);
8 m$ S+ d) _' \: x* ~成员函数指针如何调用。( c; _3 T8 m: }3 _" |& ?6 w8 P1 V
假设成员函数指针名字为func
8 S0 t: t! T4 @! l* k5 K
/ H( }& f5 \1 W' fvoid (ClassName::*func)(int);
0 y2 ~/ H' f. h/ B对象式调用。
; h, ~8 s. r) r- h* U" [ClassName c; // 被调用的对象
1 F* z% m% G. s) c. ?8 U7 q+ j2 ^成员函数指针名字当作正常函数那样写。
+ n! i: |& L4 G, ?c.func(3);! C4 n. C+ y  P2 G7 k
成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。1 Z; w& b) ]. J6 s1 A- l& w
c.*func(3);
2 E! t$ J! F6 P最后用括号把调用对象、成员访问运算符.、间接寻址运算符*、和成员函数指针的名称包围起来。
( I; k$ t# J" r4 @(c.*func)(3);/ V& O+ B/ @" D6 ?8 {* K( y
为何要加上括号? 根据c++的优先级标准,取成员运算符. > 函数调用() > 间接引用符*。 *号优先级比函数调用要低, 成员函数指针还没取得对象就被调用了,自然报错。 另外
. M6 F' V( d5 y: W0 u. h(c.(*func))(3);* ^% C, v; H- v0 g, ?6 N2 ~
这样的写法也不行。 .*和->*是整体作为一个运算符的,中间不能用括号隔开。: g8 D" a0 k' {8 v/ c  f
指针式调用
8 h' u1 ~. D, j8 t  QClassName* p; // 被调用的对象的指针
) q' R6 R/ M1 m成员函数指针名字当作正常函数那样写。  j$ I! |: d5 d& B/ i
p->func(3);
! k3 j  T5 R& d; Y9 g2 N* W成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。4 X/ f' |2 U) `% H5 V7 a$ v) f# A
p->*func(3);
$ |$ g0 K: h5 j; A最后用括号把调用对象、成员访问运算符->、间接寻址运算符*、和成员函数指针的名称包围起来。
6 ?& ^: s8 J0 Q4 |. D8 X(p->*func)(3);& r* L% b+ B" c
函数指针使用完整例子5 Z9 L' @* E7 z8 ^/ U  B) R
struct Cal
' R9 A) J. ^; S' w0 t- O{
2 f' {9 t3 g/ D- S5 X' K    int add(int a, int b); : z7 |- f: e! P. }' Z, S9 u& Z( [
    int sub(int a, int b);
0 k! L  }. F9 I3 m, r8 |};# ^: t, P- Z7 L" T0 v9 s
- x* O4 s- D  i) F: P: [7 p
int main()
' o- k- S3 [* L7 y, g5 G: e" p3 j{/ Q) _2 r( E  P
    int (Cal::*fun)(int, int) = &Cal::add;: W& ]# N* W( `9 D, v
    fun = &Cal::sub;
8 ?+ a2 O: T6 V. o, T& m
$ ^! I0 O8 U; ~  B    Cal* p_cal = new Cal();; L% T- q3 ]5 y5 ]+ K8 [, p
    int r1 = (p_cal->*fun)(2, 3);4 I2 O8 f# h% z7 H# r( `" J
    delete p_cal;
: o+ [' L, c( P& N6 g6 `2 K% i
4 P( p# Y* Q' z& h, _    Cal local_cal;! K3 J1 |5 `3 x& w4 O1 T$ s' V
    int r2 = (local_cal.*fun)(8, 6);  `& ]% s) }6 A
}1 c2 P% K. H6 m+ }3 Z* ?( ]- A

! t, l. v2 a# X4 n; N成员变量指针
3 B8 E5 ~8 B% B8 z成员变量指针比成员函数指针还要简单些,没有函数调用, 无需考虑函数调用和间接引用符*的优先级问题。
8 e5 ]# n5 a% _, k6 @0 ~; Q
; i" P. U, g9 q成员变量指针的定义
' @0 M$ {  E0 h  U- H8 A* X假如以下结构体C。! i, g0 L: Z1 f5 R$ u5 v; G
) k" M( x" Z1 A: N
struct C
7 Z6 B8 b, r* }3 h; u4 z, x, l{
% ~& o3 \  f' F- g    int m; / v) A) U7 `1 Q9 W' v2 L0 g  k2 J
};
3 g" P) c6 z3 g7 v: g* A2 i% c, J单个成员变量指针定义 3 d4 T: v, o! t
假设名称为p, 类似静态成员变量定义那样声明! Z' G( U) v  M/ Q5 G* ~4 R5 h$ G
int C::p;
4 W; Q  g" p) g# h) v$ v) a在名称前面加上指针标识号*
1 v+ \& J5 q8 N! w' J1 Hint C::*p;& P0 O  A) q0 Y2 P
typedef或using方式定义
. p5 D2 f: W. S5 \7 d6 R! Otypedef int C::*MemberPointer;
0 v. {; l4 Z8 a4 H5 q. yusing MemberPointer = int C::*;" I7 @4 a6 h" i
成员变量指针的使用。
/ _9 k8 {9 h, _% D2 y类似成员函数指针那样,直接使用指向成员的指针运算符:.* 和->*即可。& Q; d% S, C) R
成员变量指针, 能让我们实现一些遍历成员的动态功能。 例如把一个类/结构体的多个同类型的成员变量放进一个容器里,然后遍历访问这些成员变量。
2 V" p) R1 w- I7 @0 K% H* S6 ~; h: x- E
完整例子
( o- G" c6 V( M8 Gstruct C { int m; };
5 v$ ]4 x8 u# s  r5 F) Fint main()
  y/ U) N$ f7 z* I( C- T& m% j{# F  n  l' k/ N( a
    int C::* p = &C::m;          // pointer to data member m of class C
$ j, G' l$ ~- d. ^+ X3 V& Y    C c = {7};
, u: V* P5 s; f( q9 @! W1 _  r9 t    std::cout << c.*p << '\n';   // prints 7  L( i8 M: m5 Y; Y+ k* o+ U) x
    C* cp = &c;& Z, A( w9 n; O' G+ a$ J
    cp->m = 10;
8 l; |( n5 d: }3 f# |# U    std::cout << cp->*p << '\n'; // prints 10" W# v- T, p* c3 ?
& G* v0 o& q# n7 [* m& G
————————————————- S  h; ?6 v$ |0 B2 W9 \6 r' Q# d
版权声明:本文为CSDN博主「南风fahaxiki」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。+ U2 F. i1 L# ]: z. v) W
原文链接:https://blog.csdn.net/m0_64407685/article/details/126788115' U: A: K: z8 h8 V' w) x- T  v

$ ?, C/ y% f1 i( ~, N  ]1 ^" i  J  p2 F& f8 y: ~1 `1 J$ `/ h





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