C - 将代码正确划分为多个文件
C - properly partitioning the code into multiple files
我正在用 C 语言编写一个程序,我将各个部分分开放在不同的 .c 文件(模块)中。
一些模块(例如 B 和 C)包含相同的模块(例如 A),当这些模块(B 和 C)被另一个模块(例如 D)包含时,会形成菱形 "hierarchy"导致重新定义问题。
例如
vec3.c
#include <math.h>
typedef struct {
float x, y, z;
} vec3;
float dot(vec3 v1, vec3 v2) {
return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
}
/* ... other stuff ... */
ray.c
#include "vec3.c"
typedef struct {
vec3 A, B;
} ray;
vec3 origin(ray r) {
return r.A;
}
/* ... other stuff ... */
sphere.c
#include "ray.c"
typedef struct {
float t;
vec3 p;
vec3 n;
} record;
typedef struct {
vec3 center;
float radius;
} sphere;
typedef enum {false, true} bool;
bool hit_sphere(sphere s, ray r, float tmin, float tmax, record *rec) {
/* ... do stuff ... */
}
/* ... other stuff ... */
camera.c
#include "ray.c"
typedef struct {
vec3 origin;
vec3 lower_left_corner;
vec3 horizontal;
vec3 vertical;
} camera;
/* ... other stuff ... */
vec3 origin = {0.0, 0.0, 0.0};
vec3 lower_left_corner = {-2.0, -1.0, -1.0};
vec3 horizontal = {4.0, 0.0, 0.0};
vec3 vertical = {0.0, 2.0, 0.0};
camera cam = {origin, lower_left_corner, horizontal, vertical};
main.c
#include <stdio.h>
#include <float.h>
#include "sphere.c"
#include "camera.c"
/* ... other stuff ... */
int main() {
/* ... do stuff ... */
}
在这种情况下会出现问题,因为 camera.c 和 sphere.c 都包含 ray.c(因此也包含 vec3.c),因此存在结构的双重定义和这些模块中定义的函数。
如何重新排列代码才能避免这些问题?
谢谢!
您永远不应包含 .c
个文件。
为每个实现文件(.c
)创建一个头文件(.h
)并在其中声明函数。然后将头文件包含在相应的实现文件中。声明并包含后,您应该在实现文件中编写这些声明函数的实现。
然后使用包含头文件的函数。
头文件示例 (example.h):
#ifndef EXAMPLE_H
#define EXAMPLE_H
/* Function declarations */
void example();
#endif /* EXAMPLE_H */
实施文件示例 (example.c):
#include "example.h"
void example() {
/* TODO: add logic */
}
关于文件组织没有 language-defined 规则。实际上,包含在源文件中的所有文件都是由预编译器创建的 so-called "compilation unit" 形式,并被编译器视为单个文件。预编译器的指令 #include
确实是这样做的……将其他文件包含到正在处理的当前文件中。因此,必须确保文件只包含一次。
一个经典的方法是使用预处理器的#ifndef 指令。大多数编译器支持指令 #pragma once
但它不是标准工具,在循环包含的情况下有时会出现不可预测的行为。
调用包含可重用声明和定义的文件 "headers" 并为它们提供 .h(有时为 .hpp)是公认的做法。 C++ 标准 headers 同意完全没有扩展以避免将它们与 C headers 混合。例如。 stdio.h 被 cstdio 取代,前者不应在 C++ 中使用。有时需要包含大量重复定义,可以得到不同的扩展名 .inc、.incl 等。
那么为什么人们会说 "never include .c files"。它与所使用的工具链有关,即控制应用程序构建过程的一组实用程序。您几乎永远不会 运行 手动编译。总是有一个构建工具通常决定如何处理 file ,该决定基于扩展名。 .c 文件通常被认为代表单独的编译模块,因此工具会 运行 编译它们中的每一个,然后再尝试 link 它们在一起。
Headers 不应该定义 objects 之类的函数或变量,他们可以声明它们用于外部 linking。为什么?如果将带有已定义变量的 header 文件包含到多个编译单元中,您会从 linker 那里得到一个错误,因为所有单元都将包含相同的符号。因此,定义应该是唯一的或程序被认为是 ill-formed。默认情况下,函数始终假定具有外部 linking
/* myheader.h */
#ifndef ___MYHEADER_H
#define ___MYHEADER_H
extern int globalVariable;
typedef struct MyType {
};
int foo(MyType);
#endif
/* myheader.cpp */
#include "myheader.h"
int globalVariable = 5;
int foo(MyType param)
{
/* body of foo */
}
项目中文件的组织方式由设计者决定。如果您是团队的一员,您应该遵循该团队的首席设计师或他们批准的文档的建议。
我正在用 C 语言编写一个程序,我将各个部分分开放在不同的 .c 文件(模块)中。
一些模块(例如 B 和 C)包含相同的模块(例如 A),当这些模块(B 和 C)被另一个模块(例如 D)包含时,会形成菱形 "hierarchy"导致重新定义问题。
例如
vec3.c
#include <math.h>
typedef struct {
float x, y, z;
} vec3;
float dot(vec3 v1, vec3 v2) {
return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
}
/* ... other stuff ... */
ray.c
#include "vec3.c"
typedef struct {
vec3 A, B;
} ray;
vec3 origin(ray r) {
return r.A;
}
/* ... other stuff ... */
sphere.c
#include "ray.c"
typedef struct {
float t;
vec3 p;
vec3 n;
} record;
typedef struct {
vec3 center;
float radius;
} sphere;
typedef enum {false, true} bool;
bool hit_sphere(sphere s, ray r, float tmin, float tmax, record *rec) {
/* ... do stuff ... */
}
/* ... other stuff ... */
camera.c
#include "ray.c"
typedef struct {
vec3 origin;
vec3 lower_left_corner;
vec3 horizontal;
vec3 vertical;
} camera;
/* ... other stuff ... */
vec3 origin = {0.0, 0.0, 0.0};
vec3 lower_left_corner = {-2.0, -1.0, -1.0};
vec3 horizontal = {4.0, 0.0, 0.0};
vec3 vertical = {0.0, 2.0, 0.0};
camera cam = {origin, lower_left_corner, horizontal, vertical};
main.c
#include <stdio.h>
#include <float.h>
#include "sphere.c"
#include "camera.c"
/* ... other stuff ... */
int main() {
/* ... do stuff ... */
}
在这种情况下会出现问题,因为 camera.c 和 sphere.c 都包含 ray.c(因此也包含 vec3.c),因此存在结构的双重定义和这些模块中定义的函数。
如何重新排列代码才能避免这些问题?
谢谢!
您永远不应包含 .c
个文件。
为每个实现文件(.c
)创建一个头文件(.h
)并在其中声明函数。然后将头文件包含在相应的实现文件中。声明并包含后,您应该在实现文件中编写这些声明函数的实现。
然后使用包含头文件的函数。
头文件示例 (example.h):
#ifndef EXAMPLE_H
#define EXAMPLE_H
/* Function declarations */
void example();
#endif /* EXAMPLE_H */
实施文件示例 (example.c):
#include "example.h"
void example() {
/* TODO: add logic */
}
关于文件组织没有 language-defined 规则。实际上,包含在源文件中的所有文件都是由预编译器创建的 so-called "compilation unit" 形式,并被编译器视为单个文件。预编译器的指令 #include
确实是这样做的……将其他文件包含到正在处理的当前文件中。因此,必须确保文件只包含一次。
一个经典的方法是使用预处理器的#ifndef 指令。大多数编译器支持指令 #pragma once
但它不是标准工具,在循环包含的情况下有时会出现不可预测的行为。
调用包含可重用声明和定义的文件 "headers" 并为它们提供 .h(有时为 .hpp)是公认的做法。 C++ 标准 headers 同意完全没有扩展以避免将它们与 C headers 混合。例如。 stdio.h 被 cstdio 取代,前者不应在 C++ 中使用。有时需要包含大量重复定义,可以得到不同的扩展名 .inc、.incl 等。
那么为什么人们会说 "never include .c files"。它与所使用的工具链有关,即控制应用程序构建过程的一组实用程序。您几乎永远不会 运行 手动编译。总是有一个构建工具通常决定如何处理 file ,该决定基于扩展名。 .c 文件通常被认为代表单独的编译模块,因此工具会 运行 编译它们中的每一个,然后再尝试 link 它们在一起。
Headers 不应该定义 objects 之类的函数或变量,他们可以声明它们用于外部 linking。为什么?如果将带有已定义变量的 header 文件包含到多个编译单元中,您会从 linker 那里得到一个错误,因为所有单元都将包含相同的符号。因此,定义应该是唯一的或程序被认为是 ill-formed。默认情况下,函数始终假定具有外部 linking
/* myheader.h */
#ifndef ___MYHEADER_H
#define ___MYHEADER_H
extern int globalVariable;
typedef struct MyType {
};
int foo(MyType);
#endif
/* myheader.cpp */
#include "myheader.h"
int globalVariable = 5;
int foo(MyType param)
{
/* body of foo */
}
项目中文件的组织方式由设计者决定。如果您是团队的一员,您应该遵循该团队的首席设计师或他们批准的文档的建议。