基本概念
成員函數
//頭文件Sales_data.h struct Sales_data{ std::string isbn() const { return bookNo; } Sales_data& combine(const Sales_data); double avg_price() const; std::string bookNo; unsigned units_sold; double revenue; }; Sales_data add(const Sales_data&, const Sales_data&); std::ostream &print(std::ostream&, const Sales_data&); std::istream &read(std::istream&, Sales_data&);
在內部聲明,通常在外部定義。
成員函數通過一個額外的隱式形參this獲取對象的地址,可以等價的認爲是如下形式
total.isbn(); //Sales_data::isbn(&total)
在成員函數的內部,可以直接使用對象的成員,對類成員的直接訪問可以被看做this的飲式引用。
std::string isbn() const { return this -> bookNo; }
this是一個常量指針。
常量成員函數
緊隨參數列表後的const關鍵字,作用是修改隱式this指針的類型。默認下,this的類型僅是頂層常量指針(本身是常量,指向非常量),它的初始化就會使得我們不能把this綁定到一個常量對象上。導致的結果就是,我們不能在一個常量對象上調用普通的成員函數。
前提條件是在isbn函數體內不會改變this所指對象的成員,因爲此時this的聲明已經是const Sales_data *const,這樣也可以提高函數的靈活性,畢竟常量對象(的引用或指針)只能調用常量成員函數。
成員函數的定義
通常放在外部,與聲明保持一致,使用作用域運算符。
返回this對象的函數
Sales_data& Sales_data::combine(const Sales_data& rhs) { units_sold += rhs.units_sold; revenue += rhs.revenue; return *this; }
當定義的函數類似於某個內置運算符時,應該令該函數的行爲儘量模仿這個運算符,內置的賦值運算符把它的左值運算當成佐值返回,此處返回Sales_data的引用是爲了與其保持一致。
類相關的非成員函數
雖然實際上並不屬於類本身,但是類接口的組成部分,一般都在同一頭文件中聲明。
std::istream &read(std::istream &is, Sales_data& itm) { double price = 0; is >> itm.bookNo >> itm.units_sold >> price; itm.revenue = price * itm.units_sold; return is; }
讀寫操作會改變流的內容,所以都是普通引用
print函數不負責換行,執行輸出任務的函數應該儘量減少對格式的控制。
構造函數
初始化類對象的數據成員,在類的對象被創建的同時執行。
沒有返回類型。
不能聲明成const的
合成的默認構造函數:由編譯器創建,若存在類內的初始值,用其初始化成員;否則默認初始化各成員(vs中不支持)
帶構造函數的struct類
struct Sales_data{ Sales_data() = default; //vs2010不支持類內初始值,Sales_data() {} Sales_data(const std::string &s): bookNo(s){} Sales_data(const std::string &s, unsigned n ,double p):bookNo(s), units_sold(n), revenue(p * n){} Sales_data(std::istream &); std::string isbn() const { return this -> bookNo; } Sales_data& combine(const Sales_data&); double avg_price() const; std::string bookNo; unsigned units_sold; double revenue; };
對於不支持類內初始值的編譯器,可以在構造函數中添加一個賦值,例如:
Sales_data(const std::string &s): bookNo(s), units_sold(0){}
也可以在類的外部定義構造函數,如:
Sales_data::Sales_data(std::istream &is) { read(is, *this); }
注意,在類的聲明部分,Sales_data(std::istream &is)
沒有結構體。
拷貝、賦值和析構
若不主動定義,編譯器將會對每個對象成員執行拷貝、賦值和銷燬操作。
不過某些類不能依賴於合成的版本,比如管理動態內存的類。
訪問控制與封裝
訪問說明符
public,成員可以在整個程序內被訪問
private,僅可以被類的成員函數訪問。
新的Sales_data類
class Sales_data{ public: Sales_data() {} Sales_data(const std::string &s): bookNo(s), units_sold(0){} Sales_data(const std::string &s, unsigned n ,double p): bookNo(s), units_sold(n), revenue(p * n){} Sales_data(std::istream &); std::string isbn() const { return this -> bookNo; } Sales_data& combine(const Sales_data&); private: double avg_price() const; std::string bookNo; unsigned units_sold; double revenue; };
class和struct
struct定義在第一個訪問說明符之前的成員是public的
class定義在第一個訪問說明符之前的成員是private的
友元
使其它類或者函數訪問它的非公有成員
僅僅是指定了訪問權限,並非函數聲明,所以還要再對這些函數做一次聲明
添加了友元函數的類
class Sales_data{ friend Sales_data add(const Sales_data&, const Sales_data&); friend std::ostream &print(std::ostream&, const Sales_data&); friend std::istream &read(std::istream&, Sales_data&); public: Sales_data() {} Sales_data(const std::string &s): bookNo(s), units_sold(0){} Sales_data(const std::string &s, unsigned n ,double p): bookNo(s), units_sold(n), revenue(p * n){} Sales_data(std::istream &); std::string isbn() const { return this -> bookNo; } Sales_data& combine(const Sales_data&); private: double avg_price() const; std::string bookNo; unsigned units_sold; double revenue; };
類的其他特性
類型成員
先定義後使用
typedef std::string::size_type pos;
內聯函數
定義在類內部的成員函數默認爲inline
可以在類內部顯式聲明爲內斂函數,也可以在外部使用inline修飾函數的定義。
無須再聲明和定義處同時說明inline
可變數據成員
mutable關鍵字
永遠不會是const,即使是const對象的成員
返回*this的成員函數
返回的的調用成員函數對象的引用Screen&,而非拷貝,這樣就可以把一系列的操作連在一條表達式中
myScreen.move(4,0).set('#');
否則若返回類型是Screen,則set只會修改臨時副本,而不改變myScreen
基於const的重載
display爲一個const成員函數,返回*this,此時this是一個指向const的指針,*this是const對象。
返回類型即爲const Screen&
此時myScreen.display().set('#');中,調用set自然就會引發錯誤。
使用const重載的方式根據對象是否爲const判斷。
class Screen{ public: Screen &display() {do_display(); return *this;} const Screen &display() const {do_display(); return *this;} private: void do_display(){}; };
類的聲明
前向聲明:class Screen
在聲明之後定義之前是一個不完全類型,使用場景有限:定義指針、引用,聲明(不能定義)作爲函數參數或返回類型。
允許包含指向它自身類型的引用或指針。
友元類
友元類的成員函數可以訪問此類包括非公有成員在內的所有成員。
class Screen{ friend class Window_mgr; };
友元不存在傳遞性。
還可以只爲某個類的成員函數提供權限。
class Screen{ friend void Window_mgr::clear(ScreenIndex); };
這樣只有Window_mgr的成員函數clear可以隨意訪問Screen
對重載函數而言,雖然名字相同,但仍然是不同的函數,所以要分別對每一個進行友元聲明。
友元的聲明並不代表函數的聲明,即便是在類的內部使用,也必須要在類的外部提供相應的聲明。(有的編譯器並不強制要求,如vs)
類的作用域
一個類就是一個作用域,對於定義在類外部的成員,要使用類名和作用域運算符,此時,定義的剩餘部分就在作用域之內了。
名字查找順序:由內而外逐層查找。
編譯過程:1.編譯成員的聲明;2.類全部可見後才編譯函數體
對於普通類成員,編譯器會逐層向外查找,不用擔心衝突。
但對於類型名而言,如果外層已經將其定義成爲了一種類型,則類內部不能再重新定義。編譯器可能並不對此負責。(vs不支持內外層重名)
在內外層重名的情況下,如果一定要使用外層的,可以使用作用域運算法
::height
類的靜態成員
關鍵字static
與類本身直接相關,而不是與類的各個對象保持關聯,被所有對象共享。
利用作用域運算符直接訪問,類的對象也可以直接訪問。
可以在類的外部定義,但不能重複static關鍵字。
靜態數據成員不屬於類的任何一個對象,故不是在創建類的對象時被定義,即不是由構造函數初始化的。不能在類的內部定義和初始化靜態成員,必須在外部。
int Sales_data::testSta = 9;
可以爲靜態成員提供const整數類型的類內初始值,不過要求靜態成員必須是constexpr,初始值必須是常量表達式。例如可以用一個初始化了的靜態數據成員指定數組的維度
static const int dim = 10; //static constexpr int dim = 10;
double array[dim];
如果靜態成員的應用場景僅限於類似於上述編譯器可替換的情況,則一個初始化的const或者constexpr不需要分別定義;相反則必須要有定義語句。
靜態數據成員可以是不完全對象。
class Bar{ private: static Bar mem1; //正確 Bar* mem2; //正確,指針也可以是不完全對象 Bar mem3; //錯誤,數據成員不可以 };
可以使用靜態數據成員作爲默認實參。
構造函數詳解
初始化與賦值
有時初始值必不可少,比如常量和指針。
class ConstRef{ public: ConstRef(int num); private: int i; const int ci; int &ri; };
上述類必須提供構造函數,此時若採用賦值的方式顯然會引發錯誤,需要使用初始化的方式。
//錯誤 Screen(int ii){ i = ii; ri = ii; } //正確 Screen(int ii): i(ii), ri(ii) {}
初始化的順序
理論上是按照成員在類中定義的順序,儘量避免用一個成員初始化另一個成員。
默認實參和構造函數
Sales_data(std::string s = ""): bookNo(s), units_sold(0){} Sales_data(std::string s, unsigned n ,double p): bookNo(s), units_sold(n), revenue(p * n){}
注意默認實參不要衝突,特別是跟默認構造函數
Sales_data() {}
委託構造函數
C++11新標準,使用它所屬類的其他構造函數執行它自己的初始化過程。
Sales_data(std::string s, unsigned n ,double p): bookNo(s), units_sold(n), revenue(p * n){} Sales_data(): Sales_data("", 0, 0) {} Sales_data(std::istream &is): Sales_data() {read(is, *this); }
受委託的構造函數的初始值列表和函數體被依次執行。先執行受委託函數體的代碼,在交還給委託者的函數體。
默認構造函數
默認初始化發生情況
(1)在塊作用域中不適用任何初始值定義一個非靜態變量
(2)類中含有類類型成員且使用合成的默認構造函數
(3)類類型的成員沒有在構造函數初始值列表中顯式初始化
值初始化發生情況
(1)數組初始化中初始值數量不夠
(2)不使用初始值定義以局部靜態變量
(3)使用類似於int(8)
class NoDefault{ public: NoDefault(const std::string&); //還有其它成員,但是構造函數只有這一個 }; class A{ NoDefault mem1; }; A a; //錯誤,情況(2),不能爲A合成構造函數 class B{ B(){} //錯誤,mem2不能沒有初始值 NoDefault mem2; };
使用默認構造函數
Sale_data obj1(); //錯誤,obj1是個函數 Sale_data obj2; //正確
如果定義了其它的構造函數,最好順便也提供一個默認構造函數。
隱式的類類轉換類型
通過一個實參調用的構造函數定義了一條從構造函數的參數類型向類類型隱式轉換的規則。
string null_book = "99-99"; Sales_data item("12", 5, 4.4); item.combine(null_book);
相當於編譯器利用給定的null_book自動創建了一個臨時的Sales_data
編譯器只會自動執行一步類型轉換
item.combine("99-99"); //錯誤
抑制構造函數定義的隱式轉換
關鍵字explicit
explicit Sales_data(std::string s = ""): bookNo(s), units_sold(0){}
只能在類內聲明構造函數時使用,在類外部定義時不應重複
發生隱式轉換的另一種情況是使用拷貝形式的初始化
Sales_data item = null_book;
當使用explicit時只能使用直接初始化
Sales_data item(null_book);
可以顯式的直接使用
item.combine(Sales_data(null_book)); item.combine(Sales_data(cin)); item.combine(static_cast<Sales_data>(cin));
標準庫中的
string中,單參數的const char*不是explicit的
vector中接受容量參數的,是explicit的
聚合類
(1)所有成員都是Public的
(2)沒有定義任何構造函數
(3)沒有類內初始值
(4)沒有基類,沒有virtual函數
把初始化的任務交給了類的用戶,而不是作者
字面值常量類
除了算術類型、引用和指針外,有些類也是字面值類型
聚合類或者滿足下列條件:
(1)數據成員全爲字面值類型
(2)至少含有一個constexpr構造函數
(3)若某個數據成員含有類內初始值,則內置類型成員的初始值也必須是一條常量表達式;若是某種類類型,則初始值必須使用自己的constexpr構造函數
(4)必須使用析構函數的默認定義
constexpr的構造函數可以聲明成默認=default形式,或者是刪除函數的形式。
否則,構造函數的要求是:既不能包含返回語句(構造函數),又要保證唯一可執行的語句就是返回函數(constexpr函數的要求),所以函數體一般是空的。
前置關鍵字constexpr
必須初始化所有數據成員,使用初始值、constexpr構造函數或者常量表達式
constexpr構造函數用於產生constexpr對象以及constexpr函數的參數或返回類型。
vs中只用include頭文件
一般都把聲明放在頭文件中,編譯器先編譯了所有的變量和函數的聲明,然後再去具體查看函數的定義,vs會自動在各個cpp文件中編譯函數,若重複include cpp文件,編譯器會當做重名錯誤來處理