C++
2020-03-05 20:39:00 0 举报
AI智能生成
C++
作者其他创作
大纲/内容
C++11新特性
标准库
C++标准库的头文件不带后缀.h,例如#include<vector>
新版本C头文件不带.h,例如#include<cstdio>
新版本C头文件带.h仍可用,例如#include<stdio.h>
语言
参数可变模板
- template<typename T, typeanme... Args>
void print(T head, Args... args)
模板表达式中的空格
vector<vector<int>>
nullptr
替代0或NULL
auto
auto变量是局部变量时,函数结束自动释放
一致性初始化
vector<int> v{1,2,3,4};
int values[]{ 1,2,3,4 };
explicit
之前只能针对一个实参的构造函数,C++11可以用于一个以上的实参的构造函数
for
for(auto item : vec)
=default
表示使用编译器默认给定的,即使我们在代码中重写了
=delete
表示不需要编译器默认给定的
别名模板
别名模板只能声明在函数外部
template <typename T> using vec = vector<T>; vec<int> coll; 等同于 vector<int> coll;
模板模板参数
类型别名
typedef
typedef void(*func)(int, int);
typedef value_type = T;
using
using func = void(*)(int, int);
using value_type = T;
using
noexcept
void foo() noexcept;表示该函数不会抛出异常
override
指定一个函数鄙视是重载函数,如果不对就报错
final
规定类不能被继承
class Base final{};
修饰虚函数,规定虚函数不能被重写
virtual voidf() final;
decltype
得到一个表达式的类型
子主题
三个作用
用来声明返回类型
template<class T1, class T2> auto add(T1 x, T2 x) -> decltype(x+y);
元编程
传递lambda表达式类型
lambda
tuple
参考链接
https://www.zxpblog.cn/2019/08/03/C++2.0%E6%96%B0%E7%89%B9%E6%80%A7/
内存管理
new
执行流程
首先调用全局operator new分配内存,最终还是调用malloc()库函数分配内存
将分配得到的内准地址强行转换成对象的类型
最后调用构造函数
array new
int *p = new int[3];调用三次构造函数
placement new
允许将对象构造到一个已经分配好的内存中
没有所谓的placement delete,但是重载placement new时,需要重载placement delete
只调用构造函数,而不分配内存
代码实现:new(p), ::operator new(size, void*)
四者关系
::operator new
一个全局函数
operator new(size)
一个成员函数,可以被重载,比如foo::operator new(size_t)
注意,函数在重载是需要声明为静态成员函数,因为在调用该函数时,对象还不存在,无法通过对象调用该函数
底层调用::operator new
operator new[]
void * operator new[](size_t size)
delete
执行流程
首先调用析构函数
最后释放内存,调用全局operator delete释放内存,底层调用free()
array delete
delete p[];调用三次析构函数
对应int *p = new int[3];,利用delete p;释放内存也是正确的,因为其析构函数没有做任何事情,而对于string类型就不同
malloc
free
类的内存管理
allocator
嵌入式指针
内存块将前4个字节当做一个字节,串成一个链表
自由链表
16个指针
大小从8个字节到128
STL
容器
序列式容器
vector
array
list
queue
forward-list
stack
关联式容器
set
map
不定序容器
multiset
multimap
算法
count
count_if
find
find_if
sort
accumulate
for_each
replace
replace_if
replace_copy
binary_search
迭代器
分类
input_iterator_tag
istream_iterator
output_iterator_tag
ostream_iterator
forward_iterator_tag
forward-list
bidirectional_iterator_tag
红黑树
random_access_iterator_tag
list
vector
array
deque
通过iterator_traits获取迭代器的difference_type
仿函数
算术类别
plus
相加
minus
相减
multiplies
相乘
divides
相除
modulus
取模
negate
取负值
使用方式
int res = plus<int>()(1, 2);
逻辑类别
logical_and<T>
logical_or<T>
logical_not<T>
使用方式
bool res = logical_and<int>(1,0);
需要添加头文件#include<functional>
相对关系类别
equal_to<T>
等于
not_equal_to<T>
不等于
greater<T>
大于
greater_equal<T>
大于等于
less<T>
小于
less_equal<T>
小于等于
使用方式
priority_queue<int, vector<int>, less<T>>
bool res = less<int>()(2,3);
仿函数本质就是重载了一个operator(),创建一个行为类似函数的对象
仿函数对象仅仅占用1字节,因为内部没必要数据成员,仅仅是一个重载的方法
仿函数很少单独使用,一般在STL算法中使用
标准库提供的仿函数继承自binary_function和unary_function,仿函数可适配的条件是:仿函数必须继承自这两个函数之一。
适配器
容器适配器
stack
内含一个容器deque,对其改造
queue
底层实现是deque,对其改造得到的
迭代器适配器
reverse_iterator
rbegin()
rend()
仿函数适配器
binder2nd
count_if(vi.begin(), vi.end(), bind2nd(les<int>(), 40));
分配器
萃取机
type_traits
is_void<T>::value
is_integrial<T>::value
使用方式:bool res = is_integrial<int>::value;
分配器支持容器,容器是一个模板类,有些操作在容器中完成,有些独立处理成一个一个函数,组成了算法。迭代器是一种泛化的指针。仿函数作用类似于函数,类对象的相加相减通过仿函数实现。适配器转换一些东西,比如讲容器惊醒转换,对仿函数进行转换,对迭代器转换。
数据结构
RB-TREE
平衡二叉搜索树常被使用的一种。
set/multiset和map/multimap底层实现是红黑树
hashtable
源码学习链接:https://github.com/zxpgo/MyTinySTL
C++11并发与多线程
常用函数
创建线程
thread mythread(myfun)
myfun是自定义的初始函数
join()
阻塞主线程,让主线程等待子线程执行完毕,然后子线程和主线程汇合,然后主线程往后执行。
detach()
实现主线程和子线程分离,主线程执行自己的,子线程执行自己的,主线程不必要等待子线程执行完毕
使用detach()之后,子线程就会驻留在后台执行,子线程相当于被C++运行时库接管,子线程执行完毕,由运行时库负责清理该子线程的相关资源
一旦调用detach(),子线程就会进入后台执行,就不能再用join()
joinable()
判断是否可以成功使用join()或者detach(),joinable()返回true或false,true表示可以使用join或detach
创建线程步骤
包含一个头文件
#include <thread>
创建初始函数
函数
类对象
创建一个类,重载operator()
在主函数中创建类对象,如果主线程提前执行完毕,主线程中的对象就不存在了,但是对象实际上是被复制到子线程中去 ,所以没有影响。但是类对象里不能有引用或指针,不然就会出现异常。
lambda
main中创建线程和执行线程
join阻塞主线程,等待子线程执行完毕
detach()大坑
传递临时变量作为线程参数
虽然多线程传参中传引用实际上也是传值,但是不建议使用
绝对不能使用指针
传递int这种简单的类型,建议都是值传递,不要使用引用
如果传递对象,避免隐式转换,应该全部在创建线程时,构造临时对象,然后在函数参数中用引用来接收,否则系统还会构造临时对象,浪费资源,即构造三个对象
建议不使用detach(),只使用join(),这样就不存在局部变量失效导致对内存的非法引用问题
临时对象作为线程参数
std::this_thread::get_id()可以获取进程的id
thread mythrad(myfun, A(a));
传递类对象、智能指针作为线程参数
如果想做到子线程对象的修改影响到主线程中的对象,需要使用std::ref()函数
thread mythread(myfun, std::ref(myobj));
成员函数指针做线程参数
thread mythread(&A::thread_work, myobj, 15);
注意,类A的成员函数thread_work,myobj是一个A对象,15是成员函数thread_work的参数
多线程创建和数据共享问题
多线程的创建
vector<thread> mythreads;
放入vector中
然后循环中进行join()
数据共享问题
只读数据
安全
有读有写
互斥量mutex
std::mutex my_mutex;
my_mutex.lock();
my_mutex.unlock();
std::lock_guard类模板
直接取代lock和unlock
lock_guard的构造函数中执行了mymutex.lock(),在析构函数中执行了mymutex.unlock()
使用方式
std::lock_guard<std::mutex> sbguard(my_mutex);
可以通过加{}来实现解锁的目的
死锁解决方案
std::lock()函数模板
一次锁住两个或者带个一样的互斥量(至少两个)
std::lock(my_mutex1, my_mutex2);
my_mutex1.unlock(); my_mutex2.unlock();
std::lock_guard()和std::adopt_lock
std::lock(my_mutex1, my_mutex2);
std::lock_guard<std::mutex> subguard1(my_mutex1, std::adopt_lock());
std::lock_guard<std::mutex> subguard2(my_mutex2, std::adopt_lock());
adopt_lock是一个标记,表示这个互斥量已近lock(),不需要再lock_guard的构造函数中再对mutex对象进行lock()
std::unique_lock
unique_lock是一个类模板,可以取代lock_guard,推荐使用lock_guard
unique_lock也是对mutex对象进行加锁和解锁,但是比lock_guard灵活,但是效率低
只有互斥量作为参数时
std::unique_lock<std::mutex> subguard(my_mutex);
第二个参数
std::adopt_lock
std::try_to_lock
成员函数
lock()
unlock()
try_lock()
单例设计模式共享数据分析
两个if是必须的
std::call_once()
该函数第二个参数时函数名,call_once()能够保证第二个参数对应的函数只被调用一次
具有互斥量的特性,而且效率上,比互斥量消耗的资源更少
call_once()需要结合一个标记使用,这个表示是std::once_flag
once_flag是一个结构
call_once()通过这个标记来决定对应的函数是否执行
调用call_once()成功后,call_once()就把这个标记设置称为一种已调用的状态
后续再次调用call_once(),只要once_flag被设置为了"已调用"状态,那么对应的函数就不会被再执行了
使用方式
std::call_once(g_flag, CreateInstance);
条件变量
std::condiiton_variable
是一个类,和一个条件相关的类,即等待一个条件满足,这个类需要和互斥量配合工作,使用的时候,需要生成这个类的对象
wait()是成员函数,第一个参数是互斥量,第二个参数是可调用对象
wait()一直堵塞,知道某个线程调用notify_one()成员函数
wait()能继续执行成功,需要满足两个条件
成功拿到锁,即第一个参数上锁成功
第二个参数可调用对象返回true
notify_one()只能唤醒一个线程
notify_all()同时能够唤醒多个线程
同步
async、future创建后台任务并返回值
std::async是一个函数模板,是用来启动一个异步任务,启动一个异步任务后,返回一个std::future对象,std::future是一个类模板
启动一个异步任务,就是自动创建一个线程,并开始执行对象的线程入口函数,返回一个std::future对象。std::future对象中包含线程入口函数返回的结果,我们可以通过调用future对象的成员函数get来获取结果
使用方式
std::future<int> res = std::async(mythread);
mythread是一个线程入口函数
int ans = res.get();
get成员函数等待线程执行结束并返回结果,如果线程没有执行完毕,得不到返回值,主线程会阻塞在get()处
注意get()只能调用一次
future类还有一个wait()成员函数,只能阻塞等待线程执行完毕,无法获取返回值
成员函数当线程入口函数
std::future<int> result = std::async(&A::mythread, &myobj, mypara);
async函数还可以有另外一个参数
std::launch类型(枚举类型)
std::launch::deferred
表示线程入口函数延迟到future对象调用wait()或get()时才执行
std::launch::async
表示调用async时就创建并执行线程
async函数的默认值是std::launch::deferred | std::launch::async
表示可以异步运行也可以不运行,取决于系统
future其他成员函数
wait_for成员函数等待子线程执行一段时间
std::future_status status = result.wait_for(std::chrono::seconds(6));
std::shared_future
多个线程可以获取某个线程的结果,解决了future只能获取一次结果的问题
是一个类模板,该对象的get()函数不使用移动语义实现,而是使用复制实现的,所以可以多次调用get()来获取返回值
std::packaged_task
是一个类模板,模板参数是各种可调用对象。通过std::packaged_task将各种可调用对象包装起来,方便将来作为线程入口函数使用
std::promise
是一个类模板,我们能够在某个线程中给std::promise赋值,然后我们可以在其他线程将这个值取出来使用
可以通过将一个future绑定到这个promise,然后通过future就可以得到promise中保存的值
可以实现两个子线程之间的通信
原子操作
std::atomic<int> g_mycount= 0;
++g_mycount;是一个原子操作
原子操作针对++,--,+=,&=,|=是支持的,其他的操作符不支持
Windows临界区
使用方法跟互斥量相似
CRITICAL_SECTION my_winsec;
EnterCriticalSection(&my_winsec);
LeaveCriticalSection(&my_winsec);
自动析构技术
容器和智能指针就是RAII类,RAII类即资源获取及初始化
递归互斥量
recursive_mutex
效率比mutex低
递归次数有限,递归太多可能报异常
使用方式
std::recursive_mutex my_mutex;
std::lock_guard<std::recursive_mutex> subguard(my_mutex);
学习链接:https://www.zxpblog.cn/2019/06/17/C++%E5%B9%B6%E5%8F%91%E4%B8%8E%E5%A4%9A%E7%BA%BF%E7%A8%8B/
0 条评论
下一页