繼承和派生
- 1.基本概念
- 2.實現公有繼承
- 3.私有繼承的例子
- 4. 繼承和組合
《老九學堂C++課程》《C++ primer》學習筆記。《老九學堂C++課程》詳情請到B站搜索《老九零基礎學編程C++入門》
-------------簡單的事情重復做,重復的事情用心做,用心的事情堅持做(老九君)---------------
1.基本概念
面向對象oop–三大重要特性-- 封裝、繼承、多態
在C++中,代碼重用是通過“繼承(inheritance)”機制實現的。
在一個已經存在的類的基礎上,再建立一個新類。
從已有的類中派生出新類,派生類就繼承了原有類(基類)的特征,包括成員和方法(以后函數就叫方法)
通過繼承可以完成的功能-可升級可維護
- 在已有類的基礎上增加新的功能,對于數組類,可以添加數學計算–排序
- 給類添加數據成員,對于字符串類,可以派生類,并添加制定成員表示顏色。
- 修改類的方法,對于普通英雄,可以派生出擁有更豐富的技能的近戰英雄類
注意:
繼承機制只需要提供新的特性,甚至不需要訪問源碼就可以派生出類
允許在不公開的情況下將自己的類分發給他人,同時允許他們在類中添加新的特性。
程序升級和擴展–非常忌諱的是,修改原有的代碼。原來的代碼測試通過了,測試實際是一件非常困難的事。
開發一款RPG(Role-playing Gam)游戲
游戲職業:坦克,戰士,刺客,法師,射手,輔助
1.0 版本:戰士,法師
直接定義英雄類:戰士類,法師類–存在相同屬性和方法。
把相同的成員和方法封裝成基類。
注意:
1.派生類對象存儲了基類的數據成員
2.派生類對象可以調用基類的非私有函數
3.派生類需要自己的構造方法
4.派生類根據需要增加額外的成員和方法
繼承的繼承:稱為直接基類和間接基類。
父類的的成員和方法的公有,私有,和受保護三種屬性的訪問權限:
1.公有權限下,自己和派生類,以及外部都能訪問
2.私有權限下,只有自己訪問,派生類和外部都無法訪問
3.受保護權限下,自己和派生類可以訪問,外部無法訪問
繼承分為公有繼承,私有繼承,受保護繼承。三種方式繼承之后子類權限的變化
基類成員 | 公有繼承 | 私有繼承 | 保護繼承 |
---|---|---|---|
公有 | 公有 | 私有 | 受保護 |
受保護 | 受保護 | 私有 | 受保護 |
私有 | 不被繼承 | 不被繼承 | 不被繼承 |
全部繼承,不封裝基類–公有繼承(除了基類的私有成員不繼承,其他都是權限不變的繼承)is a 關系。
全部繼承,完全封裝基類–私有繼承(庶出,除了基類的私有成員不繼承,其他成員繼承后權限改成私有) has a關系。使用包含來實現has a ,用繼承來實現有點抽象。
全部繼承,有選擇封裝基類–受保護(除了基類的私有成員不繼承,其他成員繼承后權限被改為受保護模式)
靈活運用面向對象思想的重要體現。
不管使用哪一種繼承,派生類都不能訪問基類里的私有成員,除非改成protected.
class Emperor
{
private:string[] bueaties; // 后宮佳麗double silvers; // 私房錢
protected:string palace; // 宮殿名稱
public:Emperor();~Emperor();string reignTile; // 年號
}
// 子類的繼承關系,非實際代碼
class FourthSon:public Emperor
{
// 父類私有成員不可見,
protected:string palace; //宮殿名稱
public:FourthSon();~FourthSon();string reignTile; // 年號
}
class ThirteenSon:protected Emperor
{
protected: // 外人訪問不了,只有自己,友元,以及子類能夠訪問。string reignTile; // 年號string palace; // 宮殿名稱
public:ThirteenSon();~ThirteenSon();
}
class SecondSon: private Emperor
{
private: //僅作說明,只有自己訪問string palace; //宮殿名稱string reignTile; // 年號
piblic:SecondSon();~SecondSon();
}
2.實現公有繼承
掌握公有繼承,了解私有繼承和受保護繼承。
滿足is a 關系的可以用繼承。
clion 還不會寫配置文件,類圖生成
類對象在內存中的存儲情況
- 在沒有繼承關系時的內存模型
a)對象的成員變量存在堆內存區/棧內存區,代碼存儲在公有的成員函數代碼區。所有的對象共同享有一段函數代碼
b)如果使用sizeof 求類所占空間的大小,只是計算了成員變量的大小,并沒有把成員函數也包含在內。 - 有繼承關系時的內存模型
a)派生類的內存模型看成是基類成員變量和新增成員變量的總和,所有的成員函數仍然共有另一個區域–代碼區。
創建的時候先初始化基類再初始化派生類
釋放時先釋放派生類再釋放基類
demo1: Warrior 公有繼承
//main.cpp
#include <iostream>
#include <string>
#include "Hero.h"
#include "Warrior.h"using namespace std;
void HeroTest();
void WarriorTest();
int main()
{// HeroTest();WarriorTest();
}
void HeroTest()
{Hero hero1;cout << hero1 << endl;hero1.Move();Hero * hero2 = new Hero("測試英雄2",999,5000,5000);cout << *hero2 << endl;hero2->Move();//(*hero2).Move(); // 等價調用
}
void WarriorTest(){Warrior warrior1;// 情況1:派生類中沒有重新實現move方法,調用父類方法// 情況2:派生類中重新實現move方法,調用子類實現的該方法warrior1.Move();cout << warrior1 << endl;Hero * hero = new Warrior; // 基類指針指向了派生類--標準的多態hero->Move(); // 調用基類的實現delete hero;}
//Hero.h
//
// Created by 陳瑩瑩 on 2021/3/15.
//#ifndef CHAPTER13_1_HERO_H
#define CHAPTER13_1_HERO_H#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <assert.h>
using namespace std;
class Hero
{
private:string m_NickName;int m_Level;int m_MaxLife;int m_CurrLife;int x;int y;public:Hero();Hero(const string& nickName);Hero(const string& nickName, int level);Hero(const string& nickName, int level, int maxLife, int currLife);void Move();friend ostream& operator<<(ostream& out, const Hero& hero);// friend ostream& operator<<(ostream& out, const* hero);string GetNickName() const{return m_NickName;}int GetLevel() const{return m_Level;}int GetMaxLife() const{return m_MaxLife;}int GetCurrLife() const{return m_CurrLife;}void SetNickName(const string & nickName){this->m_NickName = nickName;}void SetLevel(int level);void SetMaxLife(int maxLife);void SetCurrLife(int currLife);void operation1();
};
#endif //CHAPTER13_1_HERO_H
//Hero.cpp
//
// Created by 陳瑩瑩 on 2021/3/15.
//#include "Hero.h"
#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <assert.h>
#include "Hero.h"
using namespace std;Hero::Hero() : m_NickName("默認英雄"),m_Level(1),m_MaxLife(100),m_CurrLife(100)
{cout << "調用了Hero的默認構造" << endl;
}
//Hero::Hero(const string& nickName):m_NickName(nickName),m_Level(1),m_MaxLife(100),m_CurrLife(100)
//{
//
//}
Hero::Hero(const string& nickName):Hero(nickName,1,100,10)
{cout << "調用了Hero 一個參數版本的構造" << endl;
}
Hero::Hero(const string& nickName, int level):Hero(nickName, level,100,10)
{cout << "調用了Hero 兩個參數版本的構造" << endl;
}
Hero::Hero(const string& nickName, int level, int maxLife, int currLife):m_NickName(nickName),m_Level(level),m_MaxLife(maxLife),m_CurrLife(currLife)
{cout << "調用了Hero 四個參數版本的構造" << endl;
}
void Hero::Move()
{// 默認移動cout << "普通英雄" << m_NickName << "正在奔跑在艾澤拉斯大陸上" << endl;
}
ostream& operator<<(ostream& out, const Hero& hero){out << "昵稱:" << hero.GetNickName() << "\n";out << "等級:" << hero.GetLevel() << "\n";out << "最大生命:" << hero.GetMaxLife() << "\n";out << "當前生命:" << hero.GetCurrLife() ;return out;
}
void Hero::operation1()
{
}
//Warrior.h
//
// Created by 陳瑩瑩 on 2021/3/16.
//
#ifndef CHAPTER13_1_WARRIOR_H
#define CHAPTER13_1_WARRIOR_H
#include "Hero.h"
// 共有繼承-體現了is a 關系
class Warrior : public Hero{
private:int m_PhysicalAttack;
public:Warrior();Warrior(const string& nickName, int phyAttack);void Move(); // 在派生類中實現派生類版本的move方法~Warrior();
};
#endif //CHAPTER13_1_WARRIOR_H
//Warrior.cpp
//
// Created by 陳瑩瑩 on 2021/3/16.
//
#include "Warrior.h"
Warrior::Warrior() :Hero("默認構造",1,100,100)
{
}
Warrior::Warrior(const string& nickName, int phyAttack):Hero(nickName,1,100,100),m_PhysicalAttack(phyAttack)
{
}
void Warrior::Move()
{// m_NickName沒辦法過呀,不能訪父類的私有屬性,需要在將父類中私有成員改為受保護成員//cout << "戰士《" << m_NickName << "》"<< "背著一大堆近戰武器正在前進。。。"<< endl;cout << "戰士《" << GetNickName() << "》"<< "背著一大堆近戰武器正在前進。。。"<< endl;
}
Warrior::~Warrior()
{
}
有關基類,派生類構造
- 實例化派生類對象時,首先會創建基類對象(調用基類構造)
- 派生類構造應通過成員初始化列表將基類信息傳遞給基類構造
- 應該在派生類構造中初始化派生類新增的數據成員
派生類與基類之間特殊關系小結
1.派生類可以使用基類的非私有成員函數(public和protect)
2.基類指針可以在不進行顯示類型轉換的情況下指向派生類對象
Warrior warrior1("諸葛達摩", 10, 100, 100);
Hero& refHero = warrior1; // 基類引用指向派生類,
Hero* ptrHero = &warrior1; // 基類指針指向派生類對象
Warrior& warrior2 = (Warrior)refHero; // 父類引用/指針需要強制轉換成子類引用/指針(前提:父類指針指向子類對象)
//不可以將基類對象的地址賦給派生類引用和對象,即不能進行逆操作
3.可以將派生類對象賦值給基類對象,程序會使用隱式重載賦值運算符
Hero hero = warrior;
hero.move(); //調用父類方法
3.私有繼承的例子
沒講完呀,暫且收一收好了
//main.cpp
#include <iostream>
#include "Teacher.h"
void TeacherTest(){Teacher teacher1(8000);// 名字設置沒有寫完
}
int main()
{TeacherTest();return 0;
}
//Teacher.h
//
// Created by 陳瑩瑩 on 2021/3/21.
//#ifndef CHAPTER13_1_TEACHER_H
#define CHAPTER13_1_TEACHER_H
#include <iostream>
#include <string>
using namespace std;
/** 用來演示私有繼承的其中一種用法* 實現組合關系,只能組合一個屬性不能組合兩個屬性* Teacher類中擁有string類型的成員name* */class Teacher :private string{ // teacher 中擁有string類型的成員
private:double salary; // 工資public:Teacher();Teacher(int _salary) : salary(_salary){}double GetSalary(){return salary;}void SetSalary(double salary){this->salary = salary;}// 難點const string& GetName() const{/** Teacher類是由string 類私有派生而來,所以,可以使用強制類型轉換,將Teacher類轉換成string類* 為了避免調用構造函數創建新的對象,所以強制轉換成了string 的引用類型返回* 本方法返回一引用,指向調用本方法的Teacher 對象中繼承而來的string對象* */return (const string&)*this; //強轉}//using string::length(); //將字符串方法聲明為本類的公有方法/*返回當前教師類對象姓名的字符串長度*/int GetLenght(){return string::length();}~Teacher();string nickName; // 使用組合關系實現比較簡單的has-a 關系protected:};
#endif //CHAPTER13_1_TEACHER_H
4. 繼承和組合
一張臉由多個類組合而成,頭發的不同總類從父類頭發那里繼承而來。
繼承是is a 縱向關系–狗是哺乳動物,戰士是英雄,橘貓是寵物–
組合是has a 橫向關系–學生有書包,戰士有武器,
繼承是C++與C最重要的區別,雖然C++也可以用C語言的編程習慣。