Có cách nào để xác định đối số mặc định cho hàm trong C không?
Có cách nào để xác định đối số mặc định cho hàm trong C không?
Câu trả lời:
Không hẳn vậy. Cách duy nhất là viết hàm varargs và điền thủ công các giá trị mặc định cho các đối số mà người gọi không vượt qua.
open(2)
gọi hệ thống sử dụng điều này cho một đối số tùy chọn có thể có mặt tùy thuộc vào các đối số được yêu cầu và printf(3)
đọc một chuỗi định dạng chỉ định số lượng đối số sẽ có. Cả hai đều sử dụng varargs khá an toàn và hiệu quả, và mặc dù bạn chắc chắn có thể làm hỏng chúng, printf()
đặc biệt là dường như khá phổ biến.
Wow, mọi người đều là một người bi quan như vậy quanh đây. Câu trả lời là có.
Điều đó không tầm thường: cuối cùng, chúng ta sẽ có chức năng cốt lõi, cấu trúc hỗ trợ, chức năng trình bao bọc và macro xung quanh chức năng trình bao bọc. Trong công việc của tôi, tôi có một bộ macro để tự động hóa tất cả điều này; một khi bạn hiểu được dòng chảy, bạn sẽ dễ dàng làm điều tương tự.
Tôi đã viết nó ở nơi khác, vì vậy đây là một liên kết bên ngoài chi tiết để bổ sung tóm tắt ở đây: http://modelingwithdata.org/arch/00000022.htm
Chúng tôi muốn biến
double f(int i, double x)
vào một hàm lấy mặc định (i = 8, x = 3.14). Xác định cấu trúc đồng hành:
typedef struct {
int i;
double x;
} f_args;
Đổi tên hàm của bạn f_base
và xác định hàm bao bọc đặt mặc định và gọi cơ sở:
double var_f(f_args in){
int i_out = in.i ? in.i : 8;
double x_out = in.x ? in.x : 3.14;
return f_base(i_out, x_out);
}
Bây giờ thêm một macro, sử dụng macro matrixdic của C. Bằng cách này, người dùng không cần phải biết rằng họ thực sự đang tạo ra một f_args
cấu trúc và nghĩ rằng họ đang làm như bình thường:
#define f(...) var_f((f_args){__VA_ARGS__});
OK, bây giờ tất cả những điều sau đây sẽ hoạt động:
f(3, 8); //i=3, x=8
f(.i=1, 2.3); //i=1, x=2.3
f(2); //i=2, x=3.14
f(.x=9.2); //i=8, x=9.2
Kiểm tra các quy tắc về cách khởi tạo ghép đặt mặc định cho các quy tắc chính xác.
Một điều không hoạt động: f(0)
, bởi vì chúng ta không thể phân biệt giữa giá trị còn thiếu và số không. Theo kinh nghiệm của tôi, đây là điều cần chú ý, nhưng có thể được quan tâm khi có nhu cầu --- một nửa thời gian mặc định của bạn thực sự bằng không.
Tôi đã trải qua những rắc rối khi viết nó lên bởi vì tôi nghĩ rằng các đối số và mặc định được đặt tên thực sự làm cho việc mã hóa trong C dễ dàng hơn và thậm chí thú vị hơn. Và C thật tuyệt vời vì quá đơn giản và vẫn có đủ ở đó để biến tất cả những điều này thành có thể.
{}
(trình khởi tạo trống) là lỗi C99.
#define vrange(...) CALL(range,(param){.from=1, .to=100, .step=1, __VA_ARGS__})
Đúng. :-) Nhưng không phải theo cách bạn mong đợi.
int f1(int arg1, double arg2, char* name, char *opt);
int f2(int arg1, double arg2, char* name)
{
return f1(arg1, arg2, name, "Some option");
}
Thật không may, C không cho phép bạn quá tải các phương thức để bạn kết thúc với hai chức năng khác nhau. Tuy nhiên, bằng cách gọi f2, bạn thực sự sẽ gọi F1 với giá trị mặc định. Đây là giải pháp "Đừng lặp lại chính mình", giúp bạn tránh sao chép / dán mã hiện có.
Chúng ta có thể tạo các hàm sử dụng các tham số được đặt tên (chỉ) cho các giá trị mặc định. Đây là phần tiếp theo của câu trả lời của bk.
#include <stdio.h>
struct range { int from; int to; int step; };
#define range(...) range((struct range){.from=1,.to=10,.step=1, __VA_ARGS__})
/* use parentheses to avoid macro subst */
void (range)(struct range r) {
for (int i = r.from; i <= r.to; i += r.step)
printf("%d ", i);
puts("");
}
int main() {
range();
range(.from=2, .to=4);
range(.step=2);
}
Tiêu chuẩn C99 xác định rằng các tên sau này trong phần khởi tạo ghi đè lên các mục trước đó. Chúng ta cũng có thể có một số tham số vị trí tiêu chuẩn, chỉ cần thay đổi chữ ký macro và hàm cho phù hợp. Các tham số giá trị mặc định chỉ có thể được sử dụng theo kiểu tham số được đặt tên.
Đầu ra chương trình:
1 2 3 4 5 6 7 8 9 10
2 3 4
1 3 5 7 9
OpenCV sử dụng một cái gì đó như:
/* in the header file */
#ifdef __cplusplus
/* in case the compiler is a C++ compiler */
#define DEFAULT_VALUE(value) = value
#else
/* otherwise, C compiler, do nothing */
#define DEFAULT_VALUE(value)
#endif
void window_set_size(unsigned int width DEFAULT_VALUE(640),
unsigned int height DEFAULT_VALUE(400));
Nếu người dùng không biết mình nên viết gì, thủ thuật này có thể hữu ích:
Không.
Ngay cả tiêu chuẩn C99 mới nhất cũng không hỗ trợ điều này.
Câu trả lời ngắn gọn: Không.
Câu trả lời dài hơn một chút: Có một cách giải quyết cũ , cũ , nơi bạn truyền một chuỗi mà bạn phân tích cho các đối số tùy chọn:
int f(int arg1, double arg2, char* name, char *opt);
trong đó opt có thể bao gồm cặp "name = value" hoặc thứ gì đó và bạn sẽ gọi như thế nào
n = f(2,3.0,"foo","plot=yes save=no");
Rõ ràng điều này chỉ đôi khi hữu ích. Nói chung khi bạn muốn một giao diện duy nhất cho một họ chức năng.
Bạn vẫn tìm thấy cách tiếp cận này trong các mã vật lý hạt được viết bởi các chương trình chuyên nghiệp trong c ++ (ví dụ như ROOT ). Ưu điểm chính của nó là nó có thể được mở rộng gần như vô thời hạn trong khi vẫn duy trì khả năng tương thích trở lại.
struct
và yêu cầu người gọi thực hiện một, điền vào các trường cho các tùy chọn khác nhau, sau đó chuyển nó theo địa chỉ hoặc chuyển NULL
cho các tùy chọn mặc định.
Có lẽ cách tốt nhất để làm điều này (có thể hoặc không thể trong trường hợp của bạn tùy thuộc vào tình huống của bạn) là chuyển sang C ++ và sử dụng nó như một 'C tốt hơn'. Bạn có thể sử dụng C ++ mà không cần sử dụng các lớp, mẫu, nạp chồng toán tử hoặc các tính năng nâng cao khác.
Điều này sẽ cung cấp cho bạn một biến thể của C với chức năng nạp chồng và các tham số mặc định (và bất kỳ tính năng nào khác bạn chọn sử dụng). Bạn chỉ cần có một chút kỷ luật nếu bạn thực sự nghiêm túc về việc chỉ sử dụng một tập hợp con bị hạn chế của C ++.
Nhiều người sẽ nói rằng đó là một ý tưởng tồi tệ khi sử dụng C ++ theo cách này và họ có thể có một điểm. Nhưng đó chỉ là một ý kiến; Tôi nghĩ việc sử dụng các tính năng của C ++ là hợp lệ mà bạn không cần phải mua toàn bộ. Tôi nghĩ rằng một phần quan trọng của lý do cho sự thành công của C ++ là nó đã được sử dụng bởi rất nhiều lập trình viên trong những ngày đầu tiên theo cách chính xác.
Một tùy chọn khác sử dụng struct
s:
struct func_opts {
int arg1;
char * arg2;
int arg3;
};
void func(int arg, struct func_opts *opts)
{
int arg1 = 0, arg3 = 0;
char *arg2 = "Default";
if(opts)
{
if(opts->arg1)
arg1 = opts->arg1;
if(opts->arg2)
arg2 = opts->arg2;
if(opts->arg3)
arg3 = opts->arg3;
}
// do stuff
}
// call with defaults
func(3, NULL);
// also call with defaults
struct func_opts opts = {0};
func(3, &opts);
// set some arguments
opts.arg3 = 3;
opts.arg2 = "Yes";
func(3, &opts);
Không.
20202020
hex, nhưng làm thế nào để tôi gõ nó?
Một mẹo khác sử dụng macro:
#include <stdio.h>
#define func(...) FUNC(__VA_ARGS__, 15, 0)
#define FUNC(a, b, ...) func(a, b)
int (func)(int a, int b)
{
return a + b;
}
int main(void)
{
printf("%d\n", func(1));
printf("%d\n", func(1, 2));
return 0;
}
Nếu chỉ có một đối số được thông qua, b
sẽ nhận giá trị mặc định (trong trường hợp này là 15)
Không, nhưng bạn có thể cân nhắc sử dụng một tập hợp các hàm (hoặc macro) để tính gần đúng bằng cách sử dụng các đối số mặc định:
// No default args
int foo3(int a, int b, int c)
{
return ...;
}
// Default 3rd arg
int foo2(int a, int b)
{
return foo3(a, b, 0); // default c
}
// Default 2nd and 3rd args
int foo1(int a)
{
return foo3(a, 1, 0); // default b and c
}
Có, với các tính năng của C99, bạn có thể làm điều này. Điều này hoạt động mà không xác định cấu trúc dữ liệu mới hoặc như vậy và không có chức năng phải quyết định trong thời gian chạy cách nó được gọi và không có bất kỳ chi phí tính toán nào.
Để được giải thích chi tiết, xem bài viết của tôi tại
http://gustyt.wordpress.com/2010/06/03/default-argument-for-c99/
Jens
Nói chung là không, nhưng trong gcc Bạn có thể đặt tham số cuối cùng của funcA () với một macro.
Trong funcB () tôi sử dụng một giá trị đặc biệt (-1) để báo hiệu rằng tôi cần giá trị mặc định cho tham số 'b'.
#include <stdio.h>
int funcA( int a, int b, ... ){ return a+b; }
#define funcA( a, ... ) funcA( a, ##__VA_ARGS__, 8 )
int funcB( int a, int b ){
if( b == -1 ) b = 8;
return a+b;
}
int main(void){
printf("funcA(1,2): %i\n", funcA(1,2) );
printf("funcA(1): %i\n", funcA(1) );
printf("funcB(1, 2): %i\n", funcB(1, 2) );
printf("funcB(1,-1): %i\n", funcB(1,-1) );
}
Tôi đã cải thiện câu trả lời của Jens Gustyt để:
Đột biến.h:
#ifndef VARIADIC
#define _NARG2(_0, _1, _2, ...) _2
#define NUMARG2(...) _NARG2(__VA_ARGS__, 2, 1, 0)
#define _NARG3(_0, _1, _2, _3, ...) _3
#define NUMARG3(...) _NARG3(__VA_ARGS__, 3, 2, 1, 0)
#define _NARG4(_0, _1, _2, _3, _4, ...) _4
#define NUMARG4(...) _NARG4(__VA_ARGS__, 4, 3, 2, 1, 0)
#define _NARG5(_0, _1, _2, _3, _4, _5, ...) _5
#define NUMARG5(...) _NARG5(__VA_ARGS__, 5, 4, 3, 2, 1, 0)
#define _NARG6(_0, _1, _2, _3, _4, _5, _6, ...) _6
#define NUMARG6(...) _NARG6(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)
#define _NARG7(_0, _1, _2, _3, _4, _5, _6, _7, ...) _7
#define NUMARG7(...) _NARG7(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)
#define _NARG8(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) _8
#define NUMARG8(...) _NARG8(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define _NARG9(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) _9
#define NUMARG9(...) _NARG9(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define __VARIADIC(name, num_args, ...) name ## _ ## num_args (__VA_ARGS__)
#define _VARIADIC(name, num_args, ...) name (__VARIADIC(name, num_args, __VA_ARGS__))
#define VARIADIC(name, num_args, ...) _VARIADIC(name, num_args, __VA_ARGS__)
#define VARIADIC2(name, num_args, ...) __VARIADIC(name, num_args, __VA_ARGS__)
// Vary function name by number of arguments supplied
#define VARIADIC_NAME(name, num_args) name ## _ ## num_args ## _name ()
#define NVARIADIC(name, num_args, ...) _VARIADIC(VARIADIC_NAME(name, num_args), num_args, __VA_ARGS__)
#endif
Kịch bản sử dụng đơn giản hóa:
const uint32*
uint32_frombytes(uint32* out, const uint8* in, size_t bytes);
/*
The output buffer defaults to NULL if not provided.
*/
#include "variadic.h"
#define uint32_frombytes_2( b, c) NULL, b, c
#define uint32_frombytes_3(a, b, c) a, b, c
#define uint32_frombytes(...) VARIADIC(uint32_frombytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)
Và với _Generic:
const uint8*
uint16_tobytes(const uint16* in, uint8* out, size_t bytes);
const uint16*
uint16_frombytes(uint16* out, const uint8* in, size_t bytes);
const uint8*
uint32_tobytes(const uint32* in, uint8* out, size_t bytes);
const uint32*
uint32_frombytes(uint32* out, const uint8* in, size_t bytes);
/*
The output buffer defaults to NULL if not provided.
Generic function name supported on the non-uint8 type, except where said type
is unavailable because the argument for output buffer was not provided.
*/
#include "variadic.h"
#define uint16_tobytes_2(a, c) a, NULL, c
#define uint16_tobytes_3(a, b, c) a, b, c
#define uint16_tobytes(...) VARIADIC( uint16_tobytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)
#define uint16_frombytes_2( b, c) NULL, b, c
#define uint16_frombytes_3(a, b, c) a, b, c
#define uint16_frombytes(...) VARIADIC(uint16_frombytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)
#define uint32_tobytes_2(a, c) a, NULL, c
#define uint32_tobytes_3(a, b, c) a, b, c
#define uint32_tobytes(...) VARIADIC( uint32_tobytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)
#define uint32_frombytes_2( b, c) NULL, b, c
#define uint32_frombytes_3(a, b, c) a, b, c
#define uint32_frombytes(...) VARIADIC(uint32_frombytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)
#define tobytes(a, ...) _Generic((a), \
const uint16*: uint16_tobytes, \
const uint32*: uint32_tobytes) (VARIADIC2( uint32_tobytes, NUMARG3(a, __VA_ARGS__), a, __VA_ARGS__))
#define frombytes(a, ...) _Generic((a), \
uint16*: uint16_frombytes, \
uint32*: uint32_frombytes)(VARIADIC2(uint32_frombytes, NUMARG3(a, __VA_ARGS__), a, __VA_ARGS__))
Và với lựa chọn tên hàm matrixdic, không thể kết hợp với _Generic:
// winternitz() with 5 arguments is replaced with merkle_lamport() on those 5 arguments.
#define merkle_lamport_5(a, b, c, d, e) a, b, c, d, e
#define winternitz_7(a, b, c, d, e, f, g) a, b, c, d, e, f, g
#define winternitz_5_name() merkle_lamport
#define winternitz_7_name() winternitz
#define winternitz(...) NVARIADIC(winternitz, NUMARG7(__VA_ARGS__), __VA_ARGS__)
ĐÚNG
Thông qua các macro
3 thông số:
#define my_func2(...) my_func3(__VA_ARGS__, 0.5)
#define my_func1(...) my_func2(__VA_ARGS__, 10)
#define VAR_FUNC(_1, _2, _3, NAME, ...) NAME
#define my_func(...) VAR_FUNC(__VA_ARGS__, my_func3, my_func2, my_func1)(__VA_ARGS__)
void my_func3(char a, int b, float c) // b=10, c=0.5
{
printf("a=%c; b=%d; c=%f\n", a, b, c);
}
Nếu bạn muốn đối số thứ 4, thì cần thêm my_func3. Lưu ý các thay đổi trong VAR_FUNC, my_func2 và my_func
4 thông số:
#define my_func3(...) my_func4(__VA_ARGS__, "default") // <== New function added
#define my_func2(...) my_func3(__VA_ARGS__, (float)1/2)
#define my_func1(...) my_func2(__VA_ARGS__, 10)
#define VAR_FUNC(_1, _2, _3, _4, NAME, ...) NAME
#define my_func(...) VAR_FUNC(__VA_ARGS__, my_func4, my_func3, my_func2, my_func1)(__VA_ARGS__)
void my_func4(char a, int b, float c, const char* d) // b=10, c=0.5, d="default"
{
printf("a=%c; b=%d; c=%f; d=%s\n", a, b, c, d);
}
Chỉ ngoại lệ rằng các biến float không thể được đưa ra các giá trị mặc định ( trừ khi đó là đối số cuối cùng như trong trường hợp 3 tham số ), bởi vì chúng cần dấu chấm ('.'), Không được chấp nhận trong các đối số macro. Nhưng có thể tìm ra một công việc xung quanh như đã thấy trong macro my_func2 ( gồm 4 trường hợp tham số )
Chương trình
int main(void)
{
my_func('a');
my_func('b', 20);
my_func('c', 200, 10.5);
my_func('d', 2000, 100.5, "hello");
return 0;
}
Đầu ra:
a=a; b=10; c=0.500000; d=default
a=b; b=20; c=0.500000; d=default
a=c; b=200; c=10.500000; d=default
a=d; b=2000; c=100.500000; d=hello
Vâng, bạn có thể làm một cái gì đó simulair, ở đây bạn phải biết các danh sách đối số khác nhau bạn có thể nhận được nhưng bạn có cùng chức năng để xử lý tất cả.
typedef enum { my_input_set1 = 0, my_input_set2, my_input_set3} INPUT_SET;
typedef struct{
INPUT_SET type;
char* text;
} input_set1;
typedef struct{
INPUT_SET type;
char* text;
int var;
} input_set2;
typedef struct{
INPUT_SET type;
int text;
} input_set3;
typedef union
{
INPUT_SET type;
input_set1 set1;
input_set2 set2;
input_set3 set3;
} MY_INPUT;
void my_func(MY_INPUT input)
{
switch(input.type)
{
case my_input_set1:
break;
case my_input_set2:
break;
case my_input_set3:
break;
default:
// unknown input
break;
}
}
Tại sao chúng ta không thể làm điều này.
Cung cấp cho đối số tùy chọn một giá trị mặc định. Theo cách đó, người gọi hàm không nhất thiết phải truyền giá trị của đối số. Đối số lấy giá trị mặc định. Và dễ dàng tranh luận đó trở thành tùy chọn cho khách hàng.
Ví dụ
void foo (int a, int b = 0);
Ở đây b là một đối số tùy chọn.