12.1 類的定義和聲明
一個類可以包含若干公有的、私有的和受保護的部分。
在public中定義的成員可被使用該類型的所有代碼訪問;在private部分定義的成員可被其他類成員訪問。
在類內部定義的函是數默認爲inline。
const成員不能改變其所操作的對象的數據成員。const必須同時出現在聲明和定義中,若只出現在其中一處,就會出現編譯錯誤。
將類定義放在頭文件中,可以保證在每個使用類的頭文件中以同樣的方式定義類。使用頭文件保護符,來保證即使頭文件在同一文件中被包含多次,類定義也只出線一次。
可以聲明一個類而不定義它:class Screen; 這種聲明爲前向聲明,在聲明之後,定義之前,類Screen是一個不完全類型,即已知Screen是一個類型,但不知道包含哪些成員。
不完全類型只能以有限方式使用。不能定義該類型的對象。不完全類型只能用於定義指向該類型的指針和引用,或者用於聲明(而不是定義)使用該類型作爲形參類型或返回類型的函數。因爲只有當類定義體完成後才能定義類,因此類不能具有自身類型的數據成員。然而只要類名一出現就可以認爲該類已聲明。因此,類的數據成員可以是指向自身類型的指針和引用:
class LinkScreen{
Screen windows;
LinkScreen *next;
LinkScreen *prev;
};
類的前向聲明一般用來編寫相互依賴的類。
例如。定義兩個類X和Y,X中有一個指向Y的指針,Y中有一個X類型的對象。
class Y;
class X{
Y *y;
};
class Y{
X x;
};
定義對象時,將爲其分配存儲空間,但定義類型時不進行存儲分配。
12.2 隱含的this指針
何時使用this指針?最常見的情況是在這樣的函數中使用this:該函數返回對調用該函數的對象的引用。
在普通的非const成員函數中,this的類型時一個指向類類型的const指針;在const成員函數中,this的類型是一個指向const類類型對象的const指針。
!!!不能從const成員函數返回指向類對象的普通引用。const成員函數只能返回*this作爲一個const引用。
有時,我們希望類的數據成員(甚至在const成員函數中)可以修改。這可以通過將他們聲明爲mutable來實現。
如,使用access_ctr來跟蹤調用Screen成員函數的頻繁程度。
void Screen::do_display(ostream& os) const
{
++access_str;
os<<contents;
}
12.3 類作用域
在類作業域外,成員只能通過對象或指針分別使用成員訪問操作符 "." 或者"->"來訪問。
函數返回類型不一定在類作用域中,如
class Screen{
typdef size_type index;
index get_cursor()const;
};
Screen::index Screen::get_cursor()const{
return cursor;
}
名字必須在使用之前進行定義。而且,一旦一個名字被用作類型名,該名字就不能重複定義。
類成員定義中的名字查找
1.首先檢查成員函數局部作用域中的聲明
2.如果在成員函數中找不到該名字的聲明,則檢查對所有類成員的聲明。
3.如果在類中找不到該名字的聲明嗎,則檢查在此成員函數定義前的作用域中出現的聲明。如:
int height;
class Screen{
public:
void dummy_fun(index height){
cursor=width*height;//height爲形參值,覆蓋掉了類數據成員和全局變量height
}
private:
index cursor;
index height,width;
};
儘管全局對象被屏蔽了,但通過用全局作用域確定操作符來限定名字,仍然可以使用它。如::height
12.4 構造函數
構造函數的名字與類的名字相同,並且不能指定返回類型。
構造函數不能聲明爲const
構造函數可以包含初始化列表:
Sales_item::Sales_item(const string &book):isbn(book),units_sold(0),revenue(0){}
Sales_item::Sales_item(const string &book){
isbn=book;
units_sold=0;
revenue=0;}
這個構造函數給類Sales_item的成員賦值,但沒有進行顯示初始化。不管是否有顯示的初始化式,在執行構造函數前,要初始化isbn成員。構造函數隱式的使用默認的string構造函數來初始化isbn。執行構造函數的函數體時,isbn成員已經有值了,該值被構造函數函數體中的賦值所覆蓋。
從理論上講,構造函數有2個執行階段:(1)初始化階段(2)普通的計算階段
內置或複合類型的成員的初始值依賴於對象的作用域:在局部作用域中這些成員不被初始化,在全局作用域中被初始化爲0
有些成員必須在構造函數初始化列表中進行初始化。對這樣的成員,在構造函數函數體中對它們進行賦值不起作用。沒有默認構造函數的類成員,以及const或者引用類型的成員,不管哪一類型,都必須在構造函數初始化列表中進行初始化。例如
class ConstRef{
public:
ConstRef(int ii);
private:
int i;
const int ci;
int &ri;
};
ConstRef::ConstRef(int ii)
{
i=ii;//ok
ci=ii;//error
ri=i;//error
}
可以初始化const對象或引用類型的對象,但不能對它們賦值。初始化const或引用類型數據成員的唯一機會是在構造函數初始化列表中。如
ConstRef::ConstRef(int ii):i(ii),ci(ii),ri(ii){}
成員被初始化的次序就是定義成員的次序。如
class X{
int i;
int j;
public:
X(int val):j(val),i(j){}//error,i先被初始化,爲隨機值;j被初始化爲val
};
一個類哪怕只定義了一個構造函數,編譯器也不會再生成默認構造函數。這條規則的根據是,如果一個類在某種情況下需要控制對象初始化,則該類很可能在所有情況下都需要控制。
只有當一個類沒有定義構造函數時,編譯器纔會自動生成一個默認構造函數。
如果一個類NoDefault沒有默認構造函數,意味着:
1.具有NoDefault成員的每個類的每個構造函數,必須通過傳遞一個初始的string值給NoDeafult構造函數來顯示地初始化NoDefault成員
2.編譯器將不會爲具有NoDefault類型成員的類合成默認構造函數。如果這樣的類希望提供默認構造函數,就必須顯示的定義,並且默認構造函數必須顯示地初始化其NoDefault成員。
3.NoDefault類型不能用作動態分配數組的元素類型
4.NoDefault類型的靜態分配數組必須爲每個元素提供一個顯示的初始化式
5.如果有一個保存NoDefault對象的容器,例如vector,就不能使用接受容器大小而沒有同時提供一個元素初始化式的構造函數。
使用默認構造函數定義一個對象的正確方式是去掉最後的空括號:
Sales_item myobj;
Sales_item myobj=Sales_item();
可以用單個實參來調用的構造函數定義了從形參類型到該類類型的一個隱式轉換。
class Sales_item{
public:
Sales_item(const string&book=" "):isbn(book),units_sold(0),revenue(0.0){}
Sales_item(istream &is);
};
上面每個構造函數都定義了一個隱式轉換。因此,在期待一個Sales_item類型對象的地方,可以使用一個string或一個istream:
string null_book=“9-999-99999-9”;
item.same_isbn(null_book);
same_isbn函數期待一個Sales_item對象作爲實參,編譯器使用接受一個string的Sales_item構造函數從null_book生成一個新的Sales_item對象。新生成的(臨時的)Sales_item被傳遞給same_isbn函數。
抑制由構造函數定義的隱式轉換,可以通過將構造函數聲明爲explicit,來防止需要隱式轉換的上下文中使用構造函數:
class Sales_item{
public:
explicit Sales_item(const string &book=""):isbn(book),units_sold(0),revenue(0.0){}
explicit Sales_item(istream &is);
};
explicit關鍵字只能用於類內部的構造函數聲明上。在類的定義體外部所做的定義上不再重複它。
item.same_item(null_book);//error
item.same_item(cin);//error
要想解決上述問題,可以用顯式的構造函數來生成轉換:
item.same_item(Sales_item(null_book));
通常除非有明顯的理由想要定義隱式轉換,否則,單形參構造函數應該爲explicit。將構造函數設置爲explicit可以避免錯誤,並且當轉換有用時,用戶可以顯式地構造函數。
12.5 友元
友元機制允許一個類將對其非公有成員的訪問權授予指定的函數或類。友元的聲明以關鍵字friend開始。友元函數的聲明可以出現在類中的任何地方:友元不是授予友元關係的那個類的成員,所以它們不受其聲明出現部分的訪問控制影響。通常,將友元聲明成組地放在類定義的開始或結尾。
友元可以是普通的非成員函數,或前面定義的其他類的成員函數,或整個類。將一個類設爲友元,友元類的所有成員函數都可以訪問授予友元關係的那個類的非公有成員。
用友元引入的類名和函數(定義或聲明),可以像預先聲明的一樣使用:
class X{
friend class Y;
friend void f(){}//可以在類內部定義
}
Class Z{
Y *ymem;//X中Y已經聲明過
void g(){return ::f();}//X中f()已定義
};
12.6 static成員
非static數據成員存在於類類型的每個對象中。不像普通的數據成員,static數據成員獨立於該類的任意對象而存在;每個static數據成員是與類關聯的對象,並不與該類的對象相關聯。
static成員函數沒有this形參,它可以直接訪問所屬類的static成員,但不能直接使用非static成員。
static成員不是任何對象的組成部分,所以static成員函數不能聲明爲const。畢竟,將成員函數聲明爲const就是承諾不會修改該函數所屬的對象。最後,static成員函數也不能聲明爲虛函數。
一般而言,類的static成員,像普通數據成員一樣,不能在類的定義體中初始化。相反,static數據成員通常在定義時才初始化。這個規則有個例外,只要初始化式是一個常量表達式,整型const static數據成員就可以在類的定義體中進行初始化。
static數據成員不是類對象的組成部分。static數據的類型可以是該成員所屬的類類型。
class Bar{
public:
private:
static Bar mem1;//ok
Bar *mem2;//ok
Bar mem3;//error
}