数学建模社区-数学中国

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

作者: 杨利霞    时间: 2022-9-12 18:49
标题: C++数组指针、函数指针、成员函数指针
C++数组指针、函数指针、成员函数指针& H0 t2 R. w+ B- q" c
C++数组指针、函数指针、成员函数指针+ h! A# j0 N: E7 e5 x% y

% c+ L3 h4 a& S5 M2 t/ n" C4 }/ |* X9 e3 t. b' R3 S' L
操作符名称
+ E" C/ _# n# y" E0 M) p# C& 取地址符(Address-Of operator)5 X1 ^. u8 G9 ^' h0 W+ K
* 间接寻址运算符(Indirection operator)
  G- w, B" \- Z1 x+ c.和-> 成员访问运算符(Member-Access operators),用于取对象的成员。9 j# K' ^& E- E  p6 V
.*和->* 指向成员的指针运算符(Pointer-To-Member operators), 用于成员函数指针和成员变量指针的取对象。2 i/ z5 }0 H0 _" r% Y
() 函数调用运算符(Function-Call operator)+ o# Y8 r" _) n1 R0 t4 L9 f" ~: \
:: 范围解析运算符(Scope-Resolution operator)
- Y/ i, A) L4 L3 {: g, w如何定义一个指针变量
) h. p+ W& j  a  G假设类型T, 变量名称name, 指针的定义如下:) h3 {7 v6 N2 r4 [0 \
2 @" _, J' w+ V, Q# V) N2 G
T* name;
, z5 K' `3 y# b5 n4 t( G1 L  b标识变量名字name, 它是T类型的指针。例如/ G0 }3 z$ Q- i4 K0 F) I! m

/ r7 V# t+ c; T) ~/ b+ E1 q$ Fint n = 0;
* T/ b$ B; B7 v3 b4 c# g- n  ]+ fint* p_n = &n;
% o3 }( M$ k' e) cp_n是int指针类型, 指向某个int型的对象。% `7 ^! w6 n* l5 K& D
0 N% k% U) ]  r+ I( {5 b; Z
指针变量的修饰
7 M4 l& h; q0 G7 x. e指针实际上也是一种变量类型, 只是它保存的内容有些特别, 是指定类型的地址值,通过间接寻址运算符(indirection operator)*, 可以访问到指针指向地址上的指定类型。8 n# ]( a; ]' K# D, U  L0 X

! \1 h, @& a$ Q! r& j指针也可以用const, volatile修饰。 const int或int const均表示一个变量类型是int, 且该变量不能修改。以下两种写法都可以:
8 z9 X/ y$ H" D7 M2 [$ d  M7 P8 W0 s- V
const int a = 1;
0 W9 l/ J$ a  Dint const b = 2;3 z! V& ?9 H# U' C- S
既然指针是也一种变量类型,同样支持被const修饰, 表示指针的值/指针的指向不允许修改, 指针所指向的那个变量是否允许修改, 那是另外修饰。写法如下:3 y7 _) ?- E2 r& m% S9 U6 |

; w! j9 l% t( }& v0 j2 G3 E# G' Hint a = 1;' g& E. R# }  H% s& A  ]4 F
int b = 2;
( `/ G+ P2 l4 ^. {/ c1 g. ^int* const cp_a = &a; // 指针的修饰词,放在*号后面。! ^. Q( G( V' h
*cp_a = 10; // 指针指向的值可以修改0 l0 ?5 Q( `4 s) @: ]) q( H1 n
cp_a = &b; // 指针不能被修改,报错!- s2 `; Q: K8 K
总结带修饰的指针的格式:; h9 e8 f, M/ W. y4 h
只要记住修饰词总是放在被修饰的内容后面。
2 u8 @5 ?- H+ p; |9 W/ X8 x6 w. J4 c4 g* i/ _8 h
cv表示const / volatile修饰词。指针定义形式如下:/ a& Q8 ]& P& I

7 h; U4 J. g- y! `9 y3 Z- h' {7 g9 o( BT [cv for T] * [cv for pointer] name
, s9 h- G2 V8 G* Q/ e0 N8 ?+ q注意对T的修饰放在T的前面也是合法的写法。
. t7 m' J0 G/ |5 G; p4 e0 y3 m7 G$ S) g" P
const int const c = 2;2 |+ q3 V6 B+ o; F! b# M0 T  M/ G
在mscv编译器下也不会报错。; H8 B2 l9 z0 O: M* ~$ D" `

! ~$ ^7 E' t0 i" b2 t完整的格式:* H: z5 }, o$ x; V: _2 E; e
[cv for T] T [cv for T] * [cv for pointer] name
; \1 x6 c5 z: n/ a3 E! O
) w' y) W$ H2 k  M8 B8 l& x" e4 SSyntax        meaning
7 U3 o. M6 g' W, f# Uconst T*        % p3 g5 v- V0 g- M! d" s
pointer to constant object
. m: o* J5 D% B4 F! R* M. @0 J4 m6 N" k( Y
T const*        pointer to constant object# t, L0 z/ K0 e( S- `; E2 _3 l" b" l
T* const        constant pointer to object
7 r) E6 ?. e: v1 E% sconst T* const        constant pointer to constant object
5 Z3 Z) n% f7 i8 QT const* const        constant pointer to constant object* ]7 j/ ^& v* a+ a( e6 U
上面格式中T还可以是一种指针, 指针的指针仍然是按照修饰词总是修饰前面的标识(T或者*)来确定修饰的意图。' K- ~8 }: ^: D# e; t9 @* v
/ I1 Y6 G4 ?0 ~: i& g  Z) Y3 i
int a = 1;$ D, \7 @  }+ ]
int b = 2;2 t1 @4 ?3 G- a7 Q
# [# O8 X( g0 p
int* p_a = &a;! \, z8 v& M4 ]) x. b0 m4 ]8 J6 T% d
*p_a = 10; // 合法
5 O  e+ }1 b' K" j* B0 J+ Y7 Hp_a = &b; //合法
- ?8 ?. c2 a* I: o% p/ ]6 D- L- I/ z5 k3 A* K( z
const int* cp_a = &a; // const修饰int类型, 并非修饰指针
7 A2 o( b, W: H7 k2 {* W*cp_a = 11; //报错! const int类型不能修改
( |% w+ Y9 h. z# D0 G+ jcp_a = &b; // 合法, 指针没有const修饰,指针可以修改。4 I5 O( b" o, W# a

2 V; N3 `) o. qint* const pc_a = &a; // const修饰指针。类型没有const修饰
+ x3 K5 R- _. a2 T, x8 o*pc_a = 12; // 合法, 因为类型没有const修饰,可以修改。
5 X  ~9 f: r3 t5 r" \pc_a = &b; //报错! 指针被const修饰, 不能修改指针。
0 a7 c1 b9 R$ y+ c& d9 u  C( R* H2 c5 l" t; d9 o& K
int const* const cpc_a = &a; // int类型被它后面的const修饰, 指针符号*后面也有const修饰
/ G' \/ G- I/ x/ m*cpc_a = 13; // 报错! 类型被const修饰,不能修改。
7 Y3 `  j* W5 ^0 Icpc_a = &b; // 报错! 指针被const修饰,不能修改。
, e" L$ V. a2 M# D4 s+ n
8 l1 m; X: K5 W2 X: P! p  w1 t 更复杂的指针的指针
$ f' }  N3 O4 ?6 i/ n! \0 }% i7 ?6 ]0 ~3 I' C6 w# k  {
int a = 1;
! _/ E( C, @4 ]" s' D! w8 |: iint b = 2;/ G' S6 o: ?; d* C/ s, N
int* p1 = &a;
) g0 J8 X  ^# o. a2 t; Wint* p2 = &b;0 ]0 U5 _, O. B. p! R$ Z
const int* ct_p1 = &a; // ct for const type
5 v# e' a. l. y3 e9 ?) E6 `' Rconst int* ct_p2 = &b; // ct for const type* ~/ l/ }$ z- k
5 F# o$ k1 x8 z( R8 U8 b
// int * * pp1; 指向(int*)类型的指针
4 m" i% l0 h- F; t2 B  ^; |8 Mint** pp1 = &p1;  
4 q. l2 ?7 M3 n5 Z: Jpp1 = &p2; // 合法, - ~/ g4 T) O) B& U. X- Q
pp1 = &ct_p1; // 报错! 类型不匹配。 (int*)不能指向(const int*)
; H1 {' S) G0 q. V% _- L
! v8 b8 U8 d, a1 m3 ]7 Y- d: u/ k// (const int) * * pp1; 指向((const int) *)类型的指针
, G3 H& a1 q+ C- `# Wconst int** ct_pp1 = &ct_p1;  . S4 z2 c  l1 E$ a
ct_pp1 = &ct_p2; // 合法% Q7 h( }+ O7 }2 I( p
ct_pp1 = &p1; // 合法!(const int*) 可以指向(int*)类型。7 Y; G1 A6 s$ n5 L
' ^: Q/ Q$ P% v3 X7 q4 B
// (const int) (*const)
2 A. C: W: `/ S, }# Wconst int * const ct_cp1 = &a; // 指针也不能修改4 ?3 |$ T' |/ U! s4 u0 l6 Z, y! [5 z0 ~1 u4 t
const int * const ct_cp2 = &b; // 指针也不能修改, H, C( S3 j# t9 t5 R; Y) o! D
ct_cp1 = &b; // 报错!指针有const修饰
1 y+ ?3 ^- i) r8 I0 r
4 I/ c/ m# T3 q! {// (const int) (* const) *  指向((const int) (*const))的指针
) z0 B/ P8 P$ X' R% Dconst int* const * ct_cp_p1 = &ct_p1;  3 S3 ^9 H: T" {- c; {7 o/ c& J
ct_cp_p1 = &ct_cp2; // 合法, 指针的指针并没有const修饰, 指向的指针有const修饰
7 @$ ^& ~% q- r*ct_cp_p1 = &a; // 报错!等价于操作ct_cp2,  指向的指针是带const修饰的不能修改% f, \& U% q. d( n1 E
8 {' \/ g* L2 W1 c7 W" d, m0 N
// (const int) (* const) (*const)  % o; C& R) c2 s7 M2 k8 ^
// 指向((const int) (*const))的指针,且该指针被const修饰
- p# _0 \6 e3 R# G: Qconst int* const * const ct_cp_cp1 = &ct_cp1; 7 {8 L+ c% J5 l; l
ct_cp_cp1 = &ct_cp2; // 报错! 指针的指针被const修饰, 不能修改指针指向。
" R+ }( j  b( B
& [2 E* P& v0 U/ S# D3 T0 k7 M4 ]一行声明多个变量& H% R' ^0 o8 c
类型 + 名称定义一个变量。+ ^. z# a9 {7 p0 C8 E0 N
变量的前面可以加*号修饰, 表示指针, 一个星号代表一层间接。**表示指针的指针。
7 w! D5 g$ \! Z+ I. P' E& a
& [# {* {# ]& H! u' L. V# |9 [int a, *b, *c, d, **e;: H' R5 P' m0 s
a = 0;
& z3 S- P/ V6 ?) gd = 1;
/ n! _5 L) }9 o8 d: ib = &a;
& X" d: Z0 Y- \& ]c = &d;
5 w* }! Z" ~. {9 h- t! E7 ?e = &b; // e为int**类型 指针的指针' Q( N6 Z6 Y: G+ V
e = &c; // e为int**类型 指针的指针
( c$ e3 c( A, R  E也可以用括号包围变量和*号。# p. C9 K9 x. n- v' H' `6 x* _; O
( S  L$ _3 j3 R6 u" b
int (a), (*b), (*c), (d), (**e); // 合法定义。
2 u6 E; ]& y2 L8 p: j; \括号可以省略,某些情况, 个人感觉加上括号更清晰一些。例如4 i: C) I6 S9 G# @1 x0 [
4 N/ g- i8 l( K9 |6 c# Q5 d) u
int (a), (const *b), (*const c) = &a, (const d), (const* const* const e) = &c;+ L  x* r2 m$ F
写成
1 k0 b2 T, d$ Z5 p4 m
4 N% r/ u" L1 E4 w( Hint a, const *b, *const c = &a, const d, const* const* const e = &c;
1 v% @3 e! S# r+ T( n更重要的是, 后面我们表达数组指针,以及函数指针时,括号是不可缺少的, 带括号的表达更加统一。
8 z4 l! u, e/ F2 E0 o* @2 l2 d. T: \, ?  C" Y
数组指针) {% _, i0 d" |2 B
数组基本表达! _; Q, i0 i3 H+ z6 j/ x  A% D
int a[10];  // 定义了类型是int, 元素个数是10的一个数组。/ `0 A7 ?8 H, r! m
由于c++要支持一行定义一个类型的多个变量。 所以数组的[]时放在名称后面的。虽然我觉得: ~- m* a# T4 _

* s& b- ]) G6 U  H& q( Qint[10] a;# W! G) g! H8 U& G; ?
这样的写法更符合类型 名称的思维, 但是如果类型都这么写的话, 没法兼容以下的写法:, O$ Y- t0 b" m- F  u
/ S, ]8 g2 K: T1 T  {
int a = 0, *b = nullptr, c[20], **d = nullptr;1 K6 H; j; l7 G! C  [5 N
c++标准规定如此,但我们可以通过每一行只定义一个变量的写法, 类型会更加清晰。
1 J6 B8 c! h) x; M. y' A# i& i1 I) k3 }' {
int a = 0;' |0 n- \% _! t+ P
int* b = nullptr; // 指针int*, ~7 Q" @8 U/ S6 K- H  H
int c[20];" F9 ?1 R2 t2 `7 R" [
int** d = nullptr; // 指针的指针int**7 I5 K/ O- l. ]! A1 s
数组的名称是什么类型
* c6 Q% A* n* b+ i数组元素类型的指针,可以直接指向数组。 并且数组跟指针一样,可以通过下标去访问元素。2 {" D2 x' Z) l& g5 l8 _) U

8 D5 ?/ `5 a! e7 c  bint a[10];
% X3 h: d: o# o) L" _int* p = a; // 指向a数组的第一个元素
8 i: O, \8 z& Y' p  W! Q1 O4 \a[1] = 1;
: g( o: [1 Z7 M) r0 Np[1] = 1; // 效果与a[1] = 1一样。
: I8 d# J+ @7 r# V; o数组可以当作T* const来使用, 但是又与T* const有些不同。sizeof()的结果不一样。7 i6 P" @+ |" r& r! |0 r# i
8 M7 h& Q* K. n1 K- ]% j
int a[10];$ J5 Q: _4 C. N) d
int b[10];; Q3 t3 W- m/ A) p; ^! C  p9 I
int* const p_a = a;
; Y2 Y% x. G' P) s: ta[0] = 1; // 合法。 数组的元素可以修改。( c, G( E* D; R; v: M
p_a[0] = 1; // 效果与a[0] = 1一样。; d0 L, d( r$ L% Q, R- m, I

1 h/ Z% l! Q) Ha = b; // 报错! 数组本身的指向不能修改。
3 B  Z5 P4 Z8 i2 x' F7 {1 {9 m; G5 Y* Y1 i! N3 T
// 所以数组a可以当作int* const来使用
& M7 G2 _% C+ d$ `& D! U: _int *const& ref1 = a; //正确。
% R; b7 Y4 F4 U9 ~int *& ref2 = a; // 报错!
. _8 i& e% I1 g, H! `2 X4 o2 W1 k/ x0 M8 i
// 但是又跟int* const有些区别。4 |9 S5 n4 i% |1 M
assert(sizeof(p_a) == 4); // 32bit程序。
2 B: t. R0 W" d% ], ^assert(sizeof(a) == 4*10); // 32bit程序8 q. v6 Y3 l& i- j* K; j: |7 f
; E" R' [" n: [4 n
数组跟元素指针的作用很相似,都可以通过下标去访问元素, 但调用sizeof()函数的结果不一样。元素指针的sizeof()返回值是4(32-bit应用)或者8(64-bit应用), 数组的sizeof()返回值是数组实际占用的空间。数组可以当作指向第一个元素地址的T* const来用其实就是我们常说的数组到指针的隐式转换。当数组作为函数参数传递后,会自动退化成T* const, 在被调用的函数内部调用sizeof()的返回值跟T* const指针大小一样。 数组传递作为函数参数后, 在被调用函数的内部与T* const是没有任何区别,只有在数组定义的可见范围内sizeof()才有获取数组占用空间大小的效果。
& E6 F0 K2 z& N, c; [6 Z, l/ j0 x. C3 X. ^' F# N, n; l, x8 C. Q
以下3个函数翻译成汇编以后,汇编代码是一样的。5 P) D6 a0 Z+ _+ o; A
7 y5 [3 @4 J; G2 q
void Func1(int* p_ary)
* b/ t7 U! L) Z5 X2 I4 @& H{
/ k5 G4 `0 ^& S3 g0 [    assert(sizeof(p_ary) == 4); // 32-bit8 u1 A) ^9 Z" N: Q1 I: C8 e  v# E/ m
    p_ary[1] = 1;
2 g7 y' q; d% B1 `$ x9 `3 _8 o; l}9 c! c, v( X% D3 ^- C$ r+ y1 K. E
2 @3 b5 K6 D$ B% V7 t" D; |
void Func2(int ary[])
4 ]6 |# r) y0 H% x5 R9 z{! I+ p4 u8 |' M" T
    assert(sizeof(ary) == 4); // 32-bit
$ @6 K; _3 o4 Y4 d  r/ X    ary[1] = 1;
5 {3 w8 F# H8 U* Y( Q}0 ~; o# x+ p5 m, S0 p  X6 S5 a
+ f, b$ W! P- ?
void Func3(int ary[10])
6 s4 j- e! t1 f# n0 @{0 D' P& y" y# l, A/ t6 F7 _& `
    assert(sizeof(ary) == 4); // 32-bit! {1 d: C. K8 V# c- m
    ary[1] = 1;! H# A9 b! _, ^9 ?. ^4 @4 [5 M$ V
}
' b, h: I9 I! h3 I& ?' I; Y) L$ z+ b5 Z# m: U
int main(int argc, char** argv)1 F6 i' r9 C7 Y7 W- f4 J9 N$ L$ ~3 H4 `% q
{5 }! D# C, ]- ?
    int a[10];! Y4 ]/ A0 X6 Z  m2 H/ T
    int b[20];
" J. U& O3 p' u    Func1(a);
3 U0 o  v& b) X4 S% B    Func2(a);
* C  p! W  @2 N% r4 b2 ]    Func3(a);
0 y4 ~+ O1 b  b    Func3(b); // 退化成int* const了, 即使数组长度不匹配也不会报错。7 Z; h) l1 \. ~* T3 ~6 E
    return 0;
; d1 n; k9 N  O- N  W$ p+ f# Q}2 ^. T! K) R2 y9 _

0 E% ]2 S5 L. V  O& s
) i% t8 _$ _7 r) g. l* y& N( c* |/ l; J8 M+ b$ i
多维数组% m$ b1 a/ C; D/ I
一个3行,4列的数组, 结构如下:
3 h" n( n( B( \( |9 u7 }0 Q0 B7 @( F( a
int a[3][4];0 N9 N" f$ o$ N. W
column 0        column 1        column 2        column 38 f* y: y2 F% l5 n. b. T
row 0        a[0][0]        a[0][1]        a[0][2]        a[0][3]
1 c0 d! r. }7 [% Arow 1        a[1][0]        a[1][1]        a[1][2]        a[1][3]
6 E% H+ ~  X  n- z' Urow 2        a[2][0]        a[2][1]        a[2][2]        a[2][3]
" [& f8 i) o: A& {  H- x; d3 ~数组初始化2 W) G  ^5 ^( M& J$ q# _. g

! j  o# S: l4 l) l7 Mint a[3][4] = {
1 e2 R$ b" _& b6 N8 }    {0, 1, 2, 3},6 i8 r* Z8 T1 N6 _" l8 i5 b
    {4, 5, 6, 7},
! k# S4 z( i' w* k; C% u. i    {8, 9, 10, 11}' |! E$ C3 \$ W6 o
};0 d: P, {3 i$ ?6 y# i, A
实际上多维数组和1维数组在开启速度优化后,翻译成汇编代码是一样的。
/ v3 Y# q# ]7 @* W0 j6 u
6 h% J/ j4 `4 j. xvoid Print(int* p_ary);
1 p' \# n/ i* V! G8 H
  v, j& u8 S/ zvoid Test1()
4 |6 w& a4 P8 h! J{; q" p, q2 B( m1 U$ p0 f
    int a[10];
& }2 b* i+ F; @" l9 E% M) t8 n    a[3] = 3;; ^+ l- Y& o; E
    a[7] = 7;
5 G, \: N, X3 F    Print(&a[0]);$ {. u0 q7 Z1 ]6 I  g: V8 o
}
" A( E* a, d) D9 A
- v% z5 m2 `# v+ b$ f) jvoid Test2()' ~% E1 B" C  ~  P  Q
{
7 b% |9 m, m$ m" q; z2 }: Z    int a[2][5];4 k: l: C0 S, i% p" r
    a[0][3] = 3;
( I# K( k6 G* V. z2 b' w/ }    a[1][2] = 7;
9 [: Y! t' @$ N    Print(&a[0][0]);
9 B  H" W4 a! O}
+ U2 l8 _$ F4 v4 V: k# r* ~8 d* }, L6 N/ E1 b( k1 h( C

4 S( t% J3 A& b- i% k, k" a* L* M
很自然地,多维数组也支持用1维数组的方式去初始化。  @4 E5 S8 ~7 a; `# f6 T3 D

$ q0 w9 i0 x) eint a[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };" U) o4 _- f/ w4 s
既然多维数组与1维数组没什么区别, 为什么还需要多维数组?) x  v1 x9 Q$ E! C# F3 O

4 y& l" E& l) ?& M2 ~- f; S, E0 g假设有一幅RGB图像720*576个像素,每个像素有RGB三个通道,每个通道的值是8bit大小。给出图像的首地址p_rgb_image, 我们要取第40行,第50个像素的R,G,B值。代码如下:: X; C$ A) J- i' _8 o$ g, y

& e* M! \! X1 y2 f. a( _$ cunsigned char* p_rgb_image;! y  V# S9 o3 |6 ?  j
unsigned char r = p_rgb_image[40*720*3 + 50 + 0];
6 b2 l3 ?1 A6 T: ~0 p7 P! n) W3 u4 Lunsigned char g = p_rgb_image[40*720*3 + 50 + 1];
7 F9 F* t- o( u5 gunsigned char b = p_rgb_image[40*720*3 + 50 + 2];
% ^/ L5 ~' A2 Z  u7 I1 m类似这样的场景, 采用多维数组的写法, 有点类似以索引为参数,可读性更高。相当于程序员和编译器打了一个配合。
) u) t3 I2 D: |6 t; J
' S7 q& q/ U* D3 d$ s6 |' J6 Jenum
. f: W. b9 c: o. F{4 N; B0 a8 o& j7 b) ~  O
Red = 0,6 g: c3 g1 [. O; B
Green = 1,( O$ ?* P" s4 \  A: b& c' k! [
Blue = 2) ^3 P9 d* {9 K3 `9 g" O3 h
};
7 O, x2 ~7 W6 G) dunsigned char rgb_image[576][720][3];
" @/ Z" Y! Q2 k" i) j7 k/ u) mint row = 40;
: ~- F+ \; Z- n: M3 wint col = 50;! h8 v8 H+ W( g! F& ~
unsigned char r = rgb_image[row][col][Red];
7 s- V! m" q& c1 uunsigned char g = rgb_image[row][col][Green];
. r2 A8 N) |9 J8 [7 Munsigned char b = rgb_image[row][col][Blue];( _6 d9 T# c! i2 [3 d( A
数组指针以及与指针数组的区别
5 S% J- f8 [2 K9 F4 L9 @数组指针,是一个指针, 指向的对象是数组。 数组指针的赋值,要求数组的长度匹配,否则会报错。当指向1维数组时, 需用用*取得数组对象的引用,再用下标来访问数组元素。
) q; x8 [5 ~, w: \2 ?指针数组,是一个数组, 数组保存的元素的类型是指针。
: y2 S# t+ p. Z7 g( _数组指针的定义
+ Y6 s, [/ n- g4 w数组指针定义先定义一个数组。) @2 W7 I+ F6 ~" ]- a: P
int a[10];8 }( u' S9 ~/ A# B# U
然后对数组里的名称用括号括起来后再在变量名称前面加个*号0 @: V2 R4 t+ Q* m
int (*a)[10];% C2 L" W; v8 b' A
后面你会发现函数指针定义类似。
2 P2 u9 v9 H! G; O3 G2 h) |// 各类定义对比
, t) l7 d6 R9 o' d: z5 Uint a, *b, **c, d[10], e[10][20], *f[10], (*g)[10], *(*h)[10];
% j$ N& J/ O+ S& a$ h% X  Q
% L5 H( w% h) B8 n) V+ G8 qint *f[10]; // 指针数组, f是包含10个元素的数组, 数组里每一个元素的类型都是int*3 t' k* `/ g5 I
int *(f2[10]); // 指针数组。另外一种定义方式。) a7 k( ?, q1 ^2 G
int(*f3[10]); // 指针数组。另外一种定义方式。6 |# h% c$ B* l0 U
int(f4)[10]; // int数组) O2 C, B- N' \+ M  r) b) @9 j; m
int(*g)[10]; // 数组指针, g是一个指针, 这个指针可以指向类型是int,元素个数是10的数组1 [' S5 J. P9 p4 F3 h
int* (*h)[10]; // 数组指针, h是一个指针, 这个指针可以指向类型是int*,元素个数是10的指针数组" ~, m- E1 ?3 A: q8 Y5 `7 s
$ f5 K* D, q, D$ N% K
int d[10];
% v7 C8 M) B2 m$ C4 R% ^7 u! \g = &d;
6 K1 {; o* k1 g0 \
( D% B/ G7 ~% I+ h, f- ~; X  Fint* e[10];  h- _7 `; s# e' P- h4 j
h = &e;
- h% [8 X5 [% ]: g6 h$ ^数组指针的使用) E2 s& `8 F* D8 u, o/ d  C  o
数组指针一般先通过*号取得指针指向的数组对象, 然后再用下标操作访问元素。
$ h* Q  `" M8 J$ |+ q) x4 `0 p0 @% K; V( K' E# @( P4 z/ n; T
int a[10];
: V2 R; ~0 @4 H, ?2 S$ P& i6 V7 G* Pint(*p_ary)[10] = &a; // p_ary是一个指针, 指向"int (*)[10]"类型的数组: `, H# w1 H* s! q8 H
for (int i = 0; i < 10; i++) {( a  |1 a- S9 S9 r. Z
// p_ary是一个指向数组的指针, 需要先通过间接寻址运算符*(indirection operator)取得数组对象8 e6 d0 v5 \" b8 J$ \( A
// 再通过下标操作访问元素。
$ Y5 |$ @. i4 @" j$ {9 Y7 Z$ N; b3 q (*p_ary) = i; + V) r" K1 _0 S: h- k
}
" _( m  m, X9 s8 }9 G& d7 q3 B; b! E: ^4 G4 p6 ]* q# h1 W
int b[10];
# Y; O- q0 h1 ]. h: t* hint c[20];! _: X/ a4 y3 j% H/ l, N
p_ary = &b; // 合法1 ~: G, d. A9 [2 }2 h  ^/ U
p_ary = &c; // 报错! 不能将 "int (*)[20]" 类型的值分配到 "int (*)[10]" 类型的实体, }& x8 i% U9 _0 m  ^" ?
数组指针指向多维数组的子数组
" W4 i: X8 y6 d( e- [! S' Cint a[10];* s7 H. z6 W+ E, T8 l3 Y
int b[4][10];
2 `# n. d, U" s1 y2 Nint(*p_ary)[10] = &a;
4 m) D# X, `1 k$ N: i1 p: ~% ifor (int i = 0; i < 10; i++) {
8 ?7 C3 [4 ]. Y (*p_ary) = 1; 3 E; j; ^, C% H- w- U; G8 ~
}
+ I8 x, O0 m8 @' Q* n- v; B* A8 y$ @2 D! I) o9 y9 w$ D
p_ary = &b[2]; // 多维数组,可以看作数组的数组,0 H+ s% x4 V; m8 d) p. O. \
// b[2][0] ~ b[2][9]的值都被改成2了
' K8 Q. B8 A$ X. T4 Q4 x+ [8 c8 nfor (int i = 0; i < 10; i++) {0 c% ^5 h1 k9 s/ K% L8 o
(*p_ary) = 2;# @$ Z1 l" Z4 P4 l3 P% W
}. @; D& [) I( C+ d; h) s
多维数组指针: n$ }" v0 T- X; f+ b% G4 m; J
多维数组指针,是一种指针,指向的对象是个多维数组,支持多个下标操作。
! ~2 o. p6 u$ F6 j- _8 o1 |1 Q8 s; k" P9 W- G
int a[2][5][10];5 K# u( w. L, K! w
int(*p_ary1)[10] = &a[1][2]; // 1维数组指针1 }; e' x. U" P
int(*p_ary2)[5][10] = &a[1]; // 2维数组指针
! _4 H, m& J! T% Ofor (int row = 0; row < 5; row++) {! V. X$ L# ^  H+ V% s, y4 C
    for (int col = 0; col < 10; col++) {6 O7 q' q3 \) R2 J9 a
        (*p_ary2)[row][col] = row * col;
% o7 |, U" r! j; _7 \: B/ A    }
6 W1 v& c- G3 d! j8 s+ n$ L}
- {% N3 _, B, b$ |- n* Z8 Y/ b数组指针和指针数组对比实例2 j; t! ], b8 \+ e
数组指针还是记住两步法即可) @; W0 }7 t7 N% v# [; a
4 C4 A) J! H6 L8 b
定义一个数组! d. o- Z+ `: M
括号包围1中定义的名称,再在名称前加个*号。
5 e- v( g8 z) D0 N9 ^. w2 dint a[10];
/ [3 O3 n# ?/ O$ A2 V/ L+ Q' z* Fint(*ary_pointer1)[10] = &a; // 数组指针. C3 {( j& x) {2 `9 E3 \; U$ L
int* pointer_ary1[10]; // 指针数组。元素类型是int*9 b; ?8 b0 i( Z" @# `& c
int *(ponter_ary2[10]); // 指针数组。另外一种定义方式。( I' r' S" ~5 X7 h
int (*ponter_ary3[10]); // 指针数组。另外一种定义方式。8 N$ T8 E* z$ Y$ g0 ^; }
int c, *d, (*ary_pointer2)[10], *pointer_ary3[10]; // 排列定义比较。
6 |( g; O" F2 L' c  g2 F+ Z  H  \: g% u$ G! t
// 指针数组可以把每个元素指向数组对应位置的地址。
" k1 `! M' N) F// 这样遍历指针数组, 可以达到遍历数组元素的效果,但是注意每个元素都是指针,
# n" H6 V9 p: q; ]0 T) M// 需要访问原数组的值的话, 需要对指针用*间接寻址运算符。
- J" P% f# J+ Q- r2 q1 Hint* pointer_ary[10];
4 @+ W* L' l' |5 a! q" e8 Lfor (int i = 0; i < 10; i++) {2 ~8 b# p+ |8 u8 t& Z' e
    pointer_ary = &a;
/ G) a* X1 t9 Z& {}, I$ _- W1 ]( S3 I7 @# u, \  f9 Y
// 类似遍历原数组效果。5 v9 _5 U/ ?; Q2 _6 k; {( L
for (int i = 0; i < 10; i++) {; ?& T; z) t4 _! q- p7 F/ T  ^4 |
    *pointer_ary = i; // 修改原数组。
7 r) j. F/ ^, @}# j# }5 y6 a0 b2 }0 e3 z! i
6 U. e, L, N, O" P8 C
函数指针8 A) B- P$ [4 p# I  }" d# g2 D$ U
取得函数地址
0 m. \% W6 A9 m) b& z! X% _. O8 F1 v函数的名称作为参数被传递时,会隐式转换成函数指针, 和在函数名称前加取地址符&等价。建议带上更加统一和清晰。
5 S0 d  ^" y" i7 u, E# |; I7 z, a9 Q$ _+ A
void f(int);8 q( z# Y0 t/ H2 a- S. j* I& f
int main()
2 z0 u  F$ Y& h{) F8 L! }7 n5 o% Q
    void (*p1)(int) = &f;- B8 @6 U; O# e  i! u0 o
    void (*p2)(int) = f; // same as &f; ~# p* v& \- L
    return 0;# t- M( T1 t0 X* {5 U" Q
}
1 }  D, H1 E5 h翻译成汇编代码, p1和p2的赋值是一样的。
- T4 w0 F  J) S4 V$ E. S8 g, @; T) y9 L* I; Q. ?

2 r6 y1 S% c5 E1 ?6 C* i/ V
' K, y1 G+ L" X函数指针的声明
7 s+ P7 ~6 E2 o4 e单个函数指针变量定义步骤
4 M& b1 O5 a- F/ N" r) v1 n( R定义一个函数。void fun1(int a, int b); int fun2(double a);
1 {) k  @% p( G0 u  {' \5 N用括号把函数名称包围起来,然后在名称前面加*号。void (*fun1)(int a, int b); int (*fun2)(double a);6 o) d, a( D& s* [; s/ Q
如果要定义函数指针数组,在定义单个函数指针的基础上,在名称后面加上[数组长度]void (*fun1[2])(int a, int b); int (*fun2[10])(double a);/ Y2 h+ I. M) U  D
typedef定义函数指针, f! b  p4 K1 t$ Z8 W% w
可读性高比单个定义要高,特别是声明多个同类型的函数指针,或者函数指针数组。  `, W  g5 i7 ?( I  I

0 |) c2 t* |  L1 \# c9 ]2 h! jtypedef定义函数指针的语法
( j3 y" F5 Y& l1 Z* ]8 v7 }% j3 vtypedef有两种做法, 一种就是定义一种函数对象,另外一种就是定义函数指针。用法稍稍不同,效果是一样。其中函数对象不支持赋值, 但是支持引用。
% I9 f$ R1 H- [( R3 ?
$ u* B' K/ o" `2 L9 I2 {& ktypedef int FuncObject(int a, int b); // FuncObject类型是函数对象5 O5 {7 }; A, h3 K9 C5 U* `( `. m
typedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针) ]! ?' J; e) N! F  t, a8 L% ^4 A9 ~0 \
FuncObject* f1 = &Add;- M! V+ l3 a( W8 h+ p0 ?
FuncPointer f2;
5 D; S* V5 y1 w4 W, if2 = f1; // f1, f2类型一样, 都是形式为int(int, int)的函数的指针。7 r3 |- }% k8 C" n8 g5 E/ {6 |
FuncObject f3 = Add; // 报错! 函数对象不支持拷贝& h2 r& E: U( |6 E
FuncObject f4 = &Add; // 报错!&Add是函数指针,与函数对象类型不匹配
! t, ^8 Z9 T1 vFuncObject& f5 = Add; // 正确$ q- G( L; D0 G7 t  D0 r* s/ c* k
int ret = f5(2, 3); // 正确# o9 w3 \2 \$ g2 Z" Q& z
FuncObject& f6 = &Add; // 报错!&Add是函数指针,与函数对象引用类型不匹配9 Q, _1 d' a% f
如何记住typedef定义函数指针的步骤0 T5 ^0 \" A# s% Z& N" {) B, U4 E0 K, C
像定义一个函数指针那样, 指定一个名称。int (*CalFun)(int a, int b);% _0 k3 \7 p+ Z* Z1 N4 D
在这个函数指针变量声明前面加上typedef。typedef int (*CalFun)(int a, int b);
! q; e; H/ E, D! [. z+ I" g! x* P完整例子2 D7 r: o& ]$ Y) O' H+ ~
typedef int(*CalFun)(int a, int b);* ^1 K( e5 r; O6 e3 e& t
, ]! z' T5 }( H  X6 k6 ^3 L9 b" P
int Add(int a, int b)# s7 j: T1 {$ f% w! G3 O" F5 W# k
{: s- u7 C3 V1 n+ I0 v( O
    return (a + b);
% ?& h$ r  X7 ^4 c}% w+ l) x/ _  ?

2 I$ X5 r. L6 ^9 ~5 s' mint Sub(int a, int b)
8 d) n: X9 q! {{
- i! w( {) c" m9 [( R) a    return (a - b);% O# Y7 o6 ^* x0 \( V- x# K- u3 G
}
( w8 E6 D7 x6 o* d! B! u5 R0 W5 ^2 \0 F$ y& ]: J: J; E
int main(int argc, char** argv)
/ `: W2 c* a  ~2 q8 c8 }{
: o5 r# l; c2 E% }  Q, Q    CalFun f1 = Add;9 }6 x; j! J9 O' @- x/ e) @
    CalFun f2 = Sub;
+ \$ {  k' A: N; |, N. t  L0 L3 C% D    int a = f1(2, 3);6 p2 R; Z  i4 h6 J) R: N5 W
    int b = f2(10, 5);. \% m& z/ Z6 o) b. j3 z

/ s5 k1 ]9 h) }4 ^1 [    // typedef定义的函数指针数组。
. x7 n: e2 K; U/ r# F& `    CalFun f_ary[2];1 p0 C- L$ _5 W# R& [1 d
    f_ary[0] = Add;
. Y0 z! e5 X2 U8 W    f_ary[1] = Sub;
5 L- x6 m7 d  O: I' L( Z0 g, `3 X0 e" O8 ~) C; {+ H: g7 h6 [$ |
    // 单个定义的函数指针数组。
( y8 @8 C$ S, \1 x    int(*f_ary2[2])(int a, int b);/ f) G* G0 x$ c9 {) N8 [: B
    f_ary2[0] = Add;
) E7 p2 M' k( A5 Z8 N) f    f_ary2[1] = Sub;- C. H" N3 J7 j9 ?

2 E* F# `3 U/ O+ |$ ^    return 0;$ e1 k; ?  R; c# C. J" z0 P4 f
}
/ A9 h* Z. W! E/ e4 }  C( n8 ]/ x
using别名定义函数指针
- o% {) Y. T- u" X7 Y8 Kc++11以后的类型别名定义--using也可以用于定义函数指针, typedef的好处它都有,个人感觉比typedef更直观。using类型别名同样分函数对象和函数指针两种方式。6 i/ M( Z) g' s) K1 x5 R& p- _
1 }6 Y  L/ ]# H$ e
typedef int FuncObject(int a, int b); // FuncObject类型是函数对象
- y/ m0 P. Q$ j# \1 L7 x0 Husing FuncObject = int(int a, int b);' L, }* }3 n& Q1 e4 j9 j
typedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针+ _& V0 ]& V5 ?4 V# V
using FuncPointer = int(*)(int a, int b);
+ g) i2 r. d0 Z7 W* Y6 l& A函数指针的调用
/ x/ k9 a% X  [0 g8 L函数指针和函数对象都可以直接后加括号调用7 j. E  E: K4 `3 y8 H! K
int f();
( H( H, @* _0 F* F' v$ g( ^* vint (*p)() = f;  // pointer p is pointing to f; t6 E- e. r! [0 E" ~* _
int (&r)() = *p; // the lvalue that identifies f is bound to a reference5 U! x; M8 n. K' v
r();             // function f invoked through lvalue reference
) {0 r# }/ j3 ]9 q5 Q(*p)();          // function f invoked through the function lvalue% O( g0 J7 a/ N* n4 T
p();             // function f invoked directly through the pointer' ?# T, q& M  c+ h6 o# @! W- P% M& \: v
如果函数有重载, 函数指针会指向匹配的那个版本。* L2 W2 p, L  z' V  j
template<typename T>3 h: q* D# G; X% R3 k3 j
T f(T n) { return n; }
% }$ v; ?" ]7 d; d# J- Z
7 A) E2 L2 O4 y0 k" p1 ?double f(double n) { return n; }2 E4 X% r) [; T4 z  u

' c4 U- h( w" r. fint main()
" ^( T2 h/ h2 t1 h* d! e) U0 s8 P- L{. w9 X4 H  ]. |0 m
    int (*p)(int) = f; // instantiates and selects f<int>/ [5 E5 v  s5 s1 q+ ~
}
) b. N( e+ r6 n* M成员函数指针
! `! h, q' u, p! Z$ i* ?9 M静态成员函数,除了增加了访问控制以外,跟普通的函数指针没什么区别,所以普通函数指针可以直接指向类的静态成员函数。但非静态的成员函数与普通函数指针不太一样,声明时需要指定函数归属的类名,并且调用需要指定对象实例。
' F4 n6 ^' ]% @8 M$ v$ E7 r$ v2 t9 H, f0 p
成员函数指针定义。5 q" W+ v4 {' E$ T
像定义类成员函数实现那样写, 并任意指定名称,这里作func。void ClassName::func(int);
" i& Q! \  y# d* ]& W  q' i括号把类名、范围解析运算符::、名称包围起来。void (ClassName::func)(int);9 j* a# ]: K" w
在名称的前面加个*号void (ClassName::*func)(int);. q' [) N3 c. N/ a: s! C
成员函数也支持typedef和using的定义方式。typedef void(C::* MemberFunc)(int); using MemberFunc = void(C::*)(int);# d1 a: ~2 V% r) ], O
成员函数指针如何调用。
' ^0 ?3 ^7 H4 r! Z; d- v8 L假设成员函数指针名字为func
; G# M) c# t9 \7 R- n3 B3 q2 Z+ j4 i8 x
void (ClassName::*func)(int);: g7 _# }& G% U  d" N- q( S; G
对象式调用。
; U1 _+ U( K+ J' I- t5 }# AClassName c; // 被调用的对象
2 \: {& f8 V' P成员函数指针名字当作正常函数那样写。
3 {" C2 Q5 X1 i! mc.func(3);5 s) a$ T4 x; J/ a% Q6 Q$ Z
成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。; ~' I3 N9 C( H. n' a
c.*func(3);6 q' E  S1 i4 ~% H8 g# f
最后用括号把调用对象、成员访问运算符.、间接寻址运算符*、和成员函数指针的名称包围起来。4 w3 u, {7 F6 J8 S! w
(c.*func)(3);
  @; c6 }* G8 \! ]$ u5 j* p为何要加上括号? 根据c++的优先级标准,取成员运算符. > 函数调用() > 间接引用符*。 *号优先级比函数调用要低, 成员函数指针还没取得对象就被调用了,自然报错。 另外" h% t5 N# S' t/ J* g) Q$ L  w! A
(c.(*func))(3);( ^4 w5 f* _' l
这样的写法也不行。 .*和->*是整体作为一个运算符的,中间不能用括号隔开。7 t/ v% {) I1 z
指针式调用
: N8 V! J2 T+ L! X9 M2 R& k6 t  z+ dClassName* p; // 被调用的对象的指针7 c* [5 e4 G' S6 i
成员函数指针名字当作正常函数那样写。2 ~, e& F! K# P+ M; l
p->func(3);
2 w& X# X2 z& K) r4 ~1 [4 O成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。
! I3 K0 s; ~' w, P. Pp->*func(3);
$ J# A: A& Y9 u! o2 ?最后用括号把调用对象、成员访问运算符->、间接寻址运算符*、和成员函数指针的名称包围起来。5 B! }- T: i& ^, J7 t
(p->*func)(3);: r& f  }8 C+ c: f( m4 T# d# s2 i- A" u
函数指针使用完整例子
. |! s  M+ D) _% N2 T9 e( W4 T. }* ]- \) Hstruct Cal . X' \% K: z1 v
{ 6 b2 V/ l% T* q) ]
    int add(int a, int b); ) f2 D$ x8 v! B. ~
    int sub(int a, int b);
& M) O3 {8 P5 Q0 J' {1 s};2 r. [1 e1 z* l% V% t' c% Y+ V1 B

! h$ b0 U. x" I) n9 t9 P) Tint main()4 o5 d; Y1 Q3 W7 G- e' {
{
4 E4 ^# {0 e7 R( w    int (Cal::*fun)(int, int) = &Cal::add;
1 J+ r. O: b1 l  ?0 c- u; t; y    fun = &Cal::sub;( H; l& ?2 I' g, w) ?+ e- D5 x
, x, F$ w' t& V6 Q
    Cal* p_cal = new Cal();" R7 I9 N7 m- U+ {
    int r1 = (p_cal->*fun)(2, 3);1 J; t( a* i7 n0 Q; f
    delete p_cal;
* S2 j. b# I, ?' `+ ?. X
: H/ _, w9 Y$ m5 \    Cal local_cal;
' P1 i$ D' X- s, O    int r2 = (local_cal.*fun)(8, 6);3 K6 z3 g- |4 _8 G. `3 }; e& N
}( L8 r  B; j4 C$ g' d2 `
1 i: I  s! {& X2 ~
成员变量指针
# }  z0 R5 R) C  l: v1 y* I成员变量指针比成员函数指针还要简单些,没有函数调用, 无需考虑函数调用和间接引用符*的优先级问题。
& V  _; ^; I- d& D
( J% V! y) U5 i' U, k* N成员变量指针的定义) ?6 [' P$ k+ q
假如以下结构体C。
0 D8 K7 L  _0 k" O% o0 [. U$ M' f2 H# K, n
struct C
7 j' s  K" c! V3 j1 X" y{ ( `% b" x0 g  ?# h. q3 U! P
    int m;
0 p/ W# S* n. a2 ~};' K6 F& w8 _  d8 J. i7 A2 a- V" h8 _
单个成员变量指针定义
1 f! x! W, b" Q. a2 _/ G7 _8 E假设名称为p, 类似静态成员变量定义那样声明
1 v: V' I: g7 t1 Qint C::p;
# L' K) G3 b( c" X# y在名称前面加上指针标识号* $ Y8 R9 ^" C; c
int C::*p;
0 T8 v1 o& ^2 W+ Utypedef或using方式定义7 G; m7 @3 |. R- e4 t0 d; P$ d. R  x. H
typedef int C::*MemberPointer;
' X" @' C4 S: t2 c) r2 w* ]1 Gusing MemberPointer = int C::*;3 \. n" f* Y+ I# r3 ?
成员变量指针的使用。
8 b) M7 }6 V/ |. b% c类似成员函数指针那样,直接使用指向成员的指针运算符:.* 和->*即可。, \) ?2 a! b) t; l
成员变量指针, 能让我们实现一些遍历成员的动态功能。 例如把一个类/结构体的多个同类型的成员变量放进一个容器里,然后遍历访问这些成员变量。
# v, Y& j' g; h( h! d( W: E9 B/ K3 n" z7 g
完整例子9 U  @( k- A7 t; P1 L% [
struct C { int m; };
$ w; z. Z! Q* Rint main()
8 _; e# n+ F: t3 {0 f! Z& G) c{
. _" u$ s5 Z% w9 l    int C::* p = &C::m;          // pointer to data member m of class C
" F5 U7 j3 s! l' s    C c = {7};
3 H) q/ t$ P8 I    std::cout << c.*p << '\n';   // prints 7
, V5 h, D$ f" v+ h    C* cp = &c;: I# J. c  F  Y: q0 X
    cp->m = 10;
) L: W! c! J) {    std::cout << cp->*p << '\n'; // prints 10: c8 N5 a. {& }4 |9 F/ M! b8 {
  w0 h  N% I! N. h/ E
————————————————  g8 X# L4 ]1 i; d- g  o  N- E
版权声明:本文为CSDN博主「南风fahaxiki」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。6 F' z$ m# s, \4 h; D. S% g. k
原文链接:https://blog.csdn.net/m0_64407685/article/details/126788115
$ H) F" C/ `# B) X. Z# W4 Q2 l/ r; h0 I) V( C) w' u
4 Q& o9 Q) ]0 @6 M- b6 ^  M





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