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?
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?
Yanıtlar:
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_list
tü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.
...
sözdiziminden önce en az bir argüman sunmak gerekli midir?
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.
<cstdarg>
yerine C ++ kullanmalısınız<stdarg.h>
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 gcc
veya clang
kullanabileceğ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. [...]
typename
vs class
kasıtlı söz konusu olduğu kullanımlarda? Varsa, lütfen açıklayın.
initializer_list
özyinelemeli bir işlev yapmak mümkün mü?
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. :
İş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:
template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Head
ve Tail...
aynıysa etkinleştirilir" şeklinde okur , burada " aynı " anlamına gelir std::conjunction<std::is_same<Head, Tail>...>
. Bu son tanımı " Head
hepsi ile aynı " olarak okuyun Tail...
.
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!
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.
,
operatörü katlama ifadeleriyle kullanabilirsiniz). Aksi halde, sanmıyorum.
C-stil varyasyon fonksiyonları C ++ 'da desteklenmektedir.
Bununla birlikte, çoğu C ++ kütüphanesi alternatif bir deyim kullanır, ancak 'c' printf
işlev değişken değişkenlerini alırken c++ cout
nesnenin <<
tür güvenliğini ve ADT'leri (belki de uygulama basitliği pahasına) ele alan aşırı yükleme kullanır .
std::initializer_lists
başlatıyorsunuz ve listeyi sonuna kadar iletiyorsunuz ... Ve bu zaten basit bir göreve büyük bir karmaşıklık getiriyor.
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 ++ ...
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.
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ü myfunc
de 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 i
ve vermenin hiçbir yolu yoktur j
.
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.
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...
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;
}
};
Diğerlerinin söylediği gibi, C tarzı varargs. Ancak, varsayılan bağımsız değişkenlerle de benzer bir şey yapabilirsiniz.
Ş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);
}
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.
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