使用GPT系统性学编程(2): Teach Yourself C++ in 21 Days By using gpt

目录

  • 第1节:开发环境的搭建与配置
  • 第2节:C++基础语法与结构
  • 第3节:循环与数组
  • 第4节:函数与作用域
  • 第5节:指针与动态内存管理
  • 第6节:面向对象编程基础
  • 第7节:继承与多态
  • 第8节:智能指针与C++内存管理
  • 第9节:模板与泛型编程
  • 第10节:异常处理与错误管理
  • 第11节:标准模板库(STL)
  • 第12节:文件输入输出与智能指针
  • 第13节:C++的标准库
  • 第14节:C++11/14/17/20新特性
  • 第15节:C++项目结构与构建系统

第1节:开发环境的搭建与配置

1.1 目标

在本节课中,学生将学习如何在Windows上搭建一个现代化的C++开发环境,并使用VSCode和CMake工具进行C++程序的开发与调试。学生将掌握以下内容:

  • 安装VSCode及C++插件
  • 安装MinGW或其他C++编译器
  • 安装并配置CMake
  • 创建并编译第一个C++项目
  • 使用VSCode调试C++程序

1.2 工具准备

  • VSCode:一个开源的跨平台文本编辑器,具有强大的插件支持和内置调试功能。
  • MinGW其他C++编译器:提供在Windows下的GCC编译工具链。
  • CMake:一个跨平台的构建工具,可以生成适用于不同平台的构建文件。
步骤1:安装VSCode
  1. 打开VSCode官方网站,下载适用于Windows的安装包。
  2. 按照提示完成安装,选择默认设置。
  3. 安装完成后,启动VSCode。
步骤2:安装C++插件
  1. 在VSCode主界面的左侧扩展图标点击,打开扩展市场。
  2. 搜索C++,找到C/C++插件(由Microsoft发布),点击安装。
  3. 安装完成后,VSCode会提示重启编辑器,按照提示操作。
步骤3:安装MinGW或其他编译器
  1. 打开MinGW官方网站,下载并安装MinGW。
  2. 安装过程中,确保选中g++编译器组件。
  3. 安装完成后,配置系统环境变量:
    • 右键“此电脑”,选择“属性” -> “高级系统设置” -> “环境变量”。
    • 在“系统变量”中,找到并编辑Path变量,添加MinGW的bin路径(例如C:\MinGW\bin)。
  4. 打开命令提示符,输入g++ --version验证安装是否成功。
步骤4:安装CMake
  1. 打开CMake官方网站,下载适用于Windows的安装包。
  2. 按照默认选项进行安装,并确保在安装过程中选中了"Add CMake to the system PATH"选项,以便CMake命令行工具能够全局访问。
  3. 安装完成后,打开命令提示符,输入cmake --version,检查是否安装成功。
步骤5:配置VSCode的C++开发环境
  1. 启动VSCode,按Ctrl+Shift+P打开命令面板,输入CMake: Quick Start,并按回车。
  2. 选择一个文件夹作为项目目录。
  3. 输入项目名称(例如HelloWorld)。
  4. 选择C++作为项目语言,继续选择最适合的编译器工具链。
  5. VSCode会自动生成一个基本的CMake项目结构,包括CMakeLists.txt文件。
步骤6:编写第一个C++程序
  1. src文件夹中,创建一个名为main.cpp的文件,输入以下代码:
    #include <iostream>
    
    int main() {
        std::cout << "Hello, World!" << std::endl;
        return 0;
    }
    
  2. 保存文件。
步骤7:编译与运行程序
  1. 在VSCode中,点击左侧活动栏中的CMake Tools图标,选择Build,CMake会生成构建文件并编译项目。
  2. 当编译完成后,点击Run运行生成的可执行文件,你将会在VSCode的终端窗口中看到输出结果:Hello, World!
步骤8:调试C++程序
  1. 点击左侧活动栏中的调试图标,点击“创建launch.json文件”并选择C++调试器。
  2. launch.json文件中,VSCode会自动生成适合C++项目的调试配置。
  3. 设置断点(在代码行号处点击)。
  4. 点击开始调试,程序将会在断点处暂停,允许你检查变量和执行流。

1.3 小结

通过本节课的学习,学生已经成功搭建了一个现代C++开发环境,并使用VSCode、CMake和MinGW编译、运行了第一个C++程序。同时,学生也初步掌握了如何在VSCode中进行调试操作。

第2节:C++基础语法与结构

2.1 目标

本节课将引导学生了解C++的基础语法,包括程序的基本结构、数据类型、变量、输入输出以及条件语句的使用。通过本节课的学习,学生将能够编写简单的C++程序,并理解C++程序的运行方式。

2.2 基本概念

2.2.1 C++程序的基本结构

C++程序的基本结构通常包含以下部分:

  1. 预处理指令:如#include <iostream>,用于引入标准库。
  2. 主函数:C++程序从main()函数开始执行。
  3. 语句:每个语句以分号结尾。

示例代码:

#include <iostream>

int main() {
    std::cout << "Welcome to C++!" << std::endl;
    return 0;
}
2.2.2 数据类型、变量与常量
  • 数据类型:C++提供了多种基本数据类型,如intfloatdoublecharbool等。
  • 变量:用来存储不同类型的数据,必须在使用前声明。
  • 常量:使用const关键字声明,其值不可更改。

示例代码:

int main() {
    int age = 20;
    float height = 1.75;
    const int year = 2024;
    std::cout << "Age: " << age << ", Height: " << height << ", Year: " << year << std::endl;
    return 0;
}
2.2.3 输入与输出
  • std::cout用于输出信息到控制台。
  • std::cin用于从控制台输入数据。

示例代码:

#include <iostream>

int main() {
    int age;
    std::cout << "Enter your age: ";
    std::cin >> age;
    std::cout << "You are " << age << " years old." << std::endl;
    return 0;
}
2.2.4 基本运算符与表达式
  • 算术运算符:+-*/%
  • 比较运算符:==!=><>=<=
  • 逻辑运算符:&&||!

示例代码:

#include <iostream>

int main() {
    int a = 5, b = 3;
    std::cout << "Sum: " << (a + b) << std::endl;
    std::cout << "Comparison (a > b): " << (a > b) << std::endl;
    return 0;
}
2.2.5 条件语句

C++中的条件语句用于根据条件的真假执行不同的代码。常用的条件语句有ifelse ifelse以及switch

  • if-else语句:根据布尔表达式的值决定执行哪段代码。
  • switch语句:用于对一个表达式的值进行多重判断。

示例代码:

#include <iostream>

int main() {
    int score;
    std::cout << "Enter your score: ";
    std::cin >> score;

    if (score >= 90) {
        std::cout << "Grade: A" << std::endl;
    } else if (score >= 80) {
        std::cout << "Grade: B" << std::endl;
    } else if (score >= 70) {
        std::cout << "Grade: C" << std::endl;
    } else {
        std::cout << "Grade: F" << std::endl;
    }

    return 0;
}

2.3 实验任务:编写一个简单的计算器

在本节的实验任务中,学生将编写一个简单的控制台计算器,用户可以输入两个整数和一个运算符,程序将计算并输出结果。

实验步骤:
  1. 创建一个新的C++文件calculator.cpp
  2. 实现以下功能:
    • 用户输入两个整数和一个运算符(+-*/)。
    • 程序根据用户输入的运算符执行相应的运算,并输出结果。
    • 如果输入无效,程序应提示用户重新输入。

示例代码:

#include <iostream>

int main() {
    int num1, num2;
    char op;

    std::cout << "Enter first number: ";
    std::cin >> num1;

    std::cout << "Enter operator (+, -, *, /): ";
    std::cin >> op;

    std::cout << "Enter second number: ";
    std::cin >> num2;

    switch (op) {
        case '+':
            std::cout << "Result: " << (num1 + num2) << std::endl;
            break;
        case '-':
            std::cout << "Result: " << (num1 - num2) << std::endl;
            break;
        case '*':
            std::cout << "Result: " << (num1 * num2) << std::endl;
            break;
        case '/':
            if (num2 != 0) {
                std::cout << "Result: " << (num1 / num2) << std::endl;
            } else {
                std::cout << "Error: Division by zero!" << std::endl;
            }
            break;
        default:
            std::cout << "Invalid operator!" << std::endl;
    }

    return 0;
}

2.4 小结

本节课通过基础的C++语法讲解,帮助学生了解了如何使用变量、运算符、输入输出以及条件语句来编写简单的C++程序。通过实验任务,学生学会了结合多个C++基础语法,开发一个简单的控制台应用程序。

第3节:循环与数组

3.1 目标

本节课将学习C++中的循环结构和数组的基本用法。学生将了解如何通过循环实现重复操作,并使用数组存储和处理多个数据。通过本节的实验任务,学生将应用这些概念实现简单的算法。

3.2 基本概念

3.2.1 循环结构

C++支持三种主要的循环结构:for循环、while循环、do-while循环。

  • for循环:通常用于已知循环次数的情况。

    语法:

    for (初始化; 条件; 更新) {
        // 循环体
    }
    

    示例代码:

    for (int i = 0; i < 5; i++) {
        std::cout << "Iteration " << i << std::endl;
    }
    
  • while循环:用于在条件为真时反复执行的场合,通常适用于条件未知或动态变化的情况。

    语法:

    while (条件) {
        // 循环体
    }
    

    示例代码:

    int i = 0;
    while (i < 5) {
        std::cout << "Iteration " << i << std::endl;
        i++;
    }
    
  • do-while循环:与while循环类似,但保证循环体至少执行一次。

    语法:

    do {
        // 循环体
    } while (条件);
    

    示例代码:

    int i = 0;
    do {
        std::cout << "Iteration " << i << std::endl;
        i++;
    } while (i < 5);
    
3.2.2 数组
  • 数组:用于存储相同类型的多个数据。数组的大小在声明时确定,并且数组的元素通过下标进行访问(从0开始)。

    语法:

    数据类型 数组名[大小];
    

    示例代码:

    int numbers[5] = {10, 20, 30, 40, 50};
    for (int i = 0; i < 5; i++) {
        std::cout << "Element at index " << i << " is " << numbers[i] << std::endl;
    }
    
3.2.3 多维数组
  • 多维数组:用于表示二维或更高维度的数据,例如矩阵。

    语法:

    数据类型 数组名[行][列];
    

    示例代码:

    int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            std::cout << matrix[i][j] << " ";
        }
        std::cout << std::endl;
    }
    

3.3 基础算法练习

3.3.1 查找
  • 线性查找:遍历数组查找特定元素,返回其下标。

    示例代码:

    int numbers[] = {10, 20, 30, 40, 50};
    int target = 30;
    int index = -1;
    for (int i = 0; i < 5; i++) {
        if (numbers[i] == target) {
            index = i;
            break;
        }
    }
    if (index != -1) {
        std::cout << "Element found at index " << index << std::endl;
    } else {
        std::cout << "Element not found" << std::endl;
    }
    
3.3.2 排序(冒泡排序)
  • 冒泡排序:一种简单的排序算法,重复遍历数组并交换相邻的元素,直到整个数组有序。

    示例代码:

    int numbers[] = {50, 30, 20, 40, 10};
    int n = 5;
    for (int i = 0; i < n-1; i++) {
        for (int j = 0; j < n-i-1; j++) {
            if (numbers[j] > numbers[j+1]) {
                // 交换
                int temp = numbers[j];
                numbers[j] = numbers[j+1];
                numbers[j+1] = temp;
            }
        }
    }
    
    std::cout << "Sorted array: ";
    for (int i = 0; i < n; i++) {
        std::cout << numbers[i] << " ";
    }
    std::cout << std::endl;
    

3.4 实验任务:学生成绩管理程序

本节实验任务是编写一个简单的学生成绩管理程序,允许用户输入多个学生的成绩,并根据成绩进行统计和排序。要求学生应用循环、数组和基础算法。

实验步骤:
  1. 创建一个新的C++文件grades.cpp
  2. 程序要求:
    • 用户输入学生的数量,然后输入每个学生的成绩。
    • 计算成绩的平均值。
    • 输出最高分和最低分。
    • 使用冒泡排序对成绩从高到低排序,并输出排序结果。

示例代码:

#include <iostream>

int main() {
    int num_students;
    std::cout << "Enter the number of students: ";
    std::cin >> num_students;

    int grades[num_students];
    std::cout << "Enter the grades:" << std::endl;
    for (int i = 0; i < num_students; i++) {
        std::cin >> grades[i];
    }

    // 计算平均值
    int sum = 0;
    for (int i = 0; i < num_students; i++) {
        sum += grades[i];
    }
    double average = static_cast<double>(sum) / num_students;
    std::cout << "Average grade: " << average << std::endl;

    // 查找最高分和最低分
    int max_grade = grades[0];
    int min_grade = grades[0];
    for (int i = 1; i < num_students; i++) {
        if (grades[i] > max_grade) {
            max_grade = grades[i];
        }
        if (grades[i] < min_grade) {
            min_grade = grades[i];
        }
    }
    std::cout << "Highest grade: " << max_grade << std::endl;
    std::cout << "Lowest grade: " << min_grade << std::endl;

    // 冒泡排序
    for (int i = 0; i < num_students-1; i++) {
        for (int j = 0; j < num_students-i-1; j++) {
            if (grades[j] < grades[j+1]) {
                int temp = grades[j];
                grades[j] = grades[j+1];
                grades[j+1] = temp;
            }
        }
    }

    // 输出排序结果
    std::cout << "Grades sorted from highest to lowest:" << std::endl;
    for (int i = 0; i < num_students; i++) {
        std::cout << grades[i] << " ";
    }
    std::cout << std::endl;

    return 0;
}

3.5 小结

本节课通过讲解循环结构和数组的使用,帮助学生掌握了处理重复操作和存储多个数据的基础技能。同时,通过基础算法的应用,学生进一步理解了如何用C++编写有效的程序。在实验任务中,学生结合所学内容实现了一个简单的成绩管理程序。

第4节:函数与作用域

4.1 目标

本节课的重点是学习如何定义和使用函数。通过函数的学习,学生将掌握如何将程序逻辑进行模块化,提高代码的可读性和复用性。此外,学生还会了解变量的作用域和生命周期。

4.2 基本概念

4.2.1 函数的定义与调用

函数是一个包含特定任务的代码块,可以通过调用来执行函数中的代码。函数的定义通常包括返回类型、函数名、参数列表和函数体。

语法:

返回类型 函数名(参数类型 参数名, ...) {
    // 函数体
    return 返回值;
}

示例代码:

#include <iostream>

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

int main() {
    int result = add(5, 3); // 函数调用
    std::cout << "Sum: " << result << std::endl;
    return 0;
}
4.2.2 函数的参数传递
  • 值传递:函数接收到的是参数的副本,对副本的修改不会影响原始变量。
  • 引用传递:通过引用传递参数,函数可以直接修改原始变量的值。

示例代码(引用传递):

#include <iostream>

// 函数定义,使用引用传递
void swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 5, y = 10;
    std::cout << "Before swap: x = " << x << ", y = " << y << std::endl;
    swap(x, y);
    std::cout << "After swap: x = " << x << ", y = " << y << std::endl;
    return 0;
}
4.2.3 函数的返回值

函数可以返回任意数据类型,也可以通过void定义不返回任何值的函数。

示例代码(返回值):

#include <iostream>

// 返回最大值的函数
int max(int a, int b) {
    return (a > b) ? a : b;
}

int main() {
    int result = max(10, 20);
    std::cout << "Max: " << result << std::endl;
    return 0;
}
4.2.4 函数重载

函数重载允许多个同名函数存在,只要它们的参数类型或数量不同即可。编译器会根据函数调用时的参数类型来确定调用哪个重载函数。

示例代码:

#include <iostream>

// 重载函数
int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {
    return a + b;
}

int main() {
    std::cout << "Int sum: " << add(3, 4) << std::endl;
    std::cout << "Double sum: " << add(3.5, 4.5) << std::endl;
    return 0;
}
4.2.5 递归函数

递归函数是指在函数的定义中调用该函数本身。递归在处理某些问题时非常有用,例如计算阶乘或解决分治问题。

示例代码(递归计算阶乘):

#include <iostream>

// 递归函数定义
int factorial(int n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

int main() {
    int result = factorial(5);
    std::cout << "Factorial of 5: " << result << std::endl;
    return 0;
}
4.2.6 变量的作用域与生命周期
  • 局部变量:声明在函数内部的变量,其作用域仅限于该函数。
  • 全局变量:声明在所有函数之外的变量,作用域为整个程序。
  • 静态变量:在函数中声明为static的变量,其生命周期在整个程序运行期间保持有效,但其作用域仍然是函数内部。

示例代码(局部与全局变量):

#include <iostream>

int globalVar = 10; // 全局变量

void showVar() {
    int localVar = 5; // 局部变量
    std::cout << "Local variable: " << localVar << std::endl;
    std::cout << "Global variable: " << globalVar << std::endl;
}

int main() {
    showVar();
    std::cout << "Global variable in main: " << globalVar << std::endl;
    return 0;
}

4.3 函数与模块化编程

函数是模块化编程的重要组成部分,它能够帮助开发者将复杂的程序拆分为多个独立的功能模块。通过合理的函数划分,代码的可读性和复用性将大大提高。

4.4 实验任务:编写一个计算几何图形面积的程序

本节的实验任务是编写一个程序,计算矩形、三角形和圆形的面积。要求学生使用函数实现每种图形的面积计算,并通过函数调用来获取不同图形的面积。

实验步骤:
  1. 创建一个新的C++文件geometry.cpp
  2. 实现以下功能:
    • 编写三个函数,分别计算矩形、三角形和圆形的面积。
    • 用户可以选择计算哪种图形的面积。
    • 根据用户输入的图形参数,调用相应的函数并输出结果。

示例代码:

#include <iostream>
#include <cmath>

// 计算矩形面积的函数
double rectangleArea(double length, double width) {
    return length * width;
}

// 计算三角形面积的函数
double triangleArea(double base, double height) {
    return 0.5 * base * height;
}

// 计算圆形面积的函数
double circleArea(double radius) {
    return M_PI * radius * radius;
}

int main() {
    int choice;
    std::cout << "Choose a shape to calculate the area:" << std::endl;
    std::cout << "1. Rectangle" << std::endl;
    std::cout << "2. Triangle" << std::endl;
    std::cout << "3. Circle" << std::endl;
    std::cin >> choice;

    switch (choice) {
        case 1: {
            double length, width;
            std::cout << "Enter length and width: ";
            std::cin >> length >> width;
            std::cout << "Rectangle area: " << rectangleArea(length, width) << std::endl;
            break;
        }
        case 2: {
            double base, height;
            std::cout << "Enter base and height: ";
            std::cin >> base >> height;
            std::cout << "Triangle area: " << triangleArea(base, height) << std::endl;
            break;
        }
        case 3: {
            double radius;
            std::cout << "Enter radius: ";
            std::cin >> radius;
            std::cout << "Circle area: " << circleArea(radius) << std::endl;
            break;
        }
        default:
            std::cout << "Invalid choice!" << std::endl;
    }

    return 0;
}

4.5 小结

本节课通过学习函数的定义、调用、参数传递、递归与重载等概念,学生能够编写结构化的C++程序。结合变量的作用域和生命周期的知识,学生可以更好地理解程序的运行过程。本节的实验任务通过计算几何图形面积,帮助学生加深了对函数的实际应用。

第5节:指针与动态内存管理

5.1 目标

本节课将深入学习C++中的指针与动态内存管理。学生将了解指针的概念及其与内存地址的关系,学习如何通过指针访问和操作内存。此外,还会介绍如何使用newdelete动态管理内存,以便在程序中灵活处理内存需求。

5.2 基本概念

5.2.1 指针的概念

指针是保存内存地址的变量。通过指针,程序可以直接访问和修改存储在该地址上的数据。

语法:

数据类型* 指针名;

示例代码:

#include <iostream>

int main() {
    int a = 10;
    int* ptr = &a; // 指针保存变量a的地址

    std::cout << "Value of a: " << a << std::endl;
    std::cout << "Address of a: " << &a << std::endl;
    std::cout << "Pointer value (address of a): " << ptr << std::endl;
    std::cout << "Dereferenced pointer (value of a): " << *ptr << std::endl;

    return 0;
}
5.2.2 指针的基本操作
  • 取地址运算符&:用于获取变量的内存地址。
  • 解引用运算符*:用于通过指针访问存储在该地址上的数据。

示例代码:

#include <iostream>

int main() {
    int x = 42;
    int* ptr = &x;  // 获取x的地址

    std::cout << "Value of x: " << x << std::endl;
    *ptr = 100;  // 通过指针修改x的值
    std::cout << "New value of x: " << x << std::endl;

    return 0;
}
5.2.3 指针与数组

在C++中,数组名本身就是一个指向数组第一个元素的指针。通过指针可以遍历数组。

示例代码:

#include <iostream>

int main() {
    int arr[3] = {10, 20, 30};
    int* ptr = arr;  // 数组名即指向第一个元素的指针

    for (int i = 0; i < 3; i++) {
        std::cout << "arr[" << i << "] = " << *(ptr + i) << std::endl;  // 通过指针访问数组元素
    }

    return 0;
}
5.2.4 动态内存分配
  • new运算符:用于动态分配内存,返回指向分配内存的指针。
  • delete运算符:用于释放动态分配的内存,防止内存泄漏。

示例代码(动态分配单个变量):

#include <iostream>

int main() {
    int* ptr = new int;  // 动态分配一个整数
    *ptr = 42;

    std::cout << "Dynamically allocated value: " << *ptr << std::endl;
    delete ptr;  // 释放动态分配的内存

    return 0;
}

示例代码(动态分配数组):

#include <iostream>

int main() {
    int* arr = new int[5];  // 动态分配一个数组

    for (int i = 0; i < 5; i++) {
        arr[i] = i * 10;
    }

    std::cout << "Dynamically allocated array:" << std::endl;
    for (int i = 0; i < 5; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;

    delete[] arr;  // 释放动态分配的数组内存

    return 0;
}
5.2.5 空指针与指针检查
  • 空指针(nullptr:用于表示指针不指向任何有效内存地址。
  • 指针检查:在使用指针之前,应确保指针不为空。

示例代码:

#include <iostream>

int main() {
    int* ptr = nullptr;  // 空指针

    if (ptr == nullptr) {
        std::cout << "Pointer is null." << std::endl;
    }

    return 0;
}
5.2.6 常见的指针错误
  • 悬空指针:指向已被释放或无效的内存。
  • 野指针:未被初始化的指针。
  • 内存泄漏:动态分配的内存未被释放。

5.3 实验任务:学生成绩的动态存储

本节的实验任务是编写一个程序,允许用户动态输入学生成绩,并存储这些成绩以便后续处理。要求学生使用指针和动态内存管理实现。

实验步骤:
  1. 创建一个新的C++文件dynamic_grades.cpp
  2. 实现以下功能:
    • 用户输入学生人数,动态分配内存以存储学生的成绩。
    • 用户输入每个学生的成绩。
    • 计算并输出成绩的平均值。
    • 释放动态分配的内存。

示例代码:

#include <iostream>

int main() {
    int num_students;
    std::cout << "Enter the number of students: ";
    std::cin >> num_students;

    // 动态分配用于存储学生成绩的内存
    int* grades = new int[num_students];

    // 输入每个学生的成绩
    std::cout << "Enter the grades:" << std::endl;
    for (int i = 0; i < num_students; i++) {
        std::cin >> grades[i];
    }

    // 计算平均成绩
    int sum = 0;
    for (int i = 0; i < num_students; i++) {
        sum += grades[i];
    }
    double average = static_cast<double>(sum) / num_students;
    std::cout << "Average grade: " << average << std::endl;

    // 释放动态分配的内存
    delete[] grades;

    return 0;
}

5.4 深入理解:指针与函数

指针可以作为函数的参数传递,用于在函数中修改实参的值或访问动态分配的内存。指针也可以用来返回动态分配的内存地址。

示例代码(指针作为参数):

#include <iostream>

// 使用指针交换两个整数的值
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 10, y = 20;
    std::cout << "Before swap: x = " << x << ", y = " << y << std::endl;

    swap(&x, &y);  // 传递变量的地址
    std::cout << "After swap: x = " << x << ", y = " << y << std::endl;

    return 0;
}

5.5 小结

本节课通过指针和动态内存管理的学习,学生能够理解内存的操作原理,并掌握动态分配和释放内存的方法。通过实验任务,学生实践了如何灵活处理数据的存储与管理。掌握指针是C++编程的重要基础,在后续的C++高级编程中,指针的概念将会更加广泛地应用。

第6节:面向对象编程基础

6.1 目标

本节课将介绍C++中的面向对象编程(OOP)概念,帮助学生理解类与对象的基本原理。通过学习类的定义、对象的创建与使用、构造函数与析构函数、成员函数与成员变量等内容,学生将掌握如何使用OOP思想来设计和实现程序。

6.2 基本概念

6.2.1 类与对象

类是面向对象编程中的核心概念,它是数据和方法的封装体。对象是类的实例,具有类中定义的属性和行为。

类的定义语法:

class 类名 {
public:
    // 公共成员变量和成员函数
    数据类型 变量名;
    返回类型 函数名(参数);
};

示例代码:

#include <iostream>

class Person {
public:
    // 成员变量
    std::string name;
    int age;

    // 成员函数
    void introduce() {
        std::cout << "Hi, my name is " << name << " and I am " << age << " years old." << std::endl;
    }
};

int main() {
    // 创建对象
    Person person;
    person.name = "Alice";
    person.age = 25;
    person.introduce();

    return 0;
}
6.2.2 构造函数与析构函数
  • 构造函数:用于在创建对象时初始化对象的状态。构造函数与类同名,没有返回类型。
  • 析构函数:用于在对象销毁时执行清理工作,析构函数的名字在类名前加~,同样没有返回类型。

示例代码:

#include <iostream>

class Person {
public:
    std::string name;
    int age;

    // 构造函数
    Person(std::string n, int a) : name(n), age(a) {
        std::cout << "Person object created!" << std::endl;
    }

    // 析构函数
    ~Person() {
        std::cout << "Person object destroyed!" << std::endl;
    }

    void introduce() {
        std::cout << "Hi, my name is " << name << " and I am " << age << " years old." << std::endl;
    }
};

int main() {
    // 使用构造函数创建对象
    Person person("Bob", 30);
    person.introduce(); // 输出介绍信息

    return 0;
}
6.2.3 访问控制(public、private、protected)
  • public:公共成员可以在类的外部访问。
  • private:私有成员只能在类的内部访问。
  • protected:受保护的成员只能在类内部或派生类中访问。

示例代码:

#include <iostream>

class Person {
private:
    std::string name;
    int age;

public:
    // 公共成员函数用于访问私有成员
    void setDetails(std::string n, int a) {
        name = n;
        age = a;
    }

    void introduce() {
        std::cout << "Hi, my name is " << name << " and I am " << age << " years old." << std::endl;
    }
};

int main() {
    Person person;
    person.setDetails("Charlie", 28);
    person.introduce();

    return 0;
}
6.2.4 成员函数与成员变量

成员函数是类内部定义的函数,用于操作成员变量。成员变量存储对象的状态,成员函数则定义对象的行为。

示例代码:

#include <iostream>

class Circle {
public:
    double radius;

    // 成员函数:计算圆的面积
    double getArea() {
        return 3.14159 * radius * radius;
    }
};

int main() {
    Circle circle;
    circle.radius = 5.0;
    std::cout << "Circle area: " << circle.getArea() << std::endl;

    return 0;
}

6.3 深入理解类与对象

6.3.1 this指针

this指针是类中隐含的指针,它指向当前对象,可以用来区分成员变量与函数参数重名的情况。

示例代码:

#include <iostream>

class Rectangle {
private:
    int width, height;

public:
    // 使用this指针来区分成员变量和参数
    void setDimensions(int width, int height) {
        this->width = width;
        this->height = height;
    }

    int getArea() {
        return width * height;
    }
};

int main() {
    Rectangle rect;
    rect.setDimensions(5, 10);
    std::cout << "Rectangle area: " << rect.getArea() << std::endl;

    return 0;
}
6.3.2 静态成员变量与静态成员函数

静态成员变量属于类,而不属于某个特定对象。所有对象共享静态成员变量。静态成员函数可以直接访问静态成员变量。

示例代码:

#include <iostream>

class Counter {
public:
    static int count;

    Counter() {
        count++;
    }

    static void showCount() {
        std::cout << "Count: " << count << std::endl;
    }
};

// 初始化静态成员变量
int Counter::count = 0;

int main() {
    Counter c1, c2, c3;
    Counter::showCount();  // 调用静态成员函数

    return 0;
}

6.4 实验任务:学生信息管理系统

本节的实验任务是设计一个简单的学生信息管理系统,使用面向对象的方式存储和管理学生的信息。要求实现以下功能:

  • 定义一个Student类,包含学生的姓名、年龄和成绩等属性。
  • 提供构造函数和成员函数用于操作学生数据。
  • 创建多个Student对象,并展示每个学生的信息。
实验步骤:
  1. 创建一个新的C++文件student_system.cpp
  2. 实现以下功能:
    • 定义Student类,包含姓名、年龄和成绩等私有成员。
    • 编写构造函数和introduce函数,用于输出学生信息。
    • main函数中创建并初始化多个学生对象,调用相关函数输出学生信息。

示例代码:

#include <iostream>

class Student {
private:
    std::string name;
    int age;
    double grade;

public:
    // 构造函数
    Student(std::string n, int a, double g) : name(n), age(a), grade(g) {}

    // 输出学生信息
    void introduce() {
        std::cout << "Student: " << name << ", Age: " << age << ", Grade: " << grade << std::endl;
    }
};

int main() {
    // 创建学生对象
    Student student1("Alice", 20, 88.5);
    Student student2("Bob", 19, 91.2);

    // 输出学生信息
    student1.introduce();
    student2.introduce();

    return 0;
}

6.5 小结

本节课学生学习了面向对象编程的基础知识,包括类的定义、对象的创建、构造函数与析构函数、成员函数与成员变量、访问控制以及静态成员等概念。通过实验任务,学生加深了对OOP思想的理解,能够运用OOP方法进行程序设计。面向对象编程是C++中的核心思想,将在后续的课程中进一步扩展。

第7节:继承与多态

7.1 目标

本节课将介绍面向对象编程中的两个重要特性:继承与多态。继承允许一个类从另一个类继承属性和方法,复用代码并扩展功能;多态则使得不同类型的对象可以通过统一的接口进行操作,从而提高代码的灵活性。学生将学习如何定义基类和派生类,以及如何使用虚函数实现多态。

7.2 基本概念

7.2.1 继承

继承是从已有的类(基类或父类)创建新类(派生类或子类)的机制。派生类继承了基类的成员变量和成员函数,可以复用基类的代码并添加新的功能。

继承语法:

class 派生类名 : 访问修饰符 基类名 {
public:
    // 派生类中的新成员变量和成员函数
};

示例代码:

#include <iostream>

class Animal {
public:
    void eat() {
        std::cout << "This animal is eating." << std::endl;
    }
};

// Dog类继承自Animal类
class Dog : public Animal {
public:
    void bark() {
        std::cout << "The dog is barking." << std::endl;
    }
};

int main() {
    Dog dog;
    dog.eat();  // 调用基类的函数
    dog.bark(); // 调用派生类的函数

    return 0;
}
7.2.2 访问修饰符在继承中的作用

继承时,可以通过访问修饰符控制基类成员在派生类中的可见性:

  • public继承:基类的public成员在派生类中保持为publicprotected成员保持为protectedprivate成员不可访问。
  • protected继承:基类的publicprotected成员在派生类中变为protectedprivate成员不可访问。
  • private继承:基类的所有非私有成员在派生类中变为private

示例代码:

#include <iostream>

class Base {
public:
    int x = 10;

protected:
    int y = 20;

private:
    int z = 30;
};

// 派生类继承自Base类
class Derived : public Base {
public:
    void show() {
        std::cout << "x = " << x << std::endl;  // 可以访问public成员
        std::cout << "y = " << y << std::endl;  // 可以访问protected成员
        // std::cout << "z = " << z << std::endl;  // 无法访问private成员
    }
};

int main() {
    Derived d;
    d.show();
    return 0;
}
7.2.3 构造函数与析构函数在继承中的调用顺序

在继承结构中,基类的构造函数在派生类的构造函数之前调用,析构函数则按相反的顺序执行,即先调用派生类的析构函数,再调用基类的析构函数。

示例代码:

#include <iostream>

class Base {
public:
    Base() {
        std::cout << "Base constructor called." << std::endl;
    }

    ~Base() {
        std::cout << "Base destructor called." << std::endl;
    }
};

class Derived : public Base {
public:
    Derived() {
        std::cout << "Derived constructor called." << std::endl;
    }

    ~Derived() {
        std::cout << "Derived destructor called." << std::endl;
    }
};

int main() {
    Derived d;
    return 0;
}

7.3 多态

7.3.1 虚函数与动态绑定

多态允许使用基类的指针或引用操作派生类对象,从而实现灵活的运行时行为。要实现多态,必须使用虚函数(virtual function)。通过将基类中的函数声明为虚函数,可以确保派生类覆盖该函数时,在运行时调用的是派生类的实现,而不是基类的实现。

示例代码:

#include <iostream>

class Animal {
public:
    // 基类中的虚函数
    virtual void sound() {
        std::cout << "This animal makes a sound." << std::endl;
    }
};

class Dog : public Animal {
public:
    // 覆盖基类的虚函数
    void sound() override {
        std::cout << "The dog barks." << std::endl;
    }
};

class Cat : public Animal {
public:
    // 覆盖基类的虚函数
    void sound() override {
        std::cout << "The cat meows." << std::endl;
    }
};

int main() {
    Animal* animal;
    Dog dog;
    Cat cat;

    // 基类指针指向派生类对象
    animal = &dog;
    animal->sound();  // 调用Dog类的sound()函数

    animal = &cat;
    animal->sound();  // 调用Cat类的sound()函数

    return 0;
}
7.3.2 纯虚函数与抽象类

如果基类中的某个虚函数没有具体实现,可以将其声明为纯虚函数,使得该类成为抽象类,不能实例化。派生类必须实现所有纯虚函数,才能创建对象。

语法:

virtual 返回类型 函数名(参数) = 0;

示例代码:

#include <iostream>

// 抽象类Shape
class Shape {
public:
    // 纯虚函数
    virtual void draw() = 0;
};

class Circle : public Shape {
public:
    // 实现纯虚函数
    void draw() override {
        std::cout << "Drawing a circle." << std::endl;
    }
};

class Square : public Shape {
public:
    // 实现纯虚函数
    void draw() override {
        std::cout << "Drawing a square." << std::endl;
    }
};

int main() {
    Shape* shape;
    Circle circle;
    Square square;

    shape = &circle;
    shape->draw();  // 调用Circle的draw()函数

    shape = &square;
    shape->draw();  // 调用Square的draw()函数

    return 0;
}

7.4 实验任务:多态的形状绘制系统

本节的实验任务是设计一个多态的形状绘制系统,要求定义一个抽象类Shape,并由CircleSquareTriangle类继承。每个派生类实现draw()函数,输出对应形状的绘制信息。要求使用多态机制,通过基类指针或引用操作派生类对象。

实验步骤:
  1. 创建一个新的C++文件shape_system.cpp
  2. 实现以下功能:
    • 定义Shape抽象类,包含纯虚函数draw()
    • CircleSquareTriangle类继承Shape并实现draw()函数。
    • main函数中,创建一个Shape指针数组,依次存放不同形状对象,通过多态调用draw()函数。

示例代码:

#include <iostream>

// 抽象类Shape
class Shape {
public:
    virtual void draw() = 0;  // 纯虚函数
};

class Circle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a circle." << std::endl;
    }
};

class Square : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a square." << std::endl;
    }
};

class Triangle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a triangle." << std::endl;
    }
};

int main() {
    // 创建形状对象的指针数组
    Shape* shapes[3];
    shapes[0] = new Circle();
    shapes[1] = new Square();
    shapes[2] = new Triangle();

    // 使用多态调用draw()函数
    for (int i = 0; i < 3; i++) {
        shapes[i]->draw();
    }

    // 释放内存
    for (int i = 0; i < 3; i++) {
        delete shapes[i];
    }

    return 0;
}

7.5 小结

本节课学生学习了继承和多态的概念与应用,继承帮助代码复用和扩展,虚函数与多态提供了灵活的运行时行为。通过实验任务,学生能够理解如何设计和实现支持多态的类层次结构,进一步掌握面向对象编程的核心思想。

第8节:智能指针与C++内存管理

8.1 目标

本节课的目标是帮助学生理解C++中的内存管理,包括动态内存分配和释放。为了更好地管理内存并避免内存泄漏,C++11引入了智能指针(smart pointers),如std::unique_ptrstd::shared_ptrstd::weak_ptr。本节将学习如何使用这些智能指针来管理动态内存,并探讨它们的适用场景。

8.2 动态内存管理

8.2.1 动态内存分配与释放

在C++中,可以使用new关键字动态分配内存,用delete关键字释放内存。动态分配的内存不会自动释放,需要手动调用delete,否则会产生内存泄漏

示例代码:

#include <iostream>

int main() {
    // 动态分配一个int变量
    int* p = new int(10);
    std::cout << "Value: " << *p << std::endl;

    // 手动释放内存
    delete p;

    return 0;
}
8.2.2 动态分配数组

动态分配数组时,使用new[]分配内存,使用delete[]释放内存。

示例代码:

#include <iostream>

int main() {
    // 动态分配一个数组
    int* arr = new int[5];
    
    for (int i = 0; i < 5; i++) {
        arr[i] = i * 10;
    }

    for (int i = 0; i < 5; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;

    // 释放数组内存
    delete[] arr;

    return 0;
}

8.3 智能指针

C++11引入了智能指针,用于自动管理动态内存,避免内存泄漏。智能指针是模板类,负责在对象不再使用时自动释放其占用的内存。

8.3.1 std::unique_ptr

std::unique_ptr是一种独占型智能指针,确保同一时刻只有一个指针指向某个对象。不能复制unique_ptr,只能通过转移所有权(使用std::move)来共享对象的控制权。

使用场景:适合对象的独占所有权场景,如RAII(资源获取即初始化)模式。

示例代码:

#include <iostream>
#include <memory>  // 包含智能指针库

class Resource {
public:
    Resource() {
        std::cout << "Resource acquired." << std::endl;
    }
    ~Resource() {
        std::cout << "Resource released." << std::endl;
    }
};

int main() {
    std::unique_ptr<Resource> res1 = std::make_unique<Resource>();  // 自动管理内存
    // std::unique_ptr<Resource> res2 = res1;  // 错误,不能复制unique_ptr

    std::unique_ptr<Resource> res2 = std::move(res1);  // 转移所有权
    if (!res1) {
        std::cout << "res1 no longer owns the resource." << std::endl;
    }

    return 0;
}
8.3.2 std::shared_ptr

std::shared_ptr是一种共享所有权的智能指针,允许多个指针同时指向同一个对象。它使用引用计数来跟踪有多少指针指向该对象,当最后一个指针被销毁时,自动释放对象的内存。

使用场景:适合多个对象共享资源的场景,如缓存或共享数据。

示例代码:

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() {
        std::cout << "Resource acquired." << std::endl;
    }
    ~Resource() {
        std::cout << "Resource released." << std::endl;
    }
};

int main() {
    std::shared_ptr<Resource> res1 = std::make_shared<Resource>();
    std::cout << "Reference count: " << res1.use_count() << std::endl;

    {
        std::shared_ptr<Resource> res2 = res1;  // 共享所有权
        std::cout << "Reference count: " << res1.use_count() << std::endl;
    }  // res2销毁,但资源不会释放,因为res1还在使用

    std::cout << "Reference count: " << res1.use_count() << std::endl;

    return 0;
}
8.3.3 std::weak_ptr

std::weak_ptr是一种弱引用,不增加对象的引用计数。它通常与std::shared_ptr配合使用,解决循环引用的问题。weak_ptr不能直接访问对象,需要先通过lock()方法转换为shared_ptr

使用场景:适合防止循环引用的场景,如观察者模式或双向关联。

示例代码:

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() {
        std::cout << "Resource acquired." << std::endl;
    }
    ~Resource() {
        std::cout << "Resource released." << std::endl;
    }
};

int main() {
    std::shared_ptr<Resource> res1 = std::make_shared<Resource>();
    std::weak_ptr<Resource> weakRes = res1;  // 创建一个弱引用

    if (auto tempRes = weakRes.lock()) {  // 检查对象是否仍然存在
        std::cout << "Resource is still alive." << std::endl;
    } else {
        std::cout << "Resource has been released." << std::endl;
    }

    return 0;
}

8.4 实验任务:内存管理与智能指针

本节的实验任务是设计一个简单的内存管理系统,使用智能指针来管理动态分配的资源。任务要求包括:

  • 使用std::unique_ptr管理独占资源。
  • 使用std::shared_ptr管理共享资源,并显示引用计数。
  • 使用std::weak_ptr避免循环引用。
实验步骤:
  1. 创建一个新的C++文件smart_pointer_demo.cpp
  2. 实现以下功能:
    • 使用std::unique_ptr创建独占资源并转移所有权。
    • 使用std::shared_ptr管理共享资源,并在不同作用域中显示引用计数。
    • 使用std::weak_ptr防止循环引用。

示例代码:

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() {
        std::cout << "Resource acquired." << std::endl;
    }
    ~Resource() {
        std::cout << "Resource released." << std::endl;
    }
};

void uniquePointerDemo() {
    std::unique_ptr<Resource> res1 = std::make_unique<Resource>();
    std::unique_ptr<Resource> res2 = std::move(res1);
    if (!res1) {
        std::cout << "Unique pointer transferred ownership." << std::endl;
    }
}

void sharedPointerDemo() {
    std::shared_ptr<Resource> res1 = std::make_shared<Resource>();
    std::cout << "Initial reference count: " << res1.use_count() << std::endl;

    {
        std::shared_ptr<Resource> res2 = res1;
        std::cout << "Reference count in scope: " << res1.use_count() << std::endl;
    }

    std::cout << "Reference count after scope: " << res1.use_count() << std::endl;
}

void weakPointerDemo() {
    std::shared_ptr<Resource> res1 = std::make_shared<Resource>();
    std::weak_ptr<Resource> weakRes = res1;

    if (auto tempRes = weakRes.lock()) {
        std::cout << "Resource is still alive." << std::endl;
    } else {
        std::cout << "Resource has been released." << std::endl;
    }
}

int main() {
    std::cout << "Unique Pointer Demo:" << std::endl;
    uniquePointerDemo();

    std::cout << "\nShared Pointer Demo:" << std::endl;
    sharedPointerDemo();

    std::cout << "\nWeak Pointer Demo:" << std::endl;
    weakPointerDemo();

    return 0;
}

8.5 小结

本节课学生学习了C++中的内存管理,理解了动态内存分配和释放的原理,并掌握了如何使用C++11引入的智能指针来避免内存泄漏。通过智能指针的自动管理功能,学生能够编写出更健壮、更安全的代码。智能指针是现代C++中非常重要的工具,它们提高了代码的可维护性并减少了内存管理的复杂性。在后续课程中,智能指针将继续用于更复杂的项目开发。

第9节:模板与泛型编程

9.1 目标

本节课的目标是帮助学生理解C++模板的基本概念以及如何进行泛型编程。模板是C++的强大特性之一,能够让代码具有更高的复用性和灵活性。通过本节的学习,学生将学会编写函数模板、类模板,并了解模板特化和变参模板等高级用法。

9.2 函数模板

9.2.1 什么是函数模板?

函数模板是一种使函数能够处理不同数据类型的工具。通过使用模板,函数可以在编译时根据传入的参数类型自动生成对应的函数代码,避免为每种类型重复编写相同的逻辑。

语法:

template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

示例代码:

#include <iostream>

template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    std::cout << "Max of 3 and 7: " << max(3, 7) << std::endl;
    std::cout << "Max of 5.6 and 3.2: " << max(5.6, 3.2) << std::endl;
    std::cout << "Max of 'a' and 'z': " << max('a', 'z') << std::endl;
    
    return 0;
}
9.2.2 使用多个模板参数

函数模板可以使用多个模板参数,使函数能够处理多种类型的组合。

示例代码:

#include <iostream>

template<typename T1, typename T2>
auto add(T1 a, T2 b) {
    return a + b;
}

int main() {
    std::cout << "Addition of 5 and 3.2: " << add(5, 3.2) << std::endl;
    
    return 0;
}

9.3 类模板

9.3.1 什么是类模板?

类模板允许定义模板类,从而创建可以处理不同数据类型的类。类模板在C++标准库(如std::vectorstd::map等容器)中大量使用。

语法:

template<typename T>
class Box {
    T value;
public:
    Box(T val) : value(val) {}
    T getValue() const { return value; }
};

示例代码:

#include <iostream>

template<typename T>
class Box {
    T value;
public:
    Box(T val) : value(val) {}
    T getValue() const { return value; }
};

int main() {
    Box<int> intBox(123);
    Box<std::string> strBox("Hello, World!");

    std::cout << "Integer Box: " << intBox.getValue() << std::endl;
    std::cout << "String Box: " << strBox.getValue() << std::endl;

    return 0;
}
9.3.2 类模板的部分特化

类模板的部分特化允许为某些特定类型或条件提供不同的实现。这在需要针对某些特殊类型优化或改变行为时非常有用。

示例代码:

#include <iostream>

template<typename T>
class Box {
    T value;
public:
    Box(T val) : value(val) {}
    T getValue() const { return value; }
};

// 对指针类型进行部分特化
template<typename T>
class Box<T*> {
    T* value;
public:
    Box(T* val) : value(val) {}
    T getValue() const { return *value; }
};

int main() {
    int num = 42;
    Box<int*> intPointerBox(&num);
    
    std::cout << "Pointer Box: " << intPointerBox.getValue() << std::endl;

    return 0;
}

9.4 变参模板

9.4.1 什么是变参模板?

变参模板(Variadic Templates)允许模板接收任意数量的参数。这使得编写更通用和灵活的模板成为可能,特别是在处理不定参数的情况下非常有用。

示例代码:

#include <iostream>

// 基础模板
template<typename T>
void print(T value) {
    std::cout << value << std::endl;
}

// 递归展开模板参数
template<typename T, typename... Args>
void print(T value, Args... args) {
    std::cout << value << ", ";
    print(args...);
}

int main() {
    print(1, 2.5, "Hello", 'A');
    return 0;
}
9.4.2 结合变参模板和折叠表达式

C++17引入了折叠表达式(Fold Expression),使得对变参模板的参数执行操作变得更加简单。

示例代码:

#include <iostream>

// 使用折叠表达式计算参数之和
template<typename... Args>
auto sum(Args... args) {
    return (args + ...);
}

int main() {
    std::cout << "Sum: " << sum(1, 2, 3, 4, 5) << std::endl;
    return 0;
}

9.5 实验任务:泛型编程

任务要求
  1. 编写一个函数模板compare,可以比较两个不同类型的值。
  2. 编写一个类模板Pair,能够存储一对不同类型的值,并实现方法first()second()分别返回第一个和第二个值。
  3. 使用变参模板编写一个printAll函数,能够接受任意数量的参数并逐个打印。
实验步骤
  1. 创建一个新的C++文件template_demo.cpp
  2. 实现函数模板compare,类模板Pair,以及变参模板函数printAll
  3. 运行程序并测试这些模板函数。

示例代码:

#include <iostream>

// 1. 函数模板 compare
template<typename T1, typename T2>
bool compare(T1 a, T2 b) {
    return a == b;
}

// 2. 类模板 Pair
template<typename T1, typename T2>
class Pair {
    T1 firstVal;
    T2 secondVal;
public:
    Pair(T1 first, T2 second) : firstVal(first), secondVal(second) {}
    T1 first() const { return firstVal; }
    T2 second() const { return secondVal; }
};

// 3. 变参模板 printAll
template<typename... Args>
void printAll(Args... args) {
    (std::cout << ... << (args << " ")) << std::endl;
}

int main() {
    // 测试 compare 函数模板
    std::cout << "Compare 10 and 10.0: " << compare(10, 10.0) << std::endl;

    // 测试类模板 Pair
    Pair<int, std::string> p(1, "Hello");
    std::cout << "Pair: " << p.first() << ", " << p.second() << std::endl;

    // 测试变参模板 printAll
    printAll(1, 2.5, "Hello", 'A');

    return 0;
}

9.6 小结

本节课学生学习了C++模板的基本概念以及如何通过泛型编程实现代码的复用和灵活性。模板使得编写通用的函数和类成为可能,而变参模板则进一步增强了模板的灵活性。在现代C++开发中,模板广泛应用于标准库和自定义容器、算法的设计。本节为后续的高级编程奠定了重要基础。

第10节:异常处理与错误管理

10.1 目标

本节课的目标是帮助学生理解C++中的异常处理机制。通过学习如何捕获、抛出和处理异常,学生将掌握在程序中有效管理错误和异常情况的技能,从而编写更健壮和可维护的代码。

10.2 异常的基本概念

10.2.1 什么是异常?

异常是程序在运行过程中发生的意外情况,通常指的是错误或异常行为,如文件未找到、内存不足等。C++通过异常处理机制提供了一种优雅的方式来捕获和处理这些异常,而不必使用复杂的错误码和条件判断。

10.2.2 异常处理的三种关键字

C++中用于异常处理的关键字主要有:

  • try:定义一个异常处理的代码块。
  • catch:用于捕获异常并处理它。
  • throw:用于抛出异常。

10.3 使用异常处理

10.3.1 抛出异常

使用throw关键字抛出异常时,可以抛出任何类型的对象(包括基本数据类型和自定义类型)。

示例代码:

#include <iostream>
#include <stdexcept>  // 用于 std::runtime_error

void checkValue(int value) {
    if (value < 0) {
        throw std::runtime_error("Negative value not allowed.");
    }
}

int main() {
    try {
        checkValue(-5);
    } catch (const std::runtime_error& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }

    return 0;
}
10.3.2 捕获异常

可以捕获特定类型的异常,如果未捕获到特定类型的异常,则会向上抛出,直到找到合适的处理程序或终止程序。

示例代码:

#include <iostream>

void division(int a, int b) {
    if (b == 0) {
        throw std::runtime_error("Division by zero.");
    }
    std::cout << "Result: " << a / b << std::endl;
}

int main() {
    try {
        division(10, 0);
    } catch (const std::runtime_error& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }

    return 0;
}

10.4 自定义异常类

10.4.1 创建自定义异常类

学生可以通过继承标准异常类(如std::exception)来创建自己的异常类,以便在特定情况下抛出和捕获。

示例代码:

#include <iostream>
#include <exception>  // 用于 std::exception

class CustomException : public std::exception {
public:
    const char* what() const noexcept override {
        return "Custom exception occurred.";
    }
};

void throwCustomException() {
    throw CustomException();
}

int main() {
    try {
        throwCustomException();
    } catch (const CustomException& e) {
        std::cout << "Caught: " << e.what() << std::endl;
    }

    return 0;
}

10.5 资源管理与异常安全

10.5.1 RAII(资源获取即初始化)

RAII是一种确保资源(如内存、文件句柄等)被正确释放的技术。通过将资源的管理绑定到对象的生命周期,RAII可以帮助避免内存泄漏和资源泄露。当一个RAII对象超出作用域时,其析构函数会被调用,从而自动释放资源。这种机制在异常发生时尤为重要,因为它确保即使在抛出异常的情况下,资源也会被正确释放。

示例代码:

#include <iostream>
#include <memory>
#include <fstream>
#include <stdexcept>

class FileHandler {
    std::ifstream file;

public:
    FileHandler(const std::string& filename) {
        file.open(filename);
        if (!file.is_open()) {
            throw std::runtime_error("Could not open file.");
        }
    }

    ~FileHandler() {
        if (file.is_open()) {
            file.close();
            std::cout << "File closed." << std::endl;
        }
    }

    void readData() {
        std::string line;
        while (std::getline(file, line)) {
            std::cout << line << std::endl;
        }
    }
};

int main() {
    try {
        FileHandler fh("example.txt"); // 假设example.txt不存在
        fh.readData();
    } catch (const std::runtime_error& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }

    // 此时FileHandler的析构函数会被调用,文件会被关闭
    return 0;
}

在上面的代码中,即使FileHandler对象在构造过程中抛出异常,程序也不会泄漏文件资源。通过RAII机制,FileHandler的析构函数确保在异常发生时文件句柄被安全关闭。

10.5.2 确保异常安全

编写异常安全的代码确保在发生异常时,程序不会处于不一致的状态。常见策略包括使用智能指针、局部对象和条件保证(如强保证和基本保证)。

示例代码:

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() {
        std::cout << "Resource acquired." << std::endl;
    }

    ~Resource() {
        std::cout << "Resource released." << std::endl;
    }
};

void functionWithResource() {
    Resource res;  // 在此处分配资源
    throw std::runtime_error("An error occurred.");  // 抛出异常
}

int main() {
    try {
        functionWithResource();
    } catch (const std::exception& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }

    // 在这里,Resource的析构函数将会被调用
    return 0;
}

在这个示例中,即使functionWithResource抛出异常,Resource对象的析构函数仍会被调用,从而确保资源被正确释放。

10.6 实验任务:异常处理

任务要求
  1. 编写一个函数safeDivide,实现安全的除法运算,确保不会抛出除零异常。
  2. 创建一个自定义异常类NegativeNumberException,用于表示负数异常,并在检测到负数时抛出。
  3. 在主函数中捕获和处理这些异常,确保程序能够正常运行。
实验步骤
  1. 创建一个新的C++文件exception_demo.cpp
  2. 实现safeDivide函数,NegativeNumberException类,以及在主函数中捕获异常的逻辑。
  3. 测试不同的输入值,确保异常处理正常工作。

示例代码:

#include <iostream>
#include <stdexcept>

class NegativeNumberException : public std::runtime_error {
public:
    NegativeNumberException() : std::runtime_error("Negative number is not allowed.") {}
};

double safeDivide(int a, int b) {
    if (b == 0) {
        throw std::runtime_error("Division by zero.");
    }
    return static_cast<double>(a) / b;
}

void checkNumber(int value) {
    if (value < 0) {
        throw NegativeNumberException();
    }
}

int main() {
    try {
        int a = 10, b = 0;
        std::cout << "Division result: " << safeDivide(a, b) << std::endl;

        checkNumber(-5);
    } catch (const std::runtime_error& e) {
        std::cout << "Caught runtime error: " << e.what() << std::endl;
    } catch (const NegativeNumberException& e) {
        std::cout << "Caught negative number exception: " << e.what() << std::endl;
    }

    return 0;
}

10.7 小结

本节课学生学习了C++中的异常处理机制,包括抛出、捕获和处理异常的方法。通过自定义异常类和理解RAII原则,学生能够编写更加健壮和可靠的代码,处理运行时错误和特殊情况。在现代C++开发中,良好的异常处理是确保代码质量和可维护性的关键部分。RAII的使用尤其重要,因为它确保在异常发生时资源能够得到妥善管理。

第11节:标准模板库(STL)

11.1 目标

本节课的目标是帮助学生理解C++标准模板库(STL)的基本组成部分。通过学习STL的容器、算法和迭代器,学生将能够高效地管理和操作数据,从而提高编程效率。

11.2 STL的基本概念

11.2.1 什么是STL?

标准模板库(STL)是C++的一部分,它提供了一组通用的类和函数模板,用于处理常见的数据结构和算法。STL的设计旨在提高程序的可重用性和灵活性,使开发者可以更快速地实现复杂的功能。

11.2.2 STL的组成部分

STL主要由以下几个部分组成:

  • 容器:用于存储数据的类,如vectorlistmap等。
  • 算法:用于对容器中的数据进行操作的函数,如排序、查找、复制等。
  • 迭代器:提供统一的方法访问容器中元素的对象。

11.3 STL容器

11.3.1 向量(vector

vector是一个动态数组,能够自动扩展。它支持随机访问,适合存储需要频繁访问和修改的元素。

示例代码:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    // 添加元素
    vec.push_back(6);
    vec.push_back(7);

    // 访问元素
    for (int i = 0; i < vec.size(); ++i) {
        std::cout << vec[i] << " ";
    }
    std::cout << std::endl;

    // 删除元素
    vec.pop_back(); // 删除最后一个元素

    return 0;
}
11.3.2 列表(list

list是一个双向链表,支持快速插入和删除操作,但不支持随机访问。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> lst = {1, 2, 3, 4, 5};

    // 添加元素
    lst.push_back(6);
    lst.push_front(0);

    // 遍历元素
    for (const int& val : lst) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    // 删除元素
    lst.remove(3); // 删除值为3的元素

    return 0;
}
11.3.3 映射(map

map是一个关联容器,用于存储键值对。它基于红黑树实现,具有自动排序的特性。

示例代码:

#include <iostream>
#include <map>

int main() {
    std::map<std::string, int> ageMap;

    // 添加元素
    ageMap["Alice"] = 30;
    ageMap["Bob"] = 25;

    // 访问元素
    for (const auto& pair : ageMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    // 查找元素
    std::string name = "Alice";
    if (ageMap.find(name) != ageMap.end()) {
        std::cout << name << "'s age is: " << ageMap[name] << std::endl;
    }

    return 0;
}
11.3.4 集合(set

set是一个不重复的有序容器,用于存储唯一元素。

示例代码:

#include <iostream>
#include <set>

int main() {
    std::set<int> s = {1, 2, 3, 4, 5};

    // 添加元素
    s.insert(6);
    s.insert(3); // 重复的元素不会添加

    // 遍历元素
    for (const int& val : s) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    // 查找元素
    if (s.find(3) != s.end()) {
        std::cout << "3 is found in the set." << std::endl;
    }

    return 0;
}

11.4 STL算法

11.4.1 常用算法概述

STL提供了一系列通用算法,可以直接作用于容器。常用的算法包括但不限于以下几种:

  • 排序(sort:对容器中的元素进行排序。
  • 查找(find:查找容器中指定元素。
  • 复制(copy:复制容器中的元素。
  • 合并(merge:将两个已排序的容器合并为一个新的已排序容器。
  • 计数(count:计算容器中指定元素的出现次数。
  • 反转(reverse:反转容器中的元素顺序。
11.4.2 排序算法

使用std::sort算法对容器进行排序。

示例代码:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {5, 3, 4, 1, 2};

    // 排序
    std::sort(vec.begin(), vec.end());

    std::cout << "Sorted vector: ";
    for (const int& val : vec) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    return 0;
}
11.4.3 查找算法

使用std::find算法查找容器中的元素。

示例代码:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    // 查找元素
    auto it = std::find(vec.begin(), vec.end(), 3);
    if (it != vec.end()) {
        std::cout << "Found 3 in the vector." << std::endl;
    } else {
        std::cout << "3 not found." << std::endl;
    }

    return 0;
}
11.4.4 复制算法

使用std::copy将容器中的元素复制到另一个容器中。

示例代码:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> src = {1, 2, 3, 4, 5};
    std::vector<int> dest(5); // 创建目标容器

    // 复制元素
    std::copy(src.begin(), src.end(), dest.begin());

    std::cout << "Copied elements: ";
    for (const int& val : dest) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    return 0;
}
11.4.5 计数算法

使用std::count计算容器中指定元素的出现次数。

示例代码:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 2, 2, 3, 4, 2};

    // 计数元素
    int count = std::count(vec.begin(), vec.end(), 2);
    std::cout << "Number of 2s in the vector: " << count << std::endl;

    return 0;
}

11.5 STL迭代器

11.5.1 迭代器的作用

迭代器是用来访问容器中元素的对象,提供了一种统一的方式来遍历各种容器。迭代器可以提高代码的可读性和可维护性。

11.5.2 常用迭代器类型
  • 输入迭代器:用于读取容器中的数据。
  • 输出迭代器:用于写入数据到容器。
  • 双向迭代器:支持双向遍历(如list)。
  • 随机访问迭代器:支持随机访问(如vector)。

示例代码:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    // 使用迭代器遍历
    for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}

11.6 实验任务:使用STL

任务要求
  1. 创建一个vector,添加一些整数元素,并对其进行排序。

  2. 使用list存储一些字符串,演示如何添加和删除元素,并遍历打印所有字符串。

  3. 创建一个map,存储学生姓名及其对应的成绩,要求能够查找某个学生的成绩。

  4. 使用set存储一些整数,演示如何添加元素、删除元素以及查找元素。

  5. 使用STL算法完成以下任务:

    • 计算一个vector中某个特定整数的出现次数。
    • 将一个vector中的元素复制到另一个vector中,并打印结果。
    • 将两个已排序的vector合并为一个新的已排序vector

11.7 总结

通过本节学习,学生将掌握标准模板库(STL)的基本使用方法,了解容器、算法和迭代器之间的关系。掌握这些知识后,学生能够更高效地编写C++程序,利用STL简化数据结构的实现和操作。

附录:常用STL算法总结

算法 功能
sort 对容器进行排序
find 查找容器中的元素
copy 复制容器中的元素
merge 合并两个已排序的容器
count 计算元素出现的次数
reverse 反转容器中的元素顺序
accumulate 计算容器中元素的累加和
for_each 对容器中的每个元素执行指定操作
transform 对容器中的元素进行转换
unique 删除相邻重复的元素

第12节:文件输入输出与智能指针

12.1 目标

本节的目标是帮助学生理解C++中的文件输入输出操作,掌握如何使用智能指针来自动管理文件资源,以提高代码的安全性和可维护性。

12.2 C++中的文件流概述

  • 文件流:用于读写文件的对象,C++提供了std::ifstream用于文件读取,std::ofstream用于文件写入,std::fstream用于同时读写。
  • 智能指针:C++11引入的智能指针(如std::unique_ptrstd::shared_ptr)可以自动管理动态分配的内存,防止内存泄漏。

12.3 文件的基本读写操作

12.3.1 写入文件

使用std::ofstream进行文件写入操作。

示例代码:

#include <iostream>
#include <fstream>

int main() {
    std::ofstream outFile("output.txt");
    if (outFile.is_open()) {
        outFile << "Hello, File!" << std::endl;
        outFile.close();
        std::cout << "Data written to output.txt" << std::endl;
    } else {
        std::cerr << "Failed to open file for writing." << std::endl;
    }
    return 0;
}
12.3.2 读取文件

使用std::ifstream进行文件读取操作。

示例代码:

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream inFile("output.txt");
    if (inFile.is_open()) {
        std::string line;
        while (std::getline(inFile, line)) {
            std::cout << line << std::endl;
        }
        inFile.close();
    } else {
        std::cerr << "Failed to open file for reading." << std::endl;
    }
    return 0;
}

12.4 使用智能指针管理文件流

通过智能指针管理文件流资源,确保文件流在作用域结束时自动释放。

12.4.1 使用 std::unique_ptr 管理文件流

为了确保文件流在智能指针超出作用域时被正确关闭,我们可以传入一个自定义删除器。

示例代码:

#include <iostream>
#include <fstream>
#include <memory>
#include <string>

int main() {
    // 使用智能指针管理文件流
    std::unique_ptr<std::ifstream, decltype(&std::ifstream::close)> inFile(
        new std::ifstream("output.txt"), &std::ifstream::close);
    
    if (inFile->is_open()) {
        std::string line;
        while (std::getline(*inFile, line)) {
            std::cout << line << std::endl;
        }
        // 无需手动关闭文件,智能指针会自动释放
    } else {
        std::cerr << "Failed to open file for reading." << std::endl;
    }

    return 0;
}
12.4.2 使用 std::shared_ptr 管理文件流

同样地,对于 std::shared_ptr,我们也可以使用自定义删除器来确保文件流被正确关闭。

示例代码:

#include <iostream>
#include <fstream>
#include <memory>
#include <string>

int main() {
    // 使用共享指针管理文件流
    std::shared_ptr<std::ofstream> outFile(
        new std::ofstream("output.txt"), [](std::ofstream* ptr) {
            ptr->close(); // 自定义删除器,关闭文件
            delete ptr; // 删除指针
        });
    
    if (outFile->is_open()) {
        *outFile << "Hello, Smart Pointer!" << std::endl;
        // 文件将在指针超出作用域后自动关闭
    } else {
        std::cerr << "Failed to open file for writing." << std::endl;
    }

    return 0;
}

12.5 文件状态和错误处理

在进行文件操作时,需要检查文件的状态以确保操作成功。智能指针的使用简化了资源管理,但依然需要检查文件是否成功打开。

示例代码:

#include <iostream>
#include <fstream>
#include <memory>

int main() {
    // 使用智能指针管理文件流
    std::unique_ptr<std::ifstream, decltype(&std::ifstream::close)> inFile(
        new std::ifstream("nonexistent.txt"), &std::ifstream::close);
    
    if (!inFile->is_open()) {
        std::cerr << "Error: File could not be opened." << std::endl;
    }

    return 0;
}

12.6 总结

在本节中,学生学习了如何在C++中进行文件输入输出操作,掌握了使用标准文件流进行文件读写的方法。同时,学生也了解了如何利用智能指针自动管理文件流的生命周期,从而提高了代码的安全性和可维护性。

课堂练习

  • 请同学们编写一个程序,使用智能指针从控制台读取用户输入的数据并将其写入文件,然后再从文件中读取并打印出来。
  • 讨论在文件操作中使用智能指针的优势,分享个人在项目中的使用经验。

第13节:C++的标准库

13.1 目标

本节的目标是让学生了解C++标准库的基本组件,学习如何使用这些库函数来简化程序的开发,提高代码的可读性和可维护性。

13.2 C++标准库概述

  • 什么是标准库:C++标准库是由C++语言标准定义的一组函数和类,提供了常用的工具和功能,简化开发过程。
  • 标准库的组成:主要包括标准输入输出库、字符串处理库、算法库、容器库等。

13.3 常用的标准库组件

13.3.1 输入输出库
  • <iostream>:用于输入输出操作的标准库。
  • std::cinstd::cout:标准输入和输出流。

示例代码:

#include <iostream>

int main() {
    int number;
    std::cout << "请输入一个数字: ";
    std::cin >> number;
    std::cout << "你输入的数字是: " << number << std::endl;
    return 0;
}
13.3.2 字符串处理库
  • <string>:提供了字符串类和字符串操作的功能。
  • 常用方法:length()substr()find()等。

示例代码:

#include <iostream>
#include <string>

int main() {
    std::string str = "Hello, C++!";
    std::cout << "字符串长度: " << str.length() << std::endl;
    std::cout << "子字符串: " << str.substr(7, 3) << std::endl; // 输出 "C++"
    return 0;
}
13.3.3 数学库
  • <cmath>:提供了常用的数学函数,如三角函数、对数、幂运算等。
  • 常用函数:std::sqrt()std::pow()std::sin()等。

示例代码:

#include <iostream>
#include <cmath>

int main() {
    double value = 16.0;
    std::cout << "平方根: " << std::sqrt(value) << std::endl; // 输出 4
    std::cout << "16的立方: " << std::pow(value, 3) << std::endl; // 输出 4096
    return 0;
}

13.4 时间和日期处理

  • <chrono>:提供了处理时间和日期的工具。
  • 常用功能:测量时间间隔、处理时间点等。

示例代码:

#include <iostream>
#include <chrono>
#include <thread>

int main() {
    auto start = std::chrono::high_resolution_clock::now();
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 暂停2秒
    auto end = std::chrono::high_resolution_clock::now();

    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    std::cout << "暂停的时间: " << duration.count() << "毫秒" << std::endl;
    return 0;
}

13.5 文件输入输出

  • <fstream>:用于文件读写操作,提供std::ifstreamstd::ofstream

示例代码:

#include <iostream>
#include <fstream>

int main() {
    std::ofstream outFile("example.txt");
    outFile << "Hello, Standard Library!" << std::endl;
    outFile.close();

    std::ifstream inFile("example.txt");
    std::string line;
    while (std::getline(inFile, line)) {
        std::cout << line << std::endl;
    }
    inFile.close();

    return 0;
}

13.6 小结

本节介绍了C++标准库的基本组成和常用组件,涵盖了输入输出、字符串处理、数学运算、时间处理及文件操作等方面。学生应通过实践,熟悉标准库的使用,以便在今后的编程中提高效率。

课堂练习

  • 编写一个程序,使用标准库生成并输出1到100之间所有偶数的平方和。
  • 使用<chrono>库编写一个简单的计时器,测量一段代码的执行时间。

第14节:C++11/14/17/20新特性

14.1 目标

本节的目标是让学生了解C++11、C++14、C++17和C++20引入的新特性,学习如何使用这些新特性来提高代码的可读性、简洁性和效率。

14.2 C++11新特性

14.2.1 Lambda 表达式
  • Lambda 表达式提供了一种在函数内定义匿名函数的方式,使得代码更加简洁。

示例代码:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::for_each(numbers.begin(), numbers.end(), [](int n) {
        std::cout << n * n << " "; // 输出每个数的平方
    });
    return 0;
}
14.2.2 自动类型推导
  • 使用auto关键字可以自动推导变量的类型,简化代码。

示例代码:

#include <iostream>
#include <vector>

int main() {
    auto x = 10;            // 整数类型
    auto y = 3.14;         // 浮点类型
    auto numbers = std::vector<int>{1, 2, 3, 4, 5};

    for (auto num : numbers) {
        std::cout << num << " "; // 输出数字
    }
    return 0;
}
14.2.3 范围 for 循环
  • 范围 for 循环提供了一种更简洁的遍历容器的方法。

示例代码:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    for (const auto& value : vec) {
        std::cout << value << " "; // 输出数字
    }
    return 0;
}
14.2.4 nullptr
  • nullptr是一个类型安全的空指针常量,替代传统的NULL

示例代码:

#include <iostream>

int main() {
    int* ptr = nullptr; // 初始化为空指针
    if (ptr == nullptr) {
        std::cout << "指针为空!" << std::endl;
    }
    return 0;
}

14.3 C++14新特性

14.3.1 二进制字面值
  • C++14支持以二进制形式表示整数。

示例代码:

#include <iostream>

int main() {
    int binValue = 0b1010; // 二进制10
    std::cout << "二进制值: " << binValue << std::endl; // 输出10
    return 0;
}
14.3.2 通用属性
  • C++14引入了通用属性,允许开发者为任何类型定义属性。

示例代码:

#include <iostream>

[[deprecated("此函数已弃用")]]
void oldFunction() {
    std::cout << "旧函数" << std::endl;
}

int main() {
    oldFunction(); // 调用被弃用的函数
    return 0;
}

14.4 C++17新特性

14.4.1 结构化绑定
  • 结构化绑定允许通过解构的方式绑定元组或结构体的成员。

示例代码:

#include <iostream>
#include <tuple>

std::tuple<int, double, char> getTuple() {
    return {1, 3.14, 'a'};
}

int main() {
    auto [i, d, c] = getTuple();
    std::cout << "整数: " << i << ", 浮点数: " << d << ", 字符: " << c << std::endl;
    return 0;
}
14.4.2 std::optional
  • std::optional用于表示可能没有值的对象,避免使用指针表示缺失值。

示例代码:

#include <iostream>
#include <optional>

std::optional<int> findValue(bool found) {
    if (found) {
        return 42; // 找到值
    }
    return std::nullopt; // 没有值
}

int main() {
    auto value = findValue(true);
    if (value) {
        std::cout << "找到的值: " << *value << std::endl;
    } else {
        std::cout << "未找到值" << std::endl;
    }
    return 0;
}

14.5 C++20新特性

14.5.1 概念(Concepts)
  • 概念提供了一种声明类型要求的方式,用于限制模板类型参数的类型。

示例代码:

#include <iostream>
#include <concepts>

template <typename T>
concept Integral = std::is_integral_v<T>;

template <Integral T>
T add(T a, T b) {
    return a + b;
}

int main() {
    std::cout << "结果: " << add(5, 10) << std::endl; // 正确
    // std::cout << add(5.5, 10.0); // 错误,编译失败
    return 0;
}
14.5.2 范围库(Ranges)
  • C++20引入范围库,提供了一种更直观的方式来处理集合数据。

示例代码:

#include <iostream>
#include <vector>
#include <ranges>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    auto squares = numbers | std::views::transform([](int n) { return n * n; });

    for (int square : squares) {
        std::cout << square << " "; // 输出每个数的平方
    }
    return 0;
}
14.5.3 协程(Coroutines)
  • C++20支持协程,使得编写异步代码更加简单直观。

示例代码:

#include <iostream>
#include <coroutine>

struct Generator {
    struct promise_type {
        int value;
        auto get_return_object() { return Generator{}; }
        auto initial_suspend() { return std::suspend_always{}; }
        auto final_suspend() noexcept { return std::suspend_always{}; }
        void unhandled_exception() { std::terminate(); }
        auto yield_value(int v) {
            value = v;
            return std::suspend_always{};
        }
    };

    using handle_t = std::coroutine_handle<promise_type>;

    handle_t coroutine;

    Generator(handle_t h) : coroutine(h) {}
    ~Generator() { coroutine.destroy(); }

    bool move_next() {
        coroutine.resume();
        return !coroutine.done();
    }

    int current_value() { return coroutine.promise().value; }
};

Generator count() {
    for (int i = 1; i <= 5; ++i) {
        co_yield i; // 生成值
    }
}

int main() {
    auto g = count();
    while (g.move_next()) {
        std::cout << g.current_value() << " "; // 输出1到5
    }
    return 0;
}

14.6 小结

本节介绍了C++11、C++14、C++17和C++20的主要新特性,包括Lambda表达式、自动类型推导、范围 for 循环、nullptr、二进制字面值、结构化绑定、std::optional、概念、范围库和协程等。通过掌握这些新特性,学生可以编写更简洁、可读性更高的代码。

课堂练习

  • 使用概念定义一个简单的模板函数,只接受整数类型并进行加法操作。
  • 使用范围库遍历一个整数向量,输出每个元素的平方。
  • 编写一个简单的协程,生成斐波那契数列的前N个数字。

第15节:C++项目结构与构建系统

15.1 目标

本节的目标是让学生了解C++项目的基本结构和组织方法,学习如何使用CMake作为构建系统,创建和管理C++项目。

15.2 C++项目结构

15.2.1 项目目录结构

一个良好的项目结构有助于代码的可读性和可维护性。以下是一个示例项目结构:

MyProject/
├── CMakeLists.txt         # CMake构建文件
├── src/                   # 源代码目录
│   ├── main.cpp           # 主程序
│   └── example.cpp        # 示例实现
├── include/               # 头文件目录
│   ├── example.h          # 示例头文件
│   └── utils.h            # 工具类头文件
├── tests/                 # 测试目录
│   └── example_test.cpp   # 示例测试文件
└── build/                 # 构建目录
15.2.2 代码组织原则
  • 分离实现与接口:将实现代码(.cpp文件)与接口代码(.h文件)分开,易于维护。
  • 模块化设计:将相关功能组织成模块,减少模块间的耦合,提高代码的可复用性。
  • 使用命名空间:使用命名空间来避免名称冲突,提高代码的可读性。

15.3 CMake入门

15.3.1 CMake介绍

CMake是一个跨平台的构建系统,可以生成适用于不同平台的Makefile或项目文件。CMake通过CMakeLists.txt文件来配置项目。

15.3.2 创建CMakeLists.txt

以下是一个简单的CMakeLists.txt示例,用于构建一个名为MyProject的项目:

cmake_minimum_required(VERSION 3.10)
project(MyProject)

# 设置C++标准
set(CMAKE_CXX_STANDARD 20)

# 包含头文件目录
include_directories(include)

# 添加源文件
set(SOURCES
    src/main.cpp
    src/example.cpp
)

# 创建可执行文件
add_executable(MyProject ${SOURCES})
15.3.3 使用CMake构建项目
  1. 创建构建目录:在项目根目录下创建一个名为build的目录。
    mkdir build
    cd build
    
  2. 运行CMake:在build目录下运行CMake,生成Makefile。
    cmake ..
    
  3. 编译项目:使用make命令编译项目。
    make
    
  4. 运行可执行文件:编译完成后,可以运行生成的可执行文件。
    ./MyProject
    

15.4 CMake的高级特性

15.4.1 添加库

CMake可以方便地管理库文件的构建与链接。以下是将库文件添加到项目的示例:

# 创建静态库
add_library(MyLibrary STATIC src/example.cpp)

# 创建可执行文件并链接库
add_executable(MyProject src/main.cpp)
target_link_libraries(MyProject MyLibrary)
15.4.2 处理依赖项

CMake能够自动处理项目中的依赖项。可以使用find_package指令来查找并链接外部库。

find_package(SomeLibrary REQUIRED)
target_link_libraries(MyProject SomeLibrary)

15.5 测试与持续集成

15.5.1 使用Google Test进行单元测试

CMake支持集成测试框架,如Google Test。可以在CMakeLists.txt中添加测试支持:

# 添加测试
enable_testing()
add_subdirectory(tests)

tests/CMakeLists.txt中:

# 添加Google Test
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

add_executable(example_test example_test.cpp)
target_link_libraries(example_test ${GTEST_LIBRARIES} pthread)

# 注册测试
add_test(NAME example_test COMMAND example_test)
15.5.2 持续集成

设置持续集成(CI)可以自动化测试和构建过程,确保代码的稳定性和可靠性。可以使用GitHub Actions、Travis CI等工具进行配置。

15.6 小结

本节介绍了C++项目的基本结构和组织方法,学习了如何使用CMake作为构建系统,创建和管理C++项目。掌握这些知识将有助于学生在实际开发中高效管理项目,提高代码质量。

课堂练习

  • 创建一个新的C++项目,组织代码结构,并编写CMakeLists.txt进行构建。
  • 使用Google Test编写一个简单的单元测试,验证项目中的功能模块。
posted @ 2024-09-30 12:27  ffl  阅读(92)  评论(0编辑  收藏  举报