设计模式详解三:Observer模式(观察者模式)

观察者模式是一种“一(Subject)对多(Observer)”的设计模式,并且当“一”发生变化的时候,对应的“多”也能够同步收到通知或者改变。

一、设计模式的结构图

Observer模式的结构图:Subject目标基类中的Attatch函数实现将Observer指针存储到list列表,Detach函数实现将Observer指针从list列表中移除,Notify函数内部是循环检查list列表中存放的Observer指针,如果存在,那么调用Observer实现的Update函数。Observer观察者基类定义Update虚函数,它的继承者SubObserverA、SubObserverB……实现Update函数。

二、程序代码实现

1、首先定义目标基类Subject以及它的子类SubSubject

class Observer;

////目标基类
class Subject
{
public:
    virtual ~Subject();
    virtual void Attach(Observer* obv);
    virtual void Detach(Observer* obv);
    virtual void Notify();
    
protected:
    
    Subject();
private:
    list<Observer* >* m_obvs;
};

////目标实现类
class SubSubject:public Subject
{
public:
    SubSubject();
    ~SubSubject();

protected:

private:
    
};

2、实现目标基类以及它的子类SubSubject

////目标基类
Subject::Subject()
{
    m_obvs = new list<Observer*>;
}

Subject::~Subject()
{
    
}

void Subject::Attach(Observer* obv)
{
    m_obvs->push_front(obv);
}

void Subject::Detach(Observer* obv)
{
    if (obv != NULL) m_obvs->remove(obv);
}

void Subject::Notify()
{
    list<Observer*>::iterator it;
    it = m_obvs->begin();
    for (;it != m_obvs->end();it++)
    {
        (*it)->Update(this);
    }
}

////目标子类
SubSubject::SubSubject()
{
}

SubSubject::~SubSubject()
{
}

3、定义观察者基类,声明Update为虚函数,同时定义观察者的子类A和观察者的子类B,并且子类A和子类B维护者指向Subject的指针。

//// Observer观察者的基类
class Observer
{
public:
    virtual ~Observer();
    virtual void Update(Subject* sub) = 0;
    
protected:
    Observer();
private:
};

//// Observer观察者的子类A
class SubObserverA:public Observer
{
public:
    virtual Subject* GetSubject();
    SubObserverA(Subject* sub);
    virtual ~SubObserverA();
    
    //传入 Subject 作为参数,这样可以让一个 View 属于多个的 Subject。
    void Update(Subject* sub);
private:
    Subject* _sub;
};

//// Observer观察者的子类B
class SubObserverB:public Observer
{
public:
    virtual Subject* GetSubject();
    SubObserverB(Subject* sub);
    virtual ~SubObserverB();
    
    //传入 Subject 作为参数,这样可以让一个 View 属于多个的 Subject。
    void Update(Subject* sub);
    
private:
    Subject* _sub;
};

4、实现观察者基类,观察者的子类A和观察者的子类B

//// Observer观察者的基类
Observer::Observer()
{
}

Observer::~Observer()
{
}

//// Observer观察者的子类A
SubObserverA::SubObserverA(Subject* sub)
{
    _sub = sub;
    _sub->Attach(this);
}

SubObserverA::~SubObserverA()
{
    _sub->Detach(this);
    if (_sub != 0)
        delete _sub;
}

Subject* SubObserverA::GetSubject()
{
    return _sub;
}

void SubObserverA::Update(Subject* sub)
{
    DEBUG_LOG("", "SubObserverA update");
    // 由于SubObserverA持有Subject的指针,因此这里可以去调用Subject的相关方法
}

//// Observer观察者的子类B
SubObserverB::SubObserverB(Subject* sub)
{
    _sub = sub;
    _sub->Attach(this);
}

SubObserverB::~SubObserverB()
{
    _sub->Detach(this);
    if (_sub != 0)
        delete _sub;
}

Subject* SubObserverB::GetSubject()
{
    return _sub;
}

void SubObserverB::Update(Subject* sub)
{
    DEBUG_LOG("", "SubObserverB update");
    // 由于SubObserverB持有Subject的指针,因此这里可以去调用Subject的相关方法
}

三、测试验证结果

代码实现完成之后,主程序输入如下所示的代码段,用于验证subject调用Notify的时候是否会通知到注册到它的列表中的观察者对象。

SubSubject * sub = new SubSubject();
Observer *obsA = new SubObserverA(sub);
Observer *obsB = new SubObserverB(sub);

sub->Notify();

运行结果如下所示,从结果看Subject调用Notify函数之后,就会调用注册到列表中的观察者对象的Update函数。

[2019-05-12 22:40:09.426  Update:110]   = SubObserverB update
[2019-05-12 22:40:09.426  Update:85]   = SubObserverA update

由于观察者对象维护者Subject的指针,因此,可以通过观察者对象来间接调用Notify函数

SubSubject * sub = new SubSubject();
Observer *obsA = new SubObserverA(sub);
Observer *obsB = new SubObserverB(sub);

SubObserverA *obsA2 =dynamic_cast<SubObserverA *>(obsA);
obsA2->GetSubject()->Notify();

四、MVC结构的理解

MVC架构是Observer的一个实例,Observer就是观察者, Observable对应目标Subject,M代表Model,V代表View,C代表Control, Control为控制模块,接收到用户的输入信息之后,传递给Model模块,该模块进行业务处理,最后展示出不同的视图View。

五、总结

观察者模式也称为发布订阅模式,目标就是就是通知的发布者,观察者是通知的订阅者。目标可以通知观察者,并且由于观察者持有目标对象的指针,因此,观察者也可以主动触发目标去通知其他的观察者对象。

 

理解策略路由

一、linux内置的路由表

linux默认三种路由表,存放在/etc/iproute2/rt_tables

local:  本地接口地址,广播地址,以及NAT地址都放在这个表。该路由表由系统自动维护,管理员不能直接修改。
main:  执行 route -n就是读取这张表的信息。如果没有指明路由所属的表,所有的路由都默认放在这个表里。
default: 默认的路由。

[root@f8s home]# ip rule show
0:      from all lookup local 
32766:  from all lookup main 
32767:  from all lookup default

二、规则和路由的关系

规则(ip rule)控制使用那个路由表,ip table往路由表中设置路由信息

三、设置策略路由

结合例子说明设置策略路由的过程

1、创建一个虚拟网卡eth2.300

# 创建虚拟网卡
vconfig add eth2 300
# 设置虚拟网卡的IP地址,并启用网卡
ifconfig eth2.300 192.168.100.50 netmask 255.255.255.0 up

通过命令ifconfig查看网卡信息

[root@f8s home]# ifconfig 
eth2      Link encap:Ethernet  HWaddr 00:0C:29:87:80:CD  
          inet addr:192.168.255.128  Bcast:192.168.255.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fe87:80cd/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:90005 errors:0 dropped:0 overruns:0 frame:0
          TX packets:87454 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:15158203 (14.4 MiB)  TX bytes:13314917 (12.6 MiB)
          Interrupt:18 Base address:0x2000 
 
eth2.300  Link encap:Ethernet  HWaddr 00:0C:29:87:80:CD  
          inet addr:192.168.100.50  Bcast:192.168.100.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fe87:80cd/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:27 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 

2、创建一条规则, 从地址192.168.100.50发送的数据,都走路由表100

# 创建路由规则
ip rule add from 192.168.100.50/32 table 100

查看规则信息:

[root@f8s home]# ip rule show
0:      from all lookup local 
32765:  from 192.168.100.50 lookup 100 
32766:  from all lookup main 
32767:  from all lookup default 

3、往路由表100,添加路由信息

# 发到192.168.100.0/24网段的网络包,从网卡eth2.300发生出去,包的源IP地址设为192.168.100.50
ip route add 192.168.100.0/24 dev eth2.300 src 192.168.100.50 table 100

路由表table 100的路由信息:

[root@f8s home]# ip route show table 100
192.168.100.0/24 dev eth2.300  scope link  src 192.168.100.50

四、总结

策略路由:首先通过ip rule定义规则,确认使用那个路由表,然后通过ip route往路由表中添加路由信息。

 

 

 

 

 

自制智能指针

一、智能指针的原理

智能指针就是封装了创建对象的指针,并且可以在对象过期的时候,让析构函数自动删除指向的内存。

二、代码实现

为了防止拷贝和赋值,我们将拷贝函数和赋值构造函数放到private私有区域内。智能指针的类模版的实现方式:

template <typename T>
class SmartPtr
{
public:
    // explicit防止隐式转换
    explicit SmartPtr(T *pObj);
    ~SmartPtr(void);

public:
    T* operator->();
    T* Get();
    void Reset(T* pObj);
    
private:
    // 防止拷贝和赋值
    SmartPtr(const SmartPtr &);
    SmartPtr & operator= (const SmartPtr &);
    
    T* m_pObj;
};

template<typename T>
SmartPtr<T>::SmartPtr(T *pObj):m_pObj(pObj)
{
    
}

template<typename T>
SmartPtr<T>::~SmartPtr(void)
{
    delete m_pObj;
    m_pObj = 0;
}

template<typename T>
T* SmartPtr<T>::operator->()
{
    return m_pObj;
}

template<typename T>
T* SmartPtr<T>::Get()
{
    return m_pObj;
}

template<typename T>
void SmartPtr<T>::Reset(T* pObj)
{
    delete m_pObj;
    m_pObj = m_pObj;
}

三、测试验证

为了验证智能指针在退出的时候是否会自动调用delete来释放动态创建的对象,我们定义实现ObjectA对象。具体的实现细节如下所示:

class ObjectA
{
public:
    // 构造函数
    ObjectA(int iNum);
    ObjectA();
    // 析构函数
    ~ObjectA();
 
private:
    int m_num;
};
ObjectA::ObjectA(int iNum):m_num(iNum)
{
    cout << "ObjectA constructor" << endl;
}

ObjectA::ObjectA()
{
    cout << "ObjectA constructor, no param" << endl;
}

ObjectA::~ObjectA()
{
    cout << "ObjectA destructor" << endl;
}

完成上面ObjectA对象的定义和实现后,接下来就开始进行验证,验证代码如下:

ObjectA *pobj = new ObjectA;
SmartPtr<ObjectA> smartPtr(pobj);

通过调试运行,最后输出的结果为:

ObjectA constructor, no param
ObjectA destructor

这说明智能指针调用自身的析构函数,而析构函数中又回去删除动态创建对象的内存。

四、注意事项

因为模版不是函数,不能单独编译,所以实现智能指针的模版类的时候,需要将所有模版信息放入头文件中,其他文件使用的时候,再包含该文件。

五、总结

智能指针的机制就是利用本地变量在退出函数之后会自动删除的特性。首先保存动态创建对象的指针,当本地变量的生命周期结束的时候,自动调用其析构函数,而析构函数则会释放保存的指针指向的内存,从而达到避免调用new而忘记delete导致出现内存泄露的问题。

设计模式详解二:原型模式(Prototype)

原型模式,简单说,就是自我复制的功能,通过已存在对象来创建新的对象。本文将通过C++的拷贝构造函数来实现原型模式。

一、Prototype模式的结构图

Prototype模式的结构图很简单,定义基类Prototype,然后声明创建新对象的接口Clone,  继承自基类Prototype的子类SubPrototype, 该类实现接口Clone,并且实现拷贝构造函数。

二、程序代码实现

Prototype模式的代码实现也比较简单,下面直接给出代码

1. 首先类的声明

class Prototype
{
public:
    // 析构函数
    virtual ~Prototype();
    // 纯虚函数
    virtual Prototype *Clone() const = 0;
    
protected:
    // 构造函数,设置为protected, 防止创建实例对象
    Prototype();
};

class SubPrototype:public Prototype
{
public:
    // 构造函数
    SubPrototype();
    // 析构函数
    ~SubPrototype();
    // 拷贝构造函数
    SubPrototype(const SubPrototype &sp);
    // 复制对象
    Prototype *Clone() const;
};

2. 接着是类的实现

Prototype::Prototype()
{
    
}

Prototype::~Prototype()
{
    
}



SubPrototype::SubPrototype()
{
    
}

SubPrototype::~SubPrototype()
{
    
}

SubPrototype::SubPrototype(const SubPrototype &sp)
{
    DEBUG_LOG("", "copy constructor");
}

Prototype *SubPrototype::Clone() const
{
    // 这里调用到拷贝构造函数
    return new SubPrototype(*this);
}

三、测试验证结果

完成代码实现之后,我们在主程序代码上输入如下代码段来确认是否调用到拷贝构造函数,是否符合我们的预期设想。

Prototype * pobj1 = new SubPrototype();

Prototype * pobj2 = pobj1->Clone();

运行结果如下所示:

[2019-04-13 14:13:37.248  SubPrototype:36]   = copy constructor

从运行结果看,调用Clone函数之后,成功调用到拷贝构造函数,这里的拷贝构造函数内部只是打印了日志信息来方便我们的验证,实际开发过程中,实现拷贝构造函数要特别注意深拷贝和浅拷贝的问题。

四、总结

Prototype模式就是复制自己的信息来创建新的对象,是属于创建型的模式。

字符串string操作效率的思考

平常开发的时候,我们使用字符串string进行各种操作的过程中,有时候为了清晰的逻辑或者操作的简便,经常喜欢将字符串拷贝到另一个字符串中,再进行各种操作,在数据量少或者操作次数比较少的情况下,一般对程序的效率是不会有太大的影响,但是当数数据量大或者操作次数大的情况下,情况会是怎样呢?话不多说,首先,给出两个分隔字符串的函数,一个是没有优化的,其中采用字符串的赋值,另一个经过了优化。

1> 没有优化的分隔字符串函数

// 按照指定的分隔符,分隔字符串
// 例如split_string_test("a|b|c","|", vec);
// 得到的结果vec = ["a","b","c"]
void Public::split_string_test(const string &src, const string &split, vector<string> &vec_ret)
{
    vec_ret.clear();
    string tmp = src;
    
    while(1)
    {
        // 注意:size_t是unsigned, 而ssize_t是有符号整型,这里必须使用ssize_t
        // 32位系统上 size_被定义为 typedef unsigned innt size_t;  ssize_t等价于int
        ssize_t iPos = tmp.find(split);
        if (iPos <= 0)
        {
            vec_ret.push_back(tmp);
            break;
        }
        vec_ret.push_back(tmp.substr(0,iPos));
        tmp = tmp.substr(iPos + split.size());
    }
}

2> 经过优化的分隔字符串函数

// 按照指定的分隔符,分隔字符串
// 例如split_string("a|b|c","|", vec);
// 得到的结果vec = ["a","b","c"]
void Public::split_string(const string &src, const string &split, vector<string> &vec_ret)
{
    vec_ret.clear();
    const string &tmp = src;
    ssize_t iPos_l = 0;
    ssize_t iPos_r = 0;
    while(1)
    {
        // 从iPos_l位置开始查找分隔符split
        iPos_r = tmp.find(split,iPos_l);
        if (iPos_r <= 0)
        {
            vec_ret.push_back(tmp.substr(iPos_l));
            break;
        }
        vec_ret.push_back(tmp.substr(iPos_l,iPos_r - iPos_l));
        iPos_l = iPos_r + split.size();
    }
}

从上面两个分隔字符串函数中,可以看出第一个分隔字符串函数将传入的源字符串拷贝到临时的变量中进行操作,而第二个分隔字符串函数通过引用和位置的索引的方式来操作源字符串。接下来,我们通过实验来看看两者之间在效率上有多大差异。

主函数输入如下所示的代码段来进行实验

int i = 0;
string src = "a|b|c";
string split = "|";
vector<string> vec_ret;
unsigned long long ull_start_time;
unsigned long long ull_end_time;
int icount = 100;

// 没有优化的函数
ull_start_time = Public::get_system_time();
for (i = 0; i < icount; i++)
{
    Public::split_string_test(src, split, vec_ret);
}
ull_end_time = Public::get_system_time();
cout << "[split_string_test] time: " << ull_end_time - ull_start_time << " ms" <<endl;
   
// 经过优化的函数
ull_start_time = Public::get_system_time();
for (i = 0; i < icount; i++)
{
    Public::split_string(src, split, vec_ret);
}
ull_end_time = Public::get_system_time();
cout << "[split_string] time: " << ull_end_time - ull_start_time << " ms" <<endl;

一、 正常情况

字符串src = “a|b|c”;设置操作的次数为icount = 100; 运行调试结果如下所示

[split_string_test] time: 0 ms
[split_string] time: 0 ms

从上面运行的结果看,两个函数基本上没有耗时,看不出任何差异

二、 操作次数增加的情况

字符串src = “a|b|c”;设置操作的次数为icount = 10000; 运行调试结果如下所示

[split_string_test] time: 10 ms
[split_string] time: 0 ms

从上面运行的结果看,操作次数增加的情况下,第一个函数比第二个函数多耗时10ms

三、数据量增加的情况

字符串src = “a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j”;设置操作的次数为icount = 100; 运行调试结果如下所示

[split_string_test] time: 20 ms
[split_string] time: 10 ms

从上面运行的结果看,数据量增加的情况下,第一个函数比第二个函数多耗时10ms

四、数据量增加,操作次数增加情况

字符串src = “a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j”;设置操作的次数为icount = 10000; 运行调试结果如下所示

[split_string_test] time: 1710 ms
[split_string] time: 600 ms

从上面运行的结果看,数据量增加,操作次数增加的情况下,第一个函数比第二个函数竟然多耗时1.11s, 这可是相当可观的数据。

五、总结

从上面实验结果看,字符串的赋值拷贝操作,数据量大、操作次数多的情况下,能够明显看出比较耗时的。因此,我们在进行字符串的操作的时候,尽量采用引用和指针的方式来提高效率。

 

两种隐式类型转换的详解

operator除了操作符的重载之外,还可以进行隐式类型的转换,这与构造函数的隐式类型转换是有区别的。本文将通过例子说明operator的隐式类型转换,再说明构造函数的隐式类型转换,最后再总结他们之间的区别。

一、 operator隐式类型转换

operator隐式类型转换是当前对象生成其他类型的对象。首先实现ObjectA对象,用于说明operator的隐式类型转换。

class ObjectA
{
public:
    // 构造函数
    ObjectA(int iNum);
    // 析构函数
    ~ObjectA();
    // operator的隐式类型转换,转换的类型是int 
    operator int();
    
private:
    int m_num;
};
ObjectA::ObjectA(int iNum):m_num(iNum)
{
    cout << "ObjectA constructor" << endl;
}

ObjectA::~ObjectA()
{
    cout << "ObjectA destructor" << endl;
}
    
ObjectA::operator int()
{
    cout << "operator int " << endl;
    return m_num;
}

1、主程序输入如下代码段

ObjectA objA(3);
cout << objA + 4 << endl;

2、 调试运行输出的结果看,ObjA虽然不是int类型,但是与整数类型4进行相加的时候,会调用到operator int函数,该函数返回的结果是int类型

ObjectA constructor   // 调用构造函数
operator int          // 调用隐式类型转换
7                     // 计算结果
ObjectA destructor    // 调用析构函数

二、构造函数的隐式类型转换

构造函数的隐式类型转换,是用其他类型来构造当前类型的临时对象。为了说明构造函数的隐式类型转换,首先先实现ObjectB对象。

class ObjectA;

class ObjectB
{
public:
    // 无参数构造函数
    ObjectB();
    // 参数类型为对象ObjectA
    ObjectB(ObjectA objA);
    // 析构函数
    ~ObjectB();
    // 打印函数,注意入参类型是ObjectB
    int print(ObjectB B);  
};
ObjectB::ObjectB()
{
    cout << "ObjectB constructor" << endl;
}

ObjectB::ObjectB(ObjectA objA)
{
    cout <<":ObjectB(ObjectA objA)" << endl;
}
    
ObjectB::~ObjectB()
{
    cout << "ObjectB destructor" << endl;
}
    
int ObjectB::print(ObjectB B)
{
    cout << "ObjectB print" << endl;
    return 0;
}

1、主程序中输入如下代码段

ObjectA objA(3);
cout << objA + 4 << endl;

cout << "====" << endl;
ObjectB objB;
objB.print(objA);   // 等价于 objB.print(ObjectB(objA));
cout << "====" << endl;

2、 调试运行输出结果看,print函数入参类型是ObjectB,  但是传递的类型是ObjectA,但是ObjectB有单一形参类型为ObjectA的构造函数,程序会隐式的将objA,转换为ObjectB(objA)

ObjectA constructor
operator int 
7
====
ObjectB constructor
:ObjectB(ObjectA objA)
ObjectB print
ObjectB destructor
ObjectA destructor
====
ObjectB destructor
ObjectA destructor

三、总结

1、operator隐式类型转换是当前对象生成其他类型的对象

2、构造函数的隐式类型转换,是用其他类型来构造当前类型的临时对象, 与operator隐式类型转换是相反的。另外,如果想禁用构造函数的隐式类型转换,那么构造函数前添加explicit (例如 explicit ObjectB(ObjectA objA); )

 

C++三种new操作符的详解

工作开发过程中,一般申请创建内存,使用的是new方法。但是new存在三种操作符,其含义和应用的场景都不同。这三种操作符分别是new operator, operator new, placement new,  本文将针对这三种操作符结合例子进行说明。最后再总结它们的区别和联系。

一、 new operator 操作符

new operator指的就是new操作符,它经历两个阶段的操作:(1)调用::operator new申请内存(operator new后面将进行详细说明,这里理解为C语言中的malloc),(2)  调用类的构造函数。

定义类Func,  用于后面的验证测试

class FUNC
{
public:
    FUNC();
    virtual ~FUNC();
};
#include "Func.hpp"
#include <iostream>

FUNC::FUNC()
{
    std::cout << __func__ << ": call constructor"  << std::endl;
}

FUNC::~FUNC()
{
    std::cout << __func__ << ": call destructor" << std::endl;
}

1、 new操作符的一般调用方法

std::string *pstr = new std::string("hello, world");
std::cout << *pstr << std::endl;
delete pstr;

2、 new操作符调用自定义类Func

FUNC *pfunc = new FUNC;
delete pfunc;

最后终端输出结果如下图所示, 可以看出调用new操作符会调用对象的构造函数,而调用delete操作符会调用对象的析构函数。

FUNC: call constructor
~FUNC: call destructor

3、 new操作符不能被重载

二、 operator new 操作符

operator new操作符单纯申请内存,并且是可以重载的函数。(注意:::operator new 和 ::operator delete前面加上::表示全局)

1、operator new操作符的一般调用方法

调用operator new申请内存,内存申请的大小为自定义类Func的大小,经过调试发现,并没有输出类Func的构造函数,也没有调用Func的析构函数

FUNC *pfunc2 = (FUNC *)::operator new(sizeof(FUNC));
::operator delete(pfunc2);

2、重载operator new操作符

1) 首先FUNC类中添加如下信息

void* operator new(size_t size);

void operator delete(void* ptr);
void* FUNC::operator new(size_t size)
{
    cout << "operator new" << endl;
    // 全局operator new
    return ::operator new(size);
}


void FUNC::operator delete(void* ptr)
{
    cout << "operator delete" << endl;
    // 全局operator delete
    ::operator delete(ptr);
}

2) 主程序中调用new创建FUNC对象,然后调用delete释放对象

FUNC *pfunc = new FUNC;
delete pfunc;

3) 运行调试之后的结果信息如下所示,new调用到重载的函数operator new, 同样的,delete也调用到重载的函数operator delete

operator new
FUNC: call constructor
~FUNC: call destructor
operator delete

3、重载operator new操作符的第二种版本

1)首先FUNC类中添加如下信息

void* operator new(size_t size, string str);
void* FUNC::operator new(size_t size, string str)
{
    cout << "operator new version 2: " << str << endl;
    return  ::operator new(size);
}

2)主程序中调用new创建FUNC对象,然后调用delete释放对象

FUNC *pfunc = new("this is my world") FUNC;
delete pfunc;

3)运行调试之后的结果信息如下所示,new调用到重载的函数operator new的第二个版本

operator new version 2: this is my world
FUNC: call constructor
~FUNC: call destructor
operator delete

三、 placement new操作符

placement new操作符是重载operator new的一个版本,该函数的执行忽略了size_t参数,只返还第二个参数,该函数允许在已经构建好的内存中创建对象

// placement new
void *operator new( size_t, void * p ) throw() { return p; }

// 调用格式
pi = new(ptr) int

1、placement new操作符的使用方法

// 提前申请创建内存
char *buf = new char[sizeof(FUNC)];
// 创建对象FUNC将对象指向已经创建好的内存地址,注意这里需要使用::, 否则可能会调用到重载的operator new
FUNC *pfunc = ::new(buf) FUNC;
// 这里使用使用变量pfunc调用类FUNC中的功能
// ...
// 使用完成调用析构函数
pfunc->~FUNC();
// 释放已经创建的内存
delete []buf;

2、 终端输出打印信息如下所示, 从中可以发现placement new会调用到对象的构造函数

FUNC: call constructor
~FUNC: call destructor

四、总结

从上面实验的结果,进行总结,具体如下:

1、 new operator即new操作符,不能被重载,调用的时候,先申请内存,再调用构造函数,这是常用的调用方式。

2、 operator new操作符,能够被重载,单纯申请内存,相当于C语言中的malloc, 如果重载了operator new操作符,又需要调用原来的函数,那么需要在操作符前面加上::(即 ::operator new),重载该操作符通常是为了实现不同的内存分配方式。

3、placement new操作符,仅仅返回已经申请好内存的指针,它通常应用在对效率要求高的场景下,提前申请好内存,能够节省申请内存过程中耗费的时间。

UML类图关系

看类图的时候,理清类与类之间的关系是很重要的。类的关系一般分为泛化,实现,依赖,一般,聚合,组合这六种关系。本文首先给出六种类关系示意图来帮助记忆!然后再分别进行介绍说明!希望能够能够帮助到大家!

一、类图的3个基本组件

类名、属性、方法。 而属性和方法,可以设置三种访问权限,+表示公共,#表示保护,-表示私有

二、 基本概念

1、类图的三大组件:类名、属性、方法。用来表示系统中的类,接口以及它们的静态结构和关系。

2、泛化关系:是一种继承的关系,是is-a关系,子类继承父类,箭头从子类指向父类。

3、实现关系:接口和实现的关系,箭头从实现类指向接口。

4、组合关系:整体与部分的关系,是contains-a的关系,是强的包含关系,部分不能离开整体而存在。实心菱形指向整体

5、聚合关系:整体与部分的关系,但是是较弱的关系,部分可以离开整体单独存在,空心菱形指向整体

6、关联关系:是一种拥有关系,可以是双向或者单向,双向关联有两个箭头,单向关联只有一个箭头。箭头指向被拥有者。

7、依赖关系:是一种使用关系,对象间最弱的一种关系,通过调用被依赖类的方法或者参数等来完成指责。箭头指向被依赖者。

三、六种关系的强弱顺序

泛化 = 实现 -> 组合 -> 聚合 -> 关联 ->依赖

 

设计模式详解一:单例模式的两种实现方式

程序设计过程中,经常会听到各种各样的设计模式,其中单例模式是最简单,也是最经常使用的一种模式,它的目的是创建一个全局的唯一对象,因此,本文说明如何实现单例模式,以便运用到程序的设计过程中!

一、第一种单例模式的实现方式

1、首先定义如下的头文件,将构造函数设置为private,并定义一个Single的静态指针变量

2、定义完成头文件之后,进行具体的实现,主要是实现GetInstance函数,该函数实现的逻辑:如果静态指针变量为空,那么创建,否则直接返回静态指针变量,实现对象的唯一性

3、完成代码的实现,接下来进行测试验证,主函数输入如下信息

4、最后点击运行,可以看到如下的信息,能够成功调用函数

二、第二种单例模式的实现方式

1、 定义如下的头文件,将构造函数设置为private

2、定义完成头文件之后,进行具体的实现,主要是实现GetInstance函数,该函数实现的逻辑:直接创建静态对象,每次用户调用返回该对象

3、完成代码编写后进行测试验证,主程序输入如下代码信息

4、最后点击运行,可以看到如下的信息,能够成功调用函数

三、 总结两种设计模式

从上面实现两种实现单例的过程中,可以发现单例模式的特点就是将构造函数设置为private,并且提供一个对外获取对象的接口,该接口的功能是每次先判断对象是否创建,如果没有创建,那么创建对象,然后返回,如果对象已经创建那么直接返回该对象。而两种单例模式的区别是,第一种是返回对象的指针,第二种是返回对象的引用。