4

我需要帮助将概念从 Java 翻译成 C++。

在 Java 中,当您创建一个类并为其提供方法(函数)时,您必须在该类中定义该函数,以便任何可能需要的类实例都可以正确调用它。例如,在Employee您要声明和定义方法的类中salaryRaise(int amount)。每当一个Employee对象想要使用它时,它就会调用它,Employee.salaryRaise(i)Java 知道在哪里可以找到它——在Employee类中。

在 C++ 中,函数在.h文件中声明,然后在其他地方定义。编译器如何知道在哪里可以找到这个方法?

4

7 回答 7

6

这实际上是链接器的工作,而不是编译器。编译器将调用引用尚未定义的符号的函数。然后,链接器将获取不同的翻译单元并按名称解析符号(即,将附加信息编码为命名空间、参数类型的错位名称......)

于 2013-10-23T20:05:24.020 回答
4

您在头文件 (.h) 中声明该函数。然后,该头文件将#include在您需要使用该函数的任何其他文件中进行编辑。这满足了编译器,因为它知道函数的签名以及如何调用它。

然后在源 (.cpp) 文件中定义该函数。源文件包含它自己的头文件,因此当您编译它时,您最终会得到一个包含该函数的完整编译代码的目标文件。

然后,链接器将您调用该函数的代码的任何部分(即包含标头的文件)与该函数的实际代码链接起来。

编辑示例:

富.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. 到目前为止,一切都很好。

酒吧.h:

#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) 的类型,并且知道参数的类型(adoublethisfoo目的)。因为没有提供函数定义,所以函数不会被内联(内联意味着函数的整个代码被复制到调用它的位置)。相反,使用指向函数内存地址的指针来调用它。因为正在使用指针,所以编译器并不关心定义——它只需要知道“我需要在内存位置 Y 使用参数 A 和 B 调用函数 X,并且我需要有一个int大小的内存部分准备好存储返回值,我假设那个地址的代码知道如何执行函数”。但是,编译器无法提前知道该函数的地址是什么(因为函数定义存在于单独的.cpp文件并将成为单独的编译作业 AKA 翻译单元的一部分)。

这就是链接器的作用。一旦所有的翻译单元都被编译(可以是任意顺序),链接器返回到编译后的代码,Bar.cpp并通过填写现在编译的定义的地址将两者链接在一起。fooFunction在它被调用的地方Bar.cpp,从而使编译后的代码完全可执行。

于 2013-10-23T20:09:46.463 回答
3

当编译器将您的源代码转换为二进制文件时,它通常会构建包含(除其他外)类的成员函数的中间文件(也称为目标文件)。

在链接中间文件时被翻译成机器代码。如果当时使用的所有成员函数都可以解析,那么一切都很好,否则会出现链接错误。

这样,您可以将成员函数的定义放在项目中的任何位置——只要链接器找到它需要的东西,它就可以构建二进制文件。但不建议这样做,因为这种方法会使人类更难阅读/理解课程目的/机制

于 2013-10-23T20:05:19.563 回答
3

在 C++ 中,函数在 .h 文件中声明,然后在其他地方定义。编译器如何知道在哪里可以找到这个方法?

因为您在提供方法的定义时告诉它在哪里可以找到它。

考虑一个假设的头文件:

class Gizmo
{
public:
  bool Foo();
};

现在我们将Foo在另一个 CPP 文件中定义上述方法:

bool Gizmo::Foo()
{
  return true;
}

这是您对上述定义的转述对话:

好的,编译器。我正在定义一个返回布尔值的函数。该函数是类 Gizmo 的一部分,该函数名为 Foo。该函数不接受任何参数。函数的定义是:return true。

范围解析标记::将封闭类的名称和函数的名称分开。

如果您忽略了这Gizmo::一点,而是写道:

bool Foo()
{
  return true;
}

您仍将定义一个名为的函数,该函数Foo不带参数并返回一个布尔值,但现在不是将其定义为 的一部分,而是将其Gizmo定义为独立的。所谓的“自由函数”或“全局函数”。这可以编译得很好,但是如果某个地方的其他代码试图使用Gizmo::Foo().

于 2013-10-23T20:37:13.543 回答
2

编译器通常具有编译阶段和链接阶段。编译阶段将源文件编译成目标文件。链接阶段将收集目标文件并创建可执行映像。在链接阶段,在编译阶段未解析的符号得到解析。这对于 Java 和 C++ 都没有太大的不同。例如,Java 类如何调用不同 Java 类的方法。

于 2013-10-23T20:06:57.813 回答
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文件传递给编译器(实际上是链接器)。因此,编译器可以看到所有内容。

于 2013-10-23T20:03:08.120 回答
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();
于 2013-10-23T20:10:36.277 回答