c++11新特性

Posted mr.chenyuelin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++11新特性相关的知识,希望对你有一定的参考价值。

NULL和nullptr

nullptr的出现以避免将空指针NULL转为整数0带来的错误.

auto和decltype

都是从表达式的类型推断出要定义的变量类型,但是decltype但是不想用该表达式的值初始化变量(初始化可以用auto)

花括号初始化

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

功能:
1.值初始化,未定义的基础数据类型的变量值设为0(或nullptr).

int i;		// i has undefined value
int j{};	// j is initialized by 0
int* p;		// P has undefined value
int* q{};	// q is initialized by nullptr

2.当自动类型转换可能使变量降低精度时报错.

int x1(5.3);		// OK, but OUCH: xl becomes 5
int x2 = 5.3;		// OK, but OUCH: x2 becomes 5
int x3{5.0};		// ERROR: narrowing
int x4 = {5.3};		// ERROR: narrowing
char c1{7};			// OK: even though 7 is an int, this is not narrowing
char c2{99999};		// ERROR: narrowing (if 9999 doesn 'fut into a char)
std::vector<int> v1{1, 2, 4, 5};		// OK
std::vector<int> v2{1, 2.3, 4, 5.6};	// ERROR: narrowing

底层依赖于模板类initializer_list,该类封装了一个array<T,n>.调用函数时该array内的元素可被编译器分解逐一传递给函数.若函数参数是initializer_list,则传入的数据不会被拆解.

void print(std::initializer_list<int> vals) {
    for (auto p = vals.begin(); p != vals.end(); ++p) { //a list of values
        std::cout << *p << endl;
    }
}

print({12, 3, 5, 7, 11, 13, 17}); 		// pass a list of values to print()

若函数同时有接收多个参数的重载版本和接收initializer list的重载版本,则优先调用接收initializer list的重载版本.

class P {
public:
    // 有两个重载版本的构造函数,uniform initialization时优先调用接收initializer list的重载版本
    P(int a, int b) {
        cout << "P(int, int), a=" << a << ", b=" << b << endl;
    }

    P(initializer_list<int> initlist) {
        cout << "P(initializer list<int>), values= ";
        for (auto i : initlist)
            cout << i << ' ';
        cout << endl;
    }
};

P p(77, 5);		// P(int, int), a=77, b=5
P q{77, 5};		// P(initializer list<int>), values= 77 5
P r{77, 5, 42}; // P(initializer list<int>), values= 77 5 42
P s = {77, 5};	// P(initializer list<int>), values= 77 5

STL中的大部分容器和算法相关函数均有接收initializer list的重载版本

=default、=delete

使用=default使得编译给类加上默认的构造函数、析构函数、拷贝构造函数、拷贝赋值函数、移动构造函数等.
=delete(或简写为=0)表示删除该函数,使得该类不具有对应的构造、析构、拷贝构造、拷贝赋值、析构等功能.

noexcept

修饰函数代表不会抛出异常

 MyString &operator=(MyString &&str) noexcept {
        ++MAsgn;
        if (this != &str) {
            if (_data) delete _data;
            _len = str._len;
            _data = str._data; //MOVE!
            str._len = 0;
            str._data = nullptr; // 将传入对象的_data指针设为nullptr,防止析构函数多次delete同一根指针
        }
        return *this;
    }

override

重写的话,父类的成员函数为virtual修饰,且子类函数与父类函数完全一致

使用关键字override好处:当子类漏写了虚函数重写的某个苛刻条件,也可以通过编译器的报错,快速改正错误

class Base {
public:
    virtual void mf1() const;
    virtual void mf2(int x);
    virtual void mf3() &;
    void mf4() const;    // is not declared virtual in Base
};
class Derived: public Base {
public:
    virtual void mf1() override;
    virtual void mf2(unsigned int x) override;
    virtual void mf3() && override;
    virtual void mf4() const override;
};

所以override加不加不影响子类重写父类

final

修饰类,该类不会被继承,修饰函数,该函数不会被重写

右值引用

用途:加快容器操作

右值是临时对象,如数值1,函数返回值等

注意:右值不能放等号左边,只能放等号右边

当右值出现在赋值运算符=的右侧时,我们认为对其资源进行偷取/搬移(move)而非拷贝(copy)是合理的,依次:

1.必须有语法让我们在调用端告诉编译器这是一个右值.

2.必须有语法让我们在被调用端写出一个专门处理右值的移动赋值函数.

专门处理右值的函数使用value_type&&声明参数:

iterator insert(const_iterator __position, const value_type& __x);
iterator insert(const_iterator __position, value_type&& __x);

我们是insert是先去调用一下中介的一个函数,可能出现改变变量的可变性和左值右值等性质,

使用std::forward()函数可以完美转交变量,不改变其可变性和左值右值等性质.

// 函数process的两个重载版本,分别处理参数是左值和右值的情况
void process(int &i) {
    cout << "process(int&):" << i << endl;
}
void process(int &&i) {
    cout << "process(int&&):" << i << endl;
}

// 中间转交函数forward使用std::forward()转交变量
void forward(int &&i) {
    cout << "forward(int&&):" << i << ", ";
    process(std::forward<int>(i));
}

forward(2);           	// forward(int&&):2, process(int&&):2	(临时变量作左值传给forward函数,forward函数体内使用std::forward函数包装变量,保留其作为右值的性质)
forward(std::move(a));  // forward(int&&):0, process(int&&):0	(临时变量作左值传给forward函数,forward函数体内使用std::forward函数包装变量,保留其作为右值的性质)

std::move()函数可将左值转为右值来传递

接下来就是我们的移动构造函数移动赋值函数

#include <cstring>

class MyString {
public:
    static size_t DCtor;    // 累计默认构造函数调用次数
    static size_t Ctor;     // 累计构造函数调用次数
    static size_t CCtor;    // 累计拷贝构造函数调用次数
    static size_t CAsgn;    // 累计拷贝赋值函数调用次数
    static size_t MCtor;    // 累计移动构造函数调用次数
    static size_t MAsgn;    // 累计移动赋值函数调用次数
    static size_t Dtor;     // 累计析构函数调用次数
private:
    char *_data;
    size_t _len;

    void _init_data(const char *s) {
        _data = new char[_len + 1];
        memcpy(_data, s, _len);
        _data[_len] = '\\0';
    }

public:
    // 默认构造函数
    MyString() : _data(nullptr), _len(0) { ++DCtor; }

	// 构造函数
    MyString(const char *p) : _len(strlen(p)) {
        ++Ctor;
        _init_data(p);
    }

    // 拷贝构造函数
    MyString(const MyString &str) : _len(str._len) {
        ++CCtor;
        _init_data(str._data);
    }

    // 拷贝赋值函数
    MyString &operator=(const MyString &str) {
        ++CAsgn;
        if (this != &str) {
            if (_data) delete _data;

            _len = str._len;
            _init_data(str._data); //COPY!
        }
        return *this;

    }

    // 移动构造函数
    MyString(MyString &&str) noexcept : _data(str._data), _len(str._len) {
        ++MCtor;
        str._len = 0;
        str._data = nullptr; 	// 将传入对象的_data指针设为nullptr,防止析构函数多次delete同一根指针
    }

	// 移动赋值函数
    MyString &operator=(MyString &&str) noexcept {
        ++MAsgn;
        if (this != &str) {
            if (_data) delete _data;
            _len = str._len;
            _data = str._data; //MOVE!
            str._len = 0;
            str._data = nullptr; // 将传入对象的_data指针设为nullptr,防止析构函数多次delete同一根指针
        }
        return *this;
    }

    //dtor
    virtual ~MyString() {
        ++Dtor;
        if (_data)
            delete _data;
    }
};

size_t MyString::DCtor = 0;
size_t MyString::Ctor = 0;
size_t MyString::CCtor = 0;
size_t MyString::CAsgn = 0;
size_t MyString::MCtor = 0;
size_t MyString::MAsgn = 0;
size_t MyString::Dtor = 0;

其实就是移动构造函数就是让当前指针指向临时指针的同一块空间,然后将临时指针置空,防止析构函数delete释放同一块内存空间,移动赋值函数就只是加了检测自我赋值的一个操作罢了

特别注意:移动构造函数和移动赋值函数参数列表之后必须加noexcept关键字,否则不会调用

容器测试:

1.在插入元素部分,只有vector容器的速度受元素是否movable影响大,这是因为只有容器vector在增长过程中会发生复制.

2.对于所有容器,其移动构造函数都远快于其拷贝构造函数,容器vector的移动复制函数仅仅发生了指针的交换,未发生元素的复制.

lambda表达式

写了一个函数但没有名字

[] {
    std::cout << "hello lambda" << std::endl;
};

// 用作变量
auto l = [] {
    std::cout << "hello lambda" << std::endl;
};
l();

// 直接执行
[] {
    std::cout << "hello lambda" << std::endl;
}();

int id = 0;
auto f = [id]() mutable {
    std::cout << "id:" << id << std::endl;
    ++id;
};
id = 42;
f();							// id:0
f();							// id:1
f();							// id:2
std::cout << id << std::endl;	// 42

lambda函数使用时相当于仿函数(functor)[…]中传入的对象相当于为仿函数的成员变量.

class Functor {
    private:
    int id; // copy of outside id
    public:
    void operator()() {
        std::cout << "id: " << id << std::endl;
        ++id; // OK
    }
};
Functor f;

一般用于当函数功能比较简单,比如修改sort函数排序规则及其它函数

// lambda函数充当predict谓词
vector<int> vi{5, 28, 50, 83, 70, 590, 245, 59, 24};
int x = 30;
int y = 100;
remove_if(vi.begin(), vi.end(),
          	[x, y](int n) { return x < n && n < y; });

以上是关于c++11新特性的主要内容,如果未能解决你的问题,请参考以下文章

C++11新特性总结

11年 C# 4.0四大新特性代码示例与解读

转C++11 标准新特性:Defaulted 和 Deleted 函数

C++11 现代C++风格的新元素--简介

c++11新特性:变长参数模板详解

C++11精要:部分语言特性