C++11语法(2)

发布于:2025-08-13 ⋅ 阅读:(28) ⋅ 点赞:(0)

        一、引言

        上期小编讲解了列表初始化、右值引用。接下来我们要解密之前提到过得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博客


网站公告

今日签到

点亮在社区的每一天
去签到