美文网首页
《C++ Primer》Note和建议及知识点等总结(第II部分

《C++ Primer》Note和建议及知识点等总结(第II部分

作者: 梦终无痕_311d | 来源:发表于2019-08-02 00:37 被阅读0次

    第8章 IO库

    1. 由于不能拷贝IO对象,因此不能将形参和返回值类型设置为流类型。进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的
    2. 管理流的状态:
      流的rdstate成员返回一个iostate值,对应流的当前状态。
      setstate操作将给定条件位置位,表示发生了对应错误。
      clear操作不接受参数的版本清除(复位)所有错误标志位。执行clear()后,调用good会返回true。
      clear的带参数版本接受一个iostate值,表示流的新状态。
      为了复位单一的条件状态位,可以先用rdstate读出当前条件状态,再用位操作将所需位复位来生成新状态,例如:
    //复位failbit和badbit,其他标志位不变
    cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
    
    1. 刷新输出缓冲区相关的操纵符:
    cout << "hi!" << endl;  //输出"hi!"和换行,刷新缓冲区
    cout << "hi!" << flush; //输出"hi!",刷新缓冲区,不附加额外字符
    cout << "hi!" << ends;  //输出"hi!"和一个空字符,刷新缓冲区
    
    cout << unitbuf;    //设为所有输出操作后都会立即刷新缓冲区
    cout << nounitbuf;  //回到正常的缓冲方式
    
    1. 交互式系统通常应该关联输入流和输出流。这意味着所有输出,包括用户提示信息,都会在读操作之前被打印出来。

    tie有两个重载的版本:一个版本不带参数,返回指向输出流的指针。

    x.tie();    //返回指向x关联到的输出流的指针;若x未关联到流,则返回空指针
    

    tie的第二个版本接受一个指向ostream的指针,将自己关联到此ostream。

    x.tie(&o);  //将流x关联到输出流o
    

    每个流最多关联到一个流,但多个流可以同时关联到同一 ostream 。

    1. 当一个 fstream 对象被销毁时,close 会自动被调用。

    2. 以 out 模式打开文件会丢弃已有数据。
      保留被 ofstream 打开的文件中已有数据的唯一方法是显式指定 app 或 in 模式。

    3. 当我们的某些工作是对整行文本进行处理,而其他一些工作是处理行内的每个单词时,通常可以使用 istringstream 。
      当我们逐步构造输出,希望最后一起打印时,可以使用 ostringstream 。
      ostringstream 对象调用 str() 返回其中保存的数据,调用 str("") 将流中保存的数据清空。

    第9章 顺序容器

    1. vector——可变大小数组
      deque——双端队列
      forward_list——单向链表
      list——双向链表
      array——固定大小数组
      string——与vector相似的容器,专门用于保存字符
    2. 以下是一些选择容器的基本原则:

    ● 除非你有很好的理由选择其他容器,否则应使用 vector 。
    ● 如果程序有很多小的元素,且空间的额外开销很重要,则不要使用 list 或 forward_list 。
    ● 如果程序要求随机访问元素,应使用 vector 或 deque 。
    ● 如果程序要求在容器的中间插入或删除元素,应使用 list 或 forward_list 。
    ● 如果程序需要在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操作,则使用 deque 。
    ● 如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则
    —— 首先,确定是否真的需要在容器中间位置添加元素。当处理输入数据时,通常可以很容易地向 vector 追加数据,然后再调用标准库的 sort 函数来重排容器中的元素,从而避免在中间位置添加元素。
    —— 如果必须在中间位置插入元素,考虑在输入阶段使用 list ,一旦输入完成,将 list 中的内容拷贝到一个 vector 中。

    如果你不确定应该使用哪种容器,那么可以在程序中只使用 vector 和 list 的公共操作:使用迭代器,不使用下标操作,避免随机访问。这样,在必要时选择使用 vector 或 list 都很方便。

    1. 当不需要写访问时,应使用cbegin和cend。

    2. 当将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同。

    3. 除array外,swap不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。

    4. emplace函数在容器中直接构造元素。传递给emplace的参数必须与元素类型的构造函数相匹配。

    5. 除了顺序容器外,标准库还定义了三个顺序容器适配器:stack、queue 和 priority_queue 。
      这些容器适配器通过成员函数 push() 和 pop() 添加和删除元素(而非 push_back() ),栈为后进先出(LIFO),队列为先进先出(FIFO),priority_queue 为优先级高的先出队,详见原书368页或中文版329页。

    第10章 泛型算法

    1. 泛型算法永远不会执行容器的操作,它们只会运行于迭代器之上,执行迭代器的操作。算法不能(直接)添加或删除元素。
    2. 对于只读取而不改变元素值的算法,通常最好使用cbegin()和cend()。但是,如果你计划使用算法返回的迭代器来改变元素的值,就需要使用begin()和end()的结果作为参数。

    3. 那些只接受单一迭代器来表示第二序列的算法,都假定第二序列至少和第一序列一样长。向目的位置迭代器写入数据的算法假定目的位置足够大,能容纳要写入的元素。
    4. 算法命名规范:
      • "_copy"版本。这些算法不会改变原序列,会在新序列保存结果。
      • "_if"版本。接受一个谓词替代原本(查找)的元素值。
    5. 一个lambda表达式具有如下形式:
      [capture list] (parameter list) -> return type { function body }
      可以忽略 参数列表返回类型 ,但必须永远包含 捕获列表函数体
      ——忽略括号和 参数列表 等价于指定了一个空参数列表。
      ——忽略 返回类型 :如果函数体只是一个return语句,会自动推断返回类型;如果包含任何单一return语句之外的内容,则返回void。
      捕获列表 只适用于局部非static变量,lambda可以直接使用局部static变量和在它所在函数之外声明的名字。
    6. 当定义一个lambda时,编译器生成一个新的与lambda对应的类类型。默认情况下,从lambda生成的类包含一个对应该lambda所捕获变量的数据成员,此数据成员在lambda对象创建时被初始化(而非调用时)。
      因此,需要保证lambda每次执行时这些数据都还有预期的意义。
      值捕获、引用捕获:与参数传递类似。
      隐式捕获:"&"表示默认引用捕获,"="表示默认值捕获,写在第一位。
    ostream &os = cout;
    char c = '.';
    auto f1 = [&, c](const string &s) { os << s << c; };    //os隐式捕获,引用捕获;c显式捕获,值捕获
    auto f2 = [=, &os](const string &s) { os << s << c; };  //os显式捕获,引用捕获;c隐式捕获,值捕获
    

    可变lambda:对于值被拷贝的变量,lambda不会改变其值,若想改变,则加关键字mutable。(引用捕获则看是否指向const)

    int v = 42;
    auto f = [v]() { return ++v; };  //错误!!表达式必须是可修改的左值
    auto f = [v]() mutable { return ++v; };  //正确!!
    
    1. 参数绑定:
    //#include <functional>
    using namespace std::placeholders;
    //假定f为可调用对象,它有5个参数
    auto g = bind(f, a, b, _2, c, _1);
    g(X, Y);  //相当于调用下一条语句
    f(a, b, Y, c, X);
    //若要传引用,则使用 ref() 或 cref()
    
    1. inserter 插入器插入的元素是正序排列的(与 back_inserter 相同,与 front_inserter 相反)。
    2. 反向迭代器调用base成员函数可以转换为普通迭代器,指向原迭代器的相邻位置。
    3. 对于 list 和 forward_list ,应该优先使用成员函数版本的算法而不是通用算法。


    第11章 关联容器

    1. 对于有序容器——map、multimap、set 以及 multiset,关键字类型必须定义元素比较的方法。默认情况下,标准库类型使用关键字类型的<运算符来比较两个关键字。

      使用自定义的比较函数compare:
      set<TypeName, decltype(compare)*> obj(&compare); //其中&符号可以去掉,因为在使用函数名时,当需要时它会自动转化为指针

    2. pair 通过成员访问运算符来分别访问两个名为 first 和 second 的成员。

    3. 关联容器还定义了以下类型:
      key_type —— 此容器的关键字类型
      mapped_type —— map中与关键字关联的类型,只适用于map
      value_type —— 对于set,为关键字类型;
              对于map,为pair<const key_type, mapped_type>

    4. 当解引用一个关联容器的迭代器时,我们会得到一个类型为容器的 value_type 的值的引用。
      必须记住,一个 map 的 value_type 是一个 pair,我们可以改变 pair 的值,但不能改变关键字成员的值。
      set 的迭代器是 const 的。

    5. 添加元素:
      对于不包含重复关键字的容器,添加单一元素的 insert 和 emplace 版本返回一个 pair:pair 的 first 成员是一个迭代器,指向容器中具有给定关键字的元素;pair 的 second 成员是一个 bool 值,指示插入操作是否成功。
      对于允许重复关键字的容器,添加单一元素的 insert 操作返回一个指向新元素的迭代器,无须返回 bool 值。

    6. 删除元素:
      关联容器定义了一个额外的接受 key_type 参数的 erase 版本,删除所有匹配给定关键字的元素,返回实际删除元素的数量。

    7. map 和 unordered_map 容器提供了下标运算符和一个对应的 at 函数。与其他下标运算符不同,如果关键字不在 map 中,会为它创建一个元素添加到 map 中,关联值进行值初始化(at 函数则会抛出 out_of_range 异常)。

    8. 访问元素:
      c.find(k):返回指向第一个关键字为 k 的元素的迭代器或尾后迭代器。
      c.count(k):返回关键字等于 k 的元素的数量。
      c.equal_range(k):返回一个迭代器 pair,表示关键字等于 k 的元素的范围。若 k 不存在,pair 的两个成员均等于 c.end()。
      c.lower_bound(k)、c.upper_bound(k):仅适用于有序容器。若存在关键字为 k 的元素,两个函数分别返回两个迭代器可组成一个迭代器范围,表示所有关键字为 k 的元素的范围;若不存在关键字为 k 的元素,则两个函数会返回关键字的第一个安全插入点——不影响容器中元素顺序的插入位置。

    9. 无序容器使用一个哈希函数和关键字类型的==运算符来组织元素。

      如果关键字类型固有就是无序的,或者性能测试发现问题可以用哈希技术解决,就可以使用无序容器。

      关于无序容器的更多内容,请见原书章节 11.4


    第12章 动态内存

    1. shared_ptr 的初始化
    //#include <memory>
    //可以使用make_shared或new返回的指针来初始化智能指针
    //指针指向一个值为"9999999999"的string
    shared_ptr<string> p = make_shared<string>(10, '9');
    shared_ptr<string> p2(new string(10, '9'));
    /*接受指针参数的智能指针构造函数是explicit的,因此如下写法是错误的,必须使用直接初始化!!
     *shared_ptr<string> p3 = new string(10, '9');*/
    
    1. 如果你将 shared_ptr 存放于一个容器中,以后不再需要全部元素,而只使用其中一部分,要记得用 erase 删除不再需要的那些元素。

    2. 使用动态内存的一个常见原因是允许多个对象共享相同的状态。

    3. new 和 delete
    //new操作
    int *p1 = new int;        //默认初始化,*p1的值未定义
    int *p2 = new int();      //值初始化,*p2为0
    auto p3 = new auto(obj);  //p3指向与obj类型相同的对象,该对象用obj进行初始化
    //内存耗尽,new失败的情况
    int *p1 = new int;            //如果分配失败,new抛出std::bad_alloc异常
    int *p2 = new (nothrow) int;  //如果分配失败,new返回空指针
    //delete操作
    delete p;     //p必须指向一个动态分配的对象或是空指针
    p = nullptr;  //指出p不再绑定任何对象
    
    1. 为了正确使用智能指针,我们必须坚持一些基本规范:
      ● 不使用相同的内置指针值初始化(或 reset )多个智能指针。
      ● 不 delete get() 返回的指针。
      ● 不使用 get() 初始化或 reset 另一个智能指针。
      ● 如果你使用 get() 返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了。
      ● 如果你使用智能指针管理的资源不是 new 分配的内存,记住传递给它一个删除器。(见12.1.4末尾 “使用我们自己的释放操作 ”和12.1.5末尾 “向 unique_ptr 传递删除器 ”)

    2. 一个 unique_ptr “拥有”它所指向的对象,在某个时刻只能有一个 unique_ptr 指向一个给定对象。因此,unique_ptr 不支持普通的拷贝或赋值操作。
      但可以调用 release 和 reset 将指针所有权从一个非 const unique_ptr 转移到另一个:
    unique_ptr<string> p1(new string("this is p1")), p2(new string("this is p2"));
    unique_ptr<string> p3(p1.release());  //release将p1置为空,p3指向"this is p1"的string
    p3.reset(p2.release());               //reset释放p3原来指向的内存,p3指向"this is p2"的string
    

      另外,不能拷贝 unique_ptr 的规则有一个例外:可以拷贝或赋值一个将要被销毁的 unique_ptr。因此可以从函数返回一个 unique_ptr

    1. 动态数组

    要记住我们所说的动态数组并不是数组类型,这是很重要的。

    //动态数组的new和delete
    int *pi1 = new int[10];               //10个未初始化的int
    int *pi2 = new int[10]();             //10个值初始化为0的int
    int *pi3 = new int[10]{ 1,2,3,4,5 };  //前5个用给定的初始化器初始化,剩余的进行值初始化
    
    delete[] p;  //p必须指向一个动态分配的数组或为空
    
    //标准库提供了可以管理new分配的数组的unique_ptr版本
    unique_ptr<int[]> up(new int[10]);  //此指针可以使用下标运算符
    up.release();   //自动调用delete[]销毁
    
    1. allocator 类
    //#include <memory>
    int n = 5;
    allocator<string> alloc;        //定义allocator对象,它可以为尖括号<>中所写类型的对象分配内存
    auto p = alloc.allocate(n);     //分配n个原始的未构造的内存,用来保存n个string
    alloc.construct(p, "hello");    //在p所指的地址构造一个string对象
    alloc.destroy(p);               //对p指向的对象执行析构函数
    alloc.deallocate(p, n);         //释放p中地址开始的内存,其中n必须与调用allocate时填写的参数具有一样的值
    
    /*allocator算法*/
    auto q = alloc.allocate(n);
    vector<string> vec{ "one","two","three" };
    //将vec中的元素拷贝到q开始的地址,返回拷贝完成后的下一个地址
    auto r = uninitialized_copy(vec.cbegin(), vec.cend(), q);
    //从r开始的地址构造n-vec.size()个对象
    uninitialized_fill_n(r, n - vec.size(), "rest");
    

    【注】整理的是个人认为的书中值得注意的“Note”、“Best practices”等条目及部分知识点、代码,仅供参考。

    相关文章

      网友评论

          本文标题:《C++ Primer》Note和建议及知识点等总结(第II部分

          本文链接:https://www.haomeiwen.com/subject/lfosxctx.html