C语言表驱动法

关注微信公众号塔容万物

if/else

使用if/else语句,可以让程序根据条件选择不同的执行路径。

import enum
class A(enum.Enum):
    A1 = 1
    A2 = 2

def fA1():
    print("A1")

def fA2():
    print("A2")

def do_something(cond: A):
    """根据条件执行不同的函数"""
    if cond == A.A1:
        fA1()
    elif cond == A.A2:
        fA2()

当需要判断的条件过多时,if/else语句的数量也随之增多

import enum
class A(enum.Enum):
    A1 = 1
    A2 = 2

def fA1():
    print("A1")

def fA2():
    print("A2")

class B(enum.Enum):
    B1 = 1
    B2 = 2

def fB1():
    print("B1")

def fB2():
    print("B2")

def do_something(cond1: A, cond2: B):
    """根据条件执行不同的函数"""
    if cond1 == A.A1 and cond2 == B.B1:
        fA1()
        fB1()
    elif cond1 == A.A1 and cond2 == B.B2:
        fA1()
        fB2()
    elif cond1 == A.A2 and cond2 == B.B1:
        fA2()
        fB1()
    elif cond1 == A.A2 and cond2 == B.B2:
        fA2()
        fB2()

从上面的代码可以发现,当需要判断的条件有两个,且每个条件有两种情况时,就需要写4个if/else语句,当条件更多时,if/else语句的数量也随之增多,而且还需要修改do_something函数的内部逻辑(添加其他的if/else语句),这样会导致do_something函数的代码越来越长,越来越难以维护。

表驱动法

表驱动法是一种编程技巧,它可以将if/else语句转换为表格,从而简化代码,提高代码的可读性和可维护性。这里的表可以是字典,也可以是数组。将上面的代码修改为字典表驱动法:

from typing import Callable

callback: dict[A | B, Callable] = {
    A.A1: fA1,
    A.A2: fA2,
    B.B1: fB1,
    B.B2: fB2,
}

def do_something(cond1: A, cond2: B):
    """根据条件执行不同的函数"""
    callback[cond1]()
    callback[cond2]()

可以发现,通过字典根据不同的条件获取不同的函数,可以将if/else语句转换为表格,从而简化代码,提高代码的可读性和可维护性。如果在后期修改A/B情况的个数的时候,只需要修改字典callback即可,不需要修改do_something函数的代码,这样可以减少代码的维护成本。

表驱动法的核心是,执行的逻辑是在运行时隐式的确定,而if/else则是在编译时确定的。因此,表驱动法的性能会比if/else差一些,但是这种性能的损失是可以接受的。

C语言实现表驱动法

在C语言种没有原生的字典类型,所以使用数组来实现表驱动法。

// 定义回调函数
typedef int (*callback)(int, int);
// 定义表驱动元素
typedef struct {
  char* op;
  callback cb;
} icallback;

// 定义多个回调函数
int
add(int a, int b)
{
  return a + b;
}

int
sub(int a, int b)
{
  return a - b;
}

// 定义表
static icallback callback_table[] = {
  { "add", add },
  { "sub", sub },
  { NULL, NULL },
};

定义了表之后,需要实现一个查找函数,根据条件查找对应的函数。

callback
find_callback(char* op)
{
  for (int i = 0; i < sizeof(callback_table) / sizeof(icallback); i++) {
    if (callback_table[i].op == NULL) {
      printf("not found %s\n", op);
      return NULL;
    }
    if (strcmp(op, callback_table[i].op) == 0) {
      return callback_table[i].cb;
    }
  }
  return NULL;
}

通过这个查找函数,就可以确定执行的函数了,然后执行即可。

// 根据条件执行不同的函数
void
do_something(char* op, int a, int b)
{
  callback cb = find_callback(op);
  if (cb == NULL) {
    printf("not found %s\n", op);
    return;
  }
  printf("%d %s %d = %d\n", a, op, b, cb(a, b));
}

// 使用
do_something("add", 1, 2); // 1 add 2 = 3
do_something("sub", 1, 2); // 1 sub 2 = -1
do_something("mul", 1, 2); // not found mul

与Python不同的是,C语言中使用函数指针,编译器无法进行内联优化,但当回调函数执行时间较长时,这种性能的损失是可以接受的。

总结

表驱动法的核心是通过在运行时动态的查找对应的函数。由于编程语言之间的特点不同,具体的实现方法也不同,但是核心思想是一样的。