网站生成静态页面工具,wordpress 网页特效,小程序制作模板免费,应用商店官方下载在C#中#xff0c;格式化输出可以使用索引占位符以及复合格式的占位符#xff0c;可以不用关心后面参数是什么类型#xff0c;使用起来非常方便#xff0c;如下简单的示例#xff1a;
Console.WriteLine({2} {1} {0} {{{2}}}, Hello, World!, 1,…在C#中格式化输出可以使用索引占位符以及复合格式的占位符可以不用关心后面参数是什么类型使用起来非常方便如下简单的示例
Console.WriteLine({2} {1} {0} {{{2}}}, Hello, World!, 1, 8.8);会输出
8.8 1 Hello, World! {8.8}规范可以参见复合格式设置
在C语言标准库中使用的printf系列函数中需要使用格式化字符串明确指定后面参数的类型如果指定错误可能会引发灾难 那C语言可以实现上述功能吗 在C11泛型的加持下使用关键字_Generic也可以实现上述功能了下面笔者就一步步来实现并且支持可以不指定索引支持C标准格式化字符串。
一、实现效果
我们假定要实现的是printx和print其使用的方式如下
main.c
#include printx.hint main() {char a A;unsigned char b 130;int age 25;unsigned int ui 123456;long l 12345678;unsigned ul 123456789;long long ll 98761234567890;unsigned long long ull 9998761234567890;float f 2.71828F;double pi 3.14159;const char* name Witton;unsigned int hexv 8192;auto x \n;printx(test\n);printx(Name: {{{}}}, Age: {}\n, name, age);printx(Age: {1}, Name: {}\n, name, age);printx(Name: {0}, Age: {1}\n, name, age);printx(Pi: {0:.2f}\n, pi);printx(Hex: {0:#x}, Again: {0:#08x}\n, hexv);printx(Swap order: {1}, {0}\n, first, second);printx(Char: {0}\n, (char)A);printx(Repeat: {0} {0} {1:.1f} {}\n, age, pi, f);printx(1:{} 2:{} 3:{} 4:{} 5:{}, 6:{}, 7:{}, 8:{}, 9:{}, 10:{}\n, name, a,b, age, ui, l, ul, ll, ull, f);print(name, a, b, age, ui, l, ul, ll, ull, (char)\n);return 0;
}其运行结果为
test
Name: {Witton}, Age: 25
Age: 25, Name: Witton
Name: Witton, Age: 25
Pi: 3.14
Hex: 0x2000, Again: 0x002000
Swap order: second, first
Char: A
Repeat: 25 25 3.1 25
1:Witton 2:A 3:130 4:25 5:123456, 6:12345678, 7:123456789, 8:98761234567890, 9:9998761234567890, 10:2.718280
Witton A 130 25 123456 12345678 123456789 98761234567890 9998761234567890需要注意的是C语言中字面字符的类型是int而不是char所以
printx(Char: {0}\n, (char)A);中需要强制转换成char类型才能正常输出字母A。 二、统一类型
由于在调用的过程中传入的参数可能是各种数据类型我们需要统一转换成一种自定义类型在自定义类型中去标识实际的数据类型。
typedef enum {T_UNKNOWN,T_CHAR,T_BYTE,T_STRING,T_BYTES,T_INT,T_UINT,T_LONG,T_ULONG,T_LONGLONG,T_ULONGLONG,T_FLOAT,T_DOUBLE,
} EDataType;typedef struct {EDataType type;union {char c;unsigned char b;int i;unsigned int u;long l;unsigned long ul;long long ll;unsigned long long ull;float f;double d;const char* s;const unsigned char* bs;} v;
} FmtArg;static inline FmtArg make_char(char v) {return (FmtArg){T_CHAR, {.c v}};
}static inline FmtArg make_uchar(unsigned char v) {return (FmtArg){T_BYTE, {.b v}};
}static inline FmtArg make_string(const char* s) {return (FmtArg){T_STRING, {.s s}};
}static inline FmtArg make_bytes(const unsigned char* s) {return (FmtArg){T_BYTES, {.bs s}};
}static inline FmtArg make_int(int v) {return (FmtArg){T_INT, {.i v}};
}
static inline FmtArg make_uint(unsigned int v) {return (FmtArg){T_UINT, {.u v}};
}static inline FmtArg make_long(long v) {return (FmtArg){T_LONG, {.l v}};
}static inline FmtArg make_ulong(unsigned long v) {return (FmtArg){T_ULONG, {.ul v}};
}static inline FmtArg make_longlong(long long v) {return (FmtArg){T_LONGLONG, {.ll v}};
}static inline FmtArg make_ulonglong(unsigned long long v) {return (FmtArg){T_ULONGLONG, {.ull v}};
}static inline FmtArg make_float(float v) {return (FmtArg){T_FLOAT, {.f v}};
}static inline FmtArg make_double(double v) {return (FmtArg){T_DOUBLE, {.d v}};
}static inline FmtArg make_unknown(void) {return (FmtArg){T_UNKNOWN};
}有了自定义类型了就可以声明具体实现的C函数了
void printx_impl(const char* fmt, int arg_count, FmtArg* argv);前面的fmt就是类似C#的格式符也可以是nullptr表明不需要格式字符串自动依次使用参数arg_count表明有几个参数决定着后面参数argv的个数argv为实际的参数信息。
而给用户调用的APIprintx和print其实是一个宏它类似如下声明
#define printx(fmt, ...)
#define print(...)我们需要在宏中调用实际工作的C函数printx_impl在调用前需要将传入的参数转换成自定义数据类型FmtArg并且准备好printx_impl函数需要的参数。
三、计算参数个数
如何计算宏参数...中包含的参数个数 在C/C中参数... 可能包含0个到多个参数我们假定最多支持10个参数。为了计算宏参数个数需要定义一个匹配或者说是取宏参数的宏
#define GET_MACRO(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, n, ...) nGET_MACRO的参数列表是_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, n, ...这里的...表示剩余参数但在宏定义中我们只取n
然后定义计算宏参数个数的宏
#define COUNT_ARGS(...) \GET_MACRO(0, ##__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)__VA_ARGS__中可能是0个参数C/C编译器有一种写法##__VA_ARGS__当是0个时会把前面的逗号去掉变为
GET_MACRO(0, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)GET_MACRO取第12个参数结果为0。
如果有多个参数则依次填充。比如参数1,2,3变为
GET_MACRO(0, 1, 2, 3, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)GET_MACRO取第12个参数结果为3。
四、转换参数
通过前面的方法我们知道了参数个数接下来就是实现参数类型的转换了要实现类型转换就需要知道是什么类型C11提供了_Generic关键字只有它才能识别类型实现泛型。其语法参见https://cppreference.cn/w/c/language/generic
定义如下宏来将参数转换成自定义类型FmtArg
#define MAKE_FMTARG(x) \_Generic((x), \char: make_char, \unsigned char: make_uchar, \const char*: make_string, \char*: make_string, \const unsigned char*: make_bytes, \unsigned char*: make_bytes, \int: make_int, \unsigned int: make_uint, \long: make_long, \unsigned long: make_ulong, \long long: make_longlong, \unsigned long long: make_ulonglong, \float: make_float, \double: make_double, \default: make_unknown)(x)使用获取参数个数一样的方法我们也可以逐个取参数然后调用MAKE_FMTARG进行转换先定义一组宏
#define APPLY0(m, a) 0 // 这里定义为0避免编译器警告
#define APPLY1(m, a) m(a)
#define APPLY2(m, a, ...) m(a), APPLY1(m, __VA_ARGS__)
#define APPLY3(m, a, ...) m(a), APPLY2(m, __VA_ARGS__)
#define APPLY4(m, a, ...) m(a), APPLY3(m, __VA_ARGS__)
#define APPLY5(m, a, ...) m(a), APPLY4(m, __VA_ARGS__)
#define APPLY6(m, a, ...) m(a), APPLY5(m, __VA_ARGS__)
#define APPLY7(m, a, ...) m(a), APPLY6(m, __VA_ARGS__)
#define APPLY8(m, a, ...) m(a), APPLY7(m, __VA_ARGS__)
#define APPLY9(m, a, ...) m(a), APPLY8(m, __VA_ARGS__)
#define APPLY10(m, a, ...) m(a), APPLY9(m, __VA_ARGS__)#define APPLY(m, ...) \GET_MACRO(0, ##__VA_ARGS__, APPLY10, APPLY9, APPLY8, APPLY7, APPLY6, APPLY5, \APPLY4, APPLY3, APPLY2, APPLY1, APPLY0)(m, __VA_ARGS__)这组宏支持0~10个参数对每个参数调用mm可以是宏也可以是函数。我们这里需要调用的是MAKE_FMTARG宏。
五、实现printx和print宏
现在可以写出printx和print宏的实现了
#define printx(fmt, ...) \printx_impl(fmt, COUNT_ARGS(__VA_ARGS__), \(FmtArg[]){APPLY(MAKE_FMTARG, ##__VA_ARGS__)})#define print(...) \printx_impl(0, COUNT_ARGS(__VA_ARGS__), \(FmtArg[]){APPLY(MAKE_FMTARG, ##__VA_ARGS__)})(FmtArg[]){APPLY(MAKE_FMTARG, ##__VA_ARGS__)}构建了一个FmtArg的数组如果参数个数为0则是(FmtArg[]){0}。
六、扩展实现fprintx和fprint宏
目前的API还只支持输出到标准输出设备stdout无法输出到文件比如想输出到日志文件只需要添加一个FILE* fp参数即可相应修改如下
void printx_impl(FILE* fp, const char* fmt, int arg_count, FmtArg* argv);#define printx(fmt, ...) \printx_impl(stdout, fmt, COUNT_ARGS(__VA_ARGS__), \(FmtArg[]){APPLY(MAKE_FMTARG, ##__VA_ARGS__)})#define print(...) \printx_impl(stdout, 0, COUNT_ARGS(__VA_ARGS__), \(FmtArg[]){APPLY(MAKE_FMTARG, ##__VA_ARGS__)})实现fprintx和fprint宏
#define fprintx(fp, fmt, ...) \printx_impl(fp, fmt, COUNT_ARGS(__VA_ARGS__), \(FmtArg[]){APPLY(MAKE_FMTARG, ##__VA_ARGS__)})#define fprint(fp, ...) \printx_impl(fp, 0, COUNT_ARGS(__VA_ARGS__), \(FmtArg[]){APPLY(MAKE_FMTARG, ##__VA_ARGS__)})七、源码
printx.h
// copyright(C), author: Witton
// email: witton163.com#ifndef _PRINT_X_H_INCLUDE_
#define _PRINT_X_H_INCLUDE_#include stdio.htypedef enum {T_UNKNOWN,T_CHAR,T_BYTE,T_STRING,T_BYTES,T_INT,T_UINT,T_LONG,T_ULONG,T_LONGLONG,T_ULONGLONG,T_FLOAT,T_DOUBLE,
} EDataType;typedef struct {EDataType type;union {char c;unsigned char b;int i;unsigned int u;long l;unsigned long ul;long long ll;unsigned long long ull;float f;double d;const char* s;const unsigned char* bs;} v;
} FmtArg;static inline FmtArg make_char(char v) {return (FmtArg){T_CHAR, {.c v}};
}static inline FmtArg make_byte(unsigned char v) {return (FmtArg){T_BYTE, {.b v}};
}static inline FmtArg make_string(const char* s) {return (FmtArg){T_STRING, {.s s}};
}static inline FmtArg make_bytes(const unsigned char* s) {return (FmtArg){T_BYTES, {.bs s}};
}static inline FmtArg make_int(int v) {return (FmtArg){T_INT, {.i v}};
}
static inline FmtArg make_uint(unsigned int v) {return (FmtArg){T_UINT, {.u v}};
}static inline FmtArg make_long(long v) {return (FmtArg){T_LONG, {.l v}};
}static inline FmtArg make_ulong(unsigned long v) {return (FmtArg){T_ULONG, {.ul v}};
}static inline FmtArg make_longlong(long long v) {return (FmtArg){T_LONGLONG, {.ll v}};
}static inline FmtArg make_ulonglong(unsigned long long v) {return (FmtArg){T_ULONGLONG, {.ull v}};
}static inline FmtArg make_float(float v) {return (FmtArg){T_FLOAT, {.f v}};
}static inline FmtArg make_double(double v) {return (FmtArg){T_DOUBLE, {.d v}};
}static inline FmtArg make_unknown(void) {return (FmtArg){T_UNKNOWN};
}#define MAKE_FMTARG(x) \_Generic((x), \char: make_char, \unsigned char: make_byte, \const char*: make_string, \char*: make_string, \const unsigned char*: make_bytes, \unsigned char*: make_bytes, \int: make_int, \unsigned int: make_uint, \long: make_long, \unsigned long: make_ulong, \long long: make_longlong, \unsigned long long: make_ulonglong, \float: make_float, \double: make_double, \default: make_unknown)(x)#define GET_MACRO(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, n, ...) n#define COUNT_ARGS(...) \GET_MACRO(0, ##__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)#define APPLY0(m, a) 0 // 这里定义为0避免编译器警告
#define APPLY1(m, a) m(a)
#define APPLY2(m, a, ...) m(a), APPLY1(m, __VA_ARGS__)
#define APPLY3(m, a, ...) m(a), APPLY2(m, __VA_ARGS__)
#define APPLY4(m, a, ...) m(a), APPLY3(m, __VA_ARGS__)
#define APPLY5(m, a, ...) m(a), APPLY4(m, __VA_ARGS__)
#define APPLY6(m, a, ...) m(a), APPLY5(m, __VA_ARGS__)
#define APPLY7(m, a, ...) m(a), APPLY6(m, __VA_ARGS__)
#define APPLY8(m, a, ...) m(a), APPLY7(m, __VA_ARGS__)
#define APPLY9(m, a, ...) m(a), APPLY8(m, __VA_ARGS__)
#define APPLY10(m, a, ...) m(a), APPLY9(m, __VA_ARGS__)#define APPLY(m, ...) \GET_MACRO(0, ##__VA_ARGS__, APPLY10, APPLY9, APPLY8, APPLY7, APPLY6, APPLY5, \APPLY4, APPLY3, APPLY2, APPLY1, APPLY0)(m, __VA_ARGS__)#define printx(fmt, ...) \printx_impl(stdout, fmt, COUNT_ARGS(__VA_ARGS__), \(FmtArg[]){APPLY(MAKE_FMTARG, ##__VA_ARGS__)})#define print(...) \printx_impl(stdout, 0, COUNT_ARGS(__VA_ARGS__), \(FmtArg[]){APPLY(MAKE_FMTARG, ##__VA_ARGS__)})#define fprintx(fp, fmt, ...) \printx_impl(fp, fmt, COUNT_ARGS(__VA_ARGS__), \(FmtArg[]){APPLY(MAKE_FMTARG, ##__VA_ARGS__)})#define fprint(fp, ...) \printx_impl(fp, 0, COUNT_ARGS(__VA_ARGS__), \(FmtArg[]){APPLY(MAKE_FMTARG, ##__VA_ARGS__)})void printx_impl(FILE* fp, const char* fmt, int arg_count, FmtArg* argv);#endif
printx.c
// copyright(C), author: Witton
// email: witton163.com#include ctype.h
#include stdio.h
#include stdlib.h
#include string.h#include printx.h#if __STDC_VERSION__ 202311L
#ifdef NULL
#undef NULL
#define NULL nullptr
#endif
#endifstatic void print_one_arg(FILE* fp, const char* fmt_spec, FmtArg arg) {if (!fmt_spec[0]) {switch (arg.type) {case T_CHAR:(void)fprintf(fp,%c, arg.v.c);break;case T_BYTE:(void)fprintf(fp,%hhu, arg.v.b);break;case T_INT:(void)fprintf(fp,%d, arg.v.i);break;case T_UINT:(void)fprintf(fp,%u, arg.v.u);break;case T_LONG:(void)fprintf(fp,%ld, arg.v.l);break;case T_LONGLONG:(void)fprintf(fp,%lld, arg.v.ll);break;case T_ULONGLONG:(void)fprintf(fp,%llu, arg.v.ull);break;case T_FLOAT:(void)fprintf(fp,%f, arg.v.f);break;case T_DOUBLE:(void)fprintf(fp,%lf, arg.v.d);break;case T_STRING:(void)fprintf(fp,%s, arg.v.s);break;default:(void)fprintf(fp,?(by witton));break;}return;}char real_fmt[64];(void)snprintf(real_fmt, sizeof(real_fmt), %%%s, fmt_spec);switch (arg.type) {case T_CHAR:(void)fprintf(fp,real_fmt, arg.v.c);break;case T_BYTE:(void)fprintf(fp,real_fmt, arg.v.b);break;case T_INT:(void)fprintf(fp,real_fmt, arg.v.i);break;case T_UINT:(void)fprintf(fp,real_fmt, arg.v.u);break;case T_LONG:(void)fprintf(fp,real_fmt, arg.v.l);break;case T_LONGLONG:(void)fprintf(fp,real_fmt, arg.v.ll);break;case T_ULONGLONG:(void)fprintf(fp,real_fmt, arg.v.ull);break;case T_FLOAT:(void)fprintf(fp,real_fmt, arg.v.f);break;case T_DOUBLE:(void)fprintf(fp,real_fmt, arg.v.d);break;case T_STRING:(void)fprintf(fp,real_fmt, arg.v.s);break;default:(void)fprintf(fp,?(by witton));break;}
}static inline void handle_printx_fmt(FILE* fp, const char* p,const char* end,int arg_count,FmtArg* argv) {char index_str[16] {0};char spec[32] {0};int idx 0;const char* colon memchr(p 1, :, end - (p 1));if (colon) {size_t len_idx colon - (p 1);strncpy(index_str, p 1, len_idx);strncpy(spec, colon 1, end - (colon 1));} else {strncpy(index_str, p 1, end - (p 1));}if (isdigit((unsigned char)index_str[0])) {idx strtol(index_str, NULL, 10);if (idx 0 idx arg_count) {print_one_arg(fp, spec, argv[idx]);} else {(void)fprintf(fp, BAD_INDEX(by witton));}} else {(void)fprintf(fp, BAD_FORMAT(by witton));}
}void printx_impl(FILE* fp, const char* fmt, int arg_count, FmtArg* argv) {if (NULL fmt) {for (int i 0; i arg_count; i) {print_one_arg(fp, , argv[i]);(void)putc( , fp);}return;}int used_args 0;const char* p fmt;while (*p) {if (p[0] { p[1] } used_args arg_count) {print_one_arg(fp, , argv[used_args]);p 2; // 跳过 }} else if (*p { *(p 1) ! {) {const char* end strchr(p, });if (!end) {(void)putc(*p, fp);continue;}handle_printx_fmt(fp, p, end, arg_count, argv);p end 1;} else if (*p { *(p 1) {) {(void)putc({, fp);p 2;} else if (*p } *(p 1) }) {(void)putc(}, fp);p 2;} else {(void)putc(*p, fp);}}
}
至此我们可以像C#一样写格式化输出代码了可以不担心格式符写错了。但是如果了使用自定义格式符即类似{1:.1f}中有冒号后面标准C格式符则依旧需要小心格式符是否写正确
如果本文对你有帮助欢迎点赞收藏