QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2440|回复: 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++数组指针、函数指针、成员函数指针
    ' ~8 r2 [0 R- BC++数组指针、函数指针、成员函数指针
    ! i; B) T. s' S4 F6 h/ Q5 K& r0 y
    . \' @6 l& G  W: \/ ~& j- T
    5 I8 o3 B: c# ~& E操作符名称
    " {# R* U' S' _, z8 ]& 取地址符(Address-Of operator)) L% _4 F9 l! `. k
    * 间接寻址运算符(Indirection operator)
    4 l: U. ]+ n2 _6 n- C1 D.和-> 成员访问运算符(Member-Access operators),用于取对象的成员。
    % w2 _" H5 S6 C( _; [# t: N6 s" @.*和->* 指向成员的指针运算符(Pointer-To-Member operators), 用于成员函数指针和成员变量指针的取对象。
    & y4 L/ G* [' G& o() 函数调用运算符(Function-Call operator)
    / h  j: _& l* @:: 范围解析运算符(Scope-Resolution operator)8 V, Z$ ?2 \. k) p$ z; U: B1 |6 e! @
    如何定义一个指针变量. y) l6 S- `0 h& v9 ^
    假设类型T, 变量名称name, 指针的定义如下:5 F# J+ _. k5 E
    & N0 O0 x% M$ `. t) D
    T* name;# I4 c+ r% w9 ?3 {9 r3 ]: y8 d# y
    标识变量名字name, 它是T类型的指针。例如- M9 r8 Y, h; J) \" S- B9 f
    $ Y# [7 V8 D7 g0 v2 ^/ F
    int n = 0;) V$ h8 p% c2 H) x) R) f
    int* p_n = &n;, B5 m7 f2 z0 Q7 K, M& b1 w3 i% N
    p_n是int指针类型, 指向某个int型的对象。
    " e* v$ p% o0 _& Y2 k# }# V5 R. J8 S8 w
    指针变量的修饰
    , g- J) F5 R& W: S指针实际上也是一种变量类型, 只是它保存的内容有些特别, 是指定类型的地址值,通过间接寻址运算符(indirection operator)*, 可以访问到指针指向地址上的指定类型。. o" Q% E8 P& ]2 F+ k
    , n2 z1 [4 m$ U' x1 V  {
    指针也可以用const, volatile修饰。 const int或int const均表示一个变量类型是int, 且该变量不能修改。以下两种写法都可以:! s( L9 K, a! v7 F
    % U2 O8 x6 w5 u# U/ ~
    const int a = 1;4 r0 A6 I/ C+ d# E! F# V! v
    int const b = 2;
    - a; S% w' h8 {  d既然指针是也一种变量类型,同样支持被const修饰, 表示指针的值/指针的指向不允许修改, 指针所指向的那个变量是否允许修改, 那是另外修饰。写法如下:7 S1 ~9 Y8 ^9 ^  R9 O; j9 a

    ) R, V4 S; d6 }* X5 aint a = 1;
    1 E- `- [3 i3 e* W) j. t1 K3 aint b = 2;$ c3 F( R& A6 G0 t3 |! [( f
    int* const cp_a = &a; // 指针的修饰词,放在*号后面。
    0 ^: j. w2 j& ]8 U/ P3 p  V*cp_a = 10; // 指针指向的值可以修改
    ! M1 x, K& m/ @' xcp_a = &b; // 指针不能被修改,报错!
    . m& M! g; D) z8 Z3 G总结带修饰的指针的格式:0 t1 l1 c# `* N# V3 U$ o+ s& k
    只要记住修饰词总是放在被修饰的内容后面。
    # o8 D0 `6 z( n& ]" m4 r) n9 y/ @9 C/ b0 l5 _7 ~4 p9 L$ E$ w+ b, p
    cv表示const / volatile修饰词。指针定义形式如下:
    / L/ C0 I7 S* Z6 e& h& y
    % K0 q7 n1 y# z3 rT [cv for T] * [cv for pointer] name+ K6 ]- {7 w/ l" e: V/ x
    注意对T的修饰放在T的前面也是合法的写法。
    ) F2 U* M) t0 H. E. u
    4 ?' C6 R) H7 D! O) Q2 i  lconst int const c = 2;7 x* e4 s# Q1 ?$ s, c' N$ E6 }' H, u) w
    在mscv编译器下也不会报错。
    ) Z' L5 G* A4 z- W& ]) E9 G% P
    完整的格式:
    5 F5 ]5 i" c% f  i[cv for T] T [cv for T] * [cv for pointer] name
    . `0 b/ w! z0 K, Y) L+ x
    : L, F, }/ a+ ?9 GSyntax        meaning5 U; R0 ]+ |. r% s# r) R8 h9 B
    const T*        ) G& R5 c( m, X' N# L& E. y& n
    pointer to constant object
    5 ]* ?/ s$ Q& t& V! V* V9 O
    6 U) h: q9 d; g9 R$ ZT const*        pointer to constant object8 t6 @2 F, ^6 g8 N) Q
    T* const        constant pointer to object
    " ?. y6 E- Z1 N3 L3 e+ [. ~5 lconst T* const        constant pointer to constant object% j* A5 g; G" H8 L% O- s
    T const* const        constant pointer to constant object
    ' Q3 r6 q* R( f! Q上面格式中T还可以是一种指针, 指针的指针仍然是按照修饰词总是修饰前面的标识(T或者*)来确定修饰的意图。
    0 C. L$ w0 [3 a% K
    ; E2 k7 F0 z  u& W* Xint a = 1;
    5 b' x5 [! ]7 y7 C* z2 o+ `: Vint b = 2;
    ( `- e, ?/ J" J& E) _* ^0 X
    ' d; ~7 Q- l* c9 \% Qint* p_a = &a;
      v, L5 V6 b/ B*p_a = 10; // 合法% r9 s. ?6 m+ D. k, [! Q+ L4 K- ?/ G
    p_a = &b; //合法3 s" t# w$ V1 M+ {0 O9 i# W- A
    ( x. w  e: p$ ]2 T" ]* o  t
    const int* cp_a = &a; // const修饰int类型, 并非修饰指针* h2 f6 o2 d& j% I8 A7 q
    *cp_a = 11; //报错! const int类型不能修改( ^3 J  @& l& n* Z) D& m
    cp_a = &b; // 合法, 指针没有const修饰,指针可以修改。+ P( b! z" ?1 T: J  V

    , k8 Q' W1 ~/ n  m# G  X8 tint* const pc_a = &a; // const修饰指针。类型没有const修饰2 z5 b2 v% d  z3 U
    *pc_a = 12; // 合法, 因为类型没有const修饰,可以修改。. w( K; E. t7 u# A- d2 m
    pc_a = &b; //报错! 指针被const修饰, 不能修改指针。8 |, g( \9 v; ?
    4 D; K5 l# |: ~' l+ c9 i/ X$ A
    int const* const cpc_a = &a; // int类型被它后面的const修饰, 指针符号*后面也有const修饰' k6 |) D. m; d0 u8 D' p
    *cpc_a = 13; // 报错! 类型被const修饰,不能修改。* `4 [9 K, |. C# Z
    cpc_a = &b; // 报错! 指针被const修饰,不能修改。& V1 D7 E# d: m
    4 {* A% s0 t% Q- i5 U# v) j) M
    更复杂的指针的指针  w, x' w- E6 C/ Y. F

    4 o/ c& g' D4 l9 y# a! C' y, |int a = 1;
    6 K; p! c4 g+ uint b = 2;+ V) e& l' m& z8 {3 I) X
    int* p1 = &a;
    ; ~) f1 S, h% \6 v& u! eint* p2 = &b;) h% K9 ~: @$ B% c$ j
    const int* ct_p1 = &a; // ct for const type2 h( x8 Z3 j6 M! x
    const int* ct_p2 = &b; // ct for const type4 a$ {$ C1 b7 B0 h. J+ U* j# |
    % |' m3 ^; l2 V2 W4 l, `: ]/ v
    // int * * pp1; 指向(int*)类型的指针# R! B" @9 ^' c
    int** pp1 = &p1;    S4 L( }9 j" F7 \" p/ m0 E
    pp1 = &p2; // 合法,
    , Z" y2 `4 {7 kpp1 = &ct_p1; // 报错! 类型不匹配。 (int*)不能指向(const int*)
    / Y2 O3 I# D0 c! x! I6 J& ^1 z+ b2 d3 k# G
    // (const int) * * pp1; 指向((const int) *)类型的指针
    # i3 @% g2 K0 lconst int** ct_pp1 = &ct_p1;  
    9 O3 j& U7 y, j/ H+ d6 ict_pp1 = &ct_p2; // 合法7 Y1 p1 A4 ^  s
    ct_pp1 = &p1; // 合法!(const int*) 可以指向(int*)类型。: n( X1 p# x* v: i" Z; X: u

    ! \) A; e2 `) H, I% l' l// (const int) (*const)
    2 ?! ~, I) R! J; uconst int * const ct_cp1 = &a; // 指针也不能修改
    8 t1 }& B. N# h/ t) b/ wconst int * const ct_cp2 = &b; // 指针也不能修改6 t. s; Z: Q( m
    ct_cp1 = &b; // 报错!指针有const修饰" k* p  W3 `0 q& Z

    6 E( k, a) z4 Q) ?( H" ?2 {6 l$ Z// (const int) (* const) *  指向((const int) (*const))的指针6 [( f2 z1 b$ L% A) F, [) N
    const int* const * ct_cp_p1 = &ct_p1;  # w2 H4 @* \4 y! q
    ct_cp_p1 = &ct_cp2; // 合法, 指针的指针并没有const修饰, 指向的指针有const修饰3 d( K; c8 Q, q
    *ct_cp_p1 = &a; // 报错!等价于操作ct_cp2,  指向的指针是带const修饰的不能修改
    - @2 k) o$ X, L) L  s* r
    ) |# H4 {" k4 N7 |# ~; M) R4 D# C// (const int) (* const) (*const)  * k  Q' e& M  z+ K- i* C- e; Z
    // 指向((const int) (*const))的指针,且该指针被const修饰! T% Q0 e, ~" X0 V$ j5 |+ f" J
    const int* const * const ct_cp_cp1 = &ct_cp1; 6 k  b9 ]/ C1 L7 `! U% k* G( O
    ct_cp_cp1 = &ct_cp2; // 报错! 指针的指针被const修饰, 不能修改指针指向。( D9 v' H5 B% D. \) J- Q

    8 F" c1 V1 e! w0 w# D一行声明多个变量+ C2 p; P% u+ ?6 \7 u3 ]. c& T# Q
    类型 + 名称定义一个变量。
    8 N6 o  k9 F- e变量的前面可以加*号修饰, 表示指针, 一个星号代表一层间接。**表示指针的指针。- j, c' `8 S( C( ], z5 T  k
    0 C; H! |7 d& \; c0 I; X! {2 V
    int a, *b, *c, d, **e;3 A+ }# g% F" x
    a = 0;% W0 [. o3 \' k3 w* ]. \& l+ q! P
    d = 1;! v2 z0 d, i6 y0 W' t3 r
    b = &a;* y7 U/ \. D' y
    c = &d;7 Y4 c, _7 @5 {- S! @+ S; T+ ^
    e = &b; // e为int**类型 指针的指针0 L3 Y, f- C& w* ^4 U/ Z2 b* F
    e = &c; // e为int**类型 指针的指针' e: b/ b" }" {
    也可以用括号包围变量和*号。# s' F% L& H- X; _. v
    & V( d. A) Q. J
    int (a), (*b), (*c), (d), (**e); // 合法定义。9 I/ |( x- y; a, @2 K1 {
    括号可以省略,某些情况, 个人感觉加上括号更清晰一些。例如/ U* l9 c6 `7 r$ q6 M

    3 ~9 y1 n+ p! R" f& d9 l  y6 X* W& Eint (a), (const *b), (*const c) = &a, (const d), (const* const* const e) = &c;: i- v$ u; I( y* i) B2 `! ]* u
    写成' S2 f# @. ^: R* n$ e/ K$ j

    + A: C7 K* n/ m3 Xint a, const *b, *const c = &a, const d, const* const* const e = &c;. R1 F0 r$ o% ^& \7 n* ^  |
    更重要的是, 后面我们表达数组指针,以及函数指针时,括号是不可缺少的, 带括号的表达更加统一。% [- E$ H/ J6 ^& q: w

    # S7 N) N" g( ~  ~数组指针( i& P" A7 ~- C$ S, X7 ^, I
    数组基本表达
    , R. b3 @) k& h6 X( _" [0 i6 oint a[10];  // 定义了类型是int, 元素个数是10的一个数组。# R) L5 s8 H; @" u5 n8 @9 ]7 K- {0 E
    由于c++要支持一行定义一个类型的多个变量。 所以数组的[]时放在名称后面的。虽然我觉得( `7 C2 n+ t* I, Y
    3 X2 }6 m+ ]: b7 r! \; @
    int[10] a;' X6 F1 K/ s. D. u
    这样的写法更符合类型 名称的思维, 但是如果类型都这么写的话, 没法兼容以下的写法:+ p* ?8 Y) _% f# o$ G% v! x' V
    , f; L. L+ u8 L/ u/ X0 A5 U2 E" [
    int a = 0, *b = nullptr, c[20], **d = nullptr;
    * z% D  ?2 u5 J$ O$ H* v- Yc++标准规定如此,但我们可以通过每一行只定义一个变量的写法, 类型会更加清晰。9 [  i& {* t( M3 M" @. W4 f: E
    3 L& O$ h8 t7 j0 }! }, e' x
    int a = 0;% W8 o# r  U# e7 V. V- B! A% n
    int* b = nullptr; // 指针int*1 u3 \7 s5 f0 A* n7 s
    int c[20];
    7 q4 T8 F1 }, Lint** d = nullptr; // 指针的指针int**
    7 `- Q! T) q- ^: I数组的名称是什么类型+ L, u$ k" F7 K0 u. O% c$ w. c
    数组元素类型的指针,可以直接指向数组。 并且数组跟指针一样,可以通过下标去访问元素。. M2 e/ I0 K8 a; M* C* x
    * w+ ~8 k5 w+ y: n
    int a[10];
    " w* J: W! ?( r2 k2 Y; \! sint* p = a; // 指向a数组的第一个元素
    . L- E+ B& |, w/ w: U3 sa[1] = 1;' a$ W8 `( m' A- w) {* h. X: ~$ s- F
    p[1] = 1; // 效果与a[1] = 1一样。
    - D5 R. m, Y9 v数组可以当作T* const来使用, 但是又与T* const有些不同。sizeof()的结果不一样。
    5 i  d. S) l: x
    & {# O3 ^; G- _1 T, v1 r9 Wint a[10];
    2 H8 N% o* e7 ]int b[10];$ D' y& K1 e8 q' N$ E, W" H
    int* const p_a = a;/ ?- ~! y% l- M/ V, k' }
    a[0] = 1; // 合法。 数组的元素可以修改。
    / H( _9 V  G' J  cp_a[0] = 1; // 效果与a[0] = 1一样。
    / s% G3 `1 Y7 Q1 O3 ?* u& h; W. K# o9 B) _8 b$ w
    a = b; // 报错! 数组本身的指向不能修改。* D) x/ E. g9 s, z6 B8 b. w+ P
    9 T7 U! M" Z8 b
    // 所以数组a可以当作int* const来使用
    # ?" `' q2 [1 K! g9 Vint *const& ref1 = a; //正确。3 h  h, y9 O7 c
    int *& ref2 = a; // 报错!
    . J7 z/ P% |/ b2 L/ Q- I/ M2 p4 X7 P/ f# }( ^
    // 但是又跟int* const有些区别。
    * [6 U7 g7 V% A( |9 J$ zassert(sizeof(p_a) == 4); // 32bit程序。
    9 E$ B" ^: m2 {) u. q0 [: D7 eassert(sizeof(a) == 4*10); // 32bit程序
    " `: b: j" |) z0 `! t7 f) w5 `' F" d, Z0 j
    数组跟元素指针的作用很相似,都可以通过下标去访问元素, 但调用sizeof()函数的结果不一样。元素指针的sizeof()返回值是4(32-bit应用)或者8(64-bit应用), 数组的sizeof()返回值是数组实际占用的空间。数组可以当作指向第一个元素地址的T* const来用其实就是我们常说的数组到指针的隐式转换。当数组作为函数参数传递后,会自动退化成T* const, 在被调用的函数内部调用sizeof()的返回值跟T* const指针大小一样。 数组传递作为函数参数后, 在被调用函数的内部与T* const是没有任何区别,只有在数组定义的可见范围内sizeof()才有获取数组占用空间大小的效果。
    6 n9 f3 B, y9 \$ G3 s3 G
    8 d9 w5 Z( m  K; [1 d- r以下3个函数翻译成汇编以后,汇编代码是一样的。
    2 D- m0 i3 y4 N/ Z8 C2 j- i- S; q3 j) U$ ~& S
    void Func1(int* p_ary)
    3 j7 M+ E( L5 u, i  r/ Y, T! b{
    0 G9 q$ N, d& f6 y/ N+ @* V    assert(sizeof(p_ary) == 4); // 32-bit
    : C1 u5 L# I! z) T8 ?    p_ary[1] = 1;
    ; b# A1 y1 B' @0 \) _3 @/ P7 u}+ b2 R& M( d* Y3 ]  h/ }
    - n9 w3 q* B5 a; T
    void Func2(int ary[])
    ) B) e8 \& p# F1 X4 r8 R# s{
    ; I; n. @2 _7 I    assert(sizeof(ary) == 4); // 32-bit
    1 d5 u1 l0 T1 K+ q    ary[1] = 1;5 F( e' s# i% o8 Z
    }5 `; H  O2 \( K& N. `. I( P& h5 N
    1 @' `1 X' e* d# r- ?; E0 B
    void Func3(int ary[10])  ~% Q  z# `5 A6 ~2 E
    {
    - u0 q# p" G* _, p# q1 n- A    assert(sizeof(ary) == 4); // 32-bit
    ' r3 Y+ y7 Z8 l    ary[1] = 1;" @! {, B5 |: V8 Y4 N- A9 \3 r+ f/ Y% F
    }* P7 z5 _% e9 d

    : L$ [% ^4 m% j  `2 x$ i. |int main(int argc, char** argv)0 `) A$ g# d! l; e7 ~
    {4 M. G* `, O# D5 X4 [8 K
        int a[10];
    * n# p( Z( E  O; T; A) z* z! w$ Y' K    int b[20];1 N, u, R3 ?( d- R5 `3 Q$ P/ @
        Func1(a);- E# x/ b( C# m" K
        Func2(a);
    1 M' q/ Q1 Y- x4 W9 ~    Func3(a);
    $ ?1 w; O+ ?" P: @& e( F    Func3(b); // 退化成int* const了, 即使数组长度不匹配也不会报错。3 @  }: ]3 W- ~, X# [+ n
        return 0;
    7 r1 L$ w2 i7 _4 ^}/ @% U  D* E2 |' O+ u& w- C
    2 O4 I" n8 D4 T. d7 q/ f% Y8 z) s
    5 G) c0 r0 X6 X+ [; K

    6 n/ z# T% B4 \5 |9 S多维数组
      Z( I& k* A, l5 p( i9 `" _2 O2 f. R  C一个3行,4列的数组, 结构如下:
    " u2 Y7 V, A1 l  `0 y) b& d) [4 A7 d
    int a[3][4];7 w; _. W: _# h4 z( }; Y
    column 0        column 1        column 2        column 3& o& c2 K2 O) A; f
    row 0        a[0][0]        a[0][1]        a[0][2]        a[0][3]
    % v2 Q- n7 X# \% srow 1        a[1][0]        a[1][1]        a[1][2]        a[1][3]
      I5 ^; A, s1 ]# l5 B, ^8 Nrow 2        a[2][0]        a[2][1]        a[2][2]        a[2][3]2 q& l2 D7 c, r
    数组初始化* N* W5 Q% Y) d. N
    ) {: Q2 r; U, I5 A: u% e
    int a[3][4] = {! _& S, i; O/ W* |4 x0 \
        {0, 1, 2, 3},- y. \! S' R, w& c/ ^8 x7 \$ P& _
        {4, 5, 6, 7},
    " q( d% |0 K, X$ N  x4 |- s    {8, 9, 10, 11}7 n/ F( T, N+ p" z" _5 |
    };
    2 S0 N' N5 ?: U 实际上多维数组和1维数组在开启速度优化后,翻译成汇编代码是一样的。
    7 F! ?9 @7 c" Y9 `% G0 X1 V" K; Y+ G) m- f
    void Print(int* p_ary);
    2 _5 [) u2 x! ]
    # `7 A, v" a  ?6 d( Q  h/ c$ Bvoid Test1()
      }. p  j2 F6 m$ W2 s5 j{
    5 `' O. {" G$ m1 J    int a[10];
    9 E7 T" L/ w( ~    a[3] = 3;
    1 X, I& I7 H; ~" u9 z4 T    a[7] = 7;" T( g& T& b# g: t8 K/ R8 M
        Print(&a[0]);& A" `+ y! ^1 Y8 m% |- d
    }1 F* X! u7 t: P
    0 o1 f* ^. r( K3 D, Q
    void Test2()
    , f: X9 a" N. {2 B. V0 Q/ X4 o{
    & J3 A2 J: J# Z1 A$ k: e$ Y    int a[2][5];
    , q% s; D/ g  y& j2 [/ x9 Y# L    a[0][3] = 3;* ~* _5 X& h0 p$ J7 j4 K2 K
        a[1][2] = 7;
    : b7 k$ h! a6 ?6 K* ]/ \9 \8 Y0 k    Print(&a[0][0]);
    ' M: @7 N/ ], [8 X0 h4 d+ ^' I}
    ) o# G7 t" _* t- _7 S* g1 }( T' K4 [# h: Y% f
    0 C0 {6 ^- R& h6 |# q
    7 |& U( g3 j3 J  n
    很自然地,多维数组也支持用1维数组的方式去初始化。& k6 N; {2 s3 S0 }! Y
    $ H8 V" v: S$ j( H
    int a[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
    8 D8 N' I4 |  L1 ]既然多维数组与1维数组没什么区别, 为什么还需要多维数组?
    " z3 \. m$ e! r8 k# _$ F  T! t2 ~  R( a3 ?
    假设有一幅RGB图像720*576个像素,每个像素有RGB三个通道,每个通道的值是8bit大小。给出图像的首地址p_rgb_image, 我们要取第40行,第50个像素的R,G,B值。代码如下:
    1 z& e- Q& J. Z# _: I) ?) E# {: `" d7 o* m6 _
    unsigned char* p_rgb_image;
    ) U: j5 M1 t& U. {0 L% Punsigned char r = p_rgb_image[40*720*3 + 50 + 0];) ?( q: G6 y" \  R2 V
    unsigned char g = p_rgb_image[40*720*3 + 50 + 1];7 ]) i3 O% _, M
    unsigned char b = p_rgb_image[40*720*3 + 50 + 2];( r6 h! U/ H" Z3 w& K: g
    类似这样的场景, 采用多维数组的写法, 有点类似以索引为参数,可读性更高。相当于程序员和编译器打了一个配合。. i+ V/ l* m2 Y, a7 d" [5 _" s

    : C1 a5 `' ?- T6 Eenum9 {0 U. G3 [1 z* u- Z0 f
    {
    $ ^& T8 W% l5 p8 V Red = 0,
    * \/ f1 q: w4 S2 k2 c1 v Green = 1,; S3 z6 l; _4 f; v/ @" d1 U" P
    Blue = 2# g* w: T7 a- p
    };
    , u) z$ o( P: S+ r  I7 xunsigned char rgb_image[576][720][3];
    7 u0 L; X8 ?, j$ I3 K/ s% e( _int row = 40;
    3 e2 f: B6 c6 S5 vint col = 50;
    ! X! q/ C8 b9 J8 V+ junsigned char r = rgb_image[row][col][Red];4 h% @  ^6 f, V- }
    unsigned char g = rgb_image[row][col][Green];
    1 [/ d1 S4 `1 F4 `! A' R% _unsigned char b = rgb_image[row][col][Blue];- m2 ~" P1 s7 g' @
    数组指针以及与指针数组的区别
    : |( d/ N- f/ @* `数组指针,是一个指针, 指向的对象是数组。 数组指针的赋值,要求数组的长度匹配,否则会报错。当指向1维数组时, 需用用*取得数组对象的引用,再用下标来访问数组元素。
    5 U& ?: r4 J, o' C9 n8 \  A5 R指针数组,是一个数组, 数组保存的元素的类型是指针。4 {, J, i: L) f: ]& d
    数组指针的定义
    ; U# Z% h/ s/ B. ~* S数组指针定义先定义一个数组。
    ! e; G; _0 `) u; w9 P: G( j$ _' [: Vint a[10];
    + K" ?" L$ Q/ x9 \然后对数组里的名称用括号括起来后再在变量名称前面加个*号
    : K. N, M# T9 f1 l8 Aint (*a)[10];8 G; c! k! |0 k' E/ G
    后面你会发现函数指针定义类似。
    ( Z# B! r; @7 V( b- _// 各类定义对比
    % |) L6 r% n8 R7 Nint a, *b, **c, d[10], e[10][20], *f[10], (*g)[10], *(*h)[10];; D$ t& k: D9 i% E

    # ~# z5 s) p2 I# h! G( ~int *f[10]; // 指针数组, f是包含10个元素的数组, 数组里每一个元素的类型都是int*
    * o# K! _' X- {% f& Z$ }* Bint *(f2[10]); // 指针数组。另外一种定义方式。& Z$ Y' z) Y( k  _& p
    int(*f3[10]); // 指针数组。另外一种定义方式。
    1 S! i/ P3 Y4 b1 K* |3 vint(f4)[10]; // int数组
    8 W3 W2 C. Q/ x5 ~, bint(*g)[10]; // 数组指针, g是一个指针, 这个指针可以指向类型是int,元素个数是10的数组
    ; F3 g9 k. L9 [$ W0 F2 J" x3 d- ~int* (*h)[10]; // 数组指针, h是一个指针, 这个指针可以指向类型是int*,元素个数是10的指针数组
    6 a3 f" j3 f: w& l. A" o5 ^$ K8 h5 G% |, V5 G$ i% n
    int d[10];* {9 ]" P  X% P6 i- y1 S
    g = &d;8 L$ [1 E$ i- S) ~2 R- K, l- B) K
    ( k8 B" E0 w" o9 M1 ?
    int* e[10];. u  R, H9 I4 H4 ^' a/ Z, r
    h = &e;1 n; ~6 o: P% s
    数组指针的使用
    ) O  r. b4 A" H: B4 L数组指针一般先通过*号取得指针指向的数组对象, 然后再用下标操作访问元素。
    / u0 G6 j1 W& k* k! I, D8 }/ i6 V0 b7 q0 g
    int a[10];
    0 F- R. z' K& e& q  \3 z! ]int(*p_ary)[10] = &a; // p_ary是一个指针, 指向"int (*)[10]"类型的数组, {' n$ F6 I' B; B8 w
    for (int i = 0; i < 10; i++) {
    3 w1 l" C! p8 z# y* S. \ // p_ary是一个指向数组的指针, 需要先通过间接寻址运算符*(indirection operator)取得数组对象9 C4 N# m; L3 M
    // 再通过下标操作访问元素。8 @" ?7 W, s0 Y, d- X
    (*p_ary) = i;
    ; B8 \1 S4 ^- U& ~4 `, W}
    5 ~8 T/ W! `) b* @; z% S
    & u% Y/ t$ [! N& T% ?int b[10];
    5 S, d1 ?3 S% Yint c[20];6 m7 D3 u& G& y9 N, N6 U
    p_ary = &b; // 合法9 r! _' ]2 K0 d8 w% _' O: D: w
    p_ary = &c; // 报错! 不能将 "int (*)[20]" 类型的值分配到 "int (*)[10]" 类型的实体
    * d2 S1 }* f6 U. G数组指针指向多维数组的子数组. F2 |1 K$ Q+ |- _* b
    int a[10];% ^2 P. Z3 _1 L/ u
    int b[4][10];
    2 d- P% _! F$ E; Hint(*p_ary)[10] = &a;6 u! i6 Q- w+ c% e' X
    for (int i = 0; i < 10; i++) {
    . E+ z! |. [8 Q5 k( }! y8 U" i (*p_ary) = 1;
    ) w/ h3 x4 c5 m2 N8 U" _' W}8 x; K8 \" G* r4 h9 S' @6 {! P0 C
    3 E5 x6 {+ l) a3 j; ]) a1 P0 k
    p_ary = &b[2]; // 多维数组,可以看作数组的数组,. }0 k: V( Z" U
    // b[2][0] ~ b[2][9]的值都被改成2了- I1 T3 f( y! f% J+ }7 D# \1 @2 K
    for (int i = 0; i < 10; i++) {7 |% C' S$ W8 h; T/ e# f
    (*p_ary) = 2;
    * N% ]% T  y' a) e' d0 b}
    / X: l: H7 q  z7 X! W/ U多维数组指针
    ! e( |8 r2 ]0 ~: T1 ]6 s2 j多维数组指针,是一种指针,指向的对象是个多维数组,支持多个下标操作。
    6 ?$ v( u5 t' q  r4 B  a6 S2 P7 D- O3 @1 G) w
    int a[2][5][10];
    , V. l; A9 ?2 i/ J- |8 Nint(*p_ary1)[10] = &a[1][2]; // 1维数组指针
    % U# }5 z$ d- jint(*p_ary2)[5][10] = &a[1]; // 2维数组指针9 P1 R( u( P; G2 p1 r6 [
    for (int row = 0; row < 5; row++) {
    ; y0 P: Y0 v% ^7 k8 s2 h8 M% h5 f    for (int col = 0; col < 10; col++) {
    5 k* L( O- w8 ?2 a% e        (*p_ary2)[row][col] = row * col;
    # h3 Q$ L. z+ R. \    }# \# F& v8 G0 D4 N3 T
    }# D* M& T# r2 r4 i* T' D+ ]) b, w* l
    数组指针和指针数组对比实例
    / c, b( y7 Y0 O, [# F数组指针还是记住两步法即可$ [9 d7 A* g6 q, g5 b& q
    % W6 ]8 O! M0 {* e, [: c
    定义一个数组
    3 F, z4 d* n4 t6 f" X5 n括号包围1中定义的名称,再在名称前加个*号。* t3 z: r* W$ @' @2 T0 ?
    int a[10];
    1 p4 P) w% a; S+ D! b$ Eint(*ary_pointer1)[10] = &a; // 数组指针/ |  y/ D* O3 o
    int* pointer_ary1[10]; // 指针数组。元素类型是int*3 E, ^& L. C, T; y6 l- s  P3 d
    int *(ponter_ary2[10]); // 指针数组。另外一种定义方式。# y( g5 t) \  R* J3 v4 J7 a
    int (*ponter_ary3[10]); // 指针数组。另外一种定义方式。
    6 {/ O. E% `) E  z/ wint c, *d, (*ary_pointer2)[10], *pointer_ary3[10]; // 排列定义比较。) I: S) F4 I8 y7 q8 l

      ?2 [3 S) K/ ^* s6 f8 F// 指针数组可以把每个元素指向数组对应位置的地址。
    9 J! h( d4 F( ?+ _& G/ I// 这样遍历指针数组, 可以达到遍历数组元素的效果,但是注意每个元素都是指针,
    , R' B0 _3 [3 B- j$ |1 P+ `// 需要访问原数组的值的话, 需要对指针用*间接寻址运算符。
      e' ^' p: A! h4 O2 u# \int* pointer_ary[10]; # o. c! p& @. A$ o( }
    for (int i = 0; i < 10; i++) {# `* H) p3 m3 S3 D; s
        pointer_ary = &a;
    ' ?5 D8 C* P) L4 H2 j; X  ]}
    . a0 r* T- d) @// 类似遍历原数组效果。
    3 J( m2 k  p  K& Hfor (int i = 0; i < 10; i++) {
    8 {$ s' v3 F. F' d) R& W. u* H    *pointer_ary = i; // 修改原数组。
    ' a  `3 l4 S& S, [% M}  E& L0 j  U$ ]* i% \. t9 W

    2 M) }* o0 V% ?函数指针
    : h# }3 T5 h7 H' }取得函数地址# t+ _1 ~& n1 F7 b, H  ^
    函数的名称作为参数被传递时,会隐式转换成函数指针, 和在函数名称前加取地址符&等价。建议带上更加统一和清晰。
    ( x" c' \/ c- ], A: q; }7 a  ]: O: [; J' p* C
    void f(int);" D% P* @+ r3 w+ \& _
    int main()
    5 O5 g4 h/ k4 k; f" [" l" z{3 B  Y! k( L5 f  v: q
        void (*p1)(int) = &f;/ B2 [" m. z* f( _5 H
        void (*p2)(int) = f; // same as &f
      W, {1 s5 I; `9 e' j8 }    return 0;3 F: [! a, V* S3 _0 u3 K8 U- u7 h6 V
    }
    ; U' f# m% x% L7 D翻译成汇编代码, p1和p2的赋值是一样的。
    , w3 F3 y9 l4 D* c9 Q" Y# K% n3 a6 k
    ) M( Z1 |9 L  p* w6 `
    ( \& b$ E5 b9 F# j, I
    函数指针的声明; u! _9 }. L( `8 i
    单个函数指针变量定义步骤# o3 I0 q1 \0 I! M: w" j. P
    定义一个函数。void fun1(int a, int b); int fun2(double a);
    0 L/ L. P- }1 U  E9 J0 X& d- J用括号把函数名称包围起来,然后在名称前面加*号。void (*fun1)(int a, int b); int (*fun2)(double a);
    2 h* g' o) R0 U$ D" q+ Z3 b如果要定义函数指针数组,在定义单个函数指针的基础上,在名称后面加上[数组长度]void (*fun1[2])(int a, int b); int (*fun2[10])(double a);) p# b: \6 G- N+ `2 _$ x  n
    typedef定义函数指针$ f8 g, k" q, E
    可读性高比单个定义要高,特别是声明多个同类型的函数指针,或者函数指针数组。) ]' L: u; l/ [  v9 K' D

    + B: M2 G/ _5 c; F7 c# ntypedef定义函数指针的语法/ j& T2 I' n8 e+ c: h7 j4 y
    typedef有两种做法, 一种就是定义一种函数对象,另外一种就是定义函数指针。用法稍稍不同,效果是一样。其中函数对象不支持赋值, 但是支持引用。
    & N6 O- T* }8 L: |- N' V3 b" w/ E% e! b) n
    typedef int FuncObject(int a, int b); // FuncObject类型是函数对象: S7 Y$ \* b7 O' l7 g
    typedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针+ q0 p! P; }; ~# U. @
    FuncObject* f1 = &Add;1 M# q) z  t0 h  e% ]
    FuncPointer f2;
    , f' S* ~  K) n/ Lf2 = f1; // f1, f2类型一样, 都是形式为int(int, int)的函数的指针。7 \$ y+ @' T. {0 v
    FuncObject f3 = Add; // 报错! 函数对象不支持拷贝
    6 f; C  I1 D# \/ {FuncObject f4 = &Add; // 报错!&Add是函数指针,与函数对象类型不匹配
    1 Q( _( }5 P( V0 K: A1 H3 AFuncObject& f5 = Add; // 正确8 J6 T3 C' c5 S( n4 B
    int ret = f5(2, 3); // 正确5 Q$ R9 y4 p* |5 K
    FuncObject& f6 = &Add; // 报错!&Add是函数指针,与函数对象引用类型不匹配$ J2 Q- t8 p  T5 [/ Y' q7 V+ x) o
    如何记住typedef定义函数指针的步骤
    ( }0 L$ S& l$ t0 p- J# e9 W8 T像定义一个函数指针那样, 指定一个名称。int (*CalFun)(int a, int b);
    3 ~- F1 O* Y% P9 ~在这个函数指针变量声明前面加上typedef。typedef int (*CalFun)(int a, int b);2 [. |6 W) ?4 h8 N# Y. ~
    完整例子
    ' Y4 {) [! \5 j2 x5 }( Atypedef int(*CalFun)(int a, int b);0 X2 n$ Z  A! w( a0 C8 y4 p

    5 q  z; r$ P" T! z1 eint Add(int a, int b)* @* J% b5 k; I9 [
    {3 k; _0 O$ C: q8 o; b
        return (a + b);
    - @1 T1 H, w9 i& }}  {/ d3 w( v- C1 q1 ~' A
    6 ?$ r) }9 |( D# U+ s7 X
    int Sub(int a, int b)
    ! K" d: n4 w9 M3 K8 e& H9 k+ ]+ ~{
    ! F6 k( ^' Q6 X0 j. h% e  E$ w    return (a - b);% [. b9 ~" z  @0 H9 L2 W/ u( {" `
    }8 R. z) H* F# Q- d! U5 y

    # |! {, B2 X" Q6 e& Z1 Y- Xint main(int argc, char** argv): H" [2 T# ~7 t  ~* b8 |
    {' B4 D" X* s$ ^4 @4 C- ?# n) Y
        CalFun f1 = Add;" }  s% d; m6 t
        CalFun f2 = Sub;  K; N% s/ K, ^) L
        int a = f1(2, 3);  P/ ~1 [& w# o$ \* |2 ^
        int b = f2(10, 5);
    9 Z! K& o! H# W2 J, c, g+ ~- {. F- O$ D, x4 ?* E5 O
        // typedef定义的函数指针数组。. Q* c# A5 X7 N& w4 J
        CalFun f_ary[2];2 D- m6 W9 H  D
        f_ary[0] = Add;* c- L% o% e  X# d0 V" O
        f_ary[1] = Sub;6 r# K6 r# q& b( K
    . g: J2 g5 K; B/ s6 U
        // 单个定义的函数指针数组。
    % K+ W1 q6 J: ^% g    int(*f_ary2[2])(int a, int b);! f9 O& }& p& @/ z& C7 s: S
        f_ary2[0] = Add;
    ; b" H' ~7 a4 g    f_ary2[1] = Sub;* i$ N3 K: u6 f! l4 a; I

    ( U6 q; x. K9 N) O    return 0;, H9 f' _8 R: O2 V2 v7 }- C& U
    }' l/ Q" c2 M# q# s9 V8 r

    ) x2 m% |' _: Xusing别名定义函数指针
    * Y" N9 ]7 N% a# {7 Ec++11以后的类型别名定义--using也可以用于定义函数指针, typedef的好处它都有,个人感觉比typedef更直观。using类型别名同样分函数对象和函数指针两种方式。
    7 c7 F& A! ]4 X1 i, t
    % |% ^2 d# B1 M& O0 ptypedef int FuncObject(int a, int b); // FuncObject类型是函数对象& H% t. O; E( b# _& u
    using FuncObject = int(int a, int b);2 }9 [0 r4 A$ O+ }
    typedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针- o4 ]- C! k- c/ U( w
    using FuncPointer = int(*)(int a, int b);0 s& g( K' C+ r+ X" ?
    函数指针的调用. @- p" v- v" M5 F
    函数指针和函数对象都可以直接后加括号调用
    $ F+ ^0 H# ?# i2 [/ G2 u' ?int f();3 r$ |# T* Y6 D/ ?! N' A
    int (*p)() = f;  // pointer p is pointing to f2 `* D, b0 M  W  o; d: `
    int (&r)() = *p; // the lvalue that identifies f is bound to a reference
    ! M9 g) \  N. G  ]6 nr();             // function f invoked through lvalue reference! ^/ p  l, a1 X9 }$ N5 g* ]! ~% x3 |
    (*p)();          // function f invoked through the function lvalue/ u( f+ M6 S; e. u6 Z7 V8 t
    p();             // function f invoked directly through the pointer
    1 _1 q  R6 Y9 v. M2 T4 a如果函数有重载, 函数指针会指向匹配的那个版本。8 K% S, P0 ^* r- b
    template<typename T>7 J: i) x6 \/ E& k3 f! v3 v
    T f(T n) { return n; }2 t! b8 W/ x) g* K% t
    * U8 E- u7 Q, f9 ^0 a. Z
    double f(double n) { return n; }
    / F9 l5 z  o- n2 ^- j0 r" a, V9 S5 r! g0 n/ R0 \2 W7 H
    int main()
    ) [+ s5 c6 L$ @$ k$ ^8 J- A{4 W/ a, L! ?5 p+ w% b
        int (*p)(int) = f; // instantiates and selects f<int>
    , g4 Q, T- V/ h( R4 Y! w) }}  k% K" ^+ Q+ u$ x2 T: ]; o# H# S
    成员函数指针
    & g) k0 ?$ V- ^  x静态成员函数,除了增加了访问控制以外,跟普通的函数指针没什么区别,所以普通函数指针可以直接指向类的静态成员函数。但非静态的成员函数与普通函数指针不太一样,声明时需要指定函数归属的类名,并且调用需要指定对象实例。
    , y) y5 s' k8 H- w  J( I7 {( ~) k1 ?; V$ J' K
    成员函数指针定义。, L, F( L3 W) B, z' @& o
    像定义类成员函数实现那样写, 并任意指定名称,这里作func。void ClassName::func(int);
    + D) m& d* ~8 r# P6 \7 c, d( E括号把类名、范围解析运算符::、名称包围起来。void (ClassName::func)(int);
    $ M5 S$ n" L9 @/ P2 C4 W在名称的前面加个*号void (ClassName::*func)(int);' N' i% ], Z* ]
    成员函数也支持typedef和using的定义方式。typedef void(C::* MemberFunc)(int); using MemberFunc = void(C::*)(int);
    + t$ R$ o- B7 U: }9 j成员函数指针如何调用。
    / p; J+ p, b7 f" n, n+ u假设成员函数指针名字为func
    9 X- S( @. ?" m& G7 K2 q* A" q
    ( e" F7 p9 f, Xvoid (ClassName::*func)(int);4 C& I6 f2 a+ N5 S
    对象式调用。
    " |9 U/ G( e5 zClassName c; // 被调用的对象
    & r- H/ }+ s! o* k# C1 M& K- N成员函数指针名字当作正常函数那样写。
    + [5 I( c4 C" i, y+ B# @0 ?# Kc.func(3);4 e- g3 k1 ?; F  \, ~4 t- `
    成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。
    4 j) v3 d7 S% E: hc.*func(3);8 {# D- S# Y: P( u  f( u
    最后用括号把调用对象、成员访问运算符.、间接寻址运算符*、和成员函数指针的名称包围起来。) n# h2 P% \/ p) Y9 U# |
    (c.*func)(3);# J$ p% l" X4 a- e: `- H" M
    为何要加上括号? 根据c++的优先级标准,取成员运算符. > 函数调用() > 间接引用符*。 *号优先级比函数调用要低, 成员函数指针还没取得对象就被调用了,自然报错。 另外7 H* R' r2 D" F+ S
    (c.(*func))(3);& K; q/ `* {. M
    这样的写法也不行。 .*和->*是整体作为一个运算符的,中间不能用括号隔开。
    , z4 Z1 {) a! @! ]8 E指针式调用
    4 w% ^6 B8 u+ t0 |1 N* dClassName* p; // 被调用的对象的指针4 |' J$ j: W% T* H
    成员函数指针名字当作正常函数那样写。
    " Q3 |- w, H2 w4 r1 q5 up->func(3);6 M- B: z6 L4 Q. C, A& P5 W
    成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。9 g( G2 }+ g% p5 S+ \
    p->*func(3);
    1 O' z8 T" }, L1 M最后用括号把调用对象、成员访问运算符->、间接寻址运算符*、和成员函数指针的名称包围起来。4 }4 r) L) c  Z2 {5 \3 p3 i$ p
    (p->*func)(3);& N2 `! P3 i8 N8 Z& T. P: S
    函数指针使用完整例子
    8 ^1 I1 g+ l& L' `5 ~2 j' Fstruct Cal
    5 m% [! H; @# G5 K% [{
    " O9 M; O0 \5 m, e    int add(int a, int b);
    ! i) L( p3 _7 b0 ~: J    int sub(int a, int b);
    + J$ b# {: g5 n( }9 l; u: }3 L0 O9 z};
    - c; l# Y# x+ P; o, Z  r+ T! U& B' h
    int main()7 \7 [: J1 @5 v' `/ o  t" _# _
    {# y! I" s; J% ^, V+ J
        int (Cal::*fun)(int, int) = &Cal::add;
    4 M! \+ n6 |  U    fun = &Cal::sub;6 Y, Y* B9 K8 y+ h
    " V" D& x% v. f
        Cal* p_cal = new Cal();1 q+ F+ r+ g5 y+ ~; a/ u
        int r1 = (p_cal->*fun)(2, 3);" a2 D' y- w: S$ z- T2 K
        delete p_cal;
    1 I3 F+ v, W: M5 f+ k
    9 [  K. Z# i0 C" _, j1 y3 Z    Cal local_cal;' ^) d' Q% t& T5 i9 J* \3 \8 x
        int r2 = (local_cal.*fun)(8, 6);7 c2 c* e! ~/ @0 W
    }6 ?4 T$ X1 s, I+ P$ C' x% _% }

    : a7 |# O- i6 H+ `4 y. i成员变量指针8 {- A% [7 i) w% d1 Y8 `% q
    成员变量指针比成员函数指针还要简单些,没有函数调用, 无需考虑函数调用和间接引用符*的优先级问题。1 w+ M3 k2 `0 p1 I  I- C

    0 R8 W+ j6 \% G1 j7 x成员变量指针的定义
    / k3 T7 {0 q9 f3 n% _# C1 [6 [假如以下结构体C。" c& ?: n' l5 t  R7 a5 w

    7 B# b9 H+ ]/ ?6 d5 ~struct C   N1 p; O3 y9 d
    {
    5 \- Y6 M2 F+ o# E    int m; 2 S- @( O' e9 k
    };/ p0 e5 y1 A5 V$ \
    单个成员变量指针定义 ; H6 U4 [  C2 V
    假设名称为p, 类似静态成员变量定义那样声明; y3 X2 K! B8 c1 ?% _: }" g4 O
    int C::p;
    : G7 A9 w# h9 ^+ J6 v$ h在名称前面加上指针标识号* 8 }, d' T: i2 l% H
    int C::*p;* z9 z. a" _; ]9 ~7 {# F
    typedef或using方式定义
    ( K# y7 i4 T1 B- Z) Atypedef int C::*MemberPointer;
    : z) O3 s" m" U1 ]% \using MemberPointer = int C::*;4 l7 D3 P/ Q- y/ G
    成员变量指针的使用。
      [! F6 N# u, f1 @  C2 u类似成员函数指针那样,直接使用指向成员的指针运算符:.* 和->*即可。
    ' R8 Y5 S) V5 R( x成员变量指针, 能让我们实现一些遍历成员的动态功能。 例如把一个类/结构体的多个同类型的成员变量放进一个容器里,然后遍历访问这些成员变量。
    $ C; X$ ^$ B1 b9 M" m7 K% l  `( {' F) G0 H
    完整例子- M; {5 z' F3 P# Q6 k" N" j) f
    struct C { int m; };
    ; b5 d! c( R4 |int main()
    ! ]! C8 [1 n! p{
    7 Z( }5 _4 u2 I: [    int C::* p = &C::m;          // pointer to data member m of class C
    3 A( J) T( x# F( p    C c = {7};
    " D8 {0 j" p8 l# P    std::cout << c.*p << '\n';   // prints 78 t' O/ `4 ~: X: R4 d! ]
        C* cp = &c;
    , X' m4 z$ |4 c& U    cp->m = 10;3 Y! E/ w* m2 t  @. `( Y
        std::cout << cp->*p << '\n'; // prints 10
    ! ^* M7 W) _/ {2 Z- _. i. x5 X( Q  r8 D0 s* N5 l
    ————————————————- C9 B, L9 @" \! b
    版权声明:本文为CSDN博主「南风fahaxiki」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    # c5 ?1 _% B0 C原文链接:https://blog.csdn.net/m0_64407685/article/details/1267881158 i5 A$ |: x* Q; f" T0 g  z

    " Y" i$ a! I0 T) |( _- Z5 _  i- j; |# ^8 e
    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-7-10 07:52 , Processed in 0.364830 second(s), 51 queries .

    回顶部