目录
一、特殊类
1.1拒绝被拷贝的类
1.2 限制在堆上创建类
1.3 限制在栈上创建的类
1.4 不能被继承的类
二、类型转换
2.1 static_cast
2.2 reinterpret_cast
2.3 const_cast
2.4 dynamic_cast
一、特殊类
什么是特殊类?在普通类的设计基础上,提出一些限制条件设计的类就是特殊类。
1.1拒绝被拷贝的类
拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载。
禁止拷贝的类只需要让该类禁止调用拷贝构造函数和赋值运算符重载函数即可。
在C++98中常用如下方式:
class CopyBan
{
public:CopyBan(){}
private:CopyBan(const CopyBan& cb);//拷贝构造函数声明CopyBan& operator=(const CopyBan& cb);//赋值运算符重载声明
};
将拷贝构造函数和赋值运算符重载函数只声明不定义,并且设置成私有。
原因:
- 只声明不定义:在调用拷贝构造和赋值运算符重载函数的时候,由于没有定义就会产生链接错误,在编译阶段就报错。
- 设置成私有:防止在类外定义拷贝构造和赋值运算符重载函数,此时即使定义了,因为私有的原因,在类外也无法调用
C++11新方法:
- 使用C++11中的给delete新赋予的意义来禁止生产拷贝构造和赋值运算符重载函数。
此时编译器也不会自动生成默认的拷贝构造和赋值运算符重载函数。
1.2 限制在堆上创建类
正常创建类对象时,会在栈上创建,并且自动调用构造函数来初始化。
- 只能在创建在堆上时,就需要让该对象只能通过new来创建,并且调用构造函数来初始化。
定义一个静态成员函数,在该函数内部new一个Heaponly对象。将构造函数和拷贝构造函数私有,并且禁止生成拷贝构造函数。
非静态成员函数在调用的时候,必须使用点(.)操作符来调用,这一步是为了传this指针。
这样的前提是先有一个HeapOnly对象,但是构造函数设置成了私有,就无法创建这样一个对象。
而静态成员函数的调用不用传this指针,也就不用必须有HeapOnly对象,只需要类域::静态成员函数即可。
静态成员函数属于HeapOnly域内,所以在new一个对象的时候,可以调用私有的构造函数。
1.3 限制在栈上创建的类
- 主要要做到不能在堆上创建类对象。
- new一个对象的时候,会调用该类的operator new(size_t size)函数,在释放资源的时候又会调用该类的operator delete(void* p)函数。
方法1:
所以防止在堆上创建类对象就是要禁止调用这两个函数。
class StackOnly
{
public://构造函数StackOnly(){}void* operator new(size_t size) = delete;//禁止调用newvoid operator delete(void* p) = delete;//禁止调用delete
};
使用delete来禁止这两个函数的调用,那么在new一个对象的时候,就会产生编译错误,从而无法在堆区上创建类对象。
方法2:
class StackOnly
{
public:static StackOnly CreateObject(){return StackOnly();}
private:StackOnly(){}
};
另一种方式就是和之前一样,通过一个静态成员函数在栈区上创建一个类对象,并且将默认构造函数私有化。
设计特殊类的核心点:只能通过静态成员函数来创建类,封掉其他所有创建方式。
1.4 不能被继承的类
C++98方式:
//基类
class NonInherit
{
private://基类构造函数私有NonInherit(){}
};//派生类
class B :public NonInherit
{};
- 基类的构造函数私有,派生类在创建对象的时候,无法调用基类的构造函数。
C++11添加了 final 关键字
class NonInherit final
{}
二、类型转换
在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语言。
2.1 static_cast
- C语言的隐式类型转换在C++中就可以使用static_cast来转换,但是不能用于两个不相关的类型进行转换。
double d = 3.14;
int a = static_cast<int>(d);
static_cast 后的<>里放要转换的类型,()里放被转换的类型。
2.2 reinterpret_cast
- C语言的显式类型转换在C++中就可以reinterpret_cast,用于将一种类型转换为另一种不同的类型。
int a = 1;
int* pa = &a;
int address = reinterpret_cast<int>(pa);
2.3 const_cast
- 用在删除变量的const属性,方便赋值。
const int a = 2;
int* p = const_cast<int*>(&a);
*p = 3;
- const_cast 更多的是一种警示,表示去除了const属性,要谨慎操作。
2.4 dynamic_cast
- 用于将一个父类对象的指针或者引用转换为子类对象的指针或引用(动态转换)。
向上转换:子类对象的指针或引用 → 父类对象的指针或引用。(不发生类型转换,是语法允许的,发生了切片)
向下转换:父类对象的指针或引用 → 子类对象的指针或引用。(用 dynamic_cast 转换是安全的)
//父类
class A
{
public:virtual void f(){}int _a = 1;
};
//子类
class B : public A
{
public:int _b = 2;
};
在main函数中,传父类指针&ax给函数,在函数中将A* pa父类指针接收该值,然后将其强转为子类指针B*,使用子类指针访问子类成员,bptr->_b = 4发生运行时错误。
形参A* pa
是父类指针,接收的也是父类指针,所以强转成子类指针后访问子类成员_b
会发生越界。
解决方法:使用dynamic_cast
将父类指针强转为父类指针。
注意:
dynamic_cast
只能用于父类含有虚函数的类。dynamic_cast
会先检查是否能转换成功,能成功则转换,不能则返回nullptr。dynamic_cast
是安全的,直接使用C语言的转换方式是不安全的。