两种隐式类型转换的详解

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,并且提供一个对外获取对象的接口,该接口的功能是每次先判断对象是否创建,如果没有创建,那么创建对象,然后返回,如果对象已经创建那么直接返回该对象。而两种单例模式的区别是,第一种是返回对象的指针,第二种是返回对象的引用。