Jianghc's Blog

Back

写在前面的#

  趁着2021年过年的这几天整理一下着半年工作过程中的学习心得,这里按照我记录的先后顺序进行了整理,后面如果有时间的话还会继续更新的。

1. 线程锁的互斥问题#

  mutex是最常见的线程同步手段,但效率不太高(函数在操作的过程中包含上锁lock和解锁unlock两个过程),引入#include<condition_variable>头文件可以使用条件变量,其中std::condition_variable可以和std::mutex一起使用,wait()可以让线程进入休眠状态,notify_one()可以唤醒位于wait()中的其中一个条件变量,notify_all()可以唤醒所有处于wait()的条件变量。

  管理互斥锁通常使用std::unique_lock,通过互斥包装器其使用更加灵活,wait()会先调用互斥锁的unlock()函数在将自己朽休眠,被唤醒后又会继续使用锁。

tips:互斥变量一般都对应一个全局的,或可传递的标志量,这样方便通过对标志量的判断完成对指定操作的保护。

2. 时钟问题#

一般使用std::chrono库。
std::chrono::duration 表示一段时间。
std::chrono::time_point 表示一个具体的时间(需要有clock记录时间)。
std::chrono::system_clock 表示当前系统时钟,和系统中运行的所有进程使用的now得到的时间是一致的。
std::chrono::steady_clock 为稳定的时间间隔,后一次调用的时间总比前一次大(单位是ns,纳秒)

3. new关键字使用的相关问题#

new关键字开辟的是一段空间,并返回指向该空间的首地址,new和指针一般是同时使用的,举个栗子:

int *p = new int(3);

int *p = new int;
*p = 3;
plaintext

4. STL容器相关知识#

stl容器的分类大体上可以分为三类,顺序容器,关联容器和STL容器适配器,可以用如下图进行总结:
STL容器总结

下面来详细介绍下每个相关的容器
vector: 可变数组,支持随机访问,插入和删除操作会引发迭代器的失效
deque:双向队列,连续访问,支持随机访问 array: 固定大小数组,比原生数组的功能强大很多 list: 双向链表,不支持随机访问,插入和删除效率高,list增删节点时元素位置不变,迭代器未失效 set/map: 底层红黑树实现,可用于排序和去重,插入删除元素迭代器不失效(根据key自动排序),自定义key类型时需要重载 operate< 放在类中或者结构体中(c++11不建议结构体),key值无法更改
unordered_set/unordered_map: 底层hash表实现,查找和删除效率高于set/map,对于自定义类型需要重载 operate == ,并提供对应的hash函数(基本数据类型和string系统已内置)
stack/queue: 默认底层实现为deque,没有迭代器的说法

tips:只有随机访问的迭代器才支持排序(sort/stable_sort)操作,包括,vector,deque,string,array,原生数组。

5. STL容器中的insert和erase,find#

以vector为例,insert的用法如下v.insert(iter,value),该元素插入的位置即当前迭代器的位置,原有元素顺势后移,具体的过程可以用下述流程表达:

a,b,c,d                 
    |          ===>       a,b,e,c,d
    e
plaintext

erase返回的是迭代器元素后的下一个元素的位置,因此迭代器不会失效,这里要注意写法

for (auto &iter = v.begin();iter!=v.end();iter++) {
    iter = v.erase(iter);
}
plaintext

map中的find返回的是迭代器,因此客户以通过firstsecond这这两个方法访问其键值,对于vector来说,若find不到元素则会返回end()迭代器

6. STL容器中的排序问题#

使用sort需要引入#include<alogrithm>头文件,vector和set的默认排序方式为升序排列(从小到大)
map和unordered_map是不能进行排序的,因为其不支持随机访问,因此如果需要排序,需要将其中的压入随机容器中进行排序,例如vecotr
对于map这种自带排序的容器,其默认按照key的升序进行排序,如果想使用降序排序,则需要在声明的开始处指定即可:

std::map<int, std::deque<std::vector<int>>, std::greater<int>> greaterMap;

std::greater<int> 从大到小排序
std::less<int> 从小到大排序
plaintext

7. C++11中的copy#

把一个序列(sequence)拷贝到一个容器(container)中去,通常可以使用std::copy方法

std::copy(start, end, std::back_inserter(container));
          ----------  ----------------------------
            迭代器     容器接口(包含push_back方法的)
plaintext

copy 只负责复制,并不负责申请空间,因此后面的迭代器的大小一定要注意,防止缓冲区溢出等问题

8. C++11中的智能指针#

这里说的智能指针主要指的share_ptr和unique_ptr,对于auto_ptr这种c++11标准是不建议进行使用的

share_ptr    非独占型     允许多个指针指向同一个对象
unique_ptr    独占型      指针独占的指向指定的对象
plaintext

安全的分配和使用动态内存的方式是调用make_share()函数,指针的返回类型为share_ptr,

auto ptr = make_share<T> (args)  用args初始化对象
plaintext

tips: 每个share_ptr都有一个引用计数器,当原有对象无引用时,该计数器为0,对应的该对象也会被释放,因此这里要注意循环引用的问题,容易造成内存泄漏。

9. 内存管理#

使用new动态分配和初始化对象
在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针

10. default和delete关键字#

这里要先说一下类的三大基本原则 与 c++11的五大原则

类的三大基本原则: 类的构造,类的析构,以及类的复制
plaintext

  通常当自己不实现构造和复制操作时,雷内会给我们自动生成默认的构造和复制函数,这样很容易发生浅拷贝的问题。
  因此为了选取/规避这种问题,c++11给出了delte和default来显示指明系统是否需要自动提取对应的函数

由于c++11中引入了左值(能够取到地址)和右值(不会占用内存,无法取地址)的概念,因此类的三大基本原则变成了5大基本原则。
右值涉及移动语义的概念,因此也产生了移动构造,左值则对应拷贝语义,因此对应拷贝构造,因此找个关系可以大致表达为:

构造----   右值   ------>   移动语义   ------>   移动构造
    -----  左值   ------>   拷贝语义   ------>   拷贝构造
plaintext
拷贝(赋值)----   右值   ------>   移动语义   ------>   移动赋值
         -----  左值   ------>   拷贝语义   ------>   拷贝赋值
plaintext

因此通常会见到如下代码:

Foo ( const Foo& ) = delete;
Foo& operate = ( const Foo& ) = delete;
拷贝构造和拷贝赋值成对出现,左值
plaintext

对于基础类型,右值无法改变,因此不能用const,volatie修饰

Foo ( Foo&& ) = delete;
Foo& operate = ( Foo&& ) = delete;
拷贝构造和拷贝赋值成对出现,左值
plaintext
C++11 相关学习心得(持续更新)
https://525511.xyz/blog/c-11-%E7%9B%B8%E5%85%B3%E5%AD%A6%E4%B9%A0%E5%BF%83%E5%BE%97-%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0
Author Haochen Jiang
Published at February 15, 2021
Comment seems to stuck. Try to refresh?✨