您的位置:首页 > 编程语言 > C语言/C++

《effective c++》学习笔记(一)

2017-08-23 19:25 295 查看

尽量以const, enum, inline替换#define

使用const,enum定义常量

当使用define时,实际上define出的名字并没有进入符号表。从而导致许多莫名其妙的错误,例如:

#define INF 123
int &b = INF;


那么编译器则会提示

invalid initialization of non-const reference of type ‘int&’ from an rvalue of type ‘int’

这是因为引用b需要一个左值,而INF却是一个右值。

所以当我们定义常量时,可以使用
const
enum
来定义,而当需要限制常量的范围,可以使用
static const


使用inline定义较短的函数

观察这一份代码

#define MAXNUM(a, b) (a > b ? a : b)
int a = 1;
int b = 5;
int c = MAXNUM(++a, b);
std::cout << a << std::endl;
std::cout << b << std::endl;
std::cout << c << std::endl;


输出符合我们的预期,a、b、c分别是2、5、5,但是如果a大于b的话

#define MAXNUM(a, b) (a > b ? a : b)
int a = 5;
int b = 1;
int c = MAXNUM(++a, b);
std::cout << a << std::endl;
std::cout << b << std::endl;
std::cout << c << std::endl;


那么此时a、b、c的值分别是7、1、7,而我们预期应该是6、1、6。

因为define只是进行简单的替换,所以当a大于b时,则会执行两次++a,这并不是我们设计这个define的初衷。

所以此时我们可以使用inline函数来代替define。

inline int MAXNUM(int a, int b) {
return a > b ? a : b;
}

int a = 5;
int b = 1;
int c = MAXNUM(++a, b);
std::cout << a << std::endl;
std::cout << b << std::endl;
std::cout << c << std::endl;


此时无论a和b谁大,执行结果都是符合预期的,并且inline也会展开函数,并不会带来函数压栈退栈的额外开销。

对于单纯常量,最好用const对象或enum替换#define

对于形似函数的宏,最好改用inline函数替换#define

尽可能使用const

根据“最小权限”原则:

最小特权原则是系统安全中最基本的原则之一。所谓最

小特权(Least Privilege),指的是”在完成某种操作时所赋予网络中每个主体(用户或进程)必不可少的特权”。最小特权原则,则是指”应限定网络中每个主体所必须的最小特权,确保可能的事故、错误、网络部件的篡改等原因造成的损失最小”。

所以当一个对象能以read权限完成工作的话,就不要赋予他write权限,以保证错误(比如编译错误)发生率最低,看以下这个例子:

class Foo {
// something
};

Foo operator*(const Foo &lhs, const Foo &rhs) {
// something
}

int main() {
Foo a;
Foo b;
Foo c;
if (a * b = c) {
// something
}
return 0;
}


当我们手误写出
a * b = c
时,实际上想表达的是
a * b == c
,但此时编译器并不会报错,因为调用了Foo::operator=。但如果我们将operator*的返回值声明为const类型,则编译器会告诉我们“不能对一个const对象赋值”,那么就可以避免这种错误。

class Foo {
// something
};

const Foo operator*(const Foo &lhs, const Foo &rhs) {
// something
}

int main() {
Foo a;
Foo b;
Foo c;
if (a * b = c) {
// something
}
return 0;
}


将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体

编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量属性”

当const和non-const成员函数有实质等价的实现时,令non-const版本调用const版本可避免代码重复

确定对象被使用前已先被初始化

由于c++的对象在有些时候会初始化,有些时候不会初始化,所以我们尽量对所有变量都进行初始化,以保证行为符合我们的预期。

类成员初始化

并且要注意的是,在class的构造函数中,成员初始化放在初始化列表,而不是放在函数体内,考虑下面两份代码:

class Foo {
Foo(int x, const string& y) {
a = x;
b = y;
}
private:
int a;
string b;
};
/////////////////////////////////
class Foo {
Foo(int x, const string& y) : a(x), b(y) {  }
private:
int a;
string b;
};


虽然最后产生的结果相同,但是第一份代码实际上会先调用a和b的默认构造函数,接着再调用一次赋值操作。而第二份代码则仅仅调用一次a和b的构造函数。

那么当赋值操作消耗性能比较大的时,第一份代码会很耗时间。

不同编译单元间初始化

将每一个cpp文件看作一个编译单元的话,考虑下面三个文件:

//a.cpp
#include <iostream>

int a = 123456789;

void print_a() {
std::cout << "a = " << a << std::endl;
}

//b.cpp
#include <iostream>

extern int a;
int b = a;

void print_b() {
std::cout << "b = " << b << std::endl;
}

//main.cpp
#include <iostream>

void print_a();
void print_b();

int main() {
print_a();
print_b();
return 0;
}


b的初值依赖于a的初值,那么如果b在a之前初始化的话,则a和b的值不会相等,而c++中不同编译单元的初始化顺序是不确定的,所以不能保证a和b的初始化顺序谁前谁后。

解决办法是,可以在b.cpp中定义一个函数,将b定义为local static对象,因为C++保证,函数内的local static对象会在“该函数被调用期间”“首次遇上该对象之定义式”时被初始化。

//a.cpp
#include <iostream>

int get_a() {
static int a = 123456789;
return a;
}

void print_a() {
std::cout << "a = " << get_a() << std::endl;
}

//b.cpp
#include <iostream>

int get_a();

int b = get_a();

void print_b() {
std::cout << "b = " << b << std::endl;
}

//main.cpp
#include <iostream>

void print_a();
void print_b();

int main() {
print_a();
print_b();
return 0;
}


为内置型对象进行手工初始化,因为c++不保证初始化它们

构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同

为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言 c++ effective-c++