资讯详情

资讯详情

C++---继承(一)

1、继承的概念继承也就是类设计层次复用继承 (inheritance) 机制是面向对象程序设计使代码可以复用的最重要的手段它允许我们在保持原有类特性的基础上进行扩展增加方法 (成员函数) 和属性 (成员变量)这样产生新的类称派生类。继承呈现了面向对象程序设计的层次结构体现了由简单到复杂的认知过程。以前我们接触的函数层次的复用继承是类设计层次的复用。下面我们看到没有继承之前我们设计了两个类 Student 和 TeacherStudent 和 Teacher 都有姓名 / 地址 / 电话 / 年龄等成员变量都有 identity 身份认证的成员函数设计到两个类里面就是冗余的。当然他们也有一些不同的成员变量和函数比如老师独有成员变量是职称学生的独有成员变量是学号学生的独有成员函数是学习老师的独有成员函数是授课。class Person { public: // 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity() { cout void identity() _name endl; } protected: string _name 张三; // 姓名 string _address; // 地址 string _tel; // 电话 int _age 18; // 年龄 }; class Student : public Person { public: // 学习 void study() { // ... } protected: int _stuid; // 学号 }; class Teacher : public Person { public: // 授课 void teaching() { //... } protected: string title; // 职称 }; int main() { Student s; Teacher t; s.identity(); t.identity(); return 0; }这里的运行结果是Person基类/ \Student Teacher 派生类student中继承到的内容是class Student : public Person {public:void study() { ... }protected:int _stuid; // 学号};在teacher中继承到的是class Teacher : public Person {public:void teaching() { ... }protected:string title; // 职称};protected成员不能再类外访问继承的定义既叫基类/派生类也叫父类/子类继承基类成员访问方式的变化基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。基类private成员在派生类中是不能被访问如果基类成员不想在类外直接被访问但需要在派生类中能访问就定义为protected。可以看出保护成员限定符是因继承才出现的。实际上面的表格我们进行一下总结会发现基类的私有成员在派生类都是不可见。基类的其他成员在派生类的访问方式 Min (成员在基类的访问限定符继承方式)public protected private。使用关键字class时默认的继承方式是private使用struct时默认的继承方式是public不过最好显式的写出继承方式。在实际运用中一般使用都是public继承几乎很少使用protected/private继承也不提倡使用protected/private继承因为protected/private继承下来的成员都只能在派生类的类里面使用实际中扩展维护性不强。public公有类内、类外、派生类都可以直接访问。protected保护✅ 类内可以访问 ✅派生类内部可以访问❌ 类外部对象.不能访问private私有✅ 只有本类内部能访问 ❌ 派生类、类外都不能直接访问2、基类和派生类之间的转化通常情况下我们把一个类型的对象赋值给另一个类型的指针或者引用时存在类型转换中间会产生临时对象所以需要加 const如int a 1; const double d a;public 继承中就是一个特殊处理的例外派生类对象可以赋值给基类的指针 / 基类的引用而不需要加 const这里的指针和引用绑定是派生类对象中的基类部分如下图所示。也就意味着一个基类的指针或者引用可能指向基类对象也可能指向派生类对象。派生类对象赋值给基类对象是通过基类的拷贝构造函数或者赋值重载函数完成的 (这两个函数的细节后面小节会细讲)这个过程就像派生类自定义部分成员切掉了一样所以也被叫做切割或者切片如下图中所示。基类对象不能赋值给派生类对象。基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型可以使用 RTTI (Run-Time Type Information) 的dynamic_cast来进行识别后进行安全转换。考点public 继承派生类对象可以直接赋值给基类指针 / 引用不用 const指针引用绑定派生对象里的基类部分。派生类对象赋值给基类对象 →切片切割只拷贝基类那部分派生独有成员丢掉。普通情况基类对象不能直接赋值给派生类对象。基类指针转派生指针要强制转换只有基指针原本就指向派生对象才安全多态可用dynamic_cast安全转换。3、继承中的作用域很重要triple star1 隐藏规则1. 在继承体系中基类和派生类都有独立的作用域。2.派生类和基类中有同名成员派生类成员将屏蔽基类对同名成员的直接访问这种情况叫隐藏。在派生类成员函数中可以使用基类::基类成员显示访问3. 需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏。4. 注意在实际中在继承体系里面最好不要定义同名的成员。class Person { protected: string _name ⼩李⼦; // 姓名 int _num 111; // ⾝份证号 }; class Student : public Person { public: void Print() { cout 姓名: _name endl; cout ⾝份证号: Person::_num endl; cout 学号: _num endl; } protected: int _num 999; // 学号 }; int main() { Student s1; s1.Print(); return 0; };运行结果是姓名:小李子身份证号:111在这里在派生类成员里使用“基类基类成员 ”来访问学号:999图片中的文字提取如下4、 4个常见默认成员函数6个默认成员函数默认的意思就是指我们不写编译器会变我们自动生成一个那么在派生类中这几个成员函数是如何生成的呢1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数则必须在派生类构造函数的初始化列表阶段显示调用。class Person { protected: string _name; public: // 父类没有默认构造因为写了带参的编译器就不给了 Person(const string name) : _name(name) { cout Person 构造 endl; } }; class Student : public Person { protected: int _id; public: // 【重点】子类必须在初始化列表显式调用父类构造 Student(const string name, int id) : Person(name), // 必须写不写就报错 _id(id) { cout Student 构造 endl; } };2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。class Student : public Person { public: // 默认生成的拷贝构造大概长这样 Student(const Student s) : Person(s), // 自动调用父类拷贝构造把 _name 也拷过去 _id(s._id) // 拷贝自己的成员 {} };3. 派生类的operator必须要调用基类的operator完成基类的复制。需要注意的是派生类的operator隐藏了基类的operator所以显示调用基类的operator需要指定基类作用域class Person { protected: string _name; public: Person operator(const Person p) { cout Person operator endl; _name p._name; return *this; } }; class Student : public Person { protected: int _id; public: // 如果子类自己写了 operator就会【隐藏】父类的 operator Student operator(const Student s) { // 必须显式指定作用域调用父类的赋值否则只会拷贝自己的 _id Person::operator(s); _id s._id; return *this; } };4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。5.派生类对象初始化先调用基类构造再调派生类构造。#include iostream using namespace std; class Person { public: Person() { cout 1. 执行 Person 的构造函数 (基类) endl; } }; class Student : public Person { public: Student() { // 就算你这里什么都不写编译器也会自动帮你调用 Person() cout 2. 执行 Student 的构造函数 (派生类) endl; } }; int main() { cout --- 准备创建 Student 对象 --- endl; Student s; // 这里触发构造 cout --- Student 对象创建完毕 --- endl; return 0; }6. 派生类对象析构清理先调用派生类析构再调基类的析构。7. 因为多态中一些场景析构函数需要构成重写重写的条件之一是函数名相同(这个我们多态章节会讲解)。那么编译器会对析构函数名进行特殊处理处理成destructor()所以基类析构函数不加virtual的情况下派生类析构函数和基类析构函数构成隐藏关系。4、6、7点class Person { public: // 注意这里没加 virtual ~Person() { cout ~Person 析构 endl; } }; class Student : public Person { public: ~Student() { cout ~Student 析构 endl; } }; int main() { Student s; // 当 main 结束时s 的析构过程 // 1. 先调用 ~Student() 清理子类成员 // 2. 编译器自动调用 ~Person() 清理父类成员 }析构函数名字因为后续多态重写章节的原因会被处理成destructor所以派生类和基类析构构成隐藏关系。派生类析构调用后会自动调用父类析构所以自己实现析构时不需要显示调用析构初始化先父类后子类。析构清理资源先子后父。5、继承和友元友元不能被继承也就是说基类友元不能访问派生类私有和保护成员。友元关系不能传递#include iostream using namespace std; // 提前声明一下 Student 类 class Student; // 这是一个外部类想要成为 Person 和 Student 的“朋友” class Teacher { public: void visitPerson(Person p); void visitStudent(Student s); }; // 基类Person class Person { // 给 Teacher 发了一张通行证 friend class Teacher; private: string _name 张三; // 私有成员 }; // 派生类Student class Student : public Person { private: int _id 1001; // 派生类自己的私有成员 }; // Teacher 的实现 void Teacher::visitPerson(Person p) { // 【成功】Teacher 是 Person 的友元可以访问 Person 的 private cout 访问父类私有成员: p._name endl; } void Teacher::visitStudent(Student s) { // 【失败编译报错】友元不能继承 // 虽然 Teacher 是 Person 的友元但 Student 并不认账 // cout s._id endl; // 报错_id 是 private // 【成功】但 Teacher 依然可以访问 Student 从父类继承来的 _name // 因为访问 s._name 相当于访问 (Person)s._name而 Teacher 是 Person 的友元 cout 访问子类继承自父类的私有成员: s._name endl; } int main() { Person p; Student s; Teacher t; t.visitPerson(p); t.visitStudent(s); // 注意这里只能打印出父类的 _name打印不了子类的 _id return 0; }
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →