文章目录
- 1. 特殊类的设计
- 1.1 不能被拷贝的类
- 1.2 只能在堆上创建对象的类
- 1.3 只能在栈上创建对象的类
- 1.4 不能被继承的类
- 1.5 只能创建一个对象的类(单列模式)
- 2. 类型转换
- 2.1 C/C++的类型转换
- 2.2 C++规定的四种类型转换
- 2.2.1 static_cast
- 2.2.2 reinterpret_cast
- 2.2.3 const_cast
- 2.2.4 dynamic_cast
1. 特殊类的设计
1.1 不能被拷贝的类
请设计一个类,不能被拷贝
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
在C++98中,将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可
原因:
- 设置成私有:如果
只声明没有设置成private,用户自己如果在类外定义
了,就可以不能禁止拷贝了 - 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
此时只要调用到这两个函数就会报错。
1.2 只能在堆上创建对象的类
实现方式:
- 将类的构造函数私有,拷贝构造声明成私有(或者delete)。防止别人调用拷贝构造在栈上生成对象。
- 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
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语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。
- 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败(整型之间、整型和浮点数之间都可以转换)
- 显式类型转化:需要用户自己处理
- 内置类型之间可以转
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);
}
- 内置类型和自定义类型之间不可以直接转,但可以依靠构造函数、运算符重载转换
内置类型->自定义类型,依靠构造函数转换
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 + 类型,没有返回值
- 自定义类型和自定义类型之间
依靠构造函数,实现自定义类型之间的转换
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只能用于父类含有虚函数的类(因为其会在虚表中添加一些标识,以识别指向父类还是子类)