QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2388|回复: 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++数组指针、函数指针、成员函数指针
    % b" u7 x5 h3 v: g; KC++数组指针、函数指针、成员函数指针
    # V% o0 W( Y' H, a/ J5 x; w$ ]; j, \  D% N* ]

    2 U# ^& ~/ w7 t& V操作符名称
    ( T* @1 P, p0 ]. y: A: m& 取地址符(Address-Of operator)" u- B' Z, x) E
    * 间接寻址运算符(Indirection operator)1 x+ b3 V) e  Z' v) M% u
    .和-> 成员访问运算符(Member-Access operators),用于取对象的成员。+ @$ ~6 P  ^4 k7 s/ M
    .*和->* 指向成员的指针运算符(Pointer-To-Member operators), 用于成员函数指针和成员变量指针的取对象。
    . t  i, F3 O9 V! H% L7 E6 o() 函数调用运算符(Function-Call operator)7 r" d' C! Q3 l2 S5 R+ r
    :: 范围解析运算符(Scope-Resolution operator)
    7 ]" F5 e/ e' E' ^如何定义一个指针变量
    ( B1 n8 w, D/ G# G. c假设类型T, 变量名称name, 指针的定义如下:
    - Y. D; Z( F/ S3 C+ S8 z
    % L/ a8 u4 j  ~T* name;3 T" b- ^0 \( @1 f
    标识变量名字name, 它是T类型的指针。例如& v4 ~+ k9 T! E$ e
    2 B( f& @/ E! O  ?
    int n = 0;
    % d# Y7 a3 F  x7 [8 uint* p_n = &n;
    * q' |) J) n- x$ l1 D4 ~2 b1 H5 Zp_n是int指针类型, 指向某个int型的对象。
    & j  L# U7 U1 n" X7 B: y" d& Q& n8 e( f8 d
    指针变量的修饰: h; K1 o6 b8 E; c5 b. O
    指针实际上也是一种变量类型, 只是它保存的内容有些特别, 是指定类型的地址值,通过间接寻址运算符(indirection operator)*, 可以访问到指针指向地址上的指定类型。9 e  a0 [8 x- K9 N5 e

    & Q7 j- X* W- X* L; n/ ?; i指针也可以用const, volatile修饰。 const int或int const均表示一个变量类型是int, 且该变量不能修改。以下两种写法都可以:# p, e8 o) C1 E+ K: a9 _9 R7 B

    & f) A+ F% F9 g( A' Bconst int a = 1;9 U/ G; d* q' O" K# d4 K, y. c& r
    int const b = 2;0 H; h! L! b- d3 M7 V8 i2 }
    既然指针是也一种变量类型,同样支持被const修饰, 表示指针的值/指针的指向不允许修改, 指针所指向的那个变量是否允许修改, 那是另外修饰。写法如下:
    . \8 B: Q, p5 S9 _' [! k8 o5 q- R% G% L
    int a = 1;6 M( T* f, Z( B7 _1 P
    int b = 2;
    / A# ^) F& |9 nint* const cp_a = &a; // 指针的修饰词,放在*号后面。! }* }6 U* Z* N: M- ?. E
    *cp_a = 10; // 指针指向的值可以修改9 j$ j8 U5 L* a$ j, b  L  ~, F
    cp_a = &b; // 指针不能被修改,报错!
    ) Y4 ~7 m: _# A2 h总结带修饰的指针的格式:- a* ?* a2 E7 I- K
    只要记住修饰词总是放在被修饰的内容后面。  T, h; x0 [" _: B( `) v2 m
    6 d, q+ x" `/ h7 T2 y( s- F4 g2 z
    cv表示const / volatile修饰词。指针定义形式如下:, R  N# d; v; a6 U
    & f5 L1 C8 x* g5 W
    T [cv for T] * [cv for pointer] name6 Q9 V2 I+ b' y, k& m2 H5 |
    注意对T的修饰放在T的前面也是合法的写法。9 V) B  H# u" d4 H; {* a

    7 g7 J5 q0 }3 F' _$ Lconst int const c = 2;
    6 S+ ]$ Z& j% v+ c- ~/ o在mscv编译器下也不会报错。" t: g! @3 ]% G- x4 q1 J$ {# ?+ S

    1 [7 X( A$ z, b9 u完整的格式:
    6 k2 J* G/ o/ A8 X[cv for T] T [cv for T] * [cv for pointer] name
    5 b6 s9 M7 {! q1 D
    / z7 y9 U4 w' TSyntax        meaning
    - F. I: E. L8 N. `+ L  `* aconst T*        9 ]! A" w) l/ ?; @
    pointer to constant object; R2 U% ^) j9 y0 r: b0 m

    $ G$ c7 B) e& t$ W: nT const*        pointer to constant object3 j5 `# I% ]# K4 s1 x) V
    T* const        constant pointer to object
    - L0 c% _' N3 f, G5 m) \const T* const        constant pointer to constant object7 D9 ]7 J( B; o, q
    T const* const        constant pointer to constant object/ x) l/ ~1 s5 e4 w
    上面格式中T还可以是一种指针, 指针的指针仍然是按照修饰词总是修饰前面的标识(T或者*)来确定修饰的意图。
    8 i/ i( ]0 M+ H. B- i$ O2 y
    " q: B: R: x6 G$ O: P4 x9 A+ y* Nint a = 1;; D! m3 s5 T) h" E. K
    int b = 2;. D: L9 e( q' d7 q0 I* k& @

    ! q3 o9 @4 B! nint* p_a = &a;6 v+ Z6 z1 T$ v4 X: |, }. \2 B
    *p_a = 10; // 合法9 h% \' S7 _* [
    p_a = &b; //合法+ X8 k7 g' Q7 w. x
    9 i# \9 E6 i: x6 ]" j
    const int* cp_a = &a; // const修饰int类型, 并非修饰指针
    8 F6 p+ B/ x- `% O4 f/ X*cp_a = 11; //报错! const int类型不能修改
    ! g' Z% o3 a4 z( fcp_a = &b; // 合法, 指针没有const修饰,指针可以修改。3 `& k1 ]; W. ^$ x
    ' O! a( M+ I+ e  [: v
    int* const pc_a = &a; // const修饰指针。类型没有const修饰& j  c0 p3 Y: p& C+ F" L7 n
    *pc_a = 12; // 合法, 因为类型没有const修饰,可以修改。5 }2 G) I7 E; }. d$ h9 W
    pc_a = &b; //报错! 指针被const修饰, 不能修改指针。
    , u" N6 n4 s6 k8 Z1 R4 {+ U8 E
    1 x+ A% h! G2 F9 ]4 gint const* const cpc_a = &a; // int类型被它后面的const修饰, 指针符号*后面也有const修饰6 l: X3 |, d$ t( a
    *cpc_a = 13; // 报错! 类型被const修饰,不能修改。
    ; f* {6 r6 s% Z8 M1 i) ]$ Acpc_a = &b; // 报错! 指针被const修饰,不能修改。5 Y0 {' e% q* G( ?5 z3 D' V

    4 w9 p% k4 U! J# a4 g7 S1 r 更复杂的指针的指针  V( U* w$ j1 t& B5 O! `
    % ~9 R) w/ j! T& f/ e; q
    int a = 1;% p$ l- N4 n7 Q$ j0 F( p8 M! J
    int b = 2;, R1 ?6 C9 s, I! L  F0 A
    int* p1 = &a;( ~+ u% q6 u) p$ g% N
    int* p2 = &b;
    8 x& n9 \+ K7 |! nconst int* ct_p1 = &a; // ct for const type2 z2 ]! l* \  |
    const int* ct_p2 = &b; // ct for const type! z' j9 v4 K" |5 x* E( t* J
    : }$ s& k- Y& H
    // int * * pp1; 指向(int*)类型的指针
    . G% t4 Z& g* s8 X3 ~int** pp1 = &p1;  " J; u1 [  _4 T6 `+ ]1 N' p
    pp1 = &p2; // 合法, " ?# Q( J! @2 u% K' O: E
    pp1 = &ct_p1; // 报错! 类型不匹配。 (int*)不能指向(const int*)
    : A' J7 J  l& e% E& _9 \& R0 v( u+ m/ L: d7 w% v9 M) e3 k8 \
    // (const int) * * pp1; 指向((const int) *)类型的指针& c: I, z, @& E7 D+ M( P
    const int** ct_pp1 = &ct_p1;  6 Z9 t1 `# I, h4 _/ z% V
    ct_pp1 = &ct_p2; // 合法* g8 e& `0 x% z& g; y+ o
    ct_pp1 = &p1; // 合法!(const int*) 可以指向(int*)类型。' m+ p7 j: j/ R) X5 U$ Z. r

    # x- D& @1 o: m: ^// (const int) (*const)- z8 g$ G) C7 F! [) y$ k
    const int * const ct_cp1 = &a; // 指针也不能修改
    2 B8 H' i6 [+ p. T- P  lconst int * const ct_cp2 = &b; // 指针也不能修改
    2 Q7 q& v1 P/ c: q5 A2 s( ~1 M1 ^ct_cp1 = &b; // 报错!指针有const修饰9 e: F- c" y: y

    " o, J0 C1 y% }' E// (const int) (* const) *  指向((const int) (*const))的指针# m% z" G# C5 e; G' Y+ i% O0 S
    const int* const * ct_cp_p1 = &ct_p1;  
    / w; [5 L/ e" k7 t4 i8 Y8 @ct_cp_p1 = &ct_cp2; // 合法, 指针的指针并没有const修饰, 指向的指针有const修饰
    2 P" J9 i, V, W# C; G7 U*ct_cp_p1 = &a; // 报错!等价于操作ct_cp2,  指向的指针是带const修饰的不能修改% d, `! @- E! N& K, @" n) x; w

    - D' i4 t* d8 q% g4 l3 h// (const int) (* const) (*const)  
    7 K/ C+ H0 k& J, Z$ c# L; o* M. e// 指向((const int) (*const))的指针,且该指针被const修饰+ r( D5 B3 G( M3 F
    const int* const * const ct_cp_cp1 = &ct_cp1; " T+ X" k# k9 i# H/ h+ J" h3 ]
    ct_cp_cp1 = &ct_cp2; // 报错! 指针的指针被const修饰, 不能修改指针指向。5 W; @; D) a* `" N0 r
    % L" ^* |2 ?' _( C
    一行声明多个变量
    0 Y) ~# k& G! P9 I, t类型 + 名称定义一个变量。' c3 }& n3 [' f
    变量的前面可以加*号修饰, 表示指针, 一个星号代表一层间接。**表示指针的指针。
    , \- m( }4 d" g' C) h9 b; x2 [/ G
    int a, *b, *c, d, **e;. ~2 g/ ?  G6 S& P+ ]
    a = 0;# U" M6 p% l" m
    d = 1;4 [  Y+ v9 ?( k$ A1 ^; N4 X# D
    b = &a;
    $ A3 r7 q$ c/ h6 M0 T5 O4 s7 `c = &d;' r# K% i# w5 {' l# \# @/ H
    e = &b; // e为int**类型 指针的指针
    / @( t4 R; [$ Le = &c; // e为int**类型 指针的指针* O1 o$ P( u8 L, u  q
    也可以用括号包围变量和*号。$ j9 O7 |" M  `' j; d; R; W

      f' _! o# z% j: l" S' x$ W9 Xint (a), (*b), (*c), (d), (**e); // 合法定义。
    9 Q7 ?7 t& O* E; i括号可以省略,某些情况, 个人感觉加上括号更清晰一些。例如
    " ^2 X( U( v2 |4 f! `* z# o  M9 m5 m# j& S+ z, `/ W) g! K8 ]
    int (a), (const *b), (*const c) = &a, (const d), (const* const* const e) = &c;! w' L) r6 c* H' P; a/ r; t; F6 m
    写成* o, C# V0 R8 A9 y3 E5 N  w& k

      D( K, h$ Q. O: S! {int a, const *b, *const c = &a, const d, const* const* const e = &c;
    0 N7 `) a$ J" r# Z. W更重要的是, 后面我们表达数组指针,以及函数指针时,括号是不可缺少的, 带括号的表达更加统一。( T! d( a4 [# f7 ^" ]
    * L* d" x" g2 W& V4 l  s
    数组指针
    " F# j8 n2 c2 H# r数组基本表达
    7 j" @9 s, S" D4 z+ fint a[10];  // 定义了类型是int, 元素个数是10的一个数组。. X6 c0 D- f9 w: c6 V
    由于c++要支持一行定义一个类型的多个变量。 所以数组的[]时放在名称后面的。虽然我觉得' j3 g$ c  C% ^. n, H: T

    ' H: F1 r" Y3 c# aint[10] a;
    % d* K' j  W8 O  t这样的写法更符合类型 名称的思维, 但是如果类型都这么写的话, 没法兼容以下的写法:& p: ]: M" F  J& i" ~/ t

    9 u9 k8 C" s, i) X! m4 V0 s0 `int a = 0, *b = nullptr, c[20], **d = nullptr;
    ' |( ]( L$ n6 T4 ^6 E. A( C" i/ pc++标准规定如此,但我们可以通过每一行只定义一个变量的写法, 类型会更加清晰。! a. M, ?& f6 Q0 |3 [4 Q

    ( F: k: G# z+ Y* B& z) iint a = 0;
    - V+ Q6 U  b; oint* b = nullptr; // 指针int*# O* f8 k8 v: \  ]( \
    int c[20];
    " ~0 r; N& v6 R; [int** d = nullptr; // 指针的指针int**
    0 F! @$ l4 G6 S4 Q. L8 x数组的名称是什么类型* n% K! {* W' f2 p* t! L
    数组元素类型的指针,可以直接指向数组。 并且数组跟指针一样,可以通过下标去访问元素。- e: E2 B& L- @* k! C$ ^; u( d( {
    / Z7 d  _& z( z& U) [( w* _  P+ q
    int a[10];; c! Y1 P: L4 X- t! J: r0 W
    int* p = a; // 指向a数组的第一个元素; l0 }/ ]3 r( S/ a5 T
    a[1] = 1;
    7 y1 F/ Z& `$ r# c" a7 z$ ]p[1] = 1; // 效果与a[1] = 1一样。+ c0 M7 _& h/ v: p
    数组可以当作T* const来使用, 但是又与T* const有些不同。sizeof()的结果不一样。6 z9 v" T) T0 j5 j* V( U: R

    * o7 ~4 ^' Y- s) T+ Qint a[10];/ }* s5 r7 x( F/ \
    int b[10];
    . q  w! M* b) ]int* const p_a = a;
    9 y7 M5 c5 k0 D- J4 E! E, {a[0] = 1; // 合法。 数组的元素可以修改。
    ' v" c# e  _8 ~p_a[0] = 1; // 效果与a[0] = 1一样。
    & m/ m4 {  y1 U+ |# }- R" Z) D
    % x& `; e) R5 r& T0 J4 aa = b; // 报错! 数组本身的指向不能修改。# c7 ^2 l8 ^9 L6 W! H7 H, {
    " x% Y8 ?4 `6 N* d
    // 所以数组a可以当作int* const来使用9 @4 G' w" a! U( f! Y6 Z) H
    int *const& ref1 = a; //正确。' ~8 r' U% M5 }5 r: H) s
    int *& ref2 = a; // 报错!  \. B1 y/ m1 a7 N# b
    5 W4 N' h" F  A  a5 `4 K
    // 但是又跟int* const有些区别。+ }. w! n7 g' y2 |+ v% }
    assert(sizeof(p_a) == 4); // 32bit程序。6 T) J2 b8 {& r* X7 X
    assert(sizeof(a) == 4*10); // 32bit程序# N0 n4 H- K) }+ |4 [

    * y3 e0 w3 k) F0 g数组跟元素指针的作用很相似,都可以通过下标去访问元素, 但调用sizeof()函数的结果不一样。元素指针的sizeof()返回值是4(32-bit应用)或者8(64-bit应用), 数组的sizeof()返回值是数组实际占用的空间。数组可以当作指向第一个元素地址的T* const来用其实就是我们常说的数组到指针的隐式转换。当数组作为函数参数传递后,会自动退化成T* const, 在被调用的函数内部调用sizeof()的返回值跟T* const指针大小一样。 数组传递作为函数参数后, 在被调用函数的内部与T* const是没有任何区别,只有在数组定义的可见范围内sizeof()才有获取数组占用空间大小的效果。! C0 P. z5 G* g% x
    4 j+ M$ X% F9 f- ~0 b2 i
    以下3个函数翻译成汇编以后,汇编代码是一样的。
    1 q/ c- Y) l$ r) f+ Q: l# a
      ~5 |6 \( @9 \+ z6 Gvoid Func1(int* p_ary)
    " H- L9 F! W6 G" m1 e/ A% [{
    + L& s( E$ M; h8 t' z! q% ]7 t, h5 M    assert(sizeof(p_ary) == 4); // 32-bit
    ' I" v. V* l' y: D3 F    p_ary[1] = 1;% A9 f# {* a' \) f3 f0 W' \5 l
    }
    ; O. r$ B6 a9 O1 ?* S4 k* ?6 H3 T$ T  W1 N8 F; t2 H
    void Func2(int ary[])
    & y- j: D. }) b. Q* s9 v# O{
    9 m7 ?1 r8 U! Q  q* _. o: J% b# s    assert(sizeof(ary) == 4); // 32-bit
      s% ?4 b1 a/ D5 G5 E* j6 i. d  H    ary[1] = 1;. x% N  D/ E" ?3 H, U/ x: R! s* ]( G
    }9 S3 F8 v, }* t0 B. e% \- \& I
    , M0 z) O* c5 s7 Y$ J
    void Func3(int ary[10])7 D% z! T: E3 ~7 Y& T
    {
    + G6 F0 E/ D8 S7 G: |    assert(sizeof(ary) == 4); // 32-bit& B+ P) ^: ?+ {7 W4 {- R
        ary[1] = 1;
    - I3 b  R6 M- m; t5 K- b}
    1 @/ E( a: j% Q2 z  Z; d- R- N' v/ j7 P( U( x) C4 I
    int main(int argc, char** argv)6 F9 A: d; }$ T4 b0 l: W2 l
    {
    ' @5 r9 ]! r) D+ q) I  l! F, M0 F) E- c    int a[10];
    , }  S8 R2 w! N    int b[20];6 H% }  m$ C9 H6 Y0 [! c7 a
        Func1(a);; I; P* X& p. L! b: J+ ]# n1 B/ c( [
        Func2(a);& V* D4 f( H5 d: E
        Func3(a);
    3 G$ }. U0 \7 {$ w4 T    Func3(b); // 退化成int* const了, 即使数组长度不匹配也不会报错。
    8 H1 O* v  X6 v" [+ m    return 0;
    2 [2 ]( B- u* p* r* i# c) g, V/ p}$ Q8 W6 M3 a) M% G

    6 I( @# e1 p) n# O  g9 T0 s, q# V4 t" W; w- v8 M' \+ \) J

    & K' n0 O& J/ W( z多维数组
    ' W4 l2 G$ g: O$ q一个3行,4列的数组, 结构如下:) w  j: v; D/ E
    ' `5 k. Z% c- T) u" B
    int a[3][4];
    / e8 T% c2 U; r: Z  ?+ o0 @+ \( ecolumn 0        column 1        column 2        column 3
    , {( k* O( U* D9 S, N7 ^row 0        a[0][0]        a[0][1]        a[0][2]        a[0][3]- h7 b8 ~( w, X" g0 N
    row 1        a[1][0]        a[1][1]        a[1][2]        a[1][3]6 T' L0 {# T2 H
    row 2        a[2][0]        a[2][1]        a[2][2]        a[2][3]7 ]  b4 k* N8 d9 _0 H
    数组初始化
      v( ]+ g7 [' A3 H: u+ R* F4 U4 m7 ~, N- f  m  [
    int a[3][4] = {
    4 Q/ h3 q: c5 I- N6 b    {0, 1, 2, 3},5 s1 t7 @. `+ a" _. x, t: U- X5 w$ s
        {4, 5, 6, 7},
    # [5 ~" f( P+ H4 [8 ~    {8, 9, 10, 11}
    & c3 c9 t9 G2 K7 C* @$ k0 h};
    + Q4 O/ V5 S' o4 D! u 实际上多维数组和1维数组在开启速度优化后,翻译成汇编代码是一样的。7 s  C6 C) C0 ]( z

    6 o' S1 O& q# K2 h2 Y" Y+ Wvoid Print(int* p_ary);
    2 Z9 p  U: E* u" T0 G( T: O  Q5 }, V0 K. M
    void Test1()7 I( W. B% A8 E0 r  c/ ?7 ?6 l) g- O
    {
    + D/ R5 F6 G$ ^& L) D4 Y( [! ]    int a[10];* i8 q1 C% V5 L' N" s8 x
        a[3] = 3;
    7 p1 G" {# g& h) S    a[7] = 7;
    9 [1 [! d' a: v7 Y    Print(&a[0]);/ I8 |5 r$ C* \
    }6 o4 i# Q4 {2 s8 r, t

    5 K! o* z8 i+ D- n- Lvoid Test2()
    ) G" c- Y: h% M* ]  L! b{
    3 J* I$ T2 @8 R+ r1 W. O4 ~! G    int a[2][5];
    : I8 ~- z2 ]- t& Q    a[0][3] = 3;( C+ p; K/ n2 a
        a[1][2] = 7;. H3 G/ j& }9 |6 |
        Print(&a[0][0]);
    8 w2 P8 y; ^$ d}
    / v% {9 ?3 B: S8 V2 w5 s3 h+ }' x, w

    3 ^, a; B( [# D3 l$ N# r; R0 s  f
    . c& A( J' S2 I. V4 m8 K很自然地,多维数组也支持用1维数组的方式去初始化。8 D9 y4 n4 ~' m4 c
    8 c6 {2 [3 @' Z! {
    int a[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
    # h$ T2 I1 I0 `既然多维数组与1维数组没什么区别, 为什么还需要多维数组?# V7 F6 S( {# s+ F$ N( C. r) C
    7 z, Z& M! B, D) M6 Z4 J, j7 F
    假设有一幅RGB图像720*576个像素,每个像素有RGB三个通道,每个通道的值是8bit大小。给出图像的首地址p_rgb_image, 我们要取第40行,第50个像素的R,G,B值。代码如下:
    6 |) _9 `, Z. l3 b5 o1 E, g, @5 ]$ o, ?$ {
    unsigned char* p_rgb_image;0 ~! ?7 V$ W, G# K+ @$ |: B
    unsigned char r = p_rgb_image[40*720*3 + 50 + 0];0 r% s$ z4 D7 ]% E
    unsigned char g = p_rgb_image[40*720*3 + 50 + 1];
    & [3 x- q  P5 a5 ounsigned char b = p_rgb_image[40*720*3 + 50 + 2];* f( X4 P8 w* }
    类似这样的场景, 采用多维数组的写法, 有点类似以索引为参数,可读性更高。相当于程序员和编译器打了一个配合。( d6 {8 N$ S7 @4 s

    " m( k  E: t3 H# z9 h  ^; Denum
    0 a# }* r; y" h{% s- ]) h4 F9 q: a- a# h
    Red = 0,1 S% e; v( [8 Y& C) `
    Green = 1,4 c* B6 {+ {% |  V9 K
    Blue = 2
    " q; k. a4 O- _};
    * K4 v& s: b: W! c6 J3 f0 n$ }unsigned char rgb_image[576][720][3];
    / L1 l( c" M" M4 eint row = 40;) E* G2 T( F# g7 z3 b
    int col = 50;
    : `% T! |5 u  z( X) punsigned char r = rgb_image[row][col][Red];
    # x7 x. b* Z4 c, o8 B# J$ t5 kunsigned char g = rgb_image[row][col][Green];, Q" F2 j5 L& v( p( @8 g
    unsigned char b = rgb_image[row][col][Blue];- j: g- K0 l! C# q) k
    数组指针以及与指针数组的区别# U3 _8 a! u* U7 G2 d9 a; o- q
    数组指针,是一个指针, 指向的对象是数组。 数组指针的赋值,要求数组的长度匹配,否则会报错。当指向1维数组时, 需用用*取得数组对象的引用,再用下标来访问数组元素。
    $ _9 q8 j, J! T1 F; e* F指针数组,是一个数组, 数组保存的元素的类型是指针。4 r, }  f. {  W& P) y
    数组指针的定义: Z$ q4 ]( g# M# {
    数组指针定义先定义一个数组。6 b) e. I% _, d3 p$ r5 a4 c
    int a[10];' [) l3 v. M( I) |" p5 W9 n7 }
    然后对数组里的名称用括号括起来后再在变量名称前面加个*号
    8 m2 z2 X* K/ f) d1 `5 v! cint (*a)[10];: H" Q" ^1 z; \3 J  u+ b9 X
    后面你会发现函数指针定义类似。
    ) {2 C# L3 G' `/ h' B7 ?( V// 各类定义对比
    3 I( N# O+ C& q& xint a, *b, **c, d[10], e[10][20], *f[10], (*g)[10], *(*h)[10];
    : M0 X$ L3 l/ d7 T4 B+ s( ?0 G: n7 l4 x) j* s: G
    int *f[10]; // 指针数组, f是包含10个元素的数组, 数组里每一个元素的类型都是int*
    8 r0 b" y% H1 N7 Xint *(f2[10]); // 指针数组。另外一种定义方式。! D4 V. ~4 ?" ?' `$ c
    int(*f3[10]); // 指针数组。另外一种定义方式。: z( S8 e7 D8 H$ L
    int(f4)[10]; // int数组$ g9 Q7 |/ d( F$ {7 J, ^
    int(*g)[10]; // 数组指针, g是一个指针, 这个指针可以指向类型是int,元素个数是10的数组/ x! ]+ y0 F; D, A9 I
    int* (*h)[10]; // 数组指针, h是一个指针, 这个指针可以指向类型是int*,元素个数是10的指针数组2 L& r; i$ X3 @. e! x5 ^2 W
    # h* u; {8 [- a! y/ p
    int d[10];
    4 E  d; b% Q8 x& |g = &d;4 _8 R. S6 `+ Z( M) j
    : Q2 ~& a( F, Q8 G5 \
    int* e[10];
      v! ]5 I: S: K" W) U3 e, lh = &e;0 Z# R8 I/ {+ }( `9 s
    数组指针的使用
    0 C& D0 b+ V" i* N. b  ?数组指针一般先通过*号取得指针指向的数组对象, 然后再用下标操作访问元素。6 d, `3 C3 V* i/ M: L% d! w

    & b$ t9 g3 E" Jint a[10];
    ! I& X$ [& j. r. R2 N8 ^int(*p_ary)[10] = &a; // p_ary是一个指针, 指向"int (*)[10]"类型的数组$ i. G$ a  W& B7 V  x$ @( q) V
    for (int i = 0; i < 10; i++) {" ^0 h% u. }3 F9 y, i* u
    // p_ary是一个指向数组的指针, 需要先通过间接寻址运算符*(indirection operator)取得数组对象) ]  R  y1 O3 E2 R3 |4 z
    // 再通过下标操作访问元素。* F# x- S9 J; J4 n$ O6 X2 I
    (*p_ary) = i; ! m- J) R, J9 X7 A7 p0 X' S
    }
    2 \" f. t$ ?1 v  D
    6 W' {$ a$ S* v; d1 uint b[10];
    5 L! ~4 C2 m. x9 K4 s$ X/ \int c[20];
    & B# q: G: t- D: u! A8 K8 ep_ary = &b; // 合法! l. G+ F5 l9 Q+ G
    p_ary = &c; // 报错! 不能将 "int (*)[20]" 类型的值分配到 "int (*)[10]" 类型的实体
    ( U1 z, u% u/ o- x数组指针指向多维数组的子数组% T+ N2 s6 x* k) A' M
    int a[10];" ?" Y3 i6 c+ u# K- M
    int b[4][10];9 J; n) z, h9 m
    int(*p_ary)[10] = &a;7 }, P: W6 K- n$ F6 P
    for (int i = 0; i < 10; i++) {
    , M3 C# d& Z6 U (*p_ary) = 1;
    4 ^! }4 H) x" `/ |& k6 c9 Z6 c}
    " Y6 ], ^& b5 Y9 t: }1 e7 X5 q; S4 R, A8 h& `5 M8 N" y
    p_ary = &b[2]; // 多维数组,可以看作数组的数组,4 i2 R5 Q4 n2 p. |' \, A
    // b[2][0] ~ b[2][9]的值都被改成2了
      x) e7 |' m+ }9 L# Zfor (int i = 0; i < 10; i++) {
    3 J; Y) K* y' A- s: J2 ~ (*p_ary) = 2;
    + L6 \; E$ w% F. p2 m# p0 w( |; |}7 z% F& H3 y$ B9 x7 b# P. M
    多维数组指针' q  s2 X9 C$ Q/ W9 g1 h
    多维数组指针,是一种指针,指向的对象是个多维数组,支持多个下标操作。0 d$ h! X/ T! j# \- K
      o' ]/ s5 t$ V; E8 J
    int a[2][5][10];* w2 \7 }+ K' D* ]% I
    int(*p_ary1)[10] = &a[1][2]; // 1维数组指针
    6 S8 C0 P8 r$ g! ]5 ]+ vint(*p_ary2)[5][10] = &a[1]; // 2维数组指针% x* j6 P( P9 x# J7 `
    for (int row = 0; row < 5; row++) {% f" T0 P% g  H+ q" X
        for (int col = 0; col < 10; col++) {
    ! s* p" Q7 P+ ^+ Z- F; E        (*p_ary2)[row][col] = row * col;
    ! [0 i  ^" h: Z: c; X# j# ], ]) z& R/ J    }
    . D$ D& D( m5 o. e, ?3 ]4 T4 g}0 R3 ~9 V! ^' s+ J
    数组指针和指针数组对比实例
    % L0 N* d7 g  r1 E" Q2 J( V数组指针还是记住两步法即可
    3 N% c! K- V: N. a( ?, b) U6 b- }- O4 \, S
    定义一个数组
    , d5 v' d/ ^9 b, _' k括号包围1中定义的名称,再在名称前加个*号。
    ! q$ }9 u$ i, T1 K9 jint a[10];
    " ^% I% B& N" R+ hint(*ary_pointer1)[10] = &a; // 数组指针
    & ?/ r# T* T% S+ c2 u6 p  lint* pointer_ary1[10]; // 指针数组。元素类型是int*8 M! n; O2 G9 r1 ?# K
    int *(ponter_ary2[10]); // 指针数组。另外一种定义方式。& i; M8 K# S1 @3 h* x
    int (*ponter_ary3[10]); // 指针数组。另外一种定义方式。
    3 Y" T' X( Q1 gint c, *d, (*ary_pointer2)[10], *pointer_ary3[10]; // 排列定义比较。0 j* a" p: p2 g

    ; W; b  S+ O. A6 |3 A// 指针数组可以把每个元素指向数组对应位置的地址。
      U7 Y6 z' D% E( K5 j) t: m// 这样遍历指针数组, 可以达到遍历数组元素的效果,但是注意每个元素都是指针,
    0 F( U$ q  ]; t/ j1 i" S$ w// 需要访问原数组的值的话, 需要对指针用*间接寻址运算符。' {( i6 P( j2 ]  k2 `5 x, h% `
    int* pointer_ary[10]; 2 e$ Z3 A9 M/ F
    for (int i = 0; i < 10; i++) {  k, n2 F" h: K$ [
        pointer_ary = &a;% p: z2 ^# y- w$ H$ U$ T/ C
    }# _7 q7 @7 _7 K6 y' W
    // 类似遍历原数组效果。7 e6 ^8 V% q5 }+ i( V
    for (int i = 0; i < 10; i++) {" s( B" y5 c: ?/ b) L- }$ v
        *pointer_ary = i; // 修改原数组。
    : a6 `8 B4 F% [3 R! A9 |}
    / h7 I( u- h9 S
    ' p! o+ ]% y# p函数指针/ O% g5 k& B4 r* m9 C
    取得函数地址
    / [( g/ N: o  M- r' e# A函数的名称作为参数被传递时,会隐式转换成函数指针, 和在函数名称前加取地址符&等价。建议带上更加统一和清晰。' w8 [% s+ J3 D  L5 `* o/ T. j) L

    & Z% j& u3 B" E8 Z+ pvoid f(int);
    ( @) X- x* f5 \1 Uint main()
    ; o4 E' J5 P! W( w. {7 p9 _7 J{- W( D; v- m+ A6 J9 W# r5 _+ b/ I0 h
        void (*p1)(int) = &f;% ^- k0 `, |; o5 z
        void (*p2)(int) = f; // same as &f
    " a2 G0 Y6 z* a    return 0;' D! v) T* \( R3 R5 X
    }
    9 K1 h6 X: P( m0 ]7 f# \翻译成汇编代码, p1和p2的赋值是一样的。& W3 g  m* l6 a9 \
    3 s$ U1 d" Q3 n1 |8 `$ z- h

    - V; X& ^+ a- l$ i! v* p7 [; d8 }
    函数指针的声明5 D8 K/ X) s/ [1 b
    单个函数指针变量定义步骤
      I8 ^$ b  l* Q定义一个函数。void fun1(int a, int b); int fun2(double a);% q( V' `6 A8 _
    用括号把函数名称包围起来,然后在名称前面加*号。void (*fun1)(int a, int b); int (*fun2)(double a);
    0 }# @, V8 e4 M3 J- p如果要定义函数指针数组,在定义单个函数指针的基础上,在名称后面加上[数组长度]void (*fun1[2])(int a, int b); int (*fun2[10])(double a);
    " J' b% d4 Q0 J% qtypedef定义函数指针
    ! p8 E+ e0 O  z' W- `& C可读性高比单个定义要高,特别是声明多个同类型的函数指针,或者函数指针数组。' [' ~3 n; d& B) O' g' N: c

    , d7 E  T0 S4 Z. w. J) U. h7 ytypedef定义函数指针的语法
    $ V& M) l- R. ?! a4 C. H, _  wtypedef有两种做法, 一种就是定义一种函数对象,另外一种就是定义函数指针。用法稍稍不同,效果是一样。其中函数对象不支持赋值, 但是支持引用。+ c% _3 T/ o8 w' L
      _" U* L2 }8 }, @
    typedef int FuncObject(int a, int b); // FuncObject类型是函数对象
    1 H* j6 W, y" e3 X; c. b9 ntypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针, c* F" F, V, L, T
    FuncObject* f1 = &Add;. I' e& N1 v2 }. L8 O+ z1 {
    FuncPointer f2;
    , U) s" t+ t& y8 o1 W$ v; p0 E' Uf2 = f1; // f1, f2类型一样, 都是形式为int(int, int)的函数的指针。
    ( @8 ^+ K8 N5 OFuncObject f3 = Add; // 报错! 函数对象不支持拷贝
    ) g( {3 @& N5 cFuncObject f4 = &Add; // 报错!&Add是函数指针,与函数对象类型不匹配* b6 N6 y0 ?* H% T( M& i: Z/ I) r
    FuncObject& f5 = Add; // 正确
    5 {: P) p  e1 I, n  Uint ret = f5(2, 3); // 正确
      C4 [8 X/ x- v: J1 t0 [5 [2 NFuncObject& f6 = &Add; // 报错!&Add是函数指针,与函数对象引用类型不匹配6 p7 k" Q- \2 [3 h" X# c
    如何记住typedef定义函数指针的步骤
    ; ~+ I+ t+ @0 x  Z1 j) v& H, F5 f像定义一个函数指针那样, 指定一个名称。int (*CalFun)(int a, int b);! ^1 {$ `3 }# ?0 @+ h. ^1 [/ D
    在这个函数指针变量声明前面加上typedef。typedef int (*CalFun)(int a, int b);# F6 b( C' K. W: v) _
    完整例子% m% O' x7 K. e) j' V
    typedef int(*CalFun)(int a, int b);
    % o/ M2 m; _" {/ [0 s9 H' W# e2 j0 s, ~- l9 }- p9 ?) e
    int Add(int a, int b)
    4 a2 n- k) V- }; q; w{9 v1 T$ O; E: T8 u; n/ X2 V
        return (a + b);
    1 x; V0 }7 ?( j+ Z}
    % H! N5 p7 O5 s; Y8 N! e+ T* \: H' @; R4 q, R5 R1 e
    int Sub(int a, int b)# D0 Z) [" h: v% [. P2 }
    {
    ! b) `0 w7 x9 ^( [' S) _! W; z, W    return (a - b);
    ; h8 H0 d5 `$ W7 W) r4 M) m3 k}% ~! x* Z: c% g" a  N* w

    9 b( o; ~6 a  Q# Cint main(int argc, char** argv)7 t6 K7 d" j. F$ ^: n' y8 w
    {
    ) L/ {- r2 F4 Z0 p9 }    CalFun f1 = Add;
    $ p2 L8 q( \+ s0 s, f/ Z. E    CalFun f2 = Sub;
    7 c- r3 t$ Y* S7 f    int a = f1(2, 3);
    . ]6 d0 e  Q1 M* w    int b = f2(10, 5);  O3 l% k& m1 K3 q. U, @( L

    ' \. ]6 P( c9 h9 L6 p    // typedef定义的函数指针数组。1 Z; ]6 Z: S( b- z/ D* ]
        CalFun f_ary[2];. {, x6 k) E; c# P1 Y, J
        f_ary[0] = Add;
    6 h& s2 z" B8 M! H    f_ary[1] = Sub;
    " G' F+ ]: o% i* _! D- Y
    . I. M. z1 Z9 n9 j5 T9 o    // 单个定义的函数指针数组。) l' Q, d$ x* h6 v! x8 _
        int(*f_ary2[2])(int a, int b);9 H, h- V8 b* L" A
        f_ary2[0] = Add;/ ~6 Y# I# F/ O/ p) y3 Z& v8 h; M
        f_ary2[1] = Sub;
    9 `+ N7 v' Y2 C+ v% C& Q7 E. e% j; n- T
        return 0;
    1 x3 @' O8 Q" n- K}+ a" x$ p% l) u# o1 z
    / A/ M$ {/ }7 n) V( M' F& \, r0 C
    using别名定义函数指针: V" n: i0 L- ]. w' D
    c++11以后的类型别名定义--using也可以用于定义函数指针, typedef的好处它都有,个人感觉比typedef更直观。using类型别名同样分函数对象和函数指针两种方式。) C9 w3 G5 {  u$ y# G  n9 O4 H& N& ^
      ~* Y" O6 d4 E  Q; a
    typedef int FuncObject(int a, int b); // FuncObject类型是函数对象
    1 s: N3 @# z4 \7 ]3 nusing FuncObject = int(int a, int b);( T: E. ]5 ^# r+ W7 ]" D
    typedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针
    / m" {6 F0 n& P/ S5 F- busing FuncPointer = int(*)(int a, int b);" X& p1 P6 \1 ~" E- B, N+ K4 D
    函数指针的调用
    2 v$ U* |$ j0 B1 }函数指针和函数对象都可以直接后加括号调用  L+ x$ C& l. Z  ~8 l; p# C; {4 W
    int f();
    : \+ V/ Y' X+ U  yint (*p)() = f;  // pointer p is pointing to f; v8 X4 k. ^. i3 K
    int (&r)() = *p; // the lvalue that identifies f is bound to a reference
    $ @' Z1 m: c. Q" K/ or();             // function f invoked through lvalue reference0 j- G& J/ L$ k
    (*p)();          // function f invoked through the function lvalue
    + O! h$ w7 d, Rp();             // function f invoked directly through the pointer9 J2 N' R4 O2 a, n! B; }
    如果函数有重载, 函数指针会指向匹配的那个版本。9 o9 Q6 f1 r' Z& f8 ]+ M4 L
    template<typename T>" o" |* U. M# l6 k
    T f(T n) { return n; }/ z- \4 ~: V0 C9 F

    0 `5 C! `' |) o* g0 `double f(double n) { return n; }
    : {, j  m: l* n0 b' j9 J! q& l0 W
    # }( _$ M1 ^/ H& |* nint main()7 |3 N" g' A- t! B/ c3 `) K
    {( l+ X( j, F3 B
        int (*p)(int) = f; // instantiates and selects f<int>  V; L. z7 l% Y1 y* }' W
    }0 b5 F( l; I) M( b
    成员函数指针
    3 R9 a( c, |; n- ?$ V静态成员函数,除了增加了访问控制以外,跟普通的函数指针没什么区别,所以普通函数指针可以直接指向类的静态成员函数。但非静态的成员函数与普通函数指针不太一样,声明时需要指定函数归属的类名,并且调用需要指定对象实例。% F( v) A9 w- T1 ]4 d

    8 h$ d2 D4 f/ K' }5 ?成员函数指针定义。
    ; J: [/ @9 A3 H4 y' b, z像定义类成员函数实现那样写, 并任意指定名称,这里作func。void ClassName::func(int);, J; b- b; T- p% ?, }0 a
    括号把类名、范围解析运算符::、名称包围起来。void (ClassName::func)(int);, S4 c- `- S# P( D" Y- V8 i
    在名称的前面加个*号void (ClassName::*func)(int);
    1 W+ S+ m: `) Z8 q, v成员函数也支持typedef和using的定义方式。typedef void(C::* MemberFunc)(int); using MemberFunc = void(C::*)(int);1 y, {8 x1 t' s6 n, v
    成员函数指针如何调用。
    3 ~0 p2 j+ U& C  ?9 g, X/ t8 l假设成员函数指针名字为func1 F6 x4 Z0 o/ o
    2 G8 d$ |( A3 a) V* r
    void (ClassName::*func)(int);7 b/ y7 E3 D$ c* s7 E
    对象式调用。
    ) m8 y. `7 B, x: A' M: uClassName c; // 被调用的对象
    % l$ I& U( s9 L6 w/ D, l成员函数指针名字当作正常函数那样写。
    % \$ ]: y$ M9 y. yc.func(3);
    8 O0 e5 N4 N$ s" G( e成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。
    6 O, U! C4 R* oc.*func(3);
    6 V- a7 c4 X) p3 `6 K. @4 q" ]最后用括号把调用对象、成员访问运算符.、间接寻址运算符*、和成员函数指针的名称包围起来。
    6 ^! t+ I; Q9 q) ]8 j7 @(c.*func)(3);/ Y: N) s/ S" s+ O* x2 A
    为何要加上括号? 根据c++的优先级标准,取成员运算符. > 函数调用() > 间接引用符*。 *号优先级比函数调用要低, 成员函数指针还没取得对象就被调用了,自然报错。 另外
    ; _7 i) x- S- O: C# q/ x(c.(*func))(3);
    0 f4 m7 E$ F3 a这样的写法也不行。 .*和->*是整体作为一个运算符的,中间不能用括号隔开。
    $ O; @0 W" k* K1 u. @5 G指针式调用1 C$ `: i  |$ D' ]5 ^( r
    ClassName* p; // 被调用的对象的指针
    4 c% r" x: J0 Z# Q8 e  A成员函数指针名字当作正常函数那样写。. B  Q* A4 I4 ]3 U$ Y; Q
    p->func(3);
    6 Z2 @' @/ G- ~2 b成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。
    ; S  q) G6 H2 Z6 N# U* n2 Ap->*func(3);
    ' o2 l7 I% s! S# b+ ]最后用括号把调用对象、成员访问运算符->、间接寻址运算符*、和成员函数指针的名称包围起来。
    $ V0 Y* L5 u4 z. r(p->*func)(3);9 E/ c# t& _% O" f# N5 V- Q# a2 }
    函数指针使用完整例子
    0 ~" ~5 I% R$ R0 M5 i5 t5 |/ y# tstruct Cal
    & [6 Y( P( P7 k{
    $ k7 _7 X1 ]2 X- Q    int add(int a, int b); $ `0 F7 k1 x$ I4 S
        int sub(int a, int b);
    9 n- Y2 Z# g# B5 @3 U- h. E};( D& ^4 l) c# J6 N

    1 `# N  x6 n) A2 v4 e( J+ o& Lint main()* \8 P) }) v% h4 f2 }5 g" s
    {3 M3 J! q4 P" j4 `3 f4 J
        int (Cal::*fun)(int, int) = &Cal::add;
    2 x* |& K% n. ^3 w7 E2 q3 k    fun = &Cal::sub;
    8 p7 m2 n0 w' Q
    . Z! i/ ~+ W6 }* _+ a0 L    Cal* p_cal = new Cal();
    . k* g2 U) q' Y3 k* b    int r1 = (p_cal->*fun)(2, 3);0 [0 |' ^: Q9 P: B3 t7 s
        delete p_cal;
    - z" k* g9 M5 `( k; u) `1 D3 Y% E: T. K
    8 a' L6 k+ ]9 {4 ^  x0 N( T    Cal local_cal;
    $ i# p* T' K! Y    int r2 = (local_cal.*fun)(8, 6);5 W9 I( ^+ F) h- j% K7 {% E
    }
    3 _) G+ z/ ~, ?. C# M' n
    % d# \  h3 s4 |4 S. z* s$ }成员变量指针
    $ U9 N. U) B$ k+ e成员变量指针比成员函数指针还要简单些,没有函数调用, 无需考虑函数调用和间接引用符*的优先级问题。
    3 `' S( _+ o' n3 i: C. ~2 `3 z! c
    ' c. I5 \; W( R) T3 d$ {成员变量指针的定义
    - a) {- |5 d6 }假如以下结构体C。3 R4 y8 y6 ]7 p: U+ j6 r+ E

    0 f1 s% P% I: Q; nstruct C
    ; y) y: H8 g7 E& F  m. y{ * q; K. k: g; f; L, |
        int m;
    ' @3 `6 T3 n! i$ i! c};
    * C, Z/ ]6 N5 R, Q: `8 i单个成员变量指针定义 $ F$ ?2 E! L6 {/ s' T
    假设名称为p, 类似静态成员变量定义那样声明* f" q5 r# z* g
    int C::p;
    % f$ l# t; b, S% t4 _' N在名称前面加上指针标识号*
    + ?! d$ y/ g5 P" }, o) n9 ?int C::*p;
    # R: ?4 D& e, d9 [6 X7 L1 L& mtypedef或using方式定义
    : m9 ^1 J7 x9 ]( s/ u* {7 Utypedef int C::*MemberPointer;
    2 t+ a- M. n' O# q. x" L; I$ H1 Iusing MemberPointer = int C::*;9 M; |6 _) H, o0 M* V9 o# o
    成员变量指针的使用。
    5 O9 l+ V) n0 l- ?& }3 Z# J! o类似成员函数指针那样,直接使用指向成员的指针运算符:.* 和->*即可。
    9 z1 o1 i5 {+ Z# u成员变量指针, 能让我们实现一些遍历成员的动态功能。 例如把一个类/结构体的多个同类型的成员变量放进一个容器里,然后遍历访问这些成员变量。1 t' |; P) S- G
    ' @, l0 c+ D2 x; w5 L
    完整例子
    : P, z( }/ u. k$ C. a) mstruct C { int m; };
    " L+ X4 U# C- Z- J# lint main()
    " K. \  j( a) d* `! w{" D+ F$ q; r( n) K: C, u% ~
        int C::* p = &C::m;          // pointer to data member m of class C
    ; c9 ~. h; i  s' z9 S, i# k    C c = {7};' }  n. C; O9 R( _
        std::cout << c.*p << '\n';   // prints 7# Z& a# }$ P0 ~* [$ }# V
        C* cp = &c;# ]7 p$ T# u& J5 T6 T- J7 a
        cp->m = 10;$ b1 W8 \: m& B5 J" ~7 B  J, X
        std::cout << cp->*p << '\n'; // prints 10
    & I$ Y3 p% A5 X- p6 G7 q0 q! p* |6 v  W% n; ~( [; p" G
    ————————————————6 ^+ J+ V' H9 h3 Z( {' q2 S
    版权声明:本文为CSDN博主「南风fahaxiki」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。2 `, Q+ C. y' [& H+ d
    原文链接:https://blog.csdn.net/m0_64407685/article/details/126788115
    ) Q% t# N9 h6 t% S; H7 ^! s7 i- k* R/ ^/ c' @+ s

    ( m5 O( @* n/ `' r
    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-10 13:16 , Processed in 0.363620 second(s), 51 queries .

    回顶部