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