1. 程式人生 > >C++ STL開發溫習與總結(三): 3.C++函式技術

C++ STL開發溫習與總結(三): 3.C++函式技術

C++ STL開發溫習與總結(三):
3.C++函式技術

       幾乎所有的C++類都有一個或多個建構函式,一個解構函式和一個賦值操作符。

       對於任何一個類A,如果不想編寫上述函式,C++編譯器將自動為A產生4個預設的函式,如下:

       A(void);// 預設的無引數建構函式
       A(constA &a); // 預設的拷貝建構函式
       ~A(void);//預設的解構函式
       A &operate = (const A & a); // 預設的賦值函式

1類的建構函式、解構函式與賦值函式

       C++提供了更好的機制來增強程式的安全性。C++編譯器具有嚴格的型別安全檢查功能,它幾乎能找出程式中所有的語法問題。

       根據經驗,不少難以察覺的程式是由於變數沒有被正確初始化或清楚造成的,而初始化和清楚工作很容易被人遺忘。C++很好的考慮了這個問題,把物件的初始化工作放在建構函式中,把清除工作放在解構函式中。

1-1 建構函式技術

       建構函式除了具有普通成員函式的所有特性外還有下面的幾個特殊點:

l  建構函式名必須和類名相同,且不得宣告任何返回值型別;

l  建構函式可以用形參形式帶進各成員資料的初值,也可以過載多個建構函式;

l  建構函式特殊的功能:測算本類物件所需的靜態記憶體容量並動態佔用該容量的記憶體資源;

l  建構函式並非沒有返回值,它所返回的值是一個指向其所定義物件的首地址的指標,但不能被傳遞;

l  若直接引用聲明於public區中的建構函式將導致該類的一個新物件生成,但若沒有相關的控制代碼與之來年系,則無法與物件進行通訊聯絡;

l  生命與private區中的建構函式只能通過定義在public區中的函式引用;

l  如果類存在繼承關係,派生類必須在其初始化表裡呼叫基類的建構函式;

l  類的const常量只能在初始化表裡被初始化,它不能在函式體內用賦值的方式來初始化;

l  類的資料成員的初始化可以採用初始化表或函式體內賦值兩種方式。非內部資料型別的成員物件應當採用第一種方式初始化,以獲取更高的效率。

預設構造使用:

Demo h;


非預設構造使用:

Demoh(0);// 或者寫成Demoh=4;
追求更高的效率
B::B(const A &a) : m_a(a) { …}

同樣的功能,下面先暗地裡建立m_a,再調類A的賦值函式

B::B(const A &a) { m_a = a; …};

1-2 解構函式技術

     解構函式由定義類物件的分程式在其執行結束之前被自動地引用。與建構函式一樣,解構函式也不得宣告任何的返回類值型、不得帶有任何引數且不得過載,也不能被置於private區,有且僅有一個解構函式。

       解構函式除了可以(用delete)釋放在建構函式中指明佔用的記憶體外,還要釋放整個物件的成員等所佔用的記憶體,這些只是不必寫進去。

       如果一個基類的解構函式被說明未虛解構函式,則它的派生類中的解構函式也是虛解構函式,不管它是否引用了關鍵字virtual進行說明。

1-3 構造和析構的次序

       構造從類層次的最根處開始,在每一層中,首先呼叫基類的建構函式,然後呼叫成員對像的建構函式。      析構則嚴格按照與構造相反的次數執行,該次序是惟一的,否則編譯器將無法自動執行析構過程。

     一個特別的現象是:成員物件初始化的次序完全不受它們在初始化表中次序的影響,只由成員物件在類中宣告的次序狀態決定,這是因為類的宣告是惟一的,而類的建構函式可以有多個,因此會有多個不同次序的初始化表。

1-4 成員初始化表設計

       C++語言在原有賦值方式的基礎上,增加了一個成為成員初始化表的特別手段來簡化這種初始化過程,由於此種手段是編譯階段由編譯器將要初始化的成員資料與引數建立了對應聯絡,所以用此法的系統在執行階段的開銷較之其他方法都要小得多

       成員初始化表放在建構函式名與建構函式體之間,用冒號與函式名部分相分隔,每個表的格式為:

       類中成員名(初值),類中成員名(初值)

 ST(unsignedint y, unsigned int, unsigned int d): yy(y), mm(m), dd(d)

       C++語言中只有const和引用型別是要在編譯時就要指明其初值。由於定義在類中的成員都是抽象的資料結構描述,不可能分配記憶體單元,因此在對建構函式進行編譯的階段也就不可能完成賦初值的操作了。為了解決這一矛盾,C++語言只有藉助於類物件“成員初始化表”的描述去將要賦初值的成員名及初值預先宣告,待執行時產生物件(即分配了記憶體單元)後再補做上述的賦初值操作。所以從某種意義上講,成員初始化表是特意為這兩種資料成員準備的也不為過,由此切記成員初始化表不可寫在宣告語句上。

1-5 類中的const成員函式的特性

 *防止本函式誤寫引數變數

返回值型別函式名(const 引數, const 引數, …)

 * 防止本函式誤寫類物件內全部的變數的

返回值型別函式名(引數表) const;

 * 防止其他函式誤寫返回地址或引用的

const 返回值型別函式名(引數表)

1-6 拷貝建構函式與賦值函式

       由於並非所有的物件都會使用拷貝函式和賦值函式,程式設計師可能對這兩個函式有些輕視(確實如此)。如果不主動編寫拷貝建構函式和賦值函式,編譯器將以“位拷貝”的方式自動生成預設的函式。倘若類中含有指標變數,那麼這兩個預設的函式就隱含了錯誤。

       以類String的兩個物件a,b為例,假設a.m_data的內容為“hello”,b.m_data的內容為“world”。現在a賦值給b,預設賦值函式的“位拷貝”意味著執行b.m_data=a.m_data。這將造成3個錯誤:

       一是b.m_data原來的記憶體沒有被釋放,造成記憶體洩漏;

       二是b.m_data和a.m_data指向同一塊記憶體,a或b任何一方變動都將影響另一方;

       三是在物件被析構時,m_data被釋放了兩次。

2在派生類中實現類的基本函式

       基類的建構函式、解構函式、賦值函式都不能被派生類繼承。如果類之間存在繼承關係,在編寫上述基本函式時應注意以下事項:

n  派生類的建構函式應在其初始化表裡呼叫積累的建構函式

基類與派生類的解構函式應為虛(即加virtual關鍵字)。

3行內函數技術

       C++語言支援函式內聯,其目的是為了提高函式的執行效率(速率)。在C程式中,可以用巨集程式碼提高執行效率。巨集程式碼本身不是函式,但使用起來湘函式。與處理器用複製巨集程式碼的方式代替函式呼叫,省去了引數壓棧、生成組合語言的CALL呼叫、返回引數、執行return等過程,從而提高了速率。使用巨集程式碼最大的缺點是容易出錯,與處理器在複製巨集程式碼時常常產生意想不到的邊際效應。例如:

#defineMAX(a,b)  (a)>(b)?(a):(b)

可改寫避免邊際效應

#defineMAX(a,b)  ((a)>(b)?(a):(b))

       函式呼叫會帶來降低效率的問題,因為呼叫函式實際上將程式執行順序轉移到函式所存放的記憶體中的某個地址,將函式的程式內容執行完後,在返回到呼叫該函式前程式語句所在位置。這種轉移操作要求在轉去前要保護現場並記憶執行的地址,轉回後要先恢復現場,並按原來儲存地址繼續執行。因此,函式呼叫要有一定的時間和空間方面的開銷,於是將影響其效率。特別是對於一些函式體程式碼不是很大,但又頻繁地被呼叫的函式來說,解決其效率問題更為重要。引入內聯實際上是為了解決這一問題。

*  在行內函數不允許用迴圈語句和開關語句

*  行內函數的定義必須出現在行內函數第一次被呼叫之前。

4友元函式技術

       只有類的成員函式才能訪問類的私有成員,程式中的其他函式是無法訪問私有成員的。非成員函式可以訪問類中的公有成員,但是如果將資料成員都定義為公有的,這又破壞了隱藏的特性。另外,應該看到在某些情況下,特別是在對某些成員函式多次呼叫時,由於引數傳遞,型別檢查和安全性檢查等都需要時間開銷,而影響程式的執行效率。

       為了解決上述問題,提出一種使用友元的方案。友元是一種定義在類外部的普通函式,但它需要在類體內進行說明,為了與該類的成員函式加以區別,在說明是前面加關鍵字friend。友元不是成員函式,但是它可以訪問類中的私有成員。友元的作用在於提高程式的執行效率,但是,它破壞了封裝性和隱藏性,使得非成員函式可以訪問類的私有成員。

       友元可以是一個函式,也可以是一個類。