为什么我可以在 C 中调用函数而不声明它,但不能在 C++ 中调用它
Why can I call a function in C without declaring it but not in C++?
在C++中,在声明函数之前调用函数是一个编译器错误。但在 C 中,它可以编译。
#include<stdio.h>
int main()
{
foo(); // foo() is called before its declaration/definition
}
int foo()
{
printf("Hello");
return 0;
}
我已经尝试过并知道它是正确的,但我无法理解它背后的原因。任何人都可以解释编译过程实际上是如何进行的,并且在两种语言中有所不同。
编译"为c程序的事实并不意味着你可以做到这一点。编译器应警告函数foo()
的隐式声明。
在这种特殊情况下,隐式声明将声明相同的foo()
并且不会发生任何不好的事情。
但假设以下情况,假设这是
主.c
/* Don't include any header, why would you include it if you don't
need prototypes? */
int main(void)
{
printf("%dn", foo()); // Use "%d" because the compiler will
// implicitly declare `foo()` as
//
// int foo()
//
// Using the "correct" specifier, would
// invoke undefined behavior "too".
return 0;
}
现在假设foo()
在不同的编译单元1foo.c 中定义为
福克
double foo()
{
return 3.5;
}
它是否按预期工作?
你可以想象如果你使用malloc()
而不包括stdio.h会发生什么,这与我上面试图解释的情况几乎相同。
这样做将调用未定义的行为2,因此在这种情况下,术语">Works"在可理解的意义上不适用。
这可以编译的原因是,在过去,c标准(即c89标准(允许它。
c++ 标准从未允许这样做,因此如果您在调用之前调用代码中没有原型(">声明"(的函数,则无法编译 c++ 程序。
现代 c 编译器对此发出警告,因为很容易发生未定义行为的可能性,并且由于忘记添加原型或包含适当的标头并不难,因此如果编译器可以对此发出警告,而不是突然出现一个非常莫名其妙的错误,对程序员来说更好。
1它不能在同一个文件中编译,因为它将使用不同的返回类型定义,因为它已经隐式声明
2从double
和int
是不同的类型开始,因此会有未定义的行为。
当 C 被开发出来时,函数名称是你能够调用它所需要的。将参数与函数参数匹配严格来说是程序员的事;编译器并不关心您是否将三个浮点数传递给只需要一个整数的东西。
然而,事实证明这很容易出错,所以后来的 C 语言迭代添加了函数原型作为(仍然是可选的(附加限制。C++这些限制进一步收紧:现在功能原型始终是强制性的。
我们可以推测原因,但部分原因是因为C++仅仅知道函数名称已经不够了。可以有多个具有相同名称但具有不同参数的函数,编译器必须确定要调用哪个函数。它还必须弄清楚如何调用(直接还是虚拟?(,甚至可能必须在模板函数的情况下生成代码。
鉴于所有这些,我认为让语言要求在调用函数时知道函数原型是有意义的。
最初,C 没有函数原型,C++也不存在。
如果你说
extern double atof();
这表示atof
是一个返回双精度的函数。 (关于它的论点,什么也没说。
如果你然后说
double d = atof("1.3");
它会起作用。 如果你说
double d2 = atof(); /* whoops, forgot the argument to atof() */
编译器不会抱怨,但如果你尝试运行它,就会发生一些奇怪的事情。
在那些日子里,如果你想捕获与使用错误数量或类型的参数调用函数相关的错误,那就是一个单独的程序的工作,lint
,而不是C编译器。
同样在那些日子里,如果你只是突然调用了一个编译器以前从未听说过的函数,比如这样:
int i = atoi("42");
编译器基本上假装你之前说过
extern int atoi();
这就是所谓的隐式函数声明。 每当编译器看到对它不知道名称的函数的调用时,编译器就会认为这是一个返回 int 的函数。
快进几年。 C++发明了我们今天所知道的功能原型。 除此之外,它们还允许您声明函数所需的参数的数量和类型,而不仅仅是其返回类型。
再快进几年,C 语言可以选择采用功能原型。 如果需要,可以使用它们,但如果不这样做,编译器仍将对它看到的任何未知函数调用执行隐式声明。
再快进几年,到 C11。 现在隐式 int 终于消失了。 如果调用函数而不先声明它,则需要编译器进行投诉。
但即使在今天,您可能正在使用 C11 之前的编译器,它仍然对隐式 int 感到满意。 C11 投诉编译器可能会选择仅发出警告(而不是编译终止错误(,如果您忘记在调用函数之前声明它。 符合 C11 标准的编译器可能会提供一个选项来关闭这些警告,并悄悄地接受隐式整数。 (例如,当我使用非常现代的 clang 时,我会安排用 -Wno-implicit-int
调用它,这意味着我不想要关于隐式 int 的警告,因为我仍然有很多旧的、我不想重写的工作代码。
为什么我可以在不声明的情况下调用 C 中的函数?
因为在 C 中,而不是在 C++ 中,假设没有原型的函数返回int
。
这是该函数的隐式声明。 如果该假设被证明是正确的(该函数稍后以返回类型int
声明(,则程序可以很好地编译。
如果该假设被证明是假的(例如,假设它返回一个int
,但实际上被发现返回一个double
(,那么你会收到一个编译器错误,两个函数不能具有相同的名称。 (例如。 int foo()
和double foo()
不能同时存在于同一个程序中(
请注意,所有这些都只是 C。
在C++中,不允许隐式声明。即使它们是,错误消息也会有所不同,因为C++具有函数重载。该错误会说函数的重载不能仅因返回类型而异。(重载发生在参数列表中,而不是返回类型(
- 是否可以将函数导入命名空间,但不能导出它?
- std::unique_ptr 在 GCC 中工作,但不能在 Visual Studio 中编译
- 声明C++具有动态大小的数组类型在 Linux 中工作正常,但不能在 Windows 中工作
- 为什么我可以隐式地将字符*转换为常量字符*,但不能将无符号字符*
- std::chrono::d uration 可以按秒初始化,但不能按毫秒初始化?
- Python/C++:可以导入犰狳(arma::)但不能导入子程序arma::arma_rng::randn
- 为什么我可以从C++文件中读取 int 值,但不能浮点?
- C++程序在将 int 与 cin 一起使用时有效,但不能使用字符串
- SqLite c++,可以创建数据库,但不能将 anthing 插入到表中
- std::remove() 按预期处理文字,但不能与取消引用的迭代器一起工作
- 16 位系统中的程序如何访问大于 65535 的整数,但不能访问地址
- C++:返回本地对象,但不能正常工作
- 可以访问一个类中的播放器结构,但不能访问另一个类中的播放器结构
- 返回实例变量的c++方法可以访问变量中的数据,但不能更改它,但在编译时不会生成错误
- 用C++替换std::字符串中的一个子字符串,但不能全部替换
- 我可以在Windows(Visual C++)中读取bin文件,但不能在linux(GCC)上读取bin文件
- QProcess可以启动程序,但不能启动python(命令行)
- C++11 向量包含 2 个不同的子类,但不能同时进行
- 需要调用gtk_init函数,但不能来自主函数-C
- 为什么我可以在 C 中调用函数而不声明它,但不能在 C++ 中调用它