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

C++11语法(2)

        一、引言

        上期小编讲解了列表初始化、右值引用。接下来我们要解密之前提到过得emplace和emplace_back函数的秘密。小编打算在这篇文章中讲C++参数包(和C语言的不定参数做做比较),可以理解为C++版的不定参数。如何写出emplace和emplace_back函数。

        下次小编打算讲解lambda表达式、包装器、捆绑器。为了方便描述,也会讲仿函数(以前应该也讲过,再提提好理解些。)。

        二、        C++11中的参数包解析

                C++既然想单独搞出一个语言,肯定就会扩大他的兼容性。对于关键性的语法提供更便捷、的使用方法。孩子长大了毕竟也是要走自己的路的。C++虽然是继承最好的语言,但是他并不想走C语言那一套,因为C语言那一套不好走。

                2.1        C语言中的不定参数

                那我们先来谈谈C语言中的不定参数吧。相信各位大佬曾经都看到过。下面的函数。

printf(const char* argv,...);

                这个函数非常奇怪,因为它的参数可以传很多个,只要你愿意你可以传递一百个、一千个、一万个。和孙悟空的金箍棒一样可大可小、可长可短。

                它(...)的本质是一种宏,一般是由__VA_ARGS__宏来进行参数代替。

                但是很快C语言设计者发现仅仅让__VA_ARGS__宏去代替(...),当传入不定参数为空时,因为','逗号的原因,不能编译通过。因为根据C语言的语法','是用来分隔的,但是在这里没有起到分隔的作用。所以报错

printf("随便写的案例",);

                C语言设计者为了取消这个逗号,单独为##新增了一项功能,放在__VA_ARGS__宏前,当不定参数为空时,取消不定参数前的逗号。也就变成下面这样了。

printf("随便写的案例",);

                下面是一些参考案例。利用printf中字符串可以自动合并为一个字符串。旨在使用不定参数打印出固定格式的信息。

#include <stdio.h>
// #include <>// 如果不加##,则传参为单参数,也就是参数包为空时,发生编译错误。
// __VA_ARGS__
// printf函数中字符串可以合并。"字符串"+另一个字符串。
//#define LOG(message,...) printf("%s-%d"message,__FILE__,__LINE__,__VA_ARGS__);
#define LOG(message,...) printf("[%s-%d]"message"\n",__FILE__,__LINE__,##__VA_ARGS__);int main()
{LOG("%s,%d,%s","爽",666,"哈哈哈");return 0;
}

                参数传递到函数中又要如何使用呢?首先它肯定不是直接在函数的参数列表中展开了。不然他怎么会用宏来进行替换呢?

                这里就要介绍它的系统调用va_start()函数、va_arg()函数、va_end()、va_copy()函数。头文件为<stdarg.h>。这个很好理解参数就是argument。std就是standard标准的意思。头文件的含义就是标准化参数的意思。

                那根据上图我们来说明一下这些函数的含义。其实不要看它像函数,其实它的本质就是宏,但是为了方便表述,小编还是称他们为函数。

                va_start()函数:初始化函数。使用该函数时,需要创建一个va_list类型的变量并传递。函数的含义是在环形链表根据last所传递的位置号的下一个开始。比如说我传递的参数包有九个参数,位置号为9,则起始指针从位置1开始。其实大家只需要了解原理即可就行了。传递给va_start()函数时只需要传递参数包含有多少个变量就行了。

                va_arg()函数:将参数从参数包中取出来。但是有个非常非常不好的特点就是他需要我们提供参数的类型才能取出变量出来。C++委员会也觉得非常不好用,毕竟C++都支持模板这种高科技了,还要什么确定类型,直接让编译器自己去推导,以前不让编译器去推,实在是以前的编译器不行。使用方法将传入va_start()函数初始化的va_list变量传给va_arg()函数。

                va_end()函数:回收va_list()函数初始化的va_list类型变量。

                va_copy()函数:从名字也可以看出这是一个拷贝类型的函数。第一个参数是要拷贝到的变量(也就是目的地destination),第二个参数是要复制的变量(也就是源source)。

                接下来给大家上代码实例。小编使用模拟printf固定格式打印。

#include <stdio.h>
#include <stdarg.h>void xprint(int count,...)
{va_list ap;// 第count个数字的下一个数字。va_start(ap,count);for(int i = 0;i < count;++i){printf("xprint[%d]:%d\n",i,va_arg(ap,int));}va_end(ap);
}int main()
{xprint(9,1,2,3,4,5,6,7,8,9);return 0;
}

        2.2        C++11中的参数包

                C++认为参数包不用太在意类型,因为可以让编译器推导出类型。直接用模板,如果怕传递其他函数参数时该函数运行造成编译器推导错误类型,我们可以用完美转发。

std::forward<类型>(要传递的对象);

                C++认为参数包最好还是和模板一起使用比较方便。所以直接将参数包归类为模板的一部分了。毕竟C语言将不定参数归类为宏。C语言中我们会用typedef之类的类型定义实现类似单模板的功能(为什么是单模板因为只能用一个类型。用其他类型的话会造成冲突,编译器不知道你说的是哪一个函数。)。或者是宏定义#define定义出规定的数据。在C语言中宏定义、类型定义相当于是低配版的模板,在struct结构体中内置其他类型的struct结构体或是指针,则相当于低配版的继承和多态。

template<class ...Args>

                上式就是参数包的定义,template里面用class或是typename定义模板都可以,参数包的名字也可以随便取一个。但是表明是参数包类型的符号是一定要带的(...)。(...)在前面代表着未展开状态。(...)在后面代表着展开状态。同时参数包为空时我们传递类型为空也是有些许小问题的。我们一般展开参数包,再进行函数递归传递参数包中的新参数。

                那如果参数包为空呢?我们在使用过程中并不知道参数包具体包含了多少参数。因为它并没有要求我们传递参数包所含的参数个数,所以我们使用时一般是不会在意参数包所包含的参数个数,参数包所含的类型。

                解决方案一:sizeof(...(args))判断参数包中是否还包含着参数。

                解决方案二:利用C++中的重载函数的特性。设计一个重载(与使用参数包的函数构成同名的)函数,无参函数。如果参数包为空,就会走到重载无参函数内。就不会发生疯狂死循环、递归的情况。

                下面是实例代码。也是模拟printf进行自定义格式化打印。

#include <iostream>void Print()
{std::cout << std::endl;
}template < typename T , typename ...Args >
void Print(T&& message,Args &&...args)
{std::cout << message << "  ";if(sizeof ...(args) > 0){Print(std::forward<Args>(args)...);}else{Print();}
}int main()
{Print(1,2,3,4,5,6,7,8,9,10,11,12,13);return 0;
}

        三、        类中的emplace和emplace_back函数

                讲完引用折叠、右值引用、万能引用、参数包emplace和emplace_back函数就好讲了。利用引用折叠,我们使用的引用类型的'&'不可能超过2确保我们不会出现类型错误。万能引用可以让我们自如的使用左值引用、右值引用。因为右值引用可以引用常量、表达式,必要的时候还可以将左值move转化为右值,实现右值拷贝。减少拷贝。还有右值引用可以直接去参加容器中其他类的拷贝构造,无需构造对象实现拷贝构造,也就是说我们使用万能引用时最好的。

                参数包的使用毫无疑问,可以我们可以一次性通过参数包,构建多个对象,在容器中插入。那我们如果将参数包带上万能引用呢?是不是就可以减少容器所存储类的拷贝构造。

                我们只要让容器支持万能引用构造,再在原本的插入方式上封装一个接口,接口调用参数包+万能引用就可以了。因为比较复杂就直接将vector、list中的emplace和emplace_back已更新在vector和list模拟实现的链接中了。

C++STL容器List的模拟实现-CSDN博客

vector的模拟实现-CSDN博客

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

相关文章:

  • Flink Redis维表:Broadcast Join与Lookup Join对比及SQL示例
  • 正则表达式解析(二)
  • pdftk - macOS 上安装使用
  • 【读代码】深度解析 Researcher:开源自动化科研助手
  • 企业级AI大模型后端基础设施管理:从理论到实践的全链路指南
  • 5 重复匹配
  • WPS文字和Word:不只是表格,段落也可以排序
  • gpt-5与gpt-5-fast
  • 【新模型速递】PAI-Model Gallery云上一键部署gpt-oss系列模型
  • 一起来聊聊GPT-5
  • c++的四种类型转换(static_cast,reinterpret_cast,const_cast,dynamic_cast)详解和代码示例
  • 使用pyqt5实现可勾选的测试用例界面
  • B站 韩顺平 笔记 (Day 16)
  • 如何以开发者的身份开发出比python更好的应用软件?
  • 攻击者将Linux摄像头武器化为攻击工具,可注入击键并发动攻击
  • 使用reqwest+select实现简单网页爬虫
  • 《Fast Automatic White Balancing Method by Color Histogram Stretching》论文笔记
  • 小米宠物空气净化器好用吗?希喂/小米/范罗士核心性能深度对比
  • 5G专网项目外场常见业务测试指南(六)-PingInfoView
  • 力扣面试150(54/150)
  • 如何构建PHP表单页面及验证相关原理(PHP基础)
  • 六十、【Linux系统lvs应用】LVS简介 、 LVS-NAT集群 、 LVS-DR集群
  • 微服务ETCD服务注册和发现
  • 3 Abp 核心框架(Core Framework)
  • 过程设计工具深度解析-软件工程之详细设计(补充篇)
  • 数字孪生如何推动智慧园区精细化管理
  • CV 医学影像分类、分割、目标检测,之【皮肤病分类】项目拆解
  • OHEM (在线难例挖掘) 详细讲解
  • 【Vue.js】生产设备规划工具(报价单Word文档生成)【开发全流程】
  • 无人机航拍数据集|第14期 无人机水体污染目标检测YOLO数据集3000张yolov11/yolov8/yolov5可训练