以員工和經(jīng)理類為例,闡述繼承的關系
成員變量的覆蓋
猜猜下面的運行結果是什么呢?
#define _CRT_SECURE_NO_WARNINGS#include <iostream>#include <string>using std::string;// 繼承// 為什么要有繼承// 代碼的重用// copy// 組合// 類,封裝// 繼承class Employee{public: Employee(const string &name, const int lev, const int salary_base = 3000) : name_(name), lev_(lev), salary_base_(salary_base) // 構造函數(shù)是不能被繼承下來的 { } int Salary() { return salary_base_ * lev_; }PRotected: string name_; int no_; int lev_; int salary_base_;};class Manager : public Employee{public: // 初始化列表中所初始化的變量是自己所擁有的的 // 也就是說在初始化之前是不用有父類中的成員變量的 Manager(const string &name, const int lev, const int salary_base = 5000) : Employee(name, lev, salary_base) { } int Salary() { return static_cast<int>(salary_base_ * lev_ * 1.5); }protected: Employee *employees_; int salary_base_; // 這個變量是重定義,數(shù)據(jù)的重定義并非覆蓋};int main(){ Employee zs("張三", 10); Manager ls("李四", 10); std::cout << zs.Salary() << std::endl; std::cout << ls.Salary() << std::endl; return 0;}運行的結果是:
這個結果是不是令人匪夷所思呢?為什么經(jīng)理的工資是0呢? 那是因為經(jīng)理類中有兩個salary_base_的變量,在經(jīng)理類中只調用Employee類中的構造函數(shù),只是給Employee類中的salary_base_變量賦值,并沒有給經(jīng)理類中的salary_base_變量賦值,所以才會出現(xiàn)經(jīng)理的工資是0的情況。改進方法是給經(jīng)理類中的salary_base_變量也賦值。代碼如下所示:
#define _CRT_SECURE_NO_WARNINGS#include <iostream>#include <string>using std::string;// 繼承// 為什么要有繼承// 代碼的重用// copy// 組合// 類,封裝// 繼承class Employee{public: Employee(const string &name, const int lev, const int salary_base = 3000) : name_(name), lev_(lev), salary_base_(salary_base) // 構造函數(shù)是不能被繼承下來的 { } int Salary() { return salary_base_ * lev_; }protected: string name_; int no_; int lev_; int salary_base_;};class Manager : public Employee{public: // 初始化列表中所初始化的變量是自己所擁有的的 // 也就是說在初始化之前是不用有父類中的成員變量的 Manager(const string &name, const int lev, const int salary_base = 5000) : Employee(name, lev), salary_base_(salary_base), employees_(nullptr) { } int Salary() { return static_cast<int>(salary_base_ * lev_ * 1.5); }protected: Employee *employees_; int salary_base_; // 這個變量是重定義,數(shù)據(jù)的重定義并非覆蓋,但是對于數(shù)據(jù)成員的重定義本身就是存在問題的};int main(){ Employee zs("張三", 10); Manager ls("李四", 10); std::cout << zs.Salary() << std::endl; std::cout << ls.Salary() << std::endl; return 0;}
所以,我們以后在管理成員變量的時候,各個類只需要(并且一定要)管理好自己本類中的成員變量即。
成員函數(shù)的覆蓋
當經(jīng)理類中沒有實現(xiàn)Salary的函數(shù)時,那么經(jīng)理類對象調用的Salary函數(shù)將是Employee類中的Salary函數(shù),這樣經(jīng)理對象得到的工資肯定就會少了,代碼如下:
#define _CRT_SECURE_NO_WARNINGS#include <iostream>#include <string>using std::string;// 繼承// 為什么要有繼承// 代碼的重用// copy// 組合// 類,封裝// 繼承class Employee{public: Employee(const string &name, const int lev, const int salary_base = 3000) : name_(name), lev_(lev), salary_base_(salary_base) // 構造函數(shù)是不能被繼承下來的 { } int Salary() { return salary_base_ * lev_; }protected: string name_; int no_; int lev_; int salary_base_;};class Manager : public Employee{public: // 初始化列表中所初始化的變量是自己所擁有的的 // 也就是說在初始化之前是不用有父類中的成員變量的 Manager(const string &name, const int lev, const int salary_base = 5000) : Employee(name, lev), salary_base_(salary_base), employees_(nullptr) { } //int Salary() //{ // return static_cast<int>(salary_base_ * lev_ * 1.5); //}protected: Employee *employees_; int salary_base_; // 這個變量是重定義,數(shù)據(jù)的重定義并非覆蓋};int main(){ Employee zs("張三", 10); Manager ls("李四", 10); std::cout << zs.Salary() << std::endl; std::cout << ls.Salary() << std::endl; return 0;}結果如下:
怎么會這樣呢?經(jīng)理的工資怎么和員工的工資一樣多了呢?經(jīng)理肯定會不高興的,不但不高興,肯定會不干的。 仔細看代碼不難發(fā)現(xiàn),經(jīng)理的工資的計算方式和員工的工資的計算方式是一樣的,所以得到的結果也是一樣的,這樣也太不公平了吧!!! 我們在經(jīng)理類中增加一個帶參數(shù)的Salary函數(shù),代碼如下所示:
#define _CRT_SECURE_NO_WARNINGS#include <iostream>#include <string>using std::string;// 繼承// 為什么要有繼承// 代碼的重用// copy// 組合// 類,封裝// 繼承class Employee{public: Employee(const string &name, const int lev, const int salary_base = 3000) : name_(name), lev_(lev), salary_base_(salary_base) // 構造函數(shù)是不能被繼承下來的 { } int Salary() { return salary_base_ * lev_; }protected: string name_; int no_; int lev_; int salary_base_;};class Manager : public Employee{public: // 初始化列表中所初始化的變量是自己所擁有的的 // 也就是說在初始化之前是不用有父類中的成員變量的 Manager(const string &name, const int lev, const int salary_base = 5000) : Employee(name, lev), salary_base_(salary_base), employees_(nullptr) { } int Salary(double multiple) // 函數(shù)重定義,只要函數(shù)名相同就會構成重定義,并且也不是覆蓋 { return static_cast<int>(salary_base_ * lev_ * multiple); }protected: Employee *employees_; int salary_base_; // 這個變量是重定義,數(shù)據(jù)的重定義并非覆蓋};int main(){ Employee zs("張三", 10); Manager ls("李四", 10); std::cout << zs.Salary() << std::endl; std::cout << ls.Salary() << std::endl; return 0;}但是,此時編譯就會出問題,錯誤提示如下所示:
這是為什么呢?員工類中是有無參Salary的函數(shù)的呀!!!那為什么經(jīng)理類中就沒有這個函數(shù)了呢?因為Salary函數(shù)重定義,導致經(jīng)理類的對象無法直接使用員工類中的Salary函數(shù),但是也不是說經(jīng)理類把員工類中的Salary函數(shù)給覆蓋了。
#define _CRT_SECURE_NO_WARNINGS#include <iostream>#include <string>using std::string;// 繼承// 為什么要有繼承// 代碼的重用// copy// 組合// 類,封裝// 繼承class Employee{public: Employee(const string &name, const int lev, const int salary_base = 3000) : name_(name), lev_(lev), salary_base_(salary_base) // 構造函數(shù)是不能被繼承下來的 { } int Salary() { return salary_base_ * lev_; }protected: string name_; int no_; int lev_; int salary_base_;};class Manager : public Employee{public: // 初始化列表中所初始化的變量是自己所擁有的的 // 也就是說在初始化之前是不用有父類中的成員變量的 Manager(const string &name, const int lev, const int salary_base = 5000) : Employee(name, lev), salary_base_(salary_base), employees_(nullptr) { } int Salary(double multiple) // 函數(shù)重定義,只要函數(shù)名相同就會構成重定義,并且也不是覆蓋 { return static_cast<int>(salary_base_ * lev_ * multiple); }protected: Employee *employees_; int salary_base_; // 這個變量是重定義,數(shù)據(jù)的重定義并非覆蓋};int main(){ Employee zs("張三", 10); Manager ls("李四", 10); std::cout << zs.Salary() << std::endl; std::cout << ls.Employee::Salary() << std::endl; //說明不是覆蓋,因為員工類中的Salary函數(shù)還是存在的 return 0;}關于組合和繼承關系的總結
#define _CRT_SECURE_NO_WARNINGS#include <iostream>#include <string>using std::string;// 繼承// 為什么要有繼承// 代碼的重用// copy// 組合// 類,封裝// 繼承class Employee{public: Employee(const string &name, const int lev, const int salary_base = 3000) : name_(name), lev_(lev), salary_base_(salary_base) // 構造函數(shù)是不能被繼承下來的 { } int Salary() { return salary_base_ * lev_; }protected: string name_; int no_; int lev_; int salary_base_;};class Manager : public Employee{public: // 初始化列表中所初始化的變量是自己所擁有的的 // 也就是說在初始化之前是不用有父類中的成員變量的 Manager(const string &name, const int lev, const int salary_base = 5000) : Employee(name, lev), salary_base_(salary_base), employees_(nullptr) { } int Salary(double multiple) // 函數(shù)重定義,只要函數(shù)名相同就會構成重定義,并且也不是覆蓋 { return static_cast<int>(salary_base_ * lev_ * multiple); } // 我們大家都普遍會認為繼承下來的函數(shù)會直接放在這個位置 // 比如員工類中的Salary函數(shù)放到這里,不就構成重載了嗎? // 那么為什么還不能直接調用無參的Salary函數(shù)呢? // int Salary() // { // return salary_base_ * lev_; // } // // 顯然不是這樣的,我們有必要區(qū)分一下下面的概念 // 參數(shù)不同,能夠構成重載 overlord 重載 相同作用域 // 普通函數(shù)(只要函數(shù)名相同就會構成重定義) overwrite 重寫,重定義 重定義的話,會默認的使用離你近的 // 虛函數(shù) override 覆蓋protected: Employee *employees_; int salary_base_; // 這個變量是重定義,數(shù)據(jù)的重定義并非覆蓋};// 這個例子中的Employee類和C++標準庫中的string類是屬于組合關系// Manager類和Employee類是屬于繼承關系// 那么,無論是組合也好,還是繼承也好,這二者作用的結果是一樣的,// 都是將其他類直接放在本類中來使用,只是這二者的表達方式不一樣(設計模式)。// 組合關系一般是 has a 是通過嵌入的方式嵌入到本類中,更多的是希望你來幫我做事情,更多的是新類型暴露出來的一些接口// 比如Employee類,我們要是使用的話,會使用Employee暴露出來的方法,不會使用string類的方法,因為這兩個類已經(jīng)變成了// 以Employee類為主要功能的一個整體。使用has a來表現(xiàn)這種關系的話,說明我這個類是一個全新的類型,你應該使用我的全新// 的類型進行操作,我的全新的類型里面有一個string類型,它會來幫我完成一些功能,這是has a的關系所表達的意思// 繼承關系一般是 is a,新類和老類之間有一些相同的接口(功能),我們希望把這個功能繼承下來,變成子類型化,有可能會// 對繼承下來的功能進行強化。int main(){ Employee zs("張三", 10); Manager ls("李四", 10); std::cout << zs.Salary() << std::endl; std::cout << ls.Employee::Salary() << std::endl; //說明不是覆蓋,因為員工類中的Salary函數(shù)還是存在的 return 0;}C++中的Operator=運算符是可以被繼承下來的
這個例子主要是想說明C++中的operator=函數(shù)是可以被繼承下來的,如果在派生類中重定義了operator=函數(shù),那么它就不會再調用基類中的operator=運算符了,如果沒有重定義operator=運算符,,它會被繼承下來的。
#define _CRT_SECURE_NO_WARNINGS#include <iostream>using std::string;class Employee{public: Employee(const char *name, const int lev, const int salary_base = 3000) : lev_(lev), salary_base_(salary_base) // 構造函數(shù)是不能被繼承下來的 { std::size_t len = strlen(name); name_ = new char[len + sizeof(char)]; strcpy(name_, name); } Employee &operator=(const Employee &other) { delete[] name_; std::size_t len = strlen(other.name_); name_ = new char[len + sizeof(char)]; strcpy(name_, other.name_); lev_ = other.lev_; salary_base_ = other.salary_base_; return *this; } const char *GetName() const { return name_; } int Salary() { return salary_base_ * lev_; } int Salary(double multiple) { return static_cast<int>(salary_base_*lev_*multiple); }protected: char *name_; int lev_; int salary_base_;};class Manager : public Employee{public: // 初始化列表中所初始化的變量是自己所擁有的的 // 也就是說在初始化之前是不用有父類中的成員變量的 Manager(const char *name, const int lev, const Employee *employee = nullptr, const int salary_base = 5000) : Employee(name, lev), salary_base_(salary_base) { if (employee) { employees_ = new Employee("", 10); //memcpy(employees_, employee, sizeof(Employee)); // 這樣的拷貝必然是一個淺拷貝,因為Employee類中有指針存在 employees_->operator=(*employee); } } Manager &operator=(const Manager &other) // 重定義 { // 必須也要調用基類的operator=運算符 Employee::operator=(other); if (!employees_) { employees_ = new Employee(*(other.employees_)); } else { *employees_ = *(other.employees_); } return *this; } ~Manager() { delete employees_; } int Salary(double multiple) // 函數(shù)重定義,只要函數(shù)名相同就會構成重定義,并且也不是覆蓋 { return static_cast<int>(salary_base_ * lev_ * multiple); } Employee *employees_;protected: int salary_base_; // 這個變量是重定義,數(shù)據(jù)的重定義并非覆蓋};// 不能被繼承的函數(shù)// 構造函數(shù) 析構函數(shù)int main(){ Employee *zs = new Employee("張三", 10); Manager *ls = new Manager("李四", 10); Manager *ww = new Manager("王五", 12, zs); *ls = *ww; // 只調用了子類的operator=運算符 delete zs; delete ww; std::cout << ls->employees_->GetName() << std::endl; std::cout << ls->GetName() << std::endl; //std::cout << zs.Salary() << std::endl; std::cout << ls->Employee::Salary(1.5) << std::endl; //說明不是覆蓋,因為員工類中的Salary函數(shù)還是存在的 return 0;}拷貝構造函數(shù)可以被派生類繼承
#define _CRT_SECURE_NO_WARNINGS#include <iostream>using std::string;class Employee{public: Employee(const char *name, const int lev, const int salary_base = 3000) : lev_(lev), salary_base_(salary_base) // 構造函數(shù)是不能被繼承下來的 { std::size_t len = strlen(name); name_ = new char[len + sizeof(char)]; strcpy(name_, name); } Employee(const Employee &other) { std::size_t len = strlen(other.name_); name_ = new char[len + sizeof(char)]; strcpy(name_, other.name_); lev_ = other.lev_; salary_base_ = other.salary_base_; } Employee &operator=(const Employee &other) { delete[] name_; std::size_t len = strlen(other.name_); name_ = new char[len + sizeof(char)]; strcpy(name_, other.name_); lev_ = other.lev_; salary_base_ = other.salary_base_; return *this; } const char *GetName() const { return name_; } int Salary() { return salary_base_ * lev_; } int Salary(double multiple) { return static_cast<int>(salary_base_*lev_*multiple); }protected: char *name_; int lev_; int salary_base_;};class Manager : public Employee{public: // 初始化列表中所初始化的變量是自己所擁有的的 // 也就是說在初始化之前是不用有父類中的成員變量的 Manager(const char *name, const int lev, const Employee *employee = nullptr, const int salary_base = 5000) : Employee(name, lev), salary_base_(salary_base) { if (employee) { employees_ = new Employee("", 10); //memcpy(employees_, employee, sizeof(Employee)); // 這樣的拷貝必然是一個淺拷貝,因為Employee類中有指針存在 employees_->operator=(*employee); } } Manager &operator=(const Manager &other) // 重定義 { // 必須也要調用基類的operator=運算符 Employee::operator=(other); if (!employees_) { employees_ = new Employee(*(other.employees_)); } else { *employees_ = *(other.employees_); } return *this; } ~Manager() { delete employees_; } int Salary(double multiple) // 函數(shù)重定義,只要函數(shù)名相同就會構成重定義,并且也不是覆蓋 { return static_cast<int>(salary_base_ * lev_ * multiple); } Employee *employees_;protected: int salary_base_; // 這個變量是重定義,數(shù)據(jù)的重定義并非覆蓋};// 不能被繼承的函數(shù)// 構造函數(shù) 析構函數(shù)int main(){ Employee *zs = new Employee("張三", 10); Manager *ls = new Manager("李四", 10); Manager ww = *ls; //Manager *ww = new Manager("王五", 12, zs); //*ls = *ww; // 只調用了子類的operator=運算符 //delete zs; //delete ww; //std::cout << ls->employees_->GetName() << std::endl; // //std::cout << ls->GetName() << std::endl; ////std::cout << zs.Salary() << std::endl; //std::cout << ls->Employee::Salary(1.5) << std::endl; //說明不是覆蓋,因為員工類中的Salary函數(shù)還是存在的 return 0;}公有繼承的類是is a的關系,可以向上轉換,其余的protected和private方式繼承的就不是is a的關系了,也就不能向上轉換了
#define _CRT_SECURE_NO_WARNINGS#include <iostream>using std::string;class Employee{public: Employee(const char *name, const int lev, const int salary_base = 3000) : lev_(lev), salary_base_(salary_base) // 構造函數(shù)是不能被繼承下來的 { std::size_t len = strlen(name); name_ = new char[len + sizeof(char)]; strcpy(name_, name); } Employee(const Employee &other) { std::size_t len = strlen(other.name_); name_ = new char[len + sizeof(char)]; strcpy(name_, other.name_); lev_ = other.lev_; salary_base_ = other.salary_base_; } Employee &operator=(const Employee &other) { delete[] name_; std::size_t len = strlen(other.name_); name_ = new char[len + sizeof(char)]; strcpy(name_, other.name_); lev_ = other.lev_; salary_base_ = other.salary_base_; return *this; } const char *GetName() const { return name_; } int Salary() { return salary_base_ * lev_; } int Salary(double multiple) { return static_cast<int>(salary_base_*lev_*multiple); }protected: char *name_; int lev_; int salary_base_;};class Manager : public Employee{public: // 初始化列表中所初始化的變量是自己所擁有的的 // 也就是說在初始化之前是不用有父類中的成員變量的 Manager(const char *name, const int lev, const Employee *employee = nullptr, const int salary_base = 5000) : Employee(name, lev), salary_base_(salary_base) { if (employee) { employees_ = new Employee("", 10); //memcpy(employees_, employee, sizeof(Employee)); // 這樣的拷貝必然是一個淺拷貝,因為Employee類中有指針存在 employees_->operator=(*employee); } } // 雖然說已經(jīng)繼承下來了基類中的拷貝構造函數(shù),但是我們還是需要重寫派生類中的拷貝構造函數(shù) // 因為基類中的拷貝構造函數(shù)對于派生類來說構造的不是很完整 // 并且我們必須在初始化列表中顯示的去調用基類中的拷貝構造函數(shù),那么這樣的話,如果派生類 // 中寫了拷貝構造函數(shù),那么基類中就必須有拷貝構造函數(shù),否則派生類中是無法重定義拷貝構造函數(shù)的 // 需要注意的是,為什么派生類的對象能夠直接當成基類的對象來使用呢? // 這是因為公有繼承的 is a 的關系,如果不是以public方式繼承的時候,此時就不是is a的關系了 Manager(const Manager &other) : Employee(other) { if (!other.employees_) { employees_ = new Employee(*(other.employees_)); } } Manager &operator=(const Manager &other) // 重定義 { // 必須也要調用基類的operator=運算符 Employee::operator=(other); if (!employees_) { employees_ = new Employee(*(other.employees_)); } else { *employees_ = *(other.employees_); } return *this; } ~Manager() { delete employees_; } int Salary(double multiple) // 函數(shù)重定義,只要函數(shù)名相同就會構成重定義,并且也不是覆蓋 { return static_cast<int>(salary_base_ * lev_ * multiple); } Employee *employees_;protected: int salary_base_; // 這個變量是重定義,數(shù)據(jù)的重定義并非覆蓋};// 不能被繼承的函數(shù)// 構造函數(shù) 析構函數(shù)int main(){ Employee *zs = new Employee("張三", 10); Manager *ls = new Manager("李四", 10); Manager ww = *ls; //Manager *ww = new Manager("王五", 12, zs); //*ls = *ww; // 只調用了子類的operator=運算符 //delete zs; //delete ww; //std::cout << ls->employees_->GetName() << std::endl; // //std::cout << ls->GetName() << std::endl; ////std::cout << zs.Salary() << std::endl; //std::cout << ls->Employee::Salary(1.5) << std::endl; //說明不是覆蓋,因為員工類中的Salary函數(shù)還是存在的 return 0;}另一個簡單的例子來說明原因 public的繼承方式
#define _CRT_SECURE_NO_WARNINGS#include <iostream>class A{};class B : public A{};int main(){ B b; A a = b; // 此時可以直接將派生類的對象轉換為基類的對象 A c; c = b; return 0;}#define _CRT_SECURE_NO_WARNINGS#include <iostream>class A{};class B : public A{public: int b_;};int main(){ A *pa = new A; B *pb = new B; A a; B b; pa = pb; // 派生類向基類轉換,就會產(chǎn)生對象切割(丟失特性) pa->b_; // 此時就無法訪問b_了,這就叫做丟失特性 pb = pa; // 基類不可以向基類轉化 a = b; b = a; return 0;}剩下的protected和private繼承就無法轉化了。讀者可以自行試驗。 但是我們可以使用強制轉換,但是這是很危險的一件事情,最好不要這樣做。
#define _CRT_SECURE_NO_WARNINGS#include <iostream>class A{};class B : public A{public: int b_;};int main(){ A *pa = new A; B *pb = new B; A a; B b; pa = pb; // 派生類向基類轉換,就會產(chǎn)生對象切割(丟失特性) pa->b_; // 此時就無法訪問b_了,這就叫做丟失特性 pb = pa; // 基類不可以向基類轉化 a = b; b = a; static_cast //做的是編譯器認可的轉化 reinterpret_cast //做的是編譯器不認可的轉換,是二進制直接轉換的 // 指針可以用它來轉換,但是對象無論如何都轉換不了的 return 0;}不能被繼承下來的函數(shù)只有構造函數(shù)和析構函數(shù)
這里我們就不再舉例子了