前言
这一篇博客我们讲特殊类设计和类型转换
1. 特殊类设计
1.1 请设计一个类,不能被拷贝
这个比较简单
第一种方法就是将赋值和拷贝构造只定义不声明然后设置为私有就可以了
第二种方法就是直接令它为delete
1.2 请设计一个类,只能在堆上创建对象
class HeapOnly
{
public:
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
private:
HeapOnly()
{}
HeapOnly(const HeapOnly&h)
{}
};
只需要把构造函数和拷贝构造私有化就可以了,然后提供一个接口
注意要有一个static,不然的话无法调用
class HeapOnly
{
public:
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
void destroy()
{
delete this;
}
private:
~HeapOnly()
{}
};
第二个方法就是将析构函数私有化,这样就不能正常在栈上创建对象了
1.3 请设计一个类,只能在栈上创建对象
class StackOnly
{
public:
StackOnly CreateObj()
{
return StackOnly();
}
private:
StackOnly()
{}
StackOnly(const StackOnly&h)
{}
};
因为new一个对象会调用构造嘛,所以把构造函数私有,就无法new了
然后还是要提供接口去调用
1.4 请设计一个类,不能被继承
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};
C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
第二个方法就是将类用final修饰,表示这个类不能继承
class A final
{
// ....
};
1.5 请设计一个类,只能创建一个对象(单例模式)
单例模式就是这个类只有一个实例化对象,而且这个东西是可以全局访问的
单例模式又分为两种模式,一个是饿汉模式,一个是懒汉模式
饿汉模式就是在程序启动前就已经创建好这个对象了
懒汉模式就是你要用的时候才创建好这个对象
我们先讲饿汉模式
{
public:
/*A() = delete;*///不能删除,因为如果删除的话,就真的一个对象都没有了
A(const A& a) = delete;
static A _a;
int _num;
private:
A()
{};
};
A A::_a;//这里会调用默认构造函数
这样就可以了,构造函数私有化,拷贝构造delete,那么就只有那一个静态成员的
注意这里A中含有一个static类型的A,在这里不是套娃
static A _a;是存在静态区的,是创建的一个变量,是全局的,只不过在A中就有了类域的限制,它不是A的一个成员,而是创建的一个对象,然后static类型的,声明与定义分离
但这个是在main函数之前就定义好了,这个对象是在main函数之前就创建好了
缺点就是,在main函数之前创建,就会导致,程序启动缓慢
下面来讲懒汉模式
class A
{
public:
/*A() = delete;*///不能删除,因为如果删除的话,就真的一个对象都没有了
A(const A& a) = delete;
static A& CreatObj()
{
if (_ptr == nullptr)
{
_ptr = new A;
}
return *_ptr;
}
int _num;
static A* _ptr;
private:
A()
{};
};
A* A::_ptr = nullptr;//main函数之前就创建了一个nullptr
A::CreatObj()._num++;
A::CreatObj()._num++;
A::CreatObj()._num++;
这里可以不用释放,因为只有一个对象,内存泄漏了,也没事
但是如果要释放的话,写个析构是不行的
因为static A* a是不会自动调用析构函数的
要释放的话,就要在里面写个专门释放的函数
然后再专门调用
或者写个获取_ptr的函数也行
void Destroy()
{
delete _ptr;
}
比如这样
或者在类里面定义一个内部类
class CGarbo {
public:
~CGarbo(){
if (Singleton::m_pInstance)
delete Singleton::m_pInstance;
}
};
然后在用这个定义一个静态的成员变量
static CGarbo Garbo;
static A a在生命周期结束的时候是会自动调用析构函数的
然后内部类可以用外部类的成员变量,就可以析构了
class A
{
public:
/*A() = delete;*///不能删除,因为如果删除的话,就真的一个对象都没有了
A(const A& a) = delete;
static A& CreatObj()
{
if (_ptr == nullptr)
{
_ptr = new A;
}
return *_ptr;
}
void Destroy()
{
delete _ptr;
}
class B
{
public:
~B()
{
cout << "h" << endl;
delete _ptr;
}
};
int _num=0;
static A* _ptr;
static B b;
~A()
{
;
}
private:
A()
{};
};
A* A::_ptr = nullptr;//main函数之前就创建了一个nullptr
A::B A::b;
因为定义了b的static,这不是成员变量,也是单独的,所以static A a是会自己析构的
class A
{
public:
/*A() = delete;*///不能删除,因为如果删除的话,就真的一个对象都没有了
A(const A& a) = delete;
static A& CreatObj()
{
static A a;
return a;
}
int _num = 0;
private:
A()
{};
};
这个是懒汉模式的第二种
只有调用CreatObj函数的时候才会创建那个对象,然后这个会自动析构,不是开辟的,不用释放
反正static的都不是成员变量,而是单独的一个对象
2. C语言中的类型转换
2.1内置类型之间
隐式类型转换:整型家族之间,整型与浮点数之间
显示类型转换(也是强制类型转换):指针与整数之间,指针与指针之间
int a = 'c';//隐式类型转换
这个就不行,因为要强制类型转换的东西,隐式类型转换是不行的
int a = 'c';//隐式类型转换
int b = (int) & a;
这里我们就可以看出了,第一,类型转换转换的是类型比较相近的,其实隐式类型转换最多就是一些精度的丢失,数据的主体意思还是不会改变,而强制类型转换,是直接就改变了数据的意义
2.2 内置类型和自定义类型之间
class A
{
public:
A(int a)
:_a1(a)
,_a2(a)
{}
A(int a1,int a2)
:_a1(a1)
, _a2(a2)
{}
private:
int _a1;
int _a2;
};
A tmp1 = 1;
A tmp2 = {1,2};
const A& tmp3 = {1,2};
这里我们可以看出,内置类型是可以直接隐式类型转换成自定义类型的,前提就是支持相关的构造函数
A tmp1 = (A)1;
当然能隐式类型转换,肯定也能强制类型转换,两个参数的我不知道怎么强制类型转换
explicit A(int a)
:_a1(a)
,_a2(a)
{}
如果在构造函数的前面加上explicit就表示这个相关的隐式类型转换就不能进行
A tmp1 = (A)1;
但是强制类型转换还是可以的
接下来我们讲自定义类型隐式类型转换为内置类型
operator int()
{
return _a1 + _a2;
}
int num = tmp1;
cout << num << endl;
在这里我们就可以看出,涉及自定义类型的转换,都是要相关的函数的
自定义隐式变内置类型,就要重载,但是这个重载比较特殊,返回值不能写,就是默认的int,能隐式肯定也能显示,就不用说了
shared_ptr中也有个隐式类型转换,是转换为bool值的,看shared_ptr指向的内容是否有效
2.3 自定义类型和自定义类型之间
只要有自定义类型,那么类型转换的话,就要有相应函数,直接转换肯定是不行的
这次的函数是构造函数
class B
{
public:
B(int a)
:_b1(a)
, _b2(a)
{}
B(int a1, int a2)
:_b1(a1)
, _b2(a2)
{}
int _b1;
int _b2;
};
class A
{
public:
A(const B& b)
{
_a1 = b._b1;
_a2 = b._b2;
}
explicit A(int a)
:_a1(a)
, _a2(a)
{}
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{}
operator int()
{
return _a1 + _a2;
}
private:
int _a1;
int _a2;
};
B b1 = { 1,2 };
A a1 = b1;
这里我们就可以看出,如果要将B类型转换为A类型,那么A类型就要支持B类型的构造函数
3. C++强制类型转换
C语言中的类型转换只有内置类型与内置类型,后面两种是C++的
虽然支持隐式类型转换,但是C++为了规范,为了让别人知道是什么类型的转换
直接引入了四种强制类型转换操作符
3.1 static_cast
static_cast对应的就是以前的隐式类型转换
以前是这样
double d = 0.22;
int a = d;
现在可以是这样
double d = 0.22;
int a = static_cast<int>(d);
<>里面的内容就是要转换的内容
3.2 reinterpret_cast
reinterpret_cast就是作用于强制类型转换的
以前是这样
int a = 0;
int b = (int)&a;
现在是这样
int a = 0;
int b = reinterpret_cast<int>( & a);
还是那句话,要求强制的就不能隐式,能隐式的绝对能强制
所以按理说static_cast可以用的,reinterpret_cast也可以用
但是static_cast可以用的,reinterpret_cast也不可以用
因为主要是为了规范,隐式就必须用static_cast,显示就必须用reinterpret_cast
3.3 const_cast
const_cast主要是对const类型的进行转换,然后还可以去掉它的const属性,意思是,要去掉const属性的时候,就要用这个,
可以看出const_cast针对的类型是指针,转换为指针,而且还要具有const属性的
所以const_cast是针对指针和引用类型,转换为指针和引用类型,然后这个指针和引用类型还具有const,用了这个就可以去掉cosnt属性
前面两个都是无法去掉const属性的
比如这个,&a是const int类型的,使用了这个转换符还是无法转换为int,意思就是无法去掉const属性
const int a = 0;
int* d = const_cast<int*>(&a);
*d = 10;
cout << a << endl;
cout <<(*d) << endl;
这个就可以去掉const属性
d这个指针就不具有const属性,就可以对指向内容进行修改了
虽然进行了修改,按理说a是已经改变了的,但是const类型的,编译器有时会把它放在寄存器,有时会把它直接用2宏替换,所以用的a一直是0,并且a的const属性不会改变
虽然d指针指向a,但是也可以当做没有指向a,重新弄了一个数据为0来指向
3.4 dynamic_cast
danamic_cast主要是针对子类和父类的相互赋值的
第一子类可以直接赋值给父类,指针引用都可以赋给父类
第二但是父类的指针引用不能给子类,因为子类指向的空间更大,会越界访问
主要是越界访问的问题
所以danamic_cast针对第一种情况成功转换
第二种情况就会返回nullptr或者返回0
但是使用这个的前提就是父类要具有虚函数
class A
{
public:
virtual void f() {}
};
class B : public A
{};
void fun(A* pa)
{
// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回
B* pb2 = dynamic_cast<B*>(pa);
if (pb2)
{
cout << "转换成功" << endl;
cout << "pb2:" << pb2 << endl;
}
else
{
cout << "转换失败" << endl;
}
}
int main()
{
A a;
B b;
fun(&a);
fun(&b);
return 0;
}
第一个pa指向的空间是A类型的,所以转换为访问B类型的,就会越界,很危险
第二个pa指向的空间是B类型的,只不过指向的一部分,但是转换为B类型的指针没有问题,因为可以访问多余的空间
4. RTTI
RTTI:Run-time Type identification的简称,即:运行时类型识别。
C++通过以下方式来支持RTTI:
- typeid运算符
- dynamic_cast运算符
- decltype
就是可以判断类型的就是RTTI
总结
下一篇博客我们讲讲linux