C++ Primer阅读心得(第八章、第九章)

1.三种流类型:

  1. iostream/istream/ostream标准输入输出流;
  2. fstream/ifstream/ofstream文件读写流;
  3. stringstream/istringstream/ostringstream字符串读写流。

为了操作wchar_t类型,标准库又定义了w开头的wiostream、wfstream、wstringstream等类型,可以使用wcin、wcout等进行输入和输出工作。

2. IO对象不允许复制或者赋值,所以只能传递它们的引用或者指针。例如:

void myprint(ostream& output, string content)
{
    output<<content;
}
注意:因为读写IO对象时需要修改对象内部的状态码,所以引用或者指针不能是const的。

3 流的状态码(iostate)包括:(你可以使用rdstate()来读取)

  • badbit:系统错误,不可恢复;必须调用clear()之后才能再次使用
  • failbit::可恢复错误,不需要调用clear()就能继续使用
  • goodbit:流未发生错误,可以继续使用
  • eofbit:到达文件结尾(注意这时failbit会同时被置1)

注意:IO对象遇到错误时将进入错误状态,不能继续使用;可以使用clear()函数进行更新状态,重新进行IO操作。

4. IO对象管理着一个缓冲区,用来缓存数据然后一次性输出来提升IO的效率。强制清空缓冲区(输入/输出)有三种方法:使用endl、ends或者flush操作符;使用unitbuf操作符(nounitbuf);或者将输入流与输出流绑定(输入流读取时输出流的缓冲区被强制清空)三种方法。例:

//使用endl ends flush
cout<<"test"<<endl; //添加换行符 
cout<<"test"<<ends; //添加null
cout<<"test"<<flush; //什么也不添加

//使用unitbuf
cout<<unitbuf<<"test"<<nounitbuf; //unitbuf刷新缓冲区,nounitbuf恢复系统对缓冲区的管理

//绑定输入流和输出流
cin.tie(&cout); //绑定
cin.tie(0); //解除绑定
5. 在c++11中,可以使用string对象创建fstream系列的对象了。(这改进...我该说什么好呢?)

6. 当一个fstream对象被销毁时,它打开的文件会被自动close。

7. stringstream与类型转换:使用stringstream做类型转换的注意了,clear()调用只是清理了流的状态并不是清理了流的缓冲区,str("")(传入一个空串)才能够清空缓冲区。我有个惨痛的经历,有一次需要导入一个几百G的文件,我使用了stringstream做类型转换(string转unsigned long long),没有正确的清理掉stringstream的缓存,导致程序运行到一半就吃光了内存崩溃了。对于stringstream实在头疼的人,可以使用c++11新增的转换函数或者c风格的strtoull()之类的函数代替。

8.标准库的顺序容器包括:vector、list、deque、forward_list和array五种;c++11新增了两种容器,分别是仿照数组的array和仿照单向链表的forward_list。

array<int, 10> arr = {1,2,3,4}; //大小为10的array对象
forward_list<string> fl = {"abc","bcd"}; //单向链表

其中,array的大小不能改变,所以它声明时必须显式的给出大小;同时,array还不支持顺序容器的resize()等调整大小的操作。forward_list是单向链表,它的很多操作都有典型的单向链表特征,与其他顺序容器的使用方法不太一样。

9. vector使用连续存储(数组)实现,高效随机访问,高效尾插入删除,低效头部中间插入删除;list使用链表实现,高效插入删除,低效随机访问;deque使用链表和数组的折中(链接着的多个小数组)实现,高效双端插入删除,较高效随机访问,低效中间插入删除;forward_list使用单向链表实现,高效插入删除,低效访问;array是固定大小的数组,高效访问,不支持插入和删除。

10.迭代器范围:由一对迭代器begin和end所确定的迭代器范围是从begin到end的左闭合区间,即:begin、begin+1、begin+2 ...end-2、end-1。注意,包含begin但不包含end(左闭合的特性)。begin和end必须满足两个条件:

  • 它们必须是指向同一个容器的迭代器
  • end必须大于begin,从begin出发,通过不断加1的方式可以抵达end
11. 容器的拷贝构造函数只能复制同类型的容器,如果要操作不同的容器,可以使用两个迭代器的版本。还可以将数组的上下界传入,通过数组构造容器,或者通过c++11新增的列表初始化来构造。例:
vector<int> ivec1;
list<int> ilist;
int a[10] = {...};
vector<int> ivec2(ivec1); //ok
vector<int> ivec3(ilist); //error
vector<int> ivec4(ilist.begin(), ilist.end()); //ok
vector<int> ivec5(a, a+10); //ok
vector<int> ivec6 = {1,2,3,4,5}; //ok

12. 赋值 assign与swap:赋值和assign会导致左操作数的内容全部被清空,swap交换两个对象的内容左操作数内容没丢失。(其实swap也就相当于交换一下指针,执行相当的快。)另外,在c++11中新增了函数式的swap,行为和成员swap一样,但是更容易让人理解。

13. 顺序容器支持比较操作符(==、!=、<、<=、>和>=):其行为类似string的比较类似,返回第一个不相同的元素的比较结果。所以,使用它们也要求容器内部的对象也支持比较操作符。

14. 在c++11中,顺序容器的insert函数返回新插入的元素的迭代器(下一次插入的位置),方便在循环中连续插入。如果范围insert的输入迭代器范围是空,则返回传入的迭代器(这其实也是下次插入的位置)。

15. c++11新引入了emplace成员函数:emplace(对应insert)、emplace_front(对应push_front)和emplace_back(对应push_back)。它们直接调用构造函数在容器中构造对象,所以调用它们的参数必须与容器元素类型的构造函数一致。这种成员函数的优势在于直接构造,省略了构造临时对象再复制的开销。

class A
{
public:
    A(string s, int a, double b):m_s(s),m_a(a),m_b(b){}
    A(string s):A(s,0,0.0){}
    A(string s, int a):A(s,a,0.0){}
    A():A("",0,0.0){}
private:
    string m_s;
    int m_a;
    double m_b;
}
vector<A> v;
v.emplace_front("abc",1,3.14); //调用构造函数
v.emplace_back("def"); //同上
v.emplace(v.begin(),"ghi",3); //同上
v.emplace_back(); //调用默认构造函数
16. 访问容器中元素的成员函数(front、back、at和下标)返回的是引用,如果容器不是const的,那么可以作为左值放在=号的左边直接修改对应的元素。
vector<int> v = {1,2,3,4};
v.front() = 5; //将v的首元素修改为5
17. 特别的forward_list:forward_list是单向链表,所以与其他的顺序容器有很多的不同。我们知道,单向链表的所有操作都是作用在当前元素的下一个元素之上,所以forward_list中的成员函数是insert_after、emplace_after和erase_after,为了操作首元素,还提供了返回链表头的before_begin()。

18. size与capacity,resize()与reserve():size是vector容器中已使用空间的大小,capacity是 vector容器中总共空间的大小,size随着插入删除而增加减少,只有容器内容大小超过capacity的时候,vector才会增长,capacity才会变。resize()函数调整size,参数比当前小则删除多余元素,参数比当前大则初始化多余的元素,保证size大小内的元素全 部可用(读写);而reserve则是修改capacity的大小,修改总空间(主要还是备用空间),reserve的参数比原来小的时候不起作用,参数比原来大的时候多出来的空间未初始化(这些元素不可用)。

19. 很多容器操作会导致迭代器失效,所以尽量不要存储迭代器,尤其不要存储end返回的迭代器。

20. vector的自增长:当vector的容量不够用时,容器首先会在内存中重新申请一块大小为原来的x倍(比如说:2倍)的空间,然后将原有的内容全部copy过去,再然后释放原有内存空间,最后将新内容加入。在《算法导论》中,使用均摊分析可以得出,在每次都倍增的情况下,算法的时间复杂度是:O(n) = 3*n。

21. string的find系列的函数在找不到目标时返回string::npos,string::npos是一个static的string::size_type(unsinged),它值是-1(也就是unsigned的最大值)。此外,还有find_first_not_of 和 find_last_not_of 这两个排除查找成员函数,某些时候应该很好用。

22. c++11中引入了多个数值数据与string转换的函数:(泪奔~~太贴心了)

  • to_string():可以转换任意的算术类型
  • stoi():string到int
  • stol():string到long
  • stoul():string到unsigned long
  • stoull():string到unsigned long long
  • stof():string到float
  • stod():string到double
  • stold():string到long double
23. 标准库的三种顺序容器适配器stack queue和priority_queue.。默认情况下,stack和queue是基于deque实现的,priority_queue是基于vector实现的,当然,你可以可以更改:
stack<string, vector<string>> svec; //要求使用vector实现的stack
此外,注意容器适配器也增加了emplace方法。

你可能感兴趣的:(C++ Primer阅读心得(第八章、第九章))