QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2455|回复: 0
打印 上一主题 下一主题

C++数组指针、函数指针、成员函数指针

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2022-9-12 18:49 |只看该作者 |正序浏览
    |招呼Ta 关注Ta
    C++数组指针、函数指针、成员函数指针# W' F8 g( j  {( ~9 L/ V
    C++数组指针、函数指针、成员函数指针
    . A2 c7 L( ^' U8 q( {6 g: B- d+ ^+ ?# ^" a# u4 c" S& m0 N/ y
    ! E) \  d. k# e! S9 }
    操作符名称0 x8 ^& N- T: [6 a
    & 取地址符(Address-Of operator)
    ; a( y! r+ K2 i7 T/ Y: F3 f: h+ l* 间接寻址运算符(Indirection operator)6 ~' R7 {6 F- |# _' r; x5 R# w5 z
    .和-> 成员访问运算符(Member-Access operators),用于取对象的成员。
    * w: t! z  j& d0 s+ W+ N7 m: y.*和->* 指向成员的指针运算符(Pointer-To-Member operators), 用于成员函数指针和成员变量指针的取对象。- e. s: O- a/ N% i$ n+ G/ i
    () 函数调用运算符(Function-Call operator)
    ( |( p( K2 Y# |; L3 k& N:: 范围解析运算符(Scope-Resolution operator)
    " v6 m# |# V$ F$ c+ v* s1 ~如何定义一个指针变量
    # C& ]: ~. [- Z% t* l假设类型T, 变量名称name, 指针的定义如下:/ X9 _/ L7 U+ A" n

    1 I  Z# ?8 @0 T; W3 {9 AT* name;
    4 b+ I* B7 M- r标识变量名字name, 它是T类型的指针。例如
    # Q" ~7 k( q% o$ i. u6 d6 I* e+ k, _+ v+ W
    int n = 0;: {. Q, G9 @5 e" w7 v
    int* p_n = &n;( |( e5 c9 {7 H, ?$ p
    p_n是int指针类型, 指向某个int型的对象。
    $ f2 o2 M" D5 K0 J9 u3 P) P9 Z
    0 N  L, c/ B( }5 y0 f, q; b指针变量的修饰5 I" z" ]7 g( Z2 Q
    指针实际上也是一种变量类型, 只是它保存的内容有些特别, 是指定类型的地址值,通过间接寻址运算符(indirection operator)*, 可以访问到指针指向地址上的指定类型。
    , T1 c/ v) o4 J+ ?+ Y$ R' Z* k: [3 C9 f0 ~! j# k( D& E
    指针也可以用const, volatile修饰。 const int或int const均表示一个变量类型是int, 且该变量不能修改。以下两种写法都可以:
    $ {  E' m7 Z* B$ s. t6 I6 E4 u4 Z' ~6 L/ [4 i, G- S4 _8 |
    const int a = 1;& ]! a) t, _, u. f1 ^) l
    int const b = 2;3 y3 \: P; r  ]/ T4 C$ o
    既然指针是也一种变量类型,同样支持被const修饰, 表示指针的值/指针的指向不允许修改, 指针所指向的那个变量是否允许修改, 那是另外修饰。写法如下:' J% a* B& p) m+ H5 V( A

    + O8 X, {5 l- ?/ U7 _: _& Fint a = 1;6 K3 Q% M3 r* F5 ]
    int b = 2;
    + b5 s1 m6 A; ?2 sint* const cp_a = &a; // 指针的修饰词,放在*号后面。
    7 D4 y- B9 |3 a" {*cp_a = 10; // 指针指向的值可以修改; n( ?" a  U1 U% B" h
    cp_a = &b; // 指针不能被修改,报错!
    9 R7 u5 }. j+ i: N1 [4 o6 P! _总结带修饰的指针的格式:
    ( [/ \2 T/ r3 @只要记住修饰词总是放在被修饰的内容后面。
    6 T2 M' n9 E0 _6 Z
    1 {! |7 \! r* u8 Q5 Jcv表示const / volatile修饰词。指针定义形式如下:
    1 `& k4 \7 [6 g1 L% B6 G/ k" l5 Y
    T [cv for T] * [cv for pointer] name" E6 ?4 r; T, i# I5 ?3 F1 {
    注意对T的修饰放在T的前面也是合法的写法。- `6 u" k5 ?, R& u6 R( z
    + W$ d  a2 m4 O8 c. t
    const int const c = 2;5 h. X( U3 y( q3 T2 k1 ^. c8 L5 {
    在mscv编译器下也不会报错。( h8 k( W9 M. |# z- u" o
    + \! T  L& ~0 X- M
    完整的格式:# l' Y4 C. y! k# v6 v
    [cv for T] T [cv for T] * [cv for pointer] name
    5 ^2 h: C( a9 U3 N
    8 M1 e* e$ |9 n# _Syntax        meaning
    ! t- d2 [- m9 p2 E9 J! U% n0 Bconst T*        7 B7 d% Y  Q. F" `8 n
    pointer to constant object2 h6 M' j; w( Z
    , v' p* H- d$ |) x: L
    T const*        pointer to constant object9 }8 m# ~7 d. E' M
    T* const        constant pointer to object/ O* N) E% S: D$ P2 ^4 B
    const T* const        constant pointer to constant object$ C' u# f2 B& W$ x- V. _% W
    T const* const        constant pointer to constant object+ p! M" r: S' f1 S
    上面格式中T还可以是一种指针, 指针的指针仍然是按照修饰词总是修饰前面的标识(T或者*)来确定修饰的意图。) }1 g# ?) m  M3 m( V
    : y, _3 K5 E+ I3 _$ v) L
    int a = 1;
    * B; H7 I6 p/ T6 fint b = 2;
    7 r3 p+ }) s- R& a' D7 d! B5 G
    0 e; `. K' A* Yint* p_a = &a;6 w9 O$ |/ [2 X' C: G- h
    *p_a = 10; // 合法
    ) k+ H" h, U+ d7 p( Vp_a = &b; //合法
    8 a! b; R9 ^6 |5 p, Y$ E) h
    : E% u/ D( p% T: uconst int* cp_a = &a; // const修饰int类型, 并非修饰指针+ m$ E- {; U! P, D
    *cp_a = 11; //报错! const int类型不能修改& M' B* ~: A0 u7 X. W) F; ]
    cp_a = &b; // 合法, 指针没有const修饰,指针可以修改。% a; u4 S: E0 r$ G6 ~+ Q

    8 L- D) n: w; U5 R) tint* const pc_a = &a; // const修饰指针。类型没有const修饰  l( ~( ?7 x3 t, b: ^( G
    *pc_a = 12; // 合法, 因为类型没有const修饰,可以修改。2 {* U- b: C4 b2 ]) _3 W
    pc_a = &b; //报错! 指针被const修饰, 不能修改指针。0 r& M; f- M( M# P% d& x" s

    & d" j8 X9 e8 i4 i$ A5 b2 Z) F3 `1 Uint const* const cpc_a = &a; // int类型被它后面的const修饰, 指针符号*后面也有const修饰6 q5 [1 T7 \0 H. o
    *cpc_a = 13; // 报错! 类型被const修饰,不能修改。) W3 C9 D4 e- u: |4 J
    cpc_a = &b; // 报错! 指针被const修饰,不能修改。
    $ {: I1 W) @/ G2 A: V2 [6 [7 p8 a$ a; g& M$ h# u
    更复杂的指针的指针
    # k" H; B% t+ {
    3 j5 u" M& C, aint a = 1;, g. g& D2 X) |2 G! U3 i  L6 j
    int b = 2;
    / p9 k7 e3 }8 x0 e8 Nint* p1 = &a;
    # u6 ?/ a- R8 Y) \; cint* p2 = &b;
    $ ]( \3 I# _( c2 n& a4 Vconst int* ct_p1 = &a; // ct for const type
    ) w: \. C7 U( T6 B' H3 gconst int* ct_p2 = &b; // ct for const type  Z4 r; f5 L- b5 V
    ; V5 f! E' E: t7 d
    // int * * pp1; 指向(int*)类型的指针# u) ^: ^3 k% d+ Y
    int** pp1 = &p1;  # U2 Q: M. L! R7 f0 O* l9 y  l+ Q5 I
    pp1 = &p2; // 合法,
    & j5 d& o: u/ g) @pp1 = &ct_p1; // 报错! 类型不匹配。 (int*)不能指向(const int*)
    $ o& p) R  ~7 P8 G' V
    9 v+ \3 ?' Z- y9 |// (const int) * * pp1; 指向((const int) *)类型的指针7 z4 {" A5 i# y' k
    const int** ct_pp1 = &ct_p1;  
    3 C# r* h' A) E. F$ _ct_pp1 = &ct_p2; // 合法
    ( \" {6 }5 ]8 u% o/ D+ jct_pp1 = &p1; // 合法!(const int*) 可以指向(int*)类型。5 G) @5 Y6 c2 @& J! h1 @! p
    9 y* h2 r4 A; |
    // (const int) (*const)0 _& ^+ X8 k7 X( G3 {! O; S7 x7 h  o
    const int * const ct_cp1 = &a; // 指针也不能修改
    % S) m" V+ N5 b( rconst int * const ct_cp2 = &b; // 指针也不能修改
    4 z: V# a7 U% C3 c0 a& vct_cp1 = &b; // 报错!指针有const修饰
    " {7 q4 b! y4 I8 x  k
    4 a; i0 Q* t6 j2 X& N# D// (const int) (* const) *  指向((const int) (*const))的指针
    3 J) z6 X) T. A& Vconst int* const * ct_cp_p1 = &ct_p1;  . S# }/ c/ u8 W) o3 Z6 L; J' S
    ct_cp_p1 = &ct_cp2; // 合法, 指针的指针并没有const修饰, 指向的指针有const修饰5 J' D% O2 |) u6 M6 T9 W+ ?3 _
    *ct_cp_p1 = &a; // 报错!等价于操作ct_cp2,  指向的指针是带const修饰的不能修改
    ; f+ d- q% N' o  j7 K% \$ k: g
    + x) G" r, N6 d8 p/ E1 X( T! V// (const int) (* const) (*const)  
    % J* K: k" @  r% R4 ^/ _" k$ ~// 指向((const int) (*const))的指针,且该指针被const修饰! `4 U+ w. D& t; p; S
    const int* const * const ct_cp_cp1 = &ct_cp1;
    ; ~8 \5 y! F  L, Y+ j: j; |ct_cp_cp1 = &ct_cp2; // 报错! 指针的指针被const修饰, 不能修改指针指向。
    8 K' |5 K( W: d. a, m6 x- L
      x2 H* ^4 O$ N6 Q+ k0 s一行声明多个变量
    + {* f) M+ [- r类型 + 名称定义一个变量。) Z; x: z4 v1 N- c' Y
    变量的前面可以加*号修饰, 表示指针, 一个星号代表一层间接。**表示指针的指针。
    % k2 K5 f- F- L6 ?( [5 V4 T& ^+ O
    int a, *b, *c, d, **e;1 F# L! R* Z, G0 G$ |! `+ ]* \
    a = 0;8 L2 c  s' f' R$ \
    d = 1;
    7 C) Z, |; V0 U4 Z  x1 V4 yb = &a;- Y. Y0 k" o4 \" J( `+ W
    c = &d;. g0 N5 s: {7 b* j8 P7 |
    e = &b; // e为int**类型 指针的指针
    5 n  U0 t* k! @/ h7 h, Ae = &c; // e为int**类型 指针的指针3 h2 B. G* Z" r, Z9 _
    也可以用括号包围变量和*号。" N  J; W* l: P) }9 I. n
      M0 n& N1 N/ U  I1 r
    int (a), (*b), (*c), (d), (**e); // 合法定义。1 B; j/ {: Z! [, P& E
    括号可以省略,某些情况, 个人感觉加上括号更清晰一些。例如
    " L5 H1 i% E3 H2 W
    4 {0 z9 g' B- o" g: Q, ~3 Y/ h9 Z1 Wint (a), (const *b), (*const c) = &a, (const d), (const* const* const e) = &c;
    + D% s3 L  V" A( L* ?写成2 B% A: J* w- r9 e) C2 r: l
    . S* B- f( a" C+ N" o
    int a, const *b, *const c = &a, const d, const* const* const e = &c;
    6 z" F3 ?# A. L3 F. j  c更重要的是, 后面我们表达数组指针,以及函数指针时,括号是不可缺少的, 带括号的表达更加统一。
    0 y" H3 A+ n2 l9 ?/ p' {. O+ t7 N& P' O
    数组指针5 y; l; s1 W# n% W
    数组基本表达
    6 h8 i* o3 |3 W; H8 H5 Nint a[10];  // 定义了类型是int, 元素个数是10的一个数组。5 b& n+ e3 J9 s
    由于c++要支持一行定义一个类型的多个变量。 所以数组的[]时放在名称后面的。虽然我觉得5 r. K  u$ z& P: Z# |5 v

    ' P* X* x$ w/ Kint[10] a;4 v' A: U- |2 n& k3 g
    这样的写法更符合类型 名称的思维, 但是如果类型都这么写的话, 没法兼容以下的写法:8 m! g; j4 o+ K' z$ G

    ( k. m" l' X1 S2 J8 K! w6 |int a = 0, *b = nullptr, c[20], **d = nullptr;
    & Q0 S, O! S6 f- X" pc++标准规定如此,但我们可以通过每一行只定义一个变量的写法, 类型会更加清晰。8 f/ N3 d- z" \; @
    5 O% }! L) V! Q/ }: a
    int a = 0;' K3 s8 D0 a  T1 e; \; n' [
    int* b = nullptr; // 指针int*
    7 \$ I" P% d- p7 D+ t; Vint c[20];' ^/ V% D2 n2 G0 q+ j9 K
    int** d = nullptr; // 指针的指针int**# F9 N" ]$ h. R( i% V: H9 @, J
    数组的名称是什么类型
    & z4 d+ h! L- t$ m) J数组元素类型的指针,可以直接指向数组。 并且数组跟指针一样,可以通过下标去访问元素。
    % S9 G1 B5 {' i/ v! c
    5 S0 L( s' O& s! B6 d2 Wint a[10];
    ( t! W. p: C$ T% h! U8 e6 Kint* p = a; // 指向a数组的第一个元素- V& T. `, M! D6 D# F6 c5 {
    a[1] = 1;4 ~& r2 B6 L0 m3 ]3 ?6 Z( c
    p[1] = 1; // 效果与a[1] = 1一样。
    5 J% u2 W5 f2 D" w0 a3 {数组可以当作T* const来使用, 但是又与T* const有些不同。sizeof()的结果不一样。
    $ x4 e# [* }8 ^% h
    - I! m0 {0 J) j9 o+ i6 Eint a[10];, h3 ?# T( Y3 L  z+ k
    int b[10];
    " V8 G2 f8 M, Uint* const p_a = a;
    9 r1 e/ }$ b! C6 l' ea[0] = 1; // 合法。 数组的元素可以修改。
    * N1 {: r$ K% ]& r/ r2 {+ o- |p_a[0] = 1; // 效果与a[0] = 1一样。0 Z, j( q4 p9 ~9 Y

    + r0 _% w8 P0 H8 K9 y+ m: Q% s9 O  qa = b; // 报错! 数组本身的指向不能修改。0 t, {. h5 e1 q3 j2 R" }4 T
    ' F# @1 `1 a: p
    // 所以数组a可以当作int* const来使用
    ( T' |: Z2 E4 {; ^; x# \9 Bint *const& ref1 = a; //正确。
    4 v8 O  |4 q$ ]3 Q6 ?$ Q8 pint *& ref2 = a; // 报错!
    2 l. }% j  c  V% F. M/ M1 Q
    ; Z5 K3 M9 Z4 v' i9 r8 B6 z5 m// 但是又跟int* const有些区别。1 j/ |% u# C- C' p- n4 \
    assert(sizeof(p_a) == 4); // 32bit程序。
    7 v, @# k) n% _3 I7 Qassert(sizeof(a) == 4*10); // 32bit程序6 ?3 J: n. |  V& P) l" c
    ; }8 ~7 w! m' Z3 b) H1 f4 ^* J
    数组跟元素指针的作用很相似,都可以通过下标去访问元素, 但调用sizeof()函数的结果不一样。元素指针的sizeof()返回值是4(32-bit应用)或者8(64-bit应用), 数组的sizeof()返回值是数组实际占用的空间。数组可以当作指向第一个元素地址的T* const来用其实就是我们常说的数组到指针的隐式转换。当数组作为函数参数传递后,会自动退化成T* const, 在被调用的函数内部调用sizeof()的返回值跟T* const指针大小一样。 数组传递作为函数参数后, 在被调用函数的内部与T* const是没有任何区别,只有在数组定义的可见范围内sizeof()才有获取数组占用空间大小的效果。9 f) o6 |5 ^: T8 R" Y6 J! t2 g2 j' e
    7 K9 i* j' g5 Q4 M& q. c& [7 J7 h
    以下3个函数翻译成汇编以后,汇编代码是一样的。6 a) B5 p. ?% @5 T6 w& x8 H1 `3 e

    * j2 ?1 s& @0 l+ L5 t% x" ]void Func1(int* p_ary)
    0 j: j1 N; Z6 q# P- N0 {{
    . v! P7 w2 O* I) C    assert(sizeof(p_ary) == 4); // 32-bit( l8 F. z' V! S' Y6 z: R. N4 k. {
        p_ary[1] = 1;
    2 V% G" W  L" Q( Z! g2 g2 V8 g}9 [/ d: ]2 C( y
    7 \. }4 e2 ^/ g% ]% f5 i6 B# C# J
    void Func2(int ary[])  g6 g; e3 F9 u* P( A- e
    {
    * v  g" q9 ^6 o; d6 J$ X; z% F    assert(sizeof(ary) == 4); // 32-bit/ i5 m" w: b, @9 ]  I: d, h
        ary[1] = 1;1 A$ l( U) w$ g& n/ d+ j) a% m' h& B/ n/ w
    }# G0 s- O5 Q- q5 t: `' V% G' r7 H0 W

    % o1 ^# m& V  b; Ovoid Func3(int ary[10])
    $ {' W. N, v* P{
    ' m3 ^  M- u* F% x; ^! [% R2 f3 e    assert(sizeof(ary) == 4); // 32-bit
    0 _% h* V7 ], R6 _- Z  F4 i, U    ary[1] = 1;+ Q* Z1 Y: g  s+ [2 u6 r; ]
    }' Q7 }3 K" G. i+ H0 M7 p' ]

    4 y, O/ E$ y$ U% X) Y4 O8 fint main(int argc, char** argv)4 O1 i2 ?! R/ z/ A
    {
    8 \7 u( ~7 v9 M/ o! g  o    int a[10];) c, }' y; ]+ ?2 O4 i* D
        int b[20];
    / ?* M; V. }- v  J" M6 X9 V    Func1(a);
      ]1 Z$ F0 u9 C/ W& B    Func2(a);
    , x  q+ Y* c" \" n8 q; P    Func3(a);) X$ r4 z$ p# s* t+ \  S% L/ u
        Func3(b); // 退化成int* const了, 即使数组长度不匹配也不会报错。8 t6 X! R  v0 G% w8 x4 V- R
        return 0;
    3 R. V5 Z8 i" s) V}
    + V/ j  m8 C$ Q, C" w: B. V8 }$ b* ^4 u) b6 w5 e/ w

    / O, c/ I- M9 |1 T
    7 @4 Q+ v# n7 e: C9 N多维数组
    ; J* n! a; D& q: z3 O% V% B! \一个3行,4列的数组, 结构如下:& G5 e1 C3 y8 F. r

    : V, g1 B! s1 ]) M6 Dint a[3][4];7 g; ^  V  H# U, K  b
    column 0        column 1        column 2        column 35 c- v3 D0 R! M7 O9 j' {1 Y
    row 0        a[0][0]        a[0][1]        a[0][2]        a[0][3]
    % N1 s. H( N& k- `$ ]) t& lrow 1        a[1][0]        a[1][1]        a[1][2]        a[1][3]9 H% D/ D: ^+ o4 x8 P' ^- A  Z  M6 @
    row 2        a[2][0]        a[2][1]        a[2][2]        a[2][3]
    3 ]& W! M* h9 ]) f数组初始化5 r" @3 R* d' ~4 L! c
    1 w, a* x. r5 T: k9 r& n/ J
    int a[3][4] = {# _' G0 I. \% N1 I" s! h# E" j
        {0, 1, 2, 3}," ]/ J; w. i0 I/ g
        {4, 5, 6, 7},! X1 T' ]; H. ^9 W
        {8, 9, 10, 11}& k, G* e" O* p" D1 ~
    };# c1 F; A& `. Z0 l
    实际上多维数组和1维数组在开启速度优化后,翻译成汇编代码是一样的。, ?# P- A' S0 A2 v% |2 l5 g7 U# F8 D

    5 S9 a$ V, E" n) y5 `" zvoid Print(int* p_ary);: [! b$ }* ^: E0 W2 F% ^

    0 |+ }  j! C$ \9 [2 @void Test1()
    / h5 e9 U" ?  F+ T  l5 U, z{) Y6 t( m6 |, I* t5 A  s: x: f- X
        int a[10];( n0 m; ^. ?# }: k
        a[3] = 3;
    , T/ H4 N. E& L, _) ~' B    a[7] = 7;! O4 ]2 w' e9 `7 f( p
        Print(&a[0]);+ ^$ a( P5 C% Z9 c
    }
    + {: W  p4 m# p; `: J
    4 A5 ]: T. N* n3 K) h+ Nvoid Test2()
    . B7 x2 v% S; k1 L{, x. L* ^- {, t# z! L+ i
        int a[2][5];
    # O9 j8 y3 J: p- p8 v1 s4 \& a    a[0][3] = 3;
    1 M! Z  c# b; z. f2 f    a[1][2] = 7;9 |6 d% K* s7 E6 m6 V7 I
        Print(&a[0][0]);
    " z5 ]/ E: q; E6 K6 ~}
    9 @( j) N' r& h+ S7 p
    3 M4 N4 T( O) V4 f1 c1 M) z
    - A" b4 Q+ `- K* W: J  k% ^" H% }
    很自然地,多维数组也支持用1维数组的方式去初始化。! k- t! c7 E8 V7 J/ T4 \7 I" _3 T
    ! W8 w. z6 @) T5 Q# o- H
    int a[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };2 B4 ]. T& \5 m. E8 L
    既然多维数组与1维数组没什么区别, 为什么还需要多维数组?: C7 W6 {: X0 X8 S1 M" O3 D
    # b: D; G. C4 o3 j
    假设有一幅RGB图像720*576个像素,每个像素有RGB三个通道,每个通道的值是8bit大小。给出图像的首地址p_rgb_image, 我们要取第40行,第50个像素的R,G,B值。代码如下:! U3 \  l) j9 U7 F$ p
    9 c) ]( p8 A, G! l% U. {
    unsigned char* p_rgb_image;
    2 d/ K4 s) a$ C5 i0 g4 X/ K" ?+ g( Punsigned char r = p_rgb_image[40*720*3 + 50 + 0];& n, G8 q' Y: P3 h
    unsigned char g = p_rgb_image[40*720*3 + 50 + 1];% ~* _4 Y5 N3 G- I
    unsigned char b = p_rgb_image[40*720*3 + 50 + 2];4 o6 b9 `5 L) A( H8 u
    类似这样的场景, 采用多维数组的写法, 有点类似以索引为参数,可读性更高。相当于程序员和编译器打了一个配合。0 [! }2 \" D$ B: g# G# |
    7 @; O- l# S4 n+ M3 |/ [3 h$ v" K
    enum& O) p1 m  v( N  E
    {/ T( x/ j! X5 f. |
    Red = 0,& }  V4 K5 k9 A2 _+ I  N
    Green = 1,
    6 G6 ]; W8 U8 Q! f+ t2 v Blue = 2
    9 Z# G6 t5 F( E1 z& p% Z2 O$ W};
    ) r) V: A& w- Q! w* o5 A$ K; }unsigned char rgb_image[576][720][3];
    % n$ I( A3 `4 B5 c2 t" }int row = 40;0 s4 o& [6 R7 r) m' V! e
    int col = 50;8 |" f6 V" I6 q2 D1 D
    unsigned char r = rgb_image[row][col][Red];) f! _- m6 q0 H
    unsigned char g = rgb_image[row][col][Green];
    ; j+ p7 W3 N/ g/ ^4 V0 ounsigned char b = rgb_image[row][col][Blue];
    9 V# F6 @2 w6 i7 E; F  `7 f数组指针以及与指针数组的区别8 F; q2 g1 u" S' a2 ]
    数组指针,是一个指针, 指向的对象是数组。 数组指针的赋值,要求数组的长度匹配,否则会报错。当指向1维数组时, 需用用*取得数组对象的引用,再用下标来访问数组元素。
    # S' P+ }  v- ^指针数组,是一个数组, 数组保存的元素的类型是指针。7 c0 E( n* j) J$ J" x
    数组指针的定义! o( ]6 V: \2 B* }
    数组指针定义先定义一个数组。
    9 K0 i: A2 j; W: W9 a: m7 q. Y+ n4 y/ Kint a[10];  r2 {0 [& c, p% M/ F( y0 E% s
    然后对数组里的名称用括号括起来后再在变量名称前面加个*号
    ( X& k" W  Q5 G; n1 D% Y4 hint (*a)[10];2 N# [9 R, }: L! }# F
    后面你会发现函数指针定义类似。! r2 H7 N9 q3 X9 J/ J' T
    // 各类定义对比
    * O+ H/ q+ T0 F: T, S7 H+ iint a, *b, **c, d[10], e[10][20], *f[10], (*g)[10], *(*h)[10];; r3 V, ]2 W! r+ v' I: P+ |

    4 U7 X: j) n# f+ ]. tint *f[10]; // 指针数组, f是包含10个元素的数组, 数组里每一个元素的类型都是int*
    3 D! g! Q% b1 _, c3 w' |int *(f2[10]); // 指针数组。另外一种定义方式。
    ' ~3 \& t9 i" B) Z- W' ?; q4 Sint(*f3[10]); // 指针数组。另外一种定义方式。
    0 o+ C) n# s% M8 M3 hint(f4)[10]; // int数组
    , I; v! t$ Y& g  w* Z- x0 S1 Sint(*g)[10]; // 数组指针, g是一个指针, 这个指针可以指向类型是int,元素个数是10的数组
    - y- ^: W2 _6 nint* (*h)[10]; // 数组指针, h是一个指针, 这个指针可以指向类型是int*,元素个数是10的指针数组5 [- ?# ^& V: f0 X. ^- s. P" K

    ( |( ]$ w1 x2 B5 i: \  ^" yint d[10];* [: [2 z( _& g
    g = &d;5 w; F# d4 \7 i9 s

    & J3 f+ R. a, j* Y# Fint* e[10];
    ! b# v' o& j1 P% O& {6 W& Kh = &e;
    : D8 l9 J. p4 ?1 W) F, U4 s$ t数组指针的使用
    : U, x* k% J: A& i数组指针一般先通过*号取得指针指向的数组对象, 然后再用下标操作访问元素。- z4 E) t! L- H

    0 u7 }6 B& T7 O# h1 i7 F9 j' ]int a[10];
    ) \$ J* a* a* e; N  Z4 Vint(*p_ary)[10] = &a; // p_ary是一个指针, 指向"int (*)[10]"类型的数组
    * S  L4 I3 M2 z- E2 ?; \3 N: Q* y5 Mfor (int i = 0; i < 10; i++) {
    + K/ w. h! x* ?9 N) K, s // p_ary是一个指向数组的指针, 需要先通过间接寻址运算符*(indirection operator)取得数组对象& ~7 j: L' m* P3 ?& ~% {
    // 再通过下标操作访问元素。& O$ C* k- f. h" B# F# @. o4 d
    (*p_ary) = i;
    2 `- T/ M: r) d: J: ?5 Q}
    & I/ j$ N- e1 R! M/ P( j* \1 Q5 U* F
    ' Z- y( Q: C- y: m& U! Gint b[10];
    ; y2 m: c" V8 P  N' {int c[20];& \* y: _+ I1 a
    p_ary = &b; // 合法; ?) T) Y0 i0 y* p8 p. Y- W# c# K' T
    p_ary = &c; // 报错! 不能将 "int (*)[20]" 类型的值分配到 "int (*)[10]" 类型的实体  ?8 ~5 N3 q* u& [
    数组指针指向多维数组的子数组, O+ ?5 Z$ s8 D2 o9 v2 U6 v5 a9 o
    int a[10];; m# s9 `# o: Q
    int b[4][10];" i- |, N$ ?5 G2 ^5 V( Z
    int(*p_ary)[10] = &a;
    " `. L. H: Z/ T& v8 j" e' Y/ P" Zfor (int i = 0; i < 10; i++) {  i7 l3 z, o1 {' }1 |
    (*p_ary) = 1;
    , j% s% |9 e: R}3 k! x# l; [  q& P7 _) C9 \

    . G" c6 d3 N# D5 S1 Lp_ary = &b[2]; // 多维数组,可以看作数组的数组,
    ; @4 H& R  f$ |2 H) Y! |// b[2][0] ~ b[2][9]的值都被改成2了
    , q2 K& ?7 ^- Jfor (int i = 0; i < 10; i++) {: F; l& f* @1 d8 s/ x0 g) H
    (*p_ary) = 2;
    ' a; V( ~. r, r" U3 t}
    , I! \0 o, q9 I: o7 M多维数组指针
    0 f& e) r7 V) f多维数组指针,是一种指针,指向的对象是个多维数组,支持多个下标操作。
    : _$ h0 Z3 D7 N0 y
    # y3 g2 l3 B& _' O0 Eint a[2][5][10];
    7 T# p8 Y) Z' o8 B$ t2 e2 ~int(*p_ary1)[10] = &a[1][2]; // 1维数组指针
    6 S2 n- t7 Z+ Bint(*p_ary2)[5][10] = &a[1]; // 2维数组指针
    4 H+ y$ @: l: ?( R* Lfor (int row = 0; row < 5; row++) {- x: [. |" Z  l( h
        for (int col = 0; col < 10; col++) {2 x5 |  @( E" X6 s) e; ?1 ?+ e8 x
            (*p_ary2)[row][col] = row * col;+ q/ Z; i; r" V2 F- Q1 I
        }
    0 t: R+ n$ {* k; C}8 l% i8 Z: P) O  w! b8 X. R
    数组指针和指针数组对比实例" b8 a, T! A! I; `6 X$ |- @; p
    数组指针还是记住两步法即可
    ' T7 b& S7 v4 z! }3 D* e9 z3 N/ ?; p; }( Q; V
    定义一个数组
    & @8 q' g0 o7 ?9 @5 R' b括号包围1中定义的名称,再在名称前加个*号。
    ; w( x! O! ^, ]1 Fint a[10];
    5 z. X, ]: k0 q" A: e( B  eint(*ary_pointer1)[10] = &a; // 数组指针! v8 x& d+ M# U+ T( T9 L
    int* pointer_ary1[10]; // 指针数组。元素类型是int*
    " q' q# i5 y5 Kint *(ponter_ary2[10]); // 指针数组。另外一种定义方式。) T. ^  \+ U" Z( O( `" |
    int (*ponter_ary3[10]); // 指针数组。另外一种定义方式。
    7 p2 _2 k! W% c; B* |int c, *d, (*ary_pointer2)[10], *pointer_ary3[10]; // 排列定义比较。
    4 T6 Y! n5 H# t. c3 ^. l. U' O* U; f$ T  q; w. s
    // 指针数组可以把每个元素指向数组对应位置的地址。
    0 W, n6 ^7 k8 ?! Y// 这样遍历指针数组, 可以达到遍历数组元素的效果,但是注意每个元素都是指针,
    5 `- ]( }; h- Y6 `& t0 |; h// 需要访问原数组的值的话, 需要对指针用*间接寻址运算符。
    7 v6 c  d: ]. \/ m9 m/ nint* pointer_ary[10]; 2 U$ Q7 E, n# ^4 ^8 o* b
    for (int i = 0; i < 10; i++) {
    7 f$ `: t! K8 X7 L    pointer_ary = &a;
    / v' d+ y) m0 s4 i8 G}
    & W! {3 t- `8 X4 R' ]7 ]( e// 类似遍历原数组效果。5 d# r& I- ^/ r
    for (int i = 0; i < 10; i++) {; F1 \9 w5 c3 F! s+ U
        *pointer_ary = i; // 修改原数组。3 m, m4 w8 f  b/ ~: O: W
    }
    2 l# k% }+ m4 u
    0 D" M: d+ w1 x9 ~函数指针
    2 r4 q" ^5 C  f1 B4 a9 W取得函数地址
    / Q0 r, U- p, X函数的名称作为参数被传递时,会隐式转换成函数指针, 和在函数名称前加取地址符&等价。建议带上更加统一和清晰。
    / G2 d+ m1 ~# O; Q( n; k7 _2 J) x
    4 P0 v! \! V1 Y: v* qvoid f(int);9 M* L$ r8 t( T$ E, S' a
    int main()
    # A3 I  n/ d0 E4 t. i{
    2 u" U# F  [$ ^3 j: l' Q    void (*p1)(int) = &f;
    ) F( ]8 d5 l, Z' ]& _. `    void (*p2)(int) = f; // same as &f
    5 J! w& u; d! W5 F5 a    return 0;! i* t# m7 b' Q" @& n2 ?
    }; V1 b. U+ P1 O1 h) W$ X' W
    翻译成汇编代码, p1和p2的赋值是一样的。1 [2 A3 O( U- e( [; j

    4 x1 }7 C8 k8 D* J0 g1 U2 k7 l0 J$ E; I* L. b

    9 h# B3 J6 s+ B2 R' ?函数指针的声明4 c  x! U. y. D: J
    单个函数指针变量定义步骤6 Z/ j# o9 m9 I5 s& {) D! C' g
    定义一个函数。void fun1(int a, int b); int fun2(double a);
    3 B% e% ~' X- @( j# i用括号把函数名称包围起来,然后在名称前面加*号。void (*fun1)(int a, int b); int (*fun2)(double a);) ?1 \3 Y; v6 n0 R
    如果要定义函数指针数组,在定义单个函数指针的基础上,在名称后面加上[数组长度]void (*fun1[2])(int a, int b); int (*fun2[10])(double a);0 O- P' j, a: ~' u( X% c) _
    typedef定义函数指针+ c% ~' y0 ~" R& G2 \0 _
    可读性高比单个定义要高,特别是声明多个同类型的函数指针,或者函数指针数组。
    5 J( t% C! R- Z  c* c, u- V. D% w4 a- e* C# w$ ^4 y
    typedef定义函数指针的语法8 P0 a; C( b& l$ r; h
    typedef有两种做法, 一种就是定义一种函数对象,另外一种就是定义函数指针。用法稍稍不同,效果是一样。其中函数对象不支持赋值, 但是支持引用。4 t' L. H3 u$ E6 H" s7 t' Z" ?' f
    3 |1 H& u$ _0 X; y6 T5 H! x
    typedef int FuncObject(int a, int b); // FuncObject类型是函数对象
    8 O0 c5 s7 h+ ^" q7 _% k0 A* rtypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针) C" |7 X2 R$ ]. ~9 e1 X6 y
    FuncObject* f1 = &Add;
    / E8 o2 [- M7 w8 a6 U" b  DFuncPointer f2;
    9 S8 q/ x1 G9 l/ W  G0 D0 Ff2 = f1; // f1, f2类型一样, 都是形式为int(int, int)的函数的指针。2 E, x( k; M; {# X  K# M' U
    FuncObject f3 = Add; // 报错! 函数对象不支持拷贝9 e$ g( b! n3 a" ^3 l3 l2 P! F
    FuncObject f4 = &Add; // 报错!&Add是函数指针,与函数对象类型不匹配6 f' ]+ z* g2 i2 r1 F+ T7 B  p$ O
    FuncObject& f5 = Add; // 正确
    . t( ]3 [6 U+ l# a' t7 g9 J2 m3 sint ret = f5(2, 3); // 正确
    3 @) F1 w, b) M; F$ j2 p) ?- LFuncObject& f6 = &Add; // 报错!&Add是函数指针,与函数对象引用类型不匹配! V/ ?' y; v1 l" ^8 c
    如何记住typedef定义函数指针的步骤
    # N& W& w# ^  l' v- j像定义一个函数指针那样, 指定一个名称。int (*CalFun)(int a, int b);
      v% b8 m- i; e! C+ ?: j在这个函数指针变量声明前面加上typedef。typedef int (*CalFun)(int a, int b);
    9 i" X- X4 @" D$ e4 K- N完整例子; p9 N- d9 f% Y( M$ K& A) z
    typedef int(*CalFun)(int a, int b);: d5 _7 [4 X1 a, Z% J  w6 A

    ) U0 W. l4 x( z4 T" j# yint Add(int a, int b)
    * O' U% g+ m! C- R- }0 H{
    + S8 h9 _. K; C* d    return (a + b);% d9 k, ^7 X* Q9 v; B. C4 e* t
    }- U0 v5 `: w% T5 N2 t
    , H( s7 x8 ?  S% U
    int Sub(int a, int b)
    7 b+ m. u3 c! t) @% N) w{* J0 K3 N  H# A: G
        return (a - b);
    . ~4 `; h/ U0 c, b9 @, U6 W2 s$ e}. t% T& E, k" |* S' W5 v

    1 M4 q2 j4 H9 r: A5 [int main(int argc, char** argv)
    1 R% H" F8 s* G" H{! ?) j* v* Z- S7 Q
        CalFun f1 = Add;
    7 F2 Z# z" z$ r+ d, R    CalFun f2 = Sub;7 S, B; u6 q3 [2 k3 ~
        int a = f1(2, 3);* j9 \6 p/ Q( Z" j
        int b = f2(10, 5);
    ( G& c/ W4 x) @$ ^' z: n( G9 i+ {5 \/ I6 b
        // typedef定义的函数指针数组。
    * E+ O2 W" S$ k9 T5 z/ p    CalFun f_ary[2];2 r/ l, \* l: d8 \1 m0 t
        f_ary[0] = Add;
    , s  A& r9 X( t9 D5 G# n    f_ary[1] = Sub;- `! J& U1 U9 m- N0 _" W+ \

    ( a, _: G4 a! q. d0 u1 }) A( ]7 d    // 单个定义的函数指针数组。
    * {7 n- b- C( j# w  }    int(*f_ary2[2])(int a, int b);8 @$ B" y% T7 ^( Z
        f_ary2[0] = Add;" m; H/ y0 ^7 w4 C# ~' I
        f_ary2[1] = Sub;* a# z0 G9 C% [% p

    7 L8 P4 k7 g0 R3 s" g( L    return 0;
    & ^& z8 V7 `) Q5 \+ I' s" o}1 y8 J) s/ L0 v: R
    ; K  O! L; o! }/ r. F
    using别名定义函数指针" a' t3 x! x) l  Y# d5 m
    c++11以后的类型别名定义--using也可以用于定义函数指针, typedef的好处它都有,个人感觉比typedef更直观。using类型别名同样分函数对象和函数指针两种方式。; a+ o. Z* d# P- z6 f
    4 [; s. s1 n  x; v
    typedef int FuncObject(int a, int b); // FuncObject类型是函数对象* f' W! T% d- p) k6 W* M' S
    using FuncObject = int(int a, int b);
    # w, A4 Q2 O( t3 K4 ~$ \7 j$ |0 Atypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针$ w3 I+ R" D& k+ X$ ]
    using FuncPointer = int(*)(int a, int b);# ~3 @" J& R  e; j# ~; ]7 z
    函数指针的调用
      ^: S" o& p- T+ Z" ^4 G函数指针和函数对象都可以直接后加括号调用* f# Y" M# L& F$ P3 x8 ]
    int f();, Q/ e7 A3 Z5 ~$ _( R. s  [
    int (*p)() = f;  // pointer p is pointing to f8 H$ i& ~9 j3 e
    int (&r)() = *p; // the lvalue that identifies f is bound to a reference
    ; S# X& z+ B- C0 r2 U( L9 S: D- Jr();             // function f invoked through lvalue reference, O% O# f6 c. c5 N$ ~$ j" |8 B
    (*p)();          // function f invoked through the function lvalue
    . \! j; J% D8 ~1 ^0 p' Y) Mp();             // function f invoked directly through the pointer* V1 s/ {  b( ?: A8 n
    如果函数有重载, 函数指针会指向匹配的那个版本。
    2 d5 m9 x/ N* Ftemplate<typename T>- b) y$ a, h7 f# d  N$ ]3 }
    T f(T n) { return n; }
    * x. i1 q) B; S8 L! u) w- C  X: u/ h7 S; v
    double f(double n) { return n; }
    $ K4 V& b+ h  P( R  O% N0 S* |# L) D; X$ n' N
    int main()& G  `5 k" N/ s; Z; W: S3 j8 k6 _# B
    {
    - e9 @; }* Q0 y+ \0 f    int (*p)(int) = f; // instantiates and selects f<int>) ~  O& Z3 X' Y! _' J" i
    }" P! r7 Q& {. L  w* s# x+ s
    成员函数指针
    & ~3 \* j1 v3 G7 P6 V9 U6 {( G静态成员函数,除了增加了访问控制以外,跟普通的函数指针没什么区别,所以普通函数指针可以直接指向类的静态成员函数。但非静态的成员函数与普通函数指针不太一样,声明时需要指定函数归属的类名,并且调用需要指定对象实例。
    . a- ]+ u9 ~. k+ ~; o, ]3 Z3 w8 v/ C% f4 c
    成员函数指针定义。; q) W3 X* S0 C7 Y, M
    像定义类成员函数实现那样写, 并任意指定名称,这里作func。void ClassName::func(int);/ d7 j( J' R8 k' M. r
    括号把类名、范围解析运算符::、名称包围起来。void (ClassName::func)(int);
    & j0 I/ d- c0 w在名称的前面加个*号void (ClassName::*func)(int);$ N3 Q- J8 k; C" v0 F
    成员函数也支持typedef和using的定义方式。typedef void(C::* MemberFunc)(int); using MemberFunc = void(C::*)(int);
    ( k& L8 |5 e6 F/ N% @* M  E9 B成员函数指针如何调用。3 N, z! ^4 n; `$ y; ^3 i9 C
    假设成员函数指针名字为func. h+ M9 _5 V, V2 ?4 f

    7 N) c4 E( o5 X3 j- e; c8 Cvoid (ClassName::*func)(int);
    0 ?! A1 x, v* o  T6 m/ k9 U+ q对象式调用。
    , ?- K6 q6 g" l/ ]  B. W  v# I7 BClassName c; // 被调用的对象
    1 i/ i# [/ _5 w7 ]成员函数指针名字当作正常函数那样写。
    # s* F2 m. [6 Lc.func(3);
    4 G( k& A0 ^/ W( _5 ]- v0 Y成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。
    ; H. P6 q, r% Z4 a7 cc.*func(3);9 J- ?/ C) h1 S( k. E9 a% b
    最后用括号把调用对象、成员访问运算符.、间接寻址运算符*、和成员函数指针的名称包围起来。
    8 ~  P- f* p8 {3 K# ]3 l(c.*func)(3);
    * T+ o0 x& q' h, t& F+ S( ^为何要加上括号? 根据c++的优先级标准,取成员运算符. > 函数调用() > 间接引用符*。 *号优先级比函数调用要低, 成员函数指针还没取得对象就被调用了,自然报错。 另外0 \. r9 U+ y7 P. S/ v0 I
    (c.(*func))(3);- X. z( W. z& t0 [! }/ q
    这样的写法也不行。 .*和->*是整体作为一个运算符的,中间不能用括号隔开。
    % X: u2 W# j/ o2 x8 P指针式调用7 m1 n) U! Q' [" }
    ClassName* p; // 被调用的对象的指针
    6 V4 F$ D* \3 |成员函数指针名字当作正常函数那样写。
    $ k3 h# L% K! d: W- p. t/ K* qp->func(3);, F4 s; @4 C9 j, Z2 ~5 D2 f' d8 s
    成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。
      X) t) I* D0 c! r; X* _p->*func(3);1 f9 G9 L7 v" t+ b' O
    最后用括号把调用对象、成员访问运算符->、间接寻址运算符*、和成员函数指针的名称包围起来。1 I$ N( f$ K# w+ @: j/ d
    (p->*func)(3);' |: u  K" O( d6 ~' n6 Y
    函数指针使用完整例子
    " U; v. I/ b, u5 q+ ~struct Cal
    8 E+ k) m) D8 f{ 2 r$ [1 V5 [' O7 [/ v9 t$ L
        int add(int a, int b); * {% M, ?: V  i
        int sub(int a, int b);0 H/ h4 |! S$ F1 ?, `9 C% s
    };. O7 z' k3 s: _) S$ f0 H

    " p3 Q3 O1 ]# A2 {9 dint main(): m! y% C. J* b+ t2 R% y
    {
    + X6 K2 G6 w& y% p3 Y4 P1 `    int (Cal::*fun)(int, int) = &Cal::add;9 _) e7 Q$ A* u  S
        fun = &Cal::sub;
    , U7 }# U" N; P# v1 I. B! f
    0 d: i8 ^( c% c2 q    Cal* p_cal = new Cal();& n: a0 o, s) e  w
        int r1 = (p_cal->*fun)(2, 3);7 d& ?9 ^7 n' |! k) d
        delete p_cal;
      z3 X+ C! t- e0 v, `
    4 o0 t# s, D4 [3 v7 p    Cal local_cal;
    + z$ Y7 E7 W2 ^0 E4 V    int r2 = (local_cal.*fun)(8, 6);* @# |- {' A0 t! P; ^
    }
      u; X* x/ v; f, u! {0 |( f5 b  M. C7 G" T
    成员变量指针
    7 r% u6 P! G7 ^+ B, i成员变量指针比成员函数指针还要简单些,没有函数调用, 无需考虑函数调用和间接引用符*的优先级问题。
    6 w! z9 u4 M1 M% ^5 V$ o
    4 N, j4 `- g6 F- O4 `成员变量指针的定义
    % S; S' ^* P& y8 O2 f, {( m假如以下结构体C。* P- c8 p, J3 G; t5 E* l& s7 u

      D7 k; Y) t* t6 H* l5 Sstruct C ' V% q' @' B$ {
    {
    2 I8 c- o8 X3 K. n3 {) Y) _; ]    int m; ) |9 e# Z5 q; Q# t! @) I8 A
    };
    ) ~0 x. i1 T0 `% K单个成员变量指针定义
    ; x5 z" Y; g& s& I/ @假设名称为p, 类似静态成员变量定义那样声明
    5 W6 F( ]; l  C6 Q% l1 G5 a9 Dint C::p;' z$ M# c+ R1 B# c& k, ?
    在名称前面加上指针标识号*
    + E4 z( b5 Q- C/ O# Bint C::*p;
    : j/ k* x1 d- Z8 Q& B  stypedef或using方式定义
    8 Z/ V; Z; ]; l& b1 I/ o1 htypedef int C::*MemberPointer;
    ; g, P0 J9 K+ H  q$ Jusing MemberPointer = int C::*;- r# A! {" J6 P+ R' ]6 x7 c
    成员变量指针的使用。
    / P6 x0 {9 \( Z# y. u, {+ O8 J类似成员函数指针那样,直接使用指向成员的指针运算符:.* 和->*即可。2 }$ w* ~( c* v6 \
    成员变量指针, 能让我们实现一些遍历成员的动态功能。 例如把一个类/结构体的多个同类型的成员变量放进一个容器里,然后遍历访问这些成员变量。/ K: ]/ Z6 @  @! Q' U- H* p9 W/ S

    6 B2 h0 n4 Y5 `5 O8 q 完整例子
    " \0 k1 I" Z4 Pstruct C { int m; };
    3 M2 O& \; f5 o. k, Z# m% yint main()/ |* A; w& u3 Z/ L7 T& k
    {1 m  n- i2 n6 s8 Q7 E8 ?0 }
        int C::* p = &C::m;          // pointer to data member m of class C# G0 G$ d8 H' _
        C c = {7};( O2 K( n$ m4 u" n- O
        std::cout << c.*p << '\n';   // prints 7
    9 `: ~  }0 X7 k    C* cp = &c;0 v- u% Y  \% n4 z: T9 B9 Y( o. I5 v7 D' e
        cp->m = 10;: ?' q- g( ]$ r
        std::cout << cp->*p << '\n'; // prints 10
    3 _8 ~$ s" ^6 b% R, o. j5 b* p
    + ?- b2 v9 J2 H% w6 [' w& _————————————————3 w# V! @  x: r7 \
    版权声明:本文为CSDN博主「南风fahaxiki」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    - |' ~9 t4 e  B7 K! t# z原文链接:https://blog.csdn.net/m0_64407685/article/details/126788115
    ' |/ c0 }% o' ^2 z: g5 R) \; e$ t6 J, r* ], D8 j1 U
    4 Q0 e* g/ ?  p/ K5 Z* M* S- r) H8 x
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-8-5 07:49 , Processed in 0.457225 second(s), 51 queries .

    回顶部