C++ 零碎知识点集锦 一

1,纯虚函数的定义:函数后面有=0,纯虚函数无需定义。

void Func() = 0;

2,虚函数:
作用:定义为虚函数,子类可以重写基类的虚函数。使该函数在基类和子类中存在多种定义(也就是多态)。

2.1,如何通过虚函数实现多态?
虚函数和普通函数主要不同之处是:虚函数是运行时才被解析,确定调用该虚函数的是基类还是子类。然而普通函数则是在编译期就解析确定。

//三个子类,一个父类SonA,SonB,SonC,Father,并且父类存在Say()这个虚函数,子类重写这个虚函数
void saySomeThing(Father* vfather)
{
    vfather.Say();
}

如上面代码,如果将father的指针传进形参,那么输出的是father的say()函数。如果将SonA的指针传入形参,那么输入的是SonA的say()函数。(这就是多态,一个函数多种实现,并且运行时确定调用那种实现(是Father的Say(),还是SonA的Say(),还是SonB的Say(),还是SonC的Say()))
注意:
a,只有通过指针或者引用的时候调用虚函数,才会动态绑定,在运行期解析。
b,通过对象调用虚函数时,不会动态绑定,在编译期就已经解析。
c,普通函数(非虚函数),不会动态绑定,在编译器就已经解析。

2.2,多态的底层原理。
通过虚表
编译器在编译的时候,发现Father类中有虚函数,此时编译器会为每个包含虚函数的类创建一个虚表(即 vtable),该表是一个一维数组,在这个数组中存放每个虚函数的地址
这里写图片描述
那么如何定位虚表呢?编译器另外还为每个对象提供了一个虚表指针(即vptr),这个指针指向了对象所属类的虚表,在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向了所属类的虚表,从而在调用虚函数的时候,能够找到正确的函数,对于第二段代码程序,由于pFather实际指向的对象类型是Son,因此vptr指向的Son类的vtable,当调用pFather->Son()时,根据虚表中的函数地址找到的就是Son类的Say()函数.

  正是由于每个对象调用的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的正确初始化是非常重要的,换句话说,在虚表指针没有正确初始化之前,我们不能够去调用虚函数,那么虚表指针是在什么时候,或者什么地方初始化呢?

  答案是在构造函数中进行虚表的创建和虚表指针的初始化,在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表,当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。

3,动态绑定

class Basic
{
    //No Define
    virtual void Test();
}
class Derived : public Basic
{
    //No Define And Derived Basic
    virtual void Test() override;
}
void Func(Basic& Ba)
{
    Ba.Test();
};

//Call Func
Func(Ba);//1
Func(De);//2

程序编译期间,编译器并不知道,Func中Ba.Test();调用的是Basic中的函数,还是Derived中的函数,只有当在运行时,根据传入Func的类型来判断。1则运行的是Basic中的函数。2运行的是Derived中的函数。这就是动态绑定

4,为什么定义抽象类?
答:抽象类是普通基类的特殊情况(普通基类所有成员函数都为纯虚函数),目的就是解决不能实例化的对象。比如说,“东西”。
应用场景:小李,将桌子上的“东西”拿过来!桌子上有水杯,有书,有笔。你能为水杯,书,笔,创建一个基类吗?不可能的!因为他们没有共同的行为。但是可以将他们定义为一个抽象类,表示他们有同一种状态(在桌子上)。

5,子类对象可以赋值给父类对象,但是父类对象不能赋值给子类对象?
答:由于子类继承父类,所以,子类的对象里面包含父类的内容,所以当访问赋值后的父类时,可以访问到父类的内容,但是访问不到子类的内容。
当父类对象赋值给子类对象的时候,访问转化后的子类的时候,调用子类本身的函数时候,就会发生地址越界。

6,如何理解注册回调函数?
答:注册回调函数,也就是将函数指针放到某个变量中,等到合适的机会调用。就是使用者自己定义一个函数,使用者自己实现这个函数的程序内容,然后把这个函数作为参数传入别人(或系统)的函数中,由别人(或系统)的函数在运行时来调用的函数。函数是你实现的,但由别人(或系统)的函数在运行时通过参数传递的方式调用,这就是所谓的回调函数。简单来说,就是由别人的函数运行期间来回调你实现的函数。

#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <vector>

class CFather
{
public:
    CFather() {};
    ~CFather() {};

    void buySomeThing(double vMoney) 
    {
        std::cout << "spend " << vMoney << "$" << std::endl;
    };

    void registerFuntion(boost::function<void(double)> vFunction) 
    {
        m_FunctionSet.push_back(vFunction);
    };

    std::vector<boost::function<void(double)>> m_FunctionSet;
};

class CMather
{
public:
    CMather() 
    {
        m_Father = new CFather;
    };
    ~CMather() 
    {
        delete m_Father;
    };

    void giveFatherMoney(double vMoney) 
    {
        m_Father->registerFuntion(boost::bind(&CFather::buySomeThing, m_Father, vMoney));
    }

    CFather* m_Father;
};


void main()
{
    CMather* Mather = new CMather;
    Mather->giveFatherMoney(32);
    Mather->giveFatherMoney(34);
    Mather->giveFatherMoney(45);

    for (auto e : Mather->m_Father->m_FunctionSet)
    {
        e(0);
    }

    system("pause");
}

7,memory barrier(内存屏障),
什么是memory barrier?
要求在内存屏障之前的操作,一定在内存屏障之后的操作之前执行。
这适用于多线程的操作。

8,单例模式的double checked
最简单的单例模式实现

Singleton* Singleton::getInstance() {
    Lock lock;      // scope-based lock, released automatically when the function returns
    if (m_instance == NULL) {
        m_instance = new Singleton;
    }
    return m_instance;
}

上面的实现,有缺陷,那就是多线程的问题。当有多个线程访问同时(simultaneously)访问的时候,将会多次实例化。
为了解决这个方案,对代码进行优化:

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance;
    if (tmp == NULL) {
        Lock lock;
        tmp = m_instance;
        if (tmp == NULL) {
            tmp = new Singleton;
            m_instance = tmp;
        }
    }
    return tmp;
}

但是上面的代码也存在问题,就是缺少同步。
此时为了解决这个问题,需要在上面代码中加入原子操作。

std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;
            std::atomic_thread_fence(std::memory_order_release);
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}

9,boost::unique_lock 与 boost::share_lock
答:boost::share_lock :主要用于多线程的读操作。使用share_lock,那么所有的线程都会访问加锁的代码段。
boost::unique_lock:主要用于多线程的写操作。使用unique_lock的时候,仅仅只有一个线程可以访问加锁代码段。

10,boost::mutex,互斥对象。
当程序多线程执行时,一个线程通过调用锁函数来获取互斥对象的所有权。
例如下面的代码。那个线程最先运行到这里,那么他就获得了互斥对象的所有权。进行lock 和 unlock的操作。

boost::mutex m_Mutex;
boost::unique_lock<boost::mutex> Lock(m_Mutex);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值