《C++ Primer (5th Edition)》笔记-Part IV. Advanced Topics

来源:互联网 发布:欧洲工商管理学院 知乎 编辑:程序博客网 时间:2024/06/04 20:00

注:本文以《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 IV. Advanced Topics

Chapter 17. Specialized Library Facilities

17.1 C++11引入了tuple类型。tuple与pair很像,但是tuple的成员个数不定,tuple的具体类型由元素个数和元素类型决定。tuple的默认构造函数value initialize其成员,tuple的构造是explicit,不能隐式转换。和pair一样,tuple也可以通过make_tuple来得到。

tuple<size_t, size_t, size_t> threeD = {1,2,3}; // error tuple<size_t, size_t, size_t> threeD{1,2,3}; // ok
17.2 tuple的成员函数只有构造函数和赋值操作符,tuple还支持关系操作符,tuple在做赋值或关系比较的时候,要求元素个数要一致(不要求元素类型一致),否则编译出错。另外,libary提供了模板get<i>(t)、tuple_size<tupleType>::value、tuple_element<i, tupleType>::type等来分别获取第i个元素、获得元素个数,获得第i个元素的类型。

17.3 template <size_t N> class bitset。小下标对应于低位,大下标对应于高位。构造函数接受unsigned long long类型或者字符串(string + iterator或者C串指针)。bitset支持IO操作符,具有转换为整型(若溢出,抛出overflow_error异常)或者字符串的成员函数:to_ulong()、to_ullong()、to_string(charZero, charOne),某位查询:test(pos), b[pos],整体查询:any(),all(),none(),count(),size(),其中all()是C++11新引入的新函数,count返回open的位的个数,size()返回bitset的总的位数,此函数为consexpr函数。某位设置或整体设置函数:set(pos, boolean), set(), reset(pos), reset(), flip(pos), flip()。

17.4 C++11加入了regular expression支持。主要由涉及的类有regex、smatch、ssub_match、sregex_iterator,主要涉及的函数:regex_match、regex_search、regex_replace。其中regex_match是匹配整个串,regex_search则是与串的子串进行匹配,但两者的参数列表都有两种形式:(str, match, reg, match_flag_type)、(str, reg, match_flag_type)。

// find the characters ei that follow a character other than cstring pattern("[^c]ei");// we want the whole word in which our pattern appearspattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*"; regex r(pattern); // construct a regex to find patternsmatch results; // define an object to hold the results of a search // define a string that has text that does and doesn't match patternstring test_str = "receipt freind theif receive";// use r to find a match to pattern in test_strif (regex_search(test_str, results, r)) // if there is a matchcout << results.str() << endl; // print the matching word
17.5 regex内部有一个flag信息,在regex构造的时候指定,若不指定,就使用flag:ECMAScript,可以指定的flag值如下表所示。正则表达式对自己的特殊字符使用反斜杠转义,而C++还需要用反斜杠转义反斜杠,所以在字符串中通常是两个反斜杠一起出现。正则表达式就相当于一门简单的语言,在run time时被编译,而且很有可能编译出错(正则表达式写的有误),这时就会抛出regex_error错误,regex_error不仅有what成员函数,还有code成员函数,来返回error code,具体code的意义可参考P.732。
try {// error: missing close bracket after alnum; the constructor will throw regex r("[[:alnum:]+\\.(cpp|cxx|cc)$", regex::icase);} catch (regex_error e){ cout << e.what() << "\ncode: " << e.code() << endl; }

17.6 正则表达可以接受C串或者string格式的char或者wchar_t字符串。但对不同类型的字符串应该使用相对应类型的类(操作都是一样的)。

17.7 正则表达式的匹配结果通常存放在smatch中,而smatch只能通过regex_search、regex_match、sregex_iterator来填充。smatch不仅保存匹配的结果,还保存匹配中group匹配的结果(作为ssub_match对象)。smatch.size()成员函数比较特殊,若匹配失败,返回0,否则返回1+subexpression的个数。smatch有一些以下标为参数的成员函数,若下标index为0(默认值),查询的是整体的匹配结果,否则,查询的是第index-1个subexpression的匹配结果。另外smatch的成员prefix()、suffix()返回分别代表匹配段之前、之后的字符串的ssub_match,所以,这两个函数对regex_match的结果没什么用处。sregex_iterator解引用后得到的是smatch,当sregex_iterator构造时,会调用regex_search得到匹配到的第一个smatch,当sregex_iterator自增时,也会调用regex_search得到下一个匹配结果。

string pattern("or");regex r(pattern, regex::icase);string str = "hey read or write according to the type";for (sregex_iterator it(str.begin(), str.end(), r),end_it; it != end_it; ++it) {auto pos = it->prefix().length(); // size of the prefixpos = pos>10 ? pos-10 : 0; //we want up to 40 characterscout << it->prefix().str().substr(pos) // last part of the prefix<< "-" << it->str() << "-" // matched word<< it->suffix().str().substr(0, 10) // first part of the suffix<< endl;}
17.8 正则表达式通常用一对括号来划分subexpression。通常用subexpression在做Data Validaion(根据某个subexpression是否又被匹配到,通常后面跟”?“),ssub_match也确实提供了matched公共数据成员。

17.9 在进行regex_replace时,通常需要和subexpression配合使用,在目标串格式中用”$"+subexpression编号(从1开始)的方式,来代表某个subexpression。不管是调用regex_search、regex_match还是regex_replace,都可以设置match_flag_type,每个match_flag_type的具体意义可以参考P.744。

17.10 在C++11之前,只能使用rand()来随机得到一个在0到system-dependent maximum value(RAND_MAX至少是32767)之间平均分布的整数。C++11中,提供了新的随机数支持,主要涉及两大类:random-number engines 和 random-number distribution。C++编程已经不提倡使用rand()函数了,可以用default_random_engine函数对象来代替。

17.11 random-number engines 都是(不是模板)函数对象,他们的调用操作符不接受参数(在构造函数中可接受种子,也可以通过seed(val)成员函数来设置种子),返回随机的无符号数。library定义了一些random-number engines ,他们在performance和“quality”上有差别。

17.12 random-number engine e返回的随机数的范围是[e.min(), e.max],当得到特定区间的随机数时,通常与random-number distribution类配合使用,library定义了20种distribution,其类型基本上都是模板函数对象,所以在使用时要显式给出类模板参数列表,但只有一中不是类模板:bernoulli_distribution,它是普通的函数对象。下面的代码中,由于没有为e设置种子,所以如果没有声明为静态变量的话,每次得到的结果将是一样的。通常的做法是用时间做种子,如default_random_engine e1(time(0));这里由于time()返回的时间的单位是秒,所以仅适用于秒级的应用。

// returns a vector of 100 uniformly distributed random numbers vector<unsigned> good_randVec(){// because engines and distributions retain state, they usually should be // defined as static so that new numbers are generated on each callstatic default_random_engine e;static uniform_int_distribution<unsigned> u(0,9); vector<unsigned> ret;for (size_t i = 0; i < 100; ++i)ret.push_back(u(e)); return ret;}
17.13 由于rand()返回的整数,所以要得到浮点数时,精度就不够搞。C++11定义了针对浮点数的distribution,如:uniform_real_distribution。

17.14 IO stream除了保存condition state外(见Part II 8.3),还保存format state,format state可以通过manipulator来进行修改(或读取),manipulator通常是函数或者object,他们能被流操作符接受,并作为其操作数,并对流状态产生影响。另外,流的一些成员函数如precision(),setf()等,也能影响流的状态。

17.15 控制bool输出:boolalpha, noboolalpha。设置数字的基(设置在basefield上):oct, dec, hex,注意,这里的基仅对整型起作用,对浮点型不起作用。控制显示数字的基:showbase, noshowbase,注意,也仅对整型起作用。控制stream自己生成的字母的大小写:uppercase, nouppercase。

17.16 设置浮点数精度:setprecision(int),默认是6;另外,流的成员函数precision()可以获得精度,precision(int)可以获得(old)并设置(new)精度。控制浮点型为整数时是否显示小数点:showpoint, onshowpoint。 控制浮点数的显示方式:fixed, scientific, hexfloat, defaultfloat;后两个为C++11新引入的manipulator;显示格式的设置,也影响setprecision所控制的位置(如,defaultfloat下setprecision控制总的精度,而fixed下setprecision控制小数点后的精度)。

17.17 填充设置。宽度设置:setw(int),填充字符设置:setfill(char_type),设置输出内容在域内的位置:left, right(默认), internal;internal实际上是left-justify符号,right-justify值。控制输入格式,是否跳过whitespace(blank, tab, newline, formfeed, carriage return),noskipws, skipews(默认)。

17.18 unformatted 输入输出操作时,前面的格式设置就不起作用了,例如总是不跳过whitspace。针对字符的主要方法有,正常读写:is.get(ch),os.put(ch);字符放回:is.unget(), is.putback(ch),后者要求更严,上次读读的字符必须是ch才行;读下个:is.get(), is.peek(),后者并没有从流中取走,都返回int,所以必大于零,正常的话肯定不会与EOF(const 负值)相等。

17.19 读字符串。is.get(sink, size, delim), is.getline(sink, size, delim),都最多读size个byte,并且遇到delim停止,唯一区别是,前者丢弃delim。正常读取:is.read(sink, size), os.write(source,size)。其他:is.gcount(),上次读的size, is.ignore(size, delim)。

17.20 虽然所有流都有支持random access的函数,但是,Random IO is system-dependent,并不是所有流都能进行随机存取(通常绑定到cin、cout、cerro、clog的流不能进行随机存取),通常能进行随机存取的只有文件流(fstream)和字符串流(sstream)。

17.21 Random access的成员函数主要有:tellg(), tellp(),分别对输入流、输出流得到当前位置。seekg(pos), seekp(pos),分别对输入流、输出流设置pos处为新的读写位置,pos是绝对位置。seekg(pos, from), seekp(pos, from)分别对输入流、输出流设置pos+from为新的读写位置,pos是相对位置,from为beg、cur、end中的一种。

Chapter 18. Tools for Large Programs

18.1 抛出异常后的处理过程叫做statck unwinding(栈展开)。展开期间,会引起一些对象的自动销毁;另外,析构函数是不应该抛出异常的。

18.2 exception object对象是有编译器管理的,exception declaration就与函数参数列表很相似,可以不写变量名,可以是非引用类型或引用类型(只有引用时,对exception的修改才会被保留),但是不能是右值引用。寻找matching handler时,不是找匹配最好的,而是找最先能够匹配的。匹配时,对conversion的要求很严格,只允许三种转换:1)nonconst to const,2)derived to base, 3)array / function to pointer;其他转换(如,算术转换、class-type转换)都是不允许的。

18.3 在catch的block内,可以rethrow已经捕获到的exception:throw;。catch(...)声明可以捕获所有异常。

try {/**/}catch (...){ //catch all/**/throw; // rethrow}
18.4 针对构造函数的初始化列表中的异常,可以使用function try block结构来处理,另外,他还能处理构造函数函数体类的异常,但不处理构造函数的参数初始化时的异常。
template <typename T> Blob<T>::Blob(std::initializer_list<T> il) try :data(std::make_shared<std::vector<T>>(il)) {/* empty body */} catch(const std::bad_alloc &e) { handle_out_of_memory(e); }
18.5 在C++11标准下,可以对函数加noexcept specification,来做nothrowing specification。像const、reference qualifier一样,要在函数的声明与定义出保持一致,至于这三者的顺序,可参考Part III 15.12。编译器是不会(也不能)在编译期verify exception specification的,所以noexcept函数是有可能抛出异常的,当这种情况发生时,不做栈展开,而是(在第四版中说,先调用unexpected库函数,其默认)调用terminate函数,来终止程序。在老版C++中有更细致的exception specification(通过 throw (exception types); ),所以,noexcept 等价于 throw();但是老版的用法没有被广泛运用,在新标准中已经被deprecated了。

18.6 noexcept specification可以加上个参数的,如果参数为true,就和不加参数时一样,表示不能抛出异常,否则,就可以抛出异常。另外,noexcept除了作为noexcept specification,还可以作为noexcept operator,做为操作符时,接收一个expression作为操作数,若expression可能抛出异常,就返回false,否则返回true。想sizeof操作符一样,noexcept并不evaluate它的操作数。通常noexcept specification与noexcept specification配合使用。下面代码中前一个noexcept是specification,后一个是operator。

void f() noexcept(noexcept(g())); // f has same exception specifier as g
18.7 noexcept specification不是函数的type的一部分,所以不能noexcept来区分重载函数,但它却影响着函数的使用。用函数赋值函数指针时,函数的exception specification不能要比函数指针的exception specification若;若函数指针声明不抛出异常,那么函数也不能抛出异常,若函数指针可以抛出任何异常,那么对函数就没有要求了(可以不抛出异常)。虚函数在子类的覆盖中,exception specification只能更强,不能变弱。编译器在合成copy-control的时候,如果该函数所invoke的其他函数都不抛出异常,那么就声明为noexcept,否则声明为noexpcet(flase);另外,如果我们自己定义的destructor是没有声明noexcept,那么编译也会按照copy-control的规则,自动合成一个specification(可能是noexcept,也可能是noexpcet(flase))。

18.8 Namespace is a scope,命名空间可以定义在全局作用域,也可以定义在其他作用域内部,但不能定义在函数或者类的内部,命名空间的定义可以不连续。虽然namespace的成员可以在namespace define的外部,但是,such definitions must appear in an enclosing namespace;即不能定义在无关的namespace中。所以,对模板的(偏)特化必须定义在相同的namespace下面。global namespace用::表示。namespace可以嵌套。

18.9 C++11标准,引入了inline namespace。与普通的嵌套namespace不一样,在inline namespace内的名字在使用时,就像是在enclosing namespace的成员一样,即不需要限定inline namespace的名字。inline必须出现在namespace的第一处定义的地方,其后的定义处,可以加也可以不加,隐含是inline的。inline namespace通常用在一个版本到下一个版本的变更时。

inline namespace FifthEd {/**/}namespace FifthEd { // implicitly inline/**/}
18.10 unnamed namespace通常定义在一个源文件的其global namespace下面,在文件中可以不连续定义,但不能跨文件定义(每个文件有自己专属的unnamed namespace),unnamed namespace中的实体都是file static的,即局部于文件(在其他文件看不到)而且具有static的lifetime。unnamed namespace可以嵌套定义在其他命名空间中,它的特性仍保持不变。unnamed namespace的作用和文件static声明的作用是一样的,但后者已经被deprecated。

18.11 可以定义命名空间别名:namespace newName = outerName::OldLongName;

18.12 using declaration(using 声明)一次只能引入一个名字(如using std::sort),他遵循正常的作用域规则,就好比把名字直接放到了using声明的作用域,会屏蔽外部作用域的名字,并且不能与所在scope已有的名字重复。using directive(using 指示)则是把整个命名空间的名字都引入了(如using namespace std;),但它把引入的名字都提升到一个能够包含命名空间自身和using directive的最近的一个作用内,这样就可能会引起名字的冲突,这种情况编译器是允许的,但不允许直接使用冲突的名字(必须显式指出要使用的是哪个版本)。

namespace N{int a = 0, c = 0;namespace Na{int a = 1, b = 1;}namespace Nb{int c = 2, d = 2;}void f(){int b = 3;using namespace Na;//using指示,Na::中的名字都被提升到N::下面++a;//error: N::Na::a与N::a冲突,用的时候会提示ambiguouscout << ++b << endl;//4, local变量bint d = 3;using Nb::c;//using声明,相当于声明一个局部变量的名字cusing Nb::d;//error: Nb::d与local变量d冲突,编译错误cout << ++c << endl;//3,N::Nb::c,而不是N::c}}
18.13 using声明和using指示都可以出现在global、local和namespace作用域内部;此时,using声明和using指示的作用域都是从出现的位置开始到所在作用域结束位置为止。另外using声明还可以出现在类作用域内(这时,只能声明基类的成员),但using指示就不能出现在类作用域内。

18.14 当执行:std::string s; std::cin>>s;时就相当于执行:operator>>(std::cin, s)。这里operator>>函数定义在std命名空间下面,却可以不用加scope限定符。这里用到了Name Lookup的另一个重要规则:当我们向函数传入的实参是类类型(或者类的指针、类的引用),编译不仅会按正常规则(Part I, 7.15和7.16)进行name lookup,还会额外在该类定义所在的namespace中进行name lookup。

18.15 在单继承的情况下,构造顺序是从最基类开始,依次向下构造。多继承时,就存在了多个分支,每个分支也都是从最基类向下构造,分支间的顺序按照继承顺序。析构时候的顺序仍跟构造顺序相反。

18.16 在C++新标准中,引入了inherited constructor(见Part III,15.21),但在多继承时,使用多个inherited constructor可能导致同时继承多个相同参数的构造函数,引起冲突,解决办法是自己定义该参数的构造函数。

struct Base1 {Base1(const std::string&); Base1(std::shared_ptr<int>);};struct Base2 {Base2(const std::string&); Base2(int);};// error: D1 attempts to inherit D1::D1 (const string&) from both base classes struct D1: public Base1, public Base2 {using Base1::Base1; // inherit constructors from Base1using Base2::Base2; // inherit constructors from Base2 };struct D2: public Base1, public Base2 {using Base1::Base1; // inherit constructors from Base1 using Base2::Base2; // inherit constructors from Base2 // D2 must define its own constructor that takes a stringD2(const string &s): Base1(s), Base2(s) { }D2() = default; // needed once D2 defines its own constructor};
18.17 多继承时,名字查找是在不同分支上同时进行的,若在不止一个基类中找到,那么这个名字的使用将是ambiguous。另见:18.19。

18.18 当出现菱形继承时,要使用virtual inheritance,被shared的基类被称作virtual bass class(虚基类)。虚继承时virtual的位置没有限制。只有virtual base class的直接子类才需要声明为虚继承。

// the order of the keywords public and virtual is not significantclass Raccoon : public virtual ZooAnimal { /* ... */ }; class Bear : virtual public ZooAnimal { /* ... */ };class Panda : public Bear, public Raccoon, public Endangered { /* ... */ };

18.19 虚继承要比普通的多继承在名字查找(18.17)时,产生二义性的可能性更小:查找成员x时,1)如果每个分支上找到的都是虚基类的,那么没有二义性;2)如果一个分支找到的虚基类的成员,另一个分支在虚基类的子类中找到,那么也没有二义性,派生类的优先级高于共享的虚基类;3)如果,有多于两个都不是派生类找到,那么就产生了二义性。

18.20 在虚继承中,虚基类的初始化,应该由most derived constructor负责。构造顺序是:先初始化虚基类,然后按继承顺序来决定分支的构造顺序,每个分支从上向下构造,最后构造most drived class。如果most drived constructor没有显式地初始化虚基类,那么将调用虚基类的默认构造函数,若找不到默认构造函数,编译出错。若非most drived constructor有初始化虚基类的代码,将被忽略掉(C++ Primer第四版是这样说的)。析构的顺序,与构造顺序相反。

Chapter 19. Specialized Tools and Techniques.

19.1 new expression的操作由三步构成:1)调用库函数operator new(或者operator new[]),2)调用构造函数,3)返回指针。

19.2 operator new和operator delete是允许重载的。编译器在寻找operator new的原则:如果申请对象是类类型,编译器就现在类的成员函数中找operator new,若找不到,就在global scope内找相匹配用户自定的operator new,若也找不到,就使用标准库的版本。

19.3 library定义了“8个”operator new和delete的重载函数(如果加上placement版本,就不止8个)。其实不论有没有nothrow_t类型参数(nothrow),operator delete函数都不会抛出异常的,nothrow_t类型参数被忽略掉了。注意:《C++ primer (5th Edtion)》把delete相关的4个函数都写错了(见P.821)。

// these versions might throw an exceptionvoid* operator new (size_t);void* operator new[] (size_t);void operator delete (void*) noexcept;void operator delete[] (void*) noexcept;// versions that promise not to throw; see Part II, 12.4void* operator new (size_t, const nothrow_t&) noexcept;void* operator new[] (size_t, const nothrow_t&) noexcept;void operator delete (void*, const nothrow_t&) noexcept;void operator delete[] (void*, const nothrow_t&) noexcept;
19.4 在定义想应的重载函数时,exception specification也必须保持一致。我们只能将"重载"函数定义在在global scope或者作为类的成员函数,当作为类的成员函数时,这些operator函数隐含(如果不显式声明为static也是static)是static的。

19.5 当operator delete和operator delete[]作为类的成员时,可以(不是必须)添加size_t类型的第二个参数,这时,编译器就会根据第一个参数(void*)所指对象的实际大小来自动设置size_t参数,通常当类是继承层次中的一部分时,需要添加这个size_t参数。operator new和operator new[]函数中的size_t参数是必须的,调用时,编译器会根据(类)对象的大小或者数组的大小,自动设置。在类内重载一个,就会遮挡其他的名字,所以需要重载的话,通常就全部重载。

19.6 当将"重载"函数定义在global scope时,通常需要使用malloc和free来申请和释放空间。同delete operator一样,free也可以接受空指针。

void* malloc (size_t size);void free (void* ptr);
19.7 除了普通new expression,还有placement new expression。placement new expression可以接受参数,并在调用operator new时将参数传入。nothrow的传入就是使用的placement new expression形式。除了上面的8个函数外,library还另外定义了4个接受一个指针作为额外的参数的版本,其中new函数直接返回第二个指针,不开辟空间,delete函数则是什么都不做。
void* operator new (size_t , void* ) noexcept;void* operator new[] (size_t , void* ) noexcept;void operator delete (void* , void* ) noexcept;void operator delete[] (void* , void* ) noexcept;
19.8 当placement new expression接受一个指针参数时,就会调用上面的函数,然后调用构造函数并返回指针,这个功能与allocator的construct函数一样,不同的是allocator的construct函数接受的指针,必须是同一个allocator通过allocate得到的,而placement new就没有这个要求。另外,我们也可以显示调用类的析构函数,这就相当于allocator的destroy函数。

19.9 我们除了可以"重载"上面的8个operator函数外,还可定义我们自己的operator new/delete。对参数的个数,参数类型都没有什么要求,使用new placement expression传入参数,自动调用。但有两点要求:1)第一个参数的类型固定,必须与上面保持一致;2)不能与上面4个operator函数一样,因为library已经定义过了,我们再定义就是重定义,编译通不过。

19.20 看了19.9可能就疑惑了:因为上面4个已经定义了,我们就不能重新定义了,但上面8个也定义了,我们为什么能够“重载”,不!不是重载,是重定义。上面针对new/delete动态分配函数,一直都是用的“重载”这个词(C++ primer也是这样用的),但这是不准确的,因为参数列表没有改变,不是重载,是重定义。但为什么在同一个命名空间下面上面的8个函数可以重定义,后面的4个就不可以呢,这是因为C++规定上面8个函数有replacable特性,当我们重新定义时,就替换掉了系统默认的版本。Soga。。。

19.21 Run-Time Type Indentification(RTTI)主要通过两个操作符实现:typeid和dynamic_cast。dynamic_cast的形式如下所示,要求:type必须是类类型或者type*为void*类型(C++ Primer在这里的讲解是有问题P.825)。在这三个形式中,e分别为指针、左值、右值。当e是空指针时,返回空指针。

dynamic_cast<type*>(e) //返回type*指针,不成功就返回nullptrdynamic_cast<type&>(e) //返回type&,不成功就抛出bad_cast异常dynamic_cast<type&&>(e)//返回type&&,不成功就抛出bad_cast异常
19.22 typeid 可以用在任意的expression或者type name上。typeid返回类型是const type_info&,当然他的动态类型可能是typeinfo的派生类型(C++ primer说的也不太合适P.827)。typeid会过滤掉top-level const和引用。对大部分操作数,typeie不会evaluate该操作数,直接返回其静态类型;只有当表达式是定义有虚函数的类的左值类型时,typeid会evaluate该operand。当在evaluate操作数时,可能需要对指针解引用,这时如果指针不是有效指针(如空指针),就会导致抛出bad_typeid异常。

19.23 type_info,公开的方法只有operator==, operator!=, name(), before(t2), hash_code()。所以我们无法定义、拷贝type_info对象。

19.24 Enumeration(枚举)groups together sets  of integral constants,而且枚举是literal类型。在以前标准中,枚举不是类型安全的,枚举被视为整数,可以和整数或其他枚举类型进行比较。C++11对enumeration进行了加强,引入了sceoped enumeration:定义的时候用enum class(或者enum struct) + enumeration name + define的方式,使用的时候必须加enumeration name::来只是指示。

enum color {red, yellow, green}; // unscoped enumerationenum stoplight {red, yellow, green}; // error: redefines enumerators enum class peppers {red, yellow, green}; // ok: enumerators are hiddencolor eyes = green; // ok: enumerators are in scope for an unscoped enumerationcolor hair = color::red; // ok: we can explicitly access the enumeratorspeppers p = green; // error: enumerators from peppers are not in scope// color::green is in scope but has the wrong type peppers p2 = peppers::red; // ok: using red from pepperscout << yellow <<endl;//ok: output yellow as a integercout << peppers::yellow << endl; //error:pepper not support IO operator
19.25 enumerator的值,默认从0开始,依次加1。我们也可以显式的指定(必须用integral const expression指定),同一enumeration内的enumerator的值可以重复。枚举只能被自己的enumerator或者相同枚举类型的object初始化或赋值,不能隐式地被整型或者其他类型的枚举初始化或赋值。普通枚举可以隐式转为整型,scoped枚举不可以。普通枚举可以和整数或其他枚举类型进行比较,scoped枚举不可以。
enum class E{a=-1, b = 5, c, d = 6 //c and d all are 6};
19.26 普通枚举定义时可以省略名字,scoped枚举不可以。在C++11标准下,可以对枚举(包括普通枚举)的指定enumerator类型,若没有指定,scoped枚举默认是int类型,而普通枚举和以前一样,没有明确类型。C++标准下可以对枚举做前向声明,但声明时必须知道枚举的size,所以对于普通枚举声明是必须指定类型,对于scoped枚举可以不指定(此时使用默认的int类型)。
enum intValues : unsigned long long {charTyp = 255, shortTyp = 65535, intTyp = 65535, longTyp = 4294967295UL,long_longTyp = 18446744073709551615ULL};// forward declaration of unscoped enum named intValuesenum intValues : unsigned long long; // unscoped, must specify a type enum class open_modes; // scoped enums can use int by default

19.27 pointer to member的类型与类的类型和member的类型是绑定的,而且只能指向nonstatic成员。在使用时,必须与具体的类对象结合,使用成员指针解引用操作符(.*)或者成员指针箭头操作符(->*)。由于括号的优先级高于这两个操作符,在使用成员函数指针时需要加括号。

const string Screen::*pdata = &Screen::data; //这里data成员必须accessable//这里Func成员必须accessable。//由于是成员函数,可能需要加const qulifier//另外,必须加括号,否则变为分成员函数的声明char (Sreen::*pFunc)(int) const = &Screen::Func;Screen myScreen, *pScreen = &myScreen;//这里不受pData实际对象的访问权限限制,即使pData指向私有成员,也可访问auto s = myScreen.*pdata;s = pScreen->*pdata;auto ch = (myScreen.*pFunc)(0);//同上,不受访问限制。另外必须加括号。ch = (pScreen->*pFun)(0);
19.28 由于成员指针在访问权限上的特点,通常在类内定义静态成员函数,返回私有成员的指针。
class Screen { std::string data;//默认,即为privatepublic://注意这里必须加Screen::限定符static const std::string Screen::*getData(){ return &Screen::data; }};auto pData = Screen::getData();auto s = myScreen.*pData;
19.29 与普通函数指针不同,成员函数不会隐式转换为成员函数指针,必须通过&取地址后才能得到成员函数指针。另外,在使用时,通常为成员函数指针定义类型别名。
using Action = char (Screen::*)(int) const;typedef char (Screen::* Action)(int) const;
19.30 由于成员函数指针必须通过object调用,所以成员函数指针不是callable object(见Part II,10.7)。但是有三种方法来将其转换为callable  object,这样成员函数指针就可以作为可调用对象,传入算法。
1)使用function函数对象模板。
//若参数为const引用,那么成员函数也必须是constfunction<bool(const string&)> f1 = &A::empty;function<bool(const string*)> f2 = &A::empty;string str, pStr = &str;bool val1 = f1(str);bool val2 = f2(pStr);
2)使用mem_fn函数模板。
auto f = mem_fn(&string::func);string str, pStr = &str;bool val1 = f(str);// ok: passes a string object; f uses .* to call emptybool val2 = f(pStr);// ok: passes a pointer to string; f uses ->* to call empty
3) 使用bind函数模板
auto f = bind(&string::func, _1);string str, pStr = &str;bool val1 = f(str);// ok: passes a string object; f uses .* to call emptybool val2 = f(pStr);// ok: passes a pointer to string; f uses ->* to call empty
19.31 与成员函数类似,嵌套类必须在类内声明,但可以在类外定义。如果嵌套类有静态成员,该成员的定义也需放在外层作用域中(个人认为,这里只是针对静态数据成员,如果是静态成员函数,就没这个要求了。但C++ primer上用的member一词,而不是data member。P.845)。
class Outer{public:class Inner;//受Outer的access control约束,这里是public};class Outer::Inner{static int static_mem;/* other define */};int Outer::Inner::static_mem = 10;
19.32 嵌套类和外围类是相互独立的两个类,但他们的scope存在嵌套关系,而且嵌套类适用name lookup的正常规则:可以访问外围类的静态成员(且此static成员不受access lable限制)、类型别名等等,但不能访问数据成员。

19.33 union(联合)是一种特殊的类,他可以有多个数据成员,但是任意时刻只有一个成员have a value,其他成员都是undefined,union的size至少与最大的数据成员大size一样大。像struct一样,union可以指定protection lables(public,private,protected),默认情况下是public。union的数据成员除了不能是引用类型、static,可以是其他大部分类型,在C++11标准下,甚至可以是带有构造函数和析构函数的类类型。

19.34 union可以定义成员函数,包括构造、析构函数,但是union不能继承其他类,也不能被其他类所继承,所以union不能有virtual函数。当用initializer初始化话union对象时,初始化的是第一个成员。

union Token {// members are public by defaultchar cval; int ival; double dval;};Token first_token = {'a'}; // initializes the cval member
19.35 当我们定义anonymous union时,编译器会自动用所定义的union生成一个没有名字的对象,该对象的成员在所在scope内直接访问。
union { // anonymous union char cval;int ival;double dval;}; // defines an unnamed object, whose members we can access directly cval = 'c'; // assigns a new value to the unnamed, anonymous union ival = 42;  // object.ival now holds the value, object.cval doesn't hold avlue
19.36 在C++11标准下,才允许union有带有构造函数/析构函数的类类型的成员。当union的成员都是built-int类型时,编译器会合成memberwise versions的默认构造、copy-control成员;当union的成员有类类型,只要该类类型(显式)定义了这些函数中的任意一个,union就会把相应的函数声明为deleted。

19.37 当union含有带构造、析构函数的类类型的成员时,对union的管理是一份复杂的工作,通常用一个类专门负责。注意,下面的类并没有实现完所有的copy-control函数(没有move constructor和move assignment)

class Token { public:// copy control needed because our class has a union with a string memberToken(): tok(INT), ival{0} { }Token(const Token &t): tok(t.tok) { copyUnion(t); } Token &operator=(const Token&);~Token() { if (tok == STR) sval.~string(); }// assignment operators to set the differing members of the unionToken &operator=(const std::string&);Token &operator=(int);private:enum {INT, STR} tok; // discriminant union { // anonymous unionint ival;std::string sval;}; // each Token object has an unnamed member of this unnamed union typevoid copyUnion(const Token&);// check the discriminant and copy the union };Token &Token::operator=(int i){if (tok == STR) sval.~string(); // if we have a string, free itival = i; // assign to the appropriate membertok = INT; // update the discriminant return *this;}Token &Token::operator=(const std::string &s){if (tok == STR)sval = s;// if we already hold a string, just do an assignmentelsenew(&sval) string(s); // otherwise construct a string tok = STR; // update the discriminant return *this;}void Token::copyUnion(const Token &t){switch (t.tok) {case Token::INT: ival = t.ival; break; case Token::STR: new(&sval) string(t.sval); break;}}Token &Token::operator=(const Token &t){// if this object holds a string and t doesn't, we have to free the old stringif (tok == STR && t.tok != STR) sval.~string(); if (tok == STR && t.tok == STR)sval = t.sval; // no need to construct a new stringelsecopyUnion(t); // will construct a string if t.tok is STR tok = t.tok;return *this;}
19.38 在函数体类定义的类叫做local class(局部类)。局部类的所有成员(函数)都必须在类内定义,所以不能有static数据成员(没地方定义)。局部类不能使用位于函数作用域中的变量,只能使用其中的类型名,static变量和枚举成员(有点像嵌套类)。其他按照正常的name lookup规则。可以再在局部类内部定义嵌套类。这种情况,嵌套类可以在内部类外部定义,但必须必须和定义局部类的同一作用域中定义。
void foo(){class Bar { public:class Nested; // declares class Nested};// definition of Nested class Bar::Nested {// ... };};
19.39 Inherently Nonportable Features(固有的不可移植的特性)。算术类型的大小随机器不同而变化,就是一种不可移植特性。另外,C++还从C继承了两个不可移植特性:bit-fields(位域)和volatile限定符,另外,C++自己还增加了一种不可移植特性:链接指示。

19.40 通常用unsigned类型来设置位域,因为存储在signed类型中的位域的行为是implementation defined。另外,我们不能对位域取地址。

typedef unsigned int Bit; class File {Bit mode: 2; // mode has 2 bitsBit modified: 1; // modified has 1 bitpublic:enum modes { READ = 01, WRITE = 02, EXECUTE = 03 }; File &open(modes){mode |= READ; // set the READ bit by default// other processingif (m & WRITE) // if opening READ and WRITE // processing to open the file in read/write mode return *this;}};
19.41 当一个变量,不仅被程序自身控制时,通常需要声明为volatile,volatile关键字给编译器了一种指示:该对象不应该执行优化。volatile跟const并不冲突,而且可以同时使用(对前后顺序没有要求),另外,两者的用法也很相似,volatile的成员函数必须通过volatile对象来调用,也同样存在volatile pointer、pointer to volatile的形式。
volatile int v; // v is a volatile intint *volatile vip; // vip is a volatile pointer to int volatile int *ivp; // ivp is a pointer to volatile intvolatile int *volatile vivp;// vivp is a volatile pointer to volatile intint *ip = &v; // error: must use a pointer to volatile*ivp = &v; // ok: ivp is a pointer to volatilevivp = &v; // ok: vivp is a volatile pointer to volatile
19.42 另外,不能通过普通类的copy-control成员(参数的都是nonvolatile引用),用volatile对象去初始化、赋值类对象,因为不能就将nonvolatile 引用绑定到volatile对象上。必要时,可以自己定义针对volatile的的copy-control成员。
class Foo { public:Foo(const volatile Foo&); // copy from a volatile object // assign from a volatile object to a nonvolatile objectFoo& operator=(volatile const Foo&);// assign from a volatile object to a volatile objectFoo& operator=(volatile const Foo&) volatile; // remainder of class Foo};
19.43 C++使用linkage directives(链接指示)来指示非C++函数所用的语言。链接指示不能出现在类定义或函数定义的内部,他必须出现函数的每个声明上。链接指示有单个和复合两种形式,另外,由于链接指示可以嵌套,所以可以将链接指示用于头文件上:
// illustrative linkage directives that might appear in the C++ header <cstring> // single-statement linkage directiveextern "C" size_t strlen(const char *); // compound-statement linkage directiveextern "C" {int strcmp(const char*, const char*); char *strcat(char*, const char*);}// compound-statement linkage directiveextern "C" {#include <string.h> // C functions that manipulate C-style strings }
19.44 需要使用其他语言写的函数指针时,也必须用链接指示,另外链接指示应用到的是整个声明,即不仅应用到函数,也应用到作为函数参数或返回类型的函数指针类型上。
// pf points to a C function that returns void and takes an int extern "C" void (*pf)(int);// f1 is a C function; its parameter is a pointer to a C function extern "C" void f1(void(*)(int));// FC is a pointer to a C functionextern "C" typedef void FC(int);// f2 is a C++ function with a parameter that is a pointer to a C function void f2(FC *);
19.45 将链接指示用在函数定义上时,就将C++函数导出成其他语言可使用的函数了。在链接指示时需要注意目标语言是否支持函数重载,C++要求编译器至少支持C语言的链接指示extern “C“,C++也可以调用C函数。但是由于C语言不支持重载,所示声明两个具有相同名字,但参数列表不同的C函数是错误的。
// error: two extern "C" functions with the same name extern "C" void print(const char*); extern "C" void print(int);classSmallInt{/* ... */}; classBigNum{/* ... */};// the C function can be called from C and C++ programs// the C++ functions overload that function and are callable from C++extern "C" double calc(double);extern SmallInt calc(const SmallInt&);extern BigNum calc(const BigNum&);



注:本文以《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



原创粉丝点击