What is Lambda?
C++11 has added a very important feature - Lambda expression. The brothers in the camp (Bet David) are all familiar with Objective-C. Many people have a special liking for blocks and use them to implement all kinds of callback functions and agents. Some people even choose to use open source frameworks such as FBKVOController and BlocksKit to change the processing of KVO and control events to be solved through block. The reason is that it is simple, convenient and intuitive, and the definition and use of functions appear in the same place. The Lambda expression here is actually very similar to block, of course if you compare it with Swift closures, that's the same thing.
This is a short story about C\C++ programmers, a short story about C++11 - the new standard that just passed...
Please don't get me wrong. The "optimization" mentioned in the question does not improve the performance of the program - Lambda expressions cannot do this. Essentially, it is just a kind of "grammatical sugar". Without using this expression, we can still write programs that meet the needs. Just like giving up C and using assembly, or giving up assembly and using machine language, the range you can control is there, no increase or decrease. But if there is a choice, I believe most people will choose assembly over machine language, C over assembly, and even C++ over C... If you really chose this way, then I have reason to believe that you will choose the Lambda expression in the new C++ standard, because it can really simplify your program and make it easier to write programs; make your program easier to read and more beautiful; and also give you more capital to show off to your peers.
Start with a practical application
Let's look at an example.
Whether it is a C language user or a C++ user, if you are engaged in algorithm development of PC programs, I am 96.57% sure that you may have used the C++ standard template library STL ( string, vector, etc.). After all, STL's abstraction is good, it's not useless, right? There is a major category in STL that is algorithms. The abstraction of these algorithms is also good. Let’s take the sort algorithm (sort) for example.
Suppose there is a structure called Student, which contains two terms ID and name - representing the student number and name respectively. In an application, if the user wants to sort a Student's array from large to small according to the ID, the program may be written in the following form (all programs in this article are compiled and passed under Visual Studio 2010):
#include <string> #include <vector> #include <iostream> #include <iterator> #include <algorithm> using namespace std; struct Student { unsigned ID; string name; Student(unsigned i, string n) : ID(i), name(n){} }; struct compareID { bool operator ()(const Student& val1, const Student& val2) const { return < ; } }; int main(int argc, char* argv[]) { Student a[] = {Student(2, “John”), Student(0, “Tom”), Student(1, “Lily”)}; sort(a, a+3, compareID()); for(int i=0; i<3; ++i) cout<<a[i].ID<<' ‘<<a[i].name<<endl; return 0 }
The program is sorted with sort, and then outputs the result with a for loop. The reason why this sort can be completed is because of the existence of the functor compardID.
Now suppose that the user's needs have changed (or another requirement) and you need to sort them by the student's name, then you need to rewrite a functor as follows:
struct compareName { bool operator ()(const Student& val1, const Student& val2) const { return < ; } };
Then modify the sort call to:
sort(a, a+3, compareName());
The problem arises, have you realized it? You just want to express a very simple sorting method, and you have to introduce a lot of lines of code to build corresponding functors. If this function is used in many places, then the value of establishing it is relatively high. If you only use it in one place, you have to have a smooth idea in the middle, and you are writing so many lines of code while cursing. On the other hand, when the program reads the corresponding part, it has to search for some part of the project with smooth ideas - how do compareName or compareID do it?
Yes, yes, as a C++ old bird, you would say that writing code like this is too unprofessional. There is no way to write functors without creating them. For example, when sorting by ID, it can be implemented by introducing bind in boost library, such as this:
sort(a, a+3, bind(less<unsigned>(), bind(&Student::ID, _1), bind(&Student::ID, _2)));
If you can write or understand this code, I admit that your C++ level is indeed reasonable (if you don't understand it, it doesn't matter, it's not the focus of this article). But is this code really good? Indeed, this allows the functor to be omitted. But the problem is that the complexity of the code has greatly increased - even for such a simple requirement, the bind expression must be so complicated, and what complex form should be written for more complex requirements? This is a torture for bind itself, those who write programs, and those who read programs - do you hold it?
What if you use Lambda expressions, well, this sort statement can be written like this:
sort(a, a+3, [](const Student& val1, const Student& val2){ return < ; });
That looks a bit strange. The third function of sort is a Lambda expression. If we remove the "[]" at the beginning and do not look at it, the latter part is very similar to a function - you can easily see what this function does: given two Student elements, compare the ID values of the two elements and return the comparison result - this thing is much easier to read than the bind result above.
In fact, using Lambda expressions, the above program can be modified to the following (only the main function is listed):
int main(int argc, char* argv[]) { Student a[] = {Student(2, “John”), Student(0, “Tom”), Student(1, “Lily”)}; sort(a, a+3, [](const Student& val1, const Student& val2){ return < ; }); for_each(a, a+3, [](const Student& val){cout<<<<' ‘<<<<endl;}); return 0 }
The for_each sentence is used to output - the Lambda expression means: for each val, output its ID and Name value - so that we save even the for loop.
Lambda expressions were introduced to make it easier to write programs and read programs. Like STL, what reason should I not use it?
Basic syntax of Lambda expressions
After gaining a sense of perception, let’s analyze the syntax of Lambda expressions.
I have no intention of translating the relevant chapters of the Lambda expression in the C++ standard draft here (I don't admire this). I just hope to explain its grammar in the most popular way here. Structurally speaking, Lambda expressions can be written in the following form:
Lambda-introducer lambda-declarator(opt) compound-statement
The Lambda-introducer is the "[]" just now, which cannot be omitted. Variables may also appear in brackets. Indicates passing local variables into Lambda expressions. lambda-declaratoropt is optional, including the expression parameter list, return value information, mutable declaration (and some other information, not discussed here). The final compound-statement is the main content of the expression.
Let's take a look at an example:
int n = 10; [n](int k) mutable -> int { return k + n; };
The second line of the program is a lambda expression, and almost everything that can appear in the lambda (of course, as I said before, there is some other information not discussed here, so it is not included). Let's analyze the things inside one by one:
- l[n] is a Lambda-introducer, and n is a variable indicating that the variable n in the scope of the expression will be passed into this expression. Taking this program as an example, the value passed in is 10. Lambda-introducer can specify that variables are passed in as values, or they can also specify that they are passed in as references in other forms. Let's take a look at its variants
- l(int k) represents the parameter list, which belongs to the lambda-declarator. You can think of an expression as a functor (as above). Here is a list of arguments for functors. If the function's parameter list is empty, this part can be omitted.
- lmutable means whether the variables in the functor can be changed. In the past, the compareID functor used as an example, I noticed that the operator () in it is const. If this mutable is introduced into the lambda expression, the definition of operator() in the corresponding functor will not contain this const - this means that the variable value in the functor (passed in by Lambda-introducer) can be changed. Discussion on the difference between operator() const and operator() has exceeded the scope of this article. If you want to know, take a look at the C++ related tutorials.
- l-> int represents the return type (here is int). If the compiler can infer the return type from the code, or the return type of the Lambda expression is void, this item can be omitted;
- l{ return k+n; } is compound-statement: function body.
Through analysis, we can see that this Lambda expression is equivalent to a function, which reads an int value k and adds the value n to return. According to the above description, this expression can be abbreviated as:
[n](int k){ return k + n; };
Lambda expressions can be stored in variables of type std::function<T> or std::reference_closure<T>. T represents the type of the function corresponding to the expression. Taking the above expression as an example, its input parameter is an int variable and its output is an int. In order to save it, it can be written in the following form:
function<int(int)> g = [n](int k){ return k + n; };
Another example is the Lambda expression used in the previous article:
[](const Student& val1, const Student& val2){ return < ; }
Can be stored infunction<bool(const Student&, const Student&)>
In variables of this type.
If you find it troublesome to write like this, you can also use another new feature in the new C++ standard: type derivation. That is, use auto as the type of variable, and let the compiler deduce the type of expression by itself:
auto g = [n](int k){ return k + n; };
No problem, writing g like this is still a strongly typed variable, but its type is derived by the compiler. The advantage is that you don't have to write too long variable types J
Lambda expressions advanced
As the end, let's look at some advanced usage of C++ Lambda expressions.
Lambda expressions were introduced mainly for functional programming. With Lambda expressions, we can also do some functional programming. For example, an application that uses a function as a return value:
auto g = [](int n) -> function<void (int)> { return [n](int k){ cout<<n+k<<' ‘; }; };
It is a Lambda expression, input an integer variable n, return a function (lambda expression), which receives an int value k and prints out k+n. How to use g is as follows:
int a[]={1,2,3,4,5,6,7,8,9,0}; function<void (int)> f = g(2); for_each(a, a+10, f);
It will output: 3 4 5 6 7 8 9 10 11 2
There is a little bit of functional programming
As for other things, such as the following expression:
[](){}();
is a valid call. where "[](){}" represents a Lambda expression, whose input parameter is empty, returns void, and does nothing. The last () means calling its evaluation - although it does nothing, the compilation can pass, which is very frightening.
OK, let's just write this. The last thing I want to say about Lambda expressions is: it is defined in the new standard C++11. The old compiler does not support it (this is why I use VS2010). Want to use it, and the benefits of other new standards? Hey, it's time for your guy (referring to the compiler) to upgrade.