浅谈C++不同继承之间的关系

 更新时间:2023年04月18日 10:23:27   作者:编程小程  
本文主要介绍了浅谈C++不同继承之间的关系,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

公有继承:“是一个” 的关系

派生类与基类:

赋值兼容规则

C++面向对象编程中一条重要的规则是:公有继承意味着 “是一个” 。一定要牢牢记住这条规则。在任何需要基类对象的地方都可以用公有派生类的对象来代替,这条规则称赋值兼容规则。它包括

以下情况:

  • 派生类的对象可以赋值给基类的对象,这时是把派生类对象中从对应基类中继承来的隐藏对象赋值给基类对象。反过来不行,因为派生类的新成员无值可赋。
  • 可以将一个派生类的对象的地址赋给其基类的指针变量,但只能通过这个指针访问派生类中由基类继承来的隐藏对象,不能访问派生类中的新成员。同样也不能反过来做。
  • 派生类对象可以初始化基类的引用。引用是别名,但这个别名只能包含派生类对象中的由基类继承来的隐藏对象。

如下代码示例:

class Object
{
public:
int value;
public:
Object(int x = 0) :value(x) {}
~Object() {}
void print(int x)
{
value = x;
cout << value << endl;
}
};
class Base : public Object
{
public:
int num;
public:
Base(int x = 0):Object(x),num(x+10) {}
};
int main()
{
Base base(10);
Object obja(0);
Object *op = &base;
Object &ob = base;
obja = base;
return 0;
}

继承关系中的构造函数与析构函数

class Person
{
	int _id;
public:
	Person(int id) :_id(id)
	{
		cout << "Create Person " << this << endl;
	}
	~Person()
	{
		cout << "Destroy Person " << this << endl;
	}
};
 
class Student : public Person
{
	int _s_id;
 
public:
	Student(int id, int s, int n) :_s_id(s),  Person(id)
	{
		cout << "Create Student: " << this << endl;
	}
	~Student()
	{
		cout << "Destroy Student" << this << endl;
	}
};
int main()
{
	Student stud(90010, 202201, 23);
	return 0;
}

定义基类person,派生类student,当在主函数中创建一个派生类对象时,首先创建person对象,再创建student对象,析构时,先析构派生类对象,再析构基类对象

继承关系中拷贝构造函数

  • 程序设计者在基类和派生类中都没有定义拷贝构造函数;C++编译器将自动产生按位拷贝的拷贝构造函数;在派生类的拷贝构造函数的初始化表中,加入基类拷贝构造函数的调用,是C++编译器合成的代码;
  • 程序设计者在基类中定义拷贝构造函数;而在派生类中没有定义拷贝构造函数;C++编译器将会在派生类中自动产生按位拷贝的拷贝构造函数。并合成代码,调用(关联)基类的拷贝构造函数。
  • 程序设计者在基类和派生类中都定义了拷贝构造函数;程序设计者在派生类中,没有指定调用基类的拷贝构造函数时。C++编译器合成的代码调用基类的缺省构造函数,如果基类中没有缺省构造函数。合成代码失败。
  • 程序设计者在基类中没有定义拷贝构造函数(C++编译器将自动产生按位拷贝的拷贝构造函数)。而在派生类中定义了拷贝构造函数。程序设计者在派生类中,没有指定调用基类的拷贝构造函数时。C++编译器合成的代码调用基类的缺省构造函数,如果基类中没有缺省构造函数。

继承关系中赋值运算符的重载

  • 程序设计者在基类和派生类中都没有重载operator=函数; C++编译器将在基类和派生类中自动产生按位赋值的,重载operator=函数;C++编译器会在派生类的重载赋值函数中,加入基类重载赋值函数的调用,是C++编译器合成的代码;(完成行为的统一);
  • 程序设计者在基类中定义重载赋值函数;而在派生类中没有定义重载赋值函数;C++编译器将会在派生类中自动产生按位赋值的重载赋值函数。并合成代码,调用(关联)基类的重载赋值函数。
  • 程序设计者在基类和派生类中都定义了重载赋值函数;程序设计者在派生类中,没有指定调用基类的重载赋值函数时。C++编译器不会合成调用基类的重载赋值函数的代码。要在派生类的重载赋值函数调用基类的重载赋值函数,程序设计者必须自己加入调用代码。
  • 程序设计者在基类中没有定义重载赋值函数(C++编译器将自动产生按位赋值的重载赋值函数。而在派生类中定义了重载赋值函数。程序设计者在派生类中,没有指定调用基类的重载赋值函数。C++编译器不会合成调用基类的重载赋值函数的代码。

通过组合体现 “有一个” 或 “用…来实现”

使某个类的对象成为另一个类的数据成员,从而实现将一个类构筑在另一个类之上,这一过程称为

"组合“,分层;

组合

通过组合来体现 “有一个” 或 “用…来实现”。
例如,“汽车有一个发动机 或 汽车用发动机来实现 ” (has-a) 关系可以用单一组合表示为:

class Engine // 发动机
{
private:
int cylinderNum; // 气缸数
public:
Engine(int n = 4) :cylinderNum(n) {}
void Start(); // 启动
};
class Car
{
private:
Engine eg;
public:
Car():eg(8) {}
void StartCar();
};

**组合关系:**通过组合体现 “有一个” 或 “用…来实现”。组合是一种耦合度更强的关联关系。存在组合关系的类表示“整体-部分”的关联关系,“整体”负责“部分”的生命周期,他们之间是共生共死的;并且“部分”单独存在时没有任何意义。

同样的“有一个”关系也能用私有继承表示:

class Engine // 发动机
{
private:
int cylinderNum; // 气缸数
public:
Engine(int n = 4) :cylinderNum(n) {}
void EnStart(); // 启动
};
class Door
{
private:
int doorNum;
public:
Door(int n = 5) :doorNum(n) {}
}
class Car : private Engine
{
public:
Car() :Engine(8) {}
void StartCar(); //通过发动引擎来发动这辆汽车
};

私有继承: 要表示类之间 “用…来实现” 的关系,可以选择是通过私有继承实现。现在这种情况下,这一技术就比分层更有优势,因为通过它可以让你告诉别人:Engine使用起来不安全,它只能用来实现其它的类

**聚合关系:**通过聚合体现 “有一个” 或 “用…来实现”。 整体类与局部类之间松耦合,相互独立。

class Engine // 发动机
{
private:
int cylinderNum; // 气缸数
public:
Engine(int n = 4) :cylinderNum(n) {}
void Start(); // 启动
};
class Car
{
private:
Engine *peg;
public:
Car():peg(nullptr) {}
void SetEngine(Engine *p) { peg = p;}
void StartCar();
};

总结

公有继承与组合的区别

继承与组合都是面向对象中代码复用的方式。

公有继承: 父类的内部细节对子类可见,其代码属于白盒式的复用;
例如: class Person ; class Student;

公有继承的优缺点

优点:

  • 支持扩展,通过继承父类,可以设计较为复杂的系统,体现了由简单到复杂的认识过程。
  • 易于修改被复用的代码。

缺点:

  • 代码白盒复用,父类的实现细节暴露给子类,破坏了封装性。
  • 当父类的实现代码修改时,可能使得子类也不得不修改,增加维护难度。
  • 子类缺乏独立性,依赖于父类,耦合度较高。
  • 不支持动态拓展,在编译期就决定了父类。

组合和私有继承

**组合:**意味着 “用…来实现”; 对象之间的内部细节不可见,其代码属于黑盒式复用。
私有继承意味着 “用…来实现”; 是组合关系,父类的内部细节对子类不可见,其代码属于黑盒式复用。

优点:

  • 代码黑盒复用,被包括的对象内部实现细节对外不可见,封装性好。
  • 整体类与局部类之间松耦合,相互独立。
  • 支持扩展每个类只专注于一项任务
  • 支持动态扩展,可在运行时根据具体对象选择不同类型的组合对象(扩展性比继承好)。

缺点:

  • 创建整体类对象时,需要创建所有局部类对象。导致系统对象很多。

公有继承与私有继承和组合如何选择?

在对象分析时明确具有是一个(is - a) 的关系,使用公有继承。
在对象分析时明确具有 “有一个” 或 "用…来实现"关系,使用组合和私有继承。

私有继承和组合如何选择?

答案很简单:尽可能地使用组合,必须时才使用私有继承。什么时候必须呢?这往往是指有保护成员
和/或虚函数介入的时候考虑考虑使用私有继承。
//私有继承在编码过程中就要指定具体的父类,其关系在编译期就确定,而组合的关系一般在运行时确定。

到此这篇关于浅谈C++不同继承之间的关系的文章就介绍到这了,更多相关C++继承关系内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:

相关文章

  • C++内存管理详细解析

    C++内存管理详细解析

    这篇文章主要给大家分享的是C++内存管理的详细内容学习,下面文章围绕C++内存管理的相关资料展开具体学习内容,需要的朋友可以参考一下,希望对你有所帮助
    2021-11-11
  • C++深入讲解类与封装的概念与使用

    C++深入讲解类与封装的概念与使用

    我们都知道C++有三大特性:封装、继承、多态,现在我们来总结一下封装的相关知识与类的概念,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-04-04
  • C++中4种管理数据内存的方式总结

    C++中4种管理数据内存的方式总结

    根据用于分配内存的方法,C++中有3中管理数据内存的方式:自动存储、静态存储和动态存储。在存在时间的长短方面,以这三种方式分配的数据对象各不相同。下面简要介绍这三种类型
    2022-09-09
  • 深入C++中struct与class的区别分析

    深入C++中struct与class的区别分析

    本篇文章是对C++中struct与class的区别进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • 详解C语言正确使用extern关键字

    详解C语言正确使用extern关键字

    这篇文章主要介绍了C语言正确使用extern关键字,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • C++解析特殊符号tab、换行符号实例代码

    C++解析特殊符号tab、换行符号实例代码

    这篇文章主要给大家介绍了关于C++解析特殊符号tab、换行符号的相关资料,这个功能在我们日常开发中经常会遇到,需要的朋友可以参考下
    2021-05-05
  • visual studio 2019编译c++17的方法

    visual studio 2019编译c++17的方法

    这篇文章主要介绍了visual studio 2019编译c++17的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • C语言实现 数据类型占多少字节指针占多少字节

    C语言实现 数据类型占多少字节指针占多少字节

    这篇文章主要介绍了 C语言 数据类型占多少字节指针占多少字节的实例代码,代码简单易懂,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-09-09
  • 探讨:用两个栈实现一个队列(我作为面试官的小结)

    探讨:用两个栈实现一个队列(我作为面试官的小结)

    作为面试官的我,经常拿这道用两个栈实现一个队列的面试题来考面试者,通过对面试者的表现和反应,有一些统计和感受,在此做个小结
    2013-05-05
  • QT使用Http协议通信的实现示例

    QT使用Http协议通信的实现示例

    使用QT进行应用开发时,有时候需要进行客户端和服务端的网络通信,本文主要介绍了QT使用Http协议通信的实现示例,具有一定的参考价值,感兴趣的可以了解一下
    2023-12-12

最新评论