C++中虚函数的理解,以及简单继承情况下的虚函数的表!

面向对象的三大特征=封装性+继承性+多态性

封装=将客观事物抽象成类,每个类对自身的数据和方法实行权限的控制

继承=实现继承+可视继承+接口继承

多态=将父类对象设置成为和一个或者更多它的子对象相等的技术,

用子类对象给父类对象赋值之后,

父类对象就可以根据当前赋值给它的子对象的特性一不同的方式运作

C++的空类有哪些成员函数

1.缺省构造函数

2.缺省拷贝构造函数

3.缺省析构函数

4.缺省赋值运算符

5.缺省取址运算符

6.缺省取址运算符const

PS:只有当实际使用的时候才会去使用这些类的成员函数的时候,编译器才回去定义它们。

拷贝构造函数和赋值运算符重载有以下两个不同之处:

1.拷贝构造函数生成的新的类对象,而赋值运算符不能;

2.由于拷贝构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用

检验源对象是否和新建对象相同。而赋值运算符则需要这个操作,另外赋值运算

中如果原来的对象中有内存分配要先把内存释放掉。

PS:当类中有指针类型的成员变量时,一定要重写拷贝构造函数和赋值运算符,不要使用默认的。

#include <iostream>
#include <stdio.h>

using namespace std;
#ifndef __GNUC__
#define __attribute__(x)
#endif // __GNUC__

__attribute__((constructor)) void before_main()
{
    cout << __FUNCTION__ << endl;
    cout << "这是在Main函数运行前执行的!" << endl;
}
__attribute__((destructor)) void after_main()
{
    cout << __FUNCTION__ << endl;
    cout << "这是在Main函数结束后执行的!" << endl;
}

对C++了解的人都知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。

在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证真实反应实际的函数。

这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以当用父类的指针来操作一个子类的时候,

这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

在C++标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置,这是为了保证正确取到虚函数的偏移量。

这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

//访问基类的私有虚函数
class A
{
public:
    virtual void g()
    {
        cout << "A:g()" << endl;
    }
    virtual void f()
    {
        cout << "A::f()" << endl;
    }
    virtual void h()
    {
        cout << "A::h()" << endl;
    }
};
/*
这类的虚函数表的排布方式是
vptr
 |
 |         &(A::g())
 |_ _ _ _ \&(A::f())
          /&(A::h())
*/

class B:public A
{
public:
    virtual void g()
    {
        cout << "B::g()" << endl;
    }
    virtual void h()
    {
        cout << "B::h()" << endl;
    }
};
/*
这个类的虚函数表是
vptr
 |
 |         &(A::g())-->&(B::g())
 |_ _ _ _ \&(A::f())
          /&(A::h())-->&(B::h())
*/
/*
Mark 一下:
   每一个具有虚函数的类都有一个虚函数表VTABLE,
   里面按在类中声明的虚函数的顺序存放着虚函数的地址,
   这个虚函数表VTABLE是这个类的所有对象所共有的,
   也就是说无论用户声明了多少个类对象,
   但是这个VTABLE虚函数表只有一个。

   在每个具有虚函数的类的对象里面都有一个VPTR虚函数指针,
   这个指针指向VTABLE的首地址,每个类的对象都有这么一种指针。
*/
class C
{
public:
    C() { cout << "C Constructor !" << endl; }
    ~C() { cout << "C Destroy !" << endl; }
};

//会在main函数调用之前执行的函数
static C s_c;
typedef void (*PFUNC)(void);

class AA
{
private:
    int a;
    double b;
public:
    virtual void func()
    {
        cout << "AA::func()" << endl;
    }
};
/*
在内存中的存储格式是
VPTR-->a-->b
  |
  |__\ &func()
     / &func()
*/

int main()
{
    cout << __FUNCTION__ << endl;
    cout << "Hello world!" << endl;
    /*
    很多人找不到vptr这个指针,其实这个B类继承自A类,且没有自己的成员变量,
    所以可以得出,B类的事例对象中的只有vptr这一个指针了。
    */
    B b;
    PFUNC pf;
    //先打印出虚函数表的地址
    cout << "虚函数表地址:" << (int*)(&b) << endl;
    cout << "虚函数表第一个函数地址:" << (int*)(*(int*)(&b)) << endl;

    for(int i=0;i<3;i++)
    {
        pf=(PFUNC)*((int*)*(int*)(&b)+i);
        pf();
    }
    return 0;
}

时间: 2024-10-27 08:35:53

C++中虚函数的理解,以及简单继承情况下的虚函数的表!的相关文章

浅谈SQL Server中的事务日志(三)----在简单恢复模式下日志的角色

浅谈SQL Server中的事务日志(三)----在简单恢复模式下日志的角色 本篇文章是系列文章中的第三篇,前两篇的地址如下: 浅谈SQL Server中的事务日志(一)----事务日志的物理和逻辑构架 浅谈SQL Server中的事务日志(二)----事务日志在修改数据时的角色 简介 在简单恢复模式下,日志文件的作用仅仅是保证了SQL Server事务的ACID属性.并不承担具体的恢复数据的角色.正如”简单”这个词的字面意思一样,数据的备份和恢复仅仅是依赖于手动备份和恢复.在开始文章之前,首先

C++:一般情况下,设计函数的形参只需要两种形式

C++:一般情况下,设计函数的形参只需要两种形式.一,是引用形参,例如 void function (int &p_para):二,是常量引用形参,例如 void function(const int &p_para). 它们的特点如下: # 引用形参适用于需要改变变量数据的情况,常量引用形参适用于不需要改变对象.变量数据的情况. # 引用形参需要对象.变量来传递值,常量引用形参则不需要,可以直接传递表达式或者函数返回值. 通过这两种方式可以涵盖所有可能需要的设计情况,而通过这种方式实现的

C#检测程序重复运行的函数(可以在多用户登录情况下检测)

上文是在网上找的检测程序重复运行的类,但是感觉不是很好用,而且还使用了API,似乎完全没有必要,于是晚上自己写了一个函数,经过测试,在多用户下仍然可以检测到程序的多次运行.当然,如果程序改了名字还是可以再次运行,不过这种方式只怕没有什么太好的办法来,除非是在.NET环境或注册表中写入一些标志,但似乎也没有必要. if (AppInstance()) { MessageBox.Show("警告:程序正在运行中! 请不要重复打开程序!", "系统提示", Message

多线程生产者、消费者模式中,如何停止消费者?多生产者情况下对“毒丸”策略的应用。

生产者.消费者模式是多线程中的经典问题.通过中间的缓冲队列,使得生产者和消费者的速度可以相互调节. 对于比较常见的单生产者.多消费者的情况,主要有以下两种策略: 通过volatile boolean producerDone =false 来标示是否完成.生产者结束后标示为true, 消费者轮询这个变量来决定自己是否退出. 这种方式对producerDone产生比较大的争用,实现起来也有诸多问题需要考虑. 比较经典的"毒丸"策略,生产者结束后,把一个特别的对象:"毒丸&quo

IE 中单元格的 colspan 属性在某些情况下会影响 TABLE 元素的自动布局

今天在写一个jsp页面时,遇到一个如下的问题:在一个table中写了如下内容,table中定义了4列,在firefox中能正常显示,而在ie8中,显示不正常, 如下如图1:第二,三,四列宽度发生变化,和代码中定义的宽度显示不一致, 图1(IE8): 图2(firefox): 代码如下: 1 <table id="cont2"> 2 <tbody> 3 <tr> 4 <td class="ct_t"> 5 是否分包<

Python多线程的理解和使用(一)Threading中join()函数的理解

1. 多线程的概念 多线程类似于同时执行多个不同程序,多线程运行有如下优点: 使用线程可以把占据长时间的程序中的任务放到后台去处理.用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度 程序的运行速度可能加快在一些等待的任务实现上如用户输入.文件读写和网络收发数据等,线程就比较有用了.在这种情况下我们可以释放一些珍贵的资源如内存占用等等.线程在执行过程中与进程还是有区别的.每个独立的线程有一个程序运行的入口.顺序执行序列和程序的出口.但是线程

设计模式粗浅理解之一------简单工厂模式

设计模式粗浅理解之------简单工厂模式 什么是简单工厂模式 从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一.简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例.简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现. 角色和职责 工厂(Creator)角色 简单工厂模式的核心,它负责实现创建所有实例的内部逻辑.工厂类可以被外界直接调用,创建所需的产

js回调函数(callback)理解

Mark! js学习 不喜欢js,但是喜欢jquery,不解释. 自学jquery的时候,看到一英文词(Callback),顿时背部隐隐冒冷汗.迅速google之,发现原来中文翻译成回调.也就是回调函数了.不懂啊,于是在google回调函数,发现网上的中文解释实在是太“深奥”了,我承认自己才疏学浅了.看了几个回调的例子后,貌似有点理解了.下面是我对回调函数的理解,要是理解错了,请指正,不甚感激. 首先还是从jquery网站上的英文定义入手,身为国人,我真感到悲剧.一个回调的定义被国内的“高手”解

构造函数为什么不能为虚函数 &amp;amp; 基类的析构函数为什么要为虚函数

一.构造函数为什么不能为虚函数 1. 从存储空间角度,虚函数相应一个指向vtable虚函数表的指针,这大家都知道,但是这个指向vtable的指针事实上是存储在对象的内存空间的.问题出来了,假设构造函数是虚的,就须要通过 vtable来调用,但是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数. 2. 从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到相应的调用.构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀.所以构造函数没有必要是虚