目錄
繼承的定義
繼承類模板
派生類和基類之前的轉換
隱藏
派生類的默認成員函數
不能被繼承的類
繼承中的友元和靜態成員
繼承模型
繼承的定義
繼承的本質是一種復用。規定Person類為基類,Student類為派生類 。
繼承方式分為public繼承,protected繼承,private繼承。一般使用public繼承,對成員的限制private > protected > public.
public繼承,基類的private成員只能在基類使用,protected成員只能在基類和派生類中使用,public成員能在任意地方使用。
protect繼承,基類的private成員只能在基類使用,protected成員只能在基類和派生類中使用,public成員只能在基類和派生類中使用。
private繼承,基類的private成員只能在基類使用,protected成員只能在基類中使用,public成員只能在基類中使用。
//基類/父類
class Person
{
public://身份認證void identity(){cout << "void identity()" << endl;}void func(){_age++;}protected:string _name = "張三";string _address;string _tel;
private:int _age = 18;
};//子類/派生類
class Student : public Person
{
public://學習void study(){cout << "void study()" << endl;}//不能在派生類聲明時直接在派生類中來改變基類的成員變量//_name = "李四";void Set_name(){_name = "李四";}
protected:int _stuid;//學號
};//不顯示寫繼承方式 默認為private繼承
//class Teacher : Person
class Teacher : public Person
{
public:void teach(){cout << "void teach()" << endl;//派生類中無法訪問基類的private成員//age++;}protected:int _work_num;//工號};int main()
{Person p;p.identity();//基類調用成員函數Student s;s.identity();//派生類調用基類的成員函數Teacher t;t.identity();s.func();//調用基類的函數來改變基類的private變量s.Set_name();//調用成員函數可以該改變基類中的potected變量return 0;
}
繼承類模板
在復用容器中的函數時會報錯找不到標識符push_back()。
因為lzk::stack<int> s實例化了stack<int>和vector<int>,但是沒有實例化vector<int>::push_back(x)。
這里的問題本質上時編譯器對模板的兩階段查找規則
第一階段(模板定義階段)模板定義時自動觸發(無需實例化)
?? ??? ??? ?編譯器會檢查所有不依賴模板參數 T 的名稱(即非依賴名稱,如直接寫的 push_back)。
?? ??? ??? ?如果名稱未在當前作用域或可見基類中聲明,直接報錯(即使基類模板實例化后可能有該成員)。
第二階段(模板實例化階段)實例化模板時觸發(如 main() 中使用)
?? ??? ??? ?檢查所有依賴 T 的名稱(如 vector<T>::push_back)。
?? ??? ??? ?此時基類模板(如 vector<int>)已實例化,可以確認成員是否存在。
//基類為類模板
namespace lzk
{template<class T>class stack : public vector<T>{public:void push(const T& x){//報錯找不到標識符//push_back();//lzk::stack<int> s;時實例化了stack<int> 和vector<int>//但是沒有實例化vector<int>::push_back(x)//這里的問題本質上時編譯器對模板的兩階段查找規則/*第一階段(模板定義階段)編譯器會檢查所有不依賴模板參數 T 的名稱(即非依賴名稱,如直接寫的 push_back)。如果名稱未在當前作用域或可見基類中聲明,直接報錯(即使基類模板實例化后可能有該成員)。這就是你遇到的 push_back 報錯的根本原因。第二階段(模板實例化階段)檢查所有依賴 T 的名稱(如 vector<T>::push_back)。此時基類模板(如 vector<int>)已實例化,可以確認成員是否存在。*///階段1:模板定義時自動觸發(無需實例化)。//階段2:實例化模板時觸發(如 main() 中使用)。vector<int>::push_back(x);}void pop(){vector<int>::pop_back();}const T& top(){return vector<int>::back();}bool empty(){return vector<int>::empty();}};}int main()
{lzk::stack<int> s;s.push_back(1);s.push_back(2);s.push_back(3);s.push_back(4);while (!s.empty()){cout << s.top() << endl;s.pop();}return 0;
}
派生類和基類之前的轉換
public繼承的派生類對象可以賦值給基類的對象和指針和引用,但是基類對象不能賦值給派生類對象。?
//派生類對象和基類對象的轉換
class Person
{
protected:string _name;string _sex;int _age;
};class Student : public Person
{
public:int _num;
};int main()
{Student s;//派生類對象可以賦值給基類的指針引用Person* p = &s;Person& rp = s;//派生類對象賦值給基類對象,通過基類的拷貝構造完成Person pojb = s;return 0;
}
隱藏
我們知道不同的類,有不同的類域,他們是相互獨立的,如果基類和派生類中存在同名成員變量或同名成員函數,派生類成員將屏蔽基類對同名成員的直接訪問,即隱藏。?
class Person
{
protected:string _name = "李四";string _sex = "男";int _age = 18;
public:void func(){cout << "func()" << endl;}
};class Student : public Person
{
public:void print(){//將基類中的_age隱藏了cout << _age << endl;}int _num = 22;int _age = 19;void func(int i){cout << "void func(int i)" << endl;}
};int main()
{Student s;s.print();//通過派生類對象調用基類隱藏函數,需要指定類域s.func(1);s.Person::func();return 0;
}
派生類的默認成員函數
構造函數的調用順序是,先調用基類的構造函數,再調用派生類的構造函數;先析構派生類對象,再析構基類對象。?
如果顯示調用基類的析構,有兩個問題
1.在編譯過后,編譯器會將基類和派生類的析構函數名稱改為destructor,那么基類和派生類的析構函數就會構成隱藏關系,則需要指定類域調用
2.調用過后發現基類析構了兩次,編譯器為了析構的順序是先派生類再基類,會在調用派生類的析構后再調用基類析構,如果有動態資源就會報錯。
//派生類的默認成員函數//基類
class Person
{
public:Person(const string& name = "張三"):_name(name){cout << "Person(const string& name = 張三)" << endl;}Person(const Person& p):_name(p._name){cout << "Person(const Person& p)" << endl;}Person& operator=(const Person& p){cout << "Person& operator=(const Person& p)" << endl;if (this != &p){_name = p._name;}return *this;}~Person(){cout << "~Person()" << endl;_name = "";}
protected:string _name;};//派生類
class Student : public Person
{
public:Student(const string& n, int age, const string& name):Person(name), _n(n), _age(age){cout << "Student(const string& n, int age, const string& name)" << endl;}Student(const Student& s):Person(s)//顯示調用基類的拷貝構造,_n(s._n),_age(s._age){cout << "Student(const Student& s)" << endl;}Student& operator=(const Student& s){cout << "Student& operator=(const Student& s)" << endl;if (this != &s){_n = s._n;_age = s._age;Person::operator=(s);//顯示調用基類的賦值重載}return *this;}~Student(){//如果顯示調用基類的析構,有兩個問題//1.//在編譯過后,編譯器會將基類和派生類的析構函數名稱改為destructor(涉及到多態)//那么基類和派生類的析構函數就會構成隱藏關系//則需要指定類域調用//2.//調用過后發現基類析構了兩次//編譯器為了析構的順序是先派生類再基類 //會在調用派生類的析構后再調用基類析構 //如果有動態資源就會報錯//Person::~Person();cout << "~student()" << endl;}
private:string _n = "李四";int _age = 19;};int main()
{Student s("李好", 19,"張斌");Student s1(s);//Student s3("李一", 18,"李二" );//s1 = s3;return 0;
}
不能被繼承的類
實現一個不能被繼承的類有兩種方法
一是將基類的構造函數列為私有成員。
二是在基類的名字后面加final關鍵字。
//實現一個不能被繼承的類
//c++11
class Base final//加關鍵字
{
public: void func5() { cout << "Base::func5" << endl; }
protected:int a = 1;
private://c++98 //將構造函數設為私有//Base()//{};
};
//報錯
//class Student : public Base
繼承中的友元和靜態成員
友元關系不能被繼承,基類的友元函數不能訪問派生類的成員變量,除非給派生類也聲明友元。
在基類中定義了靜態成員,則在派生類中使用的也是這個成員,不會再開辟新的空間。
//繼承和友元//前置聲明
class Student;class Person
{
public:friend void print(const Person& p, const Student& s);
protected:int _age = 18;
};class Student : public Person
{
protected:string _name = "李思思";friend void print(const Person& p, const Student& s);
};void print(const Person& p, const Student& s)
{cout << p._age << endl;cout << s._name << endl;
}int main()
{Student s;Person p;print(p,s);return 0;
}//靜態成員class Person
{
public:string _name;static int _count;
};//靜態成員在類外定義
int Person::_count = 0;class Student : public Person
{
protected:int _stuNum;
};int main()
{Person p;Student s;//基類和派生類公用一個靜態變量cout << &p._count << endl;cout << &s._count << endl;//基類和派生類非靜態成員的地址是不一樣的cout << &p._name << endl;cout << &s._name << endl;//通過指定類域可以訪問靜態成員cout << &Person::_count << endl;cout << &Student::_count << endl;return 0;
}
繼承模型
單繼承:一個派生類繼承一個基類。
多繼承:一個派生類繼承多個基類。
菱形繼承:諸如兩個派生類繼承同一個基類后,再被同一個派生類繼承的情況。
菱形繼承會產生數據冗余和二義性的問題,為了解決這個問題就有了虛繼承,虛繼承就是在繼承方式前面加上virtual關鍵字。那么派生類對象就可以訪問基類公開成員了。
//單繼承 多繼承 菱形繼承
class Person
{
public:string _name = "李思思";
};class Student : public Person
{
public:int _nun;
};class Teacher : public Person
{
public:int _worknum;
};class Assistant : public Student, public Teacher
{
protected:string _course; // 主修課程
};int main()
{//對_name的訪問不明確//student類和teacher類中都有_nameAssistant a;//a._name = "lisisi";//指定類域可以訪問,解決二義性問題,但是存在數據冗余a.Student::_name = "lisisi";a.Teacher::_name = "lss";
}//虛繼承
//解決數據冗余和二義性class Person
{
public:string _name = "李思思";
};//加關鍵字virtaul
class Student : virtual public Person
{
public:int _nun;
};class Teacher : virtual public Person
{
public:int _worknum;
};class Assistant : public Student, public Teacher
{
protected:string _course; // 主修課程
};int main()
{Assistant a;a._name = "李思思";return 0;
}
任何足夠先進的科技都與魔法無異,但魔法背后的真相永遠是嚴謹的代碼邏輯。愿我們既能享受創造的浪漫,也能保持對技術的敬畏之心?!🚀