C++继承模式详解

目录
  • 继承
    • 继承的概念
    • 继承的定义
    • 继承关系和访限定符
      • 继承方式
      • 父类和子类对象赋值转化
  • 继承中的作用域
  • 子类的默认成员函数
  • 继承与友元
    • 继承与静态成员
  • 复杂的菱形继承
    • 虚继承
  • 继承的总结

继承

继承的概念

继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

继承的定义

上面的基类也可以叫父类,派生类也可以叫子类。

继承关系和访限定符

继承方式

接下来用代码测试上面的继承方式

class Person
{
public :
 void Print ()
 {
 cout<<_name <<endl;
 }
protected :
 string _name = "张三" ; // 姓名
private :
 int _age = 18 ; // 年龄
};

class Student : public Person
{
protected :
 int _stunum = 22; // 学号
};

public继承

上面是给的缺省值来测试没写构造函数

s就继承了Person的name,age,基类中private的age在物理上继承了但在语法上但是不能访问的。

也可以调用基类的成员函数,但是不能直接访问基类中private的成员,prootected可以在派生类中访问,不能再在类外访问

protected继承

protected继承,在类外连基类的public成员函数都不能用了,只能在派生类的类里面使用。

同样基类中私有的不能访问

private继承就都是私有的了。

总结:

  • 1.基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
  • 2.基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的.
  • 3.基类的私有成员在子类中都是不可见的,其他成员在子类中等于权限最小的那个
  • 4.class的默认继承方式是private,struct默认的继承方式是public,最好显示的写出继承方式
  • 5.在实际应用一般使用public继承,很少使用protected和private。

父类和子类对象赋值转化

class Person
{
protected:
	string _name; // 姓名
	string _sex; // 性别
	int _age; // 年龄
};
class Student : public Person
{
public:
	int _No; // 学号
};

子类可以给父类,父类不能给子类,不仅可以是子类的对象,也可以是指针和引用

	Student s;
	Person p;
	p = s;
	Person *ptr = &s;//子类赋给父类指针
	Person &ref = s;//子类赋给父类引用

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。

基类对象不能赋值给派生类对象

基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。等到子类中的默认函数就会用到切片

继承中的作用域

class Person
{
protected:
	string _name = "法外狂徒"; // 姓名
	int _num = 11; // 身份证号
};
class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		cout << " 身份证号:" << _num << endl;
		cout << " 学号:" << _num << endl;
	}
protected:
	int _num = 2; // 学号
};

还有成员函数的隐藏

class A {
public:
	void fun(double x)
	{
		cout << "fun()->x"<< x << endl;
	}
};
class B : public A {
public:
	void fun(int i)
	{
		cout << "fun()->" << i << endl;
	}
};

int main()
{
	B b;
	b.fun(10);
	b.A::fun(11.1);//加作用域
	return 0;
}

父类和子类函数名相同不是重载而是隐藏,函数重载是在同一作用域,不同的作用域是隐藏

在子类成员函数中,可以使用 基类::基类成员 显示访问

在写代码中最好不要定义同名的成员

子类的默认成员函数

在类和对象的时候讲了6个默认的成员函数,现在子类中讲4个,构造,拷贝构造,赋值和析构

class Person //父类
{
public:
	Person(const char* name = "李四")
		: _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)
		: Person(name)//调用父类的构造函数初始化父类的成员
		, _num(num)//初始化子类的成员
	{
		cout << "Student()" << endl;
	}
	//拷贝构造
	Student(const Student& s)
		: Person(s)//这里就用到了切片,切父类的成员类拷贝
		, _num(s._num)//拷贝子类的
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator = (const Student& s)
	{
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator =(s);//调用父类的赋值
			_num = s._num;//赋值子类自己的
		}
		return *this;
	}

	~Student()
	{
		//子类的析构函数完成清理后会自动调用父类的析构函数
		cout << "~Student()" << endl;
	}
protected:
	int _num; //学号
};

总结:

  • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  • 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  • 派生类的operator=必须要调用基类的operator=完成基类的复制。
  • 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
  • 派生类对象初始化先调用基类构造再调派生类构造。
  • 派生类对象析构清理先调用派生类析构再调基类的析构。

继承与友元

友元关系不能继承,父类友元不能访问子类私有和保护成员

class Student;
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name; // 姓名
};
class Student : public Person
{
public:
	friend void Display(const Person& p, const Student& s);
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;
	Display(p, s);
	return 0;
}

继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。

class Person
{
public:
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:
	int _id; // 学号
};
class Graduate : public Student
{
protected:
	string _Course; // 科目
};

int main()
{
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " 人数 :" << Person::_count << endl;
	cout << " 人数 :" << Student::_count << endl;
	cout << " 人数 :" << &Person::_count << endl;
	cout << " 人数 :" << &Student::_count << endl;
	return 0;
}

再加上count的地址可以看出是同一个的count。计算出子类实例化了多少个对象就可以在父类中定义个count自加。

复杂的菱形继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

菱形继承:菱形继承是多继承的一种特殊情况。

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。

class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};
class Teacher : public Person
{
protected:
	int _id; // 编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _Course; // 课程
};
int main()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	//a._name = "peter";

	// 显示的调用解决了二义性,但数据冗余了
	a.Student::_name = "盖伦";
	a.Teacher::_name = "亚索";
	return 0;
}

虚继承

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。

在菱形的腰部加上virtual关键字可以解决冗余。那虚继承是怎么解决的呢?先来看看不用虚继承的

class A {
public:
	int _a;
};

class B :  public A {
public:
	int _b;
};

class C :  public A {
public:
	int _c;
};
class D : public B, public C {
public:
	int _d;
};
int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

我们可以通过内存窗口来观察对象成员的模型

菱形继承带来了二义性和数据冗余。

再来看看虚继承的

虚继承就解决了数据冗余和二义性,B和C中多了地址,在用内存窗口看看这里的地址

2个指针叫虚基表指针指向虚基表,可以通过偏移量找到公共虚基类,此时A是在下面那为什么要找呢?

	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	B b = d;//把d赋给b,把d切过去那此时要怎么找到A呢?,所以就要用虚基表找

B类中各个成员在内存中的分布:

通过偏移量找到虚基类。

还是不要用菱形继承出现问题,虚继承使得对象模型很复杂,并且会有效率的影响。

继承的总结

  • C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。
  • 多继承可以认为是C++的缺陷之一.

组合

继承是建立了父类与子类的关系,是一种“是”的关系,例如白猫是猫,组合是“有”的关系实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合就用组合。

面试题

  • 什么是菱形继承?菱形继承的问题是什么?
    菱形继承是多继承的一种特殊继承,两个子类继承同一个父类,而又有子类同时继承这两个子类。可以看出菱形继承有数据冗余和二义性的问题。
  • 什么是菱形虚拟继承?如何解决数据冗余和二义性的
    在菱形继承的腰部加上virtual,通过虚基表指针和虚基表中的偏移量可以找到虚基类,只存1份
  • 继承和组合的区别?什么时候用继承?什么时候用组合?
    继承是一种"是",组合是"有"的关系,父类和子类是的关系用继承,是有的关系用组合。

以上就是C++继承,由于作者水平有限,如有问题还请指出!

到此这篇关于C++继承模式详解的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • C++中继承的概念和定义

    目录 1.继承的概念及定义 1.1继承的概念 1.2继承的定义格式 1.3继承基类成员访问方式的变化 (1)公有继承 (2)保护继承 (3)私有继承 1.4总结 2.基类和派生类对象赋值转换 3.继承中的作用域 总结 1.继承的概念及定义 1.1继承的概念 继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类.继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程.以前我们接触的复用都是函数复用,继承

  • 老生常谈C++ 中的继承

    继承 1 什么是继承 1.1 继承的概念 继承机制是面向对象程序设计使代码可以复用的最重要的手段,这个机制允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称为派生类.继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程.以前解除的都是函数复用,继承是类设计层次的复用. 代码演示如下 #include <iostream> #include <string> using namespace std; class Person { public: v

  • C++中的继承模式深入详解

    前言 继承是OOP设计中的重要概念.在C++语言中,派生类继承基类有三种继承方式:私有继承(private).保护继承(protected)和公有继承(public). 一.继承规则 继承是C++中的重要特性,派生类可以访问基类中的protected和public成员 先上代码: #include<iostream> using namespace std; class Base { private: void func_pri(); protected: void func_pro(); p

  • C++继承模式详解

    目录 继承 继承的概念 继承的定义 继承关系和访限定符 继承方式 父类和子类对象赋值转化 继承中的作用域 子类的默认成员函数 继承与友元 继承与静态成员 复杂的菱形继承 虚继承 继承的总结 继承 继承的概念 继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能.继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程. 继承的定义 上面的基类也可以叫父类,派生类也可以叫子类. 继承关系和访限定符 继承方式 接下来用代码测试上面的

  • Javascript设计模式之装饰者模式详解篇

    一.前言: 装饰者模式(Decorator Pattern):在不改变原类和继承的情况下动态扩展对象功能,通过包装一个对象来实现一个新的具有原对象相同接口的新的对象. 装饰者模式的特点: 1. 在不改变原对象的原本结构的情况下进行功能添加. 2. 装饰对象和原对象具有相同的接口,可以使客户以与原对象相同的方式使用装饰对象. 3. 装饰对象中包含原对象的引用,即装饰对象是真正的原对象经过包装后的对象. 二.Javascript装饰者模式详解: 描述: 装饰者模式中,可以在运行时动态添加附加功能到对

  • Java设计模式之代理模式详解

    一.代理模式 代理模式就是有一个张三,别人都没有办法找到他,只有他的秘书可以找到他.那其他人想和张三交互,只能通过他的秘书来进行转达交互.这个秘书就是代理者,他代理张三. 再看看另一个例子:卖房子 卖房子的步骤: 1.找买家 2.谈价钱 3.签合同 4.和房产局签订一些乱七八糟转让协议 一般卖家只在签合同的时候可能出面一下,其他的1,2,4都由中介去做.那你问这样有什么用呢? 首先,一个中介可以代理多个卖房子的卖家,其次,我们可以在不修改卖家的代码的情况下,给他实现房子加价.打广告等等夹带私货的

  • Java设计模式中的门面模式详解

    目录 门面模式 概述 应用场景 目的 优缺点 主要角色 门面模式的基本使用 创建子系统角色 创建外观角色 客户端调用 门面模式实现商城下单 库存系统 支付系统 物流系统 入口系统 客户端调用 门面模式 概述 门面模式(Facade Pattern)又叫外观模式,属于结构性模式. 它提供一个统一的接口去访问多个子系统的多个不同的接口,它为子系统中的一组接口提供一个统一的高层接口.使得子系统更容易使用. 客户端不需要知道系统内部的复杂联系,只需定义系统的入口.即在客户端和复杂系统之间再加一层,这一层

  • Java结构型设计模式之组合模式详解

    目录 组合模式 应用场景 优缺点 主要角色 组合模式结构 分类 透明组合模式 创建抽象根节点 创建树枝节点 创建叶子节点 客户端调用 安全组合模式 创建抽象根节点 创建树枝节点 创建叶子节点 客户端调用 组合模式 组合模式(Composite Pattern)也称为整体-部分(Part-Whole)模式,属于结构型模式. 它的宗旨是通过将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示,使得客户端对单个对象和组合对象的使用具有一致性. 组合模式一般用来描述整体与部分的关系,它将对象

  • Java行为型设计模式之策略模式详解

    目录 1.策略设计模式定义 2.策略设计模式的有点与不足 3.策略设计模式的实现思路 4.代码示例 5.策略设计模式的应用场景 编程是一门艺术,大批量的改动显然是非常丑陋的做法,用心的琢磨写的代码让它变的更美观. 在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如超市促销可以釆用打折.送商品.送积分等方法. 在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有快速排序.归并排序.选

  • Java中的代理模式详解及实例代码

    java 代理模式详解 前言: 在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为"代理"的第三者来实现间接引用.代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务. 简单来说代理模式就是通过一个代理对象去访问一个实际对象,并且可以像装饰模式一样给对象添加一些功能. 静态代理 所谓静态代理即在程序运行前代理类就已经存在,也就是说我们编写代码的时候就已经把代理类的代码写好了,而动态代理则

  • PHP面向对象之事务脚本模式(详解)

    如下所示: /* 事务脚本模式: 类似于thinkphp中的model层,或者说就是操作数据库的类. 个人觉得实践中使用起来还是挺简单方便的,就是SQL语句写死了的话,灵活性就不够. 示例代码如下: */ namespace woo\process; abstract class Base{ static $DB; //pdo对象 static $stmts = array(); //sql语句句柄 function __construct (){ $dsn = \woo\base\Applic

  • JS中正则表达式只有3种匹配模式(没有单行模式)详解

    JS正则表达式对象模式仅有如下三种:  g (全文查找出现的所有 pattern) i (忽略大小写) m (多行查找) 即没有单行匹配模式,Singleline(单行模式):更改.的含义,使它与每一个字符匹配(包括换行符\n). 如java中 String regex = "(?s)(?<=interface).{0,500}(shutdown)";---------"."表示在一行. 但可以采用[\d\D]或[\w\W]或[\s\S]或(.|\s)*?来解

  • C++类中的继承实例详解

    C++类中的继承实例详解 实例效果: 实现代码: #include<iostream> #include<string> using namespace std; class Person { public: Person(const char* name = "abc") :_name(name) { cout << "Person()" << endl; } Person(const Person& pp)

随机推荐