C++如何找到函数声明
How do C++ find the function declaration
我需要帮助将一个概念从Java翻译成C++。
在 Java 中,当您创建一个类并为其提供方法(函数)时,您必须在该类中定义函数,以便任何可能需要的类实例都可以正确调用它。例如,在类Employee
中,您将声明并定义方法 salaryRaise(int amount)
.每当Employee
对象想要使用它时,它都会调用Employee.salaryRaise(i)
并且Java确切地知道在哪里可以找到它 - 在Employee
类中。
在C++中,函数在.h
文件中声明,然后在其他地方定义。编译器如何知道在哪里可以找到此方法?
这实际上是链接器的工作,而不是编译器的工作。编译器将调用引用尚未定义的符号的函数。然后,链接器将获取不同的翻译单元,并按名称解析符号(即,将附加信息编码为命名空间、参数类型等的损坏名称......
在标头 (.h) 文件中声明函数。然后,该头文件在您需要使用该函数的任何其他文件中#include
。这让编译器满意,因为它知道函数的签名以及如何调用它。
然后在源 (.cpp) 文件中定义该函数。源文件包含它自己的头文件,因此当您编译它时,您最终会得到一个包含该函数的完整编译代码的对象文件。
然后,链接器将调用函数的代码的任何部分(即包含标头的文件)与该函数的实际代码链接。
使用示例进行编辑:
噗
:#ifndef FOO_H
#define FOO_H
class Foo
{
public:
int fooFunction(double a);
};
#endif
福.cpp:
#include "Foo.h"
int Foo::fooFunction(double a)
{
// Function definition
return 1;
}
编译Foo.cpp
会生成包含fooFunction
完整定义Foo.obj
。目前为止,一切都好。
酒吧:
#ifndef BAR_H
#define BAR_H
#include "Foo.h"
class Bar
{
public:
void barFunction();
};
#endif
酒吧.cpp:
#include "Bar.h"
void Bar::barFunction()
{
Foo foo;
int returnValue = foo.fooFunction(2.0);
}
Bar.cpp
包括Bar.h
,而又包括Foo.h
。因此,当Bar.cpp
进行预处理时,Foo::fooFunction
的声明将插入到文件的顶部。因此,当编译语句int returnValue = foo.fooFunction(2.0);
时,编译器知道如何发出机器指令来调用fooFunction
,因为它知道返回值的类型(int
)并且知道参数的类型(double
和foo
对象的隐式this
指针)。由于未提供函数定义,因此不会内联该函数(内联意味着将函数的整个代码复制到调用它的点)。相反,指向函数内存地址的指针用于调用它。因为使用了指针,编译器不关心定义 - 它只需要知道"我需要在内存位置 Y 使用参数 A 和 B 调用函数 X,并且我需要有一个 int
大小的内存部分准备好存储返回值,我将假设该地址的代码知道如何执行该函数".但是,编译器无法提前知道该函数的地址是什么(因为函数定义位于单独的.cpp
文件中,并且将成为单独的编译作业(AKA 翻译单元)的一部分)。
这就是链接器的用武之地。一旦所有翻译单元都编译完(可以按任意顺序),链接器返回到编译的代码进行Bar.cpp
,并通过在Bar.cpp
中调用fooFunction
时填写现在编译的定义的地址将两者链接在一起,从而使编译的代码完全可执行。
在C++中,函数在 .h 文件中声明,然后在其他地方定义。编译器如何知道在哪里可以找到此方法?
因为当您提供方法的定义时,您会告诉它在哪里可以找到它。
考虑一个假设的头文件:
class Gizmo
{
public:
bool Foo();
};
现在我们将在另一个 CPP 文件中定义上面的Foo
方法:
bool Gizmo::Foo()
{
return true;
}
以下是您与上述定义的释义对话:
好的,编译器。 我正在定义一个返回布尔值的函数。 这 函数是类 Gizmo 的一部分,该函数名为 Foo。 该函数不带任何参数。 该函数的定义是: 返回 true。
作用域解析令牌::
分隔封闭类的名称和函数的名称。
如果你省略了Gizmo::
一点,而是写:
bool Foo()
{
return true;
}
您仍然会定义一个名为 Foo
的函数,该函数不带任何参数并返回布尔值,但现在不是将其定义为Gizmo
的一部分,而是将其定义为自己。 所谓的"自由函数"或"全局函数"。 这可以很好地编译,但是如果某处的其他代码尝试使用Gizmo::Foo()
,则会出现"未解析的外部"链接器错误。
当编译器将源代码转换为二进制时,它通常会生成包含(除其他外)类的成员函数的中间文件(也称为对象文件)。
在链接时,中间文件被转换为机器代码。 如果当时可以使用的所有成员函数进行解析,则一切正常,否则会出现链接错误。
这样,就可以将成员函数的定义放在项目中的任何位置 - 只要链接器找到它需要的内容,它就可以生成二进制文件。 但是,不建议这样做,因为这种方法会使人类更难阅读/理解类目的/机制
编译器通常具有编译阶段和链接阶段。编译阶段将源文件编译为目标文件。链接阶段将收集目标文件并创建可执行映像。在链接阶段,在编译阶段未解析的符号将被解析。这对于Java和C++没有太大区别。例如,它是 Java 类如何调用不同 Java 类上的方法。
在C++中,您可以通过类名后跟::
然后是方法声明来确定主体:
class Employee
{
void salaryRaise(int amount); // Now, compiler knows this method belongs to
// the class Employee
};
然后,您可以定义正文:
void Employee::salaryRaise(int amount) // Now, compiler knows everything about
{ ^^^^^^^^^^ // definition of the method
}
要生成目标文件(和可执行二进制文件),您必须将.cpp
文件传递给编译器(实际上是链接器)。因此,编译器可以看到所有内容。
在你的头文件中,你原型/转发声明这样的类。
class Foo
{
private:
int m_Fooint;
int m_Fooint2;
public:
Foo(int a, int b);
int getFooTotal();
};
然后,您将像这样定义成员函数
Foo::Foo(int a, int b) // prototypes names don't have to be the same
{
Foo::m_Fooint = a;
Foo::m_Fooint2 = b;
}
int Foo::getFooTotal()
{
return Foo::m_Fooint + Foo::m_Fooint2;
}
除了构造函数和析构函数之外,还需要数据类型。即使它是无效的,不返回任何东西。
所以你可能有这样的东西
float Foo::getSomeFloat();
或
double Foo::getSomeDouble();
或者您甚至可以返回一个对象
OtherClass Foo::getOtherClassObject();
- Visual Studio中的函数声明和函数定义问题
- 为什么函数声明中允许 const?
- 如果 x.h 仅由函数声明组成,为什么有必要在 x 中包含 x.h.cpp
- * 和 ** 在 C++ 函数声明中是什么意思?
- 构造函数/函数声明参数列表中的统一初始化
- 在将函数声明为友元时,尖括号的含义是什么?
- 为什么转换函数声明不需要至少一个定义类型说明符
- 如何正确编写指针函数声明?
- 在"template"和函数声明之间使用:template<typename trait> using tr = base_trait<trait> void fn(tr::t
- 为什么要将函数声明和定义放在单独的文件中
- 为什么系统日志有两个不同的函数声明?
- 我如何获取数组的大小,以便我可以从函数声明所述数组
- 使用 enable_if 在按值传递与按引用传递之间更改函数声明
- JavaScript 中的一等函数和 C++ 中的函数声明
- C++ 通过函数声明后初始化向量
- VS2017 #error: : snprintf 的宏定义与标准库函数声明冲突
- C++ 17 个友元函数声明和内联命名空间
- MSVC 2017 - 错误 - 如何将模板类 X 的模板成员函数声明为嵌套类 X::Y 的好友
- 将派生类的构造函数声明为父类的友元
- 用于从 ANSI 字符串转换为 std::basic_string <TCHAR>的正确函数声明