【C++】C++11新特性

您所在的位置:网站首页 Lambda表达式的本质 【C++】C++11新特性

【C++】C++11新特性

2023-03-21 05:27| 来源: 网络整理| 查看: 265

文章目录 一、类的改进1.1 默认生成1.2 移动构造函数1.3 移动赋值重载函数1.4 成员变量缺省值1.5 强制生成默认函数的关键字default1.6 禁止生成默认函数的关键字delete1.6.1 C++98防拷贝1.6.1 C++11防拷贝 二、lambda表达式2.1 对比2.2 lambda表达式语法2.3 捕捉列表2.4 函数对象与lambda表达式

一、类的改进 1.1 默认生成

C++ 的类有四类特殊成员函数, 它们分别是: 默认构造函数、 析构函数、 拷贝构造函数以及拷贝赋值运算符。 C++11 新增了两个:移动构造函数和移动赋值运算符重载 这两个成员函数在上一面介绍过:【C++】C++11新特性——右值引用

这些类的特殊成员函数负责创建、 初始化、 销毁, 或者拷贝类的对象。如果没有显式地为一个类定义某个特殊成员函数, 而又需要用到该特殊成员函数时,则编译器会隐式的为这个类生成一个默认的特殊成员函数。

但是, 如果程序员为类显式的自定义了非默认构造函数, 编译器将不再会为它隐式地生成默认无参构造函数。

class A { public: A(const A& aa) : _a(aa._a) {} private: int _a; }; int main() { A a;// 不存在默认的构造函数 return 0; }

而对于移动构造函数和移动赋值运算符重载函数:

1.2 移动构造函数

如果没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器就会自动生成一个默认的移动构造函数。 这个默认生成的移动构造函数: 对内置类型按照内置类型的字节序拷贝 对自定义类型则先看这个类型是否实现了移动构造,实现了就用移动构造,否则用拷贝构造

namespace yyh { class string { public: typedef char* iterator; iterator begin() { return _str; } iterator end() { return _str + _size; } string(const char* str = "") :_size(strlen(str)) , _capacity(_size) { _str = new char[_capacity + 1]; strcpy(_str, str); } void swap(string& s) { ::swap(_str, s._str); ::swap(_size, s._size); ::swap(_capacity, s._capacity); } // 拷贝构造 string(const string& s) { cout cout delete[] _str; _str = nullptr; } char& operator[](size_t pos) { assert(pos char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } void push_back(char ch) { if (_size >= _capacity) { size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2; reserve(newcapacity); } _str[_size] = ch; ++_size; _str[_size] = '\0'; } string& operator+=(char ch) { push_back(ch); return *this; } const char* c_str() const { return _str; } private: char* _str = nullptr; size_t _size = 0; size_t _capacity = 0; }; } class A { public: private: int _a = 1; yyh::string _s; };

在这里插入图片描述 在这里插入图片描述

1.3 移动赋值重载函数

如果没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器就会自动生成一个默认的移动赋值重载函数。 而编译器生成的移动赋值对内置类型和自定义类型的处理方法跟上面一样。

在这里插入图片描述 在这里插入图片描述 但是只要我们加上了析构函数 、拷贝构造、拷贝赋值重载中的任意一个。编译器就不会默认生成。

class A { public: ~A() {} private: int _a = 1; yyh::string _s; };

在这里插入图片描述

1.4 成员变量缺省值 class A { public: ~A() {} private: int _a = 1;// 缺省值 yyh::string _s = "aaa";// 缺省值 };

这里的缺省值会在构造/拷贝构造的初始化列表使用: 如果在构造/拷贝构造有这两个的初始化就不会走缺省值,谁不在初始化列表初始化就走谁的缺省值。

1.5 强制生成默认函数的关键字default

上面我们举例子写了析构函数后编译器就不会默认生成移动构造和移动赋值。 但是如果我们必须要写析构,有没有什么办法让编译器默认生成呢? 答案是可以使用default关键字。 在这里插入图片描述

class A { public: A(int a = 1, const char* s = "") : _a(a) , _s(s) {} // 只有移动构造没有拷贝构造会匹配歧义 A& operator=(const A& aa) = default; A& operator=(A&& aa) = default; A(A&& a) = default; ~A() {} private: int _a = 1; yyh::string _s; }; int main() { A a; A b; A c; // 无default A d(move(b)); c = move(a); return 0; }

在这里插入图片描述

1.6 禁止生成默认函数的关键字delete 1.6.1 C++98防拷贝

我们怎么让一个类对象不能被其他类拷贝呢? 首先我们能想到把拷贝构造写成私有,这样在外部就无法被拷贝。 但是如果有一个内部成员函数会调用拷贝函数怎么办? 在C++98中使用的方法是只声明不定义:

class A { public: A(){} // 只声明不定义 A(const A& aa); ~A() { delete[]_p; } private: int* _p = new int[10]; }; int main() { A a; A b(a); return 0; }

在这里插入图片描述

1.6.1 C++11防拷贝

C++11是使用delete关键字来防止拷贝

class A { public: A(){} A(const A& aa) = delete; ~A() { delete[]_p; } private: int* _p = new int[10]; }; int main() { A a; A b(a); return 0; }

在这里插入图片描述

二、lambda表达式 2.1 对比 struct gift { std::string _name;// 名称 int _vol;// 体积 double _val;// 价值 gift(const char* name, int vol, double val) : _name(name) , _vol(vol) , _val(val) {} }; int main() { std::vector v = { {"苹果", 10, 20.0}, {"梨子", 15, 12.8}, {"香蕉", 11, 10.2} }; // 比较 return 0; }

当我们分别想按照名称、体积、价格来进行排序的时候,我们一般会写三个仿函数。

struct CmpNameLess { bool operator()(const gift& g1, const gift& g2) { return g1._name return g1._vol return g1._val {"苹果", 10, 20.0}, {"梨子", 15, 12.8}, {"香蕉", 11, 10.2} }; //sort(v.begin(), v.end(), CmpNameLess()); sort(v.begin(), v.end(), [](const gift& g1, const gift& g2){ return g1._name return-type{statement}

说明:

[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。 (parameters): 参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略 mutable: 默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。 ->returntype: 返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。 {statement}: 函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

明白了这些语法我们就可以写一个比较大小的lambda表达式:

[](int x, int y)->bool {return x // [](int x, int y)->bool {return x < y; }; auto cmp = [](int x, int y){return x return x + a; }; cout int tmp = a; a = b; b = tmp; }; return 0; }

在这里插入图片描述 所以这里我们要加上mutable,让传进来的参数可以被修改。

int a = 1, b = 2; auto Swap = [a, b]()mutable { int tmp = a; a = b; b = tmp; }; Swap();

但是我们调试发现a和b的值都没有被改变,原因是捕获列表默认是传值,并不会改变原来的值。

int main() { int a = 1, b = 2; auto Swap = [&a, &b]() { int tmp = a; a = b; b = tmp; }; Swap(); cout int operator()(int x, int y) { return x + y; } }; int main() { // 函数对象 Add sum1; sum1(1, 2); // lambda表达式 auto sum2 = [](int a, int b) {return a + b; }; sum2(1, 2); return 0; }

在这里插入图片描述 实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3