不允许嵌套函数,但为什么允许嵌套函数原型? [C ++]

时间:2009-05-30 04:25:53

标签: c++ compiler-construction function nested

reading链接的问题引导我提出这个问题。

考虑以下代码

int main()
{
    string SomeString();
}

所有人都说,编译器将其作为函数原型而不是 string 对象。现在考虑以下代码。

int main()
{
    string Some()
    {
        return "";
    }
}

编译器说这是无效的,因为我猜不允许嵌套函数定义。 如果不允许,为什么允许嵌套函数原型?它没有给出任何优势而不是混淆(或者我在这里错过了一些有效点?)。

我发现以下内容是有效的。

int main()
{ 
  string SomeFun();
  SomeFun();
  return 0;
}

string SomeFun()
{
  std::cout << "WOW this is unexpected" << std::endl;
}

这也令人困惑。我期待函数 SomeFun()只在 main 中有一个范围。但是我错了。为什么编译器允许编译如上所述的代码?是否存在上述代码有意义的实时情况?

有什么想法吗?

6 个答案:

答案 0 :(得分:8)

你的原型只是'Forward Declaration'。请查看维基百科文章。

基本上,它告诉编译器“如果以这种方式使用标签'SomeFun',请不要惊慌。”但是你的链接器负责找到正确的函数体。

你实际上可以声明一个虚假的原型,例如: 'char SomeFun()'并在你的主要使用它。只有当链接器尝试查找伪造函数的主体时,才会出现错误。但是你的编译器很酷。

有很多好处。您必须记住函数体并不总是在同一个源代码文件中。它可以在一个链接的库中。此外,该链接库可能具有特定的“链接签名”。使用条件定义甚至可以使用您的范围原型在构建时选择正确的链接签名。虽然大多数人会使用函数指针相反。

希望这有帮助。

答案 1 :(得分:7)

正如旁注,C ++ 03确实有一种定义本地函数的迂回方式。它需要滥用本地级功能:

int main()
{
    struct Local
    {
        static string Some()
        {
            return "";
        }
    };
    std::cout << Local::Some() << std::endl;
}

答案 2 :(得分:5)

这是一个来自C的约定 - 就像很多 - C ++采用的。

在C中的另一个函数内声明函数的能力是大多数程序员可能认为遗憾和不必要的决定。特别是现代的OOP设计,其功能定义比C中的要小。

如果您希望功能仅存在于另一个功能的范围内,则有两个选项boost::lambdaC++1x lambda

答案 3 :(得分:5)

至于你的宣言

void f() {
    void g(); g();
}

比这个更好

void g();
void f() {
    g();
}

如果您将声明尽可能保持在本地,这通常很好,因此尽可能少地命名冲突结果。我说在本地声明一个函数(这种方式)是真的幸运是有争议的,因为我认为普通包括它的标题然后采用“通常”的方式仍然更好,这也不那么令人困惑人们不知道这一点。有时候,处理阴影函数也很有用

void f() {
    int g; 
    // oops, ::g is shadowed. But we can work around that
    {
        void g(); g();
    }
}

当然,在C ++中我们可以使用g调用函数its_namespace::g() - 但是在C的旧时代,这是不可能的,并且那个东西允许程序员仍然访问功能。还要注意,虽然在语法上它不相同,但从语义上讲,以下内容也会在本地范围内声明一个实际上针对不同范围的函数。

int main() {
    using std::exit;
    exit();
}

作为旁注,有更多情况,例如声明的目标范围声明出现的范围。通常,您声明的实体成为声明出现的范围。但情况并非总是如此。考虑例如朋友声明,发生那件事

struct X { friend void f() { std::cout << "WoW"; } };
int main() { void f(); f(); } // works!

即使f的函数声明(和定义!)发生在X的范围内,实体(函数本身)也成为封闭命名空间的成员。

答案 4 :(得分:3)

函数原型是编译器的提示。它们表明函数是在其他地方实现的,如果还没有发现。没什么。

答案 5 :(得分:3)

当您按原样声明原型时,基本上是告诉编译器等待链接器解析它。根据您编写原型的位置,应用范围规则。在main()函数中编写原型没有任何技术上的错误(虽然IMHO有点麻烦),它只是意味着函数只在main()中本地知道。如果您已将原型声明在源文件的顶部(或者更常见于头文件中),原型/函数将在整个源中被识别。

string foo()
{
  string ret = someString();  // Error
  return ret; 
}

int main(int argc,char**argv)
{
   string someString();
   string s = somestring(); // OK
   ...
}