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