特殊类的设计和类型转换
文章目录
- 特殊类
- 1.请设计一个类,不能被拷贝
- 2. 请设计一个类,只能在堆上创建对象
- 3. 请设计一个类,只能在栈上创建对象 (★)
- 4. 请设计一个类,不能被继承
- 5. 请设计一个类,只能创建一个对象(单例模式)
- 1. 饿汉模式
- 2. 懒汉模式
- 类型转换
- 内置类型转为自定义类型
- 自定义类型转换为内置类型
- 自定义类型转为自定义类型
- 四种强制类型转换操作符
- 1.static_cast
- 2.reinterpret_cast
- 3.const_cast
- 4.dynamic_cast
特殊类
1.请设计一个类,不能被拷贝
这个很简单,直接把拷贝构造delete就行了,但是我们还需要把赋值运算符重载也delete了,因为在赋值过程的时候需要用到拷贝构造。
//1. 设计一个类,不能别拷贝
class NoCopy
{
public:NoCopy(int x = 0):_x(x){}NoCopy(const NoCopy& cp) = delete;NoCopy& operator=(const NoCopy& cp) = delete;~NoCopy(){cout << "~NoCopy()" << endl;}private:int _x;
};
2. 请设计一个类,只能在堆上创建对象
那么我们创建的变量分为三种情况:
- 栈
- 堆
- 静态区
那也就是说我们对象都需要 n e w new new出来,那么我们就需要把构造函数delete就可以了。同时每次创建对象都只能 n e w new new出来。
//2. 请设计一个类,只能在堆上创建对象
class HeapOnly
{
public:static HeapOnly* Getobject(int a = 0){return new HeapOnly(a);}HeapOnly(const HeapOnly& hp) = delete;HeapOnly& operator=(const HeapOnly& hp) = delete;~HeapOnly(){delete this;}
private:HeapOnly(int a = 0) //构造函数变为私有那么外层无法使用,每次创建对象只能调用Getobject来创建。:_a(a){}int _a;
};
还有一种思路是把析构函数设置为私有,但是这种情况就需要手动释放资源了。
//2. 请设计一个类,只能在堆上创建对象
class HeapOnly
{
public:static HeapOnly* Getobject(int a = 0){return new HeapOnly(a);}HeapOnly(int a = 0):_a(a){}HeapOnly(const HeapOnly& hp) = delete;HeapOnly& operator=(const HeapOnly& hp) = delete;void Destory(){delete this;}
private:~HeapOnly(){cout << "~HeapOnly()" << endl;}int _a;
};
3. 请设计一个类,只能在栈上创建对象 (★)
//3. 请设计一个类,只能在栈上创建对象
class StackOnly
{
public:static StackOnly Getobject(int a = 0){return StackOnly(a);}~StackOnly(){cout << "~StackOnly()" << endl;}void* operator new(size_t size) = delete;void operator delete(void* p) = delete;
private:StackOnly(int a = 0):_a(a){}int _a;
};void Test_StackOnly()
{StackOnly s1 = StackOnly::Getobject(1); //由于我们需要赋值给s1,所以这里我们不得不把拷贝构造放出来StackOnly* s2 = new StackOnly(s1);//但是这样的情况不能避免,所以此时我们只能从new下手
}
由于new的本质为:构造函数 + operator new
delete的本质为: operator delete + 析构函数
虽然operator new和operator delete为全局函数,但是当局部写了的时候,优先使用局部的。所以这里我们只需要把operator new和operator delete取消即可。
4. 请设计一个类,不能被继承
这里有两种方法:
- 把基类的构造私有化,子类调不到父类的构造函数
- 直接使用final关键字。
5. 请设计一个类,只能创建一个对象(单例模式)
单例模式:
**一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。**比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式 :
1. 饿汉模式
就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
class Singleton
{
public:static Singleton& Getobject(int a = 0){_ins._a = a;return _ins;}Singleton(const Singleton& sl) = delete;Singleton& operator=(const Singleton& sl) = delete;void Print(){cout << _a << endl;cout << _id << endl;cout << _pos << endl;}~Singleton(){cout << "~Singleton()" << endl;}private:static Singleton _ins;Singleton(int a = 0):_a(a){cout << "Singleton()" << endl;}int _a = 0;string _id = "111111111";int _pos = 100;
};
Singleton Singleton::_ins; //在程序初始化之前创建一个对象int main()
{Singleton::Getobject(1).Print(); return 0;
}
2. 懒汉模式
懒汉模式和饿汉模式的区别就在于,饿汉模式是提前初始化好了,而懒汉模式则是调用的时候再初始化。
class Singleton
{
public:static Singleton& Getobject(int a = 0){static Singleton _ins; //调用的时候才初始化_ins._a = a;return _ins;}Singleton(const Singleton& sl) = delete;Singleton& operator=(const Singleton& sl) = delete;void Print(){cout << _a << endl;cout << _id << endl;cout << _pos << endl;}~Singleton(){cout << "~Singleton()" << endl;}private:Singleton(int a = 0):_a(a){cout << "Singleton()" << endl;}int _a = 0;string _id = "111111111";int _pos = 100;
};
int main()
{Singleton::Getobject(1).Print(); return 0;
}
类型转换
在C语言阶段我们学了隐式类型转换和显示类型转换。
//内置类型
int main()
{//隐式类型转换int x = 1;double d = 2.2;x = d;//显示类型转换int* p = (int*)x;cout << x << p << endl;return 0;
}
而在C++中我们还有自定义类型,那么自定义类型和内置类型究竟是怎么转化的呢?
内置类型转为自定义类型
利用自定义类型的构造函数,这个其实在类和对象我们已经学过了,隐式类型转换。
class A
{
public:A(int a = 0):_a(a),_b(a){}A(int a,int b):_a(a),_b(b){}void Print(){cout << _a << " " << _b << endl;}
private:int _a;int _b;
};
int main()
{A a1 = 1;A a2 = { 1,2 };a1.Print();a2.Print();return 0;
}
如上段代码,我们通过自定义类型的构造函数,让内置类型转换为自定以类型。
自定义类型转换为内置类型
重载operator(),但是由于operator()已经被占用,所以为了和别的operator()区分出来,我们需要如下定义:
operator int() //注意这里的int不能写为返回值,只能写在operator后面。
{return _a + _b;
}
int main()
{A a1(1);A a2{ 1,2 };a1.Print();a2.Print();int x = a1;int y = a2;cout << x << " " << y << endl;return 0;
}
自定义类型转为自定义类型
通过构造函数建立关联,两个不同的自定义类型强转是行不通的,所以得去写函数来建立两个类型之间的关系。
class B
{
public:B(int a = 0):_a(a){}operator int() const{return _a;}~B(){cout << "~B()" << endl;}int get() const{return _a;}
private:int _a;
};
class A
{
public:A(int a = 0):_a(a),_b(a){}A(int a,int b):_a(a),_b(b){}A(const B& bb) //直接这里的get()究竟返回的是什么由自己决定。:_a(bb.get()),_b(bb.get()){}void Print(){cout << _a << " " << _b << endl;}operator int() const{return _a + _b;}~A(){cout << "~A()" << endl;}
private:int _a;int _b;
};
再来看如下代码:
int main()
{//自己模拟实现的listbit::list<int> k = { 1,2,3,4 };//这里我们的k.begin()为普通的iteratorbit::list<int>::const_iterator it = k.begin(); //const_iterator = iteratorwhile (it !- k.end()){cout << *it << " ";++it;}cout << endl;return 0;
}
如上图:
由于我们的迭代器是由我们自己写的模版,那么他们的模版参数并不相同那么实例化出来的类型也不相同,所以这里也属于自定义类型转换自定义类型但由于我们迭代器并没有把两种关联起来,所以这里也是转换不了的 。但是库里面上面代码确可以运行,那几句说明我们库中是实现了iterator->const_iterator的转换的。
//添加一个构造函数,进来的参数为普通的迭代器 ,同时类型转化会产生临时对象,所以要用const修饰,//这样无论我们是普通迭代器还是const_iterator,虽然参数是普通迭代器,但是返回的却是按照对应类型返回。ListIteraotr(const ListIteraotr<T,T&,T*>& ls) :_node(ls._node){}
四种强制类型转换操作符
标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符。
1.static_cast
static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用
static_cast,但它不能用于两个不相关的类型进行转换
int main()
{double d = 12.34;int a = static_cast<int>(d);cout<<a<<endl;return 0;
}
2.reinterpret_cast
reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型
int main()
{//对应隐式类型转换,数据的意义没有改变,值传递double d = 12.34;int a = static_cast<int>(d);cout << a << endl;//对应强制类型转换,数据的意义改变,int -> int*int *p = reinterpret_cast<int*>(a);return 0;
}
3.const_cast
const_cast最常用的用途就是删除变量的const属性,方便赋值
void Test()
{//对应强制类型转换中有风险的去调const属性const int a = 2;int* p = const_cast<int*>(&a);*p = 3;cout << a << endl;cout << *p << endl;
}int main()
{Test();return 0;
}
但是此时我们发现为什么我们的a还是2呢?原因是编译器进行了优化,虽然内存当中我们a的值已经发生了改变,但是由于我们的a是const修饰的,为常变量,所以编译器会把它直接替换为2或者存入一个寄存器当中,所以并没有去内存当中去取。
4.dynamic_cast
继承体系中:
**向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则) **
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
注意这里是引用或者指针,如果是对象的话不可以进行强制类型转换。
在继承体系中,如果我们想要把子类的指针或者引用转化为父类的指针或者引用,会进行一个切片的操作,就是把子类中父类的部分传过去,所以不会产生临时变量。
class A
{
public:virtual void f() {}int _a = 0;
};
class B : public A
{
public:int _b = 0;
};
void fun(A* pa)
{// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回B* pb1 = (B*)pa;B* pb2 = dynamic_cast<B*>(pa);cout << "pb1:" << pb1 << endl;cout << "pb2:" << pb2 << endl;cout << pb1->_a << endl;cout << pb1->_b << endl;
}
int main()
{A a;B b;fun(&a);cout << endl;fun(&b);return 0;
}
如上图,虽然我们static_cast也可以转换,但是并没有dynamic_cast安全,这时为什么呢?
如上图,当我们调用的是子类的指针,那么我们强转之后是没有问题的,但如果是父类的指针,强转之后我们并没有对应的子类的值啊,所以当我们想要访问的时候必然会出现报错,因为访问了一个根本没有的值。
而dynamic_cast会先判断,如果我们传入的是父类指针,会返回空指针。
如果是子类的会会进行特殊处理,把子类中父类的部分切片进行传递。