摘自:《Effetive C++》中文版第三版
符号表的概念:
从编译器来看,符号表与编译的各个阶段都有交互,一般来讲,符号表有内存地址和函数/变量的对应关系,编译时节点的各种属性(类型,作用域,分配空间大小,(函数)的参数类型等)
1.为什么要用const,enum,inline替换#define
因为例如
#define kkk 1.653
代码段在编译器开始处理源码之前可能会被预处理器取走了,以至于如果在这之后程序中用到了kkk这个常量而且出错时,编译错误信息将会返回1.653而不是kkk。这种情况在多个文件共同编译时而且是有重复定义数值情况下,将无法分辨究竟是哪一个常量出现了编译问题。
而作为一个语言常量,const肯定会被编译器看到,那么一定就会进入符号表,使用const可以避免上述出现多份1.653的情况。
对enum来说,行为会比较像#define而不像const,这三者中只有const变量能够取其地址,enum和#define取地址不合法。
对于形似函数的宏,最好改用inline函数替换#define,因为将函数以宏的形式定义容易出现在预处理器中已经改变参数值的问题。
2.const成员函数bitwise const和logical const
bitwise:成员函数不改变该class的任何成员变量->不改变class内的任何一个bit。
logical:成员函数可以修改class内的某些bits,但是只有客户端监测不楚的情况才行。
const成员函数不能修改普通成员变量,这里是根据bitwise原则来的。但是如果在成员变量前面加上了mutable修饰词,那么const成员函数就可以修改这些普通成员变量了。
3.确定对象被使用前已先被初始化
在之前的编码中,以带参数构造函数例如
NewClass::NewClass(const int& num)
{
num_ = num;
}
形式,这种不叫作初始化,而叫做类成员函数的赋值,而成员初始化列表才是真儿八经地对成员变量初始化操作。
NewClass::NewClass(const int& num):num_(num){}
区别在于使用赋值操作的构造函数,成员变量是在进入该类的构造函数之后获得的,而C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。
4.避免遮掩继承而来的名称
主要会产生由变量的局部作用域和全局作用域冲突而产生变量覆盖的问题。考虑以下代码:
int x;
void func(){
double x;
std::cin>>x;
}
读取这个语句时,编译器将读取输入的double x而不是global int x,因为内层作用域的名称会覆盖外围作用域的名称。
以类的局部作用域为例
class Base{
public:
virtual void mf1() = 0;
virtual void mf2();
void mf3();
...
}
class Derived:public Base{
public:
virtual void mf1();
void mf4();
}
若一段代码
void Derived::mf4(){
mf2();
}
编译器的查找顺序为
- mf4覆盖的作用域
- Derived覆盖的作用域
- Base覆盖的作用域
若Base中也不存在
1. 在Base所属的namespace中查找
2. 在global作用域中查找
5.区分接口继承和实现继承
成员函数的分类:
- pure virtual
- impure virtual
- non virtual
pure virtual
virtual void func() = 0;
为了让派生类只继承函数接口而且必须在派生类中实现该函数
impure virtual
会在基类中提供一份实现代码,派生类可以继承该函数接口和缺省实现,也可以自行重载。这个设计突显出了基类和派生类的共同性质,避免代码的重复。
例如
class Person{
virtual bool dickcheck(){return true;}
}
class A : public Person{...};
class B : public Person{...};
/*在这种情况下A,B都在调用dickcheck()函数时都将返回true*/
现在出现了一个女人,她继承自Person类
class C : public: Person{....};
Person* human = new C;
human->dickcheck();
这就出现了问题,dickcheck()函数返回为true,但是男女有别。此时class C是无辜的,它并没有说明自己在没有dick的情况下就调用了dickcheck函数,意味着无辜地继承了缺省实现。实际上我们可以将dickcheck函数声明为pure virtual,然后再让每个具有不同特性的派生类单独实现该函数。
class Person{
public:
virtual bool dickcheck() = 0;
}
class A : public Person{
virtual bool dickcheck(){return true;}
}
class B : public Person{
virtual bool dickcheck(){ return false;}
}
本质上,通过定义我们将dickchenck这个基类中函数划分了N种形态,每种形态对应一个特性的派生类,声明部分成为接口,定义部分表现单个派生类的特性。我们在这里不提供缺省实现是避免某些特殊派生类的加入会使得修改基类代码。
non virtual
声明non-virtual函数是为了让派生类继承接口和一份强制性实现,由于non-virtual代表的是不变性和凌驾特异性,所以它绝不该在derived class中被重新定义。
6.考虑virtual函数以外的其他选择–打破继承体系的限制
Non-virtual Interface手段
通过public的non-virtual函数=PNV调用private的virtual=函数PV。这使得可以在PNV中调用PV的前后执行一些工作,例如互斥变量锁定和系统日志。其实virtual函数不一定得是private,protected的virtual函数使得派生类可以继承基类的PNV调用方式。
std::function实现strategy模式
例如现有基类flower代表一种植物,A中有一个方法void flower1(const flower&)可以让这种植物向着太阳开花,void flower2(const flower&)可以让这种植物向着土地开花。好,
我们写出函数的签名
typedef std::function<void(const flower&)> Flower;
Flower a; //向着太阳开花的植物
Flower b; //向着大地开花的植物
Flower a_f = std::bind(&Flower::flower1, a, _1);
Flower b_f = std::bind(&Flower::flower2, b, _1);
a_f(a);
b_f(b);
7.绝不重新定义继承而来的non-virtual函数
词条在5中已经提到过,这要求我们在设计基类时充分考虑到该类的特性,使得其派生类都无条件继承non-virtual函数时不会产生函数冗余。毕竟non-virtual函数静态绑定,而virtual函数时动态绑定。
8.绝不重新定义继承而来的缺省参数值–因为重新定义了也没用啊…2333
因为缺省参数值是静态绑定,在编译器决定的而不是运行期。
9.通过复合塑模出has-a或“根据某物实现出”
复合就是组合。组合(has-a)和继承(is-a)都是代码复用的方式。
组合:黑盒复用,被包含的对象的内部细节对对外是不可见的。但是为了使得包含另外一个对象的类能够友好地操纵被包含的对象,设计被包含的对象时应该安全合理地考虑对外的接口函数。
继承:白盒复用。派生类会随着基类的改变而改变。
10.private继承和protected继承
private继承不是is-a的关系,而是implemented-in-terms-of,有点类似组合。
- private继承时,编译器不会讲派生类对象转换为基类对象,这意味着在以基类对象为参数的传递时并不能用派生类对象。
- private继承过来的成员在派生类中将全部转变为private属性。
在此处要注意派生类内部和派生类对象的区别。private继承在派生类的子类内部无法访问基类的任何成员。考虑下面的情况:
class Base{
public:
int a;
protected:
int b;
private:
int c;
}
class pri : private Base{
public:
/*这里不能进行对c进行赋值,因为不论哪种继承方式,派生类均不能对基类的private成员进行操作;*/
void access(){ a = 1;b=1;}
}
#
pri Pri_1;
//错误,在private继承下,派生类对象不能访问基类的任何成员。
Pri_1.a = 1;
#
/* 派生类的子类对基类成员进行访问*/
class subPri : public pri{
public:
void access_1(){a = 1; b = 1;}
//此时派生类子类内部无法访问基类的任何成员,对比public继承,后者的子类仍然可以访问基类的成员。
}
使用情形:当派生类需要访问基类的protected成员或者需要重新定义继承而来的virtual函数时。 —这句话完全不能理解
再列举一下有关protected继承的特性:
class pro : protected Base{
public:
//正确,在proteced继承的派生类成员内部可以访问基类public和protected成员
void access(){ a = 1; b = 1;}
}
pro pro_1;
//错误,在protected继承下,派生类对象无法访问基类的任何成员。
pro_1.a = 1;
空白基类最优化–与组合相比较而言
class Empty{
}
class emptyPri : private Base{
public:
int x;
}
class composition {
public:
int x;
Empty e;
}
当我们用sizeof检查这两个类的大小时,可以发先
sizeof(emptyPri)为4;仅为int的大小
而sizeof(composition)为8
本文探讨了C++编程中常遇到的问题及解决方案,如使用const、枚举和内联函数代替预处理器指令,const成员函数的两种类型,确保对象初始化,避免名称遮掩,区分接口继承与实现继承,考虑虚拟函数之外的选择,避免重新定义继承而来的非虚拟函数及参数等。
1555

被折叠的 条评论
为什么被折叠?



