c++ primer c++11 IO库 顺序容器 vector 关联容器map 简单的泛型算法 还有lambda的本质 考点在这08-11

08IO

  • IO对象无拷贝或赋值 并且读写IO对象会改变其状态 所以一般传递IO对象使用普通引用或者指针

  • IO操作与生俱来的问题是可能引发错误 有些错误可修复 有些错误不可修复

  • endl 换行然后刷新缓冲区 ends 插入一个空字符然后刷新缓冲器 flush 刷新缓冲区

  • cout << unitbuf 设置流的内部状态后续的输出都会立刻输出 无缓冲
    cout << nounitbuf 回到正常的缓冲方式

  • 输入流关联到输出流时 任何的输入流读取操作都会刷新关联的输出流 比如cin就会刷新cout的缓冲区

  • fstream被销毁时会自动调用close解除关联

09顺序容器 这部分书里内容挺多的 下面只是一丢丢而已

  • 新标准库容器比旧版本的快很多 书上说是通过通过移动构造函数或者其他方式与经过精心设计优化的同类数据结构一样好甚至更好
    反正书上是这样说的哈

  • vector deque list forward_list array string 列举出来了这6个

    • vector 可变大小数组 快速随机访问 尾部之外的位置插入删除元素很慢

    • deque 双端队列 快速随机访问 头尾位置插入和删除都很快

    • list 双向链表 只支持双向顺序访问 在任何位置插入都很快

    • forward_list 单向链表 只支持单向顺序访问 在任何位置插入都很快
      书上说是设计目标是与最好的手写的单向链表数据结构具有相当性能 因此forward_list没有size操作
      因为保存或计算大小就会比手写的链表多出额外的开销

    • array 固定大小数组 固定大小数组 不能添加和删除元素

    • string 与vector类似 专门保存字符串 支持快速随机访问 在尾部插入删除元素很快

  • 选容器的原则

    • 一般情况下 通常情况下 vector 是最好的选择

    • list 和 forward_list 的额外开销比较多 如果额外开销比较重要或者程序有很多小元素 则不是和选择list类型的结构

    • 随机访问 vector deque

    • 容器中间频繁增删元素 list forward_list

    • 头尾都需要插入删除 deque

    • 如果只有在读取时需要在容器中间位置插入元素 后续只需要随机访问元素

      • 确定是否真的需要在中间添加元素 可以通过vector在尾部添加元素 全部完成之后 对vector进行一次排序sort即可

      • 必须在中间插入 可以考虑在插入的中间过程使用list 完成全部输入之后将list的内容拷贝到vector中

  • 如果你不确定用vector还是list还是其他容器 可以在使用过程中使用其公共的操作 迭代器

  • 对构成范围的迭代器的要求

    • 指向同一个容器中的元素或者最后一个元素下一个的位置

    • 可以通过反复递增begin达到end

  • 赋值相关运算会导致左边容器内部的迭代器引用指针失效

  • 除了array以外 swap仅仅交换了两个容器内部的数据结构 不发生任何的拷贝删除插入等操作

  • 除了string外 swap 不会 不会 不会 不会 不会 导致指向容器的迭代器引用指针失效 仍然指向swap之前的那些元素
    但是在swap之后那些元素已经属于不用的容器了
    对于array是真的交换了两个array中的元素 所以尽管在swap之后 之前的迭代器引用指针虽然绑定的元素不变 但是绑定的元素的值却变了

        vector<int> v1 = {1};
        vector<int> v2 = {2};
        auto it1 = v1.begin();
        auto it2 = v2.begin();
        swap(v1, v2);
        cout << *it1 << " " << *it2 << endl;
    
        array<int, 1> v3 = {3};
        array<int, 1> v4 = {4};
        auto it3 = v3.begin();
        auto it4 = v4.begin();
        swap(v3, v4);
        cout << *it3 << " " << *it4 << endl;
    
  • vector string deque 插入操作会使所有迭代器失效

        vector<int> v1 = {1};
        vector<int> v2 = {2};
        auto it1 = v1.begin();
        auto it2 = v2.begin();
    
        // 插入元素会使迭代器失效
        v1.push_back(8);
        v2.emplace_back(9);
        cout << *it1 << " " << *it2 << endl;
    

    c++ primer c++11 IO库 顺序容器 vector 关联容器map 简单的泛型算法 还有lambda的本质 考点在这08-11_第1张图片

  • emplace类的操作 emplace emplace_back emplace_front 使用参数在容器管理的内存空间中直接构造元素 所以效率更高
    但是push类的操作比如push_back等是进行了拷贝 具体内容需参考右值引用和移动构造函数

  • deque 删除除收尾元素的操作会使所有迭代器引用指针失效
    vector string 在删除点之后位置的迭代器引用指针会失效

  • 改变容器的大小resize 也可能导致迭代器指针引用失效

容器操作可能使迭代器失效(前面几点有简略说明)

  • 容器增加元素后

    • 如果容器是vector或者string 且内存被重新分配 则指向容器的所有迭代器(同理指针引用后续不在特别说明)都失效
      如果未重新分配内存 那么插入位置和插入位置之后的迭代器都会失效

    • 如果是deque 在除首尾外的位置插入都会导致所有的迭代器失效
      在首尾元素添加元素 迭代器会失效 但是引用和指针不会失效

  • 容器删除元素后

    • 对于deque 首位之外的位置删除元素 迭代器全部失效
      如果删除的是尾元素 尾后迭代器会失效 其他迭代器 指针 引用不收影响
      如果删除的是首元素 所有的迭代器都会失效 指针引用不受影响

    • 对于vector和string 删除元素之后的会失效之前的不会

  • 尾迭代器基本上都会失效
    增删vector或者string 或者在deque除了首元素之外的其他位置增删元素

  • vector或者string会自动分配新的空间

  • 容器适配器 stack queue priority_queue


10

  • 大多数都应以在 algorithm numeric functional 中

  • 迭代器另算法不依赖于容器 但算法依赖容器类型的操作
    所以大多数算法提供了一种方法允许我么使用自定义的操作来代替默认的运算符

  • 泛型算法运行与迭代器之上 永远不会执行容器的操作 带来一个特性算法永远不会改变底层容器的大小
    虽然有插入迭代器可以完成向容器添加元素的操作 但是泛型算法本身永远不会做这样的操作

  • 对于只读的算法 最好使用cbegin和cend的const迭代器

  • 算法不检查写操作 比如 fill_n 向目的位置迭代器写入算法假定位置足够大
    比如向一个空的vector中写入10个元素 就很有问题了 可能vec初始并咩有这个大的空间位置 这是一个很容易犯的错误

  • 插入迭代器insert_iterator 在中有以下几个标准库方法可以获取插入迭代器

    • back_inserter 赋值时使用push_back

    • front_inserter 赋值时使用push_front

    • inserter 此函数接收两个参数 在给定元素之前进行插入 类似insert的效果
      inserter返回的插入迭代器每次赋值之后迭代器还是会在同一位置 具体如下

      	auto it = inserter(c, iter);
      
      	如下解释
      	it = *val;
      	相当于如下操作
      	it = c.insert(iter, val);
      	++it;
      
    • 注意一个奇怪的现象 front_inserter 迭代器会导致顺序反过来 因为每次push_front的 而另外两个不会

lambda

  • 一元谓词 二元谓词
    一元谓词 表示只接受单一参数
    二元谓词 表示只接受两个参数

  • lambda 表达式 表示一个可调用的代码单元 可以将其理解为一个未命名的内联函数
    lambda 可能定义在函数内部

    • 格式 [capture list] (parameter list) -> return type {function body}

    • 可以忽略参数列表和返回类型 但是必须有补获列表和函数体
      忽略返回类型的话 如果函数体只有单一的return语句那么返回类型会自动推断
      如果函数体有除了return之外的任何语句 没有返回类型的话则默认返回void
      auto f = [] {return 42;}

    • 可以使用捕获列表捕捉局部变量 捕获之后就可以在lambda函数体内部使用捕获的变量
      捕获列表只用于局部非static变量 因为lambda可以直接获取局部static变量和他所在函数之外声明的名字

    • 可以采取值捕获也可以采取引用捕获 引用捕获的话加一个&即可

      • 创建lambda对象时就对所有的捕获数据成员进行了初始化 而不是在调用时捕获初始化

        • 所以值捕获方式 会在创建lambda时就进行一次拷贝

        • 如果是引用捕获的话 一定要注意匿名函数调用的时候引用还存在 比如函数返回一个lambda对象时
          返回的lambda函数一定不能包含引用捕获 不然就和返回局部变量的引用一样引发意想不到的异常

    • 应该尽量避免捕获引用指针或者使用引用捕获方式 因为这都需要保证在执行时这些捕获的值还有效

    • 想要在lambda中改变捕获变量的值

      • 值捕获需要在参数列表后面加上 mutable 关键字 比如 auto f = [v1] () mutable {++v1;}

      • 引用捕获的话 看引用是否是const

    • 在第14章的重载运算符一章谈到函数调用运算符时有提到一些lambda的内容

      • lambda函数其实是一个函数对象 编译器会将lambda翻译成一个未命名类的未命名对象

      • 默认情况下 lambda 产生的类的函数调用运算符时一个 const 成员函数 如果lambda被声明是可变的 则不是const

      • lambda表达式产生的类不含默认构造函数 赋值运算 默认析构函数 是否含有默认的拷贝/移动构造函数视捕捉的数据类型而定

bind

  • 在functional中有一个bind的标准库函数 可以理解为一个函数适配器 一般形式如下
    auto newCallable = bind(callable, arg_list);

  • 可以使用_n表示占位符 比如 _1 _2 等等 这些_n在名字空间placeholdersplaceholders又在std的名字空间中
    与bind一样 这些名字的定义也在 functional 中

  • 默认情况下 不是占位符的参数使用值拷贝 如果对某些绑定的参数要使用引用隐式的话需要使用ref函数或者cref函数(const)
    比如 auto f = bind(ref(os), _1);

iostream流迭代器

  • 标准库并不保证立即从流读取数据 具体实现可以推迟从流中读取数据 直到我们使用迭代器时才真正读取
    标准库中保证在第一次解引用之前从流中读取数据的操作已经完成

  • 对输出的ostream_iterator\ out解引用操作和递增操作都是没意义的 全部都返回out本身 不过还是推荐使用*out++和其他流的行为保持一致

    // 解引用会需要输入读取值
    istream_iterator<int> in_iter1(cin); //从cin读取int
    auto x = *in_iter1;
    cout << x;
    
    istream_iterator<int> in_iter2(cin); //从cin读取int
    istream_iterator<int> eof2;          //尾后迭代器
    
    vector<int> vec2 = {};
    while (in_iter2 != eof2)
    {
        vec2.push_back(*in_iter2++);
    }
    for (auto &v : vec2)
    {
        cout << v << " ";
    }
    cout << endl;
    
    // 也可以直接用流迭代器表示范围
    cin.clear();
    cin.ignore();
    istream_iterator<int> in_iter3(cin); //从cin读取int
    istream_iterator<int> eof3;          //尾后迭代器
    vector<int> vec3(in_iter3, eof3);
    for (auto &v : vec3)
    {
        cout << v << "_";
    }
    cout << endl;
    

反向迭代器

  • 反向迭代器可以用过 base 成员函数得到普通的迭代器

  • 重要的来了 重要的来了有一个反向迭代器 rcomma 那么 [ line.crbegin(), rcomma ) 和 [ rcomma, line.cend() ) 需要表示相同的范围
    那么会发现base成员函数必须生成相邻的成员函数而不是相同位置 然后会发现用普通迭代器去初始反向迭代器时这俩其实指向的不是同一个元素 具体入下表所示的意思

    c++ primer c++11 IO库 顺序容器 vector 关联容器map 简单的泛型算法 还有lambda的本质 考点在这08-11_第2张图片c++ primer c++11 IO库 顺序容器 vector 关联容器map 简单的泛型算法 还有lambda的本质 考点在这08-11_第3张图片

5类迭代器 特点还挺多 这里不写了 P365 自行翻阅

  • 输入迭代器

  • 输出迭代器

  • 前向迭代器

  • 双向迭代器

  • 随机访问迭代器


11关联容器

  • pair定义在utility中 pair的默认构造函数对数据成员进行值初始化

  • set的元素时const的 map的元素师pair pair的第一个元素key是const的

  • 用迭代器遍历输出map set时是按照key的升序遍历元素

  • map和set重复插入相同key的元素不会有任何影响 对于给定的关键字只有第一次带此关键字的元素才会被插入
    就是重复插入无效

  • 对map使用下标运算符 如果下标不在map中的话 会把这个key插入到map中并且进行值初始化

  • 等等等等等等等等等等等

你可能感兴趣的:(#,c++primer,读书笔记,c++)