QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2398|回复: 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++数组指针、函数指针、成员函数指针
    " J# N0 Y' u% A# @$ EC++数组指针、函数指针、成员函数指针
    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 Hint n = 0;
    . I% f" J' s/ Q4 r7 kint* p_n = &n;
    ' n7 K  a% b- T4 `; X% G9 M' qp_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 Aint a = 1;, H1 h! D# k6 ^
    int b = 2;
    + d5 j1 S1 m. @% _/ o# I# K2 uint* const cp_a = &a; // 指针的修饰词,放在*号后面。( K; I% I! ?* D/ [' B+ p
    *cp_a = 10; // 指针指向的值可以修改
    2 ^: p  a2 n, ~6 v, q/ S' ycp_a = &b; // 指针不能被修改,报错!% A1 @; j2 i  Y# x# M; R
    总结带修饰的指针的格式:6 s4 o7 o2 Q* K/ t4 H" q
    只要记住修饰词总是放在被修饰的内容后面。
    ( u: K' Y/ V0 S  J* X5 ^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) TSyntax        meaning
    * ?/ }9 j, Q5 t/ F$ Iconst 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  vT const*        pointer to constant object) O5 c! U0 r6 H# D- {
    T* const        constant pointer to object
    # |' P4 X! U# m4 p0 Hconst T* const        constant pointer to constant object
    1 H9 u4 ^% K$ v& p2 Y& e0 LT 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 Kint* 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 kconst 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 ?. Qcp_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 cpc_a = &b; //报错! 指针被const修饰, 不能修改指针。# f# X$ z9 H& O' B/ g* {

    / [$ _% {& N5 B  o8 v( F. nint 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' Qint* p2 = &b;
    & M9 X% E: k: c% xconst int* ct_p1 = &a; // ct for const type
    6 S+ y8 V: ]7 ~. K3 w6 Z0 K. Fconst 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; Xpp1 = &p2; // 合法,
    0 M/ C6 R: N" a% e5 xpp1 = &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& kct_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% Pconst int * const ct_cp1 = &a; // 指针也不能修改
    * K- d! ^; \. R6 x7 y8 U4 Bconst int * const ct_cp2 = &b; // 指针也不能修改
    1 ?, S" C6 D; @9 S" B4 A  Zct_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, Bconst int* const * const ct_cp_cp1 = &ct_cp1;
    , R3 m  P  q9 W- ?* l4 xct_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" Xc = &d;8 L* y2 e/ e, Q: |# [2 Q
    e = &b; // e为int**类型 指针的指针
    8 ]8 |1 V3 i# E9 m4 N  Qe = &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 Nint 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" ua[1] = 1;
    & |6 t4 O" p8 V! i( ^1 H  mp[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+ Tint a[10];
    9 J5 X& `# G- E  l/ ?+ i4 Z: v& j( q5 u; Xint 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 xp_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 `! cassert(sizeof(p_a) == 4); // 32bit程序。
    ( l  \) ]. `, L( h& G$ N" yassert(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' Evoid 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 g1 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% lvoid 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' N1 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. Jint a[3][4];! E+ t# C. y5 C3 q1 f
    column 0        column 1        column 2        column 3
    $ ]6 t, F6 J6 w0 c& prow 0        a[0][0]        a[0][1]        a[0][2]        a[0][3]
    $ S) ^; H( U( |& G' J) x% hrow 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 ^& Eint 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% Nvoid 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 bint 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! [/ iunsigned 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 senum
    - 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$ Oint a[10];
    + U9 m& }! w4 V1 e/ W' f! h然后对数组里的名称用括号括起来后再在变量名称前面加个*号
    5 L( E+ T7 X# e# W. r9 D' Sint (*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 wint(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 nint* e[10];
    - b+ U  E' w; eh = &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+ Gint a[10];
    4 K7 f2 \! d# B" D- {* N7 u( Mint(*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/ |- a4 ~" 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# Aint 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" Bp_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$ ifor (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 Tint 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" nint (*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- o3 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/ hint 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 C5 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 mtypedef定义函数指针的语法
    7 L( c6 J  r0 m2 D, Btypedef有两种做法, 一种就是定义一种函数对象,另外一种就是定义函数指针。用法稍稍不同,效果是一样。其中函数对象不支持赋值, 但是支持引用。
    " `" ^" ]& q  u
    ' B2 s$ D+ ~1 |# J  k$ |3 K4 b0 @. itypedef int FuncObject(int a, int b); // FuncObject类型是函数对象
    1 |! J! _2 j5 k8 J) \% g) jtypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针
      F3 x. C0 B% @FuncObject* f1 = &Add;
    4 ~( n$ C; M- uFuncPointer f2;$ Q8 _" K3 W, U# l4 x
    f2 = f1; // f1, f2类型一样, 都是形式为int(int, int)的函数的指针。
    & X/ {1 L2 [' |+ S/ J( yFuncObject f3 = Add; // 报错! 函数对象不支持拷贝
    9 E1 i- ^' d7 g" r$ AFuncObject f4 = &Add; // 报错!&Add是函数指针,与函数对象类型不匹配
    ' Z; m9 n3 d. t" ]; s' kFuncObject& f5 = Add; // 正确( Z0 F. Z. v. y/ U0 @' z) z0 D0 x
    int ret = f5(2, 3); // 正确
      g1 |+ W9 ]* h, YFuncObject& 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 yint 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" wint 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) \; q9 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& ]( vusing别名定义函数指针
    ( `. V/ x3 n4 ]2 g( q# Ac++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% {/ vusing 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 nint 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$ tp();             // 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/ }+ }! NT 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( Lint 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& Yvoid (ClassName::*func)(int);
    # I3 w. ]* @& \4 c% N9 ~; ~, c对象式调用。
    " X1 ?: o8 F! v5 UClassName 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 dp->*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" Ustruct 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 v8 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 u5 _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% xtypedef 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 L7 z# B/ x! P, T0 U1 p  d
    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-4-16 06:23 , Processed in 0.356032 second(s), 50 queries .

    回顶部