C++ Primer筆記

chendl111發表於2021-02-17

C++ Primer筆記

ch2 變數和基本型別

宣告

extern int i;
extern int i = 3.14;//定義

左值引用(繫結零一變數初始值,別名)

不能定義引用的引用;引用必須被初始化;型別嚴格匹配;不能繫結字面值/計算結果;無法二次繫結

int i=4;
int &r=i;

指標

本身是物件,允許賦值和拷貝;無需定義時賦初值;型別嚴格匹配

int *ip1, *ip2;
int ival = 42;
int *p = &ival;
*p = 0;
cout << *p;
int *p2 = p;
int &r2 = *p;

指標值:1.指向一個物件;2.指向緊令物件的下一個位置;3.nullptr;4.無效指標

不能把int變數直接賦值給指標

有時候無法清楚賦值語句改變了指標的值還是指標所指物件的值:賦值永遠改變等號左邊的物件

指標比較:(==)相等時意味著地址值相等(都為空/指向同一物件/指向同一物件的下一地址)

**表示指向指標的指標

int ival = 1024;
int *pi = &ival;
int **ppi = &pi;//ppi指向pi的地址
int *pi2 = pi; //pi2和pi指向同一物件

指向指標的引用

int i = 42;
int *p;
int *&r = p;//r是p的引用

r = &i;//r引用了p,此處讓p指向i
*r = 0;

理解r的型別是什麼,最簡單的是從右向左讀r的定義,&r的&表明r是一個引用,*說明r引用的是一個指標,int指出r引用的是一個int指標

const

必須初始化;

預設狀態下,const物件僅在檔案內有效,共享必須新增extern

//檔案間共享const物件
extern const int bufSize = fcn();//定義初始化常量,且能被其他檔案訪問
extern const int bufSize;

const引用

初始化常量引用時允許用任意表示式

int i = 42;
const int &r1=i;
const int &r2=42;
const int &r3=r1*2;

double dval = 3.14;
const int &ri = dval;//浮點數生成臨時常量再繫結

對const的引用可能會引用一個並非const的物件

指向常量的指標:不能通過該指標改變物件的值。

double dval = 3.14;
const double *cptr = &dval;

const指標(必須初始化,值不能改變)

//從右向左確定含義
int errNumb = 0;
int *const curErr = &errNumb;//curErr一直指向errNumb
const double pi = 3.14;
const double *const pip = &pi;//pip是一個指向常量物件的常量指標
  • 頂層const:指標本身是常量
  • 底層const:指標所指物件是常量
    int i = 0;
    int *const pi = &i;//頂層const
    const int ci = 42;//頂層const
    const int *p2 = &ci;//底層const
    const int *const p3 = p2;//靠右是頂層,靠左是底層
    const int &r = ci;//用於宣告的都是底層const
    //拷貝時,頂層const不受影響;底層const必須相同
    i = ci;
    p2 = p3;
    int *p = p3;//wrong,p3有底層,p沒有
    p2 = p3;//都是底層
    p2 = &i;//int*能轉換為const int*
    int &r = ci;//wrong,int&不能繫結到int常量上
    const int &r2 = i;//const int&可以繫結到int

常量表示式:constexpr

ch3 字串、向量和陣列

標頭檔案不應包含using宣告

string

  1. 定義和初始化

    string s1 = "hiya";//預設初始化使用=
    string s2("hiya");//直接初始化
    string s3(10,'c');//直接初始化
    
  2. getline(cin, line),使用endl結束當前行並重新整理緩衝區

  3. empty和size操作
    size返回值:string::size_type。返回的是一個無符號整數,不要使用int了

  4. 比較(長度/大小寫敏感)

  5. 賦值/物件相加/字面值與string相加(保證每個加法運算物件之一是string)

  6. 處理字元:range for語句

    decltype(s.size()) punct_cnt = 0;//使用s.size()返回值型別宣告punct_cnt
    for(auto &c : s){...}//加引用改變字元
    
  7. 下標(隨機訪問)

vector(容器/類别範本)

  1. 定義和初始化

    vector<T> v1//預設初始化
    vector<T> v2(v1);
    vector<T> v2 = v1;
    vector<T> v4(n);//值初始化
    vector<T> v5{a,b,c,...};//列表初始化
    vector<T> v6={a,b,c,...};
    
  2. 操作

    v.push_back()//新增
    v.empty()
    v.size()//vector<int>::size_type
    v1 = v2;//拷貝
    v1 = {a,b,c}//列表拷貝
    v1 == v2;//數量相同且元素值相同
    

    不能用下標新增元素

迭代器

*iter//返回iter所指元素的引用
iter->data//解引用獲取data的成員,等價於(*iter).data
++iter
--iter
iter1 == iter2
auto e = v.end();//因為end返回的迭代器不指示某個元素,不能對其遞增或解引用的操作

迭代器運算

陣列

//初始化
const unsigned sz = 3;
int a[] = {0,1,2};
int a[5] = {0,1,2};
string a[] = {"hi","bye"}
int a2[]=a;//wrong,不允許拷貝賦值
a2 = a;
int *ptrs[10];//ptrs含有10個整形指標
int &refs[10];//wrong
int (*Parray)[10] = &arr;//Parray指向一個含有10個整數的陣列
int (&arrRef)[10] = arr;arrRef引用一個含喲10個整數的陣列
//陣列的名稱由內向外讀
int * (&array)[10] = ptrs://array是陣列的引用,該陣列包含10個指標

訪問陣列元素(size_t,無符號)

指標和陣列

string nums[] = {"one", "two", "three"};
string *p = nums;//等價於p2 = &nums[0]
auto ia(nums);//ia是一個string指標,指向nums的第一個元素

指標也是迭代器

int ia[] = {0,1,2};
int *beg = begin(ia);
int *last = end(ia);

尾後指標不能解引用和遞增操作

C風格字串

strlen(p)
strcmp(p1, p2)
strcat(p1, p2)//p2附加到p1之後
strcpy(p1, p2)//p2拷貝給p1

混用string和C風格字串

char *str = s;//wrong
const char *str = s.c_str();

使用陣列初始化vector物件

int int_arr[] = {0,1,2,3,4,5};
vector<int> ivec(begin(int_arr), end(int_arr));
vector<int> subVec(int_arr+1, int_arr+4);
//儘量使用vector和迭代器;儘量使用string

多維陣列

int ia[2][3] = {
    {1,2,3},
    {2,3,4}
};
//下標引用
ia[2][3] = arr[0][0][0];
int (&row)[4] = ia[1];//row繫結到ia的第二個4元素陣列上
size_t cnt = 0;
for(auto &row : ia)//不加引用則無法編譯
	for(auto &col : row){
    	col = cnt++;
}
//指標和多維陣列
int ia[3][4];
int (*p)[4] = ia;
p = &ia[2];//p指向ia的尾元素

ch4 表示式

過載運算子(運算物件的型別和返回值的型別)

  • (物件被用作)左值:用的是物件的身份(記憶體地址)
    1. 賦值運算子運算左值物件,返回左值
    2. 取地址符作用於左值,返回指向該運算的指標(右值)
    3. 解引用/下標/迭代器/string和vector的下標運算子,結果為左值
    4. 內建型別和迭代器的遞增遞減運算子,作用於左值,返回左值
  • (物件被用作)右值:用的是物件的值(內容)

處理複合表示式

  • 拿不準的時候最好用括號來限制
  • 如果改變了某個運算物件的值,在表示式的其他地方不要再使用這個運算物件(*++iter例外)

溢位和其他算術運算子異常(“環繞”)

//(-m)/n和m/(-n)等於-(m/n),m%(-n)等於m%n,(-m)%n等於-(m%n)
//除法運算中,兩個運算物件的符號相同則商為正,否則為負
//取餘運算中,m和n是整數且n非0,則(m/n)*n+m%n的結果與m相同
-21 % -8 = -5	-21 / -8 = 2
21 % -5 = 1	 21 / -5 = -4

進行比較運算時除非比較的物件是布林型別,否則不要使用true和false作為運算物件。

賦值運算滿足右結合律

除非必須,否則不使用遞增遞減運算子的後置版本(相對複雜的迭代器型別,額外工作消耗巨大)

一條語句中混用解引用和遞增運算子(*iter++|後置遞增運算子返回初始的未加1的值)

while(beg != s.end() && !isspace(*beg))
	*beg = toupper(*beg++);//wrong
*beg = toupper(*beg);
*(beg+1) = toupper(*beg);

條件運算子(?)右結合律,優先順序比<<低

sizeof(所得的值是size_t型別)

//sizeof運算能夠返回整個陣列的大小
constexpr size_t sz = sizeof(ia)/sizeof(*ia);
int arr[sz];

型別轉換

  • 隱式轉換
    1. 比int小的整數型首先提升為較大的整數型別(算術轉換/陣列->指標/指標轉換)
    2. 條件中,非布林轉換為布林
    3. 初始化過程中,初始值轉換成變數型別;賦值語句中,右側運算物件轉換成左側型別
    4. 算術運算/關係運算的運算物件多種型別,需要轉換成同一種型別
    5. 函式呼叫時
  • 顯示轉換
    • static_cast:最廣泛,除了底層const

    • const_cast:之惡能改變運算物件的底層const,常量物件轉換為非常量物件

      const char *pc;
      char *p = const_cast<char*>(pc);
      char *q = static_cast<char*>(pc);//wrong
      static_cast<string>(pc);
      const_cast<string>(pc);//wrong
      
    • reinterpret_cast:為運算物件的位模式提供較低層次上的重新解釋。

    • dynamic_cast:支援運算時型別識別
      儘量避免強制型別轉換

ch5 語句

懸垂else:else就近匹配

switch語句內部的變數定義

case true:
	string file_name;//wrong,隱式初始化
	int ival = 0;//wrong,顯式初始化
	int jval;//true
	break;
case false:
	jval = next_num();
	if(file_name.empty()){}

範圍for:不能通過範圍for增加vector物件元素,因為預存end(),一旦再序列中新增/刪除元素,end函式的值就可能變得無效了。

跳轉語句

  • break:作用範圍僅限於最近迴圈/switch
  • continue:中斷迭代,繼續執行迴圈
  • goto:無條件跳轉到同一函式內的另一條語句
    不要在程式中使用goto,會使得程式難理解難修改

try-catch語句塊

  • throw表示式

    Sales_item item1, item2;
    cin >> item1 >> item2;
    if(item1.isbn()!=item2.isbn())
    	throw runtime_error("Data must refer to same ISBN!");
    //丟擲runtime_error異常,用string物件初始化
    cout << item1+item2<<endl;
    
  • try-catch()

    while(cin >> item1 >> item2){
        try{
            //
        }catch (runtime_error err){
     		cout << err.what() << endl;       
        }
    }
    

    函式在尋找處理程式碼的過程中退出:編寫異常安全程式碼十分困難

  • 標準異常

    • exception:定義最通用的異常類,報告異常的發生
    • stdexcept:定義幾種常見的異常類
    • new定義bad_alloc異常型別
    • type_info定義bad_cast異常型別

ch6 函式

函式:返回型別,函式名字,0個或多個形參組成的列表,函式體

區域性物件

  • 名字的作用域是程式文字的一部分,名字在其中可見;
  • 物件的生命週期是程式執行過程中該物件存在的一段時間。
  • static區域性靜態物件,第一次經過物件定義語句後初始化,直到程式結束才終止

變數和函式在標頭檔案中宣告(不包含函式體),在原始檔中定義

分離式編譯:多檔案組成的程式是如何編譯並執行的

引數傳遞

  • 傳值引數(不影響初始值)

    //指標形參,執行指標拷貝時,拷貝的時指標的值;拷貝後,兩個指標不同指向同一物件
    void reset(int *ip)
    {
        *ip = 0;//改變指標ip所指物件的值
        ip = 0;//改變ip的區域性拷貝,實參未被改變
    }
    int i = 0;
    reset(&i);
    //建議使用引用型別的形參替代指標
    
  • 傳引用

    void reset(int &i){ i = 0; }
    int j = 42;
    reset(j);
    //使用引用避免拷貝,若無須修改引用形參的值,宣告為常量引用
    

    const形參和實參

    const int ci = 42;//wrong,const頂層
    int i = ci;//實參初始化形參和拷貝時,忽略頂層const
    int *const p = &i;//wrong,const頂層
    *p = 0;
    void fcn(const int i)//不能向i寫值
    void fcn(int i)//wrong,重複定義
    

    指標/引用形參與const

    int i = 42;
    const int *cp = &i;//cp don't change i
    const int &r = i;//r don't change i
    const int &r2 = 42;
    int *p = cp;//wrong,type don't match
    int &r3 = r;//wrong,type don't match
    int &r4 = 42;//wrong,字面值不能初始化非常量引用
    
    const int ci = i;
    string::size_type ctr = 0;
    reset(&i);
    reset(&ci);//wrong,不能用指向const int物件指標初始化int*
    reset(i);
    reset(ci);//wrong,普通引用不能繫結在const物件上
    reset(42);//wrong,普通引用不能繫結在字面值
    rest(ctr);//wrong,type don't match
    find_char("hello world!", 'o', ctr);
    

    儘量使用常量引用
    陣列形參

    print(const int*)
    print(const int[])
    print(const int[10])
    int i = 0, j[2]={0,1};
    print(&i);
    print(j);//j轉換為int*指向j[0]
    

    管理指標形參三種常用的技術

    • 標記指定陣列長度

      void print(const char *cp){
          if(cp)
          	while(*cp) cout << *cp++ << endl;
      }
      
    • 標準庫規範

      void print(const int *beg, const int *end)
      {
          while(beg != end) cout << *beg++ << endl;
      }
      
    • 顯示傳遞陣列大小形參

      void print(const int ia[], size_t size)
      {
          for(size_t i = 0; i < size; ++i) cout << ia[i] << endl;
      }
      

    陣列引用形參

    void print(int (&arr)[10]){}
    f(int &arrp[10])//wrong,將arr宣告成了引用的陣列
    f(int (&arr)[10])//true,arr是包含10個整數的陣列引用
    
    void print(int (*matrix)[10], int rowSize){}
    void print(int matrix[][10], int rowSize){}//兩者等價
    

    含有可變形參的函式:initializer_list形參(用於與C函式互動的介面)

    void error_msg(initializer_list<string> il)
    {
        for(auto beg = il.begin();beg!=il.end();++beg)
        	cout << *beg << endl;
    }
    

return 語句:不要返回區域性物件的引用或指標

  • 引用返回左值

    char &get_val(string &str, string::size_type ix) return str[ix];
    int main()
    {
        string s("a value");
        get_val(s,0) = 'A';
        cout << s << endl;
    }
    
  • 列表初始化返回值

  • 返回陣列指標

    //宣告一個返回陣列指標的函式
    int arr[10];//10個整數的陣列
    int *p1[10];//10個整數指標
    int (*p2)[10] = &arr;//p2指向arr
    
    int (*func(int i))[10];//func的呼叫進行解引用後得到一個10的陣列
    //尾置返回型別
    auto func(int i)->int (*)[10];
    //使用decltype
    decltype(odd) *arrPtr(int i)
    {
        return (i % 2) ? &odd : &even;
    }
    

    函式過載(函式名字相同形參列表不同)

    • 過載和const形參:頂層const不影響傳入函式的物件,一個擁有頂層const的形參無法與一個沒有頂層const的形參區分開來

    • 最好是過載那些確實非常相似的操作

    • const_cast和過載

      string &shorterString(string &s1, string &s2)
      {
          auto &r = shorterString(const_cast<const string&>(s1),
          					  const_cast<const string&>(s2));
          return const_cast<string&>(r);
      }
      

    特殊用途語言特性

    • 預設實參

      typedef string::size_type sz;
      string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');
      //通常,應該在標頭檔案函式宣告中指定預設實參
      
    • 行內函數和constexpr函式(定義在標頭檔案中)

      //避免函式呼叫的開銷,在編譯時展開
      //能用於常量表示式的函式
      constexpr size_t scale(size_t cnt) { return new_sz() * cnt; }
      int arr[scale(2)];
      int i =2;
      int a2[scale(i)];//wrong,scale(i)不是常量表達
      
    • 除錯幫助

      • assert預處理巨集assert(word.size() > threshold)

      • NDEBUG預處理變數

        void print(const int ia[], size_t size)
        {
           #ifndef NDEBUG
           	cerr << __func__<< "" << endl;
           #endif
        }
        __FILE__存放檔名
        __LINE__存放當前行號
        __TIME__存放檔案編譯時間
        __DATE__存放檔案編譯日期
        

    函式匹配
    函式指標:指向是函式而非物件。函式的型別由返回型別和形參型別共同決定

    bool lengthCompare(const string &, const string &);
    bool (*pf)(const string &, const string &);//not initialize
    pf = lengthCompare;
    bool b1 = pf("hello world", "goodbye");
    bool b2 = (*pf)("hello world", "goodbye");//equal
    bool b3 = lengthCompare("hello world", "goodbye");//equal
    //過載函式的指標
    void ff(int*);
    void ff(unsigned int);
    void (*pf1)(unsigned int) = ff;
    //函式指標形參
    void useBigger(const string &s1, const string &s2,
    				bool pf(const string&, const string&));
    useBigger(s1, s2, lengthCompare);
    //函式
    typedef bool Func(const string&, const string&);
    typedef decltype(lengthCompare) Func2;//equal
    //指向函式的指標
    typedef bool (*FuncP)(const string&, const string&);
    typedef decltype(lengthComapre) *FuncP;//equal
    //返回指向函式的指標
    //auto和decltype用於函式指標
    

    宣告getFcn唯一需要注意的地方是,牢記當我們將decltype作用於某個函式時,它返回函式型別而非指標型別。顯示加上*表明需要返回指標,而非函式本身.

ch7 類

定義在類內部的函式時隱式的inline函式

成員函式通過一個名為this常量指標的額外的隱式引數來訪問呼叫它的那個物件。

const放在成員函式參數列後,表明this時一個指向常量的指標。

//定義read和print函式
istream &read(istream &is, Sailes_data &item)
{
    double price = 0;
    is >> item.bookNo >> item.units_sold >> price;
    item.revenue = price * item.units_sold;
    return is;
}
osteam &print(ostream &os, const Sales_data &item)
{
    os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price() << endl;
    return os;
}

建構函式

  1. 預設建構函式:無需任何實參
  2. 建構函式初始值列表
  3. 拷貝/賦值/析構

訪問控制和封裝(public|private;class|struct)

  • 確保使用者程式碼不會無意間破壞封裝物件的狀態。
  • 被封裝的類的具體實現細節可以隨時改變,而無需調整使用者級別的程式碼

友元函式(friend函式宣告|類定義開始/結束位置集中宣告友元)

過載成員函式

可變資料成員:mutable

//返回*this的成員函式
inline Screen &set(char);//若返回型別不是引用,則返回值時*this的副本
myScreen.display(cout).set('*');//wrong,display返回常量引用*this,無法set一個常量物件
//基於const的過載
Screen &display(std::ostream &os){return *this;}
const Screen &display(std::ostream &os) const {return *this;}
myScreen.set('&').display(cout);//呼叫非常量版本
blank.display(cout);//常量版本

建議:對於公共程式碼使用私有功能屬性

類之間的友元關係:friend宣告,友元類的成員函式可以訪問此類非公有成員

建議:使用建構函式初始值

委託建構函式

class Sales_data{
    public:
    	Sales_data(std::string s, unsigned cnt, double price):
    			bookNo(s), units_sold(cnt), revenue(cnt*price){}
    	//委託delegate
    	Sales_data():Sales_data("",0,0){}
    	Sales_data(std::string s):Sales_data(s, 0, 0){}
    	Sales_data(std::istream &is): Sales_data(){ read(is, *this);}
};

隱式類型別轉換:通過將建構函式宣告為explicit加以阻止,只能用於直接初始化,且對一個實參的建構函式有效

類的靜態成員(static關鍵字):存在於任何物件之外,成員函式不包含this指標

double r = Account::rate();
Account ac1;
Account *ac2 = &ac1;
r = ac1.rate();
r = ac2>rate();//通過物件/指標訪問

靜態成員的類內初始化,使用constexpr,但通常情況下依然在類外定義該成員。

ch8 IO庫

IO類

  • iostream定義了用於讀寫流的基本型別
  • fstream定義了讀寫命名檔案的型別
  • sstream定義了讀寫記憶體string物件的型別

標準庫能使我們忽略不同型別的流之間的差異:繼承機制實現的

IO物件無拷貝或賦值:不能將形參或返回型別設定為流型別,通常以引用方式傳遞返回流,不能使const的

緩衝重新整理的原因:

  1. 程式正常結束,main函式的return操作的一部分,緩衝重新整理被執行
  2. 緩衝區滿時,重新整理
  3. 使用endl顯示重新整理
  4. 每個書除操作之後,用unitbuf設定流的內部狀態,清空緩衝區。預設情況下,cerr時設定unitbuf的
  5. 輸出流關聯到另一個流時。如cin和cerr都關聯到cout,讀寫cin和cerr會導致cout的緩衝區被重新整理

使用flush和ends重新整理緩衝區(輸出一個空字元)

警告:如果程式崩潰,書除緩衝區不會被重新整理

檔案輸入輸出

ifstream in(ifile);
ofstream out;
out.open(ifile+".copy");
in.close();
in.open(ifile+"2");

當一個fstream物件被銷燬時,close會自動被呼叫,自動構造和析構

在每次開啟檔案時,都要設定檔案模式,可能時顯示地設定,也可能是隱式地設定。當程式未指定模式,使用預設值。

string流:某些工作對整行文字進行處理,而其他工作是處理行內地某個單詞,用istringstream

string line, word;
vector<PersonInfo> people;
while(getline(cin,line)){
    PersonInfo info;
    istringstream record(line);
    record >> info.name;
    while(record >> word)
    	info.phones.push_back(word);
    people.push_back(info);
}

ostringstream:逐步構造輸出,最後一起列印

ch9 順序容器

  • vector:可變大小陣列,支援快速隨機訪問,在尾部之外地位置插入或刪除可能很慢
  • deque:雙端佇列,支援快速隨機訪問,在頭尾位置插入/刪除速度很快
  • list:雙向連結串列,只支援雙向順序訪問,list任何位置插入/刪除速度很快
  • forward_list:單向連結串列,只支援單項順序訪問,連結串列任何位置進行插入/刪除速度很快
  • array:固定陣列大小,支援快速隨機訪問,不能新增/刪除元素
  • string:與vector相似地容器,專門儲存字元,隨機訪問很快,尾部插入/刪除快

選擇哪種順序容器

  • 通常選擇vector
  • 程式中有很多小的元素,且空間額外開銷很重要,不要使用list和forward_list
  • 程式要求隨機訪問元素,使用vector和deque
  • 要求中間插入/刪除元素,使用list和forward_list
  • 要求頭尾插入/刪除元素,中間不插入/刪除,使用deque
  • 只在讀取輸入時在中間插入元素,則
    • 首先,確定是否真的需要在容器中間插入元素,藉助vector和sort
    • 必須中間插入元素,輸入階段使用list,結束後拷貝到vector

容器操作:P330

迭代器範圍由一對迭代器表示,begin和end,[begin,end),帶r的版本返回反向迭代器,以c為開頭的版本返回const迭代器

容器定義和初始化

C c;//預設建構函式
C c1(c2);//拷貝
C c1 = c2;
C c{a,b,c};//列表初始化
C c={a,b,c};
C c(b,e)//c初始化為b和e指定範圍內元素的拷貝。
C seq(n);//seq包含n個元素,建構函式時explicit的
C seq(n,t);//seq包含n個初始值為t的元素

容器賦值運算

c1 = c2;//c1替換為c2的拷貝
c = {a,b,c};//c1元素替換為c2的拷貝
swap(c1,c2);
c1.swap(c2);//swap比從c2到c1拷貝快得多
seq.assign(b,e)//seq元素替換為b,e表示範圍內的元素
seq.assign(il)
seq.assign(n,t)
//assign可以將char*值賦予list
list<string> name;
vector<const char*> oldstyle;
names = oldstyle;//wrong,型別不匹配
names.assign(oldstylee.cbegin(),oldstyle.cend());

關鍵概念:容器元素時拷貝

push_front:list,forward_list,deque支援,常數級

vector,deque,list,string支援insert,比較耗時

slist.insert(iter, "Hello!");
svec.insert(svec.end(),10,"Anna");
slist.insert(slist.begin(),v.end()-2,v.end());
slist.insert(slist.end(),{"these","words"});
slist.insert(slist.begin(),slist.begin(),slist.end());//wrong,迭代器表示拷貝範圍,不能指向與目的位置相同的容器
iter = lst.insert(iter,word);//等同於push_front()

emplace_frontpush_front,emplaceinsert,emplace_back~push_back,用於容器中直接構造元素

at和下標操作只適用於string、vector、deque、array

back不適用於forward_list

如果容器為空,front和back時未定義的

c.pop_back()//刪除c中尾元素,c為空,行為未定義,返回void
c.pop_front()//刪除c中首元素,c為空,行為未定義,返回void
c.erase(p)
c.erase(b,e)//返回一個指向最後一個被刪元素之後元素的迭代器,若e為尾後迭代器,返回尾後迭代器
c.clear()
//刪除deque除首尾之外的任何元素都會使任何迭代器、指標、引用失效

forward_list特殊版本的新增/刪除操作

vector是如何增長的

  • size:容器已經儲存的元素數量
  • capacity:不分配新的記憶體空間最多可以儲存多少元素

不同的分配策略遵循一個原則:確保用push_back新增元素的操作由高效率

額外的string操作

string s(cp,n)//cp指向的陣列前n個字元
string s(s2,pos2)//s2從pos2開始的字元的拷貝
string s(s2,pos2,len2)//s2從pos2開始len2字元的拷貝
string s2 = s.substr(0,5)
string s2 = s.substr(6)
s.append(args);//args追加到s,返回s的引用
s.replace(range,args);//將range字元替換成args
//搜尋操作
string name("AnnaBelle");
auto pos1=name.find("Anna");
string numbers("0123456789"),name("r2d2");
auto pos = name.find_first_of(numbers);
string dept("03714p3");
auto pos = dept.find_first_not_of(numbers);
//compare函式

數值轉換

int i = 42;
string s = to_string(i);
double d = stod(s);

容器介面卡:stack,queue,priority_queue

本質上,一個介面卡是一種機制。每個介面卡都在其底層順序介面容器上定義了一個新的介面。

ch10 泛型演算法

演算法:實現了一些經典演算法的公共介面

泛型:它們可以用於不同型別的元素和多種容器型別(包括標準庫vector和list以及內建的陣列型別)

演算法遍歷由兩個迭代器指定的一個元素範圍進行操作

迭代器令演算法不依賴於容器,但演算法依賴於元素型別的操作

關鍵:演算法不會執行容器的操作,只會執行於迭代器之上,演算法永遠不會改變底層容器的大小

  • 只讀演算法

    find
    //求和
    int sum = accumulate(vec.cbegin(), vec.cend(), 0);
    string sum = accumulate(v.cbegin(), v.cend(), string(""));//去掉string會導致編譯錯誤,不能直接傳遞字面值
    //比較兩個序列
    equal(roster1.cbegin(), roster1.cend(), roster2.cbegin());//假定第二個序列至少與第一個序列一樣長
    
  • 寫容器元素演算法

    fill(vec.begin(), vec.end(), 0);//每個元素重置為0,寫操作安全
    //演算法不檢查寫操作
    fill_n(vec.begin(), vec.size(), 0);
    vector<intj> vec;
    fill_n(vec.begin(), 10, 0);//結果未定義wrong
    

    迭代器引數:兩個序列的元素可以來自不同的容器。
    插入迭代器:back_inserter

    vector<int> vec;
    fill_n(back_inserter(vec), 10, 0);
    

    copy演算法

    int a1[] = {0,1,2,3,4};
    int a2[sizeof(a1)/sizeof(*a1)];
    auto ret = copy(begin(a1), end(a1), a2);
    replace(ilst.begin(), ilst.end(), 0, 42);
    replace_copy(ilst.cbegin(), ilst.cend(), back_inserter(ivec), 0 ,42);//ivec包含ilst的一份拷貝
    
  • 重排容器元素演算法

    void elimDupe(vector<string> &words){
        sort(words.begin(), words.end());
        auto end_unique = unique(words.begin(), words.end())//返回指向不重複區域之後一個位置的迭代器
        words.erase(end_unique, words.end());
    }
    

定製操作

  • 向演算法傳遞函式

    bool isShorter(const string &s1, const string &s2){
        return s1.size() < s2.size();
    }
    sort(words.begin(), words.end(), isShorter);
    elimDups(words);
    stable_sort(words.begin(), words.end(), isShorter);
    for(const auto &s:words)//無須拷貝字串
    	cout << s << " " ;
    cout << endl;
    
  • lambda表示式(一個返回型別,一個引數列表,一個函式體)

    void biggies(vector<string> &words,
    			vector<string>::size_type sz)
    {
        elimDups(words);
        stable_sort(words.begin(), words.end(), isShorter);
    }
    //lambda
    [capture list](parameter list) -> return type { function body }
    auto f = []{return 42; }
    [](const string &s1, const string &s2){ return s1.size() < s2.size(); }
    [sz](const string &a){ return a.size() >= sz; }
    auto wc = find_if(words.begin(), words.end(),
    			[sz](const string &a)
    				{return a.size() >= sz;})
    for_each(wc, words.end(),
    	[](const string &s){cout << s << " ";});
    cout << endl;
    

    lambda捕獲和返回

    • 值捕獲
    • 引用捕獲
    • 隱式捕獲
      建議:儘量保持lambda的變數捕獲簡單化
      可變lambda:加上mutable
      指定lambda返回型別:transform
  • 引數繫結bind

    auto check6 = bind(check_size(), _1, 6);
    sort(words.begin(), words.end(), bind(isShorter, _2, _1));//重排引數順序
    for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' '));//繫結引用引數
    

再探迭代器

  • insert iterator:插入元素,it = t, *it, ++it, it++
    back_inserter:建立一個使用push_back的迭代器
    front_inserter:使用push_front的迭代器
  • iostream迭代器
    istream_iterator
    ostream_iterator
  • 反向迭代器

迭代器類別

  • 輸入迭代器:==,!=;++;*;->
  • 輸出迭代器:++;*
  • 前向迭代器:只能沿一個方向移動,replace|forward_list
  • 雙向迭代器:--,reverse
  • 隨機訪問迭代器:<,<=,>,>=;+,+=,-=;-;下標運算子

特定容器演算法

lst.merge(lst2)
lst.merge(lst2, comp)
list.remove(val)
lst.remove_if(pred)
list.reverse()
lst.sort()
lst.sort(comp)
lst.unique()
lst.unique(pred)
lst.splice(args)
//連結串列特有的操作會改變容器,如remove,unique,merge,splice

ch11 關聯容器

關聯容器中的元素是按關鍵字儲存和訪問的

主要是map和set

map,set,multimap,multiset,unordered_map,unordered_set,unordered_multimap,unordered_multiset

pair標準庫型別

有序容器:使用比較運算子組織元素

關聯容器操作

//map的value_type是一個pair,可以改變值,但不能改變關鍵字成員的值
//set的迭代器是const的
auto map_it = word_count.cbegin();
while(map_it != word_count.cend())
{
    cout << map_it->first << map_it->second << endl;
    ++map_it;
}
//新增元素
set.insert(ivec.cbegin(), ivec.cend());
set.insert({1,2,3});
word_count.insert({word,1});
word_count.insert(make_pair(word,1));
word_count.insert(pair<string,size_t>(word,1));
word_count.insert(map<string,size_t>::value_type(word,1));
//insert操作
c.insert(v);
c.emplace(args)
c.insert(b,e)
c.insert(il)
c.insert(p,v)
c.emplace(p,args)
//multimap
multimap<string,string> authors;
authors.insrt({"Barth,John","Factor"});

//刪除元素
c.erase(k)
c.erase(p)
c.erase(b,e)

//訪問元素
c.find(k)
c.count(k)
c.lower_bound(k)
c.upper_bound(k)
c.equal_range(k)
string search_item("Alain de Botton");
auto entries = authors.count(search_item);
auto iter = authors.find(search_item);
while(entries)
{
    cout << iter->second << endl;
    ++iter;
    entries--;
}
for(auto beg = authors.lower_bound(search_item), end = authors.upper_bound(search_item);
		beg != end; ++beg)
{
    cout << beg->second << endl;
}
//equal_range
for(auto pos = authors.equal_range(search_item); pos.first != pos.end; ++pos.first)
{
    cout << pos.first->second << endl;
}

對map使用find代替下標操作

無序容器:使用雜湊函式和關鍵字型別的==運算子,儲存上組織為一組桶,雜湊函式將元素對映到桶

size_t hasher(const Sales_data &sd)
{
    return hash<string>()(sd.isbn());
}
bool eqOp(const Sales_data &lhs, cont Sales_data &rhs){
    return lhs.isbn() == rhs.isbn();
}

ch12 動態記憶體

靜態記憶體:區域性static物件、類static資料成員、定義在函式之外的變數。

棧記憶體:儲存定義在函式內的非static物件

記憶體池(堆):儲存動態分配的物件,且顯式銷燬

  • shared_ptr類(模板)
    預設初始化的智慧指標中儲存著一個空指標

    if(p1 && p1->empty())
    	*p1 = "hi";
    //shared_ptr和unique_ptr都支援
    shared_ptr<T> sp
    unique_ptr<T> sp
    p
    *p
    p->mem
    p.get()//p中儲存的指標
    swap(p,q)//交換指標
    p.swap(q)
    //shared_ptr獨有
    make_shared<T>(args)
    shared_ptr<T>p(q)
    p = q
    p.unique()
    p.use_count()//用於除錯
    
    • make_shared
      最為安全 auto p6 = make_shared<vector>();

    • 拷貝賦值
      shared_ptr都有一個引用計數。

      auto r = make_shared<int>(42);
      r = q;//遞增q指向物件的引用計數,遞減r的引用計數,r原來指向的物件自動銷燬
      
    • 自動釋放相關聯的記憶體

      shared_ptr<Foo> factory(T arg)
      {
          rdturn make_shared<Foo>(arg);
      }
      shared_ptr<Foo> use_factory(T arg)
      {
          shared_ptr<Foo> p = factory(arg);
          return p;//返回p時,引用計數遞增
      }
      
    • 使用了動態生存期的資源的類

      1. 程式不知道自己需要使用多少物件(容器類)

      2. 程式不知道所需物件的準確型別(ch15)

      3. 程式需要在多個物件中共享資料

        Blob<string> b1;
        {
            Blob<string> b2 = {"a","aa","aaa"};
            b1 = b2;//b1和b2共享底層資料,b2被銷燬,但b2中的元素不能銷燬   
        }
        
    • StrBlob

    • 直接管理記憶體

      int *p1 = new int;//分配失敗,丟擲std::bad_alloc
      string *ps = new string(10,'9');
      auto p1 = new auto(obj);
      const int *pci = new const int(1025);
      delete p;
      int i, *pi1 = &i, *pi2 = nullptr;
      double *pd = new double(33), *pd2 = pd;
      delete i;//false
      delete pi1;//undefined
      delete pd;//true
      delete pd2;//undifined
      delete pi2;//true
      

      由內建指標管理的動態記憶體在顯式釋放前都會存在
      new和delete管理記憶體存在三個問題:

      1. 忘記delete
      2. 使用已經釋放掉的記憶體
      3. 同一塊記憶體釋放兩次
    • shared_ptr和new結合使用

      //接受指標引數的智慧指標建構函式是explicit
      shared_ptr<int> p1 = new int(1025);//wrong
      shared_ptr<int> p2(new int(1025));true
      shared_ptr<int> clone(int p){
          return shared_ptr<int>(new int (p));
      }
      
      //定義和改變shared_ptr
      shared_ptr<T> p(q)
      shared_ptr<T> p(u)
      shared_ptr<T> p(q,d)
      shared_ptr<T> p(p2,d)
      p.rerset()
      p.reset(q)
      p.reset(q,d)
      

      不要混合使用智慧指標和普通指標
      不要使用get初始化另一個智慧指標或為智慧指標賦值

      shared_ptr<int> p(new int(42));
      int *q = p.get();
      {
          shared_ptr<int>(q);
      }
      imt foo = *p;//未定義,p指向的記憶體已釋放
      

      reset更新引用計數,經常與unique一起使用
      if(!p.unique())
      p.reset(new string(*p));
      *p += newVal;

    • 智慧指標與異常
      智慧指標陷阱:

      1. 不使用相同的內建指標值初始化(或reset)多個智慧指標
      2. 不delete get()返回的指標
      3. 不適用get()初始化或reset另一個智慧指標
      4. 使用get()返回的指標,最後一個對應的智慧指標銷燬後,指標變為無效
      5. 使用智慧指標,傳遞一個刪除器
  • unique_ptr
    unique_ptr不支援拷貝和賦值

    //unique_ptr支援的操作
    unique_ptr<T> u1
    unique_ptr<T,D> u2
    unique_ptr<T,D> u(d)
    u = nullptr
    u.release()
    u.reset()
    u.reset(q)
    u.reset(nullptr)
    

    拷貝或賦值一個將要被銷燬的unique_ptr引數

    unique_ptr<int> clone(int p){
        return unique_ptr<int>(new int(p));
    }
    unique_ptr<int> clone(int p){
        unique_ptr<int> ret(new int(p));
        //...
        return ret;
    }
    //傳遞刪除器
    unique_ptr<objT, delT> p(new objT, fcn);
    void f(destination &d)
    {
        connection c = connect(&d);
        unique_ptr<connection, decltype(end_connection)*> p(&c,end_connection);
        //使用連線,且f退出時,connection會被關閉
    }
    
  • weak_ptr
    不控制所指向物件生存期的智慧指標,指向一個shared_ptr管理的物件,不會改變shared_ptr的引用計數。

    weak_ptr<T> w
    weak_ptr<T> w(sp)
    w = p
    w.reset()
    w.use_count()
    w.expired()
    w.lock()
    
    auto p = make_shared<int>(42);
    weak_ptr<int> wp(p);
    if(shared_ptr<int> np = wp.lock())
    //...
    

大多數應用應該使用標準庫容器而不是動態分配的陣列,使用容器更不容易出線記憶體管理錯誤且可能有更好的效能。

  • new和陣列

    int *p = new int(get_size());

  • allocator類

    allocator<T> a
    a.allocate(n)
    a.deallocate(p,n)
    a.construct(p,args)
    a.destroy(p)
    
    uninitialized_copy(b,e,b2)
    uninitialized_copy_n(b,n,b2)
    uninitialized_fill(b,e,t)
    uninitialized_fill_n(b,n,t)
    

ch13 拷貝控制

  • 拷貝建構函式

    class Foo {
        public:
        Foo();
        Foo(const Foo&);
    }
    

    合成~:

    1. 類型別成員:使用其拷貝建構函式

    2. 內建型別:直接拷貝

    3. 對於陣列,如果不是類型別,則逐元素拷貝;否則按照元素拷貝建構函式拷貝

      class Sales_data{
      public:
      Sales_data(const Sales_data&);
      private:
      std::string bookNo;
      int units_sold = 0;
      double revenue = 0.0;
      }
      Sales_data::Sales_data(const Sales_data &orig):
      bookNo(orig.bookNo),
      units_sold(orig.units_sold),
      revenue(orig.revenue)
      {}
      //拷貝初始化
      string dots(10, '.');//直接初始化
      string s(dots);//直接初始化
      string s2 = dots;//拷貝初始化
      string nullbook = "9-99-99999-9";//拷貝初始化
      string nines = string(100, '9');//拷貝初始化

    拷貝發生在:

    1. =定義變數

    2. 將一個物件從實參傳遞給非引用型別的形參

    3. 從一個返回型別為非引用型別的函式返回一個物件

    4. 花括號初始化一個陣列或一個struct
      當傳遞一個實參或從函式返回一個值時,不能隱式使用一個explicit建構函式。

      vector v1(10);//true
      vector v2 = 10;//wrong,建構函式explicit
      void f(vector);
      f(10);//wrong,不能用explicit建構函式拷貝一個實參
      f(vector(10));//true

    編譯器可以繞過拷貝建構函式

  • 拷貝賦值運算子
    過載運算子:本質上是函式,由operator後接運算子符號。

    class Foo{
        public:
        Foo& operator=(const Foo&);
    }
    

    合成拷貝賦值運算子

    Sales_data&
    Sales_data::operator=(const Sales_data &rhs){
        bookNo = rhs.bookNo;
        units_sold = rhs.units_sold;
        revenue = rhs.revenue;
        return *this;
    }
    
  • 解構函式
    解構函式不接受引數,不能過載
    解構函式被呼叫:

    1. 變數離開作用域被銷燬
    2. 當以額物件被銷燬時,其成員被銷燬
    3. 容器被銷燬時,其元素被銷燬
    4. 動態分配的物件,指向它的指標用delete被銷燬
    5. 臨時物件,建立它的表示式結束時被銷燬
      當指向一個物件的引用或指標離開作用域時,解構函式不會執行
      解構函式體不直接銷燬成員,而是在解構函式體之後隱含的析構階段被銷燬
  • 三/五原則
    如果一個類需要解構函式,那麼必然需要拷貝構造和拷貝賦值函式

    class HasPtr{
    public:
    	HasPtr(const std::string &s = std::string()):
    	ps(new std::string(s)),i(0){}
    	~HasPtr(){delete ps;}
    	//wrong,HasPtr需要一個拷貝構造和拷貝賦值
    }
    //賦值
    HasPtr f(HasPtr hp)
    {
        HasPtr ret = hp;
        return ret;
    }
    //hp和ret都被銷燬,導致ps被析構兩次
    HasPtr p("some values");
    f(p);//f結束時,p.ps被析構
    HasPtr q(p);//q和p都指向無效記憶體
    

    拷貝構造和拷貝賦值是共生的

  • 使用=defalut
    我們只能對具有合成版本的成員函式使用=defalut(預設構造,拷貝控制成員)

  • 阻止拷貝
    =delete通知編譯器,我們不希望定義這些成員。我們可以對任何函式指定,主要用途是禁止拷貝控制成員。
    解構函式不能=delete

  • 合成拷貝控制成員可能是刪除的

    1. 類的某個成員的解構函式是刪除的或private
    2. 拷貝建構函式是刪除的或private
    3. 拷貝賦值運算子是刪除的或private
    4. 類有一個引用成員,沒有類內初始化器,或有一個const成員,沒有類內初始化器,則預設建構函式是刪除的

類的行為像一個值,意味著它應該有自己的狀態,副本與原物件完全獨立。如標準庫容器和string類

類的行為像一個指標,共享狀態。副本和原物件使用相同的底層資料。如shared_ptr

IO和unique_ptr不允許拷貝和賦值,既不是指標也不是值

  • 類值

    class HasPtr{
    public:
    	HasPtr(const std::string &s = std::string()):
    	ps(new std::string(s)),i(0){}
    	HasPtr(const HasPtr &p):
    	ps(new std::string(*p.ps)), i(0){}
    	HasPtr& operator=(const HasPtr &);
    	~HasPtr(){delete ps;}
    private:
    	std::string *ps;
    	int i;
    }
    HasPtr& HasPtr::operator=(const HasPtr &rhs){
        auto newp = new string(*rhs.ps);
        delete ps;
        ps = newp;
        i = rhs.i;
        return *this;
    }
    //note
    //如果一個物件賦予它自身,賦值運算子必須正常工作
    //大多數賦值運算子組合了解構函式和拷貝建構函式的工作
    
  • 類指標

    class HasPtr{
    public:
    	HasPtr(const std::string &s = std::string()):
    	ps(new std::string(s)),i(0),use(new std::size_t(1)){}
    	HasPtr(const HasPtr &p):
    	ps(new std::string(*p.ps)), i(p.i), use(p.use{++*use;}
    	HasPtr& operator=(const HasPtr &);
    	~HasPtr();
    private:
    	std::string *ps;
    	int i;
    }
    HasPtr::~HasPtr()
    {
        if(--*use == 0)
        {
            delete ps;
            delete use;
        }
    }
    HasPtr& HasPtr::operator=(const HasPtr &rhs)
    {
    	++*rhs.use;
    	if(--*use == 0)
    	{
            delete ps;
            delete use;
    	}
    	ps = rhs.ps;
    	i = rhs.i;
    	use = rhs.use;
    	return *this;
    }
    

swap

拷貝控制示例

動態記憶體管理類

物件移動

//右值引用
int i = 42;
int &r = i;//true
int &&rr = i;//wrong
int &r2 = i*42;//wrong
const int &r3 = i*42;//true
int &&rr2 = i*42;//true

右值引用:所引用的物件即將銷燬;物件沒有其他使用者

顯示將左值轉換成右值引用:int &&rr3 = std::move(rr1);

StrVec::StrVec(StrVec &&s) noexcept//承諾不丟擲異常
: elements(s.elements), first_free(s.first_free),cap(s.cap)
{
    s.elements = s.first_free = s.cap = nullptr;
}
StrVec &StrVec::operator=(const StrVec &&rhs) noexcept
{
    if(this != &rhs)
    {
        free();
        elements = rhs.elements;
        first_free = rhs.first_free;
        cap = rhs.cap;
        rhs.elements = rhs.first_free=rhs.cap = nullptr;
    }
    return *this;
}

ch14 過載運算與型別轉換

  • 基本概念
    一元運算子有一個,二元運算子有兩個引數。operator()含有預設實參

    • 賦值=、下標[]、呼叫()和成員訪問箭頭->必須是成員
    • 複合賦值運算子一般來說是成員
    • 遞增、遞減和解引用運算子,通常應該是成員
    • 對稱性的運算子,如算術、相等、關係、位運算等,則為普通的非成員函式
  • 輸入和輸出運算子

    ostream &operator<<(ostream &os, const Sales_data &item)
    {
        os << item.isbn();
        return os;
    }
    //輸入輸出運算子必須是非成員函式
    istream &operator>>(istream &is, Sales_data &item)
    {
    	double price;
    	is >> item.bookNo >> item.units_sold >> price;
    	if(is)
    		item.revenue = item.units_sold * price;
    	else
    		item = Sales_data();
    	return is;
    }
    
  • 算術和關係運算子
    定義成非成員函式

    • 相等運算子

      bool operator==(const Sales_data &lhs, const Sales_data &rhs)
      {
          return lhs.isbn() == rhs.isbn() &&
          		lhs.units_sold == rhs.units_sold &&
          		lhs.revenue == rhs.revenue;
      }
      bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
      {
          return !(lhs == rhs);
      }
      
      1. 如果一個類含有判斷兩個物件是否相等的操作,則它應該把函式定義成operator==而非一個普通的命名函式
      2. 若定義了operator==,則應該能夠判定一組給定物件中是否含有重複資料
      3. 同時應該定義!=
      4. ==和!=應該把工作託管給其中一個
    • 關係運算子
      定義operator<比較有用

      1. 定義順序關係,使得與關聯容器中對關鍵字的要求一致
      2. 類同時有運算子,定義一種關係使得與保持一致
        如果存在唯一一種邏輯可靠的<定義,則應該考慮定義<,若同時包含,當且僅當<與產生的結果一致才定義
  • 賦值運算子
    複合賦值運算子

    Sales_data& Sales_data::operator+=(const Sales_data &rhs)
    {
        units_sold += rhs.units_sold;
        revenue += rhs.revenue;
        return *this;
    }
    
  • 下標運算子(成員函式)

    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;
    }
    
  • 遞增/遞減運算子

    //前置
    class StrBlobPtr{
    public:
    	StrBlobPtr& operator++();
    	StrBlobPtr& operator--();
    };
    StrBlobPtr& StrBlobPtr::operator++()
    {
        check(curr,"increment past end of StrBlobPtr");
        ++curr;
        return *this;
    }
    StrBlobPtr& StrBlobPtr::operator--()
    {
    	--curr;
        check(curr,"increment past begin of StrBlobPtr");
        return *this;
    }
    //後置
    class StrBlobPtr{
    public:
    	StrBlobPtr& operator++(int);
    	StrBlobPtr& operator--(int);
    };
    StrBlobPtr& StrBlobPtr::operator++(int)
    {
        StrBlobPtr ret = *this;
        ++*this;
        return ret;
    }
    StrBlobPtr& StrBlobPtr::operator--()
    {
    	StrBlobPtr ret = *this;
    	--*this;
        return ret;
    }
    
  • 成員訪問運算子

    class StrBlobPtr{
        public:
        std::string& operator*()const
        { auto p = check(curr, "dereference past end");
        	return (*p)[curr];
        }
        std::string* operator->()const
        {
            return & this->operator*();
        }
    }
    
  • 函式呼叫運算子

  • 過載、型別轉換與運算子

ch15 物件導向程式設計

OOP(object-oriented programming)核心思想是資料抽象、繼承和動態繫結。

  • 抽象:介面和實現分離
  • 繼承:定義相似的型別並對相似關係建模
  • 動態繫結:一定程度上忽略型別的卻別,以統一的方式使用他們的物件
    使用基類的引用/指標呼叫一個虛擬函式將發生動態繫結

OOP的核心思想是多型。引用/指標的靜態型別和動態型別不同正是C++支援多型的根本所在

基類與派生類

class Quote{
public:
	Quote() = default;
	Quote(const std::string &book, double sales_price):
	bookNo(book),price(sales_price){}
	std::string isbn()const {return bookNo;}
	virtual double net_price(std::size_t n)const
	{
        return n * price;
	}
	virtual ~Quote() = default;
private:
	std::string bookNo;
protected:
	double price = 0.0;
};
class Bulk_quote : public Quote{
public:
	Bulk_quote() = default;
	Bulk_quote(const string&, double, std::size_t, double);
	double net_price(std::size_t)const override;
private:
	std::size_t min_qty = 0;
	double discount = 0.0;
}
//派生類到基類的隱式轉換
Quote item;
Bulk_quote bulk;
Quote *p = &item;
p = &bulk;
Quote &r = bulk;

Bulk_quote(const std::string book, double p,std::size_t qty, double disc):
Quote(book,p),min_qty(qty),discount(disc){}//首先初始化基類,然後按照宣告順序初始化

繼承與靜態成員:只存在該成員唯一定義

防止繼承發生:final

  • 虛擬函式
    對虛擬函式的呼叫直到執行時才被解析。
    通過作用域運算子可以強行呼叫基類虛擬函式
  • 抽象基類
    含有純虛擬函式=0的類是抽象基類。不能直接建立一個抽象基類物件。
  • 訪問控制和繼承
    protected:
    • 和私有成員類似,受保護的成員對於類的使用者是不可訪問的
    • 與公有成員類似,受保護的成員對於派生類的成員和友元是可訪問的
  • 繼承中的類作用域
    派生類成員隱藏同名基類成員
    p->mem()
    1. 首先確定p的靜態型別。
    2. p的靜態型別對應的類內趙mem,如果找不到,則在直接基類內查詢直至繼承鏈的頂端,還是找不到編譯器報錯。
    3. 找到了mem,進行型別檢查
    4. 若mem是虛擬函式,通過引用/指標呼叫,則在執行時確定到底執行虛擬函式的哪個版本
    5. 反之,若是通過物件或不是虛擬函式,則產生一個常規函式呼叫
  • 建構函式與拷貝控制
    虛解構函式 = default,阻止合成移動操作
    合成拷貝控制與繼承
    派生類的拷貝控制成員
    繼承的建構函式
  • 容器與繼承

ch16 模板與泛型程式設計

  • 定義模板

    template <typename T>
    int compare(const T &v1, const T &v2)
    {
        if(v1 < v2) return -1;
        if(v2 < v1) return 1;
        return 0;
    }
    template <typename T>
    T foo(T* p)
    {
        T tmp = *p;
        return tmp;
    }
    template <typename T, class U> calc(const T&, const U&);
    

    非型別模板引數
    inline和constexpr的函式模板

    template <typename T> int compare(const T &v1, const T &v2)
    {
        if(less<T>()(v1,v2)) return -1;
        if(less<T>()(v1,v2)) return 1;
        return 0;
    }
    

    類别範本

    • 預設情況下,對於一個例項化了的類别範本,其成員只有在使用時才被例項化

    • 類别範本作用域內,可以直接使用模板名而不必指定模板實參

    • 類别範本與友元:一對一(BlobPtr可以訪問Blob,而不能訪問Blob),通用/一對多,模板自己的型別成為友元

    • 類别範本的static:每個例項都有唯一一個static物件
      模板引數

    • 當我們希望通知編譯器一個名字表示型別時,必須使用typename,而不能使用class

    • 預設模板實參

      template <typename T, typename F = less<T>>
      int compare(const T &v1, cosnt T &v2,F f = F())
      {
          if(f(v1,v2)) return -1;
          if(f(v2,v1)) return 1;
          return 0;
      }
      

    成員模板(不能是虛擬函式)

    • template <typename T> class Blob{
          template <typename It> Blob(It b, It e);
      };
      template <typename T>
      template <typename T>
      	Blob<T>::Blob(It b, It e):data(std::make_shared_ptr<std::vector<T>>(b,e)){}
      

    控制例項化

    • 多檔案中例項化相同模板的開銷非常嚴重,可以通過顯式例項化避免開銷。

      extern template class Blob<string>;
      template int compare(const int&, const int&);
      
    • 在一個類别範本的例項化定義中,所用型別必須能夠用於模板的所有成員函式
      效率和靈活性

    • 執行時繫結刪除器:shared_ptr

    • 編譯時繫結刪除器:unique_ptr

  • 模板實參推斷(函式實參->模板實參)
    呼叫中應用於函式模板

    • const轉換,將一個非const物件的引用傳遞給const引用形參

    • 陣列或函式指標轉換

    • 顯式實參

      auto val3 = sum<long,long>(i,lng);
      template <typename T1,typename T2,typename T3>
      T3 alternative_sum(T2,T1);
      auto val2 = alternative_sum(long long,int,int)(i,lng);
      
    • 尾置返回型別與型別轉換

      //允許我們在引數列表之後宣告返回型別
      template <typename It>
      auto fcn(It beg, It end)->decltype(*beg)
      {
          return *beg;
      }
      
    • std::move實現

    • 轉發:如果一個函式引數是指向模板型別引數的右值引用,它對應的實參的const屬性和左值/右值屬性將得到保持

    • 使用std::forward保持型別

  • 過載與模板
    正確定義一組過載的函式模板需要對型別間的關係和模板函式允許的有限的實參型別轉換有深刻的理解。

  • 可變引數模板

    • 模板引數包:0個/多個模板引數

      //Args是模板引數包,rest是函式引數包
      template <typename T, typename... Args>
      void foo(const T &t, const Args&... rest);
      //sizeof...運算子返回包中元素
      
    • 函式引數包:0個/多個函式引數

    • 包擴充套件

    • 轉發引數包

  • 模板特例化(本質是例項化一個模板)

    template<>
    int compare(const char* const &p1, const char* const &p2)
    	return strcmp(p1,p2);
    namespace std{
        template<>
        struct hash<Sales_data>
        {
            typedef size_t result_type;
            typedef Sales_data argument_type;
            size_t operator()(const Sales_data& s)const;
        };
        size_t
        hash<Sales_data>::operator()(const Sales_data &s)const
        {
            return hash<string>()(s.bookNo) ^
            		hash<unsigned>()(s.units_sold) ^
            		hash<double>()(s.revenue);
        }
    }
    

    部分特例化類别範本,而不能部分特例化函式模板
    特例化成員而不是類

相關文章