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