第十二章 繼承與多型

子類別建立

繼承(Inherits)為物件導向程式很重要的機制,可以從既有的類別中擴展新的功能

class A{};
class B:[public|protected|private] A{};

子類別繼承方式有public, protected, private.

private 繼承

private繼承被稱為「實作繼承」, 會將父類別中 public及protected成員變成子類別的private成員, 然後再由子類別提供public方法進行存取.

父類別 子類別
private 不繼承
protected private
public private

請參考如下程式碼

#include 
using namespace std;
class Pokemon{
	public :
		int level;
};
class Pikachu:private Pokemon{
	public:
		Pikachu(){
			printf("皮卡丘出生了\n");
		}
		void setLevel(int l){
			this->level=l;
		}
		int getLevel(){
			return level;
		}
};
int main(){
	Pikachu *p1=new Pikachu();
	p1->setLevel(10);
	printf("p1 的 level : %d\n", p1->getLevel());
	//int t=p1->level;此行會錯誤,  
}

protected 繼承

會將父類別中 public及protected成員變成子類別的protected成員

父類別 子類別
private 不繼承
protected protected
public protected

public 繼承

父類別 子類別
private 不繼承
protected protected
public public

一般都不會使用到private及protected 繼承,  通常都是使用 public 繼承
以public繼承,代表子類別會繼承父類別中宣告為public及protected的成員, private則不繼承

class A{
   private:
      int a=10;
   public :
      int b=20;
   protected :
      int c=30;
};
class B:public A{ //類別B中有隱藏著b跟c二個變數
   public:
       int getA(){ //穩死的,因為B裏面沒有a變數
          return a;
       }
       int getC(){
          return c;
       }
};
int main(){
   B *obj=new B();
   //printf("%d\n", obj->getA());
   printf("b= %d\n", obj->b);
   printf("c= %d\n", obj->getC());
}

為什麼c要用getC()呢?因為protected 的權限為允許父類別內及子類別內存取,但禁止在物件直接存取
public : 任何地方都可存取
private : 允許自己類別內存取, 子類別內不可存取, 物件不可存取
protected : 允許自己類別內存取, 子類別內亦可存取, 物件不可存取

子類別的擴充

子類別繼承父類別的方法後, 可以再擴充其他功能.

如下代碥 Pikachu *p1=new Pikachu(); 則 p1具有閃電攻擊

但如果以父類別參考子類別物件時, 擴充功能將失效, 如 Pokemon *p2=new  Pikachu();
因為p2雖是皮卡丘, 但他是以Pokemon來稱呼, 但不是每個神奇寶貝都具有閃電攻擊的, 所以 lighting當然會失效

但p2還是可以透過強制轉型成 Pikachu, 然後擴充功能就會出現

#include <iostream>
using namespace std;
class Pokemon{
	private :
		int level;
	public:
		void setLevel(int l){
			this->level=l;
		}
		int getLevel(){
			return level;
		}
};
class Pikachu:public Pokemon{
	public:
		void lighting(){
			printf("1萬伏特閃電攻擊\n");
		}
};
int main(){
	Pikachu *p1=new Pikachu();
	p1->lighting();
	Pokemon *p2=new Pikachu();
	((Pikachu *)p2)->lighting();
}

多型

上述的Pikachu, 直接宣告為 Pikachu就好, 為什麼還要宣告成Pokemon, 然後再來強制轉型, 轉來轉去的呢?

如下代碼說明, pikachuArray 只可以容網Pikachu, 但無法容納Eve, 
而 PokemonArray卻可以同時容納Pikachu及Eve

#include <iostream>
using namespace std;
class Pokemon{
	private :
		int level;
	public:
		void setLevel(int l){
			this->level=l;
		}
		int getLevel(){
			return level;
		}
};
class Pikachu:public Pokemon{
	public:
		void lighting(){
			printf("1萬伏特閃電攻擊\n");
		}
};
class Eve:public Pokemon{};
int main(){
	Pikachu **pikachuArray=new Pikachu*[10];
	pikachuArray[0]=new Pikachu();
	pikachuArray[1]=new Eve();//此行無法容納 Eve 
	Pokemon **pokemonArray=new Pokemon*[10];
	pokemonArray[0]=new Pikachu();
	pokemonArray[1]=new Eve();//此行ok 
}

方法重載(Overload)

在同一類別裏, 或者在父子類別中, 有著相同的名稱,但不同的參數,叫重載

class A{
   public :
       int num=10;
       int get(){return num;}
};
class B:public A{
   public:
       int num;
       int get(int x){
          num=x;
          return num;
       }
};
int main(){
   B *objb=new B();
   //printf("B物件的 num= %d\n", objb->get()); //A的get()雖被B接收了,但又被B的get(int x)多載,
   //所以objb->get()不能用了
   printf("B物件的 num= %d\n", objb->get(100));
   printf("B物件的父類別 num= %d\n", objb->A::get());
   system("pause");
   return 0;
}

方法覆載(Override)

子類別的方法名稱與參數跟父類別完全一樣,但子類別還是會保留一份父類別的原始資料

class A{
   public :
   int num=10;
   int get(){return num;}
};
class B:public A{
   public:
   int num=100;
   int get(){return num;}
};
int main(){
   B *objb=new B();
   printf("B物件的 num= %d\n", objb->get());
   printf("B物件的父類別 num= %d\n", objb->A::get());
}

子類別調用父類別方法

子類別覆蓋父類別方法後, 想要調用父類別方法, 就必需使用Pokemon::setLevel(10);

#include <iostream>
using namespace std;
class Pokemon{
	private :
		int level;
	public:
		void setLevel(int l){
			this->level=l;
		}
		int getLevel(){
			return level;
		}
};
class Pikachu:public Pokemon{
	public:
		Pikachu(){
			printf("皮卡丘出生了\n");
		}
		void setLevel(int l){
			printf("從子類別設定等級\n");
			Pokemon::setLevel(l);
		}
};
int main(){
	Pikachu *p1=new Pikachu();
	p1->setLevel(10);
	printf("p1 的 level : %d\n", p1->getLevel());
}

建構子的重載與繼承

子類別執行建構子時, 會先執行父類別的預設建構子. 因為編譯器會在子類別建構子後面自動加入
Pikachu():Pokemon(){}, 如下藍色字体

#include <iostream>
using namespace std;
class Pokemon{
	private :
		int level;
	public:
		Pokemon(){
			printf("神奇寶貝出生了\n");
		}
};
class Pikachu:public Pokemon{
	public:
		Pikachu():Pokemon(){
			printf("皮卡丘出生了\n");
		}
};
int main(){
	Pikachu *p1=new Pikachu();
}

父類別具有自訂建構子

當父類別有自訂建構子時, 編譯器不會自動加入預設建構子.  但是子類別會自動調用父類別的預設建構子, 所以子類別就會出錯了.

此時就需在子類別自己寫入要調用的自訂建構子, 如下監色字体

#include <iostream>
using namespace std;
class Pokemon{
	private :
		int level;
	public:
		Pokemon(int l){
			printf("神奇寶貝出生了\n");
			this->level=l;
		}
};
class Pikachu:public Pokemon{
	public:
		Pikachu():Pokemon(1){
			printf("皮卡丘出生了\n");
		}
};
int main(){
	Pikachu *p1=new Pikachu();
}

Singletone 獨生子

當建構子設定為 private時,  就無法在main中new 出物件. 但要防止new 出物件是要用抽象類別, 而不是將建構子設為 private.

將建構子設為 private, 其實是為了只能 new 出一個物件而以, 稱為獨生子, 如下代碼

#include <iostream>
using namespace std;
class Pokemon{
	private :
		int level;
		Pokemon(){}
	public:
		static Pokemon *obj;
		static Pokemon *getInstance(){
			if(obj==NULL)obj=new Pokemon();
			return obj;
		}
};
Pokemon *Pokemon::obj=NULL;
int main(){
	Pokemon *p1=Pokemon::getInstance();
	Pokemon *p2=Pokemon::getInstance();
	if(p1==p2)printf("同一隻\n");
	else printf("不同隻\n");
}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *