CPPStd涉及到的C++知识20200717

init 20200717
v0.1 20200722
v0.2 20200724 调用某个类的成员函数
v0.3 20200810 左值与右值,C++11中的using v0.4 20210620 补充STL

关于标准库

STL=Standard Template Library,标准模板库

0723

using关键字

references:

在C++11中,使用using全面替代typedef。

当调用某个类的某个成员函数

考虑继承,虚函数\动态绑定,隐藏,覆盖,重写,基类与派生类的作用域

四个步骤:

  • S1:确定静态类型;
  • S2:函数名字查找;
    • 查找且只按函数名查找,派生类中的成员函数与基类中的重名会造成(后者)隐藏;
  • S3:类型检查;
  • S4:判断是否动态绑定;

STL——各类容器简述和使用要点

顺序容器

  • vector是可自动扩容(MSVC为1.5倍扩容,GCC为2倍,这个行为由编译器具体实现决定)的动态连续数组;
  • 当前如果空间满载,vector内容会搬运到新家;
  • 其迭代器行为可以理解为指针;(但实际不是)在insert或erase之后使用迭代器会由于vector自动扩容或跳过元素产生错误;
  • insert之后vector可能会扩容,而指向某个元素位置的迭代器没有更新;这种情况下只能重新find;
  • erase迭代器所在位置的元素,如果迭代器步进,则跳过了一个元素;
  • ——所以vector迭代器在取值后,如果还有insert或erase的行为,就不要再使用了,除非正确更新过;
  • PS:可以使用emplace就地插入,避免拷贝构造,提高性能;

  • deque是通过中控器(指针数组)映射到多段连续内存组成的双端队列;——具体实现看这个里的图:STL之deque实现详解_一个菜鸟的博客-CSDN博客
  • 在map满载时会搬运中控器到新家;
  • 其迭代器存储了当前元素的指针和其所属内存段的首尾;
  • deque如果增加元素,则可能会触发扩容,由于只搬运用于映射的指针数组,所以之前取到的元素地址或者迭代器指向的元素地址没有变化,还可以继续使用;但迭代器的node指针失效了,即迭代器不能再移动了;(这里待验证)
  • deque在非头尾位置增加或删除元素后(会触发移动?)迭代器、指针、引用都会失效;
  • deque在头尾位置插入,则可能触发中控器的搬运,造成迭代器失效(node指针失效,不能移动),但元素的位置不会改变,指向元素的指针、引用仍有效;在头尾位置删除,则指向除被删除元素外的其他元素的迭代器、指针、引用仍有效;

  • 不同C++版本有不同的string实现策略;
  • 要注意c_str()返回值指向的内存是有可能在string对象释放后被释放的,所以不建议保存c_str()的返回值,除非你能保证控制好只在string的生命周期使用;

  • std::array是定长数组;

关联容器

  • list是双向循环链表;
  • 其迭代器行为可以类比为指针;
  • insert和delete都不会引起迭代器失效;
  • 但也要注意写法,不能像用下标一样用迭代器删除,erase之后iter再步进,迭代器会跑飞; ```cpp std::list l1 = {1,2,3,4,5,5,6,7,0,3}; for (auto iter = l1.begin(); iter != l1.end(); ++iter) { if (*iter == 5) l1.erase(iter); // 这样是错误的 }

for (auto iter = l1.begin(); iter != l1.end();) { if (*iter == 5) l1.erase(iter++); // 这里一定是iter++,不是++iter else ++iter; // 这样是正确的 }

  1. 1. list的迭代器重载的++i是迭代并返回(更新后的)迭代器引用;i++是迭代并返回(未更新的)迭代器备份(是一个匿名/临时对象)
  2. 1. 所以不能直接erase(iter),迭代器还没使用节点的next迈到下一步节点就被释放了,所以会跑飞;需要erase(iter++);
  3. 1. 如果只是要迭代的效果,可以使用前置++,避免临时对象的开销;
  4. <a name="wiA0S"></a>
  5. ### <set/map>
  6. - set,元素唯一且有序的红黑树,元素只能增删,不能直接修改;
  7. - map,键唯一且有序的键值对红黑树;它的迭代器是一个pair<K,V>;
  8. - 插入和删除都不会引起迭代器失效;
  9. - map使用须知:
  10. - 如果要插入元素
  11. - 如果使用下标插入,如m[key] = val;,这样对于原来没有创建key的情况会正常创建key值并写入val,如果原来key下已经有值,则会直接覆盖;
  12. - 使用m.insert(make_pair(k,v));插入,如果重复则会插入失败,可以通过返回的迭代器判断;
  13. - 如果要访问元素,不能使用[],会在这个keyval不存在的情况下创建一个出来;
  14. <a name="lHM5z"></a>
  15. ### <unordered_set/unordered_map>
  16. - 无序容器,是用链表法处理冲突的哈希表;
  17. - 使用时如果要装自定义对象,需要重载一个比较函数;
  18. <a name="mavKf"></a>
  19. ## 容器适配器
  20. <a name="sQfSA"></a>
  21. ### <stack>和<queue>
  22. - 默认由deque实现的容器适配器;
  23. - 默认不支持遍历和迭代器;
  24. <a name="w741S"></a>
  25. ### <priority_queue>
  26. - 默认由vector实现的优先队列/堆;
  27. - 堆可以方便地找到第k大元素;
  28. <a name="7f455a27"></a>
  29. # `<thread>`并发编程
  30. - [std::thread::thread - cppreference.com](https://zh.cppreference.com/w/cpp/thread/thread/thread)
  31. - [c++并发编程之thread::join()和thread::detach() - KeepInYourMind - 博客园](https://www.cnblogs.com/zhanghu52030/p/9166526.html)
  32. <a name="56fff668"></a>
  33. ## thread的构造、join和detach
  34. - join,结合,等待;
  35. - detach,分离,挥手告别;
  36. ```cpp
  37. #include <iostream>
  38. #include <thread>
  39. #include <chrono>
  40. void foo() {
  41. // 模拟昂贵操作
  42. std::this_thread::sleep_for(std::chrono::seconds(1));
  43. }
  44. void bar() {
  45. // 模拟昂贵操作
  46. std::this_thread::sleep_for(std::chrono::seconds(1));
  47. }
  48. int main() {
  49. std::cout << "starting first helper...\n";
  50. std::thread helper1(foo); // 创建一个线程对象,便开始执行其函数
  51. std::cout << "starting second helper...\n";
  52. std::thread helper2(bar);
  53. std::cout << "waiting for helpers to finish..." << std::endl;
  54. helper1.join(); // join,【结合】当前线程对这个线程等待汇合,具有同步关系;
  55. helper2.detach(); // detach,【分离】,当前线程挥手告别这个线程,不再等待;
  56. std::cout << "done!\n";
  57. }

yield

  • yield,退避、让路
  1. void count1m(int id)
  2. {
  3. while (!ready)// wait until main() sets ready...
  4. {
  5. //若线程还有没创建的,将当前线程分配的cpu时间片,让调度器安排给其他线程,
  6. //由于使用了yield函数,在 not Ready 情况下,避免了空循环,在一定程度上,可以提高cpu的利用率
  7. std::this_thread::yield();
  8. }
  9. for ( int i = 0; i < 1000000; ++i) {}
  10. std::lock_guard<std::mutex> lock(g_mutex);
  11. std::cout << "thread : "<< id << std::endl;
  12. }

互斥

mutex-Mutual Exclusion,互斥锁,(线程不安全资源的)守卫

当使用一些线程不安全的数据对象时,例如std::map则可以使用mutex实现互斥;

mutex可以产生lock动作、try_lock动作和unlock动作;也可以使用RAII风格的函数,如std::lock_guardstd::mutex,【在作用域块以内,占有互斥锁,当控制离开lock_guard所在作用域时,自动释放互斥锁】

  • lock,当前得不到锁,就阻塞直到得到锁;
  • try_lock,当前得不到锁,就返回;
  • PS:RAII-Resource Acquisition Is Initialization,资源获取即初始化
  1. std::mutex m;
  2. void bad()
  3. {
  4. m.lock(); // 请求互斥体
  5. f(); // 若 f() 抛异常,则互斥体永远不被释放
  6. if(!everything_ok()) return; // 提早返回,互斥体永远不被释放
  7. m.unlock(); // 若 bad() 抵达此语句,互斥才被释放
  8. }
  9. void good()
  10. {
  11. std::lock_guard<std::mutex> lk(m); // RAII类:互斥体的请求即是初始化
  12. f(); // 若 f() 抛异常,则释放互斥体
  13. if(!everything_ok()) return; // 提早返回,互斥体被释放
  14. }

左值与右值,右值引用(C++11)

References:

什么是左值和右值?

  • 左值是有名字的变量或对象;
  • 右值是没有名字的临时变量、对象,只在一条语句中出现;例如 int i=3; 中的3;T().set().get(); T()生成的匿名的临时对象;

C++11之后,右值也可以被引用;

  • 原来的引用(左值引用)声明如 int& r; ;右值引用声明使用 &&
  1. template<typename Type, typename... Targs>
  2. Type* DrvFactory<Type, Targs...>::Create(DWORD slotId, string name, Targs&&... args) // Create函数的args可以接受右值引用
  3. { // 自注:所谓的右值引用"&&"实际是也接受
  4. auto& instanceMap = ManInstance();
  5. auto iter = instanceMap.find(name);
  6. if (iter == instanceMap.end()) {
  7. return nullptr;
  8. }
  9. pair<BYTE, FuncType> p = iter->second;
  10. auto uniquePtr = p.second(std::forward<Targs>(args)...); // std::forward<Targs>() 的动作是: “若是左值,则传递之后仍然是左值,若是右值,则传递之后仍然是右值”
  11. Type* ptr = uniquePtr.get();
  12. if (ptr != nullptr) {
  13. // 保存创建的指针
  14. DrvManager<Type>::Insert(slotId, p.first, uniquePtr);
  15. }
  16. return ptr;
  17. }

Lambda表达式(C++11)

<algorithm>算法库

谓词函数

在使用sort()函数时常用

事实上就是定义了一个重载了运算符的类,使用它的对象产生结果;

几种类型转换

C语言的类型转换/强制类型转换/旧式转换:略

C++中的类型转换:

头文件

代码往往分为声明和实现;将声明写在头文件中,将实现写在.cpp文件中;

而模板类的声明和实现通常都写在头文件中,因为无法分离编译;0721

C++函数

  • 默认参数,从倒数第一个参数开始从后往前的连续一个或多个参数可以提供默认参数;也即,如果最后一个参数不提供默认参数,那么每个参数都没法提供默认参数;

    C++泛型

template语句将它后面的声明作为模板,以将类型作为template形参这样的形式应付多种类型的情况;


template,模板

我们通常说的“用模板”,指的就是定义一个“函数模板”“类模板”:

  1. // 函数模板
  2. template <typename T>
  3. T add(const T &a, const T &b)
  4. {
  5. return a + b;
  6. }
  • 我们注意到

  • 类模板和模板类是什么?


  • typename和class
  • 模板

使用模板的注意事项

  1. template <typename _Key,
  2. typename _Tp,
  3. typename _Compare = std::less<_Key>,
  4. typename _Alloc = std::allocator<std::pair<const _Key, _Tp>>>
  5. using map = std::map<_Key, _Tp, _Compare, _Alloc>;

分配器

new-delete/malloc-free,关于分配和释放内存、内存泄漏的理解;
在进程的堆上分配了内存但没有释放,会有什么影响?

  • 当系统重启后,内存空间的分配情况会完好如初,所以内存分配相关的问题的影响范围不会超过主机的一次运行时间;
  • 当进程退出时,进程占用的所有物理空间都会被释放,所以内存分配相关问题的影响范围也不会超过一个进程的生命周期;
  • 虽然泄漏的内存不会跨越进程的生命周期和主机的启动周期,但这并不代表内存泄漏是可以容忍的;
  • 如果泄漏内存的地方被调用的频率特别高,比如它写在一个循环里,那么在调试程序的时候就会看到当前进程的内存占用涨得飞快,运行地极为缓慢;
  • 这意味着当前这个程序既不能正常、快速地运行,也不能长久地运行;该程序的崩溃只是时间问题;