欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 金融 > C++笔记-对“继承”的进一步认识(2024-08-15)

C++笔记-对“继承”的进一步认识(2024-08-15)

2024/10/23 16:27:21 来源:https://blog.csdn.net/qq78442761/article/details/143182345  浏览:    关键词:C++笔记-对“继承”的进一步认识(2024-08-15)

基础知识点

在C++中,如果发现有了继承关系,那么注定会有,父类指针指向子类对象。父类指针调用子类方法。这里重点说下虚函数和非虚函数:

虚函数

非虚函数

定义和声明

使用 virtual 关键字声明,在基类中定义,允许在派生类中重写。

没有 virtual 关键字,不能被重写,派生类中同名的函数只是隐藏了基类的同名函数。

调用机制

使用动态绑定(或晚绑定)。函数调用在运行时根据对象的实际类型确定。

使用静态绑定(或早绑定)。函数调用在编译时根据指针或引用的类型确定。

适用场景

用于需要多态性时,例如想要实现不同类之间的统一接口时。

用于仅在基类中需要固定行为,且不需要在派生类中改变的情况。

性能

由于需要使用虚表(vtable)和指针,有一定的性能开销。

由于直接调用,没有额外的开销,通常效率更高。

析构函数

类的设计中,如果基类有虚函数,通常将析构函数声明为虚的,以确保派生类的析构函数能够被正确调用。

如果一个类的析构函数是非虚的,而通过基类指针删除派生类对象,可能导致资源泄漏和未定义行为。

补充知识点

上面是基础知识,下面来一些值得思考的知识点(编译器用的是MSVC2015):

如下类定义代码:

class Base {
public:void print() { cout << "Base print" << endl; }
};class A : public Base {
public:void print() { cout << "A print" << endl; }
};

如下调用:

	Base *base = new Base;base->print();((A*)base)->print();delete base;

运行结果如下:

这里就有点看开始奇怪了,这里的base变量是用父类new的,第二个打印只是强转成了A*,然后成功打印了A类的print,我感觉应该会运行时报错或者打印Base的print也能理解。因为A的print并没有被构造。再看下下面

如果把类定义改成:

class Base {
public:virtual void print() { cout << "Base print" << endl; }
};class A : public Base {
public:void print() { cout << "A print" << endl; }
};

运行截图如下:

	Base *base = new Base;base->print();((A*)base)->print();delete base;

加了virtual就好理解了,新加了个虚表的概念,调用print,就直接从虚表中拿函数地址,因为没有new A,所以虚表没有被刷掉,调用的还是Base类的print。

而上面那个例子,可以看出,在计算机底层中,还有个内存块,专门存储了成员函数。估计在定义时就有了。

当然不推荐大家这么使用。因为例子中给的函数很简单,如果比较复杂,比如这样的:

定义如下:

class Base {
public:void print() { cout << "Base print" << endl; }
};class A : public Base {
public:A() { value1 = 1; }void print() { cout << "A print : " << value1 << endl; }private:int value1;
};

调用如下:

	Base *base = new Base;base->print();((A*)base)->print();delete base;

运行截图如下:

就开始有问题了,打印的就不知道是哪块内存的数据。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com