一、 在多重循环中,若是有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减小 CPU 跨切循环层的次数。ios
二、 若是循环体内存在逻辑判断,而且循环次数很大,宜将逻辑判断移到循环体的外面。c++
三、 即便程序真的不须要default 处理,也应该保留语句 default : break。程序员
四、 C 语言用#define 来定义常量(称为宏常量)。 C++ 语言除了 #define 外还能够用 const 来定义常量(称为 const 常量)。数组
五、 const 与 #define 的比较安全
Ø const 常量有数据类型,而宏常量没有数据类型。编译器能够对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,而且在字符替换可能会产生意料不到的错误(边际效应)。函数
Ø 有些集成化的调试工具能够对const 常量进行调试,可是不能对宏常量进行调试。工具
六、 须要对外公开的常量放在头文件中,不须要对外公开的常量放在定义文件的头部。为便于管理,能够把不一样模块的常量集中存放在一个公共的头文件中。this
七、 const 数据成员只在某个对象生存期内是常量,而对于整个类而言倒是可变的,由于类能够建立多个对象,不一样的对象其 const 数据成员的值能够不一样。spa
八、 不能在类声明中初始化const 数据成员。如下用法是错误的,由于类的对象未被建立时,编译器不知道 SIZE 的值是什么。指针
class A
{…
const intSIZE = 100; // 错误,企图在类声明中初始化 const 数据成员
intarray[SIZE]; // 错误,未知的 SIZE
};
九、 const 数据成员的初始化只能在类构造函数的初始化表中进行。
十、 枚举常量不会占用对象的存储空间,它们在编译时被所有求值。枚举常量的缺点是:它的隐含数据类型是整数,其最大值有限,且不能表示浮点数(如 PI=3.14159)。
十一、 通常地,应将目的参数放在前面,源参数放在后面。
十二、 有时候函数本来不须要返回值,但为了增长灵活性如支持链式表达,能够附加返回值。
1三、 不少程序错误是由非法参数引发的,咱们应该充分理解并正确使用“断言”( assert)来防止此类错误。
1四、 return 语句不可返回指向“栈内存”的“指针”或者“引用”,由于该内存在函数体结束时被自动销毁。
1五、 断言 assert 是仅在 Debug 版本起做用的宏,它用于检查“不该该”发生的状况。
1六、 引用的一些规则以下:
Ø 引用被建立的同时必须被初始化(指针则能够在任什么时候候被初始化)。
Ø 不能有 NULL 引用,引用必须与合法的存储单元关联(指针则能够是 NULL)。
Ø 一旦引用被初始化,就不能改变引用的关系(指针则能够随时改变所指的对象)。
1七、 内存分配方式有三种:
Ø 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量, static 变量。
Ø 在栈上建立。在执行函数时,函数内局部变量的存储单元均可以在栈上建立,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,可是分配的内存容量有限。
Ø 从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员本身负责在什么时候用 free 或 delete 释放内存。动态内存的生存期由咱们决定,使用很是灵活,但问题也最多。
1八、 常见的内存错误及其对策以下:
Ø 内存分配未成功,却使用了它。
Ø 内存分配虽然成功,可是还没有初始化就引用它。
Ø 内存分配成功而且已经初始化,但操做越过了内存的边界。
Ø 忘记了释放内存,形成内存泄露。
Ø 释放了内存却继续使用它。(使用 free 或 delete 释放了内存后,没有将指针设置为 NULL。致使产生“野指针”。)
1九、 数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容能够改变。
20、 不能对数组名进行直接复制与比较。
2一、 用运算符 sizeof 能够计算出数组的容量(字节数)。sizeof(p)获得的是一个指针变量的字节数,至关于 sizeof(char*),而不是 p 所指的内存容量。 C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。
2二、 注意当数组做为函数的参数进行传递时,该数组自动退化为同类型的指针。
2三、 若是函数的参数是一个指针,不要期望用该指针去申请动态内存。(能够用指针的指针)
2四、 发现指针 p 被 free 之后其地址仍然不变(非 NULL),只是该地址对应的内存是垃圾, p 成了“野指针”。若是此时不把 p 设置为 NULL,会让人误觉得 p 是个合法的指针。
2五、 free()以后因为指针所指向的内存已经被释放,因此其它代码有机会改写其中的内容,至关于该指针今后指向了本身没法控制的地方,也称为野指针。
2六、 咱们发现指针有一些“似是而非”的特征:
Ø 指针消亡了,并不表示它所指的内存会被自动释放。
Ø 内存被释放了,并不表示指针会消亡或者成了 NULL 指针。
2七、 “野指针”的成因主要有两种:
Ø 指针变量没有被初始化。任何指针变量刚被建立时不会自动成为 NULL 指针,它的缺省值是随机的,它会乱指一气。因此,指针变量在建立的同时应当被初始化,要么将指针设置为 NULL,要么让它指向合法的内存。
Ø 指针 p 被 free 或者 delete 以后,没有置为 NULL,让人误觉得 p 是个合法的指针。
Ø 指针操做超越了变量的做用范围。
2八、 malloc 与 free 是 C++/C 语言的标准库函数,new/delete 是 C++的运算符。它们均可用于申请动态内存和释放内存。
2九、 对于非内部数据类型的对象而言,光用 maloc/free 没法知足动态对象的要求。对象在建立的同时要自动执行构造函数,对象在消亡以前要自动执行析构函数。因为malloc/free 是库函数而不是运算符,不在编译器控制权限以内,不可以把执行构造函数和析构函数的任务强加于 malloc/free。所以 C++语言须要一个能完成动态内存分配和初始化工做的运算符 new,以及一个能完成清理与释放内存工做的运算符 delete。注意 new/delete 不是库函数。
30、 因为内部数据类型的“对象”没有构造与析构的过程,对它们而言 malloc/free 和new/delete 是等价的。
3一、 若是用 free 释放“ new 建立的动态对象”,那么该对象因没法执行析构函数而可能致使程序出错。若是用 delete 释放“ malloc 申请的动态内存”,理论上讲程序不会出错,可是该程序的可读性不好。因此 new/delete 必须配对使用, malloc/free 也同样。
3二、 malloc/free 的使用咱们应当把注意力集中在两个要素上:“类型转换”和“ sizeof”。
Ø malloc 返回值的类型是 void *,因此在调用 malloc 时要显式地进行类型转换,将 void * 转换成所须要的指针类型。
Ø malloc 函数自己并不识别要申请的内存是什么类型,它只关心内存的总字节数。
3三、 若是 p 是 NULL 指针,那么 free 对 p 不管操做多少次都不会出问题。若是 p 不是 NULL 指针,那么 free 对 p连续操做两次就会致使程序运行错误。
3四、 若是用 new 建立对象数组,那么只能使用对象的无参数构造函数。例如
Obj *objects = newObj[100]; // 建立100 个动态对象不能写成
Obj *objects = newObj[100](1);// 建立100 个动态对象的同时赋初值 1
在用delete 释放对象数组时,留意不要丢了符号‘ []’。例如
delete []objects; // 正确的用法
delete objects; // 错误的用法
后者至关于delete objects[0],漏掉了另外 99 个对象。
3五、 c++中为何static成员函数不能声明为const?(注意是成员函数)
答:这是C++的规则,const修饰符用于表示函数不能修改为员变量的值,该函数必须是含有this指针的类成员函数,函数调用方式为thiscall,而类中的static函数本质上是全局函数,调用规约是__cdecl或__stdcall,不能用const来修饰它。
3六、 重载和内联机制既可用于全局函数也可用于类的成员函数, const 与virtual 机制仅用于类的成员函数。
3七、 只能靠参数而不能靠返回值类型的不一样来区分重载函数。
3八、 注意并非两个函数的名字相同就能构成重载。全局函数和类的成员函数同名不算重载,由于函数的做用域不一样。
3九、 小心隐式类型转换致使重载函数产生二义性。
40、 成员函数被重载的特征:
Ø 相同的范围(在同一个类中);
Ø 函数名字相同;
Ø 参数不一样;
Ø virtual 关键字无关紧要。
4一、 覆盖是指派生类函数覆盖基类函数,特征是:
Ø 不一样的范围(分别位于派生类与基类);
Ø 函数名字相同;
Ø 参数相同;
Ø 基类函数必须有virtual 关键字。
4二、 “隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则以下:
Ø 若是派生类的函数与基类的函数同名,可是参数不一样。此时,不论有无 virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。【参数不一样必定会被隐藏】
Ø 若是派生类的函数与基类的函数同名,而且参数也相同,可是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。【没有virtual必定会被隐藏】
4三、 参数缺省值只能出如今函数的声明中,而不能出如今定义体中。
4四、 若是函数有多个参数,参数只能从后向前挨个儿缺省,不然将致使函数调用语句怪模怪样。
4五、 在 C++运算符集合中,有一些运算符是不容许被重载的。这种限制是出于安全方面的考虑,可防止错误和混乱。
Ø 不能改变 C++内部数据类型(如int,float 等)的运算符。
Ø 不能重载‘ .’,由于‘ .’在类中对任何成员都有意义,已经成为标准用法。
Ø 不能重载目前 C++运算符集合中没有的符号,如#,@,$等。缘由有两点,一是难以理解,二是难以肯定优先级。
Ø 对已经存在的运算符进行重载时,不能改变优先级规则,不然将引发混乱。
4六、 在 C 程序中,能够用宏代码提升执行效率。宏代码自己不是函数,但使用起来象函数。预处理器用复制宏代码的方式代替函数调用,省去了参数压栈、生成汇编语言的 CALL调用、返回参数、执行 return 等过程,从而提升了速度。使用宏代码最大的缺点是容易出错,预处理器在复制宏代码时经常产生意想不到的边际效应。使用宏代码还有另外一种缺点:没法操做类的私有数据成员。
4七、 C++ 语言的函数内联机制既具有宏代码的效率,又增长了安全性,并且能够自由操做类的数据成员。因此在 C++ 程序中,应该用内联函数取代全部宏代码。
4八、 关键字 inline 必须与函数定义体放在一块儿才能使函数成为内联,仅将 inline 放在函数声明前面不起任何做用。
4九、 定义在类声明之中的成员函数将自动地成为内联函数。
50、 内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提升函数的执行效率。
5一、 如下状况不宜使用内联:
Ø 若是函数体内的代码比较长,使用内联将致使内存消耗代价较高。
Ø 若是函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
5二、 每一个类只有一个析构函数和一个赋值函数,但能够有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。对于任意一个类 A,若是不想编写上述函数,C++编译器将自动为 A 产生四个缺省的函数,如:
Ø A(void); //缺省的无参数构造函数
Ø A(const A &a); //缺省的拷贝构造函数
Ø ~A(void); //缺省的析构函数
Ø A & operate =(const A &a); //缺省的赋值函数
5三、 构造函数与析构函数的另外一个特别之处是没有返回值类型,这与返回值类型为 void 的函数不一样。
5四、 构造函数有个特殊的初始化方式叫“初始化表达式表”(简称初始化表)。初始化表位于函数参数表以后,却在函数体 {} 以前。这说明该表里的初始化工做发生在函数体内的任何代码被执行以前。
5五、 类的 const 常量只能在初始化表里被初始化,由于它不能在函数体内用赋值的方式来初始化。
5六、 类的数据成员的初始化能够采用初始化表或函数体内赋值两种方式,这两种方的效率不彻底相同。
5七、 非内部数据类型的成员对象应当采用第一种方式初始化,以获取更高的效率。
5八、 构造和析构的次序:构造从类层次的最根处开始,在每一层中,首先调用基类的构造函数,而后调用成员对象的构造函数。析构则严格按照与构造相反的次序执行,该次序是惟一的,不然编译器将没法自动执行析构过程。
5九、 成员对象初始化的次序彻底不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定。这是由于类的声明是惟一的,而类的构造函数能够有多个,所以会有多个不一样次序的初始化表。若是成员对象按照初始化表的次序进行构造,这将致使析构函数没法获得惟一的逆序。
60、 拷贝构造函数是在对象被建立时调用的,而赋值函数只能被已经存在了的对象调用。
6一、 深度复制(针对《c++ primer plus》一书string例题的解释):复制构造函数应当复制字符串并将副本的地址赋给str成员,而不只仅是复制字符串的地址,这样每一个对象都有本身的字符串,而不是引用另外一个对象的字符串,调用析构函数时都将释放不一样的字符串,而不会试图去释放已经被释放的字符串。
6二、 什么时候调用复制构造函数:新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。每当程序生成了对象副本时,编译器都将使用复制构造函数。具体的说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。记住,按值传递意味着建立原始变量的一个副本。编译器生成临时对象时,也将使用复制构造函数。什么时候生成临时对象随编译器而异,但不管那种编译器,当按值传递和返回对象时,都将调用复制构造函数。
6三、 String类的赋值函数要作的工做:
Ø 自我检查赋值的状况
Ø 释放成员指针之前指向的内存
Ø 复制数据而不只仅是数据的地址
Ø 指向一个返回对象的引用(不要将 return *this 错写成 return this)
6四、 基类与派生类的析构函数应该为虚(即加 virtual关键字)。
#include <iostream.h>
classBase
{
public:
virtual ~Base() { cout<< "~Base" << endl ; }
};
classDerived : public Base
{
public:
virtual ~Derived() { cout<< "~Derived" << endl; }
};
voidmain(void)
{
Base * pB = new Derived; // upcast
delete pB;
}
输出结果为:
~Derived
~Base
若是析构函数不为虚,那么输出结果为
~Base
6五、 若是使用指向对象的引用或指针来调用虚方法,程序将使用为对象类型定义的方法,而不使用为引用或指针类型定义的方法。这称为动态联编或晚期联编。这种行为很是重要,由于这样基类指针或引用能够指向派生类对象。
6六、 用 const 修饰函数的参数: 若是参数做输出用,不论它是什么数据类型,也不论它采用“指针传递”仍是“引用传递”,都不能加 const 修饰,不然该参数将失去输出功能。const 只能修饰输入参数。(参数作输出用不能加const,const只能修饰输入参数)
6七、 若是输入参数采用“值传递”,因为函数将自动产生临时变量用于复制该参数,该输入参数原本就无需保护,因此不要加 const 修饰。
6八、 const 成员函数的声明看起来怪怪的: const 关键字只能放在函数声明的尾部,大概是由于其它地方都已经被占用了。