设计模式是 被反复使用 多数人知晓 经过分类的、代码设计经验的总结
单例模式:
一个类只能创建一个对象 即单例模式,该模式可以保证系统中该类只有一个实例
单例模式分为饿汉模式和懒汉模式
一开始就创建对象(main函数之前)
假设想要vector数组全局只有一份
所以进行限制,使之不能随意创建对象 即将构造函数私有化
若想要创建对象,则通过公有的成员函数getinstallce创建
为了保证每次获取的都是同一个对象,就定义了一个静态的类类型的指针 _p
而静态的成员变量,需要在类外面初始化
在定义静态成员变量时 创建对象
此时也可添加add增加和print打印的功能
定义私有的string数组 _v,使用其push_back 添加数据str,并使记录数字+1
使用print函数打印数据
由于getinstallce函数返回值是一个指针,所以需要使用->去访问add或者print函数
还可以通过设置锁进行多线程间的安全访问
设置私有锁
由于getinstallce函数返回值是一个静态的指针,所以无论是线程t1还是线程t2都可以访问到该函数
并通过该函数调用add或者 print函数
使用to_string 将任意类型转化为string
饿汉模式
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,就会造成覆盖 丢失数据
所以采用双检查加锁的方式
//懒汉模式
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函数中创建对象)
饿汉模式的优点:
优点只有一个,简单
懒汉完美的解决了上面饿汉的问题,变得相对更复杂一点
C语言有隐式类型转换 和显式类型转换
i为int类型,想要转化为double类型,就需要进行隐式类型转换
即 先将i赋值给一个double类型的临时变量,再通过临时变量赋值给d
p作为一个指针,i作为一个int类型变量,虽然都是4个字节,但是意义不同,所以不能互相转,只能进行显式类型转换
即 将int*类型的指针强转为int类型
隐式类型转化 存在精确度丢失的问题
显式类型转化 存在代码不够清晰的问题
所以C++提出了自己的类型转化风格,引入四种强制类型转换操作符
static_cast reinterpret_cast const_cast dynamic_cast
static_cast对应c语言中的隐式类型转换
两个变量 是相关的类型 (double和int)
把int类型转化为double类型
reinterpret_cast对应C语言的显式强制类型转换
两个变量 是不相关的类型 (int和int*)
把int类型转化为 int*类型
去掉const属性
a为const int类型,转化为&a后,类型为const int*
通过const_cast后,b等待类型为int*类型,可以对b解引用修改
a的值依旧为10,不会被修改
而b的值为5
因为编译器进行优化,把a的值放入寄存器中,而b所修改实际上是寄存器的a值而不是内存中的a值,所以a依旧为10
C++独有的
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
父类作为上 ,子类作为下
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
父类对象是无法转换为子类对象的
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的,直接强制转换是不安全的)
A作为父类,B作为子类
所以将p强制转换为B*,存在风险,如果B有自己的成员,用指针可以访问这些成员,但这个访问就强制越界了,多开的一部分空间不属于你的
dynamic_cast 会先进行检查,若指向父类对象,则转换失败,若指向子类对象,则转换成功
注意:
dynamic_cast只能用于父类含有虚函数的类