数学建模社区-数学中国

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

作者: 杨利霞    时间: 2022-9-12 18:49
标题: C++数组指针、函数指针、成员函数指针
C++数组指针、函数指针、成员函数指针, B2 \) ~, s* u6 r+ T8 F/ |+ o( N3 e
C++数组指针、函数指针、成员函数指针& m- J7 r3 i" K1 E& c+ j) Q

6 Z; o& V7 t# \
% v* }2 O1 P6 N8 d" M操作符名称* J. f' j* j. q& ?$ M
& 取地址符(Address-Of operator), z! x9 ~1 g8 O4 B- {
* 间接寻址运算符(Indirection operator)1 f- q+ O* [, h
.和-> 成员访问运算符(Member-Access operators),用于取对象的成员。
0 I$ z% y5 y% h.*和->* 指向成员的指针运算符(Pointer-To-Member operators), 用于成员函数指针和成员变量指针的取对象。
/ H! H& ]6 f) }$ Q9 X4 r() 函数调用运算符(Function-Call operator)9 ^7 p, S* }; y" Z; B) }( g: z
:: 范围解析运算符(Scope-Resolution operator)
& b& J8 v9 m4 P8 T! Z如何定义一个指针变量" O+ r2 ]; D  }& O/ g
假设类型T, 变量名称name, 指针的定义如下:
6 t4 D. V$ H5 F; H4 i
0 w$ B$ `* u: `" C+ Z" jT* name;. Q" }# f, W  N* u
标识变量名字name, 它是T类型的指针。例如
# N7 U% l/ c" V. a
: t6 M: _0 b) U, V1 ~+ @, Aint n = 0;3 b+ {" t' E9 |: m
int* p_n = &n;8 L5 z$ ^9 A6 m) I3 z
p_n是int指针类型, 指向某个int型的对象。# X3 Z5 u) W! N$ Q% S. Q4 \
% b0 d  y4 `% g5 T9 ~/ h
指针变量的修饰
2 \( `# l9 G! M  V  e" V  G% a9 F指针实际上也是一种变量类型, 只是它保存的内容有些特别, 是指定类型的地址值,通过间接寻址运算符(indirection operator)*, 可以访问到指针指向地址上的指定类型。
6 s! Y3 w' P) q' U+ |- O
, z5 K. w* @7 [4 Z指针也可以用const, volatile修饰。 const int或int const均表示一个变量类型是int, 且该变量不能修改。以下两种写法都可以:6 C7 r  f  a" h, b# f7 y6 L/ G7 i

+ i3 X' Y5 Y% t$ }3 h5 p% lconst int a = 1;% B; x) j! N3 z5 [
int const b = 2;
( v) x; I! a. G; [7 i7 T既然指针是也一种变量类型,同样支持被const修饰, 表示指针的值/指针的指向不允许修改, 指针所指向的那个变量是否允许修改, 那是另外修饰。写法如下:
* j7 x/ d3 S: C5 K+ a2 U- |8 E9 B5 t! F" ?5 K
int a = 1;* S* g8 W& M% a0 _# _+ N& H0 B
int b = 2;( I1 d% p! C( Z0 j3 B- U
int* const cp_a = &a; // 指针的修饰词,放在*号后面。: S9 c. l2 z* J# }- a1 `9 V/ v
*cp_a = 10; // 指针指向的值可以修改
' D! R( k* V; d' ocp_a = &b; // 指针不能被修改,报错!2 W9 e+ J: c  n& U! `
总结带修饰的指针的格式:" }6 L( r5 b0 t! w2 w
只要记住修饰词总是放在被修饰的内容后面。
0 W. T4 H5 K/ Y9 X2 I' K% H. Z; I5 o2 l/ R6 O
cv表示const / volatile修饰词。指针定义形式如下:, p+ b3 o% C" k5 `; d
# ]0 f9 e; a# Z2 H7 l
T [cv for T] * [cv for pointer] name% I, m$ `. G' H; i/ t
注意对T的修饰放在T的前面也是合法的写法。
4 L; T6 E7 g. R! V; \# b
/ A: u) T& ^# J( d2 Vconst int const c = 2;
& ?8 Y9 h9 k! X0 F5 X在mscv编译器下也不会报错。
0 I+ u$ G1 u* [1 o& O9 o) j0 N! U* [/ ]
完整的格式:2 u! F4 U( {: u4 L% w: M
[cv for T] T [cv for T] * [cv for pointer] name# {4 O  L- k# i% A6 X
. E. W2 r6 K/ Q  `" \' t
Syntax        meaning2 {, j4 x0 K0 ?0 o2 P5 W$ V
const T*       
; C) Z! I; [, h( vpointer to constant object2 D/ p2 g$ }5 J$ i7 @
( E; Y* b2 s7 s) x1 C) |
T const*        pointer to constant object
$ P5 K3 O$ A! D  V6 M/ eT* const        constant pointer to object
1 R4 W3 H7 r" Vconst T* const        constant pointer to constant object1 Y" V2 W$ @" M& k
T const* const        constant pointer to constant object0 |2 G  i9 i  t& E/ x: r. g
上面格式中T还可以是一种指针, 指针的指针仍然是按照修饰词总是修饰前面的标识(T或者*)来确定修饰的意图。: y3 N% b+ t* C% v
3 m, s6 v- w9 g0 y3 S/ q' y
int a = 1;! Z" q! o, ]" {, S  V
int b = 2;
  q: N  d, |- |# e! P* L3 m
& C% `, g; L) y3 o. _1 cint* p_a = &a;' z' D- d) U7 p* S3 m" V! w2 s2 q4 g
*p_a = 10; // 合法1 y' O% B3 X& s9 D! _  ?3 M
p_a = &b; //合法2 K0 l5 O' w/ E4 y) F$ ^( X+ L

/ o: Z  V# ]4 v7 T1 c7 _+ tconst int* cp_a = &a; // const修饰int类型, 并非修饰指针: t! s/ ], q# E+ {0 J
*cp_a = 11; //报错! const int类型不能修改
' |$ [2 R% S5 j6 ?cp_a = &b; // 合法, 指针没有const修饰,指针可以修改。
1 N3 D1 P. w8 @, i. A0 a/ f& h/ ?3 K  C* M- G+ b
int* const pc_a = &a; // const修饰指针。类型没有const修饰
$ m2 N/ V' T3 T2 l; Q% Q' X4 D* x*pc_a = 12; // 合法, 因为类型没有const修饰,可以修改。
- `- a1 ^3 T; l+ N6 Qpc_a = &b; //报错! 指针被const修饰, 不能修改指针。
2 T: n6 s5 b+ g  W
( n: N1 {  i" X, h, ^int const* const cpc_a = &a; // int类型被它后面的const修饰, 指针符号*后面也有const修饰
, S4 W  \; V( k+ y/ @*cpc_a = 13; // 报错! 类型被const修饰,不能修改。" ~$ U9 P/ n% i: ~
cpc_a = &b; // 报错! 指针被const修饰,不能修改。& s5 n# z. }; T' t$ n3 y
. {3 A7 `3 k9 {+ T2 T
更复杂的指针的指针! F# b# N8 ]( S# W7 d. W

( g( }' o" q. O- gint a = 1;
5 ]9 a0 d+ K7 r+ k/ q+ O" Eint b = 2;
( f1 V$ J+ s3 c7 I3 S9 O+ Yint* p1 = &a;
# O; M. a) S* S8 C) Gint* p2 = &b;
0 A# v7 P. X0 N7 }: J! }6 i* h; tconst int* ct_p1 = &a; // ct for const type: c- o2 x( @0 \7 Z) s
const int* ct_p2 = &b; // ct for const type
+ i: |0 `3 m# |% K' q: A% a( w# @. Y" z; x- y+ A$ F' i4 E
// int * * pp1; 指向(int*)类型的指针" p+ r5 y/ r- r. i4 o
int** pp1 = &p1;  - \, ^- d3 o0 z6 B2 ~; J
pp1 = &p2; // 合法, . v2 I# h- v- U: a' ^
pp1 = &ct_p1; // 报错! 类型不匹配。 (int*)不能指向(const int*)
. W: u2 F! N/ x$ `! |; W# k! O: C1 F& p  X% K) }
// (const int) * * pp1; 指向((const int) *)类型的指针: Y) ?  s! R7 W3 _, Q
const int** ct_pp1 = &ct_p1;  0 @6 f% B/ G# x. T
ct_pp1 = &ct_p2; // 合法
& A) a8 o# Q- o! Q) M% s/ wct_pp1 = &p1; // 合法!(const int*) 可以指向(int*)类型。
; Q  J7 X$ f/ A( e6 H5 C  ^
' E  s& m# Z" N) t# g4 w- g5 G// (const int) (*const)
3 A; h8 ^" C- A7 b4 L7 econst int * const ct_cp1 = &a; // 指针也不能修改! F) l0 \" \6 V; X
const int * const ct_cp2 = &b; // 指针也不能修改, G" V2 `" n3 J1 G( q* b+ X0 c7 P1 W6 S
ct_cp1 = &b; // 报错!指针有const修饰
' \$ S  z" F: J3 r. _+ S- D3 |. z; A
// (const int) (* const) *  指向((const int) (*const))的指针
: X; I6 n" ^! T. iconst int* const * ct_cp_p1 = &ct_p1;  * Q0 U, m3 d6 L& ^  l& U) n
ct_cp_p1 = &ct_cp2; // 合法, 指针的指针并没有const修饰, 指向的指针有const修饰
, l2 L1 t8 S! y! R2 k2 E' I. G! c*ct_cp_p1 = &a; // 报错!等价于操作ct_cp2,  指向的指针是带const修饰的不能修改
% F1 d8 X7 Q& u, A$ G# f8 D" B7 q  ~+ C- v" G& S/ S
// (const int) (* const) (*const)  
/ Y; z! s& h; K( F+ x1 K// 指向((const int) (*const))的指针,且该指针被const修饰# W3 Z5 C$ D+ |4 k% ]* R
const int* const * const ct_cp_cp1 = &ct_cp1; * F9 u- c& c6 I: y
ct_cp_cp1 = &ct_cp2; // 报错! 指针的指针被const修饰, 不能修改指针指向。8 L, j7 g- y9 @% Z' s. g. D5 A6 O

& ~* F; A) a7 ~; P一行声明多个变量
8 A. |+ l1 j$ d) L; F* m类型 + 名称定义一个变量。
& q) v" R+ G! i变量的前面可以加*号修饰, 表示指针, 一个星号代表一层间接。**表示指针的指针。
' @- \9 j& Z  h5 c7 o4 z0 j7 r, v/ Y8 ~7 C
int a, *b, *c, d, **e;
; S; a2 C. z4 `' ]" ?a = 0;
. T! W. E, V: T7 S0 Z' Xd = 1;
/ R4 k: F; I" S7 C  O: tb = &a;
) c5 i( }' O0 L- R$ U* kc = &d;
$ `; N6 a  T9 N, S5 V7 n1 re = &b; // e为int**类型 指针的指针9 N4 q- w. T' A
e = &c; // e为int**类型 指针的指针4 x% n% @+ N9 y9 Q5 G: \
也可以用括号包围变量和*号。
+ b6 F- J  F' B4 }5 g  Z6 w0 A' h. s" d
& C( V) H1 q9 r! d# J" c8 nint (a), (*b), (*c), (d), (**e); // 合法定义。
4 T. U; R! B; t5 w' T1 q8 _括号可以省略,某些情况, 个人感觉加上括号更清晰一些。例如
# M! Z% X/ i, ^% D* Q8 l: U# z/ V8 f& m  w  p
int (a), (const *b), (*const c) = &a, (const d), (const* const* const e) = &c;, Y; {% C8 L3 o0 O) X9 J' U9 o
写成: R9 n! H: t& X) ?7 n) q1 E
& ?* |2 g; B% f( k* w1 d. P3 k: s
int a, const *b, *const c = &a, const d, const* const* const e = &c;9 ]5 L! S0 T, ^# n4 B3 i/ F0 V3 b
更重要的是, 后面我们表达数组指针,以及函数指针时,括号是不可缺少的, 带括号的表达更加统一。
8 w4 N: C6 o% u1 P1 v( A0 Y$ o% ]1 [% z* m9 t
数组指针
* x) ^. X$ P  [- `5 `! l- O/ x1 ?数组基本表达: ~3 Y5 m- _/ y9 s4 p  G
int a[10];  // 定义了类型是int, 元素个数是10的一个数组。
$ w  y; J3 b/ z9 n由于c++要支持一行定义一个类型的多个变量。 所以数组的[]时放在名称后面的。虽然我觉得/ o  k9 m+ {' c; G  U" i" t5 {

+ ~" b; f/ |4 ^* ^( o. Lint[10] a;
  q7 s  d! U( B这样的写法更符合类型 名称的思维, 但是如果类型都这么写的话, 没法兼容以下的写法:
! s) e9 E0 W0 U5 |: |0 |' T: D
1 j" s  @7 W4 |2 h$ A+ `, |int a = 0, *b = nullptr, c[20], **d = nullptr;, ]3 E& i( U  C# x1 i; u7 V
c++标准规定如此,但我们可以通过每一行只定义一个变量的写法, 类型会更加清晰。1 X1 r- p1 X% m* X8 ^0 B, C1 a9 o$ @

, L: p+ F9 n7 f7 c" Vint a = 0;
6 P4 ~- F# T: o2 m8 [6 \int* b = nullptr; // 指针int*; B' I! ]  Q* ^3 O8 G8 |! F
int c[20];: o! m7 Q5 P  Z% g1 e5 Z
int** d = nullptr; // 指针的指针int**
4 b0 L5 ~0 @9 r3 {9 i2 F/ X1 F1 J: e数组的名称是什么类型# C8 G! `" s6 V4 m8 U2 ?8 r
数组元素类型的指针,可以直接指向数组。 并且数组跟指针一样,可以通过下标去访问元素。
9 W* ~% T) I8 D6 G3 n$ G
& l' T) l1 {/ h9 B$ u0 f% Sint a[10];
% j0 x- S1 u; e( C1 p8 Bint* p = a; // 指向a数组的第一个元素
' z: r% J  q7 Ca[1] = 1;- {, P, R! G: l2 m2 Q
p[1] = 1; // 效果与a[1] = 1一样。' E8 [5 b* O: Y2 ~7 L
数组可以当作T* const来使用, 但是又与T* const有些不同。sizeof()的结果不一样。
5 f; T0 r, P$ u/ G) n7 U, e4 ^9 Q
int a[10];
6 v1 I4 G! e* xint b[10];2 M# I7 Q5 u* x4 U
int* const p_a = a;
  _! d- f) {/ U3 v! [! sa[0] = 1; // 合法。 数组的元素可以修改。2 T0 F3 W/ s& W0 ]& x
p_a[0] = 1; // 效果与a[0] = 1一样。7 x, l/ D2 h2 F2 \, J

: O$ m* Q) Q3 k, U# Ha = b; // 报错! 数组本身的指向不能修改。" \* l+ p) c" i' l! O& z

' j# ~- s) ]2 C6 }) g# [// 所以数组a可以当作int* const来使用* U, t0 X  m% p. h
int *const& ref1 = a; //正确。
3 g, c/ |* o2 @3 Q( x; ^int *& ref2 = a; // 报错!: u4 h8 Z* z9 J) j/ R

& Q6 n, I& L) w; a& \. e8 m2 H% X// 但是又跟int* const有些区别。& W$ j: ~2 }% p
assert(sizeof(p_a) == 4); // 32bit程序。" z+ b: y. i5 \6 p* ?7 V
assert(sizeof(a) == 4*10); // 32bit程序' r% D& B/ z9 O0 |  C. V

2 e# I" i9 c! w. I数组跟元素指针的作用很相似,都可以通过下标去访问元素, 但调用sizeof()函数的结果不一样。元素指针的sizeof()返回值是4(32-bit应用)或者8(64-bit应用), 数组的sizeof()返回值是数组实际占用的空间。数组可以当作指向第一个元素地址的T* const来用其实就是我们常说的数组到指针的隐式转换。当数组作为函数参数传递后,会自动退化成T* const, 在被调用的函数内部调用sizeof()的返回值跟T* const指针大小一样。 数组传递作为函数参数后, 在被调用函数的内部与T* const是没有任何区别,只有在数组定义的可见范围内sizeof()才有获取数组占用空间大小的效果。, ]4 m5 Y& u3 |0 [3 k; \6 z
4 m; u6 a0 C5 P% {! s( S
以下3个函数翻译成汇编以后,汇编代码是一样的。$ U5 v; v' [: O1 R) y2 |

9 b* j" Q% l) c% U% S+ Z- V: Fvoid Func1(int* p_ary)) y6 O: q: S# X1 V& o
{
+ P' L& t8 n0 {$ y    assert(sizeof(p_ary) == 4); // 32-bit
0 p! K: f5 n  @# Y    p_ary[1] = 1;1 P+ D0 t0 r9 O. A$ t. V- j6 e
}
0 P3 c7 i) }& \# v6 z9 m
; [3 l6 {2 _+ K2 \void Func2(int ary[])
! d, R0 {1 |  C* J* W{
6 ?1 y2 K5 U" U  [% o, Y+ L    assert(sizeof(ary) == 4); // 32-bit$ l9 X* R7 m! U( m9 {
    ary[1] = 1;
* {% \# w( h+ s/ e}
2 m/ f6 y& G) S% O* m8 G$ \; M& x* T$ F1 ^" G$ ]7 F
void Func3(int ary[10])6 M: }2 B6 m* z4 z& M8 F9 G2 P
{! P. d7 ?" i) t; w
    assert(sizeof(ary) == 4); // 32-bit$ S' i, N; m4 v& k  l0 ]4 M
    ary[1] = 1;4 a8 R2 p) @  y: e
}& B( h# r. ]2 B: {  p
1 G  c$ G/ p) o. I$ P
int main(int argc, char** argv)
+ D: _3 ^0 R  L% B- l7 |{* b2 O. x( y% U2 g5 F3 Z
    int a[10];
6 M+ q: V$ s3 o: j& k, _1 v; I    int b[20];
/ {, }: k7 ~3 n    Func1(a);
8 ~( P& t5 S7 U) l    Func2(a);
4 |$ M' `; @5 N3 p: g4 D    Func3(a);' b% u: p% _) D5 K/ m7 ^
    Func3(b); // 退化成int* const了, 即使数组长度不匹配也不会报错。
" d. |6 z4 y+ W2 e, |) f    return 0;
# ~# z/ {; M* t}/ D8 Q% K( H" K/ o
+ S. b! h1 o% E% k- [

! ?* s0 ^  ~* C( q. f2 z* h- ^* ?) y6 Z1 G( R
多维数组7 ^1 l7 o9 \. o3 r& F
一个3行,4列的数组, 结构如下:
) l/ I' P6 Y8 t4 m
+ g5 P+ N5 \! T& {! Jint a[3][4];# n9 x2 S/ \. N4 t& L
column 0        column 1        column 2        column 3
- c4 e% l* ]) f: Y4 j: @& P% }( W& Krow 0        a[0][0]        a[0][1]        a[0][2]        a[0][3]
7 I* o; C0 {% B7 irow 1        a[1][0]        a[1][1]        a[1][2]        a[1][3]
5 P1 h8 \) v: `3 ]2 w* [+ V  Srow 2        a[2][0]        a[2][1]        a[2][2]        a[2][3]
( c. c; |4 M0 w数组初始化1 K: }8 w0 m  W8 o4 U1 x; f+ R

5 O4 B* @: q/ J2 M4 x' [% ^* `int a[3][4] = {
* J1 |/ |3 n: `: |" B    {0, 1, 2, 3},
1 Q: y1 }8 _$ `- O0 n" r: `, C    {4, 5, 6, 7},
9 Z; P  t; @- z" C: `& {( s6 a    {8, 9, 10, 11}
, T* A# k$ c! I0 r0 `# L$ b};
2 n4 J/ Z7 |$ Y5 l) M. `" e 实际上多维数组和1维数组在开启速度优化后,翻译成汇编代码是一样的。
# r2 S. z1 b5 Y5 O/ X" [/ s' A( w! F
5 I0 q& m  m' W  `" r. ]void Print(int* p_ary);" F" ?" O% ?% @) C
  F' G2 D* Z- i  h7 T; {( R9 Y
void Test1()/ G4 U1 b) d$ W4 _: v3 m. R1 |
{
( \. h4 T9 D' P( A8 ~    int a[10];
% `  I' f) I) _$ ]+ w3 j2 W    a[3] = 3;
7 Q5 I: a. k4 I$ v+ h3 F4 E    a[7] = 7;
% @* ?5 ?; G9 ~' L    Print(&a[0]);4 k+ y2 v6 x8 V. [: c9 R
}* g' z9 Q; y' P. U7 S- U% \- ^2 X6 @

; X+ K8 ^7 X& M5 A. s3 z: P( fvoid Test2()- a; R' ~5 ?( l( z+ ^+ I& |
{- I, `8 L3 v" h; g( H& Y- o; |
    int a[2][5];& L9 b8 g3 s. o5 F. P
    a[0][3] = 3;6 K8 N. B/ Z& t8 e- R
    a[1][2] = 7;" m3 k% b2 S, w3 b4 Q
    Print(&a[0][0]);7 _6 I* q) v2 [$ {
}
* y1 X. h4 i' U( |9 m9 N2 L3 f$ @$ B$ k. s8 b  H
4 R2 _+ `2 f2 B* s9 b# _! y$ Y
0 Q* N, k7 X: Y
很自然地,多维数组也支持用1维数组的方式去初始化。' x! {8 V% J9 q( }" I
, c1 |. p( s: `; `+ K
int a[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };4 o  k* ]( r, O- l
既然多维数组与1维数组没什么区别, 为什么还需要多维数组?
' }8 e! h6 N  [8 e6 A3 a
# n2 w( Z- w# N  d4 `6 t假设有一幅RGB图像720*576个像素,每个像素有RGB三个通道,每个通道的值是8bit大小。给出图像的首地址p_rgb_image, 我们要取第40行,第50个像素的R,G,B值。代码如下:8 K; U0 y! Q4 y. ]; d

2 ~1 Y$ m- t  Q  k6 wunsigned char* p_rgb_image;, K* m0 w& k, F; d% U4 ?
unsigned char r = p_rgb_image[40*720*3 + 50 + 0];6 Q% N3 K$ s( Q7 b+ z9 @
unsigned char g = p_rgb_image[40*720*3 + 50 + 1];% D1 X/ l6 k# s$ V  }
unsigned char b = p_rgb_image[40*720*3 + 50 + 2];$ e( Y; c; O' Z8 ]" W
类似这样的场景, 采用多维数组的写法, 有点类似以索引为参数,可读性更高。相当于程序员和编译器打了一个配合。
* f! W2 v7 D; V( B8 m
8 m* [2 z$ J" v7 c) G2 F, Kenum) h: q% H7 g2 Y: L3 u* J
{
% v# h( m9 s3 @) y# i. Q3 i/ b: z8 g0 } Red = 0,
0 q/ v3 L7 s$ f' h) n Green = 1,  n, X5 V; h  ]2 K8 K" L4 m" I) y  J1 G
Blue = 26 ~9 H, |9 L' x
};- N- [, w& k( R  J7 a( m
unsigned char rgb_image[576][720][3];
# d- C4 `' a0 w% L! v3 q4 w. lint row = 40;
$ w% z+ S  y( h4 Bint col = 50;# U1 {3 `0 O) f( a. P
unsigned char r = rgb_image[row][col][Red];
# R! T$ ^0 r7 L4 K1 Ounsigned char g = rgb_image[row][col][Green];
" W1 T0 x4 }3 Y$ d8 Bunsigned char b = rgb_image[row][col][Blue];; F9 S. c3 }# {( D5 L3 N
数组指针以及与指针数组的区别
7 v; Z8 ^$ e8 Y  A' E数组指针,是一个指针, 指向的对象是数组。 数组指针的赋值,要求数组的长度匹配,否则会报错。当指向1维数组时, 需用用*取得数组对象的引用,再用下标来访问数组元素。
, ], a5 U6 R8 W8 a" G指针数组,是一个数组, 数组保存的元素的类型是指针。) }% T+ H" h2 |9 o# H4 Q. l
数组指针的定义
- Q  k0 {+ a( n' F8 i: R数组指针定义先定义一个数组。8 b3 `' ~. U1 E1 Y% }) ^7 G
int a[10];
3 }$ n' n; F" Z- c然后对数组里的名称用括号括起来后再在变量名称前面加个*号
; l" ^  s/ b2 }$ m7 J- iint (*a)[10];
: f+ T+ _) Q7 F/ _' t后面你会发现函数指针定义类似。7 Z* [( M7 s5 G: {
// 各类定义对比
% d$ _, ^( p$ j- W% x) uint a, *b, **c, d[10], e[10][20], *f[10], (*g)[10], *(*h)[10];2 b! j/ h( {; M' h) J7 ~8 I, D
. {) P& v2 x6 R+ w
int *f[10]; // 指针数组, f是包含10个元素的数组, 数组里每一个元素的类型都是int*
" n6 v4 O5 x3 R1 }int *(f2[10]); // 指针数组。另外一种定义方式。. J0 ~- L' ^1 x2 n& U! e
int(*f3[10]); // 指针数组。另外一种定义方式。
, e" h7 e( f  H. F% h( aint(f4)[10]; // int数组
9 E& k, p8 G- `. Y, z' jint(*g)[10]; // 数组指针, g是一个指针, 这个指针可以指向类型是int,元素个数是10的数组' F- N! W. I9 x+ k( m9 y: m
int* (*h)[10]; // 数组指针, h是一个指针, 这个指针可以指向类型是int*,元素个数是10的指针数组( M) I5 j: y) v
5 n8 [- q6 \( ^$ n
int d[10];  i2 N1 ^! m* _  m1 u
g = &d;
7 v' E8 U# \2 [6 V, o: l' ?& f, H# l; ]# x5 G% o
int* e[10];
, u' U) u" v0 D- y% W( Sh = &e;
$ a0 o; i: @$ k; x9 h0 H( a数组指针的使用$ v! a6 I# b# j# e9 [9 i
数组指针一般先通过*号取得指针指向的数组对象, 然后再用下标操作访问元素。: X3 D3 ~: m" b. K

5 H- v1 R2 k( l4 bint a[10];
$ j& ?1 a2 R2 i4 {7 Vint(*p_ary)[10] = &a; // p_ary是一个指针, 指向"int (*)[10]"类型的数组
3 e% X9 B; ~0 H6 H! l/ q* z' H7 P! ifor (int i = 0; i < 10; i++) {4 c. a6 m9 i8 c6 X
// p_ary是一个指向数组的指针, 需要先通过间接寻址运算符*(indirection operator)取得数组对象
5 `( P2 d7 ?$ O // 再通过下标操作访问元素。5 c" D$ ?6 X8 r9 F
(*p_ary) = i; % W$ U+ X# R3 i- G
}
# t6 V# _2 r1 }6 u/ K9 q/ W
0 U) j; q5 {$ K1 P1 `( t; fint b[10];
% K+ b- Q8 K3 {- Uint c[20];& X( ]# c/ i3 d4 S2 g% Y/ r& `
p_ary = &b; // 合法
! {" d) ~+ M( d! Xp_ary = &c; // 报错! 不能将 "int (*)[20]" 类型的值分配到 "int (*)[10]" 类型的实体/ `9 i3 o" ~: T' v4 }
数组指针指向多维数组的子数组; w/ s! C7 B0 @+ F9 @+ g6 W
int a[10];
) B0 y/ r& M) [3 Bint b[4][10];
# a& x, u" O5 i, ~) |0 M* Aint(*p_ary)[10] = &a;
& m7 l5 |6 o3 x! @for (int i = 0; i < 10; i++) {
/ y% G& A1 H/ r* b3 t9 \2 Y, c: \ (*p_ary) = 1; * c9 O  ~; k  h! o+ F# T) m4 }7 ]
}& i/ A- Q& V4 {- n: P! m
# r0 z5 W: `5 O$ j7 R2 }; ~7 l4 n
p_ary = &b[2]; // 多维数组,可以看作数组的数组,
+ R, Q/ l& g7 }6 W! l- E// b[2][0] ~ b[2][9]的值都被改成2了
# k+ ?( d2 l+ q2 W  nfor (int i = 0; i < 10; i++) {
2 }  {# A$ \- v (*p_ary) = 2;
. t& ~5 x, L: y$ q}* t8 S- }- e6 ^6 G
多维数组指针9 o: U, H& R# y8 q* ^6 K+ r' m4 Q
多维数组指针,是一种指针,指向的对象是个多维数组,支持多个下标操作。+ K  L) h# U3 d' r; e' }# r

4 ]1 T/ f0 h3 h$ l: O* U# G$ tint a[2][5][10];
0 z/ n4 z- _. Z. D4 oint(*p_ary1)[10] = &a[1][2]; // 1维数组指针+ \8 A2 q) H0 d1 J0 {, ^
int(*p_ary2)[5][10] = &a[1]; // 2维数组指针
) [; u/ c* r- V' f( ^/ u$ L5 L+ |for (int row = 0; row < 5; row++) {' d" Y0 X+ u* }# \
    for (int col = 0; col < 10; col++) {
; I* Y0 |  B4 R5 f7 L1 O        (*p_ary2)[row][col] = row * col;6 \& M5 p* q% P$ H& i9 c* G
    }
2 Z& I, N  j# i}2 B0 B  {9 F, `$ H
数组指针和指针数组对比实例2 r8 P5 l" Q+ g1 b, X
数组指针还是记住两步法即可
% r6 c; e9 N+ u* D# s
/ E/ |, k5 I! [" U9 J  t7 Q定义一个数组
" ?+ u0 ]5 k/ i3 S4 |8 ~括号包围1中定义的名称,再在名称前加个*号。+ y5 S2 g2 u/ `; S/ C
int a[10];! u( v. y7 j8 D6 b7 b; \' v
int(*ary_pointer1)[10] = &a; // 数组指针! g+ Q  I$ ?% I9 U
int* pointer_ary1[10]; // 指针数组。元素类型是int*
# `2 j% y+ Q* s7 y9 e! t6 {( v  r2 Fint *(ponter_ary2[10]); // 指针数组。另外一种定义方式。! z" T7 U1 o  s0 ^, j
int (*ponter_ary3[10]); // 指针数组。另外一种定义方式。- m$ t5 d( n" m( h+ T' W  L( Q+ s
int c, *d, (*ary_pointer2)[10], *pointer_ary3[10]; // 排列定义比较。
% j6 j0 X* Q) K4 g# ?3 t. ^8 c6 k3 N0 x) ~4 J
// 指针数组可以把每个元素指向数组对应位置的地址。: A$ R7 F# W) H3 @6 f5 u+ O* g" l
// 这样遍历指针数组, 可以达到遍历数组元素的效果,但是注意每个元素都是指针,
; M9 g+ [" i' s  X- V// 需要访问原数组的值的话, 需要对指针用*间接寻址运算符。" S6 N- t5 @* g* o4 x  I
int* pointer_ary[10];
7 a/ `0 t- o. y& n, Z$ ?( W; Pfor (int i = 0; i < 10; i++) {
' m. X6 L: |" B" U* Z    pointer_ary = &a;( z: r6 y) ]7 J8 j4 r
}
# D' H4 M* h7 T; I// 类似遍历原数组效果。
" ]& k( M0 p# Y1 N! S! R4 T* ?for (int i = 0; i < 10; i++) {- R" D( W, o6 X: g+ D
    *pointer_ary = i; // 修改原数组。( M' l% q: |0 S
}
$ P# S5 N- d3 ]! S2 e  a) V+ \' h3 g9 a3 y1 S  p9 V$ W& o
函数指针
% D  U, N( U+ U+ \8 u6 f取得函数地址
! w& V3 v) {; v* h3 |函数的名称作为参数被传递时,会隐式转换成函数指针, 和在函数名称前加取地址符&等价。建议带上更加统一和清晰。2 i' n! w+ X: ^! M

+ b4 J/ n5 t" X8 e. m9 {void f(int);' `8 V$ w3 l* E1 {
int main()
* m* P+ T; i) M! g, q8 t6 T( l{! f* j+ w: w8 O: |5 D9 [
    void (*p1)(int) = &f;
. h, N* Y/ [2 k    void (*p2)(int) = f; // same as &f
8 E6 ]( ~4 M3 R6 R' l    return 0;* n8 o; |; a  n) @) ]/ C, Y
}
% |6 G% w: ^8 T- `+ i, e, v6 O翻译成汇编代码, p1和p2的赋值是一样的。8 o; `& W8 A' u  A. T
5 J7 P/ f/ H$ E( s- p" F) u

9 t1 N% t! X: {$ O0 C
) X0 Q* o, q: Q函数指针的声明' B* x6 f1 d; W( |2 d+ C# N& y
单个函数指针变量定义步骤
% q- ?; z$ ~$ u4 n定义一个函数。void fun1(int a, int b); int fun2(double a);
% Y7 o' h4 m" N用括号把函数名称包围起来,然后在名称前面加*号。void (*fun1)(int a, int b); int (*fun2)(double a);$ B* ^4 A0 C# b- i- D6 H: b
如果要定义函数指针数组,在定义单个函数指针的基础上,在名称后面加上[数组长度]void (*fun1[2])(int a, int b); int (*fun2[10])(double a);8 e+ k$ L, l8 _% r
typedef定义函数指针! q6 ^' W# g1 V5 ^9 M8 g$ w
可读性高比单个定义要高,特别是声明多个同类型的函数指针,或者函数指针数组。
7 `# s$ F; h% o+ x4 ]
; G; b# C  C& Atypedef定义函数指针的语法* x( Z* I. r" O" Y6 C
typedef有两种做法, 一种就是定义一种函数对象,另外一种就是定义函数指针。用法稍稍不同,效果是一样。其中函数对象不支持赋值, 但是支持引用。
. g0 O/ y# M# Q
3 a* g' n& T1 b; ptypedef int FuncObject(int a, int b); // FuncObject类型是函数对象
$ x+ n/ c! n  b! n7 Ctypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针
) A$ U0 y% {1 ZFuncObject* f1 = &Add;
+ H! @+ s0 Q2 w; QFuncPointer f2;
( J1 e2 g3 ^8 r6 K/ Qf2 = f1; // f1, f2类型一样, 都是形式为int(int, int)的函数的指针。
4 D( h" L* ^5 g& ]. p4 i, PFuncObject f3 = Add; // 报错! 函数对象不支持拷贝% a, g1 x4 w: E- V; u" `. l
FuncObject f4 = &Add; // 报错!&Add是函数指针,与函数对象类型不匹配
2 ^. c  d) c/ [4 OFuncObject& f5 = Add; // 正确9 \% v2 _5 a# o, {' c- r
int ret = f5(2, 3); // 正确: u3 y$ K, D2 i( s) T& j$ y
FuncObject& f6 = &Add; // 报错!&Add是函数指针,与函数对象引用类型不匹配2 r0 y7 c+ u9 V, h1 u# [) a
如何记住typedef定义函数指针的步骤
" e9 {/ A( n; q; S! g+ Y$ [像定义一个函数指针那样, 指定一个名称。int (*CalFun)(int a, int b);* ], F! N" ?4 a( m6 `
在这个函数指针变量声明前面加上typedef。typedef int (*CalFun)(int a, int b);
. r  e  I6 H6 A4 k完整例子
5 d! w  }* z3 |9 y1 L' s4 o! j# wtypedef int(*CalFun)(int a, int b);4 w+ C% s3 N/ P2 n# j6 V9 C' o

' _6 [) o  Q2 s: r6 eint Add(int a, int b)% y' x; l1 z8 K3 n% d) ]2 O
{
- V1 p/ J- I6 c. v8 T! w    return (a + b);
; K5 \+ v# l3 O4 \" t) }}# e% E0 g# A; }9 }7 |$ [" C
: W4 q! S' Y, e% j! v1 |3 J1 T
int Sub(int a, int b)
3 `' ~5 j! k1 X$ b* d{
4 D' Z1 v6 k2 d, y' m    return (a - b);* U7 {: T2 ^8 d0 u& ]6 v! c) X
}
! }4 W: V7 Z2 F& Q' k& M
& h. Q3 g6 p/ A+ aint main(int argc, char** argv)
+ y1 J* x" h" }6 O: ^" L! J{7 i3 O5 N$ Y* x5 U) ^
    CalFun f1 = Add;- |4 @8 c' q9 H1 }- s, y) n
    CalFun f2 = Sub;& F5 K+ q8 l& @4 ~  S
    int a = f1(2, 3);* E9 s- {+ Z- K8 l8 V9 \+ U
    int b = f2(10, 5);4 ]( v$ E8 |: P0 e* `3 }

" K& k! N( {) q! X! T2 u' _    // typedef定义的函数指针数组。
& i* W& `- G, }! s+ }) Q8 @4 i* U- {    CalFun f_ary[2];
# U6 I- g. i3 G8 G) G' l* B    f_ary[0] = Add;5 C+ [0 N7 I( a1 ]% _" o$ |
    f_ary[1] = Sub;
' A) B/ @6 D2 x" B5 g. |! ~& z
2 ~) v, A9 P, G: j* T* j8 K    // 单个定义的函数指针数组。
% Q3 X# U5 h8 z) m- e5 D- W    int(*f_ary2[2])(int a, int b);
8 w' ]; n. z1 k" d1 I( ?) ]+ J    f_ary2[0] = Add;
% @' \+ `! Q& n    f_ary2[1] = Sub;
( n3 {/ |1 F' ]
$ l4 t. C+ M. s+ [7 N% w    return 0;
/ X( ?: d, ~6 R9 C4 ^}
9 M9 m- e( T( y* t! D7 v1 }+ C" E4 D7 c& x7 N) e
using别名定义函数指针) E" N. ^* ?% I: D
c++11以后的类型别名定义--using也可以用于定义函数指针, typedef的好处它都有,个人感觉比typedef更直观。using类型别名同样分函数对象和函数指针两种方式。
4 }3 ?9 l3 V. q8 `4 B3 X
* B4 ]* M" `. L; k1 W4 n# U; gtypedef int FuncObject(int a, int b); // FuncObject类型是函数对象
* U# X8 s6 x* `2 q% b" T  E( p( B7 iusing FuncObject = int(int a, int b);
5 s( }( X; K' J0 m6 y7 ptypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针* d' f- @( M: v9 Z+ k" ~
using FuncPointer = int(*)(int a, int b);
. Y5 n) F; L' X$ u- x) L函数指针的调用
, K3 y% p3 c0 F, J( q, T' n函数指针和函数对象都可以直接后加括号调用7 y9 q9 f8 I0 c' G
int f();
0 H8 I  M4 d% `) |8 }, Z- s, L$ u3 Zint (*p)() = f;  // pointer p is pointing to f
1 R, p8 }+ N, R+ c4 I2 Rint (&r)() = *p; // the lvalue that identifies f is bound to a reference2 _. `* g) u0 w( i) ~7 ~
r();             // function f invoked through lvalue reference
& A2 v" @! t7 `  [" m(*p)();          // function f invoked through the function lvalue5 H. ~3 J1 d6 A% B
p();             // function f invoked directly through the pointer
- b2 J) [2 q# r如果函数有重载, 函数指针会指向匹配的那个版本。/ [. g. r! @; A* ]1 u3 @
template<typename T>
0 ]1 p& Y! o* b1 NT f(T n) { return n; }
6 x  I! Q3 S& L$ x7 g% u  _) `/ `; k# [: T3 X9 f" i
double f(double n) { return n; }
' G* E' m! v" `! P9 y/ r* B
( U$ P7 i' f3 k! ?9 Vint main()
2 Q( ^0 S5 @9 B. i5 w{. e; I. q2 o1 x, f5 C
    int (*p)(int) = f; // instantiates and selects f<int>
/ @. b5 p8 ]/ s. B0 q( B; I}0 j' j5 K! I* j: |  X( B$ s
成员函数指针$ p" _/ X  r0 `  ]" Y& {$ d! ]% p
静态成员函数,除了增加了访问控制以外,跟普通的函数指针没什么区别,所以普通函数指针可以直接指向类的静态成员函数。但非静态的成员函数与普通函数指针不太一样,声明时需要指定函数归属的类名,并且调用需要指定对象实例。
+ L" ]. O" u  Z7 X! `: A+ n9 b" `( K# A. u7 T. F+ l* R
成员函数指针定义。
) a2 Y; j: }4 b9 M- [6 t. l7 w像定义类成员函数实现那样写, 并任意指定名称,这里作func。void ClassName::func(int);: L2 ^5 g" S  e' v; j
括号把类名、范围解析运算符::、名称包围起来。void (ClassName::func)(int);& ^1 f. G, G" E% \+ j) r0 b- W
在名称的前面加个*号void (ClassName::*func)(int);9 Y  a3 C) t' w2 B
成员函数也支持typedef和using的定义方式。typedef void(C::* MemberFunc)(int); using MemberFunc = void(C::*)(int);
8 L/ j$ U2 z/ r0 e. h8 R. R: E成员函数指针如何调用。
3 j& X# Y) X; }; e6 q假设成员函数指针名字为func
6 W7 f. f" [- f
" z" F& _; J/ y: n7 B" N& Bvoid (ClassName::*func)(int);' K4 W6 e; h4 K4 U
对象式调用。
) o/ J& b' {8 B4 v/ W+ ~6 A0 xClassName c; // 被调用的对象8 ^. J! ~5 |3 @1 Q+ X- W9 s
成员函数指针名字当作正常函数那样写。& @! @( F3 C: t9 v! r
c.func(3);
) |; p2 [. \  g  A成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。
& I; m) B: {) e, T, _c.*func(3);; O# t' Z7 d3 Y# K, W
最后用括号把调用对象、成员访问运算符.、间接寻址运算符*、和成员函数指针的名称包围起来。
# A/ ?$ N' g6 K: L4 m' s0 t* ?(c.*func)(3);
5 ~- a) s$ ^8 M/ q: E为何要加上括号? 根据c++的优先级标准,取成员运算符. > 函数调用() > 间接引用符*。 *号优先级比函数调用要低, 成员函数指针还没取得对象就被调用了,自然报错。 另外
: J% E" Z% w: F' ^(c.(*func))(3);9 o% a' Z6 H4 u6 {" Y
这样的写法也不行。 .*和->*是整体作为一个运算符的,中间不能用括号隔开。
7 j# Z6 N" @* l$ k# W3 a指针式调用
  `# ~7 _$ v3 a- l. eClassName* p; // 被调用的对象的指针4 u; M' e) V7 s- |) v" Y
成员函数指针名字当作正常函数那样写。+ K# k& T6 z( B9 j. y0 i: t
p->func(3);
% ^/ z# E9 n1 j5 V' E成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。$ z2 O1 l% F6 }7 W4 F8 {
p->*func(3);
: M  I* p2 {( L3 f! ?' r最后用括号把调用对象、成员访问运算符->、间接寻址运算符*、和成员函数指针的名称包围起来。
7 F( Q) z; A5 E(p->*func)(3);$ D0 A- f- e; H# b: x5 R, E
函数指针使用完整例子
# R5 f- p! \% o+ `: d" Estruct Cal ( P" }# t  G) w, Y( O
{
. m7 D# K  i3 W1 e" L    int add(int a, int b); ) C) b7 y7 j& U: ~. `
    int sub(int a, int b);, {2 u2 X: d+ w' [
};8 e9 e1 p5 T/ v& b  f
8 @; Y4 _* P& n- f! M
int main()+ y3 v# R% M, c7 p( f0 \/ f
{  R. L5 o! e/ V! B2 T# R5 |
    int (Cal::*fun)(int, int) = &Cal::add;
8 F% u! k& u3 Z; Y. l9 V- _  Z. _    fun = &Cal::sub;
9 x; G2 _. t/ a' V3 [4 o; ^8 Y3 H1 x4 V2 Q6 `
    Cal* p_cal = new Cal();4 q0 c4 p% ]2 W
    int r1 = (p_cal->*fun)(2, 3);* l" r& g8 T$ L
    delete p_cal;! B0 b8 `$ F# q# `# U( \) Z2 X! q

, a; e7 I! a( X3 m9 l" [    Cal local_cal;
# Q3 p( \$ M; e    int r2 = (local_cal.*fun)(8, 6);$ y$ A8 b$ |  [: @% G% h1 O
}
2 N, A/ ]/ V8 z) \8 h* }: w4 u
: S9 W1 h8 _+ H成员变量指针
7 I* {8 P/ _" k5 f' X; M7 ]成员变量指针比成员函数指针还要简单些,没有函数调用, 无需考虑函数调用和间接引用符*的优先级问题。3 M6 v7 V* e% ^

2 F8 @- A. Y2 O1 X* e! L) y' q成员变量指针的定义, P3 R/ l7 I( R# Z. w
假如以下结构体C。) r: F4 \* q5 q+ T; H

2 ~% K* m( o# `+ f, \+ dstruct C
: k6 w7 t3 ~1 T4 i{ 3 S7 F& I  ]# }% M$ d+ W
    int m;
, b- f# z+ Y( Y6 H0 {9 P, R+ P};
7 q! |9 ], r' r( }( E' a单个成员变量指针定义 4 F4 b2 D, E' X8 T1 R! }) L
假设名称为p, 类似静态成员变量定义那样声明
2 R9 s& f' _9 `  bint C::p;
2 V: V. D6 S, v) |9 i8 w+ [在名称前面加上指针标识号*
& |, a$ P3 {1 ~- v* m5 pint C::*p;
, e  s* Q6 X* Atypedef或using方式定义1 m! r0 G8 z- X; q+ \; B
typedef int C::*MemberPointer;9 K$ f" a/ M% H
using MemberPointer = int C::*;
  J2 j! `* C8 R# F6 z& j  c成员变量指针的使用。
  S5 }! O: t+ X$ W% F( [  e  {5 y类似成员函数指针那样,直接使用指向成员的指针运算符:.* 和->*即可。1 y6 c$ T* X/ ^9 Q, ]# t0 z; S+ [
成员变量指针, 能让我们实现一些遍历成员的动态功能。 例如把一个类/结构体的多个同类型的成员变量放进一个容器里,然后遍历访问这些成员变量。
; V$ P, \. N, z( ?# v' u0 F& J# R) k( M1 l& L6 s2 E6 F5 \
完整例子  E* D& J/ W$ t: l1 r. a: V, r
struct C { int m; };& W* \& ?# |+ {6 l1 X8 x
int main()
  \# q; r$ B( s7 }5 y{
& U( O3 V0 U, n# @. K7 l    int C::* p = &C::m;          // pointer to data member m of class C
. Y, C7 U$ e, l; j) t0 y. l    C c = {7};
4 r, s4 R3 H7 X5 h* L# T    std::cout << c.*p << '\n';   // prints 7/ W+ U+ w8 |- B2 Q9 P  P$ W7 D
    C* cp = &c;, \7 s! o$ P0 q; R8 i4 O9 C
    cp->m = 10;
6 M1 ^- L& f1 H, y+ e. Q* p    std::cout << cp->*p << '\n'; // prints 10- f3 a1 U, }" g+ F/ O% Y5 Y: m
% r3 c2 `- Z4 f& ~2 h0 T
————————————————
. l4 w  M4 K; Q! M" F( B. ?版权声明:本文为CSDN博主「南风fahaxiki」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
7 p/ h# O! W3 h! p% c原文链接:https://blog.csdn.net/m0_64407685/article/details/126788115
) Z2 \$ r. A# @2 u. ^& \7 P2 I  v$ x- B+ Z2 P

" f, ^, f2 z* N; f* l0 b




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