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

C++11_2

在这里插入图片描述

文章目录

  • 前言
  • 一、新的类功能——新的默认成员函数
    • 1. 编译器默认生成的移动构造与移动赋值
    • 2. 手动提供移动构造或移动赋值的影响
  • 二、可变参数模板
    • 1. 多参数
      • 1. 可变参数模板的基础
    • 2. 可变参数模板的展开
      • (1)递归展开参数包
      • (2)使用逗号表达式展开参数包
    • 3. 实际应用场景
  • 三、STL容器中的empalce相关接口函数
    • 1. empalce_back用法
    • 2. emplace_back与push_back与优缺
  • 四、包装器
    • 1. function包装器
      • (1)function是什么?
      • (2)function语法
      • (3)function应用
    • 2. bind
      • (1)bind语法
      • (2)bind用处
      • (3)对于成员函数
  • 总结


前言

接下来我们接着看C++11的新功能吧~


一、新的类功能——新的默认成员函数

在这里插入图片描述

这里是你的内容经过格式优化后的版本,保证清晰、易读,并符合技术文档的风格:


C++11 新增:移动构造函数与移动赋值运算符

C++11 新增了移动构造函数移动赋值运算符重载,它们主要用于资源所有权转移,提高性能,减少不必要的拷贝操作。

1. 编译器默认生成的移动构造与移动赋值

在特定条件下,编译器会自动生成默认的移动构造函数和移动赋值运算符。但需要注意以下几点:

  1. 默认移动构造函数
    • 如果类没有定义
      • 析构函数
      • 拷贝构造函数
      • 拷贝赋值运算符
    • 那么编译器会自动生成默认的移动构造函数
    • 其行为:
      • 对于内置类型(如 intdouble),执行按字节逐成员拷贝
      • 对于自定义类型,如果该类型实现了移动构造,则调用其移动构造;否则调用拷贝构造
        参考代码——string类
namespace jyf
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(const char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}// s1.swap(s2)void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;//string tmp(s._str);//swap(tmp);}string(string&& s):_str(nullptr){cout << "string(string&& s) -- 移动拷贝" << endl;swap(s);}// 赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}string& operator=(string&& s){cout << "string& operator=(string && s) -- 移动拷贝" << endl;swap(s);return *this;}~string(){delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){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)string& operator+=(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};
}

这里我们采用如下代码来进行学习:

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}//想让他强制生成就用关键字default,如下所示//Person(Person&& p) = default;//Person(const Person& p) = default;private:jyf::string _name;int _age;
};int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}

如上述代码所示,person类没有实现析构,拷贝构造,拷贝赋值,因此传右值的时候就因该调到移动拷贝。

在这里插入图片描述


  1. 默认移动赋值运算符
    • 如果类没有定义
      • 析构函数
      • 拷贝构造函数
      • 拷贝赋值运算符
    • 那么编译器会自动生成默认的移动赋值运算符
    • 其行为:
      • 对于内置类型,执行按字节逐成员拷贝
      • 对于自定义类型,如果该类型实现了移动赋值,则调用其移动赋值;否则调用拷贝赋值

注意:默认移动赋值运算符的行为与默认移动构造函数完全类似。

2. 手动提供移动构造或移动赋值的影响

如果类手动实现了

  • 移动构造函数
  • 移动赋值运算符

那么编译器不会自动提供:

  • 拷贝构造函数
  • 拷贝赋值运算符

如果你的类需要支持拷贝与移动,必须手动实现拷贝构造与拷贝赋值,否则可能导致拷贝操作被禁止(=delete)。


二、可变参数模板

1. 多参数

C++11 引入了可变参数模板,允许创建可以接受任意数量参数函数模板类模板

1. 可变参数模板的基础

在可变参数模板中,参数列表使用 ... 省略号表示。例如:

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

在上面的代码中:

  • Args...模板参数包,它可以包含**0 到 N(N ≥ 0)**个模板参数。
  • args...函数形参包,它与 Args... 一一对应。

可变参数模板的一个主要特点是:
不能直接获取参数包中的元素,只能通过展开参数包的方式访问每个参数。这也是可变参数模板最难理解的地方,因为语法不支持 args[i] 这种方式直接访问参数。


2. 可变参数模板的展开

由于无法直接访问参数包中的元素,常见的展开方式有:

(1)递归展开参数包

递归展开是最常见的方式,我们通过递归终止函数来控制递归的结束:

#include <iostream>
using namespace std;// 递归终止函数
template <class T>
void ShowList(const T& t)
{cout << t << endl;
}// 递归展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{cout << value << " ";ShowList(args...);
}int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

执行结果:

1
1 A
1 A sort

原理:

  • 递归终止函数:当 Args... 为空时,调用 ShowList(const T&) 结束递归。
  • 递归展开:每次调用 ShowList(T value, Args... args),打印 value,然后递归调用 ShowList(args...) 继续展开。

也可以这样:

void _ShowList()
{// 结束条件的函数cout << endl;
}template <class T, class ...Args>
void _ShowList(T val, Args... args)
{cout << val << " ";_ShowList(args...);
}//args代表0-N的参数包
template <class ...Args>
void CppPrint(Args... args)
{_ShowList(args...);
}int main()
{CppPrint();CppPrint(1);CppPrint(1, 2);CppPrint(1, 2, 2.2);CppPrint(1, 2, 2.2, string("xxxx"));// ...return 0;
}

(2)使用逗号表达式展开参数包

逗号表达式可以用于参数包展开,不需要额外定义递归终止函数:

#include <iostream>
using namespace std;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;
}

执行结果:

1 
1 A 
1 A sort 

原理:

  • PrintArg(args) 依次执行 PrintArg(arg1), PrintArg(arg2), PrintArg(arg3)...
  • 由于 int arr[] = { (PrintArg(args), 0)... }; 是一个初始化列表,所以所有 PrintArg(args)构造数组时就被执行,从而展开参数包。


3. 实际应用场景

我们来看一个实用一点的实际场景:

class Date
{
public:Date(int year = 1, int month = 1, int day = 1):_year(year),_month(month),_day(day){cout << "Date构造" << endl;}Date(const Date& d):_year(d._year), _month(d._month), _day(d._day){cout << "Date拷贝构造" << endl;}private:int _year;int _month;int _day;
};template <class ...Args>
Date* Create(Args... args)
{Date* ret = new Date(args...);return ret;
}int main()
{Date* p1 = Create();Date* p2 = Create(2023);Date* p3 = Create(2023, 9);Date* p4 = Create(2023, 9, 27);Date d(2023, 1, 1);Date* p5 = Create(d);return 0;
}

如下图所示:有了多参数之后,我们只要提供多参数的构造,再将构造函数写缺省,就可以更加灵活多变的传参来创建对象,和传统对象的创建相比,这个方法不需要创建额外的对象,参数包传过去会自动匹配构造函数,省去了一层拷贝,提高了效率!

在这里插入图片描述

也可以直接传日期类对象,参数包接收就会掉拷贝构造:
在这里插入图片描述

在这里插入图片描述


三、STL容器中的empalce相关接口函数

1. empalce_back用法

http://www.cplusplus.com/reference/vector/vector/emplace_back/
http://www.cplusplus.com/reference/list/list/emplace_back/

在这里插入图片描述

在这里插入图片描述

我们可以看到每一个容器都新增了empalce系类的内容,那么它有什么用呢?

template <class... Args>
void emplace_back (Args&&... args);
首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用。那么相对insert和
emplace系列接口的优势到底在哪里呢?

2. emplace_back与push_back与优缺

int main()
{list< std::pair<int, char> > mylist;// emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象// 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别mylist.emplace_back(10, 'a');mylist.emplace_back(20, 'b');mylist.emplace_back(make_pair(30, 'c'));mylist.push_back(make_pair(40, 'd'));mylist.push_back({ 50, 'e' });for (auto e : mylist)cout << e.first << ":" << e.second << endl;return 0;
}

主要是这样:mylist.emplace_back(20, 'b');,这样就不用先创建临时对象在进行拷贝构造,而是直接走参数包,有一定性能提升的。而push_back只能传对象。


我们再来看下一个场景:

int main()
{// 下面我们试一下带有拷贝构造和移动构造的jyf::string,再试试呢// 我们会发现其实差别也不大,emplace_back是直接构造了,push_back// 是先构造,再移动构造,其实也还好。std::list< std::pair<int, jyf::string> > mylist;mylist.emplace_back(10, "sort");mylist.push_back(make_pair(30, "sort"));std::list<Date> lt;Date d(2023, 9, 27);// 只能传日期类对象lt.push_back(d);// 传日期类对象// 传日期类对象的参数包// 参数包,一路往下传,直接去构造或者拷贝构造节点中日期类对象lt.emplace_back(d);lt.emplace_back(2023, 9, 27);return 0;
}

因为有移动构造的存在,所以对于深拷贝的类其实差别不大,对于浅拷贝有一定提升,但因为浅拷贝本来就没有多少资源,所以也影响不大。

在这里插入图片描述


四、包装器

1. function包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。

那么,function有什么用呢?

function在#include <functional>里面~


(1)function是什么?

我们来看下面一段代码:

#include<iostream>
using namespace std;
#include <functional>template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}double f(double i)
{return i / 2;
}struct Functor
{double operator()(double d){return d / 3;}
};int main()
{// 函数指针cout << useF(f, 11.11) << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;// lambda表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}

有三类:1.函数指针 2. 函数对象 3. lambda表达式 分别去调用useF函数,但是他们三个虽然都是起到类似定义函数变量的作用,但是他们却实例化出三份不同的函数,证据就是运行结果静态变量没有则会增加到3,而是有3个1。

在这里插入图片描述
那有没有什么方法可以包装一下这三类1.函数指针 2. 函数对象 3. lambda表达式呢?
有的兄弟,有的~
我们的包装器就要登场了——function


(2)function语法

语法:function<返回值类型(参数列表)> xxx = ???

像这样:

// 包装器 -- 可调用对象的类型问题function<double(double)> f1 = f;function<double(double)> f2 = [](double d)->double { return d / 4; };function<double(double)> f3 = Functor();

因此我们就可以吧三种不同类的函数放到同一个vector中,如下:
方法一:

vector<function<double(double)>> v = { f1, f2, f3 };

方法二:

vector<function<double(double)>> v = { f, [](double d)->double { return d / 4; }, Functor() };

因此我们就可以这样取调用它:

vector<function<double(double)>> v = { f, [](double d)->double { return d / 4; }, Functor() };double n = 3.3;
for (auto f : v)
{cout << f(n++) << endl;
}

有了function以后,我们就可以同一类型,解决实例化多份的问题,如下图,三类只实例化出一份。

#include <functional>
template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
double f(double i)
{return i / 2;
}
struct Functor
{double operator()(double d){return d / 3;}
};
int main()
{// 函数名std::function<double(double)> func1 = f;cout << useF(func1, 11.11) << endl;// 函数对象std::function<double(double)> func2 = Functor();cout << useF(func2, 11.11) << endl;// lamber表达式std::function<double(double)> func3 = [](double d)->double { return d /4; };cout << useF(func3, 11.11) << endl;return 0;
}

在这里插入图片描述


(3)function应用

我们来通过一道习题展示他的应用:
https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/submissions/

在这里插入图片描述

这是我们之前写的代码,要判断多次运算符而且冗余:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> s;for (size_t i = 0; i < tokens.size(); i++){if (tokens[i] == "+" ||tokens[i] == "-" ||tokens[i] == "*" ||tokens[i] == "/" ){int right = s.top();s.pop();int left = s.top();s.pop();switch(tokens[i][0]){case '+':s.push(left + right);break;case '-':s.push(left - right);break;case '*':s.push(left * right);break;case '/':s.push(left / right);break;}}else{s.push(atoi(tokens[i].c_str()));}}return s.top();}
};

有了function之后我们就可以这样做~

class Solution {
public:int evalRPN(vector<string>& tokens) {map<string, function<int(int, int)>> cmdFuncMap ={{"+", [](int x, int y){return x + y;}},{"-", [](int x, int y){return x - y;}},{"*", [](int x, int y){return x * y;}},{"/", [](int x, int y){return x / y;}}};stack<int> st;// 运算数入栈,运算符运算for(auto& e : tokens){if (cmdFuncMap.count(e)){//运算int right = st.top();st.pop();int left = st.top();st.pop();st.push(cmdFuncMap[e](left, right));}else{st.push(stoi(e));}}return st.top();}
};

代码看起来是不是清晰很多呢~


2. bind

(1)bind语法

  1. 对于函数:bind(函数名, placeholders::_x, placeholders::_x, ...)
  2. 对于静态成员函数:bind(作用域::静态成员函数, placeholders::_x, placeholders::_x, ...),也可以写成:bind(作用域::&静态成员函数, placeholders::_x, placeholders::_x, ...)
  3. 对于普通成员函数:bind(作用域::&普通成员函数, '&对象'或'匿名对象',placeholders::_x, placeholders::_x, ...)

(2)bind用处

bind(绑定)是什么呢?
简单来说就是传参的时候我们可以改变参数的顺序,以此达到我们想要的效果:

int Sub(int a, int b)
{return a - b;
}int main()
{function<int(int, int)> sub1 = bind(Sub, placeholders::_2, placeholders::_1);cout << sub1(10, 5);
}

在这里插入图片描述
他的原理是这样的:
在这里插入图片描述
传参的时候第一个函数会去匹配placeholder::_1,第二个会去匹配_2,而bind却是按照参数的顺序绑定的,因此我们可以更加灵活调整传参数顺序。

如果有其他参数放到对应位置正常写即可,就像这样:
在这里插入图片描述


(3)对于成员函数

class SubType
{
public:static int sub(int a, int b){return a - b;}int ssub(int a, int b, int rate){return (a - b) * rate;}
};

对于静态成员函数是这样的:
在这里插入图片描述
这个&可加可不加,建议加上。

对于普通成员变量:
在这里插入图片描述
第一个前必须加&,后面可以匿名对象,也可以&对象。其实就是.还是->问题。


总结

C++11语法持续更新中,还有智能指针等章节在后续讲解中,谢谢大家支持!

相关文章:

  • 信息学奥赛一本通 1622:Goldbach’s Conjecture | 洛谷 UVA543 Goldbach‘s Conjecture
  • 【HDFS入门】HDFS与Hadoop生态的深度集成:与YARN、MapReduce和Hive的协同工作原理
  • 深度监听 ref 和 reactive 的区别详解
  • Spring Boot 实现 Excel 导出功能(支持前端下载 + 文件流)
  • ⭐ Unity 使用Odin Inspector增强编辑器的功能:UIManager脚本实例
  • React 对state进行保留和重置
  • 【gpt生成-其一】以go语言为例,详细描述一下 ​:语法规范​​BNF/EBNF形式化描述
  • 基于深度学习并利用时间信息在X射线血管造影中进行冠状动脉血管分割|文献速递-深度学习医疗AI最新文献
  • 【gpt生成-总览】怎样才算开发了一门编程语言,需要通过什么测试
  • 【OSCP-vulnhub】GoldenEye
  • 【专业解读:Semantic Kernel(SK)】大语言模型与传统编程的桥梁
  • v-model进阶+ref+nextTick
  • 爱普生FA2016AS晶振在智能家居中的应用
  • vue3项目启动bug
  • GitHub 从入门到精通完全指南(2025版)
  • 【FPGA】【DE2-115】DDS信号发生器设计
  • 游戏引擎学习第229天
  • RAG(检索增强生成)、ReAct(推理与行动) 和 多模态AI 的详细解析,包括三者的定义、工作原理、应用场景及协同关系
  • docker能用来干什么的
  • 在Web3中实现数据保护的最佳实践
  • 国际博物馆日|航海博物馆:穿梭于海洋神话与明代造船工艺间
  • 王伟妻子人民日报撰文:81192,一架永不停航的战机
  • 吉利汽车一季度净利润大增264%,称整合极氪后实现整体效益超5%
  • 明查| 新一代AI诊疗系统可3秒筛查13种癌症?没有证据
  • 制造四十余年血腥冲突后,库尔德工人党为何自行解散?
  • 山东:小伙为救同学耽误考试属实,启用副题安排考试