access令两列运算得到新属性_C++11新特性 1

(本文是Nicolai M.Josuttis所著的The C++ Standard Library - A Tutorial and Reference的个人读书笔记)

2.1.2 C++98 和 C++11的兼容性

基本C++11编译器是兼容C++98或者C++03风格的代码的,但是由于引入了新关键字,自然旧风格的代码中不应该随意使用新的关键字当作变量名

如果需要将不同的代码按照不同C++版本进行编译,可以采用宏定义的方式来指定C++版本

#define __cplusplus 201103L
#define __cplusplus 199711L

(具体数值请查阅编译器手册)

兼容性只对源代码文件生效,与二进制文件无关。这将会导致一些问题:比如令现存的函数得到一个新的返回类型,因为不允许通过返回类型来区分重载函数。通常来说,使用C++11编译器来编译C++98程序是没有问题的,但是使用C++11编译器来链接C++98二进制文件是有可能出错的。

3.1.1 书写格式调整

模板中的空格:不再需要在”>“之间增加空格

vector<list<int> >; // 所有C++版
vector<list<int>>;//自C++11以后

nullptr和std::nullptr_t:使用nullptr来表示一个空指针(这个指针不指向任何值),这种做法可以避免空指针和int类型的0之间的二义性

nullptr是一个新的关键字,它会自动转换成对应的指针类型,但是不会转换成整型。它的类型是std::nullptrt,因此你甚至可以重载传递空指针的函数。std::nullptr_t被视为是一种基本类型

3.1.2 auto自动类型推导

在C++11中可以使用auto关键字来声明对象。使用auto关键字以后,将会根据初始化的类型来推导类型,所以使用auto时必须初始化。

当对象是一个非常复杂的类型的时候,auto关键字非常有用

vector<string> v;
auto pos = v.begin(); //vector<string>::iterator
auto l = [] (int x)->bool{...,}; //lambda类型,参数为int,返回bool

3.1.3 通用初始化和初始化列表

C++11引入了通用初始化(列表初始化)的概念来统一繁杂的初始化方法。使用{}可以进行通用初始化,并且适用于所有初始化。

int values[]{1,2,3};
std::vector<int> v{2,3,5,7,11,13,17};
std::vector<std::string> cities {
    "Berlin","New York","London","Braunschweig", "Cairo","Cologne"
};
std::complex<double> c{4.0,3.0};

初始化列表会进行值初始化,就是基本类型的局部变量会被初始化为0

int i; //undefined
int j{};//0
int*p;//undefined
int*q{};//nullptr

使用初始化列表时,将不会执行类型转化。

为了支持用户定义类型使用初始化列表,C++11提供了类模板std::initializer_list<>,它可以支持初始化多个数值,或者是在其它地方使用多个数值

void print(std::initializer_list<int> vals)
{
    for (auto p = vals.begin(); p!=val.end(); ++p){
    std::cout << *p << "n";
    } 
}
    print({12,3,5,7,11,13,17});

使用初始化列表类型来作为构造函数的参数

class P
{
public:
    P(int,int);
    P(std::initializer_list<int>);
};
P p(77,5); // 调用 P::P(int,int)
P q{77,5}; // 调用 P::P(initializer_list)
P r{77,5,42}; // 调用 P::P(initializer_list)
P s = {77,5}; // 调用 P::P(initializer_list)

由于初始化列表的出现,可以使用explicit参数来避免传入多个参数时自动类型转换。

class P
{
public:
    P(int a, int b) {
    ...
    }
    explicit P(int a, int b, int c) {
    ...
    }
};
P x(77,5); // OK
P y{77,5}; // OK
P z {77,5,42}; // OK
P v = {77,5}; // OK  允许隐式转换
P w = {77,5,42}; // ERROR 因为禁止隐式转换

3.1.4 for遍历指定区域

C++11引入了新类型的for,用来遍历给定的范围

for (decl: coll) {
    statement
}

coll是集合,decl是coll中每个元素的声明

当遍历容器时,应当将decl中的元素声明为引用类型。否则,statement将会作用于一个容器元素的局部拷贝之上。为了避免调用拷贝构造函数和析构函数,声明元素应当使用常引用类型。

以下三种形式是等价的

for ( decl : coll ) {
    statement
}

for (auto _pos=coll.begin(), _end=coll.end(); _pos!=_end; ++_pos ) {
   decl = *_pos;
statement
}

for (auto _pos=begin(coll), _end=end(coll); _pos!=_end; ++_pos ) {
    decl = *_pos;
    statement
}

3.1.5 移动语义和右值引用

移动语义可以有效地避免不必要的复制和临时变量,以下是一个简单的介绍。

样例代码

void createAndInsert (std::set<X>& coll)
{
    X x; // 创建一个X类型的对象
    ...
    coll.insert(x); // 将这个对象作为参数传入insert函数中
} 

当往集合中插入一个元素时,集合提供了创建实参的内部拷贝的成员函数

namespace std {
template <typename T, ...> class set {
public:
    ... insert (const T& v); // 常引用会导致拷贝
    ...
    };
}

这种拷贝行为是有效的,因为集合提供了值语义。此外,集合能够插入临时对象、被使用的对象和插入后可修改的对象。

X x;
coll.insert(x); // 传入x的拷贝
...
coll.insert(x+x); // 传入一个临时的左值(x+x的结果)的拷贝
...
coll.insert(x); // 传入x的拷贝即使x之后不会被使用

但是,对于后两个插入而言,由于x+x和x之后不会在被使用,所以更好的方法是移动临时量的内容作为新的元素,尤其是拷贝操作非常耗时的时候。这种移动临时量的操作就是移动语义。C++11以后,程序员可以使用move来移动将来不会被使用的临时量。

X x;
coll.insert(x); // 传入x的拷贝
...
coll.insert(x+x); // 移动临时量
...
coll.insert(std::move(x)); // 移动(或者是拷贝)x

<utility>的std::move()函数的实现并非使用任何形式的移动,而仅仅是将参数转换为一个右值引用。右值引用的声明需要使用2个&符号。右值引用代表可以修改的右值(匿名临时变量,只能出现赋值语句的右边),右值不再被需要,因此它的内容和资源可以被”窃取“。

现在,集合可以提供一种insert的重载形式,它可以处理这些右值引用

namespace std {
template <typename T, ...> class set {
public:
    ... insert (const T& x); // 左值调用
    ... insert (T&& x); // 右值调用
    ...
};
}

右值引用将会”窃取“x的资源,这种行为需要知道x的类型,因为只有x的类型才能访问它的内部成员。在x类型中应当提供移动构造函数,来简化拷贝操作

class X {
public:
    X (const X& lvalue); // copy constructor
    X (X&& rvalue); // move constructor
    ...
};

除了”窃取者“以外,被移动的对象不应该被其它角色修改。通常需要将被传递的变量清空

类似地,非简单类也应该提供移动赋值运算符重载

class X {
public:
    X& operator= (const X& lvalue); //拷贝构造函数
    X& operator= (X&& rvalue); //移动构造函数
    ...
};

对于字符串和集合而言,这些操作可以通过简单地交换内部内容和资源来完成。但是,最好还是将*this的内容清空,因为对象也可能持有资源,比如锁。

最后,注意到移动语义两个最重要的特征:

(1)为左值引用和右值引用提供重载

(2)返回左值引用

左值引用和右值引用的重载规则

void foo(X&);

只能被左值调用

void foo(const X&);

左右值均可调用

void foo(X&);//同时写两种形式
void foo(X&&);//重载

左值和右值调用会调用不同的重载形式

void foo(const X&);
void foo(X&&);

和前面一样

void foo(X&&);//只定义了一种形式

foo()被左值调用时会触发编译错误,这种性质可以用来编写不希望被左值调用的函数

从上面可以看出,如果类没有提供移动语义,只拥有拷贝构造函数和拷贝赋值运算符重载,类将会调用左值引用。总体而言,std::move()意味着在定义了移动构造函数/移动赋值运算符重载的时候会调用移动语义,否则就调用拷贝语义。

返回值是不应该move的。根据语法规则,检查下面的代码:

X foo ()
{
    X x;
    ...
    return x;
}

如果X拥有可执行的拷贝构造函数和移动构造函数,编译器会忽略拷贝构造函数,只采用移动构造函数。这就是所谓的RVO(命名返回值优化)

如果X只拥有移动构造函数,x被移动

如果X只拥有拷贝构造函数,x被拷贝

如果X没有移动构造函数或拷贝构造函数,那么将不会通过编译

注意到,如果返回对象是一个局部非静态的对象,返回它的右值引用也是一个错误行为。

X&& foo ()
{
    X x;
    ...
    return x; // ERROR: 返回不存在的对象的引用
}

右值引用也是引用,当返回一个局部对象的时候,意味着返回一个对不存在对象的引用,无论是否使用std::move()都会有问题。

3.1.6 新的字符串字面值常量

在C++11以后,你可以定义原始字符串和宽字节字符串常量

原始字符串常量

原始字符串可以定义一个字符序列,序列的内容和打印值完全一致(避免转义)。只需要在字符串前面增加一个字符R,并使用()包括即可。

为了在原始字符串中支持")"符号,可以使用区域限定符。完整的格式是R"限定()限定",限定应该是最长16字节的字符序列(不包括破折号、空格和圆括号)

   R"nc(a
        bnc()"
        )nc";

等价于

"an    bnc()"n     "

原始字符串常量在定义规则表达式时非常有用。

编码字符串常量

使用编码前缀,你可以为字符串字面值定义特殊的字符编码方式。有以下几种情况:

u8定义了UTF-8编码

u定义了char16_t字符序列

U定义了char32_t字符序列

L定义了wchar_t宽字符序列

 L"hello"//定义了"hello" 为 wchar_t字符序列 

在原始字符串常量的R之前使用编码前缀也可以定义它的字符编码方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值