找回密码
 立即注册
  • QQ空间
  • 回复
  • 收藏

Effective C++ 第一章重点条款学习

admin 2019-9-11 18:42 73人围观 C++相关

导语

注:本节代码见github仓库

https://github.com/Light-City/effective_cpuluplus

条款2:尽量使用const, enum, inline, 减少宏变量#define的使用


尽量多用编译器,少用预处理器
#define A 3.14
替换为:
const double A = 3.14;
注意宏是全局的与范围无关!

当我们以const替换#define,两种特殊情况值得说明:

(1) 定义常量指针

例如若要在头文件内定义一个常量的(不变的)char *字符串,就必须写const两次
const char* const myWord = "xxx";
而对于上述在C++中直接使用一次const即可:
const std::string myWord("xxx");
(2)class专属常量

例如:为了将常量的作用域(scope) 限制于 class内,你必须让它成为class 的一个成员 (member) ;而为确保此常量至多只有一份实体,你必须让它成为一个static 成员:
classGamePlayer {
private:
staticconstintNumTurns=5;
   intscores[NumTurns];
...
};
注意,因为此处是类的成员声明范围内,所以上面只是变量的声明和初始化,而并非定义,因此如果想获取变量的地址,需要在别处另加定义。这个定义不能有任何赋值语句,因为在类内已经规定为const:
const int GamePlayer::NumTurns;

使用枚举

当你在一个类内声明某变量,但你的编译器不允许在声明时赋值初始化:
int a;
int s[a];
此时s[a]肯定报错了,为了解决这种问题,可以使用枚举:
enum {a=5};
int s[a];

inline函数替代宏函数

inline关键字用来建议编译器把某频繁调用的函数当做内联函数,即在每次函数调用时,直接把函数代码放在函数调用语句的地址,减少堆栈浪费。

现在有如下例子:
#define CALL_MAX(a,b) f((a) > (b) ? (a) : (b))
当main函数中调用如下:
cout<<MAX(++a, b)<<endl;              // a被增加两次
cout<<MAX(++a, b+10)<<endl;           // a被累加一次
这与我们的预期结果不同!

为了解决这个问题,我们采用inline函数:
template<typenameT>
inlineintMax(constT&a, constT&b){
   return (a>b?a:b);
}
这样就避免了前面宏替换被累加两次的问题.

总结:对于常量,原先写的宏用const或者enum来替换,宏函数用inline修饰的函数!

本节额外补充:const 有地址,enum与#define没有地址原因:

  • const 定义的实际是一个变量,const只限定它不能被修改,所有变量都可在程序运行时获取其地址

  • enum类型中的枚举项只是enum类型声明的一部分,它不是定义出来的变量,所以不能取地址

  • #define出来的是宏,它是预处理的东西,预处理后的编译阶段已经不存在,所以也不可能获取宏的地址

条款3:尽可能使用const关键字


这一节内容详细见下面:

https://github.com/Light-City/CPlusPlusThings/tree/master/const

条款4:确定对象被使用前已被初始化


(1)内置类型初始化

定义的时候我们一般这样写:
int a=0;
而不应该直接写
int a;
(2)类的初始化

用户自定义的类,我们需要构造函数初始化列表来完成此类的初始化.
A::A(conststd::string&name, conststd::string&address, conststd::list<PhoneNumber>&phones){
   theName=name;
   theAddress=address;
   thePhones=phones;
}
函数里面是赋值而非初始化!正确方式如下:
A::A(conststd::string&name, conststd::string&address, conststd::list<PhoneNumber>&phones)
  :theName(name),
   theAddress(address),
   thePhones(phones)
{}
(3)引用必须被初始化
inta=1;
int&b=a;      
(4)初始化no-local static对象

现有一个场景:在两个编译单元中,分别包含至少一个no-local static对象,当这些对象发生互动时,它们的初始化顺序是不确定的,所以直接使用这些变量,就会给程序的运行带来风险。

编译单元(translation unit): 可以让编译器生成代码的基本单元,一般一个源代码文件就是一个编译单元。

非本地静态对象(non-local static object): 静态对象可以是在全局范围定义的变量,在名空间范围定义的变量,函数范围内定义为static的变量,类的范围内定义为static的变量,而除了函数中的静态对象是本地的,其他都是非本地的。

回到问题,现有以下服务器代码:
class Server{...};  
extern Server server;                 //在全局范围声明外部对象server,供外部使用
又有某客户端:
class Client{...};
Client::Client(...){
  number = server.number;
}

Client client;                       //在全局范围定义client对象,自动调用了Client类的构造函数
以上问题在于,定义对象client自动调用了Client类的构造函数,此时需要读取对象server的数据,但全局变量的不可控性让我们不能保证对象server在此时被读取时是初始化的。试想如果还有对象client1, client2等等不同的用户读写,我们不能保证当前server的数据是我们想要的。

幸运的是一个小小的设计便可完全消除这个问题。唯一需要做的是:将每个non-local static 对象搬到自己的专属函数内(该对象在此函数内被声明为static) 。这些函数返回一个 reference 指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。换句话说, non-local static 对象被 local static 对象替换了。

解决方法: 将全局变量变为本地静态变量

使用一个函数,只用来定义一个本地静态变量并返回它的引用。因为C++规定在本地范围(函数范围)内定义某静态对象时,当此函数被调用,该静态变量一定会被初始化。
class Server{...};

Server& server(){                         //将直接的声明改为一个函数
  static Server server;
  return server;
}

class Client{...};

Client::client(){                       //客户端构造函数通过函数访问服务器数据
  number = server().number;
}

Client& client(){                       //同样将客户端的声明改为一个函数
  static Client client;
  return client;
}

参考资料:

Effective C++ 第3版

https://zhuanlan.zhihu.com/p/64141116




----------------------------------------------------------------------------------------------------------------------
我们尊重原创,也注重分享,文章来源于微信公众号:光城,建议关注公众号查看原文。如若侵权请联系qter@qter.org。
----------------------------------------------------------------------------------------------------------------------

鲜花

握手

雷人

路过

鸡蛋

yafeilinux和他的朋友们微信公众号二维码

微信公众号

专注于Qt嵌入式Linux开发等。扫一扫立即关注。

Qt开源社区官方QQ群二维码

QQ交流群

欢迎加入QQ群大家庭,一起讨论学习!

我有话说......