🌈个人主页:秦jh_-CSDN博客
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12575764.html?spm=1001.2014.3001.5482
目录
前言
💬 hello! 各位铁子们大家好哇。
今日更新了C++特殊类和强制类型转换的相关内容
🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝
不能被拷贝的类
拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
C++98 将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
C++11 扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上 =delete,表示让编译器删除掉该默认成员函数。
只能在堆上创建对象的类
实现方式:
- 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
- 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
如上图,此时不能在栈上创建对象了。但是调用成员函数需要对象,所以就把成员函数设置成静态的,通过类域名就能调用了。如下图:
注意,还需要把拷贝构造和赋值重载封死。
只能在栈上创建对象的类
同上将构造函数私有化,然后设计静态方法创建对象返回即可。
new会调用构造,而拷贝构造也是构造,就可以间接new一个对象,所以要把operator new禁用。new会调用operator new和构造,默认会调用全局的operator new,但是我们可以重载一个类专属的operator new,此时new的时候不会调用全局的,而是我们重载的。
不能被继承的类
C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
C++11方法:f inal关键字,final修饰类,表示该类不能被继承。
class A final
{
// ....
};
只能创建一个对象的类(单例模式)
设计模式:
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模 式使代码编写真正工程化;
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个 访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置 信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再 通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:
饿汉模式
就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。(main函数前就创建对象)
// 饿汉模式 : 一开始就(main函数之前)就创建对象
// 问题1:如果有很多单例类,都是饿汉模式,有些单例对象初始化资源很多,导致程序启动慢,迟迟进不了main函数
// 如果两个单例类有初始化依赖关系,饿汉也无法解决。比如A类和B类是单例,A单例要连接数据库,B单例要用A单例访问数据库
class ConfigInfo
{
public:
static ConfigInfo* GetInstance()
{
return &_sInfo;
}
string GetIp()
{
return _ip;
}
void SetIp(const string& ip)
{
_ip = ip;
}
private:
ConfigInfo()
{
cout << "ConfigInfo()" << endl;
}
ConfigInfo(const ConfigInfo&) = delete;
ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
string _ip = "127.0.0.1";
int _port = 80;
//...
// 声明
static ConfigInfo _sInfo;
};
// 定义
ConfigInfo ConfigInfo::_sInfo;
int main()
{
cout << ConfigInfo::GetInstance() << endl;
cout << ConfigInfo::GetInstance() << endl;
cout << ConfigInfo::GetInstance() << endl;
ConfigInfo::GetInstance()->SetIp("192.33.3.22");
cout << ConfigInfo::GetInstance()->GetIp() << endl;
return 0;
}
如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。
懒汉模式
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化, 就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
缺点:复杂
// 懒汉:第一次调用GetInstance时创建单例对象
// 懒汉完美解决上面饿汉的问题
class ConfigInfo
{
public:
static ConfigInfo* GetInstance()
{
// C++11之前,多线程调用GetInstance
// 局部静态单例对象构造初始化,无法保证线程安全
// 也就是说,这种写法C++11之后才能用
static ConfigInfo info;
return &info;
}
string GetIp()
{
return _ip;
}
void SetIp(const string& ip)
{
_ip = ip;
}
private:
ConfigInfo()
{
cout << "ConfigInfo()" << endl;
}
ConfigInfo(const ConfigInfo&) = delete;
ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
string _ip = "127.0.0.1";
int _port = 80;
//...
};
C++11后,上面的创建实例对象才能用
class ConfigInfo
{
public:
static ConfigInfo* GetInstance()
{
// C++11之前也能保证线程安全
// 多线程调用需要考虑线程安全问题
// 双检查加锁
// t1 t2
if (_spInfo == nullptr) // 性能
{
unique_lock<mutex> lock(_mtx);
if (_spInfo == nullptr) // 线程安全
{
_spInfo = new ConfigInfo;
}
}
return _spInfo;
}
string GetIp()
{
return _ip;
}
void SetIp(const string& ip)
{
_ip = ip;
}
private:
ConfigInfo()
{
cout << "ConfigInfo()" << endl;
}
ConfigInfo(const ConfigInfo&) = delete;
ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
string _ip = "127.0.0.1";
int _port = 80;
//...
static ConfigInfo* _spInfo;
static mutex _mtx;
};
ConfigInfo* ConfigInfo::_spInfo = nullptr;
mutex ConfigInfo::_mtx;
int main()
{
cout << ConfigInfo::GetInstance() << endl;
cout << ConfigInfo::GetInstance() << endl;
cout << ConfigInfo::GetInstance() << endl;
ConfigInfo::GetInstance()->SetIp("192.33.3.22");
cout << ConfigInfo::GetInstance()->GetIp() << endl;
return 0;
}
C++11之前,多线程需要考虑线程安全问题,需要加锁解决。
C语言中的类型转换
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与 接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型 转换和显式类型转换。
- 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
- 显式类型转化:需要用户自己处理
int main()
{
int i = 1;
// 隐式类型转换
double d = i;
printf("%d, %.2f\n", i, d);
int* p = &i;
// 显示的强制类型转换
int address = (int)p;
printf("%x, %d\n", p, address);
return 0;
}
缺陷: 转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换
为什么C++需要四种类型转换
C风格的转换格式很简单,但是有不少缺点的:
- 隐式类型转化有些情况下可能会出问题:比如数据精度丢失
- 显式类型转换将所有情况混合在一起,代码不够清晰 因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。
C++强制类型转换
标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符: static_cast、reinterpret_cast、const_cast、dynamic_cast
static_cast(隐式类型转换)
static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用 static_cast,但它不能用于两个不相关的类型进行转换 。
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout<<a<<endl;
return 0;
}
reinterpret_cast(显式类型转换)
reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
// 这里使用static_cast会报错,应该使用reinterpret_cast
//int *p = static_cast<int*>(a);
int* p = reinterpret_cast<int*>(a);
return 0;
}
const_cast
const_cast最常用的用途就是删除变量的const属性,方便赋值
如上图,a被const修饰,不能直接修改,但可以通过指针间接修改,但是上面修改后,为什么a打印出来还是1?
通过监视窗口,看到a的值已经变成2了。原因:编译器进行了优化,编译器觉得a被const修饰了,不会改变了,他就把a存到寄存器,直接到寄存器访问,虽然内存里改变了,但是寄存器的还没变。监视窗口看到的是内存里的值。
此时需要用 volatile 修饰a。volatile的作用是:告诉编译器不要优化,每次都去内存取a。
c++增加了 const_cast 。
作用:时刻提醒你这个变量的const属性被去掉了,变得不稳定了,这个变量需要被 volatile 修饰。
如果不用volatile修饰,结果还是会被优化。
dynamic_cast
如上图, 形参是B类型时,因为fun用A接收,就只能看到B里的_a1部分,然后又被转回B类型,此时又能看到B类型的_b1了。
如果传A类型对象,fun里又被转成B类型,此时就会越界,因为访问了A里没有_b1,但是又访问了_b1。所以上面运行到fun(&a)是就崩溃了。
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
注意:
- dynamic_cast只能用于父类含有虚函数的类
- dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
前面的转换是无差别转换,会有越界风险。
c++设计了 dynamic_cast 进行动态转换。
dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回
RTTI(了解)
RTTI:Run-time Type identification的简称,即:运行时类型识别。
C++通过以下方式来支持RTTI:
- typeid运算符
- dynamic_cast运算符
- decltype