学习笔记之C++ How to Program(part)
C++ How to Program读书笔记总结,未完。
1、 endl与\n区别在于endl刷新输出缓冲区;
2、 static_cast<type>() 用于强制类型转换;
3、 switch(type),type可为何类型?
4、 int的取值范围介于short和long之间;
5、 在内存有限或要求执行速度的面向性能的情况中,可以考虑用较小的整数长度;
6、 如果程序的机器指令不如自然长度那么有效(例如要进行符号扩展),则用较小的整数长度会使程序减慢;
7、 输出格式:域宽,精度,左右对齐;
eg. Cout << setiosflag(ios::fixed| ios::showpoint | ios::left)
<<setprecision(精度) <<setw(域宽) <<number << endl;
8、 程序应写成一组小函数的集合,使得程序更容易编写、调试、维护和修改,小函数能提高函数的复用性;
9、 rand()产生伪随机数,srand(unsigned int seed)完成随机化过程,srand(time(0))使计算机通过时钟值自动取得种子值;
10、 自动存储可以节省内存,因为自动存储类变量在进入声明的块时生成并在退出这个块时删除,自动存储是最低权限原则的例子。
11、 任何对内联函数的改变都可能要求函数的所有客户重新编译。这样可能在有些程序的开发和维护中影响非常大;
12、 Inline限定符经常使用的小函数;
13、 使用内联函数可以减少执行时间,但会增加程序长度;
14、 按值传递的一个缺点是,如果有一个大的数据项需要传递,那么复制这些数据就需要大量的时间和内存空间;
15、 按引用传递对性能很有帮助,它可消除按值传递复制大量数据的开销,但是可能削弱安全性,因为被调用函数可能破坏调用者的数据;
16、 为了传递大型对象,使用一个常量引用参数来模拟按值传递的外观和安全性,并且避免传递大型对象的副本的开销;
17、 按值传递的参数声明为const时,也只保护原始实参的副本,而不是原始实参本身。因此在按值传递时原始实参对于被调用函数所做的修改来说是安全的;
18、 为了清晰性和良好性能起见,许多C++程序员宁愿用指针来向函数传递可修改的实参,而小的不可修改的参数采用值传递,大的不可修改的参数通过使用常量的引用来传递给函数;
19、 返回被调用函数中的自动变量的引用会产生逻辑错误;
20、 在函数原型和函数头部中同时指定默认实参会产生编译错误;
21、 在声明常量变量时没有给它初始化是一个编译错误,eg声明为const的指针;
22、 用const限定符实施最小特权的原则。使用最小特权原则适当的设计软件,可以极大的减少调试时间和不恰当的副作用,并可以使程序更易于修改和维护;
23、 可以讲static应用于局部数组声明,那样,数组就不会在每次程序调用改函数时都进行创建和初始化,也不会在程序中每次该函数结束时被销毁。这样可以提高性能,特别是在使用大型数组时;
24、 如果不用执行时的赋值语句初始化数组而用数组初始化值列表在编译时初始化数组,则程序执行速度更快
25、 函数原型中可以包括变量名,使程序更清晰,但编译器将忽略这个名称;
26、 “*”运算符通常称为间接运算符或间接引用(dereference)运算符,间接引用空指针通常是致命的运行时错误
27、 将指针传递给函数有四种方式,访问权限不同:指向非常量数据的非常量指针(最大访问权限)、指向常量数据的非常量指针(可以被修改以指向任何适当类型的其他数据项,但是不能通过该指针来修改它所指向的数据)、指向非常量数据的常量指针(始终指向同一个内存位置,通过该指针可以修改这个位置上的数据,这就是数组名的默认情况)、指向常量数据的常量指针(最小访问权限);
28、 讲函数原型放在其他函数中能保证最低权限原则,只能从该原型所在函数中正确的调用;
29、 向函数传递数组时,同时传递数组长度(而不是在函数内部固定数组大小),这样能使函数有更好的可重用性;
30、 由于sizeof是一个编译时的一元运算符,而不是一个运行时的运算符,所以实用sizeof并不会降低执行性能;
31、 将一个类型的指针赋给另一个类型(不是void*类型)的指针,而不先将第一个指针强制转换为第二个指针的类型,会造成一个编译错误;
32、 Void*指针不能被间接引用,除了将void*指针和其他指针进行比较、将其强制转换为有效的指针类型和将地址赋给void*指针之外,其他对其的操作都导致编译错误;
33、 尽管数组名是指向数组开头的指针,并且指针是可以在算术表达式中修改的,但是,由于数组名是常量指针,所以不能在算术表达式中修改数组名;
34、 引用数组元素有四种表示法(下标表示法、将数组名作为指针的指针/偏移量表示法、指针下标表示法和用指针的指针/偏移量表示法);
35、 为了使程序清晰,在操作数组时使用数组表示法,而不要用指针表示法;
36、 函数指针type (*函数名)(),记得括号,一个常见用法是用在所谓的菜单驱动系统中,用函数指针选择特定菜单项所需要调用的函数;
37、 将单个字符作为char*的字符串处理,会导致致命的运行时错误。一个char*的字符串是一个指针,它很可能是一个大整数,而一个字符是一个小整数(ASCII值范围0~255)。在许多系统中间接引用一个char值会导致错误,因为低的内存地址往往被保留做特殊用途,例如有效的操作系统的中断处理程序,因此会发生“内存访问扰乱”的错误(?内存地址与小整数char值有何关系);
38、 Char *strtok(char *s1, const char *s2)用法;
39、 在类定义中显示的将类的数据成员初始化是个语法错误;
40、 在类定义中(通过函数原型)声明成员函数而在类定义外定义这些成员函数,可以区分类的接口与实现方法。这样可以实现良好的软件工程,类的客户不能看到类成员函数的实现方法;
41、 普遍原则:避免重复代码。eg. 如果类的成员函数已经提供了类的构造函数(或其他成员函数)所需要的全部或部分功能,那么就可以在函数中调用这样的成员函数。这不仅可以简化代码的维护,而且可以减少又修改代码实现方法所引起出错的可能性;
42、 对象只包含了数据。如果对一个类的类名或该类的一个对象进行sizeof运算,结果将只报告该类的数据成员的大小。编译器只创建独立于类的所有对象的一份成员函数的副本。该类的所有对象共享这份副本。当然,每个对象都需要自己的类数据的副本,因为在对象间这些数据是不同的。函数代码是不可修改的,也称为可重入代码或纯过程。因此可以被类的不同对象所共享;
43、 类范围外,类成员是通过一个对象的句柄引用,可以是对象名、对象引用或指向对象的指针;
44、 用#ifndef、#define和#endif预处理指令防止一个程序中多次包含相同的头文件;
45、 流操纵符是个“粘性”设置,意味着一旦设定了填充字符,将在所有接下来的显示区域里有效;
46、 不要让类的public成员函数返回对该类private数据成员的非const引用(或指针),返回这种引用会破坏类的封装;
47、 赋值(=)运算符可以将一个对象赋给另一个类型相同的对象。默认情况下,这样的赋值通过逐个数据成员赋值的方式进行;
48、 对象可以作为函数的实参进行传递,也可以由函数返回。这种传递和返回默认情况下是以值传递的方式执行的,即传递或返回对象的一个副本。这样的情况下,C++创建一个新的对象,并使用拷贝构造函数将原始对象的值复制到新的对象中;
49、 将变量和对象声明为const可以提高性能,如今复杂的优化编译器可以对常量提供某些对变量来说不能提供的优化;
50、 定义为const的成员函数如果又调用同一类的同一实例的非const成员函数,将导致编译错误;
51、 在const对象上调用非const成员函数将导致编译错误;
52、 可以对const成员函数进行非const版本的重载;
53、 常量数据成员(const对象和const变量)和声明为引用的数据成员必须采用成员初始化器语法形式进行初始化,在构造函数中为这些类型数据赋值是不允许的;
54、 软件重用性的一个普遍的形式是组成,即一个类将其他类的对象作为成员;
55、 (*this).m_data = data, 注意括号
56、 This指针的可以防止对象进行自我赋值,还使串联的成员函数调用成为可能;
57、 当动态分配的内存空间不再使用时若不释放,将导致系统过早的用完内存,这有时成为“内存泄露”(memory leak);
58、 即使不存在已实例化的类的对象,类的static数据成员和static成员函数仍存在并可以使用;
59、 在文件范围内定义静态数据成员包含关键字static是编译错误;
60、 在static成员函数中使用this指针是编译错误;
61、 将static成员函数声明为const是编译错误;
62、 在删除动态分配的内存空间后,将指向这片内存的指针设置为0,杜绝野指针;
63、 #define NDEBUG忽略所有assert;
64、 描述类的功能而不管其实现细节成为数据抽象,C++的类定义了抽象数据类型(ADT),其包含两个概念,即数据表达和可在数据上执行的操作;
65、 好的软件工程有两个几本原则:一是接口与实现的分离,二是隐藏实现细节。可向客户提供代理类,只能访问类的public接口。
66、 重载一元运算符时,把运算符函数用作类的成员函数而非友元函数。因为友元的使用破坏了类的封装性;所以尽量避免使用友元函数和友元类;
67、 在派生类的构造函数中,采用成员初始化器列表显示的初始化成员对象和调用基类的构造函数,可以防止重复初始化,即调用了默认构造函数后,又在派生类的构造函数中再次修改数据成员;
68、 如果可能,尽量避免在基类中包含protected数据成员。相反,应包含有访问private数据成员的函数,以保证对象处于可靠状态;
69、 如果在派生类中包含一个与基类中同名但是有不同签名的成员函数,那么这个函数会隐藏基类版的那个函数。如果通过派生类的对象的public接口试图调用基类版的这个成员函数,将会产生编译错误;
70、 假设我们创建一个派生类对象,这个派生类及其基类中都包含其他类的对象。当这个派生类的对象被创建时,首先执行基类成员对象的构造函数,然后执行基类的构造函数,接着执行派生类成员对象的构造函数,最后执行派生类的构造函数。派生类对象析构函数的调用顺序与相应的构造函数调用顺序正好相反;
71、 基类指针和派生类指针与基类对象和派生类对象的混合匹配有四种可能:
a) 直接用基类指针指向基类的对象;
b) 直接用派生类指针指向派生类的对象;
c) 用基类指针指向一个派生类的对象。但是只能引用基类成员。如果试图通过基类指针引用只在派生类中才有的成员会产生编译错误;除非显示的把基类指针强制转换为派生类指针,这就是向下强制类型转换(downcasting),但是该技术是具有潜在危险的操作。
d) 用派生类指针指向基类对象,会产生编译错误。派生类指针必须先强制类型转换为基类指针;
72、 区分继承和组成,组成是把对象作为类的成员;
73、 基类的private成员只能在基类的定义中或由基类的友元访问;
74、 当从protected基类派生一个类时,基类的public和protected成员都变成protected成员;
75、 当从private基类派生一个类时,基类的public和protected成员都变成private成员;
76、 一旦一个函数被声明为虚函数,即使重新定义类时没有声明虚函数,那么它从该点之后的继承层次结构中都是虚函数;
77、 即使某些函数因类层次结构中的高层已声明为virtual而成为隐含的virtual函数,但为了使程序更加清晰可读,最好还是在类层次结构的每一级中都把它们显示的声明为virtual函数;
78、 如果程序通过指向派生类对象的基类指针或引用调用virtual函数,那么程序会根据所指对象的类型而不是指针类型,动态(即执行时)选择正确的派生类函数。这称为动态绑定或迟绑定;
79、 当virtual函数通过安名引用特定对象和使用圆点成员选择运算符的方式被调用时,调用哪个函数在编译时就已经决定了(称为静态绑定),所调用的virtual函数正式为该特定对象所属的类定义的(或继承而来的)函数—这并不是多态性行为;
80、 多态性程序设计可以消除不必要的switch逻辑,使程序看上去显得很简单,包含更少的分支逻辑和更简单有序的代码。这种简单化使得程序更易于测试、调试和维护;
81、 抽象类为类层次结构中的各种类定义公共的通用接口。因为它通常在继承层次中做基类,所以叫抽象基类。通过声明类的一个或多个virtual函数为纯virtual函数,可以使类称为抽象类;
82、 试图实例化抽象类的对象,将导致编译错误;具体类则可以;
83、 尽管不能实例化抽象基类的对象,但可以声明指向抽象基类对象的指针和引用。这样的指针和引用可以用来对实例化的具体派生类的对象进行多态性的操作;
84、 纯虚函数是在它的声明中“初始化值为0”的函数。Eg. Virtual void draw()const = 0; “=0”为纯指示符。纯virtual函数不提供函数的具体实现,每个派生的具体类必须重载所有基类的纯virtual函数的定义;
85、 如果一个类中含有virtual函数,那么该类中就要提供一个virtual析构函数,即使该析构函数并不一定是该类需要的。
86、 构造函数不能是virtual函数;
87、 动态绑定需要在运行时把virtual成员函数的调用传送到恰当类的virtual函数的版本。Virtual函数表简称为vtable,是一个包含函数指针的数组。每个含有virtual函数的类都有一个vtable。对于类中的每个virtual函数,在vtable中都有一个包含函数指针的项,此函数指针指向该类对象的virtual函数版本。特定类所用的virtual函数可能是该类中定义的函数,也可能是从类层次结构中较高层的基类直接或间接继承而来的函数
88、 当基类提供了一个virtual成员函数时,派生类可以重载此函数,但不是必须的。因此,派生类可以使用基类版本的virtual函数;
89、 动态绑定使得独立软件供应商(ISV)可以不用暴露所有权就可以发布软件。发布的软件可以只包含头文件和目标文件--不需要暴露源代码。之后软件开发人员可以通过继承从ISV提供的类中派生出新类。和ISV提供的类一起运行的软件也能和派生类一起运行,并能够通过动态绑定使用这些类中提供的重载的virtual函数;
90、 多态性是通过virtual函数和动态绑定实现的;
91、 C++中通过virtual函数和动态绑定实现的多态性非常高效。程序员使用这些功能时对系统性能的影响很小;
92、 dynamic_cast运算符检查指针所指对象的类型,然后判断这一类型是否与此指针正在转换成的类型有一种“是一个”的关系,如果它们之间存在“是一个”的关系,返回对象的地址,否则返回0;
93、 运算符typeid返回包含操作数数据类型信息的type_info类对象的一个引用,信息中包括数据类型的名称。要使用typeid,程序必须包含头文件<typeinfo>;
94、 调用时,type_info的成员函数name返回一个基于指针的字符串,它包含type_info对象所表示的类型名;
95、 运算符dynamic_cast和typeid是c++运行时类型信息(RTTI)特征的一部分,允许程序在运行时判断对象的类型;
96、 多继承可以把复杂的事物带入到系统中。要在设计系统中恰当的使用多继承必须非常小心。当单继承能解决问题的时候,尽量不要使用多继承;
97、 菱形继承的二义性发生在一个派生类从两个或者多个基类子对象继承的时候。使用虚继承可以很好的解决多副本子对象的问题。当使用虚继承的方式从基类继承时,只有一个子对象会出现在派生类中,这个过程叫做虚基类继承;
98、 为虚基类提供一个默认的构造函数可以简化层次设计;
99、 尽管模板提供了软件重用的优点,请记住在编译时多个函数模板特化和类模板特化是在程序(在编译时)中进行的(尽管模板只写了一遍)。这些副本将消耗相当大的内存。然而这通常不是问题,因为为模板特化产生的代码与程序员不用模板时编写的独立重载函数的代码长度相等;
100、 类模板也称作参数化类型(parameterizezd type),通过允许将泛型类实例化为明确类型的类来实现软件重用;
101、 在编译的时候适当的(可以利用一个非类型模板参数)指定一个容器类的大小(比如一个数组类或一个堆栈类),这样做消除了使用new创建动态空间的执行时间开销;
102、 在编译时制定容器的大小可以避免在new不能获得足够内存时产生的潜在致命运行时错误;
103、 异常处理用于处理同步错误,这个错误发生在一个语句正在执行的时候;
104、 通过引用捕获异常对象能够去除表示抛出的异常对象的复制开销;
105、 当没有异常发生时,异常处理代码会造成很少甚至没有性能损失。因此,实现异常处理的程序运行起来比将错误处理代码和程序逻辑混合起来的程序更有效率;
106、 带有常见错误情况的函数应该返回0或者NULL(或者其他合适的值),而不是抛出异常。调用这个函数的程序通过检查返回值来确定函数调用是否成功;
107、 在抛出条件表达式(?:)的结果时必须要小心,因为提升规则可能使结果变成非期望的类型。例如当从同一个条件表达式中抛出一个Int型或double型的值时,条件表达式会把int型转为double型;
108、 如果假设一个异常处理结束后,控制将回到抛出点后的第一条语句,那么将是一个逻辑错误;
109、 下列情况下,catch处理器参数匹配所抛出对象的类型:
a) 实际是同一类型;
b) Catch处理器参数类型是所抛出对象类型的public基类;
c) 处理器参数为基类指针或引用类型,而抛出对象为派生类指针或引用类型;
d) Catch处理器为catch(…)
110、 异常处理器可以通过throw;重新抛出异常,将异常处理推给另一个异常处理器。但是执行一个在catch处理器之外的空throw语句将导致函数terminate被调用,程序将放弃异常处理并会立即结束;
111、 抛出在异常说明中没有声明过的异常将会导致函数unexpected被调用;
112、 在以下情况中,将会调用terminate函数:
a) 对于抛出的异常,异常机制找不到匹配的catch块;
b) 析构函数试图在堆栈展开时抛出一个异常;
c) 在没有异常要处理时试图重新抛出异常;
d) 调用函数unexpected将默认调用函数terminate;
113、 当一个异常被抛出但并没有在一个特定的域内被捕获时,该函数调用堆栈就会展开,并试图在下一个外部try…catch块内捕获该异常;
114、 为了使程序更健壮,使用在失败时抛出bad_alloc异常的new版本;
115、 auto_ptr能够防止内存泄露,但在一些操作中它的使用是有限制的。例如,auto_ptr不能指向数组和标准容器类;
116、 将捕获基类对象的catch处理器放在捕获该基类的派生类对象的catch前面是一个逻辑错误。基类的catch捕获所有由基类派生的类对象,所以派生类catch将永远不会执行;
117、 要捕获所有由try语句块抛出的潜在异常,可以使用catch(…)。使用这种方式捕获异常的一个缺点是在编译时不知道捕获到的异常的类型,另外一个缺点是没有指定的参数,因此没有办法在异常处理器中查找该异常对象;
118、 标准异常层次是一个创建异常的好的出发点。使得程序员创建的程序可以抛出标准异常类,也可以抛出标准异常类的派生类,还可以抛出并不是由标准异常类派生的自定义异常类;
119、 由于没能捕获到异常而导致程序组件中断,程序可能继续占用资源(例如文件流或I/O设备)。在这种情况下,其他程序将不能得到该资源。这就是“资源泄漏”;
120、 其他异常处理和错误处理技术:
a) 忽视异常;
b) 中断程序;
c) 设置错误指示器;
d) 测试错误条件,传递错误信息以及调用exit;
e) 使用函数setjump和longjump;
f) 一些特定的错误有专用的方法进行处理,eg. New &new_handler;
121、 I/O流类的继承关系;
122、 文件打开模式;
123、 reinterpret_cast的使用是与编译器相关的,程序在不同平台上运行起来可能并不一样。所以除非有绝对的必要,都不应该使用reinterpret_cast运算符;
124、 文件可以在流对象超过了函数范围或程序执行结束前由ifstream、ofstream、fstream的析构函数关闭,但是比较好的编程习惯是不需要文件时使用clost显示关闭;
125、 自引用类包含一个指向与自己相同的类对象的一个指针;
126、 Delete并不删除指针,只是删除所指空间;
127、 对在运行时大小动态变化的数据结构使用动态内存分配可以节省内存。但是要记住,存放地址的指针要占用空间,而且动态内存分配会增加函数调用的开销;
128、 如果要修改父函数的指针,需要声明参数为指向指针的指针;
129、 Typedef为内置数据类型创建别名,使得程序更具可移植性;
130、 有时通过宏替换用内联代码实现的函数调用。这消除了函数调用的开销。内联函数比宏更好,是因为其提供了函数的类型检查服务;
131、 运算符”#”把替换文本的标记转换为带引号的字符串;”##”运算符把两个标记连在一起;
132、 宏或者符号常量的替换文本是同一行中#define指令标识符之后的所有剩余内容(和宏的参数列表)。如果替换文本超过一行,那么在该行最后加上反斜杠(\),表示替换文本继续到下一行;
133、 许多操作系统把命令行参数传递给参数列表中包含int argc和char *argv[]的main函数。参数argc是命令行参数的个数,argv是存储实际命令行参数的字符串数组;
134、 STL方法允许编写通用程序,使得代码不依赖于底层容器。这样的编程方法称为泛型编程;
135、 STL一般避免使用继承和虚函数,而是采用泛型编程,利用模板来获得更高的运行时效率;
136、 使用STL编程可以提高代码的可移植性;
137、 STL容器的通用函数及只适用于首类容器的通用函数;
138、 首类容器(序列容器和关联容器)的通用typedef;