C++Primer学习笔记:第7章 类
-
類的基本思想是數(shù)據(jù)抽象data abstraction和封裝encapsulation。數(shù)據(jù)抽象是一種依賴于接口interface和實現(xiàn)implementation分離的編程技術(shù)
-
在類中,由類的設(shè)計者負責考慮類的實現(xiàn)過程,使用該類的程序員只需要抽象地思考類型做了什么,而無須了解類型的工作細節(jié)
-
執(zhí)行加法和IO的函數(shù)應(yīng)該定義成普通函數(shù),執(zhí)行復(fù)合賦值運算的函數(shù)應(yīng)該是成員函數(shù)
-
成員函數(shù)的聲明必須在類的內(nèi)部,它的定義則既可以在類的內(nèi)部也可以在類的外部
-
成員函數(shù)通過一個名為this的額外的隱式參數(shù)來訪問調(diào)用它的對象。當我們調(diào)用一個成員函數(shù)時,用請求該函數(shù)的對象地址初始化該常量指針this。因此我們不能自定義名為this的參數(shù)或者變量
-
緊隨參數(shù)列表之后的const關(guān)鍵字修改隱式this指針為指向類類型常量版本的常量指針(默認不是指向常量的)。這意味著如果一個成員函數(shù)是普通函數(shù)(函數(shù)參數(shù)列表后沒有const),則一個常量類對象無法調(diào)用該函數(shù)(無法給this指針初始化)
-
如果成員函數(shù)內(nèi)部對對象的其他成員沒有修改,則盡可能將成員函數(shù)聲明為常量成員函數(shù),緊跟在函數(shù)參數(shù)列表后面的const表示this是一個指向常量的常量指針。將函數(shù)聲明為常量成員函數(shù)有利于提高函數(shù)的靈活性,令常量對象仍然可以調(diào)用該函數(shù)
-
常量對象,以及常量對象的引用或者指針都只能調(diào)用常量成員函數(shù)
-
類本身就是一個作用域,編譯器分兩部處理:首先編譯成員的聲明,然后才輪到成員函數(shù)體。成員函數(shù)體可以隨意使用類中的成員而無須在意這些成員出現(xiàn)的次序
-
類外部定義的成員的名字必須包含它所屬的類名,而且返回類型、參數(shù)列表和函數(shù)名都得與類內(nèi)部的聲明保持一致
double Sales_data::avg_price() const {} -
當我們定義函數(shù)類似于某個內(nèi)置運算符時,應(yīng)該令函數(shù)的行為盡量模仿這個運算符
-
如果函數(shù)在概念上屬于類但是不定義在類中,則它一般應(yīng)該與類聲明在同一個頭文件中
-
默認情況下,拷貝類的對象其實是拷貝對象的數(shù)據(jù)成員
-
構(gòu)造函數(shù)沒有返回類型,構(gòu)造函數(shù)不能被聲明為const,當我們創(chuàng)建類的一個const對象時,直到構(gòu)造函數(shù)完成初始化過程對象才真正取得常量屬性
-
如果我們的類沒有顯式地定義構(gòu)造函數(shù),則編譯器會為我們隱式定義一個默認構(gòu)造函數(shù)。默認構(gòu)造函數(shù)沒有任何實際參數(shù)。這個編譯器創(chuàng)建的構(gòu)造函數(shù)又稱作合成的默認構(gòu)造函數(shù)
- 如果存在類內(nèi)的初始值,用它來初始化成員
- 否則,默認初始化該成員
-
不能依賴默認構(gòu)造函數(shù):
- 一旦我們定義了構(gòu)造函數(shù)(不論是否有參),除非我們自己定義一個默認構(gòu)造函數(shù)(無參),否則類將沒有默認構(gòu)造函數(shù)。只有當類沒有聲明任何構(gòu)造函數(shù)的時候編譯器才會自動生成默認構(gòu)造函數(shù)
- 如果定義在塊中的內(nèi)置類型或復(fù)合類型(數(shù)組和指針)的對象被默認初始化,則其值是未定義的。除非他們都有類內(nèi)初始值
- 如果類中包含一個其他類,而且這個類沒有默認構(gòu)造函數(shù),那么編譯器將無法初始化該成員
-
如果函數(shù)定義在類內(nèi)部,則函數(shù)默認是內(nèi)聯(lián)的,如果在外部,則默認是不內(nèi)聯(lián)的
-
在C++11新標準中,我們可以通過在參數(shù)列表后面寫上=default來要求編譯器生成構(gòu)造函數(shù)(在類外仍然可以)
-
在構(gòu)造函數(shù)中可以使用構(gòu)造函數(shù)初始值列表對部分成員進行初始化,在初始值列表初始化后用類內(nèi)初始化對成員進行初始化,剩下的執(zhí)行默認初始化,然后再執(zhí)行構(gòu)造函數(shù)函數(shù)體中的內(nèi)容
-
如果我們不定義拷貝、賦值和析構(gòu)操作,則編譯器會會替我們合成。一般來說,編譯器生成的版本將對對象中的每個成員執(zhí)行拷貝、賦值和銷毀操作
- 拷貝:初始化變量、以值的方式傳遞或返回一個對象
- 賦值:使用賦值運算符
- 銷毀:當對象不再存在時銷毀
-
每個訪問說明符指定了接下來成員的訪問級別,其有效范圍直到出現(xiàn)下一個訪問說明符或者到達類的結(jié)尾為止
- 定義在public說明符之后的成員在整個程序內(nèi)可以被訪問,定義類的接口
- 定義在private說明符之后的成員可以被類的成員函數(shù)訪問,但是不能被使用該類的代碼訪問
-
class和struct的唯一區(qū)別:默認訪問權(quán)限不同,struct的默認訪問權(quán)限是public,class的默認訪問權(quán)限是private
-
類可以允許其他類或者函數(shù)訪問它的非公有成員,方法是令其他類或者函數(shù)成為它的友元。如果類想把一個函數(shù)作為它的友元,只需要增加一條以friend關(guān)鍵字開始的函數(shù)聲明語句即可。友元聲明只能出現(xiàn)在類定義的內(nèi)部,但是具體位置不限。一般來說,最好在類定義開始或結(jié)束前的位置集中聲明友元
-
友元的聲明僅僅指定了訪問的權(quán)限,而非一個通常意義上的函數(shù)聲明,我們必須在友元聲明之外再專門對函數(shù)進行一次聲明(一些編譯器允許不再次聲明,不過為了能夠讓所有的編譯器成功運行,最好還是加上)。為了使友元對類的用戶可見,我們通常把友元的聲明與類本身放置在同一個頭文件中。
-
除了定義數(shù)據(jù)和函數(shù)成員外,類還可以定義某種類型在類中的別名,由類定義的類型名字和其他成員一樣存在訪問限制
class Screen { public:typedef std::string::size_type pos;//using pos = std::string::size; }用來定義類型的成員必須先定義后使用,這一點與普通成員有所區(qū)別。因此類型成員通常出現(xiàn)在類開始的地方
-
如果我們希望無論如何都能夠修改某個類的數(shù)據(jù)成員,即使該對象是const,我們可以在變量的聲明中加入mutable關(guān)鍵字,稱作可變數(shù)據(jù)成員??梢酝茰y,一個可變數(shù)據(jù)成員無論如何都不是const的。
-
當我們提供類內(nèi)初始值時,必須以符號=或者花括號表示,不能用圓括號
-
如果想要在成員函數(shù)中返回對象本身,則返回類型應(yīng)該為引用,返回值為*this
-
一個const成員函數(shù)如果以引用的形式返回*this,則應(yīng)該返回一個常量引用
-
我們可以通過成員函數(shù)是否是const對函數(shù)進行重載,其原因如同函數(shù)的指針參數(shù)是否指向const可以進行重載一樣。如果一個函數(shù)既有const版本,又有非const版本,則對于常量對象僅僅可以調(diào)用const版本,對于非常量對象非const版本顯然是一個更好的匹配
-
在類內(nèi)使用一些小函數(shù)不會增加運行時的額外開銷,相反還會給開發(fā)帶來很多好處
class Screen {public:Screen &display(std::ostream &os) {do_display(os); return *this;}const Screen &display(std::ostream &os) const {do_display(os); return *this;}void do_display(std::ostream &os) const {os << contents;} } -
每個類定義了唯一的類型,對于兩個類來說,即使他們的成語完全一樣,這兩個類也是完全不同的類型
-
在C語言中要求類類型定義對象時加上class或struct,但是C++中不必要
-
我們也可以僅僅聲明類而不定義它,這種聲明有時被稱作前向聲明,在定義之前是一個不完全類型:可以定義指向這種類型的指針或者引用,也可以聲明(不能定義)以不完全類型作為參數(shù)或者返回類型的函數(shù)。因此,一個類的名字出現(xiàn)過以后,就被認為是聲明過了,允許包含指向它自身類型的引用或者指針
-
如果一個類指定了友元類,則友元類的成員函數(shù)可以訪問此類包括非公有成員在內(nèi)的所有成員。友元關(guān)系不具有傳遞性
-
需要注意的是,友元的聲明僅僅影響的是該函數(shù)的訪問權(quán)限,因此并不能看作聲明,為了完成對該友元的調(diào)用,必須在其作用域內(nèi)對友元進行聲明。友元的作用域和類的聲明是同一級別的
struct X {friend void f() {} //友元函數(shù)可以定義在類的內(nèi)部,但是此時在類X的作用域中是不可見的X() { f(); } //錯誤,f()不可見void g();void h(); } void X::g() { f(); } //錯誤,f()不可見 void f(); //對f進行聲明,此時對X可見 void X::h() { f(); } //正確需要注意的是,有的編譯器對上面沒有這種限制
-
定義在外部的成員函數(shù)的返回類型不在類的作用域內(nèi)
-
類的定義分兩步處理:
- 順序編譯成員的聲明
- 指導(dǎo)類全部可見后編譯函數(shù)體
-
一般來說,內(nèi)層作用域可以重新定義外層作用域名字,即使該名字已經(jīng)在內(nèi)層作用域中使用過,而在類中,如果成員使用了外層作用域中的某個名字,而該名字代表某一種類型,則類不能重新定義該名字(一些編譯器忽略這種錯誤)
-
類型名的定義通常出現(xiàn)在類的開始處,這樣保證所有使用該類型的成員都出現(xiàn)在類型之后
-
盡管全局變量有可能被覆蓋掉,但是我們可以使用::name來獲取對應(yīng)的全局變量
-
當成員定義在類的外部時,名字查找的最后一步不僅要考慮類定義之前的全局作用域中的聲明,還要考慮在成員函數(shù)定義之前的全局作用域中的聲明
-
如果沒有在構(gòu)造函數(shù)的初始值列表中顯式地初始化成員,而且該成員沒有類內(nèi)初始值,則該成員將在構(gòu)造函數(shù)體之前執(zhí)行默認初始化。有時候有的變量無法進行默認初始化(如一些沒有默認構(gòu)造函數(shù)的類對象,以及引用或者常量)
-
在構(gòu)造函數(shù)初始值中每個成員只能出現(xiàn)一次。構(gòu)造函數(shù)初始值列表只用于說明初始化成員的值,而不限定初始化具體執(zhí)行順序,成員的初始化順序與他們在類定義中的出現(xiàn)順序一致,構(gòu)造函數(shù)初始值列表中初始值的前后位置不會影響實際的初始化順序。如果使用某些成員初始化其他成員,則初始化的順序就比較重要,不過這種做法不是一個好的編程習(xí)慣
-
如果一個構(gòu)造函數(shù)為所有參數(shù)都提供了默認實參,則它實際上也定義了默認構(gòu)造函數(shù)
-
我們可以使用委托構(gòu)造函數(shù),委托其他構(gòu)造函數(shù)進行構(gòu)造
class X {X(int _a, int _b, int _c):a(_a),b(_b),c(_c){}X():X(0, 0, 0){}X(int _a):X(_a, 0, 0){}X{int _a, int _b):X(_a, _b, 0){} };委托構(gòu)造函數(shù)會先執(zhí)行被委托構(gòu)造函數(shù)的初始化列表,然后再執(zhí)行函數(shù)體
-
當對象被默認初始化或值初始化時自動執(zhí)行默認構(gòu)造函數(shù)
-
默認初始化在以下情況下發(fā)生:
- 當我們在塊作用域內(nèi)不適用任何初始值定義的一個非靜態(tài)變量或者數(shù)組時
- 當一個類本身含有類類型的成員且使用合成的默認構(gòu)造函數(shù)時
- 當類類型的成員沒有在構(gòu)造函數(shù)初始值列表中顯式地初始化時
-
值初始化在以下情況下發(fā)生:
- 在數(shù)組初始化的過程中如果我們提供的初始值的數(shù)量小于數(shù)組的大小時
- 當我們不使用初始值定義一個局部靜態(tài)變量時
- 當我們通過書寫形如t()的表達式顯式地請求值初始化時
-
在實際中,如果提供了其他構(gòu)造函數(shù),最好也提供一個默認的構(gòu)造函數(shù)
-
如果想定義一個使用默認構(gòu)造函數(shù)進行初始化的對象,正確的方法是去掉對象名后的空的括號對
sales_data obj(); //聲明了一個函數(shù) sales_data obj1; //聲明了一個對象,使用默認構(gòu)造函數(shù)進行初始化 -
能通過一個實參調(diào)用的構(gòu)造函數(shù)定義了一條從構(gòu)造函數(shù)參數(shù)類型向類類型隱式轉(zhuǎn)換的規(guī)則,但是這種轉(zhuǎn)換只允許一步
-
我們可以通過將構(gòu)造函數(shù)聲明為explicit阻止隱式轉(zhuǎn)換,只對一個實參的構(gòu)造函數(shù)有效。只能在類內(nèi)聲明構(gòu)造函數(shù)時使用explicit關(guān)鍵字,在類外定義時不應(yīng)重復(fù)。explicit構(gòu)造函數(shù)只能用于直接初始化
-
我們可以使用explicit構(gòu)造函數(shù)進行強制類型轉(zhuǎn)換
item.combine(static_cast<sales_data>(cin));- const char * -> string的構(gòu)造函數(shù)不是explicit的
- 接收一個容量參數(shù)的vector構(gòu)造函數(shù)是explicit的
- 需要注意的是**explicit阻止的隱式轉(zhuǎn)換是構(gòu)造函數(shù)參數(shù)類型轉(zhuǎn)換為類類型的轉(zhuǎn)換**,而不是構(gòu)造函數(shù)參數(shù)之間的轉(zhuǎn)換
- 允許臨時量存在的地方才允許隱式類型轉(zhuǎn)換
- 聚合類使得用戶可以直接訪問其成員,因此具有特殊的初始化語法形式,當一個類滿足如下條件時,我們說它是聚合的
- 所有成員都是public的
- 沒有定義任何構(gòu)造函數(shù)
- 沒有類內(nèi)初始值
- 沒有基類,也沒有virtual函數(shù)
例如:
-
數(shù)據(jù)成員那都是字面值類型的聚合類是字面值常量類。或者滿足以下要求:
- 數(shù)據(jù)成員必須是字面值類型
- 類必須至少含有一個constexpr構(gòu)造函數(shù)
- 如果某個成員有類內(nèi)初始值,內(nèi)置類型必須是一條常量表達式,類類型必須使用自己的constexpr構(gòu)造函數(shù)
- 類必須使用析構(gòu)函數(shù)的默認定義
-
我們可以通過在成員的聲明之前加上關(guān)鍵字static使得其與類關(guān)聯(lián)在一起。類的靜態(tài)成員存在于任何對象之外,對象中不包含任何與靜態(tài)數(shù)據(jù)成員有關(guān)的數(shù)據(jù)。因為靜態(tài)函數(shù)成員中沒有this指針),靜態(tài)成員啊還是那話不能聲明成const,也不能調(diào)用非靜態(tài)成員
-
可以使用作用域運算符直接訪問靜態(tài)成員。雖然靜態(tài)成員不屬于某個對象,但是我們還是可以使用類的對象、引用或者指針訪問靜態(tài)成員。成員函數(shù)不通過作用域運算符就能直接訪問靜態(tài)成員
-
靜態(tài)成員的static關(guān)鍵字只能在類內(nèi)部出現(xiàn)(同explicit),不能在類外重復(fù)使用
-
靜態(tài)成員不能由類的構(gòu)造函數(shù)初始化,靜態(tài)數(shù)據(jù)成員定義在任何函數(shù)之外,因此一旦被定義就存在于程序的整個生命周期中。一般來說,我們不能在類的內(nèi)部初始化靜態(tài)成員,相反的,必須在類的外部定義和初始化每個靜態(tài)成員 。
-
通常情況下,類的靜態(tài)成員不應(yīng)該在類的內(nèi)部初始化。然而我們可以為靜態(tài)成員提供const整數(shù)類型的類內(nèi)初始值,不過要求靜態(tài)成員必須是字面值常量類型的constexpr。這樣的靜態(tài)常量可以用在所有適合用于常量表達式的地方。即使該成員在類內(nèi)已經(jīng)初始化,還是應(yīng)該在類外定義一下該成員,而且不能再指定一個初始值了。這樣做的好處是能夠在類的外部使用該靜態(tài)成員。
void func(const int &x) {cout << "x:" << x << endl; } struct A {static const int a;static constexpr int aa = 6;static vector<int> v; }; vector<int> A::v(aa); const int A::a = 5; int main() {//const A a;//a.test();cout << A::v.size() << endl; func(A::a);return 0; } -
靜態(tài)成員能夠用于某些場景而普通成員不能:
- 靜態(tài)數(shù)據(jù)成員可以是不完全類型,特別的,靜態(tài)數(shù)據(jù)成員可以是它所屬的類類型,而非靜態(tài)數(shù)據(jù)成員則收到限制,只能聲明成它所屬類的指針或引用
- 靜態(tài)成員和普通成員的另外一個區(qū)別是我們可以使用靜態(tài)成員為默認實參
總結(jié)
以上是生活随笔為你收集整理的C++Primer学习笔记:第7章 类的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: a4分辨率300dpi像素多少
- 下一篇: 现在英雄联盟中,哪个英雄单挑最厉害