天天看點

C++ 面向對象的筆記總結//類的定義//成員函數的定義//帶預設值的成員函數//類的内聯成員函數://普通的内聯函數://成員函數重載//用指針或引用通路成員函數(或變量)//常成員函數://構造函數與析構函數//不同作用域範圍對象的構造與析構順序//堆對象//拷貝構造函數//無名對象的使用//構造函數用于類型轉換//作用域//靜态成員//友元//操作符重載//繼承//虛基類://虛函數: 用于多态//虛析構函數://幾個不同的"虛"//模闆//異常處理//幾個"常"//幾個不同指針的差別

關于 C++ 面向對象的筆記總結

//類的定義

class <類名>

{

private:

    ...;

protected:

    ...;

public:

    ...;

};

注意:成員變量不能在聲明時初始化(但靜态常成員函數可以);不要與JAVA的混淆

//成員函數的定義

//先搞清楚聲明與定義的差別,聲明與初始化的差別

<傳回類型> <類名>::<成員函數名>([參數表])

{

    ...;

}

//帶預設值的成員函數

隻有在聲明時才指定預設值

預設參數從右到左逐漸定義

void f(int a1, int a2 =0, int a3 = 9);

//類的内聯成員函數:

隐式implicit: 在類中同時聲明與定義

顯式explicit: inline

    類中聲明: inline <傳回類型> <函數名>([參數表]);

    類外定義: inline <傳回類型> <類名>::<函數名>([參數表])   //inline可省

              {

                  ...

              }

//普通的内聯函數:

聲明:inline <傳回類型> <函數名>([參數表]);

定義:inline <傳回類型> <類名>::<函數名>([參數表])   //inline可省

      {

          ...

      }

或者聲明定義寫一起

inline <傳回類型> 函數名([參數表]) {...}

//成員函數重載

相同的函數名,不同的參數清單(考慮:參數類型及參數個數)

//用指針或引用通路成員函數(或變量)

<類名> obj;

<類名> *p = &obj;  //可以不在聲明時初始化

<類名> &ref = obj;  //必須在聲明時初始化(指派)

p->xxx

ref.xxx

//常成員函數:

隻“讀”不可“寫”不能在函數中改變參數的值

類中聲明: <傳回類型> <函數名>([參數表]) const;

類外定義: <傳回類型> <類名>::<函數名>([參數表]) const  //const 不可省

          {

              ...

          }

//構造函數與析構函數

構造函數:對象生成時調用(如聲明, new 等)

當沒有自己定義構造函數時,編譯器提供一個預設的無參構造函數

這個函數相當于  <類名>(){}

若自己定義了一個構造函數(不管有參無參),則編譯器不再提供預設的無參構造函數

基本資料類型(整型等)的成員變量,不會自動初始化(一個随機的值) //不要與JAVA混淆

析構函數:對象生命周期結束時調用

顯式調用構造函數可以建立一個無名對象,多用于傳參

void fun(Point pt);

fun(Point(x,y));

可以重載多個不同參數的構造函數

    可以帶預設參數

    用帶預設參數的構造函數重載時,注意避免二義性錯誤如 Point() 與 Point(int x = 0, int y = 0),調用Point()時就不知道調用的是哪一個

注意:以下兩個語句的差別

<類名> obj;   //對象聲明

<類名> obj();  //函數聲明

//不同作用域範圍對象的構造與析構順序

局部對象:    按聲明的先後順序(可能出現多次生滅)

局部靜态對象:按聲明的先後順序(隻析構一次)

全局對象:    單檔案(按聲明先後);多檔案(未知)

全局靜态對象:單檔案(按聲明先後);多檔案(未知);隻析構一次

對象成員:    按聲明先後(與冒号文法中初始化表達式的順序無關)

對象生命周期結束時調用析構函數,順序與構造函數的順序相反

//堆對象

可以在用 new 配置設定空間時調用帶參數的構造函數進行初始化 Point* p = new Point(1,2);

delete 時析構

delete p;

delete[] psArray;

先析構對象,再釋放空間

//拷貝構造函數

注意“生成臨時對象”的情況,很多情況下生成臨時對象時會調用拷貝構造函數

1.       在函數參數表中用對象時(非引用)會産生一個臨時對象,并将調用拷貝構造函數

void f(OBJ obj); OBJ obj; f(obj);

2.       在函數傳回對象時,也會産生一個臨時對象,并将調用拷貝構造函數

OBJ f() {OBJ obj; return obj;}

3.       在函數傳回對象引用時,不産生臨時對象,不調用拷貝構造函數

OBJ& f(){OBJ obj; return obj;}

//注意一般不能傳回局部變量的引用,這裡隻作示範,說明傳回引用時不産生臨時對象

4.       在函數傳回臨時無名對象時,不再産生臨時對象,不調用拷貝構造函數

OBJ f(){return OBJ();}

差別:淺拷貝 (隻拷貝棧記憶體,不配置設定堆記憶體) 與深拷貝(一般由自己定義)

編譯器提供一個預設的拷貝構造函數,它以淺拷貝的方式實作,若有動态成員(指針),指派後在析構時會出現問題(同一塊記憶體被釋放了兩次)

class A

{

private:

    int a;

public:

    A(){a = 0; cout << "construct with no param\n";}

    A(int x){a = x; cout << "construct with " << x << "\n";}

    A(const A& obj)

    {

        this->a = obj.a;

        cout << "copy construct with " << a << endl;

}

}

A f() {return A();}

A a1;            // construct with no param

A a2 = a1;        // copy construct with 0

A a3;            // construct with no param

a3 = a1;          //沒有輸出

A a4;            // construct with no param

a4 = f();          // 在f 中輸出construct with no param;= 沒有輸出

A a5 = f();         // 在f 中輸出construct with no param;= 沒有輸出

聲明時

預設的指派操作為淺拷貝

自己定義一個拷貝構造函數

Point::Point(const Point& point);

//無名對象的使用

1.       作為實參傳遞給函數

void fun(OBJ obj);   fun(OBJ());

2.       用來構造新對象

Point p = Point(1,2); <=> Point p(1,2);隻調用一次構造函數,不調用拷貝構造函數

3.       初始化一個引用的聲明 Point &ref = Point(3,4);

無名對象将在生産的某個時刻調用構造函數

//構造函數用于類型轉換

class LeiXingZhuanHuan

{

public:

    LeiXingZhuanHuan(char * s) {

        cout << "construct" << endl;

    }

    LeiXingZhuanHuan(const LeiXingZhuanHuan& x) {

        cout << "copy construct" << endl;

    }

};

void f3(const LeiXingZhuanHuan& x){}

void f4(LeiXingZhuanHuan x){}

LeiXingZhuanHuan lx1 = LeiXingZhuanHuan("001");  //先調用構造,再調用拷貝構造

LeiXingZhuanHuan lx2 = "001";//直接調用構造,這裡隐式調用了構造函數,進行類型轉換;隐式調用構造函數用于類于轉換時1.隻會嘗試含有一個參數的構造函數;2.不能存在二義性

    LeiXingZhuanHuan lx3("001");      //調用構造

    LeiXingZhuanHuan& lxref1 = lx1;    //不調用構造函數

    f3(LeiXingZhuanHuan("001"));      //調用構造函數

f4("004");                       //隐式調用了構造函數,相當于實作了類型轉換

//作用域

了解函數原型作用域,塊作用域,類作用域,檔案作用域

//靜态成員

通過 <類名>::XXX 或 <對象>.XXX通路   (當然得符合通路權限public)

靜态資料成員的初始化:

   在類中聲明:   static int n;

   在類外初始化: int A::n = 100;             可以為公有或私有,但必須在全局範圍内初始化(即不能在某個函數中),可以在普通成員函數中通路靜态成員(讀或寫,但一般不在構造函數中初始化,因為它是各個類共享的)

class CHasStaticMember

{

    static const int c0 = 0;  //靜态常量成員可以在類中聲明時初始化,其他都不行

    static const int c1

    static int x;

};

//在類外初始化時不加static

const int CHasStaticMember:: c1 = 1;

int CHasStaticMember::x = 0;

靜态成員函數隻能通路靜态成員變量

//友元

1.友元函數:

      友元函數是可以直接通路類的私有成員的非成員函數。它是定義在類外的普通函數,它不屬于任何類,但需要在類的定義中加以聲明,這樣就可以通路該類的私有成員了

類中聲明:friend ... ,不管聲明在哪個通路權限下都可以(public,protected,private)

類外定義:同普通函數(其實就是一普通函數,隻是多個一項權利)

class B

{

public:

         friend void f(const B& obj);

         B():x(0){}

         B(int val) : x(val){}

protected:

private:

         int x;

};

void f(const B& obj)

{

         cout << obj.x << endl;

}

2.友元成員函數(讓類B的某個成員函數可以通路類A的私有成員)

class A;

//這個B必須定義在A的前面,因為A中有用的B::f()的原型,但前面要聲明一下A

class B

{

public:

         void f(const A& obj);

};

class A

{

private:

         int a;

public:

         friend void B::f(const A& obj);

};

void B::f(const A& obj)

{

         cout << obj.a << endl;

}

3.友元類

讓類B成為A的友元類

class B;

class A

{

private:

         int a;

         friend class B;

};

class B

{

public:

         void f(const A& obj);

};

void B::f(const A& obj)

{

         cout << obj.a << endl;

}

//操作符重載

1.重載為友元函數:

class A;

A operator + (const A& obj1, const A& obj2);

class A

{

public:

    A(){a = 0;}

    A(int x){a = x;}

    void Display()

    {

        cout << a << endl;

    }

    friend A operator + (const A& obj1, const A& obj2);

private:

    int a;

};

A operator + (const A& obj1, const A& obj2)

{

    return A(obj1.a + obj2.a);

}

2.重載為成員函數:

class A

{

public:

    A(){a = 0;}

    A(int x){a = x;}

    void Display()

    {

        cout << a << endl;

    }

    A operator + (const A& obj)

private:

    int a;

};

A A::operator + (const A& obj)

{

    return A(obj.a + a);

}

3.特殊運作符的重載

    //前置

    A& operator++ ()

    {

        ++a;

        return *this;

    }

    //後置也可以寫成 A operator++(int) ,這樣 obj1++ = obj2 不會報錯,但已經引用不到這個臨時變量了

    const A operator++(int)  // obj++ = obj2 會報錯,因為常量不能做左值

    {

        A t(*this);

        a++;

        return t;

    }

寫成友元時

類中:

friend A& operator++ (A& obj);

friend const A operator++ (A& obj, int);

類外:

    A& operator++ (A& obj)

    {

        obj.a++;

        return obj;

    }

    const A operator++(A& obj,int)

    {

        A t(obj);

        obj.a++;

        return t;

    }

輸出輸入

ostream& operator << (ostream& o, 類名對象名)

{

    o << 對象.成員;

    return o;

}

istream& operator >> (ostream& i, 類名& 對象名)

{

    i >> 對象.成員;

    return i;

}

其他操作符還有 +=,-=,*=,/=,[], , , =, ->,()等

= :聯系一下“深拷貝”與“淺拷貝”

[]: 傳回類型& operator [] (int i); 當左值時要傳回引用

->,[],(),= 隻能重載為成員運算符

類型轉換運算符

class Length

{

    int meter;

public:

    Length(int m) {meter = m;}

    operator float()

    {

        return (1.0 * meter / 1000);

    }

};

//"組合",一個類中的某個成員變量為某個類的對象,把該對象叫做 "子對象"

子對象的有參構造要使用冒号文法  <對象名>(參數),預設調用無參構造

有多個子對象時,按聲明的先後順序構造,與冒号語句的順序無關

//繼承

派生類的聲明 class <派生類名> : <繼承方式> <基類1>[, <繼承方式> <基類2>,...]

繼承方式:    public      protected     private

public         public      protected     隔離

protected      protected   protected     隔離

private        private      protected     隔離

隔離的對象,可以通過基類中繼承下來的可見的成員函數通路

繼承分為單繼承與多繼承

在繼承與派生過程中

1. 吸收基類成員(不包括成員函數和析構函數)

2. 改造

    a. 繼承方式

    b. 同名覆寫(注意:函數參數不同時為重載)

3. 添加新成員

//派生類構造與析構

派生類構造函數的調用順序

1.基類 2.子對象(對象成員) 3.派生類

析構函數的調用順序相反

如果子類的構造函數不顯式調用基類的構造函數的話,會自動調用基類的無參構造函數

如果子類的構造函數顯式調用了基類的一個有參構造函數,則不再調用基類的無參構造函數

顯式調用基類的一個有參構造函數隻能使用冒号文法,用類名

成員對象的構造也要使用冒号文法,但兩者有差別,用變量名

//冒号文法

組合與繼承的冒号文法的差別:

給合:使用對象名(變量名)

繼承:使用類名

另外,常量和引用的初始化要用冒号文法,且隻能使用冒号文法

也就是說使用冒号文法的時機有:

調用子類的有參構造函數,子對象,常量,引用的初始化

//一個單繼承的例子

class Point

{

public:

    Point(int x = 0, int y = 0)

    {

        this->x = x;

        this->y = y;

    }

    int GetX()

    {

        return x;

    }

    int GetY()

    {

        return y;

    }

    void SetX(int x = 0)

    {

        this->x = x;

    }

    void SetY(int y = 0)

    {

        this->y = y;

    }

    void Display()

    {

        cout << "(" << x << ", " << y << ")";

    }

protected:

private:

    int x, y;

};

class Circle : public Point

{

public:

    //派生類的構造函數:注意調用基類構造函數與構造子對象的不同之處

    Circle(int x = 0, int y = 0, int r = 0) : Point(x,y), r(r){}

    int GetR()

    {

        return r;

    }

    void SetR(int r)

    {

        this->r = r;

    }

    //覆寫基類的Display();

    void Display()

    {

        //調用基類的Display();

        Point::Display(); cout << " r = " << r;

    }

    void Test()

    {

        //通過類型的函數通路從基類中繼承下來的但被隔離的對象

        cout << GetX() << ", " << GetY() << ", " << r << endl;

    }

private:

    int r;

};

//一個多繼承的例子

class B1

{

public:

    int b, b1;

    B1(int b = 0, int b1 = 1) : b(b), b1(b1){}

    void f()

    {

        cout << "f() in B1" << endl;

    }

    void f1()

    {

        cout << "f1() in B1" << endl;

    }

};

class B2

{

public:

    int b, b2;

    B2(int b = 0, int b2 = 2) : b(b), b2(b2){}

    void f()

    {

        cout << "f() in B2" << endl;

    }

    void f2()

    {

        cout << "f2() in B2" << endl;

    }

};

class D : public B1, public B2

{

public:

    int d;

    Point p;

    //從子類中繼承下來的成員不能直接使用冒号文法,而是使用基類類名

    D(int b = 0, int b1 = 1, int b2 = 2, int d = 3) : B1(b, b1), B2(b * b, b2), d(d), p(1,2)

    {

    }

    void f()

    {

        cout << "f() in D" << endl;

        //調用子類中的方法

        B1::f();

        B2::f();

    }

    void Test()

    {

        cout << B1::b << endl;

        cout << B2::b << endl;

        cout << B1::b1 << endl;

        cout << B2::b2 << endl;

        cout << d << endl;

    }

};

D d(999,1,2,3);

d.Test();

d.B1::f();  //通路基類的成員函數

d.B1::f1();

d.B2::f();

d.B2::f2();

//派生類成員的辨別與通路

基類名::成員名

基類名::成員函數([參數清單])

同名覆寫時,将覆寫所有基類的同名成員

若沒有覆寫,多基類的同名成員也要通過::通路才能區分(否則會有二義性)

//虛基類:

     B0

B1        B2

     D1

使得繼承時隻儲存一份拷貝

"D1 通過 B1 繼承自 B0" 和 "D1 通過 B2 繼承自 B0" 的東西隻儲存一份拷貝

如果派生類中重新義同名函數(非重載),同樣可以同名覆寫

class B0{};

class B1 : virtual public B0 {};

class B1 : virtual public B0 {};

class D1 : public B1, public B2 {};

執行個體:

class B0

{

public:

    B0(int x0 = 0) : v0(x0) { cout << "construct B0 with " << x0 << endl; }

    ~B0(){cout << "~B0\n";}

    int v0;

};

class B1 : public B0

{

public:

    B1(int x0 = 0, int x1 = 1) : B0(x0),v1(x1) { cout << "construct B1 with " << x0 <<", " << x1 << endl; }

    ~B1(){cout << "~B1\n";}

    int v1;

};

class B2 : public B0

{

public:

    B2(int x0 = 0, int x2 = 2) : B0(x0),v2(x2) { cout << "construct B2 with " << x0 <<", " << x2 << endl; }

    ~B2(){cout << "~B2\n";}

    int v2;

};

class D : public B1, public B2

{

public:

    D(int x0 = 0, int x1 = 1, int x2 = 2) : B1(x0, x1), B2(x0, x2) { cout << "construct D with " << x0 <<", " << x1 <<", " << x2 << endl; }

    ~D(){cout << "~D\n";}

};

int main()

{

    D d;

}

輸出結果為:

construct B0 with 0

construct B1 with 0, 1

construct B0 with 0

construct B2 with 0, 2

construct D with 0, 1, 2

~D

~B2

~B0

~B1

~B0

class B0{};

class B1 : virtual public B0 {};

class B1 : virtual public B0 {};

class D1 : public B1, public B2 {};

輸出結果為:

construct B0 with 0

construct B1 with 0, 1

construct B2 with 0, 2

construct D with 0, 1, 2

~D

~B2

~B1

~B0

//虛函數: 用于多态

聲明: 類中: virtual 傳回類型函數名([參數表]);

定義: 類外: 傳回類型類名::函數名([參數表]){...}

注意: 隻有在聲明時才加 virtual

當基類用virtual聲明成員函數為虛函數時,

其派生類的同名函數(重載函數除外)自動成為虛函數(前面可加virtual也可不加)

若在派生類中不重新定義,則直接簡單繼承基類的虛函數

若在派生類中重新定義,  則通過基類的指針通路可以表現為多态

  基類指針隻能通路基類的函數或虛函數(其實也是在基類中聲明的函數,隻是可以表現多态性)

基類的指針,引用常用作函數形參實作多态

Void fun(B0* p);

Void fun(B0& p);

不用virtual 時,隻調用基類的函數

工作原理:每個對象多了一個指針空間,指向類中的虛函數表

//虛析構函數:

聲明:virtual ~類名();

定義:...

其派生類的析構函數自動變為虛析構函數

同樣用基類的指針處理時,可以從子類開始析構,而不是調用基類的析構

最好将基類的析構函數聲明為虛析構函數

//幾個不同的"虛"

虛基類:使多個來自同一虛基類的相同的東西隻拷貝一份,其他同一般的繼承,可以進行同名覆寫(覆寫之後若要調用父類的函數父類::函數名)

虛函數:既不覆寫,也不是重載,而是一個函數在不同類中表現出多态;一般通過父類的指針來表現

純虛函數:基類不需要此功能,隻為派生類服務

    聲明: virtual 傳回類型函數名(參數表) = 0;   

抽象類:包含有純虛函數的類或顯式的用abstract标記的類

class 類名

{

public:

    virtual 傳回類型函數名(參數表) = 0;

};

class 類名 abstract { ... };

若抽象類的派生類沒有給出所有純虛函數的實作,則該派生類仍為抽象類

一個虛基類和虛函數同時使用的例子:

     B0

B1        B2

     B3

B0* p;

若把 p 指向四個不同類的對象時,對B0,B1,B2,B3都能表現出多态,

則必須同時使用虛基類和虛函數

否則無法通過 p 調用 B3 的相應函數(編譯無法通過)

//模闆

函數模闆

template <class T>     //可以有多個

傳回類型函數名(參數表)

{

    ...

}

類模闆

template <class T>     //可以有多個

class <類名>

{

    ...

};

聲明對象  類名<類型> 對象名(參數);

類模闆的對象引用,可作函數參數

類模闆可以作基類

//異常處理

try

{

  throw

}

catch(...)

{

}

//幾個"常"

常引用 const int& x; //多用于函數形參

常量   const int PI = 3.14;  // <=>  int const PI = 3.14;

常函數 void print(int x) const; //隻"讀"不可"寫" ,在函數中不能對x值進行改變

常資料成員

    靜态: 

        在類外初始化

            class A{static const int x;};  const int A::x = 100;   //注意,這裡不能加static ,非常量的靜态成員初始化時也一樣

        在類内聲明時初始化 class A { static const int x = 100; } // 隻有靜态常量資料成員才可以在類中聲明時初始化

    非靜态: 在構造時用冒号文法,使用初始化清單初始化

常成員函數

    Print(x) const;                                        //隻“讀”不“寫”

//幾個不同指針的差別

指針 指向的位置 所指向的位置是否可變 通過該指針是否可以改變所指向位置的内容
int* &int
int* const &int
const int* &(const int)
const int* const &(const int)