[c++] Class

菜雞啄米

菜鳥教程閱讀筆記,有沒有什麼重難點 Since C++ 類 & 對象html

基本概念

到目前爲止,咱們已經對 C++ 的類和對象有了基本的瞭解。下面的列表中還列出了其餘一些 C++ 類和對象相關的概念,能夠點擊相應的連接進行學習。ios

概念 描述
類成員函數 類的成員函數是指那些把定義和原型寫在類定義內部的函數,就像類定義中的其餘變量同樣。
類訪問修飾符 類成員能夠被定義爲 public、private 或 protected。默認狀況下是定義爲 private。
構造函數 & 析構函數 類的構造函數是一種特殊的函數,在建立一個新的對象時調用。類的析構函數也是一種特殊的函數,在刪除所建立的對象時調用。
C++ 拷貝構造函數 拷貝構造函數,是一種特殊的構造函數,它在建立對象時,是使用同一類中以前建立的對象來初始化新建立的對象。
C++ 友元函數 友元函數能夠訪問類的 private 和 protected 成員。
C++ 內聯函數 經過內聯函數,編譯器試圖在調用函數的地方擴展函數體中的代碼。
C++ 中的 this 指針 每一個對象都有一個特殊的指針 this,它指向對象自己。
C++ 中指向類的指針 指向類的指針方式如同指向結構的指針。實際上,類能夠當作是一個帶有函數的結構。
C++ 類的靜態成員 類的數據成員和函數成員均可以被聲明爲靜態的。

 

對象的默認初始化

利用構造函數初始化。編程

 

繼承類型

咱們幾乎不使用 protected 或 private 繼承,一般使用 public 繼承。當使用不一樣類型的繼承時,遵循如下幾個規則:bash

    • 公有繼承(public):當一個類派生自公有基類時,基類的公有成員也是派生類的公有成員,基類的保護成員也是派生類的保護成員,基類的私有成員不能直接被派生類訪問,可是能夠經過調用基類的公有保護成員來訪問。
    • 保護繼承(protected): 當一個類派生自保護基類時,基類的公有保護成員將成爲派生類的保護成員。
    • 私有繼承(private):當一個類派生自私有基類時,基類的公有保護成員將成爲派生類的私有成員。

 

多繼承帶來的問題

Goto: C++多繼承中的二義性問題 數據結構

1、兩個父類中都有同名的成員函數:多線程

    1. son.Parent_f::show(); 如此指明是哪一個父類的函數。
    2. 在類中定義同名成員,直接覆蓋掉父類中的相關成員。

 

2、「菱形」問題——路徑二義性函數

Ref: C++:鑽石繼承與虛繼承【比較詳細】post

父類對祖父類的繼承方式改成虛繼承,那麼子類訪問本身從祖父類那裏繼承過來的成員就不會有二義性問題了,也就是將子類對象裏的祖父類對象統一爲一個,繼承的只有一個祖父類對象。性能

#include "iostream"
 
using namespace std;
 
class Grandpa
{
public:
    int year_old;
};
 
class Parent_f:virtual public Grandpa {};
class Parent_m:virtual public Grandpa {};
 
class Son:public Parent_f,public Parent_m {};
 
int main()
{
    Son son;
    son.year_old = 10;
    cout << "Hello world!" << endl;
    return 0;
}

 

 

*** C++ 重載運算符和重載函數 - 歸爲其餘目錄裏。學習

 

C++ 多態

1、虛函數

編譯時綁定,因此子類的函數指針過早的連接了父類。(靜態多態,早綁定)

虛函數 是在基類中使用關鍵字 virtual 聲明的函數。在派生類中從新定義基類中定義的虛函數時,會告訴編譯器不要靜態連接到該函數

咱們想要的是在程序中任意點能夠根據所調用的對象類型來選擇調用的函數,這種操做被稱爲動態連接,或後期綁定。

 

如此有是如何訪問父類的函數?

// 存儲矩形的地址
shape = &rec;
// 調用矩形的求面積函數 area
shape->area();
 
shape->Shape::area(); // 存儲三角形的地址
shape = &tri;
// 調用三角形的求面積函數 area
shape->area();

 

2、純虛函數 

接口的實現方式。

class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      // pure virtual function
      virtual int area() = 0;
};

 

C++ 數據抽象

只向外界提供關鍵信息,並隱藏其後臺的實現細節;就是純虛函數實現接口的一種表現形式。

依賴於接口和實現分離的編程(設計)技術。

 

C++ 數據封裝

C++中, 虛函數 能夠爲private, 而且能夠被子類覆蓋(由於虛函數表的傳遞),但子類不能調用父類的private虛函數。虛函數的重載性和它聲明的權限無關。

一個成員函數被定義爲private屬性,標誌着其只能被當前類的其餘成員函數(或友元函數)所訪問。

virtual修飾符則強調父類的成員函數能夠在子類中被重寫,由於重寫之時並無與父類發生任何的調用關係,故而重寫是被容許的。

編譯器不檢查虛函數的各種屬性。被virtual修飾的成員函數,不論他們是private、protect或是public的,都會被統一的放置到虛函數表中。對父類進行派生時,子類會繼承到擁有相同偏移地址的虛函數表(相同偏移地址指,各虛函數相對於VPTR指針的偏移),則子類就會被容許對這些虛函數進行重載。且重載時能夠給重載函數定義新的屬性,例如public,其只標誌着該重載函數在該子類中的訪問屬性爲public,和父類的private屬性沒有任何關係!

純虛函數 能夠設計成私有的,不過這樣不容許在本類以外的非友元函數中直接調用它,子類中只有覆蓋這種純虛函數的義務,卻沒有調用它的權利。

 

這裏的例子用到了「友元」,也必須有。

From: 虛函數用做private會怎樣?

#include <iostream>
 
using std::cout;
using std::endl;
 
class Bclass
{
private:
    virtual void fun() { cout << "Bclass fun is here!" << endl ; }

    friend int main();
};
 
class Dclass : public Bclass
{
public:
    void fun() { cout << "Dclass fun is here!" << endl; }
};
 
 
int main()
{
    Bclass *pObject = new Dclass();
    pObject->fun();  // "Dclass fun is here!"
 
    return 0;
}

 

C++ 接口(抽象類)

class Box
{
   public:
      // 純虛函數
      virtual double getVolume() = 0;
private: double length; // 長度 double breadth; // 寬度 double height; // 高度 };

 

 

 

菜雞熱身 

大綱:"也是醉了,一個.h文件就有這麼多細節問題 "

    • A: 初始化列表,使用{} 也能夠。
    • 類中的引用和const變量,必須當即在初始化列表中提早初始化。
    • 常成員函數,const 放在函數後, 常成員函數即不能改變成員變量值的函數。例如 getxxx() const;
    • mutable類型,當須要在const的函數裏面修改一些跟類狀態無關的數據成員,那麼這個數據成員就應該被mutalbe來修飾。例如 class中的計數器,類中函數都用,包括const函數。【破壞規則的設計】
    • 友元類,其全部成員函數都是另外一個類的友元函數,均可以訪問另外一個類中的隱藏信息(包括私有成員和保護成員)。【破壞規則的設計】

 

初始化列表

緣由:

    1. 若是咱們不修改Base,不去調用默認構造函數,而是顯式的調用Base自帶參構造函數呢?答案就是初始化列表。
    2. const 成員引用類型的成員。由於 const 對象或引用類型只能初始化,不能對他們賦值。

 

區別:

首先把數據成員按類型分類並分狀況說明:

      • 1.內置數據類型,複合類型(指針,引用)- 在成員初始化列表和構造函數體內進行,在性能和結果上都是同樣的
      • 2.用戶定義類型(類類型)- 結果上相同,可是性能上存在很大的差異。由於類類型的數據成員對象在進入函數體前已經構造完成,也就是說在成員初始化列表處進行構造對象的工做,調用構造函數,在進入函數體以後,進行的是對已經構造好的類對象的賦值,又調用個拷貝賦值操做符才能完成(若是並未提供,則使用編譯器提供的默認按成員賦值行爲)

 

注意點:

初始化列表的成員初始化順序:

C++ 初始化類成員時,是按照聲明的順序初始化的,而不是按照出如今初始化列表中的順序。

《一個典型的容易犯錯的例子》

class CMyClass { CMyClass(int x, int y); int m_x;  // <----這裏的生命順序決定初始化列表的順序! int m_y; }; CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y) { }

 

常成員函數

From: C++常對象與對象成員

From: C++常對象,常變量,常成員函數詳解(含添加內容)【有常見錯誤例子,不錯】

常成員函數特色:

    • 不能更新任何數據成員,
    • 也不能調用該類中沒有用const修飾的成員函數,
    • 只能調用常成員函數和常數據成員。

 

其餘特色:

    • const是函數類型的一部分,在實現部分也要帶該關鍵字
    • const關鍵字的重載
    • 常成員函數不能更新任何數據成員
    • 常成員函數能夠被其餘成員函數調用。
    • 可是不能調用其餘很是成員函數。
    • 能夠調用其餘常成員函數。

 

常對象

她的特色:

    • 常對象是指對象的數據成員的值在對象被調用時不能被改變。
    • 常對象必須進行初始化,且不能被更新。
    • 不能經過常對象調用普通成員函數,可是能夠經過普通對象調用常成員函數。
    • 常對象只能調用常成員函數。

何時用?

既要使數據能在必定範圍內共享,又要保證它不被任意修改,這時可使用const。

 

常引用

若是既要利用引用提升程序的效率,又要保護傳遞給函數的數據不在函數中被改變,就應使用常引用。 

也就是說,一個實際的一小塊內存,經過原變量能夠改變;經過「影子」引用則不能夠。

 

 

 

野雞實戰

類的聲明

#ifndef SALES_DATA_H #define SALES_DATA_H #include <iostream>
using namespace std; class Sales_data { public: //construcitors
    Sales_data(const string &s): bookName{s} {}
Sales_data(
const string &s, unsigned n, double p): bookName{s}, units_sold{n}, revenue{p*n} {}
Sales_data(
void): Sales_data{"", 0, 0} {}
Sales_data(istream
&); string getBookName() const; Sales_data& combine(const Sales_data &); double avg_price() const;
//---------------------------------------------------------------------- friend istream
& read(istream &is, Sales_data &item); friend ostream& print(ostream &os, const Sales_data &item); private: string bookName; unsigned units_sold {0}; double revenue {0.0}; mutable unsigned no_of_times_called {0}; }; #endif // SALES_DATA_H

 

成員函數等的實現

#include "sales_data.h"

using namespace std; 
// Declare istream
& read(istream &is, Sales_data &item); ostream& print(ostream &os, const Sales_data &item); //member functions Sales_data::Sales_data(istream &is) { read(is, *this); } string Sales_data::getBookName() const { ++no_of_times_called; return bookName; } double Sales_data::avg_price() const { if (units_sold) { return revenue/units_sold; } else { return 0; } } Sales_data& Sales_data::combine(const Sales_data &rhs) { cout << "combine() in." << endl; units_sold += rhs.units_sold; revenue += rhs.revenue; cout << "combine() out." << endl; return *this; } /* * Non-member class related function. * These need to be decleared as a 'friend' to work. */    istream& read(istream &is, Sales_data &item) { cout << "read() in." << endl; double price {0}; is >> item.bookName >> item.units_sold >> price; item.revenue = price * item.units_sold; cout << "read() out." << endl;

// 技巧:當輸入不符合規範,返回false的is.
return is; } ostream& print(ostream &os, const Sales_data &item) { cout << "print() in." << endl; os << item.bookName << " " << item.units_sold << " " << item.revenue << " " << item.avg_price(); cout << "print() out." << endl; return os; }

 

main.cpp

#include <iostream> #include "sales_data.h"

using namespace std; int main(void) { cout << "hello world." << endl; Sales_data curBook{"Harry Potter"}; if (read(cin, curBook)) { Sales_data newBook{""}; while (read(cin, newBook)) { cout << "While()..." << endl; if (curBook.getBookName() == newBook.getBookName()) { curBook.combine(newBook); } else { print(cout, curBook) << endl; curBook = newBook; } } print(cout, curBook) << endl; } else { cerr << "No data?!" << endl; } return 0; }

 

 

this 指針

Ref: http://c.biancheng.net/view/170.html

C++ 是在C語言的基礎上發展而來的,第一個 C++ 的編譯器其實是將 C++ 程序翻譯成C語言程序,而後再用C語言編譯器進行編譯。

在non-const成員函數中

 

在const成員函數中,自動被const

 

 

構造函數初始化列表

class A{ public: int a; A(){ cout<<"construct"<<endl; } A(A& b){ cout<<"copy construct"<<endl; } A& operator = (const A& x){ cout<<"operator"<<endl; return *this; } }; class B{ public: A a;

  // 方案一
// B(A &m){ // a = m; // }
  
// 方案二 --> 原理是:a(m)在構造階段,而非計算階段執行
B(A &m):a(m) { } };

兩方案的區別是什麼? 前者構造階段解決戰鬥;後者還須要一次計算。

Solution 01:
construct
copy construct
Solution 02: construct construct operator

 

 

舊事重提

(1) 初始化列表還能解決const與&類型的初始化問題:【以前已有提過】

 

(2) 如下是錯誤的範例,提醒注意 初始化列表中的 順序性:

#include<iostream>

class X { public:   X(int val) : j{val}, i{j} { } private:   int i;  // 應該先初始化這個   int j;  // 再初始化這個 };

 

使用{}初始化的好處

構造類型,函數調用,函數聲明間的混淆。 

看到括號,究竟是「函數「,仍是」參數初始化「?就是解決這個問題。 

 

 

 

C++中struct和class的區別

最本質的一個區別就是默認的訪問控制,體如今兩個方面:

1)默認的繼承訪問權限。struct是public的,class是private的。

2)struct做爲數據結構的實現體,它默認的數據訪問控制是public的,而class做爲對象的實現體,它默認的成員變量訪問控制是private的。

固然還有其餘區別,見連接。

 

初始化的類型轉換的最正確的方式 narrowing and casting

int i ( static_cast<int> (aDouble) )

避免 -Wall -Werror always warn.

-Werror=narrowing 

 

 

火雞破壞

破壞原有規則的」類型「

友元 (實如今外部,可是本身人)

  • 友元能夠是一個函數,該函數被稱爲友元函數;
  • 友元也能夠是一個類,該類被稱爲友元類,在這種狀況下,整個類及其全部成員都是友元。
  • 友元關係在類之間不能傳遞,即類 A 是類 B 的友元,類 B 是類 C 的友元,並不能導出類 A 是類 C 的友元。「咱倆是朋友,因此你的朋友就是個人朋友」這句話在 C++ 的友元關係上 不成立。

雖然,聲明在了定義裏,但不要忘了 這不等於其餘人引用該函數時的聲明。

 

友元函數

Ref: 友元(友元函數、友元類和友元成員函數) C++

Ref: C++ 友元函數

不是類的成員函數,在此,只是類中的一個聲明。因此天然也就沒有"this pointer"。

  • 由於友元函數沒有this指針,則參數要有三種狀況: 
  • 要訪問非static成員時,須要對象作參數;
  • 要訪問static成員或全局變量時,則不須要對象作參數;
  • 若是作參數的對象是全局對象,則不須要對象作參數;
  • 能夠直接調用友元函數,不須要經過對象或指針。
class Box
{
   double width;
public:
   double length;
   friend void printWidth( Box box );
   void setWidth( double wid );
};

 

 

友元類

class CCar
{
private:
    int price;
    friend class CDriver;  //聲明 CDriver 爲友元類
};
class CDriver { public: CCar myCar; void ModifyCar() //改裝汽車 { myCar.price += 1000; //因CDriver是CCar的友元類,故此處能夠訪問其私有成員 } };
int main() { return 0; }

 

 

mutable類型 (實如今內部,但不是本身人)

Ref: C++中mutable關鍵字存在的必要性是什麼?

(1) 定義

(2) 清空隊列/bashtable等,若是在多線程時可能會涉及到」mutex「,這個變量就have to be "mutable"!

(3) 一碼歸一碼。在類的內部 不必定就是」本身人「。

const意思是「這個函數不修改對象內部狀態」。
爲了保證這一點,編譯器也會主動替你檢查,確保你沒有修改對象成員變量——不然內部狀態就變了。

mutable意思是「這個成員變量不算對象內部狀態」。

好比,你搞了個變量,用來統計某個對象的訪問次數(好比供debug用)。它變成什麼顯然並不影響對象功用,但編譯器並不知道:它仍然會阻止一個聲明爲const的函數修改這個變量。

把這個計數變量聲明爲mutable,編譯器就明白了:這個變量不算對象內部狀態,修改它並不影響const語義,因此就不須要禁止const函數修改它了

 

 

 

其餘內容  


 

Static Data Member in Class:

Count how many objs are created by this constructor.

構造一次,counter++。

用法:單例

 

在類定義中僅聲明,在外才賦值。

 

 

隱式類型轉換:

有必要抑制此種拉風的寫法:

vector: the Constructor That Takes a Size is Explicit

類成員函數 - 幾個關鍵字

1. inline

定義在類內部的函數是隱式的內聯函數

能夠在類的內部把inline做爲聲明的一部分顯式地聲明成員函數,也能在類外部用inline關鍵字修飾函數的定義;同時聲明是合法的,可是最好只在類外部定義的地方說明inline,這樣可使類更容易理解。

2. const

用const修飾類的成員函數,必須在類內部聲明時和類外部定義時,同時指定const

3. explicit

只能在類內聲明構造函數時指定,類外定義處不該重複

4. static

類內聲明處指定,類外定義處不能重複指定

5. virtual

virtual只能出如今類內部的聲明語句以前而不能用於類外部的函數定義
 

構造函數中的C++中explicit關鍵字的使用

在C++中,咱們有時能夠將構造函數用做自動類型轉換函數。但這種自動特性並不是老是合乎要求的,有時會致使意外的類型轉換,所以,C++新增了關鍵字explicit,用於關閉這種自動特性。

即被explicit關鍵字修飾的類構造函數,不能進行自動地隱式類型轉換,只能顯式地進行類型轉換。

注意:只有一個參數的構造函數,或者構造函數有n個參數,但有n-1個參數提供了默認值,這樣的狀況才能進行類型轉換。

具體例子可見原連接:)
 

 

Class Scope:

暫時沒發現太難理解的東西。

相關文章
相關標籤/搜索