☰
继承的认识和使用
2026/10/21 11:36:42 网站建设 项目流程

目录

一、继承

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 降为 protected;private继承把所有可访问成员降为 private。

1.2 继承类模版

namespace lth { // template<class T> // class vector //{}; // stack和vector的关系,既符合is-a,也符合has-a template <class T> class stack : public std::vector<T> { public: void push(const T &x) { // 基类是类模板时,需要指定⼀下类域, // 否则编译报错:error C3861: “push_back”: 找不到标识符 // 因为stack<int>实例化时,也实例化vector<int>了 // 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到 vector<T>::push_back(x); // push_back(x); } void pop() { vector<T>::pop_back(); } const T &top() { return vector<T>::back(); } bool empty() { return vector<T>::empty(); } }; }

二、基类和派生类的转换

首先看一个代码他是会报错的

int main() { int a = 1; double& d = a; return 0; }

为什么会报错呢 是因为 d=a 会生成一个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关键字!

// C++11的⽅法 class Base final { public: void func5() { cout << "Base::func5" << endl; } protected: int a = 1; private: // C++98的⽅法 /*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),就⽤组合。

这是继承

template<class T> class stack : public vector<T> {};

这是组合

template<class T> class stack { public: vector<T> _v; };

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询