C++Primer學習筆記:第7章 類

  • 類的基本思想是數據抽象data abstraction和封裝encapsulation。數據抽象是一種依賴于接口interface和實現implementation分離的編程技術

  • 在類中,由類的設計者負責考慮類的實現過程,使用該類的程序員只需要抽象地思考類型做了什么,而無須了解類型的工作細節

  • 執行加法和IO的函數應該定義成普通函數,執行復合賦值運算的函數應該是成員函數

  • 成員函數的聲明必須在類的內部,它的定義則既可以在類的內部也可以在類的外部

  • 成員函數通過一個名為this的額外的隱式參數來訪問調用它的對象。當我們調用一個成員函數時,用請求該函數的對象地址初始化該常量指針this。因此我們不能自定義名為this的參數或者變量

  • 緊隨參數列表之后的const關鍵字修改隱式this指針為指向類類型常量版本的常量指針(默認不是指向常量的)。這意味著如果一個成員函數是普通函數(函數參數列表后沒有const),則一個常量類對象無法調用該函數(無法給this指針初始化)

  • 如果成員函數內部對對象的其他成員沒有修改,則盡可能將成員函數聲明為常量成員函數,緊跟在函數參數列表后面的const表示this是一個指向常量的常量指針。將函數聲明為常量成員函數有利于提高函數的靈活性,令常量對象仍然可以調用該函數

  • 常量對象,以及常量對象的引用或者指針都只能調用常量成員函數

  • 類本身就是一個作用域,編譯器分兩部處理:首先編譯成員的聲明,然后才輪到成員函數體。成員函數體可以隨意使用類中的成員而無須在意這些成員出現的次序

  • 類外部定義的成員的名字必須包含它所屬的類名,而且返回類型、參數列表和函數名都得與類內部的聲明保持一致

    double Sales_data::avg_price() const 
    {}
    
  • 當我們定義函數類似于某個內置運算符時,應該令函數的行為盡量模仿這個運算符

  • 如果函數在概念上屬于類但是不定義在類中,則它一般應該與類聲明在同一個頭文件中

  • 默認情況下,拷貝類的對象其實是拷貝對象的數據成員

  • 構造函數沒有返回類型,構造函數不能被聲明為const,當我們創建類的一個const對象時,直到構造函數完成初始化過程對象才真正取得常量屬性

  • 如果我們的類沒有顯式地定義構造函數,則編譯器會為我們隱式定義一個默認構造函數。默認構造函數沒有任何實際參數。這個編譯器創建的構造函數又稱作合成的默認構造函數

    • 如果存在類內的初始值,用它來初始化成員
    • 否則,默認初始化該成員
  • 不能依賴默認構造函數:

    • 一旦我們定義了構造函數(不論是否有參),除非我們自己定義一個默認構造函數(無參),否則類將沒有默認構造函數。只有當類沒有聲明任何構造函數的時候編譯器才會自動生成默認構造函數
    • 如果定義在塊中的內置類型或復合類型(數組和指針)的對象被默認初始化,則其值是未定義的。除非他們都有類內初始值
    • 如果類中包含一個其他類,而且這個類沒有默認構造函數,那么編譯器將無法初始化該成員
  • 如果函數定義在類內部,則函數默認是內聯的,如果在外部,則默認是不內聯的

  • 在C++11新標準中,我們可以通過在參數列表后面寫上=default來要求編譯器生成構造函數(在類外仍然可以)

  • 在構造函數中可以使用構造函數初始值列表對部分成員進行初始化,在初始值列表初始化后用類內初始化對成員進行初始化,剩下的執行默認初始化,然后再執行構造函數函數體中的內容

  • 如果我們不定義拷貝、賦值和析構操作,則編譯器會會替我們合成。一般來說,編譯器生成的版本將對對象中的每個成員執行拷貝、賦值和銷毀操作

    • 拷貝:初始化變量、以值的方式傳遞或返回一個對象
    • 賦值:使用賦值運算符
    • 銷毀:當對象不再存在時銷毀
  • 每個訪問說明符指定了接下來成員的訪問級別,其有效范圍直到出現下一個訪問說明符或者到達類的結尾為止

    • 定義在public說明符之后的成員在整個程序內可以被訪問,定義類的接口
    • 定義在private說明符之后的成員可以被類的成員函數訪問,但是不能被使用該類的代碼訪問
  • classstruct的唯一區別:默認訪問權限不同,struct的默認訪問權限是publicclass的默認訪問權限是private

  • 類可以允許其他類或者函數訪問它的非公有成員,方法是令其他類或者函數成為它的友元。如果類想把一個函數作為它的友元,只需要增加一條以friend關鍵字開始的函數聲明語句即可。友元聲明只能出現在類定義的內部,但是具體位置不限。一般來說,最好在類定義開始或結束前的位置集中聲明友元

  • 友元的聲明僅僅指定了訪問的權限,而非一個通常意義上的函數聲明,我們必須在友元聲明之外再專門對函數進行一次聲明(一些編譯器允許不再次聲明,不過為了能夠讓所有的編譯器成功運行,最好還是加上)。為了使友元對類的用戶可見,我們通常把友元的聲明與類本身放置在同一個頭文件中。

  • 除了定義數據和函數成員外,類還可以定義某種類型在類中的別名,由類定義的類型名字和其他成員一樣存在訪問限制

    class Screen 
    {
    public:typedef std::string::size_type pos;//using pos = std::string::size;
    }
    

    用來定義類型的成員必須先定義后使用,這一點與普通成員有所區別。因此類型成員通常出現在類開始的地方

  • 如果我們希望無論如何都能夠修改某個類的數據成員,即使該對象是const,我們可以在變量的聲明中加入mutable關鍵字,稱作可變數據成員。可以推測,一個可變數據成員無論如何都不是const的。

  • 當我們提供類內初始值時,必須以符號=或者花括號表示,不能用圓括號

  • 如果想要在成員函數中返回對象本身,則返回類型應該為引用,返回值為*this

  • 一個const成員函數如果以引用的形式返回*this,則應該返回一個常量引用

  • 我們可以通過成員函數是否是const對函數進行重載,其原因如同函數的指針參數是否指向const可以進行重載一樣。如果一個函數既有const版本,又有非const版本,則對于常量對象僅僅可以調用const版本,對于非常量對象非const版本顯然是一個更好的匹配

  • 在類內使用一些小函數不會增加運行時的額外開銷,相反還會給開發帶來很多好處

    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語言中要求類類型定義對象時加上classstruct,但是C++中不必要

  • 我們也可以僅僅聲明類而不定義它,這種聲明有時被稱作前向聲明,在定義之前是一個不完全類型:可以定義指向這種類型的指針或者引用,也可以聲明(不能定義)以不完全類型作為參數或者返回類型的函數。因此,一個類的名字出現過以后,就被認為是聲明過了,允許包含指向它自身類型的引用或者指針

  • 如果一個類指定了友元類,則友元類的成員函數可以訪問此類包括非公有成員在內的所有成員。友元關系不具有傳遞性

  • 需要注意的是,友元的聲明僅僅影響的是該函數的訪問權限,因此并不能看作聲明,為了完成對該友元的調用,必須在其作用域內對友元進行聲明。友元的作用域和類的聲明是同一級別的

    struct X
    {friend void f()	{}	//友元函數可以定義在類的內部,但是此時在類X的作用域中是不可見的X() { f(); }		//錯誤,f()不可見void g();void h();
    }
    void X::g() { f(); }	//錯誤,f()不可見
    void f();				//對f進行聲明,此時對X可見
    void X::h()	{ f(); }	//正確
    

    需要注意的是,有的編譯器對上面沒有這種限制

  • 定義在外部的成員函數的返回類型不在類的作用域內

  • 類的定義分兩步處理:

    • 順序編譯成員的聲明
    • 指導類全部可見后編譯函數體
  • 一般來說,內層作用域可以重新定義外層作用域名字,即使該名字已經在內層作用域中使用過,而在類中,如果成員使用了外層作用域中的某個名字,而該名字代表某一種類型,則類不能重新定義該名字(一些編譯器忽略這種錯誤)

  • 類型名的定義通常出現在類的開始處,這樣保證所有使用該類型的成員都出現在類型之后

  • 盡管全局變量有可能被覆蓋掉,但是我們可以使用::name來獲取對應的全局變量

  • 當成員定義在類的外部時,名字查找的最后一步不僅要考慮類定義之前的全局作用域中的聲明,還要考慮在成員函數定義之前的全局作用域中的聲明

  • 如果沒有在構造函數的初始值列表中顯式地初始化成員,而且該成員沒有類內初始值,則該成員將在構造函數體之前執行默認初始化。有時候有的變量無法進行默認初始化(如一些沒有默認構造函數的類對象,以及引用或者常量)

  • 在構造函數初始值中每個成員只能出現一次。構造函數初始值列表只用于說明初始化成員的值,而不限定初始化具體執行順序,成員的初始化順序與他們在類定義中的出現順序一致,構造函數初始值列表中初始值的前后位置不會影響實際的初始化順序。如果使用某些成員初始化其他成員,則初始化的順序就比較重要,不過這種做法不是一個好的編程習慣

  • 如果一個構造函數為所有參數都提供了默認實參,則它實際上也定義了默認構造函數

  • 我們可以使用委托構造函數,委托其他構造函數進行構造

    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){}
    };
    

    委托構造函數會先執行被委托構造函數的初始化列表,然后再執行函數體

  • 當對象被默認初始化或值初始化時自動執行默認構造函數

  • 默認初始化在以下情況下發生:

    • 當我們在塊作用域內不適用任何初始值定義的一個非靜態變量或者數組時
    • 當一個類本身含有類類型的成員且使用合成的默認構造函數時
    • 當類類型的成員沒有在構造函數初始值列表中顯式地初始化時
  • 值初始化在以下情況下發生:

    • 在數組初始化的過程中如果我們提供的初始值的數量小于數組的大小時
    • 當我們不使用初始值定義一個局部靜態變量時
    • 當我們通過書寫形如t()的表達式顯式地請求值初始化時
  • 在實際中,如果提供了其他構造函數,最好也提供一個默認的構造函數

  • 如果想定義一個使用默認構造函數進行初始化的對象,正確的方法是去掉對象名后的空的括號對

    sales_data obj();    //聲明了一個函數
    sales_data obj1;     //聲明了一個對象,使用默認構造函數進行初始化
    
  • 能通過一個實參調用的構造函數定義了一條從構造函數參數類型向類類型隱式轉換的規則,但是這種轉換只允許一步

  • 我們可以通過將構造函數聲明為explicit阻止隱式轉換,只對一個實參的構造函數有效。只能在類內聲明構造函數時使用explicit關鍵字,在類外定義時不應重復。explicit構造函數只能用于直接初始化

  • 我們可以使用explicit構造函數進行強制類型轉換

    item.combine(static_cast<sales_data>(cin));
    
    • const char * -> string的構造函數不是explicit
    • 接收一個容量參數的vector構造函數是explicit
    • 需要注意的是**explicit阻止的隱式轉換是構造函數參數類型轉換為類類型的轉換**,而不是構造函數參數之間的轉換
    • 允許臨時量存在的地方才允許隱式類型轉換
    • 聚合類使得用戶可以直接訪問其成員,因此具有特殊的初始化語法形式,當一個類滿足如下條件時,我們說它是聚合的
      • 所有成員都是public
      • 沒有定義任何構造函數
      • 沒有類內初始值
      • 沒有基類,也沒有virtual函數
        例如:
      struct Data
      {int i; string s;
      };
      Data v1 = {0, "1"};
      
      我們可以提供一個花括號括起來的成員初始值列表用來初始化,當然順序要和聲明順序一致。
  • 數據成員那都是字面值類型的聚合類是字面值常量類。或者滿足以下要求:

    • 數據成員必須是字面值類型
    • 類必須至少含有一個constexpr構造函數
    • 如果某個成員有類內初始值,內置類型必須是一條常量表達式,類類型必須使用自己的constexpr構造函數
    • 類必須使用析構函數的默認定義
  • 我們可以通過在成員的聲明之前加上關鍵字static使得其與類關聯在一起。類的靜態成員存在于任何對象之外,對象中不包含任何與靜態數據成員有關的數據。因為靜態函數成員中沒有this指針),靜態成員啊還是那話不能聲明成const,也不能調用非靜態成員

  • 可以使用作用域運算符直接訪問靜態成員。雖然靜態成員不屬于某個對象,但是我們還是可以使用類的對象、引用或者指針訪問靜態成員。成員函數不通過作用域運算符就能直接訪問靜態成員

  • 靜態成員的static關鍵字只能在類內部出現(同explicit),不能在類外重復使用

  • 靜態成員不能由類的構造函數初始化,靜態數據成員定義在任何函數之外,因此一旦被定義就存在于程序的整個生命周期中。一般來說,我們不能在類的內部初始化靜態成員,相反的,必須在類的外部定義和初始化每個靜態成員

  • 通常情況下,類的靜態成員不應該在類的內部初始化。然而我們可以為靜態成員提供const整數類型的類內初始值,不過要求靜態成員必須是字面值常量類型的constexpr。這樣的靜態常量可以用在所有適合用于常量表達式的地方。即使該成員在類內已經初始化,還是應該在類外定義一下該成員,而且不能再指定一個初始值了。這樣做的好處是能夠在類的外部使用該靜態成員。

    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;
    }
  • 靜態成員能夠用于某些場景而普通成員不能:

    • 靜態數據成員可以是不完全類型,特別的,靜態數據成員可以是它所屬的類類型,而非靜態數據成員則收到限制,只能聲明成它所屬類的指針或引用
    • 靜態成員和普通成員的另外一個區別是我們可以使用靜態成員為默認實參

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383563.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383563.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383563.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

每日一題:leetcode191.位1的個數

題目描述 題目分析 很自然地想到了二進制枚舉&#xff0c;直接循環檢查每一個二進制位。 class Solution { public:int hammingWeight(uint32_t n) {int ret 0;uint32_t t 1;for (int i 0; i < 32; i, t << 1) {if (n & t) {ret;}}return ret;} };AC之后看了…

每日一題:leetcode341.扁平化嵌套列表迭代器

題目描述 題目分析 這個題目自己大概花了一個小時&#xff0c;雖然是一遍AC&#xff0c;但是速度有點慢&#xff0c;太長時間不寫代碼導致自己對代碼不太敏感&#xff0c;寫起來慢騰騰的。 看到這個的想法就是&#xff0c;要用棧來保存列表的迭代器&#xff0c;這樣將孩子列表…

每日一題:leetcode82. 刪除排序鏈表中的重復元素 II

題目描述 題目分析 這才是正常的中等題難度嘛&#xff0c;昨天的中等題題解我半天看不懂。。。 首先&#xff0c;需要增加一個啞節點&#xff08;操作鏈表的常規操作&#xff09;&#xff0c;因為有可能刪除首節點&#xff0c;我們不想要為首節點添加單獨的邏輯。其次&#xf…

每日一題:leetcode456.132模式

題目描述 題目分析 我覺得這道題應該是我做過最難的中等題之一了&#xff0c;這是昨天的每日一題&#xff0c;但是昨天用nlogn的做法做出來以后在看題解&#xff0c;發現有些看不懂&#xff08;覺得題解有點故弄玄虛&#xff09;。然后今天中午又花了一點時間才搞懂&#xff0…

leetcode283.移動零

題目描述 題目分析 在寫簡單題放松&#xff0c;看到這道題第一個想法是用STL庫函數&#xff0c;雖然知道大概要用雙指針之類的&#xff0c;但是庫函數爽哇。 class Solution { public:void moveZeroes(vector<int>& nums) {stable_sort(nums.begin(), nums.end(), …

每日一題:leetcode61.旋轉鏈表

題目描述 題目分析 很容易發現&#xff0c;如果k是n的整數倍&#xff0c;相當于沒有移動。這樣直接對k%n使得k在一個可以接受的范圍。 因為是順序移動&#xff0c;各元素之間的相對位置保持不變&#xff0c;所以就想著將鏈表先變成一個環。然后再移動頭指針&#xff0c;最后再…

每日一題:leetcode173.二叉搜索樹迭代器

題目描述 題目分析 更加地覺得編程重要的不在于如何寫代碼&#xff0c;用什么具體的技巧&#xff0c;編碼本身只是一種將思維呈現的方式&#xff0c;但是如果思維是不清晰的&#xff0c;那么就算懂得再多的編碼的奇技淫巧也是沒有什么幫助的。相反&#xff0c;如果有一個清晰的…

Ubuntu20.04 Clion/Pycharm/IDEA 輸入中文+光標跟隨解決方案

ibus輸入法&#xff08;棄用&#xff09; 之前一直用的搜狗輸入法&#xff0c;但是搜狗輸入法無法在Jetbrains全家桶下使用&#xff0c;但是又需要輸入中文&#xff0c;沒有辦法我只能下載了谷歌輸入法&#xff0c;十分難用&#xff0c;但是也沒有其他辦法&#xff0c;經常到網…

leetcode11.盛最多水的容器

題目描述 題目分析 看到題目后第一個想法當然是O(n2)O(n^2)O(n2)的&#xff0c;但是數據范圍是3e4&#xff0c;應該會超時&#xff0c;而且這種數據范圍也不是讓暴力求解的 。 相當于求解∑i<jmax((j?i)?min(a[i],a[j]))\sum_{i<j}{max((j-i)*min(a[i],a[j]))}∑i<…

每日一題:leetcode190.顛倒二進制位

題目描述 題目分析 題目本身很簡單&#xff0c;沒覺得有什么技巧可以再進行優化了&#xff0c;覺得位運算是無法打亂相對順序的&#xff0c;而這里需要進行鏡像顛倒的操作。因此就踏實地寫了一個循環。 在使用位運算得到每一位的時候&#xff0c;我吸取了經驗&#xff0c;用一…

結構屈曲分析

結構屈曲分析主要用于判定結構受載后是否有失穩風險&#xff0c;作為工程應用&#xff0c;一般分為線性屈曲分析和非線性屈曲分析。 線性屈曲分析需要具備較多的前提條件&#xff0c;如載荷無偏心、材料無缺陷等&#xff0c;在實際工程應用中結構制作過程和加載方式很難達到線性…

每日一題:leetcode74.搜索二維矩陣

題目描述 題目分析 感覺這是一個放錯標簽的簡單題。題目非常簡單&#xff0c;思路應該很明確是二分&#xff0c;我很快寫了一個&#xff08;雖然不小心把!打成調試了一會&#xff09;。 class Solution { public:bool searchMatrix(vector<vector<int>>& mat…

每日一題:leetcode90.子集貳

題目描述 題目分析 感覺這道題讓自己對枚舉排列有了一個更好的認識&#xff0c;感覺自己的這種思路不錯。 假設沒有重復元素&#xff08;退化成78.子集&#xff09;&#xff0c;我們應該怎么做&#xff1f;初始的時候冪集中只有一個空集&#xff0c;然后對每個元素&#xff0…

每日一題:leetcode1006.笨階乘

題目描述 題目分析 因為順序一定且沒有括號&#xff0c;所以邏輯很簡單。我們要順序處理的矛盾在于&#xff0c;減號后面會再出現乘法和除法&#xff0c;我們不妨將對乘法和除法用一個臨時值進行計算&#xff0c;計算結束后再合并到值里面&#xff0c;一般來講乘法和除法的處理…

每日一題:leetcode80.刪除有序數組中的重復元素貳

題目描述 題目分析 又是一道貼錯標簽的簡單題&#xff0c;很明顯的雙指針&#xff0c;我的做法是用兩個變量保存是否需要記錄&#xff0c;官方題解的做法是直接判斷&#xff0c;人家的高明一些 class Solution { public:int removeDuplicates(vector<int>& nums) {…

每日一題:leetcode81.搜索旋轉排序數組Ⅱ

題目描述 題目分析 不含重復元素的題解&#xff08;leetcode33&#xff09; 這道題也是我們算法課的一道編程題&#xff0c;寫完以后發現當時的思路和現在沒有什么變化&#xff0c;果然是自己啊。我的想法是先判斷區間整體是升序的還是旋轉的&#xff0c;如果是升序的就按照正…

C++ JSON庫:JSON for Morden C++

緒論 最近因為項目的需要&#xff0c;需要對JSON進行一定的數據處理&#xff0c;因為想要用C進行編碼&#xff0c;便對C的JSON庫進行的調研&#xff0c;發現這個庫比較好用&#xff1a;JSON for Morder C。 使用指南 想要使用這個json庫&#xff0c;只需要在源文件中包含jso…

Linux信號實現精確到微秒的sleep函數:通過sigsuspend函數解決時序競態問題

原理就是先使用定時器定時&#xff0c;然后再使用pause函數或者sigsuspend函數主動阻塞掛起&#xff0c;最終恢復現場。 如果使用pause函數的話&#xff0c;優點是使用簡單&#xff0c;缺點是有可能產生時序競態&#xff0c;導致進程一直阻塞下去&#xff1a;在定時和掛起之間…

Linux創建多個子進程并通過捕獲SIGCHLD信號進行非阻塞回收

我們通過fork函數創建多個子進程&#xff0c;并通過exec函數族在子進程中進行其他的工作&#xff0c;但是為了避免僵尸進程&#xff0c;我們要對子進程進行回收。常用的回收方式是wait或者waitpid進行阻塞回收&#xff0c;因為如果非阻塞回收很難把握時機&#xff0c;而阻塞回收…

Linux創建守護進程

守護進程&#xff08;Daemon&#xff09;是運行在后臺的一種特殊進程。它獨立于控制終端并且周期性地執行某種任務或等待處理某些發生的事件。它不需要用戶輸入就能運行而且提供某種服務&#xff0c;不是對整個系統就是對某個用戶程序提供服務。Linux系統的大多數服務器就是通過…