目录
继承的定义
继承类模板
派生类和基类之前的转换
隐藏
派生类的默认成员函数
不能被继承的类
继承中的友元和静态成员
继承模型
继承的定义
继承的本质是一种复用。规定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;
}
任何足够先进的科技都与魔法无异,但魔法背后的真相永远是严谨的代码逻辑。愿我们既能享受创造的浪漫,也能保持对技术的敬畏之心 !🚀