2009-09-24 20 views
216

¿Hay alguna manera de especificar argumentos por defecto para una función en C?C argumentos por defecto

+32

@pmg - Y mientras estamos en eso, puede arreglar todas las características erróneas de C que no nos gustan. Y luego podemos agregar algunas características aquí y allá, y luego, ¿por qué no agregamos la sobrecarga de funciones, y luego hemos creado un lenguaje hacky que nadie usará y que confundirá a casi todos los que tienen que mantener su aplicación. Si quieres crear un idioma, por supuesto, crea un idioma, pero no hagas un nuevo idioma solo para una determinada tarea. Eso es horrible. –

+56

@Chris - no estás hablando de Bjarne S, ¿verdad? :) –

+2

solo quiero un poco mejor C, no C++. piensa C +. con una variedad de pequeñas mejoras levantadas de C++, pero no el gran desastre. Y, por favor, no hay un cargador de enlaces diferente. debería ser solo otro paso similar al preprocesador. estandarizado. en todas partes ... –

Respuesta

110

No realmente. La única forma sería write a varargs function y llenar manualmente los valores predeterminados para los argumentos que la persona que llama no pasa.

+15

Odio la falta de comprobación cuando uso varargs. – dmckee

+10

También deberías; De hecho, no recomiendo esto; Solo quería transmitir que es posible. –

+3

Sin embargo, ¿cómo quieres comprobar si la persona que llama pasa el argumento o no? Creo que para que esto funcione, ¿no tiene la persona que llama para decirle que no lo aprobó? Creo que esto hace que todo el enfoque sea menos útil, la persona que llama también podría llamar a una función con otro nombre. –

17

No, esa es una característica de lenguaje C++.

12

Respuesta corta: No.

respuesta Ligeramente más largo: Hay un viejo, viejo solución donde se pasa una cadena que PARSE de argumentos opcionales:

int f(int arg1, double arg2, char* name, char *opt); 

donde opt puede incluir un par de "nombre = valor" o algo similar, y al que llamaría como

n = f(2,3.0,"foo","plot=yes save=no"); 

Obviamente esto solo ocasionalmente es útil. Generalmente cuando se quiere una interfaz única para una familia de funcionalidad.


todavía encuentra este enfoque en los códigos de física de partículas que están escritos por programas profesionales en C++ (como por ejemplo ROOT). Su principal ventaja es que puede extenderse casi indefinidamente mientras se mantiene la compatibilidad con versiones anteriores.

+2

¡Combina esto con varargs y tendrás todo tipo de diversión! –

+4

Usaría una 'struct' personalizada y haré que el que llama, complete los campos para diferentes opciones, y luego la pase por la dirección, o pase' NULL' para las opciones predeterminadas. –

+3

¡Alguien ha estado leyendo demasiado código FORTRAN! –

18

No

incluso la última norma C99 soporta esto.

+0

un simple no habría sido aún mejor;) – KevinDTimm

+16

@kevindtimm: Eso no es posible, por lo tanto, exige un mínimo de longitud en las respuestas. Lo intenté. :) – unwind

+0

Por favor, consulte mi respuesta. :) – chaos

8

+2

¿La misma solución para los comentarios? – dmckee

+1

¿cuál es la solución? Puedo ver que es '20202020' en hexadecimal, pero ¿cómo lo escribo? – Lazer

+4

espacios finales. Ya no funciona – chaos

141

Sí. :-) Pero no de la manera que esperarías.

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

Desafortunadamente, C no le permite sobrecargar los métodos por lo que terminaría con dos funciones diferentes. Aún así, al llamar a f2, estarías llamando a f1 con un valor predeterminado. Esta es una solución "No repetir", que le ayuda a evitar copiar/pegar el código existente.

+19

FWIW, prefiero usar el número al final de la función para indicar el número de argumentos que toma. Lo hace más fácil que simplemente usar cualquier número arbitrario. :) – Macke

+1

Esta es de lejos la mejor respuesta porque demuestra una forma simple de lograr el mismo objetivo. Tengo una función que es parte de una API fija que no quiero cambiar, pero necesito que tome un nuevo parámetro. Por supuesto, es tan obvio que me lo perdí (¡me quedé atrapado al pensar en el parámetro predeterminado!) – RunHolt

+2

f2 también podría ser una macro del preprocesador – Spookbuster

3

No, pero es posible considerar el uso de un conjunto de funciones (o macros) para aproximar usando argumentos por defecto:

// 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 
} 
+0

Lo que dije ... –

3

Generalmente no, pero en gcc Usted puede hacer que el último parámetro de funcA() opcional con una macro.

En funcB() uso un valor especial (-1) para indicar que necesito el valor predeterminado para el parámetro '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)); 
} 
11

Probablemente la mejor manera de hacer esto (que puede o no puede ser posible en su caso en función de su situación) es mover a C++ y utilizarlo como 'una mejor C'. Puede usar C++ sin usar clases, plantillas, sobrecarga del operador u otras funciones avanzadas.

Esto le dará una variante de C con sobrecarga de función y parámetros predeterminados (y cualquier otra característica que elija usar).Solo tienes que ser un poco disciplinado si realmente quieres usar solo un subconjunto restringido de C++.

Mucha gente dirá que es una idea terrible utilizar C++ de esta manera, y pueden tener un punto. Pero es solo una opinión; Creo que es válido usar las características de C++ con las que se sienta cómodo sin tener que comprar todo el conjunto. Creo que una parte importante de la razón del éxito de C++ es que fue utilizado por muchísimos programadores en sus primeros días exactamente de esta manera.

12

Sin embargo, otra opción utiliza 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); 
+0

¿Alguna razón para el voto a favor? Este es el segundo downvote anónimo en dos días. –

0

sí se puede hacer simulair somthing, aquí hay que conocer las diferentes listas de argumentos, usted puede obtener, pero que tienen la misma función para manejar entonces todo.

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

Wow, todo el mundo es pesimista por aquí. La respuesta es sí.

No es trivial: al final, tendremos la función de núcleo, una estructura de soporte, una función de envoltura y una macro alrededor de la función de envoltura. En mi trabajo tengo un conjunto de macros para automatizar todo esto; una vez que comprenda el flujo, le resultará fácil hacer lo mismo.

He escrito esto en otro lugar, así que aquí tiene un enlace externo detallada para complementar el resumen aquí: http://modelingwithdata.org/arch/00000022.htm

Nos gustaría convertir

double f(int i, double x) 

en una función que toma por defecto (i = 8, x = 3.14). Definir una estructura de compañía:

typedef struct { 
    int i; 
    double x; 
} f_args; 

Cambiar el nombre de su función f_base, y definir una función de contenedor que establece valores predeterminados y llama la base:

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

Ahora añadir una macro, el uso de macros de variadic C. De esta manera los usuarios no tienen que saber que están poblando realidad una estructura f_args y piensan que están haciendo lo de siempre:

#define f(...) var_f((f_args){__VA_ARGS__}); 

bien, ahora todo lo siguiente funcionaría:

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 

Compruebe las reglas sobre cómo los inicializadores compuestos establecen los valores predeterminados para las reglas exactas.

Una cosa que no funcionará: f(0), porque no podemos distinguir entre un valor faltante y cero. En mi experiencia, esto es algo que hay que tener en cuenta, pero se puede tener en cuenta como si es necesario, la mitad de las veces el valor predeterminado es cero.

Me tomé la molestia de escribir esto porque creo que los argumentos nombrados y los valores predeterminados realmente hacen que la codificación en C sea más fácil e incluso más divertida. Y C es increíble por ser tan simple y seguir teniendo suficiente para hacer todo esto posible.

+12

+1 creatividad Tiene sus limitaciones pero también trae parámetros con nombre a la tabla. Tenga en cuenta que '{}' (inicializador vacío) es un error C99. – u0b34a0f6ae

+22

Sin embargo, aquí hay algo grandioso para usted: el estándar permite especificar miembros nombrados varias veces, la anulación posterior. Por lo tanto, para los parámetros con nombre solo puede resolver el problema de los valores predeterminados y permitir una llamada vacía. '#define vrange (...) CALL (rango, (param) {. from = 1, .to = 100, .step = 1, __VA_ARGS __})' – u0b34a0f6ae

+3

Espero que los errores del compilador sean legibles, pero este es un gran ¡técnica! Casi se parece a Python Kwargs. – totowtwo

3

Sí, con las características de C99 puede hacerlo.Esto funciona sin definir nuevas estructuras de datos más o menos y sin que la función tenga que decidir en tiempo de ejecución cómo se llamó y sin ningún gasto de cómputo.

Para una explicación detallada ver mi post en

http://gustedt.wordpress.com/2010/06/03/default-arguments-for-c99/

Jens

+0

Seel también mi [respuesta] (http://stackoverflow.com/questions/1472138/c-default-arguments#33786937) que se deriva de la suya. –

+0

Lo siento, mi conexión es muy pésima hoy. Espero que sea mejor mañana. –

35

Podemos crear funciones que utilizan parámetros con nombre (sólo) para los valores por defecto. Esta es una continuación de la respuesta de 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);                
}  

El estándar C99 define que los nombres posteriores en la inicialización anulan artículos anteriores. También podemos tener algunos parámetros posicionales estándar, simplemente cambie la macro y la firma de la función en consecuencia. Los parámetros de valor predeterminados solo se pueden usar en el estilo de parámetro con nombre.

La salida del programa:

1 2 3 4 5 6 7 8 9 10 
2 3 4 
1 3 5 7 9 
+2

Esto parece una implementación más sencilla y directa que la solución Wim ten Brink o BK. ¿Hay algún inconveniente en esta implementación que los demás no poseen? – stephenmm

14

OpenCV utiliza algo como:

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

Si el usuario no sabe lo que debe escribir, este truco puede ser útil:

usage example

+0

¡Esa es en realidad una integración IDE bastante buena para C! Por cierto, es "predeterminado", no "desafiante". – Thomas

-4

¿Por qué no podemos hacer esto.

Proporcione al argumento opcional un valor predeterminado. De esa forma, la persona que llama de la función no necesariamente tiene que pasar el valor del argumento. El argumento toma el valor predeterminado. Y fácilmente ese argumento se vuelve opcional para el cliente.

Por ej.

void foo (int a, int b = 0);

Aquí b es un argumento opcional.

+6

Impresionante idea, el problema es que C no admite argumentos opcionales o funciones sobrecargadas, por lo que la solución directa no se compila. – Thomas

1

he mejorado de Jens Gustedt answer de modo que:

  1. funciones en línea no son empleados
  2. valores predeterminados se calculan en la decodificación previa
  3. macros reutilizables modulares
  4. posible fijar error del compilador que coincide de manera significativa la caso de argumentos insuficientes para los valores predeterminados permitidos
  5. los valores predeterminados no son necesarios para formar la cola de la lista de parámetros si t Los tipos de argumentos serán inequívocos
  6. interopera con C11 _Generic
  7. ¡cambie el nombre de la función por el número de argumentos!

variadic.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 

simplificado escenario de uso:

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

Y con _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__)) 

Y con la selección de nombre de la función variadic, que no se puede combinar con _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__) 
Cuestiones relacionadas