最新文章

20260228 142212 Cpp 入门第十二课

第十二章:排序算法——让数据井然有序

你好!欢迎来到第十二章!在日常生活中,我们经常需要把东西排好序,比如按身高排队、按分数排名、按时间顺序排列照片。在计算机科学中,排序是最基础也最重要的算法之一。虽然C++的STL提供了强大的 sort 函数,但学习排序算法能帮助我们理解计算机如何工作,锻炼逻辑思维。这一章我们就来学习几种经典的排序算法,并亲手实现它们!


12.1 什么是排序算法?

排序就是将一组数据按照某种规则重新排列,比如从小到大(升序)或从大到小(降序)。

想象你有一堆乱序的扑克牌,你要把它们按数字从小到大整理好。你会怎么做?不同的方法对应不同的排序算法。

本章我们将学习:
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序(进阶)
- 归并排序(进阶)


12.2 冒泡排序

12.2.1 算法思想

冒泡排序就像水中的气泡,轻的气泡会慢慢浮到上面。每次比较相邻的两个元素,如果顺序不对(比如前大后小),就交换它们。这样每一轮都会把最大的数“冒泡”到最后。

过程演示(升序):
初始数组:[5, 3, 8, 1, 2]

第一轮:
- 比较5和3,5>3,交换 → [3, 5, 8, 1, 2]
- 比较5和8,5<8,不交换 → [3, 5, 8, 1, 2]
- 比较8和1,8>1,交换 → [3, 5, 1, 8, 2]
- 比较8和2,8>2,交换 → [3, 5, 1, 2, 8](最大数8已就位)

第二轮:
- 比较3和5,3<5,不交换 → [3, 5, 1, 2, 8]
- 比较5和1,5>1,交换 → [3, 1, 5, 2, 8]
- 比较5和2,5>2,交换 → [3, 1, 2, 5, 8](次大数5就位)

第三轮:
- 比较3和1,3>1,交换 → [1, 3, 2, 5, 8]
- 比较3和2,3>2,交换 → [1, 2, 3, 5, 8](第三大3就位)

第四轮:
- 比较1和2,1<2,不交换 → 已完成

12.2.2 代码实现

#include <iostream>
using namespace std;

void bubbleSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {          // 需要 n-1 轮
        for (int j = 0; j < n - 1 - i; j++) {  // 每轮比较次数减少
            if (arr[j] > arr[j + 1]) {          // 如果前大后小,交换
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

int main() {
    int arr[] = {5, 3, 8, 1, 2};
    int n = sizeof(arr) / sizeof(arr[0]);

    bubbleSort(arr, n);

    cout << "排序后:";
    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;

    return 0;
}

12.2.3 优化

如果某一轮没有发生任何交换,说明数组已经有序,可以提前结束。

void bubbleSortOptimized(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        bool swapped = false;               // 标记是否交换
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1]);    // 可以用swap函数
                swapped = true;
            }
        }
        if (!swapped) break;                 // 没有交换,退出
    }
}

12.2.4 阶段性练习

  1. 练习1:用冒泡排序对数组 [9, 2, 5, 1, 7] 进行升序排序,写出每轮结果。
  2. 练习2:实现降序冒泡排序(从大到小)。
  3. 练习3:统计冒泡排序中交换的次数。

12.3 选择排序

12.3.1 算法思想

选择排序的思路很简单:每一轮从未排序的部分中找出最小的元素,放到已排序部分的末尾。

过程演示(升序):
初始:[5, 3, 8, 1, 2]
- 第一轮:找到最小元素1,与第一个元素5交换 → [1, 3, 8, 5, 2]
- 第二轮:从剩余[3,8,5,2]中找到最小2,与第二个元素3交换 → [1, 2, 8, 5, 3]
- 第三轮:从剩余[8,5,3]中找到最小3,与第三个元素8交换 → [1, 2, 3, 5, 8]
- 第四轮:从剩余[5,8]中找到最小5,与第四个元素5(自身)不交换 → 完成

12.3.2 代码实现

void selectionSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int minIndex = i;                     // 假设当前是最小
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;                  // 更新最小下标
            }
        }
        if (minIndex != i) {                   // 交换
            int temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
}

12.3.3 阶段性练习

  1. 练习1:用选择排序对数组 [8, 3, 6, 1, 4] 排序,写出每轮结果。
  2. 练习2:实现降序选择排序。
  3. 练习3:选择排序的交换次数最多是多少?

12.4 插入排序

12.4.1 算法思想

插入排序就像整理扑克牌:每次将一张牌插入到已经有序的手牌中的正确位置。

过程演示(升序):
初始:[5, 3, 8, 1, 2]
- 第一张牌5,有序部分:[5]
- 取3,插入到5前面 → [3, 5, 8, 1, 2]
- 取8,比5大,位置不变 → [3, 5, 8, 1, 2]
- 取1,依次比较8、5、3,插入最前 → [1, 3, 5, 8, 2]
- 取2,依次比较8、5、3,插入3和1之间 → [1, 2, 3, 5, 8]

12.4.2 代码实现

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] > key) {
            arr[j + 1] = arr[j];    // 向后移动
            j--;
        }
        arr[j + 1] = key;           // 插入正确位置
    }
}

12.4.3 阶段性练习

  1. 练习1:用插入排序对 [7, 2, 5, 1, 9] 排序,写出每一步后数组的变化。
  2. 练习2:实现降序插入排序。
  3. 练习3:插入排序在什么情况下最快?什么情况下最慢?

12.5 快速排序(进阶)

12.5.1 算法思想

快速排序采用分治策略:选择一个基准值(pivot),将数组分成两部分,左边都比基准小,右边都比基准大,然后递归地对左右两部分排序。

过程演示(以第一个元素为基准):
数组:[5, 3, 8, 1, 2]
- 基准5,从右找小于5的2,从左找大于5的8,交换2和8 → [5, 3, 2, 1, 8]
- 继续,从右找小于5的1,从左找大于5的没有,交换1和基准位置?实际上标准做法是用两个指针,最终将基准放到正确位置。
最终得到 [2, 3, 1, 5, 8],左边都小于5,右边大于5。
- 递归排序左边 [2,3,1] 和右边 [8]。

12.5.2 代码实现

int partition(int arr[], int low, int high) {
    int pivot = arr[low];          // 选第一个为基准
    int i = low, j = high;
    while (i < j) {
        while (i < j && arr[j] >= pivot) j--; // 从右找小于pivot的
        if (i < j) arr[i++] = arr[j];
        while (i < j && arr[i] <= pivot) i++; // 从左找大于pivot的
        if (i < j) arr[j--] = arr[i];
    }
    arr[i] = pivot;                // 基准归位
    return i;                      // 返回基准位置
}

void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

调用时:quickSort(arr, 0, n-1);

12.5.3 阶段性练习

  1. 练习1:模拟快速排序对 [4, 7, 2, 1, 5, 3] 的过程。
  2. 练习2:尝试以中间元素为基准实现快速排序。

12.6 排序算法的比较

算法 平均时间复杂度 最坏时间复杂度 空间复杂度 稳定性
冒泡排序 O(n²) O(n²) O(1) 稳定
选择排序 O(n²) O(n²) O(1) 不稳定
插入排序 O(n²) O(n²) O(1) 稳定
快速排序 O(n log n) O(n²) O(log n) 不稳定
归并排序 O(n log n) O(n log n) O(n) 稳定

稳定性:如果两个相等的元素在排序前后的相对位置不变,则排序算法是稳定的。


12.7 编程实例讲解

实例1:成绩排序

题目:输入n个学生的姓名和成绩,按成绩从高到低排序,如果成绩相同则按姓名字典序排序。

#include <iostream>
#include <string>
#include <algorithm>  // 用sort,但我们可以手写排序
using namespace std;

struct Student {
    string name;
    int score;
};

// 冒泡排序(按成绩降序,成绩相同按姓名升序)
void sortStudents(Student stu[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (stu[j].score < stu[j + 1].score || 
                (stu[j].score == stu[j + 1].score && stu[j].name > stu[j + 1].name)) {
                swap(stu[j], stu[j + 1]);
            }
        }
    }
}

int main() {
    int n;
    cout << "请输入学生人数:";
    cin >> n;
    Student stu[100];
    for (int i = 0; i < n; i++) {
        cin >> stu[i].name >> stu[i].score;
    }
    sortStudents(stu, n);
    cout << "\n排序结果:" << endl;
    for (int i = 0; i < n; i++) {
        cout << stu[i].name << " " << stu[i].score << endl;
    }
    return 0;
}

实例2:排序算法效率对比

用随机数生成大量数据,测试不同排序算法的时间。

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

// 冒泡排序、选择排序、插入排序、快速排序函数声明...

int main() {
    const int N = 10000;
    int arr1[N], arr2[N], arr3[N], arr4[N];
    srand(time(0));
    for (int i = 0; i < N; i++) {
        int val = rand() % 10000;
        arr1[i] = arr2[i] = arr3[i] = arr4[i] = val;
    }

    clock_t start, end;

    start = clock();
    bubbleSort(arr1, N);
    end = clock();
    cout << "冒泡排序时间:" << double(end - start) / CLOCKS_PER_SEC << "秒" << endl;

    // 测试其他排序...
    return 0;
}

12.8 第12章编程作业

作业1:三种简单排序的对比

编写程序,生成1000个随机整数,分别用冒泡、选择、插入排序进行排序,并输出每种排序的交换次数和比较次数(在代码中加入计数器)。

作业2:排序字符串数组

输入若干单词(字符串),用插入排序按字典序排序输出。

作业3:快速排序的实现

实现快速排序,并测试其效率。

作业4:排序稳定性验证

创建一个包含姓名和分数的结构体数组,其中有两个同分不同名的学生。分别用稳定和不稳定的排序算法排序,观察他们的相对顺序是否改变。

作业5:自定义排序规则

编写一个程序,按以下规则对整数排序:奇数在前,偶数在后;奇数按升序,偶数按降序。例如 [3,2,5,1,8,4] 排序后应为 [1,3,5,8,4,2]。


恭喜你完成了第十二章的学习!现在你已经掌握了多种排序算法,能够处理各种排序问题了。加油!🚀

20260228 142049 Cpp 入门第十一课

第十一章:算法入门——模拟与高精度

你好!欢迎来到第十一章!从这一章开始,我们将正式进入算法学习。算法是解决问题的步骤和方法,就像菜谱一样,告诉计算机一步一步怎么做。本章我们学习两类基础算法:模拟高精度

  • 模拟:按照题目描述的过程,一步一步用程序实现,就像“照葫芦画瓢”。
  • 高精度:当数字太大,连 long long 都存不下时,我们需要用数组或字符串来存储和计算,比如求 100 的阶乘。

让我们一起探索吧!


11.1 模拟算法

11.1.1 什么是模拟?

模拟就是让计算机模仿现实世界的过程。比如,你要写一个程序模拟电梯的运行:有人按按钮,电梯就移动到那一层。你只需要按照电梯的规则一步一步写代码。

11.1.2 模拟程序范例

我们来看一个简单的模拟题:猜拳游戏。两个人玩石头剪刀布,规则是石头赢剪刀、剪刀赢布、布赢石头。输入两人的出拳(0代表石头,1代表剪刀,2代表布),输出谁赢。

#include <iostream>
using namespace std;

int main() {
    int a, b;
    cout << "请输入A和B的出拳(0石头 1剪刀 2布):";
    cin >> a >> b;

    if (a == b) {
        cout << "平局" << endl;
    } else if ((a == 0 && b == 1) || (a == 1 && b == 2) || (a == 2 && b == 0)) {
        cout << "A赢了" << endl;
    } else {
        cout << "B赢了" << endl;
    }

    return 0;
}

这就是一个简单的模拟:根据游戏规则判断胜负。

11.1.3 模拟的步骤

  1. 读懂题目:明确题目描述的规则和过程。
  2. 找出变量:哪些东西会变化,需要用变量表示。
  3. 模拟过程:用循环和判断一步步实现。
  4. 输出结果:根据模拟得到的结果输出。

11.1.4 模拟实例讲解

实例1:报数游戏(约瑟夫环简化)

题目:n个人围成一圈,从第1个人开始报数,数到m的人出列,然后从下一个人继续报数。输入n和m,输出出列顺序。

#include <iostream>
using namespace std;

int main() {
    int n, m;
    cout << "请输入总人数n和报数m:";
    cin >> n >> m;

    bool inGame[100] = {false}; // 标记是否还在游戏中,假设最多100人
    for (int i = 0; i < n; i++) inGame[i] = true;

    int count = 0;      // 当前报的数
    int outCount = 0;   // 已经出列的人数
    int i = 0;          // 当前人的下标

    while (outCount < n) {
        if (inGame[i]) {
            count++;
            if (count == m) {
                cout << i + 1 << " "; // 输出编号(从1开始)
                inGame[i] = false;
                outCount++;
                count = 0;
            }
        }
        i = (i + 1) % n; // 下一个,形成环
    }
    cout << endl;

    return 0;
}

运行示例

请输入总人数n和报数m:5 3
3 1 5 2 4
实例2:电梯模拟

题目:有一部电梯,初始在第1层。输入一系列请求,每个请求包含目标楼层和方向(上或下)。电梯每次移动一层,每移动一层花费1秒,停靠开门关门花费5秒。按请求顺序处理(即先来先服务),计算总时间。

#include <iostream>
using namespace std;

int main() {
    int n;
    cout << "请输入请求个数:";
    cin >> n;

    int currentFloor = 1;
    int totalTime = 0;

    for (int i = 0; i < n; i++) {
        int target;
        cout << "请输入目标楼层:";
        cin >> target;

        // 移动时间 = 楼层差绝对值
        totalTime += abs(target - currentFloor);
        currentFloor = target;
        // 停靠时间
        totalTime += 5;
    }

    cout << "总时间:" << totalTime << "秒" << endl;

    return 0;
}

11.1.5 阶段性编程练习(模拟)

  1. 练习1:模拟一个简单的计算器。输入两个数和运算符(+、-、*、/),输出结果,注意除数为0的情况。
  2. 练习2:模拟一个自动售货机。商品价格5元,投入硬币(1元、5元、10元),计算总金额,如果足够支付,输出找零,否则提示金额不足。
  3. 练习3:模拟一个猜数字游戏(电脑随机生成1-100的数,用户猜,提示大小,直到猜中,统计次数)。
  4. 练习4:模拟一个排队系统。有n个人排队,每分钟处理一个人,新来的人排到队尾。输入初始队列(用数组表示),然后模拟3分钟,每分钟输出当前队首被服务,并加入一个新来的人(编号为n+1、n+2…)。

11.2 高精度算法

11.2.1 为什么需要高精度?

C++中,int 大约能存到21亿(10位),long long 大约能存到9×10^18(19位)。如果要计算 100!(约100位),或者两个100位的整数相加,普通变量就不够用了。我们需要用数组字符串来存储每一位数字,然后模拟手工计算的过程。

11.2.2 高精度存储

通常用字符串读入,然后倒序存入整型数组(因为手工计算是从低位到高位)。

例如:数字 12345 存在数组里:a[0]=5, a[1]=4, a[2]=3, a[3]=2, a[4]=1

11.2.3 高精度加法

两个大数相加,从低位到高位逐位相加,处理进位。

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

// 高精度加法,返回结果字符串
string add(string s1, string s2) {
    // 将字符串反转,便于从低位开始计算
    reverse(s1.begin(), s1.end());
    reverse(s2.begin(), s2.end());

    // 确保s1较长
    if (s1.length() < s2.length()) swap(s1, s2);

    int carry = 0; // 进位
    for (size_t i = 0; i < s1.length(); i++) {
        int a = s1[i] - '0';
        int b = i < s2.length() ? s2[i] - '0' : 0;
        int sum = a + b + carry;
        s1[i] = sum % 10 + '0';
        carry = sum / 10;
    }
    if (carry) {
        s1 += carry + '0'; // 最高位有进位
    }
    reverse(s1.begin(), s1.end());
    return s1;
}

int main() {
    string a, b;
    cout << "请输入两个大整数:";
    cin >> a >> b;
    cout << "和:" << add(a, b) << endl;
    return 0;
}

11.2.4 高精度减法

减法需要注意借位,并且要保证结果是非负的(先比较大小)。

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

// 比较两个字符串表示的数,若a>=b返回true
bool greaterOrEqual(string a, string b) {
    if (a.length() != b.length()) return a.length() > b.length();
    return a >= b;
}

// 高精度减法,要求a>=b
string subtract(string a, string b) {
    reverse(a.begin(), a.end());
    reverse(b.begin(), b.end());

    string result;
    int borrow = 0;
    for (size_t i = 0; i < a.length(); i++) {
        int digitA = a[i] - '0' - borrow;
        int digitB = i < b.length() ? b[i] - '0' : 0;
        if (digitA < digitB) {
            digitA += 10;
            borrow = 1;
        } else {
            borrow = 0;
        }
        result += (digitA - digitB) + '0';
    }
    // 去除前导零
    while (result.length() > 1 && result.back() == '0') {
        result.pop_back();
    }
    reverse(result.begin(), result.end());
    return result;
}

int main() {
    string a, b;
    cout << "请输入两个大整数(a >= b):";
    cin >> a >> b;
    if (!greaterOrEqual(a, b)) {
        cout << "a必须大于等于b" << endl;
    } else {
        cout << "差:" << subtract(a, b) << endl;
    }
    return 0;
}

11.2.5 高精度乘法(高精度×低精度)

一个大数乘以一个普通整数(如 long long 范围内)。

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

string multiply(string a, int b) {
    reverse(a.begin(), a.end());
    int carry = 0;
    for (size_t i = 0; i < a.length(); i++) {
        int cur = (a[i] - '0') * b + carry;
        a[i] = cur % 10 + '0';
        carry = cur / 10;
    }
    while (carry) {
        a += carry % 10 + '0';
        carry /= 10;
    }
    reverse(a.begin(), a.end());
    return a;
}

int main() {
    string a;
    int b;
    cout << "请输入大整数a和普通整数b:";
    cin >> a >> b;
    cout << "积:" << multiply(a, b) << endl;
    return 0;
}

11.2.6 高精度乘法(高精度×高精度)

两个大数相乘,模拟竖式乘法。

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

string multiply(string a, string b) {
    int lenA = a.length(), lenB = b.length();
    vector<int> res(lenA + lenB, 0); // 结果最多 lenA+lenB 位

    // 将字符串反转存入整型数组
    reverse(a.begin(), a.end());
    reverse(b.begin(), b.end());
    for (int i = 0; i < lenA; i++) {
        for (int j = 0; j < lenB; j++) {
            res[i + j] += (a[i] - '0') * (b[j] - '0');
            res[i + j + 1] += res[i + j] / 10; // 进位
            res[i + j] %= 10;
        }
    }

    // 去除前导0
    while (res.size() > 1 && res.back() == 0) res.pop_back();
    // 转成字符串
    string result;
    for (int i = res.size() - 1; i >= 0; i--) {
        result += res[i] + '0';
    }
    return result;
}

int main() {
    string a, b;
    cout << "请输入两个大整数:";
    cin >> a >> b;
    cout << "积:" << multiply(a, b) << endl;
    return 0;
}

11.2.7 高精度阶乘

计算 n!,n 较大时结果很长,用高精度乘法。

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

string multiply(string a, int b) {
    // 复用之前的高精度乘低精度
    reverse(a.begin(), a.end());
    int carry = 0;
    for (size_t i = 0; i < a.length(); i++) {
        int cur = (a[i] - '0') * b + carry;
        a[i] = cur % 10 + '0';
        carry = cur / 10;
    }
    while (carry) {
        a += carry % 10 + '0';
        carry /= 10;
    }
    reverse(a.begin(), a.end());
    return a;
}

string factorial(int n) {
    string result = "1";
    for (int i = 2; i <= n; i++) {
        result = multiply(result, i);
    }
    return result;
}

int main() {
    int n;
    cout << "请输入n:";
    cin >> n;
    cout << n << "! = " << factorial(n) << endl;
    return 0;
}

11.2.8 阶段性编程练习(高精度)

  1. 练习1:输入两个大整数,输出它们的和、差(保证非负)。
  2. 练习2:输入一个大整数和一个普通整数,输出它们的积。
  3. 练习3:计算斐波那契数列的第100项(用高精度)。
  4. 练习4:输入两个大整数,输出它们的乘积。

11.3 编程实例讲解

实例3:高精度加法应用——大数求和

题目:输入若干个大整数(以0结束),求它们的总和。

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

string add(string a, string b) {
    // 复用之前的加法代码
    // ...
}

int main() {
    string sum = "0", num;
    while (true) {
        cin >> num;
        if (num == "0") break;
        sum = add(sum, num);
    }
    cout << "总和:" << sum << endl;
    return 0;
}

实例4:模拟+高精度——大数阶乘之和

题目:求 1! + 2! + … + n!,其中 n 较大(例如 n=50)。

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

string multiply(string a, int b) { /* ... */ }
string add(string a, string b) { /* ... */ }

string factorial(int n) { /* ... */ }

int main() {
    int n;
    cout << "请输入n:";
    cin >> n;
    string sum = "0";
    for (int i = 1; i <= n; i++) {
        sum = add(sum, factorial(i));
    }
    cout << "1!+2!+...+" << n << "! = " << sum << endl;
    return 0;
}

11.4 第11章编程作业

作业1:模拟银行排队

银行有2个窗口,客户到达时间和服务时间已知,模拟每个客户在哪个窗口办理,以及等待时间。输入客户数n,以及每个客户的到达时间和服务时间(整数),输出每个客户的开始服务时间和结束时间。

作业2:高精度加法器

编写一个程序,可以不断输入两个大整数,输出它们的和,直到用户输入”0 0”结束。

作业3:高精度乘法器

类似作业2,但做乘法。

作业4:大数比较

输入两个大整数,比较它们的大小(大于、小于、等于)。

作业5:回文数判断(高精度版)

输入一个很大的整数(可能100位),判断它是否是回文数(正读反读一样)。如果是,输出YES;否则输出NO。

作业6:模拟+高精度——大数进制转换

输入一个十进制大数(用字符串),将其转换为二进制(也用字符串输出)。


恭喜你完成了第十一章的学习!模拟是算法中最直接的思想,高精度则让我们突破了数据范围的限制。加油!🚀

20260227 152127 Cpp 入门第十课

第十章:C++的百宝箱——STL(标准模板库)

你好!欢迎来到第十章!在前面的章节,我们学习了数组、链表、栈、队列等数据结构,但每次都要自己写代码实现,很麻烦。其实C++已经给我们准备了一个强大的百宝箱,里面有很多常用的数据结构和算法,可以直接拿来用,这就是STL(Standard Template Library,标准模板库)。学完本章,你就能轻松地使用各种容器和算法,让编程变得事半功倍!


10.1 STL程序范例

先来看一个简单的STL程序:使用 vector(动态数组)存储一些整数,然后用 sort 排序,最后用迭代器输出。

#include <iostream>
#include <vector>   // vector容器
#include <algorithm> // 算法,比如sort
using namespace std;

int main() {
    // 创建一个vector,存储整数
    vector<int> numbers;

    // 向尾部添加元素
    numbers.push_back(5);
    numbers.push_back(2);
    numbers.push_back(8);
    numbers.push_back(1);
    numbers.push_back(9);

    cout << "排序前:";
    for (int i = 0; i < numbers.size(); i++) {
        cout << numbers[i] << " ";
    }
    cout << endl;

    // 使用STL的排序算法
    sort(numbers.begin(), numbers.end());

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

    return 0;
}

运行结果

排序前:5 2 8 1 9 
排序后:1 2 5 8 9 

这个程序展示了STL的核心三部分:容器(vector)、算法(sort)、迭代器(iterator)。下面我们一一学习。


10.2 STL概述

STL是C++标准库的一部分,它提供了三大组件:

  • 容器:存储数据的结构,比如动态数组、链表、栈、队列、集合、映射等。
  • 算法:对容器中的数据进行操作的函数,比如排序、查找、替换等。
  • 迭代器:连接容器和算法的桥梁,像指针一样访问容器中的元素。

使用STL的好处:代码简洁、高效、可靠,不用自己重复造轮子。


10.3 容器

容器是用来存放数据的对象。根据组织方式不同,分为序列式容器关联式容器

10.3.1 vector(动态数组)

vector 是一个可以动态增长的数组,支持随机访问(用下标),在尾部插入删除效率高。

基本操作
#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 定义
    vector<int> v1;               // 空vector
    vector<int> v2(5, 10);        // 5个元素,每个都是10
    vector<int> v3 = {1, 2, 3, 4}; // 初始化列表(C++11)

    // 添加元素
    v1.push_back(20);   // 尾部添加
    v1.push_back(30);

    // 访问元素
    cout << v1[0] << endl;         // 下标访问,不检查越界
    cout << v1.at(1) << endl;      // at会检查越界,抛出异常

    // 获取大小
    cout << "v1大小:" << v1.size() << endl;

    // 遍历
    for (int i = 0; i < v1.size(); i++) {
        cout << v1[i] << " ";
    }
    cout << endl;

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

    // 使用范围for循环(C++11)
    for (int x : v1) {
        cout << x << " ";
    }
    cout << endl;

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

    // 清空
    v1.clear();

    return 0;
}

常用成员函数
- push_back():尾部添加
- pop_back():删除尾部
- size():元素个数
- empty():是否为空
- clear():清空
- begin()end():返回首尾迭代器
- insert()erase():插入和删除(较复杂,需要迭代器)

10.3.2 list(双向链表)

list 是双向链表,不支持随机访问,但插入和删除非常快(尤其在中间)。

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

int main() {
    list<int> lst = {5, 2, 8, 1, 9};

    // 尾部添加
    lst.push_back(10);
    // 头部添加
    lst.push_front(0);

    // 遍历(只能用迭代器,不能用下标)
    for (list<int>::iterator it = lst.begin(); it != lst.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;

    // 排序(list有自己的sort成员函数,比通用算法快)
    lst.sort();

    for (int x : lst) {
        cout << x << " ";
    }
    cout << endl;

    return 0;
}

10.3.3 deque(双端队列)

deque 是双端队列,支持在头尾快速插入删除,也支持随机访问(但比vector稍慢)。

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

int main() {
    deque<int> dq = {1, 2, 3};
    dq.push_back(4);    // 尾部加
    dq.push_front(0);   // 头部加

    for (int i = 0; i < dq.size(); i++) {
        cout << dq[i] << " ";   // 支持下标
    }
    cout << endl;

    dq.pop_back();      // 删除尾部
    dq.pop_front();     // 删除头部
    return 0;
}

10.3.4 stack(栈)

stack 是适配器容器,它基于其他容器(默认deque)实现,提供后进先出(LIFO)接口。

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

int main() {
    stack<int> st;
    st.push(10);   // 入栈
    st.push(20);
    st.push(30);

    cout << "栈顶元素:" << st.top() << endl; // 30
    st.pop();      // 出栈(无返回值)
    cout << "栈顶元素:" << st.top() << endl; // 20
    cout << "栈大小:" << st.size() << endl;  // 2

    while (!st.empty()) {
        cout << st.top() << " ";
        st.pop();
    }
    return 0;
}

10.3.5 queue(队列)

queue 也是适配器容器,提供先进先出(FIFO)接口。

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

int main() {
    queue<int> q;
    q.push(10);   // 入队
    q.push(20);
    q.push(30);

    cout << "队头:" << q.front() << endl; // 10
    cout << "队尾:" << q.back() << endl;  // 30
    q.pop();      // 出队(队头)
    cout << "队头:" << q.front() << endl; // 20
    return 0;
}

10.3.6 set(集合)

set 是一个有序的集合,元素唯一,自动排序(默认升序)。查找效率高(红黑树)。

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

int main() {
    set<int> s;
    s.insert(5);
    s.insert(2);
    s.insert(8);
    s.insert(2);   // 重复插入无效

    // 遍历(有序)
    for (set<int>::iterator it = s.begin(); it != s.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;

    // 查找
    set<int>::iterator it = s.find(5);
    if (it != s.end()) {
        cout << "找到了:" << *it << endl;
    } else {
        cout << "没找到" << endl;
    }

    // 删除
    s.erase(2);
    return 0;
}

10.3.7 map(映射)

map 存储键值对(key-value),键唯一,自动排序。

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

int main() {
    map<string, int> scores;
    scores["小明"] = 98;        // 通过键赋值
    scores["小红"] = 95;
    scores["小刚"] = 88;

    // 遍历
    for (map<string, int>::iterator it = scores.begin(); it != scores.end(); ++it) {
        cout << it->first << " : " << it->second << endl;   // first是键,second是值
    }

    // 查找
    string name = "小红";
    map<string, int>::iterator it = scores.find(name);
    if (it != scores.end()) {
        cout << name << "的成绩是:" << it->second << endl;
    } else {
        cout << "没找到" << endl;
    }

    return 0;
}

常用成员函数
- insert(make_pair(key, value)) 或直接用 [key] = value(但 [] 如果键不存在会创建)
- find(key):查找,返回迭代器,找不到返回 end()
- erase(key):删除


10.4 迭代器

迭代器是一种类似于指针的对象,用来遍历容器中的元素。所有容器都提供 begin()end() 成员函数,返回指向第一个元素和最后一个元素之后位置的迭代器。

迭代器分类(按功能):
- 输入迭代器:只读,一次遍历
- 输出迭代器:只写,一次遍历
- 前向迭代器:可读写,只能向前(如 forward_list
- 双向迭代器:可读写,能前后移动(如 listsetmap
- 随机访问迭代器:可读写,支持 +-[] 等(如 vectordeque

使用示例

vector<int> v = {1, 2, 3, 4, 5};

// 正向迭代器
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
    *it = *it * 2;   // 修改元素
}

// 常量迭代器(只读)
for (vector<int>::const_iterator it = v.cbegin(); it != v.cend(); ++it) {
    cout << *it << " ";
}

// 反向迭代器
for (vector<int>::reverse_iterator rit = v.rbegin(); rit != v.rend(); ++rit) {
    cout << *rit << " ";
}

C++11 可以用 auto 简化:

for (auto it = v.begin(); it != v.end(); ++it) { ... }

10.5 算法

STL提供了大量通用算法,都在 <algorithm> 头文件中。它们通过迭代器操作容器,不依赖于具体容器类型。

10.5.1 sort 排序

#include <algorithm>
vector<int> v = {5, 2, 8, 1, 9};
sort(v.begin(), v.end());                // 升序
sort(v.begin(), v.end(), greater<int>()); // 降序

也可以自定义排序规则(用函数或函数对象):

bool cmp(int a, int b) {
    return a > b;   // 降序
}
sort(v.begin(), v.end(), cmp);

10.5.2 find 查找

vector<int> v = {5, 2, 8, 1, 9};
auto it = find(v.begin(), v.end(), 8);
if (it != v.end()) {
    cout << "找到了,位置:" << it - v.begin() << endl;
} else {
    cout << "没找到" << endl;
}

10.5.3 其他常用算法

  • reverse:反转
    cpp reverse(v.begin(), v.end());
  • count:计数
    cpp int cnt = count(v.begin(), v.end(), 5);
  • max_elementmin_element:找最大最小值
    cpp auto maxIt = max_element(v.begin(), v.end()); cout << "最大值:" << *maxIt << endl;
  • binary_search:二分查找(要求容器有序)
    cpp if (binary_search(v.begin(), v.end(), 8)) { ... }
  • copy:复制
    cpp vector<int> v2(v.size()); copy(v.begin(), v.end(), v2.begin());

10.5.4 算法与迭代器的配合

算法通过迭代器操作数据,所以同一个算法可以用于不同的容器。例如 find 可以用于 vector、list、deque 等。


10.6 编程实例讲解

实例1:统计学生成绩(使用vector和map)

题目:输入若干学生姓名和成绩,以“end”结束。然后输出所有学生的成绩,并按成绩从高到低排序输出。

#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <string>
using namespace std;

struct Student {
    string name;
    int score;
};

bool cmp(const Student &a, const Student &b) {
    return a.score > b.score;   // 降序
}

int main() {
    vector<Student> students;
    string name;
    int score;

    cout << "请输入学生姓名和成绩(输入end结束):" << endl;
    while (true) {
        cin >> name;
        if (name == "end") break;
        cin >> score;
        students.push_back({name, score});
    }

    // 排序
    sort(students.begin(), students.end(), cmp);

    // 输出
    cout << "\n成绩单(按分数降序):" << endl;
    for (const auto &s : students) {
        cout << s.name << " : " << s.score << endl;
    }

    return 0;
}

实例2:单词计数器(使用map)

题目:输入一行英文句子,统计每个单词出现的次数(忽略大小写)。

#include <iostream>
#include <map>
#include <string>
#include <cctype>
#include <sstream>   // 字符串流
using namespace std;

string toLower(string s) {
    for (char &c : s) {
        c = tolower(c);
    }
    return s;
}

int main() {
    string line;
    cout << "请输入一行英文:";
    getline(cin, line);

    map<string, int> wordCount;
    stringstream ss(line);
    string word;

    while (ss >> word) {
        word = toLower(word);
        // 去掉标点符号(简单处理:只保留字母数字)
        string clean;
        for (char c : word) {
            if (isalnum(c)) {
                clean += c;
            }
        }
        if (!clean.empty()) {
            wordCount[clean]++;
        }
    }

    cout << "单词统计:" << endl;
    for (const auto &pair : wordCount) {
        cout << pair.first << " : " << pair.second << endl;
    }

    return 0;
}

实例3:集合运算(使用set)

题目:输入两个集合(整数),输出它们的并集、交集和差集。

#include <iostream>
#include <set>
#include <algorithm>
#include <iterator>
using namespace std;

int main() {
    set<int> set1, set2;
    int n, x;

    cout << "请输入第一个集合的元素个数:";
    cin >> n;
    cout << "请输入" << n << "个整数:";
    for (int i = 0; i < n; i++) {
        cin >> x;
        set1.insert(x);
    }

    cout << "请输入第二个集合的元素个数:";
    cin >> n;
    cout << "请输入" << n << "个整数:";
    for (int i = 0; i < n; i++) {
        cin >> x;
        set2.insert(x);
    }

    // 并集
    set<int> unionSet;
    set_union(set1.begin(), set1.end(), set2.begin(), set2.end(),
              inserter(unionSet, unionSet.begin()));
    cout << "并集:";
    for (int v : unionSet) cout << v << " ";
    cout << endl;

    // 交集
    set<int> interSet;
    set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(),
                     inserter(interSet, interSet.begin()));
    cout << "交集:";
    for (int v : interSet) cout << v << " ";
    cout << endl;

    // 差集(set1 - set2)
    set<int> diffSet;
    set_difference(set1.begin(), set1.end(), set2.begin(), set2.end(),
                   inserter(diffSet, diffSet.begin()));
    cout << "差集(set1 - set2):";
    for (int v : diffSet) cout << v << " ";
    cout << endl;

    return 0;
}

实例4:使用栈判断括号匹配

题目:输入一个字符串,包含 ()[]{},判断括号是否匹配。

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

bool isMatching(char open, char close) {
    return (open == '(' && close == ')') ||
           (open == '[' && close == ']') ||
           (open == '{' && close == '}');
}

bool checkBrackets(const string &s) {
    stack<char> st;
    for (char c : s) {
        if (c == '(' || c == '[' || c == '{') {
            st.push(c);
        } else if (c == ')' || c == ']' || c == '}') {
            if (st.empty() || !isMatching(st.top(), c)) {
                return false;
            }
            st.pop();
        }
    }
    return st.empty();
}

int main() {
    string expr;
    cout << "请输入表达式:";
    cin >> expr;
    if (checkBrackets(expr)) {
        cout << "括号匹配" << endl;
    } else {
        cout << "括号不匹配" << endl;
    }
    return 0;
}

10.7 阶段性编程练习

练习1:vector练习

输入n个整数,存入vector,然后删除所有偶数,输出剩下的奇数。

练习2:list练习

创建一个list,存放10个随机整数(1-100)。使用list的sort排序,然后反转,输出结果。

练习3:map电话簿

实现一个简单的电话簿,用map存储姓名和电话号码。支持添加、删除、查找、显示所有联系人。

练习4:set去重

输入一串整数,可能有重复,用set去除重复后按升序输出。

练习5:队列模拟

用queue模拟一个打印任务队列。每个任务有名称和页数,按先进先出处理,并输出处理顺序。

练习6:算法综合

生成一个vector包含20个随机整数(1-100),然后:
- 排序
- 查找是否存在50
- 统计大于50的个数
- 删除所有小于30的元素(提示:用erase配合remove_if)


10.8 第10章编程作业

恭喜你学完了STL!现在来挑战几个综合题目,用STL容器和算法解决问题。

作业1:学生成绩管理系统(STL版)

用vector存储学生信息(结构体包含姓名、学号、各科成绩)。实现:
- 添加学生
- 删除学生(按学号)
- 修改成绩
- 按总成绩排序并输出
- 按学号查找
- 统计各科平均分、最高分、最低分

作业2:文章词频统计

读入一篇文章(可以从文件读或手动输入),统计每个单词出现的次数,忽略大小写和标点,最后按词频降序输出前10个单词。

作业3:迷宫最短路径(选做)

用queue实现广度优先搜索(BFS)求解迷宫最短路径。迷宫用二维vector表示,0可走,1障碍。

作业4:多项式计算器

用map存储多项式(指数为键,系数为值)。实现两个多项式的加法、减法、乘法。

作业5:停车场模拟

用stack模拟停车场,queue模拟等待车道。车辆到达时,如果有空位则停入stack,否则进入queue;车辆离开时,需要将上面的车暂时移出(用另一个stack),然后让目标车离开,再移回。输出每次操作后的状态。

20260227 151910 Cpp 入门第九课

第九章:深入理解内存——指针、引用和动态内存

你好!欢迎来到第九章!在前面的章节,我们学习了变量、数组、函数和结构体。你有没有想过,变量在内存中到底存放在哪里?我们如何直接操作内存地址?这一章我们将学习C++中最强大也最需要小心的特性——指针。同时还会学习它的好兄弟引用,以及如何动态地分配内存。学完本章,你就能更深入地理解计算机内存的运作方式!


9.1 指针程序范例

先来看一个最简单的指针程序:定义一个整数变量,然后用指针指向它,通过指针修改它的值。

#include <iostream>
using namespace std;

int main() {
    int a = 10;          // 定义一个普通变量
    int *p;              // 定义一个指针变量,它可以存放整数的地址
    p = &a;              // 把a的地址赋值给p,现在p指向a

    cout << "a的值 = " << a << endl;
    cout << "a的地址 = " << &a << endl;   // &是取地址运算符
    cout << "p的值 = " << p << endl;       // p存的就是a的地址
    cout << "p指向的值 = " << *p << endl;  // *是间接访问运算符,得到p指向的内容

    *p = 20;             // 通过指针修改a的值
    cout << "修改后a的值 = " << a << endl;

    return 0;
}

运行结果(地址会因运行环境不同而变化):

a的值 = 10
a的地址 = 0x61ff08
p的值 = 0x61ff08
p指向的值 = 10
修改后a的值 = 20

这个程序展示了指针的基本操作:&取地址,*解引用。就像你可以通过门牌号找到房子,指针就是内存地址,通过它可以访问变量。


9.2 指针的用法

9.2.1 指针的概念

指针是一个变量,它存储的是另一个变量的内存地址。简单说,指针就是地址的“容器”。每个变量在内存中都有一个地址,就像每间教室都有一个门牌号。

  • &:取地址运算符,获得变量的地址。
  • *:间接访问运算符(解引用),通过地址访问该地址存储的值。

9.2.2 指针的定义和赋值

定义指针的格式:类型 *指针名;
- 例如:int *p; 表示p是一个指向整数的指针。
- double *dp; 表示指向double的指针。
- char *cp; 表示指向字符的指针。

指针必须初始化,否则会变成野指针(指向随机地址),非常危险。

int a = 5;
int *p = &a;   // 定义时初始化
int *q;        // 未初始化,危险!
q = &a;        // 之后赋值也可以

9.2.3 指针的运算

指针可以像整数一样进行加减运算,但它的加减是以指向的类型大小为单位的。

int a[5] = {10, 20, 30, 40, 50};
int *p = &a[0];   // 指向第一个元素

cout << "*p = " << *p << endl;        // 输出10
cout << "*(p+1) = " << *(p+1) << endl; // 输出20,p+1指向下一个整数

指针还可以做减法,得到两个指针之间的元素个数。

9.2.4 指针与数组

数组名在大多数情况下可以看作指向第一个元素的指针。

int a[5] = {1, 2, 3, 4, 5};
int *p = a;          // 等价于 p = &a[0]

for (int i = 0; i < 5; i++) {
    cout << *(p + i) << " ";   // 用指针访问数组
}

但数组名不是真正的指针,它是常量,不能修改。

9.2.5 指针与函数

指针作为函数参数

通过传递指针,函数可以修改调用者的变量(因为知道了地址)。

#include <iostream>
using namespace std;

void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

int main() {
    int a = 3, b = 5;
    swap(&a, &b);   // 传递地址
    cout << "a = " << a << ", b = " << b << endl;   // a=5, b=3
    return 0;
}
指针作为函数返回值

函数可以返回指针,但要小心不能返回局部变量的地址(因为函数结束局部变量就销毁了)。

int* findMax(int *arr, int size) {
    int *max = arr;
    for (int i = 1; i < size; i++) {
        if (arr[i] > *max) {
            max = &arr[i];
        }
    }
    return max;   // 返回指向最大元素的指针(安全,因为数组生命周期还在)
}

9.2.6 空指针和野指针

  • 空指针:指向空地址的指针,用 nullptr(C++11)或 NULL 表示。解引用空指针会导致程序崩溃。
    cpp int *p = nullptr; // 推荐使用nullptr if (p != nullptr) { *p = 10; // 安全 }

  • 野指针:未初始化或指向已释放内存的指针,使用它非常危险,可能导致程序崩溃或数据损坏。一定要避免!初始化指针是最好的预防。


9.3 引用

9.3.1 引用的概念

引用是给变量起的一个别名,它和原变量共享同一块内存。定义引用时,必须初始化,而且不能改变指向。

int a = 10;
int &b = a;   // b是a的引用,b就是a的别名
b = 20;       // 修改b相当于修改a
cout << a;    // 输出20

9.3.2 引用的用法

引用常用于函数参数,避免拷贝,并且可以直接修改实参。

#include <iostream>
using namespace std;

void swap(int &x, int &y) {   // 引用参数
    int temp = x;
    x = y;
    y = temp;
}

int main() {
    int a = 3, b = 5;
    swap(a, b);   // 直接传变量,不需要取地址
    cout << "a = " << a << ", b = " << b << endl;   // a=5, b=3
    return 0;
}

引用也可以作为函数返回值,但同样不能返回局部变量的引用。

9.3.3 引用与指针的区别

特性 指针 引用
初始化 可以不初始化(但危险) 必须初始化
可修改 可以改变指向其他变量 一旦绑定,不能改变
访问方式 * 解引用 直接像普通变量一样用
空值 可以为 nullptr 不能为空
用途 动态内存、数组操作等 函数参数、操作符重载等

简单说:引用是更安全、更方便的指针,但功能稍弱。


9.4 动态内存分配

有时候我们不知道程序运行时会需要多少内存(比如需要根据用户输入决定数组大小),这时就需要动态内存分配。C++中用 newdelete 来申请和释放内存。

9.4.1 new和delete

  • new 在堆上分配内存,返回指向该内存的指针。
  • delete 释放由 new 分配的内存。
int *p = new int;   // 分配一个整数大小的内存
*p = 10;
cout << *p << endl;
delete p;           // 释放内存,避免内存泄漏
p = nullptr;        // 将指针置空,避免野指针

也可以初始化:

int *p = new int(20);   // 分配并初始化为20

9.4.2 动态数组

new[] 分配数组,用 delete[] 释放。

int n;
cout << "请输入数组大小:";
cin >> n;
int *arr = new int[n];   // 动态分配数组

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

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

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

9.4.3 内存泄漏

如果 new 分配了内存,但忘记 delete,这块内存在程序结束前都无法再使用,造成内存泄漏。程序运行时间越长,占用的内存越多,最终可能崩溃。所以一定要成对使用 new/deletenew[]/delete[]

重要规则
- new 对应 delete
- new[] 对应 delete[]
- 不要重复 delete
- 释放后将指针置空。


9.5 编程实例讲解

实例1:用指针实现字符串复制(C风格)

#include <iostream>
using namespace std;

void stringCopy(char *dest, const char *src) {
    while (*src != '\0') {
        *dest = *src;
        dest++;
        src++;
    }
    *dest = '\0';   // 添加字符串结束符
}

int main() {
    char s1[100] = {0};
    char s2[] = "Hello, C++!";
    stringCopy(s1, s2);
    cout << "复制后的字符串:" << s1 << endl;
    return 0;
}

实例2:动态创建结构体

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

struct Student {
    string name;
    int age;
    double score;
};

int main() {
    Student *p = new Student;   // 动态分配一个学生结构体
    p->name = "小明";           // 用->访问成员,等价于 (*p).name
    p->age = 12;
    p->score = 98.5;

    cout << "姓名:" << p->name << ",年龄:" << p->age << ",成绩:" << p->score << endl;

    delete p;   // 释放内存
    p = nullptr;
    return 0;
}

实例3:动态数组求平均值

#include <iostream>
using namespace std;

int main() {
    int n;
    cout << "请输入学生人数:";
    cin >> n;

    double *scores = new double[n];
    double sum = 0;

    for (int i = 0; i < n; i++) {
        cout << "请输入第" << i+1 << "个学生的成绩:";
        cin >> scores[i];
        sum += scores[i];
    }

    double avg = sum / n;
    cout << "平均分:" << avg << endl;

    delete[] scores;
    scores = nullptr;
    return 0;
}

实例4:用引用交换两个数(对比指针)

#include <iostream>
using namespace std;

void swapByPointer(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

void swapByReference(int &x, int &y) {
    int temp = x;
    x = y;
    y = temp;
}

int main() {
    int a = 3, b = 5;
    swapByPointer(&a, &b);
    cout << "a=" << a << ", b=" << b << endl;   // 5,3

    int c = 10, d = 20;
    swapByReference(c, d);
    cout << "c=" << c << ", d=" << d << endl;   // 20,10
    return 0;
}

9.6 第9章编程作业

恭喜你学完了指针、引用和动态内存!现在来挑战几个综合题目。

作业1:动态数组排序

编写程序,让用户输入数组大小n,然后动态分配数组,输入n个整数,用冒泡排序(用指针操作数组)排序后输出,最后释放内存。

作业2:字符串统计(用指针)

编写函数 int countWords(const char *str),统计字符串中单词的个数(单词之间用空格分隔)。在 main 中测试。

作业3:动态学生信息管理

用结构体 Student 和动态内存实现:
- 输入学生人数n
- 动态创建学生数组
- 输入每个学生的姓名、年龄、成绩
- 按成绩从高到低排序(用指针或引用实现交换)
- 输出排序后的结果
- 释放内存

作业4:引用与指针对比

写三个函数,分别用传值、传指针、传引用的方式实现一个函数 increment,将参数增加1。在 main 中测试,并观察原变量的变化。

作业5:动态二维数组(选做)

动态创建一个 m×n 的二维数组(用指针的指针),输入矩阵并计算转置。注意释放内存。


好了,第九章的内容就到这里!指针是C++中最灵活也最复杂的部分,需要多加练习才能熟练掌握。记住:指针一定要初始化,new和delete要成对出现。下一章我们将学习更高级的内容——类和对象,进入面向对象编程的世界。加油!🚀

20260227 151824 Cpp 入门第八课

第八章:自定义数据类型——结构体和联合体

你好!欢迎来到第八章!在前面的章节,我们学习了基本数据类型(int、double、char)和数组(一组相同类型的数据)。但生活中很多事物是由不同类型的数据组成的,比如一个学生有姓名(字符串)、年龄(整数)、身高(小数)。如果能把它们组合成一个整体,就方便多了!这就是结构体。另外还有一种特殊的类型叫联合体,可以节省内存。这一章我们就来学习如何自定义这些数据类型。


8.1 结构体程序范例

先来看一个例子:定义一个学生结构体,包含姓名、年龄、成绩,然后创建学生变量并输出信息。

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

// 定义结构体类型 Student
struct Student {
    string name;   // 姓名
    int age;       // 年龄
    double score;  // 成绩
};

int main() {
    // 创建结构体变量并初始化
    Student stu1 = {"小明", 12, 98.5};
    Student stu2;

    // 给stu2的成员赋值
    stu2.name = "小红";
    stu2.age = 11;
    stu2.score = 95.0;

    // 输出学生信息
    cout << "学生1:" << stu1.name << ",年龄" << stu1.age << ",成绩" << stu1.score << endl;
    cout << "学生2:" << stu2.name << ",年龄" << stu2.age << ",成绩" << stu2.score << endl;

    return 0;
}

运行结果

学生1:小明,年龄12,成绩98.5
学生2:小红,年龄11,成绩95

8.2 结构体的用法

8.2.1 结构体的概念

结构体是一种可以包含多个不同数据类型的成员的数据类型。它就像一张表格,每一列都有不同的类型。你可以把结构体看作是自己创造的一种新类型,然后像使用int一样用它定义变量。

8.2.2 定义结构体类型

格式:

struct 结构体名 {
    数据类型 成员1;
    数据类型 成员2;
    ...
};   // 注意最后有分号

例如:

struct Point {
    int x;
    int y;
};

struct Book {
    string title;
    string author;
    double price;
};

8.2.3 创建结构体变量

有几种方式:

// 方式1:先定义类型,再定义变量
struct Point p1;   // C++中可以省略struct关键字,直接写 Point p1;

// 方式2:定义类型的同时定义变量
struct Point {
    int x;
    int y;
} p2, p3;   // 定义了p2和p3两个变量

// 方式3:直接定义匿名结构体变量
struct {
    int x;
    int y;
} p4;   // 但这种无法再用这个类型定义其他变量

8.2.4 初始化结构体变量

可以在定义时用大括号初始化:

Point p1 = {10, 20};           // x=10, y=20
Point p2 = {0};                // x=0, y=0(未指定的成员自动初始化为0)

如果使用C++11,也可以这样:

Point p3 {30, 40};   // 等号可选

8.2.5 访问结构体成员

使用点号 . 访问成员变量:

p1.x = 100;
p1.y = 200;
cout << "(" << p1.x << ", " << p1.y << ")" << endl;

8.2.6 结构体数组

可以把结构体放进数组,就像普通类型一样:

Student class3[3] = {
    {"小明", 12, 98.5},
    {"小红", 11, 95.0},
    {"小刚", 12, 88.0}
};

// 遍历输出
for (int i = 0; i < 3; i++) {
    cout << class3[i].name << " " << class3[i].age << " " << class3[i].score << endl;
}

8.2.7 结构体作为函数参数

结构体可以像普通变量一样传递给函数:

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

struct Student {
    string name;
    int age;
    double score;
};

// 输出学生信息(传值,会复制一份)
void printStudent(Student stu) {
    cout << stu.name << " " << stu.age << " " << stu.score << endl;
}

// 修改学生年龄(传引用,可以直接修改原变量)
void setAge(Student &stu, int newAge) {
    stu.age = newAge;
}

int main() {
    Student s = {"小明", 12, 98.5};
    printStudent(s);
    setAge(s, 13);
    printStudent(s);   // 年龄变为13
    return 0;
}

8.2.8 结构体作为返回值

函数可以返回结构体:

Student createStudent(string name, int age, double score) {
    Student s;
    s.name = name;
    s.age = age;
    s.score = score;
    return s;
}

int main() {
    Student s = createStudent("小红", 11, 95.0);
    printStudent(s);
    return 0;
}

8.2.9 结构体嵌套

结构体的成员可以是另一个结构体:

struct Date {
    int year;
    int month;
    int day;
};

struct Student {
    string name;
    int age;
    double score;
    Date birthday;   // 嵌套结构体
};

int main() {
    Student s = {"小明", 12, 98.5, {2012, 5, 20}};
    cout << "出生日期:" << s.birthday.year << "年" << s.birthday.month << "月" << s.birthday.day << "日" << endl;
    return 0;
}

8.2.10 阶段性编程练习(结构体基础)

  1. 练习1:定义一个结构体 Rectangle,包含长和宽(整数)。写一个函数计算面积并返回。
  2. 练习2:定义一个结构体 Time,包含时、分、秒。写一个函数输入一个时间,输出它过了多少秒(从0:0:0开始)。
  3. 练习3:定义结构体 Student,包含姓名、学号、3门课成绩。输入5个学生,计算每个学生的平均分,并按平均分从高到低排序输出。
  4. 练习4:用结构体嵌套表示一个学生的家庭地址(省、市、街道),并输入输出。

8.3 联合体

8.3.1 联合体程序范例

联合体(union)和结构体类似,但它的所有成员共用同一块内存,也就是说,同时只能存储一个成员的值。它主要用于节省内存或处理不同类型的数据。

#include <iostream>
using namespace std;

union Data {
    int i;
    double d;
    char c;
};

int main() {
    Data data;
    data.i = 10;          // 现在存储整数
    cout << "整数:" << data.i << endl;

    data.d = 3.14;        // 现在存储浮点数,覆盖了之前的整数
    cout << "浮点数:" << data.d << endl;

    data.c = 'A';         // 现在存储字符
    cout << "字符:" << data.c << endl;

    // 注意:此时 data.i 的值已经无效,因为内存被覆盖
    cout << "整数(已无效):" << data.i << endl;   // 可能输出乱码

    return 0;
}

8.3.2 联合体的用法

  • 联合体的定义和结构体类似,只是关键字是 union
  • 所有成员共享同一块内存,所以联合体的大小等于最大成员的大小。
  • 在同一时刻只能使用一个成员,否则会导致数据混乱。
  • 常用于需要多种类型但只存一种的场景,比如协议解析、节省内存等。
定义和使用
union Value {
    int i;
    float f;
    char str[20];
};

int main() {
    Value v;
    v.i = 100;
    cout << v.i << endl;
    v.f = 3.14;   // 覆盖
    cout << v.f << endl;
    // 注意:不能再访问 v.i,因为值已被覆盖
    return 0;
}

8.3.3 联合体和结构体的区别

  • 结构体:每个成员都有自己的内存空间,可以同时存储所有成员的值。
  • 联合体:所有成员共用同一块内存,只能存储一个成员的值。

8.3.4 阶段性编程练习(联合体)

  1. 练习1:定义一个联合体,包含 int、double、char,分别输入并输出,观察内存覆盖现象。
  2. 练习2:用联合体实现一个可以存储整数或浮点数的变量,并编写一个函数,根据类型打印(需要额外用一个变量标记当前类型,这通常和联合体一起使用,构成“带标记的联合体”)。

8.4 枚举类型(补充知识)

虽然大纲没有明确要求,但枚举类型也是自定义数据类型的一种,常和结构体一起使用,这里简单介绍一下。

枚举用于定义一组命名的整数常量,使代码更易读。

#include <iostream>
using namespace std;

enum Weekday { MON, TUE, WED, THU, FRI, SAT, SUN };
// 默认 MON=0, TUE=1, ...

int main() {
    Weekday today = WED;
    if (today == WED) {
        cout << "今天是星期三" << endl;
    }

    // 枚举值可以转成整数
    cout << "MON = " << MON << endl;   // 输出0

    return 0;
}

可以指定枚举的值:

enum Color { RED = 1, GREEN = 2, BLUE = 4 };

C++11引入了强类型枚举(enum class),但初学者了解基本枚举即可。


8.5 编程实例讲解

实例1:学生成绩管理系统(结构体数组)

题目:有N个学生,每个学生有姓名、学号、语文、数学、英语成绩。要求:
- 输入学生信息
- 计算每个学生的总分和平均分
- 输出所有学生信息(包括总分平均分)
- 按总分从高到低排序输出

#include <iostream>
#include <string>
#include <algorithm>   // 用sort需要,但我们自己实现排序函数
using namespace std;

struct Student {
    string name;
    int id;
    int chinese;
    int math;
    int english;
    int total;      // 总分,可以计算后存储
    double average; // 平均分
};

// 输入学生信息
void inputStudent(Student &s) {
    cout << "请输入姓名、学号、语文、数学、英语:";
    cin >> s.name >> s.id >> s.chinese >> s.math >> s.english;
    s.total = s.chinese + s.math + s.english;
    s.average = s.total / 3.0;
}

// 输出学生信息
void printStudent(const Student &s) {
    cout << s.name << "\t" << s.id << "\t" << s.chinese << "\t" << s.math << "\t" << s.english
         << "\t总分:" << s.total << "\t平均:" << s.average << endl;
}

// 按总分冒泡排序(降序)
void sortStudents(Student stu[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (stu[j].total < stu[j + 1].total) {   // 降序
                Student temp = stu[j];
                stu[j] = stu[j + 1];
                stu[j + 1] = temp;
            }
        }
    }
}

int main() {
    const int N = 3;   // 假设3个学生
    Student students[N];

    // 输入
    for (int i = 0; i < N; i++) {
        cout << "请输入第" << i+1 << "个学生信息:" << endl;
        inputStudent(students[i]);
    }

    // 排序
    sortStudents(students, N);

    // 输出
    cout << "\n排序后的学生信息(按总分降序):" << endl;
    cout << "姓名\t学号\t语文\t数学\t英语\t总分\t平均" << endl;
    for (int i = 0; i < N; i++) {
        printStudent(students[i]);
    }

    return 0;
}

实例2:点与矩形(结构体嵌套)

定义一个点结构体,一个矩形结构体(由左上点和右下点确定)。写函数判断一个点是否在矩形内(包括边界)。

#include <iostream>
using namespace std;

struct Point {
    int x;
    int y;
};

struct Rectangle {
    Point topLeft;     // 左上角
    Point bottomRight; // 右下角
};

// 判断点p是否在矩形r内
bool isPointInRect(const Point &p, const Rectangle &r) {
    return (p.x >= r.topLeft.x && p.x <= r.bottomRight.x &&
            p.y <= r.topLeft.y && p.y >= r.bottomRight.y);   // 注意y轴方向:左上角y大,右下角y小
}

int main() {
    Rectangle rect = { {0, 10}, {10, 0} };   // 左上(0,10),右下(10,0)
    Point p;
    cout << "输入点的坐标(x y):";
    cin >> p.x >> p.y;

    if (isPointInRect(p, rect)) {
        cout << "点在矩形内" << endl;
    } else {
        cout << "点不在矩形内" << endl;
    }

    return 0;
}

实例3:带标记的联合体(简单模拟)

有时我们需要一个变量能存储不同类型的数据,并记住当前是什么类型。可以用结构体包含一个枚举类型和一个联合体。

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

enum DataType { TYPE_INT, TYPE_DOUBLE, TYPE_CHAR };

struct Variant {
    DataType type;
    union {
        int i;
        double d;
        char c;
    } data;
};

void printVariant(const Variant &v) {
    switch (v.type) {
        case TYPE_INT:
            cout << "整数:" << v.data.i << endl;
            break;
        case TYPE_DOUBLE:
            cout << "浮点数:" << v.data.d << endl;
            break;
        case TYPE_CHAR:
            cout << "字符:" << v.data.c << endl;
            break;
    }
}

int main() {
    Variant v1, v2, v3;
    v1.type = TYPE_INT;
    v1.data.i = 100;

    v2.type = TYPE_DOUBLE;
    v2.data.d = 3.14159;

    v3.type = TYPE_CHAR;
    v3.data.c = 'A';

    printVariant(v1);
    printVariant(v2);
    printVariant(v3);

    return 0;
}

8.6 第8章编程作业

恭喜你学完了结构体和联合体!现在来挑战几个综合题目。

作业1:图书管理系统

定义一个结构体 Book,包含书名、作者、出版社、价格、库存数量。实现以下功能:
- 输入一批图书信息(假设最多100本)
- 按书名查找图书,输出信息
- 按价格区间查找(如输入最低价和最高价)
- 统计库存总量和总价值

作业2:日期计算器

定义结构体 Date(年、月、日)。写函数:
- 判断某年是否是闰年
- 计算两个日期之间相差多少天(假设在同一年,或不同年)
- 输入一个日期,输出它是该年的第几天

作业3:学生成绩统计(文件版扩展)

用结构体数组存储学生信息,包括姓名、学号、多门课成绩。要求:
- 从文件读取(暂时可以先从键盘输入)
- 计算每门课的平均分、最高分、最低分
- 按总成绩排名
- 输出不及格学生名单

作业4:联合体应用——解析IP地址

IP地址通常用点分十进制表示,但也可以看作一个32位整数。用联合体实现:可以分别以整数形式和4个字节形式访问同一个IP。写程序输入一个整数形式的IP,输出点分十进制;或者反过来。(提示:可以用 unsigned intunsigned char 数组共用内存)

作业5:简单通讯录

定义结构体 Contact,包含姓名、电话、邮箱。实现一个简单的通讯录管理系统,支持添加、删除、修改、查找、显示所有联系人。


好了,第八章的内容就到这里!你已经学会了如何用结构体组织不同类型的数据,用联合体节省内存,还了解了枚举类型。这些自定义类型让程序能更好地描述现实世界中的事物。加油!🚀

20260227 120331 Cpp 入门第七课

第七章:让程序更聪明——函数

你好!欢迎来到第七章!在前面的章节,我们写的程序都是顺序执行的,代码都挤在 main 函数里。如果有一段代码需要重复使用,难道要复制粘贴很多遍吗?那太麻烦了!这时候就需要函数——把一段代码打包成一个“积木块”,需要的时候随时调用。这一章我们就来学习如何自己创造函数,让程序更聪明、更简洁!


7.1 函数程序范例

先来看一个简单的例子:我们写一个函数,用来计算两个整数的和,然后在 main 里调用它。

#include <iostream>
using namespace std;

// 定义一个函数,名字叫 add,它的功能是返回两个整数的和
int add(int x, int y) {
    int result = x + y;
    return result;      // 把结果返回给调用者
}

int main() {
    int a = 10, b = 20;
    int sum = add(a, b);   // 调用 add 函数,把 a 和 b 传进去,得到返回值
    cout << "a + b = " << sum << endl;

    int c = 30, d = 40;
    cout << "c + d = " << add(c, d) << endl;   // 也可以直接输出返回值

    return 0;
}

运行结果

a + b = 30
c + d = 70

这个程序里,我们定义了一个 add 函数,它接受两个整数参数,返回它们的和。在 main 中,我们可以多次调用它,不用重复写加法代码。


7.2 函数的用法

7.2.1 函数的概念

函数就是一段可以重复使用的代码块,它有自己的名字,你可以通过这个名字来执行它。就像你有一个“做作业”的流程:拿出作业本、写作业、检查、合上本子。你可以把这个流程打包成一个函数叫 doHomework(),每次想写作业时就调用它。

函数的好处:
- 避免重复代码:相同的逻辑只写一次。
- 模块化:把大问题拆成小问题,每个函数解决一个小问题。
- 便于修改和维护:如果需要修改某个功能,只需改函数内部,不用到处找。

7.2.2 语句块与作用域

语句块就是用大括号 {} 括起来的一组语句。比如 if 语句的后面、循环的后面,还有函数体都是语句块。

作用域指的是变量在程序中的有效范围。在一个语句块内定义的变量,只能在这个块内部使用,块外面是访问不到的。这叫做局部变量

#include <iostream>
using namespace std;

int main() {
    int a = 10;   // 这是 main 函数内的局部变量

    if (a > 5) {
        int b = 20;   // b 只在这个 if 块内有效
        cout << a << " " << b << endl;   // 可以访问 a 和 b
    }

    // cout << b;   // 错误!b 在这里已经不存在了
    return 0;
}

函数也是语句块,函数内部定义的变量只属于这个函数,其他函数不能直接访问。

7.2.3 自定义函数介绍

定义一个函数的基本格式:

返回值类型 函数名(参数列表) {
    // 函数体:要执行的代码
    return 返回值;   // 如果返回值类型不是 void,必须返回对应类型的值
}
  • 返回值类型:函数执行完后要返回什么类型的数据,比如 intdoublechar,如果没有返回值就用 void
  • 函数名:自己起名字,要符合变量命名规则,最好能说明功能。
  • 参数列表:函数需要的输入数据,可以有多个,用逗号分隔,每个参数要写明类型和名字。也可以没有参数,写成 ()(void)
  • 函数体:具体执行的代码。
  • return 语句:把结果返回给调用者。如果返回值类型是 void,可以没有 return,或者只写 return; 表示结束函数。

例子:一个没有参数、没有返回值的函数

#include <iostream>
using namespace std;

// 输出欢迎信息
void sayHello() {
    cout << "你好,欢迎学习C++!" << endl;
}

int main() {
    sayHello();   // 调用函数
    sayHello();   // 可以多次调用
    return 0;
}

例子:有参数但没有返回值

#include <iostream>
using namespace std;

// 输出两个数的和,但不返回结果
void printSum(int x, int y) {
    cout << x << " + " << y << " = " << x + y << endl;
}

int main() {
    printSum(3, 5);
    printSum(10, 20);
    return 0;
}

7.2.4 函数的返回值

return 语句有两个作用:
1. 结束函数的执行,返回到调用它的地方。
2. 把后面的值返回给调用者。

例子:返回较大值的函数

#include <iostream>
using namespace std;

int max(int x, int y) {
    if (x > y) {
        return x;
    } else {
        return y;
    }
}

int main() {
    int a = 15, b = 20;
    int m = max(a, b);
    cout << "较大的数是:" << m << endl;
    return 0;
}

注意:如果函数声明了返回值类型(非 void),那么所有分支都必须有 return,否则编译错误。

7.2.5 函数的形参与实参

  • 形参(形式参数):定义函数时写的参数,就像占位符,告诉调用者需要传什么类型的数据。比如 int add(int x, int y) 中的 xy
  • 实参(实际参数):调用函数时实际传递的值,比如 add(3, 5) 中的 35

调用时,实参会复制给形参,然后在函数内部使用形参。函数内部对形参的修改不会影响实参(除非传的是指针或引用,但初学者先不管)。

#include <iostream>
using namespace std;

void change(int x) {
    x = 100;   // 修改形参
    cout << "函数内部 x = " << x << endl;
}

int main() {
    int a = 10;
    change(a);
    cout << "main 中 a = " << a << endl;   // a 还是 10,没变
    return 0;
}

7.2.6 函数的声明

在C++中,函数必须先声明或定义,然后才能调用。如果函数的定义写在调用之后,就需要提前声明(也叫函数原型)。

#include <iostream>
using namespace std;

// 函数声明,告诉编译器有这个函数,后面再定义
int max(int x, int y);

int main() {
    int a = 5, b = 8;
    cout << max(a, b) << endl;
    return 0;
}

// 函数定义
int max(int x, int y) {
    return (x > y) ? x : y;
}

函数声明只需要写返回值类型、函数名和参数类型,可以省略参数名(但建议保留,便于阅读)。

7.2.7 函数的调用与递归

调用很简单,写函数名加括号和实参即可。

递归:函数自己调用自己。就像俄罗斯套娃,一层套一层。递归必须有一个结束条件,否则会无限循环。

例子:用递归计算阶乘(n! = 1×2×…×n)

#include <iostream>
using namespace std;

int factorial(int n) {
    if (n == 0 || n == 1) {   // 递归结束条件
        return 1;
    } else {
        return n * factorial(n - 1);   // 自己调用自己
    }
}

int main() {
    int n;
    cout << "请输入一个整数:";
    cin >> n;
    cout << n << "! = " << factorial(n) << endl;
    return 0;
}

执行过程(比如 n=3):
- factorial(3) 返回 3 * factorial(2)
- factorial(2) 返回 2 * factorial(1)
- factorial(1) 返回 1
- 然后一步步返回:factorial(2) = 21=2, factorial(3)=32=6。

递归虽然有趣,但初学者容易绕晕。刚开始只要理解概念就好。

7.2.8 数字查找之顺序和二分

这一节结合函数来实现查找算法。

顺序查找

在一个数组中找一个数,从第一个开始一个一个比较,直到找到或找完。

#include <iostream>
using namespace std;

// 顺序查找函数:在数组a中找key,返回下标,找不到返回-1
int sequentialSearch(int a[], int n, int key) {
    for (int i = 0; i < n; i++) {
        if (a[i] == key) {
            return i;   // 找到了,返回下标
        }
    }
    return -1;   // 没找到
}

int main() {
    int arr[] = {34, 67, 12, 89, 45, 23, 56};
    int n = sizeof(arr) / sizeof(arr[0]);
    int key;
    cout << "请输入要查找的数:";
    cin >> key;
    int pos = sequentialSearch(arr, n, key);
    if (pos != -1) {
        cout << "找到了,位置是:" << pos << endl;
    } else {
        cout << "没找到" << endl;
    }
    return 0;
}
二分查找

二分查找要求数组必须是有序的(升序)。它每次都和中间元素比较,如果等于就找到;如果小于中间,就在左半部分找;如果大于,就在右半部分找。这样每次都能排除一半。

#include <iostream>
using namespace std;

// 二分查找函数:在升序数组a中找key,返回下标,找不到返回-1
int binarySearch(int a[], int n, int key) {
    int left = 0, right = n - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;   // 防止溢出
        if (a[mid] == key) {
            return mid;
        } else if (a[mid] < key) {
            left = mid + 1;   // 去右半部分
        } else {
            right = mid - 1;  // 去左半部分
        }
    }
    return -1;
}

int main() {
    int arr[] = {12, 23, 34, 45, 56, 67, 89};   // 必须有序
    int n = sizeof(arr) / sizeof(arr[0]);
    int key;
    cout << "请输入要查找的数:";
    cin >> key;
    int pos = binarySearch(arr, n, key);
    if (pos != -1) {
        cout << "找到了,位置是:" << pos << endl;
    } else {
        cout << "没找到" << endl;
    }
    return 0;
}

对比:顺序查找适用于任何数组,但慢;二分查找快,但要求数组有序。


7.3 编程实例讲解

实例1:判断素数函数

写一个函数,判断一个整数是否是素数(只能被1和自身整除的数)。

#include <iostream>
#include <cmath>   // 用sqrt函数
using namespace std;

bool isPrime(int n) {
    if (n <= 1) return false;
    for (int i = 2; i <= sqrt(n); i++) {
        if (n % i == 0) {
            return false;
        }
    }
    return true;
}

int main() {
    int num;
    cout << "请输入一个整数:";
    cin >> num;
    if (isPrime(num)) {
        cout << num << " 是素数" << endl;
    } else {
        cout << num << " 不是素数" << endl;
    }
    return 0;
}

实例2:求最大公约数函数(辗转相除法)

#include <iostream>
using namespace std;

int gcd(int a, int b) {
    while (b != 0) {
        int temp = a % b;
        a = b;
        b = temp;
    }
    return a;
}

int main() {
    int x, y;
    cout << "请输入两个整数:";
    cin >> x >> y;
    cout << "最大公约数是:" << gcd(x, y) << endl;
    return 0;
}

实例3:递归求斐波那契数列第n项

斐波那契数列:1, 1, 2, 3, 5, 8, 13, … 从第三项起,每一项等于前两项之和。

#include <iostream>
using namespace std;

int fib(int n) {
    if (n == 1 || n == 2) {
        return 1;
    } else {
        return fib(n - 1) + fib(n - 2);
    }
}

int main() {
    int n;
    cout << "请输入项数:";
    cin >> n;
    cout << "第" << n << "项是:" << fib(n) << endl;
    return 0;
}

注意:递归虽然简单,但效率低,因为重复计算很多。可以用循环改进。

实例4:数组排序函数

把冒泡排序封装成函数,可以对任意整数数组排序。

#include <iostream>
using namespace std;

// 冒泡排序函数,升序
void bubbleSort(int a[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (a[j] > a[j + 1]) {
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }
}

// 输出数组函数
void printArray(int a[], int n) {
    for (int i = 0; i < n; i++) {
        cout << a[i] << " ";
    }
    cout << endl;
}

int main() {
    int arr[] = {34, 67, 12, 89, 45};
    int n = sizeof(arr) / sizeof(arr[0]);

    cout << "排序前:";
    printArray(arr, n);

    bubbleSort(arr, n);

    cout << "排序后:";
    printArray(arr, n);

    return 0;
}

7.4 第7章编程作业

恭喜你学完了函数!现在来挑战几个综合题目,检验一下学习成果。

作业1:计算器函数

写四个函数:addsubmuldiv(除法要考虑除数为0的情况)。然后在 main 中让用户输入两个数和运算符,调用对应的函数计算结果。

作业2:数组统计函数

写一个函数,接受一个整数数组和数组长度,返回数组中的最大值、最小值、平均值(可以返回多个值吗?初学者可以用引用参数或返回结构体,但这里可以定义多个函数分别求,或者用全局变量。建议先定义多个函数:int maxArray(int a[], int n)int minArray(...)double avgArray(...))。

作业3:进制转换函数

写一个函数,将一个十进制整数转换为二进制字符串(用string返回)。提示:不断除以2取余,最后反转字符串。

作业4:递归求幂

用递归实现求 x 的 n 次幂(n为非负整数)。x^n = x * x^(n-1),当n=0时返回1。

作业5:猜数字游戏(函数版)

把猜数字游戏的主要逻辑写成函数:比如 void playGame(int maxNumber, int maxTries),在 main 中调用开始游戏。


好了,你已经学会了如何自己创造函数,把程序拆分成一个个小模块,这样代码更清晰、更容易维护。加油!🚀

20260227 120039 Cpp 入门第六课

第六章:处理文字——字符串

你好!欢迎来到第六章!在前面的章节,我们学习了数字和数组。但生活中我们更多接触的是文字,比如名字、句子、文章。在C++中,文字数据叫做字符串。这一章我们会学习如何存储和操作字符串,包括古老的C风格字符串和更方便的C++ string 类。学完本章,你就能编写处理文字的程序了!


6.1 字符串程序范例

我们先来看一个简单的程序,它使用了C++中的 string 类型(最方便的一种字符串)。

#include <iostream>
#include <string>   // 使用string需要包含这个头文件
using namespace std;

int main() {
    string name;    // 定义一个字符串变量,名字叫name

    cout << "请输入你的名字:";
    cin >> name;    // 输入字符串(注意:不能有空格)

    cout << "你好," << name << "!欢迎学习C++字符串!" << endl;

    return 0;
}

运行示例(假设输入“小明”):

请输入你的名字:小明
你好,小明!欢迎学习C++字符串!

这个程序展示了字符串的基本输入输出。接下来我们会深入学习字符串的各种用法。


6.2 字符串的用法

6.2.1 字符的操作

字符串是由一个个字符组成的。在C++中,字符用 char 类型表示。我们先学会操作单个字符。

字符的定义和赋值
char ch1 = 'A';      // 单个字符用单引号括起来
char ch2 = 'b';
char ch3 = '5';
char ch4 = '!';
字符的输入输出
char ch;
cout << "请输入一个字符:";
cin >> ch;           // 输入一个字符
cout << "你输入的字符是:" << ch << endl;
字符的ASCII码

每个字符在计算机中都有一个对应的整数编码,叫做ASCII码。例如 'A' 的ASCII码是65,'a' 是97,'0' 是48。字符可以和整数进行运算。

char ch = 'A';
cout << (int)ch << endl;   // 输出65,将字符转为整数
cout << char(65) << endl;  // 输出A,将整数转为字符
字符的运算

因为字符本质上是整数,所以可以进行加减运算。

char ch = 'A';
ch = ch + 1;                // 变成 'B'
cout << ch << endl;

// 小写转大写
char lower = 'b';
char upper = lower - 32;    // 因为小写字母比大写字母ASCII码大32
cout << upper << endl;      // 输出'B'
常用字符判断(需要包含 <cctype> 头文件)

C++提供了许多判断字符类型的函数,非常方便。

函数 作用
isalpha(ch) 判断是否是字母
isdigit(ch) 判断是否是数字
islower(ch) 判断是否是小写字母
isupper(ch) 判断是否是大写字母
isspace(ch) 判断是否是空格
tolower(ch) 转换为小写
toupper(ch) 转换为大写
#include <iostream>
#include <cctype>   // 字符处理函数
using namespace std;

int main() {
    char ch;
    cout << "请输入一个字符:";
    cin >> ch;

    if (isalpha(ch)) {
        cout << ch << " 是字母" << endl;
        if (islower(ch)) {
            cout << "它是小写字母,大写形式是:" << char(toupper(ch)) << endl;
        } else {
            cout << "它是大写字母,小写形式是:" << char(tolower(ch)) << endl;
        }
    } else if (isdigit(ch)) {
        cout << ch << " 是数字" << endl;
    } else {
        cout << ch << " 是其他字符" << endl;
    }

    return 0;
}

6.2.2 字符数组

字符数组是C语言风格的字符串,就是用一个数组来存放多个字符,最后以 '\0' 结尾。

定义和初始化
char str1[10] = {'H', 'e', 'l', 'l', 'o', '\0'};  // 手动加结束符
char str2[10] = "Hello";        // 自动在末尾加 '\0',最常用
char str3[] = "Hello";          // 编译器自动计算大小为6(包括'\0')
访问和遍历
char str[] = "Hello";
for (int i = 0; i < 5; i++) {
    cout << str[i] << " ";      // 输出 H e l l o
}
cout << endl;

// 也可以用while循环,直到遇到'\0'
int i = 0;
while (str[i] != '\0') {
    cout << str[i];
    i++;
}
修改字符数组中的元素
char str[] = "Hello";
str[0] = 'h';   // 修改第一个字符
cout << str;    // 输出 "hello"

注意:字符数组的大小一旦定义就不能改变,所以要保证足够大。

6.2.3 字符串的输入和输出

C风格字符串的输入
  • cin >> 输入,但遇到空格会停止。
  • cin.getline() 可以输入带空格的整行。
#include <iostream>
using namespace std;

int main() {
    char name[50];
    cout << "请输入你的名字(无空格):";
    cin >> name;                // 只能输入一个单词
    cout << "你好," << name << endl;

    // 清空输入缓冲区(因为后面要用getline,之前cin会留下换行符)
    cin.ignore();

    char sentence[100];
    cout << "请输入一句话(可带空格):";
    cin.getline(sentence, 100); // 读取整行,最多99个字符
    cout << "你输入的是:" << sentence << endl;

    return 0;
}
string类的输入
  • cin >> 输入,同样遇空格停止。
  • getline(cin, 字符串变量) 输入整行。
#include <iostream>
#include <string>
using namespace std;

int main() {
    string name, sentence;

    cout << "请输入你的名字:";
    cin >> name;
    cout << "你好," << name << endl;

    cin.ignore();   // 忽略输入缓冲区中的换行符

    cout << "请输入一句话:";
    getline(cin, sentence);   // 读取整行
    cout << "你输入的是:" << sentence << endl;

    return 0;
}

6.2.4 字符串结束符‘\0’

C风格字符串以 '\0'(空字符,ASCII码0)作为结束标志。它告诉程序字符串在哪里结束。

为什么需要 '\0'

因为字符数组的长度可能大于字符串的实际长度,如果没有结束符,程序就不知道到哪里是字符串的结尾。比如:

char str[10] = "Hello";   // 实际存储:H e l l o \0 ? ? ? ?

当输出 str 时,函数会从第一个字符开始一直输出直到遇到 '\0'。如果忘了加 '\0',可能会输出乱码直到遇到内存中的某个0。

手动添加 '\0'

如果自己逐个字符赋值,必须手动加上 '\0'

char str[5];
str[0] = 'C';
str[1] = '+';
str[2] = '+';
str[3] = '\0';   // 必须加!
cout << str;     // 输出 "C++"

如果忘记加,cout 会继续向后输出内存中的内容,直到偶然遇到0,可能导致程序崩溃或输出乱码。

6.2.5 字符串常用函数

C语言提供了一些处理字符串的函数,它们定义在 <cstring> 头文件中。注意这些函数都用于C风格字符串(字符数组)。

函数 作用
strlen(s) 返回字符串s的长度(不包括\0
strcpy(dest, src) 将src复制到dest
strcat(dest, src) 将src连接到dest的末尾
strcmp(s1, s2) 比较s1和s2,相等返回0,s1s2返回正数
strchr(s, c) 在s中查找字符c第一次出现的位置,返回指针
strstr(s1, s2) 在s1中查找子串s2,返回指针
示例代码
#include <iostream>
#include <cstring>   // 字符串函数
using namespace std;

int main() {
    char s1[20] = "Hello";
    char s2[20] = "World";
    char s3[40];

    // 求长度
    cout << "s1长度:" << strlen(s1) << endl;   // 5

    // 复制
    strcpy(s3, s1);   // s3 = "Hello"
    cout << "s3 = " << s3 << endl;

    // 连接
    strcat(s3, " ");  // 加一个空格
    strcat(s3, s2);   // s3 = "Hello World"
    cout << "连接后:" << s3 << endl;

    // 比较
    if (strcmp(s1, s2) == 0) {
        cout << "s1和s2相等" << endl;
    } else {
        cout << "s1和s2不相等" << endl;
    }

    return 0;
}

注意:使用这些函数时,要确保目标数组足够大,否则会溢出。

6.2.6 string类

C++提供了一个更安全、更方便的字符串类型:string。它包含在头文件 <string> 中。使用 string 类可以像普通变量一样操作字符串,不需要担心数组大小和结束符。

定义和初始化
#include <string>
string s1;                // 空字符串
string s2 = "Hello";      // 直接赋值
string s3("World");       // 构造函数
string s4 = s2;           // 复制
string s5 = s2 + " " + s3; // 连接
常用操作
  • 长度s.length()s.size()
  • 访问字符s[i],下标从0开始
  • 赋值s = "new value";
  • 连接:用 ++=
  • 比较:直接用 ==!=<> 等运算符
  • 输入cin >> sgetline(cin, s)
  • 输出cout << s
示例代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string s1 = "Hello";
    string s2 = "World";
    string s3;

    // 连接
    s3 = s1 + " " + s2;
    cout << "s3 = " << s3 << endl;   // Hello World

    // 长度
    cout << "s3的长度:" << s3.length() << endl;   // 11

    // 访问字符
    for (int i = 0; i < s3.length(); i++) {
        cout << s3[i] << " ";   // H e l l o   W o r l d
    }
    cout << endl;

    // 比较
    if (s1 == s2) {
        cout << "相等" << endl;
    } else {
        cout << "不相等" << endl;
    }

    // 子串
    string sub = s3.substr(6, 5);   // 从下标6开始取5个字符
    cout << "子串:" << sub << endl; // World

    // 查找
    int pos = s3.find("World");
    if (pos != string::npos) {
        cout << "找到World,位置在:" << pos << endl;
    }

    return 0;
}
string类的更多成员函数
  • s.substr(pos, len):返回从pos开始长度为len的子串。
  • s.find(str):查找子串,返回第一次出现的位置,如果找不到返回 string::npos
  • s.replace(pos, len, str):将从pos开始长度为len的子串替换为str。
  • s.insert(pos, str):在pos位置插入str。
  • s.erase(pos, len):删除从pos开始的len个字符。
  • s.empty():判断是否为空。
  • s.clear():清空字符串。

6.2.7 阶段性编程练习(字符串基础)

  1. 练习1:输入一个字符,判断它是字母、数字还是其他字符,并输出对应信息。
  2. 练习2:输入一行字符串(可含空格),统计其中字母、数字、空格的个数。
  3. 练习3:输入一个字符串,将其中的小写字母转为大写,大写字母转为小写,其他字符不变,输出结果。
  4. 练习4:用C风格字符串实现字符串反转(不使用string类的reverse函数)。
  5. 练习5:用string类实现:输入两个字符串,连接后输出,并输出长度。

6.3 编程实例讲解

这里我们通过几个综合实例,进一步巩固字符串的操作。

实例1:统计单词个数(使用string)

题目:输入一行英文句子,统计其中有多少个单词(单词之间用空格分隔)。

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

int main() {
    string s;
    cout << "请输入一句英文:";
    getline(cin, s);   // 读取整行

    int count = 0;
    bool inWord = false;   // 标记是否正在一个单词中

    for (int i = 0; i < s.length(); i++) {
        if (s[i] != ' ') {          // 如果当前字符不是空格
            if (!inWord) {          // 并且之前不在单词中,说明新单词开始
                count++;
                inWord = true;
            }
        } else {                     // 遇到空格,表示单词结束
            inWord = false;
        }
    }

    cout << "单词个数:" << count << endl;
    return 0;
}

实例2:判断回文串

题目:输入一个字符串(忽略空格和标点,只考虑字母和数字),判断它是否是回文(正读反读一样,比如 “madam”、”12321”)。

#include <iostream>
#include <string>
#include <cctype>   // 字符处理函数
using namespace std;

int main() {
    string s;
    cout << "请输入一个字符串:";
    getline(cin, s);

    // 先过滤掉非字母数字字符,并转为小写
    string filtered;
    for (int i = 0; i < s.length(); i++) {
        if (isalnum(s[i])) {          // 如果是字母或数字
            filtered += tolower(s[i]); // 转为小写后加入
        }
    }

    // 判断回文
    bool isPalindrome = true;
    int len = filtered.length();
    for (int i = 0; i < len / 2; i++) {
        if (filtered[i] != filtered[len - 1 - i]) {
            isPalindrome = false;
            break;
        }
    }

    if (isPalindrome) {
        cout << "是回文" << endl;
    } else {
        cout << "不是回文" << endl;
    }

    return 0;
}

实例3:凯撒密码(加密)

题目:输入一个字符串和一个偏移量k,将字符串中的每个英文字母替换为字母表中后移k位的字母(循环,例如z后移1位变成a),其他字符不变,输出加密后的字符串。

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

int main() {
    string s;
    int k;
    cout << "请输入要加密的字符串:";
    getline(cin, s);
    cout << "请输入偏移量:";
    cin >> k;

    for (int i = 0; i < s.length(); i++) {
        char ch = s[i];
        if (ch >= 'a' && ch <= 'z') {
            ch = 'a' + (ch - 'a' + k) % 26;
        } else if (ch >= 'A' && ch <= 'Z') {
            ch = 'A' + (ch - 'A' + k) % 26;
        }
        s[i] = ch;
    }

    cout << "加密后的字符串:" << s << endl;
    return 0;
}

实例4:字符串排序(按字典序)

题目:输入若干个人名(字符串),按字典序从小到大排序后输出。

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

int main() {
    const int N = 5;
    string names[N];
    cout << "请输入" << N << "个人名:" << endl;
    for (int i = 0; i < N; i++) {
        cin >> names[i];   // 假设名字不含空格
    }

    // 冒泡排序
    for (int i = 0; i < N - 1; i++) {
        for (int j = 0; j < N - 1 - i; j++) {
            if (names[j] > names[j + 1]) {   // string可以直接比较
                string temp = names[j];
                names[j] = names[j + 1];
                names[j + 1] = temp;
            }
        }
    }

    cout << "排序后的名字:" << endl;
    for (int i = 0; i < N; i++) {
        cout << names[i] << endl;
    }

    return 0;
}

6.4 第6章编程作业

恭喜你学完了字符串!现在来挑战几个综合题目,检验一下学习成果。

作业1:统计字符串中每个字母出现的次数

输入一行字符串(可含空格),统计其中26个英文字母(不区分大小写)各自出现的次数,并输出。忽略非字母字符。

作业2:简单密码验证

设定一个密码字符串(如 “C++2024”),让用户输入密码,最多允许输入3次,如果正确显示“欢迎”,否则显示“密码错误”并提示剩余次数,3次错误后显示“账户锁定”。

作业3:手机键盘数字映射

在老式手机键盘上,字母映射到数字:2:abc, 3:def, 4:ghi, 5:jkl, 6:mno, 7:pqrs, 8:tuv, 9:wxyz。输入一个单词(只含字母),输出对应的数字串。例如输入 “hello”,输出 “43556”。

作业4:字符串去重

输入一个字符串,去掉其中重复的字符(只保留第一次出现的字符),输出结果。例如输入 “hello world”,输出 “helo wrd”(注意空格也要保留,但重复的空格只保留一个?可以自己定义规则)。

作业5:单词反转

输入一句英文,将句子中的单词顺序反转,但单词内部的字母顺序不变。例如输入 “I love C++”,输出 “C++ love I”。提示:可以用字符串流或手动分割单词。

20260227 120004 Cpp 入门第五课

第五章:批量存储数据——数组

你好!欢迎来到第五章!在前面的章节,我们学会了用变量存储单个数据,比如一个年龄、一个成绩。但如果要存储全班50个人的成绩,难道要定义50个变量吗?那太麻烦了!这时候就需要数组——它可以一次性定义多个相同类型的变量,就像一排带编号的柜子,每个柜子里可以放一个数据。这一章我们就来学习如何使用数组。


5.1 一维数组

5.1.1 数组程序范例

先看一个简单的例子:定义一个能装5个整数的数组,然后给它们赋值并输出。

#include <iostream>
using namespace std;

int main() {
    int a[5];          // 定义一个数组,名字叫a,里面可以放5个整数

    // 给数组的每个元素赋值
    a[0] = 10;         // 第一个格子(下标0)放10
    a[1] = 20;         // 第二个格子(下标1)放20
    a[2] = 30;
    a[3] = 40;
    a[4] = 50;

    // 输出数组的每个元素
    cout << a[0] << " " << a[1] << " " << a[2] << " " << a[3] << " " << a[4] << endl;

    return 0;
}

运行结果

10 20 30 40 50

5.1.2 数组的用法

什么是数组?

数组就是一组相同类型的数据的集合,它们在内存中连续存放。每个数据叫做数组元素,通过下标(也叫索引)来访问。下标从0开始编号。

  • 定义格式:数据类型 数组名[元素个数];
    例如:int score[5]; 定义了一个名为score的数组,可以存5个整数。

  • 访问元素:数组名[下标],下标范围是0到元素个数-1。
    例如:score[0] = 98; 给第一个元素赋值。

数组的初始化

可以在定义时直接赋值:

int a[5] = {10, 20, 30, 40, 50};   // 全部初始化
int b[] = {1, 2, 3, 4, 5};          // 不写个数,编译器自动计算为5
int c[5] = {1, 2};                   // 只给前两个赋值,后面3个默认为0
使用循环遍历数组

数组通常和循环一起使用,因为可以用循环变量作为下标。

int a[5] = {10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++) {
    cout << a[i] << " ";   // 依次输出每个元素
}
数组的注意事项
  • 下标不能越界,比如定义 int a[5];,只能使用 a[0]a[4],使用 a[5] 会导致未定义行为(可能程序崩溃)。
  • 数组一旦定义,大小不能改变。

5.1.3 编程实例讲解

实例1:从键盘输入5个整数,求它们的和与平均值
#include <iostream>
using namespace std;

int main() {
    int a[5];
    int sum = 0;

    cout << "请输入5个整数:";
    for (int i = 0; i < 5; i++) {
        cin >> a[i];          // 输入存到数组
        sum += a[i];          // 累加
    }

    double avg = (double)sum / 5;   // 平均值
    cout << "和:" << sum << ",平均值:" << avg << endl;

    return 0;
}
实例2:找出数组中的最大值和最小值
#include <iostream>
using namespace std;

int main() {
    int a[5] = {34, 67, 12, 89, 45};
    int max = a[0];   // 假设第一个是最大值
    int min = a[0];   // 假设第一个是最小值

    for (int i = 1; i < 5; i++) {   // 从第二个开始比较
        if (a[i] > max) {
            max = a[i];
        }
        if (a[i] < min) {
            min = a[i];
        }
    }

    cout << "最大值:" << max << endl;
    cout << "最小值:" << min << endl;

    return 0;
}
实例3:数组元素逆序(把数组反过来)
#include <iostream>
using namespace std;

int main() {
    int a[5] = {1, 2, 3, 4, 5};
    int temp;

    // 交换对称位置的元素
    for (int i = 0; i < 5 / 2; i++) {
        temp = a[i];
        a[i] = a[4 - i];   // 4是最后一个元素的下标
        a[4 - i] = temp;
    }

    cout << "逆序后的数组:";
    for (int i = 0; i < 5; i++) {
        cout << a[i] << " ";
    }
    cout << endl;

    return 0;
}

运行结果:5 4 3 2 1

5.1.4 阶段性编程练习

  1. 练习1:定义一个包含10个整数的数组,用循环给数组赋值为1到10,然后输出。
  2. 练习2:输入8个整数,存入数组,然后输出所有大于平均数的数。
  3. 练习3:输入10个整数,查找某个数(由用户输入)是否在数组中,如果在,输出它的位置(下标),否则输出“未找到”。
  4. 练习4:输入一个正整数n(≤20),再输入n个整数,将它们从小到大输出(可以先不管排序,直接输出,排序下一节学)。但这里可以先练习用两个循环选择最小输出,或者简单用冒泡排序提前体验。

5.2 数组排序

排序就是把一组数按从小到大(升序)或从大到小(降序)排列。我们这里学习最简单的冒泡排序

5.2.1 排序程序范例

#include <iostream>
using namespace std;

int main() {
    int a[5] = {34, 67, 12, 89, 45};
    int n = 5;

    // 冒泡排序
    for (int i = 0; i < n - 1; i++) {           // 外层循环控制比较的轮数
        for (int j = 0; j < n - 1 - i; j++) {   // 内层循环控制每轮比较的次数
            if (a[j] > a[j + 1]) {               // 如果前一个比后一个大,交换
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }

    cout << "排序后的数组:";
    for (int i = 0; i < n; i++) {
        cout << a[i] << " ";
    }
    cout << endl;

    return 0;
}

运行结果:12 34 45 67 89

5.2.2 数组排序的用法

冒泡排序的原理

想象有一排泡泡,轻的往上浮,重的往下沉。冒泡排序就是每次比较相邻的两个数,如果顺序不对(比如前大后小,我们要升序),就交换它们。这样每一轮都会把最大的数“沉”到最后。

  • 第一轮:从第一个元素开始,依次比较相邻的两个,如果前>后,交换。第一轮结束后,最大的数就到了最后。
  • 第二轮:再从第一个开始,比较到倒数第二个(因为最后一个已经最大了),把第二大的数放到倒数第二。
  • 重复n-1轮,所有数就排好了。
代码解释
  • 外层循环 i 控制轮数,一共需要 n-1 轮(因为最后剩下一个不用排)。
  • 内层循环 j 控制比较范围,每一轮比较的范围逐渐缩小,因为末尾已经排好的元素不用再比。所以 j 从0到 n-1-i。
  • 如果 a[j] > a[j+1],交换。
其他排序方法(拓展了解)
  • 选择排序:每一轮找到最小(或最大)的元素,放到前面。
  • 插入排序:像打扑克牌,每次把一张牌插入到已排好序的手牌中。

但初学者先掌握冒泡排序就好。

5.2.3 编程实例讲解

实例4:输入n个数,升序输出
#include <iostream>
using namespace std;

int main() {
    int n;
    cout << "请输入数字个数:";
    cin >> n;
    int a[100];   // 假设最多100个数

    cout << "请输入" << n << "个整数:";
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }

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

    cout << "排序后:";
    for (int i = 0; i < n; i++) {
        cout << a[i] << " ";
    }
    cout << endl;

    return 0;
}
实例5:降序排序(从大到小)

只需要把比较条件 a[j] > a[j+1] 改成 a[j] < a[j+1] 即可。

if (a[j] < a[j + 1]) {   // 如果前一个小于后一个,交换,让大的往前移
    // 交换
}
实例6:对字符串数组排序(了解)

数组也可以是字符串类型,比如 string names[5]; 排序方法和整数类似,直接用 >< 比较字符串(按字典序)。

5.2.4 阶段性编程练习

  1. 练习1:输入10个整数,用冒泡排序将它们从大到小输出。
  2. 练习2:输入n个学生的成绩,排序后输出,并输出最高分和最低分。
  3. 练习3:用选择排序实现升序排列(自学选择排序算法并实现)。
  4. 练习4:输入n个整数,去掉重复的数字后输出(提示:可以先排序,然后输出时跳过相邻相同的)。

5.3 二维数组

5.3.1 二维数组程序范例

二维数组就像一张表格,有行和列。比如定义一个3行4列的二维数组,并输出:

#include <iostream>
using namespace std;

int main() {
    int a[3][4] = {      // 3行4列
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    // 用双重循环输出每个元素
    for (int i = 0; i < 3; i++) {        // i控制行
        for (int j = 0; j < 4; j++) {    // j控制列
            cout << a[i][j] << "\t";     // \t 是制表符,对齐
        }
        cout << endl;                     // 每行结束换行
    }

    return 0;
}

运行结果

1   2   3   4
5   6   7   8
9   10  11  12

5.3.2 二维数组的用法

定义二维数组

格式:数据类型 数组名[行数][列数];
例如:int score[5][3]; 表示5行3列,可以存放5个学生3门课的成绩。

初始化
  • 按行初始化:
    cpp int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
  • 连续赋值(会自动按行填):
    cpp int a[2][3] = {1, 2, 3, 4, 5, 6}; // 等价于上面
  • 部分初始化:未指定的元素默认为0。
访问元素

使用两个下标:数组名[行下标][列下标],行和列都从0开始。
例如:a[1][2] 表示第2行第3列的元素(在数学中常称为第2行第3列,但在编程中下标从0开始,所以实际是第2行第3个)。

遍历二维数组

几乎总是用嵌套循环:外层循环遍历行,内层循环遍历列。

5.3.3 编程实例讲解

实例7:输入一个3×4矩阵,求所有元素的和
#include <iostream>
using namespace std;

int main() {
    int a[3][4];
    int sum = 0;

    cout << "请输入3行4列的矩阵:" << endl;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            cin >> a[i][j];
            sum += a[i][j];
        }
    }

    cout << "所有元素的和:" << sum << endl;
    return 0;
}
实例8:求矩阵中的最大值及其位置
#include <iostream>
using namespace std;

int main() {
    int a[3][4] = {
        {34, 56, 12, 89},
        {23, 45, 67, 90},
        {11, 22, 33, 44}
    };
    int max = a[0][0];
    int max_i = 0, max_j = 0;   // 记录最大值的位置

    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            if (a[i][j] > max) {
                max = a[i][j];
                max_i = i;
                max_j = j;
            }
        }
    }

    cout << "最大值:" << max << ",位于第" << max_i+1 << "行第" << max_j+1 << "列" << endl;
    return 0;
}
实例9:矩阵转置(行列互换)

假设有一个3行2列的矩阵,转置后变成2行3列。

#include <iostream>
using namespace std;

int main() {
    int a[3][2] = {{1, 2}, {3, 4}, {5, 6}};
    int b[2][3];   // 转置后的矩阵

    // 转置:b[j][i] = a[i][j]
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
            b[j][i] = a[i][j];
        }
    }

    // 输出转置后的矩阵
    cout << "转置后的矩阵:" << endl;
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            cout << b[i][j] << " ";
        }
        cout << endl;
    }

    return 0;
}

运行结果

转置后的矩阵:
1 3 5
2 4 6

5.3.4 阶段性编程练习

  1. 练习1:定义一个4×4的二维数组,用循环赋值为1到16,然后按矩阵形式输出。
  2. 练习2:输入一个3×3矩阵,计算主对角线(从左上到右下)上元素的和。
  3. 练习3:输入两个2×3矩阵,求它们的和(对应位置相加),输出结果矩阵。
  4. 练习4:输入一个5×5矩阵,判断它是否关于主对角线对称(即转置后等于原矩阵)。

5.4 第5章编程作业

恭喜你学完了数组!现在来挑战几个综合题目,综合运用一维、二维数组和排序。

作业1:成绩统计系统

输入一个班的学生人数n(≤30),再输入每个学生的姓名和5门课的成绩。要求:
- 计算每个学生的总分和平均分
- 按总分从高到低排序,并输出排名、姓名、总分、平均分
- 输出每门课的平均分(全班平均)

提示:可以用多个一维数组,或者结构体(但还没学结构体,可以用平行数组:名字数组、总分数组等,排序时同时交换名字和总分)。或者先简单做,只处理成绩。

作业2:矩阵乘法

输入两个矩阵,第一个是m×n,第二个是n×p,计算它们的乘积并输出。m,n,p都不超过10。

作业3:约瑟夫问题

有n个人围成一圈,从第1个人开始报数,数到m的人出列,然后从下一个人继续报数,直到所有人都出列。输出出列顺序。n和m由用户输入。(提示:可以用数组标记是否出列,用循环模拟)

作业4:统计单词

输入一行英文句子(包含空格),统计其中有多少个单词,并输出每个单词的长度。例如输入 “I love C++ programming”,输出单词个数4,以及每个单词的长度(1 4 3 11)。提示:可以用字符串数组,但还没学,可以用字符数组,用循环判断空格。

作业5:杨辉三角(二维数组版)

用二维数组存储杨辉三角的前n行(n由用户输入),然后输出。杨辉三角每个数等于它上方两数之和。

20260227 085717 Cpp 入门第四课

第四章:让程序重复做事——循环结构

你好!欢迎来到第四章!在前面的章节,我们学会了让程序做判断。但是,如果想让程序重复做同一件事很多次,比如输出100遍“你好”,难道要写100行cout吗?当然不用!这时候就需要循环。循环就像跑步,一圈一圈重复,直到达到目标才停下。这一章我们会学习三种循环:forwhiledo...while,还有控制循环的小技巧。


4.1 for循环

4.1.1 for循环程序范例

for 循环就像体育老师喊口令:“从第1个同学开始,到第10个同学结束,每个同学报数!” 我们来看一个程序,输出1到10:

#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i <= 10; i++) {
        cout << i << " ";
    }
    return 0;
}

运行结果

1 2 3 4 5 6 7 8 9 10

4.1.2 for循环的用法

for 循环的格式:

for (初始化; 条件; 更新) {
    // 循环体:要重复执行的代码
}
  • 初始化:在循环开始前执行一次,通常用来定义循环变量并赋初值,比如 int i = 1
  • 条件:每次循环开始前判断,如果为真,就执行循环体;如果为假,就退出循环。比如 i <= 10
  • 更新:每次循环体执行完后执行,通常用来改变循环变量的值,比如 i++(i增加1)。

执行顺序
1. 执行初始化(只一次)
2. 判断条件 → 如果真,进入循环体;如果假,结束循环
3. 执行循环体
4. 执行更新
5. 回到第2步

循环变量变化过程

比如上面例子:
- 初始 i=1,判断 1<=10 真 → 输出1,然后 i++ 变成2
- 判断 2<=10 真 → 输出2,i++ 变3
- … 直到 i=10 输出10,i++ 变11
- 判断 11<=10 假 → 退出循环

4.1.3 编程实例讲解

实例1:求1到100的和
#include <iostream>
using namespace std;

int main() {
    int sum = 0;          // 累加器,初始为0
    for (int i = 1; i <= 100; i++) {
        sum = sum + i;    // 把i加到sum上
    }
    cout << "1+2+...+100 = " << sum << endl;
    return 0;
}

运行结果:5050

实例2:输出偶数

题目:输出1到20之间的所有偶数。

#include <iostream>
using namespace std;

int main() {
    for (int i = 2; i <= 20; i += 2) {   // i每次增加2
        cout << i << " ";
    }
    return 0;
}

运行结果

2 4 6 8 10 12 14 16 18 20
实例3:倒序输出

题目:输出10到1。

#include <iostream>
using namespace std;

int main() {
    for (int i = 10; i >= 1; i--) {
        cout << i << " ";
    }
    return 0;
}

运行结果

10 9 8 7 6 5 4 3 2 1

4.1.4 阶段性编程练习

  1. 练习1:用for循环输出1到50,每行5个数。(提示:可以用if (i % 5 == 0) cout << endl;换行)
  2. 练习2:求1到100所有奇数的和。
  3. 练习3:输入一个整数n,计算n的阶乘(n! = 1×2×3×…×n)。
  4. 练习4:输出所有的“水仙花数”。水仙花数是指一个三位数,其各位数字的立方和等于该数本身,如153 = 1³+5³+3³。(提示:用循环遍历100-999,分离个十百位判断)

4.2 while循环

4.2.1 while循环程序范例

while 循环就像“当条件满足时,就一直做某事”。比如“当还有糖果时,就吃一颗”。看这个例子,输出1到10:

#include <iostream>
using namespace std;

int main() {
    int i = 1;           // 初始化
    while (i <= 10) {    // 条件
        cout << i << " ";
        i++;             // 更新
    }
    return 0;
}

运行结果:和for循环一样。

4.2.2 while循环的用法

格式:

while (条件) {
    // 循环体
}
  • 先判断条件,如果为真,执行循环体;然后再次判断,直到条件为假退出。
  • 注意:循环体内要有改变条件的语句,否则会变成死循环(无限循环)。

4.2.3 编程实例讲解

实例4:计算1到n的和(n由用户输入)
#include <iostream>
using namespace std;

int main() {
    int n, i = 1, sum = 0;
    cout << "请输入一个整数:";
    cin >> n;

    while (i <= n) {
        sum += i;   // 等价于 sum = sum + i
        i++;
    }
    cout << "1+2+...+" << n << " = " << sum << endl;
    return 0;
}
实例5:统计位数

题目:输入一个正整数,统计它是几位数。比如输入12345,输出5。

#include <iostream>
using namespace std;

int main() {
    int num, count = 0;
    cout << "请输入一个正整数:";
    cin >> num;

    while (num > 0) {
        num = num / 10;   // 去掉最后一位
        count++;          // 位数加1
    }
    cout << "它是" << count << "位数" << endl;
    return 0;
}

解释:每次除以10,就减少一位,直到变成0。

4.2.4 阶段性编程练习

  1. 练习1:用while循环输出1到50的平方数(1², 2², … 50²)。
  2. 练习2:输入一个整数,倒序输出它(比如输入123,输出321)。不能用数组,只能用循环。
  3. 练习3:求两个数的最大公约数(用辗转相除法,提示:用while循环,条件是b!=0,循环内求余并交换)。
  4. 练习4:猜数字游戏(用while循环,直到猜中为止)。随机生成一个1-100的数,用户猜,提示“大了”“小了”,直到猜中,统计猜的次数。

4.3 do…while循环

4.3.1 do…while循环程序范例

do...while 循环和 while 类似,但它是先执行一次循环体,再判断条件。就像“先做一次,然后看是否要继续做”。比如:

#include <iostream>
using namespace std;

int main() {
    int i = 1;
    do {
        cout << i << " ";
        i++;
    } while (i <= 10);
    return 0;
}

4.3.2 do…while循环的用法

格式:

do {
    // 循环体
} while (条件);
  • 先执行一次循环体,然后判断条件。如果条件为真,继续下一次循环;如果为假,退出。
  • 注意:最后的分号不能少。
  • 特点:至少执行一次循环体,即使条件一开始为假。
对比while
  • while:先判断,可能一次都不执行。
  • do...while:先执行,至少执行一次。

4.3.3 编程实例讲解

实例6:输入密码,直到正确

题目:假设密码是123456,让用户输入密码,如果错误就提示重新输入,直到正确。

#include <iostream>
using namespace std;

int main() {
    int password, correct = 123456;
    do {
        cout << "请输入密码:";
        cin >> password;
        if (password != correct) {
            cout << "密码错误,请重新输入。" << endl;
        }
    } while (password != correct);

    cout << "密码正确,欢迎进入系统!" << endl;
    return 0;
}
实例7:求平均分(至少输入一次)

题目:输入若干个成绩,以-1结束,计算平均分(保证至少输入一个有效成绩)。

#include <iostream>
using namespace std;

int main() {
    int score, sum = 0, count = 0;
    do {
        cout << "请输入成绩(-1结束):";
        cin >> score;
        if (score != -1) {
            sum += score;
            count++;
        }
    } while (score != -1);

    if (count > 0) {
        double avg = (double)sum / count;
        cout << "平均分:" << avg << endl;
    }
    return 0;
}

4.3.4 阶段性编程练习

  1. 练习1:用do…while循环输出1到10的立方。
  2. 练习2:编写一个菜单程序,显示选项,让用户选择,直到用户选择退出。(类似:1.开始游戏 2.设置 3.退出,输入3退出)
  3. 练习3:输入一个正整数,用do…while循环判断它是否是素数(提示:用i从2到sqrt(n)试除,一旦整除就标记不是素数)。
  4. 练习4:模拟ATM取款,初始余额1000元,每次输入取款金额,如果余额不足提示,直到输入0退出。

4.4 continue和break

4.4.1 continue和break程序范例

有时候我们需要在循环中间提前结束本次循环或整个循环。break 用来跳出整个循环,continue 用来跳过本次循环剩下的部分,直接进入下一次循环。

#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i <= 10; i++) {
        if (i == 5) {
            break;    // 当i=5时,跳出整个循环
        }
        cout << i << " ";
    }
    cout << endl;

    for (int i = 1; i <= 10; i++) {
        if (i == 5) {
            continue; // 当i=5时,跳过输出,继续下一次循环
        }
        cout << i << " ";
    }
    return 0;
}

运行结果

1 2 3 4 
1 2 3 4 6 7 8 9 10

4.4.2 continue和break的用法

  • break:立即终止当前循环(for、while、do…while、switch),程序继续执行循环后面的代码。
  • continue:跳过本次循环中剩余的代码,直接进入下一次循环(对于for循环,会先执行更新部分,再判断条件)。
使用场景
  • break:当找到想要的结果后,没必要继续循环了。
  • continue:当遇到某些不需要处理的情况时,跳过本次,继续处理下一个。

4.4.3 编程实例讲解

实例8:找出第一个能被7整除的数

题目:从1开始找,找到第一个能被7整除的数就停止。

#include <iostream>
using namespace std;

int main() {
    int i = 1;
    while (true) {   // 无限循环,用break退出
        if (i % 7 == 0) {
            cout << "第一个能被7整除的数是:" << i << endl;
            break;
        }
        i++;
    }
    return 0;
}
实例9:输出1-20中不能被3整除的数
#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i <= 20; i++) {
        if (i % 3 == 0) {
            continue;   // 跳过输出
        }
        cout << i << " ";
    }
    return 0;
}

4.4.4 阶段性编程练习

  1. 练习1:输出1-100中所有能被5整除但不能被3整除的数。
  2. 练习2:求两个数的最大公约数,用循环和break优化(找到即停止)。
  3. 练习3:输入一个正整数,判断它是否是素数(用break优化,一旦发现整除就跳出循环)。
  4. 练习4:模拟一个简单的加法考试,随机出10道题,每道题答对加10分,答错可以继续做下一题,最后显示得分。如果中途输入-1,则提前退出考试。

4.5 嵌套循环

4.5.1 嵌套循环程序范例

循环里面再套循环,就是嵌套循环。最常见的是打印图形。比如打印一个3行5列的矩形:

#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i <= 3; i++) {        // 控制行数
        for (int j = 1; j <= 5; j++) {    // 控制列数
            cout << "* ";
        }
        cout << endl;                     // 每行结束后换行
    }
    return 0;
}

运行结果

* * * * * 
* * * * * 
* * * * * 

4.5.2 嵌套循环的用法

  • 外层循环执行一次,内层循环会完整执行一轮。
  • 常用于处理二维结构,如打印图形、遍历表格等。
  • 注意循环变量的作用域和命名,通常用 i, j, k 表示。
执行过程:

外层 i=1,内层 j 从1到5输出5个星号,然后换行;
外层 i=2,内层 j 再从1到5输出5个星号,换行;
外层 i=3,同样。

4.5.3 编程实例讲解

实例10:打印直角三角形

题目:输入行数n,打印一个直角三角形,第一行1个,第二行2个,…第n行n个*。

#include <iostream>
using namespace std;

int main() {
    int n;
    cout << "请输入行数:";
    cin >> n;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) {   // 每行输出i个星号
            cout << "* ";
        }
        cout << endl;
    }
    return 0;
}

运行示例(n=5):

* 
* * 
* * * 
* * * * 
* * * * * 
实例11:打印九九乘法表
#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <= i; j++) {
            cout << j << "×" << i << "=" << i * j << "\t"; // \t是制表符,用于对齐
        }
        cout << endl;
    }
    return 0;
}
实例12:冒泡排序(简单介绍)

虽然还没学数组,但可以先看看嵌套循环的威力:排序。这里用最简单的冒泡排序对5个数排序。

#include <iostream>
using namespace std;

int main() {
    int a[5] = {5, 3, 8, 1, 2};   // 数组,暂时理解为5个盒子
    int n = 5;

    // 冒泡排序
    for (int i = 0; i < n - 1; i++) {           // 外层控制比较轮数
        for (int j = 0; j < n - 1 - i; j++) {   // 内层控制每轮比较次数
            if (a[j] > a[j + 1]) {               // 如果前面的比后面的大,交换
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }

    // 输出排序后的结果
    for (int i = 0; i < n; i++) {
        cout << a[i] << " ";
    }
    return 0;
}

运行结果:1 2 3 5 8

4.5.4 阶段性编程练习

  1. 练习1:打印倒直角三角形:第一行n个,第二行n-1个,…最后一行1个*。
  2. 练习2:打印菱形(输入奇数行数,打印菱形图案)。
  3. 练习3:输出100以内的所有素数(用嵌套循环,外层遍历2-100,内层判断每个数是否是素数)。
  4. 练习4:模拟一个简单的“猜数字”游戏升级版:程序随机生成一个4位数(每位不重复),用户猜,每次给出提示:数字正确且位置正确的个数,数字正确但位置不对的个数。用嵌套循环实现比较。

4.6 第4章编程作业

恭喜你学完了循环!现在来挑战几个综合题目,检验一下学习成果。

作业1:斐波那契数列

斐波那契数列:1, 1, 2, 3, 5, 8, 13, … 从第三项开始,每一项等于前两项之和。输入一个正整数n,输出前n项。

作业2:百钱百鸡问题

公鸡5元一只,母鸡3元一只,小鸡1元三只。用100元买100只鸡,问公鸡、母鸡、小鸡各多少只?用循环枚举所有可能。

作业3:求完数

完数是指一个数恰好等于它的因子之和(不包括自身)。例如6 = 1+2+3。找出1000以内的所有完数。

作业4:猜数字游戏(带难度选择)

实现一个猜数字游戏,可以选择难度:
- 简单:1-50,猜10次
- 中等:1-100,猜7次
- 困难:1-200,猜5次
每次提示猜大了还是猜小了,如果次数用完还没猜中,游戏结束。

作业5:打印杨辉三角

输入行数n,打印杨辉三角(等腰三角形格式)。例如n=5:

    1
   1 1
  1 2 1
 1 3 3 1
1 4 6 4 1

提示:可以用二维数组或组合数公式,但这里要求只用循环(需要一点数学知识,可以选做)。


好了,第四章的内容就到这里!你已经学会了如何让程序重复做事,这是编程中非常强大的能力。加油!🚀

20260227 085102 Cpp 入门第三课

第三章:让程序学会判断——分支结构

你好!欢迎来到第三章!在前两章,我们学会了让计算机输入、输出和计算。但程序只会按顺序执行,就像一条直线。如果想让程序根据不同情况做出不同反应,就需要用到分支结构。这一章我们会学习如何让程序“动脑筋”,根据条件决定做什么。就像你在生活中,如果下雨就带伞,如果晴天就去公园,程序也可以这样!


3.1 if语句

3.1.1 if语句程序范例

最简单的判断就是:如果……就……。看看这个程序,它判断你的年龄是否满18岁。

#include <iostream>
using namespace std;

int main() {
    int age;
    cout << "请输入你的年龄:";
    cin >> age;

    if (age >= 18) {
        cout << "你已经成年了!" << endl;
    }

    cout << "程序结束。" << endl;
    return 0;
}

运行结果示例1(输入20):

请输入你的年龄:20
你已经成年了!
程序结束。

运行结果示例2(输入15):

请输入你的年龄:15
程序结束。

3.1.2 if语句的用法

if 语句的格式:

if (条件) {
    // 条件为真时执行的语句
}
  • 条件:通常是一个比较表达式,比如 age >= 18score == 100。条件的结果要么是(true),要么是(false)。
  • 如果条件为真,就执行大括号 {} 里的代码;如果为假,就跳过这些代码,继续执行后面的语句。
  • 如果大括号里只有一条语句,可以省略大括号,但初学者建议都加上,避免出错。
比较运算符
运算符 含义 例子
== 等于 a == b
!= 不等于 a != b
> 大于 a > b
< 小于 a < b
>= 大于等于 a >= b
<= 小于等于 a <= b

注意:判断是否相等要用两个等号 ==,一个等号 = 是赋值,千万不能搞混!

3.1.3 编程实例讲解

实例1:判断奇偶数

题目:输入一个整数,如果它是偶数,就输出“偶数”。

#include <iostream>
using namespace std;

int main() {
    int num;
    cout << "请输入一个整数:";
    cin >> num;

    if (num % 2 == 0) {   // 能被2整除就是偶数
        cout << num << " 是偶数" << endl;
    }

    return 0;
}
实例2:判断是否及格

题目:输入一门课的成绩(0-100),如果大于等于60,输出“及格”。

#include <iostream>
using namespace std;

int main() {
    int score;
    cout << "请输入成绩:";
    cin >> score;

    if (score >= 60) {
        cout << "恭喜,及格了!" << endl;
    }

    return 0;
}

3.1.4 阶段性编程练习

  1. 练习1:输入一个整数,如果它是负数,输出“这是一个负数”。
  2. 练习2:输入一个整数,如果它能被5整除,输出“能被5整除”。
  3. 练习3:输入一个字符,如果它是大写字母(’A’到’Z’),输出“大写字母”。(提示:字符比较可以用 ch >= 'A' && ch <= 'Z',但逻辑运算符还没学,可以先不练,或者用后面知识。)
  4. 练习4:输入两个整数,如果第一个数大于第二个数,输出“第一个数更大”。

3.2 if…else语句

3.2.1 if…else程序范例

if 只能处理条件为真的情况,但如果条件为假也想做点什么,就需要 if...else,就像“如果……就……否则……”。

#include <iostream>
using namespace std;

int main() {
    int age;
    cout << "请输入你的年龄:";
    cin >> age;

    if (age >= 18) {
        cout << "你已经成年了,可以看电影!" << endl;
    } else {
        cout << "你还未成年,需要家长陪同。" << endl;
    }

    return 0;
}

运行示例(输入15):

请输入你的年龄:15
你还未成年,需要家长陪同。

3.2.2 if…else语句的用法

格式:

if (条件) {
    // 条件为真时执行的代码
} else {
    // 条件为假时执行的代码
}
  • ifelse 是成对出现的。
  • 条件为真,执行 if 后面的代码块;条件为假,执行 else 后面的代码块。
  • 两者只会执行其中一个。

3.2.3 编程实例讲解

实例3:判断奇偶,输出不同信息
#include <iostream>
using namespace std;

int main() {
    int num;
    cout << "请输入一个整数:";
    cin >> num;

    if (num % 2 == 0) {
        cout << num << " 是偶数" << endl;
    } else {
        cout << num << " 是奇数" << endl;
    }

    return 0;
}
实例4:判断是否通过考试
#include <iostream>
using namespace std;

int main() {
    int score;
    cout << "请输入成绩:";
    cin >> score;

    if (score >= 60) {
        cout << "通过考试!" << endl;
    } else {
        cout << "未通过,继续努力!" << endl;
    }

    return 0;
}

3.2.4 阶段性编程练习

  1. 练习1:输入一个整数,判断它是否大于0,输出“正数”或“非正数”(包括0和负数)。
  2. 练习2:输入一个年份,判断它是否是闰年。闰年条件:能被4整除但不能被100整除,或者能被400整除。(提示:条件较复杂,可以先留到学了逻辑运算再做,或者直接给出条件。)
  3. 练习3:输入一个整数,判断它是否是7的倍数,输出相应信息。
  4. 练习4:输入两个整数,输出较大的那个数。

3.3 分支的嵌套

3.3.1 分支嵌套程序范例

有时候,我们需要在一个判断里面再做判断,就像“如果下雨,就带伞;如果雨很大,就穿雨衣”。这就是分支嵌套

#include <iostream>
using namespace std;

int main() {
    int score;
    cout << "请输入你的成绩:";
    cin >> score;

    if (score >= 60) {
        cout << "及格了!";
        if (score >= 90) {
            cout << "而且成绩优秀!" << endl;
        } else {
            cout << "继续加油!" << endl;
        }
    } else {
        cout << "不及格,要努力了!" << endl;
    }

    return 0;
}

运行示例(输入95):

及格了!而且成绩优秀!

3.3.2 分支嵌套的用法

  • ifelse 的代码块里,可以再放其他的 if...else 语句。
  • 嵌套的层数不要太多,否则代码会变得难读。一般两层就够用了。

注意else 总是和它上面最近的 if 配对。为了避免混乱,一定要用好大括号 {}

3.3.3 编程实例讲解

实例5:根据分数评定等级

题目:输入成绩(0-100),输出等级:
- 90-100:优秀
- 80-89:良好
- 70-79:中等
- 60-69:及格
- 0-59:不及格

#include <iostream>
using namespace std;

int main() {
    int score;
    cout << "请输入成绩:";
    cin >> score;

    if (score >= 60) {
        if (score >= 90) {
            cout << "优秀" << endl;
        } else if (score >= 80) {   // 这里用了else if,是多重选择,但也可以嵌套
            cout << "良好" << endl;
        } else if (score >= 70) {
            cout << "中等" << endl;
        } else {
            cout << "及格" << endl;
        }
    } else {
        cout << "不及格" << endl;
    }

    return 0;
}

这个例子其实混合了嵌套和 else if,我们会在下一节正式学习多重选择。

实例6:判断三个数中的最大值
#include <iostream>
using namespace std;

int main() {
    int a, b, c;
    cout << "请输入三个整数:";
    cin >> a >> b >> c;

    if (a >= b) {
        if (a >= c) {
            cout << "最大值是:" << a << endl;
        } else {
            cout << "最大值是:" << c << endl;
        }
    } else {
        if (b >= c) {
            cout << "最大值是:" << b << endl;
        } else {
            cout << "最大值是:" << c << endl;
        }
    }

    return 0;
}

3.3.4 阶段性编程练习

  1. 练习1:输入三个整数,输出最小值。(参考上面的最大值)
  2. 练习2:输入一个年份,判断它是否是闰年,并输出对应的信息。如果是闰年,再判断它是否是世纪闰年(能被400整除)。
  3. 练习3:输入一个整数x,求分段函数的值:
    - 当 x > 0 时,y = 2x + 1
    - 当 x = 0 时,y = 0
    - 当 x < 0 时,y = x - 1
    输出y的值。
  4. 练习4:模拟自动售货机:输入金额(整数),如果金额大于等于商品价格(比如5元),则输出“购买成功”,并找零;否则输出“金额不足”。(可以嵌套判断,比如金额足够时再判断是否恰好等值)

3.4 多重选择分支

3.4.1 多重选择分支程序范例

当有多个条件需要依次判断时,可以用 if...else if...else 结构。就像成绩分等级一样。

#include <iostream>
using namespace std;

int main() {
    int score;
    cout << "请输入成绩:";
    cin >> score;

    if (score >= 90) {
        cout << "优秀" << endl;
    } else if (score >= 80) {
        cout << "良好" << endl;
    } else if (score >= 70) {
        cout << "中等" << endl;
    } else if (score >= 60) {
        cout << "及格" << endl;
    } else {
        cout << "不及格" << endl;
    }

    return 0;
}

3.4.2 多重选择分支的用法

格式:

if (条件1) {
    // 条件1为真执行
} else if (条件2) {
    // 条件1为假且条件2为真执行
} else if (条件3) {
    // 条件1、2为假且条件3为真执行
} else {
    // 所有条件都为假执行
}
  • 程序从上往下判断,遇到第一个为真的条件,就执行对应的代码块,然后跳出整个 if...else if...else 结构。
  • 最后的 else 是可选的,表示所有条件都不满足时做什么。

3.4.3 编程实例讲解

实例7:根据月份判断季节

题目:输入月份(1-12),输出对应的季节:
- 3-5月:春季
- 6-8月:夏季
- 9-11月:秋季
- 12,1,2月:冬季

#include <iostream>
using namespace std;

int main() {
    int month;
    cout << "请输入月份:";
    cin >> month;

    if (month >= 3 && month <= 5) {
        cout << "春季" << endl;
    } else if (month >= 6 && month <= 8) {
        cout << "夏季" << endl;
    } else if (month >= 9 && month <= 11) {
        cout << "秋季" << endl;
    } else if (month == 12 || month == 1 || month == 2) {
        cout << "冬季" << endl;
    } else {
        cout << "月份输入错误" << endl;
    }

    return 0;
}
实例8:简单的计算器(加减乘除)
#include <iostream>
using namespace std;

int main() {
    double a, b;
    char op;   // 运算符
    cout << "请输入表达式(如 3 + 5):";
    cin >> a >> op >> b;

    if (op == '+') {
        cout << a + b << endl;
    } else if (op == '-') {
        cout << a - b << endl;
    } else if (op == '*') {
        cout << a * b << endl;
    } else if (op == '/') {
        if (b != 0) {
            cout << a / b << endl;
        } else {
            cout << "除数不能为0" << endl;
        }
    } else {
        cout << "不支持的运算符" << endl;
    }

    return 0;
}

3.4.4 阶段性编程练习

  1. 练习1:输入一个整数,判断它是正数、负数还是零。
  2. 练习2:输入一个0-6的数字,输出对应的星期几(0代表星期日,1代表星期一,以此类推)。
  3. 练习3:输入一个年份和月份,输出该月的天数(考虑闰年2月)。
  4. 练习4:编写一个程序,根据用户输入的消费金额,计算折扣后的应付金额:
    - 满100元打9折
    - 满200元打8折
    - 满300元打7折
    - 其他情况无折扣

3.5 switch语句

3.5.1 switch语句程序范例

当判断的条件是整型字符型固定值时,用 switch 语句更清晰。比如根据数字输出星期几。

#include <iostream>
using namespace std;

int main() {
    int day;
    cout << "请输入星期几(1-7):";
    cin >> day;

    switch (day) {
        case 1:
            cout << "星期一" << endl;
            break;
        case 2:
            cout << "星期二" << endl;
            break;
        case 3:
            cout << "星期三" << endl;
            break;
        case 4:
            cout << "星期四" << endl;
            break;
        case 5:
            cout << "星期五" << endl;
            break;
        case 6:
            cout << "星期六" << endl;
            break;
        case 7:
            cout << "星期日" << endl;
            break;
        default:
            cout << "输入错误,请输入1-7之间的数字" << endl;
    }

    return 0;
}

3.5.2 switch语句的用法

格式:

switch (表达式) {
    case 常量1:
        语句;
        break;
    case 常量2:
        语句;
        break;
    ...
    default:
        语句;
}
  • 表达式的结果必须是整型(int、char等),不能是浮点型或字符串。
  • case 常量:常量必须是固定的值,不能是变量。
  • 程序会找到与表达式值相等的 case,然后从那里开始执行,直到遇到 breakswitch 结束。
  • break 用来跳出 switch,如果不写 break,会继续执行下一个 case 的代码(这叫做“穿透”)。
  • default 是可选的,当所有 case 都不匹配时执行。

3.5.3 编程实例讲解

实例9:根据成绩等级给出评语
#include <iostream>
using namespace std;

int main() {
    char grade;
    cout << "请输入成绩等级(A、B、C、D):";
    cin >> grade;

    switch (grade) {
        case 'A':
            cout << "优秀,继续努力!" << endl;
            break;
        case 'B':
            cout << "良好,再接再厉!" << endl;
            break;
        case 'C':
            cout << "中等,要加油!" << endl;
            break;
        case 'D':
            cout << "及格边缘,需加强!" << endl;
            break;
        default:
            cout << "无效等级" << endl;
    }

    return 0;
}
实例10:简易四则运算(用switch)
#include <iostream>
using namespace std;

int main() {
    double a, b;
    char op;
    cout << "请输入表达式(如 3+5):";
    cin >> a >> op >> b;

    switch (op) {
        case '+':
            cout << a + b << endl;
            break;
        case '-':
            cout << a - b << endl;
            break;
        case '*':
            cout << a * b << endl;
            break;
        case '/':
            if (b != 0)
                cout << a / b << endl;
            else
                cout << "除数不能为0" << endl;
            break;
        default:
            cout << "不支持的运算符" << endl;
    }

    return 0;
}

3.5.4 阶段性编程练习

  1. 练习1:输入一个数字1-12,输出对应的月份英文名(January, February…)。
  2. 练习2:输入一个字符,判断它是元音字母(a, e, i, o, u,包括大小写)还是辅音字母。(提示:多个case可以共用一段代码,比如 case 'a': case 'e': ...
  3. 练习3:模拟一个简单的菜单:
    - 输入1:显示“开始游戏”
    - 输入2:显示“加载存档”
    - 输入3:显示“退出游戏”
    - 其他:显示“无效选项”
  4. 练习4:输入两个整数和一个运算符(+、-、*、/、%),用switch完成运算。

3.6 逻辑运算

3.6.1 逻辑运算程序范例

有时候一个条件需要多个判断组合,比如“成绩在80到90之间”需要同时满足大于等于80且小于等于90。这就需要逻辑运算符。

#include <iostream>
using namespace std;

int main() {
    int score;
    cout << "请输入成绩:";
    cin >> score;

    if (score >= 80 && score <= 90) {   // 并且
        cout << "成绩良好" << endl;
    }

    if (score < 60 || score > 100) {    // 或者
        cout << "成绩异常" << endl;
    }

    if (!(score >= 60)) {                // 非
        cout << "不及格" << endl;
    }

    return 0;
}

3.6.2 逻辑运算的用法

运算符 名称 含义 例子
&& 两边都为真,结果才为真 a>0 && a<10
|| 两边至少一个为真,结果为真 a==0 || b==0
! 取反,真变假,假变真 !(a>b)

优先级! 最高,然后是 &&,最后是 ||。可以用括号改变顺序。

真值表(简单理解)
  • true && truetrue
  • true && falsefalse
  • false && truefalse
  • false && falsefalse
  • true || truetrue
  • true || falsetrue
  • false || truetrue
  • false || falsefalse
  • !truefalse
  • !falsetrue

3.6.3 编程实例讲解

实例11:判断闰年(用逻辑运算)
#include <iostream>
using namespace std;

int main() {
    int year;
    cout << "请输入年份:";
    cin >> year;

    if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
        cout << year << " 是闰年" << endl;
    } else {
        cout << year << " 不是闰年" << endl;
    }

    return 0;
}
实例12:判断三角形类型

题目:输入三条边长,判断是否能构成三角形,如果能,是等边、等腰还是普通三角形。

#include <iostream>
using namespace std;

int main() {
    double a, b, c;
    cout << "请输入三条边长:";
    cin >> a >> b >> c;

    // 三角形条件:任意两边之和大于第三边
    if (a + b > c && a + c > b && b + c > a) {
        if (a == b && b == c) {
            cout << "等边三角形" << endl;
        } else if (a == b || a == c || b == c) {
            cout << "等腰三角形" << endl;
        } else {
            cout << "普通三角形" << endl;
        }
    } else {
        cout << "不能构成三角形" << endl;
    }

    return 0;
}

3.6.4 阶段性编程练习

  1. 练习1:输入一个整数,判断它是否同时满足:能被3整除,且能被5整除。
  2. 练习2:输入三个整数,判断它们是否都相等。
  3. 练习3:输入一个字符,判断它是否是大写字母或小写字母。
  4. 练习4:输入一个年份和月份,判断该月有多少天(使用逻辑运算处理闰年2月)。

3.7 第3章编程作业

恭喜你学完了分支结构!来挑战几个综合题目吧。

作业1:简单的猜拳游戏

编写程序,让用户输入剪刀(0)、石头(1)、布(2),电脑随机出拳(用 rand() % 3),判断胜负并输出结果。提示:需要随机数种子 srand(time(0))

作业2:个人所得税计算器

输入月收入(整数),计算应缴个人所得税。假设税率如下:
- 不超过3000元的部分,税率3%
- 超过3000至12000元的部分,税率10%
- 超过12000至25000元的部分,税率20%
- 超过25000元的部分,税率25%

输出应缴税额(保留两位小数)。比如月收入10000,计算方法是:30003% + (10000-3000)10% = 90 + 700 = 790元。

作业3:日期合法性判断

输入年、月、日,判断这个日期是否合法(考虑闰年2月天数)。

作业4:求解一元二次方程

输入一元二次方程的系数 a, b, c(a≠0),计算判别式 Δ = b² - 4ac,根据Δ的情况输出根:

  • Δ > 0:两个不同的实根
  • Δ = 0:两个相等的实根
  • Δ < 0:无实根

输出根的值(如果有),保留两位小数。


好了,第三章的内容就到这里!你已经学会了让程序做判断。加油!