四年前读了第三版,感觉收获颇丰。
今次再读第四版,摘录一些知识点。
第一章:
Windows命令行下编译:cl -GX propName.cpp
运行后查看main函数返回值: echo %ERRORLEVEL%
第二章:
1.长字符串分行方式:(例子)
char str1[] = "Test string "
"a line stringssdv";
char str2[] = "Test string /
a line stringssdv";
str1:每行用“”括起
str2:每行的行尾加/,后面不允许有任意字符,包括空格和注释,下一行必须从首位置起
2.已初始化的extern声明被当做定义。
3.头文件不应该含有定义,下面三个列外:类,值在编译时就已知的const对象和inline函数。
第三章
1.标准库类型string:
#include<string>
using std::string;
常用操作:
s.empty() s.size() s[n] s1+=s2 s1=s2 s1==s2 (!=,<,<=,>,>=)
用getline读取整行文本:
string line
while( getline(cin,line))
cout<<word<<line;
2.字符处理函数在头文件cctype中定义:
如:isalnum(c)//c是字母或数字 isalpha(c)//c是字母 isdigit(c)//c是数字
3.标准库类型vector:
#include<vector>
using std::vector;
常用操作:
v.empty() v.size() v.push_back() v[n] v1=v2 v1==v2(!=,<,<=,>,>=)
4.迭代器iterator
每种容器类型都定义了自己的迭代器类型,重要操作:begin和end
vector<int>::const_iterator its; 该迭代器不能改写改变its指向元素的值,只能进行读取,但自身可以改变。
const vector<int>::iterator its; 迭代器its本身的值不能改变,但可以改变its指向元素的值。
5.标准库bitset
#include<bitset>
using std::bitset;
string对象和bitset对象之间是反向转化的:string对象的最右边字符用来初始化bitset对象的低阶位(即下标为0的位)。
常用操作:
b.any() b.none() b.count() b.size() b.set() b.reset() b.flip()等
第四章
1.尽量避免使用指针和数组,采用vector类型和迭代器取代一般的数值,采用string类型取代C风格的字符串。
2.typedef string *pstring;
const pstring cstr;
等同于: string *const cstr;
第五章
1.动态创建对象的默认初始化
如果不显式提供显示初始化,对于类类型的对象,用该类的默认构造函数初始化,而内置类型的对象则无初始化。
如:
int* pi= new int; //pi指向一个没有初始化的整形
int* pi= new int(); //pi指向一个初始化为0的整形
2.显式类型转换
dynamic_cast: 用于结合继续和运行时类型识别
static_cast: 编译器隐式转换的任意类型都可以由static_cast显示转换。
const_cast: 将const对象类型转换为相应的非const类型的强制转换
reinterpret_cast:将操作数内容解释为另一种不同的类型。这种强制转换本质上依赖于机器,而且非常危险
第六章
1.异常处理
throw 表达式:中断当前执行路径的表达式。每个throw 表达式都会抛出一个对象,并将控制转移到最近的可处理该类型的catch子句。
标准库异常类:定义在exception头文件中,包括 exception,runtime_error等。
2.预处理调试
__FILE__ 文件名
__LINE__ 当前行号
__TIME__ 文件被编译的时间
__DATA__ 文件被编译的日期
第七章
1.数组形参
一般形式: void f(int *); void f( int []); void f(int [10]);
通过引用传递数组:void f(int (&arr)[10]); //arr是一个到含有10个int型 的数值的引用,(&arr)两边的括号是必须的
多维数组的传递:void f(int (matrix*)[10], int rowsize ); //matrix为指向含有10个int型元素数组的指针
void f(int matrix[][10] , int rowsize ); //与上述函数声明等价
2.内联函数(inline):内联函数应该在头文件中定义。
3.const成员函数:const对象,指向const对象的指针或引用只能用于调用其const成员函数。
4.指向函数的指针
用typedef简化函数指针的定义:typedef bool (*cmpFun)(const string& , const string& );
第八章
1.标准IO库:IO对象不可复制和赋值。
2.条件状态:用来标记给定的IO对象是否处于可用状态,具体见P247。
3.如果需要刷新所有输出,最好使用unitbuf操作符。
4.文件的输入输出:ifstream,ofstream,fstream。
5.字符串流:istringstream,ostringstream,stringstream。
第九章
1.顺序容器:vector, list, deque(双端队列)
2.必须用空格隔开两个相邻的>符号,以示这个两个分开的符号,如:vector< vector<string> > lines;
3.list容器的迭代器既不支持算术运算(加法和减法),也不支持关系运算(<=,<,>=,>),他只提供前置和后置的自增、自减运算以及相等不等运算。
4.容器适配器:strack(后进先出栈):可以建立在vector, list, deque之上
queue(先进先出队列):只能建立在list之上
priority_queue(有优先级管理的队列):只能建立在vector, deque之上
第十章
1.关联容器:map,set,multimap,multiset , 均按键值有序
2.pair类型:pair<T1,T2> p(first,second); p = make_pair(first,second);
3.使用下标访问map与使用下表访问数组或vector的行为截然不同,用下标访问不存在的元素将导致在map容器中添加一个新的元素,他的键即为该下标值。
第十一章
1.泛型算法
谓词:其返回值可转换为bool值的函数,通常被泛型算法用于检查元素,标准库所使用的谓词函数不是一元(需要一个实参)的就是二元的(需要两个实参)。
2.迭代器
插入迭代器,iostream迭代器,反向迭代器
第十二章
1.像其他inline一样,inline成员函数的定义必须在调用该函数的每个源文件中是可见的,不在类定义体内定义的inline成员函数,其定义通常应放在有类定义的同一头文件中。
2.不能从const成员函数返回指向类对象的普通引用,const成员函数只能返回*this作为一个const引用。
3.可变数据成员(mutable):可变数据成员永远都不能是const,甚至当它是const对象的成员时也如此。因为,const成员函数可以改变mutable成员,要将数据成员声明为 可变的,必须将关键字mutable放在成员声明之前。
4.不管成员是否在构造函数初始化列表中显示初始化,类类型的数据成员总是在初始化阶段初始化。初始化发生在计算阶段开始之前。没有默认构造函数的类类型的成员, 以及 const或引用类型的成员,不管是哪种类型,都必须在构造函数初始化列表中进行初始化。成员被初始化的次序就是定义的次序。
5.隐式类类型转换
可以用单个实参来调用的构造函数定义了从形参类型到该类类型的一个隐式转换。
当构造函数被声明成explicit(只能用于类内部的构造函数声明上)时,编译器将不用它作为转换操作符。
单形参构造函数一般应设置为explicit。
6.友元
可以访问类中的任意成员,包括私用的。友元可以是函数、类或类的成员函数,以关键字friend开始。
7.static类成员
static函数没有this指针。
static关键字只能用于类定义体内部的声明中,定义不能标示为static。
const static数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外进行定义,只是在类内部提供初始化时,成员的定义不必再指定初始值。
8.合成的默认构造函数
编译器为没有定义任何构造函数的类创建(合成)的构造函数,这个构造函数通过运行该类的默认构造函数来初始化类类型的成员,内置类型的成员不进行初始化。
第十三章
1.复制控制:复制构造函数,赋值操作符和析构函数总称为复制控制。
2.合成的复制构造函数:执行逐个成员初始化,将新对象初始化为原对象的副本(包括指针成员)。
3.为了防止复制,类必须显示声明其复制构造函数为private。
4.合成复制操作符:逐个成员赋值(包括指针成员)。
5.如果类需要析构函数,则它也需要赋值操作符和复制构造函数。
6.合成析构函数:不管是否显示定义析构函数,编译器总是会为我们合成一个析构函数,合成析构函数按对象创建时的逆序撤销每个非static成员。
7.定义自己的复制构造函数和赋值操作符时要防止自身复制和赋值。
8.定义智能指针类
使用计数:智能指针类将一个计数器与类指向的对象相关联,使用计数跟踪该类有多少个对象共享同一个指针,使用计数为0时,删除对象。
使用计数类:P422
第十四章
1.重载操作符必须具有一个类类型或枚举类型的操作数。
2.重载操作符作为类成员的重载函数,其形参看起来比操作数目少1,是因为有一个隐含的this形参限定为第一个操作数。
3.一般不要重载逗号、取地址、逻辑与、逻辑或等具有有用的内置含义的操作符。
4.输入输出操作符
为了与IO标准库一致,操作符应该受ostream&作为第一个形参,对类类型的引用作为第二个形参,并返回ostream& 。一般定义为类的友函数。
5.为了与内置操作符保持一致,加法返回一个右值,而不是一个引用。
6.关系操作符,应定义为非成员函数。
7.赋值操作符可以重载,无论形参为何种类型,赋值操作符必须定义为成员函数,这一点与复合赋值操作符不同。
8.下标操作符([]):必须定义为类成员函数。类定义下标操作符时,一般需要定义两个版本,一个为非const成员并返回引用,另一个为const成员并返回const引用。
9.成员访问操作符
解引用操作符(*):不要求定义为成员,但一般定义为成员也正确。
箭头操作符(->):必须定义为类成员函数,必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。
10.自增操作符和自减操作符
前缀操作符一般被定义为类的成员,应该返回被增量或减量对象的引用。
后缀操作符应返回旧值,应作为值返回,而不是引用。
11.调用操作符和函数对象P449
重载函数调用操作符(()):函数调用操作符必须声明为成员函数,一个类可以定义函数操作符的多个版本,由形参的数目或类别加以区别,定义了调用操作符的类,其对 象称为函数对象。
将函数对象用于标识库算法P450 。
12.函数对象的函数适配器P453
绑定器(binder) :一种函数适配器,通常将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象。
求反器(negator):一种函数适配器,它将谓词函数对象的真值求反。
13.转换操作符
是一种特殊的类成员函数,它定义将类类型值转变为其他类类型的转换。转换操作符在类定义体内声明,在保留字operator之后跟着转换的目标类型,如operator int();
转换函数采用如下通用格式:operator type(); 转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。通常定义为const成员。
14.智能指针
一个类,定义了指针式行为和其他功能,如:引用计数、内存管理、更全面的检查等。这种类通常定义了解引用操作符(operator*)和成员访问操作符(operator->)的 重载版本。
第十五章
1.在C++中,通过基类的引用或指针调用虚函数时,发生动态绑定,引用或指针既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键,用引用或指针调用 的虚函数在运行时确定,被调用的函数是引用或指针所指对象的实际类型所定义。只用通过引用或指针调用,虚函数才在运行时确定,只有在这些情况下,知道运行时才 知道对象的动态类型。
2.派生类中虚函数的声明必须与基类中的定义完全匹配,但有一个例外:返回对基类型的引用或指针的虚函数,派生类中的虚函数可以返回基类函数所返回类型的派生类的 引用或指针。
3.派生类虚函数调用基类版本时,必须显式使用作用域操作符,如果派生类函数忽略了这么做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归。
4.派生类可以恢复继承成员的访问级别,但不能使访问级别比基类中原定的更严格或更宽松,如:using BaseClass::FunName; P484
5.友元关系不能继承,基类的友元对派生类的成员没有特殊访问权限。
6.如果派生类显式定义自己的复制构造函数和赋值操作符,则该定义将完全覆盖默认定义。被继承类的复制构造函数和赋值操作符负责对基类成分以及类自己的成员进行复 制和赋值。
7.构造函数和赋值操作符不是虚函数。如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类定义的版本。
8.如果派生类重定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。
9.句柄类 P504
第16章
1.函数模板
模板定义以关键字template开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间用逗号分隔,模板形参可以是表示类型的类型形 参,也可以是表示常量表达式的非类型形参, 如: template <typename T> int compare( const T& V1 , const T& V2);
2.inline函数模板
inline说明符放在模板形参表之后,返回类型之前,不能放在关键字template之前。
3.类模板 示例:
template <class type> class Queue
{
...
};
4.每个模板类型形参前面必须带上关键字class或typename,每个非类型形参前面必须带上类型名字。
5.模板类型形参由关键字class或typename后接说明符构成,在模板形参表中,这两个关键字具有相同的含义,都指出后面所接的名字表示一个类型。
6.可以使用关键字typename在模板定义内部制定类型。P532
7.函数模板的特化 P565
模板特化是这样一个定义,该定义中一个或多个模板形参的实际类型或实际值是制定的,特化的形式如下:
关键字template后面接一对空的尖括号(<>);
再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参;
函数形参表;
函数体。
8.类模板的特化 P567
第17章
1.异常处理
异常对象通过复制被抛出表达式的结果创建,该结果必须是可以复制的类型。当抛出一个表达式的时候,被抛出对象的静态编译时类型将决定异常对象的类型。抛出指针 通常是个坏主意:抛出指针要求在对应处理代码存在的任意地方存在指针所指向的对象。
2.析构函数应该从不抛出异常。
3.重新抛出:catch可以通过重新抛出将异常传递给函数调用链中更上层的函数,重新抛出是后面不跟类型或表达式的一个throw 。
4.函数测试块:为了处理来自构造函数初始化式的异常,必须将构造函数编写为函数测试块。 P587
5.auto_ptr类 P591
auto_ptr只能用于管理从new返回的一个对象,他不能管理动态分配的数值,不能存储在标准库容器类型中。
6.异常说明
异常说明跟在函数形参表之后,一个异常说明在关键字throw之后跟着一个(可能为空的)由圆括号括住的异常类型列表。
空说明列表指出函数不抛出任何异常。
如果一个函数声明没有指定异常说明,则该函数可以抛出任意类型的异常。
7.命名空间
命名空间作用域不能以分号结束。
8.未命名的命名空间
未命名的命名空间与其他命名空间不同,未命名的命名空间的定义局部于特定文件,从不跨越多个文本文件。
9.多重继承与虚函数
象单继承的情况一下,如果具有多个基类的类定义了自己的析构函数,该析构函数只负责清除派生类。如果派生类定义了自己的复制构造函数或赋值操作符,则类负责复 制(赋值)所有的基类子部分,只有派生类使用复制构造函数或赋值操作符的合成版本,才自动复制或赋值基类部分。
10.虚继承
多重继承的形式,这种形式中,派生类共享在层次中被包含多次的基类的一个副本。
在虚派生中,由最低层派生类的构造函数初始化虚基类。
无论虚基类出现在继承层次中任何地方,总是在构造非虚基类之前构造虚基类。
第18章
1.C++中的内存分配 P632
C++提供下面两种方法分配和释放未构造的原始内存:
1、allocator类,它提供了可感知类型的内存分配,这个类支持一个抽象接口,以分配内存并随后使用该内存保存对象。
2、标准库中的operator new 和 operator delete,它们分配和释放需要大小的原始的未类型化的内存。
注意:标准库函数operator new 和 operator delete 与表达式 new 和 delete 不是同一个概念。
一般而言,使用allocator比直接使用operator new和operator delete 更为类型安全。
调用operator delete函数不会运行析构函数,它只释放指定的内存。
如果new表达式调用全局operator new函数分配内存,则delete表达式也应该调用全局operator delete函数。
2.定位new表达式 P638
定位new在已分配的原始内存中初始化一个对象,它与new的其他版本的不同之处在于,它不分配内存。定位new表达式使我们能在特定的、预分配的内存地址构造一个对象。
定位new表达式的形式是:
new ( place_address ) type;
new ( place_address ) type( initializer-list);
3.运行时类型识别
通过运行时类型识别(RTTI),程序能够使用基类的指针或引用来检索这些指针或引用所指对象的实际派生类型。
通过下面两个操作符提供RTTI:
1、typeid操作符,返回指针或引用所指对象的实际类型。
2、dynamic_cast操作符,将基类类型的指针或引用安全的转换为派生类类型的指针和引用。
这些操作符只为带有一个或多个虚函数的类返回动态类型信息,对于其他类型,返回静态(即编译时)类型的信息。
4.typeid表达式形如:
typeid(e); 这里e是任意表达式或者是类型名。
只有当typeid操作数是带虚函数的类类型的对象的时候,才返回动态类型信息。测试指针(相对于指针指向的对象)返回指针的静态的、编译时类型。
使用示例:typeid(n); typeid(int); typeid(nNum) == typeid(int);
5.类成员指针 P653
类成员指针示例:string Screen::* ps_Screen = &Screen::contents ;
类成员函数指针示例:char (Screen::* pmf1)() const = &Screen::Get ;
调用操作符的优先级高于成员指针操作符,因此,包围Screen::*的括号是必要的。
.*和->*两个操作符,使我们能够将成员指针绑定到实际对象。这两个操作符的左操作数必须是类类型的对象或指针,右操作数是该类型的成员指针。
成员指针解引用操作符(.*)从对象或引用获取成员。
成员指针箭头操作符(->*)通过对象的指针获取成员。
因为调用操作符(())比成员指针操作符优先级高,所以调用(myScreen.*pmf)() 和 (pScreen->*pmf)() 需要括号。
6.嵌套类 P658
可以在一个类的内部定义一个类,这样的类是嵌套类,也称为嵌套类型。
7.联合union:节省空间的类 P662
联合没有静态数据成员、引用成员或类数据成员。
匿名联合不能有私有成员或受保护成员,也不能定义成员函数。
8.局部类 P665
可以在函数体内部定义类,这样的类称为局部类。局部类的所有成员(包括函数)必须完全定义在类定义体内部。
9.位域 P666
可以声明一种特殊的类数据成员,称为位域,来保存特定的位数。当程序需要将二进制数据传递给另一个程序或硬件设备时,通常使用位域。
位域必须是整形数据类型,通过在成员名后面接一个冒号以及指定位数的常量表达式,指出成员是一个位域,如:unsigned int mode::2 ;
地址操作符(&)不能应用于位域,所以不可能有引用类位域的指针,位域也不能使类的静态成员。
10.volatile限定符 P668
当可以用编译器的控制或检测之外的方式改变对象值的时候,应该将对象声明为volatile。关键字volatile是给编译器的指示,指出对这样的对象不应该执行优化。
11.链接指示:extern "C"
C++程序调用其他程序设计语言编写的函数(最常见的语言是C语言)时,要使用到链接指示。