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達到此需求。
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 = 2 y = 1
C++
C++提出了reference達成pass by address,使程式可讀性更高。
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 = 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。
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 }
執行結果
1
2
C++
array本身有很多缺點,C++建議用STL的vector取代array。
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行
vector < int > ivec(ia, ia + sizeof (ia) / sizeof ( int ));
只是個便宜行事的寫法,因為vector本身並沒有如boost::array提供initializer,所以借用了array的initializer,再由array轉vector。實務上是用pushback()將資料塞進vector。
重點應該放在14行
只需傳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語言在操作字串時含其他語言差異甚大。
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行
字串相加,並沒有如期他語言很直覺的用s1 = s1 + s2;而得用strcat(),且s1和s2都是pointer。
23行
puts( " s3 and s4 are not the same " );
else
puts( " s3 and s4 are the same " );
比較字串是否相同,也必須用strcmp()比較兩個pointer所指向的string是否相同才可比較,不可以用
因為s3和s4都是pointer,這樣是比較兩個pointer是否相同,而不是比較string是否相同,這和其他語言差異甚大,也和不符合一般人直覺。
C++
C++的STL提供了string,改進了C的char *,用法非常直覺。
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 }
執行結果
s3 and s4 are the same
18行
字串相加,只要直覺的相加即可,符合大部分語言的習慣。
25行
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。
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 }
執行結果
Name = clare
No. = 2
Name = jessie
No. = 0
No. = 1 , Name = clare
No. = 2 , Name = jessie
39行
使用了malloc()動態新增linked list的node。
64行
current = head;
while (current != NULL) {
prev = current;
current = current -> next;
free(prev);
}
由於malloc()使用了heap上的記憶體,必須手動使用free()將記憶體釋放,否則會造成memory leak。
C++
vector本身就可以動態擴張,而且又會自動釋放記憶體,因此可以取代linked list。
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 }
執行結果
Name = clare
No. = 2
Name = jessie
No. = 0
No. = 1 , Name = clare
No. = 2 , Name = jessie
15行
public :
int no;
string name;
};
C++提供了class取代struct。
22行
使用vector取代linked list。
35行
只需簡單的使用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語言
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 }
執行結果
Even: 2
11行
利用typedef定義一個predicate型態的function pointer,傳入為int,傳出為int,雖然不一定得自行用typedef定義,但function pointer很容易寫成很複雜很難懂的程式,所以建議用typedef重新定義。
21行
宣告print_array最後一個參數為predicate這個function pointer型態,可傳入一個function pointer。
13行、17行
為兩個符合predicate型態要求的function,因此日後可以傳入print_array()。
33行、36行
使 用function pointer有什麼好處呢?在這兩行可以充分的看出,print_array()是不變的,若將來需求改變,如想印3的倍數,只要符合 predicate規格的function即可,在實務上,我們常利用function pointer來達成callback。
C++
C++提供了function object(functor)取代function pointer。
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 }
執行結果
Even: 2
13行
struct is_odd {
bool operator () (T i) {
return i % 2 ? true : false ;
}
};
是 一個典型的functor,基本上是靠對operator()作overloading而來,若配合constructor還能傳參數進來,由於都是 public,所以習慣上使用struct。不一定得使用template,只是functor也支援template就是了。
27行
void print_array(T beg, T end, U fn) {
do {
if (fn( * beg))
cout << * beg << " " ;
} while ( ++ beg != end);
};
寫法幾乎和C語言一樣,只不過多了template而已,但template可用可不用。
39行、44行
也和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存取每個元素。
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 }
執行結果
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達成。
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 , research
49行和58行為C#的寫法,使用object即可,但若用C++,51行為pointer寫法,55行為reference寫法,但不能使用object寫法。
(原創) 什麼是物件導向(Object Oriented)? (C/C++) (C#)
8.Polymorphism object in container (C沒有)
若要將多型的object放進container,則一定得用pointer,因為reference不能copy,這寫是C++與C#差異很大的地方。
繼續上一個多型的程式,現在我們想將這些多型的object放進vector內。
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 }
執行結果
Mary's job:study , research
70行
// put pointer of student in member vector, can't
// put reference in vector.
vector < Student *> member;
};
member是一個要放多型物件的vector,這裡一定要用pointer,不能用reference。
79行
// _student is reference of student object
// &_student is pointer of _student reference
this -> member.push_back( & student);
}
Student& student是reference,但&student是pointer,因為&在C++同時有兩個意義,要看使用位置而定。
79行
// 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的心得做整理留下記錄。