序:Goole C++ style Guide;
无符号与带符号线程的结果会把带符号当成无符号计算
p17:带.h会使用C的标准库文件,部分IDE不支持C++程序带.h头文件
可以使用nullptr代替NULL(C++ 11 新标准)
使用两个指针判断相等,当一个地址指向某个对象,另一个指向相邻对象,判断的结果可能是true
p44:定义于任何函数体之外的变量被默认初始化为0,定义在函数体内部的内置变量将不被初始化。类的对象如果没有显式的初始化,则其值由类确定。
p55:const常量的引用必须是const对应类型的常量,对一般类型的常量引用必须是相应的内置类型,如果是变换类型C++内部机制会创建一个临时量对象,将引用指向临时 变量。const double *const p = &val;
p57:底层const指针可以变,但是顶层const不可以变
p59:const int sz = get_size(); 不是常量表达式,sz在具体运行之后才会得到具体的值 ,所以sz不是常量表达式,constexpr类型被用来验证变量的值是否是一个常量表达式,声明为constexpr的变量必须使用常量表达式初始化。
P59:constexpr变量的值必须在编译的时候就能得到,一般比较简单,显而易见,容易得到。constexpr指针必须指向固定的地址,例如函数外,
P60:contsexpr会把指针定义的指针对象置为顶层const;typedef double base,*p base是double同义词,p是double *的同义词,using SI = Sales_item; //使用别名的一种方法
P61:typedef char *pstring; const pstring *ps; //ps是一个指针,它的对象是指向char的常量指针。
p62:auto会自动忽略顶层const,保留底层const。可以为常量引用绑定到字面值上,非常量引用不可以。顶层const(值)是指const指向一个变量之后,变量的值不可随意改变,底层const(地址)是指const指向一个变量之后不可以随意更改对象变量。const int *const p;
p63:decltype可以获取一个对象变量的类型,得到这个类型可以规定变量的类型。r是一个引用,因此decltype(r)的结果是引用类型,r + 0这个表达式的结果是一个具体的值而非引用。decltype(*p)的结果类型是int &而非int decltype((i)) d d是int引用,decltype(i)一个未初始化的int
p79:无符号数和有符号数混合比较的时候,有符号数会转换为无符号数,例如n为负数,参与比较的时候,会将其转换为比较大的无符号数。
p82:for(declaration : expression) 表3.3cctype头文件中的函数
p83:数量变量的命名为 类型_cnt,遍历的新标准中的新方法。
p87:初始化vector对象的方法。
p89:一般括号构造vector对象,大括号构造列表,在大括号中的内容无法匹配的时候,编译器会尝试把大括号当成括号构造vector对象。
p95:可以使用auto定义迭代器
p97:如果容器对象是常量的,只能使用静态的迭代器 const_iterator
p98:容器的cbegin()函数返回常量迭代器
p99:不能在for循环中在vector对象之后添加元素。但凡是使用了迭代器的循环体,都不要先迭代器所属的容器添加元素(会使迭代器失效)。
p100:模板定义了迭代器的距离difference_type,为带符号整数
p102:数组的元素应为对象,因此不存在引用的数组。
p103:int *(&arry)[10] = ptrs; //arry是数组的引用,数组中的元素为地址
p104:缓冲区一处错误:下标越界并试图访问非法内存区域时,就会产生此类错误。
p105:int ia[10] auto ia2(ia); ia2是一个整型指针 decltype(ia) ia3;ia3是一个含有10个整数的数组
p106:begin函数返回指向数组的头指针,end函数返回数组的尾后指针,函数包含在iterator头文件中。一般函数在&nums[num_size]会得到尾后元素的地址
p107:两个指针相减的结果的类型为名为ptrdiff_t的标准库类型,包含在头文件cstddef头文件中
p108:内置的下标运算符所用的索引值不是无符号类型,与string和vector不同,int *p = ia + 2; p[-1] 是可以读出数据
p109:字符串数组的初始化时,没有将最后一位定位空格’\0’,则strlen函数会持续的读直至读出。
p111:可以将string类型通过c_str函数返回字符串指针,并初始化给另外的字符指针。后续改变s的值可能导致之前返回的数组不可用。
p113:int a[3][4] int (&aa)[4] = a[2]; //为引用a数组的第三行,数组引用
p114:for(auto &row : ia) for(auto &col : row){ col = ~ } //以此来处理数组中的每一个元素 要使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。
p123:运算对象的调用顺序与优先级和结合律无关。一个表达式中顺序未规定的各个函数影响同一个对象时是一个错误的表达式。
p125:(-m)%n等于-(m%n) m%(-n)等于m%n
p127:在for auto循环中使用引用可以确保元素比较大时的高效率
p129:赋值发生的时用右侧运算对象{}加上数字,可以对vector进行赋值。不可以将指针的值赋给int。
p132:作者建议选择使用前置的递增版本。
p134:条件运算符判断可以嵌套
p135:条件运算符的优先级非常低,因此条件运算符表达式注意加上括号例如 cout<<((grade < 60) ? : “fail” : “pass”); 少任何一个括号都是错误
p137:unsigned表示在任何机器上位数都至少拥有32位,int型只能确保拥有16位。
p141:整型和浮点型算术运算会把整型转换为浮点型进行运算。
p142:如果无符号类型的所有值都能存在带符号类型中,则无符号类型的运算对象转换成带符号类型。如果不能,那么带符号类型的运算对象转换成无符号类型。long的长度取决于机器类型。
p143:指向任意非常量的指针都能转换成 void *;指向任意对象的指针都能转换成const void *
p144:强制类型转换的形式:cast-name(expression); cast-name是static_cast、dynamic_cast、const_cast、和reinterpret_cast
p145:任意非常量对象的地址都能存入void *,static_cast 用于把一个较大的算术类型赋值给较小的类型,此时此标志告诉程序的读者和编译器:我们知道并且不在乎潜在的精度损失。const_cast用来改变运算对象的底层const,去掉const性质,常用于有函数重载的上下文中。reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。
p155:符合语句是指用花括号括起来的语句和声明的序列,符合语句也被称作块。一个块就是一个作用域。一个变量名字从定义初开始,一直到块的结尾为止。
p172:标签指示符可以和程序中的其他实体的标识符使用一个名字而不会收到干扰,goto语句和控制权转向的那条带标签的语句必须位于同一个函数之内。
p173:在程序中应该把处理对象的代码和用户交互的代码分离开来。
p174:runtime_error是标准库异常类型的一种,定义在stdexcept头文件中,初始化使用的是string对象或者一个C风格的字符串。
p175:异常抛出时,沿着程序的执行路径逐层回退,知道找到适当类型的catch子句为止。最终没能找到catch子句,系统会调用terminate的标准库函数。没有try语句块发生了异常也会调用terminate函数,并终止当前程序的执行。
p176:exception 和 stdexcept头文件定义了常用的异常类。我们只能以默认初始化的方式初始化exception、bad_alloc、bad_cast对象,不允许为这些对象提供初始值。其他类型的行为恰好相反。
p184:函数的个别形参不会被用到,则此类形参通常不命名以表示在函数体内不会使用它,但是是否命名形参不影响调用时提供的实参数量,不命名也要提供全部的实参。在函数内自动忽略函数体外的声明的对象,但是函数体外定义的对象存在整个执行过程中。
p191:void fcn (const int i ) void fcn (int i)是同一个函数,不是函数重载。参数是引用不可以绑定到字面值上。
p195:但是就可以把常量引用绑定到字面值上。f(int &arr[10]) 引用的数组 f(int (&arr) [10]) 数组的引用
p196:int(*matrix[10]) 声明指向含有10个整数的数组的指针
p197:传递给mian函数的参数第一个是字符串个数,第二个是命令字符串数组,第一个字符串是命令名,最后一个是0
p198:initializer_list可以用来表示参数个数可能会发生变化的参数,使用大括号括起来的字符串字面值或者字符串来进行初始化。类似于vector。类ErrCode可以用来表示不同类型的错误。
p199:···省略符形参通常用于C++访问某些特殊的C代码设置,这些代码使用名为varargs的C标准库功能。由编译器文档描述。一般放在参数列表的最后。
p202:返回局部对象的引用是错误的,即便是字符串字面值转换的局部临时对象。
p203:C++11标准规定函数可以返回花括号包围的值的列表,用来初始化容器。主函数返回值可以是EXIT_SUCCESS和EXIT_FAILURE两个预处理变量,可以保证与机器无关,被定义在cstdlib头文件中。
p205:typedef int arrT[10] arrT是一个类型别名,表示的类型是含有10个整数的数组。using arrT = int [10] arrT func(int i) func返回一个指向含有10个整数的数组的指针。 int (*func (int i )) [10]表示解引用func的调用将得到一个大小是10的数组,并且此数组的类型是int型。函数返回的是指向数组的指针。
p206:可以使用尾置返回类型表示函数返回的内容,auto func (int i)->int(*) [10]; func函数接收一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组。返回数组指针的函数定义方式可以是 int odd[] = {}; int even[] = {}; decltype(odd) *arrPtr(int i) { return (i % 2) ? &odd : &even; }
p208:一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来,但是如果形参是某钟类型的指针或者引用,则通过区分其指向的常量对象还是非常量对象可以实现函数重载,此时const是底层的。同时存在常量形参和非常量形参,当实参是非常量时编译器会优先选择非常量版本的函数。
p210:域和重载的关系中,同一般变量的声明相同,如果在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。在不同的作用域无法重载函数名。
p211:在C++语言中,名字查找发生在类型检查之前。一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。调用含有默认实参的函数时,可以省略相关实参。
p212:调用默认实参的函数时, 只能省略尾部实参。
p213:用作默认实参的名字在函数声明所在的作用域内解析,这些名字的求值过程发生在函数调用时。
p214:constexpr函数是指能用于常量表达式的函数。函数的返回值和所有形参的类型都必须是字面值类型,而且函数中必须只有一条return语句。为了能在编译过程中随时展开,constexpr函数被隐式的指定为内联函数。constexpr函数中可以包含其他在执行时不执行任何操作的语句。例如空语句、类型别名、using声明。constexpr函数的参数和返回值必须是字面值类型。
p215:内联函数和constexpr函数可以多次定义,但是必须完全相同。 assert是一种预处理宏,所谓预处理宏其实是一个预处理变量,他的行为类似内联函数,包含在cassert头文件中。assert宏使用一个表达式作为它的条件:assert(expr); 首先对expr求值,如果表达式为假,assert输出信息并终止程序的执行。如果表达式为真,assert声明也不做。assert宏定义在cassert头文件中。
p216:如果定义了NDEBUG,则关闭调试状态,assert什么也不做 CC -D NDEBUG main.C #use / D with the Microsoft compiler。预处理器定义了几个用于调试的局部静态变量 __func__存放函数的名字 FILE 存放文件名 __LINE__存放行号 __TIME__存放编译时间 __DATE__存放编译日期
p217:void f(double , double = 3.14) 为含有默认值实参的函数的声明。
p218:选择重载的函数的原则:该函数的每个实参的匹配都不劣于其他的可行函数需要的匹配,至少有一个实参的匹配优于其他可行函数提供的匹配。
p220:有时候,即使实参是一个很小的整数值,也会字节将他提升成int类型,此时使用short版本反而会导致类型转换,char实参选择使用int形参的函数而非short形参的函数。重载函数的形参为float和double时,实参为一个小数时会出现二义性。所有算数类型的转换级别都一样。实参是非常量时,用非常量对象初始化常量引用需要类型转换,接受非常量形参的版本则与实参精确匹配,故应选择非常量形参函数。
p221:函数指针中函数的类型由返回类型和形参类型共同决定,与函数名无关。当我们把函数名作为一个值使用的时候,该函数会自动地转换成指针,即在函数名前面加不加取址符都一样。在指向不同的函数类型的指针间不存在转换规则。可以直接使用指向函数的指针调用该函数,无须提前解引用指针。bool (*pf)(const string &, const string &)
括号必不可少。
p222:void use( bool pf(const string &, const string &) ) 与 void use(bool (*pf) (const string &, const string &))是等价的,形参是函数函数类型,会自动地转换成指向函数的指针。decltype返回函数类型,不会将函数类型自动转换成指针类型。 typedef bool Func(const string&, const string&); typedef decltype(lengthCompare) Func2; // equivalent type typedef bool(*FuncP)(const string&, const string&); typedef decltype(lengthCompare) *FuncP2; // equivalent type
p223:但函数返回一个指向函数的指针时,和函数类型的形参不一样,返回类型不会自动地转换成指针。必须显示地将返回类型指定为指针
p230:定义在类内部的函数是隐式的inline函数。
p231:this是一个隐式常量指针,不允许改变this中保存的地址。
p234:一般来说执行输出任务的函数应该尽量减少对格式的控制,这样可以确保有用户的代码来决定是否换行。
p235:不同于其他的成员函数,构造函数不能被声明成const函数。
p236:默认构造函数没有任何实参。只有当类没有声明任何构造函数时,编译器才会自动生成默认构造函数。复合类型的数组或者指针的默认初始化的值是未定义的。类内含没有默认构造函数的其他类类型成员时无法初始化。如果类包含有内置类型或者复合类型的成员着只有当这些成员全都被赋予了类内的初始值时,这个类才适合于使用合成的默认构造函数。有些编译器无法生成默认构造函数。
p237:在参数列表之后写上=default 来要求编译器生成默认构造函数。
p240:一个类可以包含0个或者多个访问说明符,对于访问说明符的出现次数没有严格限定,范围尾两个说明符之间,或者到类结尾。class和struct的唯一区别就是默认的访问权限,第一个访问说明符之前,在class内是private在struct内是public。
p241:如果类想把一个函数定为自己的友元,只需要增加一条以friend关键字开始的函数声明语句即可。
p242:友元声明在类内,在类内出现的具体位置不限,友元不是类的成员不受所在访问区域的控制级别的限制,一般最好在类定义开始或者结束前的位置集中声明友元。友元最好在类外也要声明一次,只需去掉friend关键字。
p244:如果类中不存在类内初始值,就需要先其他成员一样显式的初始化对象。
p245:类内成员变量被声明为mutable后,即使式const函数也能更改该变量。默认情况下使vector拥有一个默认初始化的变量。
p248:对于引用const于非const的重载是不同的,const对象调用const函数即函数名之后有const标记。
p249:即使两个类的成员列表一样,它们也是不同的类型。实际声明变量的时候可以把类名跟在关键字class和struct后面。
p250:类和函数类似,可以提前声明但不定义,提前声明时,可以使用其定义指针。友元函数能定义在类的内部,这样的函数时隐式内联的。类可以把其他的类及其他类的成员函数定义为友元。某类如果要使用另一个类的成员,只须在另一个类中加上句首为friend的类声明语句即可。
p251:另一个类中定义了一个本类的友元类,则在本类中可以使用另一个类中的所有成员,即使是私有成员。友元不存在传递性,
p252:为保证本类中函数可以使用另一个类成员,在另一个类定义一个本类的友元函数的方式为:声明另一个类;声明本类中目标函数;声明另一个类;定义另一个类的函数。重载函数要重复声明友元。友元声明的作用是影响访问权限,它本身并非普通意义上的声明。就算在类的内部定义该函数,我们也必须在类的外部提供相应的声明从而使函数可见。
p253:可以使用 :: 来取类中定的别名。因为在类域,所以类型名需要限定为类域中的类型别名。Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s)
p255:注意名字和类的作用域,名字查找的步骤。在类中,如果成员使用了外层作用域中的某个名字,而该名字代表一种类型,则类中不能再之后重新定义名字。
p256:参数名和成员名相同时,会被当成参数名。可以通过加上this指针,强制访问成员。可以使用::强制访问类外的名字。名字查找的第三步不仅要考虑类定义之前的全局作用域中的声明,还要考虑再成员函数定义之前的全局作用域中的声明。
p258:成员是const或者引用,或者成员属于某种没有定义默认构造函数的类类型时,也必须通过初始值列表将这个成员初始化。初始值列表中的成员初始化的顺序与再类中声明的顺序是一样的。
p260:如果一个构造函数为所有的参数都提供了默认实参,则它实际上也定义了默认构造函数。
p261:当一个构造函数委托另一个构造函数的时候,受委托的初始值列表和函数体被执行,随后委托者的函数体会执行。
p263:如果构造函数只接受一个实参,则它实际上定义了转换为此类型的隐式转换机制。
p264:能通过 一个实参调用 的构造函数定义了一条从构造函数的参数类型向类类型隐式转换的规则。但编译器只会自动地执行一步类型转换。
p265:在要求隐式转换的程序上下文中,我们可以通过将构造函数声明为explicit加以阻止。声明explicit只能再类内声明构造函数时使用,再类外部定义时不应重复。
p266:聚合类的所有成员是public、没有定义任何构造函数、没有类内初始值、没有基类。集合类的初始化由大括号括起来,括号中的值的顺序要与类内成员的顺序相同,括号内的值可少不可多。
p267:字面值常量类含有constexpr函数成员
p269:静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据,静态成员函数不与任何对象绑定一起,不包含this指针。适合显式使用this指针,作为结果静态成员函数不能声明成const。对象也可以访问静态成员。
p270:当类的外部定义静态成员时,不能重复static关键字,该关键字只出现再类内部的声明语句中。静态数据成员必须定义在任何函数之外。我们可以为静态成员提供const整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的constexpr。
p271:静态数据成员可以是不完全类型的,特别的,静态数据成员的类型可以是它所属的类类型,而非静态数据成员则受到限制,只能声明成它所属的类的指针或引用。静态成员和普通成员的另一个区别是我们可以使用静态成员作为默认实参。static constexpr int period = 30;
p282:关联输入和输出流
p284:接受一个iostream类型应用参数的函数可以使用一个fstream类型调用。 如果定义了一个空文件流对象,可以使用open将它与文件关联起来。
p286:保留被ofstream打开的文件中已有数据的唯一方法是显式指定app或in模式。
p287:istringstream从string读取数据 和ostringstream从string输出数据,stringstream可以从string读数据也可以string输出数据,三者都继承自iostream,可以使用输入的表达式的返回值读取输入状态。
p293:如果必须在中间位置插入元素,可以考虑在输入阶段使用list,一旦完成输入之后,将list中的内容拷贝到一个vector中。
p294:某些容器操作对元素类型有其自己的特殊要求。某些类没有构造函数,我们可以定义一个保存这种类型对象的容器,但当我们在构造这种容器时,不能只传递给它一个元素数目参数。
p297:方向迭代器是一种反向遍历容器的迭代器,与正向迭代器相比,各种操作的含义发生了颠倒,例如对一个反向迭代器执行++操作,会得到上一个元素。
p298:当我们对一个非常量对象调用迭代器成员时,得到的是返回iterator的版本,只有在对一个const对象调用这些函数时,才会得到一个const版本。
p300:为了创建一个容器来为另一个容器的拷贝,两个容器的类型及元素类型必须匹配,但当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的得到,只要能将要拷贝的元素转换为初始化容器的元素即可。forward_list也可以被迭代器范围指针初始化。
p301:当模板的元素是有默认构造函数的类,则可以只提供一个容器大小参数。如果元素类型没有默认构造函数,则还要提供显式的元素初始值。只有顺序容器才接受大小参数,关联容器不支持。大小是array类型的一部分,array不支持普通的容器构造函数。如果先array构造函数传递大小参数,最好的情况下是多余的,而且容易出错。array允许赋值。
p302:容器可以将容器的内容替换为初始化列表中的拷贝,但是array不适用。赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效,swap将不会使其失效。swap比一般的拷贝速度要快。array类型不支持assign对容器进行分配。
p303:传递给aasign的迭代器不能指向调用assign的容器。除array外容器的swap操作在常数时间内完成。除string外在swap操作之后,其内指针、引用、迭代器都不会失效。交换后会指向交换之后的另一个容器。
p304:只有当元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器。
p305:向一个vector、string、deque插入元素会使所有指向容器的迭代器、引用和指针失效。因为,可能需要重新分配内存,将元素从旧的空间移动到新的空间。
p307:除了第一个迭代器参数外,insert函数还可以接受更多的参数,与容器构造函数类似。
p308:当我们调用push或insert成员函数时,我们将元素类型的对象传递给他们,这些对象被拷贝到容器中,而当我们调用一个emplace成员函数时候,则是将参数传递给元素类型的构造函数。emplace成员使用这些参数在容器管理的内存空间中直接构造元素。push、insert函数会将类型的对象传递给它们,对象被拷贝到容器中。emplace速度更加快。
p310:在容器中访问元素的成员函数,返回的都是引用,如果容器是一个const对象,则返回的值是const的引用。at函数在参数不合法的时候,会抛出一个out_of_range异常。
p311:删除deque中除首尾位置之外的元素会使所有的迭代器、引用和指针失效。指向vector或string中删除点之后位置的迭代器、引用、指针都会失效。erase函数会删除元素返回被删除元素的下一个元素的迭代器。如果删除尾元素,则返回尾迭代器。
p312:迭代器删除一个范围的时候删除的时候,两个迭代器所指的范围是左闭右开区间。
p313:在链表中,没有简单的方法获取一个前驱,因此可以通过后驱来改变元素。forward_list并未定义insert、emplace、和erase,而是定义了名为insert_after、emplace_after、erase_after的操作。forward_list也定义了before_begin它返回一个首前迭代器。
p313:为了添加或者删除一个元素,我们需要访问其前驱,以便改变前驱的链接。在forward_list容器中进行操作是通过改变给定元素之后的元素来完成的,因而forward_list提供了一个before_begin函数,用来返回一个首前迭代器,来在链表首元素之前进行添加删除元素操作。插入函数返回一个指向最后一个插入元素的迭代器,如果范围为空,返回参数迭代器。删除函数返回一个被删除元素之后的迭代器,如果不存在这样的函数,返回尾
p315:向容器添加或者删除一个元素后,不同容器的迭代器、引用、指针的变化不同,可能有效或者无效。注意由于迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,所以必须保证每次改变容器的操作之后都正确的重新定位迭代器。对于deque,在首尾添加元素时,迭代器失效,指针引用有效,删除时删除首没影响,删除尾尾后迭代器失效,其他不受影响。
p316:insert函数在给定的位置之前插入新元素,然后指向新插入元素的迭代器。不要保存end返回的迭代器,每次增删要更新迭代end迭代器。
p318:reserve不改变容器中元素的数量,函数仅影响vector预先分配多大的内存空间。reserve请求更改容量和预留空间,resize更改的是容器中元素的数量。在调用reserve之后capacity将会大于或等于传递给reserve函数的参数。使用shrink_to_fit不保证一定退回内存空间。
p320:每个vector会选择自己的内存分配策略,但只有在迫不得已的时候才会重新分配新的的内存空间。但是保证添加元素有效率,花费的时间为常数】级别。
p320:构造string的方法。从一个字符指针创建string时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符时停止。如果我们还传递给构造函数一个计数值,数组就不必以空字符结尾。
p321:substr函数可以截取string中内容。
p323:修改string的操作。接受下标的版本返回一个s的引用,接受迭代器的版本返回指向第一个插入字符的迭代器。
p324:assign和append函数无需指定要替换string中的哪个部分,assign总是替换string中的所有内容,append总是将新字符追加到string的结尾。
p325:string搜索函数返回string::size_type值,搜索失败时,返回一个定义为一个const的名为string::npos的static成员。如果使用带符号类型存储这类函数的返回值不是好主意。
string的搜索操作
p327:s.compare的几种参数形式
p319:容器类型除顺序容器外,还存在顺序容器适配器,每个适配器都在其底层顺序容器类型之上定义了一个新的的接口:stack(默认基于deque实现,也可以在list和vector之上)、queue(默认基于deque实现)、priority_queue(默认在vector上实现)。适配器是一种机制,能使某种事物的行为看起来像另一种事物一样。 stack
表明在vector上实现适配器。
p336:算法定义在algorithm中,标准库还在头文件numeric中定义了一组数值泛型算法。
p337:算法永远不会改变底层容器的大小,算法可能改变容器中保存的元素的值,也可能在容器内移动元素,但永远不会直接添加或删除元素。
p338:accumulate的第三个参数类型决定了函数中使用哪个算法运算符以及返回值的类型。
p339:accumulate操作string元素时,将空串当作一个字符串字面值传递给第三个参数是不可以的,会导致一个编译错误。equal函数判断两个序列是否相同,第三个参数表示第二个序列的首元素,且需保证第二个序列的长度要至少与第一个序列一样长。那些只接受单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长。
p340: fill_n函数参数要保证写入数据的目的位置足够大,能够容纳要写入的元素。fill_n(vec.begin(), vec.size(), 0);
p341: back_inserter函数返回插入迭代器,可以保证有足够元素空间容纳输出数据。vector
拷贝算法copy接受三个迭代器,前两个表示一个输入范围,第三个表示目的序列的起始位置。copy 返回的是其目的位置迭代器(递增后)的值,返回拷贝到对象的尾元素之后的位置。
p342:replace函数替换replace(ilst.begin(), ilst.end(), 0, 42);
replace函数替换后拷贝赋值到另一个 replace_copy(ilst.cbegin(), ilst.cend(), back_inserter(ivec), 0, 42);
p343:unique并不真的删除任何元素,它只是覆盖相邻的重复元素,使得不重复的元素出现在序列的开始部分。unique返回的迭代器指向最后一个不重复的元素之后的位置,此位置之后的元素依然存在,当我们不知道他们的值是什么。标准库算法对迭代器而不是容器进行操作,因此,算法不能直接添加或删除元素。
p344:谓词分为一元谓词(一个参数)和二元谓词(两个参数)。返回可以转为bool类型的值的函数。
p345:使用stable_sort算法是稳定排序算法。
p346:可调用对象lambda表达式,可以定义在函数内部,形式为 [capture list] (parameter list) -> return type { function body } ,可以忽略参数列表和返回类型,但是必须包含捕获列表和函数体。find_if算法接受一堆迭代器表示范围,第三个参数是谓词,返回第一个使谓词返回非0值的元素,如果不存在这样的元素,返回尾迭代器。
p347:虽然一个lambda可以出现在一个函数中,使用其局部变量,但它只能使用那些明确指明的变量。只有通过将局部变量包含在其捕获列表中,才可以使用这些变量。
p348:lambda的捕获列表只可以捕获函数中定义的非static变量,但是可以直接使用static变量以及函数之外的名字。
p349:当像一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象:传递的参数是此编译器生成的类类型的未命名对象。
p350:由于被捕获的变量的值是在lambda创建时拷贝,因此随后对其修改不会影响到lambda内对应的值。一个以引用方式捕获的变量与其他任何类型的引用的行为类似。如果lambda可能在函数结束之后执行,捕获的引用指向的局部变量已经消失。
p351:我们可以从一个函数返回lambda。函数可以直接返回一个可调用对象,或返回一个类对象,该类含有可调用对象的数据成员。如果函数返回一个lambda,则与函数不能返回一个局部变量的引用类似,此lambda不能包含引用捕获。必须要在lambda执行时保证绑定到迭代器、指针、引用的对象仍然存在。一般应尽量减少捕获的数量,来避免问题,如果可能尽量避免捕获引用或者指针。lambda捕获从创建到执行期间的相关信息。隐式捕获:为了指示编译器推断捕获列表,应该在捕获列表中写一个&或者=,&表明采用引用方式,=表明采用值捕获方式。
p352:混合隐式和显式捕获时,捕获列表中的第一个元素必须是一个&或者=,此符号指定了默认捕获方式为引用或值。但混合使用隐式捕获和显示捕获时,显示捕获的变量必须使用与隐式捕获不同的方式。默认情况下捕获列表中的变量都被拷贝。默认情况下,一个被捕获拷贝的变量lambda不会改变其值。如果我们希望能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable。
p353:默认情况下,如果以一个lambda体包含return之外的任何语句,编译器假定此lambda返回void。当我们需要为lambda定义一个返回类型时,必须使用尾置返回类型。
p354:可以使用定义在functional头文件中的bind库函数来接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。 如auto newCallable = bind(callable, arg_list);
arg_list参数可能包含形如_n的名字。分别代表newCallable的第几个参数。
p355:名字_n都定义在一个名为placeholders的命名空间中,而这个命名空间本身定义在std命名空间中。对_1using声明为:using std::placeholders::_1;
。使用声明using namespace std::placeholders;
时会使得placeholders定义的所有的名字都可以使用。该命名空间定义在functional头文件中。
p356:auto g = bind(f, a, b, _2, c, _1);
调用g(_1, _2);
时,将调用f(a, b, _2, c, _1);
p357:如果我们希望传递给bind一个对象而不是拷贝它,就必须使用标准库的ref函数for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' '));
函数ref返回一个对象,包含给定的引用,此对象是可以拷贝的。标准库中还有一个cref函数,生成一个保存const引用的类。
p358:插入迭代器back_inserter, front_inserter, inserter inserter插入迭代器在使用时, *it = val;
意为it = c.insert(it, val); ++it;
p359:list
创建一个流迭代器时,必须指定迭代器将要读写的对象类型,一个istream_iterator使用>>来读取流。创建一个istream_iterator时,可以将他绑定到一个流,默认初始化迭代器就创建了一个可以当作尾后值使用的迭代器。
p360:对于一个绑定到流的迭代器,一旦其关联的流遇到文件尾,或者遇到IO错误,迭代器的值就与尾后迭代器相等。可以使用一堆表示元素范围的迭代器来构造vecistream_iterator
p361:标准库中的实现所保证的是,在我们第一次解引用迭代器之前,从流中读取数据的操作已经完成了。可以对任何具有输出运算符(<<)的类型定义ostream_iterator。创建一个ostream_iterator时可以提供第二个参数(必须时C风格的字符串),不允许空的或表示尾后位置的ostream_iterator。流迭代器无须解引用操作out = val,在out中输出val。*out, ++out, out++运算符存在但不对out做任何事情。每个运算符都返回out。
p363:sort(vec.rbegin(), vec.rend());
会将vec整理成递减顺序。流迭代器不支持递减运算,所以不可能从一个forward_list或一个流迭代器创建反向迭代器。
p364:使用反向迭代器初始化string时,string的顺序是反的。可以通过reverse_iterstor的base成员函数来完成反向到正向迭代器的转换,此成员函数返回其对应的普通迭代器。但反向和正向的相互转换是不同的元素,反向在正向前面一个。
p366:一共五类迭代器,有不同大小的能力。对于每个迭代器参数来说,其能力必须与规定的最小类别至少相当。向算法传递一个能力更差的迭代器会产生错误。输入迭代器不能保证状态可以保存下来并用来访问元素。输出迭代器只能用于单遍扫描算法。算法replace要求前向迭代器,forward_list上的迭代器是前向迭代器。
p367:随机访问迭代器的下标运算符(iter[n]), 与*(iter[n])等价
p368:接受一个元素值的算法通常有另一个不同名的(不是重载的版本),该版本接受一个谓词代替元素值。接受谓词参数的算法都有附加的_if前缀。
p369:函数reverse函数会反转序列中元素的顺序,要求双向迭代器。remove_if函数删除符合条件的元素,remove_copy_if将符合条件的元素拷贝到另一个序列中,原序列不变。remove_if(v1.begin(), v1.end(), [](int i) { return i % 2; }); remove_copy_if(v1.begin(), v1.end(), back_inserter(v2), [](int i) { return i % 2; });
对于list和forward_list,应该优先使用成员函数版本的算法而不是通用算法。
p370:使用splice函数来移动元素。链表特有版本与通用版本将的一个至关重要的区别是链表版本会改变底层的容器。通用merge输入序列不变,链表版本的merge会删除一个。
p376;关联容器的迭代器都是双向的。
p377:可以将关联容器初始化为另一同类型容器的拷贝,或是从一个值范围来初始化关联容器。在新标准下可以对关联容器进行值初始化。multiset中的元素可以重复。
p378:用尖括号指出要定义哪种类型的容器,自定义的操作类型必须在尖括号中紧跟着元素类型给出。
p379:在创建一个容器(对象)时,才会以构造函数参数的形式提供真正的比较操作(其类型必须与在尖括号中指定的类型相吻合)multiset
比较操作类型是一种函数指针,可以指向compareIsbn。小括号内部的参数是用来初始化容器比较类型的函数名,可以自动转化为函数指针。使用&也可以。pair标准库类型定义在utility中。
p380:make_pair(v1, v2) 返回一个用v1和v2初始化的pair,pair的类型由v1和v2的类型推断出来。pair的类型从v1和v2的类型推断出来。新标准课列表初始化,或者隐式构造初始化。set类型,key_type, value_type是一样的,set中保存的值就是关键字。对于map的value_type为pair
p383:使用关联容器定义的专用的find成员函数比调用泛型find函数要快,后者会进行顺序搜索。通过使用inserter,我们可以将关联容器当作一个目的位置来调用另一个算法。
p384:map和set可以插入一个迭代器表示的范围。insert函数或者emplace函数的返回值为一个pair,first成员是一个迭代器,指向给定关键字的元素,second为bool值,表示插入是否成功。关键字已经存在容器中,insert函数声明也不做插入失败,second为flase,反之为true;
p385:pair
p386:对允许重复关键字的容器,接受单个元素的insert操作返回一个指向新元素的迭代器。关联容器提供一个额外的erase操作,接受key_type参数,删除所有匹配的元素,返回删除数量。接受的是迭代器参数时,返回类型的是迭代器。
p387:由于下标运算符可能插入一个新元素,我们只能对非const的元素进行下标操作。为了防止插入初始化,可以使用at(k)函数访问相应关键字k的元素,带参数检查,如果访问的内容不在容器中,将会返回一个out_of_range异常。
p388:关联容器的set和map有find函数和count函数搜索元素出现次数。find返回找到的元素的迭代器,找不到返回end(),count统计有多少个元素有这个相同的关键字。
p390:lower_bound upper_bound函数放别返回第一个关键字的迭代器,第一个大于给定关键字的元素的迭代器。for (auto beg = authors.lower_bound(search_item), end = authors.upper_bound(search_item); beg != end; ++beg) cout << beg->second << endl;
如果找不到返回的话,两个函数会返回相同迭代器,指向可插入的位置。
p391:equal_range函数返回pair类型,first与second存储元素范围迭代器for (auto pos = authors.equal_range(search_item); pos.first != pos.second; ++pos.first) cout << pos.first->second << endl;
p394:无序容器是无序的,使用哈希实现,理论性能更高。
p395:无序容器使用桶管理元素。如果容器允许重复关键字,所有具有相同关键字的元素都会在同一个桶中,一个桶存储哈希值相同的元素。rehash函数和reserve函数重组存储。
p396:不能直接定义关键字类型为自定义类类型的无序容器。size_t hasher(const Sales_data &sd){ return hash
using SD_multiset = unordered_multiset
如果定义了类的运算符,则可以只重载哈希函数unordered_set
无序容器使用运算符和一个hash
p400:内存空间分为:静态内存(静态对象、定义在函数之外的变量),栈内存,内存池也称为自由空间或堆。不释放内存产生内存泄漏。智能指针与不同指针的区别是可以自动释放内存对象。智能指针:shared_ptr(允许多个指针指向同一个对象), unique_ptr(独占对象), weak_ptr(弱引用)。包含在memory头文件中。类似于vector,智能指针也是模板。
p401:make_shared利用参数来构造给定类型的参数。shared_ptr
函数make_shared在动态内存中分配一个对象并初始化它,返回指向此对象的share_ptr。p = q 会递减p的引用计数,递增q的引用计数。
p402:一旦一个share_ptr的计数器变为0,它就会自动释放自己所管理的对象。释放的过程由析构函数来进行处理。
p403:作为局部变量的智能指针在函数作用域结束时被销毁,计数值减一,如果为0,则销毁指向的对象。返回值返回一个智能指针时,计数值加一。把指针放在容器中,不适用时要使用erase函数删除。程序使用动态内存的原因:不知道自己需要使用多少对象,不知道对象的准确类型,程序需要在多个对象之间共享数据。
p404:使用动态内存的一个很常见的原因是允许多个对象共享相同的状态。例如全局对象和局部对象共享一个地址空间,局部对象销毁之后,全局不会销毁。智能指针本身也是一个类,有自己的成员函数,也可以指向具体的容器等类型。
p407:new无法为分配的对象命名,而是返回一个指向该对象的指针。在新标准下可以使用列表进行初始化。vector
p408:int *pi1 = new int;
默认初始化,值未定义 int *pi2 = new int();
值初始化为0 由于编译器要用初始化器的类型推断分配的类型,只有括号中仅有单一初始化器时才可以使用auto auto p1 = new auto(obj); 正确 auto p2 = new auto{a,b,c}错误。普通new关键字使用时,如果分配失败抛出std::bad_alloc异常,int *p2 = new (nothrow) int;
分配失败,返回一个空指针。
p409:bad_alloc nothrow都定义在new头文件中。delete执行两个动作,销毁给定的指针指向的对象,释放对应的内存。传递给delete的指针必须指向动态分配的内存或者是一个空指针。
p411:在delete指针后,很多机器上的指针依旧保留动态内存的地址,该地址变成了空悬地址。可以在delete操作之后,重新赋为nullptr
p412:接受指针参数的智能指针构造函数是explicit的。因此,我们不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针。shared_ptr
错误 shared_ptr
正确。默认情况下,一个初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete释放它关联的对象。
p413:尽量不混合使用普通指针和智能指针,当将一个shared_ptr绑定到一个普通指针时,我们将内存的管理责任交给了shared_ptr。一旦这样做,我们就不应该再使用内置指针来访问shared_ptr所指向的内存。不使用delete。
p414:智能指针类型定义了一个名为get的函数,它的返回一个内置指针,指向智能指针管理的对象。只有再确定代码不会delete指针的情况下,才可以使用get,特别是不能用get初始化另一个智能指针,或为另一个智能指针赋值。可以用reset将一个新的指针赋予一个shared_ptr。reset会更新引用计数,reset成员经常于unique一起使用,来控制多个shared_ptr共享的对象。
p416:为了避免忘记释放内存,通常可以使用智能指针来管理类,管理不具有良好定义的析构函数的类。void end_connection(connection *p) { disconnect(*p); }
connection c = connect(&d); shared_ptr
。当创建一个shared_ptr时,可以传递一个指向删除器函数的参数。
p417:如果使用的智能指针管理的资源不是new分配的内存,需要传递给它一个删除器。unique_ptr与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象,当unique_ptr被销毁时,它所指向的对象也被销毁。没有make_shared类似的函数返回一个unique_ptr。初始化unique_ptr必须使用直接初始化形式,unique_ptr不支持拷贝或者赋值操作。
p418:unique_ptr会使用delete来释放指针。u.release()函数放弃对指针的控制权返回指针将u置为空。虽然unique_ptr不能赋值,但可以通过调用release或者reset转移所有权。可以使用release初始化或者赋值,如果不用另一个智能指针保存release返回的指针,程序就需要负责资源的释放。不能拷贝unique_ptr的例外为:可以拷贝或者赋值一个将要被销毁的unique_ptr,常见的例子时从一个函数返回unique_ptr。
p419:必须在尖括号中unique_ptr指向的类型之后提供删除器类型。在创建或者reset一个unique_ptr类型对象时,必须提供指定类型的可调用对象(删除器)。
p420:将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。weak_ptr的lock成员函数检查指向的对象是否存在,如果存在返回一个shared_ptr。weak_ptr指针的成员函数use_count 返回与弱引用指针共享对象的shared_ptr的数量,expired在use_count 为0返回true,否则返回false,lock函数在为0时,返回空shared_ptr,否则返回一个指向弱引用的shared_ptr。使用弱引用核查类核查指针。
p423:标准库中包含一个名为allocator的类,允许我们将分配和初始化分离。在使用allocator时通常有更好的性能和更灵活的内存管理能力。typedef int arrT[42]; int *p = new arrT;
等于int *p = new int[42];
p424:对于动态数组不可以使用begin和end函数,原因为动态数组不是数组类型。如果初始化器数目大于元素数目,则new失败,不会分配任何内存,而且会抛出一个bad_array_new_length的异常。类似bad_alloc此异常定义在头文件new中。string *psa3 = new string[10]{"a", "an", "the",string(3,'x')};
p425:delete一个动态数组时,是按照逆序销毁的。不要使用delete[]删除单个对象,也不可以使用delete删除数组对象。否则出错。使用unique_ptr管理动态数组unique_ptr
p426:智能指针指向一个数组时,不能使用电和箭头成员运算符,智能指针不支持指针算术运算。unique_ptr支持下标运算符,shared_ptr不支持下标运算符。为了访问数组中的元素,必须使用get或缺一个内置指针,然后用它来访问数组元素。使用unique_ptr指向一个动态数组时,可以自动调用delete[],但shared_ptr必须提供自己定义的删除器,否则就像是使用delete忘记带中括号一样。shared_ptr sp(new int[10], [](int *p) { delete[] p; });
p427:标准库allocator类定义在头文件memory中,将分配内存和对象构造分离开来。类似vector,allocator是一个模板。
p428:allocator类的construct成员函数的参数为指向原始内存的地址和额外参数,额外参数必须是与构造的对象的类型相匹配的合法的初始化器。allocator对象可以的成员函数为allocate(n)分配内存、deallocate(p, n)释放内存、construct(p, args)构造对象、destroy§析构对象。
p429:可以使用allocator类中定义的两个伴随算法拷贝和填充未初始化内存。uninitialized_copy、uninitialized_copy_n、uninitialized_fill、uninitialized_fill_n。与copy不同,uninitialized_copy在给定目的位置构造元素。类似copy,uninitialized_copy返回递增后的目的位置迭代器。
p432:为了定义一个函数的返回类型,需要保证这个类型在类定义之前被声明。
p433:using,#define(预编译)、typedef。一个未被初始化的shared_ptr是空指针。lines是空shared_ptr指针lines.reset(new set
之后lines不为空。static shared_ptr
set为空,nodata不为空。对于set可以使用for auto引用遍历。
p440:如果没有显示的定义拷贝函数,编译器会自动定义,但是定义的版本或许并非所想的。
p441:对于类类型成员会使用拷贝构造函数来进行拷贝,内置类型的成员直接拷贝。拷贝初始化会将右侧对象直接拷贝到正在创建的对象中。拷贝初始化不仅仅发生在用=定义变量的时候。拷贝初始化使用=,直接初始化使用函数。
p442:在使用容器时,使用insert或者push成员函数是拷贝初始化,但是使用emplace是直接初始化。拷贝构造函数的参数必须是引用类型。使用explicit时,必须使用显式构造直接初始化。接受大小的构造函数是explicit的,所以vector
是错误的。编译器可以绕过拷贝构造函数。
p443:与拷贝构造函数一样,如果没有自己定义的赋值构造函数,编译器会合成一个赋值构造函数。
p444:赋值运算符通常应该返回一个指向左侧运算对象的引用。
p445:在一个析构函数中,首先执行函数体,之后销毁成员。成员按照初始化顺序的逆序来进行销毁。
p446:当指向一个对象的引用或指针离开作用域时,析构函数不会执行。成员是在析构函数体之后隐含的析构阶段中被销毁的。
p447:为一个类定义了一个析构函数,几乎肯定的是需要一个拷贝构造函数和一个拷贝赋值函数。自定义的析构函数配合合成版本的拷贝构造函数和拷贝赋值运算符会出现错误。
p448:拷贝构造函数与拷贝赋值函数必须共存,但是两者存在时不意味着也需要析构函数。
p449:当在类内使用=default修饰成员的声明时,合成的函数将隐式地声明为内联的。如果不希望时内联的,应该只对类外定义使用=default。在新标准下,我们可以通过将拷贝构造函数和拷贝赋值运算符定位为删除的函数来阻止拷贝。在函数的列表后面加上=delete来指出我们希望将它定义为删除的。
p450:与=default不同=delete必须出现在函数的第一次声明的时候。与=delete的另一个而不同之处是我们可以对任何函数指定=delete。一般情况下不能删除析构函数。析构函数被删除的类型可以分配这种类型的对象,定义指针指向相应的内存,但是不能释放这部分内存,而且不能定义该类型的变量。
p451:如果一个类有数据成员不能默认构造、拷贝、复制、或销毁,则对应的成员函数将定义为删除的。一个成员有删除的或不可访问的析构函数会导致合成的默认和拷贝构造函数被定义为删除的。如果一个类有const成员,则它不能使用合成的拷贝赋值运算符。对于有引用成员的类,合成拷贝赋值运算符被定义为删除的。通过声明private的拷贝构造函数,可以预先阻止任何拷贝该类型对象的企图。
p454:大多数赋值运算符组合了析构函数和拷贝构造函数的工作。一个好的模式是,先拷贝到局部对象中,销毁左侧对象成员,之后将局部对象拷贝到赋值符左侧的对象的成员中。要保证将一个对象赋予自身也能正常工作。
p456:赋值运算符必须能够正确处理自赋值。
p458:如果一个类的成员有自己类型特定的swap函数,调用std::swap就是错误的了。会进行不必要的拷贝。
p466:X++为X的后置递增。
p467:destroy函数会运行元素的析构函数。
p471:标准库容器、string和shared_ptr类型既支持移动也支持拷贝。IO类和unique_ptr类可以移动但不能拷贝。使用&&表示右值引用,特性是所引用的对象将要被销毁,该对象没有其他用户。左值表示一个对象的身份,右值表示的是对象的值。int &&rr2 = i * 42;
p472:变量是左值,因此不能将右值引用绑定到一个变量上,即使这个变量是右值引用类型也不行。int &&rr3 = std::move(rr1);可以
通过绑定一个名为move的新标准库函数来获得绑定到左值上的右值引用,此函数定义在utility中。我们可以销毁一个以后源对象,也可以赋予它新值,但是不能使用一个移后源对象的值。使用move的代码应该使用是std::move而不是move。调用move就意味着承诺:除了对对象赋值或销毁它之外,不再使用它。
p474:如果分配过程使用移动构造函数,则在移动部分抛出异常后会产生问题,对于拷贝构造函数则不会。如果希望在重新分配内存这种情况下对我们自定义的类型的对象进行移动而不是拷贝,就必须告诉标准库移动构造函数是可以安全使用的。通过将移动构造函数标记为noexcept来告诉标准库不会产生异常。
p475:在移动操作之后,移后源对象必须保持有效的、可析构的状态,但是用户不能对其进行假设。如果一个类定义了自己的拷贝构造函数、拷贝赋值运算符、或者析构函数,编译器就不会为他合成移动构造函数和移动赋值运算符了。如果一个类没有移动操作,通过正常的函数匹配,类会使用对应的拷贝操作来代替移动操作。
p476:只有当一个类没有定义任何自己版本的拷贝控制成员,且它的所有数据成员都能移动构造或移动赋值时,编译器才会为它合成移动构造函数或移动赋值运算符。什么时候将合成的移动操作定义为删除的函数遵循与定义删除的合成拷贝操作类似的原则。
p477:如果类定义了一个移动构造函数和一个移动赋值运算符,则该类的合成拷贝构造函数和拷贝赋值运算符会被定义为删除的。
p478:对象移动
p490:对于一个运算符来说,它或者是类的成员,或者至少含有一个类类型的参数。当作用于内置类型的时候,无法改变运算符的含义。对于一个重载的运算符来说优先级和结合律与对应的内置运算符保持一致。
p491:data1 + data2; operator+(data1, data2);
data1 += data2; data1.operator+=(data2);
逻辑与、逻辑或、逗号运算符的运算对象求值顺序规则无法保留下来。
p492:通常情况下不应该重载逗号、取地址、逻辑与和逻辑或运算符
p493:重载的赋值、下标、调用()和箭头运算符必须是成员函数。具有对称性的运算符可能转换任意一端的运算对象,例如算数、相等性、关系和位运算,这些应该是非成员函数。例如string s = "world"; string t = s + "!"; string u = "hi" + s;
p495:如果希望为类自定义IO运算符,则必须将其定义成非成员函数。IO运算符通常需要读写类的非公有数据成员,所以IO运算符一般被声明为友元。
p496:if (is) item.revenue = item.units_sold * price;
当读取操作发生错误的时候,输入运算符应该负责从错误中恢复。
p497:如果类同时定义了算术运算符和相关的复合赋值运算符,则通常情况下应该使用复合赋值来实现算术运算符。相等运算符和不等运算符应该把一个的工作委托给另一个。
p499:标准库vector类还定义了一种接受花括号内的元素列表作为参数的运算符。StrVec &operator=(std::initializer_list
p500:赋值运算符重载应该定义成类的成员,复合赋值运算符一般情况下也应该这样。这两类运算符都应该返回左侧运算对象的引用。
p501:如果一个类包含下标运算符,则通常会定义两个版本,一个返回普通引用,一个是类的常量成员返回常量引用。
p502:递增和递减运算符因为改变的是操作的对象的状态,所以尽量将其设定为成员函数。
p503:为了区分前置和后置,可以加上一个int型参数,这个参数不会被使用,编译器会赋值为0。StrBlobPtr& operator++();
前置 StrBlobPtr operator++(int);
后置
p504:StrBlobPtr p(a1); p.operator++(0);
调用后置 p.operator++();
调用前置
p505:重载箭头时,可以改变的是箭头从哪个对象当中获取成员,而箭头获取成员这一事实则永远不变。重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个类的对象。
p506:如果类定义了调用运算符,则该类的对象称作函数对象。
p507:for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));
PrintString使用输出流,和字符初始化,是一个函数对象。
p508:在写了一个lambda之后编译器会将lambda表达式翻译成一个未命名类的未命名对象。默认情况下lambda不能改变它捕获的变量。
p510:可以使用标准库的函数对象来搭配标准库函数来操作。例如如果要执行降序排列的话,可以传入一个greater类型的对象,该类产生一个调用运算符并负责执行待排序类型的大于运算。
sort(svec.begin(), svec.end(), greater
p511:不同类型可能拥有相同的调用形式。可以定义一个函数表用于存储指向可调用对象的指针,当程序需要执行某个特定的操作时,从表中查找该调用的函数。int add(int i, int j) { return i + j; } map
auto mod = [](int i, int j) { return i % j; };
binops.insert({"%", mod});
是错误的,每个lambda都有自己的类类型,该类型与存储binops中的值的类型不匹配。
p512:functional头文件中的function模板类型可以构造函数表。map
可以把可调用对象包括函数指针、lambda或者函数对象在内添加到这个map中。map
p513:binops["-"](10 , 5)调用minus对象的调用运算符。不能直接的将重载函数的名字存入function类型的对象中,会产生二义性。可以使用函数指针指向对象的函数,之后将函数指针插入到binops中,int (*fp)(int,int) = add; binops.insert( {"+", fp} );
或者使用lambda来消除二义性binops.insert( {"+", [](int a, int b) {return add(a, b);} } );
p514:重载、类型转换与运算符。opreator type() const;为类型转换运算符,是类的一种特殊成员函数,负责将一个类类型转换为其他类型。
p527:C++新标准允许显式标注要使用哪个成员函数改写基类的虚函数,需要在形参列表之后加上一个override关键字。动态绑定又被称为运行时绑定。在C++语言中,在使用基类的引用或指针调用虚函数的时候,将会发生运行时绑定。派生类必须在类内部对所有重新定义的虚函数进行声明。派生类可以在这样的函数之前加上virtue也可以不加。
p528:关键字virtue只能出现在类内部,不能用于类外部的函数定义。成员函数如果没有呗声明为虚函数,则其解析过程发生再编译时而非运行时。
p529:访问说明符的作用是控制派生类从基类继承而来的成员是否对派生类的用户可见。
p530:派生类到基类的类型转换,因为派生类含有基类的对应的组成部分,所以可以把派生类的对象当成基类对象使用,而且可以将基类的指针和引用绑定到派生类的基类部分上。
p531:以类名加圆括号的实参列表将帮助编译器决定应该使用哪个构造函数来初始化派生类对象的基类部分。如果没有初始化基类的话,基类将会默认初始化。
p532:要想与类的对象交互必须使用该类的接口,即使这个对象是派生类的基类部分也是这样。静态成员遵循通用的访问控制规则,如果基类中的成员是private的,则派生类无权访问它。
p533:为了避免一个类作为基类,可以使用关键字final,class NoDerived final{}
class Last final : Base{}
p534:表达式的静态类型在编译时总是已知的,它是变量声明时的类型或表达式生成的类型。动态类型则时变量或表达式表示的内存中的对象的类型,动态类型直到运行时才可知。如果表达式既不是引用也不是指针,则他的动态类型永远与静态类型一致。基类的指针和引用的静态类型可能与其动态类型不一致。
p535:即使一个基类指针或引用绑定在一个派生类对象上,我们也不能执行从基类向派生类的转换。Bulk_quote bulk; Quote *itemP = &bulk; Bulk_quote *bulkP = itemP;
基类向派生类转换时。如果基类有一个或多个更多虚拟函数,我们可以使用dynamic_cast请求一个转换,该转换的安全检查在运行时执行。某些情况下当我们知道从基到派生的转换是安全的时,我们可以使用static_cast来强制覆盖掉编译器检查工作。派生类向基类的自动类型转换只针对指针和引用有效。在将一个派生类对象赋值或初始化一个基类对象时,会调用基类的构造函数,将派生类中的基类部分拷贝、移动或赋值,派生类部分被忽略掉。
p536:和任何成员一样,派生类向基类的类型转换可能会由于访问受限变得不可行。继承体系中的大多数类仍然定义了拷贝控制成员。必须要为没有个虚函数都提供定义而不管它是否被用到,这是因为编译器无法分辨到底会使用哪个虚函数。
p537:我们把具有继承关系的类型成为多态类型。通过对象进行的函数调用将在编译时绑定到该对象所属类中的函数版本上,对这样的非虚函数的调用在编译时进行绑定。指针或者引用调用虚函数时,会在运行时解析该调用。派生类中的虚函数的返回类型必须与基类函数匹配,除当虚函数的返回类型是类本身的指针或引用时。如果D由B派生,则基类的虚函数可以返回B而派生类的对应函数可以返回D,只不过这样的返回类型要求从D到B类型转换是可以访问的。
p538:基类中的虚函数在派生类中银行地也是一个虚函数。派生类如果定义了一个函数与基类中虚函数的名字相同但是参数不同是合法的。为了避免覆盖原虚函数但是参数写错的错误,提醒编译器查错,尽量使用override关键字,来标记函数。只有虚函数才能被覆盖,final声明保证不会被覆盖。
p539:如果通过基类的引用或指针调用函数,则使用基类中定义的默认实参,即使实际运行的是派生类中的函数版本也是如此。在希望对虚函数的调用不要进行动态绑定二十强迫执行某个特定版本时,使用作用域运算符可以实现这一目的。
p540:通过在函数体的位置(即在声明语句的分号之前)书写=0便可以将一个虚函数说明为纯虚函数,不必加virtual。
p541:不能再类的内部为一个=0的函数提供函数体。不能创建抽象基类的对象。含有纯虚函数的类不能创建对象。
p542:重构负责重新设计类的体系以便将操作或数据从一个类移动到另一个类。
p543:派生类的成员或者友元只能通过派生类对象来访问基类的受保护成员。派生类对于一个基类对象中的受保护成员没有任何特权。
p544:派生访问说明符的目的是控制派生类用户(包括派生类的派生类在内)对于基类成员的访问权限。public声明后权限不变,protected将public改成protected,private将public转换为private。对于代码中的某个给定节点来说,如果基类的公有成员是可访问的,则派生类向基类的类型转换也是可访问的,反之不行。
p545:友元关系不能传递,友元关系也不能继承。每个类负责控制各自成员的访问权限。pal是base的友元,所以pal能够访问base对象的成员,这种可访问性包括了base对象内嵌在派生类对象中的情况。
p546:如果要强制改变成员的访问权限可以通过在类的内部使用using声明语句,将该类的直接或间接基类中的任何可访问成员标记出来。using声明语句中的名字的访问权限由该using声明语句之前的访问说明符来决定。using声明符出现在声明地方表示这个成员有什么样的权限。派生类只能为那些它可以访问的名字提供using声明。struct和class的默认派生运算符分别是公有和私有。struct和class的唯一差别是默认成员访问说明运算符和默认继承访问说明符。
p547:当存在继承关系时,派生类的作用域嵌套在其基类的作用域之中。
p548:我们能使用那些成员仍然是由静态类型决定的。Bulk_quote bulk; Bulk_quote *bulkP = &bulk; Quote *itemP = &bulk; bulkP->discount_policy(); itemP->discount_policy();
派生类的成员将隐藏同名的基类成员,如果要调用基类的成员可以使用::域运算符。
p549:除了覆盖继承而来的虚函数之外,派生类最好不要重用其它基类中的名字。如果内部域查找不到名字要向外部域进行查找。如果内层作用域名字与外层作用域同名则外层作用域将被屏蔽掉,即使仅仅是形参列表不一致,基类成员也会被屏蔽掉。
p550:一旦名字找到,编译器便不会再继续查找。假设基类与派生类 的虚函数接受的实参不同,则无法通过基类的引用或指针调用派生类的虚函数。
p551:实际调用的函数版本由指针的静态类型决定。覆盖重载的函数。
p552:和其他虚函数一样,析构函数的虚属性也会被继承。只要基类的析构函数是虚函数,就能确保当使用delete基类指针时将运行正确的析构函数版本。一个基类总是需要析构函数,而且他能将析构函数设定为虚函数。虚析构函数将阻止合成移动操作。
p553:类定义了析构函数则不能有合成的移动操作,一个类没有移动操作意味着它的派生类也没有。某些定义基类的方式可能导致有的派生类成员成为被删除的函数。
p554:在实际编程过程中如果基类中没有默认、构造或移动构造函数,则一般情况下派生类不会定义相应的操作。默认情况下,基类默认构造函数初始化派生类的对象的基类部分。如果要拷贝或移动基类部分,则必须在派生类的构造函数初始值列表中显式地使用基类的拷贝或移动构造函数。D(D&& d): Base(std::move(d)){}
与拷贝和移动构造函数一样,派生类的赋值运算符必须显式地为其基类部分赋值。如在派生类的赋值运算函数内使用语句Base::opsretor=(rhs);
p556:当我们创建一个对象时,需要把对象的类和构造函数的类看作是同一个。如果构造函数或析构函数调用了某个虚函数,则我们应该执行与构造函数或析构函数所属类型相对应的虚函数版本。否则这样的访问可能会使程序发生崩溃。
p567:类不能继承默认、拷贝和移动构造函数,如果派生类没有直接定义这些构造函数,则编译器将为派生类合成它们。当作用于构造函数时,using声明语句将令编译器产生代码。对于基类的每个构造函数,编译器都生成一个与之对应的派生类构造函数。using Disc_quote::Disc_quote;
一个using声明语句不能指定explicit或constexpr。如果基类的构造函数是explicit或者constexpr,则继承的构造函数也拥有相同的属性。
p558:如果基类含有几个构造函数,则除两种例外之外,大多数时候派生类会继承所有这些构造函数。因为不允许在容器中保存不同类型的元素,所以不能把具有继承关系的多种类型的对象直接存在容器中。当派生类对象被赋值给基类对象时,其中的派生类部分被切掉,因此容器和存在继承关系的类型无法兼容。
p559:如果希望在容器中存放具有继承关系的对象时,实际上存放的是基类的指针(更好的选择是智能指针)。编写Basket类。
p575:将基类的析构函数定义为虚函数的原因是为了确保在删除一个基类指针时,该指针实际指向一个派生类对象时,程序也能正确运行。protected基类的成员对于派生类的派生类也是可以访问的。抽象基类是含有一个或者多个纯虚函数的类。
p579:编译器生成的版本被成为模板的实例。
p580:在模板参数列表中typename和class没有声明区别,例如 template
除了定义类型参数,还可以在模板中定义非类型参数。绑定到非类型参数的实参必须是一个常量表达式,绑定到指针或引用非类型参数的实参必须具有静态的生存期。我们不能用一个普通局部变量或动态对象作为指针或引用非类型模板参数的实参。
p581:非类型模板参数的模板实参必须是常量表达式。函数模板可以声明为inline或constexpr的,如同非模板函数一样。inline或constexpr说明符放在模板参数列表之后,返回类型之前。模板程序应该尽量减少对实参类型的要求。在编写泛型代码的重要原则:模板中的函数参数是const的引用。函数体中的条件判断仅使用<比较运算。可能使用less来定义函数。
p582:将类定义和函数声明放在头文件中,而普通函数和类的成员的定义放在源文件中。函数模板和类模板成员函数的定义通常放在头文件中。模板直到实例化时才会生成代码。
p583:保证传递给模板的实参支持模板所要求的操作,以及这些操作在模板中能正确工作,是调用者的责任。模板函数假设实参可用符合条件。
p585:一个类模板的每个实例都形成一个独立的类,不会对其它类的成员有特殊访问权限。类模板的函数被隐式的声明为内联函数。
p586:在类外定义函数的形式:template
。
p587:默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化。
p588:当我们处于一个类模板的作用域中时,编译器处理模板自身引用时就好像我们已经提供了与模板参数匹配的实参一样。BlobPtr& operator++();
类似 BlobPtr
template
类模板和友元。
p590:一个类型别名时一族类的别名。template
template
p591:类模板的每个实例都有一个独有的static对象。类似任何其他成员函数,一个static成员函数只有在使用时才会实例化。template
Foo
ct = Foo::count();
错误。