C++ 虚函数和虚函数表详解
上次修改时间:

重载和重写

  1. 重载---同一个类中函数名相同,函数的参数列表不相同的两个及两个以上的函数就是函数重载。注意:函数的返回值不能作为函数是否重载的依据。
  2. 重写---是在子类继承父类的时候,对父类的虚函数进行了覆盖。重写会使程序发生动态联编,产生多态。
  3. 重定义---是在子类继承父类的时候,对父类的非虚函数进行了覆盖。

面向对象多态的体现:

静态多态:函数重载,运算符重载。
动态多态:通过虚函数实现。
使用指针或引用访问非虚函数时,编译器根据指针本身的类型决定要调用哪个函数。而不是不是根据指针指向对象的类型。
使用指针访问虚函数时,编译器根据指针指向的类型决定要调用哪个函数,与指针本身类型无关。
派生类可以对基类的虚函数的进行重写(重定义、覆盖):
与基类的虚函数的参数类型必须一样
与基类的虚函数的参数个数必须一样
与基类的虚函数的函数返回值类型必须一样(可以替换为子类类型指针)

C++函数数调用默认不使用动态绑定,触发动态绑定必须满足一下条件:

只有指定为虚函数的函数才能进行动态绑定
必须通过基类的指针或引用对函数进行调用

哪些函数不能为虚函数

构造函数:若有虚构造函数则不能正确调用 父类的构造函数
静态成员函数:静态成员函数对每个类来说只有一份代码,所有对象共享这份代码,它不归属于某个具体对象所有。 友元函数:本就不是成员函数
内联函数:赋值操作符重载函数:基类与子类的参数类型不同

C++ 默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。
而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。

虚函数表

每个类的指向虚函数的指针,放在表格中,这个表格被称为虚函数表(bptr)
每个对象都被添加一个指向虚函数表的指针(虚函数表指针vptr)。
虚函数表是在编译期间就已经生成了

存放位置:

  1. 虚函数表是class specific的,也就是针对一个类来说的,这里有点像一个类里面的staic成员变量,即它是属于一个类所有对象的,不是属于某一个对象特有的,是一个类所有对象共有的。
  2. 虚函数表是编译器来选择实现的,编译器的种类不同,可能实现方式不一样,vptr在一个对象的最前面,但是也有其他实现方式,不过目前gcc 和微软的编译器都是将vptr放在对象内存布局的最前面。
    • msvc中虚函数表存放在常量段。
    • gcc中存放在可执行文件的只读数据段中(rodata),这与微软的编译器将虚函数表存放在常量段存在一些差别。

image.png

不同继承模式下对象中虚函数表内存分布:

单继承

派生类中仅有一个虚函数表。这个虚函数表和基类的虚函数表不是一个表(无论派生类有没有重写基类的虚函数),但是如果派生类没有重写基类的虚函数的话,基类和派生类的虚函数表指向的函数地址都是相同的。
C不重写A的虚函数,虚函数表指针指向A的函数地址:

 class Animal{
      public:
          virtual void eat(){}
          virtual void run(){}
      private:
          int name;
          int age;
 };
 class Dog : public Animal{
      public:
          virtual void shout(){}
          virtual void stand(){}
 };

image.png
C重写A的虚函数,虚函数表指针指向C自己:

 class Animal{
      public:
          virtual void eat(){}
          virtual void run(){}
      private:
          int name;
          int age;
 };
 class Dog  : public Animal{
      public:
          virtual void eat(){}
          virtual void shout(){}
 };

image.png

多继承

派生类中有多个虚函数表,虚函数的排列方式和继承的顺序一致。派生类重写函数将会覆盖所有虚函数表的同名内容,派生类自定义新的虚函数将会在第一个类的虚函数表的后面进行扩充。
多继承和单继承差别就是多继承有多个虚函数表指针指。

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

 virtual void func2()
 {
 cout << "A::func2" << endl;
 }

public:
 int _a;
};

class B 
{
public:
 virtual void func3()
 {
 cout << "B::func1" << endl;
 }

public:
 int _b;
};

class C : public A , public B
{
 //覆盖A::func1()
 virtual void func1()
 {
 cout << "C::func1()"<<endl;
 }

 virtual void func4()
 {
 cout << "C::func4()" << endl;
 }
public:
 int _c;
};

image.png

继承的使用语法

正常继承

正常继承可以重写父类的方法,也可以补充些

class Person{
  public:
    virtual void id();
};
纯虚函数

纯虚函数的作用:实现抽象类;强制派生类重写虚函数,形如:

class Person{
  public:
    virtual void id() = 0;
};
override保留字

表示当前子类的成员函数重写了基类的虚成员函数。
如果cpp文件中没有的重写该函数,就会报错,这就限制这个函数一定要是父类的虚函数

class Person{
  public:
    virtual void id() override;
};

虚构造函数的作用

当用父指针引用子类子对象进行delete时,父类有虚构造函数会自动调用父类的构造函数,如果不是析构函数,则只会调用子类的析构函数。

class Base{
public:
    Base(){};
    ~Base(){cout<<"base deconstruct";};
};
class Child:public Base{
public:
    ~Child(){cout<<"child deconstruct";}
};
int main(){ 
// 
    Child *p = new Child();       
    delete p;   
}

非虚析构函数输出

child deconstruct   

虚析构函数输出

"base deconstruct"
"child deconstruct"
chrono库的应用
上次修改时间:

1.Clocks

chrono提供了三种不同的时钟:

  1. std::chrono::system_clock: 依据系统的当前时间 (不稳定)
  2. std::chrono::steady_clock: 以统一的速率运行(不能被调整)
  3. std::chrono::high_resolution_clock: 提供最小可能的滴答周期

std::chrono::system_clock 它表示当前的系统时钟,系统中运行的所有进程使用now()得到的时间是一致的。 每一个clock类中都有确定的time_point, duration, Rep, Period类型。 操作有: now() 当前时间time_point to_time_t() time_point转换成time_t格式 from_time_t() 从time_t转换成time_point格式 典型的应用是计算时间日期:

// system_clock example
#include <iostream>
#include <ctime>
#include <ratio>
#include <chrono>

int main ()
{
  using std::chrono::system_clock;

  std::chrono::duration<int,std::ratio<60*60*24> > one_day (1);

  system_clock::time_point today = system_clock::now();
  system_clock::time_point tomorrow = today + one_day;

  std::time_t tt;

  tt = system_clock::to_time_t ( today );
  std::cout << "today is: " << ctime(&tt);

  tt = system_clock::to_time_t ( tomorrow );
  std::cout << "tomorrow will be: " << ctime(&tt);

  return 0;
}

std::chrono::steady_clock 为了表示稳定的时间间隔,后一次调用now()得到的时间总是比前一次的值大(这句话的意思其实是,如果中途修改了系统时间,也不影响now()的结果),每次tick都保证过了稳定的时间间隔。

2.Durations

std::chrono::duration 表示一段时间,比如两个小时,12.88秒,半个时辰,一炷香的时间等等,只要能换算成秒即可。

1 template <class Rep, class Period = ratio<1> > class duration;

其中 Rep表示一种数值类型,用来表示Period的数量,比如int float double Period是ratio类型,用来表示【用秒表示的时间单位】比如second milisecond 常用的duration<Rep,Period>已经定义好了,在std::chrono::duration下: ratio<3600, 1> hours ratio<60, 1> minutes ratio<1, 1> seconds ratio<1, 1000> microseconds ratio<1, 1000000> microseconds ratio<1, 1000000000> nanosecons

这里需要说明一下ratio这个类模版的原型:

 template <intmax_t N, intmax_t D = 1> class ratio;

N代表分子,D代表分母,所以ratio表示一个分数值。 注意,我们自己可以定义Period,比如ratio<1, -2>表示单位时间是-0.5秒。 由于各种duration表示不同,chrono库提供了duration_cast类型转换函数。

 typedef std::chrono::duration<int> seconds_type;
 typedef std::chrono::duration<int,std::milli> milliseconds_type;
 typedef std::chrono::duration<int,std::ratio<60*60>> hours_type;

   hours_type h_oneday (24);                  // 24h
   seconds_type s_oneday (60*60*24);          // 86400s
   milliseconds_type ms_oneday (s_oneday);    // 86400000ms

   seconds_type s_onehour (60*60);            // 3600s
 //hours_type h_onehour (s_onehour);          // NOT VALID (type truncates), use:
   hours_type h_onehour (std::chrono::duration_cast<hours_type>(s_onehour));
   milliseconds_type ms_onehour (s_onehour);  // 3600000ms (ok, no type truncation)

不同的period提供了std::chrono::duration_cast<>()转换成相同period的方法。 同时提供了count函数返回这个period的rep格式。

3.Time points

std::chrono::time_point 表示一个具体时间,如上个世纪80年代、你的生日、今天下午、火车出发时间等,只要它能用计算机时钟表示。鉴于我们使用时间的情景不同,这个time point具体到什么程度,由选用的单位决定。一个time point必须有一个clock计时。参见clock的说明。

template <class Clock, class Duration = typename Clock::duration>  class time_point;

使用方法:

// time_point constructors
#include <iostream>
#include <chrono>
#include <ctime>

int main ()
{
  using namespace std::chrono;

  system_clock::time_point tp_epoch;    // epoch value

  time_point <system_clock,duration<int>> tp_seconds (duration<int>(1));

  system_clock::time_point tp (tp_seconds);

  std::cout << "1 second since system_clock epoch = ";
  std::cout << tp.time_since_epoch().count();
  std::cout << " system_clock periods." << std::endl;

  // display time_point:
  std::time_t tt = system_clock::to_time_t(tp);
  std::cout << "time_point tp is: " << ctime(&tt);

  return 0;
}

time_point有一个函数time_from_eproch()用来获得1970年1月1日到time_point时间经过的duration。 举个例子,如果timepoint以天为单位,函数返回的duration就以天为单位。

stl之遍历删除的正确方法
上次修改时间:

遍历删除即在遍历中删除,不同的数据类型会有不同的问题比如:

for (auto itr = mDevices.begin(); itr != mDevices.end(); itr++) {
    if (itr->first == id) {
        mDevices.erase(itr->first);
    }
}

因为itr在删除的时候已经失效了,因此itr++会报错。 正确的姿势应该是

for (auto itr = mDevices.begin(); itr != mDevices.end();) {
    if (itr->first == id) {
        auto itrSave = itr;
        itr = mDevices.erase(itrSave);
    }else{  
        itr++;
    }
}

有些erase()方法返回的是void

for (auto itr = mDevices.begin(); itr != mDevices.end();) {
    if (itr->first == id) {
        auto itrSave = mDevices.erase(itr->first);
        itr++;
        mDevices.erase(itrSave);
    }else{  
        itr++;
    }
}
std::thread正确关闭线程的用法
上次修改时间:

在使用多线程时,很多不规范的方法会导致意外的bug,现在讨论一种关闭线程不彻底导致的可能导致的隐患。

typedef struct {
bool isStop;
}ThreadCtx;
thread1(ThreadCtx *p){

    while(p->stop){
    ...some user code 
    }
}

int main(){
    ThreadCtx *p = new ThreadCtx;
    p->isStop = false;
    std::thread *t1 = new thread(thread1,p); //start the thread 
    ... some user code 

    // i just want to stop the thread
    p->isStop = true;
    // so the thread is close

}

如上代码所示,用标志位来判断线程是否关闭,隐患在于,当用户的代码在thread里面blocking时,线程是未退出的状态,这个时候主线程认为子线程已经退出了,问题在于缺少线程退出的反馈,正确的做法还需要在p->isStop = true;后面,加上

p->join(); //等待线程退出

等待线程退出,才能确保线程是退出的。

流程图如下:
thread关闭.jpeg

c++的三种继承方式
上次修改时间:

类的继承方式有:公有继承,私有继承,保护继承

类当继承的方式为公有继承时: 基类的公共成员和受保护成员在派生类中访问属性(大众还是大众,受保护的还是保护的)还是不变,但是在类族之外的派生类的对象只能访问基类的公共成员,受保护和私有成员通过对象都不可以访问。
在派生类中,只有基类的私有成员在派生类中不可直接访问

类当继承的方式为私有继承时:

基类的公用成员和保护成员都以私人身份出现在派生类中,所以在类族之外的派生类的对象不能访问基类的公共和保护的成员,当然包括基类的私有成员。
由于继承基类的函数可以说是派生类拥有对基类函数的调用权,所以在派生类中的其他成员可以直接访问基类的公共和保护成员,对于函数,一般采用在函数中调用基类的公共或保护函数的方式访问。
类当继承的方式为保护继承时:
基类的公用成员和保护成员都以保护身份出现在派生类中。
在类族之外的派生类的对象和在派生类中的其他成员对于公众,保护,私人的访问权限与私有继承相同。


#include<iostream>  
using namespace std; 

class A 
{ 
private: 
    int privatetedateA;
protected:  
    int protecteddateA; 
public:
    int publicdateA;
};

class B :public A      
{  
public:  
    void funct()  
    {  
        int b;  
        b=privatedateA;   //error:基类中私有成员在派生类中是不可见的  
        b=protecteddateA; //true:基类的保护成员在派生类中为保护成员  
        b=publicdateA;    //true:基类的公共成员在派生类中为公共成员  
    }  
};  
/  
class C :private A  
{  
public:  
    void funct()  
    {  
        int c;  
        c=privatedateA;    //error:基类中私有成员在派生类中是不可见的  
        c=protecteddateA;  //true:基类的保护成员在派生类中为私有成员  
        c=publicdateA;     //true:基类的公共成员在派生类中为私有成员  
    }  
};  

class D :protected A  
{  
public:  
    void funct()  
    {  
        int d;  
        d=privatedateA;   //error:基类中私有成员在派生类中是不可见的  
        d=protecteddateA; //true:基类的保护成员在派生类中为保护成员  
        d=publicdateA;    //true:基类的公共成员在派生类中为保护成员  
    }  
};  

int main()  
{  
    int a;   

    B objB;  
    a=objB.privatedateA;   //error:基类中私有s成员在派生类中是不可见的,对对象不可见  
    a=objB.protecteddateA; //error:基类的保护成员在派生类中为保护成员,对对象不可见  
    a=objB.publicdateA;    //true:基类的公有成员在派生类中为公共成员,对对象可见  

    C objC;  
    a=objC.privatedateA;   //error:基类中私有成员在派生类中是不可见的,对对象不可见  
    a=objC.protecteddateA; //error:基类的保护成员在派生类中为私有成员,对对象不可见  
    a=objC.publicdateA;    //error:基类的公有成员在派生类中为私有成员,对对象不可见  

    D objD;  
    a=objD.privatedateA;   //error:基类中私有成员在派生类中是不可见的,对对象不可见  
    a=objD.protecteddateA; //error:基类的保护成员在派生类中为保护成员,对对象不可见  
    a=objD.publicdateA;    //error:基类的公有成员在派生类中为保护成员,对对象不可见  

    return 0;  
}  

———————————————— 版权声明:本文为CSDN博主「Joerrot」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/DYD850804/article/details/80514591

关于博主

an actually real engineer

通信工程专业毕业,7年开发经验

技术栈:

精通c/c++

精通golang

熟悉常见的脚本,js,lua,python,php

熟悉电路基础,嵌入式,单片机

耕耘领域:

服务端开发

嵌入式开发

git

>

gin接口代码CURD生成工具

sql ddl to struct and markdown,将sql表自动化生成代码内对应的结构体和markdown表格格式,节省宝贵的时间。

输入ddl:
输出代码:

qt .ui文件转css文件

duilib xml 自动生成绑定控件代码

协议调试器

基于lua虚拟机的的协议调试器软件 支持的协议有:

串口

tcp客户端/服务端

udp 组播/udp节点

tcp websocket 客户端/服务端

软件界面

使用例子: 通过脚本来获得接收到的数据并写入文件和展示在界面上

下载地址和源码

duilib版本源码 qt qml版本源码 二进制包

webrtc easy demo

webrtc c++ native 库 demo 实现功能:

基于QT

webrtc摄像头/桌面捕获功能

opengl渲染/多播放窗格管理

janus meeting room

下载地址和源码

源码 二进制包

wifi,蓝牙 - 无线开关

实现功能:

通过wifi/蓝牙实现远程开关电器或者其他电子设备

电路原理图:

实物图:

深度学习验证工具

vtk+pcl 点云编辑工具

实现功能:

1. 点云文件加载显示(.pcd obj stl)

2. 点云重建

3. 点云三角化

4. 点云缩放

下载地址:

源码 二进制包

虚拟示波器

硬件实物图:

实现原理

基本性能

采集频率: 取决于外部adc模块和ebaz4205矿板的以太网接口速率,最高可以达到100M/8 约为12.5MPS

上位机实现功能: 采集,显示波形,存储wave文件。

参数可运行时配置

上位机:

显示缓冲区大小可调

刷新率可调节

触发显示刷新可调节

进程守护工具

基本功能:

1. 守护进程,被守护程序崩溃后自动重启。

2. 进程输出获取,显示在编辑框中。

二进制包

openblt 烧录工具

基本功能:

1. 加载openblt 文件,下载到具有openblt bootloader 运行的单片机中。

二进制包

opencv 功能验证工具(开源项目二次开发)

基本功能:

1. 插件化图像处理流程,支持自定义图像处理流程。 2. 完善的日志和权限管理

二进制包

又一个modbus调试工具

最近混迹物联网企业,发现目前缺少一个简易可用的modbus调试工具,本软件旨在为开发者提供一个简单modbus测试工具。
主打一个代码简单易修改。
特点:

1. 基于QT5

2. 基于libmodbus

3. 三方库完全跨平台,linux/windows。

二进制包

屏幕录制工具

1. 基于QT5

2. 基于ffmpeg

3. 支持自定义录屏

源代码

开源plutosdr 板卡

1. 完全开源

2. 提高固件定制服务

3. 硬件售价450 手焊产量有线

测试数据

内部DDS回环测试

接收测试

外部发送500MHZ FM波形

硬件原理图

matlab测试

2TRX版本

大部分plutosdr应用场景都是讲plutosdr板卡作为射频收发器来使用。
实际上plutosdr板卡本身运行linux 操作系统。是具有一定脱机运算的能力。 对于一些微型频谱检测,简单射频信号收发等应用完全可以将应用层直接实现在板卡上
相较于通过网卡或者USB口传输具有更稳定,带宽更高等优点。
本开源板卡由于了SD卡启动,较原版pluto支持了自定义启动应用的功能。
提供了应用层开发SDK(编译器,buildroot文件系统)。
通过usb连接电脑,经过RNDIS驱动可以近似为通过网卡连接
(支持固件的开发定制)。

二次开发例子

``` all:
arm-linux-gnueabihf-gcc -mfloat-abi=hard --sysroot=/root/v0.32_2trx/buildroot/output/staging -std=gnu99 -g -o pluto_stream ad9361-iiostream.c -lpthread -liio -lm -Wall -Wextra -lrt
clean:
rm pluto_stream

bsdiff算法补丁生成器

1. 官方bsdiff算法例子自带bzip压缩方式

2. 本例子没有压缩,直接生成补丁文件

3. 图形化界面基于DUILIB

二进制文件

版面分析即分析出图片内的具体文件元素,如文档标题,文档内容,文档页码等,本工具基于cnstd模型

Base64 Image

这个是一个互联网抓取下来的感兴趣的数据集合

查询

查询

. 闽ICP备19002644号