我们可以在C ++函数内部包含函数吗?


225

我的意思是:

int main() 
{
  void a() 
  {
      // code
  }
  a();

  return 0;
}

1
你为什么要这样做?说明您的目标可能会让某人告诉您实现目标的正确方法。
Thomas Owens 2010年

3
gcc支持嵌套函数作为非标准扩展。但是最好不要使用它,即使您正在使用gcc。而且在C ++模式下,它仍然不可用。
斯文·马纳赫

27
@Thomas:因为缩小范围会很好吗?函数中的函数是其他语言的常用功能。
约翰·科特林斯基

64
他在谈论嵌套函数。与能够在类内的下一个类类似,他希望将一个函数嵌套在一个函数内。实际上,如果可能的话,我也会这样做。有一些语言(例如F#)允许这样做,我可以告诉您,它可以使代码更加清晰,可读性和可维护性,而不会污染带有数十个在特定上下文之外没有用的辅助函数的库。;)
Mephane 2010年

16
@Thomas-嵌套函数可能是一种打破复杂函数/算法的极好机制,无需在当前范围内使用封闭范围内常用的函数。Pascal和Ada(IMO)对他们有很好的支持。与Scala和许多其他旧/新受尊重的语言相同。像任何其他功能一样,它们也可以被滥用,但这是开发人员的功能。海事组织,他们已经有害得多了。
luis.espinal,2010年

Answers:


271

现代C ++-可以使用lambdas!

在当前版本的c ++(C ++ 11,C ++ 14和C ++ 17)中,可以在函数内部使用lambda形式的函数:

int main() {
    // This declares a lambda, which can be called just like a function
    auto print_message = [](std::string message) 
    { 
        std::cout << message << "\n"; 
    };

    // Prints "Hello!" 10 times
    for(int i = 0; i < 10; i++) {
        print_message("Hello!"); 
    }
}

Lambda还可通过“按引用捕获”来修改局部变量。通过按引用捕获,lambda可以访问在lambda的作用域中声明的所有局部变量。它可以正常地修改和更改它们。

int main() {
    int i = 0;
    // Captures i by reference; increments it by one
    auto addOne = [&] () {
        i++; 
    };

    while(i < 10) {
        addOne(); //Add 1 to i
        std::cout << i << "\n";
    }
}

C ++ 98和C ++ 03-不是直接使用,而是使用局部类内部的静态函数

C ++不直接支持它。

就是说,您可以拥有局部类,并且它们可以具有函数(非staticstatic),因此您可以对此进行一定程度的扩展,尽管有点麻烦:

int main() // it's int, dammit!
{
  struct X { // struct's as good as class
    static void a()
    {
    }
  };

  X::a();

  return 0;
}

但是,我会质疑实践。每个人都知道(好吧,无论如何,现在:)),C ++不支持局部函数,因此它们习惯于不使用局部函数。但是,它们并不适用于这种冲突。我会花一些时间在这段代码上,以确保它仅在此处允许本地函数。不好。


3
如果您对返回类型不满意,Main还需要两个参数。:)(或者这是可有可无的选择,但是最近没有回报吗?我跟不上。)
Leo Davidson 2010年

3
这很糟糕-破坏了良好,简洁代码的所有约定。我想不出哪个实例是个好主意。
Thomas Owens 2010年

19
@Thomas Owens:如果您需要一个回调函数并且不想用它来污染其他名称空间,那就很好了。
大卫·戴维森

9
@Leo:标准说,有主两个允许的形式: int main()int main(int argc, char* argv[])
约翰Dibling

8
该标准说int main()int main(int argc, char* argv[])必须得到支持,可能会得到其他人的支持,但它们都具有int返回值。
JoeG 2010年

260

出于所有意图和目的,C ++通过lambdas支持此功能:1

int main() {
    auto f = []() { return 42; };
    std::cout << "f() = " << f() << std::endl;
}

f是一个lambda对象,在其中充当局部函数main。可以指定捕获以允许函数访问本地对象。

幕后f是一个函数对象(即提供的类型的对象operator())。函数对象类型由编译器基于lambda创建。


自C ++ 11起1


5
啊,很干净!我没想到 这比我的想法要好得多+1
2010年

1
@sbi:过去我实际上是使用本地结构来模拟的(是的,我为自己感到羞愧)。但是,由于局部结构不会创建闭包(即您无法访问其中的局部变量)这一事实限制了其用途。您需要通过构造函数显式传递和存储它们。
康拉德·鲁道夫

1
@Konrad:它们的另一个问题是在C ++ 98中,您不能使用本地类型作为模板参数。我认为C ++ 1x取消了该限制。(还是C ++ 03?)
2010年

3
@路易斯:我必须同意弗雷德。您在lambda上附加了它们根本没有的含义(既不使用C ++,也不使用我使用过的其他语言–记录中包括Python和Ada)。此外,在C ++中进行这种区分只是没有意义,因为C ++没有句点本地功能。它只有lambda。如果您想将类似函数的事物的范围限制为某个函数,则唯一的选择是lambda或其他答案中提到的局部结构。我想说后者太复杂了,以至于没有任何实际意义。
Konrad Rudolph

2
@AustinWBryan不,C ++中的lambda只是函子的语法糖,并且开销为零。该网站上某处存在更详细的问题。
Konrad Rudolph

42

已经提到了本地类,但是这是一种通过使用operator()重载和匿名类使它们甚至更多地显示为本地函数的方法:

int main() {
    struct {
        unsigned int operator() (unsigned int val) const {
            return val<=1 ? 1 : val*(*this)(val-1);
        }
    } fac;

    std::cout << fac(5) << '\n';
}

我不建议您使用它,这只是一个有趣的把戏(可以,但恕我直言不应该)。


2014更新:

随着C ++ 11的兴起,您现在可以拥有一些局部函数,其语法有点像JavaScript:

auto fac = [] (unsigned int val) {
    return val*42;
};

1
应该是operator () (unsigned int val),您缺少一组括号。
乔D

1
实际上,如果您需要将此函子传递给stl函数或算法(例如std::sort()或),这是完全合理的事情std::for_each()
Dima 2010年

1
@Dima:不幸的是,在C ++ 03中,本地定义的类型不能用作模板参数。C ++ 0x可以解决此问题,但还提供了lambda更好的解决方案,因此您仍然不会这样做。
Ben Voigt 2010年

糟糕,您是对的。我的错。但这仍然不是一个有趣的把戏。如果允许的话,那将是一件有用的事情。:)
Dima

3
支持递归。但是,您不能auto用来声明变量。Stroustrup给出了示例:function<void(char*b, char*e)> rev=[](char*b, char*e) { if( 1<e-b ) { swap( *b, *--e); rev(++b,e); } };反转给定开始和结束指针的字符串。
同名的2014年

17

没有。

你想做什么?

解决方法:

int main(void)
{
  struct foo
  {
    void operator()() { int a = 1; }
  };

  foo b;
  b(); // call the operator()

}

2
请注意,类实例化方法随内存分配一起提供,因此由静态方法主导。
ManuelSchneid3r

14

从C ++ 11开始,您可以使用适当的lambda。有关更多详细信息,请参见其他答案。


旧答案:可以,但是,您必须作弊并使用虚拟类:

void moo()
{
    class dummy
    {
    public:
         static void a() { printf("I'm in a!\n"); }
    };

    dummy::a();
    dummy::a();
}

除了创建对象(不确定,IMO会添加同样多的噪声)之外,不确定是否可以。除非您可以使用命名空间做一些聪明的事,但我想不出来,而且滥用语言可能不是我们已经拥有的东西的好主意。:)
Leo Davidson

摆脱假人::是其他答案之一。
塞巴斯蒂安·马赫

8

正如其他人提到的,您可以通过使用gcc中的gnu语言扩展来使用嵌套函数。如果您(或您的项目)坚持使用gcc工具链,则您的代码将可以在gcc编译器针对的不同体系结构中移植。

但是,如果可能需要您使用其他工具链来编译代码,那么我将远离此类扩展。


使用嵌套函数时,我也会小心翼翼。它们是管理复杂但有凝聚力的代码块(这些代码块不供外部/通用使用)的优美解决方案。它们在控制名称空间污染方面也非常有帮助(自然复杂/详细语言中的长类。)

但是像其他任何东西一样,它们可能容易受到虐待。

令人遗憾的是C / C ++不支持这些功能作为标准。大多数pascal变体和Ada都可以(几乎所有基于Algol的语言都可以)。与JavaScript相同。与Scala等现代语言相同。与诸如Erlang,Lisp或Python之类的古老语言相同。

而且,与C / C ++一样,不幸的是,Java(我靠自己的大部分时间来谋生)没有。

我在这里提到Java是因为我看到一些张贴者建议使用类和类的方法来替代嵌套函数。这也是Java中的典型解决方法。

简短答案:不可以。

这样做往往会在类层次结构上引入人为的,不必要的复杂性。在所有条件都相等的情况下,理想的做法是使一个类层次结构(及其包含的名称空间和范围)尽可能简单地表示一个实际域。

嵌套函数有助于处理函数内部的“私有”复杂性。缺乏这些设施,就应该避免将这种“私人”复杂性传播到自己的班级模型中。

在软件(以及任何工程学科)中,建模是权衡的问题。因此,在现实生活中,这些规则(或更确切地说是准则)将有合理的例外。不过请谨慎行事。


8

您不能在C ++中使用本地函数。但是,C ++ 11具有lambdas。Lambda基本是像函数一样工作的变量。

Lambda具有类型std::function实际上并不是很正确,但是在大多数情况下可以假设是)。要使用此类型,您需要#include <functional>std::function是一个模板,使用语法将返回类型和参数类型作为模板参数std::function<ReturnType(ArgumentTypes)。例如,std::function<int(std::string, float)>一个lambda返回an int并接受两个参数,一个std::string和一个float。最常见的是std::function<void()>,它什么也不返回,也不接受任何参数。

一旦声明了lambda,就可以像普通函数一样使用以下语法对其进行调用 lambda(arguments)

要定义lambda,请使用语法[captures](arguments){code}(还有其他方法可以执行此操作,但在此不再赘述)。arguments是lambda接受的参数,code是调用lambda时应运行的代码。通常,您放置[=][&]捕获。[=]意味着您将捕获由value定义值的范围内的所有变量,这意味着它们将保留声明lambda时具有的值。[&]表示您通过引用捕获了作用域中的所有变量,这意味着它们将始终具有其当前值,但是如果将它们从内存中删除,程序将崩溃。这里有些例子:

#include <functional>
#include <iostream>

int main(){
    int x = 1;

    std::function<void()> lambda1 = [=](){
        std::cout << x << std::endl;
    };
    std::function<void()> lambda2 = [&](){
        std::cout << x << std::endl;
    };

    x = 2;
    lambda1();    //Prints 1 since that was the value of x when it was captured and x was captured by value with [=]
    lambda2();    //Prints 2 since that's the current value of x and x was captured by value with [&]

    std::function<void()> lambda3 = [](){}, lambda4 = [](){};    //I prefer to initialize these since calling an uninitialized lambda is undefined behavior.
                                                                 //[](){} is the empty lambda.

    {
        int y = 3;    //y will be deleted from the memory at the end of this scope
        lambda3 = [=](){
            std::cout << y << endl;
        };
        lambda4 = [&](){
            std::cout << y << endl;
        };
    }

    lambda3();    //Prints 3, since that's the value y had when it was captured

    lambda4();    //Causes the program to crash, since y was captured by reference and y doesn't exist anymore.
                  //This is a bit like if you had a pointer to y which now points nowhere because y has been deleted from the memory.
                  //This is why you should be careful when capturing by reference.

    return 0;
}

您也可以通过指定特定变量的名称来捕获它们。仅指定它们的名称将按值捕获它们,而在&前面指定它们的名称将按引用捕获它们。例如,[=, &foo]将按值捕获所有变量,但foo将通过引用[&, foo]捕获,并且将按引用捕获所有变量,但foo将通过值捕获。您还可以仅捕获特定变量,例如[&foo]foo通过引用捕获而不会捕获其他变量。您也可以使用完全不捕获任何变量[]。如果尝试在未捕获的lambda中使用变量,则该变量将无法编译。这是一个例子:

#include <functional>

int main(){
    int x = 4, y = 5;

    std::function<void(int)> myLambda = [y](int z){
        int xSquare = x * x;    //Compiler error because x wasn't captured
        int ySquare = y * y;    //OK because y was captured
        int zSquare = z * z;    //OK because z is an argument of the lambda
    };

    return 0;
}

您不能更改在lambda内由value捕获的变量的值(由value捕获的变量const在lambda内具有类型)。为此,您需要通过引用捕获变量。这是一个例子:

#include <functional>

int main(){
    int x = 3, y = 5;
    std::function<void()> myLambda = [x, &y](){
        x = 2;    //Compiler error because x is captured by value and so it's of type const int inside the lambda
        y = 2;    //OK because y is captured by reference
    };
    x = 2;    //This is of course OK because we're not inside the lambda
    return 0;
}

同样,调用未初始化的lambda是未定义的行为,通常会导致程序崩溃。例如,永远不要这样做:

std::function<void()> lambda;
lambda();    //Undefined behavior because lambda is uninitialized

例子

这是您要使用lambdas在问题中要做的代码:

#include <functional>    //Don't forget this, otherwise you won't be able to use the std::function type

int main(){
    std::function<void()> a = [](){
        // code
    }
    a();
    return 0;
}

这是lambda的更高级的示例:

#include <functional>    //For std::function
#include <iostream>      //For std::cout

int main(){
    int x = 4;
    std::function<float(int)> divideByX = [x](int y){
        return (float)y / (float)x;    //x is a captured variable, y is an argument
    }
    std::cout << divideByX(3) << std::endl;    //Prints 0.75
    return 0;
}

7

不,这是不允许的。默认情况下,C和C ++都不支持此功能,但是TonyK指出(在注释中)GNU C编译器的扩展可以在C中启用此功能。


2
作为特殊扩展,GNU C编译器支持它。但仅适用于C,不适用于C ++。
TonyK 2010年

啊。我的C编译器没有任何特殊扩展。不过,很高兴知道。我将这个滴滴加入到我的答案中。
Thomas Owens 2010年

我已经使用gcc扩展来支持嵌套函数(尽管在C中不是C ++)。嵌套函数是一件很聪明的事情(例如在Pascal和Ada中),用于管理复杂但没有凝聚力的结构,这些结构并不是通用的。只要使用gcc工具链,就可以确保大多数目标架构都可以移植。但是,如果必须使用非gcc编译器编译结果代码有所变化,那么最好避免这种扩展,并尽可能地靠近ansi / posix口头禅。
luis.espinal 2010年

7

所有这些技巧都(或多或少)只是将其视为局部函数,但它们并非如此。在局部函数中,可以使用其超级函数的局部变量。这是半全球性的。这些技巧都不能做到这一点。最接近的是c ++ 0x的lambda技巧,但是它的关闭是在定义时间而不是使用时间中进行的。


现在,我认为这是最好的答案。尽管可以在一个函数中声明一个函数(我一直使用它),但是它不是许多其他语言中定义的局部函数。知道这种可能性仍然是一件好事。
Alexis Wilke


4

让我在这里发布我认为最干净的C ++ 03解决方案。*

#define DECLARE_LAMBDA(NAME, RETURN_TYPE, FUNCTION) \
    struct { RETURN_TYPE operator () FUNCTION } NAME;

...

int main(){
  DECLARE_LAMBDA(demoLambda, void, (){ cout<<"I'm a lambda!"<<endl; });
  demoLambda();

  DECLARE_LAMBDA(plus, int, (int i, int j){
    return i+j;
  });
  cout << "plus(1,2)=" << plus(1,2) << endl;
  return 0;
}

(*)在C ++世界中使用宏从未被认为是干净的。


亚历克西斯,你说对了,这不是很干净。它仍然接近干净,因为它很好地表达了程序员的意图,没有副作用。我认为编程艺术正在写出人类可读的表达方式,就像小说一样。
巴尼

2

但是我们可以在main()中声明一个函数:

int main()
{
    void a();
}

尽管语法是正确的,但有时可能会导致“最烦人的解析”:

#include <iostream>


struct U
{
    U() : val(0) {}
    U(int val) : val(val) {}

    int val;
};

struct V
{
    V(U a, U b)
    {
        std::cout << "V(" << a.val << ", " << b.val << ");\n";
    }
    ~V()
    {
        std::cout << "~V();\n";
    }
};

int main()
{
    int five = 5;
    V v(U(five), U());
}

=>没有程序输出。

(仅在编译后发出Clang警告)。

C ++最烦人的解析

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.