注:本文以《C++ Primer(英文版)》(5th Edition)为参考。
总共由四部分组成:
《C++ Primer (5th Edition)》笔记-Part I. The Basics
《C++ Primer (5th Edition)》笔记-Part II. The C++ Library
《C++ Primer (5th Edition)》笔记-Part III. Tools For Class Authors
《C++ Primer (5th Edition)》笔记-Part IV. Advanced Topics
Part II. The C++ Library
Chapter 8. The IO Library
8.1 针对wchar_t,定义了以w开头的IO类,如wistream、wifstream、wistream、wcin等等。
8.2 IO 对象不可赋值,不可拷贝。一般也不定义其const reference。
8.3 Stream的condition state保存在iostate里面,由4各状态(constexpr pattern)组成:badbit:不可恢复的系统级错误;failbit:可恢复的错误,如读数字时却读到字符;eofbit:到达文件末尾,会设置eofbit和failbit;goodbit:前三种bit都没有被设置。相应的函数的有bad(),fail(),eof(),good()。其中,不管badbit还是failbit被设置,fail都会返回true。所以判断Stream状态一般用good()和fail()。Stream做为condition的时候,就是调用的 !fail()。
8.4 stream buffer的刷新:endl,ends(插入NULL),flush;unitbuf(每写出一次就刷新),nounitbuf(正常状态,有系统负责刷新)。
8.5 流之间的绑定(tie),通常input stream应该绑定到output stream,这样在尝试读入前,先写出。默认,cin和cerr是tied to cout的。绑定是通过成员函数 tie(),参数可以为空,这是仅返回当前绑定的stream指针(可能为空指针);参数还可以是Stream指针(可以为nullptr),那么将会绑定到参数Stream上,函数返回绑定前已经绑定的stream的指针。
8.6 在C++11中,文件流打开文件的所用的文件路径可以为string类型,在之前,只允许使用C串类型。
8.7 如果文件打开失败,failbit会被设置;文件open之后,close之前,1)此文件不能再次open,否则会失败;2)此fstream不应该再与其他的file关联,否则失败。
8.8 文件打开模式:in,out,app,ate,trunc,binary,注意各自的使用限制(P319)。每次调用open,模式都是被重新设置。
8.9 stringstream的成员函数str(),参数可以为空,此时返回stream所持有的string;参数也可以为一个string,此时stream持有参数字符串,返回为void。
Chapter 9. Sequential Containers
9.1 由于move语义的引入(在后续章节中会有说明),C++ 11的容器要比老版本的容器快很多。
9.2 顺序容器有一下6种:vector,deque,list,forward_list,array,string。其中forward_list和array是C++11新引入的两种顺序容器。注意在不同的情况下容器的选择(P327)。
9.3 C++11中二维vector中的两个>可以不用加空格,即vector<vector<int>>v是合法的, list::size。
9.4 array不能用iterator范围做构造函数的参数,不用initializer list赋值(但可以用来初始化),可以用相同size的array来赋值(与built-in数组的区别),没有assign、resize等函数。forward_list没有size()成员函数,没有reverse_iterator、rbegin()等逆向iterator。关联容器的iterator没有大小比较的操作符。
9.5 由于move语义的关系,swap两个容器,要比逐个元素的swap快的多;swap两个容器,容器中的元素并没有swapped,只是一些内部数据结构被swaapped,就是说,swap前的元素的iterator、reference、pointer并没有失效,只是换了一个容器而已。swap(c1,c2),一般的容器都是直接swap的,但对于array,是逐个元素的swap。最好养成使用非成员swap函数的习惯。
9.6 list::sizie()在C++11标准中要求时间复杂度为Constant,forward_list没有size()成员函数,没有back,push_back,emplace_back等在尾部操作的函数。vector、string没有push_front、emplace_front等在首部操作的函数。在C++11标准下,添加了emplace相关的函数;insert成员函数也添加更多的重载函数,返回类型也从void变为iterator。 forward_list有特殊的begin、intsert、emplace,erase函数: before_begin,insert_after,emplace_after,erase_after。
9.7 容器的下标是unsigned类型,若为负值,则自动转为unsigned类型。容器的下标操作符是不检查下标的合法性的,另外,若容器为空,调用front()、back()将导致undefined的行为。容器的at成员函数是有检查下标的合法性的,若越界,将会抛出out_of_range异常。
9.8 插入和删除操作之后对于已有的iterator、pointer、reference的影响:对list和forward_list没有影响,对deque和vector、string有一定的影响(P353)。
9.9 C++ 11为vector、deque、string引入了shrink_to_fit函数,shrink_to_fit只是请求,不是命令。
9.10 C++标准并未vector的allocation策略,但是,it must not allocate new memory until it is forced to do so。
9.11 string的replace成员函数与std::replace的功能不一样。
9.12 C++11标准,引入了string与数字相互转换的函数:std::to_string(val); std::stoi、stol、stoul、stoll、stoull(s,p,b);stof、stod、stold(s, p)。
9.13 容器适配器:stack、queue、priority_queue。默认,前两者使用deque容器,后者使用vector容器。容器适配器除了用自身初始化外,还可以用container_type类型的容器来初始化。
9.14 适配器都有的函数:push, emplace, pop。特殊的函数:stack:top; queue:front、back;priority_queue:top。
Chapter 10 Generic Algorithm
10.1 C++的library提供了100+中算法。
10.2 T accumulate (InputIterator first, InputIterator last, T init);
bool equal ( InputIterator1 first1, InputIterator1 last1, InputIterator2 first2 );
void fill (ForwardIterator first, ForwardIterator last, const T& val);
OutputIterator fill_n (OutputIterator first, Size n, const T& val);注意越界问题。
10.3 back_inserter,赋值时,可以不解引用。
vector<int> v;
auto it = back_inserter(v);
*it = 42;
it = 43;
10.4 OutputIterator copy (InputIterator first, InputIterator last, OutputIterator result);
void replace (ForwardIterator first, ForwardIterator last, const T& old_value, const T& new_value);
OutputIterator replace_copy (InputIterator first, InputIterator last, OutputIterator result, const T& old_value, const T& new_value);
10.5 去重复:ForwardIterator unique (ForwardIterator first, ForwardIterator last);会把相邻的重复元素移到末尾,返回值即为末尾区域的第一个iterator。由于仅去掉相邻元素的重复,所以一般使用前先排序。去重复之后,再根据返回值调用容器的删除函数。
10.6 predicate。算法库使用的predicate只有两种:unary predicates和binary predicates。sort,stable_sort。
10.7 一般算法接受的callable object(即能够对其使用调用操作符的对象),C++里的callable object包括:函数、函数指针、重载了调用操作符的类和Lambda表达式。Lambda表达式定义如下:
[capture list](parameter list) -> return type{ function body }
其中参数列表和返回类型两部分均可省略。capture list只保存local nonstatic variables,因为,lambdas能够直接访问local statics和在函数外定义的variables。
10.8 当我们定义一个lambda时,编译器就为我们生成了一个相应的new (unnamed) class type。与形参不一样的地方的时,在lambda被创建的时候,capture list中的值就被拷贝了,而不是在调用的时候;若capture by reference,就不存在这种问题。
int val = 34;
auto f = [val]{return val;};
auto f2 = [val]{return val;};
decltype(f) f3 = f; //没有默认构造函数,必须赋值
auto rf = [&val]{return val;};
cout << (typeid(f) == typeid(f2))<< endl;//false
cout << (typeid(f) == typeid(f3))<< endl;//true
cout << f() << " " << rf() << endl; //34, 34
val = 0;
cout << f() << " " << rf() << endl; //34, 0
10.9 implicit capture。当capture list为[=]或者[&]时,编译器会以capture by value(或者capture by reference)的方式,自动推导出capture list。
10.10 当mix implicit and explicit captures时,capture list的第一项必须是一个&或者=,而explicitly captured variables必须使用另一种capture方式。
int val1 = 34, val2 = 0;
auto f = [&, val2]{return val1 + val2;}; //ok
auto f2 = [&, &val2]{return val1 + val2;}; //error!
10.11当capture by value,拷贝得到的变量是不可修改的,若想修改,需要在parameter list后加上关键字“mutable”,注意,此时parameter list将不可省略。当capture by reference时,变量能否修改,取决于其所引用到的原始类型是const还是nonconst。
10.12 省略返回类型时,若函数体没有return语句,就默认返回为void;若函数体内有一条返回语句,编译器自行推断返回类型(即使:return a?b:c, b跟c的类型不同,也可以);若函数体内不止一条return语句,那么编译器无法推导,必须显示写明返回类型。
10.13 在以前的C++函数bind主要用4个函数:bind1st, bind2end, not1, not2。在C++11中,统一都使用一个bind函数:bind()。
auto newCallable = bind(callable, arg_list)
其中arg_list能够包括placeholders:std::placeholders::_1、_2、 _3、 ... 。若callable的参数中有引用类型时,要使用ref或者cref函数,将相应参数封装成引用对象后,再传给bind函数。
10.14 除了容器常规的iterator之外,还有几种特殊的iterator:Insert iterators, Stream Iterators, Reverse iterators, Move iterators。
10.15 主要有三种Inserter :back_inserter, front_inserter, inserter;当*it = t 时,分别调用push_back(t), push_front(t), insert(t, p)成员函数,并implicitly ++it,所以vector、string、forward_list不能定义front_inserter,forward_list不能定义inserter;另外显式执行*it,++it, it++,do nothing to it,总是返回it。
10.16 istream_iterator使用>>从Stream中读取。默认初始化的istream_iterator可以作为off-the-end值。*in返回从stream读到的值,++in,in++则从stream中用>>读取下一个值。
istream_iterator<int> in(cin), eof;
while(in != eof)
vec.push_back(*in++);
10.17 istream_iterator使用Lazy Evaluation。即不保证理解从流中读取,通常delay reading the stream until we use the iterator。
10.18 ostream_iterator构造函数可以指定一个C串指针,每次输出之后,都紧接着输出该C串。out = val;使用<<向流中写入val。*out,++out,out++都do nothing to out,返回out。
10.19 reverse_iterator。很巧妙的排序sort(vec.rbegin(), vec.rend())。reverse_iterator的base()成员函数返回该iterator右边的正常iterator。
10.20 iterator categories:
Input iterator: read,but not write; single pass; increment only.
Output iterator: write, but not read; single pass; increment only.
Forward iterator: read and write; multi-pass; increment only.
Bidirectional iterator: read and write; multi-pass; increment and decrement,如reverse算法,不能用在forward_list上。
Random-access iterator: read and write; multi-pass; full iterator arithmetic。如sort算法,不能用在list和forward_list上。
10.21 list和forward_list特有的成员函数:merge,remove,remove_if,reverse,sort,unique。list.splice(args), forward_list.splice_after(args)。
Chapter 11 Associative Containers
11.1 Associative Container类型主要有map, multimap, unordered_map, unordered_multimap, set, multiset, unordered_set, unordered_multiset。
11.2 C++11下,Associative container可以用list initialize the elements。对于ordered container,默认使用对key type使用<操作符进行比较;当然我们也可以自己定义自己的比较操作符,但是必须满足“Strict weak ordering”,注意,严格弱排序跟升序、降序没有直接关系。只是表述了一种不对称、具有传递性的关系。以set的构造函数为例:
template < class T, // set::key_type/value_type
class Compare = less<T>, // set::key_compare/value_compare
class Alloc = allocator<T> > // set::allocator_type
>set (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());
这里的Compare可以是函数指针、lambda expression、function object。但是不能为函数类型。
A ai;
set<int, A> s1(ai); //Function object
set<int, A> s2(A()); //error: 这种写法不正确,不知道为什么。
set<int, decltype(cmp)*> s3(cmp); //Function pointer
set<int, decltype(f)> s4(f); //Lambda expression
11.3 map的value_type是pair类型,pair的默认构造函数会做value initialization,pair有按字典序的比较操作符,能够使用list initialization。
11.4 关联容器的几个别名,key_type,mapped_type(只有map大类的容器才有),value_type(Set类的value_type与key_type一致,Map类的value_type类型为pair<const key_type, mapped_type>)。
11.5 容器的keys是const的,不可更改(可以删除、添加),所以set::iterator和set::const_iterator都是只读的。由于关联容器的特性,一般不是使用通用算法对关联容器进行处理;实践中,关联容器,往往为通用算法的原序列或目标序列。
11.6 添加元素使用insert、emplace函数。当插入一个元素insert(v),emplace(args),通常返回一个pair<iterator, bool>,表示key所在元素的iterator和是否插入成功,但对于multimap和multiset返回的只是一个指向新插入元素的iterator。当插入多个元素insert(b,e),insert(initilal_list),返回为void。另外,插入一个元素时,可以额外提供一个hint iterator:insert(p,v),emplace(p,args);如果给的p比较合适的话,效率较高。
11.7删除元素使用erase函数。erase(key),返回删掉得元素个数;erase(p),返回p的下一个iterator;erase(b, e),返回e。
11.8 map(unordered_map)的下标操作符很方便,但是multimap和unordered_multimap没有下标操作符。与c[key]不同,c.at(key)会检查c中是否有key,若没有则会抛出out_of_range异常。
11.9 对于multi-关联容器,常用的函数有c.fine(k), c.count(k),
c.lower_bound(k); //Return an iterator to the first element with key not less than k;
c.upper_bound(k);//Return an iterator to the firrst element with key greater than k;
c.equal_range(k);//Return a pair of iterators。
11.10 unordered关联容器不再使用比较操作符来组织元素,而是使用hash function和key_type的==操作符。
11.11 unordered container are organized as a collection of buckets。容器具有相同hash值的elements都放在同一个bucket中,其实就是连地址法(解决冲突)。因此,容器的性能主要取决于hash函数的质量和bucket的number和size。bucket管理的有关函数如下:
11.12 unordered container对key_type的要求:要有hash函数,有==操作符。library已经对built-in类型、指针、string、智能指针、vector<bool>等,定义了hash模板函数的特化版本,所以对这些类型不需要再指定hash函数。hash函数的返回类型为size_t。
size_t hasher(const Sales_data &sd) {
return hash<string>()(sd.isbn());
}
bool eqOp(const Sales_data &lhs, const Sales_data &rhs){
return lhs.isbn() == rhs.isbn();
}
using SD_multiset = unordered_multiset<Sales_data, decltype(hasher)*, decltype(eqOp)*>;
// arguments are the bucket size and pointers to the hash function and equality operator
SD_multiset bookstore(42, hasher, eqOp);
Chapter 12. Dynamic Memory
12.1 C++11引入了智能指针:shared_ptr, unique_ptr。另外还定义了shared_ptr所管理对象的弱引用:weak_ptr。均定义在memory头文件下。
12.2 最安全的allocae和use动态内存的方法是使用库函数make_shared。
12.3 注意:使用new动态申请内存时,加空括号和不加括号的区别。不加括号则是default intialized,built-in or compound type have undefined value, class对象则被默认构造函数初始化;加上括号,则是value initialized(P459)。auto p = new auto(obj)是没问题的,根据obj推导类型;但auto p = new auto{a, b, c}就无法推出类型。另外动态申请的const 对象必须被初始化:const int *pci = new const int(1024); const string *pcs = new const string;
12.4 当new无法申请到空间时,默认抛出bad_alloc异常,我们使用placement new的形式阻止其抛出异常,而是返回空指针。
int *p1 = new int; // if allocation fails, new throws std::bad_alloc
int *p2 = new (nothrow) int; // if allocation fails, new returns a null pointer
12.5 传给delete的指针必须是使用new申请得到的或者是null pointer。delete非new出来的内存,或者同一指针删除多次(即删除dangling pointer),是undefined。
12.6 shard_ptr可以用pointer、shared_ptr、unique_ptr(必须为右值引用)来初始化,可以使用pointer通过reset成员函数,重置shared_ptr所管理的动态空间(可能会需要删除以前的空间)。不论初始化、还是重置,都可以指定一个callable object来代替默认的delete来free 动态空间。
void end_connection(connection *p) { disconnect(*p); }
void f(destination &d /* other parameters */){
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection);
// use the connection
// when f exits, even if by an exception, the connection will be properly closed
}
12.7 不要使用shared_ptr的get函数得到的指针去初始化或者赋值给另一个shared_ptr。
12.8 unique_ptr不能用另一个unique_ptr变量来赋值,但是可以用另一个unique_ptr的右值引用(在函数返回处),或者用nullptr来赋值。u=nullptr, u.reset(), u.reset(nullptr);都会删除u所管理的空间,并makes u null。u.release()不会删除,而是返回空间指针,u.reset(q),删除u管理的空间,并接管指针q所指空间。所以,通常用release和reset来传递指针。
12.9 unique_ptr与shared_ptr另外一个不同地方是:unique_ptr的deleter是类型的一部分,变量一经定义,deleter将无法更改,而shared_ptr的deleter仅仅是一个参数,随时可以修改。这也说明,在内部上,shared_ptr是通过多态特性来调用deleter的。在Part III中的16.16小项中有具体分析。
12.10 weak_ptr 并不控制他所指对象的生命周期,而是有shared_ptr负责管理的。所以weak_ptr不能用指针来初始化或赋值,只能有weak_ptr或者shared_ptr来赋值,即weak_ptr通常是和shared_ptr捆绑在一起的。
12.11 由于weak_ptr所指对象可能一经不存在,所以我们不能通过weak_ptr来访问对象。
12.12 weak_ptr的常用函数:w.reset(),makes w null;w.use_count()返回和其共享的shared_ptr的个数;w.expired(),return w.use_count()==0;w.lock(),如果expired是true,返回一个null shared_ptr,否则,返回w所指对象的shared_ptr。
12.13 数组类型也可以定义别名,使用别名进行动态申请内存时,虽然new后面没有看到[],但是编译器任然通过new[]来申请内存。
typedef int arrT[42]; // arrT names the type array of 42 ints
int *p = new arrT; // allocates an array of 42 ints; p points to the first
12.14 动态申请数组空间,仍然可以再后面加括号,来进行value initialization;C++11标准中,也可以使用list initialization初始化。
int *p = new int[10]();
12.15 动态开辟空数组是合法的,并可以保证,其所返回的地址不会与其他new出来的地址一样,但是不能对此地址解引用。
12.16 释放应该与申请保持一致,即,用new得到的空间要用delete来释放,用new []得到的空间,应该用delete[]来释放,否则将会是undefined。
12.17 unique_ptr有针对数组的版本。数组版本特有下标操作符u[i],就相当于get()[i]。shared_ptr没有提供最数组的直接支持,但是我们可以重载他的deleter,来管理数组,只是没有下标操作符罢了。
unique_ptr<int[]> up(new int[10]);
up.reset(); // automatically uses delete[] to destroy its pointer
//这里,在Primer中用的是up.release(),个人认为作者是真是意图是用reset()的。
// to use a shared_ptr we must supply a deleter
shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
sp.reset(); // uses the lambda we supplied that uses delete[] to free the array
12.18 使用allocator类能去掉construction和allocation之间的耦合,在某些场合下,可以提高效率。allocator的管理函数如下,其中construct在C++11标准中有做变动。
12.19 destory(p)只能用来析构已经constructed的元素。deallocate(p,n),这里p不能是nullptr,另外这里n也要和allocate时的n保持一致。
12.20 allocator的算法:uninitialized_copy(b, e, b2), uninitialized_copy_n(b, n, b2), uninitialized_fill(b, e, t),uninitialized_fill_n(b, n, t)。其中uninitialized_copy_n是C++11新增加的函数。除了uninitialized_fill返回void外,其余三个函数都返回iterator。
注意,在上一版的C++ primer,即第四版中,将uninitialized_fill_n形式写为:uninitialized_fill_n(b, e, t, n)应该是不正确的。
注:本文以《C++ Primer(英文版)》(5th Edition)为参考。
总共由四部分组成:
《C++ Primer (5th Edition)》笔记-Part I. The Basics
《C++ Primer (5th Edition)》笔记-Part II. The C++ Library
《C++ Primer (5th Edition)》笔记-Part III. Tools For Class Authors
《C++ Primer (5th Edition)》笔记-Part IV. Advanced Topics