在c++中我们可以在函数中使用函数吗?

我的意思是:

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


return 0;
}
322812 次浏览

现代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!");
}
}

Lambdas还可以通过**引用捕获来修改局部变量。通过引用捕获,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++不支持局部函数,所以他们习惯了没有它们。然而,他们并不习惯这种拼凑。我在这段代码上花了不少时间,以确保它确实只允许本地函数。不好的。

不,这是不允许的。C和c++默认情况下都不支持这个特性,但是TonyK指出(在评论中)有GNU C编译器的扩展可以在C中支持这个行为。

在c++中,你不能在另一个自由函数中定义一个自由函数。

不。

你想做什么?

处理:

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


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


}

从c++ 11开始,你可以使用正确的λ。更多细节请参见其他答案。


老答案:你可以,在某种程度上,但你必须欺骗和使用一个虚拟类:

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


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

无论如何,c++通过λ:1来支持这一点

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

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

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


1自c++ 11

局部类已经提到过,但是这里有一种方法可以让它们更像局部函数,使用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;
};

对于递归函数,不支持编译时类型演绎:

function<int(int)> factorial{ [&](int n)
{
return (n == 1 || n == 0) ? 1 : factorial(n - 1) * n;
} };

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

然而,如果存在可能的需求,您可能需要使用不同的工具链编译代码,那么我将远离此类扩展。


在使用嵌套函数时,我也要小心。它们是管理复杂但内聚的代码块结构的一个漂亮的解决方案(这些代码块并不用于外部/一般用途)。它们在控制名称空间污染方面也很有帮助(在啰嗦语言中,对于自然复杂/长类来说,这是一个非常实际的问题)。

但和其他事情一样,它们也可能被滥用。

遗憾的是,C/ c++不支持这样的特性作为标准。大多数pascal变体和Ada都可以(几乎所有基于algol的语言都可以)。JavaScript也是一样。像Scala这样的现代语言也是如此。像Erlang、Lisp或Python这样的古老语言也是如此。

不幸的是,就像C/ c++一样,Java(我的大部分生活都是用它来赚取的)没有。

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

简单的回答:不。

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

嵌套函数有助于处理“私有的”、函数内的复杂性。如果没有这些工具,就应该尽量避免将“私有”复杂性传播到类模型中。

在软件(以及任何工程学科)中,建模是一个权衡的问题。因此,在现实生活中,这些规则(或指导方针)会有合理的例外。不过,要小心行事。

所有这些技巧只是看起来(或多或少)像局部函数,但它们并不是这样工作的。在局部函数中,你可以使用超函数的局部变量。这是一种半全球化。这些把戏都做不到。最接近的是c++0x中的lambda技巧,但它的闭包是在定义时间内绑定的,而不是使用时间。

但是我们可以在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());
}

=>无程序输出。

(编译后只有叮当声警告)。

c++ 's most vexing parse again

让我在这里发布一个我认为最干净的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++世界中使用宏从来不被认为是干净的。

c++中不能有局部函数。然而,c++ 11有λ。lambda基本上是像函数一样工作的变量。

lambda的类型是std::function (事实上,这并不完全正确,但在大多数情况下你可以假设它是)。要使用这种类型,你需要#include <functional>std::function是一个模板,将返回类型和实参类型作为模板实参,语法为std::function<ReturnType(ArgumentTypes)>。例如,std::function<int(std::string, float)>是一个返回int并接受两个参数的lambda,一个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 reference 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捕获的变量在lambda中有const类型)。为此,您需要通过引用捕获变量。下面是一个例子:

#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;
}

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

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;
}

是的,你可以用它们做一些甚至c++ 20 Lambdas都不支持的事情。也就是说,纯递归调用自身&相关的功能。

例如,Collatz猜想是某个简单的递归函数最终将产生"1"使用显式的局部结构体和函数,我可以编写一个单独的自包含函数来运行任何“n”的测试。

constexpr std::optional<int> testCollatzConjecture(int N) {
struct CollatzCallbacks {
constexpr static int onEven(int n) {
return recurse(n >> 1); // AKA "n/2"
}
constexpr static int onOdd(int n) {
if(n==1) return 1;     // Break recursion. n==1 is only possible when n is odd.
return recurse(3 * n + 1);
}
constexpr static int recurse(int n) {
return (n%2) ? onOdd(n) : onEven(n); // (n%2) == 1 when n is odd
}
};


// Error check
if(N < 0) return {};


// Recursive call.
return CollatzCallbacks::recurse(N);
}

注意一些c++20 lambdas在这里不能做的事情:

  1. 我不需要std::function<>glue OR lambda捕获("[&]")只是为了使局部递归函数能够调用它们自己或彼此。我需要3个有名字的普通函数,这就是我要写的。
  2. 我的代码更易于阅读,(由于(1))也将运行得更快。
  3. 我在“CollatzCallbacks"”中清晰地分离了递归逻辑。从“testcollatzconjecture”的其余部分。这一切都在一个孤立的沙箱中运行。
  4. 我能让每件事都“井井有条”。而且是无状态的,所以它可以在编译时对任何常数值运行。AFAIK我需要c++23来实现无状态lambdas的递归部分。

请记住:Lambda函数实际上只是编译器生成的局部结构体,如"CollatzCallbacks",只是它们没有命名,并且只有一个"操作符()"成员函数。您总是可以直接编写更复杂的局部结构和函数,特别是在您确实需要它们的情况下。