C语言实现函数重载
C語言實現函數重載
文章目錄
- C語言實現函數重載
- 一、C++是如何實現函數重載的
- 二、C++函數的命名規則
- 三、C語言實現函數重載
- 1.可變參數
- 2.函數指針實現參數的重載
- 3.實現參數類型的重載
- 4.完整版
一、C++是如何實現函數重載的
- C++ 實現函數重載很大程度上依賴與編譯器對函數名的 Mangling(損壞,破壞),即 C++ 的源代碼被編譯后同名的重載函數名字會被破壞,一般是在原函數名前后加上特定的字符串(g++編譯器中通過在函數名后面添加參數的后綴),以區分不同重載函數,然后在調用的時候根據參數的不同選擇合適的函數
如下代碼說明了編譯器是如何處理普通函數重載的
- 幾個同名的重載函數仍然是不同的函數,編譯后應該有不同的地址,那么它們是如何區分的呢?我們自然想到函數接口的兩個要素:參數與返回值。
- 如果同名函數的參數不同(包括類型、順序不同),那么容易區別出它們是不同的函數。如果同名函數僅僅是返回值類型不同,有時可以區分,有時卻不能。例如:
- 上述兩個函數,第一個沒有返回值,第二個的返回值是 int 類型。如果這樣調用函數:
- 則可以判斷出 Function 是第二個函數。問題是在 C/C++程序中,我們可以忽略函數的返回值。在這種情況下,編譯器和程序員都不知道哪個 Function 函數被調用。
- 所以只能靠參數而不能靠返回值類型的不同來區分重載函數。
- 編譯器根據參數為每個重載函數產生不同的內部標識符。不同的編譯器可能產生不同風格的內部標識符
- 如果 C++程序要調用已經被編譯后的 C 函數,該怎么辦?
假設某個 C 函數的聲明如下:
void foo(int x, int y);該函數被 C 編譯器編譯后在庫中的名字為_foo,而
C++編譯器則會產生像_foo_int_int之類的名字用來支持函數重載和類型安全連接。
這樣一來很明顯,C和C++中對函數的生成規則是不同的,由于編譯后的名字不同,C++程序不能直接調用 C 函數。
但是C++是完全兼容C的,而且我們的C++程序往往在不斷的調用C庫,C++提供了一個 C 連接交換指定符號
extern“C”來解決這個問題。
extern “C”
{
void foo(int x, int y);
… // 其它函數
}
或者寫成 extern “C”
{
#include “myheader.h”
… // 其它 C 頭文件
}
這就告訴 C++編譯譯器,函數 foo 是個C庫的函數,那么C++編譯器應該按照C編譯器的編譯和鏈接規則來進行鏈接,也就是說到庫中找名字_foo 而不是找_foo_int_int。
C++編譯器開發商已經對 C 標準庫的頭文件作了 extern“C”處理,所以我們可以用#include 直接引用這些頭文件。
二、C++函數的命名規則
#include <iostream> using namespace std;int func(void) {cout << "func without parameters" << endl; }int func(int ia) {cout << "func with one int parameter: " << endl;cout << ia << endl; }int func(int ia, float fb) {cout << "func with one int parameter and one float parameter" << endl;cout << ia << endl;cout << fb << endl; }int main() {func();func(5);func(5, 5.0); }我們可以通過g++的-S指令,將我們的程序編譯成匯編
main: .LFB1052:pushq %rbp.seh_pushreg %rbpmovq %rsp, %rbp.seh_setframe %rbp, 0subq $32, %rsp.seh_stackalloc 32.seh_endprologuecall __maincall _Z4funcvmovl $5, %ecxcall _Z4funcimovss .LC3(%rip), %xmm1movl $5, %ecxcall _Z4funcifmovl $0, %eaxaddq $32, %rsppopq %rbpret.seh_endproc.def __tcf_0; .scl 3; .type 32; .endef.seh_proc __tcf_0 __tcf_0: .LFB1063:- 可以看到,func 的三個版本重載函數在編譯后名字都被破壞了,編譯器將他們重命名為了 _Z4funcv, _Z4funci, _Z4funcif,
- g++ 編譯器可能根據函數參數類型為函數名加上了與參數類型相關的特定后綴,如func(void) 變成了 _Z4funcv, func(int) 變成了_Z4funci, func(int, float)變成了 _Z4funcif
- 然后在調用各個版本的func()時,編譯器根據參數類型的不同選擇合適的重載函數,如調用 func() 其實是調用了 _Z4funcv, 調用 func(5, 5.0)實際上是調用了 _Z4funcif等。
三、C語言實現函數重載
1.可變參數
但是,在很多情況下,利用可變參數可以實現 C 語言的函數重載的,POSIX 接口中定義的 open 函數就是一個非常好的例子,
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);以下是一個簡單的例子,”重載”了兩個函數,第一個函數是兩個參數,第二個函數帶了三個函數,其中第三個函數是可選的,
ANSI C 標準中,有可變參數的概念,可以通過一組宏實現
| col 3 is | right-aligned |
| va_list arg_ptr | 定義一個可變參數列表指針 |
| va_start(arg_ptr, argN) | 讓arg_ptr指向參數argN |
| va_arg(arg_ptr, type) | 返回類型為type的參數指針,并指向下一個參數 |
| va_copy(dest, src) | 拷貝參數列表指針,src->dest, |
| va_end(arg_ptr) | 清空參數列表,并置參數指針arg_ptr無效。每個va_start()必須與一個va_end()對應 |
- 參數的內存存放格式:參數存放在內存的堆棧段中,在執行函數的時候,從最后一個開始入棧
- 因此,假設定義一個可變參數的函數 void f(int x, …), 通過f( x, y, z) 調用,那么,z先入棧,然后y, 然后x。 因此我們只要得到任何一個變量的地址,就可以找到其它變量的地址。
- va_start(va, n) 就是讓va指向n的地址。這樣,后面就可以得到所有參數的值。前提是,我們必須知道每個參數的類型。在本例子中,都是int類型。
2.函數指針實現參數的重載
#include <stdio.h> #include <stdlib.h>typedef struct _int_param {int param1;int param2; }INT_PARAM;typedef struct _double_param_ {double param1;double param2; }DOUBLE_PARAM;typedef void* (*ADDFUNC)(void*);void* int_add_func(void* wParam) {INT_PARAM* lParam = (INT_PARAM*)wParam;int res = lParam->param1 + lParam->param2;printf("result = %d\n", res); }void* double_add_func(void* wParam) {DOUBLE_PARAM* lParam = (DOUBLE_PARAM*)wParam;double res = lParam->param1 + lParam->param2;printf("result = %f\n", res);}void* add_func(ADDFUNC f, void* wParam) {return f(wParam); }int main() {INT_PARAM val1 = {10, 20};DOUBLE_PARAM val2 = {30.5, 40.5};add_func(int_add_func, &val1);add_func(double_add_func, &val2);return 0; }3.實現參數類型的重載
這主要是利用了 GCC 的內置函數,__builtin_types_compatible_p()和__builtin_choose_expr(),
例如:
struct s1 {int a;int b;double c; };struct s2 {long long a;long long b; };void gcc_overload_s1(struct s1 s) {printf("Got a struct s1: %d %d %f\n", s.a, s.b, s.c); }void gcc_overload_s2(struct s2 s) {printf("Got a struct s2: %lld %lld\n", s.a, s.b); }// warning: dereferencing type-punned pointer will break strict-aliasing rules #define gcc_overload(A)\__builtin_choose_expr(__builtin_types_compatible_p(typeof(A), struct s1),\gcc_overload_s1(*(struct s1 *)&A),\__builtin_choose_expr(__builtin_types_compatible_p(typeof(A), struct s2),\gcc_overload_s2(*(struct s2 *)&A),(void)0))或者一個更高級的寫法
void gcc_type_overload_aux(int typeval, ...) {switch(typeval){case 1:{va_list v;va_start(v, typeval);struct s1 s = va_arg(v, struct s1);va_end(v);gcc_overload_s1(s);break;}case 2:{va_list v;va_start(v, typeval);struct s2 s = va_arg(v, struct s2);va_end(v);gcc_overload_s2(s);break;}default:{printf("Invalid type to 'gcc_type_overload()'\n");exit(1);}} }#define gcc_type_overload(A)\gcc_type_overload_aux(\__builtin_types_compatible_p(typeof(A), struct s1) * 1\+ __builtin_types_compatible_p(typeof(A), struct s2) * 2\, A)4.完整版
#include <stdio.h> #include <stdlib.h> #include <stdarg.h>void va_overload2(int p1, int p2) {printf("va_overload2 %d %d\n", p1, p2); }void va_overload3(int p1, int p2, int p3) {printf("va_overload3 %d %d %d\n", p1, p2, p3); }static void va_overload(int p1, int p2, ...) {if (p2 == 7){va_list v;va_start(v, p2);int p3 = va_arg(v, int);va_end(v);va_overload3(p1, p2, p3);return;}va_overload2(p1, p2); }static void print_nt_strings(const char *s, ...) {va_list v;va_start(v, s);/* Stop on NULL */while (s){printf("%s", s);/* Grab next parameter */s = va_arg(v, const char *);}va_end(v); }#define COUNT_PARMS2(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _, ...) _ #define COUNT_PARMS(...)\COUNT_PARMS2(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)static void print_strings2(int count, ...) {int i;va_list v;va_start(v, count);for (i = 0; i < count; i++){/* Grab next parameter + print it */const char *s = va_arg(v, const char *);printf("%s", s);}va_end(v); }#define print_strings(...)\print_strings2(COUNT_PARMS(__VA_ARGS__), __VA_ARGS__)void count_overload1(int p1) {printf("One param: %d\n", p1); }void count_overload2(double *p1, const char *p2) {printf("Two params: %p (%f) %s\n", p1, *p1, p2); }void count_overload3(int p1, int p2, int p3) {printf("Three params: %c %d %d\n", p1, p2, p3); }void count_overload_aux(int count, ...) {va_list v;va_start(v, count);switch(count){case 1:{int p1 = va_arg(v, int);count_overload1(p1);break;}case 2:{double *p1 = va_arg(v, double *);const char *p2 = va_arg(v, const char *);count_overload2(p1, p2);break;}case 3:{int p1 = va_arg(v, int);int p2 = va_arg(v, int);int p3 = va_arg(v, int);count_overload3(p1, p2, p3);break;}default:{va_end(v);printf("Invalid arguments to function 'count_overload()'");exit(1);}}va_end(v); } #define count_overload(...)\count_overload_aux(COUNT_PARMS(__VA_ARGS__), __VA_ARGS__)void cpp_overload1(int p1) {printf("CPP One param: %d\n", p1); }void cpp_overload2(double *p1, const char *p2) {printf("CPP Two params: %p (%f) %s\n", p1, *p1, p2); }void cpp_overload3(int p1, int p2, int p3) {printf("CPP Three params: %c %d %d\n", p1, p2, p3); }#define CAT(A, B) CAT2(A, B) #define CAT2(A, B) A ## B#define cpp_overload(...)\CAT(cpp_overload, COUNT_PARMS(__VA_ARGS__))(__VA_ARGS__)#define cpp_default1(A) cpp_default2(A, "default string")void cpp_default2(int x, const char *s) {printf("Got %d %s\n", x, s); }#define cpp_default(...)\CAT(cpp_default, COUNT_PARMS(__VA_ARGS__))(__VA_ARGS__)void sizeof_overload_float(float f) {printf("Got float %f\n", f); }void sizeof_overload_double(double d) {printf("Got double %f\n", d); }void sizeof_overload_longdouble(long double ld) {printf("Got long double %Lf\n", ld); }#define sizeof_overload(A)\((sizeof(A) == sizeof(float))?sizeof_overload_float(A):\(sizeof(A) == sizeof(double))?sizeof_overload_double(A):\(sizeof(A) == sizeof(long double))?sizeof_overload_longdouble(A):(void)0)struct s1 {int a;int b;double c; };struct s2 {long long a;long long b; };void gcc_overload_s1(struct s1 s) {printf("Got a struct s1: %d %d %f\n", s.a, s.b, s.c); }void gcc_overload_s2(struct s2 s) {printf("Got a struct s2: %lld %lld\n", s.a, s.b); }// warning: dereferencing type-punned pointer will break strict-aliasing rules #define gcc_overload(A)\__builtin_choose_expr(__builtin_types_compatible_p(typeof(A), struct s1),\gcc_overload_s1(*(struct s1 *)&A),\__builtin_choose_expr(__builtin_types_compatible_p(typeof(A), struct s2),\gcc_overload_s2(*(struct s2 *)&A),(void)0))void gcc_type_overload_aux(int typeval, ...) {switch(typeval){case 1:{va_list v;va_start(v, typeval);struct s1 s = va_arg(v, struct s1);va_end(v);gcc_overload_s1(s);break;}case 2:{va_list v;va_start(v, typeval);struct s2 s = va_arg(v, struct s2);va_end(v);gcc_overload_s2(s);break;}default:{printf("Invalid type to 'gcc_type_overload()'\n");exit(1);}} }#define gcc_type_overload(A)\gcc_type_overload_aux(\__builtin_types_compatible_p(typeof(A), struct s1) * 1\+ __builtin_types_compatible_p(typeof(A), struct s2) * 2\, A)void print_type(int t, va_list *v) {switch(t){case 1:{int p = va_arg(*v, int);printf("int :%d\n", p);break;}case 2:{long long p = va_arg(*v, long long);printf("long long :%lld\n", p);break;}case 3:{double p = va_arg(*v, double);printf("double :%f\n", p);break;}case 4:{long double p = va_arg(*v, long double);printf("long double :%Lf\n", p);break;}default:{printf("Unknown type\n");exit(1);}} }void param_lister1_aux(int t1, ...) {va_list v;va_start(v, t1);printf("1st param:");print_type(t1, &v);va_end(v); }void param_lister2_aux(int t1, ...) {int t2;va_list v;va_start(v, t1);printf("1st param:");print_type(t1, &v);t2 = va_arg(v, int);printf("2nd param:");print_type(t2, &v);va_end(v); }void param_lister3_aux(int t1, ...) {int t2, t3;va_list v;va_start(v, t1);printf("1st param:");print_type(t1, &v);t2 = va_arg(v, int);printf("2nd param:");print_type(t2, &v);t3 = va_arg(v, int);printf("3rd param:");print_type(t3, &v);va_end(v); }void param_lister4_aux(int t1, ...) {int t2, t3, t4;va_list v;va_start(v, t1);printf("1st param:");print_type(t1, &v);t2 = va_arg(v, int);printf("2nd param:");print_type(t2, &v);t3 = va_arg(v, int);printf("3rd param:");print_type(t3, &v);t4 = va_arg(v, int);printf("4th param:");print_type(t4, &v);va_end(v); }#define TYPENUM(A)\__builtin_types_compatible_p(typeof(A), int) * 1\+ __builtin_types_compatible_p(typeof(A), long long) * 2\+ __builtin_types_compatible_p(typeof(A), double) * 3\+ __builtin_types_compatible_p(typeof(A), long double) * 4#define param_lister1(A)\param_lister1_aux(TYPENUM(A), A)#define param_lister2(A, B)\param_lister2_aux(TYPENUM(A), A, TYPENUM(B), B)#define param_lister3(A, B, C)\param_lister3_aux(TYPENUM(A), A, TYPENUM(B), B, TYPENUM(C), C)#define param_lister4(A, B, C, D)\param_lister4_aux(TYPENUM(A), A, TYPENUM(B), B, TYPENUM(C), C, TYPENUM(D), D)#define param_lister(...)\CAT(param_lister, COUNT_PARMS(__VA_ARGS__))(__VA_ARGS__)int main() {param_lister(1);param_lister(1, 2.0, 3, 6.0);return 0; }總結
- 上一篇: C++突破private的方案
- 下一篇: C语言实现封装、继承、多态