Item01 视C++为一个语言联邦
C++由四个次语言组成:
- C:过程形式,没有模板、没有异常、没有重载
- Object-Oriented C++:面向对象形式,类(构造函数和析构函数)、封装、继承、多态
- Template:泛型编程、模板元编程
- STL:容器、算法、迭代器和函数对象
Item02 尽量以const,enum,inline替换#define
目标是让编译器来替代预处理器,使用预处理器会存在以下问题:
1. 预处理器只进行简单的替换,变量名称不会被记录符号表,同时可能导致编译的目标文件包含常量的多份拷贝;
#define ASPECT_RATIO 1.653
// 如果没有被记录到符号表,程序遇到编译错误时,输出的错误信息1.653
// 会带来不必要的排查时间
// 替换为
const double AspectRatio = 1.653;
2. 定义字符串的采用string替换char*,避免写两次const的情况出现;
const char* const authorName = "Scott Meyers";
//替换为
const std::string authorName("Scott Meyers");
3. #define没有作用域,不能将常量的作用域限制在class内;
class GamePlayer {
private:
static const int NumTurns = 5; // 常量声明
int scores[NumTurns]; // 使用该常量
}
4. 如果上述类中,编译器不允许在类中设定初值,可以改用枚举值替代;
5. enum 和 #define定义的对象不会导致非必要的内存分配;
class GamePlayer {
private:
enum { NumTurns = 5 }; // NumTurns 成为5的记号名称
int scores[NumTurns];
}
- enum 行为类似#define,而不像const,因为对const对象取地址是合法的,对前面两个定义的对象取地址是不合法的;
- 在C++中,enum类型的取值通常被编译器实现为整数(右值),对右值取地址是非法的;
- 它们的值被编译器直接嵌入到生成的机器码中,不会在栈上开辟空间,没有内存分配的过程。
6. 形似函数的宏,最好用inline函数替换文章来源:https://www.toymoban.com/news/detail-809769.html
- 宏定义避免了函数调用的开销
- 但是存在行为不可预料以及类型安全等问题
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a被累加两次
CALL_WITH_MAX(++a, b+10); // a被累加一次
// 替换为
template<typename T>
inline void callWithMax(const T& a, const T& b) {
f(a > b ? a : b);
}
Item04 确定对象被使用前已被初始化
背景:读取初始化的值会导致不明确的行为,因此使用对象之前首先需要初始化文章来源地址https://www.toymoban.com/news/detail-809769.html
- 内置类型:手动初始化
- 自定义类型:由构造函数来进行初始化;
- 成员变量的初始化动作发生在进入构造函数本体之前;
- 初始化列表调用一次拷贝构造函数,效率比先调用默认构造函数再调用拷贝赋值效率高;
- 如果成员变量是const或者引用类型,一定要赋初值,不能被赋值;
- 成员变量的初始化顺序:
- 父类成员初始化优先于子类
- 以成员变量声明的次序被初始化(不是在初始化列表的顺序)
- 如果成员变量声明由依赖关系,注意先后顺序
class PhoneNumber {};
class ABEntry {
public:
// 初始化列表效率更高
ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones):
theName(name),
theAddress(address),
thePhones(phones),
numTimesConsulted(0) {}
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
const int test;
};
// 赋值,首先调用默认构造函数为变量设初值,然后立刻赋予新值
// ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones) {
// theName = name;
// theAddress = address;
// thePhones = phones;
// numTimesConsulted = 0;
// }
int main() {
int x = 0;
const char* text = "A C-style string";
double d;
std::cin >> d;
return 0;
}
/*
* const成员变量没有初始化
item_04.h: In constructor ‘ABEntry::ABEntry(const string&, const string&, const std::__cxx11::list<PhoneNumber>&)’:
item_04.h:10:5: error: uninitialized const member in ‘const int’ [-fpermissive]
10 | ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones):
| ^~~~~~~
item_04.h:20:15: note: ‘const int ABEntry::test’ should be initialized
20 | const int test;
*/
- non-local-static对象
- 包括:global对象、namespace作用域内对象、类内、file作用域内
- 定义于不同的编译单元内的non-local-static对象初始化顺序无明确定义
- 考虑以下场景,两个不同文件中变量,需要确保tfs被使用前被初始化;
- 解决方法是声明一个专属函数,然后返回一个静态引用
- 函数被调用期间,首次遇到该定义时初始化;
- 如果不被调用就不会引发构造和析构的成本;
- 多线程环境下需要在主线程启动前手动调用reference-returning函数
class FileSystem {
public:
std::size_t numDisks() const;
};
FileSystem& tfs() {
static FileSystem fs;
return fs;
}
class Directory {
public:
Directory();
}
Directory::Directory() {
std::size_t disks = tfs.numDisks();
}
Directory& tempDir() {
static Directory td;
return td;
}
到了这里,关于【Effective C++】让自己习惯C++的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!