跳到主内容

第五章:语句 | C++ Primer 5

只记录 switch/goto 的一些细节和 try catch 语句。

标签跳转

switch 和 goto 的标签在本质上是相同的, 都是让程序在特定条件下跳转到该处继续执行, 也拥有相同的性质。

switch 在满足条件时,跳转到对应标签运行,除非遇到 break, 否则将继续向下越过标签,这和 goto 的标签行为是一致的,因为标签只是标注一个位置, 它并没有划分出某个子语句块。

在 switch 内部进行的变量定义

若在 switch 中进行变量定义,当标签跳转时变量定义所在的语句被跳过而没有执行, 那么程序将如何表现?

由于在函数内部定义的变量其在编译时便已经声明完毕, 但如果变量的定义语句被跳过,那么它可能没有被初始化。

cpp5/ch5/switch-skip-define-1.cc (源文件)

#include <iostream>
#include <string>

int main() {
  int input;
  std::cin >> input;
  switch (input) {
  case 1:
    // 初始化     jump bypasses variable initialization
    double x = 0;
    // 未初始化
    double y;
    // 显式初始化 jump bypasses variable initialization
    std::string s1("Hello World");
    // 隐式初始化 jump bypasses variable initialization
    std::string s2;
    // 初始化但未使用 jump bypasses variable initialization
    double z = 1;
  default:
    std::cout << x << std::endl
              << y << std::endl
              << s1 << std::endl
              << s2 << std::endl;
    break;
  }
  return 0;
}

编译报错,可以发现,跳过初始化语句是不允许的, 但可以跳过声明但没有初始化的声明语句。 如果我们只留下未初始化的 y,那么程序编译将能够通过,而且可以在后续部分进行赋值。

cpp5/ch5/switch-skip-define-2.cc (源文件)

#include <iostream>

int main() {
  int input;
  std::cin >> input;
  switch (input) {
  case 1:
    // 未初始化
    double y;
  default:
    std::cin >> y;
    std::cout << y << std::endl;
    break;
  }
  return 0;
}

如果确实有在不同分支定义并初始化不同变量的需要,那么就必须在一个子语句块中进行, 以确保变量的作用域在且仅在一个分支之内:

cpp5/ch5/switch-skip-define-3.cc (源文件)

#include <iostream>
#include <string>

int main() {
  int input;
  std::cin >> input;
  switch (input) {
  case 1: {
    double x = 0;
    std::cout << x << std::endl;
  }
  default:
    break;
  }
  return 0;
}

总结一下,就是:跳转语句不能从某个已初始化变量的作用域之外跳入其作用域之内。

switch 和 goto 都有一样的性质。

throw catch 异常处理

try {
    // 要运行的代码
} catch (/* 捕获的异常的类型 */ /* 存储被捕获异常的变量 */) {
    // 处理异常的代码
}

try-catch 语句将会捕获在 try 语句块下执行代码中所发生的异常 (这里所有的异常都是运行期发生的异常), 如果在 catch 语句中匹配了对应的异常类,那么将会转入 catch 块处理, 否则将会转入标准库的 terminate 函数处理。

一个 try-catch 语句中可以有多个 catch 块,将按顺序进行匹配处理。

C++ 使用类和继承来管理异常,常用的异常类声明在 stdexcept 头文件中,大致有:

exception

基础异常,所有的异常由此派生。

runtime_error

运行时异常的基础异常。它和它的子类一般由运行时自动抛出。

range_error

运行时异常:产生的值超过了值域。

overflow_error

运行时异常:计算上溢。

underflow_error

运行时异常:计算下溢。

logic_error

逻辑错误,一般由程序员自己定义子类并抛出。

domain_error

逻辑错误:参数对应的结果值不存在。

invalid_argument

逻辑错误:无效的参数。

length_error

逻辑错误:试图创建一个超出该类型最大长度的对象。

out_of_range

逻辑错误:使用一个超出范围的值。

使用异常

每个异常类都只定义一个名为 what 的无参数成员函数,可以调用它以得到 const char* 类型的字符串,记录异常原因。 如果要定义一个异常类实例,那么必须初始化。

在处理异常时,通常只需要根据异常的类型来进行后续操作; 如果不打算处理而是直接报错退出的话, what 方法会产生作用。