C语言基础,面向对象部分,Template,STL。C++感觉好博大精深,现在貌似还是第二个阶段,想起刚学C++那会儿,一直用着C++的语法,写着面向过程的代码,实在是囧啊…
a) 当我们定义一个常量时,最好使用const常量,而非#define,因为define会无脑的替换,这个常量可以是专属于某处的,而#define所定义的是全局的。
b) 使用enum类似于#define,但是效果更好,而且enum也可以作为int类型的变量使用。
c) 有时候我们会用#define来定义一个函数,这样做很方便而且没有调用函数的开销,但是这样的函数很容易出问题。而我们使用inline函数,加上模板可以更好地的实现这种效果。
d) 虽然#define可以被这么多种东西替代,但是#define还是必不可少的。#ifndef/#ifdef更是对编译流控制的好方法。
a) 防止意外修改不该被修改的内容,使之尽早在编译时暴露出来。Const可以修饰任何对象,函数参数,函数返回类型,成员函数本体
b) 对于成员函数,如果不希望其改变对象,可以声明为const的。
c) const版本和non-const版本的成员函数实现等价时,可以令non-const版本的调用const版本的,使用const_cast去除const即可。
a) 在使用对象或者变量前,保证其被初始化,防止其为随机值。
b) 对于类的构造函数,最好使用成员初值列表(member initialization list)来初始化,初值列的成员变量排列次序与声明次序相同(这个不是强制,但是最好如是执行,比如我们申请一个数组,数组大小需要先有初值才行)。而不要用构造函数内进行赋值操作(assignment),这样会先默认初始化再赋值,相当于两次操作。
c) 跨编译单元的初始化顺序,以local static对象替换non-local-static对象。说实话这条没看懂...不过我的简单理解就是类似单例模式,我们在使用对象时,有时候不确定初始化的时机,那么我们不如在使用前,将要用的东东一起初始化了。这样就可以控制顺序了。
a) 如果我们什么都不写的话,编译器也会为我们生成默认构造函数,析构函数,拷贝构造函数,以及一个赋值运算符。
b) 如果我们自己声明了这些函数,那么C++会使用我们自己编写的这些函数,不会再使用那些默认的。
c) 编译器为我们生成的默认构造函数是没有参数的,如果我们声明了其他的有参数的构造函数,那么这个默认构造函数也会消失,我们如果仍想要无参数的构造函数,那么就要自己再声明一个。
d) 编译器为我们生成的析构函数,不会是virtual的,除非这个类的基类有virtual析构函数。
a)有时候我们不容许拷贝或者赋值一个对象时,即使我们不写拷贝或者赋值函数,编译器也会为我们生成一个,肿么办呢?答案是自己写一个private的拷贝构造函数和赋值函数,这样从类外就不能调用了。但是从类内还是可以调用的,这时候,我们可以只声明,而不定义,这样,即使有人不小心使用了,在编译链接的时候,也会报错。把错误提前暴露,才容易发现。
b)如果我们怕忘记,或者为了方便,也可以定义一个基类uncopyable,让我们的类继承这个类即可。
a) 如果我们不这样做,当使用基类指针控制子类对象时,当析构时会发生只析构基类部分,而子类部分不被释放的尴尬情况。导致内存泄露。
b) 但是这也不代表着为了保险起见,我们全都要用virtual析构函数,因为虚函数的开销是比较大的,所以一般判定需要使用虚析构函数的条件为:只要这个类中有一个其他的虚函数,那么就使用虚析构函数。
c) 既然要使用多态的特性,那么如果基类不是虚析构函数,那我们就不要去继承这个类,不然会引出一大串麻烦。比如string,标准stl等都是非虚析构函数的!(前提是使用多态特性)
d) 析构函数调用的顺序:基类的析构函数先调用,然后依次向下调用子类的析构函数。
a) 我的理解感觉就是,能不抛出异常的,或者感觉会抛出异常的功能,不要放在析构函数中,额外设置一个函数。比如.close()关闭函数。但是,在析构函数中仍然要做一下关闭的操作,防止客户忘记调用close发生异常。但是,在析构函数中的异常,不要抛出,可能会导致程序提前结束。应该在析构函数中就catch并处理。
b) 被析构函数调用的函数如果抛出异常,析构函数也应该catch掉,保证不要再抛出。
c) 如果客户需要对某个操作函数运行期间的异常做出反应,那么最好提供一个普通函数,而不要把这个东东放在析构函数中。
a) 我们知道,如果有继承,构造函数会先调用父类构造函数,而如果构造函数中有虚函数,此时子类还没有构造,所以此时的对象还是父类的,不会触发多态。更容易记的是基类构造期间,virtual函数不是virtual函数。
b) 析构函数也是一样,子类先进行析构,这时,如果有virtual函数的话,子类的内容已经被析构了,C++会视其父类,执行父类的virtual函数。
c) 总之,在构造和析构函数中,不要用虚函数。如果必须用,那么分离出一个Init函数和一个close函数,实现相关功能即可。
我们经常会为我们的类写一个operator=的函数,为了保证连锁赋值,比如x= y=z的这种情况能正常执行,最好是返回一个*this的引用。
a) 自我赋值看起来不是那么容易发生,但是程序复杂了什么情况都有,所以为了我们程序的健壮性,还是要处理一下的。
b) 自我赋值会导致程序出错的情况在于,如果字段中有指针,先删除指针原来指向的对象,指向=右边的对象。而自我赋值时会导致原来的对象被删除,而=右边的对象也被删除,因而会出错。
c)解决的方法其实最简单的就是先判定是否相等,如果和自己是同一个对象,直接返回*this,结束。
a) 正常情况下,如果我们不写拷贝构造函数,编译器会为我们生成一个默认的拷贝构造函数,但是这个函数只能实现浅拷贝,如果要实现深拷贝就必须要自己写一个拷贝构造函数。
b) 但是如果我们自己写了拷贝构造函数,就一定要拷贝每一个成分,如果忘记了某个成分,编译器是不会提醒的。尤其在发生继承的时候,更加需要注意拷贝基类成员部分。通过调用基类函数来进行拷贝。
c) 关于initialization和assignment:前者由构造函数执行,后者由operator =执行。两者对应不同的函数动作。两者可能有差不多的内容,但是不要使用一个调用另一个函数,如果实在想要抽象,不如提取一个Init函数。