- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 563258 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174200
- 相册
- 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 F3 ^" d8 a0 D/ l9 X# _8 W
C++数组指针、函数指针、成员函数指针
) ?! o+ p! c. \+ G2 ]3 }3 P( H
* D6 K- |/ y4 C4 B# n& a+ c, K+ U5 l9 j
操作符名称3 U! Q* H: [/ K: M- W
& 取地址符(Address-Of operator). D7 O2 U3 a; P/ {9 N. R
* 间接寻址运算符(Indirection operator)
e( k2 Z' p) X6 D.和-> 成员访问运算符(Member-Access operators),用于取对象的成员。
; e+ @! {8 @: R/ z.*和->* 指向成员的指针运算符(Pointer-To-Member operators), 用于成员函数指针和成员变量指针的取对象。
, a1 o5 g# U. @. [$ ] k() 函数调用运算符(Function-Call operator)
, j) J0 E( Y* X' O:: 范围解析运算符(Scope-Resolution operator)
3 x7 d A! W; I/ x如何定义一个指针变量! i1 a$ j. G4 W" Z) y% B- R4 h+ Z1 Q
假设类型T, 变量名称name, 指针的定义如下:
9 }! Z; o( D+ T J2 K
8 U! P: H) B$ s. DT* name;
0 s/ A |# l$ S2 G# t5 P/ L) `标识变量名字name, 它是T类型的指针。例如 V% L0 P6 i; t! k) F* o5 _
, L- Y5 w) \( z
int n = 0; U% G* M% Y* k5 M
int* p_n = &n;
5 X' N, j8 `! b6 dp_n是int指针类型, 指向某个int型的对象。2 ^' y4 a& m7 U) j: i. A: z; l
# F" H* e0 L9 c! ~/ ]指针变量的修饰
( x+ |) u. R" z/ i, E4 _指针实际上也是一种变量类型, 只是它保存的内容有些特别, 是指定类型的地址值,通过间接寻址运算符(indirection operator)*, 可以访问到指针指向地址上的指定类型。0 X. q4 `# L; e& d0 q
8 Z: ^( W3 i% r. b- _, A
指针也可以用const, volatile修饰。 const int或int const均表示一个变量类型是int, 且该变量不能修改。以下两种写法都可以:1 V e( B& \+ E5 t% X8 M5 a
& I( L d2 ^/ i/ d) econst int a = 1;3 ^9 T9 d( g$ L! R5 C
int const b = 2;
* O6 @. z+ @8 T8 n1 ]$ Z既然指针是也一种变量类型,同样支持被const修饰, 表示指针的值/指针的指向不允许修改, 指针所指向的那个变量是否允许修改, 那是另外修饰。写法如下:
* ^2 R& h" ~( z6 t
: S: O! P& q Gint a = 1;
# S) d* f5 ]# N! s$ g, b+ Q6 yint b = 2;
/ }1 @* H! t* {+ ^int* const cp_a = &a; // 指针的修饰词,放在*号后面。
; d8 Y- }$ Y. O*cp_a = 10; // 指针指向的值可以修改6 N& W) [# s0 D! T4 q
cp_a = &b; // 指针不能被修改,报错!/ ~4 J$ z/ D, C/ p6 {5 v
总结带修饰的指针的格式:! A9 L. f K& j" _" W4 w; E8 B
只要记住修饰词总是放在被修饰的内容后面。# z/ h& n G* f( _" F$ Q4 p
4 B: \& {. W3 z! b1 @. wcv表示const / volatile修饰词。指针定义形式如下:/ g# E0 h* e% _ D) A& s- D
' @, D# g! _7 c$ S' L) _; w
T [cv for T] * [cv for pointer] name
8 P& L4 H. f, ^* Y注意对T的修饰放在T的前面也是合法的写法。0 Z9 Y( s; M" P% U& K
$ W8 {2 w$ X1 p" E0 d. E6 S
const int const c = 2;. s; h/ L# F# b! y5 \. `9 Z, W
在mscv编译器下也不会报错。
w4 z9 ^: E. Y+ N! h9 K% t4 O7 y: C, V! l+ F
完整的格式:
0 m9 w, l9 Z( k9 q# t[cv for T] T [cv for T] * [cv for pointer] name
; D) f" e$ F E. Q [- n0 B
$ Y6 n. B9 k) A6 WSyntax meaning
2 z/ J& ^) V( |" |$ d& ^8 Jconst T*
+ ^$ j! ~" R6 u$ A6 E: Lpointer to constant object- k5 r( y8 c9 h* I9 d. }: x# V5 \
4 K% H/ T, k6 e" }0 a5 o6 r+ [T const* pointer to constant object
& S$ y+ Y" o3 b0 M7 B( cT* const constant pointer to object
2 E1 R+ {) P' q% r' z+ Q: wconst T* const constant pointer to constant object( A$ d; ?- a' g# P# e1 L2 v8 b
T const* const constant pointer to constant object/ ~! }6 o9 T4 ^
上面格式中T还可以是一种指针, 指针的指针仍然是按照修饰词总是修饰前面的标识(T或者*)来确定修饰的意图。
, E& ]" j: X- l" s1 W7 b x9 d5 l* e" X3 c* }, X ]% G
int a = 1;
' Y# D0 V- T5 n v) Eint b = 2;5 t9 c! b0 f" p+ j+ C
8 V) r4 D. u7 Q' y9 c9 s
int* p_a = &a;& ~) c6 ]# r* ]% C& y$ g* _
*p_a = 10; // 合法) I7 {3 z: `5 F3 b
p_a = &b; //合法( p4 t& ]( l! R( w. B/ H9 z. y
6 E l( \6 H5 oconst int* cp_a = &a; // const修饰int类型, 并非修饰指针
& D; K/ }5 c; f& p4 a( {; ~ f! F*cp_a = 11; //报错! const int类型不能修改
& r; J' K9 b1 |3 m6 y0 Tcp_a = &b; // 合法, 指针没有const修饰,指针可以修改。
2 L, f; C; _0 O) C( x5 r
, b7 ^6 {/ ~& w3 M6 ^int* const pc_a = &a; // const修饰指针。类型没有const修饰 Q6 ]. K( a; `4 }/ O$ s! R% v
*pc_a = 12; // 合法, 因为类型没有const修饰,可以修改。
4 H& W/ {& b7 y$ ~( ?% C- q8 Ypc_a = &b; //报错! 指针被const修饰, 不能修改指针。
8 ~: ~ Q" j8 N* y. o5 d* H2 q- C. p, M5 ~
int const* const cpc_a = &a; // int类型被它后面的const修饰, 指针符号*后面也有const修饰6 {" }; U7 U( ]& v7 j* ^
*cpc_a = 13; // 报错! 类型被const修饰,不能修改。: ?: y$ E, t+ V4 f; p
cpc_a = &b; // 报错! 指针被const修饰,不能修改。
1 z+ h' M, v5 x0 s+ Q$ g4 M% }; F2 E! D) g# C! x4 ^7 Y
更复杂的指针的指针1 U+ X% H9 G0 n* M+ R# c
' D& q6 w! \* E, A; o+ B, A& E7 s6 ]
int a = 1;
' q* J. t3 F$ D# O3 n) |int b = 2;
+ N* V* j5 E8 w( ` Q5 ~int* p1 = &a;
& h( ]- C5 W _- r% lint* p2 = &b;
+ ]" j- r$ g/ N Y8 |: O5 lconst int* ct_p1 = &a; // ct for const type
' b! u C& o0 P8 U: m- {/ qconst int* ct_p2 = &b; // ct for const type8 F' b9 q: N" @! [
4 i2 A# y i* m1 k- e
// int * * pp1; 指向(int*)类型的指针" M3 L2 X5 j+ K5 \$ v' x
int** pp1 = &p1;
% U7 b( v! s- _% g- t6 zpp1 = &p2; // 合法,
h6 d7 Y0 A+ X, Ypp1 = &ct_p1; // 报错! 类型不匹配。 (int*)不能指向(const int*)+ |& p5 @; `7 S1 ^, C6 }& k: f
3 p) s( l" f, [: k6 j, S
// (const int) * * pp1; 指向((const int) *)类型的指针
- b1 Q" `7 b: U- @const int** ct_pp1 = &ct_p1;
) a. H/ M1 I% I4 A4 }9 m1 T! k1 [! Wct_pp1 = &ct_p2; // 合法
- {5 _$ D( y- U3 ?# Fct_pp1 = &p1; // 合法!(const int*) 可以指向(int*)类型。1 G! |" [& x% ~# C1 ~' V# x+ c2 O X% U" U
3 B, F7 W! e4 A) @% |// (const int) (*const)" `3 l' C/ B% V: D L% K
const int * const ct_cp1 = &a; // 指针也不能修改
y4 }1 S3 [. b0 s3 d4 N+ f I- @4 qconst int * const ct_cp2 = &b; // 指针也不能修改
0 N' ]0 F4 _1 Q7 e$ O( w+ vct_cp1 = &b; // 报错!指针有const修饰/ `2 h% ^+ q# E6 S5 E+ k
* ]: D/ X" ^6 H& l- F* @/ ]$ h+ i// (const int) (* const) * 指向((const int) (*const))的指针
; t4 W/ h% T& P5 R5 P! Q6 oconst int* const * ct_cp_p1 = &ct_p1;
2 J3 `0 W5 A9 J& t6 Rct_cp_p1 = &ct_cp2; // 合法, 指针的指针并没有const修饰, 指向的指针有const修饰5 Y. r9 n" Z4 j8 v# h
*ct_cp_p1 = &a; // 报错!等价于操作ct_cp2, 指向的指针是带const修饰的不能修改
, L0 F9 b* o d9 U2 j4 _
5 _$ A) k$ h( D; E. K- _0 J4 n// (const int) (* const) (*const) ' `) E0 v0 ~& i+ C! Z
// 指向((const int) (*const))的指针,且该指针被const修饰
% C; i8 X) R4 zconst int* const * const ct_cp_cp1 = &ct_cp1;
3 y" p, W. N, \8 p! Y/ G }& Vct_cp_cp1 = &ct_cp2; // 报错! 指针的指针被const修饰, 不能修改指针指向。0 T" ^5 n; w) k
( P& A7 ^# J% {3 x, ? W% L一行声明多个变量' o6 l8 I5 J& F* }( ~9 R
类型 + 名称定义一个变量。
. g0 P4 \1 Z8 e: {; f变量的前面可以加*号修饰, 表示指针, 一个星号代表一层间接。**表示指针的指针。8 w7 `* F6 s. k0 h( I0 V h
P( x9 _/ X# q- i) e
int a, *b, *c, d, **e;
- [; o+ @9 H) na = 0;1 j, A$ b; }' i9 ~$ g: o) Q0 ^
d = 1;
- S6 e/ i% V E2 d _b = &a;
3 N# [+ U; _- L$ D, ? q9 D. pc = &d;
' P6 P9 y9 D" W* |( A# w7 H3 Y4 Be = &b; // e为int**类型 指针的指针
$ R3 x1 z7 M0 r5 |- Ge = &c; // e为int**类型 指针的指针5 w) d I& d; \0 e( O; A/ B a
也可以用括号包围变量和*号。8 I5 m, S, E* w
) l/ G$ f) S$ f1 n0 Z
int (a), (*b), (*c), (d), (**e); // 合法定义。
/ P! J; ~. S6 h) A$ _* w括号可以省略,某些情况, 个人感觉加上括号更清晰一些。例如
9 r) O ~* ?& G1 c' O, T* e1 e8 T" a
int (a), (const *b), (*const c) = &a, (const d), (const* const* const e) = &c;) G5 I: a1 O$ t3 X* _; S
写成3 N4 V& G) V* x! ?/ n
|& G+ @& v" v8 H! K6 a
int a, const *b, *const c = &a, const d, const* const* const e = &c;( t+ `5 g# B1 _# ~* q$ E& J+ @
更重要的是, 后面我们表达数组指针,以及函数指针时,括号是不可缺少的, 带括号的表达更加统一。
& u/ B4 U& ~# k0 @, h: i- b8 {% V, o- \- E- K. @
数组指针
& ]% n4 v3 k, e- H& S5 w7 |) P数组基本表达
9 E. S9 Y* c' A$ l" h- j7 M7 `4 ]int a[10]; // 定义了类型是int, 元素个数是10的一个数组。5 S, c% v, l% z- m$ I( m$ E/ I
由于c++要支持一行定义一个类型的多个变量。 所以数组的[]时放在名称后面的。虽然我觉得
1 S* d' e" r; r9 Y
, D% F; n8 A2 t0 o# _, F: \ kint[10] a;0 A! W2 `. D6 X
这样的写法更符合类型 名称的思维, 但是如果类型都这么写的话, 没法兼容以下的写法:
9 B8 m/ s" F3 L9 [# ?; V( r7 a9 Q/ X, P$ \4 Z; O
int a = 0, *b = nullptr, c[20], **d = nullptr;. x: S! n% Z% Q
c++标准规定如此,但我们可以通过每一行只定义一个变量的写法, 类型会更加清晰。0 M! k. C1 [' m0 R- s
3 O% s0 `9 e% o
int a = 0;+ u6 m9 C6 r0 y' y& x
int* b = nullptr; // 指针int*, ~% Z. H/ y e, ^9 q
int c[20];
% g$ f3 A$ k8 Z' \& `, zint** d = nullptr; // 指针的指针int**
( q- z3 E/ s" [0 V数组的名称是什么类型
6 o( n/ E$ _5 S% l数组元素类型的指针,可以直接指向数组。 并且数组跟指针一样,可以通过下标去访问元素。: t% j# B' V7 B8 t, }5 H
. P) F3 ]* w# i+ gint a[10];2 ^0 j+ y* w9 y2 E
int* p = a; // 指向a数组的第一个元素7 Y# G% _2 g5 w2 ~* e
a[1] = 1;% I- ^9 j9 s1 A5 Q! l
p[1] = 1; // 效果与a[1] = 1一样。
) o6 ~" l/ x; S数组可以当作T* const来使用, 但是又与T* const有些不同。sizeof()的结果不一样。
6 U L" x% ?9 W$ V6 X( J; n) c
0 J i3 |' ? Dint a[10];
! ?" E! Z# N' x. ^' d) x2 ?4 gint b[10];7 v, _' t- }+ {- M* c) ]( J, F( @
int* const p_a = a;% N1 _6 v+ e5 i T, \& ~
a[0] = 1; // 合法。 数组的元素可以修改。' S# r5 H! O" e% b0 }
p_a[0] = 1; // 效果与a[0] = 1一样。8 t1 Q) L' A6 P* X: A8 H1 ]! {3 e
[% X1 T* e1 Ka = b; // 报错! 数组本身的指向不能修改。
) [( u/ F! v9 ]* K& o! V
/ [" X& [) Q J7 u8 o$ Y// 所以数组a可以当作int* const来使用
, o, m/ H6 `7 o! c1 N% s) jint *const& ref1 = a; //正确。7 ?) {& ]: `" ?8 h9 r8 q- A7 ~ h
int *& ref2 = a; // 报错!3 ?( i, R% k8 L5 `" ~# a
- S2 X8 ?& z" `// 但是又跟int* const有些区别。, b5 j! [8 a7 R" V7 N
assert(sizeof(p_a) == 4); // 32bit程序。
2 M9 q3 `4 Y9 e# x" l3 eassert(sizeof(a) == 4*10); // 32bit程序
& V5 K* y- ~9 K" Z2 a( l: @
1 O6 ^+ Z$ o( O! g/ K数组跟元素指针的作用很相似,都可以通过下标去访问元素, 但调用sizeof()函数的结果不一样。元素指针的sizeof()返回值是4(32-bit应用)或者8(64-bit应用), 数组的sizeof()返回值是数组实际占用的空间。数组可以当作指向第一个元素地址的T* const来用其实就是我们常说的数组到指针的隐式转换。当数组作为函数参数传递后,会自动退化成T* const, 在被调用的函数内部调用sizeof()的返回值跟T* const指针大小一样。 数组传递作为函数参数后, 在被调用函数的内部与T* const是没有任何区别,只有在数组定义的可见范围内sizeof()才有获取数组占用空间大小的效果。
0 F8 t6 m) W" t$ X6 t
7 D( u) t8 }" A以下3个函数翻译成汇编以后,汇编代码是一样的。
3 t0 Z5 v0 N7 E4 b, h) f; p) M( _/ Q% k, Q! O+ V
void Func1(int* p_ary)1 d2 c5 u M+ `$ E' s4 k$ b( V3 D
{2 p4 q) {4 J6 i0 \1 V/ R& i* [$ p
assert(sizeof(p_ary) == 4); // 32-bit$ h! k" J" R3 g: p, I/ B
p_ary[1] = 1;. P2 O* Z/ Y) G" \0 o, I5 l
}* z4 L. M; G3 q
4 N' T( x8 P% } `- E
void Func2(int ary[])/ R: [5 y/ U3 A; I/ P4 f
{+ b$ F A) O3 a6 T0 `. |& ]
assert(sizeof(ary) == 4); // 32-bit
2 k% ?1 k4 x1 G& }7 q' T( y) r ary[1] = 1;) a; P* M3 B& E. N) X
}
$ X; [( }- v9 u; r' f+ ] W7 C( ^& v4 f/ S. D6 Y$ E9 L2 g' N% w
void Func3(int ary[10])
2 |( b7 O( j& Y) G- m+ ^; {2 d{2 D5 Y# \) ^! @7 V8 N
assert(sizeof(ary) == 4); // 32-bit' r1 k. M/ V3 u% d0 K9 V, a! N% [/ ]8 L
ary[1] = 1;
* N0 v. \- _/ a" o* L2 {}2 i( Q. r; I! Z
- R7 A- q0 s' K5 q$ J
int main(int argc, char** argv): W. t+ R7 S8 j! K% P( z9 {
{
4 `8 a/ d' O# Q( {6 [5 l int a[10];
i, M% [+ c9 e1 q7 z' | int b[20];
* K7 b; Z8 U$ {8 g; X$ z Func1(a);
H( y8 r7 Q- o Func2(a);
7 v9 J+ V& v' P! r+ D( m; ^+ _8 K: A Func3(a);
, I6 p$ s* Z. N. S$ B0 u* E7 a Func3(b); // 退化成int* const了, 即使数组长度不匹配也不会报错。% m" a0 D9 p' Y) A4 m: M! H
return 0;; [9 n* d( m: ^7 c0 j Y& }
}& W4 l9 Y+ V U1 }0 L
+ N% d* ?0 P$ J7 u
* s. W U5 @% {+ ~8 w2 [
$ }* v( x g$ y! ^; V7 I3 G多维数组
. g3 @6 |; `' I3 v( W$ N* a1 q) J一个3行,4列的数组, 结构如下:
( L9 I/ V7 Q: T: `( M
! \' q `4 G) V; x: t' X" X4 N/ Qint a[3][4];" v% Z4 m9 o; L5 l8 R5 R8 L0 R
column 0 column 1 column 2 column 3
/ A8 s1 \( o2 j" ]+ crow 0 a[0][0] a[0][1] a[0][2] a[0][3]& M5 U0 y2 @& T$ h. R$ s
row 1 a[1][0] a[1][1] a[1][2] a[1][3]3 |- w9 c$ _& `6 I+ q/ h
row 2 a[2][0] a[2][1] a[2][2] a[2][3]
# p, j) W& N7 `8 q, |; t1 _数组初始化
$ c2 B- d0 l% w$ C" U9 i* j$ y
( C8 Y+ B4 w( qint a[3][4] = {
4 T) {4 D/ v5 f) P8 x+ q& a, V {0, 1, 2, 3},* D# E" N) K$ h7 y6 a1 m3 t* c
{4, 5, 6, 7},) c9 G2 \- n1 q* o5 m) R
{8, 9, 10, 11}
: L. X3 L$ W8 o* I};
& [3 g/ S9 i0 ^+ k. p 实际上多维数组和1维数组在开启速度优化后,翻译成汇编代码是一样的。2 ]7 |* v8 z0 H/ H( r( K
$ W& ]" v( v# y
void Print(int* p_ary);7 e+ n' F B( l8 d
( H+ q7 x5 q$ P, u9 d& Svoid Test1()5 \4 H! U3 d& R8 I7 d4 w" v
{- {+ R; w6 H. r2 @
int a[10];2 `5 E" N% o" F
a[3] = 3;' i. l1 E6 u% ^! q( O
a[7] = 7;
1 j' e8 z" {4 l" u* L Print(&a[0]);. V4 c' l% v5 Y/ T0 U3 f
}6 l; y; V: {' m( g$ L1 e
1 h1 h4 r& V8 qvoid Test2()8 {! @: b! D4 ?4 b& G, h6 f
{& A) s3 t8 h' _; i' K, d
int a[2][5];$ @ g7 n: L+ N( M" }
a[0][3] = 3;
9 A5 F% {% j3 [& y& ~ i; s | a[1][2] = 7;; P+ A3 [1 Q7 @5 M/ F# m/ x9 X
Print(&a[0][0]);
9 l; t- q' p6 E& u9 z4 {) F9 t( E2 j}
6 _/ v1 {+ c* ` _" N* e9 I9 S" R+ x* y' N' q8 X: D. C. c: k
! k8 ?4 u7 U! n3 I; N n T6 C1 [: D( S# j( ]
很自然地,多维数组也支持用1维数组的方式去初始化。- f: W. ~ ] L3 _
3 g' v; A1 N* V% z- {/ ?# o+ \8 Qint a[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
' G/ _& J- _+ p0 T, X# ?既然多维数组与1维数组没什么区别, 为什么还需要多维数组?
0 q) J0 C% @% m( u( r F$ \* F; U' a
) B! v+ V2 w4 | ]假设有一幅RGB图像720*576个像素,每个像素有RGB三个通道,每个通道的值是8bit大小。给出图像的首地址p_rgb_image, 我们要取第40行,第50个像素的R,G,B值。代码如下:
. J! a8 U- e$ K- `; X# ~4 S; ~; S8 F( |
unsigned char* p_rgb_image;
8 F$ B( M3 G! n' Sunsigned char r = p_rgb_image[40*720*3 + 50 + 0];
/ v. R# N5 M! ]% \7 B8 t- O runsigned char g = p_rgb_image[40*720*3 + 50 + 1];' {2 U4 {( ^& M0 z, Q8 ]% f
unsigned char b = p_rgb_image[40*720*3 + 50 + 2];
% ~% b3 u9 C( R" T$ W) ] i* x( P% }类似这样的场景, 采用多维数组的写法, 有点类似以索引为参数,可读性更高。相当于程序员和编译器打了一个配合。
1 a# i4 K# s2 S! i2 Z/ F+ Q# d ~% V
enum$ j2 H0 A# D+ N- l) G
{! O7 c! o/ O. }
Red = 0,
" ]% c3 u9 `9 V Green = 1,: Y# j/ T, u+ a a* F8 m$ l: E
Blue = 2) X5 L: e+ q; W2 q8 h1 G6 R
};, l! z" G4 R3 t8 _
unsigned char rgb_image[576][720][3];9 J' \8 H% z4 }& ^
int row = 40;
* o) H: W9 x" j [7 \" iint col = 50;
' S \' E/ U" \- Eunsigned char r = rgb_image[row][col][Red];
9 a2 `* L) z6 Q# Nunsigned char g = rgb_image[row][col][Green];
, L% M+ H+ a% R7 X- munsigned char b = rgb_image[row][col][Blue];
9 X, K' v0 c7 t' a* {# E$ O' R数组指针以及与指针数组的区别
7 `0 |# q* _. Z数组指针,是一个指针, 指向的对象是数组。 数组指针的赋值,要求数组的长度匹配,否则会报错。当指向1维数组时, 需用用*取得数组对象的引用,再用下标来访问数组元素。
9 Z% ?' c" y5 ~% S0 \3 l指针数组,是一个数组, 数组保存的元素的类型是指针。* P, u4 h- u4 ~! S2 Y+ x2 K
数组指针的定义
* Y: i! \! Q; j+ K9 C% r0 \& |% d1 i数组指针定义先定义一个数组。. _# U! o+ q* _! c. `+ q2 z: Q
int a[10];6 Y0 k. @ u! R2 R. t) x
然后对数组里的名称用括号括起来后再在变量名称前面加个*号; l5 d* S. @3 M2 I1 i$ u1 i% _
int (*a)[10];
7 j: s' E3 }0 N- o* ]: V& R后面你会发现函数指针定义类似。
8 J3 `# b4 q9 _" N// 各类定义对比
& B+ Q4 j4 y3 j% }/ W: m; ]- i( qint a, *b, **c, d[10], e[10][20], *f[10], (*g)[10], *(*h)[10];# ]9 {5 J8 f4 x3 o( c
5 h; V& b! n$ J1 E
int *f[10]; // 指针数组, f是包含10个元素的数组, 数组里每一个元素的类型都是int*
) Q/ U; F' S* eint *(f2[10]); // 指针数组。另外一种定义方式。- v, Q! ~) N6 p% q3 N( V
int(*f3[10]); // 指针数组。另外一种定义方式。! H8 m1 T! g/ m. m6 n
int(f4)[10]; // int数组& s- W3 S X% R% c6 k1 s2 X
int(*g)[10]; // 数组指针, g是一个指针, 这个指针可以指向类型是int,元素个数是10的数组) j$ l1 q" i$ y8 r! f. Y
int* (*h)[10]; // 数组指针, h是一个指针, 这个指针可以指向类型是int*,元素个数是10的指针数组
6 \$ e8 y% i5 F7 G
8 b. c3 c5 }+ V& ]/ |- u# t5 [int d[10];6 V9 g! U: f+ @" O6 Y9 l% @
g = &d;
: z1 y1 p3 x5 L; n5 Z" b8 ?0 z1 [( ?! M2 ~6 F8 c
int* e[10];0 l3 Y8 ~' s% T/ s7 g6 W) v
h = &e;: D7 J. |6 y6 A, T. f3 q; A
数组指针的使用
0 w* N: x, k+ _) A; m数组指针一般先通过*号取得指针指向的数组对象, 然后再用下标操作访问元素。
+ n) T z7 d5 a4 K+ G6 W* _
& Z2 c" ^- [/ X; s" G: |$ Cint a[10];
. R, o2 v" c+ O2 a1 _: aint(*p_ary)[10] = &a; // p_ary是一个指针, 指向"int (*)[10]"类型的数组
$ T8 W7 t9 n3 W7 Ufor (int i = 0; i < 10; i++) {
4 i: N, d$ J7 t2 w! o2 t, d // p_ary是一个指向数组的指针, 需要先通过间接寻址运算符*(indirection operator)取得数组对象7 W5 Z( Z D: i* ?
// 再通过下标操作访问元素。& N9 `; ?0 f+ W1 j3 Y8 g
(*p_ary) = i; ' {7 O* h. m4 Z! v* X
}6 }8 x" q3 h, i3 I0 {& H
1 Y n5 I; N! {- ~: d
int b[10];
M, V- i" l/ rint c[20];6 o" k$ E- d4 t+ U8 H/ Y: P
p_ary = &b; // 合法* J/ f- c; e& }
p_ary = &c; // 报错! 不能将 "int (*)[20]" 类型的值分配到 "int (*)[10]" 类型的实体9 q+ d- D$ n: T4 O! n4 g `# p6 l
数组指针指向多维数组的子数组. W; d% c9 e6 Z, [4 t: ^
int a[10];: L: l3 I9 H& a) V0 b# m! X
int b[4][10];
6 D( {9 g5 \; z8 Cint(*p_ary)[10] = &a;
8 t" L/ Y7 L* X: Mfor (int i = 0; i < 10; i++) {
$ R( U6 r- f; t9 ?7 Y (*p_ary) = 1;
' a$ v: m5 j( O; Q( W+ l}
9 P+ e& j, T( [0 x. k
6 ` A! I9 }- a& I' u8 \3 cp_ary = &b[2]; // 多维数组,可以看作数组的数组,3 Y4 F$ ~& O. \, r
// b[2][0] ~ b[2][9]的值都被改成2了0 M0 `) h, E- E/ C5 x
for (int i = 0; i < 10; i++) {
& ]( _" H' Z# D0 I (*p_ary) = 2;
. V5 V# k& J( z* j1 w}
7 k" b" a: p' A, |) j1 X多维数组指针
9 z9 G9 F. I* z多维数组指针,是一种指针,指向的对象是个多维数组,支持多个下标操作。- Y t* d9 w4 e/ T5 ]0 J
- t2 b' {7 `* Y. s8 k2 X6 x+ `: K
int a[2][5][10];
I- G) Y* w0 }5 x6 g9 m, Tint(*p_ary1)[10] = &a[1][2]; // 1维数组指针+ F' H. c6 O/ k6 P7 s% P
int(*p_ary2)[5][10] = &a[1]; // 2维数组指针) n! O$ V" {# N% w6 W! P% p) F% f
for (int row = 0; row < 5; row++) {0 D) I- W" F' E( D ]3 w' u
for (int col = 0; col < 10; col++) {& t, s: P8 L: F0 b7 ~; e1 I1 l- \
(*p_ary2)[row][col] = row * col;( X( G8 F' ]7 A0 w3 |) L
}
! j# }1 D P; H7 l}! c" i7 k4 V/ s
数组指针和指针数组对比实例. `7 Y% S+ {9 A$ t! m2 b" e, C
数组指针还是记住两步法即可7 x3 C' y7 j2 m9 y* M
1 o4 X" ~' l( m. r0 [定义一个数组
1 t" a o) z6 P/ \4 X1 g括号包围1中定义的名称,再在名称前加个*号。
; u3 l s1 D% d s% c- P" E7 oint a[10];7 `$ M; L- w4 x8 p/ w, }
int(*ary_pointer1)[10] = &a; // 数组指针, j% b& ]: ^6 G
int* pointer_ary1[10]; // 指针数组。元素类型是int*' B$ O7 ~& |2 K! D# q8 b1 ^
int *(ponter_ary2[10]); // 指针数组。另外一种定义方式。* ]5 {. `1 D0 [8 o& b6 b4 F( o# @
int (*ponter_ary3[10]); // 指针数组。另外一种定义方式。
+ {2 l& `+ g0 Nint c, *d, (*ary_pointer2)[10], *pointer_ary3[10]; // 排列定义比较。
4 }% i' p7 u* J2 N- q% F9 ^
! c) j! T" R% ~" H+ y& j4 |3 o; o// 指针数组可以把每个元素指向数组对应位置的地址。, E1 H5 d; g7 R5 {
// 这样遍历指针数组, 可以达到遍历数组元素的效果,但是注意每个元素都是指针,
5 R+ O3 A2 N4 i: s1 @* [# J/ v// 需要访问原数组的值的话, 需要对指针用*间接寻址运算符。' J8 k4 L1 Z# C3 ^+ \) m+ F |
int* pointer_ary[10];
& T3 v/ y* H0 W# O/ U' lfor (int i = 0; i < 10; i++) {
1 o1 `1 z7 ^- Z4 q: C) Y) A pointer_ary = &a;
( ~$ ]% n8 F8 W/ v3 y/ H4 n: s}
) U. P" k6 I% A$ Q1 `6 l// 类似遍历原数组效果。: Z8 O5 j2 L6 u8 d- B$ N
for (int i = 0; i < 10; i++) {' S$ j3 z& Q$ _ e
*pointer_ary = i; // 修改原数组。/ r5 A# `$ P* {$ f
}) l0 `, F6 ]9 I% j0 |9 w
$ b' E/ Y$ a$ |5 K, I
函数指针4 N: J; C2 A3 G% Z. g- f
取得函数地址
4 \ a/ N$ G ^- ~+ a6 ~- [函数的名称作为参数被传递时,会隐式转换成函数指针, 和在函数名称前加取地址符&等价。建议带上更加统一和清晰。! v) a8 P; V/ z* t" }5 z
+ L1 {9 M: k# y5 i
void f(int);! Q" p+ C' F4 z! w+ ]0 a
int main()
5 X5 ^) h9 |+ `( S# f{
, U: O1 u: R" E8 z1 M void (*p1)(int) = &f;" W7 k+ B) q. z
void (*p2)(int) = f; // same as &f
. _$ _7 h% X6 C' u return 0;6 J: R: a D( _0 f0 w% \
}! X+ p! d3 m) X! ]/ z! e
翻译成汇编代码, p1和p2的赋值是一样的。
9 h5 Y# g% l0 F* a+ I% P, v* z7 @5 }$ n- V5 O0 W0 d; v
3 y6 v0 E6 E, I. R& T
' M/ ?+ P4 x6 L2 z# g
函数指针的声明+ `" x$ a+ [# ^
单个函数指针变量定义步骤: @, W5 y) O* J4 S0 L3 e) B& n3 O
定义一个函数。void fun1(int a, int b); int fun2(double a);
# Q M: a' h; _ z( Z7 s用括号把函数名称包围起来,然后在名称前面加*号。void (*fun1)(int a, int b); int (*fun2)(double a);! g; N+ X% p0 u5 d: P* z% t
如果要定义函数指针数组,在定义单个函数指针的基础上,在名称后面加上[数组长度]void (*fun1[2])(int a, int b); int (*fun2[10])(double a);
$ n% A% S* |& W+ i4 I2 j atypedef定义函数指针0 X* ?) Y& f6 Q1 S4 W) a
可读性高比单个定义要高,特别是声明多个同类型的函数指针,或者函数指针数组。
- }: K3 t6 Q9 V: C2 H5 F
U% H' x. ~* M# O% b) \9 I7 w: @' Ytypedef定义函数指针的语法0 o* G$ k6 b* r2 n( F* {
typedef有两种做法, 一种就是定义一种函数对象,另外一种就是定义函数指针。用法稍稍不同,效果是一样。其中函数对象不支持赋值, 但是支持引用。
$ g: ^1 |% O; q5 ~3 y/ v, P) h# Q; L# E; W
typedef int FuncObject(int a, int b); // FuncObject类型是函数对象4 M6 s; | F1 a {3 E& b* u
typedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针+ k! N. A- ]5 X6 u6 r
FuncObject* f1 = &Add;
6 k& V# i1 B1 `* GFuncPointer f2;
" _* Z4 o1 D+ A' o1 Af2 = f1; // f1, f2类型一样, 都是形式为int(int, int)的函数的指针。! P! l2 U& _, e5 F
FuncObject f3 = Add; // 报错! 函数对象不支持拷贝
5 O' G& [( @7 A9 EFuncObject f4 = &Add; // 报错!&Add是函数指针,与函数对象类型不匹配/ G$ O2 |! ]! ~- e! a! Q1 j
FuncObject& f5 = Add; // 正确
! |0 x3 Y, \0 `$ yint ret = f5(2, 3); // 正确) d3 z7 ?% G4 k6 M& ~& k
FuncObject& f6 = &Add; // 报错!&Add是函数指针,与函数对象引用类型不匹配9 G9 u% {) v* l/ s7 N; ?9 }
如何记住typedef定义函数指针的步骤
- g; |3 v. `- A6 V. P3 {9 b6 \6 f) g像定义一个函数指针那样, 指定一个名称。int (*CalFun)(int a, int b);
' o5 c' [+ L' @% J8 R在这个函数指针变量声明前面加上typedef。typedef int (*CalFun)(int a, int b);! s" i! Z) z; H6 ~
完整例子
; [" C& j5 @7 qtypedef int(*CalFun)(int a, int b);' q' w$ n9 e" g6 D% l! @ v
$ A1 F: {2 K E# W
int Add(int a, int b)
; A) B5 P0 N! V9 `- Q{
F( N$ B& b5 M$ ]7 H& q; s return (a + b);3 \; |: q; l4 k! P
}- L9 _% u) \, F7 \: Q
- w& C$ O) ~* o2 h9 u
int Sub(int a, int b)
9 d: ~2 z3 F/ o* v{7 O) L3 N- l2 L- T
return (a - b);
/ L: x% F' M: N: J}
: ]7 x2 }& e! L0 u, G
0 a, B/ Y. `6 `& X% ?8 nint main(int argc, char** argv)
& Q! t( y2 d) B3 b; U) h# i{" D# }$ N0 B6 `8 e" F' y
CalFun f1 = Add;3 F$ M. \9 `, i* v) l* M
CalFun f2 = Sub;
. O3 g( h" p2 @$ A$ k int a = f1(2, 3);! N. c% |' c; [6 I
int b = f2(10, 5);4 R; I8 ^; m4 k7 I- j# i& X Z
& L4 _: K8 H6 K6 I- p/ K$ ?! Y+ [
// typedef定义的函数指针数组。
5 J, W0 T) e9 z CalFun f_ary[2];4 X) G2 o7 j' C; ^6 g
f_ary[0] = Add;& m- Q3 c! e: K
f_ary[1] = Sub;' ~1 n% V0 {) u7 X6 K
/ A. V6 n* R8 }) ^5 o$ C // 单个定义的函数指针数组。
) V, o7 V+ n7 m* b% j1 U int(*f_ary2[2])(int a, int b);
i4 I. v$ S# ^$ ^6 q8 X+ t3 e: B f_ary2[0] = Add;
) ~3 H0 j0 [* J- M, D f_ary2[1] = Sub;
- w* F3 \, m+ q
# T' G/ p5 b7 W0 i: A2 l" ` return 0;' a1 T% }4 ]* h. [, q
}% [& H2 a3 A# r- e2 c! h
& n2 J0 ?! b4 T! A( q% m' x$ o, |using别名定义函数指针
$ o7 O1 k6 ?0 a8 d% pc++11以后的类型别名定义--using也可以用于定义函数指针, typedef的好处它都有,个人感觉比typedef更直观。using类型别名同样分函数对象和函数指针两种方式。, f4 E, i9 i: e! Z' H
\5 C# f& V) m" H8 ]+ r
typedef int FuncObject(int a, int b); // FuncObject类型是函数对象 d$ H! U3 l) Y4 @& U7 u
using FuncObject = int(int a, int b);
& O6 {' u1 f4 t* Dtypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针
1 O, l0 ^, r' d2 `2 T1 @' S4 Busing FuncPointer = int(*)(int a, int b);
- ?0 N4 m4 _' W7 H, o+ ^4 I函数指针的调用' k4 r9 ]: d6 R6 b$ ~7 h
函数指针和函数对象都可以直接后加括号调用# U, O" p& x/ P. D( V# d
int f();
9 P: v* m) I$ F3 e, [3 xint (*p)() = f; // pointer p is pointing to f
# Y0 s# |. \% Y, e9 yint (&r)() = *p; // the lvalue that identifies f is bound to a reference
: E; t2 L9 W: d, pr(); // function f invoked through lvalue reference
+ y8 R& B7 K& L) S(*p)(); // function f invoked through the function lvalue
0 f- V) [( z! v( qp(); // function f invoked directly through the pointer
- v& N* S0 d8 V) _# i0 h& M: {如果函数有重载, 函数指针会指向匹配的那个版本。& g) X8 c: i: \/ Z) O# T0 R
template<typename T>
Y. U* ?# Z& k9 L( ^! H, K( Z6 |+ ^T f(T n) { return n; }7 ` R! D, S) T( D6 ?5 t9 E
2 I) j& d0 u1 pdouble f(double n) { return n; }
4 `# C+ L0 t/ P- e0 y1 h* u, g+ a% m% V2 ^" ]3 D6 X, J' g q
int main()
5 v5 m5 m# e! C( d3 ]{; {% O9 L6 j- ^5 Q
int (*p)(int) = f; // instantiates and selects f<int>; M, z0 k* c2 e' B9 _
}
: L# ?: }0 d' o# I8 ]. C! E$ u成员函数指针* k) ^+ T/ t5 }& \. s& p- G
静态成员函数,除了增加了访问控制以外,跟普通的函数指针没什么区别,所以普通函数指针可以直接指向类的静态成员函数。但非静态的成员函数与普通函数指针不太一样,声明时需要指定函数归属的类名,并且调用需要指定对象实例。0 H% ? {( [6 D, k
1 X2 L1 I8 F, S B
成员函数指针定义。
0 `1 v H2 e- j4 O9 r$ t像定义类成员函数实现那样写, 并任意指定名称,这里作func。void ClassName::func(int);+ j" g2 d+ Q5 }7 t/ Z
括号把类名、范围解析运算符::、名称包围起来。void (ClassName::func)(int);0 S9 H5 R2 D* o) c- r \& Q
在名称的前面加个*号void (ClassName::*func)(int);
' j- |% F6 H& `5 B) I成员函数也支持typedef和using的定义方式。typedef void(C::* MemberFunc)(int); using MemberFunc = void(C::*)(int);
$ F& A. P0 Z2 L$ z4 u成员函数指针如何调用。
' g4 p" W2 ]( w9 K假设成员函数指针名字为func: o2 d7 e0 W0 j1 _
j5 M5 U" |' c5 t' ? r: B3 |void (ClassName::*func)(int);: z6 \6 e$ e9 p7 L
对象式调用。- x F, k' C/ ?$ Y4 q3 }
ClassName c; // 被调用的对象
2 e' W( G' \. ^/ E成员函数指针名字当作正常函数那样写。; `* [/ D% h* V0 k/ Y
c.func(3);
* o n- Q3 s+ O5 a6 W成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。' D& d1 E7 @- d/ F) b4 U
c.*func(3);+ ^: a3 F# i- R0 l( R- F
最后用括号把调用对象、成员访问运算符.、间接寻址运算符*、和成员函数指针的名称包围起来。; n. z, Q2 C" |7 T9 l+ p
(c.*func)(3);
$ Z+ j2 c! {# l- @2 B为何要加上括号? 根据c++的优先级标准,取成员运算符. > 函数调用() > 间接引用符*。 *号优先级比函数调用要低, 成员函数指针还没取得对象就被调用了,自然报错。 另外3 Y a7 k, ]5 y/ G$ \
(c.(*func))(3);
; E) C( c: L; B: f- f5 W4 f' H这样的写法也不行。 .*和->*是整体作为一个运算符的,中间不能用括号隔开。
5 b! J9 e$ G3 }: U7 G, m指针式调用# n( L/ @, {+ I; h' t% p9 [
ClassName* p; // 被调用的对象的指针) V7 h% J' m- K' {$ g: x! x$ w# j
成员函数指针名字当作正常函数那样写。2 I# M6 N3 o8 ~1 @/ g
p->func(3);
( U7 V7 I w, N! }; N成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。
" p9 Q8 k$ q `: C6 {p->*func(3);
/ s+ R9 `1 q. q- |' A5 T R最后用括号把调用对象、成员访问运算符->、间接寻址运算符*、和成员函数指针的名称包围起来。
) c$ @6 s! v2 i3 l3 [(p->*func)(3);
7 h0 o# I |( e7 C函数指针使用完整例子# z0 J) v5 v! J* U! d* G: Z' v
struct Cal 5 C: E$ p+ |4 h, X7 r8 a
{ 2 M( {% Q5 \, O- D0 u
int add(int a, int b);
& I; L H) g) o" I" |7 {. X# c int sub(int a, int b);+ d7 k0 M( R' e. M& B6 |% Q
};
5 o0 t# H. @1 p1 w: ^
4 C1 |# S. d1 d; J! q/ M: O% t \int main()
, Q- x* p7 [% N/ i0 C) c{7 k/ i1 _8 H; l: H8 N% S) W
int (Cal::*fun)(int, int) = &Cal::add;
: n; O) n$ G5 _/ U) Z fun = &Cal::sub;# w' Z+ `* r. ~7 J) s3 x& b
7 x! g. m0 Z$ H5 @# ]2 b; U
Cal* p_cal = new Cal();% T {9 Y1 u1 F ]
int r1 = (p_cal->*fun)(2, 3);
, v7 d- l: w# T) Z+ z# s s6 p delete p_cal;( X2 |8 Y% }, g$ f2 h7 W) X
+ a, O$ W2 Y( G7 Z
Cal local_cal;
; {: B$ F8 s. ~, V) x int r2 = (local_cal.*fun)(8, 6);$ v2 o; @/ s3 B5 ? d
}) ?; R" R6 Q+ z/ ^5 W6 l
0 E: x1 ~/ M2 t3 g# n3 w成员变量指针
2 Y6 U' X2 W1 Q1 z* Z. G6 d7 a, H成员变量指针比成员函数指针还要简单些,没有函数调用, 无需考虑函数调用和间接引用符*的优先级问题。
3 E6 t, H3 K) x
7 p: X' g* a! u! {3 w9 i: K成员变量指针的定义
3 j& Z/ Q$ c% \& k* v假如以下结构体C。+ A9 J# K) R5 T: @
( ?6 l4 x. ^# R5 L) bstruct C
1 ?8 B4 r& G2 ~7 Y{ " a4 `7 y2 [8 h9 @
int m; / U9 P* H. V e1 H7 o, Y
};+ y+ }) |3 Z* J' K+ _& I
单个成员变量指针定义 5 w9 K7 }2 }8 R* G, v0 w% n" f# Y
假设名称为p, 类似静态成员变量定义那样声明
/ X0 N( o0 @: d Wint C::p;3 K, g ^. H0 T" ~( e1 x ^
在名称前面加上指针标识号* 9 H; ^$ c/ l! u. C3 n/ z: e
int C::*p;
' r! H& Q; h" a) E: `typedef或using方式定义6 ?, H& X+ M0 s) M
typedef int C::*MemberPointer;5 R7 a' r1 x5 e4 Z2 W+ R, J# H" [' [
using MemberPointer = int C::*;
: H: G% E& K$ J: X1 C$ x成员变量指针的使用。
( y3 Q3 c7 S" T1 y1 w& j2 [类似成员函数指针那样,直接使用指向成员的指针运算符:.* 和->*即可。
& {; m2 I: t* [8 @1 H5 _成员变量指针, 能让我们实现一些遍历成员的动态功能。 例如把一个类/结构体的多个同类型的成员变量放进一个容器里,然后遍历访问这些成员变量。) ~/ q. N) x: Y7 o& H
: K8 O+ R" a* {) ~
完整例子1 I1 N5 y$ Y* i8 ^: o/ w9 @8 ^
struct C { int m; };
! s/ F* s! h. l/ `, z5 Vint main()% n3 d/ u" ~6 A; H8 }
{
3 ]; L5 t) ?* M* G int C::* p = &C::m; // pointer to data member m of class C& A6 E R: S. q6 _
C c = {7};
% }* M. u; o* g9 x5 o0 Q4 x. e std::cout << c.*p << '\n'; // prints 7
( m2 [2 e+ ~5 P5 q2 |! s5 @ C* cp = &c;8 Y- d' \9 E6 S7 q4 y( ^
cp->m = 10;
2 f7 y+ S7 s( w9 I. `4 w# o5 _ std::cout << cp->*p << '\n'; // prints 10
; B7 Y1 n: W; v8 h
- e2 l ?+ N& u0 S- z7 {6 y————————————————3 v: J/ L) I: Z t) E6 a, \
版权声明:本文为CSDN博主「南风fahaxiki」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。# a4 z4 P$ r* g* N }
原文链接:https://blog.csdn.net/m0_64407685/article/details/126788115 _: i5 f6 l: {8 n2 w# b" o
* Q5 f M5 R: F. Z, v
Q x5 X- h% [
|
zan
|