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

第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”等条目及部分知识点、代码,仅供参考。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342