C++98/11/17表达式类别(小结)

目标

以下代码能否编译通过,能否按照期望运行?

#include <utility>
#include <type_traits>

namespace cpp98
{

struct A { };
A func() { return A(); }

int main()
{
 int i = 1;
 i = 2;
 // 3 = 4;
 const int j = 5;
 // j = 6;
 i = j;
 func() = A();
 return 0;
}

}

namespace cpp11
{

#define is_lvalue(x) std::is_lvalue_reference<decltype((x))>::value
#define is_prvalue(x) !std::is_reference<decltype((x))>::value
#define is_xvalue(x) std::is_rvalue_reference<decltype((x))>::value
#define is_glvalue(x) (is_lvalue(x) || is_xvalue(x))
#define is_rvalue(x) (is_xvalue(x) || is_prvalue(x))

void func();
int non_reference();
int&& rvalue_reference();
std::pair<int, int> make();

struct Test
{
 int field;
 void member_function()
 {
 static_assert(is_lvalue(field), "");
 static_assert(is_prvalue(this), "");
 }
 enum Enum
 {
 ENUMERATOR,
 };
};

int main()
{
 int i;
 int&& j = std::move(i);
 Test test;

 static_assert(is_lvalue(i), "");
 static_assert(is_lvalue(j), "");
 static_assert(std::is_rvalue_reference<decltype(j)>::value, "");
 static_assert(is_lvalue(func), "");
 static_assert(is_lvalue(test.field), "");
 static_assert(is_lvalue("hello"), "");

 static_assert(is_prvalue(2), "");
 static_assert(is_prvalue(non_reference()), "");
 static_assert(is_prvalue(Test{3}), "");
 static_assert(is_prvalue(test.ENUMERATOR), "");

 static_assert(is_xvalue(rvalue_reference()), "");
 static_assert(is_xvalue(make().first), "");

 return 0;
}

}

namespace reference
{

int&& rvalue_reference()
{
 int local = 1;
 return std::move(local);
}

const int& const_lvalue_reference(const int& arg)
{
 return arg;
}

int main()
{
 auto&& i = rvalue_reference(); // dangling reference
 auto&& j = const_lvalue_reference(2); // dangling reference
 int k = 3;
 auto&& l = const_lvalue_reference(k);
 return 0;
}

}

namespace auto_decl
{

int non_reference() { return 1; }
int& lvalue_reference() { static int i; return i; }
const int& const_lvalue_reference() { return lvalue_reference(); }
int&& rvalue_reference() { static int i; return std::move(i); }

int main()
{
 auto [s1, s2] = std::pair(2, 3);
 auto&& t1 = s1;
 static_assert(!std::is_reference<decltype(s1)>::value);
 static_assert(std::is_lvalue_reference<decltype(t1)>::value);

 int i1 = 4;
 auto i2 = i1;
 decltype(auto) i3 = i1;
 decltype(auto) i4{i1};
 decltype(auto) i5 = (i1);
 static_assert(!std::is_reference<decltype(i2)>::value, "");
 static_assert(!std::is_reference<decltype(i3)>::value, "");
 static_assert(!std::is_reference<decltype(i4)>::value, "");
 static_assert(std::is_lvalue_reference<decltype(i5)>::value, "");

 auto n1 = non_reference();
 decltype(auto) n2 = non_reference();
 auto&& n3 = non_reference();
 static_assert(!std::is_reference<decltype(n1)>::value, "");
 static_assert(!std::is_reference<decltype(n2)>::value, "");
 static_assert(std::is_rvalue_reference<decltype(n3)>::value, "");

 auto l1 = lvalue_reference();
 decltype(auto) l2 = lvalue_reference();
 auto&& l3 = lvalue_reference();
 static_assert(!std::is_reference<decltype(l1)>::value, "");
 static_assert(std::is_lvalue_reference<decltype(l2)>::value, "");
 static_assert(std::is_lvalue_reference<decltype(l3)>::value, "");

 auto c1 = const_lvalue_reference();
 decltype(auto) c2 = const_lvalue_reference();
 auto&& c3 = const_lvalue_reference();
 static_assert(!std::is_reference<decltype(c1)>::value, "");
 static_assert(std::is_lvalue_reference<decltype(c2)>::value, "");
 static_assert(std::is_lvalue_reference<decltype(c3)>::value, "");

 auto r1 = rvalue_reference();
 decltype(auto) r2 = rvalue_reference();
 auto&& r3 = rvalue_reference();
 static_assert(!std::is_reference<decltype(r1)>::value, "");
 static_assert(std::is_rvalue_reference<decltype(r2)>::value, "");
 static_assert(std::is_rvalue_reference<decltype(r3)>::value, "");

 return 0;
}

}

namespace cpp17
{

class NonMoveable
{
public:
 int i = 1;
 NonMoveable(int i) : i(i) { }
 NonMoveable(NonMoveable&&) = delete;
};

NonMoveable make(int i)
{
 return NonMoveable{i};
}

void take(NonMoveable nm)
{
 return static_cast<void>(nm);
}

int main()
{
 auto nm = make(2);
 auto nm2 = NonMoveable{make(3)};
 // take(nm);
 take(make(4));
 take(NonMoveable{make(5)});
 return 0;
}

}

int main()
{
 cpp98::main();
 cpp11::main();
 reference::main();
 auto_decl::main();
 cpp17::main();
}

C++98表达式类别

每个C++表达式都有一个类型:42的类型为int,int i;则(i)的类型为int&。这些类型落入若干类别中。在C++98/03中,每个表达式都是左值或右值。

左值(lvalue)是指向真实储存在内存或寄存器中的值的表达式。“l”指的是“left-hand side”,因为在C中只有lvalue才能写在赋值运算符的左边。相对地,右值(rvalue,“r”指的是“right-hand side”)只能出现在赋值运算符的右边。

有一些例外,如const int i;,i虽然是左值但不能出现在赋值运算符的左边。到了C++,类类型的rvalue却可以出现在赋值运算符的左边,事实上这里的赋值是对赋值运算符函数的调用,与基本类型的赋值是不同的。

lvalue可以理解为可取地址的值,变量、对指针解引用、对返回类型为引用类型的函数的调用等,都是lvalue。临时对象都是rvalue,包括字面量和返回类型为非引用类型的函数调用等。字符串字面量是个例外,它属于不可修改的左值。

赋值运算符左边需要一个lvalue,右边需要一个rvalue,如果给它一个lvalue,该lvalue会被隐式转换成rvalue。这个过程是理所当然的。

动机

C++11引入了右值引用和移动语义。函数返回的右值引用,顾名思义,应该表现得和右值一样,但是这会破坏很多既有的规则:

  • rvalue是匿名的,不一定有存储空间,但右值引用指向内存中的具体对象,该对象还要被维护着;
  • rvalue的类型是确定的,必须是完全类型,静态类型与动态类型相同,而右值引用可以是不完全类型,也可以支持多态;
  • 非类类型的rvalue没有cv修饰(const和volatile),但右值引用可以有,而且修饰符必须保留。

这给传统的lvalue/rvalue二分法带来了挑战,C++委员会面临选择:

  • 维持右值引用是rvalue,添加一些特殊规则;
  • 把右值引用归为lvalue,添加一些特殊规则;
  • 细化表达式类别。

上述问题只是冰山一角;历史选择了第三种方案。

C++11表达式类别

C++11提出了表达式类别(value category)的概念。虽然名叫“value category”,但类别划分的是表达式而不是值,所以我从标题开始就把它译为“表达式类别”。C++标准定义表达式为:

An expression is a sequence of operators and operands that specifies a computation. An expression can result in a value and can cause side effects.

每个表达式都是三种类别之一:左值(lvalue)、消亡值(xvalue)和纯右值(prvalue),称为主类别。还有两种混合类别:lvalue和xvalue统称范左值(glvalue),xvalue和prvalue统称右值(rvalue)。

#define is_glvalue(x) (is_lvalue(x) || is_xvalue(x))
#define is_rvalue(x) (is_xvalue(x) || is_prvalue(x))

C++11对这些类别的定义如下:

  • lvalue指定一个函数或一个对象;
  • xvalue(eXpiring vavlue)也指向对象,通常接近其生命周期的终点;一些涉及右值引用的表达式的结果是xvalue;
  • gvalue(generalized lvalue)是一个lvalue或xvalue;
  • rvalue是xvalue、临时对象或它们的子对象,或者没有关联对象的值;
  • prvalue(pure rvalue)是不是xvalue的rvalue。

这种定义不是很清晰。具体来讲,lvalue包括:(点击展开)

lvalue的性质:

  • 与glvalue相同;
  • 内置取地址运算符可以作用于lvalue;
  • 可修改的lvalue可以出现在内置赋值运算符的左边;
  • 可以用来初始化一个左值引用。

prvalue包括:

prvalue的性质:

  • 与rvalue相同;
  • 不能是多态的;
  • 非类类型且非数组的prvalue没有cv修饰符,即使写了也没有;
  • 必须是完全类型;
  • 不能是抽象类型或其数组。

xvalue包括:

xvalue的性质;

  • 与rvalue相同;
  • 与glvalue相同。

glvalue的性质:

  • 可以隐式转换为prvalue;
  • 可以是多态的;
  • 可以是不完全类型。

rvalue的性质:

  • 内置取地址运算符不能作用于rvalue;
  • 不能出现在内置赋值或复合赋值运算符的左边;
  • 可以绑定给const左值引用(见下);
  • 可以用来初始化右值引用(见下);
  • 如果一个函数有右值引用参数和const左值引用参数两个重载,传入一个rvalue时,右值引用的那个重载被调用。

还有一些特殊的分类:

  • 对于非静态成员函数mf及其指针pmf,a.mf、p->mf、a.*pmf和p->*pmf都被归类为prvalue,但它们不是常规的prvalue,而是pending(即将发生的) member function call,只能用于函数调用;
  • 返回void的函数调用、向void的类型装换和throw语句都是void表达式,不能用于初始化引用或函数参数;
  • C++中最小的寻址单位是字节,因此位域不能绑定到非const左值引用上;const左值引用和右值引用可以绑定位域,它们指向的是位域的一个拷贝。

终于把5个类别介绍完了。表达式可以分为lvalue、xvalue和prvalue三类,lvalue和prvalue与C++98中的lvalue和rvalue类似,而xvalue则完全是为右值引用而生,兼有glvalue与rvalue的性质。除了这种三分类法外,表达式还可以分为lvalue和rvalue两类,它们之间的主要差别在于是否可以取地址;还可以分为glvalue和prvalue两类,它们之间的主要差别在于是否存在实体,glvalue有实体,因而可以修改原对象,xvalue常被压榨剩余价值。

引用绑定

我们稍微岔开一会,来看两个与表达式分类相关的特性。

引用绑定有以下类型:

  • 左值引用绑定lvalue,cv修饰符只能多不能少;
  • 右值引用可以绑定rvalue,我们通常不给右值引用加cv修饰符;
  • const左值引用可以绑定rvalue。

左值引用绑定lvalue天经地义,没什么需要关照的。但rvalue都是临时对象,绑定给引用就意味着要继续用它,它的生命周期会受到影响。通常,rvalue的生命周期会延长到绑定引用的声明周期,但有以下例外:

  • 由return语句返回的临时对象在return语句结束后即销毁,这样的函数总是会返回一个空悬引用(dangling reference);
  • 绑定到初始化列表中的引用的临时对象的生命周期只延长到构造函数结束——这是个缺陷,在C++14中被修复;
  • 绑定到函数参数的临时对象的生命周期延长到函数调用所在表达式结束,把该参数作为引用返回会得到空悬引用;
  • 绑定到new表达式中的引用的临时对象的生命周期只延长到包含new的表达式的结束,不会跟着那个对象。

简而言之,临时变量的生命周期只能延长一次。

#include <utility>

int&& rvalue_reference()
{
 int local = 1;
 return std::move(local);
}

const int& const_lvalue_reference(const int& arg)
{
 return arg;
}

int main()
{
 auto&& i = rvalue_reference(); // dangling reference
 auto&& j = const_lvalue_reference(2); // dangling reference
 int k = 3;
 auto&& l = const_lvalue_reference(k);
}

rvalue_reference返回一个指向局部变量的引用,因此i是空悬引用;2绑定到const_lvalue_reference的参数arg上,函数返回后延长的生命周期达到终点,因此j也是悬空引用;k在传参的过程中根本没有临时对象创建出来,所以l不是空悬引用,它是指向k的const左值引用。

auto与decltype

从C++11开始,auto关键字用于自动推导类型,用的是模板参数推导的规则:如果是拷贝列表初始化,则对应模板参数为std::initializer_list<T>,否则把auto替换为T。至于详细的模板参数推导规则,要介绍的话未免喧宾夺主了。

还好,这不是我们的重点。在引出重点之前,我们还得先看decltype。

decltype用于声明一个类型("declare type"),有两种语法:

  • decltype(entity);
  • decltype(expression)。

第一种,decltype的参数是没有括号包裹的标识符或类成员,则decltype产生该实体的类型;如果是结构化绑定,则产生被引类型。

第二种,decltype的参数是不能匹配第一种的任何表达式,其类型为T,则根据其表达式类别讨论:

  • 如果是xvalue,产生T&&——#define is_xvalue(x) std::is_rvalue_reference<decltype((x))>::value;
  • 如果是lvalue,产生T&——#define is_lvalue(x) std::is_lvalue_reference<decltype((x))>::value;
  • 如果是prvalue,产生T——#define is_prvalue(x) !std::is_reference<decltype((x))>::value。

因此,decltype(x)和decltype((x))产生的类型通常是不同的。

对于不带引用修饰的auto,初始化器的表达式类别会被抹去,为此C++14引入了新语法decltype(auto),产生的类型为decltype(expr),其中expr为初始化器。对于局部变量,等号右边加上一对圆括号,可以保留表达式类别。

#include <utility>
#include <type_traits>

int non_reference() { return 1; }
int& lvalue_reference() { static int i; return i; }
const int& const_lvalue_reference() { return lvalue_reference(); }
int&& rvalue_reference() { static int i; return std::move(i); }

int main()
{
 auto [s1, s2] = std::pair(2, 3);
 auto&& t1 = s1;
 static_assert(!std::is_reference<decltype(s1)>::value);
 static_assert(std::is_lvalue_reference<decltype(t1)>::value);

 int i1 = 4;
 auto i2 = i1;
 decltype(auto) i3 = i1;
 decltype(auto) i4{i1};
 decltype(auto) i5 = (i1);
 static_assert(!std::is_reference<decltype(i2)>::value);
 static_assert(!std::is_reference<decltype(i3)>::value);
 static_assert(!std::is_reference<decltype(i4)>::value);
 static_assert(std::is_lvalue_reference<decltype(i5)>::value);

 auto n1 = non_reference();
 decltype(auto) n2 = non_reference();
 auto&& n3 = non_reference();
 static_assert(!std::is_reference<decltype(n1)>::value, "");
 static_assert(!std::is_reference<decltype(n2)>::value, "");
 static_assert(std::is_rvalue_reference<decltype(n3)>::value, "");

 auto l1 = lvalue_reference();
 decltype(auto) l2 = lvalue_reference();
 auto&& l3 = lvalue_reference();
 static_assert(!std::is_reference<decltype(l1)>::value, "");
 static_assert(std::is_lvalue_reference<decltype(l2)>::value, "");
 static_assert(std::is_lvalue_reference<decltype(l3)>::value, "");

 auto c1 = const_lvalue_reference();
 decltype(auto) c2 = const_lvalue_reference();
 auto&& c3 = const_lvalue_reference();
 static_assert(!std::is_reference<decltype(c1)>::value, "");
 static_assert(std::is_lvalue_reference<decltype(c2)>::value, "");
 static_assert(std::is_lvalue_reference<decltype(c3)>::value, "");

 auto r1 = rvalue_reference();
 decltype(auto) r2 = rvalue_reference();
 auto&& r3 = rvalue_reference();
 static_assert(!std::is_reference<decltype(r1)>::value, "");
 static_assert(std::is_rvalue_reference<decltype(r2)>::value, "");
 static_assert(std::is_rvalue_reference<decltype(r3)>::value, "");
}

用auto定义的变量都是int类型,无论函数的返回类型的引用和const修饰;用decltype(auto)定义的变量的类型与函数返回类型相同;auto&&是转发引用,n3类型为int&&,其余与decltype(auto)相同。

C++17表达式类别

众所周知,编译器常会执行NRVO(named return value optimization),减少一次对函数返回值的移动或拷贝。不过,这属于C++标准说编译器可以做的行为,却没有保证编译器会这么做,因此客户不能对此作出假设,从而需要提供一个拷贝或移动构造函数,尽管它们可能不会被调用。然而,并不是所有情况下都能提供移动构造函数,即使能移动构造函数也未必只是一个指针的交换。总之,我们明知移动构造函数不会被调用却还要硬着头皮提供一个,这样做非常形式主义。

所以,C++17规定了拷贝省略,确保在以下情况下,即使拷贝或移动构造函数有可观察的效果,它们也不会被调用,原本要拷贝或移动的对象直接在目标位置构造:

  • 在return表达式中,运算数是忽略cv修饰符以后的返回类型的prvalue;
  • 在初始化中,初始化器是与变量相同类型的prvalue。

值得一提的是,这类行为在C++17中不能算是一种优化,因为不存在用来拷贝或移动的临时对象。事实上,C++17重新定义了表达式类别:

  • glvalue的求值能确定对象、位域、函数的身份;
  • prvalue的求值初始化对象或位域,或计算运算数的值,由上下文决定;
  • xvalue是表示一个对象或位域的资源能被重用的glvalue;
  • lvalue是不是xvalue的glvalue;
  • rvalue是prvalue或xvalue。

这个定义在功能上与C++11中的相同,但是更清晰地指出了glvalue和prvalue的区别——glvalue产生地址,prvalue执行初始化。

prvalue初始化的对象由上下文决定:在拷贝省略的情形下,prvalue不曾有关联的对象;其他情形下,prvalue将产生一个临时对象,这个过程称为临时实体化(temporary materialization)。

临时实体化把一个完全类型的prvalue转换成xvalue,在以下情形中发生:

  • 把引用绑定到prvalue上;
  • 类prvalue被获取成员;
  • 数组prvalue被转换为指针或下标取元素;
  • prvalue出现在大括号初始化列表中,用于初始化一个std::initializer_list<T>;
  • 被使用typeid或sizeof运算符;
  • 在语句expr;中或被转换成void,即该表达式的值被丢弃。

或者可以理解为,所有非拷贝省略的场合中的prvalue都会被临时实体化。

class NonMoveable
{
public:
 int i = 1;
 NonMoveable(int i) : i(i) { }
 NonMoveable(NonMoveable&&) = delete;
};

NonMoveable make(int i)
{
 return NonMoveable{i};
}

void take(NonMoveable nm)
{
 return static_cast<void>(nm);
}

int main()
{
 auto nm = make(2);
 auto nm2 = NonMoveable{make(3)};
 // take(nm);
 take(make(4));
 take(NonMoveable{make(5)});
}

NonMoveable的移动构造函数被声明为delete,于是拷贝构造函数也被隐式delete。在auto nm = make(2);中,NonMoveable{i}为prvalue,根据拷贝省略的第一条规则,它直接构造为返回值;返回值是NonMoveable的prvalue,与nm类型相同,根据第二条规则,这个prvalue直接在nm的位置上构造;两部分结合,该声明式相当于NonMoveable nm{2};。

在MSVC中,这段代码不能通过编译,这是编译器未能严格遵守C++标准的缘故。然而,如果在NonMoveable的移动构造函数中添加输出语句,程序运行起来也没有任何输出,即使在Debug模式下、即使用C++11标准编译都如此。这也侧面反映出拷贝省略的意义。

总结

C++11规定每个表达式都属于lvalue、xvalue和prvalue三个类别之一,表达式另可分为lvalue和rvalue,或glvalue和prvalue。返回右值引用的函数调用是xvalue,右值引用类型的变量是lvalue。

const左值引用和右值引用可以绑定临时对象,但是临时对象的声明周期只能延长一次,返回一个指向局部变量的右值引用也会导致空悬引用。

标识符加上一对圆括号成为表达式,decltype用于表达式可以根据其类别产生相应的类型,用decltype(auto)声明变量可以保留表达式类别。

C++17中prvalue是否有关联对象由上下文决定,拷贝省略规定了特定情况下对象不经拷贝或移动直接构造,NRVO成为强制性标准,使不能被移动的对象在语义上可以值传递。

参考

Value categories - cppreference.com

Value categories - [l, gl, x, r, pr]values

Value Categories in C++17

到此这篇关于C++98/11/17表达式类别的文章就介绍到这了,更多相关C++98/11/17表达式类别内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • c++使用正则表达式提取关键字的方法

    下面看下c++通过正则表达式提取关键字,代码如下所示: string text = "岳云鹏的对象叫铁锤"; regex pattern("(.*)的对象叫(.*)"); smatch results; if (regex_match(text, results, pattern)) { for (auto it = results.begin(); it != results.end(); ++it) cout << *it << endl

  • C++表达式new与delete知识详解

    在C++中,new表达式用于动态创建对象,即在堆(自由存储区)空间上为对象分配内存,而程序员也要小心的使用这些申请来的内存空间,当不再使用时应该调用delete表达式来释放该存储空间并且将指针置零. 本文学习了如何动态创建对象,动态创建的对象与一般对象的区别,动态创建的对象的初始化以及释放动态分配的内存等知识点. C++中分配的内存大致有三类:静态存储区,栈内存和堆内存 其中,静态存储区是在程序编译阶段就已经分配好的,用于全局变量,static变量等:堆栈是比较常用的对象存储方式. new和de

  • 浅谈C/C++ 语言中的表达式求值

    经常可以在一些讨论组里看到下面的提问:"谁知道下面C语句给n赋什么值?" m = 1; n = m+++m++; 最近有位不相识的朋友发email给我,问为什么在某个C++系统里,下面表达式打印出两个4,而不是4和5: a = 4; cout << a++ << a; C++ 不是规定 << 操作左结合吗?是C++ 书上写错了,还是这个系统的实现有问题? 注:运行a = 4; cout << a++ << a; 如在Visua

  • C++利用链栈实现表达式求值

    本文实例为大家分享了C++利用链栈实现表达式求值的具体代码,供大家参考,具体内容如下 #include<iostream.h> typedef int Status; typedef char Cstack; #define OK 1 #define ERROR 0 typedef struct StackNode { Cstack data; struct StackNode *next; }StackNode,*LinkStack; Status InitStack(LinkStack &

  • C++中的Lambda表达式详解

    我是搞C++的 一直都在提醒自己,我是搞C++的:但是当C++11出来这么长时间了,我却没有跟着队伍走,发现很对不起自己的身份,也还好,发现自己也有段时间没有写C++代码了.今天看到了C++中的Lambda表达式,虽然用过C#的,但是C++的,一直没有用,也不知道怎么用,就可怜的连Lambda语法都看不懂.好了,这里就对C++中的Lambda进行一个简单的总结,就算是对自己的一个交代,我是搞C++的,我是一个C++ programmer. 一段简单的Code 我也不是文艺的人,对于Lambda的

  • 实例讲解C++编程中lambda表达式的使用

    函数对象与Lambdas 你编写代码时,尤其是使用 STL 算法时,可能会使用函数指针和函数对象来解决问题和执行计算.函数指针和函数对象各有利弊.例如,函数指针具有最低的语法开销,但不保持范围内的状态,函数对象可保持状态,但需要类定义的语法开销. lambda 结合了函数指针和函数对象的优点并避免其缺点.lambda 与函数对象相似的是灵活并且可以保持状态,但不同的是其简洁的语法不需要显式类定义. 使用lambda,相比等效的函数对象代码,您可以写出不太复杂并且不容易出错的代码. 下面的示例比较

  • C++ 中lambda表达式的编译器实现原理

    什么是Lambda? C++ 11加入了一个非常重要的特性--Lambda表达式.营里(戴维营)的兄弟都对Objective-C很熟悉,许多人多block情有独钟,将各种回调函数.代理通通都用它来实现.甚至有人选择用FBKVOController.BlocksKit等开源框架将KVO.控件事件处理都改为通过block解决.原因就是简单.方便.直观,函数的定义和使用出现在同一个地方.这里的Lambda表达式实际上和block非常类似,当然如果你用它和Swift语言的闭包比较,那就是一回事了. 现在

  • C++ 中的Lambda表达式写法

    小喵的唠叨话: 寒假之后,小喵在家里无所事事,最近用C++写代码的时候,用到了std::sort这个函数,每次用这个函数,小喵似乎都得查一下lambda表达式的写法.正好最近很闲,不如总结一下. 在Bing上搜索 C++ lambda ,第一条记录就是MSDN上的C++ lambda的介绍.本文也是基于这篇文章来写的. 那么接下来,我们分几个部分来介绍. 一.什么是Lambda表达式 MSDN上对lambda表达式的解释: 在 C++ 11 中,lambda 表达式(通常称为 "lambda&q

  • C++98/11/17表达式类别(小结)

    目标 以下代码能否编译通过,能否按照期望运行? #include <utility> #include <type_traits> namespace cpp98 { struct A { }; A func() { return A(); } int main() { int i = 1; i = 2; // 3 = 4; const int j = 5; // j = 6; i = j; func() = A(); return 0; } } namespace cpp11 {

  • C++11 关键字 const 使用小结

    Const 的作用及历史 const (computer programming) - Wikipedia 一.历史 按理来说,要想了解一件事物提出的原因,最好的办法就是去寻找当时的历史背景,以及围绕这件事所发生的故事. 可是非常抱歉,我并没没有找到C语言中const 提出的背景,但是一个可以参考的历史是,常量这种数据形式早在汇编语言中就有所体现,汇编语言中的constant 是一个确定的数值,在汇编阶段就可以确定直接编码在于指令代码中,不是保存在寄存器中的可以变化的量. 常量是需求,C 语言没

  • 深入解析C++11 lambda表达式/包装器/线程库

    目录 零.前言 一.lambda表达式 1.lambda的引入 2.lambda表达式语法 3.捕获列表说明 4.函数对象与lambda表达式 二.包装器 1.function包装器 2.bind 概念: 三.线程库 1.线程的概念及使用 2.线程函数参数 3.原子性操作库(atomic) 4.lock_guard与unique_lock 1.mutex的种类 2.lock_guard 3.unique_lock 5.两个线程交替打印奇数偶数 零.前言 本章是讲解学习C++11语法新特性的第三篇

  • 一文读懂c++11 Lambda表达式

    1.简介 1.1定义 C++11新增了很多特性,Lambda表达式(Lambda expression)就是其中之一,很多语言都提供了 Lambda 表达式,如 Python,Java ,C#等.本质上, Lambda 表达式是一个可调用的代码单元[1]^{[1]}[1].实际上是一个闭包(closure),类似于一个匿名函数,拥有捕获所在作用域中变量的能力,能够将函数做为对象一样使用,通常用来实现回调函数.代理等功能.Lambda表达式是函数式编程的基础,C++11引入了Lambda则弥补了C

  • Rust实现一个表达式Parser小结

    目录 正文 lexer parser traversal 说在最后 正文 在 src/lib.rs 补上一个函数和一个 smoke test, 如下 pub use traversal::{eval, format}; pub fn build_ast(expr: &str) -> Result<Node, String> { let root = syntax(lex(expr)?)?; Ok(root) } #[cfg(test)] mod tests { use super

  • PHP程序员最常犯的11个MySQL错误小结

    对于很多新手们来说,使用PHP可以在短短几个小时之内轻松地写出具有特定功能的代码.但是,构建一个稳定可靠的数据库却需要花上一些时日和相关技能.下面列举了我曾经犯过的最严重的11个MySQL相关的错误(有些同样也反映在其他语言/数据库的使用上)... 1.使用MyISAM而不是InnoDB MySQL有很多数据库引擎,但是你最可能碰到的就是MyISAM和InnoDB. MySQL默认使用的是MyISAM.但是,很多情况下这都是一个很糟糕的选择,除非你在创建一个非常简单抑或实验性的数据库.外键约束或

  • 你可能从未使用过的11+个JavaScript特性(小结)

    重要 这篇文章中描述的大多数功能已被暂停使用(甚至不推荐使用). 它们仍然在许多图书中很常见,因此值得学习. 一.逗号运算符 , 是用于分隔表达式并返回链中最后一个表达式的运算符. let oo = (1, 2, 3) console.log(oo) // 3 这里有三个主要表达式 1 . 2 和 3.所有这些表达式均被求值,最后一个赋给 oo. 我们在 for 循环中看到这个: for(let i = 0, ii = 1; i< 10; i++, ii--) { ... } 当我们要编写短的

  • C++11 lambda表达式在回调函数中的使用方式

    目录 一.lambda表达式在C++异步框架中的应用 二.如何在C-style注册回调函数中使用lambda表达式? 在回调函数中使用lambda表达式的好处,在于可以利用C++的RAII机制来做资源的自动申请释放,避免手动管理出错. 一.lambda表达式在C++异步框架中的应用 1. 一个boost asio的例子 // // async_tcp_echo_server.cpp // ~~~~~~~~~~~~~~~~~~~~~~~~~ // // Copyright (c) 2003-202

  • C++11中的可变参数模板/lambda表达式

    目录 1.可变参数模板 递归函数方式展开参数包 逗号表达式展开参数包 2.lambda表达式 先来看看lambda表达式的例子: lambda表达式语法 1.可变参数模板 C++11的新特性可变参数模板能够让我们创建可以接受可变参数的函数模板和类模板,相比C++98和C++03,类模板和函数模板中只能含固定数量的模板参数,可变参数模板无疑是一个巨大的改进.可是可变参数模板比较抽象,因此这里只会写出够我们使用的部分. 下面是一个基本可变参数的函数模板 // Args是一个模板参数包,args是一个

  • C++11 lambda(匿名函数)表达式详细介绍

    目录 前言 概念及基本用法 捕获变量 lambda表达式类型 声明式的编程风格 总结 前言 Lambda(匿名函数)表达式是C++11最重要的特性之一,lambda来源于函数式编程的概念,也是现代编程语言的一个特点. 优点如下: 声明式编程风格:就地匿名定义目标函数或函数对象,有更好的可读性和可维护性. 简洁:不需要额外写一个命名函数或函数对象,,避免了代码膨胀和功能分散. 更加灵活:在需要的时间和地点实现功能闭包. 概念及基本用法 lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的

随机推荐