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