Apakah ada cara untuk mencapai kelebihan fungsi di C? Saya melihat fungsi sederhana menjadi kelebihan beban
foo (int a)
foo (char b)
foo (float c , int d)
Saya pikir tidak ada jalan lurus ke depan; Saya mencari solusi jika ada.
Apakah ada cara untuk mencapai kelebihan fungsi di C? Saya melihat fungsi sederhana menjadi kelebihan beban
foo (int a)
foo (char b)
foo (float c , int d)
Saya pikir tidak ada jalan lurus ke depan; Saya mencari solusi jika ada.
Jawaban:
Ada beberapa kemungkinan:
Iya!
Sejak pertanyaan ini diajukan, standar C (tidak ada ekstensi) telah secara efektif mendapatkan dukungan untuk kelebihan fungsi (bukan operator), berkat penambahan _Generic
kata kunci di C11. (didukung dalam GCC sejak versi 4.9)
(Overloading tidak benar-benar "built-in" dalam mode yang ditunjukkan dalam pertanyaan, tapi mati-matian mudah untuk mengimplementasikan sesuatu yang berfungsi seperti itu.)
_Generic
adalah operator waktu kompilasi dalam keluarga yang sama dengan sizeof
dan _Alignof
. Ini dijelaskan dalam bagian standar 6.5.1.1. Ia menerima dua parameter utama: ekspresi (yang tidak akan dievaluasi saat runtime), dan daftar asosiasi tipe / ekspresi yang terlihat agak seperti switch
blok. _Generic
dapatkan keseluruhan jenis ekspresi dan kemudian "aktifkan" untuk memilih ekspresi hasil akhir dalam daftar untuk jenisnya:
_Generic(1, float: 2.0,
char *: "2",
int: 2,
default: get_two_object());
Ekspresi di atas mengevaluasi ke 2
- jenis ekspresi pengendali int
, jadi ia memilih ekspresi yang terkait dengan int
nilainya. Tidak ada yang tersisa saat runtime. ( default
Klausa ini opsional: jika Anda membiarkannya dan jenisnya tidak cocok, itu akan menyebabkan kesalahan kompilasi.)
Cara ini berguna untuk kelebihan fungsi adalah dapat dimasukkan oleh preprocessor C dan memilih ekspresi hasil berdasarkan pada jenis argumen yang diteruskan ke makro pengontrol. Jadi (contoh dari standar C):
#define cbrt(X) _Generic((X), \
long double: cbrtl, \
default: cbrt, \
float: cbrtf \
)(X)
Makro ini menerapkan cbrt
operasi kelebihan beban , dengan mengirimkan jenis argumen ke makro, memilih fungsi implementasi yang sesuai, dan kemudian meneruskan argumen makro asli ke fungsi tersebut.
Jadi untuk menerapkan contoh asli Anda, kami bisa melakukan ini:
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
Dalam hal ini kita bisa menggunakan default:
asosiasi untuk kasus ketiga, tetapi itu tidak menunjukkan bagaimana memperluas prinsip ke beberapa argumen. Hasil akhirnya adalah bahwa Anda dapat menggunakan foo(...)
kode Anda tanpa khawatir (banyak [1]) tentang jenis argumennya.
Untuk situasi yang lebih rumit, misalnya fungsi yang kelebihan jumlah argumen yang lebih besar, atau jumlah yang bervariasi, Anda dapat menggunakan makro utilitas untuk secara otomatis menghasilkan struktur pengiriman statis:
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"
}
( implementasi di sini ) Jadi, dengan sedikit usaha, Anda dapat mengurangi jumlah pelat ketel agar tampak seperti bahasa dengan dukungan asli untuk kelebihan muatan.
Selain itu , sudah dimungkinkan untuk membebani jumlah argumen (bukan tipe) di C99.
[1] perhatikan bahwa cara C mengevaluasi tipe mungkin membuat Anda tersandung. Ini akan memilih foo_int
jika Anda mencoba memberikan karakter literal, misalnya, dan Anda perlu sedikit mengacaukannya jika Anda ingin overload Anda mendukung string literal. Namun secara keseluruhan masih cukup keren.
Seperti yang telah dinyatakan, kelebihan dalam arti yang Anda maksud tidak didukung oleh C. Ungkapan umum untuk menyelesaikan masalah adalah membuat fungsi menerima gabungan yang ditandai . Ini diimplementasikan oleh struct
parameter, di mana struct
itu sendiri terdiri dari semacam indikator jenis, seperti enum
, dan union
dari berbagai jenis nilai. Contoh:
#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
s menjadi fungsi yang terpisah ( set_int
, set_float
, dll). Kemudian "penandaan dengan tipe" menjadi "tambahkan nama tipe ke nama fungsi". Versi dalam jawaban ini melibatkan lebih banyak pengetikan, lebih banyak biaya runtime, lebih banyak kemungkinan kesalahan yang tidak akan tertangkap saat kompilasi ... Saya gagal melihat keuntungan sama sekali untuk melakukan hal-hal dengan cara ini! 16 suara positif ?!
Berikut adalah contoh paling jelas dan paling ringkas yang saya temukan mendemonstrasikan kelebihan fungsi di C:
#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;
}
Jika kompiler Anda adalah gcc dan Anda tidak keberatan melakukan pembaruan tangan setiap kali Anda menambahkan kelebihan baru Anda dapat melakukan beberapa keajaiban makro dan mendapatkan hasil yang Anda inginkan dari segi penelepon, tidak baik untuk menulis ... tetapi mungkin saja
lihat __builtin_types_compatible_p, lalu gunakan untuk mendefinisikan makro yang melakukan sesuatu seperti
#define foo(a) \
((__builtin_types_compatible_p(int, a)?foo(a):(__builtin_types_compatible_p(float, a)?foo(a):)
tapi ya jahat, hanya saja jangan
EDIT: C1X akan mendapatkan dukungan untuk tipe ekspresi generik yang terlihat seperti ini:
#define cbrt(X) _Generic((X), long double: cbrtl, \
default: cbrt, \
float: cbrtf)(X)
Ya, semacam.
Ini contohnya:
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);
}
Ini akan menampilkan 0 dan halo .. dari printA dan printB.
Pendekatan berikut mirip dengan a2800276 , tetapi dengan beberapa makro C99 ditambahkan:
// 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;
}
Ini mungkin tidak membantu sama sekali, tetapi jika Anda menggunakan dentang Anda dapat menggunakan atribut overloadable - Ini berfungsi bahkan ketika dikompilasi sebagai C
http://clang.llvm.org/docs/AttributeReference.html#overloadable
Header
extern void DecodeImageNow(CGImageRef image, CGContextRef usingContext) __attribute__((overloadable));
extern void DecodeImageNow(CGImageRef image) __attribute__((overloadable));
Penerapan
void __attribute__((overloadable)) DecodeImageNow(CGImageRef image, CGContextRef usingContext { ... }
void __attribute__((overloadable)) DecodeImageNow(CGImageRef image) { ... }
Dalam arti yang Anda maksud - tidak, Anda tidak bisa.
Anda dapat mendeklarasikan va_arg
fungsi seperti
void my_func(char* format, ...);
, tetapi Anda harus memberikan beberapa jenis informasi tentang jumlah variabel dan jenisnya dalam argumen pertama - seperti yang printf()
dilakukan.
Biasanya kutil untuk menunjukkan jenis ditambahkan atau diawali dengan nama. Anda bisa lolos dengan makro adalah beberapa contoh, tetapi itu tergantung pada apa yang Anda coba lakukan. Tidak ada polimorfisme di C, hanya paksaan.
Operasi generik sederhana dapat dilakukan dengan makro:
#define max(x,y) ((x)>(y)?(x):(y))
Jika kompiler Anda mendukung typeof , operasi yang lebih rumit dapat dimasukkan ke makro. Anda kemudian dapat memiliki simbol foo (x) untuk mendukung jenis operasi yang sama berbeda, tetapi Anda tidak dapat memvariasikan perilaku di antara kelebihan beban yang berbeda. Jika Anda menginginkan fungsi yang sebenarnya daripada makro, Anda mungkin dapat menempelkan tipe ke nama dan menggunakan tempelan kedua untuk mengaksesnya (saya belum mencoba).
Jawaban Leushenko benar-benar keren - semata-mata: foo
contohnya tidak dikompilasi dengan GCC, yang gagal pada foo(7)
, tersandung FIRST
makro dan panggilan fungsi yang sebenarnya ( (_1, __VA_ARGS__)
, tetap dengan koma surplus. Selain itu, kami dalam masalah jika kami ingin memberikan kelebihan tambahan , seperti foo(double)
.
Jadi saya memutuskan untuk menguraikan jawaban sedikit lebih jauh, termasuk untuk memungkinkan kekosongan yang berlebihan ( foo(void)
- yang menyebabkan beberapa masalah ...).
Gagasannya sekarang adalah: Tetapkan lebih dari satu generik dalam makro yang berbeda dan biarkan pilih yang benar sesuai dengan jumlah argumen!
Jumlah argumen cukup mudah, berdasarkan jawaban ini :
#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
Itu bagus, kami memutuskan untuk salah satu SELECT_1
atau SELECT_2
(atau lebih banyak argumen, jika Anda ingin / membutuhkannya), jadi kami hanya perlu mendefinisikan yang tepat:
#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 \
) \
)
OK, saya sudah menambahkan void overload - namun, ini sebenarnya tidak tercakup oleh standar C, yang tidak memungkinkan argumen variadik kosong, yaitu kita kemudian bergantung pada ekstensi kompilator !
Pada awalnya, panggilan makro kosong ( foo()
) masih menghasilkan token, tetapi yang kosong. Jadi makro penghitungan sebenarnya mengembalikan 1 bukannya 0 bahkan pada panggilan makro kosong. Kita dapat "dengan mudah" menghilangkan masalah ini, jika kita menempatkan koma setelah __VA_ARGS__
kondisional , tergantung pada daftar yang kosong atau tidak:
#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)
Itu terlihat mudah, tetapi COMMA
makronya cukup berat; untungnya, topik ini sudah dibahas di blog Jens Gustedt (terima kasih, Jens). Trik dasarnya adalah fungsi makro tidak diperluas jika tidak diikuti oleh tanda kurung, untuk penjelasan lebih lanjut, lihat blog Jens ... Kita hanya perlu memodifikasi makro sedikit untuk kebutuhan kita (saya akan menggunakan nama yang lebih pendek dan lebih sedikit argumen untuk singkatnya).
#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 ,
Dan sekarang kita baik-baik saja ...
Kode lengkap dalam satu blok:
/*
* 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;
}
Tidak bisakah Anda menggunakan C ++ dan tidak menggunakan semua fitur C ++ lainnya kecuali yang ini?
Jika masih ada C ketat saja maka saya akan merekomendasikan fungsi variadic sebagai gantinya.
Cobalah untuk mendeklarasikan fungsi-fungsi ini seolah- extern "C++"
olah kompiler Anda mendukung ini, 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 *));
}