C'de fonksiyon aşırı yüklemesi elde etmenin bir yolu var mı? Aşırı yüklenecek basit fonksiyonlara bakıyorum
foo (int a)
foo (char b)
foo (float c , int d)
Bence doğrudan bir yol yok; Varsa geçici çözümler arıyorum.
C'de fonksiyon aşırı yüklemesi elde etmenin bir yolu var mı? Aşırı yüklenecek basit fonksiyonlara bakıyorum
foo (int a)
foo (char b)
foo (float c , int d)
Bence doğrudan bir yol yok; Varsa geçici çözümler arıyorum.
Yanıtlar:
Birkaç olasılık vardır:
Evet!
Bu sorunun sorulmasından bu yana, standart C (uzantı yok ), anahtar kelimenin C11'e eklenmesi sayesinde işlev aşırı yüklemesi (operatörler değil) için etkili bir şekilde destek kazanmıştır_Generic
. (4.9 sürümünden beri GCC'de desteklenmektedir)
(Aşırı yükleme, soruda gösterilen şekilde gerçekten "yerleşik" değildir, ancak böyle çalışan bir şeyi uygulamak çok kolaydır.)
_Generic
aynı aile içinde bir derleme zamanı operatörüdür sizeof
ve _Alignof
. Standart bölüm 6.5.1.1'de açıklanmaktadır. İki ana parametreyi kabul eder: bir ifade (çalışma zamanında değerlendirilmeyecektir) ve biraz switch
blok gibi görünen bir tür / ifade ilişkilendirme listesi . _Generic
ifadenin genel türünü alır ve ardından türünün listesindeki son sonuç ifadesini seçmek için üzerinde "geçiş yapar":
_Generic(1, float: 2.0,
char *: "2",
int: 2,
default: get_two_object());
Yukarıdaki ifade şu şekilde değerlendirilir 2
- kontrol eden ifadenin türü int
, yani int
değer olarak ilişkili ifadeyi seçer . Bunların hiçbiri çalışma zamanında kalmaz. ( default
Cümle isteğe bağlıdır: kapalı bırakırsanız ve tür eşleşmezse, bir derleme hatasına neden olur.)
Bunun aşırı işlev yüklemesi için yararlı olması, C ön işlemcisi tarafından eklenebilmesi ve kontrol makrosuna iletilen argümanların türüne göre bir sonuç ifadesi seçebilmesidir. Yani (C standardından örnek):
#define cbrt(X) _Generic((X), \
long double: cbrtl, \
default: cbrt, \
float: cbrtf \
)(X)
Bu makro cbrt
, makroya argüman türünü göndererek, uygun bir uygulama işlevi seçip orijinal makro bağımsız değişkenini bu işleve geçirerek aşırı yüklenmiş bir işlem gerçekleştirir.
Orijinal örneğinizi uygulamak için bunu yapabiliriz:
foo_int (int a)
foo_char (char b)
foo_float_int (float c , int d)
#define foo(_1, ...) _Generic((_1), \
int: foo_int, \
char: foo_char, \
float: _Generic((FIRST(__VA_ARGS__,)), \
int: foo_float_int))(_1, __VA_ARGS__)
#define FIRST(A, ...) A
Bu durumda default:
, üçüncü durum için bir ilişkilendirme kullanabilirdik, ancak bu prensibi birden çok argümana nasıl genişleteceğimizi göstermez. Sonuç olarak, foo(...)
kodunuzda argümanlarının türü hakkında endişelenmeden (çok [1]) kullanabilirsiniz.
Daha karmaşık durumlar, örneğin daha fazla sayıda argümanı aşırı yükleyen işlevler veya değişen sayılar için, statik dağıtım yapılarını otomatik olarak oluşturmak için yardımcı program makrolarını kullanabilirsiniz:
void print_ii(int a, int b) { printf("int, int\n"); }
void print_di(double a, int b) { printf("double, int\n"); }
void print_iii(int a, int b, int c) { printf("int, int, int\n"); }
void print_default(void) { printf("unknown arguments\n"); }
#define print(...) OVERLOAD(print, (__VA_ARGS__), \
(print_ii, (int, int)), \
(print_di, (double, int)), \
(print_iii, (int, int, int)) \
)
#define OVERLOAD_ARG_TYPES (int, double)
#define OVERLOAD_FUNCTIONS (print)
#include "activate-overloads.h"
int main(void) {
print(44, 47); // prints "int, int"
print(4.4, 47); // prints "double, int"
print(1, 2, 3); // prints "int, int, int"
print(""); // prints "unknown arguments"
}
( Burada uygulama ) Yani biraz çabayla sen aşırı için doğal destek ile bir dil gibi hemen hemen seyir için Demirbaş miktarını azaltabilir.
Bir yana, C99'da argümanların sayısını (türü değil) aşırı yüklemek zaten mümkün olmuştu .
[1] C'nin türleri değerlendirme biçiminin sizi harekete geçirebileceğini unutmayın. Bu, foo_int
örneğin bir karakter değişmezi iletmeyi denediğinizi seçecektir ve aşırı yüklerinizin dize değişmezlerini desteklemesini istiyorsanız biraz karışıklık yapmanız gerekir . Yine de genel olarak oldukça güzel.
Daha önce de belirtildiği gibi, demek istediğin aşırı yükleme C tarafından desteklenmiyor. Sorunu çözmek için ortak bir deyim, işlevin etiketli birliği kabul etmesini sağlamaktır . Bu, kendisinin a ve farklı değer türlerinden biri gibi bir tür tür göstergesinden oluştuğu bir struct
parametre tarafından uygulanır . Misal:struct
enum
union
#include <stdio.h>
typedef enum {
T_INT,
T_FLOAT,
T_CHAR,
} my_type;
typedef struct {
my_type type;
union {
int a;
float b;
char c;
} my_union;
} my_struct;
void set_overload (my_struct *whatever)
{
switch (whatever->type)
{
case T_INT:
whatever->my_union.a = 1;
break;
case T_FLOAT:
whatever->my_union.b = 2.0;
break;
case T_CHAR:
whatever->my_union.c = '3';
}
}
void printf_overload (my_struct *whatever) {
switch (whatever->type)
{
case T_INT:
printf("%d\n", whatever->my_union.a);
break;
case T_FLOAT:
printf("%f\n", whatever->my_union.b);
break;
case T_CHAR:
printf("%c\n", whatever->my_union.c);
break;
}
}
int main (int argc, char* argv[])
{
my_struct s;
s.type=T_INT;
set_overload(&s);
printf_overload(&s);
s.type=T_FLOAT;
set_overload(&s);
printf_overload(&s);
s.type=T_CHAR;
set_overload(&s);
printf_overload(&s);
}
whatever
(ayrı fonksiyonlara içine s set_int
, set_float
vs.). Sonra "tür ile etiketleme" işlev adını "tür adını ekleyin" olur. Bu yanıtın sürümü, daha fazla yazım, daha fazla çalışma süresi maliyeti, derleme zamanında yakalanmayacak hataların şansını daha fazla içerir ... İşleri bu şekilde yapmak için hiçbir avantaj göremiyorum ! 16 upvotes ?!
İşte C fonksiyon aşırı yüklemesi gösteren bulduğum en açık ve en kısa örnek:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int addi(int a, int b) {
return a + b;
}
char *adds(char *a, char *b) {
char *res = malloc(strlen(a) + strlen(b) + 1);
strcpy(res, a);
strcat(res, b);
return res;
}
#define add(a, b) _Generic(a, int: addi, char*: adds)(a, b)
int main(void) {
int a = 1, b = 2;
printf("%d\n", add(a, b)); // 3
char *c = "hello ", *d = "world";
printf("%s\n", add(c, d)); // hello world
return 0;
}
Derleyiciniz gcc ise ve her yeni yük eklediğinizde el güncellemeleri yapmayı düşünmüyorsanız, biraz makro sihir yapabilir ve arayanlar açısından istediğiniz sonucu elde edebilirsiniz, yazmak iyi değildir ... ama mümkün
__builtin_types_compatible_p dosyasına bakın, ardından böyle bir şey yapan bir makro tanımlamak için kullanın
#define foo(a) \
((__builtin_types_compatible_p(int, a)?foo(a):(__builtin_types_compatible_p(float, a)?foo(a):)
ama evet, sadece yapma
EDIT: C1X, şöyle göründükleri tür genel ifadeler için destek alacak:
#define cbrt(X) _Generic((X), long double: cbrtl, \
default: cbrt, \
float: cbrtf)(X)
Evet, bir çeşit.
İşte size örnek:
void printA(int a){
printf("Hello world from printA : %d\n",a);
}
void printB(const char *buff){
printf("Hello world from printB : %s\n",buff);
}
#define Max_ITEMS() 6, 5, 4, 3, 2, 1, 0
#define __VA_ARG_N(_1, _2, _3, _4, _5, _6, N, ...) N
#define _Num_ARGS_(...) __VA_ARG_N(__VA_ARGS__)
#define NUM_ARGS(...) (_Num_ARGS_(_0, ## __VA_ARGS__, Max_ITEMS()) - 1)
#define CHECK_ARGS_MAX_LIMIT(t) if(NUM_ARGS(args)>t)
#define CHECK_ARGS_MIN_LIMIT(t) if(NUM_ARGS(args)
#define print(x , args ...) \
CHECK_ARGS_MIN_LIMIT(1) printf("error");fflush(stdout); \
CHECK_ARGS_MAX_LIMIT(4) printf("error");fflush(stdout); \
({ \
if (__builtin_types_compatible_p (typeof (x), int)) \
printA(x, ##args); \
else \
printB (x,##args); \
})
int main(int argc, char** argv) {
int a=0;
print(a);
print("hello");
return (EXIT_SUCCESS);
}
PrintA ve printB'den 0 ve merhaba .. çıktılar.
Aşağıdaki yaklaşım a2800276'nınkine benzer , ancak bazı C99 makro sihirleri eklendi:
// we need `size_t`
#include <stddef.h>
// argument types to accept
enum sum_arg_types { SUM_LONG, SUM_ULONG, SUM_DOUBLE };
// a structure to hold an argument
struct sum_arg
{
enum sum_arg_types type;
union
{
long as_long;
unsigned long as_ulong;
double as_double;
} value;
};
// determine an array's size
#define count(ARRAY) ((sizeof (ARRAY))/(sizeof *(ARRAY)))
// this is how our function will be called
#define sum(...) _sum(count(sum_args(__VA_ARGS__)), sum_args(__VA_ARGS__))
// create an array of `struct sum_arg`
#define sum_args(...) ((struct sum_arg []){ __VA_ARGS__ })
// create initializers for the arguments
#define sum_long(VALUE) { SUM_LONG, { .as_long = (VALUE) } }
#define sum_ulong(VALUE) { SUM_ULONG, { .as_ulong = (VALUE) } }
#define sum_double(VALUE) { SUM_DOUBLE, { .as_double = (VALUE) } }
// our polymorphic function
long double _sum(size_t count, struct sum_arg * args)
{
long double value = 0;
for(size_t i = 0; i < count; ++i)
{
switch(args[i].type)
{
case SUM_LONG:
value += args[i].value.as_long;
break;
case SUM_ULONG:
value += args[i].value.as_ulong;
break;
case SUM_DOUBLE:
value += args[i].value.as_double;
break;
}
}
return value;
}
// let's see if it works
#include <stdio.h>
int main()
{
unsigned long foo = -1;
long double value = sum(sum_long(42), sum_ulong(foo), sum_double(1e10));
printf("%Le\n", value);
return 0;
}
Bu hiç yardımcı olmayabilir, ancak clang kullanıyorsanız yüklenebilir özniteliğini kullanabilirsiniz - Bu, C olarak derlenirken bile çalışır
http://clang.llvm.org/docs/AttributeReference.html#overloadable
Başlık
extern void DecodeImageNow(CGImageRef image, CGContextRef usingContext) __attribute__((overloadable));
extern void DecodeImageNow(CGImageRef image) __attribute__((overloadable));
uygulama
void __attribute__((overloadable)) DecodeImageNow(CGImageRef image, CGContextRef usingContext { ... }
void __attribute__((overloadable)) DecodeImageNow(CGImageRef image) { ... }
Normalde, tipin isme eklendiğini veya ismin başına eklendiğini gösteren bir siğil. Makrolarla kaçabileceğiniz bazı örneklerdir, ancak ne yapmaya çalıştığınıza bağlıdır. C'de polimorfizm yoktur, sadece baskı vardır.
Basit genel işlemler makrolarla yapılabilir:
#define max(x,y) ((x)>(y)?(x):(y))
Derleyiciniz typeof'i destekliyorsa , makroya daha karmaşık işlemler eklenebilir. Daha sonra aynı işlemi farklı türleri desteklemek için foo (x) sembolüne sahip olabilirsiniz, ancak farklı aşırı yükler arasındaki davranışı değiştiremezsiniz. Makrolar yerine gerçek işlevler istiyorsanız, adı ada yapıştırabilir ve erişmek için ikinci bir yapıştırma kullanabilirsiniz (denemedim).
Leushenko'nun cevabı gerçekten havalı - sadece: foo
örnek foo(7)
, FIRST
makro ve gerçek işlev çağrısında tökezleyen ( (_1, __VA_ARGS__)
fazla virgülle kalan GCC) ile derlenmiyor . gibi foo(double)
.
Bu yüzden cevabı biraz aşırı yüklemeye izin vermek de dahil olmak üzere biraz daha ayrıntılandırmaya karar verdim ( foo(void)
- bu biraz sorun yarattı ...).
Şimdi fikir: Farklı makrolarda birden fazla genel tanımlayın ve bağımsız değişkenlerin sayısına göre doğru olanı seçelim!
Bu cevaba dayanarak argüman sayısı oldukça kolaydır :
#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__)
#define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__)
#define CONCAT(X, Y) CONCAT_(X, Y)
#define CONCAT_(X, Y) X ## Y
Bu güzel, ya biz ya SELECT_1
da SELECT_2
(ya da eğer isterseniz / ihtiyacınız varsa daha fazla argüman) çözüyoruz, bu yüzden sadece uygun tanımlara ihtiyacımız var:
#define SELECT_0() foo_void
#define SELECT_1(_1) _Generic ((_1), \
int: foo_int, \
char: foo_char, \
double: foo_double \
)
#define SELECT_2(_1, _2) _Generic((_1), \
double: _Generic((_2), \
int: foo_double_int \
) \
)
Tamam, zaten boşluk yükünü ekledim - ancak, bu aslında C standardı tarafından kapsanmıyor, bu da boş varyasyon argümanlarına izin vermiyor, yani derleyici uzantılarına güveniyoruz !
İlk başta, boş bir makro çağrısı ( foo()
) hala bir belirteç üretir, ancak boş bir belirteç üretir. Böylece, sayım makrosu, boş makro çağrısında bile 0 yerine 1 döndürür. Listenin boş olmasına bağlı olarak __VA_ARGS__
koşullu olarak virgül koyarsak, bu sorunu "kolayca" ortadan kaldırabiliriz :
#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)
Bu kolay görünüyordu , ancak COMMA
makro oldukça ağırdır; Neyse ki, konu zaten bir Jens Gustedt bloğunda yer alıyor (teşekkürler, Jens). Temel hile, fonksiyon makrolarının parantez tarafından takip edilmezse genişletilmemesidir, daha fazla açıklama için Jens'in bloguna bir göz atın ... Makroları biraz ihtiyaçlarımıza göre değiştirmeliyiz (daha kısa isimler kullanacağım) ve kısalık için daha az argüman).
#define ARGN(...) ARGN_(__VA_ARGS__)
#define ARGN_(_0, _1, _2, _3, N, ...) N
#define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 1, 0)
#define SET_COMMA(...) ,
#define COMMA(...) SELECT_COMMA \
( \
HAS_COMMA(__VA_ARGS__), \
HAS_COMMA(__VA_ARGS__ ()), \
HAS_COMMA(SET_COMMA __VA_ARGS__), \
HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \
)
#define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3)
#define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3
#define COMMA_0000 ,
#define COMMA_0001
#define COMMA_0010 ,
// ... (all others with comma)
#define COMMA_1111 ,
Ve şimdi iyiyiz ...
Tek bir blokta tam kod:
/*
* demo.c
*
* Created on: 2017-09-14
* Author: sboehler
*/
#include <stdio.h>
void foo_void(void)
{
puts("void");
}
void foo_int(int c)
{
printf("int: %d\n", c);
}
void foo_char(char c)
{
printf("char: %c\n", c);
}
void foo_double(double c)
{
printf("double: %.2f\n", c);
}
void foo_double_int(double c, int d)
{
printf("double: %.2f, int: %d\n", c, d);
}
#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__)
#define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__)
#define CONCAT(X, Y) CONCAT_(X, Y)
#define CONCAT_(X, Y) X ## Y
#define SELECT_0() foo_void
#define SELECT_1(_1) _Generic ((_1), \
int: foo_int, \
char: foo_char, \
double: foo_double \
)
#define SELECT_2(_1, _2) _Generic((_1), \
double: _Generic((_2), \
int: foo_double_int \
) \
)
#define ARGN(...) ARGN_(__VA_ARGS__)
#define ARGN_(_0, _1, _2, N, ...) N
#define NARG(...) ARGN(__VA_ARGS__ COMMA(__VA_ARGS__) 3, 2, 1, 0)
#define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 0)
#define SET_COMMA(...) ,
#define COMMA(...) SELECT_COMMA \
( \
HAS_COMMA(__VA_ARGS__), \
HAS_COMMA(__VA_ARGS__ ()), \
HAS_COMMA(SET_COMMA __VA_ARGS__), \
HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \
)
#define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3)
#define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3
#define COMMA_0000 ,
#define COMMA_0001
#define COMMA_0010 ,
#define COMMA_0011 ,
#define COMMA_0100 ,
#define COMMA_0101 ,
#define COMMA_0110 ,
#define COMMA_0111 ,
#define COMMA_1000 ,
#define COMMA_1001 ,
#define COMMA_1010 ,
#define COMMA_1011 ,
#define COMMA_1100 ,
#define COMMA_1101 ,
#define COMMA_1110 ,
#define COMMA_1111 ,
int main(int argc, char** argv)
{
foo();
foo(7);
foo(10.12);
foo(12.10, 7);
foo((char)'s');
return 0;
}
Sadece C ++ kullanamaz ve bunun dışındaki tüm C ++ özelliklerini kullanamaz mısınız?
Hala sadece sıkı C değilse, bunun yerine varyasyon fonksiyonları tavsiye ederim .
Bu işlevleri extern "C++"
derleyiciniz destekliyormuş gibi bildirmeye çalışın , http://msdn.microsoft.com/en-us/library/s6y4zxec(VS.80).aspx
#include <stdio.h>
#include<stdarg.h>
int fun(int a, ...);
int main(int argc, char *argv[]){
fun(1,10);
fun(2,"cquestionbank");
return 0;
}
int fun(int a, ...){
va_list vl;
va_start(vl,a);
if(a==1)
printf("%d",va_arg(vl,int));
else
printf("\n%s",va_arg(vl,char *));
}