表驱动法(更优雅的写if-else、switch-case)

表驱动法是一种编程模式,用于替代复杂的if-else或switch-case语句。通过将数据存储在表中,实现更简洁、易维护的代码。文章介绍了直接访问、阶梯访问等应用场景,并展示了如何在处理星期几、月份天数以及评分等级等场景下运用表驱动法,提高代码的可读性和可扩展性。同时,讨论了在阶梯访问中注意的端点处理、二分查找优化等问题。

表驱动法

表驱动法就是一种编程模式(scheme)——从表里面查找信息而不使用逻辑语句(if
和case)。事实上,凡是能通过逻辑语句来选择的事物,都可以通过查表来选择。对简单的情况而言,使用逻辑语句更为容易和直白。但随着逻辑链的越来越复杂,查表法也就愈发显得更具吸引力。

直接访问

一个很简单的例子: 今天星期几

const day = new Date().getDay()
let day_zh;
if(day === 0){
    day_zh = '星期日'
}else if(day === 1) {
    day_zh = '星期一'
}
...
else{
    day_zh = '星期六'
}

// 或者 用 switch case
switch(day) {
    case 0:
        day_zh = '星期日'
        break;
    case 1:
        day_zh = '星期一'
        break;
        ...
}


实现同样功能的一种更简单、更容易修改的方法是把这些数据存到一张表里面。

const week = ['星期日', '星期一',..., '星期六']
const day = new Date().getDay(
const day_zh = week[day]

第二个例子:假设你需要一个可以返回每个月中天数的函数(为简单起见不考虑闰年)
使用if else

function iGetMonthDays(iMonth) {
  let iDays;
  if(1 == iMonth) {iDays = 31;}
  else if(2 == iMonth) {iDays = 28;}
  else if(3 == iMonth) {iDays = 31;}
  else if(4 == iMonth) {iDays = 30;}
  else if(5 == iMonth) {iDays = 31;}
  else if(6 == iMonth) {iDays = 30;}
  else if(7 == iMonth) {iDays = 31;}
  else if(8 == iMonth) {iDays = 31;}
  else if(9 == iMonth) {iDays = 30;}
  else if(10 == iMonth) {iDays = 31;}
  else if(11 == iMonth) {iDays = 30;}
  else if(12 == iMonth) {iDays = 31;}
  return iDays;
}

使用表驱动(包括闰年判断)

const monthDays = [
  [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
  [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
]
function getMonthDays(month, year) {
  let isLeapYear = (year % 4 === 0) && (year % 100 !== 0 || year % 400 === 0) ? 1 : 0
  return monthDays[isLeapYear][(month - 1)];
}
console.log(getMonthDays(2, 2000))

对象中的应用
形如:

if (key = "Key A")
{
   处理 Key A 相关的数据。
}
else if (key = "Key B")
{
   处理 Key B 相关的数据。
}

if (key = "Key A")
{
   执行 Key A 相关的行为。
}
else if (key = "Key B")
{
   执行 Key B 相关的行为。
}

可以改装成:

let table = {
  A: {
    data: "数据1",
    action: "行为1"
  },
  B: {
    data: "数据2",
    action: "行为2"
  }
}

function handleTable(key) {
  return table[key]
}
console.log(handleTable('A').data)

let table = {
  A: {
    data: "数据1",
    action () {
      console.log('action 1')
    }
  },
  B: {
    data: "数据2",
    action () {
      console.log('action 2')
    }
  }
}

function handleTable(key) {
  return table[key]
}
handleTable('A').action()

回顾一下我以前写的代码,好多复杂的代码都通过这种方式简化了不少,比如:

let vm = this
let adTable = {
  bdkq () {
    window.clLib.localStorageOperating.updateData('default_hospital_code', '871978')
    vm.$router.push('/hospital')
  },
  etyy () { vm.goSp(10012) },
  szn () { vm.goCh('szn') }
}
adTable[data.key] ? adTable[data.key]() : location.href = data.ad_url

以前是怎么写的呢? 看看就知道了,特别复杂!

switch (data.key) {
  case 'bdkq':
    window.clLib.localStorageOperating.updateData('default_hospital_code', '871978')
    this.$router.push('/hospital')
    break
  case 'etyy':
    this.goSp(10012)
    break
  case 'szn':
    this.goCh('szn')
    break
  default:
    location.href = data.ad_url
    break
}

阶梯访问
这种方法不像索引访问那么直接,但比索引访问要节省空间。

典型的例子就是等级评定

考试按分数评优良中差四个等级

简单做法

    let level = '优'
    if(score <60 ){
        level = '差'
    }else if(score < 80) {
        level = '中'
    }else if(score < 90) {
        level = '良'
    }

如果需要添加等级 60-70 之间 是一般,就需要改逻辑,逻辑简单的还好

    let level = '优'
    if(score <60 ){
        level = '差'
    }else if(score < 70) {
        level = '一般'
    }else if(score < 80) {
        level = '中'
    }else if(score < 90) {
        level = '良'
    }

这时用阶梯查询就比较方便,而且扩展性也好

    const levelTable = ['差', '中', '良', '优']
    const scoreCeilTable = [60, 80, 90, 100]
    
    function getLevel(score) {
        const len = scoreCeilTable.length
        for(let i = 0; i < len; i++) {
            const scoreCeil = scoreCeilTable[i]
            if(score <= scoreCeil) {
                return levelTable[i]
            }
            
        }
        return levelTable[len - 1]
    }
    

就算后面需要添加等级60-70 —> ‘一般’ 也只需要简单添加数据表就可以了,而不需要修改逻辑

    const levelTable = ['差', '一般', '中', '良', '优']
    const scoreCeilTable = [60, 70, 80, 90, 100]

阶梯访问表需要注意几个问题

  1. 留心端点
  2. 二分查找替代顺序查找:上面例子用了顺序查找,当数据比较大时,查找成本还是比较大的,应该考虑用二分查找替代顺序查找
  3. 考虑用索引访问来取代阶梯技术,阶梯查找比较耗时,如果速度非常重要,可以用索引访问来取代阶梯,用空间换时间,但也要分情况,因为离散空间是不能够完全替代连续空间的

二分查找跳转

<think> 我们正在讨论if-elseswitch语句的适用情况。根据引用[2]和[5],我们可以总结如下: if-else语句的适用场景: 1. 条件判断较为复杂,需要组合多个条件(使用逻辑运算符如&&、||等)时。 2. 判断条件为范围(如$x > 10$)或布尔达式时。 3. 分支数量较少(通常少于3个)时,if-else结构简洁。 4. 需要处理非离散值(如浮点数、字符串模式匹配等)时。 switch语句的适用场景: 1. 基于同一个变量的不同离散值(如整数、枚举、字符等)进行多分支选择。 2. 分支数量较多(通常大于3个)时,switch结构清晰易读。 3. 需要利用case穿透(fall-through)特性时。 4. 代码可读性要求高,希望明确列出所有可能取值时。 性能考虑: - 在分支较少时,if-elseswitch性能相近。 - 分支较多时,编译器通常会对switch进行优化(如跳转),使其效率高于if-else链[^2]。 设计原则: 1. 单一职责原则:每个条件分支应只处理单一逻辑。 2. 开闭原则:通过策略模式等可替代条件语句,便于扩展[^3]。 3. 可读性优先:当两种结构均可使用时,选择易读的形式。 替代方案(引用[3]): - 表驱动法:用映射(如数组、哈希)替代多分支。 - 多态与策略模式:通过面向对象特性消除条件判断。 - 状态模式:适用于状态转移逻辑复杂的情况。 $$ \text{决策公式}:\text{选择} = \begin{cases} \text{if-else} & \text{if } n \leq 3 \text{ or 条件复杂} \\ \text{switch} & \text{if } n > 3 \text{ and 离散值} \\ \text{替代方案} & \text{if 分支频繁变动} \end{cases} $$ 其中$n$为分支数量。 示例对比: ```java // if-else处理复杂条件 if (score >= 90 && attendance > 0.8) { grade = 'A'; } else if (score >= 80 || (score >= 75 && attendance > 0.9)) { grade = 'B'; } else { grade = 'C'; } // switch处理离散值 switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: days = 31; break; case 4: case 6: case 9: case 11: days = 30; break; case 2: days = isLeapYear ? 29 : 28; break; } ``` 结论: 在分支少或条件复杂时首选if-else,分支多且为离散值时首选switch。当分支逻辑频繁变化时,应考虑驱动或设计模式[^3]。 相关问题: 1. 如何用策略模式替代复杂的if-else结构? 2. switch语句在不同编程语言中的实现有何差异? 3. 表驱动法的具体应用场景有哪些? 4. 编译器如何处理switch语句以实现高效跳转? 5. 在函数式编程中如何避免条件分支语句? </think> ### if-elseswitch 语句的适用场景分析 根据编程实践和研究[^2][^5],if-elseswitch 语句各有其最佳应用场景,以下是详细对比和适用情况分析: #### 一、if-else 语句的适用场景 1. **简单条件判断** 当只需要判断一个布尔值(真/假)时: ```c if (is_rainning) { take_umbrella(); } ``` 2. **复杂条件组合** 需要组合多个条件时(使用 `&&`, `||`, `!` 等逻辑运算符): ```c if (age >= 18 && has_id_card && !is_expired) { allow_entry(); } ``` 3. **范围判断** 处理连续范围或不等式时: ```c if (score >= 90) { grade = 'A'; } else if (score >= 80) { // 80-89 grade = 'B'; } // ... ``` 4. **非离散值处理** 适用于浮点数、字符串等非离散类型: ```c if (temperature > 37.5) { alert_fever(); } ``` 5. **少量分支(通常 ≤3)** 分支较少时结构简洁[^2]: $$ \text{代码复杂度} \propto \frac{1}{\text{分支数量}} \quad \text{(当分支数小时)} $$ #### 二、switch 语句的适用场景 1. **多分支离散值判断** 基于同一变量的不同离散值时清晰: ```c switch (month) { case 1: case 3: case 5: // 31天的月份 days = 31; break; case 2: days = is_leap ? 29 : 28; break; // ... } ``` 2. **枚举类型处理** 完美匹配枚举值: ```c enum State {IDLE, RUNNING, ERROR}; switch (device_state) { case IDLE: init_operation(); break; case RUNNING: monitor(); break; case ERROR: recover(); break; } ``` 3. **分支较多时(通常 >3)** 分支多时比多层 if-else 易维护[^5]: $$ \text{可读性优势} = k \cdot n \quad (n>3, \ k>0) $$ 4. **需要穿透(fall-through)特性** 多个 case 共享相同逻辑时: ```c switch (error_code) { case 404: case 408: // 共享处理流程 handle_network_error(); break; case 500: handle_server_error(); break; } ``` #### 三、决策参考 | 考量维度 | if-else 优 | switch 优 | |-------------------|----------------------------------|----------------------------------| | 分支数量 | ≤3 个分支 | >3 个分支 | | 判断类型 | 布尔值、范围值、复合条件 | 离散值(整型/枚举) | | 可扩展性 | 新增条件需修改逻辑 | 新增 case 不影响既有结构[^3] | | 执行效率 | 分支少时略快 | 分支多时编译器可能优化为跳转 | | 代码可读性 | 简单条件时直观 | 多选项时结构清晰 | #### 四、替代方案(引用[3]) 当分支过于复杂时,可考虑: 1. **表驱动法**:用数组/映射存储结果 ```c int days_per_month[] = {0,31,28,31,...}; if (month == 2 && is_leap) days = 29; else days = days_per_month[month]; ``` 2. **策略模式**:通过函数指针/多态实现 3. **状态机**:适用于复杂状态转换 > 关键原则:**优先选择使代码最清晰可读的结构**。当两种结构都可行时,分支数量是决定性因素[^2][^5]。 --- ### 相关问题 1. 编译器如何优化 switch 语句实现高效跳转? 2. 如何处理既需要范围判断又需要离散值的混合条件场景? 3. 函数式编程中如何避免使用条件语句? 4. switch 语句在不同编程语言(C/Java/Python)中有何差异? 5. 何时应该用多态替代条件语句?[^3] 6. 复杂业务逻辑中如何降低条件语句的圈复杂度?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值