9.1、单独编译
单独编译是指将一个大型程序划分为多个源文件,并将这些源文件分别编译为单独的目标文件,再将这些目标文件链接为最终的可执行文件。使用单独编译可以简化程序的维护和管理,并提高代码的复用性和可读性。
在 C++ 中,单独编译的工具是编译器和链接器。编译器负责将源代码编译为目标文件,而链接器负责将这些目标文件链接在一起,生成最终的可执行文件。在单独编译的过程中,开发人员需要合理编写头文件和源文件,以尽可能减小重复编译的次数,从而提高编译效率。
下面是一个使用单独编译的示例:
// Circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
class Circle {
public:
Circle(double r);
double getArea() const;
double getCircumference() const;
private:
double radius;
};
#endif // CIRCLE_H
// Circle.cpp
#include "Circle.h"
constexpr double PI = 3.1415926;
Circle::Circle(double r) : radius(r) {}
double Circle::getArea() const {
return PI * radius * radius;
}
double Circle::getCircumference() const {
return 2 * PI * radius;
}
// main.cpp
#include <iostream>
#include "Circle.h"
int main() {
Circle c(2.0);
std::cout << "Area of circle = " << c.getArea() << ", circumference of circle = " << c.getCircumference() << std::endl;
return 0;
}
在这个示例中,我们将 Circle
类的定义分别放在头文件 Circle.h
中,将实现代码放在源文件 Circle.cpp
中。在 main
函数中,我们直接包含了 Circle.h
头文件,并创建了一个 Circle
类型的对象,并调用其方法来得到圆的面积和周长。
在编译时,我们需要先将 Circle.cpp
编译为目标文件 Circle.o
,然后再将 main.cpp
编译为目标文件 main.o
,最后使用链接器将这两个目标文件链接在一起,生成最终的可执行文件。
总之,单独编译是一种优秀的编程实践,它可以提高代码的复用性和可维护性,尤其对于大型程序来说更是如此。编写可重用的模块和合理地划分源文件是实现单独编译的关键。
9.2、存储持续性、作用域和链接性
C++ 中有三个重要的概念,分别是存储持续性、作用域和链接性,它们可以影响变量的生命周期、可见性和可访问性。
1、存储持续性:表示变量在程序的整个运行周期内的存在形式。C++ 中有四种存储持续性,分别是:
- 自动存储持续性(auto):由编译器在其所在函数或程序块执行时为其分配存储空间,当函数或程序块结束时,该变量被自动销毁。默认情况下,所有函数中定义的变量都是具有自动存储持续性的。
- 静态存储持续性(static):表示变量在程序的整个运行周期内都存在,但其作用域仅限于当前文件,其他文件无法访问。静态变量在第一次被初始化时被创建,直到程序结束才会被销毁。
- 动态存储持续性(dynamic):表示变量在程序运行期间通过函数库函数 malloc() 等动态分配的内存存在,只有在使用 free() 函数将其释放时才会被销毁。
- 线程存储持续性(thread_local):表示变量在程序的整个运行周期内都存在,但其作用域与自动或静态变量不同,它的作用域仅限于定义该变量的线程。在多线程程序中,每个线程都拥有独立的存储空间。
2、作用域:表示变量所能被访问到的范围或可见性。C++ 中有三种作用域,分别是:
- 块作用域:表示变量仅在其被定义的块内可见,离开该块后就无法访问。
- 函数作用域:表示变量仅在其被定义的函数内可见,离开该函数就无法访问。
- 文件作用域:表示变量在整个文件中都可见,其他文件无法访问。
3、链接性:表示变量在程序内可能链接到的其他同名变量的情况。C++ 中有两种链接性,分别是:
- 内部链接性(internal):表示变量仅在当前文件内可见,其他文件无法访问。使用 static 关键字修饰的变量具有内部链接性。
- 外部链接性(external):表示变量可以被程序中所有文件访问。使用 extern 关键字修饰的变量具有外部链接性,默认情况下,所有非静态变量都具有外部链接性。
总之,存储持续性、作用域和链接性三个概念是 C++ 程序运行中关键的概念之一,了解它们有助于编写程序并且可以更好地理解 C++ 程序的工作方式。
9.2.1、作用域和链接
作用域和链接是 C++ 中关键的概念,在变量和函数的声明和定义中起到重要作用。
1、作用域
作用域是指变量或函数的可见性,即在哪些地方可以使用该变量或函数。在 C++ 中,有以下三种作用域:
- 块作用域(局部作用域):变量或函数定义在一个块内(例如,一个函数,for 循环等),只在该块内可见,该变量或函数称为局部变量或局部函数。局部变量和局部函数的作用域以及生命周期都在其包含块内。
- 函数作用域:变量或函数定义在一个函数之内,只在该函数内可见,该变量或函数称作函数局部变量或函数局部函数。函数局部变量和函数局部函数的作用域以及生命周期都在其定义的函数内。
- 文件作用域(全局作用域):变量或函数定义在一个源文件之内,可以在该文件内的任何位置被访问,有时候文件作用域也被称为全局作用域。与函数局部变量相似,文件作用域变量的生命周期从它的定义处开始,直到程序结束。
2、链接
链接指的是全局标识符(全局变量或函数)的可见性和可访问性,即程序中所有文件间标识符的行为方式。在 C++ 中,有以下两种链接性:
- 外部链接性:表示该标识符可跨文件访问。例如,多个源文件都需要使用到同一个函数,那么该函数应该是具有外部链接性的。具有外部链接性的变量或函数可以在源文件之间共享。
- 内部链接性:表示该标识符只在当前文件内可见。在一个源文件内定义的具有内部链接性的标识符只能在同一文件中可见。
默认情况下,所有未显式标记为 static
的全局标识符具有外部链接性。但是,使用 static 关键字来修饰一个全局标识符,可以将其修改为具有内部链接性。
总之,作用域和链接是 C++ 中非常重要的概念,需要程序员理解并妥善处理它们,以确保程序的正确性和可维护性。
9.2.2、自动存储持续性
自动存储持续性是指在 C++ 中,函数内的局部变量默认情况下具有的一种存储持续性,即仅在函数调用时存在,函数返回时该变量的值也会被销毁。换句话说,局部变量在进入函数时被分配内存,在函数退出时,变量所占用的内存将被释放。
例如,下面是一个计算阶乘的函数:
#include <iostream>
using namespace std;
int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
int main() {
int n = 5;
int f = factorial(n);
cout << f << endl;
return 0;
}
在上述代码中,变量 result
是一个具有自动存储持续性的局部变量。它仅在函数 factorial
被调用时存在,并且在函数返回时被销毁。因此,每个函数调用都将创建一个新的变量 result
,而这些变量在不同的函数调用中是相互独立的。
自动存储持续性是 C++ 中函数作用域的一个重要特性,它可以帮助程序员临时存储和处理数据,并在函数完成后自动释放内存。然而,需要注意的是,如果局部变量的生命周期较长,在函数调用之间保持其状态,就需要使用其他类型的存储持续性,例如静态存储持续性或动态存储持续性。
9.2.3、静态持续变量
静态持续变量是指在 C++ 程序运行期间始终存在,其作用域为当前文件或函数。与自动存储持续性不同,静态变量的生命周期不依赖于函数调用。在程序启动时,静态变量被创建并分配内存,在程序结束时才会被销毁。
在 C++ 中,可以通过在变量声明前加上 static
关键字来定义静态变量。例如:
#include <iostream>
using namespace std;
void test() {
// 声明静态变量
static int count = 0;
count++;
cout << "Count: " << count << endl;
}
int main() {
for (int i = 0; i < 5; i++) {
test();
}
return 0;
}
上述代码中的 count
变量是一个局部的静态持续变量。每次调用 test()
函数时,count
的值都会加 1,并打印出结果。由于 count
是一个静态变量,它的值会被保留,因此输出结果为:
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
需要注意的是,虽然静态变量的生命周期与程序运行时间相同,但专门在多线程编程中使用时,因为静态变量共享存储空间,因此可能存在线程安全的问题,需要小心使用。
9.2.4、静态持续性、外部链接性
静态持续变量和外部链接性是 C++ 程序中的两个重要概念。静态变量是一个变量,它的存储持续性是静态的,即在整个程序的运行期间都存在,同时,它的作用域被限制在当前文件或函数内。外部链接性是一个变量的链接特征,它具有外部链接性的变量可以被其他文件中的函数或变量所共享。
在 C++ 中,可以将静态变量的链接特征设置为外部链接性,以便在程序的多个文件中访问它。要实现静态持续性和外部链接性,需要在静态变量的声明前添加 static
关键字和 extern
关键字。例如:
// a.h 文件
extern int static_var;
// a.cpp 文件
int static_var = 0;
// b.cpp 文件
#include "a.h"
void func() {
static_var++;
}
在上述代码中,变量 static_var
在 a.cpp
文件中进行了定义,并且在 a.h
文件中进行了声明,以便其他文件中的函数可以访问该变量。在 b.cpp
文件中,函数 func()
使用了变量 static_var
,并将其递增。由于 static_var
具有外部链接性和静态持续性,它在整个程序的运行期间都存在,并且可以在多个文件中共享。
需要注意的是,在使用静态变量时,应该考虑到其生命周期和可见性,同时了解其作用域和链接性以确保变量的正确使用。
9.2.5、静态持续性、内部链接性
静态持续变量和内部链接性是 C++ 程序中的两个重要概念。静态变量是一个变量,它的存储持续性是静态的,即在整个程序的运行期间都存在,同时,它的作用域被限制在当前文件或函数内。内部链接性是一个变量的链接特征,它使得该变量只能在定义该变量的文件中访问。
在 C++ 中,可以将静态变量的链接特征设置为内部链接性,以便在文件内部访问它。要实现静态持续性和内部链接性,需要在静态变量的声明前添加 static
关键字。例如:
// a.cpp 文件
static int static_var = 0;
void func() {
static_var++;
}
在上述代码中,变量 static_var
具有内部链接性和静态持续性,它在整个程序的运行期间都存在,并且只能在 a.cpp
文件内部访问。函数 func()
使用了变量 static_var
并将其递增。
需要注意的是,在使用静态变量时,应该考虑到其生命周期和可见性,同时了解其作用域和链接性以确保变量的正确使用。除非必要,否则应尽量避免使用具有内部链接性的变量,因为它们可能会引起命名冲突,并增加程序的复杂性。
9.2.6、静态存储持续性、无链接性
静态存储持续性和无链接性是 C++ 程序中的两个重要概念。静态存储持续性指在程序运行期间,存在于固定的存储位置,并保留其值的变量。无链接性是一个变量的链接特征,它使得该变量只能在当前的函数或作用域内访问。
在 C++ 中,可以将静态变量的存储持续性设置为静态存储持续性并将链接特征设置为无链接性,以便在当前函数或作用域内使用它。要实现静态存储持续性和无链接性,需要在静态变量的声明前添加 static
关键字和 extern
关键字。例如:
#include <iostream>
using namespace std;
void func() {
static int num = 0;
num++;
cout << "Num: " << num << endl;
}
int main() {
func();
func();
return 0;
}
在上述代码中,变量 num
具有静态存储持续性和无链接性。在第一次调用函数 func()
时,num
被创建并分配了内存,并且在第二次调用该函数时,num
的值将在上一次的值上递增。在整个程序的运行期间,变量 num
一直存在且不可访问。
需要注意的是,具有无链接性的变量只能在其定义的函数或作用域内访问。例如,在上述代码中,在函数 main()
中无法访问变量 num
。同时,需要注意避免在多个函数或作用域内使用重名的无链接性变量,以免引起命名冲突。
9.2.7、说明符和限定符
在 C++ 中,说明符和限定符用于在变量或函数的类型前修饰它们的声明。它们可以用于指定变量或函数存储类型、作用域、链接性等特性,或者对变量或函数进行限制和约束。常见的说明符和限定符包括:
static
关键字:用于定义静态变量或函数,使其具有静态持续性、内部链接性或静态存储持续性。extern
关键字:用于声明变量或函数,表示该变量或函数在其他文件中已经定义。auto
关键字:用于定义自动存储变量或函数,使其具有自动存储周期和块作用域。register
关键字:用于定义寄存器变量或函数,使其具有较快的访问速度和块作用域。const
关键字:用于定义常量或函数,表示该变量或函数的值无法修改。volatile
关键字:用于定义易变变量或函数,表示该变量或函数的值可以在程序的运行期间随时变化,需要进行特殊处理。mutable
关键字:用于定义可变数据成员,表示该成员可以在const
函数中被修改。inline
关键字:用于定义单行代码函数,表示该函数可能在使用时被展开,以提高运行效率。friend
关键字:用于定义友元关系,使得其他类或函数可以访问该类的私有或保护成员。
需要注意的是,这些说明符和限定符的用法和作用范围在不同的场合下可能有所不同,需要根据具体的情况灵活运用和选择。同时,在使用时,应该注意它们可能对程序结构和性能产生的影响,以免引起不必要的问题。
9.2.8、函数和链接性
在 C++ 中,函数的链接性是指函数的可见性和共享性,即可以在程序中的哪些位置访问该函数。C++ 中的函数具有四种链接性:
-
内部链接性:函数只能在定义函数的文件中访问,其他文件无法访问该函数。使用
static
关键字来实现内部链接性。 -
外部链接性:函数可以在任意文件中访问,其他文件也可以访问该函数。使用
extern
关键字显式声明函数的名称和类型,并在另一个文件中定义该函数。 -
无链接性:函数只能在当前函数或作用域中访问,其他函数或作用域无法访问该函数。使用
static
关键字来实现无链接性。 -
动态链接性:在运行时连接库中的函数,通常是在动态链接库(DLL)或共享对象(SO)中使用。动态链接使得代码可以动态地加载和卸载,可以实现模块化、高效、灵活的软件设计。
需要注意的是,函数的链接性和作用域不同,作用域是指变量或函数在代码中的可见范围,而链接性是指变量或函数在整个程序中的可见范围。同时,在使用函数时,应该注意函数的链接性和作用域,以确保函数可以被正确地访问和使用。
9.2.9、语言链接性
语言链接性是指编程语言规范中定义的函数和变量的链接性。在 C++ 中,函数和变量的链接性是通过关键字和语法规则来实现的,例如 static
关键字用于实现内部链接性,extern
关键字用于实现外部链接性。
C++ 标准库中的函数和变量也有不同的链接性,这取决于库文件的实现方式和编译器的链接方式。通常情况下,标准库的函数和变量具有外部链接性,这意味着它们可以在程序中的任何位置访问,并且可以被多个文件共享。
在编写程序时,应该注意语言链接性和库函数的链接性,以确保正确地链接库、调用库函数、使用库变量。同时,应该避免对库函数和变量进行重定义或多重链接,以避免编译器产生错误或警告信息。
9.2.10、存储方案和动态分配
存储方案和动态分配是 C++ 程序中重要的内存管理概念,对于程序的可靠性和效率具有关键作用。
存储方案包括自动、静态和动态三种。自动存储方案指在函数中声明的局部变量,在函数退出时自动释放。静态存储方案指用 static 关键字声明的变量,在程序运行期间分配一段固定的内存空间,并在程序结束时释放。动态存储方案指使用 new 或 malloc 等函数动态分配内存,需要显示地调用 delete 或 free 等函数释放内存。
动态分配内存是动态存储方案的重要应用,其意义在于程序可以在运行时决定需要多少内存,而不是在编译时就预先分配好内存。动态分配可以避免内存浪费,也可以适应程序在运行时动态增长的需求。但同时,需要注意在使用动态分配内存时,需要正确、合理地管理内存,避免内存泄漏或者过多占用内存,导致程序性能降低甚至崩溃。
9.3、名称空间
9.3.1、传统的C++名称空间
在 C++ 中,名称空间是一种用于封装代码的机制,它可以帮助程序员避免命名冲突问题。名称空间可以将全局命名空间划分为若干个子空间,每个子空间中包含了一组相关的函数、类、变量等成员。相同名称空间内的成员可以直接访问,不同名称空间内的成员需要通过限定符 ::
访问。
在传统的 C++ 中,名称空间的语法形式如下:
namespace MyNamespace {
// 声明或定义 MyNamespace 中的成员
void myFunction();
class MyClass { ... };
int myVariable;
}
// 在其他地方访问 MyNamespace 中的成员
MyNamespace::myFunction();
MyNamespace::MyClass obj;
其中,MyNamespace
是名称空间的名称,myFunction
、MyClass
和 myVariable
是该名称空间中的成员。在其他地方访问该名称空间中的成员时,需要使用限定符 ::
,即 MyNamespace::
。
在 C++11 以后的版本中,还引入了 namespace
关键字后面跟名称空间成员的定义方式,如下所示:
namespace MyNamespace {
void myFunction();
class MyClass { ... };
int myVariable;
}
using namespace MyNamespace; // 可以直接访问 MyNamespace 中的成员
其中,using namespace
语句可以省略限定符 ::
,直接访问名称空间中的成员,但需要注意,这可能会带来命名冲突的问题,因此建议在全局作用域中使用 using
语句,而不是在局部作用域中使用。
9.3.2、新的名称空间特性
在 C++11 以后的版本中,名称空间的功能得到了进一步扩展和完善。下面介绍一些新特性:
1、嵌套名称空间
C++11 以后的版本允许在一个名称空间中定义另一个名称空间,即嵌套名称空间。这样可以进一步划分子空间,提高代码的可读性和可维护性。
namespace MyNamespace {
namespace MySubNamespace {
void myFunction();
class MyClass { ... };
int myVariable;
}
}
using namespace MyNamespace::MySubNamespace;
2、匿名名称空间
在 C++ 中,匿名名称空间是一个不带名称的名称空间,其中的成员在编译时会被自动转换为静态变量。这些变量只能在当前文件中访问,在其他文件中无法使用。这可以用于定义私有静态变量的资源管理。
namespace {
int myStaticVariable; // 私有静态变量
}
void myFunction() {
myStaticVariable++;
}
3、内联名称空间
内联名称空间是一种新型的名称空间,允许将命名空间中的所有成员嵌入到父命名空间中。这样,程序可以像使用直接定义的成员一样使用内联命名空间中的成员,而不需要使用限定符 ::
。
namespace MyNamespace {
inline namespace MyInlineNamespace {
void myFunction();
class MyClass { ... };
int myVariable;
}
}
using namespace MyNamespace;
myFunction();
MyClass obj;
4、namespace
关键字的别名
C++11 以后的版本中,可以使用 namespace
关键字的别名来定义一个新的名称空间,这样可以简化代码和提高可读性。
namespace MyNamespace = MyOtherNamespace::MySubNamespace;
using namespace MyNamespace;
myFunction();
MyClass obj;
9.3.3、名称空间示例
下面是一个简单的示例,演示如何使用名称空间:
#include <iostream>
namespace MyNamespace {
int myVariable = 42;
void myFunction() {
std::cout << "Hello from MyNamespace!" << std::endl;
}
}
int main() {
MyNamespace::myFunction();
std::cout << "myVariable = " << MyNamespace::myVariable << std::endl;
return 0;
}
这个程序定义了一个名称空间 MyNamespace
,其中包含了一个整型变量 myVariable
和一个函数 myFunction
。在 main
函数中,使用限定符 ::
调用了该名称空间中的函数和变量。
输出结果为:
Hello from MyNamespace!
myVariable = 42
9.3.4、名称空间及其前途
名称空间是一种很便利的代码组织方式,能够有效避免名称冲突问题。在现代 C++ 中,名称空间得到了进一步的扩展和完善,例如嵌套名称空间、匿名名称空间和内联名称空间等特性,可以更好地满足实际编程需求。
名称空间的前途也非常广阔。随着软件规模和复杂度的不断增加,代码的组织和管理变得越来越重要。良好的代码组织和规范能够提高代码的可读性、可维护性和可复用性,减少 bug 的出现和修复,从而提高软件的质量和效率。名称空间正是为此而生的一种机制,它可以将代码分组、分类和打包,使得代码更加清晰和易于管理。
除了名称空间,C++ 还有一些其他的机制和工具,例如模板、STL 等,都可以帮助程序员编写高质量、高效率的代码。