2-8 具有多个代码文件的程序

向项目添加文件

随着程序规模扩大,出于组织或复用目的,通常会将程序拆分为多个文件。使用集成开发环境(IDE)的一大优势在于,它能显著简化多文件协作流程。您已掌握创建和编译单文件项目的方法,向现有项目添加新文件同样轻而易举。

最佳实践
向项目添加新代码文件时,请为其指定 .cpp 扩展名。

对于Visual Studio 用户:

在 Visual Studio 中,右键单击“解决方案资源管理器”窗口中的“源文件”文件夹(或项目名称),然后选择“添加”>“新建项…”。

image

请确保已选中C++文件(.cpp)。为新文件命名后,它将被添加到项目中。

注意:您的Visual Studio可能默认显示紧凑视图而非上图所示的全视图。您可以直接使用紧凑视图,或点击“显示所有模板”切换至全视图。

image

注意:若通过“文件”菜单新建文件而非在“解决方案资源管理器”中直接创建,新文件不会自动添加到项目中。您需要手动将其添加至项目:在“解决方案资源管理器”中右键单击“源文件”,选择“添加”>“现有项”,然后选中目标文件。

现在编译程序时,您应能看到编译器在编译过程中列出该文件的名称。

对于Code::Blocks 用户:

在 Code::Blocks 中,请前往文件菜单,选择新建 > 文件…

image

在“新建模板”对话框中,选择“C/C++源文件”,然后单击“确定”。

image

此时您可能会看到C/C++源文件向导的欢迎对话框,也可能不会。若出现该对话框,请单击“下一步”。

image

在向导的下一页中,选择“C++”并单击“下一步”。

image

现在为新文件命名(别忘了添加 .cpp 扩展名),点击“全部”按钮确保选中所有构建目标。最后选择“完成”。

image

现在当你编译程序时,应该能看到编译器在编译过程中列出文件名。

对于 gcc /clang用户
您可以在命令行中使用喜欢的编辑器自行创建附加文件并命名。编译程序时,需在编译命令行中包含所有相关代码文件。例如:g++ main.cpp add.cpp -o main,其中 main.cpp 和 add.cpp 是您的代码文件名,main 是输出文件名。
image
clang的完整配置见Vim目录::vim应用:构建运行C++/C

VS Code用户指南:
创建新文件时,请从顶部导航栏选择“视图 > 资源管理器”打开资源管理器窗格,点击项目名称右侧的“新建文件”图标。或直接选择顶部导航栏的“文件 > 新建文件”。为新文件命名时请注意添加.cpp扩展名。若文件出现在.vscode文件夹内,请将其拖拽至上级项目文件夹。
接着打开 tasks.json 文件,找到 “${file}” 这行。
此时有两种选择:

  • 若需明确指定编译文件,请将 “${file}” 替换为待编译文件名,每行一个,例如:
    “main.cpp”,
    “add.cpp”,
  • 读者“geo”反馈:在Windows系统中,将“${file}‘替换为’${fileDirname}\**.cpp”可实现VS Code自动编译目录内所有.cpp文件。
  • 读者“Orb”反馈:Unix系统中使用“${fileDirname}/**.cpp”同样有效。

一个多文件示例

第2.7课——前向声明与定义中,我们曾分析过一个无法编译的单文件程序:

#include <iostream>

int main()
{
    std::cout << "The sum of 3 and 4 is: " << add(3, 4) << '\n';
    return 0;
}

int add(int x, int y)
{
    return x + y;
}

当编译器执行到主函数第5行的add函数调用时,它并不知道add是什么,因为我们直到第9行才定义add!解决方法是重新排列函数顺序(将add放在首位)或为add添加前向声明。

现在来看一个类似的多文件程序:

add.cpp:

int add(int x, int y)
{
    return x + y;
}

main.cpp:

#include <iostream>

int main()
{
    std::cout << "The sum of 3 and 4 is: " << add(3, 4) << '\n'; // compile error
    return 0;
}

您的编译器可能先编译 add.cpp 或 main.cpp。无论哪种情况,main.cpp 都将编译失败,并产生与前例相同的编译器错误:

main.cpp(5) : error C3861: 'add': identifier not found

clang下显示:
image

原因完全相同:当编译器到达 main.cpp 的第 5 行时,它并不知道 add 这个标识符是什么。

请记住,编译器对每个文件进行独立编译。它既不知道其他代码文件的内容,也不会记住先前编译过的代码文件中的任何信息。因此,即使编译器可能在之前见过函数add的定义(如果它先编译了add.cpp),它也不会记住。

这种有限的可见性和短暂的记忆是故意的,原因有几个:

  1. 它允许项目中的源文件以任意顺序进行编译。
  2. 修改源文件时,仅需重新编译该文件。
  3. 降低不同文件间标识符命名冲突的可能性。

关于命名冲突的具体处理,我们将在下一课(2.9节——命名冲突与命名空间导论)中探讨。

解决方案与之前相同:将函数add的定义置于函数main之前,或通过前向声明满足编译器要求。由于函数add位于其他文件中,重新排序方案不可行。

此处的解决方案是使用前向声明:

main.cpp(含前向声明):

#include <iostream>

int add(int x, int y); // needed so main.cpp knows that add() is a function defined elsewhere

int main()
{
    std::cout << "The sum of 3 and 4 is: " << add(3, 4) << '\n';
    return 0;
}

add.cpp(保持不变):

int add(int x, int y)
{
    return x + y;
}

image

现在,当编译器编译 main.cpp 时,它将知道 add 是哪个标识符,并能正常处理。链接器会将 main.cpp 中对 add 的函数调用与 add.cpp 中 add 函数的定义进行连接。

通过这种方法,我们可以让文件访问位于另一个文件中的函数。

请亲自尝试编译添加了前向声明的add.cpp和main.cpp。若出现链接器错误,请确认已将add.cpp正确添加至项目或编译路径中。

提示
由于编译器会单独编译每个代码文件(且不会记住已处理过的内容),因此每个使用 std::cout 或 std::cin 的代码文件都需要包含 头文件。

在上例中,若 add.cpp 使用了 std::cout 或 std::cin,则必须包含 头文件。

关键要点
当标识符出现在表达式中时,该标识符必须与其定义建立关联。

  • 若编译器在当前编译文件中既未发现该标识符的前向声明也未找到其定义,则会在标识符使用处报错。
  • 否则,若同一文件中存在定义,编译器将把标识符的使用与其定义建立关联。
  • 若定义位于其他文件(且链接器可见),则由链接器建立连接。
  • 否则链接器将报错,提示无法找到该标识符的定义。

出错了!

首次尝试处理多个文件时,可能出现各种问题。若您尝试上述示例时遇到错误,请检查以下事项:

  1. 若编译器报错提示 main 中未定义 add 函数,可能是您忘记在 main.cpp 中为 add 函数添加前向声明。
  2. 若链接器报错提示未定义 add 函数,例如:
unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z) referenced in function _main

2a. …最可能的原因是 add.cpp 未正确添加到项目中。编译时,编译器应同时列出 main.cpp 和 add.cpp。若仅显示 main.cpp,则 add.cpp 肯定未被编译。若使用Visual Studio或Code::Blocks,应在IDE左侧或右侧的解决方案资源管理器/项目窗格中看到add.cpp。若未显示,请右键点击项目,添加该文件后重新编译。若通过命令行编译,请务必在编译命令中同时包含main.cpp和add.cpp。
2b. …可能是将add.cpp添加到了错误的项目中。
2c. …可能是该文件被设置为不编译或不链接。检查文件属性,确保配置为可编译/链接状态。在Code::Blocks中,编译和链接是独立复选框,均需勾选;在Visual Studio中需将“exclude from build”选项设为“no”或保持空白。请分别检查每种构建配置(如调试版和发布版)。

  1. 请勿在 main.cpp 中 #include “add.cpp”。虽然此操作在此情况下能编译通过,但包含 .cpp 文件会增加命名冲突及其他意外后果的风险(尤其当程序规模扩大、复杂度提升时)。我们将在第 2.10 课——预处理器入门中进一步探讨 #include 机制。

摘要

C++的设计使得每个源文件都能独立编译,无需了解其他文件的内容。因此,文件实际编译的顺序不应产生影响。

进入面向对象编程阶段后,我们将频繁处理多个文件,因此现在正是掌握如何添加和编译多文件项目的最佳时机。

提醒:每次创建新代码文件(.cpp)时,都需将其添加到项目中才能进行编译。


测验时间

问题 #1

将以下程序拆分为两个文件(main.cpp 和 input.cpp)。main.cpp 应包含 main 函数,input.cpp 应包含 getInteger 函数。

显示提示

提示:别忘了在 main.cpp 中为函数 getInteger() 添加前向声明。
#include <iostream>

int getInteger()
{
	std::cout << "Enter an integer: ";
	int x{};
	std::cin >> x;
	return x;
}

int main()
{
	int x{ getInteger() };
	int y{ getInteger() };

	std::cout << x << " + " << y << " is " << x + y << '\n';
	return 0;
}

image

显示方案

input.cpp

#include <iostream> // we need iostream since we use it in this file

int getInteger()
{
	std::cout << "Enter an integer: ";
	int x{};
	std::cin >> x;
	return x;
}

main.cpp

#include <iostream> // we need iostream here too since we use it in this file as well

int getInteger(); // forward declaration for function getInteger

int main()
{
	int x{ getInteger() };
	int y{ getInteger() };

	std::cout << x << " + " << y << " is " << x + y << '\n';
	return 0;
}

如果链接器报错提示未定义引用 getInteger(),那么你很可能忘记编译 input.cpp 文件了。

posted @ 2026-02-09 15:21  游翔  阅读(1)  评论(0)    收藏  举报