目录
C++ 支持模块化开发和单独编译。单独编译是指将一个程序分成多个独立的模块,每个模块可以单独进行编译,然后再将编译后的模块链接在一起形成最终可执行文件。
可以将程序分为以下三部分:
头文件:包含结构声明和使用这些结构的函数的原型
源代码文件:包含与结构有关的函数的代码
源代码文件:包含调用与结构相关的函数的代码
在 C++ 中,预处理指令是在实际编译之前由预处理器处理的一类指令,它们以 `#` 符号为前缀。以下是一些常见的 C++ 预处理指令:
- #include//用于包含头文件
- #define//用于定义宏
- #ifdef//条件编译指令,若某个标识符已定义,则执行后续代码
- #ifndef//条件编译指令,若某个标识符未定义,则执行后续代码
- #if//条件编译指令,根据给定的条件表达式来决定是否执行后续代码
- #else//条件编译指令的分支语句,如果前面的条件不满足,则执行后续代码
- #elif//条件编译指令的分支语句,如果前面的条件不满足但满足当前条件,则执行后续代码
- #endif//条件编译指令的结束标志
- #undef//用于取消宏定义
- #error//用于产生一个编译错误,并输出指定的错误消息
- #pragma//用于向编译器发送特定的指示
- #line//用于改变当前的行号和文件名
-
在 C++ 中,作用域和链接描述了变量、函数和其他命名实体在程序中的可见性和可访问性。
1. 作用域 (Scope):作用域指的是变量、函数或其他命名实体在程序中可被访问的范围。C++ 中有多种类型的作用域:
- 块作用域 (Block Scope):块作用域适用于在代码块内部声明的变量,比如在函数中的局部变量。这些变量在代码块内部可见,并且在代码块外部不可访问。
- 函数作用域 (Function Scope):函数作用域适用于在函数内部声明的变量。这些变量在整个函数内部可见,但在函数外部不可访问。
- 文件作用域 (File Scope):文件作用域适用于在全局范围内(即函数外部)声明的变量和函数。这些变量和函数可以在整个源文件中访问。
- 命名空间作用域 (Namespace Scope):命名空间作用域适用于在命名空间中声明的变量、函数和类。这些实体可以在整个命名空间范围内访问。
2. 链接 (Linkage):链接描述了同一个名字或符号在不同源文件中的连接性质。C++ 中有三种类型的链接:
- 外部链接 (External Linkage):具有外部链接的变量、函数和非常量的全局变量可以在不同的源文件中访问。它们具有全局作用域,其定义在一个源文件中,可以在其他源文件中通过声明来访问。
- 内部链接 (Internal Linkage):具有内部链接的变量和函数只能在同一个源文件中访问。它们具有文件作用域,仅限于定义它们的源文件内部。
- 无链接 (No Linkage):具有无链接的变量和函数只在其定义所在的作用域内有效,不能被其他源文件访问。局部变量和函数参数属于无链接。
在 C++ 中,自动存储持续性是一种存储类别,适用于在函数内部声明的局部变量。当一个变量使用自动存储类别时,它会在程序的执行流程进入该变量所在的代码块时被创建,并在程序执行流程离开该代码块时被销毁。
具体来说,在使用自动存储类别的变量的情况下:
1. 当程序执行流程进入该变量的作用域时,它会被创建并分配内存空间。这意味着变量的初始化会在每次进入该作用域时发生。
2. 在变量所在的代码块内部,该变量是可见和可用的。
3. 一旦程序执行流程离开了变量所在的代码块,该变量会被销毁并释放其占用的内存空间。但是,变量可能会在下次进入该作用域时重新被创建。
自动存储持续性使得局部变量的生命周期与其所在代码块的执行流程相关联。变量的作用域仅限于其所在的代码块。这种存储类别的变量在每次进入和离开代码块时都会被自动管理,减轻了编程者的负担,可以更灵活地使用和管理内存。
需要注意的是,自动存储持续性是 C++ 中默认的存储类别,如果未明确指定其他存储类别(如静态存储持续性),那么变量将使用自动存储持续性。同时,自动存储类别也适用于函数的参数,它们在调用函数时创建并在函数执行结束后被销毁。
-
- #include
-
- void foo()
- {
- int x = 5; // 自动存储类别的局部变量
- std::cout << x << std::endl;
- } // 变量 x 在此处被销毁
-
- int main()
- {
- foo(); // 输出 5
- // 在 main 函数中不可访问变量 x
-
- return 0;
- }
在上面的示例中,变量 `x` 使用了自动存储持续性,它在函数 `foo()` 被调用时创建并初始化为 5,在函数执行结束后被销毁。在 `main()` 函数中无法访问变量 `x`。
静态存储持续性是 C++ 中的一种存储类别,用于声明静态变量。静态持续变量具有以下特点:
1. 静态变量在程序的整个执行期间都存在,其生命周期与程序的生命周期相同。
2. 静态变量在首次声明时初始化,并且只进行一次初始化。之后,变量会保留其值,直到程序结束。
3. 静态变量的作用域限定在其定义所在的代码块内(对于函数内部声明的静态变量)或整个文件内(对于全局静态变量)。在函数内部的静态变量只能在该函数内部访问,在文件内的静态变量可以在整个文件内访问。
4. 静态变量可以被多次访问和修改,因为它们在程序的整个执行期间都存在。
使用静态存储持续性的变量在内存中只有一个实例,这使得它们适用于需要在多次调用之间保持数据状态的情况。同时,静态变量在全局范围内可见,可以在不同的函数之间共享数据。
静态变量在 C++ 中有两种常用的用法:1. 静态局部变量 (Static Local Variables):在函数内部声明为静态的变量被称为静态局部变量。它们在首次进入函数时被初始化,并且在函数调用之间保留其值。静态局部变量的作用域限定在声明它的函数内部。
- #include
-
- void foo()
- {
- static int x = 0; // 静态局部变量
- x++;
- std::cout << "x: " << x << std::endl;
- }
-
- int main()
- {
- foo(); // 输出 x: 1
- foo(); // 输出 x: 2
- foo(); // 输出 x: 3
-
- return 0;
- }
2. 静态全局变量 (Static Global Variables):在任何函数之外声明为静态的变量被称为静态全局变量。它们在程序启动时被初始化,并且在整个程序执行期间保留其值。静态全局变量的作用域限定在所在的源文件内,其他源文件无法直接访问。
- // file1.cpp
- #include
-
- static int g_count = 0; // 静态全局变量
-
- void increment()
- {
- g_count++;
- }
-
- void display()
- {
- std::cout << "Count: " << g_count << std::endl;
- }
-
- // file2.cpp
- #include
-
- extern void increment();
- extern void display();
-
- int main()
- {
- increment();
- increment();
- display(); // 输出 Count: 2
-
- return 0;
- }
在上述示例中,静态局部变量 `x` 在每次函数调用时都会递增。而静态全局变量 `g_count` 在两个不同的源文件中共享并进行递增操作。
使用静态持续变量可以保留变量的状态并实现不同函数之间的数据共享,因此在面对这样的需求时,静态变量是一种有用的工具。
外部链接性(External Linkage)是一个用于描述变量、函数或对象在程序中的可见性和共享性的概念。它指定了变量、函数或对象是否可以在不同的源文件之间进行共享和访问。
具有外部链接性的变量、函数或对象可以在一个源文件中声明,并且可以被其他源文件引用和使用。这允许多个源文件之间进行数据共享和函数调用。
在 C/C++ 中,有以下几种外部链接性的声明方式:
1. 外部变量:全局变量或静态全局变量默认具有外部链接性。它们可以在不同的源文件之间共享和访问。
2. 外部函数:函数声明默认具有外部链接性。函数可以被其他源文件中的函数直接调用。
3. extern 关键字:通过在变量或函数声明之前使用 extern 关键字,可以明确指定它们具有外部链接性。例如,可以使用 extern int variable; 来声明一个外部变量。
4. 链接器(Linker):链接器是将各个源文件中的目标代码(Object Code)组合成可执行程序的工具。它负责解析和处理外部链接性,以使不同源文件中的变量和函数能够正确连接。
使用外部链接性时要小心,因为过度使用全局变量或函数可能会导致代码的可维护性变差。良好的软件工程实践建议尽量限制对全局变量和函数的使用,并使用最小化的接口进行模块化设计。
内部链接性(Internal Linkage)是一个用于描述变量、函数或对象在程序中的可见性和共享性的概念。它指定了变量、函数或对象是否只能在同一个源文件内访问,而不能被其他源文件引用和使用。
具有内部链接性的变量、函数或对象只能在声明它们的源文件内部访问,对于其他源文件是不可见的。这意味着它们不能在不同的源文件之间进行共享和访问。
在 C/C++ 中,以下情况会具有内部链接性:
1. 静态变量:在函数内部或在文件范围内使用 static 关键字声明的变量具有内部链接性。它们只在声明它们的源文件内部可见。
2. 静态函数:使用 static 关键字声明的函数具有内部链接性。它们只能在声明它们的源文件内调用,不能被其他源文件使用。
使用内部链接性可以限制特定变量或函数的作用域,使它们在其他源文件中不可见,避免了全局命名冲突和变量/函数的误用。
例如,下面是一个示例,展示了具有内部链接性的静态变量和函数的使用:
- // 文件1.cpp
- static int staticVar = 10; // 具有内部链接性的静态变量
-
- static void staticFunc() { // 具有内部链接性的静态函数
- // 函数实现
- }
-
- // 文件2.cpp
- extern int staticVar; // 错误:staticVar 是具有内部链接性的,不可见
-
- int main() {
- staticVar = 20; // 错误:staticVar 是具有内部链接性的,不可见
- staticFunc(); // 错误:staticFunc 是具有内部链接性的,不可见
- return 0;
- }
在这个示例中,文件1.cpp中声明了一个具有内部链接性的静态变量`staticVar`和一个具有内部链接性的静态函数`staticFunc`。在文件2.cpp中,试图引用和修改这些具有内部链接性的实体将会导致编译错误。
总结来说,内部链接性将变量、函数或对象的可见性限制在声明它们的源文件内部,在其他源文件中是不可见的。这有助于避免全局命名冲突和不必要的访问。
无链接性(No Linkage)是一个用于描述变量、函数或对象在程序中的可见性和共享性的概念。它指定了变量、函数或对象是在局部作用域内私有的,只能在其定义所在的作用域内访问,而不能被其他作用域(包括其他源文件)引用和使用。
具有无链接性的变量、函数或对象只能在其定义所在的作用域内部访问,对于其他作用域是不可见的。这意味着它们在其他作用域中无法共享和访问。
在 C/C++ 中,以下情况会具有无链接性:
1. 局部变量:在函数内部使用的局部变量具有无链接性。它们只能在函数内部访问,对于其他函数是不可见的。
2. 局部函数:在函数内部定义的函数具有无链接性。它们只能在定义它们的函数内部调用,对于其他函数是不可见的。
3. 代码块中的变量和函数:在代码块(例如 if 语句、for 循环等)内部定义的变量和函数具有无链接性。它们只能在代码块内部访问,对于外部作用域是不可见的。
使用无链接性可以限制变量或函数的作用范围,使其只在所在的局部作用域内起作用,避免了全局命名冲突和不必要的访问。
以下是一个示例,展示了具有无链接性的局部变量和函数的用法:
- #include
-
- void myFunction() {
- int localVar = 10; // 局部变量,具有无链接性
-
- std::cout << "Local variable: " << localVar << std::endl;
- }
-
- int main() {
- myFunction();
- // std::cout << localVar; // 错误:局部变量对于其他函数是不可见的
- return 0;
- }
在这个示例中,`localVar`是一个具有无链接性的局部变量,它只能在`myFunction()`的作用域内访问。在`main()`函数中,我们无法直接访问`localVar`,尝试这样做将导致编译错误。
总结来说,无链接性将变量、函数或对象的可见性限制在其定义所在的作用域内部,对于其他作用域是不可见的。这有助于避免全局命名冲突和不必要的访问。
C++的new运算符用于在堆上动态分配内存,并用于创建对象。当我们使用new运算符来创建对象时,它会在堆上分配足够的内存来存储该对象,并返回对象的指针。
定位new运算符(Placement new)是对标准new运算符的一种特殊形式,它允许我们在已经分配的内存块上进行对象的构造。通常情况下,new运算符会在堆上分配内存并构造对象,而定位new运算符会在指定的内存地址上构造对象。
定位new运算符的语法如下:
new (地址) 类型(构造参数);
其中,地址表示要在其上构造对象的内存地址,类型表示要构造的对象类型,构造参数表示传递给对象构造函数的参数。
以下是一个示例,演示了定位new运算符的使用:
- #include
-
- class MyClass {
- public:
- MyClass(int val) : value(val) {
- std::cout << "Constructor called. Value: " << value << std::endl;
- }
-
- ~MyClass() {
- std::cout << "Destructor called. Value: " << value << std::endl;
- }
-
- void PrintValue() {
- std::cout << "Value: " << value << std::endl;
- }
-
- private:
- int value;
- };
-
- int main() {
- // 分配内存
- void* memory = operator new(sizeof(MyClass));
-
- // 在已分配的内存上构造对象
- MyClass* object = new (memory) MyClass(10);
-
- // 调用对象的成员函数
- object->PrintValue();
-
- // 销毁对象
- object->~MyClass();
-
- // 释放内存
- operator delete(memory);
-
- return 0;
- }
在这个示例中,我们首先使用`operator new`分配了足够的内存来存储`MyClass`对象。然后,使用定位new运算符在这块内存上构造了一个`MyClass`对象,传递参数`10`给构造函数。接下来,我们调用对象的成员函数`PrintValue`来打印对象的值。最后,我们使用析构函数和`operator delete`手动释放内存。
定位new运算符的使用场景多样,特别适用于在已经分配的内存块上构造对象,例如在自定义内存管理器中或处理特定的内存布局要求时。但要注意使用定位new运算符时,我们需要手动管理对象的构造和析构,以确保正确的对象生命周期管理。
1. 使用 using 编译指令:
使用 using 编译指令可以直接引入整个命名空间或命名空间中的特定成员到当前的全局命名空间中。这样,在代码中就可以直接使用命名空间中的成员,而不需要在前面加上命名空间的限定符。
例如,使用 `using namespace MyNamespace;` 将 MyNamespace 命名空间中的所有成员引入到当前命名空间中。这样,在代码中就可以直接使用 MyNamespace 中的成员,而不需要写完整的命名空间限定符。
下面是示例代码:
- #include
-
- namespace MyNamespace {
- int variable = 42;
- void printMessage() {
- std::cout << "Hello from MyNamespace!" << std::endl;
- }
- }
-
- int main() {
- using namespace MyNamespace;
-
- printMessage();
- std::cout << "Variable value: " << variable << std::endl;
-
- return 0;
- }
在这个示例中,我们使用 using namespace MyNamespace; 将 MyNamespace 命名空间中的成员引入到当前命名空间中。因此,我们可以在 main 函数中直接调用 printMessage() 函数和访问 variable 变量。
2. 使用 using 声明:
使用 using 声明可以在局部范围内引入单个命名空间成员到当前作用域中,而不是引入整个命名空间。这样,在代码中就可以直接使用该命名空间成员,而不需要写完整的命名空间限定符。
例如,使用 `using MyNamespace::printMessage;` 将 MyNamespace 命名空间中的 printMessage 函数引入到当前作用域中。这样,在代码中就可以直接调用 printMessage 函数,而不需要写 MyNamespace::printMessage。
下面是示例代码:
- #include
-
- namespace MyNamespace {
- int variable = 42;
- void printMessage() {
- std::cout << "Hello from MyNamespace!" << std::endl;
- }
- }
-
- int main() {
- using MyNamespace::printMessage;
-
- printMessage();
- std::cout << "Variable value: " << MyNamespace::variable << std::endl;
-
- return 0;
- }
在这个示例中,我们使用 using MyNamespace::printMessage; 将 MyNamespace 命名空间中的 printMessage 函数引入到当前作用域中。因此,我们可以在 main 函数中直接调用 printMessage() 函数。
无论是使用 using 编译指令还是 using 声明,都需要小心使用,以避免命名冲突和引入太多不必要的符号。在全局命名空间和头文件中避免使用 using 编译指令,而在局部作用域中使用 using 声明来提升代码的可读性和简洁性。