课程网站建设 碧辉腾乐,网站搭建报价,wordpress+做仿站,东莞常平新楼盘【c随笔12】继承 一、继承1、继承的概念2、3种继承方式3、父类和子类对象赋值转换4、继承中的作用域——隐藏5、继承与友元6、继承与静态成员 二、继承和子类默认成员函数1、子类构造函数 二、子类拷贝构造函数3、子类的赋值重载4、子类析构函数 三、单继承、多继承、菱形继承1… 【c随笔12】继承 一、继承1、继承的概念2、3种继承方式3、父类和子类对象赋值转换4、继承中的作用域——隐藏5、继承与友元6、继承与静态成员 二、继承和子类默认成员函数1、子类构造函数 二、子类拷贝构造函数3、子类的赋值重载4、子类析构函数 三、单继承、多继承、菱形继承1、单继承一个子类只有一个直接父类我们称这种继承关系为单继承。2、多继承一个子类有两个或以上直接父类我们称这种继承关系为多继承。3、菱形继承Diamond Inheritance是指在类继承关系中存在一个派生类同时继承自两个直接或间接基类并且这两个基类又共同继承自一个共同的基类从而形成了菱形状的继承结构。3.1、菱形继承可能引发以下问题3.2、为了解决菱形继承带来的问题可以采用以下方法 4、继承和组合 原创作者郑同学的笔记 原创地址https://zhengjunxue.blog.csdn.net/article/details/131795289 qq技术交流群921273910
C 是基于面向对象的程序面向对象有三大特性 —— 封装、继承、多态。
一、继承
1、继承的概念
继承inheritance机制是面向对象程序设计使代码可以复用的最重要的手段。它允许程序员在保持原有类特性的基础上进行扩展以增加功能。这样产生新的类称为派生类。继承呈现了面向对象程序设计的层次结构体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用而继承是类设计层次的复用。
#include iostream
#includestring
using namespace std;
class Person {/* 共有的信息 */
public:void print(){cout name m_name \nage m_age endl;}string m_name;int m_age;
};/* Student 公有继承了 Person */
class Student : public Person {string m_stuID; // 学号
};/* Teacher 公有继承了 Person */
class Teacher : public Person {string m_employeeID; // 工号
};int main()
{Person p;p.print();Student su;su.print();Teacher t;t.print();return 0;
}输出 继承的定义格式 Student 是 子类我们也称之为派生类。Person 是父类我们也称之为 基类。
class 派生类名:继承方式 基类名{派生类新增加的成员
};2、3种继承方式
访问限定符public / protected / private
下表汇总了不同继承方式对不同属性的成员的影响结果
继承方式/基类成员public成员protected成员private成员public继承publicprotected不可见protected继承protectedprotected不可见private继承privateprivate不可见
由于 private 和 protected 继承方式会改变基类成员在派生类中的访问权限导致继承关系复杂所以实际开发中我们一般使用 public。
3、父类和子类对象赋值转换
子类对象可以赋值给父类的对象、父类的指针、父类的引用
#include iostream
#includestring
using namespace std;
class Person {/* 共有的信息 */
public:void print(){cout name m_name \nage m_age endl;}string m_name;int m_age;
};/* Student 公有继承了 Person */
class Student : public Person {string m_stuID; // 学号
};int main()
{Student s;Person p s;Person* pointer_p s;Person ref_p s;p.print();pointer_p-print();ref_p.print();return 0;
}
输出 注意事项
① 父类对象不能赋值给子类对象
Student s; // 子类
Person p; // 父类s p; ❌ ② 父类的指针可以通过强转赋值给子类的指针但是必须是父类的指针是指向子类对象时才是安全的。 这里父类如果是多态类型可以使用 RTTIRun-Time Type Information即运行时类型识别的 dynamic_cast 来进行识别后进行安全转换。 #include iostream
#includestring
using namespace std;
class Person {/* 共有的信息 */
public:void print(){cout name m_name \nage m_age endl;}string m_name;int m_age;
};/* Student 公有继承了 Person */
class Student : public Person {
public:string m_stuID; // 学号
};int main()
{Student s;// 父类的指针可以通过强制类型转换赋值给子类的指针Person* pointer_p s;Student* pointer_s (Student*)pointer_p;pointer_s-m_stuID;Person p;// 这种情况虽然可以但是会存在越界访问问题Person* pointer_p2 p;Student* pointer_s2 (Student*)pointer_p2;pointer_s-m_stuID;return 0;
}4、继承中的作用域——隐藏
继承体系中的父类和子类都有独立的作用域如果子类和父类有同名成员此时子类成员会屏蔽父类对同名成员的直接访问这种情况叫做 “隐藏” 有文章把它叫重定义其实我不建议这种叫法因为重定义指的是同一个作用域重复定义。 在子类成员函数中可以使用如下方式进行显式访问 基类::基类成员 – 例如在Student类中 Person::print() #include iostream
#includestring
using namespace std;
class Person {/* 共有的信息 */
public:void print(){cout name m_name \nage m_age endl;}string m_name;int m_age;
};/* Student 公有继承了 Person */
class Student : public Person {
public:void print(){cout name m_name \nage m_age \nstuID m_stuID endl;}string m_stuID; // 学号
};int main()
{Student s;s.print();return 0;
}5、继承与友元
友元关系不能继承也就是说父类友元不能访问子类私有和保护成员
#include iostream
#includestring
using namespace std;class Student;
class Person {
public:friend void Display(const Person p, const Student s);/* 共有的信息 */protected:void print(){cout name m_name \nage m_age endl;}string m_name;int m_age;
};/* Student 公有继承了 Person */
class Student : public Person {protected:void print(){cout name m_name \nage m_age \nstuID m_stuID endl;}string m_stuID 110; // 学号
};void Display(const Person p, const Student s)
{cout p.m_name endl;cout s.m_stuID endl;
}int main()
{Person p;Student s;Display(p, s);return 0;
}报错 “Student::m_stuID”: 无法访问 protected 成员(在“Student”类中声明) 6、继承与静态成员
父类定义了 static 静态成员则整个继承体系里面中有一个这样的成员。 可以理解为共享父类的静态成员可以在子类共享父类和子类都能去访问它。 无论派生出多少个子类都只有一个 static 成员实例 #include iostream
#includestring
using namespace std;class Person {
public:Person() {m_count;}void print(){cout name m_name \nage m_age endl;}
protected:string m_name;int m_age;
public:static int m_count;
};int Person::m_count 0;
/* Student 公有继承了 Person */
class Student : public Person
{
protected:string m_stuID 110; // 学号
};int main()
{Student s1;Student s2;Student s3;Person s;cout 大家都可以访问 endl;cout 人数 : Person::m_count endl;cout 人数 : Student::m_count endl;cout 大家也都可以变动 endl;s3.m_count 0;cout 人数 : Person::m_count endl;cout 并且他们的地址也都是一样的因为所有继承体系中只有一个 endl;cout 人数 : Person::m_count endl;cout 人数 : Student::m_count endl;return 0;
}输出 二、继承和子类默认成员函数
1、子类构造函数
子类的构造函数必须调用父类的构造函数初始化父类的那一部分成员。 如果 父类没有默认的构造函数则必须在子类构造函数的初始化列表阶段显式调用。(如下面的demo) 子类对象初始化先调用父类构造再调子类构造。
#include iostream
#includestring
using namespace std;class Person {
public:Person(const char* m_name hello) {cout构造 Person \n;}
protected:string m_name;int m_age;
};class Student : public Person
{
public:Student(const char* name, int stuID) :Person(name), //如果 父类没有默认的构造函数则必须在子类构造函数的初始化列表阶段显式调用。m_stuID(stuID){cout 构造 Student \n;}
protected:int m_stuID; // 学号
};int main()
{Student s1(,18);return 0;
}思考如何设计一个不能被继承的类
将父类的构造函数私有化
class A
{
private:A() {}
};父类的构造函数私有化后在父类的外部父类自己也没法初始化了 单例模式可以这么做如下 class A {
public:static A CreateObject() { // 提供一个获取对象的方式return A();}
private:A() {}
};class B : public A {};int main(void)
{A a A::CreateObject();return 0;
}二、子类拷贝构造函数
子类的拷贝构造函数必须调用父类的拷贝构造完成拷贝初始化。
#include iostream
#includestring
using namespace std;class Person {
public:Person(const char* m_name hello) {cout构造 Person \n;}Person(const Person p):m_name(p.m_name){cout 拷贝构造 Person \n;}
protected:string m_name;int m_age;
};class Student : public Person
{
public:Student(const char* name, int stuID) :Person(name), //如果 父类没有默认的构造函数则必须在子类构造函数的初始化列表阶段显式调用。m_stuID(stuID){cout 构造 Student \n;}Student(const Student s):Person(s), //子类的拷贝构造函数必须调用父类的拷贝构造完成拷贝初始化。m_stuID(s.m_stuID){cout 拷贝构造 Student \n;}
protected:int m_stuID; // 学号
};int main()
{Student s1(haha,18);Student s2(s1);return 0;
}输出 3、子类的赋值重载
子类的 operator 必须要调用父类的 operator 完成父类的复制。
#include iostream
#includestring
using namespace std;class Person {
public:Person(const char* m_name hello) {cout构造 Person \n;}Person operator(const Person p){cout 赋值重载 Person \n;if(this ! p){m_name p.m_name;}return *this;}
protected:string m_name;int m_age;
};class Student : public Person
{
public:Student(const char* name, int stuID):Person(name), //如果 父类没有默认的构造函数则必须在子类构造函数的初始化列表阶段显式调用。m_stuID(stuID){cout 构造 Student \n;}Student operator(const Student s){cout 赋值重载 Student \n;if (this ! s){Person::operator(s); //子类的 operator 必须要调用父类的 operator 完成父类的复制。m_stuID s.m_stuID;}return *this;}
protected:int m_stuID; // 学号
};int main()
{Student s1(小白,18);Student s2(小黑, 18);s1 s2;return 0;输出 4、子类析构函数
为了保证子类对象先清理子类成员再清理父类成员的顺序先子后父。 子类析构先子后父子类对象的析构清理是先调用子类析构再调父类析构。 子类析构函数完成后会自动调用父亲的析构函数所以不需要我们显式调用。
#include iostream
#includestring
using namespace std;class Person {
public:Person(const char* m_name hello) {cout构造 Person \n;}~Person(){cout 析构 Person \n;}
protected:string m_name;int m_age;
};class Student : public Person
{
public:Student(const char* name, int stuID):Person(name), //如果 父类没有默认的构造函数则必须在子类构造函数的初始化列表阶段显式调用。m_stuID(stuID){cout 构造 Student \n;}~Student(){cout 析构 Student \n;}
protected:int m_stuID; // 学号
};int main()
{Student s1(小白,18);return 0;
}输出 三、单继承、多继承、菱形继承
1、单继承一个子类只有一个直接父类我们称这种继承关系为单继承。
2、多继承一个子类有两个或以上直接父类我们称这种继承关系为多继承。
3、菱形继承Diamond Inheritance是指在类继承关系中存在一个派生类同时继承自两个直接或间接基类并且这两个基类又共同继承自一个共同的基类从而形成了菱形状的继承结构。
下面是一个示例代码来说明菱形继承的概念
class Animal {
public:void eat() {cout Animal eats. endl;}
};class Mammal : public Animal {
public:void run() {cout Mammal runs. endl;}
};class Bird : public Animal {
public:void fly() {cout Bird flies. endl;}
};class Bat : public Mammal, public Bird {
public:void sleep() {cout Bat sleeps. endl;}
};在上述代码中Animal 是基类Mammal 和 Bird 是直接派生类而 Bat 是通过多重继承同时派生自 Mammal 和 Bird 的派生类。注意到 Mammal 和 Bird 都继承自 Animal这就形成了菱形继承结构。
3.1、菱形继承可能引发以下问题
二义性Ambiguity由于 Bat 同时继承自 Mammal 和 Bird如果两个基类都定义了相同的成员函数或变量编译器就无法确定该使用哪个版本从而导致二义性错误。冗余数据由于两个基类都继承自同一个基类 Animal当 Bat 对象被创建时会在内存中存在两份相同的 Animal 的数据。
3.2、为了解决菱形继承带来的问题可以采用以下方法
使用虚拟继承Virtual Inheritance在 Mammal 和 Bird 继承 Animal 时使用 virtual 关键字表示虚拟继承这样就可以消除冗余数据和二义性问题。
class Mammal : virtual public Animal {// ...
};class Bird : virtual public Animal {// ...
};使用间接继承在 Bat 类中只直接继承 Mammal 或 Bird 的一个而间接继承另一个基类的成员函数或变量。
class Bat : public Mammal {
private:Bird bird;
public:// 使用 bird 对象来访问 Bird 类中的成员
};菱形继承是多重继承中的一种特殊情况需要谨慎使用并采取适当的解决方案来避免引发问题。
4、继承和组合
继承和组合 public继承是一种 is-a 的关系。也就是说每个派生类对象都是一个基类对象。组合是一种 has-a 的关系。假设B组合了A每个B对象中都有一个A对象。优先使用对象组合而不是类继承 。继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用 white-box reuse。术语“白箱”是相对可视性而言在继承方式中基类的内部细节对子类可见 。 继承一定程度破坏了基类的封装基类的改变对派生类有很大的影响。派生类和基类间的依赖关 系很强耦合度高。对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对 象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用black-box reuse 因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系 耦合度低。优先使用对象组合有助于你保持每个类被封装。实际尽量多去用组合。组合的耦合度低代码维护性好。不过继承也有用武之地的有些关系就适 合继承那就用继承另外要实现多态也必须要继承。类之间的关系可以用继承可以用组合就用组合。 class A {// ...
};// 继承
class B : public A {};class C {// ...
};// 组合
class D {C _c;
};继承就是团体出行A 任何成员的修改都有可能影响 B 的实现。 组合就是自由出行C 只要不修改公有就不会对 D 有影响。