【C++】特殊类的设计 | 类型转换

2023-10-17 09:06:43 浏览数 (1)

1. 特殊类的设计

单例模式

设计模式是 被反复使用 多数人知晓 经过分类的、代码设计经验的总结


单例模式: 一个类只能创建一个对象 即单例模式,该模式可以保证系统中该类只有一个实例

单例模式分为饿汉模式和懒汉模式

饿汉模式

一开始就创建对象(main函数之前)

假设想要vector数组全局只有一份 所以进行限制,使之不能随意创建对象 即将构造函数私有化

若想要创建对象,则通过公有的成员函数getinstallce创建 为了保证每次获取的都是同一个对象,就定义了一个静态的类类型的指针 _p 而静态的成员变量,需要在类外面初始化


在定义静态成员变量时 创建对象


此时也可添加add增加和print打印的功能

定义私有的string数组 _v,使用其push_back 添加数据str,并使记录数字 1


使用print函数打印数据


由于getinstallce函数返回值是一个指针,所以需要使用->去访问add或者print函数


还可以通过设置锁进行多线程间的安全访问

设置私有锁


由于getinstallce函数返回值是一个静态的指针,所以无论是线程t1还是线程t2都可以访问到该函数 并通过该函数调用add或者 print函数 使用to_string 将任意类型转化为string

具体代码
代码语言:javascript复制
饿汉模式
class stu
{
public:
	static stu* getinstallce()
	{
		return _p;
	}
	void add(const string& str)
	{
		_mtx.lock();//加锁
		_v.push_back(str);
		  _x;
		_mtx.unlock();//解锁
	}
	void print()
	{
		_mtx.lock();//加锁
		for (auto& e : _v)
		{
			cout << e << " ";
			cout << endl;
		}
		_mtx.unlock();//解锁
	}
private:
	//限制类外不能随意创建对象
	//构造函数私有化
	stu(int x=0)
		:_x(x)
	{
	}
private:
	mutex _mtx;
	int _x=0;
	vector<string>_v;
	static stu* _p;
};
//static成员变量在类外定义
stu* stu:: _p = new stu;


int main()
{
	int n = 10;
	thread t1([n]() {
		for (int i = 0; i < n; i  )
		{
			stu::getinstallce()->add("t1线程:" to_string(i));
		}
		});
	thread t2([n]() {
		for (int i = 0; i < n; i  )
		{
			stu::getinstallce()->add("t2线程:"   to_string(2*i));
		}
		});
	t1.join();
	t2.join();
	stu::getinstallce()->print();
}
int main()
{
	stu::getinstallce()->add("张三");
	stu::getinstallce()->add("李四");
	stu::getinstallce()->print();//打印
	return 0;
}

懒汉模式

第一次访问实例对象时创建(第一次调用getinstallce函数时创建)

在饿汉模式的代码的基础上进行改造


在定义静态成员变量时设置为空


若_p指针为空,在创建对象,并返回 在调用getinstallce函数时才创建对象


虽然看似没有问题,但是在多线程下还存在线程安全的问题

定义一个静态锁,用于保护getinstallce函数中的实例对象


在初始化时,是不需要显示给值的


每次获取对象都要加锁解锁,但实际上只需要保证第一次即可


这样的写法依旧是不行的,当两个线程t1 t2同时进入if循环中, 当线程t1 new后解锁,线程t2获取锁,继续new,就会造成覆盖 丢失数据


所以采用双检查加锁的方式

具体代码
代码语言:javascript复制
//懒汉模式
class stu
{
public:
	static stu* getinstallce()
	{
		//双检查加锁
		if (_p == nullptr)
		{
			_imtx.lock();//加锁
			if (_p == nullptr)
			{
				_p = new stu;
			}
			_imtx.unlock();//解锁
		}
		return _p;
	}
	void add(const string& str)
	{
		_mtx.lock();//加锁
		_v.push_back(str);
		  _x;
		_mtx.unlock();//解锁
	}
	void print()
	{
		_mtx.lock();//加锁
		for (auto& e : _v)
		{
			cout << e << " ";
			cout << endl;
		}
		_mtx.unlock();//解锁
	}
	//特殊情况下释放单例对象
	static void delinstance()
	{
		_imtx.lock();
		if (_p)
		{
			delete _p;
			_p = nullptr;
		}
		_imtx.unlock();
	}
private:
	//限制类外不能随意创建对象
	//构造函数私有化
	stu(int x = 0)
		:_x(x)
	{
	}
	
	//防拷贝
	stu(const stu& s) = delete;
	stu& operator=(const stu& s) = delete;

private:
	static mutex _imtx;
	mutex _mtx;
	int _x = 0;
	vector<string>_v;
	static stu* _p;
};
//static成员变量在类外定义	 
stu* stu::_p = nullptr;
//将静态锁在类外初始化
mutex stu::_imtx;

懒汉模式和饿汉模式的优缺点

饿汉模式的缺点: 1.若单例对象初始化很慢(如初始化动作多),main函数之前就要申请,暂时不需要使用 就会造成 占用资源、程序启动会变慢受影响 2.若两个单例都是饿汉,并且有依赖关系,要求单例1先创建,单例2再创建,饿汉无法控制顺序,懒汉才可以

(两者是懒汉,则都是使用 成员的静态指针进行new创建对象的,谁先new是控制不住的 而两者都是饿汉,则都是在getinstallce函数中创建对象, 可以控制单例1先在getinstallce函数中创建对象,再让单例2在getinstallce函数中创建对象)

饿汉模式的优点: 优点只有一个,简单

懒汉完美的解决了上面饿汉的问题,变得相对更复杂一点

2. C 的类型转换

C语言的类型转换

C语言有隐式类型转换 和显式类型转换

i为int类型,想要转化为double类型,就需要进行隐式类型转换 即 先将i赋值给一个double类型的临时变量,再通过临时变量赋值给d

p作为一个指针,i作为一个int类型变量,虽然都是4个字节,但是意义不同,所以不能互相转,只能进行显式类型转换 即 将int*类型的指针强转为int类型

C 的类型转换

隐式类型转化 存在精确度丢失的问题 显式类型转化 存在代码不够清晰的问题 所以C 提出了自己的类型转化风格,引入四种强制类型转换操作符 static_cast reinterpret_cast const_cast dynamic_cast

static_cast

static_cast对应c语言中的隐式类型转换 两个变量 是相关的类型 (double和int)

把int类型转化为double类型

reinterpret_cast

reinterpret_cast对应C语言的显式强制类型转换 两个变量 是不相关的类型 (int和int*)

把int类型转化为 int*类型

const_cast

去掉const属性

a为const int类型,转化为&a后,类型为const int* 通过const_cast后,b等待类型为int*类型,可以对b解引用修改 a的值依旧为10,不会被修改 而b的值为5

因为编译器进行优化,把a的值放入寄存器中,而b所修改实际上是寄存器的a值而不是内存中的a值,所以a依旧为10

dynamic_cast

C 独有的

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


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


父类对象是无法转换为子类对象的


向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的,直接强制转换是不安全的)

A作为父类,B作为子类 所以将p强制转换为B*,存在风险,如果B有自己的成员,用指针可以访问这些成员,但这个访问就强制越界了,多开的一部分空间不属于你的


dynamic_cast 会先进行检查,若指向父类对象,则转换失败,若指向子类对象,则转换成功

注意: dynamic_cast只能用于父类含有虚函数的类

0 人点赞