在 C 语言中,模板编程通常用预处理器进行,以避免代码冗余并提高可维护性。本文将介绍几种常见的 C 语言模板实现方法,并通过示例代码进行说明。
1. 简单的 C 宏
使用 C 语言宏是一种常见的模板方法,通过预处理器将重复代码封装。在下面的示例中,我们定义了一个宏 DO_RANDOM_STUFF,用于处理不同类型的数组。
#define DO_RANDOM_STUFF(type) do { \int i; \type *p = buf; \for (i = 0; i < len; i++) \p[i] = p[i] * k; \
} while (0)int func(void *buf, int len, float k, int request) {if (request == INT8) DO_RANDOM_STUFF(int8_t);else if (request == INT16) DO_RANDOM_STUFF(int16_t);else if (request == INT32) DO_RANDOM_STUFF(int32_t);
}
2. 完整函数的简单 C 宏
除了宏,还可以使用宏来声明完整的函数。例如,下面的代码使用 DECLARE_FUNC 来生成不同位数的乘法函数。
#define DECLARE_FUNC(n) \
static void func_##n(int##n##_t *p, int len, float k) { \int i; \for (i = 0; i < len; i++) \p[i] = p[i] * k; \
}DECLARE_FUNC(8)
DECLARE_FUNC(16)
DECLARE_FUNC(32)
这段代码将会创建 func_8(),func_16() 和 func_32() 函数。
3. 替代函数创建
在某些情况下,通过模板减少输入的冗余是出于性能考虑。以下是一个具体的例子:
int process_image(void *img, int width, int height, const int n) {int x, y;for (y = 0; y < height; y++) {for (x = 0; x < width; x++) {if (n == 0) foo(img, x, y);else if (n == 1) bar(img, x, y);else baz(img, x, y);}}
}
我们可以通过使用 inline 函数创建特定逻辑的 wrapper,避免在每次像素处理时都进行函数调用。该方法可以让编译器更容易优化代码:
static inline int process_image(void *img, int width, int height, const int n)
{int x, y;for (y = 0; y < height; y++) {for (x = 0; x < width; x++) {if (n == 0) foo(img, x, y);else if (n == 1) bar(img, x, y);else baz(img, x, y);}}
}int process_image_foo(void *img, int width, int height)
{return process_image(img, width, height, 0);
}int process_image_bar(void *img, int width, int height)
{return process_image(img, width, height, 1);
}int process_image_baz(void *img, int width, int height)
{return process_image(img, width, height, 2);
}
4. 混合完整函数机制和宏
如果发现前面的例子中冗余过多,可以通过使用宏来减轻负担:
#define DECLARE_PROCESS_IMAGE_FUNC(name, n) \
int process_image_##name(void *img, int width, int height) { \return process_image(img, width, height, n); \
}DECLARE_PROCESS_IMAGE_FUNC(foo, 0)
DECLARE_PROCESS_IMAGE_FUNC(bar, 1)
DECLARE_PROCESS_IMAGE_FUNC(baz, 2)
这种方法结合了宏和函数,减少了冗余,同时又保持了可读性。
5. 使用外部文件
另一种方法是将模板代码放在外部文件中。以下示例展示了如何通过包括不同的预处理器指令,生成不同类型的函数:
逻辑很简单;让我们从caller.c的内容开始:
#include <stdint.h>
#define TEMPLATE_U16
#include "evil_template.c"
#undef TEMPLATE_U16
#define TEMPLATE_U32
#include "evil_template.c"
#undef TEMPLATE_U32
#define TEMPLATE_FLT
#include "evil_template.c"
#undef TEMPLATE_FLT
#define TEMPLATE_DBL
#include "evil_template.c"
#undef TEMPLATE_DBL
在 evil_template.c 中,可以定义不同类型的模板:
#if defined(TEMPLATE_U16)# define RENAME(N) N ## _u16
# define TYPE uint16_t
# define SUM_TYPE uint32_t
# define XDIV(x, n) (((x) + ((1<<(n))-1)) >> (n))#elif defined(TEMPLATE_U32)# define RENAME(N) N ## _u32
# define TYPE uint32_t
# define SUM_TYPE uint64_t
# define XDIV(x, n) (((x) + ((1<<(n))-1)) >> (n))#elif defined(TEMPLATE_FLT)# define RENAME(N) N ## _flt
# define TYPE float
# define SUM_TYPE float
# define XDIV(x, n) ((x) / (float)(1<<(n)))#elif defined(TEMPLATE_DBL)# define RENAME(N) N ## _dbl
# define TYPE double
# define SUM_TYPE double
# define XDIV(x, n) ((x) / (double)(1<<(n)))#endifTYPE RENAME(func)(const TYPE *p, int n)
{int i;SUM_TYPE sum = 0;for (i = 0; i < 1<<n; i++)sum += p[i];return XDIV(sum, n);
}#undef RENAME
#undef TYPE
#undef SUM_TYPE
#undef XDIV
如果模板中包含一组函数,这种方法可能会比较方便,但对许多人来说却是万恶之源。