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