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