(原創) C/C++哪些地方會用到pointer呢? (C/C++) (C)

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

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

C C++
1.Pass by Address Reference
2.Pass Array to Function Vector
3.char * std::string
4.Dynamic Allocation
(malloc(),linked list)
STL container
5.Function Pointer Function Object
6.N/A Iterator
7.N/A Polymorphism
8.N/A Polymorphism 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的心得做整理留下記錄。

posted on 2008-07-21 15:47  真 OO无双  阅读(26569)  评论(9编辑  收藏  举报

导航