QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2429|回复: 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++数组指针、函数指针、成员函数指针
    . L' s% K7 f/ a6 i" @8 _) w1 bC++数组指针、函数指针、成员函数指针( b* ~/ ~" ?" u3 T" Y

    ' K' p3 G4 K, D! D- M$ u
    ) Q: q8 K% J! r6 v操作符名称
    . q6 X! O9 V$ b' t; ?& 取地址符(Address-Of operator)- {4 f" C! d9 I" z6 g% c
    * 间接寻址运算符(Indirection operator)
    ' ?& ?: y" S/ V$ U& d' ?; T; z.和-> 成员访问运算符(Member-Access operators),用于取对象的成员。7 @1 `% \9 y/ C( F6 ~
    .*和->* 指向成员的指针运算符(Pointer-To-Member operators), 用于成员函数指针和成员变量指针的取对象。
      X0 |. X5 Z) [8 |: [7 [; J" @() 函数调用运算符(Function-Call operator)
      T5 h4 o3 k, d* F9 d:: 范围解析运算符(Scope-Resolution operator)
    : K- z* c4 B! u如何定义一个指针变量* W. N. Y! K, o$ a" ]
    假设类型T, 变量名称name, 指针的定义如下:
    4 H+ t# G; l4 d" ]7 ]* ]! s& M
    4 {3 Z3 l: k- M9 Q/ XT* name;
    3 ?9 C0 {& o0 D1 A1 m% X4 w) D- A标识变量名字name, 它是T类型的指针。例如, Y! H7 M; B( V

    8 k% F8 u7 o( v7 zint n = 0;
      z( S# y: E4 s( c% s. {int* p_n = &n;5 }: Y& e. x! X
    p_n是int指针类型, 指向某个int型的对象。" N" h5 T$ y# v. [9 D4 a7 [

    $ I1 N7 Q7 s# F. b指针变量的修饰
    ! @" y7 \$ ?- t/ O) K' i- S; A! U指针实际上也是一种变量类型, 只是它保存的内容有些特别, 是指定类型的地址值,通过间接寻址运算符(indirection operator)*, 可以访问到指针指向地址上的指定类型。
    8 c6 A" I! N6 r% b1 n4 K/ @& H7 X6 m6 i1 j/ p
    指针也可以用const, volatile修饰。 const int或int const均表示一个变量类型是int, 且该变量不能修改。以下两种写法都可以:
    - w/ W+ y* i( j* X2 @2 g- {6 r
    & X" J) T& Y8 n; iconst int a = 1;
    + O" c) n; |) M* bint const b = 2;
    8 n& @5 ?5 _1 H2 O1 I既然指针是也一种变量类型,同样支持被const修饰, 表示指针的值/指针的指向不允许修改, 指针所指向的那个变量是否允许修改, 那是另外修饰。写法如下:
    * ^! ]7 O, @2 Z6 y
    ; X' U! T. g( s% Q9 ?. ~& b0 Hint a = 1;) C  \" y4 ~6 D: A# \
    int b = 2;+ b* t0 E9 t2 Y  J2 _/ A) b: a4 t5 Q
    int* const cp_a = &a; // 指针的修饰词,放在*号后面。2 A9 i3 n) H" M" R/ t) ~# K
    *cp_a = 10; // 指针指向的值可以修改
    2 u: ^6 ^$ n% P9 n2 Qcp_a = &b; // 指针不能被修改,报错!, D" R% P# q* T  i
    总结带修饰的指针的格式:
    $ Y% F, R% G* E9 d9 _* c0 C9 Z3 b* L# H只要记住修饰词总是放在被修饰的内容后面。
    * ]) {) k- n+ y0 G+ B$ O5 P+ @2 D9 n
    cv表示const / volatile修饰词。指针定义形式如下:0 s; `7 a7 V, R" ]" Q

    $ [; c, J# f/ t% [' O0 W% fT [cv for T] * [cv for pointer] name  X/ @6 ?0 H. X0 I
    注意对T的修饰放在T的前面也是合法的写法。! d( S' a0 M  E1 D% F$ p  `5 L2 n' j
    # e3 P" b. m5 c/ J
    const int const c = 2;
    4 T! N, W9 G4 B在mscv编译器下也不会报错。
      j& o( m6 E& d: c' Q) G5 ^. `: c; T7 o# h
    完整的格式:
      g" }# J8 B0 d+ [8 Q' ?[cv for T] T [cv for T] * [cv for pointer] name& Y+ }- ]  I1 Q! r# X% @' o3 t* ]5 d
    ! c# I4 Q. c$ I5 ^0 r" L
    Syntax        meaning# b( C* T) w9 l6 ?/ U8 B1 X" `
    const T*       
    , ]4 \- p. M* ~$ [pointer to constant object
    - Q% {; I! h# Y6 Z6 v
    2 V0 c! e: G6 m7 ZT const*        pointer to constant object1 f5 T& P! C3 t6 j1 s; ^5 W4 k
    T* const        constant pointer to object2 ]3 b0 g! ?7 n' }9 ^7 j
    const T* const        constant pointer to constant object
    0 u9 x+ Z; c8 ^4 n8 l4 l/ ZT const* const        constant pointer to constant object
    # a4 K$ T9 m. \) e上面格式中T还可以是一种指针, 指针的指针仍然是按照修饰词总是修饰前面的标识(T或者*)来确定修饰的意图。
    . ?( K: p# n% L8 p- V3 r' ~# M( k1 ]
    " {* r* l1 }( x! ?# K* cint a = 1;* a0 y% l0 F9 ~
    int b = 2;/ S5 m* L" q! W
    3 R2 z, E! T1 h: L# M1 X5 X
    int* p_a = &a;
      @1 u: _& ?. D. a4 @*p_a = 10; // 合法
    " Z) a2 Y9 v9 [4 r  vp_a = &b; //合法
    % T0 Z5 `" g3 u! ~+ Z& E8 T- J
    & G+ s& c, W6 G! F$ s0 t/ Kconst int* cp_a = &a; // const修饰int类型, 并非修饰指针2 ~) p, A# n6 C% x; A
    *cp_a = 11; //报错! const int类型不能修改6 E7 w) j. P. b. ~
    cp_a = &b; // 合法, 指针没有const修饰,指针可以修改。. `. R7 H. V# `& V, ~4 R
    7 S6 K9 X' v4 w  P* }4 B
    int* const pc_a = &a; // const修饰指针。类型没有const修饰
    0 Z2 q! X+ _4 h*pc_a = 12; // 合法, 因为类型没有const修饰,可以修改。
    0 C) P# ~# c& s5 @- c# Spc_a = &b; //报错! 指针被const修饰, 不能修改指针。9 m4 \; Y( t, j" p
    6 R7 g/ g: v0 B1 a0 a4 [! J, G
    int const* const cpc_a = &a; // int类型被它后面的const修饰, 指针符号*后面也有const修饰
    ' ^: X+ Z/ N$ W4 ]6 i! E0 k" V*cpc_a = 13; // 报错! 类型被const修饰,不能修改。# N. Q* A. b* g
    cpc_a = &b; // 报错! 指针被const修饰,不能修改。" p& j3 s/ A1 U5 R) O

    ' X, P$ ^! S7 T5 h 更复杂的指针的指针: ^  ?6 y5 e( E" u) n6 z
    0 A" Q! O' J( A, i9 P5 W
    int a = 1;
    . r/ i' Z" U- Gint b = 2;
    5 N3 ^5 _0 B. bint* p1 = &a;
    1 I" Z" p" O5 d7 V9 d' Z9 H. X/ }int* p2 = &b;% W/ p$ m* I8 P+ s5 r
    const int* ct_p1 = &a; // ct for const type  `; _- N& H; X7 o
    const int* ct_p2 = &b; // ct for const type
    % X7 ^4 i. U8 s1 J1 E. I" \/ y* \+ t! o! V0 C
    // int * * pp1; 指向(int*)类型的指针
    9 c8 E/ `0 N) M: Z: H; i5 kint** pp1 = &p1;  8 L  d. t' g( P
    pp1 = &p2; // 合法, , @0 x& A3 _, v7 [5 O% Q
    pp1 = &ct_p1; // 报错! 类型不匹配。 (int*)不能指向(const int*)
    ! F1 l5 l% Y4 H% N6 X# W# C& ]8 \( C# ?, D! B' T4 t9 S
    // (const int) * * pp1; 指向((const int) *)类型的指针
    * x# C% ?) N! e% f; z0 E5 r3 fconst int** ct_pp1 = &ct_p1;  
    4 Z$ n* c) t* p% `. B- P( w0 Bct_pp1 = &ct_p2; // 合法
    5 x2 U0 m; L2 ^3 [- W  ^ct_pp1 = &p1; // 合法!(const int*) 可以指向(int*)类型。
    ; l4 d+ S  Z: w" E
    ' k' {. d1 Z2 i: |; i, T// (const int) (*const)
    / d( f+ r1 l0 _6 a4 b2 j# vconst int * const ct_cp1 = &a; // 指针也不能修改
    1 C8 Z& B: Y8 ?- P9 z: Q! Lconst int * const ct_cp2 = &b; // 指针也不能修改  f; y6 [: H& p, X' a# x
    ct_cp1 = &b; // 报错!指针有const修饰0 g3 y8 b% }5 T3 V6 y
    & \! E2 m; Z) P" {. |, G- ]
    // (const int) (* const) *  指向((const int) (*const))的指针7 h$ i; o% p) S( z! O
    const int* const * ct_cp_p1 = &ct_p1;  1 ~' m- o8 J2 R5 F4 x
    ct_cp_p1 = &ct_cp2; // 合法, 指针的指针并没有const修饰, 指向的指针有const修饰
    6 _/ c. l+ V4 m7 _5 e*ct_cp_p1 = &a; // 报错!等价于操作ct_cp2,  指向的指针是带const修饰的不能修改% V3 |- d; H' w; H6 K& Q3 E
    - y: z- q% J% F' }6 O# b
    // (const int) (* const) (*const)  , z5 A/ _+ V  w
    // 指向((const int) (*const))的指针,且该指针被const修饰
    # s; P  O1 u+ K) t( Z: Bconst int* const * const ct_cp_cp1 = &ct_cp1; ; q$ D0 _. ]8 m
    ct_cp_cp1 = &ct_cp2; // 报错! 指针的指针被const修饰, 不能修改指针指向。
    ' A. x7 d" w" b, _; {0 @5 K3 X' w* p( p" J
    一行声明多个变量2 H/ a' R  l2 Y
    类型 + 名称定义一个变量。
    . |5 _" e" P3 y8 w0 x3 c变量的前面可以加*号修饰, 表示指针, 一个星号代表一层间接。**表示指针的指针。5 u; q7 [8 K2 T7 d) l

    7 Y$ X. T0 ~7 c/ o# B% Qint a, *b, *c, d, **e;. ~5 P" w! q  q" [! }4 V
    a = 0;
    " y% t& v  S; p  h5 {3 [7 \d = 1;
    , g5 L) J* B" p1 {  P3 @b = &a;+ `! s7 P. L' k# {/ p
    c = &d;
    : h+ g7 @, O2 ]" R1 G! W# r/ `e = &b; // e为int**类型 指针的指针% V1 N( T) t4 Y/ V$ t" B
    e = &c; // e为int**类型 指针的指针
    0 ^# T2 U/ J/ R! d3 L8 l也可以用括号包围变量和*号。
    4 S+ b, i& |( V2 X0 G+ @7 f: v1 \3 i% O% ?4 l' g
    int (a), (*b), (*c), (d), (**e); // 合法定义。, c/ j6 _8 D# {6 m+ S8 a
    括号可以省略,某些情况, 个人感觉加上括号更清晰一些。例如) L% u: @" F  O) W) O

    3 p; s" h" K9 s6 Lint (a), (const *b), (*const c) = &a, (const d), (const* const* const e) = &c;' t7 h( S8 |' X1 ?: J; [
    写成! y( A4 |: R  K. l0 g, j

    0 \* ]1 V9 }" K! wint a, const *b, *const c = &a, const d, const* const* const e = &c;+ D" `) F, A: T, S9 _
    更重要的是, 后面我们表达数组指针,以及函数指针时,括号是不可缺少的, 带括号的表达更加统一。+ X6 z5 ?- o4 m( K& K

    2 E( A! o7 ~1 c1 ?6 L# v" s数组指针( c( H; F1 v- Z( m
    数组基本表达& {3 z4 L9 s) U3 P' f6 \
    int a[10];  // 定义了类型是int, 元素个数是10的一个数组。
    " x) y# j8 u( ~% P. V+ [由于c++要支持一行定义一个类型的多个变量。 所以数组的[]时放在名称后面的。虽然我觉得
    , y& h  T/ c9 c8 ~- H! F1 v+ r; D1 E$ v: [: W
    int[10] a;+ ]0 _' h& h9 c* M" z7 A/ f/ l# s' @
    这样的写法更符合类型 名称的思维, 但是如果类型都这么写的话, 没法兼容以下的写法:
    - u5 w  D. k" n+ D+ S, l9 ?% `) N/ E' K, x; X4 d
    int a = 0, *b = nullptr, c[20], **d = nullptr;: W0 u( k; X. h" y! K
    c++标准规定如此,但我们可以通过每一行只定义一个变量的写法, 类型会更加清晰。. H  H) }! W# z3 Z- y
    $ r0 D3 W8 ^, a* ]$ z+ @) ^
    int a = 0;
    % J8 Q- l, x, o2 T. ?3 ?8 Nint* b = nullptr; // 指针int*
    0 [# L- w: ?( k% Sint c[20];
    : r9 b* F* H  u" q  S9 m( xint** d = nullptr; // 指针的指针int**
    $ D4 n% r* k+ ?) \; A: T# I数组的名称是什么类型
    2 h  {% _2 B2 Y* x8 s  O& V& x数组元素类型的指针,可以直接指向数组。 并且数组跟指针一样,可以通过下标去访问元素。5 _8 ?; r% [% A3 M  y' _

    & q7 m" L  ^8 r  G& xint a[10];
    0 \, `( ?+ S8 V5 G- x  Wint* p = a; // 指向a数组的第一个元素1 f1 t7 `" k- O. c/ z3 A
    a[1] = 1;
    / x5 K% g/ L* O. ~0 {, w9 O5 Lp[1] = 1; // 效果与a[1] = 1一样。
    ' W4 e* K) c8 G1 Y% M9 X- h数组可以当作T* const来使用, 但是又与T* const有些不同。sizeof()的结果不一样。- z* S. f4 Z/ R) g0 m" E1 J

      K. w) j* j' i( U- t  i( k, g* K& L- \int a[10];9 B$ i) U1 J, X; H8 U' Y4 l
    int b[10];
    9 M/ r* }# I6 h2 B& k" N0 U8 Xint* const p_a = a;
    ! W; S) x) J$ ]- ca[0] = 1; // 合法。 数组的元素可以修改。2 e! _; n5 ^7 p# v0 c
    p_a[0] = 1; // 效果与a[0] = 1一样。
    $ N6 l( o0 @/ |+ v  G1 u
    5 ]5 a6 Y* I. H! u& Qa = b; // 报错! 数组本身的指向不能修改。7 Z( q6 J$ T5 U+ I1 ?0 O

    ! ^/ X8 g! k; s4 r2 b// 所以数组a可以当作int* const来使用6 z. A3 F  u  w! T' ~2 O
    int *const& ref1 = a; //正确。
    ' g! V0 |: S1 ^$ Tint *& ref2 = a; // 报错!0 x3 O" a5 O- ^' v$ `

      j. m9 ~% h" p; p// 但是又跟int* const有些区别。! ?% u9 J) U2 H/ g  w* o& A6 G
    assert(sizeof(p_a) == 4); // 32bit程序。7 d/ J5 T. I3 D. L- q# t; c2 L
    assert(sizeof(a) == 4*10); // 32bit程序
    3 R: @" i% s3 P" P5 i- `3 u) U
    1 c* [2 J  R1 x3 W! |/ T: w数组跟元素指针的作用很相似,都可以通过下标去访问元素, 但调用sizeof()函数的结果不一样。元素指针的sizeof()返回值是4(32-bit应用)或者8(64-bit应用), 数组的sizeof()返回值是数组实际占用的空间。数组可以当作指向第一个元素地址的T* const来用其实就是我们常说的数组到指针的隐式转换。当数组作为函数参数传递后,会自动退化成T* const, 在被调用的函数内部调用sizeof()的返回值跟T* const指针大小一样。 数组传递作为函数参数后, 在被调用函数的内部与T* const是没有任何区别,只有在数组定义的可见范围内sizeof()才有获取数组占用空间大小的效果。
    2 ]" Q0 Z0 n9 B/ a+ H( e2 {4 \6 n6 {
    以下3个函数翻译成汇编以后,汇编代码是一样的。
    % D4 W0 K0 m7 M! i) J: E
    1 {! h! N. x9 j0 j6 c9 fvoid Func1(int* p_ary)
    ( p  X+ \6 _7 u6 D{
    $ H0 }& R2 t, f. N- y1 S    assert(sizeof(p_ary) == 4); // 32-bit6 s% l5 W- }( t( L
        p_ary[1] = 1;
    6 J/ d. p4 I, P. t( {) ?  G}
    4 K2 x# v# |$ H( f. o- K. D0 p3 T  E
    ; S. k. J+ r; y) Z, Gvoid Func2(int ary[])
    , x5 d/ H* f; P$ X" d{/ L% I( B: N+ o
        assert(sizeof(ary) == 4); // 32-bit: @) r( i9 x' M' U2 v9 F
        ary[1] = 1;" F; L! J( R5 \
    }) V# B/ E/ n6 @) l1 n
      D3 g5 k9 |7 x5 e5 M
    void Func3(int ary[10])1 p( Q6 q+ I( A" T: ?1 e
    {- U* [3 T6 Z$ o' `* q8 y! O. b
        assert(sizeof(ary) == 4); // 32-bit# p* X& k0 h, z8 q1 y7 I
        ary[1] = 1;
    ; w4 x+ B) R$ F# y}
    7 I- c3 b' d2 G
    " m! ]4 q- S" N7 y$ f, c, G% mint main(int argc, char** argv)
    + E3 F7 `# g- s: O" N" l{
    9 E! @* @3 s& G( Z* b    int a[10];
    6 M8 v, H  }0 }3 ^3 B    int b[20];
    " o" s, P# U7 u8 _; A    Func1(a);
    # B1 V& @- \$ G+ u$ h    Func2(a);, y; q# `1 J9 u4 q% Z1 P
        Func3(a);
    1 B5 m) _. v4 |3 ?9 C/ L. I    Func3(b); // 退化成int* const了, 即使数组长度不匹配也不会报错。
    # |# ]: y" }6 t1 u    return 0;) c. m8 U$ _0 |" d/ b
    }3 S' s" i3 V8 o$ V+ v5 L9 ^

    6 C: ^0 o7 F% r8 k  s- A1 N  u" R
    ) D) h/ S: y3 r, ]- t; f  o& |+ X. ]% e) j6 _/ P! S5 o3 ?% E
    多维数组' j& H1 |* G- o2 b: F; Z- L- E
    一个3行,4列的数组, 结构如下:
      h* @+ D/ C$ W! t0 U9 a9 O4 V
    ) n3 P$ _+ G! Rint a[3][4];5 K6 ?+ r8 t. j  `: m8 l5 A3 |
    column 0        column 1        column 2        column 3" s% ^  ]6 x( L) I7 n4 Y
    row 0        a[0][0]        a[0][1]        a[0][2]        a[0][3]( c% a3 @& j* Q# a% x6 q0 C
    row 1        a[1][0]        a[1][1]        a[1][2]        a[1][3]: P% ^8 q, F8 c, E* g6 `. a
    row 2        a[2][0]        a[2][1]        a[2][2]        a[2][3]* r' W1 @% i: k0 Z& X5 s1 g
    数组初始化( V/ i# S* o- V$ S( {9 G  j  k
    8 O' @* v/ U# H1 J- \! c% g% Z! o
    int a[3][4] = {* i0 @7 A5 U, X2 y
        {0, 1, 2, 3},
    - P! S7 D& D2 V, W: ?+ G) P    {4, 5, 6, 7},
    ; A4 X& W& a$ H+ R9 n    {8, 9, 10, 11}
    & e# O  B* A# d7 n" C7 Z};
    ; |2 w/ ?7 b# B2 d3 H 实际上多维数组和1维数组在开启速度优化后,翻译成汇编代码是一样的。; U, d8 {8 G8 ~4 P5 B8 J
    ; @1 v, }; p7 T. C% b; w+ {& O+ k
    void Print(int* p_ary);
    ( d6 b4 P7 N2 \
    . _4 y- ^8 S" [% M8 R9 ^void Test1()7 j) K$ |3 y3 S! B; m. i9 S
    {
    . w" T5 ~/ a5 r7 A    int a[10];8 Z  w) F+ E: o8 f& ?3 A# c9 M6 Q
        a[3] = 3;
    5 `; z- f! L5 g1 D7 m1 `3 R6 @    a[7] = 7;  K, ?: l* m* T" b7 K9 Y
        Print(&a[0]);7 ~$ F. c# O/ D5 E4 V' O2 u( ?; G
    }
    ( v0 S; M+ F" T% V" k, A
    / O' z' Y& M% j/ `void Test2()
    6 }& j# G0 R; K; X% U{: n2 ^3 R: S* u4 ^& n
        int a[2][5];4 J5 ], A# |" Y; o7 w6 ]8 ~
        a[0][3] = 3;+ g) J9 \5 k$ p9 g- [% W
        a[1][2] = 7;. i1 H1 Q7 v! p4 D, L
        Print(&a[0][0]);9 @' E/ G0 w4 s3 E, W4 i# J
    }
    : |7 g; w, F) b& Z1 h# n6 z. _* U0 V, N7 z% h& q8 j

    6 h8 u7 f* ]& t6 ~" d' c5 l8 \: B
    很自然地,多维数组也支持用1维数组的方式去初始化。' j; m. z) ]3 i/ b( q- A
    & R2 o8 ?! t" `& K
    int a[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };; P6 n9 r1 d/ h; ]2 ?' ^$ v3 [( t& X
    既然多维数组与1维数组没什么区别, 为什么还需要多维数组?
    % Y8 u: I4 j0 y! n$ k; a2 K7 H: M
    假设有一幅RGB图像720*576个像素,每个像素有RGB三个通道,每个通道的值是8bit大小。给出图像的首地址p_rgb_image, 我们要取第40行,第50个像素的R,G,B值。代码如下:/ b  W0 [% P! _, s2 {, t

    / o# s; o1 w, i2 W( d; {unsigned char* p_rgb_image;5 T6 t9 e- O' O( [% s, Z% x; d) K
    unsigned char r = p_rgb_image[40*720*3 + 50 + 0];
      o- y7 H! c( K) j& ^( Dunsigned char g = p_rgb_image[40*720*3 + 50 + 1];& `% s5 z/ D+ L
    unsigned char b = p_rgb_image[40*720*3 + 50 + 2];
    $ f9 f6 }* L& r9 J' m类似这样的场景, 采用多维数组的写法, 有点类似以索引为参数,可读性更高。相当于程序员和编译器打了一个配合。
    5 n$ ^- o) u7 r+ e# m) D/ w3 T. w  |5 M$ d
    enum
    ; h8 k/ K$ k- C9 Y- h{
    & Y4 P  M  T5 H  }' b Red = 0,
    " Y" M/ j) E  d& Y% S Green = 1,' E% B9 T8 b9 }, b+ T; @
    Blue = 2
      }& C* T4 y% a5 T; c. i};3 c. W/ G" V6 K8 _) E% ^" c
    unsigned char rgb_image[576][720][3];
    ! l8 b7 g: d9 H' J5 B8 uint row = 40;
    1 N; J% Q6 s8 l" }3 aint col = 50;4 r! _4 y  M/ t; y! w8 O8 `
    unsigned char r = rgb_image[row][col][Red];' C) a; o6 _& D& U! Q
    unsigned char g = rgb_image[row][col][Green];" I* Z/ {( i' w) F: b$ d. N
    unsigned char b = rgb_image[row][col][Blue];
    ' N, P7 l$ R. P7 @1 Y3 q数组指针以及与指针数组的区别) [7 y$ o5 ?% a, L& [. ]
    数组指针,是一个指针, 指向的对象是数组。 数组指针的赋值,要求数组的长度匹配,否则会报错。当指向1维数组时, 需用用*取得数组对象的引用,再用下标来访问数组元素。
    . q' s8 S+ s. C& i* P指针数组,是一个数组, 数组保存的元素的类型是指针。
    & W+ b9 g% L: J# G0 {数组指针的定义5 z1 }6 [/ O/ X0 D  t
    数组指针定义先定义一个数组。, C# L# y* j" W
    int a[10];
    3 ?% _- w) _, [1 J3 C7 e然后对数组里的名称用括号括起来后再在变量名称前面加个*号$ U% q& b& Y7 K6 n
    int (*a)[10];
    / g& p/ ^( H; |后面你会发现函数指针定义类似。4 G. {9 _' i) z: {3 y$ X& C  F$ Y
    // 各类定义对比
    2 ^9 a. d$ f4 ]8 _# i# A( W0 Jint a, *b, **c, d[10], e[10][20], *f[10], (*g)[10], *(*h)[10];
    / I* R, a8 m3 {+ @5 h/ n$ k, ^; }+ V
    int *f[10]; // 指针数组, f是包含10个元素的数组, 数组里每一个元素的类型都是int*& y  M) ]# X, Q, e
    int *(f2[10]); // 指针数组。另外一种定义方式。
    4 C1 e: d2 T0 B% y% Tint(*f3[10]); // 指针数组。另外一种定义方式。
    # J9 @! U+ [+ V" R! P& Q/ N0 a& G8 tint(f4)[10]; // int数组
    $ s6 s% Q: A+ O# F1 Uint(*g)[10]; // 数组指针, g是一个指针, 这个指针可以指向类型是int,元素个数是10的数组
    8 H5 C1 p8 R4 M: p1 u; [0 D+ V/ Z6 Iint* (*h)[10]; // 数组指针, h是一个指针, 这个指针可以指向类型是int*,元素个数是10的指针数组) ]. b* e3 v2 ?
    " _; L9 Q9 q# }; A7 B. u
    int d[10];
    . S3 Y! Q4 T' x7 u. [& [0 qg = &d;
    9 m' z3 i7 Y) i8 p  }) i
    & h' Y; E" b! n# jint* e[10];2 J# ?. m3 m* j( h0 {1 K
    h = &e;: l/ G/ f, d1 |- \/ E
    数组指针的使用
    8 v$ O6 w; U/ m) I5 P6 H数组指针一般先通过*号取得指针指向的数组对象, 然后再用下标操作访问元素。
    , _- ]) P* e4 v/ v$ z: \# t9 P9 \& ?9 v3 Z% H
    int a[10];% W. C3 U% Z0 c9 w  c5 F7 ]
    int(*p_ary)[10] = &a; // p_ary是一个指针, 指向"int (*)[10]"类型的数组
    6 \$ {$ V  e- o8 d3 Wfor (int i = 0; i < 10; i++) {2 \9 ]. N1 d5 F0 m7 I  z
    // p_ary是一个指向数组的指针, 需要先通过间接寻址运算符*(indirection operator)取得数组对象
    * h4 h0 X. D! Q1 w3 z& i1 ~ // 再通过下标操作访问元素。: f4 N, f; Q+ W  R/ `
    (*p_ary) = i; : U( c! x9 Y1 B' }; Z
    }/ J7 O2 I+ o* F# l2 o

    ; d8 @+ b; ~7 Y+ u; W7 l9 N6 [int b[10];9 D8 s, c! W3 W8 w2 [; [1 ~
    int c[20];7 F) K1 N! @9 \2 B$ o& H3 T$ K
    p_ary = &b; // 合法4 z- }7 L, s& y6 `* a* w: J
    p_ary = &c; // 报错! 不能将 "int (*)[20]" 类型的值分配到 "int (*)[10]" 类型的实体, A) p4 `2 k3 ?
    数组指针指向多维数组的子数组
    0 j/ z( ?$ X# eint a[10];7 H% N+ b& g% K( j
    int b[4][10];# d! c2 T& r5 y% x$ O, P# b
    int(*p_ary)[10] = &a;( X; ~* U' a* a7 F: _5 @
    for (int i = 0; i < 10; i++) {
    $ W+ H9 u$ A: c1 L* [% Y7 Y (*p_ary) = 1; 8 s0 w/ }6 \3 V; K! a& ^
    }( d) h$ R: X9 K5 |9 y# R

    1 p- `2 _! N2 B9 c  r6 ~; Bp_ary = &b[2]; // 多维数组,可以看作数组的数组,7 Y# x( J9 T8 J* L& |
    // b[2][0] ~ b[2][9]的值都被改成2了
    3 y( u& ^# s4 A# R. {" ]for (int i = 0; i < 10; i++) {+ i: V4 }) Z1 h6 u/ K. N1 q
    (*p_ary) = 2;# s* V3 E- _, b+ n8 j
    }
    : k; c# K; _2 `多维数组指针
    1 q$ x& N" j1 C8 d" D3 ]" o多维数组指针,是一种指针,指向的对象是个多维数组,支持多个下标操作。) q5 T& T) g& ~/ k  A3 d6 v
    ( Y- s  f+ x- g2 Z9 n/ B
    int a[2][5][10];& |6 u/ l1 y/ c. C; W
    int(*p_ary1)[10] = &a[1][2]; // 1维数组指针* B2 o; N0 H7 k+ s5 a
    int(*p_ary2)[5][10] = &a[1]; // 2维数组指针
      V( V( U$ ]+ n% W9 P; Sfor (int row = 0; row < 5; row++) {
    6 v- z5 ~. w$ [) F    for (int col = 0; col < 10; col++) {
    # l0 k& c, g, Q  O) Z  V        (*p_ary2)[row][col] = row * col;" c  Z2 s, p0 P* Q$ f: V' I; ^) O
        }0 W/ B6 d8 z# m+ n, \+ j4 A- y
    }
    ( I4 {) m' v! \* E数组指针和指针数组对比实例
    * W8 \7 J( R! U5 e: c  @数组指针还是记住两步法即可$ t* {) N' D) q& _& d
    + `& e) L# Y/ F' l7 D
    定义一个数组
    ) E: d8 M% i# ~' E) I+ S2 w9 m括号包围1中定义的名称,再在名称前加个*号。1 q. _( {) Y+ |+ q: I5 I4 C4 p* ]
    int a[10];, c% b8 ?' _: Q
    int(*ary_pointer1)[10] = &a; // 数组指针
    ; d* S+ C& e, R3 o2 M, k, H3 rint* pointer_ary1[10]; // 指针数组。元素类型是int*
    ; p# l2 r+ ~4 R" x/ ^) {- sint *(ponter_ary2[10]); // 指针数组。另外一种定义方式。$ `2 z% b! q$ z" v3 S8 [
    int (*ponter_ary3[10]); // 指针数组。另外一种定义方式。
    5 n  D! j: `7 m8 j$ zint c, *d, (*ary_pointer2)[10], *pointer_ary3[10]; // 排列定义比较。
    2 G. s+ b5 P' Y) s1 Q9 n- m5 `
    - Q8 L0 Z4 v. p% H& K! r5 ]// 指针数组可以把每个元素指向数组对应位置的地址。& M" x9 l0 |# n* e2 Q0 @9 N
    // 这样遍历指针数组, 可以达到遍历数组元素的效果,但是注意每个元素都是指针,  K8 v9 ~8 X& t5 M0 u
    // 需要访问原数组的值的话, 需要对指针用*间接寻址运算符。
    0 y! H. s7 Z8 W7 @int* pointer_ary[10];
    1 E4 |6 {" R7 A: w! ^: o- J( r- Ffor (int i = 0; i < 10; i++) {5 J8 l7 |! S1 P4 Q3 j' g
        pointer_ary = &a;- D8 D9 D) I/ P4 R! Q  D+ I2 V! z
    }
    ; i) c' I# I4 R: D1 m2 t% s! P0 B// 类似遍历原数组效果。
    6 V# k2 C6 v  M: A, z) `7 rfor (int i = 0; i < 10; i++) {1 r$ M1 Q/ u8 H
        *pointer_ary = i; // 修改原数组。
    , n% [4 m  o* R2 a0 }5 E}
    1 S. \# q" O+ Y, o/ o9 `. a9 T
    4 g7 H' D& J( c  o4 W, P函数指针
    8 G: x) d/ V, b% y0 A7 C* N取得函数地址
    3 t3 V: X; v$ D! X5 c函数的名称作为参数被传递时,会隐式转换成函数指针, 和在函数名称前加取地址符&等价。建议带上更加统一和清晰。0 G, W3 l5 T2 j* b( s- Z- T
    ' f7 e3 q9 _  w0 h
    void f(int);' u0 R* f+ k4 S$ o
    int main()
    + [9 I- M/ D- g8 F5 }" f( o- `9 C{* [& }- D" h7 S3 N* X  H8 z
        void (*p1)(int) = &f;
    ' z3 U6 _* y  a* U4 {2 e" F) a    void (*p2)(int) = f; // same as &f* D  B( I8 z2 T3 P
        return 0;
    . x5 R& F% ]0 _0 [( W. t}" ~; l4 I/ b6 f8 T# \$ B' h7 O; d
    翻译成汇编代码, p1和p2的赋值是一样的。  Q. W8 `* y+ f! x: o3 p

    + i1 I4 c7 k: P4 Y3 f3 j
    : B1 G$ @9 i3 V, E) |
    / \5 a8 k* o2 e8 M函数指针的声明
    ( J% Y" K: R% S+ A' L单个函数指针变量定义步骤
    9 x! H" j. k: m( J0 z定义一个函数。void fun1(int a, int b); int fun2(double a);% b+ D6 _7 d* v* L/ O
    用括号把函数名称包围起来,然后在名称前面加*号。void (*fun1)(int a, int b); int (*fun2)(double a);
    * N3 ^  ^' U2 c8 n# G如果要定义函数指针数组,在定义单个函数指针的基础上,在名称后面加上[数组长度]void (*fun1[2])(int a, int b); int (*fun2[10])(double a);
    9 q8 D' w( f9 M# mtypedef定义函数指针
    ' _8 _# }1 T$ T. B$ N- M4 }9 ?! ?可读性高比单个定义要高,特别是声明多个同类型的函数指针,或者函数指针数组。
    3 n: J( X  t1 o! Y9 B
    . J, K/ a9 V. B9 W  `typedef定义函数指针的语法
    ) V& r9 w2 b: o* l( x/ Atypedef有两种做法, 一种就是定义一种函数对象,另外一种就是定义函数指针。用法稍稍不同,效果是一样。其中函数对象不支持赋值, 但是支持引用。
    , ~! c8 h# }/ M/ v# ?! T1 M- {8 {) ~9 Y1 \
    typedef int FuncObject(int a, int b); // FuncObject类型是函数对象# x1 }- q) d# ], Q. T
    typedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针
    ) y8 [9 _9 ]1 ^& S* iFuncObject* f1 = &Add;4 D3 }/ [/ m) I' t
    FuncPointer f2;: {3 Q  d! P( E# ?
    f2 = f1; // f1, f2类型一样, 都是形式为int(int, int)的函数的指针。
    1 G9 Z0 j3 ^8 J- O0 j& h( M& CFuncObject f3 = Add; // 报错! 函数对象不支持拷贝
    " e1 k' g8 B; q# s6 C& ?" u+ A7 mFuncObject f4 = &Add; // 报错!&Add是函数指针,与函数对象类型不匹配
    2 ]/ B  L+ ?% D6 k) a2 b7 YFuncObject& f5 = Add; // 正确4 s! F( ~. H9 @; ]7 `
    int ret = f5(2, 3); // 正确* O4 B8 k9 U7 ~. H' k, w) H9 c
    FuncObject& f6 = &Add; // 报错!&Add是函数指针,与函数对象引用类型不匹配
    ; P4 t3 G$ x' a+ c& u如何记住typedef定义函数指针的步骤4 U. Y1 N7 K# G6 M6 x6 S6 f
    像定义一个函数指针那样, 指定一个名称。int (*CalFun)(int a, int b);
    $ Z( W' d+ p6 p! Y/ U; _在这个函数指针变量声明前面加上typedef。typedef int (*CalFun)(int a, int b);
    4 u2 M- g% \! z. R! `& t完整例子; U2 u, K) t6 {! ?4 X; B# v7 G
    typedef int(*CalFun)(int a, int b);
    2 R; T) U$ F4 g8 A
    & }1 S0 `3 R4 U6 Yint Add(int a, int b)  k6 y6 m( ~: r9 w
    {* @5 W2 j+ T' P' {
        return (a + b);* {5 X/ \# n# ?! e2 |
    }
    5 Y: {; B: `; p/ w5 ]$ C% W6 E+ u
    int Sub(int a, int b), T  m% x# T5 v
    {
    2 K% o% s/ k" n5 E2 K( [2 T. t! S7 O    return (a - b);& E9 N- n# B2 A. a9 h5 B) g
    }: w: M: G5 p. j

    : K- N/ W. i1 t; z5 ]int main(int argc, char** argv)9 |5 L8 T" o  D' B0 b
    {
    8 U# e. t3 M, g/ v1 }    CalFun f1 = Add;
      z  K6 s9 m# w; H/ _    CalFun f2 = Sub;
    5 Y1 t6 p& k5 o5 w    int a = f1(2, 3);& A$ }( s9 ]% l0 n% z! U
        int b = f2(10, 5);7 L' K4 L" L2 M
      r; {- Y9 ~( t
        // typedef定义的函数指针数组。$ r$ C! m) b& r- D6 t
        CalFun f_ary[2];
    - d$ ?" e: s- u( Z    f_ary[0] = Add;  T7 F; r$ u* l+ J/ Z; _( K* |
        f_ary[1] = Sub;! b0 d9 W/ O$ f# G! w( B
    % O" \# l( |- V/ |
        // 单个定义的函数指针数组。; m* u" V; Q% G8 Y: s
        int(*f_ary2[2])(int a, int b);9 v+ ~! }3 V' d0 U6 [
        f_ary2[0] = Add;
    & T  r) z0 b$ E' L    f_ary2[1] = Sub;! E( `+ d- l$ M0 v* F

    * Z- t( d1 a7 v# U/ a- j    return 0;
    7 P! e! e# E5 r; k5 [}+ z/ \  f8 G3 \+ N8 }
    ' |$ N4 d( C5 A! p% Q& d
    using别名定义函数指针
    2 P* G" f1 `# ~% u, M' y6 @$ Ec++11以后的类型别名定义--using也可以用于定义函数指针, typedef的好处它都有,个人感觉比typedef更直观。using类型别名同样分函数对象和函数指针两种方式。
    % I+ s4 y- V, J4 R$ D3 D7 m1 I) s; P3 [- u
    typedef int FuncObject(int a, int b); // FuncObject类型是函数对象# T1 |: b- e. ^$ A. Q$ Z( o
    using FuncObject = int(int a, int b);
    ; u# F) t. k5 Gtypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针+ {5 Z8 m# @5 `( t' p# I6 R( q$ H
    using FuncPointer = int(*)(int a, int b);6 R# ~; e/ |' |/ x
    函数指针的调用1 r9 K  O  g& I( J# a
    函数指针和函数对象都可以直接后加括号调用4 Y; j# _" J! M& e! x. Q2 H- U& X
    int f();: L" l0 H( t8 P0 B
    int (*p)() = f;  // pointer p is pointing to f1 W( g0 K6 I4 P* z" L% ^
    int (&r)() = *p; // the lvalue that identifies f is bound to a reference$ x0 J& H( ?- @0 G
    r();             // function f invoked through lvalue reference
    0 b3 V% i, m; m: K" g7 m! l(*p)();          // function f invoked through the function lvalue. ~+ d- n1 j6 M! r4 m2 i3 x
    p();             // function f invoked directly through the pointer) k/ f* {7 F- E8 c. ?8 [2 I7 G
    如果函数有重载, 函数指针会指向匹配的那个版本。
    ) ^8 x. f2 C1 e9 O9 Vtemplate<typename T>
    1 @% Z/ _& x; F' V: G; w5 ST f(T n) { return n; }& W0 ]- U- G, j( b4 {

    $ g0 P" T- u# r( I: G0 ?double f(double n) { return n; }: n; A5 H% S5 A% C8 ^

    - L& y1 e7 L8 q+ C" Lint main()
    ' r$ ^. \1 c! r" _; h( D{
    : p. q. A$ [  f0 K" q+ d8 C, }2 x    int (*p)(int) = f; // instantiates and selects f<int>$ {6 l( F  r9 y) z8 U/ c7 J
    }% _4 B6 K. A$ j9 N4 T( Q8 A
    成员函数指针0 m, C* N0 q4 m" x
    静态成员函数,除了增加了访问控制以外,跟普通的函数指针没什么区别,所以普通函数指针可以直接指向类的静态成员函数。但非静态的成员函数与普通函数指针不太一样,声明时需要指定函数归属的类名,并且调用需要指定对象实例。
    + h' V/ _8 ]3 j, W! i7 G6 |( d; s/ @6 ]0 Q, o
    成员函数指针定义。
    4 ^, ]7 P$ @& d6 e  b9 m像定义类成员函数实现那样写, 并任意指定名称,这里作func。void ClassName::func(int);
    - i  t9 _1 X- |) f$ s括号把类名、范围解析运算符::、名称包围起来。void (ClassName::func)(int);
    0 E4 p4 y: e) p1 q. q在名称的前面加个*号void (ClassName::*func)(int);- J6 P/ I' `, C5 {3 U$ @+ y( n
    成员函数也支持typedef和using的定义方式。typedef void(C::* MemberFunc)(int); using MemberFunc = void(C::*)(int);. n7 X* j+ a* f& Q% q4 `: l$ W- p  J
    成员函数指针如何调用。& }* A) H, ?+ e! k2 i
    假设成员函数指针名字为func
    . c; F) s9 X. v3 R1 w. t- p* E1 y  S* w* N' ~) Y# s
    void (ClassName::*func)(int);
      @8 Q3 j; O- k$ W" r对象式调用。! `! ]6 Q5 S) f/ q& R- u
    ClassName c; // 被调用的对象
      `1 }6 J1 P" s4 v: O0 q1 K/ l4 h成员函数指针名字当作正常函数那样写。6 O7 D" c, H/ Z% b1 q9 A/ v. P
    c.func(3);
    / Y' t% m+ s  ]& O5 p成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。
    & Z5 p7 H, J2 q- w7 B9 Kc.*func(3);
    7 W) @+ ~7 |+ l最后用括号把调用对象、成员访问运算符.、间接寻址运算符*、和成员函数指针的名称包围起来。2 }# y( m7 i: I2 t
    (c.*func)(3);
    ) I( F& l) t) _, \( R为何要加上括号? 根据c++的优先级标准,取成员运算符. > 函数调用() > 间接引用符*。 *号优先级比函数调用要低, 成员函数指针还没取得对象就被调用了,自然报错。 另外. `  j: V. p9 @
    (c.(*func))(3);
    9 k" h3 z1 k  A4 ]这样的写法也不行。 .*和->*是整体作为一个运算符的,中间不能用括号隔开。9 ?+ H! u% F! N' N) t9 @, @
    指针式调用
    3 \7 B" ^# S$ a$ {ClassName* p; // 被调用的对象的指针
    # l# U, R& G; P% z( T; p! c成员函数指针名字当作正常函数那样写。
    5 p7 W: n, \( A' O  @2 Vp->func(3);
    * _: [( }: w/ B成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。, r; g+ _4 {) R% ]$ M% H
    p->*func(3);* W* I) c( w1 H1 ~. |
    最后用括号把调用对象、成员访问运算符->、间接寻址运算符*、和成员函数指针的名称包围起来。
    2 t. N( H" e1 Y2 }(p->*func)(3);
    % Y& F7 k2 E$ q0 M1 P/ G函数指针使用完整例子" j; C! S) E+ S+ B4 P' {+ _
    struct Cal
    ; u7 k, j8 G0 C6 I  L; s. e{ / l7 r7 V2 j# j1 [. k+ [. g6 n; S
        int add(int a, int b); & a( K" y# b& V$ Z$ |9 d
        int sub(int a, int b);
    & k! N, b6 H/ ~% J% }};# R, f5 @5 C  @4 L& k( G; j

    ) k% e8 [& H& ^int main()) t4 k. D  k; }3 v. S
    {/ U5 L, ?& A. g+ {
        int (Cal::*fun)(int, int) = &Cal::add;
    % _3 M2 K: m( r: w3 W    fun = &Cal::sub;
    5 w, Y4 v3 A! G9 ^
    / H0 v8 u1 L+ y8 P# G; C/ l    Cal* p_cal = new Cal();  t8 K7 ]6 ]' @& V3 s1 O
        int r1 = (p_cal->*fun)(2, 3);
    ! {+ m. H( G6 w7 y7 Q    delete p_cal;
    " s, H: S" L& P+ @. O
    4 D/ S' \  u2 N% ?+ W    Cal local_cal;2 A* F' r! @2 B+ N8 ^, H7 F6 ]5 f
        int r2 = (local_cal.*fun)(8, 6);) ]* H3 h9 q+ ^6 D5 V7 R4 R1 v3 o0 J
    }
    9 O' C' z6 I6 |1 c9 ^
      `/ H( ~8 N* z4 |8 K成员变量指针
    - f: r9 u  d$ U, a& V7 |5 v+ c' J成员变量指针比成员函数指针还要简单些,没有函数调用, 无需考虑函数调用和间接引用符*的优先级问题。. f/ c/ Y( v  \3 y7 l6 z5 R6 P
    / j3 V2 k/ e/ ^/ B) ~: ^* Q4 u. P0 J
    成员变量指针的定义
    ( `' I. M0 q5 ^假如以下结构体C。' e! X: r9 l; o2 ^* x- ?) L# T( o

    $ q0 w: w+ d3 jstruct C
    9 z' b; W4 l6 T% {* B) a{ 9 ?5 f+ K& w5 k  E8 N5 ~
        int m;
    1 I/ j: \5 a7 m9 ~7 d3 F/ d# ~$ w" t};
    4 K5 H: S0 r1 d: N& o5 _+ i单个成员变量指针定义
    4 j& r& N( ]7 i; r0 l6 G8 h" O" I假设名称为p, 类似静态成员变量定义那样声明* R: `0 t' p+ ~& I, Z( u
    int C::p;+ o9 Y/ Z  Y  m
    在名称前面加上指针标识号*
    # A! ^/ n3 z" a+ `5 t" }+ Iint C::*p;& T; y9 Z0 ?5 u
    typedef或using方式定义
    ( N7 Y2 R# E; _  P9 \2 q0 K4 P5 Ftypedef int C::*MemberPointer;- W( y( x, y% ^6 D: `
    using MemberPointer = int C::*;) Q9 D( I; U* T0 x+ P
    成员变量指针的使用。
    3 z* [0 ~  ^1 ]: A% n/ O$ d# L类似成员函数指针那样,直接使用指向成员的指针运算符:.* 和->*即可。. @! S, x$ {0 N# `' H
    成员变量指针, 能让我们实现一些遍历成员的动态功能。 例如把一个类/结构体的多个同类型的成员变量放进一个容器里,然后遍历访问这些成员变量。4 h( G' p) Y1 k: j# `) n

    2 n& x& {  \/ i0 \3 I 完整例子8 T" N6 E, E" i  I+ m7 |- F
    struct C { int m; };/ X  q; F2 f, k! B& M% A/ y& v
    int main()4 b; |6 p5 m% ^+ T/ M
    {
    ) P% }: k1 n/ a1 W4 M9 o    int C::* p = &C::m;          // pointer to data member m of class C
    0 @- C, x  K! E    C c = {7};' K' t* A1 N* I. y0 y( j! h! n
        std::cout << c.*p << '\n';   // prints 7  Q0 a/ H8 J& L# X
        C* cp = &c;
    7 U5 g5 {( [: v- a    cp->m = 10;1 c& @/ }  o1 Q: c3 g/ d
        std::cout << cp->*p << '\n'; // prints 10
    9 o, O1 b; Y7 y( |8 u  H2 ]( r# D
    2 w! L2 j9 V% ^0 L  \  }% t————————————————" u: O& }! W  h; T7 _. K: F. n1 e: g
    版权声明:本文为CSDN博主「南风fahaxiki」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。8 Q* f6 M+ c8 V5 ~3 a
    原文链接:https://blog.csdn.net/m0_64407685/article/details/126788115% z* ]4 w; R" O2 ~+ Y- _" k& U
    : M& a$ G) C+ t% X/ u+ ], M
    3 q7 m, r" q5 E. t% }6 l
    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-6-18 15:07 , Processed in 1.235538 second(s), 50 queries .

    回顶部