第1章 全新的C++语言
1.2左值与右值
左值:一个可以用来存储数据的变量,有实际的内存地址,表达式结束后依然存在,可以用取地址符号&获取地址。
右值:匿名的临时变量,在表达式结束时,生命周期终止,不能村存放数据,不能用取地址符号&获取地址。
[root@192 C++11]# cat lrdemo.cpp
#include <iostream>
using namespace std;
int main()
{
int x = 0;
cout<<"x:"<<x<<endl;
int *p = &++x;
cout<<"x:"<<x<<endl;
cout<<"&x:"<<&x<<endl;
cout<<"p:"<<p<<endl;
++x = 10;
cout<<"x:"<<x<<endl;
//p = &x++;//后置++操作返回一个临时对象,不能取地址或赋值,是右值,编译错误,错误信息为error: lvalue required as unary ‘&’ operand
return 0;
}
[root@192 C++11]# g++ lrdemo.cpp -o lrdemo -std=c++11
[root@192 C++11]# ./lrdemo
x:0
x:1
&x:0x7ffc14f61664
p:0x7ffc14f61664
x:10
1.2.2右值引用
C++11/14使用“T&&“的形式表示右值引用,而原来的"T&"表示左值引用。对一个对象使用右值引用,意味着显式地标记这个对象为右值,可以被转移来优化。同时也相当于为它添加了一个临时的名字,生命周期得到延长,不会在表达式结束时消失,而是与右值引用绑定在一起。
[root@192 C++11]# cat rrefdemo.cpp
#include <iostream>
using namespace std;
int main()
{
int x = 0;
cout<<"x:"<<x<<endl;
int& r1 = ++x;//左值引用,先加1,后引用
cout<<"x:"<<x<<endl;
cout<<"r1:"<<r1<<endl;
int&& r2 = x++;//右值引用,引用了自增后的临时对象
cout<<"x:"<<x<<endl;
cout<<"r2:"<<r2<<endl;
const int& r3 = x++;//常量左值引用
cout<<"x:"<<x<<endl;
cout<<"r3:"<<r3<<endl;
const int&& r4 = x++;//常量右值引用,没有实际意义
cout<<"x:"<<x<<endl;
cout<<"r4:"<<r4<<endl;
return 0;
}
[root@192 C++11]# g++ rrefdemo.cpp -o rrefdemo -std=c++11
[root@192 C++11]# ./rrefdemo
x:0
x:1
r1:1
x:2
r2:1
x:3
r3:2
x:4
r4:3
在泛型编程中需要操作未知类型,C++11/14对此做出了新的规定——引用折叠:对引用类型TR再进行左引用操作是T&,右引用操作则不变,因此,函数参数如果使用T&&的形式将会总保持原类型不变。
1.2.3转移语义
C++11/14标准为class新增加了参数类型为T&&的转移构造函数和转移赋值函数,只要类实现了这两个特殊函数就能够利用右值对象零成本构造,这就是转移语义。只要对象被move()标记为右值引用,那么就可以毫无损失地转移资源,再也不需要担心深拷贝的成本。
[root@192 C++11]# cat movedemo.cpp
#include <iostream>
using namespace std;
class moveable //实现转移构造函数和转移赋值函数的类
{
private:
int x;
public:
moveable(){}
moveable(moveable&& other)//转移构造函数
{ swap(x,other.x);}
moveable& operator=(moveable&& other)//转移赋值函数
{
swap(x,other.x);
return *this;
}
static moveable create()
{
moveable obj;//栈上创建对象
return obj;//返回临时对象,即右值,会引发转移语义
}
};
int main()
{
moveable m1;//缺省构造函数创建对象
moveable m2(move(m1));//调用转移构造函数,m1被转移
moveable m3 = moveable::create();//调用转移赋值函数
return 0;
}
[root@192 C++11]# g++ movedemo.cpp -o movedemo -std=c++11
[root@192 C++11]# ./movedemo
1.2.4完美转发
标准头文件<utility>里还有一个函数forward(),用于在泛型编程时实现完美转发,可以把函数的参数原封不动地转发给其他函数。
[root@192 C++11]# cat forwarddemo.cpp
#include <iostream>
using namespace std;
void check(int&)
{
cout<<"lvalue"<<endl;
}
void check(int&&)
{
cout<<"rvalue"<<endl;
}
template<typename T>
void print(T&& v)
{
check(forward<T>(v));
}
int main()
{
int x = 10;
print(x);
print(move(x));
return 0;
}
[root@192 C++11]# g++ forwarddemo.cpp -o forwarddemo -std=c++11
[root@192 C++11]# ./forwarddemo
lvalue
rvalue
使用完美转发时函数的参数必须声明为T&&,只有这样类型才能保持原状。
1.3自动类型推导
1.3.1 auto
auto可用在赋值表达式中声明变量,并在编译期自动推导出表达式类型。
auto使用中需要注意的地方:
(1)auto只能用于赋值语句里的类型推导,不能直接声明变量(因为无表达式供推导)。
(2)auto总是推断出值类型,非引用。
(3)auto允许使用const/volatile/&/*等修饰,从而得到新的类型。
(4)auto&&总是推断出引用类型。
在C++14中auto还可用来自动推导函数返回值类型。比如
auto func(int x){ return x*x;}
[root@192 C++11]# cat autodemo.cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
int x = 0;
const long y = 100;
volatile string s("one punch");
auto a1 = ++x;//值类型int
auto& a2 = x;//引用类型int&
auto a3 = y*y;//值类型long
auto& a4 = y;//引用类型const long&
auto a5 = move(y);//值类型long
auto&& a6 = move(y);//引用类型const long&
const auto a7 = x+x;//常量值类型const int
auto* a8 = &y;//const long*,auto本身推导为值类型
auto&& a9 = s;//引用类型volatile string&
//auto a10;//不是赋值初始化,无法推导,编译报错,错误信息为error: declaration of ‘auto a10’ has no initializer
return 0;
}
[root@192 C++11]# g++ autodemo.cpp -o autodemo -std=c++11
1.3.2 decltype
decltype的形式很像是函数调用:
decltype(expression)
decltype可以像auto一样用在赋值语句,也可以根据表达式的结果类别和表达式的性质推断出引用或非引用类型,能够更精确地控制类型。
decltype使用中需要注意的细节:
(1)decltype(e)的形式获得表达式计算结果的值类型
(2)decltype((e))的形式获得表达式计算结果的引用类型,类似于auto&&。
[root@localhost C++11]# cat decltypedemo.cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
decltype(0.0f) func(decltype(0L) x)//用于函数返回值和参数声明
{
return x*x;
}
template<typename T> class demo {};
int main()
{
int x = 0;
const long y = 100;
decltype(x) d1 = x;//类型是int
decltype(x)& d2 = x;//类型是int&
decltype(&x) d3 = &x;//类型是int*
decltype(x+y) d4 = x+y;//类型是long
decltype(y)& d5 = y;//类型是const long&
decltype(less<int>()) functor;//声明一个函数对象
typedef decltype(func)* func_ptr;//简单地定义函数指针类型
vector<int> v;
decltype(v)::iterator iter;//计算v的类型,再取其迭代器类型
demo<decltype(v)> obj;//在模板参数列表里使用decltype
return 0;
}
[root@localhost C++11]# g++ decltypedemo.cpp -o decltypedemo -std=c++11
下面例子演示decltype(e)和decltype((e))的区别
[root@localhost C++11]# cat decltypedemo1.cpp
#include <iostream>
using namespace std;
int main()
{
struct demo {int x = 0;};
volatile auto *p = new demo;
decltype(p->x) d5 = 42;//推导为值类型int
cout<<"d5:"<<d5<<endl;
cout<<"p->x:"<<p->x<<endl;
decltype((p->x)) d6 = p->x;//推导为引用类型volatile int&
cout<<"d6:"<<d6<<endl;
cout<<"p->x:"<<p->x<<endl;
//decltype(p->x)& d7 = p->x;//错误,int&类型不匹配,错误信息为error: binding reference of type ‘int&’ to ‘volatile int’ discards qualifiers
//cout<<"d7:"<<d6<<endl;
return 0;
}
[root@localhost C++11]# g++ decltypedemo1.cpp -o decltypedemo1 -std=c++11
[root@localhost C++11]# ./decltypedemo1
d5:42
p->x:0
d6:0
p->x:0