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

7

我正在阅读链接的问题,这启发我提出了这个问题。

考虑以下代码:

int main()
{
    string SomeString();
}

所有人都说,编译器将其视为函数原型而不是字符串对象。现在考虑以下代码。
int main()
{
    string Some()
    {
        return "";
    }
}

编译器说这是无效的,我猜嵌套函数定义是不允许的。如果不允许,为什么允许嵌套函数原型?这不会带来任何优势,只会造成混乱(或者我错过了一些有效的观点吗?)。
我发现以下内容是有效的。
int main()
{ 
  string SomeFun();
  SomeFun();
  return 0;
}

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

这也很令人困惑。我原本以为函数SomeFun()只在main中有作用域,但我错了。编译器为什么允许编译上述代码?是否存在实时情况下类似上述代码有意义的情况?
你有什么想法吗?

刚好遇到了相同的问题,下面的答案已经包含了所有信息以及更多内容。 - slashmais
6个回答

8

你的原型只是 '前向声明'。请查看维基百科文章。

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

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

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

希望这可以帮助你。


7

顺带提一下,C++03有一种定义本地函数的绕路方法。它需要滥用本地类特性:

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

2
语言的绝妙滥用。 - Joshua
这为什么是“滥用”?(顺便说一句,我真的很好奇) - Samaursa

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()调用函数g。但在早期的C语言中,这是不可能的,这种方法允许程序员仍然可以访问该函数。另请注意,虽然语法上不同,但以下语法也在本地作用域中声明了一个函数,实际上它针对的是不同的作用域。

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

作为一个附注,有更多类似的情况,其中声明的目标范围并不是其出现的范围。通常情况下,您声明的实体成为声明所在范围的成员。但并非总是如此。例如,考虑友元声明,这种情况就会发生。
struct X { friend void f() { std::cout << "WoW"; } };
int main() { void f(); f(); } // works!

尽管函数声明(包括定义!)发生在X的作用域内,但该实体(函数本身)成为封闭命名空间的成员。

1
+1 那个“朋友”东西很奇怪 - 只是不能信任朋友 ;) - slashmais
我不懂 friend 的用法。它是否会使 X::f() 属于封闭的命名空间? - ofavre

5
这是C语言常见的做法,C++也采用了这种方法。在C语言中,允许在一个函数内部声明另一个函数,但大多数程序员认为这种做法不必要且令人遗憾,特别是在现代面向对象编程设计中,函数定义比C语言中的函数更加简洁。如果想要让函数只存在于另一个函数的作用域内,有两个选项:boost::lambdaC++1x lambda

3
当你像现在这样声明原型时,基本上是告诉编译器等待链接器来解决它。取决于你在哪里编写原型,作用域规则将适用。在 main() 函数内部编写原型没有什么技术上的问题(尽管在我看来有点混乱),这意味着该函数仅在 main() 中局部已知。如果你在源文件的顶部声明原型(或更常见的是在头文件中),则原型/函数将在整个源中都可用。
string foo()
{
  string ret = someString();  // Error
  return ret; 
}

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

3

函数原型是编译器的提示。它们表明函数在其他地方实现,如果还没有被“发现”的话。就这样,没什么了不起的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接