C++之面向對象編程20170912

/******************************************************************************************************************/express

1、C++面向對象編程_訪問控制和繼承編程

1.繼承關係安全

class Person {函數

private:this

static int cnt;  指針

char *name;   對象

int age;blog

public:繼承

static int getCount(void);內存

}

class Student : public Person {

//定義Student類,繼承了Person

};

2.訪問控制

private 外界不可見,不能直接訪問

protected 外界不可見,不能直接訪問;子類能夠訪問

public 外界能夠直接訪問

 

3.調整訪問權限

派生類內部能夠調整繼承過來的成員(派生類可見的父類成員)的訪問權限,能夠提高或下降權限

使用using來實現,例:

class Father {

private:

         int money;

protected:

         int room_key;

}

class Son : public Father {

private:

         int toy;

         //using Father::it_skill;

public:

         using Father::room_key;//使用using來調整權限爲public

}

4.不一樣繼承方式(類型)

主要包括三種繼承類型:public ,protected,private//公有繼承,保護繼承,私有繼承

繼承產生的權限結果見圖

基類private成員派生類不可見(不可見 權限天然不會被改變)

公有繼承,基類成員訪問權限不變

保護繼承,基類成員訪問權限全變爲protected

私有繼承, 基類成員訪問權限全變爲private

 

1).不管哪一種繼承方式,在派生類內部使用父類時並沒有差異

2). 不一樣的繼承方式,會影響這兩方面:

外部代碼(類外)對派生類的使用(通常是建立對象使用)、

派生類的子類(派生類的子類怎麼使用派生類裏面的成員)

5.覆寫

子類能夠覆寫從父類繼承來的成員函數

6.派生類對象的空間分佈

1).

class Student : public Person {

private:

         int grade;

         void setGrade(int grade) {this->grade = grade;}

         int getGrade(void) {return grade;}

public:

         void printInfo(void)

         {

                   cout<<"Student ";

                   Person::printInfo();//調用父類的打印函數  

}

};

2).派生類對象內存空間分部

派生類對象內存空間=父類內存空間+本身的獨特的屬性

//相似在基類內存空間後面追加一份屬於派生類本身獨特屬性的空間

 

3).(向上)轉型

void test_func(Person &p)

{

         p.printInfo();

}

int main(int argc, char **argv)

{

         Person p("lisi", 16);

         Student s;

         s.setName("zhangsan");

         s.setAge(16);

         test_func(p);

         test_func(s); /* 等同於 Person &p = (s裏面的Person部分);

p引用的是"s裏面的Person部分",因此test_func裏使用的是person的printInfo函數*/

         s.printInfo();//

return 0;

}

派生類是基類的一種,因此 基類=派生類 時 等同於派生類裏面的基類部分賦值給基類

/******************************************************************************************************************/

2、C++面向對象編程_多重繼承

1.多重繼承

class Sofabed : public Sofa, public Bed {

//不寫public默認是私有繼承

};

2.多個基類中有相同成員函數的情形

1).能夠指定成員函數具體是屬於哪一個基類的

s.Sofa::setWeight(100);

 

2).抽出相同的成員造成基類的父類

同時使用虛擬繼承,來保證基類共用其父類的同一成員,從而保證派生類使用的是惟一成員(只使用了一個成員,派生類所佔內存中只有該成員只佔一份空間)

class Sofa : virtual public Furniture

{

};

class Bed : virtual public Furniture

{

};

class Sofabed : public Sofa, public Bed

{

};

 

int main(int argc, char **argv)

{

         Sofabed s;

         s.watchTV();

         s.sleep();

         s.setWeight(100);

        

         return 0;

}

儘可能避免使用多重繼承,這樣會使得程序更加複雜,更容易出錯

/******************************************************************************************************************/

3、C++面向對象編程_再論構造函數

1.構造順序

先父後兒:

1)先調用基類的構造函數,

先虛擬繼承的基類後通常繼承的基類(相同類型的,先繼承哪一個就先調用哪一個構造函數)

注意,虛擬繼承的基類,構造函數只執行一次(虛擬基類中,只使用一分內存,因此只執行一次)

2)自身

先對象成員,後本身的

 

2.派生類調用基類的有參構造函數

在派生類的有參構造函數中使用:號加上類名(參數)

class LeftRightSofabed : public Sofabed, virtual public LeftRightCom {

private:

         Date date;

         Type type;

public:

         LeftRightSofabed()

{

cout <<"LeftRightSofabed()"<<endl;

}

         LeftRightSofabed(char *str1, char *str2, char *str3) : Sofabed(str1), LeftRightCom(str2), date(str3)//派生類的有參構造函數中使用:號加上類名(參數)

{

cout <<"LeftRightSofabed()"<<endl;

}

};

3.類中對象的構造函數的調用

在派生類的有參構造函數中使用:號加上對象名(參數)

class LeftRightSofabed : public Sofabed, virtual public LeftRightCom {

private:

         Date date;

         Type type;

public:

         LeftRightSofabed()

{

cout <<"LeftRightSofabed()"<<endl;

}

LeftRightSofabed(char *str1, char *str2, char *str3) : Sofabed(str1), LeftRightCom(str2), date(str3)

{//派生類的有參構造函數中使用:號加上對象名(參數)

cout <<"LeftRightSofabed()"<<endl;

}

};

/******************************************************************************************************************/

4、C++面向對象編程_多態

1.直接傳入

沒有轉型,實現不了對應的狀態,即沒法實現多態

class Human

{

public:

         void eating(void) { cout<<"use hand to eat"<<endl; }

};

class Englishman : public Human

{

public:

         void eating(void) { cout<<"use knife to eat"<<endl; }

};

class Chinese : public Human

{

public:

         void eating(void) { cout<<"use chopsticks to eat"<<endl; }

};

void test_eating(Human& h)

{

         h.eating();

}

 

int main(int argc, char **argv)

{

         Human h;

         Englishman e;

         Chinese c;

 

         test_eating(h);

         test_eating(e);

         test_eating(c);//執行結果所有調用基類的函數,沒實現多態

 

         return 0;

}

2.引入虛函數

基類函數名加上virtual表示虛函數,基類對應的函數能夠加也能夠不加,同時派生類對應的函數能夠加也能夠不加,已是虛函數的屬性了,此時在向上轉型中派生類實現的與基類同樣的函數就能夠被調用(通常來講也就是實現了覆寫),不然在向上轉型中調用的都是基類的(即沒有加virtual的狀況)。

所以,析構函數要加上virtual,加上後在向上轉型中,即基類指針指向派生類的對象時(多態性),若是刪除該轉換後的基類指針;就會調用該指針指向的派生類析構函數(由於是虛函數),而派生類的析構函數又自動調用基類的析構函數,這樣整個派生類的對象徹底被釋放。若是析構函數不被聲明成虛函數,則編譯器實施靜態綁定,在刪除轉換後的基類指針時,只會調用基類的析構函數而不調用派生類析構函數,這樣就會形成派生類對象析構不徹底。

注意,派生類的對象固然能夠調用本身的成員函數不論是否有virtual。

向上轉型:子對象轉換爲父對象(基類指針能夠指向派生類的對象),轉換時,若是代碼中有執行基類沒有的成員函數,則編譯就會報錯,因此是安全的。

class Human

{

private:

         int a;

public:

         virtual void eating(void)

{

cout<<"use hand to eat"<<endl;

}

};

class Englishman : public Human

{

public:

         void eating(void) { cout<<"use knife to eat"<<endl; }

};

class Chinese : public Human

{

public:

         void eating(void) { cout<<"use chopsticks to eat"<<endl; }

};

void test_eating(Human& h)

{

         h.eating();

}

 

int main(int argc, char **argv)

{

         Human h;

         Englishman e;

         Chinese c;

 

         test_eating(h);

         test_eating(e);//執行結果調用派生類的函數,實現多態

         test_eating(c);//執行結果調用派生類的函數,實現多態

         return 0;

}

內部實現機制

靜態聯編:非虛函數,在編譯時就肯定了調用哪個

動態聯編:

1).類裏面有虛函數,則其對象裏有指針,指向虛函數表,子類對象裏因爲繼承也有這個指針,指向虛函數表

2).調用函數的時候,找到指針指向的虛函數表,而後調用裏面的虛函數

因此使用虛函數時,對象佔用的空間會變大

見下圖:

 

3.虛函數注意事項

1).函數參數使用對象的指針或者引用,纔有多態

傳值時,無多態(強制轉換爲基類類型,只剩下基類部分,就沒有指針了,靜態聯編,調用的就只能是基類了)

2).只有類的成員函數才能聲明爲虛函數

3).靜態成員不能是虛函數

4).內聯函數不能是虛函數

5).構造函數不能是虛函數

6).析構函數通常都聲明爲虛函數

這樣才能夠先釋放派生類的(調用本身的清理函數),再調用基類的析構(清理)函數,而不是隻釋放基類的

class Human

{

private:

         int a;

public:

         virtual void eating(void) { cout<<"use hand to eat"<<endl; }

         virtual ~Human() { cout<<"~Human()"<<endl; }

};

class Englishman : public Human

{

public:

         void eating(void) { cout<<"use knife to eat"<<endl; }

         virtual ~Englishman() { cout<<"~Englishman()"<<endl; }

};

class Chinese : public Human

{

public:

         void eating(void) { cout<<"use chopsticks to eat"<<endl; }

         virtual ~Chinese() { cout<<"~Chinese()"<<endl; }

};

 

7).重載函數(函數參數不一樣),不能夠設爲虛函數

重載已是多態了,因此不能夠設置爲虛函數了。

或者能夠理解爲,多態是相同的調用方法,能夠調用到不一樣類裏面實現的函數,而重載函數參數不一樣,已經不是相同的調用方法了,因此不能也須要設爲虛函數

8).覆寫(覆蓋)函數(函數參數,返回值都相同),能夠設置爲虛函數

9).函數參數都相同,返回值不一樣時,不能夠設置爲虛函數,有個例外:

當返回值是本類指針或引用時,能夠設置爲虛函數

/******************************************************************************************************************/

5、C++面向對象編程_類型轉換

1.隱式類型轉換:

double d = 100.1;

int i = d;  // double轉爲int

char *str = "123";

int *p = str; // char *轉爲int *

2.顯式類型轉換:

兼容c的類型轉換,同時具備新的轉換特性,

以下的各類轉換語句的意思都是把expression轉換成type-id類型的對象,

1).reinterpret_cast<type_id>(expression)//從新解析轉換 強制類型轉換

//是模版函數

//至關於c風格的用小括號()實現的強制類型轉換

int *p = reinterpret_cast<int *>(str2); // char *轉換爲int * ,至關於C風格的()

沒法轉換const或volatile屬性的變量(不能轉換隻讀的爲可讀可寫),否則會編譯報錯

 

2).const_cast<type_id>(expression)//模版函數

用來去除原來類型的const或volatile屬性

char *str2 = const_cast<char *>(str); //轉換const型變量使用到

int *p = reinterpret_cast<int *>(str2); // char *轉換爲int * ,至關於C風格的()

 

3).dynamic_cast<type_id>(expression)//動態類型轉換

該運算符把expression轉換成type-id類型的對象。

 

I、Type-id必須是類的指針、類的引用或者void *;

若是type-id是類指針類型,那麼expression也必須是一個指針;

若是type-id是一個引用,那麼expression也必須是一個引用。

 

 

例子:

void test_eating(Human& h)

{//Human& h傳進來的由程序來決定的,不能事先肯定,這個肯定過程是動態的,因此稱爲動態轉換 

Englishman *pe;

         Chinese    *pc;

         h.eating();

         /* 想分辨這個"人"是英國人仍是中國人? */

         if (pe = dynamic_cast<Englishman *>(&h))//指針的轉換

/*根據指針找到虛函數表找到類信息從而知道對象是否屬於某一個類(虛函數表中有類信息以及繼承信息)           

         cout<<"This human is Englishman"<<endl;

 

         if (pc = dynamic_cast<Chinese *>(&h))

                   cout<<"This human is Chinese"<<endl;

/*根據指針找到虛函數表找到類信息從而知道對象是否屬於某一個類,同時虛函數表裏面除了類信息還有繼承信息,因此也能知道類屬於哪一個父類*/

}

因此動態類型轉換隻能用在含有虛函數的類裏面,即用於多態的場合

 

 

II、動態轉換能夠轉換指針也能夠轉換引用

若是一個引用不能指向一個實體就沒有存在的必要了,引用也不能用來做判斷,因此會致使程序崩潰,

因此動態轉換常用指針而不是引用

 

III、主要用於類層次間的上行轉換(派生類對象轉爲基類對象)和下行轉換(基類對象轉爲派生類對象),還能夠用於類之間的交叉轉換

 

在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是同樣的;

在進行下行轉換時,dynamic_cast具備類型檢查的功能,比static_cast更安全

 

進行上下行轉換是經過虛函數表的類繼承信息來判斷,轉換有可能成功或失敗

 

4).static_cast<type_id>(expression)

//轉換在編譯時由編譯器決定的,轉換是事先肯定的,因此是靜態轉換,因此不能轉換時編譯器會報錯

Expression //待轉換的對象

type_id //轉換後的類型

返回轉換後的變量

該運算符把expression轉換爲type-id類型,

 

但運行時沒有類型檢查來保證轉換的安全性。

例子:

Human h;

         //Englishman e;

         //Chinese c;

         Guangximan g;

         Englishman *pe;

 

         pe = static_cast<Englishman *>(&h);//能夠轉換但不安全

 

         //Englishman *pe2 = static_cast<Englishman *>(&g);//不能轉換

        

Chinese *pc = static_cast<Chinese *>(&g);//能夠轉換

 

使用場景:

I、用於類層次結構中基類和子類之間指針或引用的轉換。

II、進行上行轉換(把子類的指針或引用轉換成基類表示)是安全的(轉換時,若是代碼中有執行基類沒有的成員函數,則編譯就會報錯,因此是安全的);

III、進行下行轉換(把基類指針或引用轉換成子類指針或引用)時,因爲沒有動態類型檢查,因此是不安全的。

IV、用於基本數據類型之間的轉換,如把int轉換成char,把int轉換成enum:這種轉換的安全性也要開發人員來保證。

V、把void指針轉換成目標類型的指針(不安全!!)

VI、把任何類型的表達式轉換成void類型。

注意:static_cast不能轉換掉expression的const、volitale、或者__unaligned屬性。

相關文章
相關標籤/搜索