使用命名空间、头文件和实现文件
***************************************************
更多精彩,歡迎進入:http://shop115376623.taobao.com
***************************************************
2.3? 使用命名空間、頭文件和實現文件
使新的throttle類滿足程序的需求將是非常有意義的(畢竟,我們絕不會知道何時將需要節流閥類)。我們將在此前提下繼續完善throttle類,并隱藏類的實現細節。此外,也不應當使其他程序員擔心他們自己選擇的變量名稱是否會碰巧與我們使用的名稱發生沖突。
可以通過以下3個步驟來實現上述目標:
(1) 創建命名空間。
(2) 編寫頭文件。
(3) 編寫實現文件。
下面將討論上述每個步驟的實現意圖和相關技術,并闡明另一個程序員如何使用根據這些技術編寫的數據項。
2.3.1? 創建命名空間
當程序使用了由多個不同程序員編寫的不同的類時,將有可能發生命名沖突。舉例來說,我們在前面編寫了一個節流閥類,但也許國家航空航天局(NASA)也編寫了一個節流閥類,而一個程序可能會需要同時使用這兩個類。也許用節流閥的例子并不能說明問題,但是普通的現實名稱確實會經常發生沖突。
可以利用稱為命名空間的程序組織技術來解決這一問題。命名空間(namespace)是一個程序員所選擇的用于區分屬于他或她的工作的名稱。這個名稱應該為描述性的,但它也應該包含程序員的真實姓名或email地址的一部分,從而盡可能地避免造成命名沖突。第2章中的第一個命名空間是main_savitch_2A;在本章的后面,還將會使用main_savitch_2B;其他章節中還將使用類似的名稱。
屬于命名空間的所有內容必須存放于命名空間分組(namespace grouping)中,并采用如下的形式:
namespace main_savitch_2A
{
??? || The usual class definition appears here.
}
單詞namespace是一個C++關鍵字。單詞main_savitch_2A是為命名空間選擇的名稱,它可以是任何合法的C++標識符。所有其他代碼出現在兩個花括號的內部。舉例來說,throttle的類聲明和throttle的成員函數實現都可以放置在命名空間中。
一個單獨的命名空間(例如main_savitch_2A)可以擁有多個不同的命名空間分組。舉例來說,throttle的類定義可以出現在main_savitch_2A的一個命名空間分組中,它位于程序中的某一個位置。隨后,當準備實現throttle的成員函數時,可以為main_savitch_2A打開第二個命名空間分組,并將函數實現放在第二個分組中。這兩個命名空間分組都屬于main_savitch_2A命名空間,但它們并不需要存在于同一個文件中。典型的情況是,它們位于兩個單獨的文件中:
●?????? 類的定義位于頭文件(header file)中,頭文件提供了程序員所需要的有關如何使用類的全部信息。
●?????? 成員函數的定義位于一個單獨的實現文件(implementation file)中。
本章剩余的部分將介紹throttle類的頭文件和實現文件的格式,并通過一個示例講解程序如何使用命名空間中的數據項。
2.3.2? 頭文件
類的頭文件提供了有關程序員如何使用類的所有信息。事實上,有關如何使用類的所有信息都應當出現在頭文件頂端的頭文件注釋(header file comment)中。如果要使用類,程序員只需閱讀這段信息的注釋即可。注釋應當包含所有公有成員函數的列表,而且每個函數都應附帶前置條件/后置條件協議(如果函數沒有前置條件,那么通常可以省略之,并只列出函數的后置條件)。注釋中不應當列出任何私有成員,因為使用類的程序員并不關心私有成員。
類定義位于頭文件注釋后面的命名空間分組中,但僅僅是類定義出現在這里—— 成員函數的實現并不出現在這里(除了內聯函數之外)。
將類定義置于頭文件中還有其他一些問題。其中一個問題是:使用類的程序員可能會認為他們不得不閱讀類的詳細定義才能使用這個類。實際上并非如此,因為有關如何使用類的所有信息都包含在頭文件注釋中。但是C++要求把類定義放在頭文件中,因此對于此問題并沒有辦法解決。
另一個問題源于頭文件的使用方式。在后面的章節中我們將會看到,一個程序有時會多次包含同一個頭文件,其結果是類定義也出現了多次,這使得編譯過程會由于“重復的類定義”而造成失敗。可以將所有的頭文件定義放置在稱為宏保護(macro guard)的編譯器指令內部來避免重復的類定義。在我們的命名空間中,帶有宏保護的throttle類聲明的完整形式如下所示:
#ifndef MAIN_SAVITCH_THROTTLE_H
#define MAIN_SAVITCH_THROTTLE_H
namespace main_savitch_2A
{
??? class throttle
??? {
??????? || The usual class definition appears here.
??? };
}
#endif
第一行中的#ifndef MAIN_SAVITCH_THROTTLE_H表明宏保護的開始,從這里至#endif之間出現的所有語句都處于宏保護的控制范圍之內。只有當編譯器在此之前沒有遇到單詞MAIN_SAVITCH_THROTTLE_H的時候,這段語句才會被編譯。
那么這又是如何避免類的重復定義呢?在這段代碼第一次出現時:
●?????? 類定義被編譯。
●?????? 單詞MAIN_SAVITCH_THROTTLE_H也被定義(通過語句#define MAIN_SAVITCH_ THROTTLE_H定義)。
此時,如果這段代碼再次出現,那么第二次的類定義將會被略過(因為MAIN_SAVITCH_THROTTLE_H已經被定義了)。圖2-5顯示的是throttle類的頭文件throttle.h。在過去,大多數程序員使用.h作為頭文件名稱的結尾(例如throttle.h),盡管這種做法現在已經變得越來越不通用了,因為標準頭文件(例如iostream)不再使用.h作為結尾。然而,本書將會繼續使用.h,因為某些文本編輯程序或者編譯器基于.h文件類型提供了一些特殊模式。
類的頭文件
當設計和實現一個類時,應當提供單獨的頭文件。
在頭文件的頂端,放置有關程序員如何使用該類的所有文檔信息。
類定義位于文檔信息之后。但僅僅是類定義出現在此處,成員函數的實現并不出現在此(不包括內聯函數)。
將類定義置于一個命名空間之內,并用“宏保護”包圍整段代碼。宏保護用于防止意外的重復定義。
頭文件
// FILE: throttle.h
// CLASS PROVIDED: throttle (part of the namespace main_savitch_2A)
//
// CONSTRUCTORS for the throttle class:
//?? throttle( )
//???? Postcondition: The throttle has one position above the shut_off
//? ???position, and it is currently shut off.
//
//?? throttle(int size)
//???? Precondition:? size > 0.
//???? Postcondition: The throttle has size positions above the shut_off
//???? position, and it is currently shut off.
//
// MODIFICATION MEMBER FUNCTIONS for the throttle class:
//?? void shut_off( )
??? //成員函數通常沒有前置條件。
//???? Postcondition: The throttle has been turned off.
//
//?? void shift(int amount)
//???? Postcondition: The throttle's position has been moved by
//???? amount (but not below 0 or above the top position).
//
// CONSTANT MEMBER FUNCTIONS for the throttle class:
//?? double flow( ) const
//???? Postcondition: The value returned is the current flow as a
//???? proportion of the maximum flow.
//
//?? bool is_on( ) const
//???? Postcondition: If the throttle's flow is above 0 then
//???? the function returns true; otherwise it returns false.
//
// VALUE SEMANTICS for the throttle class:
//??? Assignments and the copy constructor may be used with throttle objects.
#ifndef MAIN_SAVITCH_THROTTLE
??? //宏保護的開始
#define MAIN_SAVITCH_THROTTLE?????????????
namespace main_savitch_2A
??? //命名空間分組的開始
{
??? class throttle
??? {
??? public:
??????? // CONSTRUCTORS
??????? throttle( );
??????? throttle(int size);
??????? // MODIFICATION MEMBER FUNCTIONS
圖2-5? throttle類的頭文件
??????? void shut_off( ) { position = 0; }
??????? void shift(int amount);
??????? // CONSTANT MEMBER FUNCTIONS
??????? double flow( ) const { return position / double(top_position); }
??????? bool is_on( ) const { return (position > 0); }
??? private:
??????? int top_position;
??????? int position;
??? };
}
??? //命名空間分組的結束
#endif
??? //宏保護的結束
圖2-5? (續)
在頭文件中描述類的值語義
類的值語義(value semantic)決定了如何將值從一個對象復制到另一個對象。在C++中,值語義包含兩種操作:賦值操作符和復制構造函數。
賦值操作符(assignment operator) ?對于兩個對象x和y,賦值語句y = x將x的值復制到y中。對于任何新定義的類,允許類似這樣的賦值。對于一個新類,C++通常用如下的方法完成賦值:簡單地把賦值操作符右側對象的成員變量逐個復制到賦值操作符左側的對象中。這種復制方法稱為自動賦值操作符(automatic assignment operator)。在后面我們將會展示一些自動賦值操作符不起作用的示例,但是目前,我們的新類可以使用這種自動賦值操作符。
復制構造函數(copy constructor)? 復制構造函數是只具有一個參數的構造函數,而參數的數據類型與構造函數的類相同。例如,throttle的復制構造函數具有一個參數,而參數就是throttle本身。復制構造函數通常的用途是將一個新的對象初始化為另一個已存在對象的嚴格副本。舉例來說,下面的一段代碼創建了一個稱為x的具有100個位置的節流閥,并將x調節到它的中間位置;然后代碼聲明了第二個節流閥,并將其初始化為x的一個嚴格副本:
throttle x(100);
x.shift(50);
throttle y(x);
//節流閥y被初始化為x的一個副本,使得兩個節流閥都位于100個位置中的第50個位置。
高亮的語句激活throttle的復制構造函數,以便將y初始化為x的一個嚴格副本。在初始化之后,x和y可能會執行不同的操作,并處于不同的燃料流量;但是在此刻,兩個節流閥都被置于100個位置中的第50個位置。
還有另外一個替代的語法用于調用復制構造函數。除了使用throttle y(x);之外,也可以使用throttle y = x;來調用復制構造函數。這種替代語法看起來就像是賦值語句,但是切記,其實際的效果有一些不同。賦值y = x;只是將x復制到已存在的對象y中。而另一方面,聲明throttle y = x;不僅聲明了一個新對象y,而且還調用了復制構造函數以便將y初始化為x的一個副本。程序設計中通常使用原始形式throttle y(x);,因為這種形式不易與普通的賦值語句相混淆。
類的實現者可以像編寫其他的構造函數一樣,編寫一個復制構造函數——后面的章節中將會這么做。但是在這里,我們可以利用一個C++的特性:C++提供了一個自動復制構造函數(automatic copy constructor)。自動復制構造函數僅僅通過復制已存在對象的所有成員變量來初始化一個新對象。舉例來說,在聲明throttle y(x);中,自動復制構造函數將已存在的節流閥x中的兩個成員變量復制到新節流閥y中。
對于許多類來說,自動賦值操作符和自動復制構造函數都能很好地工作,但是前文也曾經警告過,后面我們將會展示一些這種自動版本發生失效的類。僅僅復制成員變量并不總是足夠的,正因為如此,程序員對使用賦值和復制構造函數非常謹慎。為了解決這一問題,建議在頭文件的注釋文檔中包含一則注釋,用于表明可以放心使用類的值語義。
編程提示?
注明值語義
在實現一個類時,文檔中應當包含一則注釋,用于表明可以放心使用類的值語義。舉例來說,在throttle的頭文件中,編寫了如下代碼:
// VALUE SEMANTICS for the throttle class:
//??? Assignments and the copy constructor may be used with throttle objects.
2.3.3? 實現文件
類的實現文件(implementation file)包含以下幾項:首先出現的是一小段注釋,表明類的具體說明文檔存放于頭文件中。接著是一條include指令,促使編譯器從頭文件中獲取類的定義。在節流閥的示例中,這一include指令如下所示:
#include "throttle.h"
在書寫頭文件名稱throttle.h的時候,此處使用了雙引號,而不是尖括號。尖括號只適用于包含標準庫中的工具(例如include指令:#include <iostrem>),而雙引號則表明使用的是自己編寫的頭文件。
在include指令之后,程序重新打開命名空間,并給出類成員函數的實現。重新打開命名空間使用的語法與在頭文件中所使用的語法相同:
namespace main_savitch_2A
{
??? || The definitions of the member functions are written here.
}
大多數編譯器需要特定的實現文件后綴名,例如.cpp或.C。而本書將使用.cxx作為實現文件的后綴名,圖2-6顯示的就是完整的實現文件throttle.cxx。
類的實現文件
每個類都有一個獨立的實現文件,其中包含了類成員函數的實現。請訪問www.cs.colorado.edu/~main/separation.html獲取更多有關實現文件和頭文件的信息。
實現文件
// FILE: throttle.cxx
// CLASS IMPLEMENTED: throttle (See throttle.h for documentation)
#include <cassert>???? ?? // Provides assert
#include "throttle.h"? ? // Provides the throttle class definition
namespace main_savitch_2A
{
???
??? throttle::throttle( )
??? {?? // A simple on-off throttle
??????? top_position = 1;
??????? position = 0;
??? }
???
??? throttle::throttle(int size)
??? // Library facilities used: cassert
??? {
??????? assert(size > 0);
??????? top_position = size;
??????? position = 0;
??? }
???
??? void throttle::shift(int amount)
?? ?{
??????? position += amount;
??????? if (position < 0)
??????????? position = 0;
??????? else if (position > top_position)
??????????? position = top_position;
??? }
}
圖2-6? throttle類的實現文件
2.3.4? 使用命名空間中的數據項
當頭文件和實現文件編寫完畢之后,任何程序就可以使用這個新定義的類。為了使用新定義的類,需要在程序的頂部放置include指令用于包含頭文件,如上述示例所示:
#include "throttle.h"
注意,此處只是包含了頭文件,而沒有包含實現文件。
在include指令之后,程序可以使用下述3種方法中的任何一種來使用命名空間中定義的數據項:
(1) 放置一條using語句,以便使命名空間的所有數據項可用。語句格式如下:
using namespace main_savitch_2A;
using語句使特定命名空間(main_savitch_2A)中的所有數據項可用,這與前面提到過的獲取標準庫中所有可用數據項的技術(使用語句using namespace std;)相同。
(2) 如果只需要命名空間中的特定數據項,那么using語句的格式則為:在關鍵字using之后緊跟命名空間的名稱,然后是兩個冒號,最后是想要使用的數據項。例如:
using main_savitch_2A::throttle;
這種方法將允許我們使用命名空間中的throttle;如果命名空間中還有其他的數據項,那么我們仍舊無法使用這些數據項。
(3) 在不使用using語句的情況下,只需在使用數據項的地方為數據項增加前綴—— 命名空間名稱和::,就仍然能夠使用任何數據項。舉例來說,可以使用如下語句聲明一個throttle變量:
main_savitch_2A::throttle apollo;
::的使用是生存空間解析操作符(在前面講述過)的一個示例,它澄清了我們請求使用的到底是哪一個特殊的throttle。
圖2-7總結了創建和使用命名空間的相關內容,其中包括一則警告,即不要在頭文件中放置using語句。
1. 全局命名空間:沒有被顯式地放置于某一個命名空間的任何數據項都將成為所謂的全局命名空間(global namespace)的一部分。可以在任何地方使用這些數據項,且無須再用using語句或生存空間解析操作符。
2. C++標準庫:如果使用新的C++頭文件名稱(例如<iostream>或者<cstdlib>),那么C++標準庫中的所有數據項都將自動地作為std命名空間的一部分。使用這些數據項的最簡單方法就是在include語句的后面放置一個using指令:using namespace std;。另一方面,如果使用舊的C++頭文件名稱(例如<iostream.h>或<stdlib.h>),那么所有的數據項都將會作為全局命名空間的一部分,因此也就不再需要using語句或者生存空間解析操作符。
3. 創建自己的命名空間:如果要創建一個新的命名空間,則需將數據項放置到命名空間分組中,并使用如下的形式:
namespace < The name for the namespace >
{
??? || Any item that belongs to the namespace is written here.
}
單詞namespace是一個C++關鍵字。命名空間的名稱可以是任意的C++標識符,但也應當作謹慎的選擇,以避免與其他命名空間發生沖突(可以使用我們的真實姓名或email地址)。一個單獨的命名空間可以包含多個不同的命名空間分組,并存在于不同的文件中。舉例來說,頭文件中的一個命名空間分組包含了類定義,實現文件中的另一個命名空間分組包含了成員函數的定義,而兩個命名空間分組都屬于同一個命名空間。
4. 使用命名空間:
● 如果要使用命名空間中的所有數據項,應當在所有include語句的后面放置一個
using指令,如下所示:
using namespace <The name for the namespace>;
● 如果要使用命名空間中的一個數據項,應當在所有include語句的后面放置一個特
殊的using指令,如下所示:
using <The name for the namespace>::<The name of the item>;
● 如果不使用using指令,但仍然要在程序中使用某個數據項,則應當在該數據項的
前面放置命名空間的名稱和::。
隱患?
不要將using語句放置在頭文件中
某些時候,頭文件自身也需要使用命名空間中的內容,在這種情況下,應當使用上述的第三種方式,而絕不能在頭文件中使用using語句(如果其他的程序包含了這個頭文件,那么將會發生意想不到的結果)。
圖2-7? 創建和使用命名空間的總結
圖2-8列出了使用修改后的throttle類的完整程序文檔。當實際編譯這個完整程序時,可能需要提供一些額外的信息,以便指明在何處能夠找到實現文件throttle.cxx的編譯后的版本。這個過程稱為鏈接(linking),它會因編譯器的不同而情況各異(參見附錄D)。
程序
// FILE: demo2.cxx
// This small demonstration shows how the revised throttle class is used.
#include <iostream>? ? // Provides cout and cin
#include <cstdlib>???? ? // Provides EXIT_SUCCESS
#include "throttle.h"? // Provides the throttle class
using namespace std;?? // Allows all Standard Library items to be used
using main_savitch_2A::throttle;
const int DEMO_SIZE = 5;? // Number of positions in a demonstration throttle
int main( )
{
??? throttle sample(DEMO_SIZE); // A throttle to use for our demonstration
??? int user_input;????? ???????? // The position that we set the throttle to
???
??? // Set the sample throttle to a position indicated by the user.
??? cout << "I have a throttle with " << DEMO_SIZE << " positions." << endl;
??? cout << "Where would you like to set the throttle?" << endl;
??? cout << "Please type a number from 0 to " << DEMO_SIZE << ": ";
??? cin >> user_input;
??? sample.shift(user_input);
??? // Shift the throttle down to zero, printing the flow along the way.
??? while (sample.is_on( ))
??? {
??????? cout << "The flow is now " << sample.flow( ) << endl;
??????? sample.shift(-1);
??? }
??? cout << "The flow is now off" << endl;
??? return EXIT_SUCCESS;
}
示范對話
I have a throttle with 5 positions.
Where would you like to set the throttle?
Please type a number from 0 to 5:? 3
The flow is now 0.6
The flow is now 0.4
The flow is now 0.2
The flow is now off
圖2-8? 使用修改后的throttle類的示范程序
2.3.5? 自測習題
13. 如果程序員要學習如何使用一個新類,應當閱讀什么內容?
14. 宏保護的目的是什么?
15. 如果x和y均為對象,則賦值y = x通常會發生什么操作?
16. 假設x是一個節流閥,那么聲明throttle y(x)將會產生什么影響?
17. 試編寫一個使用throttle類的主程序,其中必須包含#include指令和using語句。
18. 設計并實現類circle_location,用于跟蹤以圓形軌道運行的點的位置。這個類對象使用角度坐標記錄點的位置,角度坐標以圓的頂端作為起點,以順時針方向作為正方向。該類應當包含下述公有成員函數:
●?????? 默認構造函數,其作用是將點放置到圓的頂端。
●?????? 另一個構造函數,其作用是將點放置到指定位置。
●?????? 用于將點圍繞圓心轉動指定角度的函數。正的參數表示順時針方向轉動,負的參數表示逆時針方向轉動。
●?????? 用于返回點的當前位置的函數,其返回值是點相對于圓的頂端順時針轉過的角度。
需要完成的內容包括:獨立的頭文件、實現文件和使用該類的主程序。
19. 設計并實現類clock。clock對象保存著一個時間值的實例,例如9:48 P.M.。該類至少具有以下公有成員函數:
●?????? 默認構造函數,其作用是將時間設置為午夜。
●?????? 用于將clock顯式地設置為指定的時間的函數(試為該函數選取合適的參數)。
●?????? 用于查詢信息的函數,需要查詢的信息包括:當前小時和當前分鐘。判斷函數,用于確定當前時間是否小于或等于正午時間,其返回值為布爾類型。
●?????? 用于根據給定的分鐘值向前調節時間的函數。
20. 什么是全局命名空間?
21. 如果頭文件中需要使用命名空間的數據項,那么應當采用2.3.4節中的三種方法的哪一種。
總結
以上是生活随笔為你收集整理的使用命名空间、头文件和实现文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: List转json 顺序不一致
- 下一篇: 火狐 和 谷歌Google Chrome