C++ Primer筆記
指針和typedef:
typedef string *pstring
const pstring cstr;
等價于
string * const cstr
cstr為const指針,而不是cstr指向const string,typedef不是簡單的文本擴展,聲明const pstring時,const修飾的是pstring,pstring是指針,所以const修飾的是指針而不是指針所指的對象。
在switch內部只可以在最后一個case或default標號中定義變量,這個規則是為了避免出現代碼跳過變量的定義和初始化的情況,如果要為某個case定義爆裂,可引入塊語句({})。
指向指針的引用:*&,如int *&i。
數組形參:
當數組作為函數的參數傳遞時,應將數組名作為實參,函數聲明中的形參可以有三種形式:
void pringArray(int *);
void printArray(int arr[]);
void printArray(int arr[10]);
這三種形式是等價的,因為最終都會被轉化為第一種形式,數組名為指向第一個元素的指針,第三種形式會引起誤解,因為其會轉化為指針,并無法保證數組的長度。
數組形參可以聲明為引用,這樣的話,編譯器不會將數組實參轉化為指針,而是傳遞數組的引用本身,數組的大小成為形參和實參的一部分,編譯器檢查數組實參的大小與形參的大小是否匹配,如:void pringArray(int (&arr)[10])可保證數組的長度為10.
&arr兩邊的括號是必須的,因為[]的優先級更高。
int &arr[10] //an array of 10 references
int (&arr)[10] //a referenceto to an array of 10 ints
類的const成員函數:
類的每個非static成員函數都有一個額外的、隱含的形參this,通過在形參表后加const可使其成為const成員函數,此時this作為const類型,不能改變所指對象的狀態。const對象、指向const對象的引用或指針只能訪問const成員變量和const成員函數,因為非const成員可能會改變對象的狀態。但有一個例外,聲明為mutable的類成員為可變數據成員,表示可以被修改(甚至在const成員函數內),所以其不可能為const,即使其為const對象的成員??梢孕薷?span style="font-size: 15px">static成員,因為static成員不屬于個別對象而屬于類。
局部作用域中函數的聲明會屏蔽其外圍作用域中的所有同名函數,無論其形參列表是否相同,因為編譯器以名字查找函數調用,當在一個作用域中找到名字后就不再在其外圍作用域中繼續查找。
僅當形參是引用或指針時,才能只基于形參是否為const實現重載,當定義這種形式的重載時,因為不能將const引用或指針傳遞給非const引用或指針形參(非const可能改變所引向或指向的對象的值),所以const實參會調用const版本;非const引用或指針既可初始化非const引用或指針,也可初始化const引用或指針,但初始化const型時會引發轉換,所以非const版本更匹配,非const實參會調用非const版本。
指向函數的指針:
函數類型由其返回類型與形參表確定,與函數名無關:
int (* func)(int,int);
這個語句聲明一個指向函數的指針func,它所指向的函數帶有兩個int參數,返回一個int。
func兩側的括號是必須的:
int *func(int,int);
表示聲明一個函數,擁有兩個int參數,返回int *
用typedef簡化函數指針的定義:
typedef int (*func)(int,int);
該定義表示func是一個指向函數的指針類型的名字,在要使用這種函數指針類型時,只需直接使用func即可。
函數指針用作參數時有兩種形式:
int userFunc(int,int,int (int,int));
int userFunc(int,int,int (*)(int,int));
返回指向函數的指針:
int (*ff(int))(int,int);
該定義表示ff是一個函數,有一個int參數,這個函數返回一個指向函數的指針,這個指向函數的指針指向這樣一個函數:擁有兩個int參數,返回一個int值。
使用typedef可使該定義更易理解:
typedef int(*PF)(int,int);
PF ff(int);
可以把ff(int)想象成一個普通的標識符,如前面例子中的func。
對于類的成員函數,形參表和函數體出現在成員名字之后,處于類作用域中,但返回類型成出現在成員名字之前,如果函數在類定義體之外定義,則用于返回類型的名字在類的作用域之外,如果返回類型使用由類定義的類型,則應使用其完全限定名。
static數據成員必須在類定義體外部定義,在定義時初始化,整型(char,int,short,long及相應無符號的)const static成員可在類定義體內初始化。
將復制構造函數定義為private可防止對象被復制,但類的友元和成員仍可以復制,若想阻止類的友元和成員進行復制,可以聲明一個private復制構造函數而不定義。
一般如果需要復制構造函數,也會需要賦值操作符(operator=)。
重載操作符必須具有一個類類型操作數,因為不能對內置類型進行操作符能重載。
重載操作符后操作符的優先級、結合性和操作數數目不能改變。
重載操作符并不保證操作數的求值順序,尤其是不會保證&&,||, 逗號操作符的操作數求值,在&&和||的重載版本中,兩個操作數都要進行求值,而且對操作數的求值順序不做規定,即其短路求值特性消失,所以重載這三個操作符不是好做法。
既定義了算術操作符又定義了相關復合賦值操作符的類,一般應使用復合賦值操作符實現算術操作符(在算術操作符中簡單地調用相關的復合賦值操作符)。
賦值操作符必須定義為類的成員函數,返回對*this的引用。一般而言,賦值操作符與復合賦值操作符應返回左操作數的引用。
類定義下標操作符時,一般需要定義兩個版本:一個為非const成員并返回引用,另一個為const成員并返回const引用。
解引用操作符(*)也一般也需要兩個版本:const與非const版本。
箭頭操作符可能表現得像二元操作符一樣:接受一個對象和一個成員名,對對象解引用以獲取成員,但箭頭操作符不接受顯式形參。由編譯器處理獲取成員的工作。重載箭頭操作符時,編譯器將按如下對表達式 object->action 進行求值:
1. 如果object是一個指針,指向具有名為action的成員的類對象,則編譯器將代碼編譯為調用該對象的action成員。
2. 否則,如果action是定義了operator->操作符的類的一個對象,則object->action與object.operator->()->action相同,即執行object的operator->(),然后使用返回的結果重復這三步。
3. 否則,代碼出錯。
函數調用操作符(括號操作符,必須定義為類成員),函數對象:
如下例子,類absInt封閉地int類型的值轉換為絕對值的操作。
class absInt
{
int operator() (int val)
{
return val<0?-val:val;
}
}
該類公定義了一個函數調用操作符(括號操作符),通過為該類的對象提供一個實參表而使用調用操作符,所用的方式看起來像一個函數調用:
int i=-23;
absInt abs;
unsigned int ui=abs(i); //cals absInt::operator(int)
盡管abs是一個對象而不是一個函數,但我們仍可以“調用”該對象,效果是運行由abs對象定義的重載調用操作符,該操作符接受一個int值并返回它的絕對值。
定義了調用操作符的類,其對象常稱為函數對象,即它們是行為類似函數的對象。
在C++中,基類必須指出希望派生類重定義哪些函數,定義為virtual的函數是基類期待派生類重新定義的,基類希望派生類繼承的函數不能定義為虛函數。
除了構造函數外,任意非static成員函數都可以是虛函數,保留字只能在類內部的成員函數聲明中出現,不能用在類定義體外部出現的函數定義上。
派生類只能通過派生類對象訪問其基類的protected成員,派生類對其基類類型對象的protected成員沒有特殊訪問權限。
派生類中虛函數的聲明必須與基類中的定義方式完全匹配,但有一個例外:返回值為基類型的引用(或指針)的虛函數。派生類中的虛函數可以返回基類函數所返回類型的派生類的引用。比如基類中有一個虛函數返回值為另一個類Base的引用,則此類的派生類中相應的函數可以返回Base類的派生類Derived的引用。
用作基類的類必做是已定義的,只是聲明而沒有定義的類不能用作基類。因為派生類將包含并須可以訪問其基類的成員,為了使用這些成員,派來類必須故道它們是什么。
派生類的前向聲明只應包含類名,而不包含派生列表。如class B:public A,則B的前向聲明應為:class B,而不是class B:public A。
非虛函數總是在編譯時根據調用該函數的對象、引用或指針的類型而確定,稱為靜態綁定,而虛函數在實際執行的時候根據對象的實際類型調用相應的函數,稱為動態綁定。
在某些情況下,可能希望覆蓋虛函數機制并強制函數調用使用虛函數的特定版本,這時可以使用域操作符,如:
Base *b=&derived;
b->Base::virtualFunc();
這樣做的常見是為了在派生類的虛函數中調用基類中的版本,在這種情況下,基類版本可以完成繼承層次中所有類型的公共任務,而每個派生類型只添加自己的特殊工作。
虛函數與默認參數:
虛函數也可以有默認參數,如果一個調用省略了具有默認值的參數,則參數的值由調用該函數的類型定義,與對象的實際類型無關,如果是通過基類的引用或指針調用虛函數,則參數值為在基類虛函數聲明中指定的值,如果是通過派生類的引用或指針高用虛函數,則參數值為在派生類虛函數聲明中指定的值,也就是說虛函數的默認參數是在編譯時靜態綁定的,所以應該避免給虛函數聲明不同的默認參數,因為用不同的對象調用可能會有不同的行為。
public派生類繼承基類的public接口,使用public派生類稱為接口繼承,使用protected或private派生的類繼承基類的成員但不使其成為其接口的一部分,被稱為實現繼承。
可以通過using改變繼承自基類的成員的訪問級別,如:
class Base
{
protected:
int i;
};
class Derived:private Base {…};
在這一繼承層次中,i 在Base中為protected,但在Derived中為private,為了使i在Derived中為不同的訪問級別,可以在Derived相應部分增加一個using聲明,如下這樣改變Derived的定義,可以使i成員的訪問級別恢復為protected:
class Derived : private Base
{
public:
using Base::i;
};
class默認為private繼承,struct默認為public繼承。
如果基類定義了static成員,則整個繼承層次中只有一個這樣的成員,無論從基類派生出多少個派生類,每個static成員只有一個實例。如果static成員在基類中為private,則派生類不能訪問它。假定可以訪問(非private),則既可以通過基類訪問,也可以通過派生類訪問,既可以使用作用域操作符也可以使用點或箭頭成員訪問操作符。
對于public繼承,派生類對象可以轉換為基類對象的引用,不可以轉換為基類對象,但可以對基類對象進行初始化和賦值,初始化或賦值時只是將派生類對象的基類部分成員復制到基類對象中,對于非public繼承,不可轉換為基類對象的引用,不可對基類成員初始化與賦值。
使用dynamic_cast的時候必須有多態即虛函數。
定義派生類的復制構造函數時,一般應顯式使用基類復制構造函數初始化對象的基類部分,否則基類部分會通過基類默認構造函數進行初始化。同理,如果派生類定義了賦值操作符,也應調用基類的賦值操作符。(賦值操作符應防止自身賦值)
刪除指向動態分配對象的指針時,需要運行釋放對象的內存之前清除對象,處理繼承層次中的對象時, 指針的靜態類型可能與被刪除對象的動態類型不同,如果刪除基類,則運行基類析構函數并清除基類的成員,如果對象實際是派生類型的,則沒有定義該行為,為保證運行適當的析構函數,根類中的析構函數必須為虛函數。所以一般情況下,即使析構函數沒有工作要做,繼承層次的根類也應該定義一個虛析構函數
像其他虛函數一樣,析構函數的虛函數性質會被繼承,因此,如果層次中基類的析構函數為虛函數,則派生類的析構函數也將是虛函數,無論派生類是顯示定義的析構函數還是合成的。
構造函數不能定義為虛函數,構造函數是在對象完全構造之前運行的,在構造函數運行的時候,對象的動態類型還不完整。可以在基類中將賦值操作符operator= 定義為虛函數,但這樣做沒有意義,因為賦值操作符有一個形參是自身類類型的引用,不同于任意其他類的賦值操作符的形參類型。
構造派生類對象時首先運行基類的構造函數初始基類部分,在執行基類構造函數時對象的派生類部分是未初始化的,實際上,此時對象還不是一個派生類對象。
撤銷派生類對象時,首先撤銷它的派生類部分,然后按照與構造順序的一逆序撤銷它的基類部分。
在這兩種情況下,對象都是不完整的,為了適應這種不完整,編譯器將對象的類型視為在構造或析構期間在發生了變化。在基類的構造函數或析構函數中,將派生類對象當作基類類型對象對待,此時的對象類型對虛函數的綁定會有影響,如果在構造函數或析構函數中調用虛函數,則運行的是為構造函數或析構函數自身類型定義的版本,因為此時對象不是派生類的類型,派生類的成員沒有定義。
名字查找在編譯時發生:
每個類都保持著自己的作用域,在該作用域中定義了成員的名字。在繼承的情況下,派生類的作用域嵌套在基類作用域中,如果不能在派生類作用域中確定名字,就在外圍基類作用域中查找該名字的定義,正是這種類作用域的層次嵌套使我們能夠直接訪問基類的成員,就好像這些成員是派生類的成員一樣。名字的查找在編譯時發生,所以對象、引用或指針的靜態類型決定了對象能夠完成的行為。
繼承中的名字沖突:
在派生類中與基類成員同名的派生類成員將屏蔽對基類成員的訪問,但可以使用作用域操作符訪問被屏蔽的成員。
在基類和派生類中使用同一名字的成員函數,其行為與數據成員一樣:在派生類作用域中派生類成員將屏蔽基類成員,即使函數原型不同,基類成員也會被屏蔽,編譯器一旦在派生類中找到名字,就不再在基類中查找。
派生類重定義基類函數:
派生類可以重定義從基類繼承的函數,如果派生類想通過自身類型使用基類的所有重載版本,則派生類必須要么重定義所有重載版本,要么一個也不定義。
有時類需要僅僅重定義一個重載集合中的某些版本的行為,并且想要繼承其他版本的含義,在這種情況下,可以為重載成員提供using聲明,一個using聲明只能指定一個名字,不能指定形參表,這樣可以將基類成員函數的所有重載版本加到派生類的作用域,然后只需重定義相應的版本(using 聲明類似為:using Base::func,類限定符加函數名,無需括號與形參表)。
模板函數也可以聲明為內聯函數,inline說明符應放在模板形參列表之后,返回值之前。
可以使用函數模板對函數指針進行初始化或賦值,這樣做的時候,編譯器使用指針的類型實例化具有適當模板實參的模板版本。

浙公網安備 33010602011771號