c语言调用同一目录下的函数,从C中同一目录中的另一个文件调用函数
这里有几件不同的事情。首先,我将介绍多个文件的基本编译是如何工作的。
如果你有多个文件,重要的是声明和函数定义之间的区别。定义可能是您在定义函数时习惯的定义:您编写函数的内容,如
int square(int i) {
return i*i;
}
另一方面,声明允许您向编译器声明您知道函数存在,但是您不告诉编译器它是什么。例如,你可以写
int square(int i);
编译器会期望函数“square”在别处定义。
现在,如果你有两个不同的文件想要互操作(例如,假设函数“square”在add.c中定义,并且你想在main.c中调用square(10)),你需要同时做一个定义和一个声明。首先,在add.c中定义square。然后,在main.c的开头声明它。这让编译器知道它在编译main.c时有一个函数“square”,它在别处定义。现在,您需要将main.c和add.c编译为目标文件。你可以通过电话来做到这一点
gcc -c main.c
gcc -c add.c
这将生成文件main.o和add.o.它们包含已编译的函数,但不完全可执行。这里要理解的重要一点是,main.o在某种意义上是“不完整的”。在编译main.o时,你告诉它函数“square”存在,但函数“square”没有在main.o中定义。因此main.o对函数“square”有一种“悬空引用”。除非将它与另一个包含“square”定义的.o(或.so或.a)文件合并,否则它不会编译成完整的程序。如果您只是尝试将main.o链接到程序中,即
gcc -o executable main.o
您将收到错误,因为编译器将尝试解析对“square”函数的悬空引用,但不会找到它的任何定义。但是,如果在链接时包含add.o(链接是将.o文件转换为可执行文件或.so文件时解析所有这些对未定义函数的引用的过程),那么就不会有任何问题。即
gcc -o executable main.o add.o
这就是如何在C文件中功能性地使用函数,但在风格上,我刚刚向您展示的是“不正确的方式”。我做的唯一原因是因为我认为它会更好地帮助你了解正在发生的事情,而不是依赖于“#include magic”。现在,您可能已经注意到,如果您必须重新声明要在main.c顶部使用的每个函数,事情会变得有点混乱。这就是为什么C程序经常使用名为“headers”的辅助文件,其扩展名为.h 。标题的概念是它只包含函数的声明,而没有它们的定义。这样,为了使用add.c中定义的函数编译程序,您不需要手动声明您正在使用的每个函数,也不需要#include代码中的整个add.c文件。相反,你可以#include add.h,它只包含add.c的所有函数的声明。
现在,重新开始#include:#include只是将一个文件的内容直接复制到另一个文件中。所以,例如,代码
abc
#include "wtf.txt"
def
完全等同于
abc
hello world
def
假设wtf.txt包含文本“hello world”。
那么,如果我们将add.c的所有声明都放在add.h中(即
int square(int i);
然后在main.c的顶部,我们写
#include "add.h"
这在功能上与我们刚刚在main.c顶部手动声明函数“square”一样。
所以使用标题的一般想法是你可以有一个特殊的文件,通过#including它自动声明你需要的所有函数。
但是,标题还有一个更常见的用途。我们假设main.c使用50个不同文件的函数。 main.c的顶部看起来像:
#include "add.h"
#include "divide.h"
#include "multiply.h"
#include "eat-pie.h"
...
相反,人们经常将所有#includes移动到main.h头文件中,只需从main.c中#include main.h.在这种情况下,头文件有两个目的。它声明了main.c中的函数,供其他文件包含时使用,并且包含main.c中包含的main.c的所有依赖项。以这种方式使用它也允许依赖链。如果#include add.h,不仅可以获得add.c中定义的函数,还可以隐式获取add.c使用的任何函数,以及它们使用的任何函数,等等。
另外,更巧妙的是,#包含来自它自己的.c文件的头文件会隐式检查你所犯的错误。例如,如果您不小心将square定义为
double square(int i);
在add.h中,你通常可能没有意识到,直到你链接main.o正在寻找square的一个定义,add.o正在提供另一个不兼容的。这会导致链接时出错,因此在构建过程的后期才会意识到错误。但是,如果你从add.c #include add.h到编译器,你的文件看起来像
#include "add.h"
int square(int i) {
return i*i;
}
处理完#include语句之后会是这样的
double square(int i);
int square(int i) {
return i*i;
}
编译add.c时编译器会注意到哪些,并告诉你。实际上,以这种方式包括您自己的标题可以防止您错误地向其他文件宣传您提供的功能类型。
为什么你可以在没有声明的情况下使用函数
正如您所注意到的,在某些情况下,您实际上可以使用函数而不是每次声明它或#including任何声明它的文件。这是愚蠢的,每个人都同意这是愚蠢的。但是,它是C编程语言(和C编译器)的遗留特性,如果你在没有首先声明它的情况下使用函数,它只是假设它是一个返回类型为“int”的函数。所以实际上,使用函数隐式地将该函数声明为一个函数,如果它尚未声明,则返回“int”。如果你考虑它,这是非常奇怪的行为,编译器应警告你,如果你做这种行为。
头卫
另一种常见做法是使用“Header Guards”。为了解释标题保护,让我们看看可能存在的问题。假设我们有两个文件:herp.c和derp.c,它们都希望使用彼此包含的函数。遵循上述准则,您可能会有一行herp.h
#include "derp.h"
和一行derp.h
#include "herp.h"
现在,如果你考虑一下,#include“derp.h”将被转换为derp.h的内容,而derp.h又包含#include“herp.h”行,它将被转换为herp的内容。 h,包含……等等,所以编译器将继续只是扩展包含。类似地,如果main.h#包括herp.h和derp.h,并且herp.h和derp.h都包含add.h,我们看到在main.h中,我们最终得到了两个add.h副本,一个是#including herp.h的结果,另一个是包含derp.h的结果。那么,解决方案呢?一个“Header guard”,即一段防止任何标题被#included两次的代码。例如,对于add.h,执行此操作的常规方法是:
#ifndef ADD_H
#define ADD_H
int sqrt(int i);
...
#endif
这段代码基本上是告诉预处理器(处理所有“#XXX”语句的编译器部分)来检查是否已经定义了“ADD_H”。如果它不是(ifndef)那么它首先定义“ADD_H”(在这个上下文中,ADD_H不必定义为任何东西,它只是一个定义或不定义的布尔值),然后定义其余的标题的内容。但是,如果已经定义了ADD_H,则#including此文件将不执行任何操作,因为#ifndef块之外没有任何内容。因此,我们的想法是,只有第一次将它包含在任何给定文件中时,它才会实际向该文件添加任何文本。之后,#include它不会在文件中添加任何其他文本。 ADD_H只是您选择的任意符号,用于跟踪add.h是否已包含在内。对于每个标题,您使用不同的符号来跟踪它是否已被包含。例如,herp.h可能会使用HERP_H而不是ADD_H。使用“标题保护”将解决我上面列出的任何问题,其中包含文件的重复副本,或#includes的无限循环。
更多推荐



所有评论(0)