如何在另一个.cpp文件中调用一个.cpp文件中的函数

How can I call functions from one .cpp file in another .cpp file?

本文关键字:文件 cpp 函数 一个 调用 另一个      更新时间:2023-10-16

我尝试查找这个,并使用头文件等得到混合结果。

基本上,我有多个.cpp文件,其中包含我为与二叉树,BST,链表等一起使用的所有功能。

我不必根据需要复制和粘贴函数,而是希望能够执行以下操作:

#include <myBSTFunctions.h>

并且能够调用和使用我自己的函数。

完成此操作的步骤是什么? 用我使用的所有函数原型制作一个头文件?

我应该在哪里放置包含所有实际功能的.cpp和头文件?

有没有办法直接调用函数文件的目录?

也就是说,我更想把它与主源文件放在同一个文件夹中,.cpp与我的一些同事共享。

我怎样才能做到这一点?

在Windows和MinGW编译器上。

在标头中声明函数:

// File MyFunctions.h
int myFunction1(int, int);
int myFunction2(int, int);

在文件 MyFunctions.cpp 中实现它们:

// File MyFunctions.cpp
#include "MyFunctions.h"
int myFunction1(int a, int b){
// Your code
int myFunction2(int a, int b){
// Your code
}

将标头包含在所需的任何文件中:

// OtherFile.cpp
#include "MyFunctions.h"
// Now you have access to the functions defined in MyFunctions.h in this file

我不知道MinGW,但它应该看起来像这样:

g++ otherfile.cpp MyFunctions.cpp...

你似乎缺少一些关于如何构建程序的非常基本的概念。我将给你一个非常基本的入门知识,但你必须去其他地方找到更完整的答案,才能真正了解正在发生的事情并获得你的设置的细节。

  1. 您通常告诉编译器编译每个 cpp 文件。当 cpp 文件具有#include语句时,它基本上是在编译之前将included 文件复制并粘贴到 cpp 文件中(这是由预处理器完成的)。编译器处理的每个完整单元(cpp 文件,包括)都称为翻译单元。每个翻译单元生成一个对象文件

  2. 目标文件包含已编译的代码,但它们通常不完整。也就是说,它们包含对未包含在其中的代码的引用。基本上,他们可以说"现在调用这个函数,我不知道它在哪里或它做什么,但你应该调用它"。

  3. 然后,链接器用于将对象文件(可能与库一起)链接到可执行文件(或库)中。链接器的工作是通过在其他对象文件和库中查找相关代码来"解析"每个对象文件中对外部代码的所有引用。

  4. 有两种形式:共享库(Windows中的.dll)和静态库。静态库由链接器链接到可执行文件(或其他库)中,这意味着您可以在没有库的情况下使用可执行文件(相关库代码成为可执行文件的一部分)。您还可以将可执行文件/库链接到共享库,在这种情况下,每次运行可执行文件时都需要该共享库的副本 - 操作系统需要在运行共享库之前将编译的代码动态链接到共享库。

所以,回到你的问题。

从广义上讲,您有三种选择:编译,然后每次直接在单个项目中链接所有cpp文件;将有用的可重用代码编译为静态库,然后将项目链接到静态库;或者将有用的可重用代码编译到共享库中,将项目链接到它,并确保将共享库与结果一起发布,以便它可以运行。

任何合理规模的大多数项目都会结合其中的至少两个。多个cpp文件将成为项目代码的一部分,这些代码将编译为单独的翻译单元并提供给链接器。大多数项目还将使用一些库(您自己编写的,或者其他人编写的),这些库根据需要静态或动态链接。

不幸的是(恕我直言)C++作为一种语言并没有一个单一的构建系统来为你组织所有这些(最近的语言经常这样做)。有几种不同的编译器/链接器和许多不同的构建系统都可以完成所有这些工作。不幸的是,您需要采取的具体步骤在很大程度上取决于您选择的编译器和构建系统。

这很简单。正如 JMAA 所说,您应该做一些研究来理解这些概念,但为了实用,这就是您要做的:

您将定义一个函数示例.cpp您必须在其中定义所有函数,以及一个函数示例.h,您将在其中声明您的函数。

您将将其作为函数示例.cpp

#include "functionsExample.h"
#include <iostream>
int example(int x, int y)
{
return x + y;
}

将此作为函数示例.h

#ifndef FUNCTIONSEXAMPLE_H
#define FUNCTIONSEXAMPLE_H
int example(int x, int y);
#endif

然后在.cpp文件中,您要运行示例函数。只需添加:

#include "functionsExample.h"

但正如我所说,你应该对标头保护、预处理器指令和文件组织做一些研究,以便对此有更深入的了解。我会推荐的一些链接:

头文件

预处理器指令

最简单的方法是使用构建工具。 我个人更喜欢介子,但CMake更受欢迎。 还有其他的,但其中任何一个都不会出错。 两者都是跨平台的,支持不同的工具链。 这意味着只要你的代码是标准的C++你就可以使用MinGW,Visual Studio,G ++或Clang轻松地编译它,而无需更改任何内容。 它们允许您从平台上的可用工具链中进行选择。每个网站都有快速入门教程。

您编写的配置文件指定要使用的程序源文件以及要生成的可执行文件。 在配置步骤中选择您使用的构建链,之后您可以通过运行 Make 或 Ninja(Meson需要并推荐用于CMake)来构建可执行文件。如果您更改了源,则只需重新运行 Make 或 Ninja。 当然,只有更改的部分会被重建。 只有受更改影响的可执行文件也会重新链接。

观看一些详细的构建以熟悉系统上的构建过程可能会很有启发性。

PS:传统上,在 #includes 中使用尖括号(<>)作为系统标题,使用引号(")作为您自己的。 这与首先在哪里寻找它们有关。

这篇文章旨在补充现有答案,这些答案不能解决一个非常常见的问题(尤其是在初学者中):包括头文件,其中包含在单独的源文件中实现的模板。


基本上,我有多个.cpp文件,其中包含我为与二叉树、BST、链表等一起使用的所有功能。

因为:

  • 您已使用 c++ 标记了您的问题,并且
  • C++有模板,并且
  • 你有类似容器的数据结构(BST、链表等),我假设使用模板

我想指出一个在其他答案中没有提到的常见问题。请考虑以下两个文件:

文件class.hpp

#pragma once // Non-standard but supported by almost (if not) all compilers
// Some class
template<class T> class Class {
T data;
public:
Class();
};
// Some function
template<class T> T square(const T& x);

文件class.cpp

#include "class.hpp"
#include <iostream>
template<class T> Class<T>::Class()
{
std::cout << "Class()" << 'n';
}
template<class T> T square(const T& x)
{
return x*x;
}

尝试实例化Class对象,或调用square()

#include "class.hpp"
#include <iostream>
int main()
{
Class<int> clss; // should output Class()
std::cout << square<int>(3); // should print 9
}

编译方式:

g++ class.cpp main.cpp -o cpptest.exe
./gcc/bin/ld.exe: main.cpp(.text+0x15): undefined reference to 'Class<int>::Class()'
./gcc/bin/ld.exe: main.cpp(.text+0x28): undefined reference to 'int square<int>(int const&)'
<小时 />

问题

1. 模板

假设你有这个函数:

template<class T> T add(const T& x, const T& y)
{
return x + y;
}

你这样称呼它:

int res = add<int>(2, 3);

在调用的那一刻(即当编译器看到对add()的调用时),编译器会创建函数实现的副本:

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

然后你的电话将变成:

int res = add(2, 3);

这显然意味着在找到调用时,模板函数实现必须对编译器可见,因此编译器将能够复制它。为了使编译器查找/查看它,它必须位于同一翻译单元中。

这就是模板魔术在引擎盖下的工作方式。有关详细信息,请参阅此答案。

2. 编译器和链接器

完成编译步骤:

  1. 将所有#include指令替换为包含的文件内容。
  2. 分别编译每个.cpp文件(也称为"翻译单元")。结果将是每个单元的目标文件。请注意,某些代码(如main.cpp)引用另一个代码(class.cpp)。这将在下一步中解决。
  3. 将翻译单元完全链接到可执行文件中。(有关更多详细信息,请参阅JMAA的答案)。

应用上面的步骤 1,我们将有两个翻译单元(.cpp文件):

文件class.cpp

template<class T> class Class {
T data;
public:
Class();
};
template<class T> T square(const T& x);
/* <iostream> code ... */
template<class T> Class<T>::Class()
{
std::cout << "Class()" << 'n';
}
template<class T> T square(const T& x)
{
return x*x;
}

文件main.cpp

template<class T> class Class {
T data;
public:
Class();
};
template<class T> T square(const T& x);
/* <iostream> code ... */
int main()
{
Class<int> clss;
std::cout << square<int>(3);
}

应用上面的步骤 2,我们将只有地址(这不是真正的目标代码的样子,它只是一个简化):

文件class.cpp

/* Nothing to generate for 'Class' definition. Remember, templates are just models (hence the word "template"), not concrete classes/functions. */
/* Nothing to generate for square()'s definition: It's a template, not concrete. */
iostream_object_code
/* Nothing to generate for 'Class' implementation. It's a template, not concrete. */
/* Nothing to generate for square()'s implementation: It's a template, not concrete. */

文件main.cpp

/* Class is not concrete: nothing is generated. */
/* square() is not concrete: nothing is generated. */
iostream_object_code
int main()
{
class_int_obj_placeholder clss;
__operator_xx(cout_obj_ref, fn100017(3));
}

应用上面的步骤 3,你将收到undefined reference错误:链接器不知道任何翻译单元中的任何fn100017()。由于square()是模板(或模型),因此编译器未生成任何具体实现。这同样适用于Class.

解决方案

  1. 显而易见的解决方法是将定义和实现放在同一个头文件中。这样,当包含在main.cpp中时,编译器将看到两者,因为它们位于同一翻译单元中。
  2. 或者,如果要将定义和实现分开,请将实现放在头文件中,然后将其包含在定义标头的末尾:

文件class.hpp

#pragma once
template<class T> class Class {
T data;
public:
Class();
};
template<class T> T square(const T& x);
#include "class_impl.hpp"

文件class_impl.hpp

#include <iostream>
template<class T> Class<T>::Class()
{
std::cout << "Class()" << 'n';
}
template<class T> T square(const T& x)
{
return x*x;
}

使用相同的命令编译和运行main.cpp

Class()
9