Question Nombre variable de paramètres dans la fonction en C ++


Comment je peux avoir un nombre variable de paramètres dans ma fonction en C ++.

Analog in C #:

public void Foo(params int[] a) {
    for (int i = 0; i < a.Length; i++)
        Console.WriteLine(a[i]);
}

public void UseFoo() {
    Foo();
    Foo(1);
    Foo(1, 2);
}

Analog en Java:

public void Foo(int... a) {
    for (int i = 0; i < a.length; i++)
        System.out.println(a[i]);
}

public void UseFoo() {
    Foo();
    Foo(1);
    Foo(2);
}

26
2017-10-16 18:43


origine


Réponses:


On les appelle Fonctions variadiques. Listes Wikipedia exemple de code pour C ++.

Mettre en œuvre de manière portative variadic   fonctions dans la programmation en C   langue, la norme stdarg.h entête   fichier doit être utilisé. Le plus vieux   L'en-tête varargs.h est obsolète   en faveur de stdarg.h. En C ++, le   En tête de fichier cstdarg Devrait être utilisé.

Pour créer une fonction variadique, un   ellipse (...) doit être placé à la   fin d'une liste de paramètres. À l'intérieur de   corps de la fonction, une variable de   type va_list doit être défini. Puis le   les macros va_start(va_list, last fixed param), va_arg(va_list, cast type),    va_end(va_list) peut être utilisé. Pour   Exemple:

#include <stdarg.h>

double average(int count, ...)
{
    va_list ap;
    int j;
    double tot = 0;
    va_start(ap, count); //Requires the last fixed parameter (to get the address)
    for(j=0; j<count; j++)
        tot+=va_arg(ap, double); //Requires the type to cast to. Increments ap to the next argument.
    va_end(ap);
    return tot/count;
}

37
2017-10-16 18:47



La véritable solution C ++ est constituée de modèles variadiques. Vous aurez besoin d'un compilateur assez récent et activez le support C ++ 11 si nécessaire.

Deux manières de gérer le problème "faire la même chose avec tous les arguments de fonction": récursivement, et avec une solution laide (mais très très conforme aux normes).

le solution récursive ressemble un peu à ceci:

template<typename... ArgTypes>
void print(ArgTypes... args);
template<typename T, typename... ArgTypes>
void print(T t, ArgTypes... args)
{
  std::cout << t;
  print(args...);
}
template<> void print() {} // end recursion

Il génère un symbole pour chaque collection d'arguments, puis un pour chaque étape de la récursivité. C'est sous-optimal pour le moins, donc les gens C ++ géniaux ici à SO pensé à un truc génial en utilisant l'effet secondaire d'une initialisation de liste:

struct expand_type {
  template<typename... T>
  expand_type(T&&...) {}
};
template<typename... ArgTypes>
void print(ArgTypes... args)
{ 
  expand_type{ 0, (std::cout << args, 0)... };
}

Le code n'est pas généré pour un million d'instanciations de modèles légèrement différentes, et en bonus, vous obtenez un ordre préservé de vos arguments de fonction. Voir l'autre réponse pour les détails concrets de cette solution.


11
2017-12-14 16:47



Outre les autres réponses, si vous essayez juste de passer un tableau d'entiers, pourquoi pas:

void func(const std::vector<int>& p)
{
    // ...
}

std::vector<int> params;
params.push_back(1);
params.push_back(2);
params.push_back(3);

func(params);

Vous ne pouvez pas l'appeler en paramètre, form, cependant. Vous devez utiliser l'une des fonctions variadiques listées dans vos réponses. C ++ 0x autorisera les modèles variadiques, ce qui le rendra compatible avec les types, mais pour l'instant, il s'agit essentiellement de mémoire et de conversion.

Vous pouvez émuler une sorte de paramètre variadic-> chose vectorielle:

// would also want to allow specifying the allocator, for completeness
template <typename T> 
std::vector<T> gen_vec(void)
{
    std::vector<T> result(0);
    return result;
}

template <typename T> 
std::vector<T> gen_vec(T a1)
{
    std::vector<T> result(1);

    result.push_back(a1);

    return result;
}

template <typename T> 
std::vector<T> gen_vec(T a1, T a2)
{
    std::vector<T> result(1);

    result.push_back(a1);
    result.push_back(a2);

    return result;
}

template <typename T> 
std::vector<T> gen_vec(T a1, T a2, T a3)
{
    std::vector<T> result(1);

    result.push_back(a1);
    result.push_back(a2);
    result.push_back(a3);

    return result;
}

// and so on, boost stops at nine by default for their variadic templates

Usage:

func(gen_vec(1,2,3));

5
2017-10-16 18:55



Voir Fonctions variadiques en C, Objective-C, C ++ et D

Vous devez inclure stdarg.h et ensuite utiliser va_list, va_start, va_arg et va_end, comme le montre l'exemple de l'article de Wikipedia. C'est un peu plus encombrant qu'en Java ou en C #, car C et C ++ ne disposent que d'un support intégré limité pour varargs.


3
2017-10-16 18:47



En C ++ 11 et versions ultérieures, vous pouvez également utiliser des listes d'initialisation.

int sum(const initializer_list<int> &il)
{
    int nSum = 0;
    for (auto x: il) 
        nSum += x;
    return nsum;
}

cout << sum( { 3, 4, 6, 9 } );

3
2017-11-01 08:31



Si vous ne vous souciez pas de la portabilité, vous pouvez transférer ce code C99 en C ++ en utilisant expressions de l'instruction gcc:

#include <cstdio>

int _sum(size_t count, int values[])
{
    int s = 0;
    while(count--) s += values[count];
    return s;
}

#define sum(...) ({ \
    int _sum_args[] = { __VA_ARGS__ }; \
    _sum(sizeof _sum_args / sizeof *_sum_args, _sum_args); \
})

int main(void)
{
    std::printf("%i", sum(1, 2, 3));
}

Vous pouvez faire quelque chose de similaire avec les expressions lambda de C ++ 0x, mais la version de gcc que j'utilise (4.4.0) ne les prend pas en charge.


1
2017-10-16 22:19



Les réponses de GManNickG et de Christoph sont bonnes, mais les fonctions variadiques vous permettent ... paramètre que vous voulez, pas seulement des entiers. Si vous volontévouloir dans le avenir, pour pousser de nombreuses variables et valeurs de différent taper dans une fonction sans utiliser la fonction variadic, parce que c'est trop difficile ou trop compliqué pour vous, ou vous n'aimez pas la façon de l'utiliser ou vous ne voulez pas inclure les en-têtes requis pour l'utiliser, alors vous pouvez toujours utilisation void** paramètre.

Par exemple, Stephan202 a publié:

double average(int count, ...)
{
    va_list ap;
    int j;
    double tot = 0;
    va_start(ap, count); //Requires the last fixed parameter (to get the address)
    for(j=0; j<count; j++)
        tot+=va_arg(ap, double); //Requires the type to cast to. Increments ap to the next argument.
    va_end(ap);
    return tot/count;
}

cela peut aussi être écrit comme:

double average(int count, void** params)
{
    int j;
    double tot = 0;
    for (j=0; j<count; j++)
       tot+=*(double*)params[j];
    return tot/count;
}

Maintenant, utilisez-le comme ceci:

int _tmain(int argc, _TCHAR* argv[])
{
    void** params = new void*[3];
    double p1 = 1, p2 = 2, p3 = 3;
    params[0] = &p1;
    params[1] = &p2;
    params[2] = &p3;
    printf("Average is: %g\n", average(3, params));
    system("pause");
    return 0;
}

pour le code complet:

#include "stdafx"
#include <process.h>

double average(int count, void** params)
{
    int j;
    double tot = 0;
    for (j=0; j<count; j++)
        tot+=*(double*)params[j];
    return tot/count;
}

int _tmain(int argc, _TCHAR* argv[])
{
    void** params = new void*[3];
    double p1 = 1, p2 = 2, p3 = 3;
    params[0] = &p1;
    params[1] = &p2;
    params[2] = &p3;
    printf("Average is: %g\n", average(3, params));
    system("pause");
    return 0;
 }

SORTIE:

La moyenne est: 2

Appuyez sur n'importe quelle touche pour continuer . . .


1
2017-12-14 16:02