C++ and C 指针比较

Abstract
學習C/C++,大家最大的障礙就是pointer,本文試著將pointer做整體的討論。

Introduction
C很多地方都用到pointer,C++則有不少替代方案,以下是C和C++會用到pointer的地方。

CC++
1.Pass by AddressReference
2.Pass Array to FunctionVector
3.char *std::string
4.Dynamic Allocation
(malloc(),linked list)
STL container
5.Function PointerFunction Object
6.N/AIterator
7.N/APolymorphism
8.N/APolymorphism object in container

1.Pass by Address
C語言

為了達成pass by address,C利用pointer達到此需求。

1  /*  
2  (C) OOMusou 2007 http://oomusou.cnblogs.com
3 
4  Filename    : pointer_swap.cpp
5  Compiler    : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
6  Description : Demo how to use pointer to implement pass by address
7  Release     : 02/25/2007 1.0
8  */
9  #include < stdio.h >
10  #include < conio.h >
11 
12  void swap( int   * x, int   * y) {
13    int tmp =   * y;
14    * y =   * x;
15    * x = tmp;
16  }
17 
18  int main() {
19    int x =   1 ;
20    int y =   2 ;
21 
22    printf( " x=%d, y=%d/n " , x, y);
23    swap( & x, & y);
24    printf( " x=%d, y=%d/n " , x, y);
25  }


執行結果

x = 1 y = 2
x
= 2 y = 1


C++
C++提出了reference達成pass by address,使程式可讀性更高。

1  /*  
2  (C) OOMusou 2007 http://oomusou.cnblogs.com
3 
4  Filename    : reference_swap.cpp
5  Compiler    : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
6  Description : Demo how to use reference to implement pass by address
7  Release     : 02/25/2007 1.0
8  */
9  #include < iostream >
10 
11  using   namespace std;
12 
13  void swap( int   & x, int   & y) {
14    int tmp = y;
15    y = x;
16    x = tmp;
17  }
18 
19  int main() {
20    int x =   1 ;
21    int y =   2 ;
22 
23    cout <<   " x= "   << x <<   " y= "   << y << endl;
24    swap(x,y);
25    cout <<   " x= "   << x <<   " y= "   << y << endl;
26  }

 

執行結果

x = 1 y = 2
x
= 2 y = 1


執行結果一樣,功能也一樣,皆是pass by address,C++的reference是不是更簡潔呢?

See Also
(原創) pointer和reference有什么差别呢? (C/C++)

2.Pass Array to Function
C語言

將陣列傳到function時,由於陣列可能很大,若用pass by value的方式傳進function,勢必造成大量copy的動作而降低效能,C語言是靠pointer的方式,將陣列第一個元素的位址以pointer的方式傳進function。

1  /*  
2  (C) OOMusou 2007 http://oomusou.cnblogs.com
3 
4  Filename    : ArrayPassToFunctionCStyle.c
5  Compiler    : Visual C++ 8.0 / ISO C++
6  Description : Demo how to use pass array to function by C Style
7  Release     : 01/03/2007 1.0
8  */
9  #include < stdio.h >
10 
11  void func( int   * start, size_t size) {
12    int   * end = start + size;
13   
14    while (start != end)
15      printf( " %d/n " , * start ++ );
16  }
17 
18  int main() {
19    int ia[] = { 0 , 1 , 2 };
20    func(ia, 3 );
21  }


執行結果

0
1
2


C++
array本身有很多缺點,C++建議用STL的vector取代array。

1  /*  
2  (C) OOMusou 2007 http://oomusou.cnblogs.com
3 
4  Filename    : VectorPassToFunction.cpp
5  Compiler    : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
6  Description : Demo how to use pass vector to function
7  Release     : 02/26/2007 1.0
8  */
9  #include < iostream >
10  #include < vector >
11 
12  using   namespace std;
13 
14  void func(vector < int >   const   & ivec) {
15    vector < int > ::const_iterator iter = ivec.begin();
16    for (;iter != ivec.end() ; ++ iter) {
17      cout <<   * iter << endl;
18    }
19  }
20 
21  int main() {
22    int ia[] = { 0 , 1 , 2 };
23    vector < int > ivec(ia, ia +   sizeof (ia) /   sizeof ( int ));
24    func(ivec);
25  }


21行和22行

int ia[] =   {0, 1, 2} ;
vector
< int > ivec(ia, ia +   sizeof (ia) /   sizeof ( int ));


只是個便宜行事的寫法,因為vector本身並沒有如boost::array提供initializer,所以借用了array的initializer,再由array轉vector。實務上是用pushback()將資料塞進vector。

重點應該放在14行

void func(vector < int >   const   & ivec)


只需傳vector型態即可,不須用pointer,也不需傳size。

vector還有很多優點,如靜態動態一次搞定,不像array還分靜態array和動態array,array唯一的優點就是速度較快,若你的程式非常重視執行速度,則應該考慮使用array。

See Also
(原創) 如何将array转成std::vector? (使用vector.insert) (C++) (STL)
(原創) 如何将array转成std::vector? (使用constructor) (C++) (STL)
(原創) 如何動態建立一維陣列? (C/C++)
(原創) 如何動態建立二維陣列(多維陣列)? (C)
(原創) 如何動態建立二維陣列(多維陣列)? (C++)
(原創) 由一維陣列模擬二維陣列(多維陣列) (C/C++)

3.字串
C語言
C語言沒有字串型別,而是用char array來模擬字串,由於本質是array,所以可以用pointer來表示字串,也因如此,造成C語言在操作字串時含其他語言差異甚大。

1  /*  
2  (C) OOMusou 2007 http://oomusou.cnblogs.com
3 
4  Filename    : C_string.c
5  Compiler    : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
6  Description : Demo how to use C-Style string
7  Release     : 08/12/2007 1.0
8  */
9  #include < stdio.h >
10  #include < string .h >
11 
12  int main() {
13    char s1[ 15 ] =   " Hello " ;
14    char s2[] =   " World " ;
15   
16    strcat(s1, s2);
17   
18    printf( " %s/n " ,s1);
19   
20    char   * s3 =   " Hello " ;
21    char   * s4 =   " Hello " ;
22   
23    if (strcmp(s3, s4))
24      puts( " s3 and s4 are not the same " );
25    else
26      puts( " s3 and s4 are the same " );
27  }


執行結果

執行結果
Hello World
s3 and s4 are the same


16行

strcat(s1, s2);


字串相加,並沒有如期他語言很直覺的用s1 = s1 + s2;而得用strcat(),且s1和s2都是pointer。

23行

if (strcmp(s3, s4))
  puts(
" s3 and s4 are not the same " );
else
  puts(
" s3 and s4 are the same " );


比較字串是否相同,也必須用strcmp()比較兩個pointer所指向的string是否相同才可比較,不可以用

if (s3 == s4)


因為s3和s4都是pointer,這樣是比較兩個pointer是否相同,而不是比較string是否相同,這和其他語言差異甚大,也和不符合一般人直覺。

C++
C++的STL提供了string,改進了C的char *,用法非常直覺。

1  /*  
2  (C) OOMusou 2007 http://oomusou.cnblogs.com
3 
4  Filename    : CPP_string.cpp
5  Compiler    : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
6  Description : Demo how to use std::string
7  Release     : 08/12/2007 1.0
8  */
9  #include < iostream >
10  #include < string >
11 
12  using   namespace std;
13 
14  int main() {
15    string s1 =   " Hello " ;
16    string s2 =   " World " ;
17   
18    s1 = s1 + s2;
19   
20    cout << s1 << endl;
21   
22    string s3 =   " Hello " ;
23    string s4 =   " Hello " ;
24   
25    if (s3 == s4)
26      cout <<   " s3 and s4 are the same "   << endl;
27    else
28      cout <<   " s3 and s4 are not the same "   << endl;
29  }


執行結果

Hello World
s3 and s4 are the same


18行

s1 = s1 + s2;


字串相加,只要直覺的相加即可,符合大部分語言的習慣。

25行

if (s3 == s4)
  cout
<<   " s3 and s4 are the same "   << endl;
else
  cout
<<   " s3 and s4 are not the same "   << endl;


字串比較,也直接用==即可,簡單明瞭。

在C++,建議使用string,而且還可搭配強大的STL algorithm,功能強大且代碼乾淨易懂。

4.Dynamic Allocation
C語言
由於C語言是典型的靜態語言,使用array時必須事先定義好大小,這樣compiler才能做最佳化,若要讓C語言有動態功能,必須使用malloc()配合資料結構的linked list。

1  /*  
2  (C) OOMusou 2008 http://oomusou.cnblogs.com
3 
4  Filename    : DS_linked_list_simple.c
5  Compiler    : Visual C++ 8.0
6  Description : Demo how to use malloc for linked list
7  Release     : 03/22/2008 1.0
8  */
9  #include < stdio.h >
10  #include < stdlib.h >
11  #include < string .h >
12 
13  #define SLEN 255
14 
15  struct list {
16    int   no;
17    char name[SLEN];
18    struct list * next;
19  };
20 
21  int main() {
22    int no;
23    char s[ 255 ];
24   
25    struct list * head    = NULL;
26    struct list * current = NULL;
27    struct list * prev    = NULL;
28   
29    while ( 1 ) {
30      printf( " No. = " );
31      scanf( " %d " , & no);
32     
33      if (no ==   0 )
34        break ;
35   
36      printf( " Name = " );
37      scanf( " %s " , s);
38     
39      current = ( struct list * )malloc( sizeof ( struct list));
40      if (current == NULL)
41        exit(EXIT_FAILURE);
42       
43      current -> next = NULL;
44     
45      current -> no = no;
46      strncpy(current -> name, s, SLEN - 1 );
47      current -> name[SLEN - 1 ] =   ' /0 ' ;
48     
49      if (head == NULL)
50        head = current;
51      else
52        prev -> next = current;
53       
54      prev = current;
55    }
56   
57    // display linked list
58    current = head;
59    while (current != NULL) {
60      printf( " No. = %d, Name = %s/n " , current -> no, current -> name);
61      current = current -> next;
62    }
63   
64    // free linked list
65    current = head;
66    while (current != NULL) {
67      prev = current;
68      current = current -> next;
69      free(prev);
70    }
71  }


執行結果

No. =   1
Name
= clare
No.
=   2
Name
= jessie
No.
=   0
No.
=   1 , Name = clare
No.
=   2 , Name = jessie


39行

current = ( struct list * )malloc( sizeof ( struct list));


使用了malloc()動態新增linked list的node。

64行

// free linked list
current = head;
while (current != NULL) {
  prev
= current;
  current
= current -> next;
  free(prev);
}


由於malloc()使用了heap上的記憶體,必須手動使用free()將記憶體釋放,否則會造成memory leak。

C++
vector本身就可以動態擴張,而且又會自動釋放記憶體,因此可以取代linked list。

1  /*  
2  (C) OOMusou 2008 http://oomusou.cnblogs.com
3 
4  Filename    : DS_linked_list_simple_vector_class.cpp
5  Compiler    : Visual C++ 8.0
6  Description : Demo how to use vector instead of linked list
7  Release     : 03/22/2008 1.0
8  */
9  #include < iostream >
10  #include < string >
11  #include < vector >
12 
13  using   namespace std;
14 
15  class List {
16  public :
17    int no;
18    string name;
19  };
20 
21  int main() {
22    vector < List > vec;
23   
24    while ( 1 ) {
25      List list;
26      cout <<   " No. = " ;
27      cin >> list.no;
28     
29      if (list.no ==   0 )
30        break ;
31   
32      cout <<   " Name = " ;
33      cin >> list.name;
34     
35      vec.push_back(list);
36    }
37   
38    vector < List > ::iterator iter = vec.begin();
39    for (; iter != vec.end(); ++ iter)
40      cout <<   " No. = "   << iter -> no <<   " , Name = "   << iter -> name << endl;
41  }


執行結果

No. =   1
Name
= clare
No.
=   2
Name
= jessie
No.
=   0
No.
=   1 , Name = clare
No.
=   2 , Name = jessie


15行

class List {
public :
 
int no;
 
string name;
};


C++提供了class取代struct。

22行

vector < List > vec;


使用vector取代linked list。

35行

vec.push_back(list);


只需簡單的使用push_back()即可動態新增。

而且vector也不用管理記憶體,不會有memory leak的問題。

See Also
(原創) 簡單的Linked List實現 (C/C++) (Data Structure) 
(原創) 如何將輸入的字串存到記憶體後,再一起印出來? (C/C++)

5.Function Pointer
function pointer出自一個很簡單的需求:『該如何將function如變數一樣傳到另外一個function?』C語言使用function pointer來達成。

C語言

1  /*  
2  (C) OOMusou 2007 http://oomusou.cnblogs.com
3 
4  Filename    : funtion_pointer.c
5  Compiler    : Visual C++ 8.0
6  Description : Demo how to use function pointer
7  Release     : 03/30/2008 1.0
8  */
9  #include < stdio.h >
10 
11  typedef int ( * predicate)( int );
12 
13  int is_odd( int i) {
14    return i %   2   ?   1 : 0 ;
15  }
16 
17  int is_even( int i) {
18    return i %   2   ?   0 : 1 ;
19  }
20 
21  void print_array( int   * beg, int   * end, predicate fn) {
22    do {
23      if (( * fn)( * beg))
24        printf( " %d " , * beg);
25    } while ( ++ beg != end);
26  }
27 
28  int main() {
29    int ia[] = { 1 , 2 , 3 };
30    int size =   sizeof (ia) /   sizeof ( int );
31   
32    printf( " Odd: " );
33    print_array(ia, ia + size, is_odd);
34    printf( " /n " );
35    printf( " Even: " );
36    print_array(ia, ia + size, is_even);
37  }


執行結果

Odd: 1   3
Even:
2  


11行

typedef int ( * predicate)( int );


利用typedef定義一個predicate型態的function pointer,傳入為int,傳出為int,雖然不一定得自行用typedef定義,但function pointer很容易寫成很複雜很難懂的程式,所以建議用typedef重新定義。

21行

void print_array( int   * beg, int   * end, predicate fn) {


宣告print_array最後一個參數為predicate這個function pointer型態,可傳入一個function pointer。

13行、17行

int is_odd( int i) {

int is_even( int i) {


為兩個符合predicate型態要求的function,因此日後可以傳入print_array()。

33行、36行

print_array(ia, ia + size, is_odd);

 

print_array(ia, ia + size, is_even);


使 用function pointer有什麼好處呢?在這兩行可以充分的看出,print_array()是不變的,若將來需求改變,如想印3的倍數,只要符合 predicate規格的function即可,在實務上,我們常利用function pointer來達成callback。

C++
C++提供了function object(functor)取代function pointer。

1  /*  
2  (C) OOMusou 2007 http://oomusou.cnblogs.com
3 
4  Filename    : funtion_object2.cpp
5  Compiler    : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
6  Description : Demo how to use function object
7  Release     : 03/30/2008 1.0
8  */
9  #include < iostream >
10 
11  using   namespace std;
12 
13  template < typename T >
14  struct is_odd {
15    bool   operator () (T i) {
16      return i % 2 ?   true : false ;
17    }
18  };
19 
20  template < typename T >
21  struct is_even {
22    bool   operator () (T i) {
23      return i % 2 ?   false : true ;
24    }
25  };
26 
27  template < typename T, typename U >
28  void print_array(T beg, T end, U fn) {
29    do {
30      if (fn( * beg))
31        cout <<   * beg <<   "   " ;
32    } while ( ++ beg != end);
33  };
34 
35  int main() {
36    int ia[] = { 1 , 2 , 3 };
37   
38    cout <<   " Odd: " ;
39    print_array(ia, ia +   3 , is_odd < int > ());
40   
41    cout << endl;
42   
43    cout <<   " Even: " ;
44    print_array(ia, ia +   3 , is_even < int > ());
45  }


執行結果

Odd: 1   3
Even:
2  


13行

template < typename T >
struct is_odd {
 
bool   operator () (T i) {
   
return i % 2 ?   true : false ;
  }
};


是 一個典型的functor,基本上是靠對operator()作overloading而來,若配合constructor還能傳參數進來,由於都是 public,所以習慣上使用struct。不一定得使用template,只是functor也支援template就是了。

27行

template < typename T, typename U >
void print_array(T beg, T end, U fn) {
 
do {
   
if (fn( * beg))
      cout
<<   * beg <<   "   " ;
  }
while ( ++ beg != end);
};


寫法幾乎和C語言一樣,只不過多了template而已,但template可用可不用。

39行、44行

print_array(ia, ia +   3 , is_odd < int > ());

 

print_array(ia, ia +   3 , is_even < int > ());


也和C語言很類似。

function object的優點在於語法較高階,若配合constructor,則比function object更強,在(原創) Function Pointer、Delegate和Function Object (C/C++) (template) (C#)有詳細的討論。

See Also
(原創) Function Pointer、Delegate和Function Object (C/C++) (template) (C#)
(原創) 如何使用Function Object? (C/C++) (STL)

接下來要談的,都是C++專屬的東西,在C沒有。一個基本的觀念:『C++的pointer最好只把它當成operator去看待,不要再用C的pointer是一個記憶體位址,指向一個變數』的思維,也就是說,* 只是個符號,代表一些特定的意義,這樣會比較容易理解。

6.Iterator (C沒有)
C++ STL的iterator,是個操作很像poiner的smart pointer(請參考(原創) iterator到底是不是pointer? (C/C++) (STL))。STL的container,就是利用iterator存取每個元素。

1  #include < vector >
2  #include < iostream >
3 
4  using   namespace std;
5 
6  int main() {
7    vector < int > ivec;
8    ivec.push_back( 1 );
9    ivec.push_back( 2 );
10    ivec.push_back( 3 );
11    ivec.push_back( 4 );
12   
13    for (vector < int > ::iterator iter = ivec.begin(); iter != ivec.end(); ++ iter)
14      cout <<   * iter << endl;
15  }


執行結果

1
2
3
4


iterator可以++、--、*、->,使用起來跟pointer一樣。

See Also
(原創) iterator到底是不是pointer? (C/C++) (STL)

7.Polymorphism (C沒有)
C++有三種物件表示方式:object, pointer, reference,C#只有object很單純,但對於最重要的多型,C++不能用object表示,這會造成object slicing,必須用pointer和reference達成。


1  /*  
2  (C) OOMusou 2007 http://oomusou.cnblogs.com
3 
4  Filename    : PolymorphismPointerReference.cpp
5  Compiler    : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
6  Description : Demo how to use pointer & reference for polymorphism
7  Release     : 03/20/2007 1.0
8  */
9  #include < iostream >
10  #include < string >
11 
12  using   namespace std;
13 
14  class Student {
15  public :
16    string name;
17   
18  protected :
19    Student() {}
20    Student( char   const * name) : name( string (name)) {}
21 
22  public :
23    virtual   string job() const   =   0 ;
24  };
25 
26  class Bachelor : public Student {
27  public :
28    Bachelor() {}
29    Bachelor( char   const * name) : Student(name) {}
30 
31  public :
32    string job() const {
33      return   " study " ;
34    }
35  };
36 
37  class Master : public Student {
38  public :
39    Master() {}
40    Master( char   const * name) : Student(name) {}
41   
42  public :
43    string job() const {
44      return   " study, research " ;
45    }
46  };
47 
48  int main() {
49    // C# : Student John = new Bachelor("John");
50    // use pointer
51    Student * John =   & Bachelor( " John " );
52    cout << John -> job() << endl;
53   
54    // use reference
55    Student & Jack = Bachelor( " Jack " );
56    cout << Jack.job() << endl;
57 
58    // C# : Student Mary = new Master("Mary"); 
59    Student * Mary =   & Master( " Mary " );
60    cout << Mary -> job() << endl;
61   
62    Master * Jeny;
63    Jeny =   new Master;
64    cout << Jeny -> job() << endl;
65  }


執行結果

study
study
study
, research


49行和58行為C#的寫法,使用object即可,但若用C++,51行為pointer寫法,55行為reference寫法,但不能使用object寫法。

See Also
(原創) 什麼是物件導向(Object Oriented)? (C/C++) (C#)


8.Polymorphism object in container (C沒有)
若要將多型的object放進container,則一定得用pointer,因為reference不能copy,這寫是C++與C#差異很大的地方。

繼續上一個多型的程式,現在我們想將這些多型的object放進vector內。

  1  /*  
  2  (C) OOMusou 2006 http://oomusou.cnblogs.com
  3 
  4  Filename    :Polymorphism.cpp
  5  Compiler    : Visual C++ 8.0 / ISO C++
  6  Description : Demo how to use Object Decomposition and Polymorphism.
  7  Release     : 01/12/2007 1.0
  8  */
  9  #include < iostream >
10  #include < vector >
11  #include < string >
12 
13  using   namespace std;
14 
15  class Student {
16  protected :
17    // constructor of abstract base class, since student
18    // can't be initiated, constructor just can be called
19    // by constructor of derived class, so put it in protected
20    // level.
21    Student( const   char * _name) : name( string (_name)) {}
22 
23  public :
24    string getName() const { return   this -> name; }
25    // pure virtual fuction
26    virtual   string job() const   =   0 ;
27 
28  private :
29    string name;
30  };
31 
32  // public inheritance
33  class Bachelor : public Student {
34  public :
35    // call constructor of abc myself.
36    Bachelor( const   char * name) : Student(name) {};
37 
38  public :
39    string job() const { return   " study " ; };
40  };
41 
42  class Master : public Student {
43  public :
44    Master( const   char * name) : Student(name) {};
45 
46  public :
47    string job() const { return   " study, research " ; };
48  };
49 
50  // new class for further
51  /*
52  class Doctor : public Student {
53  public:
54    Doctor(const char* name) : Student(name) {};
55 
56  public:
57    string job() const { return "study, research, teach"; };
58  };
59  */
60 
61  class Lab {
62  public :
63    // pass reference of student
64    void add(Student & );
65    void listAllJob() const ;
66 
67  private :
68    // put pointer of student in member vector, can't
69    // put reference in vector.
70    vector < Student *> member;
71  };
72 
73  void Lab::add(Student & student) {
74    // _student is reference of student object
75    // &_student is pointer of _student reference
76    this -> member.push_back( & student);
77  }
78 
79  void Lab::listAllJob() const {
80    // POWER of Polymorphism !!
81    // (*iter) automatically refer to derived object,
82    // this is called "dynamic binding".
83    // if you add new object in the future, you don't
84    // need to maintain this code.
85    for (vector < Student *> ::const_iterator iter =   this -> member.begin(); iter !=   this -> member.end(); ++ iter) {
86      cout << ( * iter) -> getName() <<   " 's job: "   << ( * iter) -> job() << endl;
87    }
88  }
89 
90  int main() {
91    Bachelor John( " John " );
92    Master   Mary( " Mary " );
93    // Doctor   Jack("Jack");
94 
95    Lab CSLab;
96    CSLab.add(John);
97    CSLab.add(Mary);
98    // CSLab.add(Jack);
99 
100    CSLab.listAllJob();
101  }


執行結果

John's job:study
Mary's job:study
, research


70行

private :
 
// put pointer of student in member vector, can't
 
// put reference in vector.
  vector < Student *> member;
};


member是一個要放多型物件的vector,這裡一定要用pointer,不能用reference。

79行

void Lab::add(Student & student) {
 
// _student is reference of student object
 
// &_student is pointer of _student reference
  this -> member.push_back( & student);
}


Student& student是reference,但&student是pointer,因為&在C++同時有兩個意義,要看使用位置而定。

79行

void Lab::listAllJob() const {
 
// POWER of Polymorphism !!
 
// (*iter) automatically refer to derived object,
 
// this is called "dynamic binding".
 
// if you add new object in the future, you don't
 
// need to maintain this code.
  for (vector < Student *> ::const_iterator iter =   this -> member.begin(); iter !=   this -> member.end(); ++ iter) {
    cout
<< ( * iter) -> getName() <<   " 's job: "   << ( * iter) -> job() << endl;
  }
}


由於*iter還是pointer,所以要括號後再用->,由於這是個多型的container,所以日後若有新的多型object,就再也不需要改code了,這就是多型的威力。

Conclusion
總算將全篇寫完了,也了了自己的心願,從頭到尾橫跨的時間超過一年,哈,主要是將自己學習C與C++關於pointer的心得做整理留下記錄。

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值