继承
- 继承顾名思义就是对长辈本有的东西进行获取与使用,即两个以及两个类以上的关系
- 在获取与使用时会存在一些情况:
- public:长辈对外公开的自身所有物,最终都会是后代的
- protected:受保护的自身所有物,最终也是后代的,但外人不知道
- private:长辈私人所有的隐私物,不想让任何人知道和获取,所以后代以及其他人都不可知
- 长辈与后代必须有严格先后,长辈的下一代不能是长辈的下一代的下一代,即兄弟关系不能变成父子关系。用程序语言来说,子类继承时,不能继承和自己平级的类,如下例子 [1]:
class Shape
{
protected:
float width_;
float height_;
};
// 错误情况,在当前例子中,Rectangle类将会同时继承Shape和PaintCost两类
// 这样会导致无法区分所继承的成员变量是由哪一类传递来的
// class PaintCost: public Shape
// {
// };
// 正确情况
class PaintCost
{
};
class Rectangle : public Shape, public PaintCost
{
public:
int getArea()
{
// 如果使用错误情况,此时无法明确width_和height_是继承自哪里
// 因为PaintCost类也继承了Shape类,所以也有width_和height_变量
return (width_ * height_);
}
};
重载和重写
区别
- 重载与重写都是与类相关的技术,具体区别如下 [2] :
重载 | 重写 | |
---|---|---|
范围 | 同一类 | 不同类(基类与派生类之间,即多态概念) |
声明样式 | 函数名相同,参数不同 | 函数名相同,参数可变,返回值相同或者协变 |
关键字 | 可忽略不写 | 基类的函数必须带有 virtual 关键字 |
重载
- 同一类下,同一函数名,不同参数类型,返回值可同、可不同
- 实例如下:
#include <iostream>
class Base
{
public:
void override_test(int a)
{
std::cout << "int_1 Base::override_test(): " << a << std::endl;
}
// 错误重载:参数必须不同,单独返回值不同无效
// int override_test(int a)
// {
// std::cout << "int_2 Base::override_test(): " << a << std::endl;
// return a;
// }
char override_test(char a)
{
std::cout << "char Base::override_test(): " << a << std::endl;
return a;
}
// 正确重载
void override_test(float a)
{
std::cout << "float Base::override_test(): " << a << std::endl;
}
};
int main()
{
Base b;
b.override_test(1);
b.override_test(1.0f);
return 0;
}
重写
- 不同类(基类与派生类),同函数名,参数可同可不同
- 实例如下:
#include <iostream>
class Base
{
public:
// 必须带有 virtual 关键词
virtual void test()
{
std::cout << "Base::test()" << std::endl;
}
virtual Base* getThis()
{
std::cout << "Base::test()" << std::endl;
return this;
}
};
class Derived : public Base
{
public:
virtual void test()
{
std::cout << "Derived::test()" << std::endl;
}
virtual void test(int a)
{
std::cout << "Derived::test(): int param " << a << std::endl;
}
// 协变: 指当类型 T 是类型 S 的子类型时,允许 T 的实例被用作 S 的实例。
// 协变在泛型设计中较为常用
virtual Derived* getThis()
{
std::cout << "Derived::getThis()" << std::endl;
return this;
}
};
int main()
{
Base *pd = new Derived;
// 假设派生类 Derived 类中不存在 virtual void test() 函数
// 此时 pd->test()调用的是基类Base的test()函数
// 是一种隐藏规则:覆盖
pd->test();
pd->test(1);
delete pd;
return 0;
}
虚析构函数
- 在使用基类指针pd指向派生类对象后,在结束 delete pd 时,会有派生类析构函数无法调用的情况
- 原因出在于指针声明的是Base基类,且基类的析构函数不是虚函数,所以不会考虑派生类的析构问题(即编译器会将析构函数直接解析为Base::~Base() ) [3] ,此时有可能出现内存泄露的情况,所以基类的析构函数最好是虚函数
- 示例如下
#include <iostream>
class Base {
public:
Base() { std::cout << "Base constructor" << std::endl; }
virtual ~Base() { std::cout << "Base destructor" << std::endl; } // 虚析构函数
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived constructor" << std::endl; }
~Derived() { std::cout << "Derived destructor" << std::endl; } // 派生类析构函数
};
int main() {
Base* base = new Derived();
delete base; // 通过基类指针删除派生类对象
return 0;
}
虚继承
- 使用该方式的缘由是,基类的两个派生类的派生类有相同的派生类,如下图所示:
- 示例代码:
class A
{
public:
A() { std::cout << "A::A()" << std::endl; }
virtual ~A() { std::cout << "A::~A()" << std::endl; }
};
class B : virtual public A // 虚继承 A 类
{
public:
B() { std::cout << "B::B()" << std::endl; }
~B() { std::cout << "B::~B()" << std::endl; }
};
class C : virtual public A // 虚继承 A 类
{
public:
C() { std::cout << "C::C()" << std::endl; }
~C() { std::cout << "C::~C()" << std::endl; }
};
class D : public B, public C // 同时继承 B、C 类
{
public:
D() { std::cout << "D::D()" << std::endl; }
~D() { std::cout << "D::~D()" << std::endl; }
};
void virtual_inherit_test()
{
// 直接类对象实例化
D d;
// 基类指针的派生类实例化
A *pa = new D();
delete pa;
}
- 如果不使用虚继承,会出现两个问题:
- 直接实例化D类对象时,会产生两份A类成员;
- 使用指针实现A类指针的D类实例化,会出现基类不明确的错误。
虚函数
- 每一个带有虚函数的类,都有对应的,有且只有一个虚函数表(类级别,不是对象级别)
- 每一个实例化的类对象都会指向虚函数表的头指针,所以带有虚函数的类都至少占用一个指针的长度,即一个机器长度(64位为8字节,32位为4字节)
纯虚函数
- 指无实现主体的虚函数,如下实例:
class Shape
{
public:
int width_;
int height_;
Shape(int width, int height)
{
width_ = width;
height_ = height;
}
virtual void area() = 0; // 纯虚函数,无实现主体
};
- 此时该基类也被称为抽象基类(至少包含有一个纯虚函数)
- 该基类的派生类必须实现纯虚函数,否则不允许被实例化
class Rectangle : public Shape
{
public:
Rectangle(int width, int height) : Shape(width, height) {}
void area()
{
std::cout << "Rectangle area: " << width_ * height_ << std::endl;
}
};
多态
- 以美术老师教学生画画为例:
- 美术老师是基类,美术老师的绘画是一项技能(无具体实现方法,纯虚函数)
- 此时,他画了一条小狗(绘画技能的具体实现,虚函数),他让学生们也跟着画一条小狗
- 学生是派生类,跟着老师老师学习绘画(继承)
- 此时,他们按照着自己的绘画方法、和自己对小狗的理解(多态),画出了不同的小狗画(重写)
- 代码实现此场景:
class ArtTeacher
{
public:
ArtTeacher(){}
ArtTeacher(std::string name) { name_ = name; }
virtual void drawDog()
{ std::cout << name_ << "老师 画了一条 小狗" << std::endl; }
private:
std::string name_;
};
class DrawKeJiStudents : public ArtTeacher
{
public:
DrawKeJiStudents(std::string name) { name_ = name; }
void drawDog()
{ std::cout << name_ << " 同学 画了一条 柯基" << std::endl; }
private:
std::string name_;
};
class DrawZhongHuaTianYuanQuanStudents : public ArtTeacher
{
public:
DrawZhongHuaTianYuanQuanStudents(std::string name) { name_ = name; }
void drawDog()
{ std::cout << name_ << " 同学 画了一条 中华田园犬" << std::endl; }
private:
std::string name_;
};
class DrawZangAoStudents : public ArtTeacher
{
public:
DrawZangAoStudents(std::string name) { name_ = name; }
void drawDog()
{ std::cout << name_ << " 同学 画了一条 藏獒" << std::endl; }
private:
std::string name_;
};
- 绘画结果展示方式有两种
-
每位同学自己展示(直接实例化)
DrawKeJiStudents* xiao_ming = new DrawKeJiStudents("小明"); xiao_ming->drawDog(); delete xiao_ming;
-
美术老师自己去查看每位同学的绘画(基类指针的派生类实例化)
ArtTeacher* wang_teacher = new DrawKeJiStudents("小明"); wang_teacher->drawDog(); delete wang_teacher;
-
参考文献
[1] https://www.w3cschool.cn/cpp/cpp-inheritance.html
[2] https://www.cnblogs.com/ybqjymy/p/16087574.html
[3] https://blog.csdn.net/weixin_45482816/article/details/107623904