首页 文章

C中可变数量的参数?

提问于
浏览
222

如何编写一个接受可变数量参数的函数?这可能吗,怎么样?

15 回答

  • 4

    在C 11中,有一种方法可以使用变量参数模板,这种模板可以实现具有可变参数函数的非常优雅且类型安全的方式 . Bjarne本人在C++11FAQ中给出了printf using variable argument templates的一个很好的例子 .

    就个人而言,我认为这很优雅,我甚至不会在C中使用变量参数函数,直到该编译器支持C 11变量参数模板 .

  • 7

    您可能不应该 va_list 获取 va_list 类型以及对其进行操作的三个函数,称为 va_start()va_arg()va_end() .

    #include<stdarg.h>
    
    int maxof(int n_args, ...)
    {
        va_list ap;
        va_start(ap, n_args);
        int max = va_arg(ap, int);
        for(int i = 2; i <= n_args; i++) {
            int a = va_arg(ap, int);
            if(a > max) max = a;
        }
        va_end(ap);
        return max;
    }
    

    如果你问我,这是一团糟 . 它看起来很糟糕,它充满了技术细节,与你在概念上要实现的目标无关 . 相反,请考虑使用重载或继承/多态,构建器模式(如在流中的 operator<<() 中)或默认参数等 . 这些都更安全:编译器会更多地了解您尝试执行的操作,因此有更多情况可以在你的腿断了之前阻止你 .

  • 0

    在C 11中,您有两个新选项,因为Alternatives部分中的Variadic functions参考页面指出:

    Variadic模板也可用于创建带有可变数量参数的函数 . 它们通常是更好的选择,因为它们不对参数的类型施加限制,不执行整数和浮点促销,并且是类型安全的 . (自C 11起)如果所有变量参数共享一个公共类型,则std :: initializer_list为访问变量参数提供了一种方便的机制(尽管语法不同) .

    以下是显示两种备选方案的示例(see it live):

    #include <iostream>
    #include <string>
    #include <initializer_list>
    
    template <typename T>
    void func(T t) 
    {
        std::cout << t << std::endl ;
    }
    
    template<typename T, typename... Args>
    void func(T t, Args... args) // recursive variadic function
    {
        std::cout << t <<std::endl ;
    
        func(args...) ;
    }
    
    template <class T>
    void func2( std::initializer_list<T> list )
    {
        for( auto elem : list )
        {
            std::cout << elem << std::endl ;
        }
    }
    
    int main()
    {
        std::string
            str1( "Hello" ),
            str2( "world" );
    
        func(1,2.5,'a',str1);
    
        func2( {10, 20, 30, 40 }) ;
        func2( {str1, str2 } ) ;
    }
    

    如果您正在使用 gccclang ,我们可以使用PRETTY_FUNCTION magic variable来显示函数的类型签名,这有助于了解正在发生的事情 . 例如使用:

    std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;
    

    将结果int跟随示例中的可变参数函数(see it live):

    void func(T, Args...) [T = int, Args = <double, char, std::basic_string<char>>]: 1
    void func(T, Args...) [T = double, Args = <char, std::basic_string<char>>]: 2.5
    void func(T, Args...) [T = char, Args = <std::basic_string<char>>]: a
    void func(T) [T = std::basic_string<char>]: Hello
    

    在Visual Studio中,您可以使用FUNCSIG .

    Update Pre C++11

    预C 11 std::initializer_list的替代方案将是std::vector或其他standard containers之一:

    #include <iostream>
    #include <string>
    #include <vector>
    
    template <class T>
    void func1( std::vector<T> vec )
    {
        for( typename std::vector<T>::iterator iter = vec.begin();  iter != vec.end(); ++iter )
        {
            std::cout << *iter << std::endl ;
        }
    }
    
    int main()
    {
        int arr1[] = {10, 20, 30, 40} ;
        std::string arr2[] = { "hello", "world" } ; 
        std::vector<int> v1( arr1, arr1+4 ) ;
        std::vector<std::string> v2( arr2, arr2+2 ) ;
    
        func1( v1 ) ;
        func1( v2 ) ;
    }
    

    可变参数模板的替代方案是variadic functions虽然它们不是类型安全的,但通常是error prone and can be unsafe to use但是唯一的另一种可能的替代方法是使用默认参数,尽管使用有限 . 以下示例是链接引用中示例代码的修改版本:

    #include <iostream>
    #include <string>
    #include <cstdarg>
    
    void simple_printf(const char *fmt, ...)
    {
        va_list args;
        va_start(args, fmt);
    
        while (*fmt != '\0') {
            if (*fmt == 'd') {
                int i = va_arg(args, int);
                std::cout << i << '\n';
            } else if (*fmt == 's') {
                char * s = va_arg(args, char*);
                std::cout << s << '\n';
            }
            ++fmt;
        }
    
        va_end(args);
    }
    
    
    int main()
    {
        std::string
            str1( "Hello" ),
            str2( "world" );
    
        simple_printf("dddd", 10, 20, 30, 40 );
        simple_printf("ss", str1.c_str(), str2.c_str() ); 
    
        return 0 ;
    }
    

    使用可变参数函数时,您可以传递的参数也有限制,详见draft C++ standard部分 5.2.2 函数调用第7段:

    当给定参数没有参数时,参数的传递方式使得接收函数可以通过调用va_arg(18.7)来获取参数的值 . 在参数表达式上执行左值到右值(4.1),数组到指针(4.2)和函数到指针(4.3)标准转换 . 在这些转换之后,如果参数没有算术,枚举,指针,成员指针或类类型,则程序格式错误 . 如果参数具有非POD类类型(第9节),则行为未定义 . [...]

  • 16

    C支持C风格的可变参数功能 .

    但是,大多数C库使用另一种习惯用法,例如而 'c' printf 函数采用变量参数, c++ cout 对象使用 << 重载,它解决了类型安全和ADT(可能以实现简单为代价) .

  • 2

    在c 11你可以这样做:

    void foo(const std::list<std::string> & myArguments) {
       //do whatever you want, with all the convenience of lists
    }
    
    foo({"arg1","arg2"});
    

    列表初始化FTW!

  • 13

    除了varargs或重载之外,您可以考虑在std :: vector或其他容器(例如std :: map)中聚合您的参数 . 像这样的东西:

    template <typename T> void f(std::vector<T> const&);
    std::vector<int> my_args;
    my_args.push_back(1);
    my_args.push_back(2);
    f(my_args);
    

    通过这种方式,您将获得类型安全性,并且这些可变参数的逻辑含义将是显而易见的 .

    当然这种方法可能存在性能问题,但除非您确定无法付出代价,否则不要担心它们 . 这是一种“pythonic”方法来...

  • 318

    唯一的方法是通过使用C样式变量参数,如here所述 . 请注意,这不是推荐的做法,因为它不是类型安全且容易出错的 .

  • 15

    一个C 17解决方案:完整类型安全好的调用语法

    自从在C 11中引入可变参数模板和在C 17中引入折叠表达式,可以定义一个模板函数,该函数在被调用者站点可被调用,就好像它是一个varidic函数,但具有以下优点:

    • 是强类型安全的;

    • 没有参数个数的运行时信息,或者没有使用"stop"参数 .

    以下是混合参数类型的示例

    template<class... Args>
    void print(Args... args)
    {
        (std::cout << ... << args) << "\n";
    }
    print(1, ':', " Hello", ',', " ", "World!");
    

    另一个强制类型匹配所有参数:

    #include <type_traits> // enable_if, conjuction
    
    template<class Head, class... Tail>
    using are_same = std::conjunction<std::is_same<Head, Tail>...>;
    
    template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
    void print_same_type(Head head, Tail... tail)
    {
        std::cout << head;
        (std::cout << ... << tail) << "\n";
    }
    print_same_type("2: ", "Hello, ", "World!");   // OK
    print_same_type(3, ": ", "Hello, ", "World!"); // no matching function for call to 'print_same_type(int, const char [3], const char [8], const char [7])'
                                                   // print_same_type(3, ": ", "Hello, ", "World!");
                                                                                                  ^
    

    更多信息:

  • 1

    如果不采用C风格的变种( ... ),没有标准的C方式可以做到这一点 .

    当然,默认参数可以根据上下文“看起来”像可变数量的参数:

    void myfunc( int i = 0, int j = 1, int k = 2 );
    
    // other code...
    
    myfunc();
    myfunc( 2 );
    myfunc( 2, 1 );
    myfunc( 2, 1, 0 );
    

    所有四个函数调用都使用不同数量的参数调用 myfunc . 如果没有给出,则使用默认参数 . 但请注意,您只能省略尾随参数 . 没有办法,例如省略 i 并仅给出 j .

  • 1

    您可能需要重载或默认参数 - 使用默认参数定义相同的函数:

    void doStuff( int a, double termstator = 1.0, bool useFlag = true )
    {
       // stuff
    }
    
    void doStuff( double std_termstator )
    {
       // assume the user always wants '1' for the a param
       return doStuff( 1, std_termstator );
    }
    

    这将允许您使用以下四种不同的调用之一调用该方法:

    doStuff( 1 );
    doStuff( 2, 2.5 );
    doStuff( 1, 1.0, false );
    doStuff( 6.72 );
    

    ...或者你可能正在寻找来自C的v_args调用约定 .

  • 8

    正如其他人所说,C式varargs . 但你也可以用默认参数做类似的事情 .

  • 126

    如果你知道将提供的参数数量范围,你总是可以使用一些函数重载,比如

    f(int a)
        {int res=a; return res;}
    f(int a, int b)
        {int res=a+b; return res;}
    

    等等...

  • 16
    int fun(int n_args, ...) {
       int *p = &n_args; 
       int s = sizeof(int);
       p += s + s - 1;
       for(int i = 0; i < n_args; i++) {
         printf("A1 %d!\n", *p);
         p += 2;
       }
    }
    

    普通版

  • 8

    使用可变参数模板,例如重现 console.log ,如JavaScript中所示:

    Console console;
    console.log("bunch", "of", "arguments");
    console.warn("or some numbers:", 1, 2, 3);
    console.error("just a prank", "bro");
    

    文件名,例如 js_console.h

    #include <iostream>
    #include <utility>
    
    class Console {
    protected:
        template <typename T>
        void log_argument(T t) {
            std::cout << t << " ";
        }
    public:
        template <typename... Args>
        void log(Args&&... args) {
            int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
            cout << endl;
        }
    
        template <typename... Args>
        void warn(Args&&... args) {
            cout << "WARNING: ";
            int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
            cout << endl;
        }
    
        template <typename... Args>
        void error(Args&&... args) {
            cout << "ERROR: ";
            int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
            cout << endl;
        }
    };
    
  • 2

    如果所有参数都是const且类型相同,我们也可以使用initializer_list

相关问题