1. 程式人生 > 實用技巧 >8.面向物件 二 繼承與多型

8.面向物件 二 繼承與多型

友元
  • 作用: 可以訪問類中私有成員 關鍵字friend
  • 全域性函式做友元
/**
  *Created by Lex on 2020/11/23.
  */
  #include <iostream>
  using namespace std;
#include <string>
  class Build {
      friend   void goodGay(Build *building);
  public:
      Build(){
          m_SittingRoom="客廳";
          m_BedRoom="臥室";
      }
   string m_SittingRoom;
  private:
      string  m_BedRoom;
  };
  void goodGay(Build *building){
      std::cout << "你的好友正在訪問您的=>"<<building->m_SittingRoom << std::endl;
      std::cout << "你的好友正在訪問您的=>"<<building->m_BedRoom << std::endl;
  }
  void  test(){
      Build build;
      goodGay(&build);
  }
  int main(int argc, char const *argv[]) {
test();
      return 0;
  }


  • 類做友元

    /**
      *Created by Lex on 2020/11/23.
      * 類做友元
      */
    
    #include <iostream>
    using namespace std;
    #include <string>
    class Building;
    class goodGay{
    public:
    
        void  visit();
        goodGay();
    
    private:
        Building * building;
    };
    class Building{
        friend class goodGay ;
    public:
        Building();
        string m_sittingRoom;
    private:
        string m_bedRoom;
    };
    //building的實現
    Building::Building() {
        this->m_bedRoom="臥室";
        this->m_sittingRoom="客廳";
    }
    //GOOdGay的實現
    goodGay::goodGay() {
      building = new Building;
    }
    void goodGay::visit() {
        std::cout << "你正在訪問=>>"<< building->m_sittingRoom << std::endl;
        std::cout << "你正在訪問=>>"<< building->m_bedRoom << std::endl;
    }
    
    
    void  test(){
        goodGay gay;
        gay.visit();
    }
    int main(int argc, char const *argv[]) {
    test();
        return 0;
    }
    
運算子過載
  • 運算子過載主要針對於自定義型別的資料操作

    /**
      *Created by Lex on 2020/11/23.
      運算子+過載
      */
    
    #include <iostream>
    using namespace std;
    class  Person{
    public:
    
        int m_num;
        int m_number;
    };
    void test(){
        Person p1;
        p1.m_number=10;
        p1.m_num=20;
        Person p2;
        p2.m_number=10;
        p2.m_num=20;
        Person p3;
        p3=p1+p2;//報錯
    }
    int main(int argc, char const *argv[]) {
    
        return 0;
    }
    //---------------------------------------------------------------------------------------------
    
    //運算子遞增或者遞減過載
    #include <iostream>
    using namespace std;
    class MyInteger
    {
    	friend ostream& operator<<(ostream& cout, MyInteger myint);
    public:
    	MyInteger();
    	~MyInteger();
        //前置遞增
    	MyInteger& operator--() {
    		num--;
    		return *this;//返回自身
    	}
        //後置遞增
    	MyInteger operator--(int) {
    	MyInteger temp;
    		temp = *this;
    	num--;
    		return temp;
    }
    private:
    	int num;
    };
    
    MyInteger::MyInteger()
    {
    	num = 0;
    }
    
    MyInteger::~MyInteger()
    {
    }
    ostream& operator<<(ostream& cout, MyInteger myint) {
    	cout << myint.num;
    	return cout;
    }
    void test() {
    	MyInteger mi;
    	cout << mi-- << endl;
    	cout << mi;
    }
    int main() {
    	test();
    	return 0;
    }
    //--------------------------------------------------------------------------------------------
    //賦值運算子過載
    #include <iostream>
    using namespace std;
    class Person
    {
    public:
    	Person(int age);
    	~Person();
    
    	int *myage;
    	Person& operator=(Person& p) {
    		if (myage!=NULL)
    		{
    			delete myage;
    			myage = NULL;
    		}
    		myage = new int(*p.myage);
    		return *this;
       }
    };
    
    Person::Person(int age)
    {
    	myage=new int(age);
    }
    
    Person::~Person()
    {
    	if (myage!=NULL)
    	{
    		delete myage;
    		myage = NULL;
    	}
    }
    int main() {
    	Person p1(18);
    	Person p2(20);
    	Person p3(45);
    	p3 = p2 = p1;
    	cout<<"p1的年齡:" << *p1.myage<<endl;
    	cout << "p2的年齡:" << *p2.myage << endl;
    	cout << "p3的年齡:" << *p3.myage << endl;
    	return 0;
    }
    
    
    • 全域性函式運算子過載

      Person operator+(Person &person1,Person &person){
          Person temp;
          temp.m_num=person.m_num+person1.m_num;
          temp.m_number=person.m_number+person1.m_number;
          return  temp;
      }
      ostream & operator<<(ostream &out,Person &p){
      out<<p.M_a<<p.m_b;
          return out
      }
      
    • 成員函式運算子過載

      //+過載
      class  Person{
      public:
          Person operator+(Person &person){
              Person temp;
              temp.m_num=person.m_num+ this->m_num;
              temp.m_number=person.m_number+ this->m_number;
              return  temp;
          }
          int m_num;
          int m_number;
      };
      //成員函式無法對<<過載,得不到預定結果
      //關係運算符過載
      bool operator==(Person &p){
          if(this->myage==p.myage&&this->name==p.name){
              return true;
          }
          return false;
      }
      bool operator!=(Person &p){
          if(this->myage==p.myage&&this->name==p.name){
              return false;
          }
          return true;
      }
      
繼承

/**
  *Created by Lex on 2020/11/30.
  */
  class Base{
  public:
      int m_a;
  protected:
      int m_b;
  private:
      int m_c;
  };
  class son: public Base{
  public:
      void func(){
          m_a=1000;
          m_b=1000;
//          m_c=1000 //公有繼承私有屬性不可訪問
      }
  };

  class  son2: protected Base{
  public:
      void func(){
          m_a=1000;
          m_b=1000;
//          m_c=1000 //保護繼承私有屬性不可訪問
      }
  };

  class  son3: private Base{
  public:
      void func(){
          m_a=1000;
          m_b=1000;
//          m_c=1000 //保護繼承私有屬性不可訪問
      }
  };
void  test(){
    son son;
    son.m_a=1000;//類外訪問不改變公有屬性的訪問許可權,無法訪問protected許可權的屬性
//      son.m_b=100
    son2 son2;//類外無法訪問子類屬性,保護繼承將公有屬性變為了protected許可權
    son3 son3;//無法訪問子類屬性,私有繼承將公有屬性和保護屬性變為了私有屬性
}

私有屬性無法訪問,但已經被繼承下來了,只是被編譯器隱藏了

父類和子類的執行順序:

繼承中同名屬性或者函式如何訪問(包含靜態屬性函式):

子類中直接訪問,父類中加上父類作用域 => 父類名::屬性名或者函式名

多繼承語法(不推薦使用):對於二義性的解決方案:加上作用域

              class son : public Base2,public Base1{

}

 
//菱形繼承 解決方案 虛繼承 virtual 

多型
  • 多型分為兩類:一類是靜態多型 類似函式過載,運算子過載 二是動態多型 :派生類和虛擬函式執行時多型

  • 兩者區別:靜態多型是函式地址早繫結,即在編譯階段確定函式地址。 動態多型是函式地址晚繫結 ,即執行階段確定函式地址

  • 實驗:animal類和cat類 cat繼承animal類 通過virtual關鍵字改變繫結時間

    早繫結

/**
  *Created by Lex on 2020/12/2.
  */
#include <iostream>
class Animal{
public:
    //函式地址編譯時已經確定
    void func(){
        std::cout << "父類執行" << std::endl;
    }
};
class Cat : public Animal{
public:
    //函式地址編譯時已經確定
    void func(){
        std::cout << "子類執行" << std::endl;
    }
};

void speak(Animal &animal){//函式地址編譯時已經確定
    animal.func();
}
void test(){
    Cat cat;
    speak(cat);

}
int main(){
test();//呼叫父類的fun函式
return 0;
}


實現晚繫結

class Animal{
public:
    //通過virtual關鍵字實現晚繫結
   virtual void func(){
        std::cout << "父類執行" << std::endl;
    }
};
class Cat : public Animal{
public:
    void func(){
        std::cout << "子類執行" << std::endl;
    }
};
void speak(Animal &animal){
    animal.func();
}
void test(){
    Cat cat;
    speak(cat);

}
int main(){
test();
return 0;
}

通過圖中我們發現父類animal和子類cat的大小已經變為4 這是由於在函式前加上virtual關鍵字,編譯器會為這個函式新增vfptr 指標(指標的大小在32位系統下為4, 64位系統下為8 ;虛擬函式指標:指向虛擬函式表 vftable:記錄虛擬函式的地址)指向這個虛擬函式.子類中重寫了父類的func ,此時虛擬函式表中父類的vfptr被子類的vfptr覆蓋

  • 純虛擬函式和抽象類

​ 純虛擬函式語法:virtual 返回值型別 函式名(引數列表)=0

​ 抽象類特點:無法例項化物件,子類必須重寫抽象類中的純虛擬函式

class Animal{
public:
    //純虛擬函式
   virtual void func()=0;
};
class Cat : public Animal{
public:
//    重寫抽象類的純虛擬函式
    void func(){
        std::cout << "子類執行" << std::endl;
    }
};
void speak(Animal &animal){
    animal.func();
}
void test(){
    Cat cat;
    speak(cat);

}
int main(){
test();

return 0;
}
  • 虛析構和純虛析構

​ 區別:共同點=>解決父類釋放子類物件 ,需要具體的函式實現

​ 不同點=>純虛析構屬於抽象類,無法例項化

/**
  *Created by Lex on 2020/12/2.
  */
#include <iostream>
using namespace  std;
#include <cstring>
//抽象類的標誌:只有有一個純虛擬函式,就是抽象類
class Animal{
public:
    //純虛擬函式
   virtual void func()=0;
    Animal(){
        std::cout << "animal建構函式執行了" << std::endl;
    }
    ~Animal(){
        std::cout << "animal解構函式執行了" << std::endl;
    }
};
class Cat : public Animal{
public:
//    重寫抽象類的純虛擬函式
    void func(){
    std::cout << "cat建構函式執行了" << std::endl;
        std::cout << *cat_name<<"在唱歌" << std::endl;
    }
    string *cat_name;
Cat(string name){
   cat_name= new string(name);
}
~Cat(){
    if (cat_name!= nullptr){
        std::cout <<"cat解構函式執行了" << std::endl;
        delete cat_name;
        cat_name= nullptr;
    }
}
};
void speak(Animal &animal){
    animal.func();
}
void test(){
   Animal *animal =new Cat("jack");
   animal->func();
    delete  animal;

}
int main(){
test();

return 0;
}


通過圖中可以發現子類解構函式沒有執行,子類在堆區建立的物件沒有釋放,對於這種現象可以使用虛析構和純虛析構解決

class Animal{
public:
    //純虛擬函式
   virtual void func()=0;
    Animal(){
        std::cout << "animal建構函式執行了" << std::endl;
    }
    virtual ~Animal(){
        std::cout << "animal解構函式執行了" << std::endl;
    }
   
};

class Animal{
public:
    //純虛擬函式
   virtual void func()=0;
    Animal(){
        std::cout << "animal建構函式執行了" << std::endl;
    }
    virtual ~Animal()=0;

};
Animal::~Animal(){
    std::cout << "animal解構函式執行了" << std::endl;
}