C++ 類的學習(2)

類的友元

友元是C++提供的一種破壞數據封裝和數據隱藏的機制,通過將一個模塊聲明爲另一個模塊的友元,一個模塊能夠引用另一個模塊中本是被隱藏的信息。

爲啥需要友元呢?有時候我們需要不斷的提取對象中的成員的數據,而提前成員的數據,而只有類本身的成員函數纔可以訪問類的成員,在很多次使用成員函數的過程中,會造成時間的浪費,而使用友元的話,我們可以直接訪問某個類的成員變量。

友元函數

友元函數是在類聲明中由關鍵詞friend修飾說明的非成員函數,在它的函數體中能夠通過對象名訪問private和protected成員。

例如

class A
{
private:
    int a,b;
public:
    A(int x,int y):a(x),b(y){}
    friend double show(A &x,A &y);
};
double show(A &x,A &y)
{
    double a=x.a-y.a;
    double b=x.b-y.b;
    return sqrt(a*a+b*b);
}
int main()
{
    A b(1,2),b1(2,3);
    cout<<show(b,b1)<<endl;
    return 0;
}

在show函數裏我們用了很多次對象的成員,如果用函數來提取對象成員的值的話,會造成時間的浪費。

這裏有個隱患,就是show傳過來的是兩個引用,而引用是雙向傳遞的,如果show函數出了問題,我們所使用的那兩個對象的值可能會被改變。可以加一個 const 來消除這個隱患

友元類

在一個類中聲明另一個類是它的朋友,舉個例子,

在類A裏聲明B是A的友元,所以在B的函數set裏可以直接使用B成員中對象a的成員變量。

類的友元關係是單向的:聲明B類是A類的友元 不等於 A類是B類的友元,所以上面這個friend class B可以這麼來理解,A授權了B來訪問自己,但是B並沒有授權A可以訪問自己。

 

共享數據的保護

使用const來定義常類型,起到只能使用不能修改的作用,在定義常類型的時候必須初始化。

常對象:用const修飾的對象

#include<bits/stdc++.h>
using namespace std;
class A
{
private:
    int a;
    const int b;//常成員變量
public:
    A(int i):a(i){}
    void print() const;//常成員函數
};

void A::print() const//編譯器在編譯時候看到const,就會仔細審查函數中是否有改變狀態語句,如果有就報錯
{

}
int main()
{
    A const b(1);//常對象
    return 0;
}

常對象只能調用常函數

常變量只能由常指針來指向

前項引用聲明

前項引用聲明實際上就是聲明,適用於下列情況

class B;//前向引用聲明
class A
{
public:
    void f(B b);
};
class B
{
 public:
     void g(A a);   
};

A類裏有參數是B類對象的函數,B類裏有參數是A類對象的函數,必須有一個類先聲明一下,所以這裏聲明瞭B類

但是如果是這樣的話,前項引用聲明就失效了

class B;//前向引用聲明
class A
{
private:
    B x;
public:
    void f(B b);
};
class B
{
 public:
     void g(A a);   
};

在A類裏出現了B 類的對象 x,但是這個B類並沒有被定義,編譯器在編譯這一行的時候,不知道B類裏面什麼,因而無法定義x,所以會報錯

這裏可以用 B *x來代替B x;

 

類的繼承與派生

類的繼承是指保持已有類的特性而構造新類的過程叫繼承

類的派生是指在已有類的基礎上新增自己的特性而產生的新類的過程叫派生

被繼承的類叫基類(父類)

派生出的類叫派生類(子類)

直接參與派生出某類的基類成爲直接基類

基類的基類甚至更高層的基類成爲間接基類

繼承的目的:實現設計與代碼的重用

派生的目的:當新的問題出現是,原有程序無法解決時,需要對原有程序進行改造

 

單繼承時派生類的定義

語法

class 派生類名 :繼承方式 基類名

{

      成員聲明;

}

例:

class Derived:public Base

{

public:

Derived();

~Derived();

};

多繼承時派生類的定義

class 派生類名 :繼承方式1 基類名1,繼承方式2 基類名2, ....

{

      成員聲明;

}

公有繼承

簡單來講就是,繼承過來的公有成員和保護成員變成派生類的公有成員了。在類的聲明外派生類也可以訪問基類的公有成員和保護成員,私有成員不可訪問

派生類的對象只能訪問公有成員

私有繼承

基類的公有和保護成員繼承過來都變成私有成員了,派生類的對象不能直接訪問從基類繼承的任何成員。

※ 在派生類的函數裏調用基類的成員函數要使用 類名::函數名(參數)的形式來調用

 

保護繼承

基類的公有成員和保護成員都以保護的身份出現在派生類裏,派生類的成員函數可以直接訪問基類裏的公有成員和保護成員

就是說,保護繼承和私有繼承的區別主要在於,保護繼承的派生類的成員函數可以直接使用基類的公有和保護成員,但是在類的聲明外,例如在主函數裏,對象不能直接使用基類的公有和保護成員。

舉個栗子

#include<bits/stdc++.h>
using namespace std;
class A{

protected:

int x;

};
class B:public A
{
public :
    void fun();
};
void B::fun()
{
    x=5;//這是可以的!!!!!!!!!
}
int main()
{
    B a;
    B.x=1;//這是錯誤噠!!!!!!!!
}

c++11提供了final

用final標記的類無法被繼承

語法

class 類名 final{ }

派生類的構造函數

 默認情況下,基類的構造函數不被繼承。

在c++ 11裏可以使用 using B::B的形式繼承基類中的構造函數,但是有一個問題,派生類的繼承基類構造函數後,基類的構造函數只能初始化基類的成員。

如果沒有繼承基類的構造函數:

需要手動定義構造函數初始化,對於繼承來的成員,程序將自動調用基類構造函數進行初始化,並且派生類的構造函數需要給基類的構造函數傳遞參數。

單繼承時構造函數的定義語法

派生類名::派生類名(基類所需形參,本類成員所需形參):基類名(參數表),本類成員初始化列表

{

};

例子

#include<bits/stdc++.h>
using namespace std;
class A
{
public:
    A();
    A(int i):x(i){cout<<"A"<<endl;}
private:
    int x;
};
class B:public A
{
public:
    B();
    B(int i,int j):A(i),a(j){cout<<"B"<<endl;}
private:
    int a;
};
int main()
{
    B c(1,2);
    return 0;
}

輸出結果

A

B

多繼承時構造函數的定義語法

派類名::派類名(參數表):

基類名1(基類1初始化參數表),

基類名2(基類2初始化參數表),

。。。。。。

基類n(基類n初始化參數表),

本類成員初始化列表

{

};

如果派生類的構造函數沒有給基類的構造函數傳遞參數,那麼程序將調用基類的默認構造函數。

 

多繼承且有對象成員時派生的構造函數定義

派生類名::派生類名(形參表):

基類名1(參數),基類名2(參數)。。。。基類名n(參數),

本類成員(含對象成員)初始化列表

{

 

};

構造函數的調用順序是

1.調用基類構造函數:順序按照它們被繼承時聲明的順序(從左向右)

2.對初始化列表中的成員進行初始化。

順序按照它們在類中的定義順序

 

舉個例子

class A:public Base2,public Base1,public Base3
{
public:
    A(int a,int b,int c,int d):Base1(a),member2(d),member1(c),Base3(b){}
   //注意基類名的個數與順序,注意成員對象名的個數與順序
private://派生類的私有成員對象
    Base1 member1;
    Base2 member2;
    Base3 member3;
};

初始化列表裏列出來的次序並不是調用順序,Base1(a),member2(d),member1(c),Base3(b)

複製構造函數

複製構造函數只有一個參數,對於派生類來說,我們可以把要複製的那個對象的引用傳給派生類的複製構造函數和基類的複製構造函數

   class Base { /* ........*/ };

    class Derived : public Base {

        public : 

                 //Base :: Base( const Base & ) not Invoked automatically 

                Derived ( const Derived & d ) : Base( d ) /* other member initialization */ { /* .......*/ }

    };

        初始化函數Base( d ) 將派生類對象d轉換爲他的基類部分的引用,並調用基類複製構造函數
基類的複製構造函數是基類對象的引用,但實參可以是派生類對象的引用

 

派生類的析構函數

析構函數不被繼承,派生類如果需要,要自行聲明析構函數

派生類對象過期時,先執行派生類函數的函數體,在調用基類的析構函數

 

訪問從基類繼承的成員

1.當派生類與基類中有相同成員時

若未特別限定,則通過派生類對象使用的是派生類中的同名成員。

此時如果要訪問基類中同名成員時,應使用基類名和::來限定

#include<bits/stdc++.h>
using namespace std;
class A
{
public:
    int a;
};
class B:public A
{
public:
    int a;
};
int main()
{
    B x;
    x.A::a=1;
    x.B::a=2;
    cout<<x.a<<endl;
}

輸出結果

 

2.二義性問題

如果從不同基類中繼承了同名成員,但是在派生類中沒有定義同名成員,則訪問成員存在二義性問題

解決方法:加類名和作用域符來限定

 

虛基類

虛基類是什麼呢?當類之間的繼承關係出現下圖這種關係時候,就可以用虛基類

正常情況下,Derived類裏會有兩部分從Base0的成員,這樣就出現了二義性問題,需要用Base1::var或Base2::var的方式來使用,但是很多情況下會帶來冗餘,不僅僅回來帶空間的浪費,而且會帶來不一致性。(同一個數據在不同地方出現了重複存儲現象叫做冗餘,同一個數據在不同地方出現了不相同的值叫做不一致性)

我們在繼承的時候使用 virtual說明基類爲虛基類,爲最遠派生類提供唯一的基類成員,而不產生重複複製,簡單來說就是Derived類裏只有一部分Base0。

要求是在第一類繼承的時候就使用virtual,如上圖中,在聲明Base1和Base2的時候就要使用class Base1 virtual public Base0的語句來繼承虛基類。

上圖這種繼承關係還存在一個構造函數傳參的問題。

將Base0作爲虛基類後,我們需要在聲明Derived類的時候給Base0的構造函數傳參數,程序在調用Base1和Base2的構造函數時候會忽略調用虛基類的構造函數。簡單來講就是在聲明Derived類的時候直接給Base1,Base2,Base0傳參數就行

Derived::Derived(int a,int b,int c):Base1(a),Base2(b),Base0(c){}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章