【c++11】c++11新特性(下)(可变参数模板、default和delete、容器新设定、包装器)

news2025/4/26 7:03:11

🌟🌟作者主页:ephemerals__
🌟🌟所属专栏:C++

目录

前言

五、可变参数模板

1. 概念及简单定义

2. 包扩展

六、 default和delete

七、容器新设定

1. 新容器

2. 新接口

emplace系列接口

八、函数包装器

1. function

2. bind

总结


前言

        之前我们学习了c++11的部分新特性:列表初始化、右值引用、类的新默认成员函数和lambda表达式等:

【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)-CSDN博客

本篇文章,我们继续学习其余几个:可变参数模板、default和delete、容器新设定、包装器

正文开始

五、可变参数模板

1. 概念及简单定义

        所谓可变参数模板,就是指模板可以接收可变数量的参数。通过处理不同数量的参数类型,代码的灵活性和重用性大大提高。

可变参数模板可以用于类模板,也可用于函数模板。

可变参数模板不仅支持类型实例化,也可支持个数实例化。(参数数量可以变化)

当然,可变参数的函数模板当中,其函数的参数也是可变的。

接下来我们定义一个简易的可变参数模板:

#include <iostream>
using namespace std;

//可变参数模板
template<class ...Args>
void print(Args... args)
{
    cout << sizeof...(args) << endl;
}

int main()
{
    print(1);
    print(1, 2, 3);
    print('a', "xxxx", 3.5, 9);
    return 0;
}

这里有几点需要注意:

1. 模板参数定义时,要在前面加上“...”,表示这是一个可变模板参数。这里的Args叫做“模板参数包”,它可以接收多个任意类型的参数,在编译时按需实例化。

2. 函数参数的类型使用模板参数包进行定义,其变量名args叫做“函数参数包”,表示多个函数形参。注意函数参数包前面要加上“...”。

3. 模板参数包和函数参数包可以任意取名,不一定非得是“Args”或者“args”。

4. 函数体当中,我们使用了一个新的运算符“sizeof...”它用于计算函数参数包中的参数个数

程序运行结果:

这里我们调用了三次print函数,这三次调用使得可变参数模板的形参列表被分别实例化为:

void print<int>(int args);
void print<int, int, int>(int args, int args, int args);
void print<char, const char *, double, int>(char args, const char *args, double args, int args);

因此,可变参数模板和普通模板的原理还是类似的,本质还是按需实例化,只不过额外支持了参数个数的实例化。

当然,可变参数模板的函数形参也可以用左值引用或右值引用表示,其折叠规则与普通模板相同。

void print(Args... args)
void print(Args&... args)
void print(Args&&... args)

2. 包扩展

        刚才我们定义的可变参数模板当中,只是计算了参数的个数并输出,若只能如此的话,未免也太缺乏实用性了。那么能否对每一个参数进行相应的处理呢? 这里就要使用“包扩展”了。

        所谓“包扩展”,是c++11引入的一个机制,用于分解出函数参数包中的一个个参数。要实现包扩展,就要使用包扩展运算符“...”

一个简单的使用示例:

#include <iostream>
using namespace std;

//可变参数模板
template<class ...Args>
void print(Args... args)
{
    (cout << ... << args) << endl;
}

int main()
{
    print(1, 2, 'a');
    return 0;
}

运行结果:

这里我们使用了“...”将args进行扩展,然后一个个地输出。但这种方法的原理较难理解,并且灵活性也较差,所以我们通常用编译时递归实例化的方式进行包扩展。示例如下:

#include <iostream>
using namespace std;

void ShowList()//递归出口,无参
{
        cout << endl;
}

template<class T, class ...Args>
void ShowList(T&& x, Args&&... args)//接收第一个参数和剩余参数包
{
        cout << x << ' ';//打印第一个参数
        ShowList(forward<Args>(args)...);//将剩余参数包一个个展开,并传入,递归调用
}


template<class ...Args>
void Print(Args&&... args)
{
        ShowList(forward<Args>(args)...);//将参数包一个个展开,并传入
}

int main()
{
        Print(1, 2, 'a');//函数调用
        return 0;
}

运行结果: 

不难发现,这里的扩展方法十分巧妙:先使用Print将多个参数打包成函数参数包,然后使用“...”进行扩展,并传入ShowList。ShowList也是一个可变参数模板,它将接收到的参数分解成两部分:第一个参数和剩余部分,并将剩余部分打包成一个新的参数包。函数体内部打印了第一个参数后,将剩余参数包再传给自己去调用,这样递归地传入下去,编译器在编译时一个个地按需实例化对应版本,就能够一个一个地输出所有参数了。而当所有参数都被打印后,再次传给自己的参数包就是无参的,此时由于我们定义了一个同名无参函数当函数模板和同名函数同时符合参数要求时,会优先调用同名函数,这样就相当于递归出口,终止了函数模板的递归调用。

注:这里将参数进行完美转发后传入,是为了保持右值属性,减少递归时的拷贝。

需要注意:传入参数包时,一定要配合包扩展运算符,将参数包分解之后再传入;注意将包括展运算符与参数列表中的“...”区分开,它们不是同一个含义;包扩展运算符要写在参数包之后。

当然,递归式的包扩展也可以这样实现,原理是一样的:

template <class T>
void Print(T&& x) 
{
    cout << x << ' ';
}

template <class T, class ... Args>
void Print(T&& x, Args&&... args) 
{
    cout << x << ' ';
    Print(forward<Args>(args)...);
}

六、 default和delete

        c++11引入了default和delete这两个关键字(用于类的定义中),它们的作用是对类的默认成员函数进行显式的控制。

        假如你要使用某个默认生成的成员函数,但由于一些原因,并没有默认生成,此时你就可以使用default强制生成对应默认成员函数。使用示例:

class A
{
public:
    A(const A& a)//只显式实现了拷贝构造,因此不会生成默认的移动构造和构造函数
    {
        //...
    }

    A(A&& a) = default;//强制生成移动构造
    A() = default;//强制生成默认构造函数
private:
    //...
};

        如果你想限制某些默认成员函数,使其不可被外界显式调用,可以将其设置为私有成员,也可以使用delete关键字,将其强制删除。示例:

class A
{
public:
    A& operator=(const A& a) = delete;//强制删除赋值重载

private:
    //...
};

注:强制删除某个默认成员函数后,该成员函数也不可显式实现。

七、容器新设定

1. 新容器

        c++11引入了几个新的STL容器,例如array(定长数组)forward_list(单链表)unordered系列容器。其中最为实用的还是unordered系列容器,可以将哈希的高效性充分融入到实际开发中。

c++11 STL容器介绍与使用方法查阅:Reference - C++ Reference

2. 新接口

        由于右值引用initializer_list的出现,c++11还引入了容器插入、拷贝、初始化相关的全新重载版本,提高了效率。

emplace系列接口

        c++11还引入了容器的emplace系列接口,相比push_back、insert等接口,它的特点是直接在容器内部构造元素,而不是而不是先创建临时对象再拷贝或移动到容器中,更加高效

举个例子:

#include <iostream>
#include <list>
#include <string>
using namespace std;

int main()
{
    list<string> l;
    l.push_back("hello world");
    l.emplace_back("hello world");
    return 0;
}

这里我们分别调用push_back、emplace_back给list尾插两个字符串,相比push_back,emplace_back会更加高效。因为调用push_back时,首先会将"hello world"构造为string类型得临时对象,然后再执行插入操作,最后析构这个string的临时对象;而emplace_back是一个可变参数模板,传入"hello world"之后,被实例化为const str*类型,之后直接用字符串构造出一个节点,避免产生string的临时对象,效率更高。

并且,由于其是可变参数模板,所以也支持多个参数传入,并构造节点:

list<pair<string, int>> l;
l.push_back({ "苹果",1 });
l.emplace_back("苹果", 1); // 多参数传入

当然,许多容器除了emplace_back,还有emplace_frontemplace接口,分别支持头插和任意位置的插入操作。

八、函数包装器

        函数包装器是c++11引入的工具,它可以对函数进行“封装”,后续需要时调用,功能类似于函数指针,且更加安全。c++11的函数包装器一共有两个:functionbind

1. function

        function本质是一个类模板,包含在头文件<functional>中,它可以接收一个可调用对象,例如函数指针、lambda表达式、仿函数对象等,并可以通过function调用它。这样就可以将不同类型的可调用对象统一化。示例:

#include <iostream>
#include <functional>
using namespace std;

//函数
int f1(int a, int b)
{
    return a + b;
}

//仿函数
struct F2
{
    int operator()(int a, int b)
    {
        return a - b;
    }
}f2; 

//lambda表达式
auto f3 = [](int a, int b)->int{ return a * b; };

int main()
{
    //定义包装器,其接收的可调用对象,参数为两个int,返回值为int
    function<int(int, int)> f;

    //使用包装器调用可调用对象
    f = f1; // 接收函数
    cout << f(3, 5) << endl;

    f = f2; // 接收仿函数对象
    cout << f(3, 5) << endl;

    f = f3; // 接收lambda表达式
    cout << f(3, 5) << endl;
    return 0;
}

运行结果:

定义function时,注意要显式实例化要接收对象的参数和返回值;调用时,直接用函数调用的方式进行传参。

注意:用function包装非静态成员函数时,别忘记第一个参数是this指针,显式实例化时要将对象指针类型写在参数列表中:

#include <iostream>
#include <functional>
using namespace std;

class A
{
public:
    A(int n, int m)
        :_n(n)
        , _m(m)
    {}
    void Print()
    {
        cout << _n + _m << endl;
    }
private:
    int _n;
    int _m;
};

int main()
{
    A a(1, 2);
    function<void(A*)> f1 = &A::Print; // 包装成员函数Print

    f1(&a); // 调用时传入对象地址
    return 0;
}

当然,参数也可以不设置为对象指针,可以直接设置成对象本身或对象的引用类型,传参时传入对象即可:

function<void(A)> f2 = &A::Print;
f2(a);

function<void(A&)> f3 = &A::Print;
f3(a);

function<void(A&&)> f4 = &A::Print;
f4(move(a));

2. bind

        bind的本质是一个函数模板, 包含在头文件<functional>中,它也可以接收可调用对象,但与function不同的是,它可以返回一个在原对象基础上调整传参顺序增加默认参数的可调用对象,更加灵活。

它的基本使用方式如下:

#include <iostream>
#include <functional>
using namespace std;
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;

void fun(int a, int b, int c)
{
    cout << a << endl;
    cout << b << endl;
    cout << c << endl;
}

int main()
{
    auto f = bind(fun, _1, _2, _3); // 包装fun函数

    //函数调用
    f(1, 2, 3);
    return 0;
}

运行结果:

使用bind包装fun函数时,bind的参数列表第一个位置是fun函数的地址其余位置表示要传入的参数的代号(注意这个代号在std的placeholders空间中,这里为了方便直接展开了)。然后用一个auto变量接收bind的返回值,就可以对fun函数进行调用。

改变bind的代号的顺序,就可以调整传参顺序:

int main()
{
    auto f = bind(fun, _1, _3, _2); // 调整第二个参数和第三个参数的相对位置

    //函数调用
    f(10, 20, 30);
    return 0;
}

运行结果:

这里我们在包装fun时,交换了第三个参数和第二个参数的相对位置,调用fun时的参数就会按照包装时的顺序进行传入,而不是从左到右的顺序,这样可以使一些原型不一致的函数进行适配

在bind代号位置传入初值,就可以为任意参数设置初值:

int main()
{
    auto f = bind(fun, _1, 5, _2); // 给第二个参数设置初值5,注意这里的_2不是表示函数的第二个参数,而是调用时传入的第二个参数

    //函数调用
    f(10, 20);
    return 0;
}

运行结果:

相比缺省参数,它的好处是可以给任意位置的参数设置初值,而缺省参数只能从右到左设初值。 

有了bind之后,我们就可以绑死一些参数,例如成员函数的对象,这样每次调用成员函数就不用显式写明对象了。

总结

        本篇文章我们学习了c++11的四个新语法:可变参数模板、default和delete、容器的新设定和函数包装器。它们大大提高了c++编程的灵活性,也提高了我们的开发效率。之后博主会和大家分享智能指针以及其他包装器的相关知识和用法。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2343079.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PyTorch 实现食物图像分类实战:从数据处理到模型训练

一、简介 在计算机视觉领域&#xff0c;图像分类是一项基础且重要的任务&#xff0c;广泛应用于智能安防、医疗诊断、电商推荐等场景。本文将以食物图像分类为例&#xff0c;基于 PyTorch 框架&#xff0c;详细介绍从数据准备、模型构建到训练测试的全流程&#xff0c;帮助读者…

Qt —— 在Linux下试用QWebEngingView出现的Js错误问题解决(附上四种解决办法)

错误提示:js: A parser-blocking, cross site (i.e. different eTLD+1) script, https:xxxx, is invoked via document.write. The network request for this script MAY be blocked by the browser in this or a future page load due to poor network connectivity. If bloc…

命名空间(C++)

命名空间主要用于大型项目中。 局部命名在该局部会覆盖全局命名。C语言中唯一一种在局部调用全局相同命名的全局变量的方式&#xff1a;指针在C中可以用作用域运算符来访问全局变量&#xff0c;作用域运算符的前面可以是作用域也可以是类。 命名空间实际上是对全局作用域的再次…

LabVIEW圆锥滚子视觉检测系统

基于LabVIEW平台的视觉检测系统提高圆锥滚子内组件的生产质量和效率。通过集成高分辨率摄像头和先进的图像处理算法&#xff0c;系统能够自动识别和分类产品缺陷&#xff0c;从而减少人工检查需求&#xff0c;提高检测的准确性和速度。 ​​ ​ 项目背景 随着制造业对产品质…

OpenAI 推出「轻量级」Deep Research,免费用户同享

刚刚&#xff0c;OpenAI 正式上线了面向所有用户的「轻量级」Deep Research 版本&#xff0c;意味着即便没有付费订阅&#xff0c;也能体验这一强大工具的核心功能。 核心差异&#xff1a;o4-mini vs. o3 模型迭代 传统的深度研究功能基于更大规模的 o3 模型。轻量级版本则改以…

罗伯·派克:Go语言创始者的极客人生

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 罗伯派克&#xff1a;Go语言创始者的极客人生 一、传奇程序员的成长历程 1. 早年经历…

小白工具视频转MPG, 功能丰富齐全,无需下载软件,在线使用,超实用

在视频格式转换需求日益多样的今天&#xff0c;小白工具网的在线视频转 MPG 功能https://www.xiaobaitool.net/videos/convert-to-mpg/ &#xff09;脱颖而出&#xff0c;凭借其出色特性&#xff0c;成为众多用户处理视频格式转换的优质选择。 从格式兼容性来看&#xff0c;它支…

day32 学习笔记

文章目录 前言一、霍夫变换二、标准霍夫变换三、统计概率霍夫变换四、霍夫圆变换 前言 通过今天的学习&#xff0c;我掌握了霍夫变换的基本原本原理及其在OpenCV中的应用方法 一、霍夫变换 霍夫变换是图像处理中的常用技术&#xff0c;主要用于检测图像中的直线&#xff0c;圆…

CentOS 7上Memcached的安装、配置及高可用架构搭建

Memcached是一款高性能的分布式内存缓存系统&#xff0c;常用于加速动态Web应用的响应。本文将在CentOS 7上详细介绍Memcached的安装、配置&#xff0c;以及如何实现Memcached的高可用架构。 &#xff08;1&#xff09;、搭建memcached 主主复制架构 Memcached 的复制功能支持…

如何让 HTML 文件嵌入另一个 HTML 文件:详解与实践

目录 一、为什么需要在HTML中嵌入其他HTML文件&#xff1f; 二、常用的方法概览 三、利用 1. 基本原理 2. 使用场景 3. 优缺点 4. 实践示例 5. 适用建议 四、利用JavaScript动态加载内容 1. 原理简介 2. 实现步骤 示例代码 3. 优缺点分析 4. 应用场景 5. 实践建…

人工智能与机器学习:Python从零实现逻辑回归模型

&#x1f9e0; 向所有学习者致敬&#xff01; “学习不是装满一桶水&#xff0c;而是点燃一把火。” —— 叶芝 我的博客主页&#xff1a; https://lizheng.blog.csdn.net &#x1f310; 欢迎点击加入AI人工智能社区&#xff01; &#x1f680; 让我们一起努力&#xff0c;共创…

windows服务器及网络:搭建FTP服务器

前言&#xff1a;&#xff08;各位大佬们&#xff0c;昨天太忙了&#xff0c;整得没有发布昨天那该写的那一篇&#xff0c;属实有点可惜的说QAQ&#xff0c;不过问题已经解决&#xff0c;我又回来啦&#xff09; 今天我要介绍的是在Windows中关于搭建FTP服务器的流程与方法 注…

欧拉计划 Project Euler56(幂的数字和)题解

欧拉计划 Project Euler 56 题解 题干思路code 题干 思路 直接暴力枚举即可&#xff0c;用c要模拟大数的乘法&#xff0c;否则会溢出 code // 972 #include <bits/stdc.h>using namespace std;using ll long long;string mul(const string &num1, int num2) {int…

C++初窥门径

const关键字 一、const关键字 修饰成员变量 常成员变量&#xff1a;必须通过构造函数的初始化列表进行初始化&#xff0c;且初始化后不可修改。 示例&#xff1a; class Student { private: const int age; // 常成员变量 public: Student(string name, int age) : age(ag…

AlarmClock4.8.4(官方版)桌面时钟工具软件下载安装教程

1.软件名称&#xff1a;AlarmClock 2.软件版本&#xff1a;4.8.4 3.软件大小&#xff1a;187 MB 4.安装环境&#xff1a;win7/win10/win11(64位) 5.下载地址&#xff1a; https://www.kdocs.cn/l/cdZMwizD2ZL1?RL1MvMTM%3D 提示&#xff1a;先转存后下载&#xff0c;防止资…

白鲸开源WhaleStudio与崖山数据库管理系统YashanDB完成产品兼容互认证

近日&#xff0c;北京白鲸开源科技有限公司与深圳计算科学研究院联合宣布&#xff0c;双方已完成产品兼容互认证。此次认证涉及深圳计算科学研究院自主研发的崖山数据库管理系统YashanDB V23和北京白鲸开源科技有限公司的核心产品WhaleStudio V2.6。经过严格的测试与验证&#…

【金仓数据库征文】- 金融HTAP实战:KingbaseES实时风控与毫秒级分析一体化架构

文章目录 引言&#xff1a;金融数字化转型的HTAP引擎革命一、HTAP架构设计与资源隔离策略1.1 混合负载物理隔离架构1.1.1 行列存储分区策略1.1.2 四级资源隔离机制 二、实时流处理与增量同步优化2.1 分钟级新鲜度保障2.1.1 WAL日志增量同步2.1.2 流计算优化 2.2 物化视图实时刷…

Windows与CasaOS跨平台文件同步:SyncThing本地部署与同步配置流程

文章目录 前言1. 添加镜像源2. 应用安装测试3. 安装syncthing3.1 更新应用中心3.2 SyncThing安装与配置3.3 Syncthing使用演示 4. 安装内网穿透工具5. 配置公网地址6. 配置固定公网地址 推荐 ​ 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽…

59、微服务保姆教程(二)Nacos--- 微服务 注册中心 + 配置中心

Nacos— 微服务 注册中心 + 配置中心 一.什么是Nacos? Nacos是阿里的一个开源产品,是针对微服务架构中的服务发现、配置管理、服务治理的综合型解决方案。 Nacos核心定位是“一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台”,也就是我们的注册中心和配…

第一部分:git基本操作

目录 1、git初识 1.1、存在的问题 1.2、版本控制器 1.3、git安装 1.3.1、CentOS平台 1.3.2、ubuntu平台 2、git基本操作 2.1、创建仓库 2.2、配置git 3、工作区、暂存区、版本库 4、基本操作 4.1、场景一 4.2、场景二 4.3、修改文件 5、版本回退 6、撤销修改 …