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