我需要帮助将概念从Java翻译成C ++。
在Java中,当您创建一个类并为其提供方法(函数)时,必须在该类中定义该函数,以便可以由任何可能需要的类实例正确调用它。
例如,在类Employee
中,您将声明并定义方法salaryRaise(int amount)
。
只要Employee
对象想要使用它,它就会调用Employee.salaryRaise(i)
并且Java知道在Employee
类中确切找到它的位置。
在C ++中,函数在.h
文件中声明,然后在其他地方定义。
编译器如何知道在哪里找到这种方法?
答案 0 :(得分:6)
这实际上是链接器的工作,而不是编译器。编译器将调用引用尚未定义的符号的函数。然后,链接器将获取不同的转换单元并按名称解析符号(即,将其他信息编码为命名空间,参数类型的错位名称)。
答案 1 :(得分:4)
您在标题(.h)文件中声明该函数。然后,该头文件将在您需要使用该函数的任何其他文件中获得#include
。这满足了编译器,因为它知道函数的签名以及如何调用它。
然后在源(.cpp)文件中定义该函数。源文件包含它自己的头文件,因此在编译它时,最终会得到一个包含该函数的完整编译代码的目标文件。
然后,链接器将您调用该函数的代码的任何部分(即包含标题的文件)与该函数的实际代码链接起来。
使用示例进行编辑:
<强> foo.h中:强>
#ifndef FOO_H
#define FOO_H
class Foo
{
public:
int fooFunction(double a);
};
#endif
<强> Foo.cpp中:强>
#include "Foo.h"
int Foo::fooFunction(double a)
{
// Function definition
return 1;
}
编译Foo.cpp
会生成Foo.obj
,其中包含fooFunction
的完整定义。到目前为止一切都很好。
<强> Bar.h:强>
#ifndef BAR_H
#define BAR_H
#include "Foo.h"
class Bar
{
public:
void barFunction();
};
#endif
<强> Bar.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
和this
对象的隐式foo
指针。因为没有提供函数定义,所以函数不会被内联(内联意味着函数的整个代码被复制到调用它的位置)。而是使用指向函数的内存地址的指针来调用它。因为正在使用指针,所以编译器不关心定义 - 所有它需要知道的是“我需要在内存位置Y用参数A和B调用函数X,我需要有一个int
大小的内存部分准备存储返回值,我假设该地址的代码知道如何执行函数“。但是,编译器无法事先知道该函数的地址(因为函数定义位于单独的.cpp
文件中,并且将成为单独的编译作业AKA转换单元的一部分。) p>
这就是链接器的用武之地。一旦编译完所有的翻译单元(可以是任意顺序),链接器就会回到Bar.cpp
的编译代码,并通过填写将两者连接在一起现在编译的fooFunction
定义的地址,在Bar.cpp
中调用它的位置,从而使编译的代码完全可执行。
答案 2 :(得分:3)
当编译器将源转换为二进制时,它通常会构建包含(除此之外)类的成员函数的中间文件(也称为目标文件)。
在链接时,中间文件被翻译成机器代码。如果当时可以解析所有使用的成员函数,一切都很好,否则会出现链接错误。
这样,您可以将成员函数的定义放在项目的任何位置 - 只要链接器找到它需要的内容,它就可以构建二进制文件。但是不建议这样做,因为这种方法会使阅读/理解课程目的/机制对人类更加困难
答案 3 :(得分:2)
编译器通常具有编译阶段和链接阶段。编译阶段将源文件编译为目标文件。链接阶段将收集目标文件并创建可执行映像。在链接阶段,编译阶段未解决的符号得到解决。对于Java和C ++来说,这没什么不同。例如,Java类可以在不同的Java类上调用方法。
答案 4 :(得分:2)
在C ++中,函数在.h文件中声明,然后在其他地方定义。编译器如何知道在哪里找到这种方法?
因为在提供方法定义时告诉它在哪里找到它。
考虑一个假设的头文件:
class Gizmo
{
public:
bool Foo();
};
现在我们将在另一个CPP文件中定义上面的Foo
方法:
bool Gizmo::Foo()
{
return true;
}
以下是您对上述定义的释义:
好的,编译器。我正在定义一个返回bool的函数。该 function是Gizmo类的一部分,该函数名为Foo。 该函数不带参数。该函数的定义是: 返回true。
范围解析标记::
分隔封闭类的名称和函数的名称。
如果您已将Gizmo::
位移开,而是写下:
bool Foo()
{
return true;
}
您仍然会定义一个名为Foo
的函数,该函数不接受任何参数并返回一个bool,但现在不是将其定义为Gizmo
的一部分,而是将其定义为在其上拥有。所谓的“自由功能”或“全球功能”。这可以很好地编译,但如果其他某些代码试图使用Gizmo::Foo()
,你会得到一个“未解析的外部”链接器错误。
答案 5 :(得分:1)
在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
文件传递给编译器(实际上是链接器)。因此,编译器可以看到所有内容。
答案 6 :(得分:0)
在你的头文件中,你原型/转发声明这样的类。
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();