Số lượng đối số biến trong C ++?


293

Làm thế nào tôi có thể viết một hàm chấp nhận một số lượng đối số khác nhau? Điều này có thể, làm thế nào?


49
Tại thời điểm này với C ++ 11 , câu trả lời cho câu hỏi này sẽ rất khác biệt
K-ballo

1
@ K-ballo Tôi cũng đã thêm các ví dụ về C ++ 11 vì một câu hỏi gần đây đã hỏi điều tương tự gần đây và tôi cảm thấy điều này cần thiết để biện minh cho việc đóng nó stackoverflow.com/questions/16337459/
trộm

1
Đã thêm tùy chọn C ++ 11 vào câu trả lời của tôi, vì vậy bây giờ nó sẽ bao gồm hầu hết các lựa chọn có sẵn.
Shafik Yaghmour

@ K-ballo không có cách nào để làm điều đó trong C ++ trong trường hợp bạn cần loại đối số bắt buộc .. không có cấu trúc như foo (int ... giá trị): / Nếu bạn không quan tâm đến các loại, thì có, các mẫu biến đổi trong C ++ 11 hoạt động rất tốt
Graywolf

Câu trả lời:


152

Bạn có thể không nên, và có lẽ bạn có thể làm những gì bạn muốn làm một cách an toàn và đơn giản hơn. Về mặt kỹ thuật để sử dụng số lượng đối số biến đổi trong C, bạn bao gồm stdarg.h. Từ đó bạn sẽ nhận được các va_listloại cũng như ba chức năng hoạt động trên nó được gọi là 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;
}

Nếu bạn hỏi tôi, đây là một mớ hỗn độn. Nó trông tệ, không an toàn và có đầy đủ các chi tiết kỹ thuật không liên quan gì đến những gì bạn đang cố gắng đạt được về mặt khái niệm. Thay vào đó, hãy cân nhắc sử dụng quá tải hoặc thừa kế / đa hình, mẫu xây dựng (như trong operator<<()luồng) hoặc đối số mặc định, v.v ... Tất cả đều an toàn hơn: trình biên dịch biết thêm về những gì bạn đang cố gắng để có nhiều lần nó có thể dừng lại bạn trước khi bạn thổi bay chân của bạn.


7
Có lẽ, bạn không thể chuyển tham chiếu đến hàm varargs vì trình biên dịch sẽ không biết khi nào nên chuyển theo giá trị và khi nào bằng tham chiếu và vì các macro C bên dưới sẽ không nhất thiết phải biết phải làm gì với tham chiếu - đã có những hạn chế về điều gì bạn có thể chuyển vào hàm C với các đối số thay đổi vì những thứ như quy tắc quảng cáo.
Jonathan Leffler

3
có cần thiết phải cung cấp ít nhất một đối số trước ...cú pháp không?
Lazer

3
@Lazer nó không phải là một yêu cầu về ngôn ngữ hoặc thư viện, nhưng thư viện tiêu chuẩn không cung cấp cho bạn phương tiện để nói về độ dài của danh sách. Bạn cần người gọi cung cấp cho bạn thông tin này hoặc bằng cách nào đó tự tìm ra nó. printf()Ví dụ, trong trường hợp , hàm phân tích đối số chuỗi cho các mã thông báo đặc biệt để tìm ra có bao nhiêu đối số bổ sung mà nó sẽ mong đợi trong danh sách đối số biến.
wilmustell

11
bạn có thể nên sử dụng <cstdarg>trong C ++ thay vì<stdarg.h>
newacct

11
Số lượng đối số biến là tuyệt vời để gỡ lỗi hoặc cho các hàm / phương thức điền vào một số mảng. Ngoài ra, nó rất tốt cho nhiều hoạt động toán học, chẳng hạn như tối đa, tối thiểu, tổng, trung bình ... Sẽ không gây rối khi bạn không làm phiền với nó.
Tomáš Zato - Phục hồi Monica

395

Trong C ++ 11, bạn có hai tùy chọn mới, như trang tham chiếu các hàm Variadic trong phần Thay thế nêu rõ:

  • Các mẫu biến thể cũng có thể được sử dụng để tạo các hàm có số lượng đối số thay đổi. Chúng thường là lựa chọn tốt hơn vì chúng không áp đặt các hạn chế đối với các loại đối số, không thực hiện các quảng cáo tích hợp và dấu phẩy động và là loại an toàn. (kể từ C ++ 11)
  • Nếu tất cả các đối số biến chia sẻ một loại phổ biến, std :: initizer_list cung cấp một cơ chế thuận tiện (mặc dù với một cú pháp khác nhau) để truy cập các đối số biến.

Dưới đây là một ví dụ hiển thị cả hai lựa chọn thay thế ( xem trực tiếp ):

#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 } ) ;
} 

Nếu bạn đang sử dụng gcchoặc clangchúng ta có thể sử dụng biến ma thuật PRETTY_FUNCTION để hiển thị chữ ký loại của hàm có thể hữu ích trong việc hiểu những gì đang diễn ra. Ví dụ: sử dụng:

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

sẽ cho kết quả int sau cho các hàm matrixdic trong ví dụ ( xem trực tiếp ):

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

Trong Visual Studio, bạn có thể sử dụng FUNCSIG .

Cập nhật Pre C ++ 11

Pre C ++ 11 thay thế cho std :: initizer_list sẽ là std :: vector hoặc một trong các thùng chứa tiêu chuẩn khác :

#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 ) ;
}

và lựa chọn thay thế cho các mẫu matrixdic sẽ là các hàm matrixdic mặc dù chúng không an toàn về kiểuthường dễ bị lỗi và có thể không an toàn khi sử dụng nhưng chỉ có một sự thay thế tiềm năng khác là sử dụng các đối số mặc định , mặc dù điều đó đã hạn chế sử dụng. Ví dụ dưới đây là phiên bản sửa đổi của mã mẫu trong tham chiếu được liên kết:

#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 ;
} 

Việc sử dụng các hàm matrixdic cũng đi kèm với các hạn chế trong các đối số bạn có thể vượt qua, được nêu chi tiết trong dự thảo tiêu chuẩn C ++ trong phần 5.2.2 Chức năng gọi đoạn 7 :

Khi không có tham số cho một đối số đã cho, đối số được truyền theo cách sao cho hàm nhận có thể nhận được giá trị của đối số bằng cách gọi va_arg (18.7). Các chuyển đổi tiêu chuẩn lvalue-to-rvalue (4.1), mảng-to-con trỏ (4.2) và hàm chuyển sang hàm (4.3) được thực hiện trên biểu thức đối số. Sau các chuyển đổi này, nếu đối số không có số học, liệt kê, con trỏ, con trỏ đến thành viên hoặc loại lớp, chương trình sẽ không được định dạng. Nếu đối số có loại lớp không POD (mệnh đề 9), hành vi không được xác định. [...]


typenameso với classviệc sử dụng của bạn ở trên có chủ ý? Nếu vậy, hãy giải thích.
kevinarpe

1
@kevinarpe không cố ý, mặc dù vậy nó không thay đổi gì cả.
Shafik Yaghmour

Thay vào đó, liên kết đầu tiên của bạn có lẽ phải là en.cppreference.com/w/cpp/lingu/variadic_argument .
Alexey Romanov

có thể thực hiện một hàm lấy initializer_listđệ quy không?
idclev 463035818

33

Giải pháp C ++ 17: an toàn kiểu đầy đủ + cú pháp gọi đẹp

Do việc giới thiệu các mẫu matrixdic trong C ++ 11 và các biểu thức gấp trong C ++ 17, có thể định nghĩa một hàm mẫu, tại trang web của người gọi, có thể gọi được như thể đó là một hàm varidic nhưng có ưu điểm là :

  • loại mạnh an toàn;
  • làm việc mà không có thông tin về thời gian chạy của số lượng đối số hoặc không sử dụng đối số "dừng".

Dưới đây là một ví dụ cho các loại đối số hỗn hợp

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

Và một loại khác có loại khớp được thi hành cho tất cả các đối số:

#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!");
                                                                                              ^

Thêm thông tin:

  1. Các mẫu biến thể, còn được gọi là gói tham số Gói tham số (kể từ C ++ 11) - cppreference.com .
  2. Biểu thức gấp biểu thức gấp (kể từ C ++ 17) - cppreference.com .
  3. Xem một cuộc biểu tình chương trình đầy đủ trên coliru.

13
một ngày nào đó tôi hy vọng tôi có thể đọc đượctemplate<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Eladian

1
@Eladian đọc nó là "Điều này chỉ được kích hoạt nếu HeadTail... giống nhau ", trong đó " giống nhau " có nghĩa std::conjunction<std::is_same<Head, Tail>...>. Đọc definitinon cuối cùng này là " Headgiống như tất cả Tail...".
YSC

24

trong c ++ 11 bạn có thể làm:

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

foo({"arg1","arg2"});

danh sách khởi tạo FTW!


19

Trong C ++ 11, có một cách để thực hiện các mẫu đối số biến dẫn đến một cách thực sự thanh lịch và loại an toàn để có các hàm đối số biến. Bản thân Bjarne đưa ra một ví dụ hay về printf bằng cách sử dụng các mẫu đối số biến trong C ++ 11FAQ .

Cá nhân, tôi cho rằng điều này thanh lịch đến mức tôi thậm chí không bận tâm đến hàm đối số biến trong C ++ cho đến khi trình biên dịch đó hỗ trợ cho các mẫu đối số biến C ++ 11.


@donlan - Nếu bạn đang sử dụng C ++ 17, bạn có thể sử dụng biểu thức gấp để làm mọi thứ đơn giản hơn rất nhiều trong một số trường hợp (suy nghĩ sáng tạo ở đây, bạn có thể sử dụng ,toán tử với biểu thức gấp). Mặt khác, tôi không nghĩ vậy.
Omnifarious

15

Các hàm matrixdic kiểu C được hỗ trợ trong C ++.

Tuy nhiên, hầu hết các thư viện C ++ đều sử dụng một thành ngữ thay thế, ví dụ, trong khi 'c' printfhàm có các đối số biến đối c++ couttượng sử dụng <<quá tải nhằm giải quyết vấn đề an toàn và ADT (có lẽ phải trả giá bằng việc đơn giản thực hiện).


Ngoài ra, điều này dường như chỉ hoạt động trong trường hợp của một chức năng như in ấn, trong đó bạn thực sự có một lần lặp của một hàm đối số duy nhất trên mỗi đối số. Mặt khác, bạn chỉ đang khởi tạo một danh sách và chuyển danh sách cho đến hết std::initializer_lists... Và điều này đã giới thiệu sự phức tạp lớn trong một nhiệm vụ đơn giản.
Christopher

13

Ngoài các biến thể hoặc quá tải, bạn có thể xem xét để tổng hợp các đối số của mình trong một std :: vector hoặc các thùng chứa khác (ví dụ std :: map). Một cái gì đó như thế này:

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);

Theo cách này, bạn sẽ đạt được sự an toàn kiểu và ý nghĩa logic của các đối số dao động này sẽ rõ ràng.

Chắc chắn phương pháp này có thể có vấn đề về hiệu suất nhưng bạn không nên lo lắng về chúng trừ khi bạn chắc chắn rằng bạn không thể trả giá. Nó là một cách tiếp cận "Pythonic" đối với c ++ ...


6
Cleaner sẽ không thực thi các vectơ. Thay vào đó, hãy sử dụng một đối số mẫu chỉ định bộ sưu tập theo kiểu STL sau đó lặp qua nó bằng các phương thức bắt đầu và kết thúc của đối số. Bằng cách này, bạn có thể sử dụng std :: vector <T>, c ++ 11's std :: mảng <T, N>, std :: initizer_list <T> hoặc thậm chí tạo bộ sưu tập của riêng bạn.
Jens Åkerblom

3
@ JensÅkerblom Tôi đồng ý nhưng đây là loại lựa chọn cần được phân tích cho vấn đề hiện tại, để tránh kỹ thuật quá mức. Vì đây là vấn đề về chữ ký API, điều quan trọng là phải hiểu sự đánh đổi giữa tính linh hoạt tối đa và sự rõ ràng của ý định / khả năng sử dụng / khả năng bảo trì, v.v.
Francesco

8

Cách duy nhất là thông qua việc sử dụng các đối số biến kiểu C, như được mô tả ở đây . Lưu ý rằng đây không phải là một thực tiễn được khuyến nghị, vì nó không an toàn và dễ bị lỗi.


Do lỗi dễ xảy ra tôi giả sử bạn có nghĩa là rất nguy hiểm? Đặc biệt là khi làm việc với đầu vào không tin cậy.
Kevin Loney

Có, nhưng vì các vấn đề an toàn loại. Hãy suy nghĩ tất cả các vấn đề có thể xảy ra mà printf thông thường có: chuỗi định dạng không khớp với các đối số đã truyền, và như vậy. printf sử dụng kỹ thuật tương tự, BTW.
Dave Van den Eynde

7

Không có cách C ++ tiêu chuẩn để làm điều này mà không cần dùng đến varargs kiểu C ( ...).

Tất nhiên có các đối số mặc định sắp xếp "trông" giống như số lượng đối số khác nhau tùy thuộc vào ngữ cảnh:

void myfunc( int i = 0, int j = 1, int k = 2 );

// other code...

myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );

Tất cả bốn cuộc gọi hàm gọi myfuncvới số lượng đối số khác nhau. Nếu không được đưa ra, các đối số mặc định được sử dụng. Lưu ý, tuy nhiên, bạn chỉ có thể bỏ qua các đối số theo dõi. Không có cách nào, ví dụ để bỏ qua ivà chỉ đưa ra j.


4

Có thể bạn muốn quá tải hoặc tham số mặc định - xác định chức năng tương tự với tham số mặc định:

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 );
}

Điều này sẽ cho phép bạn gọi phương thức bằng một trong bốn cuộc gọi khác nhau:

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

... hoặc bạn có thể đang tìm kiếm các quy ước gọi v_args từ C.


2

Nếu bạn biết phạm vi số lượng đối số sẽ được cung cấp, bạn luôn có thể sử dụng một số chức năng nạp chồng, như

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

và như thế...


2

Sử dụng các mẫu matrixdic, ví dụ để tái tạo console.lognhư đã thấy trong JavaScript:

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

Tên tệp, vd 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;
    }
};


0

Hiện tại có thể ... sử dụng boost any và mẫu Trong trường hợp này, loại đối số có thể được trộn lẫn

#include <boost/any.hpp>
#include <iostream>

#include <vector>
using boost::any_cast;

template <typename T, typename... Types> 
void Alert(T var1,Types... var2) 
{ 

    std::vector<boost::any> a(  {var1,var2...});

    for (int i = 0; i < a.size();i++)
    {

    if (a[i].type() == typeid(int))
    {
        std::cout << "int "  << boost::any_cast<int> (a[i]) << std::endl;
    }
    if (a[i].type() == typeid(double))
    {
        std::cout << "double "  << boost::any_cast<double> (a[i]) << std::endl;
    }
    if (a[i].type() == typeid(const char*))
    {
        std::cout << "char* " << boost::any_cast<const char*> (a[i]) <<std::endl;
    }
    // etc
    }

} 


void main()
{
    Alert("something",0,0,0.3);
}

0

Kết hợp các giải pháp C và C ++ cho tùy chọn đơn giản nhất về mặt ngữ nghĩa, hiệu suất và năng động nhất. Nếu bạn làm hỏng, hãy thử một cái gì đó khác.

// spawn: allocate and initialize (a simple function)
template<typename T>
T * spawn(size_t n, ...){
  T * arr = new T[n];
  va_list ap;
  va_start(ap, n);
  for (size_t i = 0; i < n; i++)
    T[i] = va_arg(ap,T);
  return arr;
}

Người dùng viết:

auto arr = spawn<float> (3, 0.1,0.2,0.3);

Về mặt ngữ nghĩa, điều này trông và cảm nhận chính xác như một hàm n-argument. Dưới mui xe, bạn có thể giải nén nó bằng cách này hay cách khác.


-1

Chúng tôi cũng có thể sử dụng một initizer_list nếu tất cả các đối số là const và cùng loại


-2
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;
   }
}

Phiên bản đơn giản


1
Và hành vi không xác định sẽ không hoạt động trên bất cứ điều gì khác ngoài x86.
SS Anne
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.