C++ 编程
大多数操作系统要求文件由名称后缀特定的扩展名来标识。C++ 标准没有对文件如何命名或组织强加任何特定的规则。
文件组织的特定约定既有技术原因,也有组织上的好处,与我们稍后将要探讨的代码风格约定非常相似。大多数控制文件的约定源于历史偏好和实践,这些偏好和实践尤其与 C++ 之前出现的低级语言有关。当考虑到 C++ 是基于 C89 ANSI 标准构建的,并且考虑到兼容性,大多数实践都保持不变,除了操作系统对文件的改进支持以及对文件资源管理的便利性。
在处理语言标准上的文件名时,一个演变是默认的包含文件将没有扩展名。大多数实现仍然提供旧的 C 风格头文件,这些头文件使用 C 的文件扩展名“.h”来表示 C 标准库,但是以相同方式结尾的 C++ 特定头文件名现在没有扩展名(例如,iostream.h
现在是 iostream
)。这种对旧 C++ 头文件的更改是与 命名空间的实现同时进行的,特别是 std 命名空间
。
选择文件名与命名变量、函数以及通常所有事物的要求相同。名称是一个标识符,不仅方便通信,还方便结构和组织。
大多数关于文件名考虑因素都是常识性的
- 名称应该共享同一种语言:在这个方面,项目的国际化应该是一个因素。
- 名称应该具有描述性,并且由相关头文件共享,扩展名将提供必要的区别。
- 名称将区分大小写,请记住保持一致性。
- 不要重用标准头文件名
正如你将在后面看到的,C++ 标准定义了 头文件列表。如果在包含源文件的搜索路径中放置了与标准头文件同名的文件,则行为是未定义的。
扩展名只有一个用途:指示操作系统、IDE 或编译器文件中包含什么内容。就其本身而言,扩展名不能保证内容。
由于 C 语言源文件通常具有扩展名“.c”和“.h”,因此在最初,C++ 源文件通常共享相同的扩展名,或者使用不同的变体来明确地指示 C++ 代码文件。今天,大多数 C++ 实现文件将使用“.cpp”扩展名,而“.h”用于声明头文件(最后一个扩展名仍然在大多数汇编程序和 C 编译器中共享)。
还有其他常见的扩展名变体,例如,“.cc”、“.C”、“.cxx”和“.c++”用于“实现”代码。对于头文件,使用相同的扩展名变体,但扩展名的第一个字母通常替换为“h”,例如,“.hh”、“.H”、“.hxx”、“.hpp”、“.h++”等等。
头文件将在后面介绍 预处理器部分时详细讨论,其中将介绍 #include 指令和标准头文件,但总的来说,头文件是一种特殊的 源代码 文件,通过 预处理器 的 #include 指令包含进来,通常在“.cpp”文件的开头使用。
即使使用单个文件,C++ 程序也可以编译,但任何复杂的项目都将从拆分为多个源文件以方便管理和允许代码重用中获益。初学者会认为这是一个额外的复杂因素,其好处不明显,尤其是由于大多数初次尝试可能会导致问题。本节将不仅介绍好处和最佳实践,还将解释标准化方法将如何避免和减少复杂性。
- 为什么将代码拆分为多个文件?
简单的程序将适合单个源文件或至少两个文件,除此之外,程序可以拆分为多个文件,以便
- 提高组织性和更好的代码结构。
- 促进代码重用,在同一个项目中以及跨项目进行代码重用。
- 方便多个(通常是同时)编辑。
- 提高编译速度。
- 源文件类型
有些作者将扩展名为“.cpp”的文件称为“源文件”,将扩展名为“.h”的文件称为“头文件”。但是,这两者都属于源代码。作为本书的约定,所有代码,无论是包含在“.cpp”扩展名中(程序员会将代码放在这里),还是包含在“.h”扩展名中(用于头文件),都将称为源代码。任何时候我们谈论“.cpp”文件,我们都会称之为“实现文件”,任何时候我们谈论头文件,我们都会称之为“声明文件”。你应该检查编辑器/IDE 或更改配置以适合你和其他将读取和使用这些文件的人。
- 声明与定义
一般来说,声明为链接器指定标识符、类型以及语言元素(如变量和函数)的其他方面。它用于向编译器宣布元素的存在,编译器要求在使用之前声明变量。
定义为在声明阶段预留的内存区域分配值。对于函数,定义提供了函数体。虽然变量或函数可以声明多次,但它通常只定义一次。
目前这一点并不重要,但它是一个特殊的特性,会影响源代码在文件中的分配方式以及编译器子系统如何处理源代码。在介绍 变量类型后,我们将 更详细地介绍这一点。
实现文件包括程序执行的具体细节,即定义。虽然灯的头文件声明了灯可以做什么,但灯的“.cpp”文件定义了灯如何运行。
我们将在后面详细介绍类定义;下面是一个预览
#include "light.h"
Light::Light () : on(false) {
}
void Light::toggle() {
on = (!on);
}
bool Light::isOn() const {
return on;
}
头文件 通常包含将在程序其余部分使用的声明。类的骨架通常在头文件中提供,而相应的实现文件则提供定义,以实现在该骨架上的内容。头文件不会被编译,而是通过使用#include
提供给程序的其他部分。
一个典型的头文件如下所示
// Inside sample.h
#ifndef SAMPLE_H
#define SAMPLE_H
// Contents of the header file are placed here.
#endif /* SAMPLE_H */
由于头文件包含在其他文件中,因此如果它们被包含多次,则可能会出现问题。这通常会导致使用“头文件保护”,使用预处理器指令(#ifndef、#define 和 #endif)。#ifndef 检查 SAMPLE_H 是否已经出现过,如果没有,则包含头文件并定义 SAMPLE_H。如果 SAMPLE_H 最初被定义,则该文件已经被包含,不再被包含。
类通常在头文件中声明。我们将在后面详细介绍类声明;这里是一个预览
// Inside light.h
#ifndef LIGHT_H
#define LIGHT_H
// A light which may be on or off.
class Light {
private:
bool on;
public:
Light (); // Makes a new light.
void toggle (); // If light is on, turn it off, if off, turn it on
bool isOn(); // Is the light on?
};
#endif /* LIGHT_H - comment indicating which if this goes with */
此头文件“light.h”声明将存在一个 light 类,并给出 light 的属性以及它提供的 method。其他程序员现在可以通过在其实现文件中键入#include "light.h"
来包含此文件,这使他们能够使用此新类。请注意,这些程序员不包含与该类相关的实际 .cpp 文件,该文件包含有关 light 如何实际工作的详细信息。我们将在讨论实现文件后回到这个案例研究。
目标文件是编译器在源代码和最终可执行文件之间作为中间步骤使用的临时文件。所有其他不是源代码或源自源代码的源文件,构建(创建)程序所需的支撑数据。这些文件的扩展名可能因系统而异,因为它们取决于 IDE/编译器和程序的必要性,它们可能包括图形文件或原始数据格式。
编译器生成源代码的等效机器代码(目标代码),包含二进制语言(机器语言)指令,供计算机使用以按照源代码中的指令进行操作,然后可以将其链接到最终程序。此步骤确保代码有效,并将顺序排列成可执行程序。大多数目标文件使用文件扩展名 (.o),具有与上述 (.cpp/.h) 文件相同的限制。
库通常以二进制形式分发,使用 (.lib) 扩展名和提供其使用接口的头文件 (.h)。库也可以动态链接,在这种情况下,扩展名可能取决于目标操作系统,例如,windows 库通常使用 (.dll) 扩展名,这将在本书后面关于库部分中介绍。
源代码通常附带一个名为“Makefile”的特定脚本文件(没有标准扩展名或标准解释器)。这种类型的脚本文件不受 C++ 标准的涵盖,即使它被广泛使用。
在一些项目中,尤其是当处理大量外部依赖项或特定配置(如支持特殊硬件)时,需要自动化大量不兼容的编译序列。这些脚本旨在减轻此任务。详细解释程序员在使用(或不使用)此类系统时可能做出的无数变化和可能的选择超出了本书的范围。您应该查看 IDE、make 工具的文档或您尝试编译的源代码上提供的信息。