QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2425|回复: 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++数组指针、函数指针、成员函数指针
    0 Z, |$ p) b$ B! l9 OC++数组指针、函数指针、成员函数指针. [, j$ G7 s6 S! d
    , O2 _" ]2 k( ]( A5 W9 M5 x

    - r" `+ S2 T" T操作符名称
    7 A  @4 {1 n  ?2 ^& 取地址符(Address-Of operator); K0 O6 g. `! x' ]; Z) q
    * 间接寻址运算符(Indirection operator)
    6 ~8 S7 `& R/ F+ E( E4 {/ h.和-> 成员访问运算符(Member-Access operators),用于取对象的成员。
    # ^, {" R8 N* `/ i4 n7 R: z) R6 \, V.*和->* 指向成员的指针运算符(Pointer-To-Member operators), 用于成员函数指针和成员变量指针的取对象。6 Y: c: t- D$ ?0 l( C' Q4 X
    () 函数调用运算符(Function-Call operator)2 S4 k+ j7 P& L
    :: 范围解析运算符(Scope-Resolution operator). K3 \2 W& y# W4 d! b! N8 S
    如何定义一个指针变量
    2 U- L" u& u! ~& n2 Q/ I假设类型T, 变量名称name, 指针的定义如下:
    2 V2 a6 r, f$ N" }( J( E$ W
    / M* ^) L- ^- W  Y: d3 t7 kT* name;6 ~8 Z+ W: a6 f& S! H7 o/ |5 o
    标识变量名字name, 它是T类型的指针。例如
    3 v- x( Q% F9 J6 ?  K) p& p) y
    3 Z  H/ B1 h# l. B& _0 nint n = 0;
    , {$ \8 T4 f- \9 pint* p_n = &n;5 e+ h3 Z1 e# j! E+ k
    p_n是int指针类型, 指向某个int型的对象。' W: k9 J) _2 @  P" o7 ^

    ! S3 T. w: Z+ Z0 a- i指针变量的修饰$ d3 m$ l+ J+ O5 _7 C# }
    指针实际上也是一种变量类型, 只是它保存的内容有些特别, 是指定类型的地址值,通过间接寻址运算符(indirection operator)*, 可以访问到指针指向地址上的指定类型。
    : t+ l8 V7 w( |2 l+ `9 c7 _8 E
    - ?9 s+ [; k+ s: z) _3 }9 N* U4 k7 a指针也可以用const, volatile修饰。 const int或int const均表示一个变量类型是int, 且该变量不能修改。以下两种写法都可以:
    / B+ ?8 A2 q; a9 ]" \
    : }% h4 b* E) Oconst int a = 1;
    ! y  b2 A" z. U4 F" }int const b = 2;
    2 w" K8 n7 Q; x/ U: z4 }4 @5 r0 E: o既然指针是也一种变量类型,同样支持被const修饰, 表示指针的值/指针的指向不允许修改, 指针所指向的那个变量是否允许修改, 那是另外修饰。写法如下:
    . |& g0 y. `% v) y
    9 p; i* i& }9 t( k  sint a = 1;
      v$ G8 f5 \" [/ [: X2 A7 c% Uint b = 2;
    ; D& p$ Q8 m7 @' E! i; D) R4 j+ kint* const cp_a = &a; // 指针的修饰词,放在*号后面。
    . o8 X% p# c% G) L" \*cp_a = 10; // 指针指向的值可以修改6 P4 I: G& [! i6 k
    cp_a = &b; // 指针不能被修改,报错!# z( Y6 V, z" q
    总结带修饰的指针的格式:
    5 ]/ P, ]* g, T! a) R# d/ \3 |3 A只要记住修饰词总是放在被修饰的内容后面。9 ~" z8 k. G6 D' j* N9 c* ^
    5 u) z, K2 ^6 B
    cv表示const / volatile修饰词。指针定义形式如下:' }$ V$ g$ e6 X
    " d) z; Z9 k9 w/ H! G5 D# i
    T [cv for T] * [cv for pointer] name
    7 s6 x5 O7 g& Q5 M( ^& n注意对T的修饰放在T的前面也是合法的写法。$ i0 j' p& r( p+ j5 [
    3 d) f; f8 ~! z
    const int const c = 2;
    6 b: z1 R' }( H4 |; h" l在mscv编译器下也不会报错。2 [- }: q& q5 X0 r9 F) s
    & }$ @$ I3 c, m5 S. b1 N: E( V
    完整的格式:& I! C6 ]9 m1 E$ r
    [cv for T] T [cv for T] * [cv for pointer] name
    ( {% i, B4 B" T) F& F+ J! g7 h9 K7 m3 L. H5 j0 K
    Syntax        meaning0 [( S/ N; i. q. G' j2 V
    const T*        - U, B; {' B1 b7 {# R
    pointer to constant object+ S) D+ ]- [2 f& i
    0 Y- ?6 Z/ b; b
    T const*        pointer to constant object; [, n- j: [5 n! m& |
    T* const        constant pointer to object5 C& b. Q! a+ G& F6 D. n
    const T* const        constant pointer to constant object' B7 s* R, a, z* h$ Z8 K
    T const* const        constant pointer to constant object% y$ u" `- t# x8 s
    上面格式中T还可以是一种指针, 指针的指针仍然是按照修饰词总是修饰前面的标识(T或者*)来确定修饰的意图。
    & P1 i) c  p& s. P" D' D) ^0 z4 Q% u
    int a = 1;/ Y/ f9 H( ]0 e! f
    int b = 2;3 V# p- i5 f( m: z- X1 _

      D- ?& ], F# X+ ^/ u1 x1 h5 Mint* p_a = &a;
    - s( T1 J/ R* G& u*p_a = 10; // 合法
    9 T% h) x$ T5 p: T6 Z. np_a = &b; //合法
    % O: H! \. f& q! z, s. D& |$ \
    1 T) @8 O0 L7 _& K! P/ {const int* cp_a = &a; // const修饰int类型, 并非修饰指针' @0 S2 q) x# i& M8 d9 h
    *cp_a = 11; //报错! const int类型不能修改1 N) @; z6 q4 W  ?
    cp_a = &b; // 合法, 指针没有const修饰,指针可以修改。
    . ]- u$ W! q1 R+ H: K+ z) c3 G. T- J0 ]5 L+ @
    int* const pc_a = &a; // const修饰指针。类型没有const修饰, g) f' H5 v5 z1 G) e
    *pc_a = 12; // 合法, 因为类型没有const修饰,可以修改。- w* X, u: V8 F% A. F2 M7 f4 `0 X
    pc_a = &b; //报错! 指针被const修饰, 不能修改指针。" U, `  ^: O6 A5 }! w* e
    * q' ?' k' @' u( U
    int const* const cpc_a = &a; // int类型被它后面的const修饰, 指针符号*后面也有const修饰; U& E" v2 m9 W* F7 e$ R% |9 m$ n
    *cpc_a = 13; // 报错! 类型被const修饰,不能修改。
    ; y4 s3 e, D  z6 M% Dcpc_a = &b; // 报错! 指针被const修饰,不能修改。. q, O: @# S8 J& M" J2 z) Y

    4 o+ A6 p6 w; I$ y! H 更复杂的指针的指针% c8 m7 ~% n5 v
    5 i0 m' ?4 h' H) g8 W- t6 f$ Y
    int a = 1;
    9 W, }, y2 U4 u0 A  Y4 j$ Hint b = 2;
      G, r2 |' t/ _) gint* p1 = &a;
      ]! W' E* q( u) E# `int* p2 = &b;
    ; T' u' [" O: Cconst int* ct_p1 = &a; // ct for const type% w  ]0 \) \; v$ j3 S+ V
    const int* ct_p2 = &b; // ct for const type3 q) V9 Q( }3 i: N3 Z
    ! o# O: ^; R0 a3 I) j* ^. |
    // int * * pp1; 指向(int*)类型的指针( W& Y, w1 I( F/ p; U
    int** pp1 = &p1;  7 v" I' k( m) g& u; i
    pp1 = &p2; // 合法,
    : {# I+ `9 f) V' H- Y! b3 H- xpp1 = &ct_p1; // 报错! 类型不匹配。 (int*)不能指向(const int*)
    / h9 i% K8 y& z' e, N4 V0 U
    8 e1 r2 L7 B! T// (const int) * * pp1; 指向((const int) *)类型的指针& {# K" a, ^- E* D$ q
    const int** ct_pp1 = &ct_p1;  / D, n$ j; C; F6 _1 {, T! L
    ct_pp1 = &ct_p2; // 合法- [" `9 o3 C5 @% e- C
    ct_pp1 = &p1; // 合法!(const int*) 可以指向(int*)类型。; |% O1 G. j) a" c9 n5 e1 k

    / q5 m/ V4 F7 h7 p$ ?4 M// (const int) (*const)
    " N! ~7 Y- \& \/ _! B: w. g. Cconst int * const ct_cp1 = &a; // 指针也不能修改
    # i. I, x/ ?+ V' iconst int * const ct_cp2 = &b; // 指针也不能修改) j2 a% D4 {0 Z% Q4 u/ Z1 C& ^
    ct_cp1 = &b; // 报错!指针有const修饰: z" h0 w# ?& s2 U2 L% Q4 h
    ) a5 V1 E2 ^; `+ K
    // (const int) (* const) *  指向((const int) (*const))的指针
    1 @# _4 |& m- w) m" bconst int* const * ct_cp_p1 = &ct_p1;  
    1 q4 M# ?+ c# I8 u- ?ct_cp_p1 = &ct_cp2; // 合法, 指针的指针并没有const修饰, 指向的指针有const修饰( y- g. @$ K6 l: ]+ y& M' [" Q
    *ct_cp_p1 = &a; // 报错!等价于操作ct_cp2,  指向的指针是带const修饰的不能修改
    2 L2 \* N- A7 o4 k& [
    / Q" Z" g# X" {$ i' J& I// (const int) (* const) (*const)  / Z1 b9 ?0 A. Q
    // 指向((const int) (*const))的指针,且该指针被const修饰
    ; t( ^" f# Y  j1 yconst int* const * const ct_cp_cp1 = &ct_cp1;
    - _& A& s! q) q& P( @# [ct_cp_cp1 = &ct_cp2; // 报错! 指针的指针被const修饰, 不能修改指针指向。+ P6 H) p4 B+ b! f+ g6 |& h

    # K, x/ j: V" s4 m. {: i; L; ?一行声明多个变量
    4 k6 U6 j  D, i/ a- X  k类型 + 名称定义一个变量。
    4 D' W9 x6 O5 `( C6 V变量的前面可以加*号修饰, 表示指针, 一个星号代表一层间接。**表示指针的指针。
    ) O) @- C( T+ G' n  q9 b' _0 P6 x6 |8 B; I0 a. i# L
    int a, *b, *c, d, **e;1 l3 s5 J& u% O: ~* E
    a = 0;
    . H8 v& j7 V" K8 bd = 1;9 r9 H1 k& u  t$ f: h
    b = &a;
    7 T0 {7 ]# a+ d* sc = &d;
    7 o; g4 I( c3 S: T& m! a* K$ f/ ]e = &b; // e为int**类型 指针的指针& y( R! p3 m; o; o! Z* p) q
    e = &c; // e为int**类型 指针的指针/ ?# J' J$ \% S3 `  o3 V) G
    也可以用括号包围变量和*号。
    - C. b. y, i; n9 |! o5 z, w2 ~' E9 N% C9 V1 m- ?4 V
    int (a), (*b), (*c), (d), (**e); // 合法定义。8 O( p9 I7 }, K5 c& x( p
    括号可以省略,某些情况, 个人感觉加上括号更清晰一些。例如
    ( ]; X/ v0 G( d/ d+ b) L$ E* ?! y( l: a$ b" j$ u0 o
    int (a), (const *b), (*const c) = &a, (const d), (const* const* const e) = &c;9 k9 c  t7 K9 P9 H" Z' D
    写成
    % E; x$ V( k& m" h! _) l& a
    7 z7 v. F+ h8 [- c) ~5 m, M( O& xint a, const *b, *const c = &a, const d, const* const* const e = &c;
    1 u; A/ i* p9 w8 r  _! v更重要的是, 后面我们表达数组指针,以及函数指针时,括号是不可缺少的, 带括号的表达更加统一。; x, A2 S/ L( Q* H

    1 b6 ~! ^8 ?! E8 |9 \6 M数组指针$ j1 B0 P3 U+ D% [6 g# p
    数组基本表达9 X( P- {( H% [5 Y7 ?5 F( g; L
    int a[10];  // 定义了类型是int, 元素个数是10的一个数组。* B! y8 h  G$ l3 t/ c# f
    由于c++要支持一行定义一个类型的多个变量。 所以数组的[]时放在名称后面的。虽然我觉得; F$ ~7 X% v) W; x3 }. C
    ; t4 K* S9 w/ q
    int[10] a;
    & Z& W: Z/ F; I" u+ j7 h这样的写法更符合类型 名称的思维, 但是如果类型都这么写的话, 没法兼容以下的写法:
    : i' S2 @5 O9 v! U* E+ |, l
    ) B6 g' i/ m0 e9 [3 ^+ |int a = 0, *b = nullptr, c[20], **d = nullptr;
    + F7 e  ~, f* ~, C( l/ Oc++标准规定如此,但我们可以通过每一行只定义一个变量的写法, 类型会更加清晰。
    5 I1 h3 W+ e, T8 v6 A% ]! J
    $ a. S+ V  n9 qint a = 0;
    ) @9 j, O4 K" d/ p$ Z7 mint* b = nullptr; // 指针int*, M6 X6 b% z. ?  L- b
    int c[20];1 }3 F( t# j) k0 o
    int** d = nullptr; // 指针的指针int**3 Z, }/ o( L) K& m
    数组的名称是什么类型
    9 q/ Z& o2 \1 f数组元素类型的指针,可以直接指向数组。 并且数组跟指针一样,可以通过下标去访问元素。" O; ?5 k; H3 ~+ m

    6 O- b! V( z8 vint a[10];$ ^- Z1 {% w9 K( r1 x
    int* p = a; // 指向a数组的第一个元素* q- I. h* [# p# n0 m1 a1 V5 N
    a[1] = 1;! w" t, n5 A+ y9 f
    p[1] = 1; // 效果与a[1] = 1一样。9 c+ ~7 Z3 ]! }; ]" e' f" G
    数组可以当作T* const来使用, 但是又与T* const有些不同。sizeof()的结果不一样。8 C! ^; ~; I3 D' j

    3 T0 @8 r2 T! `# |, Q7 ?  Pint a[10];
    : J0 L( }4 u( {* E. \0 D$ Eint b[10];
    6 f0 W( u2 A# E- T, l$ j* @. cint* const p_a = a;# a$ e4 H# c3 T
    a[0] = 1; // 合法。 数组的元素可以修改。- g4 t( P2 f: ~/ q6 p- i$ E* p
    p_a[0] = 1; // 效果与a[0] = 1一样。
    6 n* O( I5 I$ u% I4 b$ A
    : n& w4 _6 V9 K) I. T, n! v) @a = b; // 报错! 数组本身的指向不能修改。
    4 ^, g4 s6 Q+ r* J5 K
    ) @8 S' \# R( [, |% a" D) V, m// 所以数组a可以当作int* const来使用
    9 ^# J6 u( Y* o% J7 E0 gint *const& ref1 = a; //正确。
    5 `3 C. @' V; H5 T8 mint *& ref2 = a; // 报错!8 |0 D% F. e" x5 r
      k+ M* m' `) r
    // 但是又跟int* const有些区别。# ^- j/ d0 [4 D, D0 Y- e
    assert(sizeof(p_a) == 4); // 32bit程序。
    , s/ u2 I9 ?& f& ^/ i/ iassert(sizeof(a) == 4*10); // 32bit程序  _8 S% d+ |$ H6 L

    / E& D7 D2 t: X, i9 y1 m数组跟元素指针的作用很相似,都可以通过下标去访问元素, 但调用sizeof()函数的结果不一样。元素指针的sizeof()返回值是4(32-bit应用)或者8(64-bit应用), 数组的sizeof()返回值是数组实际占用的空间。数组可以当作指向第一个元素地址的T* const来用其实就是我们常说的数组到指针的隐式转换。当数组作为函数参数传递后,会自动退化成T* const, 在被调用的函数内部调用sizeof()的返回值跟T* const指针大小一样。 数组传递作为函数参数后, 在被调用函数的内部与T* const是没有任何区别,只有在数组定义的可见范围内sizeof()才有获取数组占用空间大小的效果。
    , t. K- R$ ?/ `' d
    + B: P/ W4 h2 W; n- |4 S以下3个函数翻译成汇编以后,汇编代码是一样的。
    2 X' [3 b  O4 I" B& X. l0 ]# K/ s% i& p9 s; b! `
    void Func1(int* p_ary)
    9 g# W$ e5 r; w! @8 E) B{
    ! a! n; ]% l& _! y4 R1 i% W& Z: z* Q    assert(sizeof(p_ary) == 4); // 32-bit
    2 h* ~4 ?( H+ y# g    p_ary[1] = 1;3 O& ~- o6 l+ I! q0 Z+ E
    }
    7 X4 K" F) i* v5 L- s: K# e4 u% f: e8 p  ]
    void Func2(int ary[])+ k) [; F8 V" v$ j% h
    {
    & P5 {! T* H# z    assert(sizeof(ary) == 4); // 32-bit
    ; ]3 I7 b1 U; b2 h& d6 ]% q    ary[1] = 1;+ m& U) l  Y. I2 [( e
    }8 ?4 |% p! f/ c7 A

    ; Z+ U+ |/ h3 }) Kvoid Func3(int ary[10])" b' L" x8 V6 B! \0 w6 J
    {
    7 r# _0 X1 \, z( |) g+ o% O    assert(sizeof(ary) == 4); // 32-bit
    $ W6 v: t3 f% n# t7 ]    ary[1] = 1;$ t5 J% i$ q: l3 M/ d
    }
    # c6 z# a1 V& W1 I* _# S! `5 g; Q0 P! W: M
    int main(int argc, char** argv)5 F- B* A) K) Y" i+ k; w
    {6 [7 ^* c) R  r" W
        int a[10];
    . i1 q4 v# w; p: G    int b[20];
    ; b" r# O$ B  Z    Func1(a);& ^) X  I2 b& ?" Y4 I' _1 D
        Func2(a);* t  a0 L- u6 m
        Func3(a);) Z  q* B$ @3 z! {: G$ k1 F$ q$ K
        Func3(b); // 退化成int* const了, 即使数组长度不匹配也不会报错。
    2 ~) A1 X; B7 Z5 N! W. j    return 0;
    , y) M8 c1 j2 k4 `* z* W/ e}+ H: @+ S' Z6 B  d( n- ^

    5 D* W$ m8 P9 Q; w/ i
    . o& ?: y/ p' U- E$ R, I% p$ k# c/ z
    多维数组3 ^0 T+ r9 f" w% s
    一个3行,4列的数组, 结构如下:. b( j+ l) {* a6 y
    & n& V/ C6 ]3 b7 o
    int a[3][4];; b. p3 Y+ a* B
    column 0        column 1        column 2        column 3
    % h6 K# Z6 ]' k+ crow 0        a[0][0]        a[0][1]        a[0][2]        a[0][3]
    9 q7 O3 U& R0 O! v' ?7 Q+ U  L3 \row 1        a[1][0]        a[1][1]        a[1][2]        a[1][3]3 L3 y  m4 R+ Y2 u- V  Y- A  e
    row 2        a[2][0]        a[2][1]        a[2][2]        a[2][3]" j+ m6 S1 T4 D
    数组初始化
    3 x1 e" ^. r, F0 t% P! B  O
    8 `4 d( v6 a2 V9 t4 u1 Eint a[3][4] = {
    0 X9 |( ~" y# O) R    {0, 1, 2, 3},- D1 F7 T' R6 g; Y9 L7 M+ o3 \
        {4, 5, 6, 7},
    * U3 t; `# j0 y% F  O    {8, 9, 10, 11}
    0 X9 e8 y1 y. f' [- X6 {- C};9 [% O' I3 O9 F" ^# p
    实际上多维数组和1维数组在开启速度优化后,翻译成汇编代码是一样的。& `$ F% R: X, B& @% @; p; ~" m( @
    3 e/ y1 S" |' [# m& V. C
    void Print(int* p_ary);
    + U( }6 _/ \6 o2 }$ h
    : P! Z2 y  U' Rvoid Test1()
    , c+ `! ]3 ?: b{2 q# E* V) _6 j4 x) J, x
        int a[10];
    * r6 t" H1 l7 V( k) c: H7 x    a[3] = 3;: i: a; k$ e6 k4 R+ s" x7 m
        a[7] = 7;& a% u, f; ]* S8 q
        Print(&a[0]);
    7 w7 B: O) I, a& g3 _' y- q}# n5 |& }7 @, T- {! a1 r& A
    5 w5 h8 U, _" f
    void Test2()3 w) K) @$ X! e0 a: Q9 N
    {  S' p6 F8 M5 i1 [$ E, X+ }
        int a[2][5];
    # A2 F3 T- {, ^+ Y/ X    a[0][3] = 3;
    ; i/ Z( Z, L" O! M0 T    a[1][2] = 7;8 O6 g- N/ e; F1 W
        Print(&a[0][0]);* |2 t0 g, n# _; c$ r4 r
    }
    ) r. @4 B1 _0 {' \
    ' B+ _) D- U; L. n7 K: D4 J) W) B& Z! _8 ?9 M/ q
    & d- ]. r( n( h4 a- k& z  W" p
    很自然地,多维数组也支持用1维数组的方式去初始化。+ t$ T( O2 F6 m0 c1 j
    2 R& H7 ]4 x0 M
    int a[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };1 Q: v6 g0 W. W. b; S
    既然多维数组与1维数组没什么区别, 为什么还需要多维数组?: J8 ?; F; }) e! b# ^0 Q, w* S: |

    * H5 a: K! y" ^& w7 [假设有一幅RGB图像720*576个像素,每个像素有RGB三个通道,每个通道的值是8bit大小。给出图像的首地址p_rgb_image, 我们要取第40行,第50个像素的R,G,B值。代码如下:
      v- l# j5 i5 I8 c9 H
    1 J1 w- \+ @+ |5 A1 |unsigned char* p_rgb_image;
    ( ?& A$ n1 x( y* _unsigned char r = p_rgb_image[40*720*3 + 50 + 0];+ |2 Y! u3 \* q- h% T! [: q4 Q
    unsigned char g = p_rgb_image[40*720*3 + 50 + 1];; [) E7 s! Y- f6 x8 e
    unsigned char b = p_rgb_image[40*720*3 + 50 + 2];
    2 C) _2 `# K6 C# E5 \类似这样的场景, 采用多维数组的写法, 有点类似以索引为参数,可读性更高。相当于程序员和编译器打了一个配合。3 `7 N. w8 q- V
    & Q( ~4 m% h8 k( L' K. I
    enum
    0 `/ o$ m! Q( `4 ^) J{
    " q) u  Q$ M8 C5 P5 E( M6 F% U Red = 0,! g2 P3 N5 v; B
    Green = 1,% l9 G) c# I: t& t+ l  g% k7 i) a
    Blue = 2' \+ p! J+ N; U, ^* A
    };6 I# P8 {: I4 t8 R
    unsigned char rgb_image[576][720][3];$ d4 w4 K: ]7 [: S. s! v2 M
    int row = 40;1 t1 J0 d% v% T  S9 k3 _
    int col = 50;; n0 w% U4 I( u+ G7 _7 @, b; C
    unsigned char r = rgb_image[row][col][Red];
    , ?' i5 s* G5 \+ T& L% g! @" Tunsigned char g = rgb_image[row][col][Green];- W( W, l& z  Y* Q0 m3 H2 P
    unsigned char b = rgb_image[row][col][Blue];" O7 S. G. J& c' Y+ [3 U  Y* a. a$ ^
    数组指针以及与指针数组的区别( E2 n: H, d8 z, A
    数组指针,是一个指针, 指向的对象是数组。 数组指针的赋值,要求数组的长度匹配,否则会报错。当指向1维数组时, 需用用*取得数组对象的引用,再用下标来访问数组元素。2 B$ F+ C) l8 v( H4 f; k2 V
    指针数组,是一个数组, 数组保存的元素的类型是指针。
    2 Q0 G8 I% a) v数组指针的定义
    9 [$ G* k0 i/ t- v& `数组指针定义先定义一个数组。
    3 w  g; ]+ ?/ P8 o& h2 Fint a[10];- b8 J, F' w5 m0 |
    然后对数组里的名称用括号括起来后再在变量名称前面加个*号* ?/ `8 r/ a$ c- m1 }6 m
    int (*a)[10];
    . C5 V  q$ B- u7 q8 l后面你会发现函数指针定义类似。
    / z: F" E) s) J* Z7 d- e  Y2 Z. z. z3 }// 各类定义对比5 F% P0 G# ^; {% U, e& S* {9 y
    int a, *b, **c, d[10], e[10][20], *f[10], (*g)[10], *(*h)[10];9 H$ z+ A; u; q3 L, ^3 M4 s  g

    , u% W4 q0 W# B8 k. d& dint *f[10]; // 指针数组, f是包含10个元素的数组, 数组里每一个元素的类型都是int*, t3 s9 ]) U' u& B! C
    int *(f2[10]); // 指针数组。另外一种定义方式。- Q1 Z4 m6 m9 m
    int(*f3[10]); // 指针数组。另外一种定义方式。
    ) Q+ X/ N+ Z: Hint(f4)[10]; // int数组
    + L' E. ?/ k) m# h6 `% ~5 f  Z0 Z9 Q- [int(*g)[10]; // 数组指针, g是一个指针, 这个指针可以指向类型是int,元素个数是10的数组: K/ U+ Z2 ~  S% g
    int* (*h)[10]; // 数组指针, h是一个指针, 这个指针可以指向类型是int*,元素个数是10的指针数组
    2 ^6 q& w( R8 N
    7 A  @: w  v$ P! F, x6 Oint d[10];
    * a6 Y! z9 F0 T! U' ]g = &d;0 V# h7 @9 z# {" y; ~
    7 a* u  p. O# L* _
    int* e[10];
    : j1 H. X- s' z( l' ^9 \! Ph = &e;
    . e7 m7 o7 q3 r! L3 S% X' t数组指针的使用9 l2 |8 u7 F: q7 R
    数组指针一般先通过*号取得指针指向的数组对象, 然后再用下标操作访问元素。& {/ }" D( d- ^. x
    5 J, n1 k! s- B& E/ m; @+ F
    int a[10];. B6 }6 f$ K5 h, {5 _) f9 S, o
    int(*p_ary)[10] = &a; // p_ary是一个指针, 指向"int (*)[10]"类型的数组7 y3 @. S' m  q! M3 w0 y- K
    for (int i = 0; i < 10; i++) {
    . Z7 F* q3 F+ B- n; H# E/ L! j // p_ary是一个指向数组的指针, 需要先通过间接寻址运算符*(indirection operator)取得数组对象! }/ ~" H$ s7 V! Y( d2 T. m
    // 再通过下标操作访问元素。
    % u) [% o4 a9 G (*p_ary) = i; ) w7 w- t% o8 h( g
    }
    % @9 X$ o  n& ~
    3 S/ m) A( {2 ~6 X) C3 n3 rint b[10];
    " B1 }2 R5 m. B9 }7 y! O; Qint c[20];
    ' ]3 c% [2 m7 z; _p_ary = &b; // 合法
    2 r* S% E5 v& y. f" I% u" \; k# hp_ary = &c; // 报错! 不能将 "int (*)[20]" 类型的值分配到 "int (*)[10]" 类型的实体! P- E& i, G+ ]! z3 b; R7 e- k' l
    数组指针指向多维数组的子数组+ P+ H9 r0 u1 k; j
    int a[10];
    ! D5 c5 D' p% p$ X- N5 I: K! Cint b[4][10];' z* ~% x8 d3 u
    int(*p_ary)[10] = &a;
    * H# }: v# e3 Q/ lfor (int i = 0; i < 10; i++) {
    : a# ], Z; h. K" p% Y (*p_ary) = 1; 8 o- S' b1 B5 s/ T2 I) F  G
    }; _" r, q- U; ]. M
    4 `3 I5 [) l9 F
    p_ary = &b[2]; // 多维数组,可以看作数组的数组,
    2 e1 E+ }1 V4 P// b[2][0] ~ b[2][9]的值都被改成2了# ^  R# \3 t. F( e
    for (int i = 0; i < 10; i++) {- C7 R+ e% Z& `/ n/ U& @! a6 K2 V
    (*p_ary) = 2;
    # b8 n4 K4 R: H$ J}% z5 |3 M* E* S5 \2 L) |+ j
    多维数组指针
    8 E! o, ^9 r/ x- @3 V! g9 W多维数组指针,是一种指针,指向的对象是个多维数组,支持多个下标操作。/ B. F2 b& Z4 m  j9 O" s: h, b
    7 X0 c% h! p/ ^- v
    int a[2][5][10];! u/ p* V. C1 H" T: }9 _$ c
    int(*p_ary1)[10] = &a[1][2]; // 1维数组指针- Q5 |+ F4 @7 z+ {9 l7 Z5 n" I
    int(*p_ary2)[5][10] = &a[1]; // 2维数组指针
    8 w- ~. l! s" \$ T' vfor (int row = 0; row < 5; row++) {
    1 D9 z6 m# W" O; w& a7 {    for (int col = 0; col < 10; col++) {. A* M) H+ M/ B& \# z
            (*p_ary2)[row][col] = row * col;
    * x) \2 U  {' k$ S7 M1 a    }
    ( f2 i. f* \; T7 m% r/ y1 `}
    # E$ L, l* L1 o5 M1 @数组指针和指针数组对比实例& t# B# j3 L4 D, j' [/ m1 d" w
    数组指针还是记住两步法即可% i- z( z3 q$ k8 P
    $ Y7 E3 H0 w5 E9 C5 m! e
    定义一个数组9 T( @- p# S# T$ O  {
    括号包围1中定义的名称,再在名称前加个*号。
    0 N$ R: N8 M/ i4 Q! m* E6 i" b# [int a[10];. ~4 _; o- S: i) }1 M7 f4 K9 ]
    int(*ary_pointer1)[10] = &a; // 数组指针
    " d" R5 C* e2 i) }8 uint* pointer_ary1[10]; // 指针数组。元素类型是int*
    0 p, g) K- i4 \3 F9 O6 w0 O; [/ Qint *(ponter_ary2[10]); // 指针数组。另外一种定义方式。/ [2 w- T# F, V+ S
    int (*ponter_ary3[10]); // 指针数组。另外一种定义方式。! v/ z" T$ s6 Z  M  P% |1 g% S" G
    int c, *d, (*ary_pointer2)[10], *pointer_ary3[10]; // 排列定义比较。
    7 h' s. [) |, z9 n% l) I6 p$ A! c* z+ M1 |, b( u1 E* o
    // 指针数组可以把每个元素指向数组对应位置的地址。
    $ m  l& `& T: s* S# s; r) Y// 这样遍历指针数组, 可以达到遍历数组元素的效果,但是注意每个元素都是指针,
    ! l/ z8 ~# o* m; c& Q// 需要访问原数组的值的话, 需要对指针用*间接寻址运算符。
    ; y3 w$ J# b6 V4 w2 n8 B1 `, Hint* pointer_ary[10];
    7 s! K2 J$ g* \- J: vfor (int i = 0; i < 10; i++) {
    ) l1 s" E. I2 u/ k# n    pointer_ary = &a;5 s$ {3 ^' [, t7 g! |, p% V; {2 j
    }% F" a6 D$ e% ^& I
    // 类似遍历原数组效果。* T+ V, X9 M% z" ^4 y) T) E
    for (int i = 0; i < 10; i++) {$ v* X: V/ I2 C; t
        *pointer_ary = i; // 修改原数组。
    ( K" L+ k# y, w4 M6 I% Y) }# H% x2 O}
    4 ?9 m  @" k2 `5 u& i
    , j5 _+ O6 F/ A2 I3 R$ w- u函数指针
    / {5 e3 M8 j! R: Q. u9 o. N3 M取得函数地址5 N1 k6 g9 [' V% `! B- W6 n
    函数的名称作为参数被传递时,会隐式转换成函数指针, 和在函数名称前加取地址符&等价。建议带上更加统一和清晰。4 a7 e6 ^% V+ H1 ^9 e
    : @% n5 K+ Y3 g( c2 N
    void f(int);0 E# ^1 W3 k. n5 J1 B7 [
    int main()5 _- {, `  F# x9 ~% Z' n* B, C3 V( k
    {- M& L; R3 V, ?; X; e: Q0 Q& p) \
        void (*p1)(int) = &f;& L( D; G& l. m$ C  A
        void (*p2)(int) = f; // same as &f/ Z# o2 y0 s9 D" V6 l
        return 0;
    # t- X5 c4 D( R; S' J* o% o( T}
    0 K; x' W0 a6 k6 d3 _9 L* Z翻译成汇编代码, p1和p2的赋值是一样的。
    4 x$ o" x- d; |& X
    ) \, ~1 T" Z, p: H0 M/ d( J' w; N( M% z% f

    : \% q& ~$ `; I2 ~函数指针的声明
    & |7 [( m0 t3 G9 b, y6 c: ~单个函数指针变量定义步骤+ [2 ~" r$ j( T9 T2 |- Q' Q
    定义一个函数。void fun1(int a, int b); int fun2(double a);
    / I+ X  M' w5 J6 R5 }用括号把函数名称包围起来,然后在名称前面加*号。void (*fun1)(int a, int b); int (*fun2)(double a);# s1 N7 j  M" F/ q+ }1 H9 A
    如果要定义函数指针数组,在定义单个函数指针的基础上,在名称后面加上[数组长度]void (*fun1[2])(int a, int b); int (*fun2[10])(double a);
    & g* z. e+ O* X" `" b' l, v4 Stypedef定义函数指针
    7 H3 {5 j. J+ m% f) j3 {: v可读性高比单个定义要高,特别是声明多个同类型的函数指针,或者函数指针数组。* T: G7 F! _! z4 P8 Z/ s

    + s( w% a& p+ p% h2 Z' Ztypedef定义函数指针的语法
    3 @% U8 Y) F! b4 wtypedef有两种做法, 一种就是定义一种函数对象,另外一种就是定义函数指针。用法稍稍不同,效果是一样。其中函数对象不支持赋值, 但是支持引用。
    # H* n7 z7 @, a/ F) Z$ Q. E5 h, o6 [" _
    typedef int FuncObject(int a, int b); // FuncObject类型是函数对象
    * F. A" V' V5 gtypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针
    , A4 \4 e6 G; r6 \FuncObject* f1 = &Add;! b! I( a4 n5 L2 X& }& B! k
    FuncPointer f2;
    8 A" m! ?$ x7 g5 |f2 = f1; // f1, f2类型一样, 都是形式为int(int, int)的函数的指针。
    7 V- {) [: o9 w! m8 WFuncObject f3 = Add; // 报错! 函数对象不支持拷贝% L  W9 l' y* x6 i
    FuncObject f4 = &Add; // 报错!&Add是函数指针,与函数对象类型不匹配! d0 `8 X- K5 Q- M# X; I
    FuncObject& f5 = Add; // 正确
    . t  }1 ?* V  ~  Lint ret = f5(2, 3); // 正确
      N  {" M7 @- c* gFuncObject& f6 = &Add; // 报错!&Add是函数指针,与函数对象引用类型不匹配3 G& h. d9 M- a
    如何记住typedef定义函数指针的步骤& l2 T, b0 e- ^
    像定义一个函数指针那样, 指定一个名称。int (*CalFun)(int a, int b);- Q6 g# B* f9 y
    在这个函数指针变量声明前面加上typedef。typedef int (*CalFun)(int a, int b);
    & x  ~$ ?) g/ Q: z完整例子
    9 j$ ^9 w. T3 @5 k- w  A. |typedef int(*CalFun)(int a, int b);
    - R0 y4 `9 s) H/ ^9 F
    ' b* `' I) e$ |int Add(int a, int b)' A4 t! z2 R" H8 _) {
    {
    $ d) I; \% m$ l- ~1 ]    return (a + b);; N5 I" F1 [) O2 E2 O) D
    }0 b! O& E" F7 J/ |

    8 G5 a: p7 g3 x6 o- w" H" Gint Sub(int a, int b)5 J" i$ l( u9 e" K* G( |# U8 X
    {
    7 H/ C9 ^8 ~5 u. |2 M    return (a - b);- y$ [+ z' r3 b) j
    }2 }9 M- r5 h3 _5 X# g% B: F

    * Y5 E8 }! W8 A( J9 f+ gint main(int argc, char** argv)
    . M0 A; l- b# N3 e{* y9 p7 D7 m& N' n. P5 Z
        CalFun f1 = Add;
    , q0 S! y4 p/ B4 I: t    CalFun f2 = Sub;
    6 Y9 d2 \6 P% M    int a = f1(2, 3);
    5 ~# U" K) y: A4 ^! {3 ]0 A    int b = f2(10, 5);7 f% f: @" F  q! l3 |! G! N- \

    1 d2 s1 g# P7 u1 S( N    // typedef定义的函数指针数组。
    ' }! t* z* i0 J    CalFun f_ary[2];
    6 F* }  T% C" a9 I    f_ary[0] = Add;& R: O( `/ t- o
        f_ary[1] = Sub;
    8 H- R5 q! ^3 Y' S3 U1 r
    " }" ?+ D3 r1 Z- e. I: \$ `    // 单个定义的函数指针数组。# S. K: a' Y6 X! x
        int(*f_ary2[2])(int a, int b);7 W. W2 [2 F5 w" \
        f_ary2[0] = Add;
    6 K* l/ R8 g3 A! A& I" i$ `    f_ary2[1] = Sub;2 f4 L8 w) d- t! ^2 {1 R+ |

    4 j0 k, w& p) o7 ?    return 0;
    ( m4 _  I; S+ o: H& }' p/ w% m* T}8 F) }/ }+ N: t3 T

    ( u( n5 D; L2 U) @% k3 Fusing别名定义函数指针
    % P" D6 r# }& D! w6 R* ^5 q/ Z5 pc++11以后的类型别名定义--using也可以用于定义函数指针, typedef的好处它都有,个人感觉比typedef更直观。using类型别名同样分函数对象和函数指针两种方式。9 ]8 Q* v- j* v$ G8 Q& j

    " l' e. K2 h9 o7 X6 e2 y; Atypedef int FuncObject(int a, int b); // FuncObject类型是函数对象
    - [, v  v* N0 `& V8 eusing FuncObject = int(int a, int b);3 d" O% M1 I( A& I+ N) r: o3 L( m8 E
    typedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针  _% ^# V' ^7 ^; P  [
    using FuncPointer = int(*)(int a, int b);+ z8 B. f! ]* C
    函数指针的调用
    ( x- y3 A+ R/ W( t) ^函数指针和函数对象都可以直接后加括号调用
    3 x6 O+ A4 w$ N6 D' I+ Fint f();
    1 K3 {# K1 G0 `! Wint (*p)() = f;  // pointer p is pointing to f( w: Z! M7 G1 j8 w% j7 D1 Z6 F
    int (&r)() = *p; // the lvalue that identifies f is bound to a reference9 ~) i. m, |- `5 Z
    r();             // function f invoked through lvalue reference
    ' A1 W: r- I2 [  l- B(*p)();          // function f invoked through the function lvalue
    & }( \7 q* O" Xp();             // function f invoked directly through the pointer  Q6 U4 k4 \# d8 v2 M7 {; O' S) A
    如果函数有重载, 函数指针会指向匹配的那个版本。( L) H5 w2 [4 ~0 c' l1 U2 v5 o$ P9 D
    template<typename T>/ d5 R* z+ U6 H0 v0 m$ r7 ~
    T f(T n) { return n; }- E" t; q! \/ G$ k, B
    $ ]+ x; [9 z, Q$ Q7 C8 G1 I
    double f(double n) { return n; }
    # j& \" q' K8 r4 c
    ; J0 W4 @4 @; ]8 Cint main()7 R/ Z2 F! u$ P) I, K( }9 i
    {
    4 P0 K: e5 E* ~' U1 Y4 Y0 d8 d    int (*p)(int) = f; // instantiates and selects f<int>
    ) Y# ^: b+ u5 E  @}
    6 Q! F/ c5 {2 e6 L) ]1 `3 n, m成员函数指针
    2 {9 H2 g/ p1 S4 w静态成员函数,除了增加了访问控制以外,跟普通的函数指针没什么区别,所以普通函数指针可以直接指向类的静态成员函数。但非静态的成员函数与普通函数指针不太一样,声明时需要指定函数归属的类名,并且调用需要指定对象实例。& p8 ]0 g2 l2 Y3 r5 E

    $ ?% [: v( p. j7 X0 `, e3 X; S; W成员函数指针定义。4 M1 e% K+ C1 B9 Y0 Q: ?& t1 @
    像定义类成员函数实现那样写, 并任意指定名称,这里作func。void ClassName::func(int);
    ) r1 u' p% J$ M. N& f8 D括号把类名、范围解析运算符::、名称包围起来。void (ClassName::func)(int);7 Z, x% m, R/ r8 g! E6 C
    在名称的前面加个*号void (ClassName::*func)(int);
    & m6 @" ~( j/ z# p' \成员函数也支持typedef和using的定义方式。typedef void(C::* MemberFunc)(int); using MemberFunc = void(C::*)(int);. B3 w. W  ^! i* j
    成员函数指针如何调用。, h3 q& v" e/ l. B
    假设成员函数指针名字为func! V" t( r% ]' {5 U8 z+ e8 N  ~& c

    * g* h' x  ?4 I' }5 Tvoid (ClassName::*func)(int);
    * O: |" n* w8 I8 t# ]对象式调用。
    # a7 [% s6 A, t+ ~9 aClassName c; // 被调用的对象9 a9 t- \* P6 m- Q5 y
    成员函数指针名字当作正常函数那样写。6 a; F* U) M8 u
    c.func(3);
    7 y& D  @  `" Q% ?2 r3 f4 f% `成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。
    ( p4 J# m# x6 qc.*func(3);
    & a$ x# j7 @, E1 J最后用括号把调用对象、成员访问运算符.、间接寻址运算符*、和成员函数指针的名称包围起来。
    / ?, J5 f7 F8 n: L8 z% k: b2 I(c.*func)(3);. e. r  v" y2 V* p3 y& k
    为何要加上括号? 根据c++的优先级标准,取成员运算符. > 函数调用() > 间接引用符*。 *号优先级比函数调用要低, 成员函数指针还没取得对象就被调用了,自然报错。 另外0 |, t2 n# H7 m$ ^2 P! y4 H
    (c.(*func))(3);4 g6 N4 C8 X$ J& `
    这样的写法也不行。 .*和->*是整体作为一个运算符的,中间不能用括号隔开。
    5 `& w1 P5 h' X' T指针式调用6 l* U( |! S; g- T! i- `; M
    ClassName* p; // 被调用的对象的指针
    & `0 s9 v! G+ V: U/ C3 a成员函数指针名字当作正常函数那样写。
    3 m3 Y1 Q" ?6 m# Np->func(3);' M! d* ?% u  r+ k
    成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。0 _% ]1 d0 e9 l8 O
    p->*func(3);  E. R& m. v$ u: {
    最后用括号把调用对象、成员访问运算符->、间接寻址运算符*、和成员函数指针的名称包围起来。2 `' i  o8 _2 k) `
    (p->*func)(3);
      q, p# b9 T# z4 _5 w9 N3 _* \- [函数指针使用完整例子. |7 f  o: i, `9 }9 |! b
    struct Cal
    8 h( I% U% l2 V{
    4 }+ u, j2 s; h! Y    int add(int a, int b); * U: d/ A% A  p  N8 ?8 f1 g: X  {9 Q
        int sub(int a, int b);: P, y. m; k: m1 ^
    };' O5 V2 A  X8 q

    2 A+ @4 |; k# A) v& mint main()& b$ g+ d2 U- q: ~9 U" v% j+ b
    {' N. ~$ F6 m- O: x! o
        int (Cal::*fun)(int, int) = &Cal::add;4 v/ f6 {" Q5 _; O1 F' K% y
        fun = &Cal::sub;  I. a- `* b0 R' [, q4 o
    " c1 a" H- ^3 s1 `# t  G# ^
        Cal* p_cal = new Cal();
    " W* z2 e, {/ ]" z  a7 f    int r1 = (p_cal->*fun)(2, 3);1 w4 p# I$ @; G  k
        delete p_cal;
    . N* W6 ~! H( _" }; H' E' ~% W. ~2 Q  i* z" N5 u; Z& M( E, ]
        Cal local_cal;
      k5 G, [2 |+ y9 f0 b    int r2 = (local_cal.*fun)(8, 6);
    2 N6 t* J2 W0 y9 C3 ^}, m* ]$ s: _0 h0 }

    * p0 O+ v/ v, [! `# U: ^% P' Z成员变量指针
    $ }0 g1 W; v9 R! j+ r, @4 v成员变量指针比成员函数指针还要简单些,没有函数调用, 无需考虑函数调用和间接引用符*的优先级问题。+ D" E# a- |$ m
    . z( L0 a/ `0 Z6 X% h# P
    成员变量指针的定义& m- ?. }) ]- j8 }8 f* S
    假如以下结构体C。8 y; d# p7 A1 W5 a

    ! H' w4 `* Y5 U  U4 d. nstruct C ! h4 `# c( Z/ K( _
    {
    5 |% V+ {- n2 O, X9 f8 j    int m; % Z) k6 p/ ^3 s6 v4 r4 i+ I; W
    };
    1 U+ w, K" t& F9 s' \4 P单个成员变量指针定义 ! u8 }3 E; t( u5 K$ R
    假设名称为p, 类似静态成员变量定义那样声明" Q' e* g* F. q$ J9 U' c" X3 `
    int C::p;
    8 H  T: _, s3 T在名称前面加上指针标识号*
    5 S5 _* K9 y0 J" W  h3 uint C::*p;( _( `- S% t) H7 G2 _1 ^
    typedef或using方式定义- U5 D* K/ i5 }: l0 D# E# M
    typedef int C::*MemberPointer;& r: S0 o4 e4 L1 Q0 D* h" ~" [
    using MemberPointer = int C::*;
    7 [& p6 i% O8 A  U: `  V5 ~成员变量指针的使用。
    / F" ]" {) A/ m2 o5 F4 g% ~类似成员函数指针那样,直接使用指向成员的指针运算符:.* 和->*即可。+ |2 X7 a5 A" o: v
    成员变量指针, 能让我们实现一些遍历成员的动态功能。 例如把一个类/结构体的多个同类型的成员变量放进一个容器里,然后遍历访问这些成员变量。
    / K3 o% I. ^7 _# w4 u# j8 a" X' P4 x2 c. N, Q7 n' ]
    完整例子
    8 i0 @" t* M( `1 ?2 }% f1 N9 k3 W- Qstruct C { int m; };
    ( f8 L! `! T2 X' n* H3 Zint main()
    " j. G7 b4 t/ P' l5 U  ^9 {; H{. ]2 t: @$ f+ i3 ^
        int C::* p = &C::m;          // pointer to data member m of class C
    $ Y3 l2 S7 y9 b    C c = {7};$ p/ G* p& u1 X+ x: a
        std::cout << c.*p << '\n';   // prints 77 Q6 W& Y. b0 Y: n7 ~; Z
        C* cp = &c;( I+ X7 K) h' S# o% ^8 W, \
        cp->m = 10;
    % j* G, h' C+ o# ]* o" E    std::cout << cp->*p << '\n'; // prints 106 L9 G. N1 l5 V" t) b& i
    / {2 e& a5 A2 b+ z0 E" c
    ————————————————  w* ~7 g- h0 F9 `( m9 l
    版权声明:本文为CSDN博主「南风fahaxiki」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。% |+ d& z1 ?9 K: z
    原文链接:https://blog.csdn.net/m0_64407685/article/details/126788115
    ' b* o, c( I$ W" _. W( g9 z; K
    & H* x7 I) B' V1 `: l5 l: b! U; T& K8 g& v4 W  `; t" }# S
    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-17 12:51 , Processed in 0.565246 second(s), 55 queries .

    回顶部