条款 01:视C++为一个语言联邦
-
C++的次语言:
- C
- Object-Oriented C++
- Template C++
- STL
-
C++的编译流程:
C++代码的编译流程通常包括了多个阶段,从源代码到可执行程序的生成。下面是一个简要的C++代码编译流程的概述:1. 预处理(Preprocessing):- 在这个阶段,编译器会处理所有的预处理指令,例如 `#include`、`#define` 等,以及删除注释。- 预处理指令的处理会生成一个经过预处理的源代码文件。2. 编译(Compilation):- 经过预处理的源代码会被编译器转换为汇编语言代码(通常是中间表示形式)。- 编译阶段主要负责检查语法错误和类型错误,并生成对应的中间表示文件。3. 汇编(Assembly):- 在这个阶段,汇编器将中间表示的代码转换为机器代码,生成目标文件(通常是以 `.obj` 或 `.o` 为扩展名)。- 目标文件包含了计算机可执行的二进制代码和相关的元信息。4. 链接(Linking):- 当你的程序使用了多个源文件或库文件时,这些目标文件需要被链接在一起,生成最终的可执行程序。- 链接器会解决函数调用、变量引用等,并将不同目标文件中的代码和数据合并。- 最终的可执行文件就是由链接器生成的。5. 加载(Loading)(可选):- 如果你的操作系统需要,可执行文件会被加载到内存中,准备运行。- 这一步骤通常由操作系统完成。6. 运行(Execution):- 可执行程序在内存中运行,执行其中的代码,产生相应的计算结果。
需要注意的是,这个流程可能在不同的编译器和操作系统上有所不同。另外,现代的编译器和开发环境通常会在这些阶段中进行优化,以提高代码的性能和效率。
条款 02:尽量以const, enum, inline替换 #define
- #define
是一个预处理指令,用于创建一个简单的宏定义。它允许你在代码中为一个特定的标识符(通常是一个变量名或常量,通常大写)定义一个文本替换。当代码被编译前的预处理阶段处理时,所有使用这个宏定义的地方都会被替换为指定的文本。
缺点: 可能导致代码可读性下降、类型不匹配的问题、难以调试等。在现代C++中,倾向于使用类型安全的常量、枚举、内联函数以及更高级的抽象来取代宏,以提高代码的可维护性和可读性。
#define PI 3.14159
- const
C++ 中的“类专属常量”(Class-specific Constants)通常是指在类内部定义的常量。这些常量在类的作用域内可见,只能通过类名访问,而不能通过类的对象访问。它们可以在类的任何成员函数中使用,无需实例化对象。
通常情况下,类专属常量会使用static
关键字来定义,因为它们属于类而不是类的实例。定义为static
的常量将在所有类的实例之间共享,且只存在一份副本,可以节省内存。
#include <iostream>class MyClass {
public:static const int classConstant = 42; // 类专属常量static const double pi; // 声明,定义在类外部void printClassConstant() {std::cout << "Class constant: " << classConstant << std::endl;}
};const double MyClass::pi = 3.1415926; // 在类外部定义类专属常量int main() {MyClass obj;obj.printClassConstant(); // 调用类专属常量的成员函数std::cout << "Pi: " << MyClass::pi << std::endl; // 通过类名访问类专属常量return 0;
}
- enum
在 C++ 中,非静态成员变量(例如 Array)不能使用类的静态成员变量(例如 ConstNumber)作为数组大小。在编译时,数组大小必须是一个在编译期就可以确定的常量表达式。
//错误的,ConstNumber 虽然是类的静态常量,但是它在编译期是无法确定的,因此不能用作数组大小。
class ANonDefine
{
public:static const int ConstNumber;int Array[ConstNumber];
};
解决方法:
在ConstNumber声明时就赋值。
使用enum
class ANonDefine
{
public:enum {ArraySize=5};int Array[ArraySize];
};
- inline
宏函数容易出错,可以使用inline代替
#include <iostream>#define MAX_MACRO(a, b) ((a) >= (b) ? (a) : (b))inline int max_inline(int a, int b) {return (a >= b) ? a : b;
}int main() {int x = 5, y = 6;int result_macro = MAX_MACRO(++x, y); // 使用宏,产生不同的结果x = 5; y = 6; // 正确的变量赋值int result_inline = max_inline(++x, y); // 使用内联函数,产生不同的结果std::cout << "Macro result: " << result_macro << std::endl; //7std::cout << "Inline result: " << result_inline << std::endl;//6system("pause");return 0;
}
条款 03:尽可能使用const
- const 是一个关键字,用于指定变量、函数参数、函数返回类型等为常量。const 关键字的使用可以在代码中增加类型安全性,并帮助防止对变量进行意外的修改。
// const出现在*左边,表示被指物是常量,出现在*右边,表示指针自身是常量
const int x = 5; // 声明一个不可修改的整数常量
const int* ptr = &x; // 声明一个指向常量的指针,指针指向的常量不可修改
int const * ptr = &x; // 声明一个指向常量的指针,指针指向的常量不可修改
int y = 10;
int* const ptr = &y; // 声明一个指针常量,指针不可修改
const int* const ptr = &y; // 指针及其指向的常量都不可修改
- stl 中的 const
stl 的迭代器基础是指针,所以迭代器的作用就像个T *指针。
声明迭代器为 const 就像 T * const。表示指针不改变,可以改内容。
如果希望迭代器所指的东西不可改变,const_iterator代替iterator,表示指针可变,不可修改内容。
std::vector<int>Vector;
for(int i=0;i<10;++i)
{Vector.push_back(i);
}
const std::vector<int>::iterator Iterator=Vector.begin();
*Iterator=10;//所指物可以修改
//Iterator++;迭代器不可修改std::vector<int>::const_iterator ConstIterator=Vector.begin();
//*ConstIterator=10;所指物不可修改
ConstIterator++;//指针可以修改
for (auto i=ConstIterator;i!=Vector.end();++i)
{FFunctionLibrary::Println(*i);
}
- const成员函数
在成员函数声明和定义的末尾,使用 const 关键字来表示这是一个常量成员函数。
在常量成员函数中,不能修改类的非静态成员变量,包括类的数据成员和普通成员函数。
常量对象(通过 const 关键字声明的对象)只能调用常量成员函数。非常量对象可以调用常量成员函数。
const 成员函数的返回类型是否加上 const 关键字会影响返回值的常量性,如果 const 成员函数的返回类型不加上 const 关键字,那么返回的值被修改。如果 const 成员函数的返回类型加上 const 关键字,那么返回的值将被视为常量,不允许修改,只能读取。
C++允许在类中同时定义常量和非常量版本的同名函数,它们可以通过对象的常量性来区分。这被称为函数的 const 重载。const 类对象调用 const 成员函数,non-const 类对象调用普通成员函数。
#include <iostream>class MyClass {
public:void printValue() {std::cout << "Non-const function: " << value << std::endl;}void printValue() const {std::cout << "Const function: " << value << std::endl;}void setValue(int newValue) {value = newValue;}
private:int value = 0;
};int main() {MyClass obj;const MyClass constObj;obj.setValue(42);obj.printValue(); // 调用非常量成员函数constObj.printValue(); // 调用常量成员函数return 0;
}
- mutable
mutable 用于修饰类的非静态成员变量,在常量成员函数内部允许对这些变量进行修改,即使对象被标记为 const。
使用 mutable 可以在常量成员函数中实现对某些状态的修改,而不影响常量性的约束。
class MyClass {
public:int getValue() const; // 声明常量成员函数
private:mutable int count; // 声明一个可变成员变量
};int MyClass::getValue() const {// 在常量成员函数内修改可变成员变量count++;return count;
}
- const和non-const成员函数中避免重复
non-const成员函数调用const成员函数是一个避免代码重复的安全做法,需要一个转型(casting)动作,通过static_cast将‘*this’非常对象转换为常量对象,然后调用常量版本的成员函数,const_cast将常函数返回的常引用转换为普通引用。
注意:不可以让const版本调用non-const版本以避免重复。
const int& operator[](int index)const
{return IntNumber;
}
int& operator[](int index)
{//return IntNumber;return const_cast<int&>(static_cast<const Const&>(*this)[index]);
}
条款 04:确定对象被使用前已先被初始化
- 内置类型需要手工初始化,其他的初始化需要构造函数。
- 构造函数最好使用成员初值列(member initialization list),而不要在构造函数本体内使用赋值操作(assignment)初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
- 全局变量的初始化早于main函数。
- C++对于定义不同编译单元的非局部静态对象的初始化次序并无要求。如果某编译单内的某个非局部对象的初始化依赖于另一个编译单元内某个非局部对象,而这个对象可能尚未被初始化,然后就会导致未定义行为。为解决这个问题可以使用局部静态变量代替non-local static变量。
//file1.cpp
#include <iostream>int getGlobalValue();int main() {std::cout << "Global value from File1: " << getGlobalValue() << std::endl;return 0;
}
//file2.cpp
#include <iostream>int getGlobalValue() {static int value = 42; // 局部静态变量,仅在首次调用时初始化return value;
}
在这个示例中,File1.cpp 和 File2.cpp 是两个不同的编译单元。File1.cpp 中的 main 函数调用了 getGlobalValue 函数,而 getGlobalValue 函数在 File2.cpp 中定义,返回一个局部静态变量。由于局部静态变量的初始化仅在首次调用时进行,因此不需要担心初始化顺序的问题,从而避免了编译单元之间的依赖。