
老朋友(非官方文档):cplusplus 官方文档(同步更新):cppreference
基类的构造函数私有,派生类的构成必须调用基类的构造函数,但是基类的构成函数私有化以后,派生类看不见就不能调用了,那么派生类就无法实例化出对象。

运行一下——

这里必须调用基类的构造,但是基类这里是私有的,看不见,所以就不能再调用了。
C++11新增了一个final关键字,final修改基类,派生类就不能继承了。

我们同样运行一下——

// C++11的方法
class Base final
{
public:
void func5() { cout << "Base::func5" << endl; }
protected:
int a = 1;
private:
// C++98的方法
/*Base()
{}*/
};
class Derive :public Base
{
void func4() { cout << "Derive::func4" << endl; }
protected:
int b = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}在本文博主不展开讲,下篇博客,博主会介绍C++进阶中又一个重要的模块——【多态】,在【多态】中,博主会介绍两个涉及到【多态】中的重写相关知识点的关键字:override和final。
也就是说,final充当了两个作用——
(1)直接实现一个不能被继承的类(【继承】篇涉及知识点); (2)不让重写基类虚函数(【多态】篇即将涉及的知识点)。
友元关系不能继承。
这句话也就是说基类友元不能访问派生类私有和保护成员。
把派生类也变成基类的友元的友元即可。
// 前置声明
class Student;
class Person
{
// 友元关系不能被子类继承
friend void Display(const Person& p, const Student& s);
public:
protected:
string _name; // 姓名
};
class Student : public Person
{
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;
// 编译报错:error C2248:“Student::_stuNum”:无法访问 protected 成员
// 解决方案:Display也变成Student 的友元即可
Display(p, s);
return 0;
}运行一下——

这段代码是能顺利运行的,但是,我们看下面这段代码——
class Person
{
// 友元关系不能被子类继承
friend void Display(const Person& p, const Student& s);
public:
protected:
string _name; // 姓名
};
class Student : public Person
{
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;
// 编译报错:error C2248:“Student::_stuNum”:无法访问 protected 成员
// 解决方案:Display也变成Student 的友元即可
Display(p, s);
return 0;
}不加前置声明会报下面的错——


因为友元关系不能继承,因此我们要给派生类也变成基类友元的友元。
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个派生类,都只有一个static成员实例。
非静态成员的继承是父类和子类各一份,地址不一样。
静态成员的继承是父类和子类共用同一份,地址也一样。
class Person
{
public:
string _name;
static int _count;
};
// 静态成员会被继承
int Person::_count = 10;
class Student : public Person
{
protected:
int _stuNum;
};
int main()
{
Person p;
Student s;
// 从这里的运行结果可以看到非静态成员_name的地址是不一样的
// 说明派生类继承下来了,父类、派生类对象各有一份
cout << &p._name << endl;
cout << &s._name << endl;
// 从这里的运行结果可以看到静态成员_count的地址是一样的
// 说明派生类和基类共用同一份静态成员
cout << &p._count << endl;
cout << &s._count << endl;
// 公有的情况下,父类、派生类指定类域都可以访问静态成员
cout << Person::_count << endl;
cout << Student::_count << endl;
return 0;
}运行一下——

如上图所示。

事先说明:多继承是个大坑!!!


class Person
{
public:
Person(const char* name)
:_name(name)
{ }
public:
string _name; // 姓名
//int* _age; // 年龄
//int _tel; // 电话
//string _address; // 地址
};
//class Student : public Person
class Student : virtual public Person // 虚继承
{
public:
Student(const char* name,int num)
:Person(name)
,_num(num)
{ }
protected:
int _num; // 学号
};
//class Teacher : public Person
class Teacher : virtual public Person
{
public:
Teacher(const char* name, int id)
:Person(name)
,_id(id)
{ }
protected:
int _id; // 教职工编号
};
基类数据越多,这两个问题越严重。
数据冗余:如下图所示,Person有两个——

二义性:访问不明确~>指定类域勉强解决
菱形继承——多继承延伸的坑

多继承不是问题,多继承实现的菱形继承才是问题。

因此设计了“菱形虚拟继承”来解决,下面我们会介绍虚继承。
关键词virtual加在腰部位置,如下图所示——

都加上virtual可不可以?——当然不行。
换个说法,药能多吃吗?会影响底层的空间模型,能编译通过但底层空间会乱。

虚继承太复杂了,无论是使用还是底层,都太复杂。
不要玩菱形继承!!!当然,菱形继承也是有应用的,库里面的IO库就是搞成菱形继承的,IO库的使用会专门在IO库讲。

class Person
{
public:
Person(const char* name)
:_name(name)
{ }
public:
string _name; // 姓名
//int* _age; // 年龄
//int _tel; // 电话
//string _address; // 地址
};
//class Student : public Person
class Student : virtual public Person // 虚继承
{
public:
Student(const char* name,int num)
:Person(name)
,_num(num)
{ }
protected:
int _num; // 学号
};
//class Teacher : public Person
class Teacher : virtual public Person
{
public:
Teacher(const char* name, int id)
:Person(name)
,_id(id)
{ }
protected:
int _id; // 教职工编号
};
class Assistant : public Student, public Teacher
{
public:
Assistant(const char* name1,const char* name2,const char* name3)
:Person(name1)
,Student(name2,1)
,Teacher(name3,2)
,_majorCourse("计算机")
{ }
protected:
string _majorCourse; // 主修课程
};
int main()
{
// 思考一下这里a对象中_name是“张三”,“李四”,“王五”中的哪一个?
Assistant a("张三", "李四", "王五");
// 编译报错:error C2385:对“_name”的访问不明确
/*Assistant a;*/
a._name = "peter";
// 需要显示指定访问哪个基类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
// 再比如
a.Student::_name = "小黄";
a.Teacher::_name = "黄老师";
cout << endl;
return 0;
}运行一下——

我们可以设计出多继承,但是不建议设计出菱形继承,因为菱形虚拟继承以后,无论是使用还是底层都会复杂很多。当然有多继承语法支持,就一定存在会设计出菱形继承,像Java是不支持多继承的,就避开了菱形继承。



下面说法正确的是( ) 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;
}正确答案:C。

运行一下——

1、public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。 2、组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。 3、继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-boxreuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对派生类可见。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。 4、对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-boxreuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。 5、优先使用组合,而不是继承。实际尽量多去用组合,组合的耦合度低,代码维护性好。不过也不太那么绝对,类之间的关系就适合继承(is-a)那就用继承,另外要实现多态,也必须要继承。类之间的关系既适合用继承(is-a)也适合组合(has-a),就用组合。


// 继承和组合
// Tire(轮胎)和Car(⻋)更符合has-a的关系
class Tire {
protected:
string _brand = "Michelin"; // 品牌
size_t _size = 17; // 尺寸
};
class Car {
protected:
string _colour = "白色"; // 颜色
string _num = "陕ABIT00"; // 车牌号
Tire _t1; // 轮胎
Tire _t2; // 轮胎
Tire _t3; // 轮胎
Tire _t4; // 轮胎
};
class BMW : public Car {
public:
void Drive() { cout << "好开-操控" << endl; }
};
// Car和BMW/Benz更符合is-a的关系
class Benz : public Car {
public:
void Drive() { cout << "好坐-舒适" << endl; }
};
template<class T>
class vector
{};
// stack和vector的关系,既符合is-a,也符合has-a
template<class T>
class stack : public vector<T>
{};
template<class T>
class stack
{
public:
vector<T> _v;
};
int main()
{
return 0;
}is-a用继承,has-a用组合。
// 继承和组合
// Tire(轮胎)和Car(⻋)更符合has-a的关系
// 轮胎类
class Tire {
protected:
string _brand = "Michelin";
size_t _size = 17;
};
// 汽车基类
class Car {
protected:
string _colour = "白色";
string _num = "陕ABIT00";
Tire _tires[4]; // 使用数组更合适
};
// 派生类
class BMW : public Car {
public:
void Drive() { cout << "好开-操控" << endl; }
};
class Benz : public Car {
public:
void Drive() { cout << "好坐-舒适" << endl; }
};
// 正确的stack实现
template<class T>
class Stack {
private:
vector<T> _v; // 组合关系
public:
void push(const T& x) { _v.push_back(x); }
void pop() {
if (!_v.empty())
_v.pop_back();
}
T& top() {
if (!_v.empty())
return _v.back();
throw std::out_of_range("Stack is empty");
}
bool empty() const { return _v.empty(); }
size_t size() const { return _v.size(); }
};
int main() {
BMW bmw;
bmw.Drive();
Stack<int> s;
s.push(1);
s.push(2);
cout << s.top() << endl; // 输出2
return 0;
}运行一下——

白盒测试:更加难,一般由研发人员写并且测试,看得见、透明——保护、私有都可使用; 黑盒测试:看不见,不透明; 白盒 / 黑盒好坏的依据是从软件设计角度出发的。
高内聚,低耦合——可维护性(其中一个修改,另一个不受影响)。
打成一个个模块,哪个出问题改哪个,不受影响。

组件:静态库、动态库——不可执行的二进制文件——
(1)编译时间降低; (2)看不到源码(二进制编译)。
如下图所示——


实践的角度:优先使用组合;既符合继承也符合组合,我们使用组合;但是要注意:是“优先使用组合”,不是必须使用,但是像多态这些需要继承的地方还是要用继承。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<list>
using namespace std;
//// 前置声明
//class Student;
//
//class Person
//{
// // 友元关系不能被子类继承
// friend void Display(const Person& p, const Student& s);
//public:
//protected:
// string _name; // 姓名
//};
//
//class Student : public Person
//{
// 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;
// // 编译报错:error C2248:“Student::_stuNum”:无法访问 protected 成员
// // 解决方案:Display也变成Student 的友元即可
//
// Display(p, s);
//
// return 0;
//}
//class Person
//{
//public:
// string _name;
// static int _count;
//};
//
//// 静态成员会被继承
//int Person::_count = 10;
//
//class Student : public Person
//{
//protected:
// int _stuNum;
//};
//
//int main()
//{
// Person p;
// Student s;
// // 从这里的运行结果可以看到非静态成员_name的地址是不一样的
// // 说明派生类继承下来了,父类、派生类对象各有一份
// cout << &p._name << endl;
// cout << &s._name << endl;
// // 从这里的运行结果可以看到静态成员_count的地址是一样的
// // 说明派生类和基类共用同一份静态成员
// cout << &p._count << endl;
// cout << &s._count << endl;
//
// // 公有的情况下,父类、派生类指定类域都可以访问静态成员
// cout << Person::_count << endl;
// cout << Student::_count << endl;
//
// return 0;
//}
//class Person
//{
//public:
// Person(const char* name)
// :_name(name)
// { }
//
//public:
// string _name; // 姓名
// //int* _age; // 年龄
// //int _tel; // 电话
// //string _address; // 地址
//};
////class Student : public Person
//class Student : virtual public Person // 虚继承
//{
//public:
// Student(const char* name,int num)
// :Person(name)
// ,_num(num)
// { }
//protected:
// int _num; // 学号
//};
//
////class Teacher : public Person
//class Teacher : virtual public Person
//{
//public:
// Teacher(const char* name, int id)
// :Person(name)
// ,_id(id)
// { }
//protected:
// int _id; // 教职工编号
//};
//
//class Assistant : public Student, public Teacher
//{
//public:
// Assistant(const char* name1,const char* name2,const char* name3)
// :Person(name1)
// ,Student(name2,1)
// ,Teacher(name3,2)
// ,_majorCourse("计算机")
// { }
//
//protected:
// string _majorCourse; // 主修课程
//};
//
//int main()
//{
// // 思考一下这里a对象中_name是“张三”,“李四”,“王五”中的哪一个?
// Assistant a("张三", "李四", "王五");
//
// // 编译报错:error C2385:对“_name”的访问不明确
// /*Assistant a;*/
// a._name = "peter";
//
// a.Student::_name = "小黄";
// a.Teacher::_name = "黄老师";
//
// cout << endl;
//
// return 0;
//}
//// 继承 is-a
//class stack : public vector
//{ };
//
//// 组合 has-a
//class stack
//{
// vector _v;
//};
//// 写一个不能被继承的类
//
//class Base
//{
//public:
// void func5() { cout << "Base::func5" << endl; }
//protected:
// int a = 1;
//private:
// // C++98的办法:构造函数私有的类不能被继承 —— 间接
// Base()
// {}
//};
//class Derive : Base
//{};
class Base final
{
public:
void func5() { cout << "Base::func5" << endl; }
protected:
int a = 1;
};
class Derive : Base // 修饰基类,派生类不能再被继承了 —— 直接
{};
int main()
{
Derive d;
return 0;
}往期回顾:
【C++:继承】面向对象编程精要:C++继承机制深度解析与最佳实践
结语:都看到这里啦!那请大佬不要忘记给博主来个“一键四连”哦!
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა