[C++]C++11零碎特性总结

右值引用

C++11中,右值分为纯右值和将亡值。

  • 临时变量,常量、一些运算表达式(1+3)等叫纯右值
  • 声明周期将要结束的对象叫将亡值

右值引用只能指向右值,使用&&声明,定义时必须初始化
左值引用可以引用左值,也可以引用右值

// 纯右值
int&& a = 3 + 1;
int&& b = 3.14 + 3;
cout << a << endl; // 4
cout << b << endl; // 6
// 演示将亡值
String GetString(char* pStr) {
	String strTemp(pStr);
	return strTemp;
}

void test() {
	String s1(GetString("hehe"));
}

编译器用调用GetString,创造一个匿名对象strTemp,返回的临时对像(可能会优化成直接返回)
若String类中未写右值引用版本拷贝构造,则默认使用左值引用版本重新拷贝一份资源给s1,然后释放临时对象。这样就造成不必要的拷贝。
在string类中加入以下函数即可优化上述步骤:

String(String&& s)
    : _str(s._str)
    , _size(s._size)
    , _capacity(s._capacity) {
    s._str = nullptr;
    s._size = 0;
    s._capacity = 0;
}
默认移动构造/赋值

编译器隐式生成一个(如果没有用到则不会生成)移动构造函数,如果声明了移动构造或拷贝构造或移动赋值,则编译器不会生成默认版本。
编译器生成的默认移动构造函数实际和默认的拷贝构造函数类似,都是浅拷贝。

同理,编译器默认生成移动赋值,如果声明了移动赋值或赋值运算符重载或移动构造,则编译器不会生成默认版本。

注意:在C++11中,拷贝构造/移动构造/赋值/移动赋值函数必须提供,或者同时不提供,程序才能保证类同时具有拷贝和移动语义。

move()

将一个左值强制转化为右值引用,搭配移动构造使用,可以达到类似对象数据转移的效果。

完美转发

函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相应实参是右值,它就应该被转发为右值。
C++11通过forward函数实现:

void Fun(int& x) { cout << "lvalue ref" << endl; }
void Fun(int&& x) { cout << "rvalue ref" << endl; }
void Fun(const int& x) { cout << "const lvalue ref" << endl; }
void Fun(const int&& x) { cout << "const rvalue ref" << endl; }

template<typename T>
void PerfectForward(T&& t) { Fun(std::forward<T>(t)); }

int main() {
    PerfectForward(10); // rvalue ref
    int a;
    PerfectForward(a); // lvalue ref
    PerfectForward(std::move(a)); // rvalue ref
    const int b = 1;
    PerfectForward(b); // const lvalue ref
    PerfectForward(std::move(b)); // const rvalue ref
    return 0;
}

auto

C++11中,auto全新的含义:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

  • 使用auto时必须进行初始化,编译阶段需根据初始化表达式来推导auto的实际类型
  • auto是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型
  • 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
  • auto同一行声明多个变量时,变量必须类型相同,否则会报错,编译器只对第一个进行推导,然后用推导出来的类型定义其他变量
  • auto不能作为函数的参数,不能直接用来声明数组
  • 不能定义类的非静态成员变量
  • 实例化模板时不能使用auto作为模板参数

decltype

int Test(size_t size) {
    cout << "Test()" << endl;
    return 0;
}
int main() {
    // 推导函数类型 RTTI
    cout << typeid(decltype(Test)).name() << endl;

    // 推导的是函数返回值的类型,只推演,不会执行函数 RTTI
    decltype(Test(0)) a = 3;
    return 0;
}

RTTI缺点:会降低运行的效率

基于范围的for循环

  • for循环迭代的范围必须是确定的,对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end方法,begin和end就是for循环迭代的范围
void Test(int arr[]) {
	for(auto& e : arr) // arr为指针,编译器并不知道数组的范围所以报错
		cout << e << endl;
}
  • 迭代的对象要实现++和==的操作

nullptr

NULL实际是一个宏,在传统的C头文件(stddef.h) NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。

C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下
将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0

  • C++11提供了nullptr代表一个指针空值常量。其类型为nullptr_t,仅仅可以被隐式转化为指针类型,nullptr_t被定义在头文件中:typedef decltype(nullptr) nullptr_t;
  • 使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的
  • 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同

lambda表达式

如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法
#include <algorithm>
但在想要自定义比较规则时必须提供仿函数对象
C++11 中lambda表达式可以简化这个过程:

class AAA {
public:
    AAA(int a, int b) {
        _a = a;
        _b = b;
    }
    int _a;
    int _b;
};
class Greater {
public:
    bool operator()(const AAA& a, const AAA& b) {
        return a._a > b._a;
    }
};
int main() {
    vector<AAA> v{ {1, 3}, {6, 7} , {4, 1} };
    // 必须提供仿函数的比较方法
    
    sort(v.begin(), v.end(), Greater());

    sort(v.begin(), v.end(),
         [](AAA& a, AAA& b)->bool {
        return a._a > b._a;
    });

    for (auto& e : v)
        cout << e._a << " ";
    return 0;
}

lambda表达式是一个匿名函数,可借助auto将其赋值给一个变量,然后调用。
表达式格式
[捕捉列表] (参数列表) mutable -> 返回值类型 { 函数体 }
[]捕捉列表:
获取父作用域的变量,供函数使用,编译器根据[]来判断接下来
的代码是否为lambda函数。
[a, b]: 以值的形式获取父作用域的变量a,b
[=]: 以值的形式获取父作用域的所有变量, 变量的属性都为const(包括this)
[=, a]: 错误写法,捕捉a重复
[=, &a]: 正确写法,除了a,其他以值的形式获取父作用域变量, a以引用的形式获取
[&] : 以引用的形式获取父作用域的所有变量,(包括this)
[this]: 获取成员变量的this指针,只能在成员函数内部使用
()参数列表:
如果不需要参数传递,则可以连同()一起省略
mutable:
可选,删除捕捉列表中以值的形式获取的变量的const属性,使用该修饰符时,参数列表不可省略(即使参数为空)。
->返回值类型:
用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导
{}函数体:
在函数体内,可以使用参数外,以及所有捕获到的变量。
使用注意事项:

  • 在块作用域以外的lambda函数捕捉列表必须为空。
  • 在块作用域中的lambda函数仅能捕捉父作用域中局部变量
  • lambda表达式之间不能相互赋值
  • 可以将lambda表达式赋值给相同类型的函数指针

lambda底层:
在这里插入图片描述
通过汇编我们可以清楚的看到,编译器会自动生成一个类,在其中重载了operator()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值