目录一、继承1.1.继承基类成员访问方式的变化1.2 继承类模版二、基类和派生类的转换三、派生类的默认成员函数构造函数拷贝构造函数赋值重载析构构造一个无法继承的类四、继承与友元五、继承与静态成员六、多继承和菱形继承问题使用虚继承七、继承和组合问题一、继承如果你不好好学习就只能回家继承家产跟我一起回家种地了呜呜呜。简单看一下代码吧#include iostream #include string class Person { public: void identity() { std::cout void identity() _name std::endl; } private: std::string _name 张三; std::string _addr; std::string _tel; int _age 18; }; class Student : public Person { public: void study() { } protected: int _stuid; }; class Teacher : public Person { public: void teach() { } protected: std::string title; }; int main() { Student s1; Teacher t1; s1.identity(); t1.identity(); return 0; }上述是简单的使用继承 简单来说就是学生类和教师类都继承了人类1.1.继承基类成员访问方式的变化继承方式有三种访问限定符也有三种都是 public protected private 三种众所周知啊被private和protected修饰的在类外是访问不了的 那private和protected有啥区别呢private vs protected区别在派生类你爹的很多东西都是你的以后但是他的私房钱他的手机这些私有的不是你的狗头虽然你用不了但是可以通过函数通过public的函数去访问这样组合是9种基类private成员在任何继承方式下都不可访问派生类无法直接使用。派生类中的访问权限 min(继承方式, 基类成员原权限)权限高低顺序public protected private。public继承保持原权限protected继承把 public 降为 protectedprivate继承把所有可访问成员降为 private。1.2 继承类模版namespace lth { // templateclass T // class vector //{}; // stack和vector的关系既符合is-a也符合has-a template class T class stack : public std::vectorT { public: void push(const T x) { // 基类是类模板时需要指定⼀下类域 // 否则编译报错:error C3861: “push_back”: 找不到标识符 // 因为stackint实例化时也实例化vectorint了 // 但是模版是按需实例化push_back等成员函数未实例化所以找不到 vectorT::push_back(x); // push_back(x); } void pop() { vectorT::pop_back(); } const T top() { return vectorT::back(); } bool empty() { return vectorT::empty(); } }; }二、基类和派生类的转换首先看一个代码他是会报错的int main() { int a 1; double d a; return 0; }为什么会报错呢 是因为 da 会生成一个double的临时变量 临时变量具有常性 一个具有const的变量怎么可以一个不具有const去接受呢int main() { int a 1; const double d a; return 0; }这样就不会再报错了这样就可以运行了派⽣类对象 可以赋值给 基类的指针 / 基类的引⽤⽽不需要加const这⾥的指针和引⽤绑定是派⽣类对象中的基类部分如果是派生类 赋值给了基类 会发生切割 就是切掉了一部分可以理解class Person { protected: std::string _name; // 姓名 std::string _sex; // 性别 int _age; // 年龄 }; class Student : public Person { public: int _No; // 学号 }; int main() { Student sobj; // 1.派⽣类对象可以赋值给基类的指针/引⽤ Person *pp sobj; Person rp sobj; // ⽣类对象可以赋值给基类的对象是通过调⽤后⾯会讲解的基类的拷⻉构造完成的 Person pobj sobj; // 2.基类对象不能赋值给派⽣类对象这⾥会编译报错 sobj pobj; return 0; }三、派生类的默认成员函数构造函数派生类的成员 就是生成默认的构造那基类的成员整体要调用基类的构造#include iostream #include vector using namespace std; class Person { public: Person(const char *name peter) : _name(name) { cout Person() 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; } protected: string _name; // 姓名 }; class Student : public Person { protected: int _num; }; int main() { Student s1; return 0; }如果这样就会有问题#include iostream #include vector using namespace std; class Person { public: Person(const char *name peter) : _name(name) { cout Person() 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; } protected: string _name; // 姓名 }; class Student : public Person { public: Student(const char* name, int num, const char* addr) :_name(name), _num(num), _addr(addr) { } protected: int _num; std::string _addr; }; int main() { // Student s1; return 0; }要改成这样class Student : public Person { public: Student(const char* name, int num, const char* addr) :Person(name), _num(num), _addr(addr) { } protected: int _num; std::string _addr; };初始化列表 走的顺序和顺序无关 和声明的顺序有关先父类后自己 是声明的顺序调用拷贝构造也是也会调用基类的拷贝构造函数派生类会给生成默认的浅拷贝存在深拷贝才会自己写拷贝构造函数因为基类也写了拷贝构造函数 这样就变成了Person p s;Student(const Student s) :Person(s),_num(s._num),_addr(s._addr) { }赋值重载一样的子类调用之类的父类调用父类的没有就使用默认的子类自定义赋值时必须手动调父类赋值Student operator(const Student s) { if(this ! s) { Person::operator(s); _num s._num; _addr s._addr; } }析构析构函数的名字因为多态的额原因会被处理成destructor 所以会构成隐藏关系~Student() { Person::~Person(); }派生类调用析构后会自动调用调用父类析构所以自己实现析构时候不需要显示调用~Student() { // Person::~Person(); }自动调用视为了先子后父 清理的时候构造一个无法继承的类1. 可以给构造函数的方法私有2. 也可以使用final关键字// C11的⽅法 class Base final { public: void func5() { cout Base::func5 endl; } protected: int a 1; private: // C98的⽅法 /*Base() {}*/ }; class Derive : public Base { void func4() { cout Derive::func4 endl; } protected: int b 2; }; int main() { Base b; Derive d; return 0; }使用final修饰即可四、继承与友元友元关系不能被继承就和你爹的朋友不是你的一样class Student; class Person { public: friend void Display(const Person p, const Student s); protected: string _name; // 姓名 }; class Student : public Person { protected: int _stuNum; // 学号 }; void Display(const Person p, const Student s) { cout p._name endl; cout s._stuNum endl; } int main() { Person p; Student s; // 编译报错error C2248: “Student::_stuNum”: ⽆法访问 protected 成员 // 解决⽅案Display也变成Student 的友元即可 Display(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; // 这⾥的运⾏结果可以看到⾮静态成员_name的地址是不⼀样的 // 说明派⽣类继承下来了⽗派⽣类对象各有⼀份 cout p._name endl; cout s._name endl; // 这⾥的运⾏结果可以看到静态成员_count的地址是⼀样的 // 说明派⽣类和基类共⽤同⼀份静态成员 cout p._count endl; cout s._count endl; // 公有的情况下⽗派⽣类指定类域都可以访问静态成员 cout Person::_count endl; cout Student::_count endl; return 0; }六、多继承和菱形继承问题有两个或者多个基类就是多继承下面就是菱形继承那棱形继承有啥问题吗D会包含两份A子对象一份来自B一份来自C。所以sizeof(D)里会有两份A的数据这会导致数据冗余同时具有二义性因为B和C都有 不知道来自于谁这二义性的解决方法 是使用类域但是数据冗余没有解决使用虚继承就可以使用虚继承来解决这个数据冗余和二义性的问题class Person { public: string _name; // 姓名 }; class Student :virtual public Person { protected: int _num; // 学号 }; class Teacher :virtual public Person { protected: int _id; // 职⼯编号 }; class Assistant : public Student, public Teacher { protected: string _majorCourse; // 主修课程 }; int main() { // 编译报错error C2385: 对“_name”的访问不明确 Assistant a; a._name peter; // 需要显⽰指定访问哪个基类的成员可以解决⼆义性问题但是数据冗余问题⽆法解决 a.Student::_name xxx; a.Teacher::_name yyy; return 0; }class Person { public: Person(const char *name) : _name(name) { } string _name; // 姓名 }; class Student : virtual public Person { public: Student(const char *name, int num) : Person(name), _num(num) { } protected: int _num; // 学号 }; class Teacher : virtual public Person { public: Teacher(const char *name, int id) : Person(name), _id(id) { } protected: int _id; // 职⼯编号 }; // 不要去玩菱形继承 class Assistant : public Student, public Teacher { public: Assistant(const char *name1, const char *name2, const char *name3) : Person(name3) , Student(name1, 1) , Teacher(name2, 2) { } protected: string _majorCourse; // 主修课程 }; int main() { // 思考⼀下这⾥a对象中_name是张三, 李四, 王五中的哪⼀个 Assistant a(张三, 李四, 王五); return 0; }试想一下我们去初始化 Assistant 的时候 调用构造函数 那studnet和teacher会不会调用person的构造函数呢是不是这样就一下子复杂了很多啊在编译器中他们是会忽略掉person的构造函数 就是初始化Assistant a这个时候调用他的构造函数的时候会调用student和teacher的构造函数 他俩的构造函数就会忽略掉person的构造函数所以一定要手动去调用person的构造函数七、继承和组合问题public继承是⼀种is-a的关系。也就是说每个派⽣类对象都是⼀个基类对象。组合是⼀种has-a的关系。假设B组合了A每个B对象中都有⼀个A对象优先使⽤组合⽽不是继承。实际尽量多去⽤组合组合的耦合度低代码维护性好。不过也不太那么绝对类之间的关系就适合继承(is-a)那就⽤继承另外要实现多态也必须要继承。类之间的关系既适合⽤继承(is-a)也适合组合(has-a)就⽤组合。这是继承templateclass T class stack : public vectorT {};这是组合templateclass T class stack { public: vectorT _v; };