C++:特殊类的设计和类型转换

这篇具有很好参考价值的文章主要介绍了C++:特殊类的设计和类型转换。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

特殊类的设计

1.设计一个类,不能被拷贝

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。

//(1)C++98:将拷贝构造函数与赋值运算符重载只声明不定义并且将其访问权限设置为私有即可。
class A 
{
public:
	A(){}
private:
	A(A&);  
	A& operator=(const A&);
	int x;
};


//(2)C++11:扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上
//= delete,表示让编译器删除掉该默认成员函数。
class B
{
public:
	B() {}
	B(B&) = delete;
	B& operator=(const B&) = delete;
	int x;
};

2.设计一个类,只能在堆上创建对象

两种实现方式:

  1. 将类的构造函数私有拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
  2. 析构函数私有化,提供destory接口释放空间。
//只能在堆上开空间
// 第一种方案:构造、拷贝构造私有化,提高static返回创建对象指针
class A {
public:
	static A* get()
	{
		return new A;
	}
private:
	A(A&){}
	A(){}
};

//第二种方案:析构函数私有化,提供destory接口释放空间
class B {
public:
	void Destory()
	{
		delete this;
	}
private:
	~B()  //栈上变量函数调用结束前调不动析构
	{
		cout << "~B" << endl;
	}
};

3.设计一个类,只能在栈上创建对象

构造函数私有化,然后设计静态方法创建对象返回即可

//设计一个类,只能在栈上面开空间
//禁用new,设计static方法返回局部对象
class C {
public:
	static C get()
	{
		return C();
	}
private:
	C(){}
	void* operator new(size_t s) = delete;
};


4.设计一个类,不能被继承

//不能被继承
// (1)C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class A
{
public:
	static A GetInstance()
	{
		return A();
	}
private:
	A(){}
};


//(2)C++11方法可用final关键字,final修饰类,表示该类不能被继承。
class B final
{
	///
};

5.单例模式

单例模式:一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。


两种设计:

  1. 饿汉模式:在main函数执行前就创建好
//单例化模式的设计
//饿汉模式:在main函数前创建好
//要点:(1)只能右一个实例,把构造和拷贝构造私有
//(2)要在main函数前就创建好,我们可以设计成静态成员,类似与全局变量
//(3)提供全局函数返回对象引用或指针
//优点:简单
//缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
class A {
public:
	static A& get()
	{
		return a;
	}
	//需要什么变量和方法自己添加
private:
	A() {};
	A(A&) {};
	A& operator=(A&) = delete;//赋值最好禁止掉,但自己给自己赋值也影响不大
	static A a;
};
A A::a;  //类外定义
  1. 懒汉模式:需要使用的使用才创建
//懒汉模式:需要的时候创建
//要点:(1)只能有一个实例,把构造和拷贝构造私有
//(2)设计一个静态变量指针,初始化为空
//(3)第一次调用get方法的时候才创建对象
class B {
public:
	static B* get()
	{
		if (b == nullptr)
		{
			b = new B();
		}
		return b;
	}
private:
	B() {};
	B(B&) {};
	B& operator=(B&) = delete;
	static B* b;
};
B* B::b = nullptr;



//如果需要在退出时进行数据持久化,可以利用析构函数和内部类
//可以手动调用,但不调也会在main结束前自动调用
class B {
public:
	static B* get()
	{
		if (b == nullptr)
		{
			b = new B();
		}
		return b;
	}

	static void destory()
	{
		if (b) {
			//数据持久化操作
			delete b;  b = nullptr;  //懒汉释放空间其实不重要,重要的是可以在这个过程进行数据持久化
			cout << "destory" << endl;
		}
	}
private:
	B() {};
	B(B&) {};
	B& operator=(B&) = delete;
	static B* b;
	class C {
	public:
		~C()
		{
			B::destory();
		}
	};
	static C c;  //在main函数结束前会调用相应的析构函数
};
B* B::b = nullptr;
B::C B::c;



C++的类型转换

1. C语言中的类型转换

在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换

  1. 隐式类型:编译器在编译阶段自动进行,能转就转,不能转就编译失败
  2. 显式类型转化:需要用户自己处理
void Test()
{
	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);
}

2.C语言类型转换的缺点

  1. 隐式类型转化有些情况下可能会出问题:比如数据精度丢失
  2. 显式类型转换将所有情况混合在一起,代码不够清晰

因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格


3.C++的强制类型转换

标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:

  1. static_cast
int main()
{
	//相近类型(意义也相近)的转化(对应C的int、double、char之间转换)
  	double d = 12.34;
  	int a = static_cast<int>(d);
  	cout<<a<<endl;
  	return 0;
}
  1. reinterpret_cast
int main()
{
	//reinterpret_cast用于有一定关联,但意义不相似的类型间转换(对应C的int与int*)
	 double d = 12.34;
	 int a = static_cast<int>(d);
	 cout << a << endl;
	 // 这里使用static_cast会报错,应该使用reinterpret_cast
	 //int *p = static_cast<int*>(a);
	 int *p = reinterpret_cast<int*>(a);
	 return 0;
}
  1. const_cast
void Test()
{
	//const_cast最常用的用途就是删除变量的const属性,方便赋值
	//注意:const_cast属于比较危险的转换
	volatile const int a = 2;
	int* p = const_cast<int*>(&a);
	*p = 3;
	//这里加volatile是因为编译器对const变量有优化,可能会放到寄存器,也有可能是把a直接替换为2
	//所以有时内存中数据修改了,但是打印发现没有变化,加volatile可以强制每次都去内存取
	cout << a << endl;
}
  1. dynamic_cast

dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(向下转型,动态转换)

  • 向上转型:子类对象指针 / 引用->父类指针 / 引用(不需要转换,赋值兼容规则)
  • 向下转型:父类对象指针 / 引用->子类指针 / 引用(用dynamic_cast转型是安全的)

使用注意:

  1. dynamic_cast只能用于父类含有虚函数的类(虚函数我在多态那一文讲过)
    为什么:dynamic_cast 的工作原理是基于运行时的类型信息(RTTI)。当一个类包含至少一个虚函数时,编译器会自动为该类生成一个虚函数表,其中包含了所有虚函数的地址。每个该类的对象都会存储一个指向虚函数表的指针。因此,通过检查这个指针,我们可以确定对象的实际类型。但是,如果一个类没有虚函数,那么它就不会有虚函数表,也就无法在运行时确定其实际类型。在这种情况下,使用 dynamic_cast 进行类型转换会导致未定义的行为。 ---- 简而言之就是不知道指针指向的类型,不能确保安全性

  2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回空

class A
{
public :
	virtual void f(){}
};
class B : public A
{};
void fun (A* pa)
{
	// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回
	B* pb1 = static_cast<B*>(pa);
	B* pb2 = dynamic_cast<B*>(pa);
	cout<<"pb1:" <<pb1<< endl;
	cout<<"pb2:" <<pb2<< endl;
}
int main ()
{
	 A a;
	 B b;
	 fun(&a);
	 fun(&b);
	 return 0;
}



C++中const引用做参数的特殊机制

先看一种常见情况:

void fun(vector<int> v)
{
	//不做操作
}

int main()
{
	initializer_list<int> li = { 1, 2, 3 };
	//这里很明显,也很常见,是隐式类型转换
	//只要vector<int>支持了initializer_list<int>做参数的构造即可
	fun(li);
}

再看引用做参数:

void fun(vector<int>& v)
{
	//不做操作
}

int main()
{
	initializer_list<int> li = { 1, 2, 3 };
	//这里会报错,也很好理解,引用底层是指针,initializer_list<int>* 和 vector<int>* 不支持隐式转换
	fun(li);
}

最后看const引用做参数:

void fun(const vector<int>& v)
{
	//不做操作
}

int main()
{
	initializer_list<int> li = { 1, 2, 3 };
	//这里不报错,原因是触发了隐式转换(存在对应构造函数),为什么:
	//(1)const修饰后是不支持修改的,这个时候隐式转换是安全的
	//(2)如果普通引用也支持隐式类型转换的话,可能修改关键数据造成错误
	fun(li);
}



RTTI(扩展)

RTTI:Run-time Type identification的简称,即运行时类型识别
C++通过以下方式来支持RTTI:文章来源地址https://www.toymoban.com/news/detail-820368.html

  1. typeid运算符
  2. dynamic_cast运算符(本质是检查虚函数表)
  3. decltype

到了这里,关于C++:特殊类的设计和类型转换的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++特殊类设计&&类型转换

    在普通类的设计基础上,提出一些限制条件设计的类就是特殊类。 拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类 不能调用拷贝构造函数 以及 赋值运算符重载 即可。  C++98中的方式: 将拷贝构造函数与赋值运算符

    2024年01月16日
    浏览(48)
  • C++特殊类设计及类型转换

    目录 一、特殊类的设计 1.不能被拷贝的类 2.只能在堆区构建对象的类 3.只能在栈区构建对象的类 4.不能被继承的类 二、单例模式 1.饿汉模式 2.懒汉模式 3.线程安全 4.单例的释放 三、C++类型转换 1.C语言的类型转换 2.static_cast 3.reinterpret_cast 4.const_cast 5.dynamic_cast 6.总结 特殊类就

    2024年02月17日
    浏览(44)
  • 【C++】特殊类设计+单例模式+类型转换

    需要云服务器等云产品来学习Linux的同学可以移步/--腾讯云--/--阿里云--/--华为云--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。   目录 一、设计一个类,不能被拷贝 1、C++98 2、C++11 二、设计一个类,只能在堆上创建对象 1、将构造设为私有 2、将析构设为

    2024年02月06日
    浏览(54)
  • c++学习之特殊类设计与类型转换

    方法:c++98,通过私有且只申明不实现拷贝构造与赋值函数,从而实现该类不能被拷贝。c++11引入delete后,可以使构造构造与赋值函数等于delete。效果也是无法被拷贝。 方法一,析构私有化 方法二,构造私有化 方法一: 还是构造私有化,但是注意拷贝构造,我们拷贝

    2024年01月20日
    浏览(46)
  • 【C++】特殊类设计+类型转换+IO流

    🌇个人主页:平凡的小苏 📚学习格言:命运给你一个低的起点,是想看你精彩的翻盘,而不是让你自甘堕落,脚下的路虽然难走,但我还能走,比起向阳而生,我更想尝试逆风翻盘 。 🛸 C++专栏 : C++内功修炼基地 家人们更新不易,你们的👍点赞👍和⭐关注⭐真的对我真

    2024年02月05日
    浏览(45)
  • 【C++】特殊类的设计

    💕 C++98方式: 在C++11之前,想要一个一个类不被拷贝,只有将 拷贝构造函数 定义为私有,这样在类外就不能调用拷贝构造函数来构造对象了。但是在类内还是可以调用拷贝构造函数来构造对象。 所以正确的做法是 将拷贝构造函数定义为私有,同时拷贝构造函数只声明,不

    2024年02月07日
    浏览(45)
  • C++之特殊类的设计

    目录 一、单例模式 1、设计模式 2、单例模式 1、饿汉模式 2、懒汉模式 3、单例对象的释放问题 二、设计一个不能被拷贝的类 三、设计一个只能在堆上创建对象的类 四、设计一个只能在栈上创建对象的类 五、设计一个不能被继承的类 概念: 设计模式(Design Pattern)是一套被

    2024年02月08日
    浏览(44)
  • 【C++高阶(八)】单例模式&特殊类的设计

    💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:C++从入门到精通⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习C++   🔝🔝 在实际场景中,总会遇见一些特殊情况, 比如设计一个类,只能在堆上开辟空间, 亦或者是设计一个类只能实例化一个对象 在实际需求的场景

    2024年02月04日
    浏览(47)
  • 【C++】异常+智能指针+特殊类和类型转换

    上天可能觉得我太孤独,派你来和我一起对抗虚无。 1. C语言传统处理错误的方式无非就是返回错误码或者直接是终止运行的程序。例如通过assert来断言,但assert会直接终止程序,用户对于这样的处理方式是难以接受的,比如用户误操作了一下,那app直接就终止退出了吗?这

    2024年02月08日
    浏览(51)
  • 【C++】特殊类的设计(只在堆、栈创建对象,单例对象)

    🌏博客主页: 主页 🔖系列专栏: C++ ❤️感谢大家点赞👍收藏⭐评论✍️ 😍期待与大家一起进步! 实现方式: 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建 实现方法:

    2024年02月06日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包