C PRIMER学习笔记
C++Primer学习笔记
第2章变量和基本类型
1.在命令行下看main函数的返回值,Unix为(echo$?),Windows为(echo
%ERRORLEVEL%)。
2.为了兼容C语言,C++中所有的字符串字面值都由编译器自动在末尾添加一个空字
符(null)。
3.多行字面值:在一行的末尾加一反斜线符号(\)可将此行和下一行当做同一行处理。
4.C++支持两种初始化变量的形式:复制初始化和直接初始化。复制初始化语法用等
号(=),直接初始化则是把初始化放在括号中。
5.声明和定义:声明不能初始化,也不会分配空间。在C++语言中,变量必须且仅能
定义一次,而且在使用变量之前必须定义或声明变量。
6.因为常量在定义后就不能被修改,所以定义时必须初始化。非const变量默认为
extern,const变量默认非extern。
7.引用是别名:必须在定义引用时进行初始化。当引用初始化后,只要该引用存在,
它就保持绑定到初始化时指向的对象,不可能将应用绑定到另一个对象。不能定义引用类型的引用。
8.const引用是指向const的引用,可以绑定到const对象、非const对象和右值。非const
引用只能绑定到非const对象。
9.因为头文件包含在多个源文件中,所以不应该含有变量或函数的定义。但有三例外:
头文件可以定义类、值在编译时就知道的const对象和inline函数。
第3章标准库类型
1.不应该在头文件中使用using(namespace),因为头文件会影响包含它的源文件。
2.任何存储string的size操作结果的变量必须为string::size_type类型。特别重要的是,
不要把size的返回值赋值给一个int变量。(为什么我测试可以???)
3.当进行string对象和字符串字面值混合连接操作时,+操作符的左右操作数必须至少
有一个是string类型的。
4.vector:vector容器内的元素可以通过下标操作和迭代器进行访问修改,但添加元素
必须通过vector的方法insert、push_back才行。
5.const_iterator vs const的iterator:const_iterator是指迭代器指向的内容的值不能改
变,用来只读vector;const的iterator是指迭代器不能改变,很少用到。
6.任何改变vector长度的操作都会使已存在的迭代器失效。例如,再调用push_bach
之后,就不能再信赖指向vector的迭代器的值了。
7.string、vector、bitset是由标准库提供的类型,所以他们都有相应的方法;C++提供
的内置类型int、数组、指针等都没有提供方法。
第4章数组和指针
1.数组维数必须用值大于1的常量表达式定义,不允许数组直接复制和赋值。
2.指针保存0值,表明它不指向任何对象,所以删除指针后置0是个好办法。
3.C++提供了一种特殊的指针类型void*,它可以保存任何类型对象的地址。void*指针
只支持几种有限的操作:与另一个指针进行比较;向函数传递void*指针或者从函数返回void*指针;给另一个void*指针赋值。不允许使用void*指针操纵它所指向的对象。
4.指向const对象的指针和const指针:指向const对象的指针(const int*pi)不能通
过该指针修改指向对象的值;const指针(int*const pi)指针本身不能变。
指针和引用:引用是别名,必须在定义时初始化,且定义后不能再绑定到其他对象。
5.C风格字符串:char数组,且以null结束。char ca[]={'c','+','+','\o'};char*ca1=”primer”;
sizeof的时候会算上\0,strlen不会算上\0。
6.用new创建动态数组:int size=get_size();int*p=new int[size];
用delete释放空间:delete[]p;([]必须,数组的释放不同一般指针的释放)
7.int*ip[4]:声明了一个4维度的指针数组
int(*ip)[4]:声明了一个指向数组的指针,可以迭代二维数组。
第5章表达式
1.sizeof操作符的作用是返回一个对象或类型名的长度,返回值的类型为size_t。数组
ia的元素个数:int size=sizeof(ia)/sizeof(*ia);
2.逗号表达式是一组由逗号分隔的表达式,这些表达式从左向右计算。逗号表达式的
结果是其最右边表达式的值。
3.类型转换(分隐式转换和显式转换)。有四种强制类型转换符:static_cast、
dynamic_cast、const_cast、reinterpret_cast。dynamic_cast操作符用于将基类类型对象的引用或指针转换为同一继承层次中其他类型的引用或指针。const_cast用于转换掉表达式的const性质。static_cast能显式完成编译器隐式执行的任何类型转换。
reinterpret_cast通常为操作数的位模式提供较低层次的重新解释。
旧式强制转换:type(expr)或(type)expr两种形式。
第6章语句
1.Switch case:case标号必须是整型常量表达式(case3.14:和case ival:就不行)。在
switch内部要定义常量,必须用块结构(防止影响其他case)。
2.标准异常类定义在四个头文件中:
i.exception头文件定义了最常见的异常类,它的类名是exception。这个类只通知
异常的产生,但不会提供更多的信息。
ii.stdexception头文件定义了几种常见的异常类:runtime_error,range_error, overflow_error,underflow_error,logic_error,domain_error(参数的结果值不存在),
invalid_argument,length_error,out_of_range
iii.new头文件定义了bad_alloc异常类型,提供因无法分配内存而由new抛出的异常。
iv.type_info头文件定义了bad_cast异常类型,dynamic_cast失败抛出的异常。
3.预处理器定义了四种在调试时非常有用的常量:
__FILE__文件名
__LINE__当前行号
__TIME__文件被编译的时间
__DATE__文件被编译的日期
第7章函数
1.形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值;
如果形参为引用类型,则它只是实参的别名。
2.如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为const引用。
3.引用指针的形参:void ptrswap(int*&v1,int*&v2)
4.c++程序员倾向与通过传递指向容器中需要处理的元素的迭代器来传递容器。
5.数组当形参时,非引用(起始是传递指针,能改变数组的内容)会忽略数组第一维。
通过引用传递数组:void printValue(int(&arr)[10])(括号是必须的)
6.理解返回引用至关重要的是:千万不能返回局部变量的引用。
7.既可以在函数声明也可以在函数定义中指定默认实参。但是,在一个文件中,只能
为一个形参指定默认实参一次。通常,应在函数声明中指定默认实参,并将该声明放在合适的头文件中。
8.内联函数应该在头文件中定义,这一点不同于其他函数。在头文件中加入或修改内
联函数时,使用了该头文件的所有源文件都必须重新编译。
9.指向函数的指针:bool(*pf)(const string&,const sting&)(*pf两侧的圆括号是必需
的)。函数指针只能通过同类型的函数或函数指针或0值常量表达式进行初始化或赋值。如bool lengthCompare(const sting&,const string&);pf=lengthCompare;调用时可以:pf(“hi”,”bye”)或(*pf)(“hi”,”bye”)。
第8章标准IO库
1.I/O对象不可复制或赋值,故函数参数和返回值只能是I/O对象的指针或引用。
2.标准库提供了三种类型的流:iostream,fstream,stringstream,多次读取时要注意流
状态的清除。
3.刷新缓存区:使用endl(插入换行符),flush(不添加任何字符),ends(插入null),
使用unitbuf操作符(cout< 4.如果程序员需要重用文件流读写多个文件,必须在读另一个文件之前调用clear清除 该流的状态。(其他IO流也需要clear清除流状态) 5.字符串流stringstreasm可以提供字符串的转化和格式化。 const使用总结 1.const修饰变量,表示该变量为常量,必须在定义是初始化,之后不能修改它的值。 2.const修饰指针: ?const int*ip=&val:ip是指向const对象(int)的指针,即不能通过(*ip)修改val的值(如果val不是const常量,可以通过其他方式修改val的值); ?int*const ip=&val:ip是指向int对象的const指针,即不能修改ip的值,但(*ip)的值可以修改; ?const int*const ip=&val:ip是指向const对象的const指针。 3.Const修饰函数:const int&op(const int)const ?const修饰返回值,表示返回值不能修改,只有在返回引用类型时才有效(不能返回局部对象的引用); ?const修饰参数,表示该参数不能在函数中修改。需要注意的是参数是指针的情况,到底是const指针还是指向const对象的指针; ?const修饰函数:const修饰类的成员函数时,表示该函数不能修改类的成员变量。 4. 第9章顺序容器 1.关联容器和顺序容器的本质区别在于:关联容器通过键(key)存储和读取元素,而 顺序容器的元素排列次序与元素值无关,是由元素添加到容器里的次序决定。 2.标准库定义了三种顺序容器类型:vector,list和deque。vector是连续存储的,能支 持快速随机访问;list不需要连续存储,所以能在中间快速插入和删除;deque是双端队列。标准库还提供了三种容器适配器:后进先出的stack、先进先出的queue和有优先级管理的priority_queue。stack可以建立在vector、list、deque上,queue要能提供push_front操作,不能建立在vector上,priority_queue要求提供随机访问能力,只能建立在vector或deque上。 3.定义元素是容器的容器时,必须用空格隔开两个相邻的>符号,以示这是两个分开的 符号,否则,系统会认为>>是单个符号,为右移操作符,并结果导致编译时错误;example:vector 4.容器元素类型必须满足两个约束: ?元素类型必须支持赋值运算 ?元素类型的对象必须可以复制 引用不支持一般意义的赋值运算,IO标准库类型不支持赋值和复制操作,所以不 能创建存储他们的容器。 5.顺序容器上的操作 ?初始化(适用于关联容器) i.C ii.C c(c2)创建容器c2的副本c,两个容器的类型和元素类型都必须相同 iii.C c(b,e)创建c,其元素是迭代器b和c标示的范围内元素的副本,只要迭代器存储的值能转化为容器的元素即可,不需要容器类型相同。 iv.C c(n,t)/C c(n)创建有n个值为t(或默认值)的容器c,只适用于顺序容器。 ?向容器内添加元素 有三类函数push_back()、push_front()、insert(),用insert()在指定位置插入单个元素时,返回指向新元素的迭代器,其他的都返回void ?容器大小操作 size(),max_size(),empty()resize()。resize()能删除多出来的元素。 ?访问元素 用迭代器迭代容器或者使用下标操作(list不支持下标操作) ?删除元素 erase()(返回指向删除元素后的迭代器),clear(),pop_back(),pop_front()(pop_操作只删除元素,不返回删除的元素值,返回void) ?assign赋值操作,swap交换容器操作(不会破坏迭代器) 6.list vs vector deque ?list的元素不是连续存储,所以不支持随机访问,所以不支持下标操作([])和at()操作,其上的迭代器不支持+n操作和大小比较操作(只支持等于,不等操 作) ?vector不支持前端操作(push_front()pop_front()) 7.修改容器时,会使容器上的迭代器失效。vector和deque连续存储,在中间插入删 除元素时,会重组织存储。 8.string类型不支持以栈方式操纵容器;string支持的其他操作:substr append replace find rfind find_frist_of compare。 第10章关联容器 1.标准库提供的关联容器有:map,set,multimap,multiset。map存储键值对,set 存储单个键,multi支持同一个见多次出现。 2.pair类型和make_pair函数:对map上的迭代器解引用是pair 3.map、set上的键类型,唯一的约束就是必须支持<操作符,至于是否支持其他的关 系或相等运算,则不做要求。 4.map,set,multimap,multiset ?都支持insert,count,find,erase操作以及lower_bound,upper_bound,equal_range 操作(注意他们的返回值),初始化见顺序容器; ?只有map支持下标操作,而且与下标访问数组或vector的行为截然不同:用下标访问不存在的元素将导致在map容器中添加一个新元素,它的键即为该下标 值,值为值类型的默认初始化值。 第11章泛型算法 1.泛型算法中,所谓“泛型“指的是两个方面:这些算法可作用与各种不同的容器类型, 而这些容器又可以容纳多种不同的元素。泛型算法必须包含 2.泛型算法的形参模式:alg(beg,end,beg2,end2,other parms)、 beg,end标记第一个范围,这两个参数泛型算法都有;beg2,end2标记第二个范围,可能没有这个两个参数;parms表示其他算法需要的值或谓词。 3.算法不直接修改容器的大小,如果需要添加和删除元素,则必须使用容器操作。 4.泛型算法的分类 ?只读算法:只读输入范围内的元素,而不会写这些元素。find(),accumulate(), find_first_of()是只读算法; ?写容器元素算法:将数据写入第一或第二输入范围。fill(),fill_n(),copy(),replace()都是这类算法。直接将元素写入目标很危险,因为泛型算法不会调用容器提供 的操作(如insert)(相当于用下标读vector的内容,再修改),所以很类算法一 般要和插入迭代器配合使用。有三种插入迭代器:back_inserter,创建使用 push_back实现插入的迭代器;front_inserter,使用push_front实现插入(不能用 在vector上);inserter,使用insert实现插入。 vector replace_copy(ilist.begin(),ilist.end(),inserter(ivec,ivec.begin()),0,42); ?对容器元素重新排序的算法:sort(),unique() 5.五种迭代器 ?输入迭代器:读,不能写;只支持自增运算。istream_iterator是输入迭代器 ?输出迭代器:写,不能读;只支持自增运算。ostream_iterator是输出迭代器 ?前向迭代器:读和写,只支持自增运算。replace算法需要前向迭代器 ?双向迭代器:读和写,支持自增和自减运算,map、set、list提供双向迭代器。 Reverse算法需要双向迭代器 ?随机访问迭代器:读和写,支持完整的迭代器算术运算,string、vector和deque 提供双向迭代器。 6.list容器特有的算法 list上提供双向迭代器,故很多需要随机访问迭代器的算法不能在其上运行,故标准库为list容器定义了更精细的操作集合,如merge(),remove(),sort(),reverse(),splice(),unique() 第12章类 1.在类内部定义的成员函数,将自动作为inline处理。也可以显式的将成员函数声明 为inline。inline成员函数的定义必须在调用该函数的每个源文件中是可见的,故inline函数的定义通常放在定义该类的头文件中。 2.类声明:为了在类定义之前使用它,我们可以先声明它,此时该类称为不完全类型。 不完全类型只能以有限访问时用。不能定义该类型的对象。不完全类型只能用于定义指向该类型的指针及引用,或者用与声明(而不是定义)使用该类型作为形参类型或返回类型的函数。类的声明一般用来编写相互依赖的类。 3.可以把数据成员声明为mutable(不能同时为const修饰),mutable数据成员可以在 const成员函数中修改。 4.当成员函数的返回类型在类中定义时,而且是在类外定义的,则定义时需要使用完 全限定名。 5.构造函数不能声明为const。必须对任何const或引用类型成员以及没有默认构造函 数的类类型的任何成员使用初始化式。使用构造函数初始化列表初始化,数据成员被初始化的次序就是类定义成员的次序,而不是在构造函数初始化列表中的次序。 6.使用默认实参的构造函数能减少代码重复。 7.只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。 8.隐式类类型转换:可以用单个实参调用的构造函数,定义了从形参类型到该类类型 的一个隐式转换。可以将构造函数声明为explicit,来防止隐式转换。explicit只能用 于类内部声明上(不能用于定义上)。 9.注意友元声明的顺序:声明类A,把类A声明为B的友元(在类B的定义中),定 义类A(需要用到B的定义)。 10.static类成员 ?static成员函数不是任何对象的组成部分,故没有this形参,可以直接使用类的static成员,但不能直接使用非static成员,不能被声明为const和虚函数。可以 使用类作用域操作符从类中直接调用static成员,也可以通过对象调用。 ?static数据成员必须在类定义体的外面定义(正好一次),不能通过类构造函数初始化,而是应该在外面定义时初始化。static关键值只能用于类定义体内部的声 明中,定义时不能标示为static。const static数据成员可以在类的定义体中初始 化,但仍必须在类的定义体外部定义(不必再指定初始化值)。 ?static成员不同与非static成员的使用:static数据成员的类型可以是该成员所属的类类型(Class Bar{static Bar mem;}),非static成员被限定声明为其自身类对 象的指针或引用(因为这时类还没定义完,相当于类的向前声明);同样,static 数据成员可以作默认实参,非static不能(因为它不能独立于所属对象)。 第13章复制控制 1.复制控制函数包括复制构造函数、赋值操作符、析构函数 ?复制构造函数:只有单个参数,而且该形参是对本类类型对象的引用(常用const 修饰),如T(const T&)。如果没有定义复制构造函数(即使定义了其他构造函数), 编译器会为我们合成一个——执行逐个成员初始化,将新对象初始化为原对象 的副本。所以为了防止复制,类必须显式声明其复制构造函数为private。不允 许复制的类对象只能作为引用或指针传递给函数或从函数返回,也不能用作容 器的元素。 ?赋值操作符:就是重载’=‘操作符,如T&operator=(const T&)(注意返回值为类的引用,因为=返回左操作符)。合成赋值操作符,将右操作数对象的每个成员 赋值给左操作数对象的对应成员,还能对数组赋值。复制和赋值常一起使用。 ?析构函数:构造函数的互补,释放对象获得的资源。合成构造函数不会自动删除指针对象所指向的对象。如果类需要析构函数,则它也需要赋值操作符和复 制构造函数,这是一个有用的经验法则,常称为三法则(rule of three)。析构函 数与复制构造函数或赋值操作符之间的一个重要区别是:即使我们编写了自己 的析构函数,合成析构函数仍然运行。 2.管理指针成员:包含指针的类需要特别注意复制控制,原因是复制指针时只复制指 针中的地址,而不会复制指针指向的对象。 ?默认(合成)复制/赋值管理指针,指针共享同一对象,可能出现悬垂指针。 ?用智能指针管理指针复制/赋值:新定义一个类管理指针,并增加一个计数功能,当复制/赋值时计数加一,对象销毁时(析构函数)减一,当计数为0时,释放 指针。 ?定义值型类:给指针成员提供值语义,创建一个新对象。 3.复制/赋值操作要考虑自己复制/赋值自己。 第14章重载操作符与转换 1.重载操作符不能创建任何新的操作符,不能改变操作符的优先级、结合性或操作数 数目。 2.重载操作符的定义 i.形式是保留字operator后接许定义的操作符符号,如 item operator+(const item&,const item&) ii.重载操作符必须具有至少一个类类型或枚举类型的操作符。这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。 iii.重载操作符可以定义为类的成员函数或非成员函数 i.作为类成员的重载函数,其形参看起来比操作数数目少1。作为成员函数的 操作符有一个隐含的this形参,限定为第一个操作数(故输入输出操作符不 能重载为类成员) ii.操作符定义为非成员函数时,通常必须将他们设置为所操作类的友元,以访问类的私有变量。 3.重载操作符的设计 i.不要重载具有内置含义的操作(逗号、取地址、逻辑与、逻辑或等操作通常不 重载) ii.重载操作符是为了使用方便,当一个重载操作符的含义不明显,或操作很少时,没必要重载操作符,定义普通函数就行了。 iii.选择成员或非成员的实现:赋值(=)、下标([])、调用(())、成员访问(->)、转换操作符等操作必须定义为成员;输入和输出操作符必须定义为非成员;对 等的操作符,如算法操作符、相等操作符、关系操作符和位操作符等对称的操 作符,最好定义为非成员;改变对象状态或与给定类型紧密联系的一些操作符, 如自增、自减和解引用,通常定义为类成员。 4.输出操作符通常应进行最小限度的格式化,不应该输出换行符;输入操作符必须处 理错误和文件结束的可能性,如果可能,要确定错误回复措施,保证数据是内在一致的。 5.重载操作符需要注意返回值的类型:输入、输出操作符返回对第一个参数(流对象) 的引用;加法(+)操作符返回新建的对象,自增、自减对象也是返回新建的对象; 复合操作符、赋值操作符返回第一个操作数的引用;下标([])操作符返回对应下标内容的引用;解引用(*)返回对象的引用;箭头(->)操作符一般返回指向对象的指针。 6.读且返回引用的操作符,如下标、箭头、解引用等,应该提供const版本和非const 版本 7.自增、自减操作符:为了区别前缀和后缀,后缀式操作符函数接受一个额外的(无 用的)int型形参,如item operator++(int)为后缀式。 8.调用操作符和函数对象 i.调用操作符的定义:int operator()(int val),调用看起来像个函数调用: int value=item(10)。定义了调用操作符的类,其对象称为函数对象,函数对象比函数更灵活,因为它们可以改变成员变量(即函数的参数) ii.标准库定义了一组算术、关系、与逻辑函数对象类,包含在functional头文件中。 9.标准库定义的函数对象 i.使用:他们都是模板类,plus ii.分为一元函数对象(接受一个参数)和二元函数对象 iii.标准库还定义了函数适配器来特化和扩展函数对象。绑定器:bindlst和bind2nd,绑定一个参数到函数对象的第一个或第二个参数;求反器:not1和not2,求反 一元或二元函数对象。not1(bind2nd(less_equal 10.转换操作符:就是定义从类类型的转换(到类类型的转换是构造函数) i.通用形式:operator type()(通常应定义为const),返回一个type类型的对象(虽 然没有返回值),这样类型就可以(默认)转换为type类型。 ii.类类型转换后不能再跟另一个类类型转换。 class A{operator B();}class B{operator C();} A a: B b; C c;不能直接c=a,应该分成两步:b=a;c=b iii.转换操作符使用好了能简洁代码,但特别容易引起二义性。下面几条经验规则会有所帮组: i.不要定义相互转换的类,即如果类Foo具有接受类Bar的对象的构造函数, 不要再为类Bar定义到类型Foo的转换操作符,否则Foo foo=bar有二义性ii.避免到内置算术类型的转换。集体而言,如果定义了到算术类型的转换,则(1)不要定义接受算术类型的操作符的重载版本,让转换去完成该功能(2) 不要定义转换到一个以上算术类型的转换,让标准转换提供到其他算术类型 的转换。 第15章面向对象编程 4.虚函数:基类希望派生类重新定义的函数定义为virtual。除了构造函数外,任意非 static成员函数都可以是虚函数(一般需要定义虚析构函数)。保留字virtual只在类内部的成员函数声明中出现,不能用在类定义体外部出现的函数定义上。派生类型必须对想要重定义的每个继承成员进行声明,且必须与基类中的定义方式完全匹配(只有一个例外,返回对基类型的引用或指针,可以变为返回派生类型的引用或指针)。一旦函数在基类中声明为虚函数,它就一直为虚函数。 5.动态绑定:动态绑定使编译器能够在运行时决定使用基类中定义的函数还是派生类 中定义的函数。要触发动态绑定,必须满足两个条件:第一,只有指定为虚函数的成员函数才能进行动态绑定;第二,必须通过基类类型的引用或指针进行函数调用。 6.派生类:定义派生类形式class classname:access-label base-class ?(派生类对基类)访问权限:public和private跟其他非继承对象一样,关键是protected。protected成员可以被派生类对象访问,但不能被该类型的普通用户访 问。此外,protected还有另一重要性质:派生类只能通过派生类对象访问其基 类的protected成员,派生类对其基类类型对象的protected成员没有特殊访问权 限。 ?成员在派生类中的访问级别:是由成员在基类中的级别,和继承方式access-label 共同决定的。如果要恢复在基类中的访问级别(只能是public级别),则用using base-class::(成员名)。 ?public派生类继承基类的接口;使用private或protected派生的类不继承基类的接口,这些派生通常称为实现继承。 7.友元关系不能继承。 8.即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。 9.通过基类的引用或指针调用函数,首先在基类中找:如果函数非虚函数,则直接调 用基类的该函数;如果为虚函数,则看派生类是否重新定义了虚函数,如果定义了,则调用派生类的方法,不需要管派生类是否进行了其他重载(会屏蔽基类的函数),这与通过对象调用不同。 10.纯虚函数:在函数形参表后面写上=0以指定纯虚函数。含有(或继承)一个或多个 纯虚函数的类是抽象基类,不能创建抽象类型的对象。 11.容器与继承:不应该用容器保存基类类型的对象,因为这样会把派生类中多余的成 员切掉,应该保存基类类型的指针(引用不能一般意义的赋值,不能保存在容器中)。 指针的复制和赋值需要特别小心,句柄类就是用来存储和管理基类,以安全的进行指针的复制和赋值。 第16章模板与泛型编程 11.泛型编程就是以独立于任何特定类型的方式编写代码。 12.模板定义:template template后。模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参,非类型模板实参必须是编译时常量表达式。 13.模板形参不能为空。模板形参的名字不能在模板内部重用。声明必须指出函数或类 是一个模板(带上template)。 14.用typename在模板定义内部指定类型:如T::Y既可能是类型也可以是数据成员, 为了显式指定为类型,可以使用:typename T::Y。 15.类型形参的实参受限转换:类型为模板形参的那些实参转换受限,类型必须完全匹 配。 16.函数模板的显式实参:当模板实参推断不能确定模板类型时,需要显式指定模板形 参所用的类型或值。template 17.标准C++为编译模板代码定义了两种模型:包含编译模型和分别编译模型。包含编 译模型:在声明模板的头文件中添加一条#include,引入包含相关定义的源文件。分别编译模型:定义模板时,在template之前包含关键字export实现。 18.在类外定义类的成员函数的开头应该是:template Queue 19.类模板中的友元声明 i.普通友元:以非模板函数或非模板类为模板类的友元,跟普通的一样 ii.一般模板友元关系: template template template } iii.特定的模板友元关系 tempalte friend class Fool2 friend class Fool friend void function } 20.模板特化:指定一个或多个模板形参的实际类型或实际值。 i.完全特化 (函数)template<> int compare ii.类模板部分特化 template 第17章用于大型程序的工具 1.栈展开:用于描诉在查找catch捕获抛出异常的函数过程。在进入相应catch之前, 撤销在异常之前构造的局部对象。 2.重新抛出:一个空的throw——没有指定表达式的throw。只有捕获子句或者从catch 直接或间接调用的函数中的重新抛出才有效,其效果是将接到的异常对象重新抛出。 3.捕获所有异常的catch子句为catch(...)。如果catch(...)与其他catch子句结合使用, 它必须是最后一个,否则,任何跟在它后面的catch子句都将不能被匹配。 4.函数测试块:为了处理来自构造函数初始化的异常,必须将构造函数编写为函数测 试块。 T::T() try:val(0),str(““) {}catch(){} 5.资源分配即初始化:来封装资源的分配和释放,可以保证正确释放资源。这一技术 常称为“资源分配即初始化“,简称RAII。 6.auto_ptr类:一个库类模板,提供对动态分配对象的异常安全的访问。不能将auto_ptr 对象绑定到数组或变量指针(auto_ptr释放资源,会破还该指针,一般新建一个指针给auto_ptr,如auto_ptr 7.异常说明:用于函数声明定义上,指出函数抛出什么(如果有)异常类型。void print(string)throw(runtime_error)。空列表表示函数不抛出异常,没有异常说明的函数可以抛出任何异常。派生类的异常说明不能比基类还多,不能增加异常抛出。 8.命名空间:命名空间可以是不连续的,可以在几个部分中定义,在程序的不同部分, 命名空间可以是打开的、关闭的和重新打开的。 9.未命名空间:没有命名的命名空间。与其他命名空间不同,未命名的命名空间的定 义局部于特定文件,从不跨越多个文本文件。未命名的命名空间中定义的名字可以无须使用作用域操作符而直接访问。 10.命名空间成员的使用,三种访问:using声明(using std::cout);命名空间别名 (namespace s=std);using指示(using namespace std;)。 11.接受类类型形参(或类类型指针及引用形参)的且与类本身定义在同一个命名空间 中的函数(包括重载操作符),在用类类型对象(或类类型的引用及指针)作为实参的时候是可见的。如getline(std::cin,s),getline()跟cin在同一命名空间,故其可见。 12.虚继承:使用virtual继承的基类。效果:使继承多个虚基类的派生类中只出现一个 虚基类。如B虚继承A,C虚继承A,D继承B、C,则由于虚继承D中只有一个 A基类。虚基类有最低层派生类(即D)的构造函数初始化,且最先初始化(在B、C中不再初始化A)。 第18章特殊工具与技术 1.C++中的内存分配,两种原始内存分配和释放的方法 ?allocator类:allocator可以实现内存分配与对象构造分离开。 ?标准库中的operator new和operator delete,我们常用的new、delete就是调用的他们。 2.运行时类型识别(RTTI),运行时直接询问对象的动态类型。RTTI只适用于定义了 虚函数的类(且是指针或引用),没有定义虚函数的类型的类型信息是可用的但反映静态类型。 ?typeid操作符:返回对象的实际类型。typeid(e),e是任意表达式或者类型名。 ?dynamic_cast操作符:将基类类型的指针或引用安全的转换为派生类型的指针或引用。 3.成员指针:用来获取类特定成员的指针。成员指针包含类的类型以及成员的类型。 class T{string content;},string T::*ps_content=&T::content; 4.联合:类形式的聚合类型,它可以定义多个数据成员,但在任意点只有一个成员可 以具有值。联合的成员必须为简单类型:内置类型,复合类型,没有定义构造函数、析构函数和赋值操作符的类类型。union不能具有静态数据成员或引用成员。union 可以定义成员函数,包括构造函数和析够函数。但是,union不能作为基类使用。 5.局部类:在函数体内部定义的类称为局部类。局部类只在定义它的函数内部可见, 类的所有成员必须定义在类定义体内部。局部类不能有静态成员,局部类成员不能访问外围函数中定义的局部变量,它们可以使用外围函数中定义的类型名、静态变量和枚举成员。 6.C++的三个固有的不可移植的特征:位域、volatile限定符和链接指示。 ?位域:有符号或无符号整型类成员,它指定了分配给成员的维数。 class File{ int mode:2; int modified:1; int next:29; } ?volatile:类型限定符,告诉编译器可以在程序的直接控制之外改变一个变量。 它告诉编译器不能执行某些优化的信号。 ?链接指示:extern“C”,在C++程序中调用其他程序设计语言编写的函数。