一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。
但与函数不同,lambda可能定义在函数内部。一个lambda表达式具有如下形式
其中,capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空);return type、parameter list和function body与任何普通函数一样,分别表示返回类型、参数列表和函数体。
但与普通函数不同,lambda必须使用尾置返回
来指定返回类型。
我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体
举例:
auto f=[]{return 42;}; //将lambda表达式(匿名函数)类似变量一样赋给f
此例中,我们定义了一个可调用对象f,它不接受参数,返回42。
lambda的调用方式与普通函数的调用方式相同,都是使用调用运算符:
f();//42
尽量“匿名”使用 lambda 表达式
因为 lambda 表达式毕竟不是普通的变量,向上面那样直接赋给f并不是很好的写法, C++ 鼓励程序员尽量“匿名”使用 lambda 表达式。
像下面这样:
这样写的好处是,lambda 表达式调用完后也就不存在了,最小化了它的影响范围,让代码更加安全
注意点:
1、捕获列表中捕获的变量是lambad表达式所在函数中的局部变量,函数外的全局变量无需捕获可以直接使用
2、值捕获 vs 引用捕获:
使用“[=]”按值捕获的时候,lambda 表达式使用的是变量的独立副本,非常安全。
而使用“[&]”的方式捕获引用就存在风险,当 lambda 表达式调用时可能引用的变量生命周期已经结束了。就会导致出错
开发建议:
建议在使用捕获功能的时候要小心,对于“就地”使用的小 lambda 表达式,可以用“[&]”来减少代码量,保持整洁;
而对于非本地调用、生命周期较长的 lambda 表达式应慎用“[&]”捕获引用
最好是在“[]”里显式写出变量列表,避免捕获不必要的变量。
变量捕获举例:
func(){
int n = 10; // 一个外部变量
auto f = [=](int x) // lambda表达式,用“=”值捕获
{
cout << x*n << endl; // 直接操作外部变量
};
f(3); // 调用lambda表达式
}
注意:如果是引用捕获的话,需要注意在捕获变量生命周期结束之前
其实这种捕获外部变量的行为还有个名字叫闭包。
怎么理解闭包?
可以把闭包理解为一个“活的代码块”、“活的函数”。它虽然在出现时被定义,但因为保存了定义时捕获的外部变量,就可以跳离定义点,把这段代码“打包”传递到其他地方去执行、
和面向过程的比较:
面向过程中,程序流程是按步骤执行的“死程序”,而lambda中是一个个的“活函数”,像做数学题那样逐步计算、推导出结果,有点像下面的这样:
auto a = [](int x) // a函数执行一个功能
{...}
auto b = [](double x) // b函数执行一个功能
{...}
auto c = [](string str) // c函数执行一个功能
{...}
auto f = [](...) // f函数执行一个功能
{...}
return f(a, b, c) // f调用a/b/c运算得到结果
和面向对象的比较:
你也可以再对比面向对象来理解。在面向对象编程里,程序是由一个个实体对象组成的,对象通信完成任务。而在函数式编程里,程序是由一个个函数组成的,函数互相嵌套、组合、调用完成任务。
二者都是类型推断符,让编译器帮我们做类型推断
最大的区别在于:
二者推导规则不同在于对引用的处理,具体如下:
decltype 会保留引用类型,而 auto 会抛弃引用类型,直接推导出它的原始类型。请看下面的例子:
#include
using namespace std;
int main() {
int n = 10;
int &r1 = n;
//auto推导
auto r2 = r1;
r2 = 20;
cout << n << ", " << r1 << ", " << r2 << endl;
//decltype推导
decltype(r1) r3 = n;
r3 = 99;
cout << n << ", " << r1 << ", " << r3 << endl;
return 0;
}
运行结果:
10, 10, 20
99, 99, 99
从运行结果可以发现,给 r2 赋值并没有改变 n 的值,这说明 r2 没有指向 n,而是自立门户,单独拥有了一块内存,这就证明 r 不再是引用类型,它的引用类型被 auto 抛弃了。
给 r3 赋值,n 的值也跟着改变了,这说明 r3 仍然指向 n,它的引用类型被 decltype 保留了。
1、导入命名空间
使用C++在写不同的功能模块时,为了防止命名冲突,建议对模块取命名空间,这样在使用时就需要指定是哪个命名空间。
2、指定别名
通常用typedef给一个类型取别名,但是typedef不能为模版取别名
c++11提供 的using ,可以用于模板别名,
template <typename Val>
using str_map_t = std::map<std::string, Val>;
3、使用 using 关键字在派生类中引用基类成员,同时修改访问权限
//基类People
class People {
public:
void show();
protected:
char *m_name;
int m_age;
};
//派生类Student 继承 基类People
class Student : public People {
public:
void learning();
public:
using People::m_name; //将protected改为public
using People::m_age; //将protected改为public
float m_score;
private:
using People::show; //将public改为private
};
使用noexcept表明函数或操作不会发生异常
好处是可以减少运行时开销,使得编译器有更大的优化空间。
因为当出现异常时,会抛出一些相关的堆栈错误信息, 这里面包含:错误位置, 错误原因, 调用顺序和层级路径等信息;
当使用noexcept声明一个函数不会抛出异常时,编译器就不会去生成这些额外的代码, 减少运行时开销.
当函数的实参数量未知,但是我们知道全部实参的类型相同,那么就可以用initializer_list类型的形参。即initializer_list用于表示某种特定类型的值的数组
initializer_list提供的操作如下:
initializer_list<T> lst;
//默认初始化;T类型元素的空列表
initializer_list<T> lst{a,b,c...};
//lst的元素数量和初始值一样多;lst的元素是对应初始值的副本
lst2(lst)
lst2=lst
//拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本元素共享
lst.size() //列表中的元素数量
lst.begin() //返回指向lst中首元素的指针
lst.end() //返回指向lst中尾元素下一位置的指针
需要注意的是:initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中元素的值。并且,拷贝或赋值一个initializer_list对象不会拷贝列表中的元素,其实只是引用而已,原始列表和副本共享元素。
和使用vector一样,我们也可以使用迭代器访问initializer_list里的元素
void error_msg(initializer_list<string> il)
{
for(auto beg=il.begin();beg!=il.end();++beg)
cout<<*beg<<" ";
cout<<endl;
}
如果想向initializer_list形参中传递一个值的序列,则必须把序列放在一对花括号内:
//expected和actual是string对象
if(expected != actual)
error_msg({"functionX",expectde,actual});
else
error_msg({"functionX","okay"});
说了这么多,那initializer_list到底有什么应用呢?
有了initializer_list之后,对于STL的container的初始化就方便多了,比如以前初始化一个vector需要这样:
std::vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
而现在c++11添加了initializer_list后,我们可以这样初始化
std::vector v = { 1, 2, 3, 4 };
tuple是一个固定大小的不同类型值的集合,是泛化的std::pair。std::tuple理论上可以有无数个任意类型的成员变量,而std::pair只能是2个成员,因此在需要保存3个及以上的数据时就需要使用tuple元组了。
使用:
std::tuple<double, char, std::string> t = std::make_tuple(3.14, 'A', "StoneLiu");
std::cout << "tuple length " << std::tuple_size<decltype(t)>::value;
std::cout << "first: " << std::get<0>(t) << ", two: " << std::get<1>(t) << ", three: " << std::get<2>(t) ;
bitset是一种类似数组的结构,它的每一个元素只能是0或1,每个元素仅用1bit空间。常用于位运算
定义和初始化:
bitset<n> b; //b有n位,每位都为0
bitset<n> b(u); //b是unsigned long型u的一个副本
bitset<n> b(s); //b是string对象s中含有的位串的副本
bitset<n> b(s, pos, n); //b是s中从位置pos开始的n个位的副本
使用:
#include
#include
using namespace std;
int main() {
bitset<10> v1;//定义10位的bitset变量
v1 = 8;//将v1赋值为8
bitset<10> v2(v1);//初始化,与v1相同
bitset<10> v3 = v2;//赋值,=v2
//从右往左数,最右边的为第一位,向左依次加1
v1[1] = 1;//将v1的第一位置为1
//v1.set(1);
cout << "v1=" << v1 << endl;//用二进制的形式输出v1
cout << "v2=" << v2 << endl;
cout << "v3=" << v3 << endl;
for (int i = (int)(v1.size()) - 1; i >= 0; --i)//一位一位的输出
cout << v1[i];
cout << endl;
cout << "any() --- " << v1.any() << endl;
cout << "count() --- " << v1.count() << endl;
cout << "flip --- " << v1.flip() << endl;
cout << "none() --- " << v1.none() << endl;
//其他函数用法相似
return 0;
}