使用多个文件制作和编译 C++ 项目
Making and compiling C++ projects with multiple files
我是 C/C++ 的新手,我已经了解基础知识,现在我开始学习更高级的概念。
目前我正在用C++开发一个项目,比较大,所以结构化我的项目会更好。
据我所知,一个好的结构至少依赖于文件夹:/src
和 /include
。所有 .cpp 文件都应该放在 /src
文件夹中,.hpp 文件应该放在 /include
文件夹中。这里出现了第一个疑问:如何包含一个不在同一目录或标准目录中的 header ?在搜索之后我得出结论,最好的方法是将选项 -I../include
传递给编译器。
我有三个源文件:main.cpp、GraphData.cpp 和 RandomGraphGenerator.cpp。我读过,最好为每个文件设置一个 header,以将声明与定义分开。现在我也有两个 header,但我不知道如何编译所有这些。
我专注于使 GraphData.cpp 正确链接到 main.cpp,我评论了所有需要 RandomGraphGenerator.cpp 的内容。这是我的主文件:
// main.cpp
#include <iostream>
#include <string>
#include <Snap.h>
#include <GraphData.hpp>
int main() {
std::string file_path;
char path[1024];
DataPoint **data_set;
int motif_size, num_null_models;
std::cin >> file_path;
std::cin >> motif_size;
std::cin >> num_null_models;
data_set = new DataPoint*[num_null_models];
strncpy(path, file_path.c_str(), sizeof(path));
path[sizeof(path) - 1] = 0;
//read input
PNGraph G = TSnap::LoadEdgeList<PNGraph>(path, 0, 1);
//enumerate and classify k-subgraphs in G
GD::extractData(&G, motif_size, data_set[0]);
//generate random graphs and enumerate and calssify k-subgraphs on them
for(int i=1; i<=num_null_models; i++) {
//GM::randomize(&G);
GD::extractData(&G, motif_size, data_set[i]);
}
//detect motifs
GD::discoverMotifs(data_set);
return 0;
}
我的图表数据header:
#ifndef GRAPHDATA_HPP_INCLUDED
#define GRAPHDATA_HPP_INCLUDED
#include <Snap.h>
struct DataPoint;
namespace GD {
void extractData(PNGraph*, int, DataPoint*);
DataPoint* discoverMotifs(DataPoint**);
}
#endif // GRAPHDATA_HPP_INCLUDED
还有我的 GraphData 文件:
#include <GraphData.hpp>
struct DataPoint {
int label;
int frequency = 0;
};
void GD::extractData(PNGraph* G, int k, DataPoint* data_array) {
//stuff
}
DataPoint* GD::discoverMotifs(DataPoint** data_set) {
//dummy code
DataPoint* dp;
dp = new DataPoint[2];
dp[0].label = 10;
dp[1].label = 99;
return dp;
}
我正在尝试在 Ubuntu 14.10 下使用 GCC 4.9.2 进行编译,使用:
g++ -o main main.cpp /usr/include/Snap-2.3/snap-core/Snap.o -I../include -I/usr/include/Snap-2.3/snap-core -I/usr/include/Snap-2.3/glib-core
但是它给我一个未定义引用的错误:
main.cpp:(.text+0x179): undefined reference to `GD::extractData(TPt<TNGraph>*, int, DataPoint*)'
main.cpp:(.text+0x1b9): undefined reference to `GD::extractData(TPt<TNGraph>*, int, DataPoint*)'
main.cpp:(.text+0x1dd): undefined reference to `GD::discoverMotifs(DataPoint**)'
collect2: error: ld returned 1 exit status
我有点迷茫,我错过了什么?
[注意:这不是问题的答案,只是解释如何一个一个地编译多个文件]
你可以单独编译源文件,但是你必须告诉g++
创建目标文件,这然后你们link在一起。
像这样:
$ g++ -Wall -g -c -I../include -I/usr/include/Snap-2.3/snap-core -I/usr/include/Snap-2.3/glib-core main.cpp
$ g++ -Wall -g -c -I../include -I/usr/include/Snap-2.3/snap-core -I/usr/include/Snap-2.3/glib-core GraphData.cpp
$ g++ -g main.o GraphData.o /usr/include/Snap-2.3/snap-core/Snap.o -o main
选项 -Wall
告诉 g++
启用更多警告,这很好,因为警告是您可能做错事情的迹象(例如导致 undefined behavior)。 -g
选项告诉 g++
生成调试信息,在开发时总是很好,以防您需要 运行 调试器中的程序。最后,-c
选项告诉 g++
生成一个 目标文件 而不是一个可执行程序。
然后你有一个单独的命令link将目标文件组合在一起形成最终的可执行文件。
如果您获得的文件不止几个,这一切都将成为一种痛苦,然后您应该了解 make
,这是一个可以为您自动完成大部分工作的程序。例如,它将检查文件上的时间戳以确保您不会编译未修改的文件。
g++ 默认情况下会在编译完所有输入文件后立即调用 linker (ld)。生成二进制可执行文件的方法有很多种,但最常见的两种是:
- 在单个
g++
命令中包含所有源文件。您需要将 GraphData.cpp
和 main.cpp
添加到对 g++
的同一调用中。
此方法确保 linker 可以访问所有符号以创建可执行文件。
- 将每个文件分别编译成一个目标文件,然后 link 在一个单独的步骤中将它们编译(这对于大型软件项目来说更为常见)。
为此,将每个 cpp 文件的
-c
开关传递到 g++
,然后再次调用 g++ 并传递 .o
文件而不是 .cpp
文件。
无论哪种方式,在某些时候,您必须立即将所有符号提供给 linker,以便它可以创建可执行文件。
正如 Joachim 所解释的那样,除了几个文件之外的任何事情都会变得非常痛苦,此时学习构建系统可能会非常有利可图。他提到了 make,但我也会考虑研究 cmake,它具有更简单的语法并且是跨平台的。它甚至可以自动为您生成 Makefiles
。
我是 C/C++ 的新手,我已经了解基础知识,现在我开始学习更高级的概念。
目前我正在用C++开发一个项目,比较大,所以结构化我的项目会更好。
据我所知,一个好的结构至少依赖于文件夹:/src
和 /include
。所有 .cpp 文件都应该放在 /src
文件夹中,.hpp 文件应该放在 /include
文件夹中。这里出现了第一个疑问:如何包含一个不在同一目录或标准目录中的 header ?在搜索之后我得出结论,最好的方法是将选项 -I../include
传递给编译器。
我有三个源文件:main.cpp、GraphData.cpp 和 RandomGraphGenerator.cpp。我读过,最好为每个文件设置一个 header,以将声明与定义分开。现在我也有两个 header,但我不知道如何编译所有这些。
我专注于使 GraphData.cpp 正确链接到 main.cpp,我评论了所有需要 RandomGraphGenerator.cpp 的内容。这是我的主文件:
// main.cpp
#include <iostream>
#include <string>
#include <Snap.h>
#include <GraphData.hpp>
int main() {
std::string file_path;
char path[1024];
DataPoint **data_set;
int motif_size, num_null_models;
std::cin >> file_path;
std::cin >> motif_size;
std::cin >> num_null_models;
data_set = new DataPoint*[num_null_models];
strncpy(path, file_path.c_str(), sizeof(path));
path[sizeof(path) - 1] = 0;
//read input
PNGraph G = TSnap::LoadEdgeList<PNGraph>(path, 0, 1);
//enumerate and classify k-subgraphs in G
GD::extractData(&G, motif_size, data_set[0]);
//generate random graphs and enumerate and calssify k-subgraphs on them
for(int i=1; i<=num_null_models; i++) {
//GM::randomize(&G);
GD::extractData(&G, motif_size, data_set[i]);
}
//detect motifs
GD::discoverMotifs(data_set);
return 0;
}
我的图表数据header:
#ifndef GRAPHDATA_HPP_INCLUDED
#define GRAPHDATA_HPP_INCLUDED
#include <Snap.h>
struct DataPoint;
namespace GD {
void extractData(PNGraph*, int, DataPoint*);
DataPoint* discoverMotifs(DataPoint**);
}
#endif // GRAPHDATA_HPP_INCLUDED
还有我的 GraphData 文件:
#include <GraphData.hpp>
struct DataPoint {
int label;
int frequency = 0;
};
void GD::extractData(PNGraph* G, int k, DataPoint* data_array) {
//stuff
}
DataPoint* GD::discoverMotifs(DataPoint** data_set) {
//dummy code
DataPoint* dp;
dp = new DataPoint[2];
dp[0].label = 10;
dp[1].label = 99;
return dp;
}
我正在尝试在 Ubuntu 14.10 下使用 GCC 4.9.2 进行编译,使用:
g++ -o main main.cpp /usr/include/Snap-2.3/snap-core/Snap.o -I../include -I/usr/include/Snap-2.3/snap-core -I/usr/include/Snap-2.3/glib-core
但是它给我一个未定义引用的错误:
main.cpp:(.text+0x179): undefined reference to `GD::extractData(TPt<TNGraph>*, int, DataPoint*)'
main.cpp:(.text+0x1b9): undefined reference to `GD::extractData(TPt<TNGraph>*, int, DataPoint*)'
main.cpp:(.text+0x1dd): undefined reference to `GD::discoverMotifs(DataPoint**)'
collect2: error: ld returned 1 exit status
我有点迷茫,我错过了什么?
[注意:这不是问题的答案,只是解释如何一个一个地编译多个文件]
你可以单独编译源文件,但是你必须告诉g++
创建目标文件,这然后你们link在一起。
像这样:
$ g++ -Wall -g -c -I../include -I/usr/include/Snap-2.3/snap-core -I/usr/include/Snap-2.3/glib-core main.cpp
$ g++ -Wall -g -c -I../include -I/usr/include/Snap-2.3/snap-core -I/usr/include/Snap-2.3/glib-core GraphData.cpp
$ g++ -g main.o GraphData.o /usr/include/Snap-2.3/snap-core/Snap.o -o main
选项 -Wall
告诉 g++
启用更多警告,这很好,因为警告是您可能做错事情的迹象(例如导致 undefined behavior)。 -g
选项告诉 g++
生成调试信息,在开发时总是很好,以防您需要 运行 调试器中的程序。最后,-c
选项告诉 g++
生成一个 目标文件 而不是一个可执行程序。
然后你有一个单独的命令link将目标文件组合在一起形成最终的可执行文件。
如果您获得的文件不止几个,这一切都将成为一种痛苦,然后您应该了解 make
,这是一个可以为您自动完成大部分工作的程序。例如,它将检查文件上的时间戳以确保您不会编译未修改的文件。
g++ 默认情况下会在编译完所有输入文件后立即调用 linker (ld)。生成二进制可执行文件的方法有很多种,但最常见的两种是:
- 在单个
g++
命令中包含所有源文件。您需要将GraphData.cpp
和main.cpp
添加到对g++
的同一调用中。 此方法确保 linker 可以访问所有符号以创建可执行文件。 - 将每个文件分别编译成一个目标文件,然后 link 在一个单独的步骤中将它们编译(这对于大型软件项目来说更为常见)。
为此,将每个 cpp 文件的
-c
开关传递到g++
,然后再次调用 g++ 并传递.o
文件而不是.cpp
文件。
无论哪种方式,在某些时候,您必须立即将所有符号提供给 linker,以便它可以创建可执行文件。
正如 Joachim 所解释的那样,除了几个文件之外的任何事情都会变得非常痛苦,此时学习构建系统可能会非常有利可图。他提到了 make,但我也会考虑研究 cmake,它具有更简单的语法并且是跨平台的。它甚至可以自动为您生成 Makefiles
。