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?
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?
Câu trả lời:
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_list
loại cũng như ba chức năng hoạt động trên nó được gọi là va_start()
, va_arg()
và 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.
...
cú pháp không?
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.
<cstdarg>
trong C ++ thay vì<stdarg.h>
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 gcc
hoặc clang
chú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ểu và thườ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. [...]
typename
so với class
việc sử dụng của bạn ở trên có chủ ý? Nếu vậy, hãy giải thích.
initializer_list
đệ quy không?
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à :
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:
template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Head
và Tail...
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à " Head
giống như tất cả Tail...
".
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!
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.
,
toán tử với biểu thức gấp). Mặt khác, tôi không nghĩ vậy.
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' printf
hàm có các đối số biến đối c++ cout
tượ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).
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.
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 ++ ...
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.
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 myfunc
vớ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 i
và chỉ đưa ra j
.
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.
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ế...
Sử dụng các mẫu matrixdic, ví dụ để tái tạo console.log
như đã 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;
}
};
Như những người khác đã nói, varargs kiểu C. Nhưng bạn cũng có thể làm một cái gì đó tương tự với các đối số mặc định.
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);
}
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.
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