GESP C++ 四级核心知识点精讲与真题剖析

📋 目录

  1. 指针 (Pointers)
  2. 二维及多维数组
  3. 结构体 (Struct)
  4. 函数与作用域
  5. 排序算法
  6. 递推算法
  7. 文件操作
  8. 异常处理

1. 📌 指针 (Pointers)

代码示例与说明

#include <iostream>
using namespace std;

int main() {
    int num = 42;
    // 1. 定义与初始化:指针p存储变量num的地址
    int *p = &num;
    cout << "num的地址: " << p << endl; // 输出地址值
    cout << "通过指针访问值: " << *p << endl; // 输出42,*为解引用运算符

    // 2. 通过指针修改原变量
    *p = 100;
    cout << "修改后num的值: " << num << endl; // 输出100

    // 3. 指针与数组的关系:数组名可视为指向首元素的指针常量
    int arr[3] = {10, 20, 30};
    int *ptr = arr; // 等价于 int *ptr = &arr[0];
    cout << "arr[1] = " << arr[1] << endl;
    cout << "*(ptr + 1) = " << *(ptr + 1) << endl; // 同样输出20

    // 4. 指针常量与常量指针
    const int *p1 = &num; // 指向常量的指针:不能通过p1修改num的值
    // *p1 = 200; // 错误!
    int *const p2 = &num; // 常量指针:p2的指向不能改变
    // p2 = &arr[0]; // 错误!
    return 0;
}

关键点&取地址,*在声明中表示指针类型,在表达式中表示解引用。数组名是指向首元素的指针常量

历年真题完整引用与解析

  • 2025年09月 第1题

    第 1 题 运行下面程序后变量 a 的值是()。

    int a = 10;
    int* p = &a;
    *p = 20;
    

    A. 10 B. 20 C. 编译错误 D. 不确定

    解析:代码中p指向a*p = 20;即通过指针p解引用,修改了其指向的内存(变量a)的值为20。因此a的值变为20。

  • 2025年09月 第2题

    第 2 题 以下关于数组的描述中,()是错误的。 ☐ A. 数组名是一个指针常量
    ☐ B. 随机访问数组的元素方便快捷
    ☐ C. 数组可以像指针一样进行自增操作
    ☐ D. sizeof(arr) 返回的是整个数组 arr 占用的字节数

    解析:数组名是一个指向首元素的指针常量,其值(地址)不能改变,因此不能进行自增/自减操作(如arr++)。选项C的描述是错误的。

易错点分析

  1. 定义错误int *p = a;(错误,a不是地址)与 int *p = &a;(正确)。
  2. 操作混淆:误以为可以对数组名进行自增操作(arr++),实际上数组名是指针常量。
  3. sizeof误解:混淆sizeof(arr)(返回整个数组字节数)与sizeof(p)(返回指针变量本身大小)。真题中明确指出sizeof(arr)返回整个数组占用的字节数是正确的描述。

2. 📊 二维及多维数组

代码示例与说明

#include <iostream>
using namespace std;

// 正确传递二维数组:必须指定第二维(列)的大小
void printMatrix(int mat[][4], int rows) {
    for(int i = 0; i < rows; i++) {
        for(int j = 0; j < 4; j++) {
            cout << mat[i][j] << " ";
        }
        cout << endl;
    }
}

int main() {
    // 1. 二维数组的初始化
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    // 2. 访问元素
    cout << "matrix[1][2]: " << matrix[1][2] << endl; // 输出7

    // 3. 理解内存连续性:按行优先连续存储
    int *p = &matrix[0][0];
    cout << "*(p + 4*1 + 2): " << *(p + 4*1 + 2) << endl; // 同样输出7,计算偏移

    // 4. 将二维数组传递给函数
    printMatrix(matrix, 3);

    return 0;
}

关键点:内存按行连续存储。作为函数参数时,第一维大小可省略,第二维大小必须指定

历年真题完整引用与解析

  • 2025年06月 第2题

    第 2 题 下面的函数接收一个3行4列的二维数组并输出其中元素,则横线上不能填写()。

    void printArray(___) {
        for (int i = 0; i < 3; ++i)
            for (int j = 0; j < 4; ++j)
                std::cout << arr[i][j] << " ";
    }
    

    □ A. int arr[3][4] □ B. int arr[][4] □ C. int (arr)[4] □ D. int* arr

    解析:函数接收二维数组参数时,必须指定列数。int** arr是指向指针的指针,其内存模型与二维数组不同,不能直接接收matrix这样的二维数组名。因此D选项是错误的。

  • 2024年09月 第7题

    第 7 题 在 C++ 中,()正确声明了一个 3 行 4 列的二维数组。 ☐ A. int arr[3, 4]; □ B. int arr[3][4]; □ C. int arr[4][3]; □ D. int arr(3, 4);

    解析:C++中二维数组的正确声明方式是type name[row][col];,因此int arr[3][4];是唯一正确的选项。

易错点分析

  1. 函数参数声明错误:常见错误写法int arr[][]int **arr。正确写法为int arr[][4]int (*arr)[4]
  2. 初始化语法混淆:结构体和数组的初始化应使用花括号{}。真题中明确指出Point p = {1,2};是正确的,而Point p = (1,2);是错误的。

3. 🏗️ 结构体 (Struct)

代码示例与说明

#include <iostream>
#include <string>
#include <algorithm> // 使用sort函数需要包含此头文件
using namespace std;

// 1. 结构体定义
struct Student {
    string name;
    int age;
    float score;
};

// 2. 结构体嵌套
struct Class {
    string className;
    Student monitor; // 嵌套Student结构体
};

// 3. 用于sort的比较函数:按成绩降序排序
bool cmpByScoreDesc(const Student &a, const Student &b) {
// 此处形参为什么用 const .. &  ?
// & 引用传递,避免复制大结构体时的性能损失
// const 确保函数不会修改结构体内容
// 不考虑性能可以直接 Student a, Student b; 来定义每个参数

// 第一个参数a 代码前面 ,第二个参数b 代码后面
// 我们希望前面的分数大于后面的分数,才返回true

    return a.score > b.score; // 降序
}

// 4. 用于sort的比较函数:先按年龄升序,年龄相同再按成绩降序
bool cmpComplex(const Student &a, const Student &b) {
    // 年龄不同,按年龄升序
    if (a.age != b.age)
        return a.age < b.age; // 年龄升序
    // 否则年龄相同,按成绩降序
    else
        return a.score > b.score; // 成绩降序
}

int main() {
    // 5. 结构体变量的初始化(使用花括号{})
    Student stu1 = {"张三", 15, 95.5};
    Student stu2 = {"李四", 16, 90.0};
    Student stu3 = {"王五", 15, 88.0};
    Student stu4 = {"赵六", 16, 92.0};

    // 6. 结构体数组
    Student classA[4] = {stu1, stu2, stu3, stu4};

    // 7. 使用自定义比较函数对结构体数组排序
    // 7.1 按成绩降序排序
    sort(classA, classA + 4, cmpByScoreDesc);
    cout << "按成绩降序排序后:" << endl;
    for (int i = 0; i < 4; i++) {
        cout << classA[i].name << " " << classA[i].age << " " << classA[i].score << endl;
    }

    // 7.2 按复杂规则排序(先年龄升序,再成绩降序)
    sort(classA, classA + 4, cmpComplex);
    cout << "\n先年龄升序,再成绩降序排序后:" << endl;
    for (int i = 0; i < 4; i++) {
        cout << classA[i].name << " " << classA[i].age << " " << classA[i].score << endl;
    }

    // 8. 嵌套结构体的初始化和访问
    Class myClass = {"四年级一班", stu1};
    cout << myClass.className << "的班长是" << myClass.monitor.name << endl;

    return 0;
}

关键点:使用struct关键字定义新类型,用.操作符访问成员,初始化必须使用{}。对结构体数组排序时,需要自定义比较函数来指定排序规则。

历年真题完整引用与解析

  • 2025年09月 第7题

    第 7 题 关于结构体初始化,以下哪个选项中正确的是()。

    1 struct Point {int x, y;}
    

    A. Point p = (1,2); B. Point p = {1,2}; C. Point p = new {1,2}; D. Point p = <1,2>;

    解析:C++中结构体变量正确的初始化方式是使用花括号{}进行列表初始化。因此选项B Point p = {1,2};是唯一正确的。

  • 2025年09月 第8题

    第 8 题 运行如下代码会输出()。

    struct Cat {
        string name;
        int age;
    };
    void birthday(Cat& c) {
        c.age++;
    }
    int main() {
        Cat kitty{"Mimi", 2};
        birthday(kitty);
        cout << kitty.name << " " << kitty.age;
    }
    

    A. Mimi 2 B. Mimi 3 C. kitty 3 D. kitty 2

    解析birthday函数的参数是Cat&(引用传递),因此函数内c.age++直接修改了实参kittyage成员。kitty.age初始为2,自增后变为3。输出为Mimi 3

易错点分析

  1. 初始化语法错误:误用圆括号()、尖括号<>new进行初始化。唯一正确语法是花括号{}
  2. 成员访问混淆:通过结构体变量访问成员用.,通过结构体指针访问成员用->
  3. 结构体排序规则定义错误:对结构体数组使用sort函数时,必须提供自定义比较函数。比较函数应返回bool值,表示第一个参数是否应排在第二个参数之前。定义多级排序规则时,逻辑要清晰。

4. 📏 函数与作用域

代码示例与说明

#include <iostream>
using namespace std;

int globalVar = 100; // 全局变量

// 1. 函数声明(可指定默认参数)
int add(int a, int b = 5);

// 2. 参数传递方式对比
void byValue(int x) {
    x += 10; // 只修改局部副本
}
void byReference(int &x) {
    x += 10; // 修改原变量
}
void byPointer(int *x) {
    if (x) *x += 10; // 通过指针修改原变量
}

int main() {
    int localVar = 20;

    // 3. 局部变量屏蔽全局变量
    int globalVar = 50; // 局部变量,屏蔽了全局的globalVar
    cout << "局部 globalVar: " << globalVar << endl; // 输出50
    cout << "全局 globalVar: " << ::globalVar << endl; // 使用::访问全局,输出100

    // 4. 测试参数传递
    int num = 1;
    byValue(num);
    cout << "after byValue: " << num << endl; // 输出1,未改变
    byReference(num);
    cout << "after byReference: " << num << endl; // 输出11,已改变
    byPointer(&num);
    cout << "after byPointer: " << num << endl; // 输出21,已改变

    // 5. 使用默认参数
    cout << "add(3): " << add(3) << endl; // b使用默认值5,输出8
    cout << "add(3, 4): " << add(3, 4) << endl; // b使用4,输出7
    return 0;
}

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

关键点:局部作用域内定义的变量会屏蔽同名的全局变量。引用传递和指针传递可以修改实参并避免大对象拷贝。

历年真题完整引用与解析

  • 2025年09月 第5题

    第 5 题 下面这段代码会输出()。

    int x = 5;
    void foo() {
        int x = 10;
        cout << x << " ";
    }
    void bar() {
        cout << x << " ";
    }
    int main() {
        foo();
        bar();
    }
    

    A. 5 5 B. 10 10 C. 5 10 D. 10 5

    解析foo()函数内定义了局部变量x,其值为10,屏蔽了全局变量x,因此输出10。bar()函数内没有定义局部变量x,因此使用全局变量x,其值为5。最终输出10 5

  • 2025年03月 第5题

    第 5 题 执行下述代码,将输出()。

    void swap(int a, int &b) {
        int temp = a;
        a = b;
        b = temp;
    }
    int main() {
        int x = 1, y = 2;
        swap(x, y);
        std::cout << x << y;
        return 0;
    }
    

    □ A. 12 □ B. 21 □ C. 22 □ D. 11

    解析:函数swap的参数a是值传递,b是引用传递。调用swap(x, y)时,ax的副本(值为1),by的引用。函数内交换了ab的值,即交换了局部变量ay。因此y被改为1,x不变。输出xy11

易错点分析

  1. 作用域混淆:认为局部变量修改后,全局变量也随之改变。实际上局部变量会屏蔽全局变量,要访问被屏蔽的全局变量需使用::
  2. 参数传递效果不清:误以为值传递可以修改实参。只有引用传递和指针传递才能修改实参。
  3. 默认参数位置:默认参数应在函数声明中指定,而非定义(除非声明与定义合一)。

5. 🧮 排序算法

代码示例与说明

#include <iostream>
#include <vector>
using namespace std;

// 1. 选择排序 (不稳定)
void selectionSort(vector<int>& nums) {
    int n = nums.size();
    for (int i = 0; i < n - 1; ++i) {
        int minIndex = i; // 记录未排序部分最小元素的下标
        for (int j = i + 1; j < n; ++j) {
            if (nums[j] < nums[minIndex]) { // 寻找最小值
                minIndex = j;
            }
        }
        // 将找到的最小值与当前位置i交换
        if (minIndex != i) {
            swap(nums[i], nums[minIndex]);
        }
    }
}

// 2. 冒泡排序 (稳定,可优化)
void bubbleSort(vector<int>& nums) {
    int n = nums.size();
    for (int i = 0; i < n - 1; ++i) {
        bool swapped = false; // 优化标志
        for (int j = 0; j < n - i - 1; ++j) {
            if (nums[j] > nums[j + 1]) { // 相邻元素比较
                swap(nums[j], nums[j + 1]);
                swapped = true;
            }
        }
        if (!swapped) break; // 本轮无交换,说明已有序,提前结束
    }
}

// 3. 插入排序 (稳定)
void insertionSort(vector<int>& nums) {
    int n = nums.size();
    for (int i = 1; i < n; ++i) { // 从第二个元素开始
        int key = nums[i]; // 待插入的元素
        int j = i - 1;
        // 将大于key的元素向后移动
        while (j >= 0 && nums[j] > key) {
            nums[j + 1] = nums[j];
            j--;
        }
        nums[j + 1] = key; // 插入key到正确位置
    }
}

关键点:掌握三种排序的双层循环结构。选择排序不稳定,冒泡和插入稳定。插入排序在最好情况(已有序)下时间复杂度为O(n)。

历年真题完整引用与解析

  • 2025年09月 第9题

    第9题 关于排序算法的稳定性,以下说法错误的是()。 A. 稳定的排序算法不改变相等元素的相对位置
    B. 冒泡排序是稳定的排序算法
    C. 选择排序是稳定的排序算法
    D. 插入排序是稳定的排序算法

    解析:稳定性是指排序后相等元素的相对顺序保持不变。冒泡排序和插入排序是稳定的。选择排序在交换元素时可能改变相等元素的原始顺序,因此是不稳定的。选项C的说法错误。

  • 2025年09月 第10题

    第 10 题 下面代码试图实现选择排序,使其能对数组 nums 排序为升序,则横线上应分别填写()。

    void selectionSort(vector<int>& nums) {
        int n = nums.size();
        for (int i = 0; i < n - 1; ++i) {
            int minIndex = i;
            for (int j = i + 1; j < n; ++j) {
                if ( ___ ) { // 在此处填入代码
                    minIndex = j;
                }
            }
        }
        // 在此处填入代码
    }
    

    □ A. 1 nums[j] < nums[minIndex] 2 swap(nums[i], nums[minIndex])

    □ B

    1 nums[j] > nums[minIndex] 2 swap(nums[i], nums[minIndex])

    □ C

    1 nums[j] <= nums[minIndex] 2 swap(nums[j], nums[minIndex])

    D

    1 nums[j] <= nums[minIndex] 2 swap(nums[i], nums[j])

    解析:选择排序升序排序时,内循环应寻找最小元素的下标。因此比较条件应为nums[j] < nums[minIndex]。找到最小元素下标minIndex后,应将其与当前位置i的元素交换。因此正确答案是A。

  • 2025年09月 第11题

    第 11 题 下面程序实现插入排序(升序排序),则横线上应分别填写()。

    void insertionSort(int arr[], int n) {
        for (int i = 1; i < n; i++) {
            int key = arr[i];
            int j = i - 1;
            while (j >= 0 && ___) { // 在此处填入代码
                arr[j + 1] = arr[j];
                j--;
            }
            ___; // 在此处填入代码
        }
    }
    

    □ A

    1 arr[j] > key 2 arr[j + 1] = key

    B

    1 arr[j] < key 2 arr[j + 1] = key

    □ C

    1 arr[j] > key 2 arr[j] = key

    D

    1 arr[j] < key 2 arr[j] = key

    解析:插入排序升序排序时,内循环while的任务是将所有大于key的元素向后移动一位。因此条件应为arr[j] > key。循环结束后,j+1的位置就是key应该插入的位置。因此正确答案是A。

易错点分析

  1. 稳定性判断错误:最常考且最易错的是认为选择排序是稳定的。必须牢记:选择排序不稳定,冒泡和插入稳定。
  2. 代码细节混淆
    • 选择排序内循环找的是最小(升序)或最大(降序)元素的下标,而不是值。
    • 插入排序内循环的移动条件是arr[j] > key(升序),且循环结束后插入位置是j+1
    • 冒泡排序的优化标志位swapped用于提前终止。
  3. 时间复杂度记错:插入排序在最好情况(已有序)下时间复杂度是O(n),而非总是O(n²)。

6. 🔄 递推算法

代码示例与说明

#include <iostream>
using namespace std;

// 经典问题:爬楼梯,每次可以爬1阶或2阶,求到第n阶的方法数
// 递推关系:f(n) = f(n-1) + f(n-2), 其中 f(1)=1, f(2)=2
int climbStairs(int n) {
    if (n <= 2) return n; // 边界条件
    // 使用三个变量滚动计算,避免使用数组
    int prev2 = 1; // f(i-2),初始为f(1)
    int prev1 = 2; // f(i-1),初始为f(2)
    int current = 0;
    for (int i = 3; i <= n; ++i) {
        current = prev1 + prev2; // 计算 f(i)
        // 更新状态,为下一次迭代做准备
        prev2 = prev1;
        prev1 = current;
    }
    return current;
}

// 斐波那契数列:f(n) = f(n-1) + f(n-2), f(0)=0, f(1)=1
int fibonacci(int n) {
    if (n <= 1) return n;
    int a = 0, b = 1, c;
    for (int i = 2; i <= n; ++i) {
        c = a + b;
        a = b;
        b = c;
    }
    return b;
}

int main() {
    cout << "爬到第5阶有 " << climbStairs(5) << " 种方法" << endl; // 输出8
    cout << "斐波那契第6项是 " << fibonacci(6) << endl; // 输出8
    return 0;
}

关键点:确定边界条件递推关系式。使用循环和少量变量(滚动数组)迭代计算,避免递归的低效。

历年真题完整引用与解析

  • 2025年09月 第13题

    第 13 题 小杨正在爬楼梯,需要 n 阶才能到达楼顶,每次可以爬 1 阶或 2 阶,求小杨有多少种不同的方法可以爬到楼顶,横线上应填写()。

    int climbStairs(int n) {
        if (n <= 2) return n;
        int prev2 = 1;
        int prev1 = 2;
        int current = 0;
        for (int i = 3; i <= n; ++i) {
            // 在此处填入代码
        }
        return current;
    }
    

    □ A

    prev2 = prev1; prev1 = current; current = prev1 + prev2;

    □ B

    current = prev1 + prev2; prev2 = prev1; prev1 = current;

    □ C

    current = prev1 + prev2; prev1 = current; prev2 = prev1; prev1 = current; prev2 = prev1; current = prev1 + prev2;

    解析:递推关系为current = prev1 + prev2。计算完新的current后,需要更新prev2prev1为下一次迭代做准备,正确的更新顺序是:先将prev1的值赋给prev2,再将current的值赋给prev1。因此选项B current = prev1 + prev2; prev2 = prev1; prev1 = current; 是正确的。

易错点分析

  1. 状态更新顺序错误:如选项A,先更新prev2prev1,再计算current,这会导致current使用了错误的前状态值。
  2. 边界条件遗漏:未处理n <= 2的情况直接进入循环,会导致数组越界或逻辑错误。
  3. 与递归混淆:递推是正向迭代,递归是函数自调用。递推效率更高,是考题首选实现方式。

7. 📁 文件操作

代码示例与说明

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

// 方法1:使用文件流对象 (最清晰、最常用)
void method1_file_stream() {
    cout << "=== 方法1:使用文件流对象 ===" << endl;
    
    // 写入文件
    ofstream outFile("data1.txt");
    if (outFile.is_open()) {
        outFile << "Hello, GESP!" << endl;
        outFile << 100 << " " << 200 << endl;
        outFile.close();
        cout << "文件写入成功" << endl;
    } else {
        cout << "文件打开失败" << endl;
    }
    
    // 读取文件
    ifstream inFile("data1.txt");
    string line;
    int a, b;
    if (inFile.is_open()) {
        getline(inFile, line);
        cout << "第一行: " << line << endl;
        inFile >> a >> b;
        cout << "两个数: " << a << ", " << b << endl;
        inFile.close();
    } else {
        cout << "文件打开失败" << endl;
    }
    cout << endl;
}

// 方法2:使用freopen重定向标准输出
void method2_freopen() {
    cout << "=== 方法2:使用freopen重定向标准输出 ===" << endl;
    
    // 重定向cout到文件
    freopen("output.txt", "w", stdout);
    cout << "This goes to file." << endl;
    cout << "Another line." << endl;
    
    // 恢复标准输出(部分环境需要)
    fclose(stdout);
    
    // 重新打开标准输出到控制台
    freopen("CON", "w", stdout);
    cout << "Back to console" << endl;
    cout << "文件写入成功" << endl;
    cout << endl;
}

// 方法3:使用rdbuf重定向(较高级)
void method3_rdbuf() {
    cout << "=== 方法3:使用rdbuf重定向 ===" << endl;
    
    ofstream outFile("data2.txt");
    streambuf *oldBuf = cout.rdbuf(); // 保存旧缓冲区
    
    // 重定向cout到文件
    cout.rdbuf(outFile.rdbuf());
    cout << "Redirected by rdbuf" << endl;
    cout << "Another line using rdbuf" << endl;
    
    // 恢复cout
    cout.rdbuf(oldBuf);
    cout << "Back to console" << endl;
    cout << "文件写入成功" << endl;
    
    outFile.close();
    cout << endl;
}

// 方法4:文件读取的不同方式
void method4_file_reading() {
    cout << "=== 方法4:文件读取的不同方式 ===" << endl;
    
    // 准备测试文件
    ofstream outFile("test_read.txt");
    outFile << "Line 1: Hello" << endl;
    outFile << "Line 2: 123 456" << endl;
    outFile << "Line 3: Test" << endl;
    outFile.close();
    
    // 方式1:逐行读取
    ifstream inFile1("test_read.txt");
    string line;
    cout << "逐行读取:" << endl;
    while (getline(inFile1, line)) {
        cout << "" << line << endl;
    }
    inFile1.close();
    
    // 方式2:按空格分隔读取
    ifstream inFile2("test_read.txt");
    string word;
    cout << "\n按空格分隔读取:" << endl;
    while (inFile2 >> word) {
        cout << "" << word << endl;
    }
    inFile2.close();
    cout << endl;
}

int main() {
    method1_file_stream();
    method2_freopen();
    method3_rdbuf();
    method4_file_reading();
    
    cout << "所有文件操作方法演示完成!" << endl;
    return 0;
}

关键点ofstream用于写,ifstream用于读。freopen可以重定向cin/cout。核心是区分数据写入的目标是文件流对象还是标准流。

历年真题完整引用与解析

  • 2025年03月 第15题

    第 15 题 下面哪种方式不能实现将字符串 "Happy Spring!" 输出重定向到文件 log.txt()。

    □ A

    1 freopen("log.txt", "w", stdout); 2 cout << "Happy Spring!" << endl; 3 fclose(stdout);

    □ B

    std::ofstream outFile("log.txt"); outFile << "Happy Spring!" << endl; outFile.close();

    □ C

    std::ofstream outFile("log.txt"); cout << "Happy Spring!" << endl; outFile.close();

    □ D

    1 ofstream log_file("log.txt"); 2 streambuf* org_cout = cout.rdbuf(); 3 cout.rdbuf(log_file.rdbuf()); 4 cout << "Happy Spring!" << endl; 5 cout.rdbuf(org_cout);

    解析:选项C中,虽然创建了文件流对象outFile,但输出语句cout << "Happy Spring!" << endl;仍然将内容输出到了标准控制台(cout),而不是文件log.txt。因此选项C不能实现重定向到文件的功能。

易错点分析

  1. 目标混淆:定义了文件流对象(如outFile),却错误地使用cout进行输出,导致内容输出到屏幕而非文件。
  2. 文件未检查是否打开:直接对文件流进行读写操作,如果文件打开失败可能导致程序问题。良好的习惯是使用is_open()检查。
  3. 重定向后未恢复:使用freopenrdbuf重定向标准流后,在某些情况下需要恢复,否则后续的所有输出都会受到影响。

8. 🚨 异常处理

代码示例与说明

#include <iostream>
#include <stdexcept> // 包含标准异常类
using namespace std;

double safeDivide(int numerator, int denominator) {
    if (denominator == 0) {
        // 抛出异常,可以使用标准异常或自定义字符串
        throw runtime_error("除数不能为零!");
        // 也可以抛出其他类型: throw "Division by zero";
    }
    return static_cast<double>(numerator) / denominator;
}

int main() {
    int a = 10, b = 0;

    try {
        // try块包含可能抛出异常的代码
        double result = safeDivide(a, b);
        cout << "结果是: " << result << endl;
        // 如果上一句抛出异常,则try块内后面的代码不会执行
        cout << "这行不会被执行" << endl;
    }
    // catch块按顺序匹配异常类型
    catch (const runtime_error& e) { // 捕获runtime_error类型异常
        cout << "捕获到标准异常: " << e.what() << endl; // e.what()返回错误信息
    }
    catch (const char* msg) { // 捕获字符串字面量类型的异常
        cout << "捕获到字符串异常: " << msg << endl;
    }
    catch (...) { // 捕获所有其他类型的异常
        cout << "捕获到未知类型异常" << endl;
    }

    // 异常被处理后,程序继续执行
    cout << "程序继续运行..." << endl;

    // 示例:未捕获的异常
    // throw 100; // 如果取消注释,此异常未被捕获,程序会调用terminate终止
    return 0;
}

关键点try块尝试执行可能出错的代码;throw抛出异常;catch捕获并处理特定类型的异常。未被捕获的异常会导致程序终止。

历年真题完整引用与解析

  • 2025年09月 第15题

    第 15 题 关于异常处理,以下说法错误的是()。 ☐ C. throw 语句用于抛出异常 ☐ D. 所有异常都必须被捕获,否则程序会崩溃

    解析throw语句确实用于抛出异常,说法C正确。说法D“所有异常都必须被捕获”是错误的。C++中,如果异常在try块中抛出但没有匹配的catch块捕获它,该异常将传播到上一层调用者。如果在整个调用链中都未被捕获,程序将调用std::terminate()函数终止,这通常表现为程序崩溃,但“必须被捕获”不是语法强制要求,因此说法不准确。

  • 2024年12月 第15题

    第 15 题 运行下面的代码,将出现什么情况?()

    double hmean(double a, double b) {
        if (a == -b)
            throw runtime_error("Runtime error occurred");
    }
    return 2.0 * a * b / (a + b);
    
    int main() {
        double x = 10;
        double y = -10;
        try {
            int result = hmean(x, y);
            cout << "hmean: " << result << endl;
        }
        catch (const runtime_error& e) {
            cout << "Caught: " << e.what() << endl;
        }
        catch (...) {
            cout << "Caught an unknown exception." << endl;
        }
        return 0;
    }
    

    □ A. 屏幕上输出 Caught: Runtime error occurred □ B. 屏幕上输出 Caught an unknown exception □ C. 程序调用 std::terminate() □ D. 编译错误

    解析:调用hmean(10, -10)时,满足a == -b条件,函数抛出runtime_error异常。在maintry块中,此异常被后面的catch (const runtime_error& e)子句成功捕获,因此会执行该catch块中的代码,输出Caught: Runtime error occurred。程序不会崩溃、不会调用terminate,也没有编译错误。

易错点分析

  1. 对“必须捕获”的误解:认为所有可能抛出的异常都必须在代码中用catch捕获,否则无法通过编译。实际上,语法上并不强制,但未捕获的异常会导致运行时程序终止。
  2. 执行流程不清:误认为try块中抛出异常后,其后的代码仍会执行。实际上,控制流会立即跳转到匹配的catch块。
  3. catch块顺序catch (...)(捕获所有异常)应该放在所有具体类型catch块的后面,否则它会拦截所有异常,使后面的具体catch块失效。