当前位置: 首页 > news >正文

一文学会《C++》进阶系列之C++11

文章目录

  • 统一列表初始化
    • {}初始化
    • std::initializer_list
  • 左值引用和右值引用
    • 左值引用
    • 💪右值引用
    • 左值引用右值引用比较
  • 🚩 右值引用的使用场景和意义
    • ⭐左值引用使用场景及短板
    • 右值引用和移动语义解决
  • 🚩完美转发
    • ⭐模板中的&&万能引用
    • ⭐std::forword
  • 新的类功能
    • 💪default关键字
    • 💪delete关键字
    • final和override关键字
  • 可变参数模板
    • 递归函数方式展开参数包
    • 逗号表达式展开参数包
    • emplace
  • ❤lambda表达式
    • lambda语法
    • ⭐函数对象与lambda表达式
  • ❤function包装器

统一列表初始化

{}初始化

c++98允许花括号对数组或结构体进行初始化,如:

using namespace std;
struct point {int _x;int _y;
};
int main()
{int array1[] = { 1,2,3,4,5 };int array2[5] = { 0 };point p = { 1,2 };
}

c++11扩大了花括号的使用范围,使其可以定义所有的内置类型或自定义类型使用初始胡列表时可以加=号也可以不加

int x = 1;
int y{ 1 };int array1[]{ 1,2,3,4,5 };
int array2[5]{ 0 };
point p{ 1,2 };//也可以用在new
int* pa = new int[4] {0};

创建对象时也可以用列表初始化调用构造函数,

class Date {
public:Date(int year, int month, int day):_year(year),_month(month),_day(day){cout << "(int year, int month, int day)"<<endl;}
private:int _year;int _month;int _day;
};
int main()
{//c++98Date d1(2025, 10, 1);//c++11Date d2{ 2025,10,1 };return 0;
}

std::initializer_list

查看initializer_list类型

auto li = {1,4,7};
cout << typeid(li).name()<<endl;

在这里插入图片描述

initializer_list使用场景一般作为容器构造函数参数,c++11大部分容器都加了initializer_list构造,这样初始化容器就简单了,
也可以用于operator=参数,就可以用{}初始化了

int main()
{//c++要一个个insertvector<int> v = { 2,5,7,8 };list<int> l = { 1,3,5,6 };//c++98要make_pair,这里里面自动生成pairmap<string, string> mp{ {"apple","苹果"},{"pear","梨"} };//用花括号赋值v = { 1,3,5,7 };return 0;
}

自己模拟实现的vector添加initializer_list功能

namespace jib {template<class T>class vector {public:typedef T* iterator;vector(initializer_list<T> l){_start = new T[l.size()];_finish = _start + l.size();_endofstorage = _start + l.size();iterator vit = _start;typename initializer_list<T>::iterator lit = l.begin();while (vit != _finish){*vit = *lit;vit++;lit++;}}vector<T>& operator=(initializer_list<T> l) {vector<T> tmp(l);swap(_start, tmp._start);swap(_finish, tmp._finish);swap(_endofstorage, tmp._endofstorage);return *this;}private:iterator _start;iterator _finish;iterator _endofstorage;};
}
int main()
{jib::vector<int> v = { 1,4,6 };return 0;
}

左值引用和右值引用

c++11新增右值引用,从此我们之前学的引用都叫做左值引用无论左值引用还是右值引用,都是给对象起别名

左值引用

左值:我们可以对它取地址+赋值,左值可以出现在赋值符号的左边,右值不能出现在符号的左边,被const修饰的左值不能赋值,但可以取地址对左值引用就是左值引用

int main()
{//下面是左值int* p = new int[10];int a = 1;const int b = 2;//以下是左值引用int*& rp = p;int& ra = a;const int& rb = b;int& pp = *p;return 0;
}

💪右值引用

右值:一种表示数据的表达式,如字面常量,表达式返回值,函数返回值(这个不能是左值引用返回),右值可以出现在赋值符号右边,不能出现在左边,不能取地址。右值引用就是对右值取引用,是右值的别名。

int f(int x, int y)
{return x + y;
}
int main()
{int x = 1;int y = 2;//以下是右值10;x + y;f(x, y);//以下是右值引用和int&& xv = 10;int&& yv = x + y;int&& fv = f(x, y);return 0;
}

注意:右值本身是不能取地址,但是被引用后,右值就换了个特殊地方存储,就可以被取地址,
就是说 10本来是不能取地址的,但是xv引用了10,就可以对xv取地址,也可以修改xv,但是右值引用应用场景不在这里,所以这个特性不是很重要、

int&& xv = 10;
xv = 20;

左值引用右值引用比较

左值引用总结
1,左值引用只能引用左值不能引用右值
🚩2,加了const的左值引用既可以引用左值也可以引用右值

	int x = 10;int& rx1 = x;//int& rx2=10; 错误因为10是右值//const既可以引用左值也可以引用右值const int& rx3 = 10;const int& rx4 = x;

右值引用总结:
1,右值引用只能引用右值不能引用左值
🚩2,可以引用move以后的左值

int&& rx = 10;
int a = 10;
int&& ra = std::move(a);

🚩 右值引用的使用场景和意义

我们看看左值引用的短板,以及右值引用如何补齐这个短板的

namespace jib
{class string {public:typedef char* iterator;public:string(const char* str = ""){_size = strlen(str);_capacity = _size;if (nullptr == str){assert(false);return;}_str = new char[strlen(str) + 1];strcpy(_str, str);cout << "string(const char* str=" ")" << endl;}//拷贝构造string(const string& s):_str(new char[strlen(s._str) + 1]),_size(strlen(s._str)),_capacity(_size){strcpy(_str, s._str);cout << "string(const string& s)---深拷贝" << endl;}//移动构造string(string&& s):_str(nullptr),_size(0),_capacity(0){cout << "string(string&& s)--移动构造" << endl;Swap(s);}//赋值重载string& operator =(string& s){string tmp(s);Swap(s);cout << "operator =(string s)---深拷贝" << endl;return *this;}//移动赋值string& operator =(string&& s){cout << "operator =(string&& s)---移动赋值" << endl;Swap(s);return *this;}void push_back(char c){if (_size == _capacity){reserve(_capacity * 2 + 1);}_str[_size++] = c;_str[_size] = '\0';}string& operator +=(char c){push_back(c);return *this;}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void clear(){_size = 0;_str[_size] = '\0';}void Swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}const char* C_Str(){return _str;}~string(){delete[] _str;_str = nullptr;}private:char* _str;size_t _size;size_t _capacity;};
}

⭐左值引用使用场景及短板

做参数和返回值都能提高效率

void func1(jib::string s1)
{}
void func2(const jib::string& s2)
{}
int main()
{jib::string s("hello");func1(s);func2(s);//传引用减少拷贝,提高了效率s += 'w';//string operator+=(char ch); 存在深拷贝//string& operator+=(char ch);引用无需拷贝,提高效率return 0;
}

短板:
当函数返回值是局部变量,出了作用域就销毁了就不能左值引用返回,只能传值返回,例如下面函数(我拿我老师的板书来说了),只能传值返回,而传值返回又导致至少1次拷贝构造(旧编译器可能会两次拷贝构造)
在这里插入图片描述

在这里插入图片描述

右值引用和移动语义解决

移动语义将右值的资源剽窃过来,那么我们就不需要深拷贝了,就是拿别人的资源来构造自己,移动构造没有开辟新空间,所以效率提升了
在这里插入图片描述
拿我左值引用短板那块代码运行

jib::string f()
{jib::string str;return str;
}
int main()
{jib::string s1;s1 = "qq";cout << " " << endl;jib::string str=move(f());return 0;
}

在这里插入图片描述
💪仔细分析以下:

第一行:构建s1对象
❤第二行:“qq”隐式构造临时对象
第三行:移动赋值

第五行:f()中str默认构造
第六行:返回右值调用移动构造

⭐插一句 string s=“hello” 如果编译器优化的话就是直接构造,如果不优化,就是先构造临时对象再调用拷贝构造

🚩完美转发

⭐模板中的&&万能引用

模板中的&&不是右值引用,而是万能引用,既能接受左值又能接受右值
我们看以下代码:

void fun(int& x)
{cout << "左值引用" << endl;
}
void fun(int&& x)
{cout << "右值引用" << endl;
}
void fun(const int& x)
{cout << "左值引用" << endl;
}
void fun(const int&& x)
{cout << "右值引用" << endl;
}
template<class T>
void perfectforward(T&& t)
{fun(t);
}
int main()
{perfectforward(10);  //右值int a;perfectforward(a);   //左值perfectforward(move(a)); //右值const int b=1;perfectforward(b);   //左值perfectforward(move(b)); //右值return 0;
}

在这里插入图片描述
怎么全是左值引用????
答:模板中的&&确实接受了左值右值,但只限制了接收的类型,之后对t的操作都是左值属性

❤总结:
1,模板中的&&是万能引用,既能接受左值也能接受右值
2,t是右值,但之后的操作都退化成左值

⭐std::forword

std::forward在完美转发中保留对象的原生类型属性

template<class T>
void perfectforward(T&& t)
{fun(std::forward<T> (t));
}

在这里插入图片描述
完美转发实际应用

template<class T>
struct ListNode
{ListNode* _next = nullptr;ListNode* _prev = nullptr;T _data;
};
template<class T>
class List
{typedef ListNode<T> Node;
public:List(){_head = new Node;_head->_next = _head;_head->_prev = _head;}void PushBack(T&& x){//Insert(_head, x);Insert(_head, std::forward<T>(x));}void PushFront(T&& x){//Insert(_head->_next, x);Insert(_head->_next, std::forward<T>(x));}// prev newnode posprev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;void Insert(Node* pos, T&& x){Node* prev = pos->_prev;Node* newnode = new Node;newnode->_data = std::forward<T>(x); // 关键位置void Insert(Node * pos, const T & x){Node* prev = pos->_prev;Node* newnode = new Node;newnode->_data = x; // 关键位置// prev newnode posprev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}}
private:Node* _head;
};
int main()
{List<jib::string> lt;lt.PushBack("1111");lt.PushFront("2222");return 0;
}

用右值引用插入,少了一次临时构造string

新的类功能

默认成员函数
原来C++类中,有6个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的
C++11 新增了两个:移动构造函数和移动赋值运算符重载
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的
    意一个
    。那么编译器会自动生成一个默认移动构造默认生成的移动构造函数,对于内置类
    型成员
    会执行逐成员按字节拷贝自定义类型成员,则需要看这个成员是否实现移动构造
    如果实现了就调用移动构造,没有实现就调用拷贝构造
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载
    的任意一个,那么编译器会自动生成一个默认移动赋值默认生成的移动构造函数,对于内
    置类型成员
    会执行逐成员按字节拷贝自定义类型成员,则需要看这个成员是否实现移动赋
    ,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造
    完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值

💪default关键字

default可以强制生成默认函数
如果我们实现了拷贝构造,移动构造可能不会自动生成,就可以用default强制生成默认函数

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p):_name(p._name), _age(p._age){}Person(Person&& p) = default; //显式强制生成移动构造
private:jib::string _name;int _age;
};
int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}

💪delete关键字

同样的,禁止编译器生成默认函数就用delete关键字

Person(Person&& p) = delete;

final和override关键字

这个我在继承和多态文章里说过了,可以去那两篇文章查看

可变参数模板

c++98类模板和函数模板仅支持固定数量的模板参数,c++11新增可变参数模板,作者水平有限,这里只讲基础用法

一个基本可变参数的函数模板:

template <class ...Args>
void Showlist(Args... args)
{}

Args是模板参数包,args是形参参数包
Args… args包含了0——N个参数(N≥0)

递归函数方式展开参数包

//递归终止函数
template<class T>
void Showlist(const T& t) //1号
{cout << t << endl;
}
//展开函数
template <class T,class ...Args>
void Showlist(T value,Args... args) //2号
{cout << value<<" ";Showlist(args...);
}
int main()
{Showlist('1');Showlist('1', 'h');Showlist('1', 'h', "hello");return 0;
}

在这里插入图片描述
第一行:直接进入终止函数,因为就一个参数
第二行:先进入2号函数,打印1,剩1个参数,调用终止函数
第三行:先进入2号函数,剩两个参数,再进入2号函数,剩一个参数吗,调用终止函数

逗号表达式展开参数包

逗号表达式展开参数包不需要终止函数,是在expand函数体中实现的,printfarg处理参数包中的每一个参数逗号表达式会顺序执行逗号前面的表达式

expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行
printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列
表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…}将会展开成((printarg(arg1),0),
(printarg(arg2),0), (printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof…
(Args)]
。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)
打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在
数组构造的过程展开参数

template <class T>void PrintArg(T t){cout << t << " ";}//展开函数
template <class ...Args>void ShowList(Args... args){int arr[] = { (PrintArg(args), 0)... };cout << endl;}int main(){ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;}

emplace

template<class... Args>
void emplace_back(Args&&... args)
{}

emplace系列接口接受模板可变参数,同时是万能引用,那么相比较insert有啥优势呢?

int main()
{list<pair<int, char>> lt;lt.emplace_back(1, 'a');lt.emplace_back(make_pair('2', 'b'));lt.push_back({'3','c'});lt.push_back(make_pair('4', 'd'));return 0;
}

emplace支持可变参数,拿到可构造pair对象就可以直接构造,那么我们可以知道emplace与push_back没什么区别,

#include <list>
#include <string>
int main()
{list<pair<int, string>> lt;lt.emplace_back(1, "hello");lt.emplace_back(make_pair(2, "hello"));lt.push_back({ 3,"hello"});lt.push_back(make_pair(4, "hello"));return 0;
}

我们再看一下支持移动构造的string,看看emplace和push有何区别
只有(1,“hello”)是直接构造,其他都是先临时构造pair对象,再移动构造

❤两者效率并无太大差别,(就是因为移动构造的存在使得emplace显得不那么重要)

❤lambda表达式

C++98中要对数据排序要这样做:

#include <iostream>
#include <algorithm>
using namespace std;
int main()
{int a[] = { 1,5,7,8,4,3 };sort(a ,a+sizeof(a)/sizeof(a[0]));//sort(a, a + sizeof(a) / sizeof(a[0]),greater<int>());return 0;
}

对自定义类型要仿函数

#include <vector>
#include <algorithm>
class fruit
{
public:fruit(const char* name, double price):_name(name),_price(price){}string _name;double _price;
};struct pricegreater
{bool operator()(const fruit& a, const fruit& b){return a._price > b._price;}
};
int main()
{vector<fruit> v = { {"apple",1.5},{"pair",2.1},{"grape",1.0} };sort(v.begin(), v.end(), pricegreater());return 0;
}

C++11觉得比较太麻烦了,每次比较都要写一个类,如果比较逻辑不同还要写更多的类,所以发明了lamdba表达式
用法:

#include <algorithm>
#include <vector>
int main()
{vector<fruit> v = { {"apple",1.5},{"pair",2.1},{"grape",1.0} };sort(v.begin(), v.end(), [](const fruit& a, const fruit& b) {return a._price < b._price;});sort(v.begin(), v.end(), [](const fruit & a, const fruit & b) { return a._price > b._price; });return 0;
}

lambda语法

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }
各部分说明:

  • [capture-list]捕捉列表,编译器根据[]来判断接下来是否是lambda函数,捕捉列表能捕捉上下文变量来供lambda使用
  • (parameters) 参数列表,与普通函数参数列表完全一致,如果不传参数,可以省略
  • mutable lambda是常性函数,mutable可以取消常性,使用mutable时,参数列表不可省略
  • -> return-type :返回值类型也可省略,编译器会根据返回值自动推导
  • { statement }:函数体,在函数体中不仅可以使用参数,还可以使用捕捉i到的所有变量

⭐注意:lambda中参数列表和返回值类型都可省略,但[]和{}至少为空,因此最简单lambda函数[]{},该函数不能做任何事情

int main()
{int a = 3;int b = 4;[=] {return a + 3;};//省略参数列表和返回值类型cout << a << " " << b << endl;auto func1=[&](int c) {return b = a + c;};//省略返回值类型func1(10);cout << a << " " << b << endl;auto func2 = [=, &b](int c)->int {return b += a + c;};//完善的lambdafunc2(10);cout << a << " " << b << endl;int x=10;//auto add_x = [x](int c) {x *= 2;return x + 2;};//常性,x不能修改auto add_x = [x](int c) mutable {x *= 2;return x + 2;};cout << add_x(10)<<endl;return 0;
}

lambda可以理解是无名函数,不能直接调,但是可以用auto接收

捕捉列表说明:
捕捉列表决定上下文那些数据可以被使用,以及是使用方式传值还是传引用

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

注意:
⭐1,父作用域指的是包含lambda表达式的语句块
⭐2,捕捉列表可以捕捉多项,以逗号分隔

[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

⭐3 ,捕捉列表中不能重复捕捉,否则编译错误

比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复

⭐4, 在块作用域以外的lambda函数捕捉列表必须为空
⭐5,在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者
非局部变量都会导致编译报错。
⭐ 6,lambda表达式之间不能相互赋值,即使看起来类型相同

void (*PF)();
int main()
{auto f1 = [] {cout << "hello world" << endl; };auto f2 = [] {cout << "hello world" << endl; };//f1 = f2;    // 编译失败--->提示找不到operator=()// 允许使用一个lambda表达式拷贝构造一个新的副本auto f3(f2);f3();PF = f2;// 可以将lambda表达式赋值给相同类型的函数指针return 0;
}

⭐函数对象与lambda表达式

函数对象又称仿函数,指的像函数一样使用的对象,就是类中重载operator()的类对象

class Rate
{
public:Rate(double rate) : _rate(rate){ }double operator()(double money, int year){  return money * _rate * year;}
private:
double _rate;
};
int main()
{// 函数对象double rate = 0.49;Rate r1(rate);r1(10000, 2);// lamberauto r2 = [=](double monty, int year)->double {return monty * rate * year;};r2(10000, 2);return 0;
}

两者使用差不多,
🚩实际上,lambda就是通过仿函数实现的,使用lambda时,编译器会自动生成一个类,并重载()

在这里插入图片描述

❤function包装器

template <class F,class T>
T Usef(F f,T x)
{static int count = 0;cout << ++count << endl;cout << &count << endl;return f(x);
}double f(double x)
{return x / 2;
}struct Functor {double operator()(double x){return x / 3;}
};
int main()
{cout << Usef(f,11.11) << endl; //函数名(函数指针)cout << Usef(Functor(), 11.11) << endl;//函数对象cout << Usef([](double x) {return x / 4;},11.11);//lambda表达式return 0;
}

在这里插入图片描述
我们发现Usef被实例化了三份,效率太低了,所以发明了function

template <class Ret, class… Args>
class function<Ret(Args…)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参

#include <functional>
template<class F,class T>
void Usef(F f,T x)
{static int count = 0;cout << ++count << endl;cout << &count << endl;
}
double f(double x)
{return x / 2;
}struct Functor {double operator()(double x){return x / 3;}
};class Plus
{
public:static double plusi(double x){return x / 4;}double plusd(double x){return x / 5;}
};
int main()
{function<double(double x)>func1 = f;cout << func1(11.11) << endl;function<double(double)> func2 = Functor();cout << func2(11.11) << endl;function<double(double)> func3 = [](double x) {return x / 6;};cout << func3(11.11) << endl;function<double(double)> func4 = &Plus::plusi;cout << func4(11.11) << endl;function<double(Plus, double)> func5= &Plus::plusd;cout << func5(Plus(), 11.11) << endl;Usef(func1, 11.11);Usef(func2, 11.11);Usef(func3, 11.11);Usef(func4, 11.11);Usef(func5, 11.11);return 0;
}

在这里插入图片描述
我们发现 function<double(double)> 只被实例化了1份

http://www.dtcms.com/a/446295.html

相关文章:

  • 腊肉网站的建设前景网页版微信可以发朋友圈吗
  • 大连凯杰建设有限公司网站wordpress 文章链接失效
  • 百度网站优化升上去国外网站入口
  • BIT*算法
  • Python常用三方模块——psutil
  • 网站开发的优势建设京东物流网站的目标是什么
  • 制作网站详细步骤爱客crm系统登录
  • Linux事件循环——高效处理多任务(高并发)
  • 【Linux】POSIX信号量、环形队列、基于环形队列实现生产者消费者模型
  • SELinux系列专题(一):SELinux是什么?
  • 三角函数公式全归纳
  • 热 动漫-网站正在建设中-手机版wordpress活动报名
  • 建设银行扬中网站织梦网站仿站
  • 网站建设公司伟置鄂尔多斯 网站制作
  • Hi3516DV500/HI3519DV500开发笔记之例程编译和测试
  • 路由策略与路由控制实验
  • Leetcode 84. 柱状图中最大的矩形 单调栈
  • 专门用来制作网页的软件是河南网站关键词优化
  • 什么是企业网站策划案企业网站空间买虚拟主机
  • 高并发场景下API网关的熔断策略:Hystrix与Sentinel的对比测试
  • llama.cpp Flash Attention 论文与实现深度对比分析
  • Python 3 与 MongoDB 的集成指南
  • 网站生成手机端wordpress高亮插件
  • 基础动态规划问题
  • js多久可以做网站网站建设后帐号密码
  • 第十五篇:Python高效调试与性能优化技巧
  • leetcode 66.加一 python
  • 书生浦语实战营L1-G4000探索大模型能力边界
  • Prometheus 05-02: 告警规则与Alertmanager配置
  • 工信部申诉备案网站免费关键词优化工具