天天看点

C++中和C的可变参数函数、initializer_list与省略号形参

文章目录

    • 一、可变参数函数
    • 二、initializer_list(初始化列表)
      • 2.1 begin、end便利与size获取元素的个数
      • 2.2 复制和赋值
      • 2.3 初始化列表为构造函数参数
    • 三、省略号形参
      • 3.1 c语言可变参数的实现
      • 3.2 c语言可变参数的注意事项

一、可变参数函数

  • 需求:有时候需要像函数传递的参数数量不固定
  • C++11提供了initializer_list标准库类型
  • 前提:所有形参的类型相同

二、initializer_list(初始化列表)

  • 头文件需要包含:
#include <initializer_list>
//范例
initializer_list<int>myarray;//数组,元素类型是int,空列表
initializer_list<int>myarray2 = {12,14,16,20,30};
           
  • 注意:initializer_list对象中的元素永远都是常量值,不能被改变

2.1 begin、end便利与size获取元素的个数

#include <iostream>
#include <initializer_list>

using namespace std;

void printvalue(initializer_list<string> tmpstr,int tmpvalue)
{
    //遍历每个元素:begin指向数组首元素的指针,end指向数组尾元素之后
    for(auto & v:tmpstr)
    {
        std::cout<<v<<std::endl;
    }
    std::cout<<tmpstr.size()<<std::endl;
    std::cout<<"tmpvalue = "<<tmpvalue<<std::endl;
}

int main()
{
    printvalue({"aa","bb","cc"},6);//传值就要把这个序列放到花括号里面来
    return 0;
}


           
  • 打印效果如下
    C++中和C的可变参数函数、initializer_list与省略号形参

2.2 复制和赋值

int main()
{
    initializer_list<string> myarry3 = {"aa","bb","cc"};
    initializer_list<string> myarry4(myarry3);
    initializer_list<string> myarry5;
    myarry5 = myarry4;
    return 0;
}
           

2.3 初始化列表为构造函数参数

#include <iostream>
#include <initializer_list>
#include <vector>

using namespace std;


class CT
{
public:
    CT(const std::initializer_list<int> &tmpvalue) //单参数构造函数
    {
        m_vecIntegers.insert(m_vecIntegers.end(),tmpvalue.begin(),tmpvalue.end());
    }
    
    ~CT()
    {}

    void print()
    {
        size_t size = m_vecIntegers.size();
        for(auto &v :   m_vecIntegers)
        {
            std::cout<<v<<" ";
        }
        std::cout<<std::endl;
    }

    void append(const std::initializer_list<int> &integers)//const是常量引用,也是左值引用
    {
        m_vecIntegers.insert(m_vecIntegers.end(),integers.begin(),integers.end());
    }

private:
    std::vector<int> m_vecIntegers;

};


int main()
{
    CT ct0 = {10,20,30,40};//隐式类型转换
    //如果要禁止隐式类型转换
    //explicit CT(const std::initializer_list<int> &tmpvalue){}
    CT ct{1,2,3};//显示类型转换
    ct.print();

    std::cout<<"神佑觉醒"<<std::endl;

    ct.append({4,5,6});
    ct.print();

    return 0;
}

           
  • 补充:隐式转换的缺点

隐式类型转换的缺点:它们的存在将导致错误的发生。

例如:

class Rational {

public:

operator double() const; // 转换Rational类成double类型

};

在下面这种情况下,这个函数会被自动调用:

Rational r(1, 2); // r 的值是1/2

double d = 0.5 * r; // 转换 r 到double,然后做乘法

假设你有一个如上所述的Rational类,你想让该类拥有打印有理数对象的功能,就好像它是一个内置类型。因此,你可能会这么写:

Rational r(1, 2);

cout << r; // 应该打印出"1/2"

当编译器调用operator<<时,会发现没有这样的函数存在,但是它会试图找到一个合适的隐式类型转换顺序以使得函数调用正常运行。类型转换顺序的规则定义是复杂的,但是在现在这种情况下,编译器会发现它们能调用Rational::operatordouble函数来把r转换为double类型。所以上述代码打印的结果是一个浮点数,而不是一个有理数。这简直是一个灾难,但是它表明了隐式类型转换的缺点:它们的存在将导致错误的发生。

三、省略号形参

3.1 c语言可变参数的实现

#include <iostream>
#include <initializer_list>
#include <vector>
#include <stdarg.h>
// #include <stdio.h>
// #include <string.h>
using namespace std;

double average(int num,...) //num代表的是参数数量
{
    va_list valist;         //创建一个va_list类型变量
    double sum = 0;
    va_start(valist,num);   //使va_list指向起始的参数
    for(int i = 0;i<num;++i)
    {
        //遍历参数
        sum += va_arg(valist,int);//参数2(实参)说明返回的类型为int
    }
    va_end(valist);         //释放va_list
    return num/sum;
}

int main()
{
    std::cout<<average(3,100,200,300)<<std::endl;
    std::cout<<double(3.000/600.000)<<std::endl;
    return 0;
}
           
  • 模仿printf打印数据
void funcTest(const char* msg,...)
{
    va_list valist;
    int csgs = atoi(msg);       //拿到可变参数的数量

    va_start(valist,msg);       //使va_list指向起始的参数
    int paraMount = 0;          //计算参数的个数
    while(paraMount<csgs)       //不知道几个参数,一直循环就好了
    {
        char* p;
        //这块假定给的参数都是字符串,若要printf,就要根据第一个参数里面的%d,%s来分析va_arg()里面的第二个参数类型
        p = va_arg(valist,char*);
        printf("第%d个参数是: %s\n",paraMount,p);
        ++paraMount;
    }
    va_end(valist);             //释放va_list
}


int main()
{
    funcTest("3","aa","bb","cc");
    return 0;
}

           
C++中和C的可变参数函数、initializer_list与省略号形参

3.2 c语言可变参数的注意事项

  • 至少有一个有效的形参,形参不能完全是省略号形参(…)
  • 省略号形参只能出现在形参列表最后的一个位置,例如
void myfunc(参数列表,...);
           
  • 针对代码行:void funcTest(const char* msg,…){}其中三个点前面的逗号可以省略
void funcTest(const char* msg...){}
           
  • 如果有多个非可变参数,那va_start(valist,msg);这种代码行就要注意,必须要绑定"…"之前的那个形参(下面的pszFormat)
void testFun(char* pszDest,int DestLen,const char* pszFormat,...){}
           

那么调用va_start就要这样写

va_start(valist,pszFormat);

  • 不建议在C++中使用省略号形参

继续阅读