c++ 虚函数和纯虚函数
一、总结
1.
为了实现C++的多态,C++使用了一种动态绑定的技术。这个技术的核心是虚函数表,每个包含了虚函数的类都包含一个虚表。
我们知道,当一个类(A)继承另一个类(B)时,类A会继承类B的函数的调用权。所以如果一个基类包含了虚函数,那么其继承类也可调用这些虚函数,换句话说,一个类继承了包含虚函数的基类,那么这个类也拥有自己的虚表。
虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针。需要指出的是,普通的函数即非虚函数,其调用并不需要经过虚表,所以虚表的元素并不包括普通函数的函数指针。
虚表内的条目,即虚函数指针的赋值发生在编译器的编译阶段,也就是说在代码的编译阶段,虚表就可以构造出来了。
具体见:https://blog.csdn.net/primeprime/article/details/80776625
在你设计一个基类的时候,如果发现一个函数需要在派生类里有不同的表现,那么它就应该是虚的。从设计的角度讲,出现在基类中的虚函数是接口,出现在派生类中的虚函数是接口的具体实现。通过这样的方法,就可以将对象的行为抽象化。
2.虚函数(impure virtual),C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现。子类可以重写父类的虚函数实现子类的特殊化。
3.纯虚函数(pure virtual),C++中包含纯虚函数的类,被称为是“抽象类”。抽象类不能使用new出对象,只有实现了这个纯虚函数的子类才能new出对象。
C++中的纯虚函数更像是“只提供申明,没有实现”,是对子类的约束,是“接口继承”。
C++中的纯虚函数也是一种“运行时多态”。
纯虚函数
1.当在基类中不能为虚函数给出一个有意义的实现时,可以将其声明为纯虚函数,其实现留待派生类完成。
2.纯虚函数的作用是为派生类提供一个一致的接口。
3.纯虚函数不能实例化,但可以声明指针。
纯虚函数在基类只声明不用定义,继承类必须实现。
4.虚函数和纯虚函数对比:
4.1.虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。
4.2.虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义。
4.3.虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。
4.4.虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。
4.5.虚函数的定义形式:virtual{method body} 纯虚函数的定义形式:virtual { } = 0; 在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。
4.6.如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。
5.非类的成员函数不能定义为虚函数,类的成员函数中静态成员函数和构造函数也不能定义为虚函数,但可以将析构函数定义为虚函数。实际上,优秀的程序员常常把基类的析构函数定义为虚函数。因为,将基类的析构函数定义为虚函数后,当利用delete删除一个指向派生类定义的对象指针时,系统会调用相应的类的析构函数。而不将析构函数定义为虚函数时,只调用基类的析构函数。
实例化时:先调用基类构造函数,然后子类构造函数;释放时:先子类析构函数,然后基类析构函数
#ifndef BASE_H
#define BASE_H class Base
{
public:
Base();
~Base();
virtual void test()=;
}; #endif // BASE_H
#include "base.h"
#include <QDebug>
Base::Base()
{
qDebug()<<"Base()";
} Base::~Base()
{
qDebug()<<"~Base()";
}
#ifndef C1_H
#define C1_H
#include "base.h" class C1:public Base
{
public:
C1();
~C1();
void test() override;
}; #endif // C1_H
#include "c1.h"
#include<QDebug>
C1::C1()
{
qDebug()<<"C1()";
} C1::~C1()
{
qDebug()<<"~C1()";
} void C1::test()
{
qDebug()<<"C1::test";
}
Base()
C1()
~Base()
基类析构函数声明为虚函数后,执行结果
Base()
C1()
~C1()
~Base()
6.只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。
7.当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数(函数名相同、参数列表完全一致、返回值类型相关)自动成为虚函数。
8.如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。在以该类为基类的派生类中,也不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。
9.构造函数不能声明为虚函数
构造函数一般是用来初始化对象的,因而只有在一个对象生成之后才能发挥多态作用。虚函数表在构造函数调用之后才建立,因而构造函数不可能声明为虚函数。虚函数的调用需要虚函数表指针,而该指针存放在对象的内存空间中;
若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表地址来调用虚函数。
10.静态成员函数不能是虚函数
静态成员函数对于每一个类只有一份代码,所有的对象共享这份代码,它不归某个对象所有,所以没有动态绑定的必要性,不能被继承,只属于该类
11.内联函数不能是虚函数
内联函数在程序编译的时候展开,在函数调用处进行替换,虚函数是进行动态绑定的
#pragma once
class Shape
{
public:
Shape();
virtual ~Shape(); void Draw1();
virtual void Draw2();
virtual void Draw3()=;
};
#include "Shape.h"
#include <iostream> using namespace std;
Shape::Shape()
{
cout << "shape 构造函数" << endl;
} Shape::~Shape()
{
cout << "shape 析构函数" << endl;
} void Shape::Draw1()
{
cout << "shape Draw1 画个图形" << endl;
}
void Shape::Draw2()
{
cout << "shape Draw2 画个图形" << endl;
}
#pragma once
#include "Shape.h"
class Rectangle:public Shape
{
public:
Rectangle();
~Rectangle(); void Draw1();
void Draw2();
void Draw3();
};
#include "Rectangle.h"
#include <iostream> using namespace std;
Rectangle::Rectangle()
{
cout << "Rectangle 构造函数" << endl;
} Rectangle::~Rectangle()
{
cout << "Rectangle 析构函数" << endl;
} void Rectangle::Draw1()
{
cout << "Rectangle Draw1 画个矩形" << endl;
}
void Rectangle::Draw2()
{
cout << "Rectangle Draw2 画个矩形" << endl;
} void Rectangle::Draw3()
{
cout << "Rectangle Draw3 画个矩形" << endl;
}
#pragma once
#include "Shape.h"
class Circle:public Shape
{
public:
Circle();
~Circle();
void Draw1();
void Draw2();
void Draw3();
};
#include "Circle.h"
#include <iostream> using namespace std;
Circle::Circle()
{
cout << "Circle 构造函数" << endl;
} Circle::~Circle()
{
cout << "Circle 析构函数" << endl;
}
void Circle::Draw1()
{
cout << "Circle Draw1 画个圆形" << endl;
}
void Circle::Draw2()
{
cout << "Circle Draw2 画个圆形" << endl;
} void Circle::Draw3()
{
cout << "Circle Draw3 画个圆形" << endl;
}
// ConsoleApplication2.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include<iostream>
#include<memory>
#include "Shape.h"
#include "Rectangle.h"
#include "Circle.h" using namespace std;
void Run()
{ //Shape *_c;
//_c = new Rectangle();
//_c->Draw1();
//_c->Draw2(); //_c = new Circle();
//_c->Draw1();
//_c->Draw2(); //delete _c; shared_ptr<Shape> _s(new Rectangle());
_s->Draw1();
_s->Draw2();
_s->Draw3();
_s.reset(new Circle());
_s->Draw1();
_s->Draw2();
_s->Draw3(); } int main()
{ Run(); system("pause");
return ;
}
运行结果:
shape 构造函数
Rectangle 构造函数
shape Draw1 画个图形
Rectangle Draw2 画个矩形
Rectangle Draw3 画个矩形
shape 构造函数
Circle 构造函数
Rectangle 析构函数
shape 析构函数
shape Draw1 画个图形
Circle Draw2 画个圆形
Circle Draw3 画个圆形
Circle 析构函数
shape 析构函数
请按任意键继续. . .
Qt 使用智能指针 Pro 添加CONFIG += c++11;
智能指针属于std, std::shared_ptr<Shape> _s(new Rectangle());
c++ 虚函数和纯虚函数的更多相关文章
- C++ - 虚基类、虚函数与纯虚函数
虚基类 在说明其作用前先看一段代码 class A{public: int iValue;}; class B:public A{public: void bPrintf(){ ...
- C++ Primer--虚函数与纯虚函数的区别
首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实 ...
- C++ 虚函数与纯虚函数
#include<iostream> #include<string> using namespace std; class A{ public: virtual void f ...
- c/c++ 基金会(七) 功能覆盖,虚函数,纯虚函数控制
1.功能覆盖 ClassA , ClassB ,其中ClassB继承ClassA 类的定义如下面的: #ifndef _CLASSA_H #define _CLASSA_H #include < ...
- C++ 虚函数 、纯虚函数、接口的实用方法和意义
也许之前我很少写代码,更很少写面向对象的代码,即使有写多半也很容易写回到面向过程的老路上去.在写面向过程的代码的时候,根本不管什么函数重载和覆盖,想到要什么功能就变得法子的换个函数名字,心里想想:反正 ...
- 【C++】C++中的虚函数与纯虚函数
C++中的虚函数 先来看一下实际的场景,就很容易明白为什么要引入虚函数的概念.假设我们有一个基类Base,Base中有一个方法eat:有一个派生类Derived从基类继承来,并且覆盖(Override ...
- C++中虚函数和纯虚函数的区别与总结
首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实 ...
- C++虚函数与纯虚函数用法与区别(转载)
1. 虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class) ...
- C++(九)— 虚函数、纯虚函数、虚析构函数
1.虚函数 原因:通过指针调用成员函数时,只能访问到基类的同名成员函数.在同名覆盖现象中,通过某个类的对象(指针及引用)调用同名函数,编译器会将该调用静态联编到该类的同名函数,也就是说,通过基类对象指 ...
随机推荐
- 让C#可以像Javascript一样操作Json
Json的简介 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.它基于ECMAScript的一个子集. JSON采用完全独立于语言的文本格式,但是也使用了 ...
- 在Windows Server 2012 R2上安装SharePoint 2013 with SP1失败,提示没有.net4.5的解决办法
现在的Server用Windows Server 2012 R2的越来越多了,在部署带Sp1的SharePoint2013的时候,走完预安装工具后,点击setup提示缺少.net4.5. 其实Wind ...
- jquey on
1.如果你的元素是用clone方法复制出来的,并且,用了on来绑定事件的话,必须在clone的后边添加true,负责你的事件不会生效. 2.必须在on $('.js-liubody').on('cli ...
- sFlow-RT
sFlow-RT™ incorporates InMon's asynchronous analytics technology (patent pending), delivering real-t ...
- Jquery揭秘系列:ajax原生js实现
讲到ajax这个东西,我们要知道两个对象XMLHTTPRequest和ActiveXObject ,提供了对 HTTP 协议的完全的访问,包括做出 POST 和 HEAD 请求以及普通的 GET 请求 ...
- linux搭载discuz
公司论坛是用discuz,简单了解了一下,原来是php+mysql搭载的论坛,于是本鸟便试试搭载discuz试试怎么玩,下面是linux下搭载discuz的过程 第一步是下载,由于discuz是运行在 ...
- outlook 2016 for windows 每次刷新发送接收邮件会弹出登陆界面
Q: outlook2016 for windows 每次刷新发送接收邮件会弹出登陆界面,office365 ProPlus 都是正常激活了,Word 和Excel都不存在此类问题 A: 排除用户的o ...
- 【USACO 3.1】Score Inflation(完全背包)
完全背包. http://train.usaco.org/usacoprob2?a=3Srffjlf4QI&S=inflate /* TASK:inflate LANG:C++ URL: */ ...
- CIDR详解和ip最长地址前缀匹配
1.CIDR是什么 无类域间路由(CIDR)编址方案 摒弃传统的基于类的地址分配方式,允许使用任意长度的地址前缀,有效提高地址空间的利用率. 就是一个ip加一个网络掩码,不过这个掩码不是之前只有3个值 ...
- JS实现网页批量下载文件,支持PC/手机
//把下载链接放入集合里 var downloadData = new Array{"http://www.empli.com/data1.apk","http://ww ...