C ++如何找到函数声明

时间:2013-10-23 20:00:12

标签: c++ methods

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

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

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

7 个答案:

答案 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)并且它知道类型参数(doublethis对象的隐式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();