C++语法(26)--- 特殊类设计_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131879800?spm=1001.2014.3001.5501
目录
1.类型转换
1.C语言的转换模式
2.C++四种类型转换
1.static_cast
2.reinterpret_cast
3.const_cast
4.dynamic_cast
3.C++线程库
1.thread
2.线程安全
1.加锁
2.原子性
3.条件变量
1.类型转换
1.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. 显式类型转换将所有情况混合在一起,代码不够清晰
四种类型转换:static_cast、reinterpret_cast、const_cast、dynamic_cast这些类型是C++期望大家使用的类型转换规范
1.static_cast
多用于类型相似转换的地方,例如int和double
int main() { double d = 12.34; int a = static_cast<int>(d); return 0; }
注意圆括号在后面
2.reinterpret_cast
适用于不相关的类型之间的转换,例如int*转int
int main() { 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; }
3.const_cast
const_cast最常用的用途就是删除变量的const属性
void Test () { const int a = 2; int* p = const_cast< int*>(&a ); *p = 3; cout<<a <<endl; cout<<*p <<endl; }
特别注意:调试时,监视窗口的a和*p都是3,但是最后的输出结果a为2,*p为3。
这是因为,在编译器中const类型的量会被认为是不会被修改得的,所以就会被放在栈上而不是在静态区中,随着会把a的数据放到寄存器中。修改*p其实修改了栈,也就是内存的数。而真正的a数据在寄存器中。监视窗口需要显示,所以一定会在栈上拿数据3。
void Test () { volatile const int a = 2; int* p = const_cast< int*>(&a ); *p = 3; cout<<a <<endl; cout<<*p <<endl; }
结果都为3,因为volatile保存内存可见性,不绕过内存直接读取寄存器。
4.dynamic_cast
1.向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
2.向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
1. dynamic_cast只能用于父类含有虚函数的类,拒绝父类转型子类
2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0class 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; }
3.C++线程库
1.thread
int main() { int n, m; cin >> n >> m; vector<thread>vt; vt.resize(n); for (auto& t : vt) { t = thread([m] { for (size_t i = 0; i < m; i++) { cout << this_thread::get_id() << ":" << i << endl; } }); } for (auto& t : vt) { t.join(); } return 0; }
1.thread类型可以先不传入执行任务
2.thread中的拷贝构造delete了,所以范围for语句就不能直接t:vt即可,因为for底层调用拷贝构造,在前面加个&就不需要进行拷贝构造
3.在线程创建好后,主线程一定要记得join所有的线程,以免报错
2.线程安全
1.加锁
mutex
try_lock:非阻塞上锁
int val = 0; mutex mtx; void Func(int n) { mtx.lock(); for (size_t i = 0; i < n; i++) { ++val; } mtx.unlock(); } int main() { thread t1(Func,2000); thread t2(Func,1000); t1.join(); t2.join(); cout << val << endl; return 0; }
1.为了线程安全,非原子性的全局变量进行多线程操作时,需要对该操作进行加锁
2.多线程可以执行同一份函数代码的原因是他们同时拥有函数这一公共资源。线程互相不干扰是因为线程之间有自己的独立栈结构
3.加锁进行减少执行粒度,使得时间成本减少。要考虑是否频繁获取上下文,执行代码是否繁琐。
recursive_mutex:递归互斥锁,递归调用锁的函数,使得不自己把自己阻塞。判断是增加条件当前的线程和上一层递归的线程的地址是否一致。
lock_guard
int main() { mutex mtx; atomic<int> aval; auto func = [&aval, &mtx](int n) { { lock_guard<mutex> lock(mtx); cout << this_thread::get_id() << "->" << val << endl; } aval++; }; thread t1(func, 2000); thread t2(func, 1000); t1.join(); t2.join(); cout << aval << endl; return 0; }
2.原子性
CAS:Compare and Swap
CAS的原理:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B。两个线程存储的都为A,想要对V进行修改。当一个线程同时修改内存值时,V会被临时存储起来,修改后同步内存,此时另一个也进行修改,那么就会被比较当前另一个的A和当前修改的V,一旦对不上,则另一个线程就会等待之前修改的线程先更新。
atomic
atomic<int> aval; void Func(int n) { for (size_t i = 0; i < n; i++) { ++aval; } } int main() { thread t1(Func, 2000); thread t2(Func, 1000); t1.join(); t2.join(); cout << aval << endl; return 0; }
3.条件变量
两个线程,交替打印,一个打印奇数,一个打印偶数
模型一:
int main() { int i = 0; thread t1([&i]{ while (i < 100) { if (i % 2 == 0) { cout << this_thread::get_id() << "->" << i << endl; ++i; } } }); thread t2([&i] { while (i <= 100) { if (i % 2 == 1) { cout << this_thread::get_id() << "->" << i << endl; ++i; } } }); t1.join(); t2.join(); return 0; }
缺点:当一个数不是if条件,那么它就会不断的循环while,直到if条件满足,这样的做法极大程度浪费了事件,因为我们在执行彼此任务同时,还需要对方不断的循环判断是否满足条件。
模型二:
条件变量 -- condition_variable
wait:等待通知
notify:发送通知
条件变量本身不是线程安全的,想要配合锁实现。
//线程交替打印 int main() { int i = 0; mutex mtx; condition_variable cv; bool flag = true; //奇数 flag==false thread t1([&]{ while (i < 100) { unique_lock<mutex> lock(mtx); while(flag==true) cv.wait(lock); cout << "t1:" << this_thread::get_id() << "->" << i << endl; ++i; flag = true; cv.notify_one(); } }); //偶数 flag==true thread t2([&] { while (i <= 100) { unique_lock<mutex> lock(mtx); while (flag == false) cv.wait(lock); cout << "t2:" << this_thread::get_id() << "->" << i << endl; ++i; flag = false; cv.notify_one(); } }); /* thread t1([&]{ while (i < 100) { unique_lock<mutex> lock(mtx); while(i%2) cv.wait(lock); cout << "t1:" << this_thread::get_id() << "->" << i << endl; ++i; cv.notify_one(); } }); //偶数 flag==true thread t2([&] { while (i <= 100) { unique_lock<mutex> lock(mtx); while (!(i%2)) cv.wait(lock); cout << "t2:" << this_thread::get_id() << "->" << i << endl; ++i; cv.notify_one(); } }); */ t1.join(); t2.join(); return 0; }
分析:该方式不会自旋的判断,一旦条件成立,通知就会唤醒线程。这样就不会占用cpu运行了。
pred的使用:
第二个wait函数的pred用于判断,如果pred返回false则会阻塞
thread t1([&]{ while (i < 100) { unique_lock<mutex> lock(mtx); cv.wait(lock, [&i] {return i % 2; }); cout << "t1:" << this_thread::get_id() << "->" << i << endl; ++i; cv.notify_one(); } }); thread t2([&] { while (i <= 100) { unique_lock<mutex> lock(mtx); cv.wait(lock, [&i] {return !(i % 2); }); cout << "t2:" << this_thread::get_id() << "->" << i << endl; ++i; cv.notify_one(); } });