成員變量
每個類可以沒有成員,也可以定義多個成員,成員可以是數據、函數或類型別名。
一個類可以包含若干公有的、私有的和受保護的部分。在 public 部分定義的成員可被使用該類型的所有代碼訪問;在 private 部分定義的成員可被其他類成員訪問。protected可以被子類訪問。
構造函數
與類同名,且沒有返回值的函數,用於構造一個對象。
一般使用一個初始化列表來初始化數據成員,如下:
Sales_item() : units_sold(0),revenue(0.0){}
成員函數
在類內部,聲明成員函數是必需的,而定義成員函數則是可選的。
在類內部定義的函數默認為 inline。
在類外部定義的成員函數必須指明它們是在類的作用域中。Sales_item::avg_price 的定義使用作用域操作符來指明這是Sales_item 類中 avg_price 函數的定義。
將const加加到形參表之後,就可以變為常成員函數,在該函數內不能修改成員變量的值,如: double avg_price() const;
const必須出現在聲明和定義中,若出現一處,就會出現一個編譯時錯誤。
ps:const成員函數可以區分重載函數
Ø 顯示指定inline成員函數
可以在類定義體內部指定一個成員為 inline,作為其聲明的一部分。或者,也可以在類定義外部的函數定義上指定 inline。在聲明和定義處指定 inline都是合法的。在類的外部定義 inline 的一個好處是可以使得類比較容易閱讀。像其他 inline 一樣,inline 成員函數的定義必須在調用該函數的每個源文件中是可見的。不在類定義體內定義的inline成員函數,其定義通常應放在有類定義的同一頭文件中。
Ø 定義一個類
class Test{
}; //注意分號不能少
聲明一個類
class Test; //叫前向聲明
Ø 因為只有當類定義體完成後才能定義類,因此類不能具有自身類型的數據成員。然而,只要類名一出現就可以認為該類已聲明。因此,類的數據成員可以是指向自身類型的指針或引用,如下:
class LinkScreen {
Screen window;
LinkScreen *next;
LinkScreen *prev;
};
Ø 類的定義分號結束。分號是必需的,因為在類定義之後可以接一個對象定義列表。定義必須以分號結束:
class Sales_item{ /* ... */ };
class Sales_item{ /* ... */ } accum, trans;
Ø 隱含的this指針
成員函數具有一個附加的隱含形參,即指向該類對象的一個指針。這個隱含形參命名為 this,與調用成員函數的對象綁定在一起。成員函數不能定義this 形參,而是由編譯器隱含地定義。成員函數的函數體可以顯式使用 this 指針,但不是必須這麼做(比如return *this;)。如果對類成員的引用沒有限定,編譯器會將這種引用處理成通過 this 指針的引用。
Ø const 成員函數返回*this
在普通的非 const 成員函數中,this 的類型是一個指向類類型的 const指針。可以改變 this 所指向的值,但不能改變 this 所保存的地址。在 const 成員函數中,this 的類型是一個指向 const 類類型對象的const 指針。 既不能改變 this 所指向的對象, 也不能改變 this 所保存的地址。
****不能從 const 成員函數返回指向類對象的普通引用。const 成員函數只能返回 *this作為一個 const 引用。****
Ø 基於const的重載
基於成員函數是否為 const, 可以重載一個成員函數;同樣地,基於一個指針或者引用形參是否指向 const,可以重載一個函數。const對象只能使用 const 成員。非 const 對象可以使用任一成員,但非 const 版本是一個更好的匹配。
Ø 可變數據成員
可變數據成員(mutable data member)永遠都不能為 const(沖突),甚至當它是const 對象的成員時也如此。因此,const 成員函數可以改變mutable 成員。要將數據成員聲明為可變的,必須將關鍵字 mutable 放在成員聲明之前:
mutableint num;
Ø 形參表和函數體處於類作用域中
在定義於類外部的成員函數中,形參表和成員函數體都出現在成員名之後。這些都是在類作用域中定義,所以可以不用限定而引用其他成員。
charScreen::get(index r, indexc) const
{
index row = r * width; // compute the row location
return contents[row + c]; // offset by c to fetch specifiedcharacter
}
該函數用 Screen 內定義的 index 類型來指定其形參類型。因為形參表是在 Screen 類的作用域內,所以不必指明我們想要的是 Screen::index。我們想要的是定義在當前類作用域中的,這是隱含的。同樣,使用 index、width 和contents 時指的都是 Screen 類中聲明的名字。
Ø 函數返回類型不一定在類作用域中
與形參類型相比,返回類型出現在成員名字前面。如果函數在類定義體之外定義,則用於返回類型的名字在類作用域之外。如果返回類型使用由類定義的類型,則必須使用完全限定名。例如,考慮 get_cursor 函數:
classScreen {
public:
typedef std::string::size_type index;
index get_cursor() const;
};
inline Screen::index Screen::get_cursor() const
{
return cursor;
}
Ø 構造函數
Sales_items(); 對象在棧區
Sales_item*p=new Sales_item(); 對象在堆區
ps:構造函數不能聲明為const。
Sales_item()const; //error
pps:與其他函數相同的是,構造函數具有名字(與類名相同),形參表,函數體,不同的是無返回值,返回類型,且包含一個構造函數初始化列表:
Sales_item::Sales_item(conststring &book):isbn(book), units_sold(0), revenue(0.0) { }
等價於
Sales_item::Sales_item(conststring &book)
{
isbn = book;
units_sold = 0;
revenue = 0.0;
}
注意:這個構造函數給類 Sales_item 的成員賦值,但沒有進行顯式初始化。不管是否有顯式的初始化式,在執行構造函數之前,要初始化 isbn 成員。這個構造函數隱式使用默認的 string 構造函數來初始化 isbn。執行構造函數的函數體時,isbn成員已經有值了。該值被構造函數函數體中的賦值所覆蓋。
從概念上講,可以認為構造函數分兩個階段執行:(1)初始化階段;(2)普通的計算階段。計算階段由構造函數函數體中的所有語句組成。
不管成員是否在構造函數初始化列表中顯式初始化,類類型的數據成員總是在初始化階段初始化。初始化發生在計算階段開始之前。在構造函數初始化列表中沒有顯式提及的每個成員,使用與初始化變量相同的規則來進行初始化。運行該類型的默認構造函數,來初始化類類型的數據成員。內置或復合類型的成員的初始值依賴於對象的作用域:在局部作用域中這些成員不被初始化,而在全局作用域中它們被初始化為 0。
ppps:有時需要構造函數初始化列表。如果沒有為類成員提供初始化式,則編譯器會隱式地使用成員類型的默認構造函數。如果那個類沒有默認構造函數,則編譯器嘗試使用默認構造函數將會失敗。在這種情況下,為了初始化數據成員,必須提供初始化式。有些成員必須在構造函數初始化列表中進行初始化。對於這樣的成員,在構造函數函數體中對它們賦值不起作用。沒有默認構造函數的類類型的成員,以及 const 或引用類型的成員,不管是哪種類型,都必須在構造函數初始化列表中進行初始化。
如:
classConstRef {
public:
ConstRef(int ii);
private:
int i;
const int ci;
int &ri;
};
ConstRef::ConstRef(intii)
{ i =ii; // ok
ci = ii;// error: cannot assign to a const,只能初始化
ri = i; // assigns to ri which was not bound to an object,errpr
}
改為:ConstRef::ConstRef(int ii): i(ii), ci(i), ri(ii) { }
ps:成員初始化的順序
每個成員在構造函數初始化列表中只能指定一次,這不會令人驚訝。畢竟,給一個成員兩個初始值意味著什麼?也許更令人驚訝的是,構造函數初始化列表僅指定用於初始化成員的值,並不指定這些初始化執行的次序。成員被初始化的次序就是定義成員的次序。 第一個成員首先被初始化,然後是第二個, 依次類推。初始化的次序常常無關緊要。然而,如果一個成員是根據其他成員而初始化,則成員初始化的次序是至關重要的。
如:
class X{
int i;
int j;
public:
// run-time error: i is initialized before j
X(int val): j(val), i(j) { }
};
在這種情況下,構造函數初始化列表看起來似乎是用 val 初始化 j,然後再用 j 來初始化 i。然而,i 首先被初始化。但這個初始化列表的實際效果是用尚未初始化的 j 值來初始化 i!
如果數據成員在構造函數初始化列表中的列出次序與成員被聲明的次序不同,那麼有的編譯器非常友好,會給出一個警告。按照與成員聲明一致的次序編寫構造函數初始化列表是個好主意。此外,盡可能避免使用成員來初始化其他成員。
寫成X(int val): i(val), j(val) { }可能更好!
l 初始化式可以是任意表達式,Sales_item(conststd::string &book, int cnt, double price) : isbn(book), units_sold(cnt),revenue(cnt * price) { }
l 類類型的數據成員的初始化式,可以使用該類型的任意構造函數。例如,Sales_item 類可以使用任意一個 string構造函數來初始化 isbn。也可以用 ISBN 取值的極限值來表示isbn 的默認值,而不是用空字符串。即可以將 isbn 初始化為由 10 個 9 構成的串:
Sales_item(): isbn(10, '9'),units_sold(0), revenue(0.0) {}
l 構造函數也可以有默認參數
l 默認構造函數
只有當一個類沒有定義構造函數時,編譯器才會自動生成一個默認構造函數。哪怕只定義了一個構造函數,編譯器也不會再自動生成默認構造函數了。合成的默認構造函數使用與變量初始化相同的規則來初始化成員。即類成員使用默認構造函數,內置變量不初始化(與java不同,java中都是值初始化,即類成員為null,int為0,double為0.0)。
ps:通常應為類定義一個默認構造函數,如果沒有,則會有以下問題:
1、定義對象時必須傳參
2、該類不能用作動態分配數組的元素類型
3、如果容器保存該類對象,如vector,則不能使用只接受容器大小的構造函數
pps:使用默認構造函數
Sales_item myobj; //正確!
或者 Sales_item myobj=Sales_item(); //先創建一個無名對象,再用它復制給myobj
Sales_item myobj(); //當成了一個函數聲明!!
Ø 隱式類類型轉換
classSales_item {
public:
Sales_item(const std::string &book = ):isbn(book),units_sold(0), revenue(0.0) { }
Sales_item(std::istream &is);
};
當string null_book = 9-999-99999-9;
item.same_isbn(null_book);
和item.same_isbn(cin); 都會隱式執行構造函數創建一個臨時對象,再調用函數same_isbn。
但是如下:
classSales_item {
public:
explicit Sales_item(const std::string&book = ):isbn(book), units_sold(0), revenue(0.0) { }
explicit Sales_item(std::istream&is);
};
explicit 關鍵字只能用於類內部的構造函數聲明上。在類的定義體外部所做的定義上不可再寫它!!
item.same_isbn(null_book); // error: string constructor is explicit
item.same_isbn(cin); // error:istream constructor is explicit
只能item.same_isbn(Sales_item(null_book));
ps:通常,除非有明顯的理由想要定義隱式轉換,否則,單形參構造函數(轉換構造函數??)應該為 explicit。將構造函數設置為explicit可以避免錯誤,並且當轉換有用時,用戶可以顯式地構造對象。
Ø 當類的全體數據成員都是 public時,可以Data val2 = { 1024, Anna Livia Plurabelle };
這樣初始化,且列表順序為成員變量定義的順序。
Ø 友元
友元機制允許一個類將對其非公有成員的訪問權授予指定的函數或類。友元的聲明以關鍵字friend 開始。它只能出現在類定義的內部。友元聲明可以出現在類中的任何地方:友元不是授予友元關系的那個類的成員,所以它們不受聲明出現部分的訪問控制影響。通常, 將友元聲明成組地放在類定義的開始或結尾是個好主意。
ps:在類內部public下的typedef 的類型名,在類外訪問要用類名::類型名,如vector
例子:
classScreen {
friend class Window_Mgr; //友元類,則Window_Mgr 的所有成員函數可以直接引用 Screen 的私有成員。
};
ps:友元可以是普通的非成員函數,或前面定義的其他類的成員函數,或整個類。將一個類設為友元,友元類的所有成員函數都可以訪問授予友元關系的那個類的非公有成員。
classScreen {
friendWindow_Mgr&Window_Mgr::relocate(Window_Mgr::index,Window_Mgr::index,Screen&); //友元成員函數
};
ps:當我們將成員函數聲明為友元時,函數名必須用該函數所屬的類名字加以限定。
² 友元聲明與作用域
為了正確地構造類,需要注意友元聲明與友元定義之間的互相依賴。在前面的例子中,類 Window_Mgr 必須先定義。否則,Screen 類就不能將一個Window_Mgr 函數指定為友元。然而,只有在定義類 Screen 之後,才能定義relocate 函數——畢竟,它被設為友元是為了訪問類 Screen 的成員。
Ø static類成員
類可以定義共享的 static 數據成員,類也可以定義 static 成員函數。static 成員函數沒有 this 形參,它可以直接訪問所屬類的 static 成員,但不能直接使用非 static 成員。
ps:可以通過作用域操作符從類直接調用 static 成員,或者通過對象、引用或指向該類類型對象的指針間接調用。如:
classAccount {
public:
void applyint() { amount += amount * interestRate; }
static double rate() {return interestRate; }
static void rate(double);// sets a new rate
private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
};
Accountac1;
Account*ac2 = &ac1;
rate =ac1.rate(); // through an Account object or reference
rate =ac2->rate(); // through a pointer to anAccount object
rate =Account::rate(); // directly from the class using thescope operator
ps:當我們在類的外部定義 static 成員時,無須重復指定 static 保留字,該保留字只出現在類定義體內部的聲明處,同explicit。
pps:因為 static 成員不是任何對象的組成部分,所以 static 成員函數不能被聲明為 const。畢竟,將成員函數聲明為const 就是承諾不會修改調用該函數的對象,而static成員函數本質上是類名換用(即使用對象調用了!)。 最後, static 成員函數也不能被聲明為虛函數(不被重寫!!)。
ppps:static 數據成員必須在類定義體的外部定義(正好一次)。不像普通數據成員,static成員不是通過類構造函數進行初始化,而是應該在定義時進行初始化。
double Account::interestRate = initRate();
同static成員函數,static 關鍵字只能用於類定義體內部的聲明中,定義不能標示為static。
² 特殊的整型 const static 成員,可以在類的定義體中進行初始化。
classAccount {
public:
static double rate() { return interestRate; }
static void rate(double); // sets a new rate
private:
static const int period = 30; // interest postedevery 30 days
double daily_tbl[period]; // ok: period is constant expression
};
constint Account::period; //沒有static,沒有初始值
ps:const static 數據成員在類的定義體中初始化時,該數據成員仍必須在類的定義體之外進行定義。在類內部提供初始化式時,成員的定義不必再指定初始值,如上。