C argumentos por defecto


279

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


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

Pregunta relacionada que no vi en la lista de la barra lateral.
Shelby Moore III

Yo diría que dejen de ser bárbaros y aprendan a usar C ++ (11, ...) bien, ¡jk! / yo apaga las llamas ... pero ... te encantará ... jajaja no puedo evitarlo, lo siento.
moodboom

Respuestas:


147

Realmente no. La única forma sería escribir una función varargs y completar manualmente los valores predeterminados para los argumentos que la persona que llama no pasa.


22
Odio la falta de verificación cuando uso varargs.
dmckee --- ex-gatito moderador

16
Como bien deberías; En realidad no recomiendo esto; Solo quería transmitir que es posible.
Eli Courtwright

44
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 pasó? 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.
Johannes Schaub - litb

3
La open(2)llamada al sistema usa esto para un argumento opcional que puede estar presente dependiendo de los argumentos requeridos, y printf(3)lee una cadena de formato que especifica cuántos argumentos habrá. Ambos usan varargs de manera bastante segura y efectiva, y aunque ciertamente puedes arruinarlos, printf()especialmente parece ser bastante popular.
Chris Lutz

44
@Eli: No todos los compiladores de C son gcc. Hay algo de magia de compilación avanzada que le avisa cuando sus argumentos printf () no coinciden con su cadena de formato. Y no creo que sea posible obtener advertencias similares para sus propias funciones variadas (a menos que usen el mismo estilo de cadena de formato).
tomlogic

280

Wow, todos son tan pesimistas por aquí. La respuesta es sí.

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

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

Nos gustaría girar

double f(int i, double x)

en una función que toma valores predeterminados (i = 8, x = 3.14). Defina una estructura complementaria:

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

Cambie el nombre de su función f_basey defina una función de contenedor que establezca valores predeterminados y llame a 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 agregue una macro, usando las macros variadas de C. De esta manera, los usuarios no tienen que saber que en realidad están poblando una f_argsestructura y piensan que están haciendo lo habitual:

#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

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

Una cosa que no funcionará f(0), porque no podemos distinguir entre un valor perdido y cero. En mi experiencia, esto es algo a tener en cuenta, pero se puede solucionar cuando surja la necesidad, la mitad del tiempo que su valor predeterminado realmente es cero.

Tuve la molestia de escribir esto porque creo que los argumentos y 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 tener suficiente para hacer todo esto posible.


16
+1 creativo! 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

28
Sin embargo, aquí hay algo excelente para usted: el estándar permite especificar miembros nombrados varias veces, la anulación posterior. Por lo tanto, solo para parámetros con nombre puede resolver el problema predeterminado y permitir una llamada vacía. #define vrange(...) CALL(range,(param){.from=1, .to=100, .step=1, __VA_ARGS__})
u0b34a0f6ae

3
Espero que los errores del compilador sean legibles, ¡pero esta es una gran técnica! Casi parece Python Kwargs.
totowtwo

66
@RunHolt Aunque ciertamente es más simple, no es objetivamente mejor; Los parámetros nombrados vienen con beneficios como la facilidad de lectura de las llamadas (a expensas de la lectura del código fuente). Uno es mejor para los desarrolladores de la fuente, el otro es mejor para los usuarios de la función. Es un poco apresurado tirar "¡este es mejor!"
Alice

3
@DawidPi: C11 6.7.9 (19), al inicializar agregados: "todos los subobjetos que no se inicializan explícitamente se inicializarán implícitamente igual que los objetos que tienen una duración de almacenamiento estático" Y como saben, los elementos de duración estática se inicializan a cero | NULL | \ 0. [Esto también fue en c99.]
bk.

161

Si. :-) Pero no de la forma 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, en realidad estarías llamando a f1 con un valor predeterminado. Esta es una solución "No te repitas", que te ayuda a evitar copiar / pegar el código existente.


24
FWIW, preferiría usar el número al final de la función para indicar el número de argumentos que toma. Hace que sea más fácil que usar cualquier número arbitrario. :)
Macke

44
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 la necesito para tomar un nuevo parámetro. Por supuesto, es tan cegadoramente obvio que me lo perdí (¡me quedé estancado pensando en el
parámetro

44
f2 también podría ser una macro de preprocesador
osvein

41

Podemos crear funciones que utilizan parámetros con nombre (solo) para los valores predeterminados. 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 los elementos 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 predeterminado solo se pueden usar en el estilo de parámetro con nombre.

Salida del programa:

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

2
Parece una implementación más sencilla y sencilla que la solución Wim ten Brink o BK. ¿Hay alguna desventaja en esta implementación que los demás tampoco poseen?
stephenmm

24

OpenCV usa 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:

ejemplo de uso


19

No.

Ni siquiera el último estándar C99 lo admite.


un simple no hubiera sido aún mejor;)
KevinDTimm

21
@kevindtimm: Eso no es posible, por lo que SO exige una duración mínima en las respuestas. Lo intenté. :)
Descanse

2
Por favor, consulte mi respuesta. :)
caos

18

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


14

Respuesta corta: no.

Respuesta un poco más larga: hay una solución antigua y antigua en la que pasa una cadena que analiza para obtener argumentos opcionales:

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

donde opt puede incluir el par "nombre = valor" o algo así, y al que llamarías como

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

Obviamente, esto solo es útil ocasionalmente. En general, cuando desea una interfaz única para una familia de funcionalidades.


Todavía encuentra este enfoque en códigos de física de partículas escritos por programas profesionales en c ++ (como, por ejemplo, ROOT ). Su principal ventaja es que puede extenderse casi indefinidamente mientras se mantiene la compatibilidad.


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

55
Usaría un personalizado structy pediría a la persona que llama que haga uno, complete los campos para diferentes opciones y luego lo pase por dirección o pase NULLpor las opciones predeterminadas.
Chris Lutz

¡Copiar patrones de código desde ROOT es una idea terrible!
innisfree

13

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

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

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


13

Otra opción más usa structs:

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

9

No.


2
¿Cuál es la solución? Puedo ver que está 20202020en hexadecimal, pero ¿cómo lo escribo?
Lazer

5

Otro truco usando macros:

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

Si solo se pasa un argumento, brecibe el valor predeterminado (en este caso 15)


4

No, pero puede considerar el uso de un conjunto de funciones (o macros) para aproximarse utilizando argumentos predeterminados:

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

4

Sí, con las características de C99 puede hacer esto. 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 ninguna sobrecarga computacional.

Para una explicación detallada, vea mi publicación en

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

Jens


Seel también mi respuesta que se deriva de la tuya.
Shelby Moore III

1
En línea un ejemplo.
user877329

3

Generalmente no, pero en gcc Puede hacer que el último parámetro de funcA () sea 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) );
}

1

Mejoré la respuesta de Jens Gustedt para que:

  1. no se emplean funciones en línea
  2. los valores predeterminados se calculan durante el preprocesamiento
  3. macros modulares reutilizables
  4. posible establecer un error del compilador que coincida significativamente con el 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 los tipos de argumento no serán ambiguos
  6. interopera con C11 _Generic
  7. ¡Varíe el nombre de la función por la cantidad 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

Escenario de uso simplificado:

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 función variable, 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__)

1

SI

A través de macros

3 parámetros:

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

Si desea el 4to argumento, entonces se necesita agregar my_func3 adicional. Observe los cambios en VAR_FUNC, my_func2 y my_func

4 parámetros:

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

La única excepción es que las variables flotantes no pueden tener valores predeterminados (a menos que sea el último argumento como en el caso de los 3 parámetros ), porque necesitan un punto ('.'), Que no se acepta dentro de los argumentos macro. Pero puede resolver un problema como se ve en la macro my_func2 ( de 4 parámetros )

Programa

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

Salida:

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  

0

Sí, puede hacer algo similar, aquí debe conocer las diferentes listas de argumentos que puede obtener, pero tiene la misma función que manejar.

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

-6

¿Por qué no podemos hacer esto?

Otorgue al argumento opcional un valor predeterminado. De esa manera, el llamador de la función no necesariamente necesita pasar el valor del argumento. El argumento toma el valor predeterminado. Y fácilmente ese argumento se vuelve opcional para el cliente.

Por ej.

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

Aquí b es un argumento opcional.


10
Una visión sorprendente, el problema es que C no admite argumentos opcionales o funciones sobrecargadas, por lo que la solución directa no se compila.
Thomas
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.