當寫C語言寫多了,自然就喜歡C++了----小話c++(1)
[Mac 10.7.1 Lion x64 Intel-based gcc4.2.1 xcode4.2]
Q: 解釋下標題吧。
A: 依稀記得,寫一個數值絕對值的函式時,寫到第三個,實在感覺很痛苦,重複了這麼多遍,立刻體會了過載和STL的重要意義。
int abs(int n)
{
return n < 0 ? -n : n;
}
long abs_long(long n)
{
return n < 0 ? -n : n;
}
double abs_double(double n)
{
return n < 0 ? -n : n;
}
寫到第三個函式的時候,感覺實在不爽,原來c++中的過載這麼現實,很清楚c程式設計師的痛苦之處;對於基本型別,很多絕對值操作都是類似的,為什麼還要寫這麼多函式?寫程式碼不是比誰寫的多,是比簡潔易懂和穩定, STL更明白寫上面程式碼的痛苦。template<class T>
T abs(T a)
{
return a < 0 ? -a : a;
}
當然,也可以用巨集來實現,不過不是很推薦:#define ABS(a) ((a) < 0 ? (-a) : (a))
不小心就可能有問題:std::cout << ABS(-12.4) << std::endl;
編譯提示:error C2105: '--' needs l-value
哦,原來ABS(-12.4)被巨集替換成了--12.4.巨集有的時候真得小心啊...#define ABS(a) ((a) < 0 ? -(a) : (a))
這樣編譯就ok了。另外,還有,用c語言的時候,經常會寫到一個結構體以及對結構體的操作,必須申請空間來構造某個結構,寫了N個malloc,直到每次寫malloc都有種想吐的感覺,c++明白了這個痛苦之處,建構函式讓寫malloc到吐的程式設計師迅速愛上c++.
還有很多地方是c++對於c語言的一些改進,這裡不一一介紹了。
Q: 在c++中,使用cstring標頭檔案和string.h有什麼區別?
A: 先來看兩個例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));
#define PRINT_STR(str) printf(#str" is %s\n", (str));
#define FOR_EVER() { while(1) ; }
int main()
{
size_t len = ::strlen("hello");
return 0;
}
如上,儲存為std_strlen.c, 編譯:error: expected expression [1]
size_t len = ::strlen("hello");
^
1 error generated.
可以看出,c語言中並沒有作用域運算子,這裡編譯錯誤;#include <iostream>
#include <cstring>
int main()
{
size_t len = ::strlen("hello");
return 0;
}
儲存為main.cpp,編譯,沒出現什麼問題。這裡就體現了cstring和string.h的不同之處:c++支援了作用域運算子::, 原先c庫中的函式被預設當成c++中全域性作用域的函式,當然也是std作用域的函式。如下:
len = std::strlen("hello");
上面的程式碼依然可以編譯通過。現在再看看cstring和string.h標頭檔案裡面的內容:cstring標頭檔案(部分):
_GLIBCXX_BEGIN_NAMESPACE(std)
using ::memcpy;
using ::memmove;
using ::strcpy;
using ::strncpy;
using ::strcat;
using ::strncat;
using ::memcmp;
using ::strcmp;
using ::strcoll;
using ::strncmp;
using ::strxfrm;
using ::strcspn;
using ::strspn;
......
......
_GLIBCXX_END_NAMESPACE
string.h標頭檔案(部分):
__BEGIN_DECLS
void *memchr(const void *, int, size_t);
int memcmp(const void *, const void *, size_t);
void *memcpy(void *, const void *, size_t);
void *memmove(void *, const void *, size_t);
void *memset(void *, int, size_t);
char *strcat(char *, const char *);
char *strchr(const char *, int);
int strcmp(const char *, const char *);
int strcoll(const char *, const char *);
char *strcpy(char *, const char *);
size_t strcspn(const char *, const char *);
char *strerror(int) __DARWIN_ALIAS(strerror);
size_t strlen(const char *);
char *strncat(char *, const char *, size_t);
int strncmp(const char *, const char *, size_t);
char *strncpy(char *, const char *, size_t);
char *strpbrk(const char *, const char *);
char *strrchr(const char *, int);
size_t strspn(const char *, const char *);
char *strstr(const char *, const char *);
char *strtok(char *, const char *);
size_t strxfrm(char *, const char *, size_t);
__END_DECLS
Q: c++中的引用到底和類似功能的指標有什麼不同?
A: 從本質上來說,基本沒什麼不同;從使用上來看,是有不同的。如下例子:
#include <iostream>
#include <cstring>
#define COUT_ENDL(str) std::cout << #str << " is " << (str) << std::endl;
void swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
void swap(int *pa, int *pb)
{
int temp = *pa;
*pa = *pb;
*pb = temp;
}
int main()
{
int a = 1, b = 2;
swap(a, b);
COUT_ENDL(a)
COUT_ENDL(b)
a = 1, b = 2;
swap(&a, &b);
COUT_ENDL(a)
COUT_ENDL(b)
return 0;
}
儲存為main.cpp.void swap(int &a, int &b);函式的彙編如下:
0x0000000100000c00 <_Z4swapRiS_+0>: push %rbp
0x0000000100000c01 <_Z4swapRiS_+1>: mov %rsp,%rbp
0x0000000100000c04 <_Z4swapRiS_+4>: mov %rdi,-0x8(%rbp)
0x0000000100000c08 <_Z4swapRiS_+8>: mov %rsi,-0x10(%rbp)
0x0000000100000c0c <_Z4swapRiS_+12>: mov -0x8(%rbp),%rsi
0x0000000100000c10 <_Z4swapRiS_+16>: mov (%rsi),%eax
0x0000000100000c12 <_Z4swapRiS_+18>: mov %eax,-0x14(%rbp)
0x0000000100000c15 <_Z4swapRiS_+21>: mov -0x10(%rbp),%rsi
0x0000000100000c19 <_Z4swapRiS_+25>: mov (%rsi),%eax
0x0000000100000c1b <_Z4swapRiS_+27>: mov -0x8(%rbp),%rsi
0x0000000100000c1f <_Z4swapRiS_+31>: mov %eax,(%rsi)
0x0000000100000c21 <_Z4swapRiS_+33>: mov -0x14(%rbp),%eax
0x0000000100000c24 <_Z4swapRiS_+36>: mov -0x10(%rbp),%rsi
0x0000000100000c28 <_Z4swapRiS_+40>: mov %eax,(%rsi)
0x0000000100000c2a <_Z4swapRiS_+42>: pop %rbp
0x0000000100000c2b <_Z4swapRiS_+43>: retq
void swap(int *pa, int *pb);函式的彙編如下:
0x0000000100000c30 <_Z4swapPiS_+0>: push %rbp
0x0000000100000c31 <_Z4swapPiS_+1>: mov %rsp,%rbp
0x0000000100000c34 <_Z4swapPiS_+4>: mov %rdi,-0x8(%rbp)
0x0000000100000c38 <_Z4swapPiS_+8>: mov %rsi,-0x10(%rbp)
0x0000000100000c3c <_Z4swapPiS_+12>: mov -0x8(%rbp),%rsi
0x0000000100000c40 <_Z4swapPiS_+16>: mov (%rsi),%eax
0x0000000100000c42 <_Z4swapPiS_+18>: mov %eax,-0x14(%rbp)
0x0000000100000c45 <_Z4swapPiS_+21>: mov -0x10(%rbp),%rsi
0x0000000100000c49 <_Z4swapPiS_+25>: mov (%rsi),%eax
0x0000000100000c4b <_Z4swapPiS_+27>: mov -0x8(%rbp),%rsi
0x0000000100000c4f <_Z4swapPiS_+31>: mov %eax,(%rsi)
0x0000000100000c51 <_Z4swapPiS_+33>: mov -0x14(%rbp),%eax
0x0000000100000c54 <_Z4swapPiS_+36>: mov -0x10(%rbp),%rsi
0x0000000100000c58 <_Z4swapPiS_+40>: mov %eax,(%rsi)
0x0000000100000c5a <_Z4swapPiS_+42>: pop %rbp
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等變數的宣告:
extern istream cin; ///< Linked to standard input
extern ostream cout; ///< Linked to standard output
extern ostream cerr; ///< Linked to standard error (unbuffered)
extern ostream clog; ///< Linked to standard error (buffered)
Q: 對於虛擬函式可以實現動態繫結,可以認為c++是動態語言嗎?
A: 按照動態語言的基本概念,標準c++算不上,它依然是要求比較嚴格的編譯型語言。對於動態繫結,c++也僅僅用靜態的方式實現了略微有些動態特性的功能。實現動態繫結,一般都有虛表的支援,編譯器會根據類以及繼承體系中虛擬函式函式的名稱,組裝出一個虛表,然後根據呼叫程式碼的具體含義簡單地呼叫對應虛表位置的函式。雖然,表面看起來很單純,實際上,編譯器早可以計算出實際呼叫的是什麼(當然除了引數為基類指標的單獨函式除外,這也是為什麼要有虛表的原因之一).
如果這個特性可以看成動態的話,那麼c++也就是具備了一點點動態特性的語言而已。
xichen
2012-5-30 13:07:59
相關文章
- C++重寫C++
- 編譯warp,d語言寫的c/c++前處理器.編譯C++
- C語言C++學到什麼程度可以寫遊戲輔助?C語言C++遊戲
- C/C++ 檔案讀寫C++
- 1138:將字串中的小寫字母轉換成大寫字母(C C++)字串C++
- C++讀寫檔案C++
- C++寫檔案操作C++
- C++檔案讀寫C++
- C++ STL的go語言版本,歡迎各位大佬完善C++Go
- 聊聊C語言/C++—程式和程式語言C語言C++
- C++對C語言的擴充套件(1)--引用C++C語言套件
- 細學C++之C++語言的特點C++
- 試題 演算法提高 小寫轉換為大寫 C++演算法C++
- C++語法小技巧C++
- C/C++如何寫除錯宏C++除錯
- 咋就這麼不喜歡手寫路由呢路由
- C++檔案讀寫操作C++
- c++中的讀寫鎖C++
- C++讀寫檔案操作C++
- 「趣圖」這是用 e 語言寫的 C++ 程式碼,客官請慢用C++
- C++當前日期加1天C++
- C++和c語言的分別C++C語言
- C/C++語言的學習方向C++
- C/C++語言精髓 *和&詳解C++
- Python呼叫C++編寫的方法PythonC++
- 如何編寫 C++ 遊戲引擎C++遊戲引擎
- json的使用(python寫,c++讀)JSONPythonC++
- mfc 讀寫 excel 示例 C++ libxlExcelC++
- C++ hpp檔案的編寫C++
- C++手寫記憶體池C++記憶體
- Redpanda:用C++重寫的KafkaC++Kafka
- C++ 的指令碼語言:ChaiScriptC++指令碼AI
- C語言:將字串中所有小寫字母轉為大寫字母C語言字串
- C++學習筆記-C++對C語言的函式擴充C++筆記C語言函式
- Dev C++編寫C/C++程式 出現[Error] ld returned 1 exit status報錯分析及解決devC++Error
- 自己動手寫Vector【Cherno C++教程】C++
- C++ 類别範本的寫法C++
- C++語言菜鳥快速入門C++
- 一、程式語言簡介與C++C++