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