C++ 继承(二)

2024-09-06 02:52
文章标签 c++ 继承

本文主要是介绍C++ 继承(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1. 实现一个不能被继承的类

 2. 友元与继承

 3.继承与静态成员

4.多继承及其菱形继承问题

(1). 继承模型

(2). 虚继承

(2.1)虚继承解决数据冗余和二义性的原理

(3). 多继承中指针偏移问题

(4). IO库中的菱形虚拟继承

5. 继承和组合


1. 实现一个不能被继承的类

方法1:父类的构造函数私有,子类构成必须调用父类的构造函数,但是父类的构造函数私有后,子类就不能调用了。那子类将无法实例化处对象。

 如下代码所示

#include<iostream>
#include<algorithm>using namespace std;
class Teacher
{
private:Teacher(){x = 1;}int x;
};class Student: public Teacher
{
public:Student(){ss = 1;}
private:int ss;
};

 因为父类构造函数不能调用出错

 

方法2:C++11中新增了final关键字,在父类,类名后加上final修饰子类就不能继承了

 代码如下

#include<iostream>
#include<algorithm>using namespace std; 
class Teacher final
{
public:Teacher(){x = 1;}
private:int x;
};class Student: public Teacher
{
public:Student(){ss = 1;}
private:int ss;
};

 不可以将其当做基类(父类)

 2. 友元与继承

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

 代码如下

#include<iostream>
#include<algorithm>using namespace std; class Student;//提前声明否则友元函数定义找不到Student
class Teacher 
{public:friend void playval( const Teacher& t, const Student& s);Teacher(int xx=11){x = xx;}
private:
//protected:int x;
};class Student: public Teacher
{
public:Student():Teacher(11){ss = 1;}
private:int ss;
};void playval(const Teacher& t,const Student& s )
{cout << t.x << endl;cout << s.ss << endl;
}int main()
{Teacher t(16);Student s;playval(t, s);
}

结果如图,不能找到s的私有成员

 当然子类的友元也不能访问父类的保护和私有成员

 如下代码所示

#include<iostream>
#include<algorithm>using namespace std; class Teacher 
{public:Teacher(int xx=11){x = xx;}
private:
//protected:int x;
};class Student: public Teacher
{
public:friend void inputval(const Teacher& t, const Student& s);Student():Teacher(11){ss = 1;}
private:int ss;
};void inputval(const Teacher& t, const Student& s)
{cout << t.x << endl;cout << s.ss << endl;
}int main()
{Teacher t(16);Student s;inputval(t, s);return 0;
}

结果如下图所示子类的友元函数找不到其父类的私有与保护成员 

 3.继承与静态成员

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

 如下代码所示

#include<iostream>
using namespace std;class Teacher
{
public:string _name;static int _count;
};
int Teacher::_count = 0;
class Student : public Teacher
{
protected:int _stuNum;
};
int main()
{Teacher t;Student s;// 这⾥的运⾏结果可以看到⾮静态成员_name的地址是不⼀样的 // 说明⼦类继承下来了,⽗⼦类对象各有⼀份 cout << &t._name << endl;cout << &s._name << endl;// 这⾥的运⾏结果可以看到静态成员_count的地址是⼀样的 // 说明⼦类和⽗类共⽤同⼀份静态成员 cout << &t._count << endl;cout << &s._count << endl;// 公有的情况下,⽗⼦类指定类域都可以访问静态成员 cout << Teacher::_count << endl;cout << Student::_count << endl;return 0;
}

运行结果如下

我们可以看到子类对象中的_name与父类对象中的_name地址不同,而子类对象与父类对象的_count地址是相同的。说明父类对象与子类对象共用一个静态成员

4.多继承及其菱形继承问题

(1). 继承模型

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

 如下图

 

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承,多继承对象在内存中的模型是  先继承的父类在前面,后继承的父类在后面,子类成员在最后面

 如下图

在内存中的分布如下

菱形继承:菱形继承是多继承的一种特殊情况。菱形继承的问题,从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题,在Assistant的对象中Person成员会有两份。支持多继承就一定会有菱形继承,实践中我们尽量不设计出菱形继承这样的模型

 

 代码演示

#include<iostream>
#include<string>using namespace std; class Person
{
public:string _name; // 姓名 
};
class Student : public Person
{
protected:int _num; 
};
class Teacher : public Person
{
protected:int _id; 
};
class headmaster : public Student, public Teacher
{
protected:string _Course; 
};

上述代码即为一个菱形继承

int main()
{// 编译报错: 对“_name”的访问不明确 headmaster a;a._name = "peter";return 0;
}

这样调用编译会报错

编译器不知道调用的是哪个父类中的_name

我们可以通过显式指定访问那个父类的成员来解决二义性的问题,但是无法解决数据冗余的问题

int main()
{//a.Student::_name = "xxx";a.Teacher::_name = "yyy";return 0;
}
(2). 虚继承

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

using namespace std;class Person
{
public:string _name; // 姓名 
};
class Student : virtual public Person
{
protected:int _num;
};
class Teacher : virtual public Person
{
protected:int _id;
};
class headmaster : public Student, public Teacher
{
protected:string _Course;
};int main()
{headmaster h;h._name = "lisi";cout << h._name << endl;h.Student::_name = "l";cout << h._name << endl;h.Teacher::_name = "s";cout << h._name << endl;cout << &h._name << endl;cout << &h.Student::_name << endl;cout << &h.Teacher::_name << endl;return 0;
}

输出结果如下

 可以看到这三个在内存中用了一个地址空间,这样解决了二义性和数据冗余的问题

(2.1)虚继承解决数据冗余和二义性的原理

如下代码

#include<iostream>
#include<string>using namespace std;class Person
{
public:int a;
};
class Student : virtual public Person
{
public:int _num;
};
class Teacher : virtual public Person
{
public:int _id;
};
class headmaster : public Student, public Teacher
{
public:int test;
};int main()
{headmaster h;h.Student::a = 1;h.Teacher::a = 2;h._num = 3;h._id = 4;h.test = 5;return 0;
}

 在内存中如下所示

我们上面提到过,多继承中,先继承的父类在前面后继承的父类在后面,子类成员放在最后面。我们这里可以看出headmaster对象中将Person放到了对象组成的最下面,这个Person同时属于Student和Teacher,那Student和Teacher如何去找到公共的Person呢?

这里是通过它们的两个指针指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表虚基表中存的偏移量。通过偏移量可以找到Person

 内存2是p1指向的地址,内存3是p2指向的地址它们下面指针的指向是相同的

Teacher和Student自己定义的对象也可以通过这样来找到Person

如下

int main()
{Teacher t;Student s;t.a = 1;cout << t.a << endl;s.a = 2;cout << t.a << endl;cout << s.a << endl;return 0;
}

结果为

(3). 多继承中指针偏移问题

关于下面程序说法正确的是

A: p1==p2==p3  B: p1<p2<p3  C: P1==p3!=p2  D: p1!=p2!=p3

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main()
{Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}

先继承的父类在前面,后继承的父类在后面,子类成员在最后面,所以p1与p3指向相同

选C

(4). IO库中的菱形虚拟继承

template<class CharT, class Traits = std::char_traits<CharT>>
class basic_ostream : virtual public std::basic_ios<CharT, Traits>
{};
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_istream : virtual public std::basic_ios<CharT, Traits>
{};

5. 继承和组合

1. public继承是一种is-a的关系。也就是说每个子类对象都是一个父类对象

2. 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象

3. 继承允许你根据父类的实现来定义子类的实现。这种通过生成子类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,父类的内部细节对子类可见。继承一定程度破坏了父类的封装,父类的改变,对子类有很大影响。子类和父类之间的依赖关系很强,耦合度高

4. 对象组合时类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装

5. 优先使用组合,而不是继承。实际尽量多去用组合,组合的耦合度低,代码维护性好。不过也不那么绝对,类之间的关系适合继承(is-a)那就用继承,另外要实现多态也必须要继承。类之间的关系既适合用继承(is-a)也适合用组合(has-a),那就用组合

6. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂,性能也会由一些损失,所以最好不要设计出菱形继承。多继承可以认为是C++的缺陷之一,后来的一些编程语言都没有多继承比如Java

例如汽车类(car) 和 轮胎类(tire) 适合使用组合方式实现,动物类可以作为狗类的父类(继承思想)


这篇就到这里啦(づ ̄3 ̄)づ╭❤~

这篇关于C++ 继承(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1140800

相关文章

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

c++中的set容器介绍及操作大全

《c++中的set容器介绍及操作大全》:本文主要介绍c++中的set容器介绍及操作大全,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录​​一、核心特性​​️ ​​二、基本操作​​​​1. 初始化与赋值​​​​2. 增删查操作​​​​3. 遍历方

解析C++11 static_assert及与Boost库的关联从入门到精通

《解析C++11static_assert及与Boost库的关联从入门到精通》static_assert是C++中强大的编译时验证工具,它能够在编译阶段拦截不符合预期的类型或值,增强代码的健壮性,通... 目录一、背景知识:传统断言方法的局限性1.1 assert宏1.2 #error指令1.3 第三方解决

C++11委托构造函数和继承构造函数的实现

《C++11委托构造函数和继承构造函数的实现》C++引入了委托构造函数和继承构造函数这两个重要的特性,本文主要介绍了C++11委托构造函数和继承构造函数的实现,具有一定的参考价值,感兴趣的可以了解一下... 目录引言一、委托构造函数1.1 委托构造函数的定义与作用1.2 委托构造函数的语法1.3 委托构造函

C++11作用域枚举(Scoped Enums)的实现示例

《C++11作用域枚举(ScopedEnums)的实现示例》枚举类型是一种非常实用的工具,C++11标准引入了作用域枚举,也称为强类型枚举,本文主要介绍了C++11作用域枚举(ScopedEnums... 目录一、引言二、传统枚举类型的局限性2.1 命名空间污染2.2 整型提升问题2.3 类型转换问题三、C