Değişken sayısı C ++?


293

Değişken sayıda bağımsız değişkeni kabul eden bir işlevi nasıl yazabilirim? Bu mümkün mü, nasıl?


49
C ++ 11 ile şu anda bu sorunun cevapları çok farklı olacaktır
K-ballo

1
@ K-ballo C ++ 11 örneklerini de ekledim, çünkü son bir soru son zamanlarda aynı şeyi sordu ve bunu kapatmayı haklı çıkarmak için bir tane gerekli olduğunu hissettim stackoverflow.com/questions/16337459/…
Shafik Yaghmour

1
Cevabıma da C ++ 11 öncesi seçenekler eklendi , bu yüzden şimdi mevcut seçeneklerin çoğunu kapsamalıdır.
Shafik Yaghmour

@ K-ballo afaik C argümanında zorla argüman türüne ihtiyacınız varsa bunu yapmanın bir yolu yoktur .. foo (int ... değerleri) gibi bir yapı yok: / Türleri umursamıyorsanız, evet, varyasyon şablonları C ++ 11'de harika çalışıyor
graywolf

Yanıtlar:


152

Muhtemelen yapmamalısınız ve muhtemelen yapmak istediğiniz şeyi daha güvenli ve daha basit bir şekilde yapabilirsiniz. Teknik olarak C'de değişken sayıda argüman kullanmak için stdarg.h ifadesini dahil edersiniz. Elde edeceğinin itibaren va_listtürünü yanı sıra denir üzerinde işlem üç işlev va_start(), va_arg()ve 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;
}

Bana sorarsan, bu bir karmaşa. Kötü görünüyor, güvenli değil ve kavramsal olarak elde etmeye çalıştığınız şeyle ilgisi olmayan teknik detaylarla doludur. Bunun yerine, aşırı yükleme veya kalıtım / polimorfizm, oluşturucu deseni ( operator<<()akışlarda olduğu gibi ) veya varsayılan bağımsız değişkenler vb. bacağını uçurmadan önce seni.


7
Muhtemelen, bir varargs işlevine başvurular iletemezsiniz çünkü derleyici değere ne zaman ve ne zaman referansa göre geçileceğini bilemez ve temeldeki C makroları referanslarla ne yapılacağını bilmeyebilir - zaten ne üzerinde kısıtlamalar vardır promosyon kuralları gibi şeyler nedeniyle değişken argümanlarla C işlevine geçebilirsiniz.
Jonathan Leffler

3
...sözdiziminden önce en az bir argüman sunmak gerekli midir?
Lazer

3
@Lazer bir dil veya kütüphane gereksinimi değildir, ancak standart kütüphane size listenin uzunluğunu belirtmek için bir araç sunmaz. Arayanın size bu bilgileri vermesi veya bir şekilde kendiniz çözmeniz gerekir. Durumunda printf(), örneğin, işlevi değişken bağımsız değişken listesinde beklemeliyim kaç ekstra argümanlar dışarı şekle özel simgeleri için dize argümanı ayrıştırır.
wilhelmtell

11
muhtemelen <cstdarg>yerine C ++ kullanmalısınız<stdarg.h>
newacct

11
Değişken bağımsız değişken sayısı hata ayıklama veya bazı dizileri dolduran işlevler / yöntemler için mükemmeldir. Ayrıca max, min, sum, ortalama gibi birçok matematiksel işlem için harikadır ... Onunla uğraşmadığınızda karışıklık olmaz.
Tomáš Zato - Monica

395

In C ++ 11 siz iki yeni seçenek var Değişkin fonksiyonları referans sayfası Alternatifler bölüm devletler:

  • Değişken şablonları, değişken sayıda bağımsız değişken alan işlevler oluşturmak için de kullanılabilir. Genellikle daha iyi bir seçimdir, çünkü argüman türlerine kısıtlama getirmezler, integral ve kayan nokta promosyonları yapmazlar ve tip güvenlidirler. (C ++ 11'den beri)
  • Tüm değişken bağımsız değişkenleri ortak bir türü paylaşıyorsa, std :: initializer_list, değişken bağımsız değişkenlerine erişmek için uygun bir mekanizma (farklı bir sözdizimi de olsa) sağlar.

Aşağıda her iki alternatifi de gösteren bir örnek bulunmaktadır ( bakınız canlı ):

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

Eğer kullanıyorsanız gccveya clangkullanabileceğimiz PRETTY_FUNCTION sihirli değişken olan biteni anlamada yararlı olabilir fonksiyonun tipi imzasını görüntülemek için. Örneğin:

std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;

örnekte varyasyon fonksiyonları için int ile sonuçlanır ( bakınız canlı ):

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

Visual Studio'da FUNCSIG kullanabilirsiniz .

Ön C ++ 11'i Güncelle

Ön C ++ 11 alternatif std :: initializer_list olacaktır Std :: vektör veya başka bir standart konteyner :

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

ve değişken tipli şablonların alternatifi, tip-güvenli ve genel olarak hataya eğilimli olmaları ve kullanımı güvensiz olmalarına rağmen değişken işlevler olacaktır , ancak diğer tek potansiyel alternatif , sınırlı kullanıma sahip olmasına rağmen varsayılan bağımsız değişkenleri kullanmak olacaktır. Aşağıdaki örnek, bağlantılı başvurudaki örnek kodun değiştirilmiş bir sürümüdür:

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

Variadic işlevlerin kullanılması , İşlev çağrısı paragraf 7 bölümündeki taslak C ++ standardında ayrıntılı olarak verilebilen bağımsız değişkenlerde kısıtlamalarla birlikte gelir :5.2.2

Belirli bir bağımsız değişken için parametre olmadığında, bağımsız değişken alma işlevi va_arg (18.7) öğesini çağırarak bağımsız değişkenin değerini alabilecek şekilde geçirilir. Lvalue-rvalue (4.1), dizi-işaretçi (4.2) ve işlev-işaretçi (4.3) standart dönüşümleri bağımsız değişken ifadesinde gerçekleştirilir. Bu dönüşümlerden sonra, bağımsız değişkenin aritmetik, numaralandırma, işaretçi, üyeye işaretçi veya sınıf türü yoksa, program kötü biçimlendirilir. Bağımsız değişken POD olmayan bir sınıf türüne sahipse (madde 9), davranış tanımsızdır. [...]


Senin olduğunu typenamevs classkasıtlı söz konusu olduğu kullanımlarda? Varsa, lütfen açıklayın.
kevinarpe

1
@kevinarpe kasıtlı değil, yine de hiçbir şey değişmez.
Shafik Yaghmour

İlk bağlantınız muhtemelen bunun yerine en.cppreference.com/w/cpp/language/variadic_arguments olmalıdır .
Alexey Romanov

initializer_listözyinelemeli bir işlev yapmak mümkün mü?
idclev 463035818

33

Bir C ++ 17 çözümü: tam tip güvenlik + hoş arama sözdizimi

C ++ 11'de değişken şablonların ve C ++ 17'de katlama ifadelerinin kullanılmasından bu yana, arayan sitede sanki bir varidik işlevmiş gibi, ancak avantajları ile çağrılabilen bir şablon işlevi tanımlamak mümkündür. :

  • güçlü bir şekilde yazın;
  • bağımsız değişken sayısının çalışma zamanı bilgisi olmadan veya "durdur" bağımsız değişkeni kullanılmadan çalışır.

İşte karışık argüman türlerine bir örnek

template<class... Args>
void print(Args... args)
{
    (std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");

Ve tüm bağımsız değişkenler için zorunlu tür eşleşmesine sahip bir diğeri:

#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!");
                                                                                              ^

Daha fazla bilgi:

  1. Parametre paketi olarak da bilinen varyasyon şablonları Parametre paketi (C ++ 11'den beri) - cppreference.com .
  2. Katlama ifadeleri katlama ifadesi (C ++ 17'den beri) - cppreference.com .
  3. Bir bakın tam bir program gösteri coliru üzerinde.

13
bir gün hasta umarım okuyabilirimtemplate<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Eladian

1
@Eladian "Bu şey sadece Headve Tail... aynıysa etkinleştirilir" şeklinde okur , burada " aynı " anlamına gelir std::conjunction<std::is_same<Head, Tail>...>. Bu son tanımı " Headhepsi ile aynı " olarak okuyun Tail....
YSC

24

c ++ 11'de şunları yapabilirsiniz:

void foo(const std::list<std::string> & myArguments) {
   //do whatever you want, with all the convenience of lists
}

foo({"arg1","arg2"});

liste başlatıcısı FTW!


19

C ++ 11'de, değişken argüman işlevlerine sahip olmak için gerçekten zarif ve güvenli bir yol sağlayan değişken argüman şablonları yapmanın bir yolu vardır. Bjarne kendini güzel örnek verir değişken argüman şablonları kullanarak printf içinde C ++ 11FAQ .

Şahsen, bu kadar zarif olduğunu düşünüyorum, bu derleyicinin C ++ 11 değişken argüman şablonları için desteği olana kadar C ++ 'da değişken argüman işleviyle bile uğraşmayacağım.


@donlan - C ++ 17 kullanıyorsanız, bazı durumlarda işleri daha basit hale getirmek için katlama ifadelerini kullanabilirsiniz (yaratıcı düşünün, ,operatörü katlama ifadeleriyle kullanabilirsiniz). Aksi halde, sanmıyorum.
Omnifarious

15

C-stil varyasyon fonksiyonları C ++ 'da desteklenmektedir.

Bununla birlikte, çoğu C ++ kütüphanesi alternatif bir deyim kullanır, ancak 'c' printfişlev değişken değişkenlerini alırken c++ coutnesnenin <<tür güvenliğini ve ADT'leri (belki de uygulama basitliği pahasına) ele alan aşırı yükleme kullanır .


Ayrıca, bu yalnızca her argüman boyunca tek bir argüman işlevinin yinelemesine sahip olduğunuz yazdırma gibi bir işlev durumunda işe yarıyor gibi görünüyor. Aksi takdirde, sadece bir liste std::initializer_listsbaşlatıyorsunuz ve listeyi sonuna kadar iletiyorsunuz ... Ve bu zaten basit bir göreve büyük bir karmaşıklık getiriyor.
Christopher

13

Varargs veya aşırı yükleme dışında, argümanlarınızı bir std :: vector veya diğer kaplarda toplamayı düşünebilirsiniz (örneğin, std :: map). Bunun gibi bir şey:

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

Bu şekilde tip güvenliği elde edersiniz ve bu varyasyon argümanlarının mantıksal anlamı açıktır.

Elbette bu yaklaşımın performans sorunları olabilir, ancak fiyatı ödeyemeyeceğinizden emin değilseniz onlar için endişelenmemelisiniz. Bu bir çeşit bir "Pythonic" yaklaşım c ++ ...


6
Daha temiz vektörleri zorlamamak olacaktır. Bunun yerine, STL stilindeki koleksiyonu belirten bir şablon argümanı kullanın ve ardından argümanın başlangıç ​​ve bitiş yöntemlerini kullanarak yineleyin. Bu şekilde std :: vector <T>, c ++ 11's std :: dizi <T, N>, std :: initializer_list <T> kullanabilir veya hatta kendi koleksiyonunuzu oluşturabilirsiniz.
Jens Åkerblom

3
@ JensÅkerblom Kabul ediyorum ama bu, mühendislikten kaçınmak için eldeki sorun için analiz edilmesi gereken bir seçim. Bu bir API imzası meselesi olduğundan, maksimum esneklik ile niyet / kullanılabilirlik / bakım kolaylığı vb. Arasındaki dengeyi anlamak önemlidir
Francesco

8

Tek yol, burada açıklandığı gibi C stili değişken argümanlarının kullanılmasıdır . Tipik ve hataya açık olmadığı için bunun önerilen bir uygulama olmadığını unutmayın.


Yüzüstü eğilime göre, potansiyel olarak çok tehlikeli olduğunu mu düşünüyorsunuz? Özellikle güvenilir olmayan girdilerle çalışırken.
Kevin Loney

Evet, ancak tür güvenlik sorunları nedeniyle. Düzenli printf'in sahip olabileceği tüm olası sorunları düşünün: iletilen argümanlarla eşleşmeyen biçim dizeleri vb. printf BTW ile aynı tekniği kullanır.
Dave Van den Eynde

7

C tarzı varargs ( ...) başvurmadan bunu yapmak için standart bir C ++ yolu yoktur .

Elbette bağlama bağlı olarak değişken sayısı gibi "görünüş" gibi varsayılan argümanlar vardır:

void myfunc( int i = 0, int j = 1, int k = 2 );

// other code...

myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );

Dört işlev çağrısının tümü myfuncde değişen sayıda argümanla çağrılır. Hiçbiri verilmezse, varsayılan bağımsız değişkenler kullanılır. Ancak, yalnızca sondaki bağımsız değişkenleri atlayabileceğinizi unutmayın. Örneğin atlamanın ive vermenin hiçbir yolu yoktur j.


4

Aşırı yükleme veya varsayılan parametreler isteyebilirsiniz - aynı işlevi varsayılan parametrelerle tanımlayın:

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

Bu, yöntemi dört farklı çağrıdan biriyle çağırmanızı sağlar:

doStuff( 1 );
doStuff( 2, 2.5 );
doStuff( 1, 1.0, false );
doStuff( 6.72 );

... ya da C'den v_args çağrı kurallarını arıyor olabilirsiniz.


2

Sağlanacak bağımsız değişkenlerin sayısını biliyorsanız, her zaman aşağıdaki gibi aşırı işlev yükü kullanabilirsiniz:

f(int a)
    {int res=a; return res;}
f(int a, int b)
    {int res=a+b; return res;}

ve bunun gibi...


2

Değişken şablonları kullanma console.log, JavaScript'te görüldüğü gibi çoğaltma örneği :

Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");

Dosya adı örneğin 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;
    }
};


0

Şimdi mümkün ... herhangi bir destek ve şablonlar kullanma Bu durumda, argüman türü karıştırılabilir

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

0

Anlamsal olarak en basit, en performanslı ve en dinamik seçenek için C ve C ++ çözümlerini birleştirin. Berbat edersen, başka bir şey dene.

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

Kullanıcı şunu yazar:

auto arr = spawn<float> (3, 0.1,0.2,0.3);

Anlamsal olarak, bu tam olarak bir n-argüman işlevi gibi görünür ve hissedilir. Kaputun altında, şu şekilde açabilirsiniz.


-1

Tüm bağımsız değişkenler sabit ve aynı türdeyse bir initializer_list de kullanabiliriz


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

Düz sürüm


1
Ve x86 dışında bir şey üzerinde çalışmayan tanımsız davranış.
SS Anne
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.