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