C++ Primer第五版(1)——C++基礎

1. C++基礎

  • 大多數編程語言通過兩種方式來進一步補充其基本特征
    1)賦予程序員自定義數據類型的權利,從而實現對語言的擴展
    2)將一些有用的功能封裝成庫函數提供給程序員

1.1 變量和基本類型

  • 算術類型


  • 類型的轉換


  • 一個算術表達式既有無符號數又有int值時,int值就會轉換成無符號數(負數總是加上模)

  • 指定字面值的類型


  • 變量提供了一個具名的、可供程序操作的存儲空間。數據類型決定著變量所占內存空間的大小和布局方式、該空間能存儲的值的范圍,以及變量能參與的運算。

  • 在c++中,初始化和賦值時兩個完全不同的操作。
    初始化不是賦值,初始化的含義是創建變量時賦予其一個初始值,而賦值的含義是把對象的當前值擦除,而以一個新值替代。

  • 在C++ 11中,用花括號來初始化變量得到了全面應用。這種初始化的形式被稱為列表初始化。當用于內置類型的變量時,列表初始化有一個重要特點:如果使用列表初始化且初始值存在丟失信息的風險,則編譯器將報錯。

  • 定義于函數體內的內置類型的對象如果沒有初始化,則其值未定義。類的對象如果沒有顯示地初始化,則其值由類確定。

  • 命名規范



  • 在變量使用的附近定義變量。這樣更容易找到變量的定義,并且付給它一個比較合理的初始值。

  • 左值引用——為對象起了另外一個名字,引用必須初始化。引用本身不是一個對象,因此一旦定義了引用,就無法令其再綁定到另外的對象。
    一般初始化變量時,初始值會被拷貝到新建的對象中,然而定義引用時,程序把引用和它的初值綁定在一起,而不是將初值拷貝給引用。
    引用只能綁定在對象上,而不能與字面值或某個表達式的計算結果綁定在一起。

  • 指針本身是一個對象;指針無需在定義時賦值。

  • 在新標準下,C++程序最好使用nullptr(字面值)來對空指針進行賦值,同時盡量避免使用NULL(預處理變量)。

  • 建議初始化所有的指針,并且在可能的情況下,盡量等定義了對象之后再定義指向它的指針。如果實在不清楚指針應該指向何處,就把它初始化為nullptr。

  • 引用本身不是一個對象,因此不能定義指向引用的指針。但指針使對象,所以存在對指針的引用。

int i = 43;
int *p;
int *&r = p; // r是對指針p的引用,從右往左進行分析

r = &i;
*r = 0;
  • const 引用
    當一個常量引用被綁定到另外一種類型時,會綁定到一個臨時量對象。C++將這種行為歸為非法。
int i = 42;
const int &r1 = i;
const int &r2 = 42;
const int &r3 = r1 * 2;
錯誤 int &r4 = r1 * 2;
  • const引用可能引用一個并非const的對象,此時允許通過其他途徑改變其值。
int i = 42;
int &r1 = i;
const int &r2 = i;
r1 = 0;
錯誤r2 = 0;
  • 編譯器在編譯過程中,將用到const變量的地方替換成對應的值,默認情況下,const對象被設定為僅在文件內有效。如果想在多個文件之間共享const對象,必須在變量定義之前添加extern關鍵字。
  • 頂層const表示指針本身是個常量,底層const表示指針所指的對象是一個常量。
    頂層const 可以表示任意的對象時常量,這一點對任何數據類型都適用,如算術類型、類、指針等。底層const則與指針和引用等符合類型的基本類型部分有關。
int i = 0;
int *const p1 = &i; // 不能改變p1,頂層
const in ci = 42; // 不能改變c1,頂層
const int *p2 = &ci; //允許改變p2,底層const
const int *const p3 = p2; // 頂層和底層
const int &r = ci; // 用于聲明引用的const都是底層const
  • 常量表達式是指值不會改變且在編譯過程中能得到計算結果的表達式。
    C++ 11規定,允許將變量聲明為constexpr類型以便由編譯器來驗證變量的值是否是一個常量表達式。聲明為cosntexpr的變量一定是一個常量,而且必須用常量表達式初始化。
    一個constexpr指針的初始值必須是nullptr,或者是存儲于某個固定地址中對象。
    函數體內定義的自動變量并非固定地址,不能用constexpr指針。定義于函數體外的對象其地址不變,可以用來初始化constexpr。
  • constexptr把它所定義的對象置為了頂層const。
const int *p = nullptr; // p是一個指向常量的指針
constexpr int *q = nullptr; // q是一個常量指針
  • 兩種方法定義類型別名
    1)typedef
    2)using SI = Sales_item; // SI是Sales_item的同義詞
typedef char *pstring;
const pstring cstr = 0; // cstr是指向char的常量指針
const pstring *ps;  // ps是一個指針,它的對象時指向char的常量指針
錯誤的理解 const char *cstr = 0; // 是對const pstring cstr的錯誤理解,typedef定義后,就變成了不可分割的整體
  • auto讓編譯器分析表達式所屬的類型,使用auto也能在一條語句中聲明多個變量。因為一條聲明語句只能有一個基本數據類型,所以該語句中的所有變量的初始基本數據類型都必須一樣。
    1)使用引用其實使用的是引用的對象,真正參與初始化的是引用對象的值,因此編譯器以引用對象的類型作為auto的類型
    2)auto會忽略掉頂層const。同時底層const則會保留下來。
    3)如果希望推斷出的auto類型是一個頂層const,需要明確指出
    4)設置一個類型為auto的引用時,初始值中的頂層常量屬性仍然保留
int i = 0, &r = i;
auto a = r; //a是一個整數

const int ci = i, &cr = ci;
auto b = ci; // b是一個整數,ci的頂層const被忽略
auto c = cr; // c是一個整數
auto d = &i; // d的是一個整型指針
auto e = &ci; //ci是一個指向整型常量的指針
  
const auto f = ci; //f是const int

auto &g = ci; //g是一個整型常量引用,綁定到ci
錯誤 auto &h = 42; 
const auto &j = 42; //可以為常量引用綁定字面值
  • decltype類型指示符
    選擇并返回操作數的數據類型,在此過程中,編譯器分析表達式并得到它的類型,卻并不計算表達式的值。
    decltype與auto有些許不同
const int ci = 0, &cj = ci;
decltype(ci) x = 0;  // x 是const int
decltype(cj) y = x;   // y 是const int &, y綁定到x
  • 如果表達式的內容十解引用操作,則decltype將得到引用類型。
    變量如果加上括號,會得到引用類型。
    decltype((variable)) 結果永遠是引用,而decltype(variable)結果只有當variable本身是一個引用時才是引用。
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // 結果是int
錯誤 decltype(*p) c;  // c是int &,必須初始化

錯誤 decltype((i)) d; // d是int&,必須初始化
decltype(i) e; //e是int

1.2 字符串、向量和數組

  • 作用域操作符::,編譯器從操作符左側名字的作用域中尋找右側那個名字。
  • 頭文件一般不應該使用using
  • string初始化



    1)拷貝初始化
    使用=,執行的是拷貝初始化,編譯器將等號右側的初始值拷貝到新創建的對象中去。
    2)直接初始化
    不是用等號

  • string的操作


  • string::size_type是無符號類型的值。注意不要在表達式中混用帶符號樹和無符號數。
  • 標準庫允許把字符字面值和字符串字面值轉換成string對象,所以在需要string對象的地方可以使用這兩種字面值來代替。當把string對象和字符字面值及字符串字面值混在一條語句中使用時,必須確保每個加號運算符(+)的兩側的運算對象至少有一個是string。
  • 為了與C兼容,C++中字符串字面值并不是標準庫類型string對象。字符串字面值與string是不同類型。
  • 處理string對象中的字符



    range for可以處理string中的每個字符。


  • vector——類模板
    模板本身不是類或函數,相反可以將模板看作為編譯器生成類或函數編寫的一份說明。編譯器根據模板創建類或函數的過程稱為實例化。
    老式的聲明vector<vector<int> >,需要添加一個空格,C++11不需要。


  • 圓括號和花括號的區別,花括號是列表初始化:


  • 范圍for語句體內不應改變其所遍歷序列的大小。
  • vector操作



  • 迭代器
    有效迭代器指向某個元素或者指向容器中尾元素的下一位置,其他情況都屬于無效。


  • 標準庫容器的迭代器都定義了==和!=,大多數沒有定義<。
  • 使迭代器失效的操作
    1)不能在范圍for循環中向vector對象添加元素
    2)任何一種可能改變vector對象容量的操作,都會使vector對象迭代器失效
  • 迭代器運算



    距離的類型名是difference_type的帶符號整型數。


  • 數組 —— 跟C語言一樣
    size_t在cstddef頭文件里面進行了定義。
    指針也是迭代器
  • 在iterator頭文件中定義了begin和end函數,end函數返回尾元素下一位置的指針。尾后指針不能執行解引用和遞增操作。
    兩個指針相減的結果類型是ptrdiff_t。
int *pbeg = begin(arr), *pend = end(arr);
while (pbeg != pend && *pbeg >= 0) {
    ++pbeg;
}

  • 盡管C++支持C風格字符串,但在C++最好還是不要使用它們。因為C風格字符串使用不方便,且極易引發程序漏洞。
    現代C++程序應該盡量使用vector和迭代器,避免使用內置數組和指針;應該盡量使用string,避免使用C風格的基于數組的字符串。

  • 多維數組

int ia[3][4];
for (auto p = begin(ia); p != end(ia); ++p) {
    for (auto q = begin(*p); q != end(*p); ++q) {
        cout << *q << ' ';
    }
    cout << endl;
}

1.3 表達式

  • 重載運算符時,其包括運算對象的類型和返回值的類型,都是由該運算符定義的,但是運算對象的個數、運算符的優先級和結合律都是無法改變的。
  • 左值和右值:當一個對象被用作右值時,用的是對象的值(內容);當對象被用作左值時,用的是對象的身份(在內存中的位置)。

    如果表達式的求值結果是左值,decltype作用于該表達式(不是變量)得到一個引用類型,例如p是int 類型,decltype(p)是int &。
  • 只有四種運算符明確規定了運算對象的求值順序:&& || ?: ,四種運算符
  • 求值順序、優先級、結合律
    1)當拿不準時候,最好用括號來強制讓表達式的組合關系符合程序邏輯的要求
    2)如果改變了某個運算符對象的值,在表達式的其他地方不要使用這個運算對象。例外:當改變運算符的子表達式就是另外一個子表達式的運算對象時。例如:*++iter
  • c++新標準規定商一律向0取整(即直接切除小數部分)
  • 除非必要,否則不用遞增遞減運算符的后置版本

  • 在大多數用到數組的表達式中,數組自動轉換成指向數組首元素的指針;數組用作decltype、&、sizeof、typeid的運算對象時除外。
  • 四種類型的顯式轉換
    cast-name<type>(expression)
    1)static_cast
    任何具有明確定義的類型轉換,只要不包含底層const,都可以使用static_cast。
    2)dynamic_cast
    支持運行時類型識別
    3)const_cast
    只能改變運算對象底層const
    4)reinterpret_cast
    為運算對象的位模式提供較低層次上的重新解釋。
const char *pc;
char *p = const_cast<char *>(pc); // 正確,但是通過p寫值是未定義的行為
  • 避免強制類型轉換,尤其是reinterpret_cast
    在有重載函數的上下文中使用const_cast無可厚非

  • 運算符優先級表



1.4 語句

  • break語句負責終止離它最近的while、do while、for或switch語句,并從這些語句之后的第一條語句開始繼續執行。
    continue終止最近的循環中的當前迭代并立即開始下一次迭代。
  • 異常處理機制為程序中異常檢測和異常處理兩部分的協作提供支持。
    1)throw表達式,異常檢測部分使用throw表達式來表示它遇到了無法處理的問題。
    2)try語句塊,異常處理部分使用try語句塊處理異常。try語句塊中代碼拋出的異常通常會被某個catch子句處理。
    3)一套異常類,用于在throw表達式和相關的catch子句之間傳遞異常的具體信息。
  • 當異常被拋出時,首先搜索拋出該異常的函數。如果沒有找到匹配的catch子句,終止該函數,并在調用該函數的函數中繼續尋找。如果還是沒有找到匹配的catch子句,這個新的函數也被終止,繼續搜索調用它的函數。以此類推。沿著程序的執行路徑逐層回退,直到找到適當類型的catch子句為止。如果最終沒有找到任何匹配的catch子句,程序轉到terminate的標準庫函數。
  • 異常類分別定義在4個頭文件中
    1)exception頭文件定義最通用的異常類。只報告異常的發生,不提供任何額外的信息
    2)stdexcept頭文件定義了幾種常用的異常類



    3)new頭文件定義了bad_alloc異常類型
    4)type_info定義了bad_cast異常類型

  • 只能以默認初始化的方式初始化exception bad_alloc bad_cast對象,不允許為這些對象提供初始值。
    其他異常類型的行為恰好相反:應該使用string對象或C風格字符串初始化這些類型的對象,不允許使用默認初始化方式。
    異常類型之定義了一個what成員函數,返回const char*,提供異常的一些文本信息。

1.5 函數

  • 在C++中,名字有作用域,對象有生命周期。
  • 函數的形參盡量使用常量引用,如果將其定義為普通引用,就不能將const 對象、字面值或者需要類型類型轉換的對象傳遞給普通的引用形參。
  • 編寫能處理不同數量實參的參數,C++ 11新標準提供了兩種主要的方法
    1)如果所有的實參類型相同,可以傳遞一個名為intializer_list的標準庫類型



    initializer_list對象中的元素用于是常量值,無法改變。
    2)如果實參類型不同,可以編寫可變參數模板

void error_msg(initializer_list<string> il) {
    for (auto beg = il.begin(); beg != il.end(); ++beg) {
        cout << *beg << " ";
    }
    cout << endl;
}
  • vargargs省略符形參應該僅僅用于C和C++通用的類型。特別注意,大多數類型的對象在傳遞給省略符形參時都無法正確拷貝。

  • 返回一個值的方式和初始化一個變量或形參的方式完全一樣:返回的值用于初始化調用點的一個臨時量,該臨時量就是函數調用的結果。
  • 返回引用的函數得到左值,其他返回類型得到右值。
  • C++11規定,函數可以返回花括號包圍的值的列表。類似于其他返回結果,此處的列表也用來對表示函數返回的臨時量進行初始化。
vector<string> process() {
    if (expected.empty()) {
        return {};
    } else if (expected == actual ) {
        return {"functionX", "okay"};
    } else {
        return {"functionX", expected, actual};
    }
}
  • C++ 11可以使用尾置返回類型來簡化復雜返回類型的函數聲明
auto func(int i) -> int (*) [10]; // func接受一個int類型的實參,返回一個指針,該指針指向還有10個整數的數組

  • 重載函數:同一作用域內的幾個函數名字相同但形參列表不同。編譯器會根據傳遞的實參類型推斷想要的是哪個函數。
    不允許兩個函數除了返回類型外其他所有要素都相同。
  • 頂層const 不影響傳入函數的對象,所以無法和沒有頂層const的形參區分開來;
    底層const可以重載,此時形參是某種類型的指針或引用,當傳遞一個非常量對象或指向非常量對象的指針時,編譯器會優先選用非常量版本的函數。
    頂層const 可以表示任意的對象時常量,這一點對任何數據類型都適用,如算術類型、類、指針等。底層const則與指針和引用等符合類型的基本類型部分有關。
Record lookup(Phone);
Record lookup(const Phone);  // 重復聲明

Record lookup(Phone*);
Record lookup(Phone* const); // 重復聲明

Record lookup(Account &);
Record lookup(const Account &); // 新函數

Record lookup(Account*);
Record lookup(const Account *); // 新函數
  • const_cast和重載:將const轉變為普通的無const
const string &shorterString(const string &s1, const string &s2) {
    return s1.size() <= s2.size() ? s1 : s2;
}

string &shorterString(string &s1, string &s2) {
    auto &r = shorterString(const_cast<const string&>(s1),  const_cast<const_string&>(s2));
     return const_cast<string&>(r);
}
  • 函數匹配(重載確定):
    1)編譯器找到一個與實參最佳匹配的函數,并生成調用該函數的代碼
    2)找不到任何一個函數與調用的實參匹配,此時編譯器發出無匹配的錯誤信息
    3)有多于一個函數可以匹配,但是每一個都不是明顯的最佳選擇。此時也將發生錯誤,稱為二義性調用。
  • 若在內層作用域中聲明重載函數,它將隱藏外層作用域中聲明的同名實體。編譯器首先在當前作用域尋找,如果找到相應的函數,編譯器就會忽略掉外層作用域中的同名實體,剩下的工作就是檢查函數調用是否有效。
void print(const string&);
void print(double);
void fooBar(int ival) {
    void print(int); //隱藏了外層作用域的兩個print
    print("value: "); //錯誤
    print(ival);  //正確
    print(3.14);  //調用print(int)
}

正確做法:
void print(const string&);
void print(double);
void print(int);
void fooBar(int ival) {   
    print("value: "); //print(const string&)
    print(ival);  //print(int)
    print(3.14);  //print(double)
}

  • 默認實參
    一旦某個形參被賦予了默認值,它后面的所有形參都必須有默認值。當設計含有默認實參的函數時,其中一項任務是合理設置形參的順序,盡量讓不怎么使用默認值的形參出現在前面,而讓那些經常使用默認值的形參出現在后面。
  • 內聯函數
    內聯說明只是向編譯器發出的一個請求,編譯器可以選擇忽略這個請求。一般來說,內聯機制用于優化規模較小、流程直接、頻繁調用的函數。
  • constexpr函數
    能用于常量表達式的函數,要遵循幾項約定:函數的返回類型及所有形參的類型都得是字面值類型,而且函數體中必須有且只有一條return語句。
    為了能在編譯過程中隨時展開,constexpr函數被隱式地指定為內聯函數。
    constexpr函數不一定返回常量表達式
constexpr int new_sz() { return 42;}
constexpr size_t scale(size_t cnt) {
    return new_sz() * cnt;
}

int arr[scale(2)]; // 正確
int i = 2;
int a2[scale(i)]; // 錯誤:scale(i)不是常量表達式
  • 調試幫助
    程序可以包含一些用于調試的代碼,但是這些代碼只是在開發程序時使用。當應用程序編寫完成準備發布時,要先屏蔽掉調試代碼。
    1)assert預處理宏(cassert頭文件)
    assert(expr); 如果表達式為假(即0),assert輸出信息并終止程序的執行。如果為真,則什么也不做。
    預處理名字由預處理器而非編譯器管理。
    2)NDEBUG預處理變量
    assert的行為依賴于一個名為NDEBUG的預處理變量的狀態。如果定義了NDEBUG,則assert什么也不做。默認狀態下沒有定義NDEBUG,此時assert將執行運行時檢查。
    可以使用編譯命令選項-D來定義NDEBUG。
    可以將assert當成調試程序的一種輔助手段,但是不能用它替代真正的運行時邏輯檢查,也不能替代程序本身應該包含的錯誤檢查。
    預處理器定義對于程序調試很有用的名字:
    __func__ 函數名字
    __FILE__ 文件名
    __LINE__行號
    __TIME__編譯時間
    __DATE__日期

  • 函數匹配
    1)函數匹配的第一步是選定本次調用對應的重載函數集,集合中的函數稱為候選函數。候選函數具備兩個特征:一是與被調用的函數同名,二是其聲明在調用點可見。
    2)第二步考察本次調用提供的實參,然后從候選參數中選出能被這組實參調用的函數,這些新選出的函數稱為可行函數。可行函數也有兩個特征:一是其形參數量與本次調用提供的實參數量相等,二是每個實參的類型與對應的形參類型相同,或者能轉換成形參的類型。
    如果沒有找到可行函數,編譯器將報告無匹配函數的錯誤。
    3)第三步是從可行函數選擇與本次調用最匹配的函數。實參類型與形參類型越接近,它們匹配得越好。
    如果沒有最匹配的函數,則該調用錯誤。
    調用重載函數時應盡量避免強制類型轉換。如果在實際應用中確實需要強制類型轉換,則說明我們設計的形參集合不合理。
  • 為了確定最佳匹配,編譯器將實參類型到形參類型的轉換劃分成幾個等級,具體排序如下所示:


void ff(int);
void ff(short);
ff('a');  // char提升成int;調用f(int)

void manip(long);
void manip(float);
manip(3.14);   // 錯誤:二義性調用

Record lookup(Account &);
Record lookup(const Account&);
const Account a;
Account b;

lookup(a);  // 調用lookup(const Account &)
lookup(b); // 調用lookup(Account &)

  • 函數指針
    函數的類型由它的返回類型和形參類型共同決定,與函數名無關。
    1)函數或者函數指針都可以作為形參,函數類型實際上被當成了指針使用
    2)作為返回類型時,編譯器不會自動將函數返回類型當成對應的指針類型處理。
using F = int(int*, int); // F是函數類型,不是指針
using PF = int(*)(int*, int); //PF是指針類型

PF f1(int); // 正確
F f1(int); // 錯誤,不能返回函數類型
F *f1(int); //正確

上面等價于下面的直接聲明:
int (*f1(int))(int*, int);
auto f1(int) -> int (*) (int*, int);
  • decltype作用于某個函數時,它返回函數類型而非指針類型。
string:size_type sumLength(const string&, const string&);
string:size_type largerLength(const string&, const string&);
//下面的函數返回上面兩個函數的指針的一個
decltype(sumLength) *getFcn(const string&); // decltype返回的是函數類型

1.6 類

  • 類的基本思想是數據抽象和封裝。數據抽象是一種依賴于接口和實現分離的編程技術。封裝實現了類的接口和實現的分離。
  • 成員函數通過一個名為this的額外的隱式參數來訪問調用它的那個對象。當我們調用一個成員函數時,用請求該函數的對象地址初始化this。
  • 把const關鍵字放在成員函數的參數列表之后,表示this是一個指向常量的指針。默認情況下,this的類型是指向類類型非常量版本的常量指針。因此常量成員函數不能改變調用它的對象的內容。
std::string isbn() const {} // 將this聲明成 const Sales_data *const,指向常量對象
  • 編譯器分兩步處理:首先編譯成員的聲明,然后才輪到成員函數體。因此,成員函數體可以隨意使用類中的其他成員而無須在意這些成員出現的次序。
  • 函數如果定義在類的內部,默認是內聯的,如果定義在類的外部,默認是不內聯的。
  • 一般來說,當定義的函數類似于某個運算符時,應該令該函數的行為盡量模仿這個運算符。內置的賦值運算符把它的左側運算對象當成左值返回,因此為了保持一致,combine必須返回引用類型。
Sales_data& Sales_data::combine(const Sales_data &rhs) {
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}
  • 一般來說,如果非成員函數是類接口的組成部分,則這些函數的聲明應該與類在同一個頭文件內。
    IO類型屬于不能被拷貝的類型,因此只能通過引用來傳遞。讀取和寫入的操作會改變流的內容,所以是普通引用。
istream &read(istream &is, Sales_data &item) {
    double price = 0;
    ...
    return is;
}
  • 如果類沒有顯式地定義構造函數,那么編譯器就會隱式地定義一個默認構造函數——合成的默認構造函數。
    1)如果存在類內初始值,用它來初始化成員
    2)否則,默認初始化該成員。
  • 某些類不能依賴合成的默認構造函數
    1)沒有聲明任何構造函數是才自動生成
    2)合成的默認構造函數可能執行錯誤的操作,利于默認初始化的值是未定義
    3)不能為某些類合成默認的構造函數。類中含有一個其他類類型的成員,且這個成員的類型沒有默認構造函數。
  • 定義默認構造函數
    1)不接受任何實參,所以是一個默認構造函數
    2)C++11中,如果需要默認行為,可以在參數列表后加上= default
Sales_data() = default;
  • 盡管編譯器能夠合成拷貝、賦值和銷毀操作,但是對于某些類來說合成版本無法正常工作。特別是,當類需要分配類對象之外的資源時。

  • 友元
    類可以允許其他類或者函數訪問它的非公有成員,方法是令其他類或函數稱為它的友元。
    友元聲明只能出現在類定義的內部,即以friend關鍵字開頭的函數聲明。

  • 可變數據成員mutable
    不會是const,即使它是const對象的成員。一個const成員函數可以改變可變成員的值。

  • 類內初始值必須使用=的初始化形式或者花括號括起來的直接初始化形式。
  • 一個常量成員函數如果以引用的形式返回*this,那么它的返回類型將是常量引用。
  • 通過區分成員函數是否是const,可以對其進行重載。

  • 在實踐中,設計良好的C++代碼常常包含大量的小函數。

  • 不完全類型:聲明之后定義之前都是不完全類型
    不完全類型只能在非常有限的情景下使用:可以定義指向這種類型的指針或引用,也可以聲明(但是不能定義)以不完全類型作為參數或者返回類型的函數。

  • 友元關系不存在傳遞性。
  • 要想令某個成員函數作為友元,必須仔細組織程序的結構以滿足聲明和定義的彼此依賴關系
    1)首先定義Window_mgr類,其中聲明clear函數,但是不能定義它。在clear使用Screen的成員之前必須先聲明Screen
    2)接下來定義Screen,包括對于clear的友元聲明
    3)最后定義clear,此時才可以使用Screen的成員
class Screen{
    friend void Window_mgr::clear(ScreenIndex);
};
  • 對于定義在類內部的成員函數,解析其中名字的方式:
    1)首先編譯成員的聲明
    2)直到類全部可見后才編譯函數體
  • 成員函數中使用的名字按照如下方式解析:
    1)首先,在成員函數內查找該名字的聲明。只有在函數使用之前出現的聲明才被考慮
    2)如果在成員函數內沒有找到,則在類內繼續查找,類的所有成員都可以被考慮
    3)如果類內沒有找到該名字的聲明,在成員函數定義之前的作用域內繼續查找


  • 如果成員是const、引用,或者屬于某種未提供默認構造函數的類類型,我們必須通過構造函數初始值列表為這些成員提供初值。
    建議使用構造函數初始值,而非賦值。
  • 最好令構造函數初始值的順序與成員聲明的順序保持一致。
  • 委托構造函數,一個委托構造函數使用它所屬類的其他構造函數執行自己的初始化過程,或者說它把自己的一些或者全部職責委托給了其他構造函數。
class Sales_data {
public:
    Sales_data(std::string s, unsigned cnt, double price) : bookNo(s), units_sold(cnt), revenue(cnt *price) {}
    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抑制構造函數定義的隱式轉換,并且只能在類內聲明構造函數時使用explicit,在外部定義時不應重復。

  • 聚合類——struct,所有成員都是可以直接訪問的
  • 字面值常量類



  • 類的靜態成員:需要一些成員與類本身直接相關,而不是與類的各個對象保持聯系。
    類的靜態成員存在于任何對象之外,對象中不包含任何與靜態數據成員有關的數據。
    靜態成員函數不包含this指針,不能聲明成const。
    static只出現在類內部的聲明語句中。
    要想確保對象只定義次,最好的辦法是把靜態數據成員的定義與其他非內聯函數的定義放在同一文件中。
    即使一個常量靜態數據成員在類內部被初始化了,通常情況下也應該在類的外部定義一下該成員。
    靜態成員可以是不完全類型;靜態成員可以作為默認實參,非靜態成員不可以。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容