C++中的繼承詳解
繼承基本知識
定義:
繼承是面向對復用的重要手段。通過繼承定義一個類,繼承是類型之間的關系建模,共享公有的東西,實現各自本質不同的東西。
繼承關系:
三種繼承關系下基類成員的在派生類的訪問關系變化(圖)
舉個栗子(公有繼承)
```c++
class Person
{
public :
Person(const string& name)
: _name(name )
{}
void Display ()
{
cout<<_name <<endl;
}
protected :
string _name ; // 姓名
string _sex ;
class Student : public Person //公有繼承
{
protected :
int _num ; // 學號
};
### 繼承圖例解釋: ![這裏寫圖片描述](https://img-blog.csdn.net/2018042122253862?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4NjQ2NDcw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) **私有繼承和保護繼承很少用到,我們重點要掌握公有繼承** ### 繼承與轉換--賦值兼容規則--public繼承 1. 子類對象可以賦值給父類對象(切割/切片) 2. 父類對象不能賦值給子類對象 3. 父類的指針/引用可以指向子類對象 4. 子類的指針/引用不能指向父類對象(可以通過強制類型轉換完成) ```C++ class Person { public: void Display() { cout << "AA" << endl; } protected: string _name; // 姓名 }; class Student : public Person { public: int _num; // 學號 }; int main() { Person a; Student b; a = b; //子類對象賦值給基類對象(切片)這個特性是編譯器支持的 b = a; //父類對象不能賦值給子類對象 Person *p1 = &b; //特性3 //Person &a1 = b; //特性3 Student *p2 = (Student*)&a; //特性4 Student& b1 = (Student&)a; //特性4 getchar(); return 0; }
繼承體系中的作用域
- 在繼承體系中基類和派生類都有獨立的作用域。
- 子類和父類中有同名成員(成員函數,成員變量)子類成員將屏蔽父類對成員的直接訪問。(在子類成員函數中,可以使用 基類::基類成員 訪問)--隱藏(重定義)
- ==註意在實際中在繼承體系裏面最好不要定義同名的成員==。
class Person { public: Person(const char *name = "",int num = 0) :_name(name) ,_num(num) {}
protected:
string _name; // 姓名
int _num;
};
class Student : public Person
public:
Student(const char* name = "", const int num1 = 0, int num2 = 0)
:Person(name,num1)
,_num(num2)
{}
void Display()
{
cout << _num << endl;
cout <<Person:: _num << endl;//必須顯示指出基類作用域才能打印基類成員
}
protected:
int _num; // 學號
};
int main()
{
Person a("boday",15);
Student b("crash",1502,17);
b.Display();
return 0;
}
**運行結果:**
![這裏寫圖片描述](https://img-blog.csdn.net/20180422193345134?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4NjQ2NDcw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
可以很明顯看出此時打印的是子類的成員,而隱藏掉了父類的成員,(```這就是隱藏```)
### 派生類的默認成員函數
 在繼承關系裏面,在派生類中如果沒有顯示定義這六個成員函數,編譯系統則會默認合成這六個默認的成員函數。
![這裏寫圖片描述](https://img-blog.csdn.net/20180421222558568?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4NjQ2NDcw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
**來個栗子說說默認成員函數的前四個(` 後兩個不常用`)**
```cpp
class Person
{
public:
Person(const char *name = "",int num = 0) //父類構造函數
:_name(name)
,_num(num)
{}
~Person()//父類析構函數
{
cout << "~Person()" << endl;
}
Person(const Person& p)//父類拷貝構造函數
:_name(p._name)
,_num(p._num)
{}
Person& operator=(const Person& p)//父類賦值運算符重載
{
if (this != &p)
{
_name = p._name;
_num = p._num;
}
return *this;
}
protected:
string _name; // 姓名
int _num;
};
class Student : public Person
{
public:
Student(const char* name = "", const int num1 = 0, int num2 = 0)//子類構造函數
:Person(name,num1)
,_num(num2)
{}
~Student()//子類析構函數
{
cout << "~Student()" << endl;
}
Student(const Student& s)//子類拷貝構造函數
:Person(s)
,_num(s._num)
{}
Student& operator=(const Student& s)//子類賦值運算符重載
{
Person::operator=(s); //顯示調用父類賦值運算符重載
_num = s._num;
}
protected:
int _num; // 學號
};
先調用父類構造函數,在調用基類構造函數;析構函數調用順序與構造函數相反(先構造後析構,這個和棧的規則有關(先入後出))
繼承方式(單繼承,多繼承,菱形繼承)
1.單繼承
定義:一個子類只有一個直接父類時稱這個繼承關系為單繼承
代碼示例:
class A
{
protected:
int _a;
};
class B : public A //B類 繼承 A類
{
protected:
int _b;
};
2.多繼承
定義:一個子類有兩個或以上直接父類
時稱這個繼承關系為多繼承
代碼示例:
class A
{
protected:
int _a;
};
class B
{
protected:
int _b;
};
class C : public A,B
{
protected:
int _c;
};
3.菱形繼承
代碼示例:
class Person
{
public:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _num; //學號
};
class Teacher : public Person
{
protected:
int _id; // 職工編號
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修課程
};
void Test()
{
// 顯示指定訪問哪個父類的成員(二義性問題)
Assistant a;
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";//數據冗余問題
}
很明顯菱形繼承存在問題,存在二義性和數據冗余的問題。為了解決這個問題就引入了虛繼承。
虛繼承:解決菱形繼承的二義性和數據冗余的問題
在聲明派生類時,指定其繼承方式時聲明為虛繼承的方式。如
class A
{
public:
int _a;
};
class B : virtual public A //聲明為虛基類
{
protected:
int _b;
};
class C : virtual public A //聲明為虛基類
{
protected:
int _c;
};
class D : public B,public C
{
protected:
int _d;
};
看看測試效果:
void Test()
{
D d;
d._a = 10;
}
是不是很疑惑到底是如何解決的?那就要深入到底層探索下
這裏在虛繼承時用一個虛基表存放偏移量,這樣B和C類同時使用一個虛基表存放A相對於B和C的偏移量,當發生虛繼承時A會存放在一個公共區域,這就很好的解決了二義性問題,同時也節省了空間。
虛繼承很好的解決了菱形繼承帶來的問題。
這裏建議大家寫下代碼調試一下,同時查看內存變化。
C++中的繼承詳解