Maison >développement back-end >Tutoriel C#.Net >Combiné avec les nouvelles fonctionnalités de C 11 pour apprendre l'utilisation des expressions lambda en C
En C 11, une expression lambda (souvent appelée "lambda") est un moyen pratique de définir un objet fonction anonyme au point où il est appelé ou passé en argument à une fonction. Les lambdas sont généralement utilisés pour encapsuler un petit nombre de lignes de code transmises à un algorithme ou à une méthode asynchrone. Cet article définit ce que sont les lambdas, les compare à d'autres techniques de programmation, décrit leurs avantages et fournit un exemple de base.
Parties d'une expression Lambda
La norme ISO C montre un simple lambda passé comme troisième argument à la fonction std::sort() :
#include <algorithm> #include <cmath> void abssort(float* x, unsigned n) { std::sort(x, x + n, // Lambda expression begins [](float a, float b) { return (std::abs(a) < std::abs(b)); } // end of lambda expression ); }
Ce diagramme montre les composants d'un lambda :
Clause de capture (également appelée bootstrapping lambda dans la spécification C.)
Liste des paramètres (facultatif). (également appelé déclarateur lambda)
Spécification de la variable (facultatif).
Spécification d'exception (facultatif).
Type de retour de fin (facultatif).
"corps lambda"
Clause de capture
Un Lambda peut introduire de nouvelles variables dans son corps (en C14), il peut aussi accéder (ou "capturer") des variables en la portée environnante. Un lambda commence par une clause Capture (le bootstrap lambda dans la syntaxe standard), qui spécifie la variable à capturer et s'il faut capturer par valeur ou par référence. Les variables préfixées par une esperluette (&) sont accessibles par référence, les variables sans ce préfixe sont accessibles par valeur.
Une clause de capture vide [ ] indique que le corps de l'expression lambda n'accède pas aux variables dans la portée englobante.
Le mode de capture par défaut (capture-default dans la syntaxe standard) peut être utilisé pour indiquer comment capturer toutes les variables externes référencées dans le lambda : [&] signifie capturer toutes les variables référencées par référence, tandis que [=] signifie les capturer par valeur . Vous pouvez utiliser le mode de capture par défaut, puis spécifier explicitement le mode opposé pour une variable spécifique. Par exemple, si le corps lambda accède à la variable externe total par référence et à la variable externe facteur par valeur, la clause de capture suivante est équivalente :
[&total, factor] [factor, &total] [&, factor] [factor, &] [=, &total] [&total, =]
Utilisez capture -default, seules les variables mentionnées dans le lambda seront capturées.
Si une clause de capture contient capture-default&, alors aucune capture dans l'identifiant de cette clause de capture ne peut prendre la forme & identifiant. De même, si une clause de capture contient capture-default=, la capture de la clause de capture ne peut pas prendre la forme = identifiant. identifiant ou celui-ci ne peut apparaître plus d’une fois dans la clause de capture. Les extraits de code suivants donnent quelques exemples.
struct S { void f(int i); }; void S::f(int i) { [&, i]{}; // OK [&, &i]{}; // ERROR: i preceded by & when & is the default [=, this]{}; // ERROR: this when = is the default [i, i]{}; // ERROR: i repeated }
la capture suivie de points de suspension est une extension de package, comme le montre l'exemple de modèle variadique suivant :
template<class... Args> void f(Args... args) { auto x = [args...] { return g(args...); }; x(); }
Pour utiliser une expression lambda dans le corps d'une méthode de classe, transmettez le pointeur this à la clause Capture pour donner accès aux méthodes et aux données membres de la classe englobante. Pour un exemple montrant comment utiliser des expressions lambda avec des méthodes de classe, consultez « Exemple : Utilisation d'expressions Lambda dans les méthodes » dans Exemples d'expressions Lambda.
Lors de l'utilisation de la clause de capture, il est recommandé de rappeler les points suivants (en particulier lors de l'utilisation de lambdas qui prennent plusieurs threads) :
La capture de référence peut être utilisée pour modifier des variables externes, mais la capture de valeur ne peut pas réaliser cette opération. (Un mutable permet la modification de la copie, mais pas de l'original.)
La capture de référence reflète les mises à jour des variables externes, contrairement à la capture de valeur.
La capture de référence introduit des dépendances à vie, tandis que la capture de valeur n'a pas de dépendances à vie. Ceci est particulièrement important lorsque le lambda est exécuté de manière asynchrone. Si vous capturez une variable locale par référence dans un lambda asynchrone, la variable locale disparaîtra très probablement lors de l'exécution du lambda, provoquant une violation d'accès à l'exécution.
Capture universelle (C 14)
En C 14, de nouvelles variables peuvent être introduites et initialisées dans une clause Capture sans qu'elles existent dans la portée englobante de la fonction lambda. L'initialisation peut être exprimée par n'importe quelle expression arbitraire ; et le type de la nouvelle variable est déduit du type produit par cette expression. L'un des avantages de cette fonctionnalité est qu'en C 14, il est possible de capturer des variables de déplacement uniquement (telles que std::unique_ptr) de la portée environnante et de les utiliser dans un lambda.
pNums = make_unique<vector<int>>(nums); //... auto a = [ptr = move(pNums)]() { // use ptr };
Liste des paramètres
En plus de capturer des variables, lambda peut également accepter des paramètres d'entrée. La liste d'arguments (appelée déclarateur lambda dans la syntaxe standard) est facultative et est à bien des égards similaire à la liste d'arguments d'une fonction.
int y = [] (int first, int second) { return first + second; };
En C 14, si le type de paramètre est générique, vous pouvez utiliser le mot-clé auto comme spécificateur de type. Cela indique au compilateur de créer l'opérateur d'appel de fonction comme modèle. Chaque instance de auto dans la liste des paramètres équivaut à un paramètre de type différent.
auto y = [] (auto first, auto second) { return first + second; };
lambda 表达式可以将另一个 lambda 表达式作为其参数。 有关详细信息,请参阅 Lambda 表达式的示例主题中的“高阶 Lambda 表达式”。
由于参数列表是可选的,因此在不将参数传递到 lambda 表达式,并且其 lambda-declarator: 不包含 exception-specification、trailing-return-type 或 mutable 的情况下,可以省略空括号。
可变规范
通常,lambda 的函数调用运算符为 const-by-value,但对 mutable 关键字的使用可将其取消。 它不会生成可变的数据成员。 利用可变规范,lambda 表达式的主体可以修改通过值捕获的变量。 本文后面的一些示例将显示如何使用 mutable。
异常规范
你可以使用 throw() 异常规范来指示 lambda 表达式不会引发任何异常。 与普通函数一样,如果 lambda 表达式声明 C4297 异常规范且 lambda 体引发异常,Visual C++ 编译器将生成警告 throw(),如下所示:
// throw_lambda_expression.cpp // compile with: /W4 /EHsc int main() // C4297 expected { []() throw() { throw 5; }(); }
返回类型
将自动推导 lambda 表达式的返回类型。 无需使用 auto 关键字,除非指定尾随返回类型。 trailing-return-type 类似于普通方法或函数的返回类型部分。 但是,返回类型必须跟在参数列表的后面,你必须在返回类型前面包含 trailing-return-type 关键字 ->。
如果 lambda 体仅包含一个返回语句或其表达式不返回值,则可以省略 lambda 表达式的返回类型部分。 如果 lambda 体包含单个返回语句,编译器将从返回表达式的类型推导返回类型。 否则,编译器会将返回类型推导为 void。 下面的代码示例片段说明了这一原则。
auto x1 = [](int i){ return i; }; // OK: return type is int auto x2 = []{ return{ 1, 2 }; }; // ERROR: return type is void, deducing // return type from braced-init-list is not valid
lambda 表达式可以生成另一个 lambda 表达式作为其返回值。 有关详细信息,请参阅 Lambda 表达式的示例中的“高阶 Lambda 表达式”。
Lambda 体
lambda 表达式的 lambda 体(标准语法中的 compound-statement)可包含普通方法或函数的主体可包含的任何内容。 普通函数和 lambda 表达式的主体均可访问以下变量类型:
从封闭范围捕获变量,如前所述。
参数
本地声明变量
类数据成员(在类内部声明并且捕获 this 时)
具有静态存储持续时间的任何变量(例如,全局变量)
以下示例包含通过值显式捕获变量 n 并通过引用隐式捕获变量 m 的 lambda 表达式:
// captures_lambda_expression.cpp // compile with: /W4 /EHsc #include <iostream> using namespace std; int main() { int m = 0; int n = 0; [&, n] (int a) mutable { m = ++n + a; }(4); cout << m << endl << n << endl; }
输出:
5 0
由于变量 n 是通过值捕获的,因此在调用 lambda 表达式后,变量的值仍保持 0 不变。 mutable 规范允许在 lambda 中修改 n。
尽管 lambda 表达式只能捕获具有自动存储持续时间的变量,但你可以在 lambda 表达式的主体中使用具有静态存储持续时间的变量。 以下示例使用 generate 函数和 lambda 表达式为 vector 对象中的每个元素赋值。 lambda 表达式将修改静态变量以生成下一个元素的值。
void fillVector(vector<int>& v) { // A local static variable. static int nextValue = 1; // The lambda expression that appears in the following call to // the generate function modifies and uses the local static // variable nextValue. generate(v.begin(), v.end(), [] { return nextValue++; }); //WARNING: this is not thread-safe and is shown for illustration only }
下面的代码示例使用上一示例中的函数,并添加了使用 STL 算法 generate_n 的 lambda 表达式的示例。 该 lambda 表达式将 vector 对象的元素指派给前两个元素之和。 使用了 mutable 关键字,以使 lambda 表达式的主体可以修改 lambda 表达式通过值捕获的外部变量 x 和 y 的副本。 由于 lambda 表达式通过值捕获原始变量 x 和 y,因此它们的值在 lambda 执行后仍为 1。
// compile with: /W4 /EHsc #include#include #include #include using namespace std; template void print(const string& s, const C& c) { cout << s; for (const auto& e : c) { cout << e << " "; } cout << endl; } void fillVector(vector<int>& v) { // A local static variable. static int nextValue = 1; // The lambda expression that appears in the following call to // the generate function modifies and uses the local static // variable nextValue. generate(v.begin(), v.end(), [] { return nextValue++; }); //WARNING: this is not thread-safe and is shown for illustration only } int main() { // The number of elements in the vector. const int elementCount = 9; // Create a vector object with each element set to 1. vector v(elementCount, 1); // These variables hold the previous two elements of the vector. int x = 1; int y = 1; // Sets each element in the vector to the sum of the // previous two elements. generate_n(v.begin() + 2, elementCount - 2, [=]() mutable throw() -> int { // lambda is the 3rd parameter // Generate current value. int n = x + y; // Update previous two values. x = y; y = n; return n; }); print("vector v after call to generate_n() with lambda: ", v); // Print the local variables x and y. // The values of x and y hold their initial values because // they are captured by value. cout << "x: " << x << " y: " << y << endl; // Fill the vector with a sequence of numbers fillVector(v); print("vector v after 1st call to fillVector(): ", v); // Fill the vector with the next sequence of numbers fillVector(v); print("vector v after 2nd call to fillVector(): ", v); }
输出:
vector v after call to generate_n() with lambda: 1 1 2 3 5 8 13 21 34 x: 1 y: 1 vector v after 1st call to fillVector(): 1 2 3 4 5 6 7 8 9 vector v after 2nd call to fillVector(): 10 11 12 13 14 15 16 17 18
更多结合C++11新特性来学习C++中lambda表达式的用法相关文章请关注PHP中文网!