《C++Primer》14、15章
第14章 重載運算符與類型轉換
14.1 基本概念
只有當操作的含義對於用戶來說清晰明了時才使用運算符。
選擇作為成員還是非成員?
賦值、下標、調用和成員訪問運算符必須是成員。
復合賦值運算符一般是成員。
改變對象狀態或者與給定類型密切相關的,如遞增、解引用通常是成員。
具有對稱性的運算符可能轉換任意一端元素的運算對象,例如算數、相等、關系和位運算等,通常是非成員函數。
14.2 輸入輸出運算符(略)
14.3 算術與關系運算符(略)
14.4 賦值運算符(略)
14.5 下標運算符
通常會定義兩個版本:一個返回普通引用,一個返回常量引用。
class StrVec{
public:
std::string& operator[](std:size_t n)
{return elements[n];}
const std::string& operator[](std::size_t n) const
{return elements[n];}
private:
std::string *elements;
}
StrVec svec(10);
svec[0] = “zero”;
14.6 遞增和遞減運算符
區分前置和後置運算符
後置定義時加上(int)
為了與內置版本保持一致,前置運算符應該返回遞增或遞減後對象的引用。
後置運算符應該返回對象的原值。
解釋了有哪些是生成右值?
14.7 成員訪問運算符(略)
14.8 函數調用運算符
如果類定義了調用運算符,則該類的對象稱為函數對象。
實驗:函數調用運算符必須是非靜態成員函數
函數調用運算符適用於對象的名稱,而不是函數的名稱。 |
14.8.1 lambda是函數對象
當我們編寫一個lambda後,編譯器將該表達式翻譯成一個未命名的類的未命名對象。該類中包含一個重載的函數調用運算符。
14.8.2 標準庫定義的函數對象
plus<Type>
less<Type>
equal_to<Type>
在算法中使用標準庫函數對象:
比如
vector<string> svec;
sort(svec.begin(), svec.end(), greater<string>());
14.8.3 可調用對象與function
普通函數
int add(int i, int j) {return i + j;}
lambda
auto mod = [] (int i, int j) {return i%j};
函數對象類
struct divide {
int operater()(int denominator, int divisor) {
return denominator /divisor;
}
};
標準庫function類型:function<int(int,int)> 它表示接受兩個int、返回一個int的可調用對象。
function<int(int,int)> f1 = add;
function<int(int,int)> f2 = divide();
function<int(int,int)> f3 = [](int j,int i){return j*i;};
5種可調用對象,完善函數指紋的不能完成的
map<string, function<int(int,int)>>binops = {
{“+”, add},
{“-”, std::minus<int>()},
{“/”, divide()},
{“*”, [](int i, int j){return i*j;}},
{“%”, mod}
};
14.9 重載、類型轉換與運算符
顯示的類型轉換運算符
為了防止異常情況的發生,C++11引入了顯示的類型轉換運算符(explicit conversion operator)
class SmallInt {
public:
SmallInt(int i =0):val (i)
{
}
explicit operator int() const { return val;}
private:
std::size_t val;
};
當表達式如下時,顯示的類型轉換將被隱式地執行:
if、while、do、for語句的條件表達式、邏輯運算、條件運算符
第15章 面向對象程序設計
15.1 OOP:概述
面向對象程序設計的核心思想:數據抽象、繼承和動態綁定。
數據抽象:可以將類的接口和實現分離;
繼承:相似性的類、相似性關系建模;
動態綁定:忽略相似性類的區別,以統一的方式使用對象。
虛函數:希望它的派生類各自定義適合自己的版本。
class Quote{
public:
string isbn() const;
virtual double net_price(std::size_t n) const;
};
class Bulk_quote: public Quote{
public:
double net_price(std::size_t n) const override;
};
動態綁定(運行時綁定):當我們使用基類的引用或指針調用一個虛函數時將發送動態綁定。
每個類負責定義各自的接口,要想與類的對象交互必須使用該類的接口。
防止繼承的發生
class NoDerived final {};
15.2 定義基類和派生類
類型轉換:
從派生類向基類的轉換只對指針或引用類型有效;
基類向派生類不存在隱式類型轉換(不怕出問題的顯示強制轉換是可以的);
15.3 虛函數
基類希望其派生類進行覆蓋的函數。
override和final的作用
15.4 抽象基類
純虛函數:無需定義。函數聲明處最後=0。
class exp{
public:
double net_price(std::size_t) const = 0;
};
含有(或者未經覆蓋直接繼承)純虛函數的類稱為抽象基類。抽象基類負責定義接口。不能創建抽象基類的對象。
15.5 訪問控制和繼承
每個類負責控制自己的成員的訪問控制。有元關系不能傳遞,也不能繼承。
struct D1:Base{}; // 默認public繼承
class D2:Base{}; //默認private繼承
struct和class唯一區別:默認成員訪問說明符和默認派生訪問說明符。
15.6 繼承中的類作用域
在編譯時進行名字查找。
除了覆蓋繼承而來的虛函數之外,派生類最好不要重用其他定義的基類中的名字。
名字查找先於類型檢查。如果派生類的成員和基類的某個成員同名,則派生類將在其作用域內隱藏該基類成員。所以要麽重載所有基類的某類函數,要麽一個也不覆蓋。
15.7 構造函數和拷貝控制
派生類中的創建、拷貝、移動、賦值、銷毀。
15.7.1 虛析構函數
將基類的析構函數設置為虛函數,確保當我們delete基類指針時將運行正確的析構函數。
15.7.2 合成拷貝控制與繼承
基類因為定義了析構函數而不能擁有合成的移動操作,因此當我們移動基類時,實際運行的是基類的拷貝操作。基類沒有移動,意味著派生類也沒有移動操作。
但是可以在基類中顯示的定義移動、拷貝操作。
15.7.3 派生類的拷貝控制成員
派生類的析構函數只負責派生類自己分配的資源。派生類的構造和移動需要負責包括基類部分成員部分。
class Base{};
Base &Base::operator=(const Base&){
//...
return *this;
};
class D:public Base{
public:
D(const D& d):Base(d){};
D(&& d):Base(std::move(d)){};
};
D &D::operater=(const D&rhs)
{
Base::operater=(rhs);
//...
return *this;
}
如果想在派生類中拷貝和移動基類部分,則必須在派生類的構造函數初始值列表中顯示的使用基類的拷貝構造函數。
15.8 容器與繼承
當派生類對象被賦值給基類對象時,其中的派生類部分將被切掉,因此容器和存在繼承關系的類型無法兼容。
解決辦法:在容器中放置(智能)指針而非對象。
例子:
vector<shared_ptr<Quote>> basket;
basket.push_back(make_shared<Quote>(“123-3344”,2));
basket.push_back(make_shared<Bulk_quote>(“234-344”,50));
《C++Primer》14、15章