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