程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++箴言:理解Terminology術語

C++箴言:理解Terminology術語

編輯:C++入門知識
  這是一個所有程序員都應該了解的小型的 C++ 詞匯表。下面的條目都足夠重要,值得我們對它們的含義務必取得完全一致。

  聲明(declaration)告訴編譯器關於某物的名字和類型,但它省略了某些細節。以下這些都是聲明:

extern int x; // object declaration

std::size_t numDigits(int number); // function declaration

class Widget; // class declaration

template<typename T> // template declaration
class GraphNode; // (see Item 42 for info on
// the use of "typename")

  注意即使是內建類型,我還是更喜歡將整數看作一個 "object",某些人將 "object" 這個名字保留給用戶定義類型,但我不是他們中的一員。再有就是注意函數 numDigits 的返回類型是 std::size_t,也就是說,namespace std 中的 size_t 類型。這個 namespace 是 C++ 標准庫中每一樣東西實際所在的地方。但是,因為 C 標准庫(嚴謹地說,來自於 C89)在 C++ 中也能使用,從 C 繼承來的符號(諸如 size_t)可能存在於全局范圍,std 內部,或兩者都有,這依賴於哪一個頭文件被 #included。在本書中,我假設 C++ 頭文件被 #included,這也就是為什麼我用 std::size_t 代替 size_t 的原因。當行文中涉及到標准庫組件時,我一般不再提及 std,這依賴於你認可類似 size_t,vector,以及 cout 之類的東西都在 std 中,在示例代碼中,我總是包含 std,因為真正的代碼沒有它將無法編譯。

  順便說一下,size_t 僅僅是某些供 C++ 對某物計數時使用的 unsigned 類型的 typedef(例如,一個基於 char* 的 string 中字符的個數,一個 STL 容器中元素的個數,等等)。它也是 vector,deque,以及 string 的 operator[] 函數所持有的類型,這是一個在 Item 3 中定義我們自己的 operator[] 函數時將要遵守的慣例。

  每一個函數的聲明都表明了它的識別標志(signature),也就是它的參數和返回類型。一個函數的識別標志(signature)與它的類型相同。對於 numDigits 的情況,識別標志(signature)是 std::size_t (int),也就是說,“函數持有一個 int,並返回一個 std::size_t”。官方的“識別標志(signature)”的 C++ 定義排除了函數的返回類型,但是在本書中,將返回類型考慮為識別標志的一部分更加有用。

  定義(definition)為編譯器提供在聲明時被省略的細節。對於一個對象,定義是編譯器為對象留出內存的地方。對於一個函數或一個函數模板,定義提供代碼本體。對於一個類或一個類模板,定義列出了類或者模板的成員:

int x; // object definition

std::size_t numDigits(int number) // function definition.
{
  // (This function returns
  std::size_t digitsSoFar = 1; // the number of digits
  // in its parameter.)
  while ((number /= 10) != 0) ++digitsSoFar;
  return digitsSoFar;
}

class Widget {
  // class definition
  public:
   Widget();
   ~Widget();
  ...
};

template<typename T> // template definition
class GraphNode {
public:
  GraphNode();
  ~GraphNode();
  ...
};

  初始化(Initialization)是設定一個對象的第一個值的過程。對於用戶定義類型的對象,初始化通過構造函數完成任務。缺省構造函數(default constructor)就是不需要任何引數(arguments)就可以調用的構造函數。這樣的一個構造函數既可以是沒有參數(parameters),也可以是每一個參數都有缺省值:

class A {
public:
  A(); // default constructor
};

class B {
public:
  explicit B(int x = 0, bool b = true); // default constructor; see below
}; // for info on "explicit"

class C {
public:
  explicit C(int x); // not a default constructor
};

  這裡 B 和 C 的構造函數都被聲明為 explicit(顯式的)。這是為了防止它們被用來執行隱式類型轉換(implicit type conversions),雖然他們還可以被用於顯示類型轉換(explicit type conversions):

void doSomething(B bObject); // a function taking an object of
// type B

B bObj1; // an object of type B

doSomething(bObj1); // fine, passes a B to doSomething

B bObj2(28); // fine, creates a B from the int 28
// (the bool defaults to true)

doSomething(28); // error! doSomething takes a B,
// not an int, and there is no
// implicit conversion from int to B

doSomething(B(28)); // fine, uses the B constructor to
// explicitly convert (i.e., cast) the
// int to a B for this call. (See
// Item 27 for info on casting.)

  構造函數被聲明為 explicit(顯式的)通常比 non-explicit(非顯式)的更可取,因為它們可以防止編譯器執行意外的(常常是無意識的)類型轉換。除非我有一個好的理由允許一個構造函數被用於隱式類型轉換(implicit type conversions),否則就將它聲明為 explicit(顯式的)。我希望你能遵循同樣的方針。

  構造函數被聲明為 explicit(顯式的)通常比 non-explicit(非顯式)的更可取,因為它們可以防止編譯器執行意外的(常常是無意識的)類型轉換。除非我有一個好的理由允許一個構造函數被用於隱式類型轉換(implicit type conversions),否則就將它聲明為 explicit(顯式的)。我希望你能遵循同樣的方針。

  請注意我是如何突出上面的示例代碼中的強制轉換(cast)的。貫穿本書,我用這樣的突出引導你注意那些應該注意的材料。(我也突出章節號碼,但那僅僅是因為我想讓它好看一些。)

  拷貝構造函數(copy constructor)被用來以一個對象來初始化同類型的另一個對象,拷貝賦值運算符(copy assignment operator)被用來將一個對象中的值拷貝到同類型的另一個對象中:

class Widget {
public:
  Widget(); // default constructor
  Widget(const Widget& rhs); // copy constructor
  Widget& operator=(const Widget& rhs); // copy assignment operator
  ...
};
Widget w1; // invoke default constructor
Widget w2(w1); // invoke copy constructor
w1 = w2; // invoke copy
// assignment operator

  當你看到什麼東西看起來像一個賦值的話,要仔細閱讀,因為 "=" 在語法上還可以被用來調用拷貝構造函數:

Widget w3 = w2; // invoke copy constructor!
  幸運的是,拷貝構造函數很容易從拷貝賦值中區別出來。如果一個新的對象被定義(就象上面那行代碼中的 w3),一個構造函數必須被調用;它不可能是一個賦值。如果沒有新的對象被定義(就象上面那行 "w1 = w2" 代碼中),沒有構造函數能被調用,所以它就是一個賦值。

  拷貝構造函數是一個特別重要的函數,因為它定義一個對象如何通過傳值的方式被傳遞。例如,考慮這個:

bool hasAcceptableQuality(Widget w);

...
Widget aWidget;
if (hasAcceptableQuality(aWidget)) ...

  參數 w 通過傳值的方式被傳遞給 hasAcceptableQuality,所以在上面的調用中,aWidget 被拷貝給 w。拷貝動作通過 Widget 的拷貝構造函數被執行。通過傳值方式傳遞意味著“調用拷貝構造函數”。(無論如何,通過傳值方式傳遞用戶定義類型通常是一個不好的想法,傳引用給 const 通常是更好的選擇。)
 STL 是標准模板庫(Standard Template Library),作為 C++ 的標准庫的一部分,致力於容器(containers)(例如,vector,list,set,map,等等),迭代器(iterators)(例如,vector<int>::iterator,set<string>::iterator,等等),算法(algorithms)(例如,for_each,find,sort,等等),以及相關機能。相關機能中的很多都通過函數對象(function objects)——行為表現類似於函數的對象——提供。這樣的對象來自於重載了 operator() ——函數調用運算符——的類,如果你不熟悉 STL,在讀本書的時候,你應該有一本像樣的參考手冊備查,因為對於我來說 STL 太有用了,以至於不能不利用它。一但你用了一點點,你也會有同樣的感覺。

  從 Java 或 C# 那樣的語言來到 C++ 的程序員可能會對未定義行為(undefined behavior)的概念感到吃驚。因為各種各樣的原因,C++ 中的一些結構成分(constructs)的行為沒有確切的定義:你不能可靠地預知運行時會發生什麼。這裡是兩個帶有未定義行為的代碼的例子:

int *p = 0; // p is a null pointer

std::cout << *p; // dereferencing a null pointer
// yields undefined behavior
char name[] = "Darla"; // name is an array of size 6 (don’t
// forget the trailing null!)

char c = name[10]; // referring to an invalid array index
// yields undefined behavior

  為了強調未定義行為的結果是不可預言而且可能是令人討厭的,有經驗的 C++ 程序員常常說帶有未定義行為的程序能(can)刪除你的硬盤。這是真的:一個帶有未定義行為的程序可以(could)刪除你的硬盤。只不過可能性不太大。更可能的是那個程序的表現反復無常,有時會運行正常,有時會徹底完蛋,還有時會產生錯誤的結果。有實力的 C++ 程序員能以最佳狀態避開未定義行為。本書中,我會指出許多你必須要注意它的地方。

  另一個可能把從其它
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved