C++FAQ
Tuesday, February 07, 2006
為什麼我應該使用++i,而不要使用i++?
Q : 有人和我說如果我要對一個變數做增加(increment)的動作 ,我應該使用
++x 而不是使用
x++ ,這是真的嗎?
A : 如果你要做的事就只是增加 (沒有要把這個變數給(assign)別人),那麼這是個好習慣,特別是你在一個class上使用increment operator時。
// Prefix operators are preferred in cases in the following common cases: for ( ; checkstate ( x ) ; + + x ) dosomething ( x ) ; + + x ; 標準的資料型態其實是不會有效能上的差異,但是對於 class 的確是有的。原因是(大多數的實作上),postfix operator會對原本的變數做一個暫時的複製,而回傳值是以 by value 的方式回傳,不是 by reference,下面的這個例子可以說明一切:
class MyClass { public : MyClass & operator + + ( ) //Prefix increment operator (++x) { //Perform increment operation return * this ; } MyClass operator + + ( int ) //Postfix increment operator (x++) { MyClass temp = * this ; //Perform increment operation return temp ; } } ;
如何overload postfix的++以及--運算子?
Q: 如何overload postfix的++以及--運算子? (像是x++, x--) ?
A: 必須以一個
int 做為參數:
class MyClass { public : MyClass & operator + + ( ) ; //Prefix increment operator (++x) MyClass operator + + ( int ) ; //Postfix increment operator (x++) MyClass & operator - - ( ) ; //Prefix decrement operator (--x) MyClass operator - - ( int ) ; //Postfix decrement operator (x--) } ;
Operator
Thursday, February 02, 2006
傳遞參數到functions有哪幾種不同的方法?
Q : 參數是如何傳遞到function的?
A : 在C++當中有二種方式,一種是by value,一種是by reference。By value表示被呼叫者的stack會產生一份原參數的複製品,任何在此複製品上的動作都不會影響到原本function的參數。而by reference代表東西的位置會被傳遞(reference握有某個東西位置,但它表現得像是它自己就是那東西),所以被呼叫者可以直接更動最原始被傳進來的那個東西。
Q : 那麼所謂的"pass by pointer"是什麼?
A : 沒有這種東西,這是一種錯誤的概念。一個指標就是握有某個記憶體位置。你可以傳遞一個指標變數到一個function中,但這個指標變數是"pass by value"的。你可以簡單的說,如果一個function prototype的參數是有'&'的,那麼它就是pass by reference,否則就是pass by value。
Q : 我可以看些例子嗎?
A : 可以,下面是幾個例子:
第一個例子是一個整數由pass by value傳遞到function foo()中,並且將它們變大。由於一份複製品會產生,所以變大不會影響原本的變數,所以程式會輸出0 1 0。
void foo ( int i ) { i + + ; cout < < i < < endl ; } int main ( ) { int i = 0 ; cout < < i < < endl ; foo ( i ) ; cout < < i < < endl ; return 0 ; } 由於pass by value需要建立一個複製品,所以當你在傳遞東西時,如果它是個class或是struct的話,它們必須提供copy constructor才行,因為這樣才能讓function去做複製品。
下面這個例子在foo() function中使用了pass by reference,它可以真的去改變原始的參數,所以它會輸出 0 1 1 。
void foo ( int & i ) { i + + ; cout < < i < < endl ; } int main ( ) { int i = 0 ; cout < < i < < endl ; foo ( i ) ; cout < < i < < endl ; return 0 ; } 你也可以將一個指向整數的指標(即一個位置)傳遞到foo()中。指標的傳遞是pass by value (這代表如果你在foo()當中改變pointer的話不會影響在main當中的pointer值),但是由於這複製品亦是main當中數值的位置,所以我們可以直接改變該數。這個程式也會輸出0 1 1。
void foo ( int * i ) { ( * i ) + + ; cout < < * i < < endl ; } int main ( ) { int i = 0 ; cout < < i < < endl ; foo ( & i ) ; cout < < i < < endl ; return 0 ; } 當function以指標為參數時,有可能發生別人將NULL pointer傳進來的情況(各種理由都有可能),所以你最好逼免掉這樣的bug。
假設,你想要傳遞一個指標某個buffer的指標,並且希望該function(重新)為它配置記憶體空間,並且將此buffer填滿,就像這樣:
// 這程式是"錯的" void foo ( byte * buffer ) { // delete the previous buffer if ( buffer ! = NULL ) delete [ ] buffer ; // allocate new memory buffer = new byte [ 2 ] ; // fill the memory buffer [ 0 ] = 0 ; buffer [ 1 ] = 1 ; } int main ( ) { byte * buffer = NULL ; foo ( buffer ) ; delete [ ] buffer ; return 0 ; } 這將會造成錯誤,就如同前面所解釋的,由於傳遞指標是pass by value,一份指標複製品會產生,當function return回到main之後,main當中的指標仍舊會是指向原本的位置,和呼叫foo()之前一樣 (在這個例子中就是NULL)。要做到你想做的事,你必須在傳遞指標時 (即buffer的起始位置),使用pass by referene。
// 這是修正後正確的程式 void foo ( byte * & buffer ) { // delete the previous buffer if ( buffer ! = NULL ) delete [ ] buffer ; // allocate new memory buffer = new byte [ 2 ] ; // fill the memory buffer [ 0 ] = 0 ; buffer [ 1 ] = 1 ; } int main ( ) { byte * buffer = NULL ; foo ( buffer ) ; delete [ ] buffer ; return 0 ; } 這可以保證在foo()中更改指向byte陣列的指標,可以真的更動到main當中的指標值。
Monday, January 30, 2006
'this'指標是什麼?
Q: 'this'指標是什麼?
A: 一般人很容易誤以為'this'指標是一個class或struct的隱函成員。實際上它是non-static member function的隱藏參數。當你宣告一個function時,Compiler會自動在前面加上這個參數到你function的prototype中。這個參數的形別是根據你在如何宣告你的function。根據C++標準,9.3.2.1中所提到:
在nonstatic member function中,關鍵字'this'是一個non-lvalue expression,它的值是目前呼叫此function的物件之記憶體位置。若此function是宣告在class X中,則這個'this'的形別就是X*。若這個member function是被宣告為const,則此'this'的形別就是const X*,如果這個function宣告為volatile,則這個'this'的形別就是volatile X*,如果這個function被宣告為const volatile,則此'this'的形別就是const volatile X*。 比如說:
class T { public : void foo ( int a ) ; i nt goo ( ) const ; } ; 實際上是:
class T { public : void foo ( T * this , int a ) ; int goo ( const T * this ) const ; } ; Static member function不會有這個額外的參數。你不能使用non-static member function做為thread function,即使它有正確的prototype。(One consequence is that you cannot use a non-static member function as a thread function even if it has the correct prototype,不太確定對不對)
比如:
UINT ThreadFunction ( LPVOID param ) ; 因為上述的理由,會變成:
UINT ThreadFunction ( T * this , LPVOID param ) ;
Sunday, January 29, 2006
如何把數值形別變成字串形別?
Q: 如何把數值形別變成字串形別?
A: 舊時 C 的做法 (已不被建議使用): char c [ 10 ] ; // simply large enough - don't forget the // extra byte needed for the trailing '\0' int i = 1234 ; sprintf ( c , "%d" , i ) ; 可以在如MSDN查到'sprintf()'的細節。
使用 'CString': int i = 1234 ; CString cs ; cs .Format ( "%d" , i ) ; 使用的方式類似於'sprintf()'。可以在MSDN查到更多細節。
注意: 如果指定的specifiers (也就是上面'%d'的部份)與實際傳入的參數不同,將會造成不可預期的結果,'sprintf()'與'CString::Format()'都要小心這種錯誤。
C++的做法 下面的這個例子告訴你如何使用標準的C++ class來做:
#include <string> #include <sstream> #include <iostream> template < class T > std : : string to_string ( T t , std : : ios_base & ( * f ) ( std : : ios_base & ) ) { std : : ostringstream oss ; oss < < f < < t ; return oss .str ( ) ; } int main ( ) { // the second parameter of to_string() should be one of // std::hex, std::dec or std::oct std : : cout < < to_string < long > ( 123456 , std : : hex ) < < std : : endl ; std : : cout < < to_string < long > ( 123456 , std : : oct ) < < std : : endl ; return 0 ; } /* output: 1e240 361100 */ 這個方法不只非常漂亮,而且是type safe的,因為Compiler在compile time時根據運算元(operand)的形別選擇適當的'std::ostringstream::operator << ()'。
另外: (下面這段不知道怎麼翻)
There is a new method with 'boost::format', which combines the advantages of printf with the type-safety and extensibility of streams.
One of the advantages (as with printf) is you can store the entire format string as a template (not a C++ template). This is better for internationalisation (making string tables), as well as the fact that even just using a local string table can significantly reduce the code-size.
有哪些字串的形別(type)?
Q: 有哪些字串的形別(type)?
A: 'char*' -> 這也稱為C-Style string,或是叫做ANSI string.'wchar_t*' -> 這是wide character string,i.e. 'unsigned short*' 'CString' -> 這是string wrapper class,是Microsoft Foundation Classes(MFC)的一部份。'std::string' -> 這是標準的C++ class,用以包含char string。這是STL的一部份。'std::wstring' -> 這是標準的C++ class,用以包含wchar_t string。這是STL的一部份。'BSTR' -> 這是基本字串,或稱為binary string,是一個指向wide character string used by Automation data manipulation functions. (不知道怎麼翻)
字串
有哪些字串的形別(type)? '\n'與'\r\n'的差異在哪? 如何在非MFC的程式中使用'CString'? 如何給定或比較字串? 'CString'與'std::string'的差異在哪? 如何在'CString'與'std::string'之間轉換? 如何把數值形別變成字串形別? 如何把字串形別變成數值形別?
C++ Headers是什麼?
Q : 標準之前(pre-standard)的傳統(traditional) C++ header是怎麼回事?
A : 傳統的C++ header定義classes, values, macros, 以及function,並且以.h做為副檔名(extension)。包括了非標準的STL header,也是以.h做為副檔名 (iostream.h, fstream.h, vector.h, ... 諸如此類),以及過去C的header也是 (stdlib.h, stdio.h, ... 諸如此類)。標準之前的header具有全域的 namespace (Pre-standard headers have all the code in the global namespace,不太確定怎麼翻這句。)
Q : C++的標準header是怎麼回事?
A : 就是那些已經被列入了C++標準的headers。
Q : 我怎麼知道某個header是不是C++的標準header?
A : 命名的方式和傳統的header命名是一樣的,只是去掉了'.h'的副檔名。比如說,'fstream.h'變成了'fstream','memory.h'變成了'memory',諸如此類。
Q : 'iostream.h'是怎麼回事?
A : 這個'iostream.h'從未成為官方的C++標準,這代表任何實作iostream.h的C++ compiler,可以依著它想要的方式去做,而這樣的compiler仍舊會被視為與C++標準相容的。無論如何,C++標準有把input/output library列入其中。
Q : 傳統ANSI C的標準header是怎麼回事?A : 傳統的ANSI C標準header是以'c'字母為開頭。因此,如'stdio.h'就變成了'cstdio','stdlib.h'就變成了'cstdlib','math.h'就變成了'cmath','time.h'就變成了'ctime',依此類推。以下是完整的列表:<assert.h> - <cassert> <ctype.h> - <cctype> <errno.h> - <cerrno> <float.h> - <cfloat> <iso646.h> - <ciso646> <limits.h> - <climits> <locale.h> - <clocale> <math.h> - <cmath> <setjmp.h> - <csetjmp> <signal.h> - <csignal> <stdarg.h> - <cstdarg> <stddef.h> - <cstddef> <stdio.h> - <cstdio> <stdlib.h> - <cstdlib> <string.h> - <cstring> <time.h> - <ctime> <wchar.h> - <cwchar> <wtype.h> - <cwtype> Q : 哪一些是C++標準header?
A : 以下是列表:
語言支援:
Types: <cstddef> Implementation properties: <limits>, <climits>, <cfloat> Start and termination: <cstdlib> 動態記憶體管理: <new>
Type identification: <typeinfo> Exception handling: <exception> Other runtime support: <cstdarg>, <csetjmp>, <ctime>, <csignal>, <cstdlib> 偵錯:
Exception classes: <stdexcept> Assertions: <cassert> Error numbers: <cerrno> 一般公用:
Utility components: <utility> Function objects: <functional> Memory: <memory> Date and time: <ctime> 字串:
Character traits: <string> String classes: <string> Null-terminated sequence utilities: <cctype>, <cwctype>, <cstring>, <cwchar>, <cstdlib> 本地化:
Locales: <locale> C library locales: <clocale> 容器:
Sequences: <deque>, <list>, <queue>, <stack>, <vector> Associative containers: <map>, <set> Bitset: <bitset> Iterators: <iterator> 演算法:
Non-modifying sequence operations: <algorithm> C library algorithms: <cstdlib> 數值:
Complex numbers: <complex> Numeric arrays: <valarray> Generalized numeric operations: <numeric> C library: <cmath>, <cstdlib> 輸入/輸出:
Forward declarations: <iosfwd> Standard iostream objects: <iostream> Iostreams base classes: <ios> Stream buffers: <streambuf> Formatting and manipulators: <istream>, <ostream>, <iomanip> String streams: <sstream>, <cstdlib> File streams: <fstream>, <cstdio>, <cwchar> Q : 我仍舊可以使用舊版的C header嗎 (stdlib.h, stdio.h, etc.)?
A : 所有與C++相容的compiler都支援上述那18個以'.h'做為結尾的header檔,即使它們是不被建議使用的 (stdlib.h, stdio.h, etc.)。這些*.h的 C header檔都是global namespace。使用這些版本的C header仍舊被視為具有可攜性的 (portability),但它有可能與未來的標準不相容。無論如何,這些header在未來不太完全可能被官方標準移除。
Q : 我仍舊可以使用那些非標準的,以'.h'做為副檔名版本的STL header嗎 (iostream.h, fstream.h, vector.h, etc.) ?
A : 這樣的STL header從未成為C++官方標準,大部份的C++ Compiler支援這樣非標準的header,也有些過時的Compiler(如Turbo C++)只支援這樣非標準的寫法。有些更新穎的Compiler,如VC++ 7.1已經不再支援這樣非標準的寫法。除非你必須要使用這些過時的Compiler,否則的話最好是使用標準的寫法 (<iostream>, <fstream>, <vector>
, etc.)。使用*.h的寫法不好。 另外: 在某些Compiler上,你可以發現它同時有'iostream'以及'iostream.h'。如上所述,在include 'iostream'時那些成員(members)是在std namespace,而'iostream.h'是在global namespace。這可能會在某些時後造成混淆(ambiguities),namespace的設計就是為了避免這樣的情況。這也是建議不要使用*.h的原因之一。
到底ntohl()與htonl()做了什麼?
Q : 到底ntohl()與htonl()做了什麼?
A : 'ntohl()'與'htonl()'是四個相關function中的其中二個,另外二個是'ntohs()'與'htons()',以下是Linux manual中的解釋:
'htonl()' function將unsigned integer hostlong從host byte order轉為network byte order. 'htons()' function將unsigned short integer hostshort從host byte order轉為network byte order. 'htohl()' function將unsigned integer netlong從network byte order轉為host byte order. 'ntohs()' function將unsigned short integer netshort從network byte order轉為host byte order. 這四個function在host byte order與network byte order之間做轉換。當二者的byte order不同時,則使用這些function會造成endian-ness的改變。當二者的byte ordre一樣時,則不會有任何的改變。因此,當你只是單純想要做endian-ness的轉換(無關乎平台),則不該使用這些function。
這一篇FAQ 告訴你如何做無關乎平台的endian-ness轉換。
Special notes: 'ntohs()', 'ntohl()', 'htons()', and 'htonl()' 並非C的標準函式,所以不能保證portability。 POSIX 實作'ntohs()', 'ntohl()', 'htons()' and 'htonl()' 時,參數是以'uint16_t' 與 'uint32_t'做為type,這二個type可以在netinet/in.h 這二個header file中找到。 Windows實作使用'unsigned short'與'unsigned long',可以在winsock2.h 這個header file中找到。 'ntoht ()' 與 'htont ()' 在其它的系統上可能還有著不同的變形,像是'ntohi()'/'htoni()' 或 'ntohll()'/'htonll()'.
我如何轉換Big-Endian與Little-Endian的格式?
Q : 我如何轉換Big-Endian與Little-Endian的格式?
A :
轉換的動作是可逆的 (Big-Endian <-> Little-Endian),下面這個例子可以用來轉換unsigned data types.
inline void endian_swap ( unsigned short & x ) { x = ( x > > 8 ) | ( x < < 8 ) ; } inline void endian_swap ( unsigned int & x ) { x = ( x > > 24 ) | ( ( x < < 8 ) & 0x00FF0000 ) | ( ( x > > 8 ) & 0x0000FF00 ) | ( x < < 24 ) ; } // __int64 for MSVC, "long long" for gcc inline void endian_swap ( unsigned __int64 & x ) { x = ( x > > 56 ) | ( ( x < < 40 ) & 0x00FF000000000000 ) | ( ( x < < 24 ) & 0x0000FF0000000000 ) | ( ( x < < 8 ) & 0x000000FF00000000 ) | ( ( x > > 8 ) & 0x00000000FF000000 ) | ( ( x > > 24 ) & 0x0000000000FF0000 ) | ( ( x > > 40 ) & 0x000000000000FF00 ) | ( x < < 56 ) ; } Q : 為何不直接使用ntohl()與htonl()就好了?
A : 這二個function:
不一定會改變endian-ness 不一定具有可攜性 (portability) 更多的資訊,請看
到底ntohl()與htonl()做了什麼?
General
C++有什麼推薦的好書? 我如何轉換Big-Endian與Little-Endian的格式? 到底ntohl()與htonl()做了什麼? 如何宣告與使用二維陣列? 不同的數字表示法差異何在? 浮點數是怎麼表示的? 如何處理references? 物件導向設計(OOD)的概念精神是什麼? Inline Function與Macros的差異何在? 'this'指標是什麼? 傳遞參數到functions有哪幾種不同的方法? C++ Headers是什麼? 一個class有什麼已經被隱涵定義好的成員?
C++ FAQ Sections
General Preprocessor MemoryManagement Operator ExceptionHandling Structure Casting Callback Template Polymorphism DesignPattern Profiling RandomNumber String
C++ FAQ
The purpose I made this Blog is that when I see the
C++ FAQ in CodeGuru , I think it is very useful for any newbie who is studying C++, so I want to translate it into Chinese. I am a C++ newbie too, I will try to do my best to translate the FAQ, and any comment is appreciated.
Archives
January 2006
February 2006