- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 564716 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174638
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
C++数组指针、函数指针、成员函数指针
0 Z, |$ p) b$ B! l9 OC++数组指针、函数指针、成员函数指针. [, j$ G7 s6 S! d
, O2 _" ]2 k( ]( A5 W9 M5 x
- r" `+ S2 T" T操作符名称
7 A @4 {1 n ?2 ^& 取地址符(Address-Of operator); K0 O6 g. `! x' ]; Z) q
* 间接寻址运算符(Indirection operator)
6 ~8 S7 `& R/ F+ E( E4 {/ h.和-> 成员访问运算符(Member-Access operators),用于取对象的成员。
# ^, {" R8 N* `/ i4 n7 R: z) R6 \, V.*和->* 指向成员的指针运算符(Pointer-To-Member operators), 用于成员函数指针和成员变量指针的取对象。6 Y: c: t- D$ ?0 l( C' Q4 X
() 函数调用运算符(Function-Call operator)2 S4 k+ j7 P& L
:: 范围解析运算符(Scope-Resolution operator). K3 \2 W& y# W4 d! b! N8 S
如何定义一个指针变量
2 U- L" u& u! ~& n2 Q/ I假设类型T, 变量名称name, 指针的定义如下:
2 V2 a6 r, f$ N" }( J( E$ W
/ M* ^) L- ^- W Y: d3 t7 kT* name;6 ~8 Z+ W: a6 f& S! H7 o/ |5 o
标识变量名字name, 它是T类型的指针。例如
3 v- x( Q% F9 J6 ? K) p& p) y
3 Z H/ B1 h# l. B& _0 nint n = 0;
, {$ \8 T4 f- \9 pint* p_n = &n;5 e+ h3 Z1 e# j! E+ k
p_n是int指针类型, 指向某个int型的对象。' W: k9 J) _2 @ P" o7 ^
! S3 T. w: Z+ Z0 a- i指针变量的修饰$ d3 m$ l+ J+ O5 _7 C# }
指针实际上也是一种变量类型, 只是它保存的内容有些特别, 是指定类型的地址值,通过间接寻址运算符(indirection operator)*, 可以访问到指针指向地址上的指定类型。
: t+ l8 V7 w( |2 l+ `9 c7 _8 E
- ?9 s+ [; k+ s: z) _3 }9 N* U4 k7 a指针也可以用const, volatile修饰。 const int或int const均表示一个变量类型是int, 且该变量不能修改。以下两种写法都可以:
/ B+ ?8 A2 q; a9 ]" \
: }% h4 b* E) Oconst int a = 1;
! y b2 A" z. U4 F" }int const b = 2;
2 w" K8 n7 Q; x/ U: z4 }4 @5 r0 E: o既然指针是也一种变量类型,同样支持被const修饰, 表示指针的值/指针的指向不允许修改, 指针所指向的那个变量是否允许修改, 那是另外修饰。写法如下:
. |& g0 y. `% v) y
9 p; i* i& }9 t( k sint a = 1;
v$ G8 f5 \" [/ [: X2 A7 c% Uint b = 2;
; D& p$ Q8 m7 @' E! i; D) R4 j+ kint* const cp_a = &a; // 指针的修饰词,放在*号后面。
. o8 X% p# c% G) L" \*cp_a = 10; // 指针指向的值可以修改6 P4 I: G& [! i6 k
cp_a = &b; // 指针不能被修改,报错!# z( Y6 V, z" q
总结带修饰的指针的格式:
5 ]/ P, ]* g, T! a) R# d/ \3 |3 A只要记住修饰词总是放在被修饰的内容后面。9 ~" z8 k. G6 D' j* N9 c* ^
5 u) z, K2 ^6 B
cv表示const / volatile修饰词。指针定义形式如下:' }$ V$ g$ e6 X
" d) z; Z9 k9 w/ H! G5 D# i
T [cv for T] * [cv for pointer] name
7 s6 x5 O7 g& Q5 M( ^& n注意对T的修饰放在T的前面也是合法的写法。$ i0 j' p& r( p+ j5 [
3 d) f; f8 ~! z
const int const c = 2;
6 b: z1 R' }( H4 |; h" l在mscv编译器下也不会报错。2 [- }: q& q5 X0 r9 F) s
& }$ @$ I3 c, m5 S. b1 N: E( V
完整的格式:& I! C6 ]9 m1 E$ r
[cv for T] T [cv for T] * [cv for pointer] name
( {% i, B4 B" T) F& F+ J! g7 h9 K7 m3 L. H5 j0 K
Syntax meaning0 [( S/ N; i. q. G' j2 V
const T* - U, B; {' B1 b7 {# R
pointer to constant object+ S) D+ ]- [2 f& i
0 Y- ?6 Z/ b; b
T const* pointer to constant object; [, n- j: [5 n! m& |
T* const constant pointer to object5 C& b. Q! a+ G& F6 D. n
const T* const constant pointer to constant object' B7 s* R, a, z* h$ Z8 K
T const* const constant pointer to constant object% y$ u" `- t# x8 s
上面格式中T还可以是一种指针, 指针的指针仍然是按照修饰词总是修饰前面的标识(T或者*)来确定修饰的意图。
& P1 i) c p& s. P" D' D) ^0 z4 Q% u
int a = 1;/ Y/ f9 H( ]0 e! f
int b = 2;3 V# p- i5 f( m: z- X1 _
D- ?& ], F# X+ ^/ u1 x1 h5 Mint* p_a = &a;
- s( T1 J/ R* G& u*p_a = 10; // 合法
9 T% h) x$ T5 p: T6 Z. np_a = &b; //合法
% O: H! \. f& q! z, s. D& |$ \
1 T) @8 O0 L7 _& K! P/ {const int* cp_a = &a; // const修饰int类型, 并非修饰指针' @0 S2 q) x# i& M8 d9 h
*cp_a = 11; //报错! const int类型不能修改1 N) @; z6 q4 W ?
cp_a = &b; // 合法, 指针没有const修饰,指针可以修改。
. ]- u$ W! q1 R+ H: K+ z) c3 G. T- J0 ]5 L+ @
int* const pc_a = &a; // const修饰指针。类型没有const修饰, g) f' H5 v5 z1 G) e
*pc_a = 12; // 合法, 因为类型没有const修饰,可以修改。- w* X, u: V8 F% A. F2 M7 f4 `0 X
pc_a = &b; //报错! 指针被const修饰, 不能修改指针。" U, ` ^: O6 A5 }! w* e
* q' ?' k' @' u( U
int const* const cpc_a = &a; // int类型被它后面的const修饰, 指针符号*后面也有const修饰; U& E" v2 m9 W* F7 e$ R% |9 m$ n
*cpc_a = 13; // 报错! 类型被const修饰,不能修改。
; y4 s3 e, D z6 M% Dcpc_a = &b; // 报错! 指针被const修饰,不能修改。. q, O: @# S8 J& M" J2 z) Y
4 o+ A6 p6 w; I$ y! H 更复杂的指针的指针% c8 m7 ~% n5 v
5 i0 m' ?4 h' H) g8 W- t6 f$ Y
int a = 1;
9 W, }, y2 U4 u0 A Y4 j$ Hint b = 2;
G, r2 |' t/ _) gint* p1 = &a;
]! W' E* q( u) E# `int* p2 = &b;
; T' u' [" O: Cconst int* ct_p1 = &a; // ct for const type% w ]0 \) \; v$ j3 S+ V
const int* ct_p2 = &b; // ct for const type3 q) V9 Q( }3 i: N3 Z
! o# O: ^; R0 a3 I) j* ^. |
// int * * pp1; 指向(int*)类型的指针( W& Y, w1 I( F/ p; U
int** pp1 = &p1; 7 v" I' k( m) g& u; i
pp1 = &p2; // 合法,
: {# I+ `9 f) V' H- Y! b3 H- xpp1 = &ct_p1; // 报错! 类型不匹配。 (int*)不能指向(const int*)
/ h9 i% K8 y& z' e, N4 V0 U
8 e1 r2 L7 B! T// (const int) * * pp1; 指向((const int) *)类型的指针& {# K" a, ^- E* D$ q
const int** ct_pp1 = &ct_p1; / D, n$ j; C; F6 _1 {, T! L
ct_pp1 = &ct_p2; // 合法- [" `9 o3 C5 @% e- C
ct_pp1 = &p1; // 合法!(const int*) 可以指向(int*)类型。; |% O1 G. j) a" c9 n5 e1 k
/ q5 m/ V4 F7 h7 p$ ?4 M// (const int) (*const)
" N! ~7 Y- \& \/ _! B: w. g. Cconst int * const ct_cp1 = &a; // 指针也不能修改
# i. I, x/ ?+ V' iconst int * const ct_cp2 = &b; // 指针也不能修改) j2 a% D4 {0 Z% Q4 u/ Z1 C& ^
ct_cp1 = &b; // 报错!指针有const修饰: z" h0 w# ?& s2 U2 L% Q4 h
) a5 V1 E2 ^; `+ K
// (const int) (* const) * 指向((const int) (*const))的指针
1 @# _4 |& m- w) m" bconst int* const * ct_cp_p1 = &ct_p1;
1 q4 M# ?+ c# I8 u- ?ct_cp_p1 = &ct_cp2; // 合法, 指针的指针并没有const修饰, 指向的指针有const修饰( y- g. @$ K6 l: ]+ y& M' [" Q
*ct_cp_p1 = &a; // 报错!等价于操作ct_cp2, 指向的指针是带const修饰的不能修改
2 L2 \* N- A7 o4 k& [
/ Q" Z" g# X" {$ i' J& I// (const int) (* const) (*const) / Z1 b9 ?0 A. Q
// 指向((const int) (*const))的指针,且该指针被const修饰
; t( ^" f# Y j1 yconst int* const * const ct_cp_cp1 = &ct_cp1;
- _& A& s! q) q& P( @# [ct_cp_cp1 = &ct_cp2; // 报错! 指针的指针被const修饰, 不能修改指针指向。+ P6 H) p4 B+ b! f+ g6 |& h
# K, x/ j: V" s4 m. {: i; L; ?一行声明多个变量
4 k6 U6 j D, i/ a- X k类型 + 名称定义一个变量。
4 D' W9 x6 O5 `( C6 V变量的前面可以加*号修饰, 表示指针, 一个星号代表一层间接。**表示指针的指针。
) O) @- C( T+ G' n q9 b' _0 P6 x6 |8 B; I0 a. i# L
int a, *b, *c, d, **e;1 l3 s5 J& u% O: ~* E
a = 0;
. H8 v& j7 V" K8 bd = 1;9 r9 H1 k& u t$ f: h
b = &a;
7 T0 {7 ]# a+ d* sc = &d;
7 o; g4 I( c3 S: T& m! a* K$ f/ ]e = &b; // e为int**类型 指针的指针& y( R! p3 m; o; o! Z* p) q
e = &c; // e为int**类型 指针的指针/ ?# J' J$ \% S3 ` o3 V) G
也可以用括号包围变量和*号。
- C. b. y, i; n9 |! o5 z, w2 ~' E9 N% C9 V1 m- ?4 V
int (a), (*b), (*c), (d), (**e); // 合法定义。8 O( p9 I7 }, K5 c& x( p
括号可以省略,某些情况, 个人感觉加上括号更清晰一些。例如
( ]; X/ v0 G( d/ d+ b) L$ E* ?! y( l: a$ b" j$ u0 o
int (a), (const *b), (*const c) = &a, (const d), (const* const* const e) = &c;9 k9 c t7 K9 P9 H" Z' D
写成
% E; x$ V( k& m" h! _) l& a
7 z7 v. F+ h8 [- c) ~5 m, M( O& xint a, const *b, *const c = &a, const d, const* const* const e = &c;
1 u; A/ i* p9 w8 r _! v更重要的是, 后面我们表达数组指针,以及函数指针时,括号是不可缺少的, 带括号的表达更加统一。; x, A2 S/ L( Q* H
1 b6 ~! ^8 ?! E8 |9 \6 M数组指针$ j1 B0 P3 U+ D% [6 g# p
数组基本表达9 X( P- {( H% [5 Y7 ?5 F( g; L
int a[10]; // 定义了类型是int, 元素个数是10的一个数组。* B! y8 h G$ l3 t/ c# f
由于c++要支持一行定义一个类型的多个变量。 所以数组的[]时放在名称后面的。虽然我觉得; F$ ~7 X% v) W; x3 }. C
; t4 K* S9 w/ q
int[10] a;
& Z& W: Z/ F; I" u+ j7 h这样的写法更符合类型 名称的思维, 但是如果类型都这么写的话, 没法兼容以下的写法:
: i' S2 @5 O9 v! U* E+ |, l
) B6 g' i/ m0 e9 [3 ^+ |int a = 0, *b = nullptr, c[20], **d = nullptr;
+ F7 e ~, f* ~, C( l/ Oc++标准规定如此,但我们可以通过每一行只定义一个变量的写法, 类型会更加清晰。
5 I1 h3 W+ e, T8 v6 A% ]! J
$ a. S+ V n9 qint a = 0;
) @9 j, O4 K" d/ p$ Z7 mint* b = nullptr; // 指针int*, M6 X6 b% z. ? L- b
int c[20];1 }3 F( t# j) k0 o
int** d = nullptr; // 指针的指针int**3 Z, }/ o( L) K& m
数组的名称是什么类型
9 q/ Z& o2 \1 f数组元素类型的指针,可以直接指向数组。 并且数组跟指针一样,可以通过下标去访问元素。" O; ?5 k; H3 ~+ m
6 O- b! V( z8 vint a[10];$ ^- Z1 {% w9 K( r1 x
int* p = a; // 指向a数组的第一个元素* q- I. h* [# p# n0 m1 a1 V5 N
a[1] = 1;! w" t, n5 A+ y9 f
p[1] = 1; // 效果与a[1] = 1一样。9 c+ ~7 Z3 ]! }; ]" e' f" G
数组可以当作T* const来使用, 但是又与T* const有些不同。sizeof()的结果不一样。8 C! ^; ~; I3 D' j
3 T0 @8 r2 T! `# |, Q7 ? Pint a[10];
: J0 L( }4 u( {* E. \0 D$ Eint b[10];
6 f0 W( u2 A# E- T, l$ j* @. cint* const p_a = a;# a$ e4 H# c3 T
a[0] = 1; // 合法。 数组的元素可以修改。- g4 t( P2 f: ~/ q6 p- i$ E* p
p_a[0] = 1; // 效果与a[0] = 1一样。
6 n* O( I5 I$ u% I4 b$ A
: n& w4 _6 V9 K) I. T, n! v) @a = b; // 报错! 数组本身的指向不能修改。
4 ^, g4 s6 Q+ r* J5 K
) @8 S' \# R( [, |% a" D) V, m// 所以数组a可以当作int* const来使用
9 ^# J6 u( Y* o% J7 E0 gint *const& ref1 = a; //正确。
5 `3 C. @' V; H5 T8 mint *& ref2 = a; // 报错!8 |0 D% F. e" x5 r
k+ M* m' `) r
// 但是又跟int* const有些区别。# ^- j/ d0 [4 D, D0 Y- e
assert(sizeof(p_a) == 4); // 32bit程序。
, s/ u2 I9 ?& f& ^/ i/ iassert(sizeof(a) == 4*10); // 32bit程序 _8 S% d+ |$ H6 L
/ E& D7 D2 t: X, i9 y1 m数组跟元素指针的作用很相似,都可以通过下标去访问元素, 但调用sizeof()函数的结果不一样。元素指针的sizeof()返回值是4(32-bit应用)或者8(64-bit应用), 数组的sizeof()返回值是数组实际占用的空间。数组可以当作指向第一个元素地址的T* const来用其实就是我们常说的数组到指针的隐式转换。当数组作为函数参数传递后,会自动退化成T* const, 在被调用的函数内部调用sizeof()的返回值跟T* const指针大小一样。 数组传递作为函数参数后, 在被调用函数的内部与T* const是没有任何区别,只有在数组定义的可见范围内sizeof()才有获取数组占用空间大小的效果。
, t. K- R$ ?/ `' d
+ B: P/ W4 h2 W; n- |4 S以下3个函数翻译成汇编以后,汇编代码是一样的。
2 X' [3 b O4 I" B& X. l0 ]# K/ s% i& p9 s; b! `
void Func1(int* p_ary)
9 g# W$ e5 r; w! @8 E) B{
! a! n; ]% l& _! y4 R1 i% W& Z: z* Q assert(sizeof(p_ary) == 4); // 32-bit
2 h* ~4 ?( H+ y# g p_ary[1] = 1;3 O& ~- o6 l+ I! q0 Z+ E
}
7 X4 K" F) i* v5 L- s: K# e4 u% f: e8 p ]
void Func2(int ary[])+ k) [; F8 V" v$ j% h
{
& P5 {! T* H# z assert(sizeof(ary) == 4); // 32-bit
; ]3 I7 b1 U; b2 h& d6 ]% q ary[1] = 1;+ m& U) l Y. I2 [( e
}8 ?4 |% p! f/ c7 A
; Z+ U+ |/ h3 }) Kvoid Func3(int ary[10])" b' L" x8 V6 B! \0 w6 J
{
7 r# _0 X1 \, z( |) g+ o% O assert(sizeof(ary) == 4); // 32-bit
$ W6 v: t3 f% n# t7 ] ary[1] = 1;$ t5 J% i$ q: l3 M/ d
}
# c6 z# a1 V& W1 I* _# S! `5 g; Q0 P! W: M
int main(int argc, char** argv)5 F- B* A) K) Y" i+ k; w
{6 [7 ^* c) R r" W
int a[10];
. i1 q4 v# w; p: G int b[20];
; b" r# O$ B Z Func1(a);& ^) X I2 b& ?" Y4 I' _1 D
Func2(a);* t a0 L- u6 m
Func3(a);) Z q* B$ @3 z! {: G$ k1 F$ q$ K
Func3(b); // 退化成int* const了, 即使数组长度不匹配也不会报错。
2 ~) A1 X; B7 Z5 N! W. j return 0;
, y) M8 c1 j2 k4 `* z* W/ e}+ H: @+ S' Z6 B d( n- ^
5 D* W$ m8 P9 Q; w/ i
. o& ?: y/ p' U- E$ R, I% p$ k# c/ z
多维数组3 ^0 T+ r9 f" w% s
一个3行,4列的数组, 结构如下:. b( j+ l) {* a6 y
& n& V/ C6 ]3 b7 o
int a[3][4];; b. p3 Y+ a* B
column 0 column 1 column 2 column 3
% h6 K# Z6 ]' k+ crow 0 a[0][0] a[0][1] a[0][2] a[0][3]
9 q7 O3 U& R0 O! v' ?7 Q+ U L3 \row 1 a[1][0] a[1][1] a[1][2] a[1][3]3 L3 y m4 R+ Y2 u- V Y- A e
row 2 a[2][0] a[2][1] a[2][2] a[2][3]" j+ m6 S1 T4 D
数组初始化
3 x1 e" ^. r, F0 t% P! B O
8 `4 d( v6 a2 V9 t4 u1 Eint a[3][4] = {
0 X9 |( ~" y# O) R {0, 1, 2, 3},- D1 F7 T' R6 g; Y9 L7 M+ o3 \
{4, 5, 6, 7},
* U3 t; `# j0 y% F O {8, 9, 10, 11}
0 X9 e8 y1 y. f' [- X6 {- C};9 [% O' I3 O9 F" ^# p
实际上多维数组和1维数组在开启速度优化后,翻译成汇编代码是一样的。& `$ F% R: X, B& @% @; p; ~" m( @
3 e/ y1 S" |' [# m& V. C
void Print(int* p_ary);
+ U( }6 _/ \6 o2 }$ h
: P! Z2 y U' Rvoid Test1()
, c+ `! ]3 ?: b{2 q# E* V) _6 j4 x) J, x
int a[10];
* r6 t" H1 l7 V( k) c: H7 x a[3] = 3;: i: a; k$ e6 k4 R+ s" x7 m
a[7] = 7;& a% u, f; ]* S8 q
Print(&a[0]);
7 w7 B: O) I, a& g3 _' y- q}# n5 |& }7 @, T- {! a1 r& A
5 w5 h8 U, _" f
void Test2()3 w) K) @$ X! e0 a: Q9 N
{ S' p6 F8 M5 i1 [$ E, X+ }
int a[2][5];
# A2 F3 T- {, ^+ Y/ X a[0][3] = 3;
; i/ Z( Z, L" O! M0 T a[1][2] = 7;8 O6 g- N/ e; F1 W
Print(&a[0][0]);* |2 t0 g, n# _; c$ r4 r
}
) r. @4 B1 _0 {' \
' B+ _) D- U; L. n7 K: D4 J) W) B& Z! _8 ?9 M/ q
& d- ]. r( n( h4 a- k& z W" p
很自然地,多维数组也支持用1维数组的方式去初始化。+ t$ T( O2 F6 m0 c1 j
2 R& H7 ]4 x0 M
int a[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };1 Q: v6 g0 W. W. b; S
既然多维数组与1维数组没什么区别, 为什么还需要多维数组?: J8 ?; F; }) e! b# ^0 Q, w* S: |
* H5 a: K! y" ^& w7 [假设有一幅RGB图像720*576个像素,每个像素有RGB三个通道,每个通道的值是8bit大小。给出图像的首地址p_rgb_image, 我们要取第40行,第50个像素的R,G,B值。代码如下:
v- l# j5 i5 I8 c9 H
1 J1 w- \+ @+ |5 A1 |unsigned char* p_rgb_image;
( ?& A$ n1 x( y* _unsigned char r = p_rgb_image[40*720*3 + 50 + 0];+ |2 Y! u3 \* q- h% T! [: q4 Q
unsigned char g = p_rgb_image[40*720*3 + 50 + 1];; [) E7 s! Y- f6 x8 e
unsigned char b = p_rgb_image[40*720*3 + 50 + 2];
2 C) _2 `# K6 C# E5 \类似这样的场景, 采用多维数组的写法, 有点类似以索引为参数,可读性更高。相当于程序员和编译器打了一个配合。3 `7 N. w8 q- V
& Q( ~4 m% h8 k( L' K. I
enum
0 `/ o$ m! Q( `4 ^) J{
" q) u Q$ M8 C5 P5 E( M6 F% U Red = 0,! g2 P3 N5 v; B
Green = 1,% l9 G) c# I: t& t+ l g% k7 i) a
Blue = 2' \+ p! J+ N; U, ^* A
};6 I# P8 {: I4 t8 R
unsigned char rgb_image[576][720][3];$ d4 w4 K: ]7 [: S. s! v2 M
int row = 40;1 t1 J0 d% v% T S9 k3 _
int col = 50;; n0 w% U4 I( u+ G7 _7 @, b; C
unsigned char r = rgb_image[row][col][Red];
, ?' i5 s* G5 \+ T& L% g! @" Tunsigned char g = rgb_image[row][col][Green];- W( W, l& z Y* Q0 m3 H2 P
unsigned char b = rgb_image[row][col][Blue];" O7 S. G. J& c' Y+ [3 U Y* a. a$ ^
数组指针以及与指针数组的区别( E2 n: H, d8 z, A
数组指针,是一个指针, 指向的对象是数组。 数组指针的赋值,要求数组的长度匹配,否则会报错。当指向1维数组时, 需用用*取得数组对象的引用,再用下标来访问数组元素。2 B$ F+ C) l8 v( H4 f; k2 V
指针数组,是一个数组, 数组保存的元素的类型是指针。
2 Q0 G8 I% a) v数组指针的定义
9 [$ G* k0 i/ t- v& `数组指针定义先定义一个数组。
3 w g; ]+ ?/ P8 o& h2 Fint a[10];- b8 J, F' w5 m0 |
然后对数组里的名称用括号括起来后再在变量名称前面加个*号* ?/ `8 r/ a$ c- m1 }6 m
int (*a)[10];
. C5 V q$ B- u7 q8 l后面你会发现函数指针定义类似。
/ z: F" E) s) J* Z7 d- e Y2 Z. z. z3 }// 各类定义对比5 F% P0 G# ^; {% U, e& S* {9 y
int a, *b, **c, d[10], e[10][20], *f[10], (*g)[10], *(*h)[10];9 H$ z+ A; u; q3 L, ^3 M4 s g
, u% W4 q0 W# B8 k. d& dint *f[10]; // 指针数组, f是包含10个元素的数组, 数组里每一个元素的类型都是int*, t3 s9 ]) U' u& B! C
int *(f2[10]); // 指针数组。另外一种定义方式。- Q1 Z4 m6 m9 m
int(*f3[10]); // 指针数组。另外一种定义方式。
) Q+ X/ N+ Z: Hint(f4)[10]; // int数组
+ L' E. ?/ k) m# h6 `% ~5 f Z0 Z9 Q- [int(*g)[10]; // 数组指针, g是一个指针, 这个指针可以指向类型是int,元素个数是10的数组: K/ U+ Z2 ~ S% g
int* (*h)[10]; // 数组指针, h是一个指针, 这个指针可以指向类型是int*,元素个数是10的指针数组
2 ^6 q& w( R8 N
7 A @: w v$ P! F, x6 Oint d[10];
* a6 Y! z9 F0 T! U' ]g = &d;0 V# h7 @9 z# {" y; ~
7 a* u p. O# L* _
int* e[10];
: j1 H. X- s' z( l' ^9 \! Ph = &e;
. e7 m7 o7 q3 r! L3 S% X' t数组指针的使用9 l2 |8 u7 F: q7 R
数组指针一般先通过*号取得指针指向的数组对象, 然后再用下标操作访问元素。& {/ }" D( d- ^. x
5 J, n1 k! s- B& E/ m; @+ F
int a[10];. B6 }6 f$ K5 h, {5 _) f9 S, o
int(*p_ary)[10] = &a; // p_ary是一个指针, 指向"int (*)[10]"类型的数组7 y3 @. S' m q! M3 w0 y- K
for (int i = 0; i < 10; i++) {
. Z7 F* q3 F+ B- n; H# E/ L! j // p_ary是一个指向数组的指针, 需要先通过间接寻址运算符*(indirection operator)取得数组对象! }/ ~" H$ s7 V! Y( d2 T. m
// 再通过下标操作访问元素。
% u) [% o4 a9 G (*p_ary) = i; ) w7 w- t% o8 h( g
}
% @9 X$ o n& ~
3 S/ m) A( {2 ~6 X) C3 n3 rint b[10];
" B1 }2 R5 m. B9 }7 y! O; Qint c[20];
' ]3 c% [2 m7 z; _p_ary = &b; // 合法
2 r* S% E5 v& y. f" I% u" \; k# hp_ary = &c; // 报错! 不能将 "int (*)[20]" 类型的值分配到 "int (*)[10]" 类型的实体! P- E& i, G+ ]! z3 b; R7 e- k' l
数组指针指向多维数组的子数组+ P+ H9 r0 u1 k; j
int a[10];
! D5 c5 D' p% p$ X- N5 I: K! Cint b[4][10];' z* ~% x8 d3 u
int(*p_ary)[10] = &a;
* H# }: v# e3 Q/ lfor (int i = 0; i < 10; i++) {
: a# ], Z; h. K" p% Y (*p_ary) = 1; 8 o- S' b1 B5 s/ T2 I) F G
}; _" r, q- U; ]. M
4 `3 I5 [) l9 F
p_ary = &b[2]; // 多维数组,可以看作数组的数组,
2 e1 E+ }1 V4 P// b[2][0] ~ b[2][9]的值都被改成2了# ^ R# \3 t. F( e
for (int i = 0; i < 10; i++) {- C7 R+ e% Z& `/ n/ U& @! a6 K2 V
(*p_ary) = 2;
# b8 n4 K4 R: H$ J}% z5 |3 M* E* S5 \2 L) |+ j
多维数组指针
8 E! o, ^9 r/ x- @3 V! g9 W多维数组指针,是一种指针,指向的对象是个多维数组,支持多个下标操作。/ B. F2 b& Z4 m j9 O" s: h, b
7 X0 c% h! p/ ^- v
int a[2][5][10];! u/ p* V. C1 H" T: }9 _$ c
int(*p_ary1)[10] = &a[1][2]; // 1维数组指针- Q5 |+ F4 @7 z+ {9 l7 Z5 n" I
int(*p_ary2)[5][10] = &a[1]; // 2维数组指针
8 w- ~. l! s" \$ T' vfor (int row = 0; row < 5; row++) {
1 D9 z6 m# W" O; w& a7 { for (int col = 0; col < 10; col++) {. A* M) H+ M/ B& \# z
(*p_ary2)[row][col] = row * col;
* x) \2 U {' k$ S7 M1 a }
( f2 i. f* \; T7 m% r/ y1 `}
# E$ L, l* L1 o5 M1 @数组指针和指针数组对比实例& t# B# j3 L4 D, j' [/ m1 d" w
数组指针还是记住两步法即可% i- z( z3 q$ k8 P
$ Y7 E3 H0 w5 E9 C5 m! e
定义一个数组9 T( @- p# S# T$ O {
括号包围1中定义的名称,再在名称前加个*号。
0 N$ R: N8 M/ i4 Q! m* E6 i" b# [int a[10];. ~4 _; o- S: i) }1 M7 f4 K9 ]
int(*ary_pointer1)[10] = &a; // 数组指针
" d" R5 C* e2 i) }8 uint* pointer_ary1[10]; // 指针数组。元素类型是int*
0 p, g) K- i4 \3 F9 O6 w0 O; [/ Qint *(ponter_ary2[10]); // 指针数组。另外一种定义方式。/ [2 w- T# F, V+ S
int (*ponter_ary3[10]); // 指针数组。另外一种定义方式。! v/ z" T$ s6 Z M P% |1 g% S" G
int c, *d, (*ary_pointer2)[10], *pointer_ary3[10]; // 排列定义比较。
7 h' s. [) |, z9 n% l) I6 p$ A! c* z+ M1 |, b( u1 E* o
// 指针数组可以把每个元素指向数组对应位置的地址。
$ m l& `& T: s* S# s; r) Y// 这样遍历指针数组, 可以达到遍历数组元素的效果,但是注意每个元素都是指针,
! l/ z8 ~# o* m; c& Q// 需要访问原数组的值的话, 需要对指针用*间接寻址运算符。
; y3 w$ J# b6 V4 w2 n8 B1 `, Hint* pointer_ary[10];
7 s! K2 J$ g* \- J: vfor (int i = 0; i < 10; i++) {
) l1 s" E. I2 u/ k# n pointer_ary = &a;5 s$ {3 ^' [, t7 g! |, p% V; {2 j
}% F" a6 D$ e% ^& I
// 类似遍历原数组效果。* T+ V, X9 M% z" ^4 y) T) E
for (int i = 0; i < 10; i++) {$ v* X: V/ I2 C; t
*pointer_ary = i; // 修改原数组。
( K" L+ k# y, w4 M6 I% Y) }# H% x2 O}
4 ?9 m @" k2 `5 u& i
, j5 _+ O6 F/ A2 I3 R$ w- u函数指针
/ {5 e3 M8 j! R: Q. u9 o. N3 M取得函数地址5 N1 k6 g9 [' V% `! B- W6 n
函数的名称作为参数被传递时,会隐式转换成函数指针, 和在函数名称前加取地址符&等价。建议带上更加统一和清晰。4 a7 e6 ^% V+ H1 ^9 e
: @% n5 K+ Y3 g( c2 N
void f(int);0 E# ^1 W3 k. n5 J1 B7 [
int main()5 _- {, ` F# x9 ~% Z' n* B, C3 V( k
{- M& L; R3 V, ?; X; e: Q0 Q& p) \
void (*p1)(int) = &f;& L( D; G& l. m$ C A
void (*p2)(int) = f; // same as &f/ Z# o2 y0 s9 D" V6 l
return 0;
# t- X5 c4 D( R; S' J* o% o( T}
0 K; x' W0 a6 k6 d3 _9 L* Z翻译成汇编代码, p1和p2的赋值是一样的。
4 x$ o" x- d; |& X
) \, ~1 T" Z, p: H0 M/ d( J' w; N( M% z% f
: \% q& ~$ `; I2 ~函数指针的声明
& |7 [( m0 t3 G9 b, y6 c: ~单个函数指针变量定义步骤+ [2 ~" r$ j( T9 T2 |- Q' Q
定义一个函数。void fun1(int a, int b); int fun2(double a);
/ I+ X M' w5 J6 R5 }用括号把函数名称包围起来,然后在名称前面加*号。void (*fun1)(int a, int b); int (*fun2)(double a);# s1 N7 j M" F/ q+ }1 H9 A
如果要定义函数指针数组,在定义单个函数指针的基础上,在名称后面加上[数组长度]void (*fun1[2])(int a, int b); int (*fun2[10])(double a);
& g* z. e+ O* X" `" b' l, v4 Stypedef定义函数指针
7 H3 {5 j. J+ m% f) j3 {: v可读性高比单个定义要高,特别是声明多个同类型的函数指针,或者函数指针数组。* T: G7 F! _! z4 P8 Z/ s
+ s( w% a& p+ p% h2 Z' Ztypedef定义函数指针的语法
3 @% U8 Y) F! b4 wtypedef有两种做法, 一种就是定义一种函数对象,另外一种就是定义函数指针。用法稍稍不同,效果是一样。其中函数对象不支持赋值, 但是支持引用。
# H* n7 z7 @, a/ F) Z$ Q. E5 h, o6 [" _
typedef int FuncObject(int a, int b); // FuncObject类型是函数对象
* F. A" V' V5 gtypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针
, A4 \4 e6 G; r6 \FuncObject* f1 = &Add;! b! I( a4 n5 L2 X& }& B! k
FuncPointer f2;
8 A" m! ?$ x7 g5 |f2 = f1; // f1, f2类型一样, 都是形式为int(int, int)的函数的指针。
7 V- {) [: o9 w! m8 WFuncObject f3 = Add; // 报错! 函数对象不支持拷贝% L W9 l' y* x6 i
FuncObject f4 = &Add; // 报错!&Add是函数指针,与函数对象类型不匹配! d0 `8 X- K5 Q- M# X; I
FuncObject& f5 = Add; // 正确
. t }1 ?* V ~ Lint ret = f5(2, 3); // 正确
N {" M7 @- c* gFuncObject& f6 = &Add; // 报错!&Add是函数指针,与函数对象引用类型不匹配3 G& h. d9 M- a
如何记住typedef定义函数指针的步骤& l2 T, b0 e- ^
像定义一个函数指针那样, 指定一个名称。int (*CalFun)(int a, int b);- Q6 g# B* f9 y
在这个函数指针变量声明前面加上typedef。typedef int (*CalFun)(int a, int b);
& x ~$ ?) g/ Q: z完整例子
9 j$ ^9 w. T3 @5 k- w A. |typedef int(*CalFun)(int a, int b);
- R0 y4 `9 s) H/ ^9 F
' b* `' I) e$ |int Add(int a, int b)' A4 t! z2 R" H8 _) {
{
$ d) I; \% m$ l- ~1 ] return (a + b);; N5 I" F1 [) O2 E2 O) D
}0 b! O& E" F7 J/ |
8 G5 a: p7 g3 x6 o- w" H" Gint Sub(int a, int b)5 J" i$ l( u9 e" K* G( |# U8 X
{
7 H/ C9 ^8 ~5 u. |2 M return (a - b);- y$ [+ z' r3 b) j
}2 }9 M- r5 h3 _5 X# g% B: F
* Y5 E8 }! W8 A( J9 f+ gint main(int argc, char** argv)
. M0 A; l- b# N3 e{* y9 p7 D7 m& N' n. P5 Z
CalFun f1 = Add;
, q0 S! y4 p/ B4 I: t CalFun f2 = Sub;
6 Y9 d2 \6 P% M int a = f1(2, 3);
5 ~# U" K) y: A4 ^! {3 ]0 A int b = f2(10, 5);7 f% f: @" F q! l3 |! G! N- \
1 d2 s1 g# P7 u1 S( N // typedef定义的函数指针数组。
' }! t* z* i0 J CalFun f_ary[2];
6 F* } T% C" a9 I f_ary[0] = Add;& R: O( `/ t- o
f_ary[1] = Sub;
8 H- R5 q! ^3 Y' S3 U1 r
" }" ?+ D3 r1 Z- e. I: \$ ` // 单个定义的函数指针数组。# S. K: a' Y6 X! x
int(*f_ary2[2])(int a, int b);7 W. W2 [2 F5 w" \
f_ary2[0] = Add;
6 K* l/ R8 g3 A! A& I" i$ ` f_ary2[1] = Sub;2 f4 L8 w) d- t! ^2 {1 R+ |
4 j0 k, w& p) o7 ? return 0;
( m4 _ I; S+ o: H& }' p/ w% m* T}8 F) }/ }+ N: t3 T
( u( n5 D; L2 U) @% k3 Fusing别名定义函数指针
% P" D6 r# }& D! w6 R* ^5 q/ Z5 pc++11以后的类型别名定义--using也可以用于定义函数指针, typedef的好处它都有,个人感觉比typedef更直观。using类型别名同样分函数对象和函数指针两种方式。9 ]8 Q* v- j* v$ G8 Q& j
" l' e. K2 h9 o7 X6 e2 y; Atypedef int FuncObject(int a, int b); // FuncObject类型是函数对象
- [, v v* N0 `& V8 eusing FuncObject = int(int a, int b);3 d" O% M1 I( A& I+ N) r: o3 L( m8 E
typedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针 _% ^# V' ^7 ^; P [
using FuncPointer = int(*)(int a, int b);+ z8 B. f! ]* C
函数指针的调用
( x- y3 A+ R/ W( t) ^函数指针和函数对象都可以直接后加括号调用
3 x6 O+ A4 w$ N6 D' I+ Fint f();
1 K3 {# K1 G0 `! Wint (*p)() = f; // pointer p is pointing to f( w: Z! M7 G1 j8 w% j7 D1 Z6 F
int (&r)() = *p; // the lvalue that identifies f is bound to a reference9 ~) i. m, |- `5 Z
r(); // function f invoked through lvalue reference
' A1 W: r- I2 [ l- B(*p)(); // function f invoked through the function lvalue
& }( \7 q* O" Xp(); // function f invoked directly through the pointer Q6 U4 k4 \# d8 v2 M7 {; O' S) A
如果函数有重载, 函数指针会指向匹配的那个版本。( L) H5 w2 [4 ~0 c' l1 U2 v5 o$ P9 D
template<typename T>/ d5 R* z+ U6 H0 v0 m$ r7 ~
T f(T n) { return n; }- E" t; q! \/ G$ k, B
$ ]+ x; [9 z, Q$ Q7 C8 G1 I
double f(double n) { return n; }
# j& \" q' K8 r4 c
; J0 W4 @4 @; ]8 Cint main()7 R/ Z2 F! u$ P) I, K( }9 i
{
4 P0 K: e5 E* ~' U1 Y4 Y0 d8 d int (*p)(int) = f; // instantiates and selects f<int>
) Y# ^: b+ u5 E @}
6 Q! F/ c5 {2 e6 L) ]1 `3 n, m成员函数指针
2 {9 H2 g/ p1 S4 w静态成员函数,除了增加了访问控制以外,跟普通的函数指针没什么区别,所以普通函数指针可以直接指向类的静态成员函数。但非静态的成员函数与普通函数指针不太一样,声明时需要指定函数归属的类名,并且调用需要指定对象实例。& p8 ]0 g2 l2 Y3 r5 E
$ ?% [: v( p. j7 X0 `, e3 X; S; W成员函数指针定义。4 M1 e% K+ C1 B9 Y0 Q: ?& t1 @
像定义类成员函数实现那样写, 并任意指定名称,这里作func。void ClassName::func(int);
) r1 u' p% J$ M. N& f8 D括号把类名、范围解析运算符::、名称包围起来。void (ClassName::func)(int);7 Z, x% m, R/ r8 g! E6 C
在名称的前面加个*号void (ClassName::*func)(int);
& m6 @" ~( j/ z# p' \成员函数也支持typedef和using的定义方式。typedef void(C::* MemberFunc)(int); using MemberFunc = void(C::*)(int);. B3 w. W ^! i* j
成员函数指针如何调用。, h3 q& v" e/ l. B
假设成员函数指针名字为func! V" t( r% ]' {5 U8 z+ e8 N ~& c
* g* h' x ?4 I' }5 Tvoid (ClassName::*func)(int);
* O: |" n* w8 I8 t# ]对象式调用。
# a7 [% s6 A, t+ ~9 aClassName c; // 被调用的对象9 a9 t- \* P6 m- Q5 y
成员函数指针名字当作正常函数那样写。6 a; F* U) M8 u
c.func(3);
7 y& D @ `" Q% ?2 r3 f4 f% `成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。
( p4 J# m# x6 qc.*func(3);
& a$ x# j7 @, E1 J最后用括号把调用对象、成员访问运算符.、间接寻址运算符*、和成员函数指针的名称包围起来。
/ ?, J5 f7 F8 n: L8 z% k: b2 I(c.*func)(3);. e. r v" y2 V* p3 y& k
为何要加上括号? 根据c++的优先级标准,取成员运算符. > 函数调用() > 间接引用符*。 *号优先级比函数调用要低, 成员函数指针还没取得对象就被调用了,自然报错。 另外0 |, t2 n# H7 m$ ^2 P! y4 H
(c.(*func))(3);4 g6 N4 C8 X$ J& `
这样的写法也不行。 .*和->*是整体作为一个运算符的,中间不能用括号隔开。
5 `& w1 P5 h' X' T指针式调用6 l* U( |! S; g- T! i- `; M
ClassName* p; // 被调用的对象的指针
& `0 s9 v! G+ V: U/ C3 a成员函数指针名字当作正常函数那样写。
3 m3 Y1 Q" ?6 m# Np->func(3);' M! d* ?% u r+ k
成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。0 _% ]1 d0 e9 l8 O
p->*func(3); E. R& m. v$ u: {
最后用括号把调用对象、成员访问运算符->、间接寻址运算符*、和成员函数指针的名称包围起来。2 `' i o8 _2 k) `
(p->*func)(3);
q, p# b9 T# z4 _5 w9 N3 _* \- [函数指针使用完整例子. |7 f o: i, `9 }9 |! b
struct Cal
8 h( I% U% l2 V{
4 }+ u, j2 s; h! Y int add(int a, int b); * U: d/ A% A p N8 ?8 f1 g: X {9 Q
int sub(int a, int b);: P, y. m; k: m1 ^
};' O5 V2 A X8 q
2 A+ @4 |; k# A) v& mint main()& b$ g+ d2 U- q: ~9 U" v% j+ b
{' N. ~$ F6 m- O: x! o
int (Cal::*fun)(int, int) = &Cal::add;4 v/ f6 {" Q5 _; O1 F' K% y
fun = &Cal::sub; I. a- `* b0 R' [, q4 o
" c1 a" H- ^3 s1 `# t G# ^
Cal* p_cal = new Cal();
" W* z2 e, {/ ]" z a7 f int r1 = (p_cal->*fun)(2, 3);1 w4 p# I$ @; G k
delete p_cal;
. N* W6 ~! H( _" }; H' E' ~% W. ~2 Q i* z" N5 u; Z& M( E, ]
Cal local_cal;
k5 G, [2 |+ y9 f0 b int r2 = (local_cal.*fun)(8, 6);
2 N6 t* J2 W0 y9 C3 ^}, m* ]$ s: _0 h0 }
* p0 O+ v/ v, [! `# U: ^% P' Z成员变量指针
$ }0 g1 W; v9 R! j+ r, @4 v成员变量指针比成员函数指针还要简单些,没有函数调用, 无需考虑函数调用和间接引用符*的优先级问题。+ D" E# a- |$ m
. z( L0 a/ `0 Z6 X% h# P
成员变量指针的定义& m- ?. }) ]- j8 }8 f* S
假如以下结构体C。8 y; d# p7 A1 W5 a
! H' w4 `* Y5 U U4 d. nstruct C ! h4 `# c( Z/ K( _
{
5 |% V+ {- n2 O, X9 f8 j int m; % Z) k6 p/ ^3 s6 v4 r4 i+ I; W
};
1 U+ w, K" t& F9 s' \4 P单个成员变量指针定义 ! u8 }3 E; t( u5 K$ R
假设名称为p, 类似静态成员变量定义那样声明" Q' e* g* F. q$ J9 U' c" X3 `
int C::p;
8 H T: _, s3 T在名称前面加上指针标识号*
5 S5 _* K9 y0 J" W h3 uint C::*p;( _( `- S% t) H7 G2 _1 ^
typedef或using方式定义- U5 D* K/ i5 }: l0 D# E# M
typedef int C::*MemberPointer;& r: S0 o4 e4 L1 Q0 D* h" ~" [
using MemberPointer = int C::*;
7 [& p6 i% O8 A U: ` V5 ~成员变量指针的使用。
/ F" ]" {) A/ m2 o5 F4 g% ~类似成员函数指针那样,直接使用指向成员的指针运算符:.* 和->*即可。+ |2 X7 a5 A" o: v
成员变量指针, 能让我们实现一些遍历成员的动态功能。 例如把一个类/结构体的多个同类型的成员变量放进一个容器里,然后遍历访问这些成员变量。
/ K3 o% I. ^7 _# w4 u# j8 a" X' P4 x2 c. N, Q7 n' ]
完整例子
8 i0 @" t* M( `1 ?2 }% f1 N9 k3 W- Qstruct C { int m; };
( f8 L! `! T2 X' n* H3 Zint main()
" j. G7 b4 t/ P' l5 U ^9 {; H{. ]2 t: @$ f+ i3 ^
int C::* p = &C::m; // pointer to data member m of class C
$ Y3 l2 S7 y9 b C c = {7};$ p/ G* p& u1 X+ x: a
std::cout << c.*p << '\n'; // prints 77 Q6 W& Y. b0 Y: n7 ~; Z
C* cp = &c;( I+ X7 K) h' S# o% ^8 W, \
cp->m = 10;
% j* G, h' C+ o# ]* o" E std::cout << cp->*p << '\n'; // prints 106 L9 G. N1 l5 V" t) b& i
/ {2 e& a5 A2 b+ z0 E" c
———————————————— w* ~7 g- h0 F9 `( m9 l
版权声明:本文为CSDN博主「南风fahaxiki」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。% |+ d& z1 ?9 K: z
原文链接:https://blog.csdn.net/m0_64407685/article/details/126788115
' b* o, c( I$ W" _. W( g9 z; K
& H* x7 I) B' V1 `: l5 l: b! U; T& K8 g& v4 W `; t" }# S
|
zan
|