/******************************************************************************************************************/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屬性。