程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 當寫C語言寫多了,自然就喜歡C++了----小話C++(1)

當寫C語言寫多了,自然就喜歡C++了----小話C++(1)

編輯:C++入門知識

[Mac  10.7.1  Lion  x64  Intel-based  gcc4.2.1  xcode4.2]

Q: 解釋下標題吧。
A: 依稀記得,寫一個數值絕對值的函數時,寫到第三個,實在感覺很痛苦,重復了這麼多遍,立刻體會了重載和STL的重要意義。
[cpp]
1. int abs(int n) 
2. { 
3.     return n < 0 ? -n : n; 
4. } 
5.  
6. long    abs_long(long n) 
7. { 
8.     return n < 0 ? -n : n; 
9. } 
10.  
11. double  abs_double(double n) 
12. { 
13.     return n < 0 ? -n : n; 
14. } 
寫到第三個函數的時候,感覺實在不爽,原來c++中的重載這麼現實,很清楚c程序員的痛苦之處;對於基本類型,很多絕對值操作都是類似的,為什麼還要寫這麼多函數?寫代碼不是比誰寫的多,是比簡潔易懂和穩定, STL更明白寫上面代碼的痛苦。
[cpp]
1. template<class T> 
2. T   abs(T a) 
3. { 
4.     return a < 0 ? -a : a; 
5. } 
當然,也可以用宏來實現,不過不是很推薦:
[cpp]
1. #define ABS(a)  ((a) < 0 ? (-a) : (a)) 
不小心就可能有問題:
[cpp]
1. std::cout << ABS(-12.4) << std::endl; 
編譯提示:
[cpp]
1. error C2105: '--' needs l-value 
哦,原來ABS(-12.4)被宏替換成了--12.4.宏有的時候真得小心啊...
[cpp]
1. #define ABS(a)  ((a) < 0 ? -(a) : (a)) 
這樣編譯就ok了。
另外,還有,用c語言的時候,經常會寫到一個結構體以及對結構體的操作,必須申請空間來構造某個結構,寫了N個malloc,直到每次寫malloc都有種想吐的感覺,c++明白了這個痛苦之處,構造函數讓寫malloc到吐的程序員迅速愛上c++.
  還有很多地方是c++對於c語言的一些改進,這裡不一一介紹了。

Q: 在c++中,使用cstring頭文件和string.h有什麼區別?
A: 先來看兩個例子:
[cpp]
1. #include <stdio.h> 
2. #include <stdlib.h> 
3. #include <string.h> 
4.  
5. #define PRINT_D(intValue)       printf(#intValue" is %d\n", (intValue)); 
6. #define PRINT_STR(str)          printf(#str" is %s\n", (str)); 
7. #define FOR_EVER()              { while(1) ; } 
8.  
9. int main() 
10. { 
11.     size_t len = ::strlen("hello"); 
12.     return 0; 
13. } 
如上,保存為std_strlen.c, 編譯:
[cpp]
1. error: expected expression [1] 
2.      size_t len = ::strlen("hello"); 
3.                            ^ 
4. 1 error generated. 
可以看出,c語言中並沒有作用域運算符,這裡編譯錯誤;
[cpp]
1. #include <iostream> 
2. #include <cstring> 
3.  
4. int main() 
5. { 
6.     size_t len = ::strlen("hello"); 
7.     return 0; 
8. } 
保存為main.cpp,編譯,沒出現什麼問題。
這裡就體現了cstring和string.h的不同之處:c++支持了作用域運算符::, 原先c庫中的函數被默認當成c++中全局作用域的函數,當然也是std作用域的函數。如下:
[cpp]
1. len = std::strlen("hello"); 
上面的代碼依然可以編譯通過。現在再看看cstring和string.h頭文件裡面的內容:
cstring頭文件(部分):
[cpp]
1. _GLIBCXX_BEGIN_NAMESPACE(std) 
2.  
3.   using ::memcpy; 
4.   using ::memmove; 
5.   using ::strcpy; 
6.   using ::strncpy; 
7.   using ::strcat; 
8.   using ::strncat; 
9.   using ::memcmp; 
10.   using ::strcmp; 
11.   using ::strcoll; 
12.   using ::strncmp; 
13.   using ::strxfrm; 
14.   using ::strcspn; 
15.   using ::strspn; 
16.   ...... 
17.   ...... 
18.  
19.   _GLIBCXX_END_NAMESPACE 

string.h頭文件(部分):
[cpp]
1. __BEGIN_DECLS 
2. void    *memchr(const void *, int, size_t); 
3. int  memcmp(const void *, const void *, size_t); 
4. void    *memcpy(void *, const void *, size_t); 
5. void    *memmove(void *, const void *, size_t); 
6. void    *memset(void *, int, size_t); 
7. char    *strcat(char *, const char *); 
8. char    *strchr(const char *, int); 
9. int  strcmp(const char *, const char *); 
10. int  strcoll(const char *, const char *); 
11. char    *strcpy(char *, const char *); 
12. size_t   strcspn(const char *, const char *); 
13. char    *strerror(int) __DARWIN_ALIAS(strerror); 
14. size_t   strlen(const char *); 
15. char    *strncat(char *, const char *, size_t); 
16. int  strncmp(const char *, const char *, size_t); 
17. char    *strncpy(char *, const char *, size_t); 
18. char    *strpbrk(const char *, const char *); 
19. char    *strrchr(const char *, int); 
20. size_t   strspn(const char *, const char *); 
21. char    *strstr(const char *, const char *); 
22. char    *strtok(char *, const char *); 
23. size_t   strxfrm(char *, const char *, size_t); 
24. __END_DECLS 

Q: c++中的引用到底和類似功能的指針有什麼不同?
A: 從本質上來說,基本沒什麼不同;從使用上來看,是有不同的。如下例子:
[cpp]
1. #include <iostream> 
2. #include <cstring> 
3.  
4. #define COUT_ENDL(str)  std::cout << #str << " is " << (str) << std::endl; 
5.  
6. void    swap(int &a, int &b) 
7. { 
8.     int temp = a; 
9.     a = b; 
10.     b = temp; 
11. } 
12.  
13. void    swap(int *pa, int *pb) 
14. { 
15.     int temp = *pa; 
16.     *pa = *pb; 
17.     *pb = temp; 
18. } 
19.  
20.  
21. int main() 
22. { 
23.     int a = 1, b = 2; 
24.     swap(a, b); 
25.     COUT_ENDL(a) 
26.     COUT_ENDL(b) 
27.      
28.     a = 1, b = 2; 
29.     swap(&a, &b); 
30.     COUT_ENDL(a) 
31.     COUT_ENDL(b) 
32.      
33.     return 0; 
34. } 
保存為main.cpp.
void swap(int &a, int &b);函數的匯編如下:
[cpp]
1. 0x0000000100000c00 <_Z4swapRiS_+0>:   push   %rbp 
2. 0x0000000100000c01 <_Z4swapRiS_+1>:   mov    %rsp,%rbp 
3. 0x0000000100000c04 <_Z4swapRiS_+4>:   mov    %rdi,-0x8(%rbp) 
4. 0x0000000100000c08 <_Z4swapRiS_+8>:   mov    %rsi,-0x10(%rbp) 
5. 0x0000000100000c0c <_Z4swapRiS_+12>:  mov    -0x8(%rbp),%rsi 
6. 0x0000000100000c10 <_Z4swapRiS_+16>:  mov    (%rsi),%eax 
7. 0x0000000100000c12 <_Z4swapRiS_+18>:  mov    %eax,-0x14(%rbp) 
8. 0x0000000100000c15 <_Z4swapRiS_+21>:  mov    -0x10(%rbp),%rsi 
9. 0x0000000100000c19 <_Z4swapRiS_+25>:  mov    (%rsi),%eax 
10. 0x0000000100000c1b <_Z4swapRiS_+27>:  mov    -0x8(%rbp),%rsi 
11. 0x0000000100000c1f <_Z4swapRiS_+31>:  mov    %eax,(%rsi) 
12. 0x0000000100000c21 <_Z4swapRiS_+33>:  mov    -0x14(%rbp),%eax 
13. 0x0000000100000c24 <_Z4swapRiS_+36>:  mov    -0x10(%rbp),%rsi 
14. 0x0000000100000c28 <_Z4swapRiS_+40>:  mov    %eax,(%rsi) 
15. 0x0000000100000c2a <_Z4swapRiS_+42>:  pop    %rbp 
16. 0x0000000100000c2b <_Z4swapRiS_+43>:  retq  

void swap(int *pa, int *pb);函數的匯編如下:
[cpp]
1. 0x0000000100000c30 <_Z4swapPiS_+0>:   push   %rbp 
2. 0x0000000100000c31 <_Z4swapPiS_+1>:   mov    %rsp,%rbp 
3. 0x0000000100000c34 <_Z4swapPiS_+4>:   mov    %rdi,-0x8(%rbp) 
4. 0x0000000100000c38 <_Z4swapPiS_+8>:   mov    %rsi,-0x10(%rbp) 
5. 0x0000000100000c3c <_Z4swapPiS_+12>:  mov    -0x8(%rbp),%rsi 
6. 0x0000000100000c40 <_Z4swapPiS_+16>:  mov    (%rsi),%eax 
7. 0x0000000100000c42 <_Z4swapPiS_+18>:  mov    %eax,-0x14(%rbp) 
8. 0x0000000100000c45 <_Z4swapPiS_+21>:  mov    -0x10(%rbp),%rsi 
9. 0x0000000100000c49 <_Z4swapPiS_+25>:  mov    (%rsi),%eax 
10. 0x0000000100000c4b <_Z4swapPiS_+27>:  mov    -0x8(%rbp),%rsi 
11. 0x0000000100000c4f <_Z4swapPiS_+31>:  mov    %eax,(%rsi) 
12. 0x0000000100000c51 <_Z4swapPiS_+33>:  mov    -0x14(%rbp),%eax 
13. 0x0000000100000c54 <_Z4swapPiS_+36>:  mov    -0x10(%rbp),%rsi 
14. 0x0000000100000c58 <_Z4swapPiS_+40>:  mov    %eax,(%rsi) 
15. 0x0000000100000c5a <_Z4swapPiS_+42>:  pop    %rbp 
16. 0x0000000100000c5b <_Z4swapPiS_+43>:  retq   

可以看出,兩段匯編代碼完全一致。其實也可以這麼理解,編譯器對於引用其實就是默認看成傳指針,當然這取決於編譯器,不能確定的是所有使用引用和指針方式的代碼的匯編代碼都一致,但是至少它們最終完成的功能是一致的。

Q: 對於像上面的代碼,同為swap函數,編譯器最終如何將它們區分開?
A: 既然有不同,編譯器自然能把它們區分開。正如上面的匯編中顯示的,第一個swap函數在編譯器內部的名稱是_Z4swapRiS_,第二個swap函數的名稱是_Z4swapPiS_.
使用nm命令查看生成的可執行文件內部的符號表(在這裡工程默認生成的可執行為testForCpp):
 \

可以看出,確實存在這兩個名稱。對於為什麼會是這樣的名字,這裡只能提供一個常用的命名規則,一般會采用"返回值+(命名空間)+函數名+參數形式",具體對於不同編譯器處理不盡相同。

Q: c++中的輸入輸出,cout, cin到底和printf、scanf函數有什麼區別?
A: 從一個角度來說,cout和cin是對象;printf和scanf是函數;另外一個角度來說,cout的效率很可能比printf函數要低,因為它內部封裝了許多函數,為了安全原因或者模塊化的考慮,而printf相對比較直接。對於cout是否調用printf函數,應該說不能完全確定,盡管有說法是如此,也許是兼容的原因,不同平台應該有不同的考慮。
如下是cout等變量的聲明:
[cpp]
1. extern istream cin;     ///< Linked to standard input 
2. extern ostream cout;        ///< Linked to standard output 
3. extern ostream cerr;        ///< Linked to standard error (unbuffered) 
4. extern ostream clog;        ///< Linked to standard error (buffered) 

Q: 對於虛函數可以實現動態綁定,可以認為c++是動態語言嗎?
A: 按照動態語言的基本概念,標准c++算不上,它依然是要求比較嚴格的編譯型語言。對於動態綁定,c++也僅僅用靜態的方式實現了略微有些動態特性的功能。實現動態綁定,一般都有虛表的支持,編譯器會根據類以及繼承體系中虛函數函數的名稱,組裝出一個虛表,然後根據調用代碼的具體含義簡單地調用對應虛表位置的函數。雖然,表面看起來很單純,實際上,編譯器早可以計算出實際調用的是什麼(當然除了參數為基類指針的單獨函數除外,這也是為什麼要有虛表的原因之一).
如果這個特性可以看成動態的話,那麼c++也就是具備了一點點動態特性的語言而已。

xichen

 

 

摘自 陳曦的分享

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved