QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2488|回复: 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++数组指针、函数指针、成员函数指针; `+ n. W$ n- S4 S& |, ]5 i
    C++数组指针、函数指针、成员函数指针
    , [3 P2 E* F8 _& b9 \5 X' u' Y3 p* F
    . R/ W( L6 A7 V' }- j; c
    操作符名称7 `. B- I: F% E0 {0 X5 a- j  v4 q
    & 取地址符(Address-Of operator)
    " p( _# q* U+ w, G& T0 y4 `0 X* 间接寻址运算符(Indirection operator)" u/ e: L" b' x8 b: |) e
    .和-> 成员访问运算符(Member-Access operators),用于取对象的成员。
    ! q$ L) e! u! {4 {* ^/ Z9 V.*和->* 指向成员的指针运算符(Pointer-To-Member operators), 用于成员函数指针和成员变量指针的取对象。
    : {# V6 ?+ i5 n& C) Z() 函数调用运算符(Function-Call operator)( P2 s; i5 d! P4 Z
    :: 范围解析运算符(Scope-Resolution operator)
      H& T/ ]$ {7 b$ T. [9 [如何定义一个指针变量- y' q% i  h+ L! [5 z
    假设类型T, 变量名称name, 指针的定义如下:" U2 v! t$ h( k4 P" @0 W" W4 J9 T

    9 I2 L8 Y0 G4 ^; Y4 }6 j  LT* name;% f% f: }- ^# ?) `" g+ ?" }" J
    标识变量名字name, 它是T类型的指针。例如
    8 W6 ~: I7 B$ g8 i, O# x; y+ T5 P9 I8 c8 g" [
    int n = 0;3 a) Q) q' h2 M' }1 y
    int* p_n = &n;
    9 \! H$ C' Y" xp_n是int指针类型, 指向某个int型的对象。  ^0 M% R3 k0 I. z, R. P" A( Z

    9 ^6 ~1 a( O8 _' n# H7 Q. b指针变量的修饰" ]$ @. g- \3 A
    指针实际上也是一种变量类型, 只是它保存的内容有些特别, 是指定类型的地址值,通过间接寻址运算符(indirection operator)*, 可以访问到指针指向地址上的指定类型。
    . O; W3 Y3 q8 r) ^% Q+ {8 N
    2 _1 @7 h1 J: t- e/ P指针也可以用const, volatile修饰。 const int或int const均表示一个变量类型是int, 且该变量不能修改。以下两种写法都可以:
    , s. W3 B$ d" Q7 t/ F$ a
    ) S. H" x1 W+ ~! G" W0 s8 V& g; yconst int a = 1;! x+ w" T9 d1 T& Q! s( B
    int const b = 2;: N( t7 E) E0 \: Y
    既然指针是也一种变量类型,同样支持被const修饰, 表示指针的值/指针的指向不允许修改, 指针所指向的那个变量是否允许修改, 那是另外修饰。写法如下:; S7 j5 t  V8 Q! c8 E
    $ c3 V2 C( B% j8 ^: _7 P
    int a = 1;
    ( I* h% ^* Q. `( C1 ]  @* Wint b = 2;. X7 A- x* }4 A
    int* const cp_a = &a; // 指针的修饰词,放在*号后面。# h& |( F2 a4 J  {
    *cp_a = 10; // 指针指向的值可以修改
    ( U1 Z) J1 Y- F0 B' W& Zcp_a = &b; // 指针不能被修改,报错!/ z% b  A0 p2 t8 B; h  B! ~
    总结带修饰的指针的格式:) B5 ]$ N! T* x) C& R% }6 M8 W
    只要记住修饰词总是放在被修饰的内容后面。
    * `$ q" v% T% Q" H. [, V- [) N: w
    cv表示const / volatile修饰词。指针定义形式如下:3 w" e  V9 g- w7 V" K% D+ e

    1 G: _" }* c1 oT [cv for T] * [cv for pointer] name# G- O* J5 R, H* D' f, M% Z" O
    注意对T的修饰放在T的前面也是合法的写法。* p0 n2 Y; P. C% P1 Z+ h+ n- l
      R5 w, f* V- a, D  Z+ E4 ^
    const int const c = 2;
    - c' S1 j  x6 d8 G+ B在mscv编译器下也不会报错。
    5 h' X6 s2 m1 e1 F8 A* j) [! \" V4 G$ L! U9 C
    完整的格式:
    6 H- F. K6 n4 w* j8 ~1 a, t2 f[cv for T] T [cv for T] * [cv for pointer] name- b. J) a8 _: a  I9 J# W( U
    ' ?, i; m* a' ]- U3 I* b$ T8 N
    Syntax        meaning6 J% b: }& v' F' L3 C- f
    const T*       
    # Z7 z' h3 _0 \% zpointer to constant object5 n( J8 |! l) D. l
    7 v, u! @0 N. H4 S2 ?! A
    T const*        pointer to constant object
    ' i. E4 Y4 q+ E  e' ?4 ?6 _T* const        constant pointer to object
    + C' e+ A6 j6 bconst T* const        constant pointer to constant object
    ' [6 K  V6 ~0 ~2 I' sT const* const        constant pointer to constant object* I$ ~6 v+ A1 x4 f9 V9 |1 d
    上面格式中T还可以是一种指针, 指针的指针仍然是按照修饰词总是修饰前面的标识(T或者*)来确定修饰的意图。
    & i3 K- K8 J  T* g
    3 B/ a3 B8 N) j1 f! g: }- pint a = 1;4 E" S( e6 w& ]1 C4 {* v, m
    int b = 2;
    0 m7 I% ^8 c" [6 o; {. ?
    & x+ R! Z: F' Cint* p_a = &a;
    + [; S! C/ R7 O*p_a = 10; // 合法
    , u2 N$ D; h5 Kp_a = &b; //合法7 L% V1 G  T, X+ G8 C& f

    4 l$ b( [' v! K+ J; W9 E' aconst int* cp_a = &a; // const修饰int类型, 并非修饰指针8 ~! V% H, h' g( Q' @% h8 S5 V% _
    *cp_a = 11; //报错! const int类型不能修改+ ~. u2 _7 ]3 @" h
    cp_a = &b; // 合法, 指针没有const修饰,指针可以修改。
    ' p& y3 ?5 M' [6 x3 b7 P0 P( `; u5 A7 R
    int* const pc_a = &a; // const修饰指针。类型没有const修饰
    2 f* s3 k/ T; r. C: w2 L*pc_a = 12; // 合法, 因为类型没有const修饰,可以修改。
      N( X% o0 u# V# ?3 W/ qpc_a = &b; //报错! 指针被const修饰, 不能修改指针。
    4 S! [$ d* N% ]' i  C- O* w+ f$ t* ~& W  f/ k
    int const* const cpc_a = &a; // int类型被它后面的const修饰, 指针符号*后面也有const修饰
    ( u: A! ^% S8 S- b*cpc_a = 13; // 报错! 类型被const修饰,不能修改。
    8 `( g0 [" g1 X* v. Rcpc_a = &b; // 报错! 指针被const修饰,不能修改。
    8 d& q6 ~1 A# x3 ?! O+ g. X; Y: V) `$ _8 h* G8 H
    更复杂的指针的指针
    + e. _/ L& @: Q: i/ F- \) [- L9 |6 {2 [' b9 K' Z6 o# B* w
    int a = 1;
    3 s" B9 i4 _2 _1 p' pint b = 2;
    % q5 v+ [) Y9 }' D% o1 X- ^int* p1 = &a;- ]8 X2 }" O& \* w' {& A5 r; X
    int* p2 = &b;. O$ F0 v( q; t0 A. }
    const int* ct_p1 = &a; // ct for const type
    ! l/ d4 w" ?7 Wconst int* ct_p2 = &b; // ct for const type5 k8 ~1 I" m8 L8 E

    # ?6 j# l) \. l2 K// int * * pp1; 指向(int*)类型的指针
    # T' z6 F' k$ `' l8 {1 dint** pp1 = &p1;  0 n5 t) T, M8 s% Q  ^
    pp1 = &p2; // 合法, / A7 f; l) ^( G2 U# `  s% z! v
    pp1 = &ct_p1; // 报错! 类型不匹配。 (int*)不能指向(const int*)
    4 g9 H3 J; W2 w  d; ~8 g8 d- q% U: B8 A
    // (const int) * * pp1; 指向((const int) *)类型的指针
    % F/ d+ h: D/ Mconst int** ct_pp1 = &ct_p1;  % j- R: \' t. t
    ct_pp1 = &ct_p2; // 合法1 }- g0 B! s9 r$ o# P; y1 [/ r
    ct_pp1 = &p1; // 合法!(const int*) 可以指向(int*)类型。
    ; ]! e& S  V  v! z; `( R0 w2 I, ]; z- M: I* |9 I# s. R
    // (const int) (*const)
    - N- Z& C. J% ?* t2 kconst int * const ct_cp1 = &a; // 指针也不能修改
    0 p; o# j( \( G, [" h, kconst int * const ct_cp2 = &b; // 指针也不能修改
    / _1 U7 Q4 w( i! m6 k8 j: u- Oct_cp1 = &b; // 报错!指针有const修饰
    * n' h2 a" l/ _& L, W6 |, i0 n2 \, e# ], L
    // (const int) (* const) *  指向((const int) (*const))的指针' u) U, h0 n2 d) W( x5 S' l9 \
    const int* const * ct_cp_p1 = &ct_p1;  # ?# _1 Y. l6 I( ^1 g/ M+ A
    ct_cp_p1 = &ct_cp2; // 合法, 指针的指针并没有const修饰, 指向的指针有const修饰
    % g% |; p3 J( ~* B*ct_cp_p1 = &a; // 报错!等价于操作ct_cp2,  指向的指针是带const修饰的不能修改
    4 N! e: n( c* c& u# s$ D# U- P
    $ c$ m& u3 @/ P// (const int) (* const) (*const)  
    8 r& i$ [1 I, X' L/ g// 指向((const int) (*const))的指针,且该指针被const修饰
    / H( g9 ~$ b+ h* y3 w& c# e2 uconst int* const * const ct_cp_cp1 = &ct_cp1; & ]- l6 V4 ~2 Z$ M7 m3 n/ r
    ct_cp_cp1 = &ct_cp2; // 报错! 指针的指针被const修饰, 不能修改指针指向。
    7 B3 |1 b" {) ~! Y
    * Z0 W! s% p/ h" \) \+ P2 y一行声明多个变量
    9 q7 k) B' v/ V; ^, f) y  k5 n' p& Y类型 + 名称定义一个变量。( h% e! J. y6 z  p& _& d& P
    变量的前面可以加*号修饰, 表示指针, 一个星号代表一层间接。**表示指针的指针。
    6 w) V5 X3 G0 W1 Z- m0 x( |0 }, \+ v& R
    int a, *b, *c, d, **e;
    3 F' U2 k) s! g2 U& @a = 0;
    6 a& h. Z) b8 }% B; |% s( C9 Dd = 1;5 j( }% g" h4 a/ O; U0 \9 C  o
    b = &a;
    9 d4 n( r7 K* D+ ?4 G/ o; s& Sc = &d;
    1 k# a" S+ R: `1 f# J1 K- R: K4 we = &b; // e为int**类型 指针的指针! V/ Y2 J1 k9 S$ y5 _0 ~
    e = &c; // e为int**类型 指针的指针
    2 y4 O! p5 x, L! r2 Q! s! B$ ^也可以用括号包围变量和*号。
      ?& ^! c2 c$ ^% L+ Z4 A  a- Z" p) ]& d+ t3 e
    int (a), (*b), (*c), (d), (**e); // 合法定义。/ }/ h- C' `2 H! {& V
    括号可以省略,某些情况, 个人感觉加上括号更清晰一些。例如  R5 y, P9 i% Z6 E2 N
    $ D3 {6 @7 I8 I& E! r
    int (a), (const *b), (*const c) = &a, (const d), (const* const* const e) = &c;! ?4 A- H8 L( m( k
    写成5 W6 }( w; O6 B9 M4 |, h% x; O) Z: Y
    6 k. k( R0 K0 v; k* ^% u
    int a, const *b, *const c = &a, const d, const* const* const e = &c;
    1 A& |! W$ o+ \9 \. A更重要的是, 后面我们表达数组指针,以及函数指针时,括号是不可缺少的, 带括号的表达更加统一。
    & a4 @' p2 m. T. }* i( x0 P% _$ J
    $ T5 E8 l( c, C  `* _& S数组指针
    " N& u$ w# B0 W, |; R, h$ ]) ^3 L- M数组基本表达
    . \1 U  W. T9 `/ A8 ^( ?1 N9 p, eint a[10];  // 定义了类型是int, 元素个数是10的一个数组。) n6 e! A: Y# X2 V. E, d
    由于c++要支持一行定义一个类型的多个变量。 所以数组的[]时放在名称后面的。虽然我觉得
    ) T( }% `" O7 k7 h" r! C* U  _. F; L* E: N3 H
    int[10] a;
    ! Y# Z, ?! l( W3 `5 G3 E这样的写法更符合类型 名称的思维, 但是如果类型都这么写的话, 没法兼容以下的写法:4 G! ?% N( {3 C0 ~" y7 m4 |

    + R! R' Z3 J$ K9 E) r5 Fint a = 0, *b = nullptr, c[20], **d = nullptr;
    # T. |1 p- i) p& C/ k! X" Nc++标准规定如此,但我们可以通过每一行只定义一个变量的写法, 类型会更加清晰。4 A- I. U: Y- z: R$ V0 W" [- D
    & w( M+ F1 g+ j' u
    int a = 0;
    + T  G* u0 N! H3 B, ~int* b = nullptr; // 指针int*
    # V/ F1 o- H0 K, |0 {" Gint c[20];4 I! X+ v: r  s- B5 w. R
    int** d = nullptr; // 指针的指针int**
      m/ w; Y1 \: ]8 d% C* u数组的名称是什么类型. I& o1 H: c4 y2 P
    数组元素类型的指针,可以直接指向数组。 并且数组跟指针一样,可以通过下标去访问元素。8 l- r6 o/ Z- x) S' z3 H% c0 A
    : K0 x- C8 L' H3 Q4 z- n1 ]( v2 [
    int a[10];
    - m: i' T$ K2 A! l% {int* p = a; // 指向a数组的第一个元素2 c: y9 }) c! a( v$ a" U, b5 d3 D
    a[1] = 1;+ ~8 m; t7 p( S( T! j
    p[1] = 1; // 效果与a[1] = 1一样。/ q9 y3 u  C9 o& i9 b/ \
    数组可以当作T* const来使用, 但是又与T* const有些不同。sizeof()的结果不一样。& B/ ~; s& J) E1 j& a# F

    4 y( W" H# U: E, X5 Y: Jint a[10];: \5 t& T; k$ w& \4 \& w& M, a
    int b[10];
    2 }0 }0 ^& y1 y4 U) Dint* const p_a = a;$ w+ N7 y, H& I: x' d' J
    a[0] = 1; // 合法。 数组的元素可以修改。
    ! O7 R) j0 B( s+ @5 _p_a[0] = 1; // 效果与a[0] = 1一样。+ g% H# H' D3 ]- [& @6 B2 e
    7 w  t7 V7 O& F- ]0 S
    a = b; // 报错! 数组本身的指向不能修改。5 v% H) d- E* R3 |! E/ i2 f

    6 v' y6 A. X$ c// 所以数组a可以当作int* const来使用. \; }/ J0 N) j& f* t7 Q, j' L
    int *const& ref1 = a; //正确。7 h+ Z4 d3 b" r3 j
    int *& ref2 = a; // 报错!' c1 f- L" n7 X& v* ~, E: O2 N8 D9 B

    ' i" G1 t  H, z9 k// 但是又跟int* const有些区别。" V( p  S9 I5 I" W% ]* }0 A
    assert(sizeof(p_a) == 4); // 32bit程序。
    & T. j$ q  \. a( E5 }+ x: zassert(sizeof(a) == 4*10); // 32bit程序
    3 N5 w& G9 Y5 v
    2 y: X# Y& m1 C# f数组跟元素指针的作用很相似,都可以通过下标去访问元素, 但调用sizeof()函数的结果不一样。元素指针的sizeof()返回值是4(32-bit应用)或者8(64-bit应用), 数组的sizeof()返回值是数组实际占用的空间。数组可以当作指向第一个元素地址的T* const来用其实就是我们常说的数组到指针的隐式转换。当数组作为函数参数传递后,会自动退化成T* const, 在被调用的函数内部调用sizeof()的返回值跟T* const指针大小一样。 数组传递作为函数参数后, 在被调用函数的内部与T* const是没有任何区别,只有在数组定义的可见范围内sizeof()才有获取数组占用空间大小的效果。
    " N& w( ~  u, M- s. r0 ~5 d+ u2 ~1 \+ j5 s% Z% f5 y" z8 a
    以下3个函数翻译成汇编以后,汇编代码是一样的。
    ) U2 k% o$ k+ Y; s
    ! c$ c4 B" h; [void Func1(int* p_ary)/ E# n3 h) |  n- f
    {
    " z- s% y, `$ c' f8 W    assert(sizeof(p_ary) == 4); // 32-bit
    - [' s3 o( ^+ Z* G6 N; V    p_ary[1] = 1;
    # E# j3 `5 V& K) U}
    , o; o. \0 J; Q1 n  j" A: `. _9 ?
    void Func2(int ary[]); [. G& M0 J& ^
    {
    $ ^2 e5 Z/ L* P0 [2 M    assert(sizeof(ary) == 4); // 32-bit/ @( ]- J# u! c9 j( S
        ary[1] = 1;, `, o0 S9 k1 Y' L! M
    }# F, U. o8 y! ^1 h# I

    ! h, x% ?& J9 u/ y9 zvoid Func3(int ary[10])
    0 ~  i( b) V( s- q# w{
    ; N) L  @1 z+ e; x, C1 J    assert(sizeof(ary) == 4); // 32-bit
    " {" ?/ K; W. ~% G( ?+ K    ary[1] = 1;
    , D' n& k& {/ }% u}
    0 `( c) j: ^) M0 B5 X  W1 z; R
    ( l/ e8 Q8 X9 z1 U) Gint main(int argc, char** argv)
    ! B: \* I( C1 U# n* e{
    % S- j( n5 N0 u7 x6 @    int a[10];
    ( q( ^; E1 S. b$ v    int b[20];- F2 ]  w9 i( O% P$ A  x
        Func1(a);% j8 X/ y" T6 b- H$ ~5 F3 @
        Func2(a);+ o1 g; N" d4 j! u  Z& X' R2 E
        Func3(a);
    - h. X8 d6 g/ S& K1 h' [    Func3(b); // 退化成int* const了, 即使数组长度不匹配也不会报错。# W5 ]4 f+ A4 |6 J" ^- ~# o% r
        return 0;
    1 G9 f3 n% C$ k( P: }1 h3 a}' k' D5 }9 L0 V! @% A, h

    , y3 g* L5 ~2 |! {8 Q7 _5 k1 P5 Y9 ~% U5 [

    0 H* T8 I* H: B$ m多维数组
    7 a# Q8 S  X9 a一个3行,4列的数组, 结构如下:
    4 W0 M0 r& L1 _9 U4 ?, [; E1 o: j# G4 U* K
    int a[3][4];" ^% [. {. i% W' y, t
    column 0        column 1        column 2        column 3
    8 g5 H. y) F+ e0 \; ~row 0        a[0][0]        a[0][1]        a[0][2]        a[0][3]
    5 d  F$ D" j5 R3 a- q  Wrow 1        a[1][0]        a[1][1]        a[1][2]        a[1][3]  m% n5 e2 F7 W: M: a
    row 2        a[2][0]        a[2][1]        a[2][2]        a[2][3]2 |6 F0 q1 F, P5 l/ b
    数组初始化8 u) {- v0 c# n8 \! F. H
    . v( e, I4 E% H/ i% k
    int a[3][4] = {
    : S6 T8 s: A( y1 D( K0 H0 W/ ~" G    {0, 1, 2, 3},: ?" w. V: J4 y; n) ?7 ]- [
        {4, 5, 6, 7},
    * C5 R5 T6 l- k1 t0 W    {8, 9, 10, 11}3 w3 Z" h" X9 @% Z7 m" a, ?
    };
    + D0 k) {; `5 G+ p 实际上多维数组和1维数组在开启速度优化后,翻译成汇编代码是一样的。7 _& ^! ]% N% @9 T+ \" ?
    ; N; g: f8 z5 C4 _* D
    void Print(int* p_ary);5 B# C8 r# I. p; o4 L$ l# l, a
    9 \* K/ l0 O+ H$ X; t  M$ h
    void Test1()) ^$ N1 l. n' u. ]8 F
    {1 L  |) E  ?+ \; J  W! y8 a
        int a[10];
    3 m) t" I  H. Z) E( L% X4 p    a[3] = 3;, E, [9 ]1 G6 H5 m
        a[7] = 7;/ r, W2 p9 \) a2 u$ q2 C& {
        Print(&a[0]);
    3 u) c" k" X5 @2 S3 X: J' g}  z; i! j5 h* x0 {, _
    9 [* i1 W' A7 g
    void Test2()( h1 R6 y; P4 M8 o
    {
    " T, h/ G+ _/ o) O7 |3 d    int a[2][5];
    $ r/ N8 {5 I# K! J3 ]    a[0][3] = 3;
    % a' O' `8 Q! P# \) f    a[1][2] = 7;0 a( k, X# V8 z7 g7 J
        Print(&a[0][0]);
    3 O7 z: _( s# y3 U  U$ g}
    : n. ]8 @; F5 r' ]- ^1 @% e2 J% {- e7 A7 h2 |
    0 k' q# c7 ?& P% \6 p) [% G: F

    1 o' A7 e) r  z很自然地,多维数组也支持用1维数组的方式去初始化。* T6 X" ]% ~0 M7 @5 J  D4 |) R

    * L/ `* v, G2 x) @. N' |& uint a[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };7 o3 p1 C6 x5 a* H; U0 Q
    既然多维数组与1维数组没什么区别, 为什么还需要多维数组?/ o$ q& o# K+ R# w
    7 F  V' y/ O9 E" [2 w
    假设有一幅RGB图像720*576个像素,每个像素有RGB三个通道,每个通道的值是8bit大小。给出图像的首地址p_rgb_image, 我们要取第40行,第50个像素的R,G,B值。代码如下:
    ; H; Q; b; n: l( ]9 y. ^0 Q5 m( ^
    unsigned char* p_rgb_image;
    $ L- T* r& t0 K- Z2 p3 Tunsigned char r = p_rgb_image[40*720*3 + 50 + 0];5 q4 _' |0 S8 N0 k7 [
    unsigned char g = p_rgb_image[40*720*3 + 50 + 1];
    . j' O( x. k( Z  t" w: H4 punsigned char b = p_rgb_image[40*720*3 + 50 + 2];
    + ]& s$ l4 i! }2 |类似这样的场景, 采用多维数组的写法, 有点类似以索引为参数,可读性更高。相当于程序员和编译器打了一个配合。) {1 s+ M! T$ a

    6 v% e% `2 S% q' t; S5 r+ Henum
    8 u3 Y! Z  A! n: S: M{! }! m* Q, V! @, A8 d' P( ?% A9 l
    Red = 0,' y- z  d; S6 n4 G% G8 p$ X3 G% L- H- G
    Green = 1,' _4 p) A( H; o9 X# I; [
    Blue = 20 F" T5 J& g. l# H, y% ^0 ?  @
    };
    % s5 H8 Q0 X- B8 e; h% o0 Punsigned char rgb_image[576][720][3];
    * c5 s; D& L9 _% [9 tint row = 40;0 V8 f  m$ J( ]7 i
    int col = 50;- a  P( a2 @( `( ^8 ]( p2 G
    unsigned char r = rgb_image[row][col][Red];# f6 M2 `1 }$ g4 O: h# k
    unsigned char g = rgb_image[row][col][Green];
    2 I; S/ m* }9 Q; X* e1 Kunsigned char b = rgb_image[row][col][Blue];
    7 a1 ?. W: A) Z- N+ U9 ?, B数组指针以及与指针数组的区别
    , L, S; A% Y& |- q% g+ |7 M- G数组指针,是一个指针, 指向的对象是数组。 数组指针的赋值,要求数组的长度匹配,否则会报错。当指向1维数组时, 需用用*取得数组对象的引用,再用下标来访问数组元素。
    , v4 R# i+ o0 I指针数组,是一个数组, 数组保存的元素的类型是指针。6 Z4 S" o1 [; H" `" y- g) Q7 f
    数组指针的定义1 n" H9 }" m$ S- [7 n
    数组指针定义先定义一个数组。: l8 T, V- b, y
    int a[10];6 W& Y/ @4 ]: I  J% y/ q5 l- V% u
    然后对数组里的名称用括号括起来后再在变量名称前面加个*号
    ! U! ^* R" e; q7 W! B9 G5 j$ Wint (*a)[10];
    # U% P7 B: z/ r后面你会发现函数指针定义类似。# v  g2 s7 u$ d! ^% c
    // 各类定义对比
    . X  s9 e# c$ c0 f1 o8 g; r. Qint a, *b, **c, d[10], e[10][20], *f[10], (*g)[10], *(*h)[10];9 W  P; Z6 B' G' i
    3 M7 G* z" a2 n
    int *f[10]; // 指针数组, f是包含10个元素的数组, 数组里每一个元素的类型都是int*
    8 D! T! r% R- \$ t/ n( Fint *(f2[10]); // 指针数组。另外一种定义方式。
    / _0 X! Y' {, N9 q* H  B2 eint(*f3[10]); // 指针数组。另外一种定义方式。
    " h3 c9 ~" M3 B# f' B% Aint(f4)[10]; // int数组2 ~4 ]8 o' f+ |: z% V
    int(*g)[10]; // 数组指针, g是一个指针, 这个指针可以指向类型是int,元素个数是10的数组
    7 w, ?- D- B2 e5 V( W( g1 |0 bint* (*h)[10]; // 数组指针, h是一个指针, 这个指针可以指向类型是int*,元素个数是10的指针数组3 o4 K7 o$ P9 [1 ^7 D. d! c
    ; l+ y3 C* e; g# X3 H' g
    int d[10];
    : r; F0 y- ?" l+ o( fg = &d;
    3 k! ?9 L; V4 ?+ O0 G
    ; {! F2 `& ^- S" _  y" k$ rint* e[10];; t6 [2 N, N. z  ?: c
    h = &e;
    $ _. k! L% O# G& r数组指针的使用
    # y$ O5 V2 i+ k2 ^& `数组指针一般先通过*号取得指针指向的数组对象, 然后再用下标操作访问元素。/ k& k, `3 E4 X
    ; n/ g/ I0 ?7 a* B6 H3 D6 z* e# G
    int a[10];
    : g6 G1 ?/ w; K7 E; r: x/ Iint(*p_ary)[10] = &a; // p_ary是一个指针, 指向"int (*)[10]"类型的数组
    9 B; h1 I- j& e: o$ L, V- Lfor (int i = 0; i < 10; i++) {" Y: J5 t5 [; y7 e9 T9 H" a
    // p_ary是一个指向数组的指针, 需要先通过间接寻址运算符*(indirection operator)取得数组对象
    1 Q* \) x4 l; C // 再通过下标操作访问元素。# X" ?: _+ s' ]9 X) A- ?$ q
    (*p_ary) = i;
    # k$ p2 u3 w( v6 ~) u4 H1 S( F}
    8 [0 H. u2 T8 y. q8 t8 `" U; A: ?1 @. P7 o
    int b[10];
      K5 ^, ?7 Q+ N% T6 r+ iint c[20];
    * q7 \& l# z) C# u2 ~( O9 ^& Qp_ary = &b; // 合法
    ! y3 n. R2 A2 S1 L# Q1 z  op_ary = &c; // 报错! 不能将 "int (*)[20]" 类型的值分配到 "int (*)[10]" 类型的实体
    , I- I% J. u2 `: F0 ^数组指针指向多维数组的子数组
    ) N& i9 W; d  hint a[10];
    8 z: t+ d( k! F8 `' mint b[4][10];& z3 W$ B0 |/ e, |/ v3 `
    int(*p_ary)[10] = &a;
    % t& X! T/ I: N- vfor (int i = 0; i < 10; i++) {
    * _& M! _& A; V- Y (*p_ary) = 1;
    ; T0 [' S+ }6 B4 i9 U+ W0 W}
    . a* l4 e) M" k. h3 b
    # ~0 B6 j' z! Lp_ary = &b[2]; // 多维数组,可以看作数组的数组,
    ; A1 \$ R& I+ z6 p// b[2][0] ~ b[2][9]的值都被改成2了, |! h/ r- l9 P& v
    for (int i = 0; i < 10; i++) {
    ' T* n% G/ K  D9 u8 {/ ` (*p_ary) = 2;2 h% K7 z0 ~3 o
    }
    $ t. A6 d. ~+ Z0 P. o9 i" Z& J0 e多维数组指针: m/ l; G# ~  |5 r. `8 ]' B
    多维数组指针,是一种指针,指向的对象是个多维数组,支持多个下标操作。
      B$ E3 I& }3 t/ W1 c# k0 x  j* i$ G) w0 S- c& W+ g
    int a[2][5][10];
    & J) `+ K2 J; S: }% L# Tint(*p_ary1)[10] = &a[1][2]; // 1维数组指针  |% s% `% V) T8 _
    int(*p_ary2)[5][10] = &a[1]; // 2维数组指针, @# j4 z0 E3 S/ ]8 W, L2 I
    for (int row = 0; row < 5; row++) {
    - _3 q- F& Q0 z( A5 ?    for (int col = 0; col < 10; col++) {4 N" ?8 R7 r" l# x. i5 c
            (*p_ary2)[row][col] = row * col;
    ( _( |  t2 n4 {' q* Y$ c; {    }! z6 ^  S. ?1 t7 W  P0 y
    }. W0 U7 y- _- e6 o, W7 G
    数组指针和指针数组对比实例9 T. \- X( x: q9 l
    数组指针还是记住两步法即可  ~  U% n' @6 H7 m) X- A
    6 b( J1 X- p5 S9 U- Z" f6 n0 C( \7 w
    定义一个数组
    9 N6 [7 n, X4 x1 b3 A  O: y) Z括号包围1中定义的名称,再在名称前加个*号。
    ' ~* T& L0 o& uint a[10];. i; W; n/ w$ Q
    int(*ary_pointer1)[10] = &a; // 数组指针7 j+ A! m8 J3 H7 p
    int* pointer_ary1[10]; // 指针数组。元素类型是int*( |& _& @; r5 l9 z2 d
    int *(ponter_ary2[10]); // 指针数组。另外一种定义方式。
    1 ~: O! O# r# i6 N9 Rint (*ponter_ary3[10]); // 指针数组。另外一种定义方式。. B3 Y" L) d. L- s' o2 X
    int c, *d, (*ary_pointer2)[10], *pointer_ary3[10]; // 排列定义比较。
    - |. Z3 r) b1 l7 ]& E" F8 {% X3 \0 ?, W) r& @/ D" b9 N
    // 指针数组可以把每个元素指向数组对应位置的地址。& ~+ b2 n% O, J1 J& f' w
    // 这样遍历指针数组, 可以达到遍历数组元素的效果,但是注意每个元素都是指针,
    " T) q2 Z% I  P. u% z0 ^// 需要访问原数组的值的话, 需要对指针用*间接寻址运算符。5 @# @, c0 F3 e5 G5 S+ J
    int* pointer_ary[10];
    # n0 @- ^! s% x- P( {, F' Q1 e6 c$ Ofor (int i = 0; i < 10; i++) {6 k9 X' Z" q& Z5 l( j$ |4 D
        pointer_ary = &a;6 A" B7 @& W1 n0 h- ?* _2 ^
    }
    ! g, t+ Y, `+ h# T// 类似遍历原数组效果。
    9 W. Y& M# \: Z1 z% u- f7 e& {for (int i = 0; i < 10; i++) {
    2 A2 n; L  ?/ B% a. d# |4 K    *pointer_ary = i; // 修改原数组。; b9 G% ~! M* ]# N! s6 N
    }
    6 f3 J( V8 l0 e* o: Q# M: i# o' K! e* `/ f* {- y1 P: E% L
    函数指针
    & |/ I- \: D$ T! c& `1 ^& d' _取得函数地址2 |8 y. U/ G5 {3 i9 D5 I" K% H
    函数的名称作为参数被传递时,会隐式转换成函数指针, 和在函数名称前加取地址符&等价。建议带上更加统一和清晰。
    ; g/ U" O  }" L2 C9 z3 O! N4 [0 O$ f' A3 P4 w2 j4 p5 T
    void f(int);
    * z: U0 C$ f: I" w8 vint main()/ _0 G$ r# A  c/ b4 K
    {
    1 v. G' s9 i: W, L# |& K6 Y    void (*p1)(int) = &f;
    2 k( q7 Z- \2 U: n    void (*p2)(int) = f; // same as &f) j/ U. t9 G/ }5 Q# ~. t; a: T) M
        return 0;/ S5 W  a) d5 Z3 u1 M
    }
    # P5 A+ b2 [, F7 d0 z! Z. ]翻译成汇编代码, p1和p2的赋值是一样的。
    8 _1 p! l. e2 r# O3 e
    0 B: x  _( z. e1 L
    1 l2 }; n) _7 _" q) U9 R1 @' s; C. U: ^& p6 E+ j% o7 r
    函数指针的声明
    % c7 q2 t9 W2 \  S! k/ o单个函数指针变量定义步骤
    2 R) A+ e# m. i" O& e( b定义一个函数。void fun1(int a, int b); int fun2(double a);
    6 ^+ K' ?3 g; j' m, D% q用括号把函数名称包围起来,然后在名称前面加*号。void (*fun1)(int a, int b); int (*fun2)(double a);
    % p; D8 U$ D3 I  l: ~如果要定义函数指针数组,在定义单个函数指针的基础上,在名称后面加上[数组长度]void (*fun1[2])(int a, int b); int (*fun2[10])(double a);
    0 Z. T( `8 y7 j3 ?" ytypedef定义函数指针5 a9 t0 I+ Q1 V5 B9 {& i8 M
    可读性高比单个定义要高,特别是声明多个同类型的函数指针,或者函数指针数组。! \. Q; w* t6 D' f7 w) f) I
    ! v$ e9 _/ w4 B, X5 ]! ?6 _3 |
    typedef定义函数指针的语法. f4 N: P3 }# g
    typedef有两种做法, 一种就是定义一种函数对象,另外一种就是定义函数指针。用法稍稍不同,效果是一样。其中函数对象不支持赋值, 但是支持引用。! }8 q! q( O* w% }
    9 `! H' D  b7 B4 g3 H- O5 x# F1 \9 \
    typedef int FuncObject(int a, int b); // FuncObject类型是函数对象
    ' d. [1 n, Q# `1 D2 k; ptypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针
    ) w; J& }8 y. k6 w9 @8 E) AFuncObject* f1 = &Add;
    / b, R1 ?" \5 f7 `4 hFuncPointer f2;- W- V, P3 s6 E0 q$ _
    f2 = f1; // f1, f2类型一样, 都是形式为int(int, int)的函数的指针。, E" I: t5 z, v  h7 z3 z* T* x
    FuncObject f3 = Add; // 报错! 函数对象不支持拷贝
    * l+ {4 V) C/ I0 @; ?5 lFuncObject f4 = &Add; // 报错!&Add是函数指针,与函数对象类型不匹配
    ( M) ~7 _4 N; [$ \, w6 sFuncObject& f5 = Add; // 正确. n' {3 l' ~0 K! T1 {: ?$ |
    int ret = f5(2, 3); // 正确
    & P9 y9 W. Y- X2 uFuncObject& f6 = &Add; // 报错!&Add是函数指针,与函数对象引用类型不匹配4 ~2 [2 Q6 j4 C1 H0 S7 R
    如何记住typedef定义函数指针的步骤
    8 s0 A1 W' L) I" v. S  x- d像定义一个函数指针那样, 指定一个名称。int (*CalFun)(int a, int b);
    & h' w5 l+ }9 Z. ?7 v在这个函数指针变量声明前面加上typedef。typedef int (*CalFun)(int a, int b);) r3 E4 Q; H8 G' x9 A: P3 Q1 z; S
    完整例子! B: D2 `  W) `9 D
    typedef int(*CalFun)(int a, int b);
    1 o4 M  D8 `# p5 i. P7 X1 p
    7 @7 k7 P) v# ]/ r( a* W( Uint Add(int a, int b)
    $ v. z) r" T" p1 S3 G+ W{, y; g/ j# f) M- d8 q0 g
        return (a + b);+ L0 `+ J' k1 |" `5 d, U
    }
    7 z5 O9 O3 z5 Q, ?/ q/ v) r( ?  k5 j5 w0 g
    int Sub(int a, int b)
    - s& N: E( ~( _( y% c6 k8 x2 j; W{
    / p, q7 I$ H& F6 h. M; T/ s& N    return (a - b);
    8 V6 M5 w4 k+ `% S}( F' v# @! z$ }( ?) L6 j: M

    4 N( V6 `& }# r% Z8 yint main(int argc, char** argv)" H6 l% E; g8 y, t6 M+ s
    {$ l/ v5 A1 Q5 |( y% I8 r: |
        CalFun f1 = Add;
    & R+ m& |9 B" B! L3 h    CalFun f2 = Sub;2 j; l) ]# X' N6 g, t3 i1 x1 j
        int a = f1(2, 3);
    ( o" p4 D, h: Q2 \    int b = f2(10, 5);4 J+ R6 A4 m9 V0 |: q+ a

    ' J4 r3 D/ E: o( f4 [4 V    // typedef定义的函数指针数组。
    3 r- _  S; C$ S7 d' y    CalFun f_ary[2];1 v8 X6 l1 d- z$ |8 S
        f_ary[0] = Add;
    : J8 e6 A( x, c1 n2 N: z    f_ary[1] = Sub;) r6 w- P- L, k1 [" Y5 t

    * L1 ^7 Q$ `; o/ C    // 单个定义的函数指针数组。
    ! _& j1 x9 W$ F' w    int(*f_ary2[2])(int a, int b);$ h0 D9 g: z, ?% o- o! f
        f_ary2[0] = Add;; x% F. O+ q; q% _+ m
        f_ary2[1] = Sub;* ?' t$ m; ]" u: F

    ; C5 j7 z0 J& ~( J; z2 Z. S( ~    return 0;
    ) h0 w9 X( F+ j, {7 `+ S* L}$ e+ E4 N4 j& r& G

    # t/ I! i5 |/ u$ a) lusing别名定义函数指针) O; n7 _! g8 l7 @
    c++11以后的类型别名定义--using也可以用于定义函数指针, typedef的好处它都有,个人感觉比typedef更直观。using类型别名同样分函数对象和函数指针两种方式。
    * {. Y% j( G% ]7 O0 l; n
    4 l3 t) X4 j( Y2 s8 b7 Atypedef int FuncObject(int a, int b); // FuncObject类型是函数对象
    9 c* G: ~! c& l  z! F6 `using FuncObject = int(int a, int b);
    " o- c! r- Z2 k2 c2 n1 w" ltypedef int (*FuncPointer)(int a, int b); // FuncPointer类型是函数指针+ [5 B3 F" S% G3 s, [6 a. Y
    using FuncPointer = int(*)(int a, int b);+ n7 j; P2 n* P3 L
    函数指针的调用
      G! S5 Y) l1 m% h* K; b7 }函数指针和函数对象都可以直接后加括号调用1 u0 E8 p% O1 P- Q6 C
    int f();$ o' R/ i9 w# Q; n6 ?- B, o: H
    int (*p)() = f;  // pointer p is pointing to f
    0 r: _. D) A7 o  @7 U- hint (&r)() = *p; // the lvalue that identifies f is bound to a reference
    6 U# A7 B# f8 b6 u* a+ T% J: {r();             // function f invoked through lvalue reference
    / n; V0 d0 w2 ?. h3 G/ u(*p)();          // function f invoked through the function lvalue
    $ b" ^6 ~( u. Mp();             // function f invoked directly through the pointer4 J  q  N# i7 \/ {4 F$ @
    如果函数有重载, 函数指针会指向匹配的那个版本。
    1 U) O+ u4 ~$ K8 k+ ~3 S( Ptemplate<typename T>
    * B7 [7 n% _+ o+ u9 c) {( JT f(T n) { return n; }, {# Y8 a# g  A% L; G
    $ G! t+ s; t* V6 q
    double f(double n) { return n; }& E4 j) p: {4 m2 ?  {% q4 a) M
    ; V, b) W. i6 G! w) V
    int main()
    ' Q& k  z* h) X: z{
    1 m# H4 w3 y& J0 t    int (*p)(int) = f; // instantiates and selects f<int>
    ' ?$ B' _$ ]  r}
    8 M+ S  M$ p1 [8 ]1 Q. q7 _成员函数指针
    5 ~* s- P2 C: h静态成员函数,除了增加了访问控制以外,跟普通的函数指针没什么区别,所以普通函数指针可以直接指向类的静态成员函数。但非静态的成员函数与普通函数指针不太一样,声明时需要指定函数归属的类名,并且调用需要指定对象实例。  m( s. J, G2 q1 p% _+ _3 b

    2 g: @* _$ i% |" y+ g7 x0 L: C成员函数指针定义。1 L, B" C0 p' A1 z
    像定义类成员函数实现那样写, 并任意指定名称,这里作func。void ClassName::func(int);
    6 g1 W  ?0 B8 |8 S9 s9 F括号把类名、范围解析运算符::、名称包围起来。void (ClassName::func)(int);
    9 Y8 }% [! I9 g在名称的前面加个*号void (ClassName::*func)(int);* l$ ^! t1 {8 n- e) ]) V8 q
    成员函数也支持typedef和using的定义方式。typedef void(C::* MemberFunc)(int); using MemberFunc = void(C::*)(int);
    5 s+ ~7 c* R6 C4 k6 X成员函数指针如何调用。
    4 N$ U. ?& m3 ?" Z/ ?) W5 g假设成员函数指针名字为func3 ^/ g: [5 ?3 O1 p
    $ W& t5 y" N; A' y% S6 K
    void (ClassName::*func)(int);
    & Y, B4 e) y2 O" U  A+ v! u对象式调用。
    , C; T* M! q9 D) \% ?ClassName c; // 被调用的对象! _, z5 ?" x; d4 r7 j
    成员函数指针名字当作正常函数那样写。
    : V; q5 {- ]: W7 ]  c7 X$ I9 Jc.func(3);. G7 H% |! L3 J2 P3 L
    成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。
    + g6 r5 }# ?) \' D" i7 s& Mc.*func(3);7 n' c- D# |2 d% ~
    最后用括号把调用对象、成员访问运算符.、间接寻址运算符*、和成员函数指针的名称包围起来。
    0 m; N* ~6 I' Q8 U0 o% q(c.*func)(3);% R2 T6 D0 s5 ?/ \
    为何要加上括号? 根据c++的优先级标准,取成员运算符. > 函数调用() > 间接引用符*。 *号优先级比函数调用要低, 成员函数指针还没取得对象就被调用了,自然报错。 另外6 [2 H+ i5 _) ]+ g  I+ u4 k/ Z' E3 F6 E
    (c.(*func))(3);. ]- j+ z& J! k6 C  |: C
    这样的写法也不行。 .*和->*是整体作为一个运算符的,中间不能用括号隔开。' v* \8 j& K1 u/ \5 m9 C
    指针式调用# T* e  z- w4 r  i' O5 `: Y0 w5 \5 T
    ClassName* p; // 被调用的对象的指针
    ) u1 ^# T! O  o% h( O成员函数指针名字当作正常函数那样写。
    " _/ `1 M! y/ H6 A+ pp->func(3);
    4 v. a+ ^* z' X, E6 x成员函数指针是指针, func名称前面需加上间接寻址运算符*,变成函数对象。
    7 ~  s& p. O  W' B& K- U7 c( `8 Np->*func(3);
    ) O$ {4 y. @  y8 p* }! T最后用括号把调用对象、成员访问运算符->、间接寻址运算符*、和成员函数指针的名称包围起来。
      |, r4 z6 E( N7 @8 D) d(p->*func)(3);
    0 S# F# F- T! Q  B函数指针使用完整例子
    ; h9 s, x. r2 ~; b0 ~struct Cal
    ( `+ n1 y  Q- R2 p9 B{ 0 X% [) U" C; p& ~1 I9 O" B" E0 T1 Q$ Z9 E
        int add(int a, int b); 9 Q$ B* r7 }+ g$ w9 _8 W
        int sub(int a, int b);/ |! \& `2 _; I6 ~' S
    };6 w) J9 B% a3 ]! ^& b

    * B  i: p2 ^1 ~7 L" w/ d  tint main()
    " ?& C  S7 w. P6 Z  z{
    # W. `* z" x4 v/ N  Z2 p    int (Cal::*fun)(int, int) = &Cal::add;
    " H4 P( o# Q; R2 i    fun = &Cal::sub;% T! v/ d; }8 N$ V. l- {0 Y

    + w) r6 Y2 X5 e! b4 }1 l    Cal* p_cal = new Cal();1 `( B+ o% v) F; X2 y& L7 \! N
        int r1 = (p_cal->*fun)(2, 3);
    ! m1 p9 T, {5 ?+ x    delete p_cal;, e5 L3 D) p3 b3 q" ^( y
    ( a# b) j0 u5 Q- P; t+ c5 H
        Cal local_cal;8 ~: t- r4 Y: t1 B. s* g1 u
        int r2 = (local_cal.*fun)(8, 6);+ d' B, ?' S3 J/ R5 n' L; o
    }. y: @6 ~- E2 A" T$ s! ~/ \; z7 Q2 _
    % {  N  A% Q& c  _
    成员变量指针
    ) U( ~5 X1 W; h, D: l成员变量指针比成员函数指针还要简单些,没有函数调用, 无需考虑函数调用和间接引用符*的优先级问题。
    ! Z* }" M7 p) d1 x0 H
    ' e" T# k& l5 x& h  |; c成员变量指针的定义, i: d5 u3 o8 V7 [3 u
    假如以下结构体C。, N5 ~+ R, h- J4 a$ p- B! @
    9 y7 m4 Q2 s% r- s" |
    struct C
    6 p( ?5 i6 M  D$ ?% d{
    ; n  q$ T; ^1 F0 }+ W3 U7 c) U    int m;
    " h' g9 c0 b5 m9 t6 ?};3 j% ^5 a! F6 k( N
    单个成员变量指针定义 ! I% }6 R) O' U+ w. @
    假设名称为p, 类似静态成员变量定义那样声明
    , m3 F( f) p& L- q( ^' x7 d" I7 l! fint C::p;
    " S/ P# \) l/ E( n- S在名称前面加上指针标识号*   F. c; s* B+ D' G1 h" l( b
    int C::*p;3 r; [9 V" E  J- y
    typedef或using方式定义
    & B$ P3 j5 R$ vtypedef int C::*MemberPointer;
    , ]# u+ |2 D1 K+ n+ E; p4 S4 p) r% |using MemberPointer = int C::*;5 d! r: y4 T) x: D
    成员变量指针的使用。' L. S: R5 }5 g( ^
    类似成员函数指针那样,直接使用指向成员的指针运算符:.* 和->*即可。
    ! w5 e/ D: [4 J成员变量指针, 能让我们实现一些遍历成员的动态功能。 例如把一个类/结构体的多个同类型的成员变量放进一个容器里,然后遍历访问这些成员变量。  A6 I- _9 D$ }& _2 [# Q

    5 I% Y6 W+ T/ N 完整例子
    $ f) }& F% y/ ~" I2 t+ C( _struct C { int m; };
    4 L+ B5 D* j" J8 A  \9 w. wint main()
    / j5 n) Q- \% C9 j8 m3 [) ?9 P{5 H" Z- Y0 J5 H. b/ ?$ V
        int C::* p = &C::m;          // pointer to data member m of class C
    0 s2 L" {1 V2 N    C c = {7};  a1 x9 W" M0 H( Z, N# R& q6 u
        std::cout << c.*p << '\n';   // prints 7
    ( W. X" N0 S4 d- K/ z- s    C* cp = &c;% [+ F% ?: [; f& l) }  g
        cp->m = 10;0 T( |7 u0 \+ k: P
        std::cout << cp->*p << '\n'; // prints 10
    6 a( o! v' D7 Y* V8 d  R: t3 S$ P9 z
    ————————————————
    3 a* o  d- R; c+ {/ {' l版权声明:本文为CSDN博主「南风fahaxiki」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ! s8 b% ^3 }/ R, i# @3 l' ^原文链接:https://blog.csdn.net/m0_64407685/article/details/126788115
    # T* Z' j# I0 n0 y1 E# ], }  [  {3 D, I+ k) N

    ( B, p3 q+ a: r/ V# z" P
    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-9-13 15:26 , Processed in 0.828781 second(s), 50 queries .

    回顶部