广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >【C++】C++11语法 ~ 可变参数模板
  • 881
分享到

【C++】C++11语法 ~ 可变参数模板

c++java开发语言 2023-08-17 07:08:28 881人浏览 八月长安
摘要

🌈欢迎来到c++专栏~可变参数模板

🌈欢迎来到c++专栏~可变参数模板


  • (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort
  • 目前状态:大三非科班啃C++中
  • 🌍博客主页:张小姐的猫~江湖背景
  • 快上车🚘,握好方向盘跟我有一起打天下嘞!
  • 送给自己的一句鸡汤🤔:
  • 🔥真正的大师永远怀着一颗学徒的心
  • 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
  • 🎉🎉欢迎持续关注!
    在这里插入图片描述

请添加图片描述

请添加图片描述

一. 类的新功能

🥑默认成员函数

C++11之后,有八个默认成员函数

在C++11之前,一个类中有如下六个默认成员函数:

  • 构造函数
  • 析构函数
  • 拷贝构造函数
  • 拷贝赋值函数
  • 取地址重载函数
  • const取地址重载函数

其中前四个成员函数最重要,后面两个成员函数一般不会用到,这里“默认”的意思就是你不写编译器会自动生成。在C++11标准中又增加了两个默认成员函数,分别是移动构造函数和移动赋值重载函数

✨默认移动构造和移动赋值的生成条件

  • 默认移动构造生成条件:没有自己实现移动构造函数,并且没有自己实现析构函数、拷贝构造函数和拷贝赋值函数的任意一个
  • 移动赋值重载函数的生成条件:没有自己实现移动赋值重载函数,并且没有自己实现析构函数、拷贝构造函数和拷贝赋值函数

😎特别注意: 如果我们自己实现了移动构造或者移动赋值,就算没有实现拷贝构造和拷贝赋值,编译器也不会生成默认的拷贝构造和拷贝赋值

默认生成的移动构造和移动赋值会做什么?

  • 对于内置类型的成员会完成值拷贝(浅拷贝),对于自定义类型的成员,如果该成员实现了移动构造/移动赋值就调用它的移动构造/移动赋值,否则就调用它的拷贝构造

对此我们展开验证,这里需要模拟实现一个简化版的string类,类当中只编写了几个我们需要用到的成员函数。

代码如下:

namespace ljj{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]; //为存储字符串开辟空间(多开一个用于存放'\0')strcpy(_str, str); //将C字符串拷贝到已开好的空间}//交换两个对象的数据void swap(string& s){//调用库里的swap::swap(_str, s._str); //交换两个对象的C字符串::swap(_size, s._size); //交换两个对象的大小::swap(_capacity, s._capacity); //交换两个对象的容量}//拷贝构造函数(现代写法)string(const string& s):_str(nullptr), _size(0), _capacity(0){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str); //调用构造函数,构造出一个C字符串为s._str的对象swap(tmp); //交换这两个对象}//移动构造string(string&& s)//右值引用:_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 资源转移" << endl;swap(s); //资源互换}//赋值运算符重载(现代写法)string& operator=(const string& s){cout << "string& operator=(const string& s) -- 深拷贝" << endl;string tmp(s); //用s拷贝构造出对象tmpswap(tmp); //交换这两个对象return *this; //返回左值(支持连续赋值)}//移动赋值string& operator=(string&& s){cout << "string& operator=(const string&& s) -- 移动赋值" << endl;swap(s);return *this; //返回左值(支持连续赋值)}//析构函数~string(){delete[] _str;  //释放_str指向的空间_str = nullptr; //及时置空,防止非法访问_size = 0;      //大小置0_capacity = 0;  //容量置0}//[]运算符重载char& operator[](size_t i){assert(i < _size); //检测下标的合法性return _str[i]; //返回对应字符}//改变容量,大小不变void reserve(size_t n){if (n > _capacity) //当n大于对象当前容量时才需执行操作{char* tmp = new char[n + 1]; //多开一个空间用于存放'\0'strncpy(tmp, _str, _size + 1); //将对象原本的C字符串拷贝过来(包括'\0')delete[] _str; //释放对象原本的空间_str = tmp; //将新开辟的空间交给_str_capacity = n; //容量跟着改变}}//尾插字符void push_back(char ch){if (_size == _capacity) //判断是否需要增容{reserve(_capacity == 0 ? 4 : _capacity * 2); //将容量扩大为原来的两倍}_str[_size] = ch; //将字符尾插到字符串_str[_size + 1] = '\0'; //字符串后面放上'\0'_size++; //字符串的大小加一}//+=运算符重载string& operator+=(char ch){push_back(ch); //尾插字符串return *this; //返回左值(支持连续+=)}//返回C类型的字符串const char* c_str()const{return _str;}private:char* _str;size_t _size;size_t _capacity;};}

再编写一个简单的Person类,Person类中的成员name的类型就是我们模拟实现的string类

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& operator=(const Person& p){if (this != &p){_name = p._name;_age = p._age;}return *this;}//析构函数~Person(){}private:cl::string _name; //姓名int _age;         //年龄};

因为Person类中实现了拷贝构造、拷贝赋值和析构函数等,所以不会默认生成移动构造和移动赋值

int main(){Person s1("张三", 7);Person s2 = s1;//拷贝构造Person s3 = std::move(s1);//移动构造(没有移动构造,就会调用拷贝构造)return 0;}

在这里插入图片描述

ps:由于VS2013没有完全支持C++11,因此上述代码无法在VS2013当中验证,需要使用更新一点的编译器进行验证,比如VS2019

🥑类成员变量初始化

默认生成的构造函数,对于自定义类型的成员会调用其构造函数进行初始化,但并不会对内置类型的成员进行处理。于是C++11支持非静态成员变量在声明时进行初始化赋值,默认生成的构造函数会使用这些缺省值对成员进行初始化

class Person{public://...private://非静态成员变量,可以在成员声明时给缺省值ljj::string _name = "张三"; //姓名int _age = 20;             //年龄static int _n; //静态成员变量不能给缺省值};

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

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成

下面函数实现了拷贝构造

class Person{public://拷贝构造函数Person(const Person& p):_name(p._name), _age(p._age){}private:cl::string _name; //姓名int _age;         //年龄};

这样编译器就无法生成默认的构造函数了,因为默认构造函数生成的条件是没有编写任意类型的构造函数包括拷贝构造函数

这时我们就可以使用default关键字强制生成默认的构造函数

class Person{public:Person() = default; //强制生成默认构造函数//拷贝构造函数Person(const Person& p):_name(p._name), _age(p._age){}private:cl::string _name; //姓名int _age;         //年龄};

主要使用场景:用于构造函数,因为如果我们实现了拷贝构造(也算构造),就不会默认生成构造函数了,需要强制生成

🥑禁止生成默认函数的关键字delete

当我们想要限制某些默认函数生成

  • C++98中,可以将该函数设置成私有,并且只用声明不用定义,这样当外部调用该函数时就会报错。
  • C++11中,可以在该函数声明后面加上=delete,表示让编译器不生成该函数的默认版本,我们将=delete修饰的函数称为删除函数
class Person{public://构造函数Person(const char* name = "", int age = 0):_name(name), _age(age){}//不想要Person对象拷贝Person(const Person& p) = delete;private:ljj::string _name; //姓名int _age;         //年龄};int main(){Person s1("张三", 7);Person s2 = s1;//拷贝构造return 0;}

来道题目:要求delete关键字实现,一个类只能在堆上创建对象

class HeapOnly{public:HeapOnly(){_str = new char[10];}~HeapOnly() = delete;void Destroy(){delete[] _str;//删除开创的空间free(this);//删除ptr指针}private:char* _str;};int main(){HeapOnly* ptr = new HeapOnly;ptr->Destroy();return 0;}

如果是构造时,对象有数据,则要我们手动实现一个类似析构函数的函数,析构要注意两块空间哦:ptr指针和new的空间都要释放了

二. 可变模板参数

C++11 新增一员大将就是可变参数模板,他可以允许可变参数的函数模板和类模板来作为参数,使得参数高度泛化

💦模板定义

函数的可变参数模板定义方式如下:

templatevoid ShowList(Args… args){  //函数体}
  • Args是一个模板参数包,args是一个函数形参参数包
    声明一个参数包Args…args,这个参数包中可以包含0到任意个模板参数

模板参数包 Args 和函数形参参数包 args 的名字可以任意指定,并不是说必须叫做 Args 和 args

int main(){//函数传参就可以传多个不同类型参数了string str("hello");ShowList();ShowList(1);ShowList(1, 'A');ShowList(1, 'A', str);return 0;}

也可以通过sizeof算出参数包中参数的个数:...是在外面的

templatevoid ShowList(Args... args){cout << sizeof...(args) << endl; //获取参数包中参数的个数}

此时最大的难点来了,就是怎么样直接获取参数包中的每个参数?

  • 语法并不支持使用 args[i] 的方式来获取参数包中的参数,只能通过展开参数包的方式来获取,这是使用可变参数模板的一个主要特点

下面是错误示范:(语法不支持!)

templatevoid ShowList(Args... args){cout << sizeof...(args) << endl;//错误示例:for (int i = 0; i < sizeof...(args); i++){cout << args[i] << " "; //打印参数包中的每个参数}cout << endl;}

💦参数包的展开

😎递归函数方式展开

该方法大概分为三步:

  • 给函数模板增加一个模板参数,从接收的参数包中把第一个参数分离出来
  • 在函数模板中递归调用该函数模板,调用时传入剩下的参数包
  • 直到递归到参数包为空,退出递归
//可变参数的函数模板 templatevoid ShowList(const T& val, Args... args){cout << val << " ";//打印分离出的第一个参数ShowList(args...);//继续递归调用}//递归至空,终止函数void ShowList(){cout << endl;}int main(){string str("hello");ShowList(1, 'A', str);return 0;}

在这里插入图片描述

那如果我们不使用递归调用,该怎么样写呢?

😎逗号表达式展开

逗号表达式规则是会从左到右依次计算各个表达式,并将最后一个表达式的值作为返回值返回,我们将最后一个表达式设为整型值,确保最后返回的是一个整型

将处理参数个数的动作封装成一个函数,将该函数作为逗号表达式的第一个表达式

template void PrintArg(const T& x){cout << x << " ";}template void ShowList(Args... args){int a[] = { (PrintArg(args), 0)... };//开创输入参数个整数类型的数组}

我们要的是打印出参数包中的各个参数,因此处理函数当中要做的就是将传入的参数进行打印即可

ps:可变参数的省略号...需要加在逗号表达式外面,表示需要先将逗号表达式展开,如果直接加在 args 后面,那么参数包将会被展开后全部传入 PrintArg,会展开成 {(PrintArg(arg1), 0), (PrintArg(arg2), 0), (PrintArg(arg3), 0), etc…}

在这里插入图片描述

当然我们不使用逗号表达式也是可以的,这里的问题是初始化整型数组时必须用整数,那我们可以将处理函数的返回值设为整型,然后用这个返回值去初始化整型数组也是可以的:

void ShowList(){cout << endl;}//处理函数templateint PrintArg(const T& t)//返回值为int类型{cout << t << " ";return 0;}//展开函数templatevoid ShowList(Args... args){int arr[] = { PrintArg(args)... }; //列表初始化cout << endl;}

三. emplace

C++11 给 STL 容器增加 emplace 的插入接口,比如 list 容器的 push_front、push_back 和insert 函数,都有了对应的 emplace_front、emplace_back 和 emplace 函数:

在这里插入图片描述

这些emplace版本的插入接口支持模板的可变参数,比如vector容器的emplac函数的声明如下:

在这里插入图片描述

✨使用方法

调用 push_back 插入元素时,可以传入左值对象或右值对象,也可以使用列表进行初始化;调用emplace_back 插入元素时,也可以传入左值对象或右值对象,但不可以使用列表进行初始化

emplace系列接口最大的特点就是,插入元素可传入用于构造元素的参数包

int main(){//对于整形:没有区别vector v;v.push_back(1);v.emplace_back(2);//对于pair类型数据vector> v1;v1.push_back(make_pair("sort", 1));v1.emplace_back("sort", 1);return 0;}

那么emplace会比push_back更加高效吗?不一定!

✨工作原理

emplace 接口先通过空间配置器为新结点获取一块内存空间,注意这里只会开辟空间,不会自动调用构造函数对这块空间进行初始化

  • 调用 allocator_traits::construct 函数对这块空间进行初始化,调用该函数会传入这块空间的地址和用户传入的参数,注意要完美转发
  • 在 allocator_traits::construct 中会使用定位 new 表达式,显示调用构造函数对这块空间进行初始化,调用构造函数时会传入用户传入的参数,这里同样需要完美转发

✨意义

emplace 接口的可变参数模板类型都是万能引用,因此既可以接收左值,也可以接收右值,还可以接收参数包

  • 调用 emplace 接口时传入的是参数包,就可以直接调用函数进行插入,并最终使用定位 new 表达式调用构造函数对空间进行初始化时,匹配到构造函数
  • 调用 emplace 接口时传入的是左值or右值,首先需要先在此之前调用构造函数实例化出一个对象,最后使用定位 new 表达式调用构造函数对空间进行初始化时,会匹配到拷贝构造函数or拷贝构造

总的来说:如果传入参数包,只需要调用构造函数

在这里插入图片描述

emplace 最大特点就是支持传入参数包,用这些参数包直接构造出对象,这样就能减少一次拷贝,这就是为什么有人说 emplace 系列接口更高效的原因

emplace 真正高效的情况是传入参数包的时候, 直接通过参数包构造出对象,避免了中途的一次拷贝 直接通过参数包构造出对象,避免了中途的一次拷贝

举例演示:

class Date{public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){cout << "构造函数" << endl;}Date(const Date& d):_year(d._year), _month(d._month), _day(d._day){cout << "拷贝构造" << endl;}Date& operator=(const Date& d){cout << "赋值重载" << endl;return *this;}private:int _year;int _month;int _day;};int main(){list lt1;lt1.push_back(Date(2023, 2, 3));cout << endl;lt1.emplace_back(2023, 2, 3);}

在这里插入图片描述

所以验证了emplace_back是比push_back要少一次拷贝构造

📢写在最后

请添加图片描述

来源地址:https://blog.csdn.net/qq_42996461/article/details/128828649

--结束END--

本文标题: 【C++】C++11语法 ~ 可变参数模板

本文链接: https://www.lsjlt.com/news/372509.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 【C++】C++11语法 ~ 可变参数模板
    🌈欢迎来到C++专栏~可变参数模板 ...
    99+
    2023-08-17
    c++ java 开发语言
  • C++11可变参数模板怎么使用
    本篇内容主要讲解“C++11可变参数模板怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++11可变参数模板怎么使用”吧!可变参数函数C语言中,可变参数函数可以说是一个比较神奇的存在。例...
    99+
    2023-06-19
  • C++11中的可变参数模板/lambda表达式
    目录1.可变参数模板递归函数方式展开参数包逗号表达式展开参数包2.lambda表达式先来看看lambda表达式的例子:lambda表达式语法1.可变参数模板 C++11的新特性可变参...
    99+
    2023-03-24
    C++11 lambda表达式 C++11 可变参数模板
  • C++11可变参数模板的参数转发举例分析
    本篇内容主要讲解“C++11可变参数模板的参数转发举例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++11可变参数模板的参数转发举例分析”吧!实例很多软件系统都存在日志(log)功能,通...
    99+
    2023-06-19
  • C++可变参数模板深入深剖
    目录概念模板定义参数包展开递归函开逗号表达式展开emplace使用方法工作原理意义总结概念 C++11 新增一员猛将就是可变参数模板,他可以允许可变参数的函数模板和类模板来作为参数,...
    99+
    2022-11-13
    c++ 可变参数模板 C++ 可变参数 c++11 可变参数模板
  • C++11新特性之变长参数模板详解
    目录C++11 变长参数模板变长函数参数包如何解参数包sizeof()获得函数参数个数递归模板函数变参模板展开结论C++11 变长参数模板 在C++11之前,无论是类模板 还是函数...
    99+
    2022-11-12
  • C++11中的可变参数模板和lambda表达式怎么使用
    本篇内容介绍了“C++11中的可变参数模板和lambda表达式怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1.可变参数模板C++1...
    99+
    2023-07-05
  • 浅析C++可变参数模板的展开方式
    目录前言可变参数模板的定义参数包的展开递归函数方式展开逗号表达式展开enable_if方式展开折叠表达式展开(c++17)总结前言 可变参数模板(variadic templates...
    99+
    2022-11-13
  • C++11模板函数的默认模板参数举例分析
    这篇文章主要介绍“C++11模板函数的默认模板参数举例分析”,在日常操作中,相信很多人在C++11模板函数的默认模板参数举例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C++11模板函数的默认模板参数举...
    99+
    2023-06-19
  • c++可变参数模板使用示例源码解析
    目录前言认识可变模板参数使用可变模板参数递归法特例化包拓展完美转发总结前言 我们知道,C++模板能力很强大,比起Java泛型这种语法糖来说,简直就是降维打击。而其中,可变参数模板,...
    99+
    2023-01-13
    c++可变参数模板 c++可变参数
  • C++可变参数模板的展开方式是什么
    这篇文章主要讲解了“C++可变参数模板的展开方式是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++可变参数模板的展开方式是什么”吧!可变参数模板(variadic templates...
    99+
    2023-06-29
  • C语言可变参数函数详解
    目录C语言可变参数函数总结C语言可变参数函数 C 语言允许定义参数数量可变的函数,这称为可变参数函数(variadic function)。这种函数需要固定数量的强制参数(manda...
    99+
    2022-11-12
  • C/C++可变参数的使用
    可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是int,double还可以是char*,类,结构体等等。可变参数是实现printf(),sprintf()...
    99+
    2022-11-15
    可变参数 C C++
  • 一篇文章让你彻底明白c++11增加的变参数模板
    目录前言1. 什么是变参数模板2. 变参数模板的基础-模板形参包2.1 非类型模板形参包2.2 类型模板形参包2.3 模板模板形参包3. 模板形参包的延伸-函数形参包4. 模板形参包...
    99+
    2022-11-12
  • C语言进阶可变参数列表
    可变参数 可变参数是C语言提供的一种参数可变的机制,咱希望函数带有可变数量的参数,而不是预定义数量的参数。它允许咱定义一个函数,能根据具体的需求接受可变数量的参数,比如这种: int...
    99+
    2022-11-13
  • C++11中的变长模板的示例详解
    目录1.C99中的变长函数2.C++11中的变长函数3.详解变长模板3.1 更一般的SFINAE规则3.2 模板参数包的概念3.3 三个简单的例子3.4 函数参数包3.5 包扩展的进...
    99+
    2023-02-06
    C++11变长模板 C++ 变长模板 C++11 模板
  • C++新标准难点解析之什么是可变模板参数
    本篇内容介绍了“C++新标准难点解析之什么是可变模板参数”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! 前言C++的新特性--可变...
    99+
    2023-06-15
  • C/C++中可变参数的用法详细解析
    可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是int,double还可以是char*,类,结构体等等。可变参数是实现printf(),sprintf()...
    99+
    2022-11-15
    C 可变参数
  • C语言中可变参数如何使用
    C语言中可变参数如何使用,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。一、什么是可变参数我们在C语言编程中有时会遇到一些参数个数可变的函数,例如printf()...
    99+
    2023-06-17
  • 浅析C/C++中的可变参数与默认参数
    千万要注意,C不支持默认参数 C/C++支持可变参数个数的函数定义,这一点与C/C++语言函数参数调用时入栈顺序有关,首先引用其他网友的一段文字,来描述函数调用,及参数入栈: ---...
    99+
    2022-11-15
    可变参数 默认参数
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作