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

  1. 如何overload postfix的++以及--運算子?
  2. 為什麼我應該使用++i,而不要使用i++?

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);
int 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:

 

字串

  1. 有哪些字串的形別(type)?
  2. '\n'與'\r\n'的差異在哪?
  3. 如何在非MFC的程式中使用'CString'?
  4. 如何給定或比較字串?
  5. 'CString'與'std::string'的差異在哪?
  6. 如何在'CString'與'std::string'之間轉換?
  7. 如何把數值形別變成字串形別?
  8. 如何把字串形別變成數值形別?

 

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',依此類推。以下是完整的列表:




Q: 哪一些是C++標準header?

A: 以下是列表:

語言支援:
動態記憶體管理: <new>
偵錯:
一般公用:
字串:
本地化:
容器:
演算法:
數值:
輸入/輸出:

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中的解釋:


這四個function在host byte order與network byte order之間做轉換。當二者的byte order不同時,則使用這些function會造成endian-ness的改變。當二者的byte ordre一樣時,則不會有任何的改變。因此,當你只是單純想要做endian-ness的轉換(無關乎平台),則不該使用這些function。這一篇FAQ告訴你如何做無關乎平台的endian-ness轉換。

Special notes:

 

我如何轉換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:


更多的資訊,請看到底ntohl()與htonl()做了什麼?

 

General

  1. C++有什麼推薦的好書?
  2. 我如何轉換Big-Endian與Little-Endian的格式?
  3. 到底ntohl()與htonl()做了什麼?
  4. 如何宣告與使用二維陣列?
  5. 不同的數字表示法差異何在?
  6. 浮點數是怎麼表示的?
  7. 如何處理references?
  8. 物件導向設計(OOD)的概念精神是什麼?
  9. Inline Function與Macros的差異何在?
  10. 'this'指標是什麼?
  11. 傳遞參數到functions有哪幾種不同的方法?
  12. C++ Headers是什麼?
  13. 一個class有什麼已經被隱涵定義好的成員?

 

C++ FAQ Sections


 

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  

This page is powered by Blogger. Isn't yours?