【C++】特殊类的设计与类型转换

在这里插入图片描述

1. 特殊类的设计

1.1 不能被拷贝的类

请设计一个类,不能被拷贝

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可

在C++98中,将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可

在这里插入图片描述
原因:

  1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
  2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了

在这里插入图片描述
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。

此时只要调用到这两个函数就会报错。
在这里插入图片描述

1.2 只能在堆上创建对象的类

实现方式:

  1. 类的构造函数私有,拷贝构造声明成私有(或者delete)。防止别人调用拷贝构造在栈上生成对象。
  2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建

在这里插入图片描述

class A
{
public:
    //设置为静态可直接使用类名访问,无需对象调用
    static A* Create(int a){
        return new A(a);
    }

private:
    A(int a = 0)
        :_a(a)
    {}

    A(const A& a)
        :_a(a._a)
    {}
    int _a;
};

int main()
{
    //不能在栈上创建对象
    //A aa1;
    //A aa2(10);

    A* aa3 = A::Create(20);//调用规定函数在堆上创建对象
    //A aa4(*aa3);//若不禁掉拷贝构造,仍可在栈上创建对象
    return 0;

1.3 只能在栈上创建对象的类

设计一个类,不得使用new创建对象

方法一:将构造函数私有化,然后设计静态方法创建对象返回,同时将拷贝构造禁掉,使其使用移动构造。

在这里插入图片描述

很明显,方法一并不能封的很死。

方法二:将构造函数私有化,然后设计静态方法创建对象返回,同时将operator new 与operator delete 禁掉,使其无法使用new;并且将拷贝构造禁掉,创建对象时使用移动构造

在这里插入图片描述
这种方法也不能完全封死,依旧有隐患。
所以只要把拷贝构造或者移动构造暴露,就会有隐患,使用时按需选择。

1.4 不能被继承的类

C++98中构造函数私有化,派生类中调不到基类的构造函数,则无法继承。

在这里插入图片描述

C++11中,使用final关键字,final修饰类,表示该类不能被继承。

在这里插入图片描述

1.5 只能创建一个对象的类(单列模式)

  • 单例模式:一个类只能创建一个对象,即单例模式。该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享
  • 用处:比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

单例模式有两种实现模式:

  • 饿汉模式:不管你将来用不用,程序启动时就创建一个唯一的实例对象。

在这里插入图片描述
注意:static类型的对象不在对象里面,在静态区,它仅受类域的限制,因此不会引发无穷递归

为了防止使用拷贝构造和赋值重载创建多个对象,所以也应该封一下。

在这里插入图片描述
到此就完整了

class InfoManager
{
public:
    static InfoManager& GetInstance()
    {
        return _instance; //每次返回的都是同一个
    }

private:
    InfoManager()  //将构造函数私有
    {
        cout << "InfoManager()" << endl;
    }
    InfoManager(const InfoManager& p) = delete;
    InfoManager& operator=(const InfoManager& p) = delete;

private:
    string _ip = "192.168.2.4";
    int _potr = 80;
    //...
    static InfoManager _instance; //声明一个在静态区的对象,该对象受类域的限制
};

InfoManager InfoManager::_instance; //创建对象

int main()
{
    InfoManager::GetInstance();
    //InfoManager copy(InfoManager::GetInstance());
}

如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

  • 懒汉模式:第一次使用实例对象时,创建对象

对于该模式来说,它跟饿汉模式差不多,只不过它在第一次调用GetInstance时创建对象,所以我们可以将其设置为指针。
在这里插入图片描述

还有一种更简单的方式(C++11后推荐使用):

在这里插入图片描述

注意:成员函数也不在对象里面,在代码段

如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

2. 类型转换

2.1 C/C++的类型转换

在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化。

C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换

  • 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败(整型之间、整型和浮点数之间都可以转换)
  • 显式类型转化:需要用户自己处理
  1. 内置类型之间可以转
void Test()
{
    int i = 10;
    // 隐式类型转换
    double d = i;
    printf("%d, %.1f\n", i, d);

    int* p = &i;
    // 显示的强制类型转换
    int address = (int)p;
    printf("%x, %d\n", p, address);
}
  1. 内置类型和自定义类型之间不可以直接转,但可以依靠构造函数、运算符重载转换

内置类型->自定义类型,依靠构造函数转换

class A
{
public:
    A(int a)
        :_a(a)
        ,_b(a)
    {}
    A(int a,int b)
        :_a(a)
        ,_b(b)
    {}
private:
    int _a;
    int _b;
};

int main()
{
    A aa1 = 1;//调单参数的构造转换
    A aa2 = { 10,20 };//调多参数的构造转换
}

自定义类型->内置类型,使用 operator + 类型,没有返回值

在这里插入图片描述

  1. 自定义类型和自定义类型之间

依靠构造函数,实现自定义类型之间的转换

在这里插入图片描述

2.2 C++规定的四种类型转换

标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符,目的就是规范转换,使转换更加清晰。

2.2.1 static_cast

static_cast对应的是隐式类型转换

在这里插入图片描述

2.2.2 reinterpret_cast

reinterpret_cast用于将一种类型转换为另一种不同的类型,对应强制类型转换

在这里插入图片描述

2.2.3 const_cast

const_cast最常用的用途就是删除变量的const属性,方便赋值(对应强制类型转换中有风险的去掉const属性

但是下面的代码中我们发行并没有修改掉变量a,这是由于编译器的优化。

在这里插入图片描述
这里我们可以加上volatile关键字。

在这里插入图片描述

volatile作用:不让编译器优化,规规矩矩的执行,达到稳定访问内存的目的(volatile忽略编译器的优化,保持内存可见性)

2.2.4 dynamic_cast

dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(运行时转换)

  • 向上转型:子类对象指针/引用->父类指针/引用(不需要转换,切片)
  • 向下转型:父类对象指针/引用->子类指针/引用(使用强制类型转换是不安全的,可能会越界访问;用dynamic_cast转换是安全的)

dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0。(如果父类指针或引用指向子类,此时转换为子类是安全的;如果父类指针或引用指向父类,此时转换为子类是不安全的,会越界)
在这里插入图片描述

dynamic_cast只能用于父类含有虚函数的类(因为其会在虚表中添加一些标识,以识别指向父类还是子类)

评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值