C/C++ GOTO妙用

目录

GOTO 语句

C/C++ 的 goto 语句用来在一个函数内进行任意跳转,用起来也是很方便。示例如下:

c 复制代码
int a() {
    int x = 0, sum = 0;
L1: if (x < 10) {
        x++;
        sum += x;
        goto L1;
    }
    return sum;
}

只需要在函数内部某处加一个标签(Label),通过 goto <label> 即可直接跳转。

但是由于 goto 的跳转比较原始,没有结构化,当程序比较庞大的时候调试起来就比较麻烦,代码可读性会大大降低,因此 E.W.Dijikstra 在 1965 年提出结构化程序设计来规避这种错误,并使代码更易读。很多时候我们尽量避免使用 goto 语句,转而使用其他的结构化方式。

尽管如此,也有一些地方使用 goto 语句相比结构化的方式更加高效。以下给出若干例子。

跳出多层循环

跳出循环的语句是 break,跳过一次循环的语句是 continue,但它们仅能跳出/跳过一层循环。当需要跳出多层循环时,可以考虑使用 goto 语句:

c 复制代码
LOOP: for (...) // loop 1
  for (...) // loop 2
    for (...) // loop 3
      if (...)
        goto LOOP;

此处的 goto 语句从第三层循环跳到第一层循环,相当于在第一层循环下执行了 continue 语句。而如果要用结构化的方式来实现,往往要借助条件变量。尽管如此,仍然需要一次次地跳出当前循环:

c 复制代码
int flag = 0;
for (...) // loop 1
  for (...) // loop 2
    for (...) { // loop 3
      if (...) {
        flag = 1;
        break;
      }
    }
    if (flag) break;
  }
}

可以看到,这次除了引入一个 flag 变量外,用了两个 break 语句才实现了等价功能。

很多高级语言都不支持 goto 语句以防止可能遇到的问题,比如 Java。但是 Java 支持用 break <label>continue <label> 来支持多层循环的跳出与跳过,比如该 goto 语句示例在 Java 中的等价写法是把 goto 关键字换成 continue。因此,这种用法不应该被嫌弃。

循环首次部分跳过

如果把一个循环体分为前后两部分 A 和 B,那么循环展开后的执行情况为:

plain 复制代码
ABABABAB...ABAB

但是,很多情况下我们可能要求跳过第一次运行的 A 或者最后一次运行的 B,那么实际的运行情况为:

plain 复制代码
BABABAB...ABAB

最经典的例子就是打印一个数组且要求结尾没有空格。此时可以考虑使用 goto 语句:

c 复制代码
void print_array(int arr[], int n) {
  int i = 0;
  if (i<n) goto PNT;
  for (; i<n; i++) {
    putchar(' '); // A
PNT:
    printf("%d", arr[i]); // B
  }
  puts(); // 换行
}

此时循环展开就完美按照 BABABA...BA 的顺序执行以打印数组。如果不用 goto 语句,那么可以考虑两种方案:

  1. 给 A 部分加上条件判断是否为第一次循环。但这样的话,每次循环时都会进行判断,造成不必要的判断过程;
  2. 直接把 B 部分代码放到循环前面。但是当 B 部分代码特别多时,该方法会导致严重的代码重复。尽管可以使用函数将 B 部分封装,但是函数调用也会带来额外开销。

相比之下,这种情况使用 goto 语句完全不影响循环体代码,效率更高。

注意,这种情况下要求循环体的循环条件语句尽量简单(大多数情况下是简单的,比如此处的 i<n),因为对于 for、while 等循环会在进入循环前判断一次,因此这种写法通常会在 goto 前加上 if 语句进行同样的循环判断。如果判断语句过长过复杂,同样会造成一定的代码冗余。当然,对于 do-while 循环,其循环条件是在每次循环后判断,此时不必在 goto 跳转前进行判断。

另外,该方法在编写代码时,如果要达到 ABABAB...ABA 的效果,即跳过最后一次循环的后半部分,应该直接在循环体内将 A、B 的顺序调换,并用 goto 语句跳过第一次循环的前半部分。


原文地址:https://www.cnblogs.com/RainbowC0/p/18765900 ,未经作者许可禁止转载。

相关推荐
_OP_CHEN6 小时前
【算法基础篇】(五十八)线性代数之高斯消元法从原理到实战:手撕模板 + 洛谷真题全解
线性代数·算法·蓝桥杯·c/c++·线性方程组·acm/icpc·高斯消元法
_OP_CHEN7 小时前
【Linux系统编程】(二十九)深度解密静态链接:从目标文件到可执行程序的底层魔法
linux·操作系统·链接·文件系统·c/c++·静态链接
_OP_CHEN1 天前
【Linux系统编程】(二十八)深入 ELF 文件原理:从目标文件到程序加载的完整揭秘
linux·操作系统·编译·c/c++·目标文件·elf文件
_OP_CHEN1 天前
【算法基础篇】(五十七)线性代数之矩阵乘法从入门到实战:手撕模板 + 真题详解
线性代数·算法·矩阵·蓝桥杯·c/c++·矩阵乘法·acm/icpc
_OP_CHEN2 天前
【算法基础篇】(五十六)容斥原理指南:从集合计数到算法实战,解决组合数学的 “重叠难题”!
算法·蓝桥杯·c/c++·组合数学·容斥原理·算法竞赛·acm/icpc
_OP_CHEN2 天前
【Linux系统编程】(二十七)手撕动静态库原理与实战:从底层逻辑到代码落地
linux·操作系统·动态库·静态库·c/c++·库的原理与制作
_OP_CHEN3 天前
【算法基础篇】(五十五)卡特兰数封神之路:从括号匹配到二叉树构造,组合数学的万能钥匙!
算法·蓝桥杯·c/c++·组合数学·卡特兰数·算法竞赛·acm/icpc
_OP_CHEN3 天前
【Linux系统编程】(二十六)一文吃透 Ext 系列文件系统软硬链接:原理、实战与底层逻辑揭秘
linux·操作系统·文件系统·c/c++·硬链接·软链接·ext2文件系统
_OP_CHEN4 天前
【算法基础篇】(五十四)解析错排问题:从信封错位到编程实战,一次性搞懂排列组合中的 “反常识” 难题!
算法·蓝桥杯·c/c++·组合计数·算法竞赛·acm/icpc·错排问题
_OP_CHEN4 天前
【Linux系统编程】(二十五)从路径到挂载:Ext 系列文件系统的 “导航” 与 “整合” 核心揭秘
linux·操作系统·文件系统·c/c++·ext2文件系统·路径解析·挂载分区