实验一 现代C++编程初体验

实验任务1 标准库反转与旋转

程序源代码 task1.cpp

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

template<typename T>
void output(const T &c) {
    for (const auto &item : c) std::cout << item << ' ';
    std::cout << '\n';
}

void test1() {
    using namespace std;
    string s0{"0123456789"};
    cout << "s0 = " << s0 << '\n';
    string s1(s0);
    reverse(s1.begin(), s1.end());
    cout << "s1 = " << s1 << '\n';
    string s2(s0.size(), ' ');
    reverse_copy(s0.begin(), s0.end(), s2.begin());
    cout << "s2 = " << s2 << '\n';
}

void test2() {
    using namespace std;
    vector<int> v0{2, 0, 4, 9};
    cout << "v0: "; output(v0);
    vector<int> v1{v0};
    reverse(v1.begin(), v1.end());
    cout << "v1: "; output(v1);
    vector<int> v2{v0};
    reverse_copy(v0.begin(), v0.end(), v2.begin());
    cout << "v2: "; output(v2);
}

void test3() {
    using namespace std;
    vector<int> v0{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    cout << "v0: "; output(v0);
    vector<int> v1{v0};
    rotate(v1.begin(), v1.begin() + 1, v1.end());
    cout << "v1: "; output(v1);
    vector<int> v2{v0};
    rotate(v2.begin(), v2.begin() + 2, v2.end());
    cout << "v2: "; output(v2);
    vector<int> v3{v0};
    rotate(v3.begin(), v3.end() - 1, v3.end());
    cout << "v3: "; output(v3);
    vector<int> v4{v0};
    rotate(v4.begin(), v4.end() - 2, v4.end());
    cout << "v4: "; output(v4);
}

int main() {
    std::cout << "测试1:\n"; test1();
    std::cout << "\n测试2:\n"; test2();
    std::cout << "\n测试3:\n"; test3();
    return 0;
}

实际运行记录

屏幕截图 2026-10-20 164650

观察与思考

1. reverse 和 reverse_copy 有什么区别?

我的理解是,两个函数都能实现元素顺序的反转,但保存结果的位置不同。reverse 直接修改原来的字符串或数组;reverse_copy 则把反转后的内容放到另一个位置,原来的内容不变。

例如,原字符串是 "12345",使用 reverse 后,原字符串变成 "54321";使用 reverse_copy 后,原字符串还是 "12345",目标字符串变成 "54321"。使用后者时,要提前给目标字符串准备足够的空间。

2. rotate 算法是如何改变元素顺序的?它的三个参数分别代表什么?

rotate 实现的是循环移动。可以把序列从指定位置分成前后两部分,再把后半部分放到前面,前半部分放到后面,各部分内部的顺序不变。

三个参数中,第一个表示操作的起始位置,第二个指定哪个位置的元素移动后成为第一个元素,第三个表示操作区间最后一个元素的后面一个位置,这个位置本身不参与操作。

例如,对 0 1 2 3 4 使用 rotate(v.begin(), v.begin() + 2, v.end()),结果是 2 3 4 0 1,相当于循环左移两位。

实验任务2 随机数 排序与统计

程序源代码 task2.cpp

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <iomanip>
#include <cstdlib>
#include <ctime>

template<typename T>
void output(const T &c) {
    for (const auto &item : c) std::cout << item << ' ';
    std::cout << '\n';
}

int generate_random_number() {
    return std::rand() % 101;
}

void test1() {
    using namespace std;
    vector<int> v0(10);
    generate(v0.begin(), v0.end(), generate_random_number);
    cout << "v0: "; output(v0);
    vector<int> v1{v0};
    sort(v1.begin(), v1.end());
    cout << "v1: "; output(v1);
    vector<int> v2{v0};
    sort(v2.begin() + 1, v2.end() - 1);
    cout << "v2: "; output(v2);
}

void test2() {
    using namespace std;
    vector<int> v0(10);
    generate(v0.begin(), v0.end(), generate_random_number);
    cout << "v0: "; output(v0);
    auto min_iter = min_element(v0.begin(), v0.end());
    auto max_iter = max_element(v0.begin(), v0.end());
    cout << "最小值: " << *min_iter << '\n';
    cout << "最大值: " << *max_iter << '\n';
    auto ans = minmax_element(v0.begin(), v0.end());
    cout << "最小值: " << *ans.first << '\n';
    cout << "最大值: " << *ans.second << '\n';
    double avg1 = accumulate(v0.begin(), v0.end(), 0.0) / v0.size();
    cout << "均值: " << fixed << setprecision(2) << avg1 << '\n';
    sort(v0.begin(), v0.end());
    double avg2 = accumulate(v0.begin() + 1, v0.end() - 1, 0.0)
                  / (v0.size() - 2);
    cout << "去掉最大值、最小值之后,均值: " << avg2 << '\n';
}

int main() {
    std::srand(static_cast<unsigned>(std::time(nullptr)));
    std::cout << "测试1:\n"; test1();
    std::cout << "\n测试2:\n"; test2();
    return 0;
}

实际运行记录

屏幕截图 2026-10-20 210244

观察与思考

1. generate 算法的作用是什么?

generate 的作用是按照指定的生成规则,给一个区间内的元素依次赋值。它每处理一个位置,就调用一次生成函数,并把返回值放到这个位置。

本题中,vector 已经有10个元素,所以会调用10次随机数生成函数,给这10个元素分别赋值。它不会自动增加容器中的元素数量。

2. minmax_element 和分别调用 min_element、max_element 相比,有什么优势?

min_element 用来找最小值的位置,max_element 用来找最大值的位置,而 minmax_element 一次调用就能同时得到这两个位置,写起来更简洁,也能减少比较次数。

返回结果中的 first 表示最小值的位置,second 表示最大值的位置,前面加上 * 才能取出对应的数值。我理解它比较适合同时需要最大值和最小值的情况。

3. 改成 lambda 表达式后,效果是否等同?如何理解这种写法?

改成 [](){ return std::rand() % 101; } 后,功能是等同的,仍然是每次生成一个0~100范围内的整数。

我把 lambda 表达式理解成一个可以直接写在使用位置的“没有名字的小函数”。这里的 [] 表示不捕获外部局部变量,() 表示不接收参数,花括号里写的是函数执行的内容。

对于这种代码很短、只在这里使用的功能,lambda 可以省去单独声明和定义函数的步骤。如果函数比较复杂,或者需要在多个地方使用,我觉得单独定义函数会更清楚。另外,两次运行产生的随机数可能不同,这并不说明两种写法的功能不同。

实验任务3 字符串变换

程序源代码 task3.cpp

#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>

unsigned char func(unsigned char c) {
    if (c == 'z') return 'a';
    if (c == 'Z') return 'A';
    if (std::isalpha(c)) return static_cast<unsigned char>(c + 1);
    return c;
}

void test1() {
    std::string s1{"Hello World 2049!"};
    std::cout << "s1 = " << s1 << '\n';
    std::string s2;
    for (unsigned char c : s1)
        s2 += static_cast<char>(std::tolower(c));
    std::cout << "s2 = " << s2 << '\n';
    std::string s3;
    for (unsigned char c : s1)
        s3 += static_cast<char>(std::toupper(c));
    std::cout << "s3 = " << s3 << '\n';
}

void test2() {
    std::string s1{"I love cosmos!"};
    std::cout << "s1 = " << s1 << '\n';
    std::string s2(s1.size(), ' ');
    std::transform(s1.begin(), s1.end(), s2.begin(), func);
    std::cout << "s2 = " << s2 << '\n';
}

int main() {
    std::cout << "测试1: 字符串大小写转换\n"; test1();
    std::cout << "\n测试2: 字符变换\n"; test2();
    return 0;
}

实际运行记录

屏幕截图 2026-10-20 212911

观察与思考

1. 自定义函数 func 的功能是什么?

func 的功能是把英文字母替换成字母表中的下一个字母,并保留原来的大小写。例如,a 变成 b,A 变成 B。

对于最后一个字母,需要单独处理:z 变成 a,Z 变成 A。数字、空格和标点符号保持不变。因此,"I love cosmos!" 经过处理后会变成 "J mpwf dptnpt!"。

2. tolower 和 toupper 的功能分别是什么?

tolower 用来把大写字母转换成对应的小写字母,toupper 用来把小写字母转换成对应的大写字母。数字、空格和标点符号等不需要进行大小写转换的字符保持不变。

这两个函数一次处理一个字符,所以本题通过循环,依次处理字符串中的每个字符,完成整个字符串的大小写转换。

3. transform 的4个参数分别表示什么?把第三个参数 s2.begin() 改成 s1.begin() 有什么区别?

结合本题,四个参数分别表示:

  • s1.begin():从原字符串的第一个字符开始处理。
  • s1.end():处理到原字符串末尾,所指的尾后位置不参与处理。
  • s2.begin():从目标字符串的第一个位置开始保存结果。
  • func:规定每个字符按照什么规则进行转换。

第三个参数是 s2.begin() 时,转换结果保存在 s2 中,s1 保持不变。改成 s1.begin() 后,结果就直接写回 s1,原字符串本身会被修改。

这时 s2 仍然是初始化时的空格,所以如果想查看转换结果,也要把输出对象改成 s1。

实验任务4 回文串判断

程序源代码 task4.cpp

#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>

bool is_palindrome(const std::string &s);
bool is_palindrome_ignore_case(const std::string &s);

int main() {
    std::string s;

    // 将布尔值输出为 true 或 false。
    std::cout << std::boolalpha;

    // 判断5组字符串和1组单字母,共6组,处理完后自动结束。
    for (int i = 0; i < 6; ++i) {
        if (!(std::cin >> s)) {
            return 0;
        }
        std::cout << "区分大小写: "
                  << is_palindrome(s) << '\n';
        std::cout << "不区分大小写: "
                  << is_palindrome_ignore_case(s) << "\n\n";
    }

    return 0;
}

// 区分大小写:将副本反转,再与原字符串比较。
bool is_palindrome(const std::string &s) {
    std::string temp = s;
    std::reverse(temp.begin(), temp.end());
    return temp == s;
}

// 不区分大小写:先统一转成小写,再判断回文。
bool is_palindrome_ignore_case(const std::string &s) {
    std::string temp = s;

    for (char &c : temp) {
        c = static_cast<char>(
            std::tolower(static_cast<unsigned char>(c))
        );
    }

    return is_palindrome(temp);
}

实际运行记录

屏幕截图 2026-10-20 221008

观察与思考

如果希望测试的字符串包含空格,代码应该如何调整?

原来的 cin >> s 遇到空格就会结束本次字符串读取。例如输入 "hello oop",第一次只能读到 "hello",不能把整行作为一个字符串处理。

可以把 while (std::cin >> s) 改成 while (std::getline(std::cin, s))。这样每次会读取完整的一行,字符串中间的空格也能保留下来,同时仍然支持多组输入。

需要注意,读取进来的空格也会参与回文判断,程序不会自动忽略它们。如果前面还使用过 cin >> 读取其他数据,需要先处理输入中残留的换行符,再使用 getline。

实验任务5 进制转换

程序源代码 task5.cpp

#include <iostream>
#include <string>
#include <algorithm>

std::string dec2n(int x, int n = 2);

int main() {
    int x;

    // 输入3个非负整数,分别输出进制转换结果。
    for (int i = 0; i < 3; ++i) {
        if (!(std::cin >> x)) {
            return 0;
        }

        if (x < 0) {
            std::cout << "请输入非负整数。\n";
            --i;
            continue;
        }

        std::cout << "十进制: " << x << '\n'
                  << "二进制: " << dec2n(x) << '\n'
                  << "八进制: " << dec2n(x, 8) << '\n'
                  << "十二进制: " << dec2n(x, 12) << '\n'
                  << "十六进制: " << dec2n(x, 16) << '\n'
                  << "三十二进制: " << dec2n(x, 32) << "\n\n";
    }

    // 3组结果输出完后,输入一个字母(例如q)并回车结束。
    char end;
    std::cin >> end;

    return 0;
}

std::string dec2n(int x, int n) {
    if (x < 0 || n < 2 || n > 36) {
        return "参数错误";
    }

    if (x == 0) {
        return "0";
    }

    const std::string digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    std::string result;

    // 每次取出的余数按从低位到高位的顺序保存。
    while (x > 0) {
        result += digits[x % n];
        x /= n;
    }

    // 反转后得到正常的高位在前的表示。
    std::reverse(result.begin(), result.end());
    return result;
}

实际运行记录

屏幕截图 2026-10-20 222558

实验任务6 字母密文对照表

程序源代码 task6.cpp

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

int main() {
    // 输出表头:小写字母 a~z
    cout << "   ";
    for (int j = 0; j < 26; ++j) {
        cout << char('a' + j) << ' ';
    }
    cout << '\n';

    // 输出第 1~26 行
    for (int i = 1; i <= 26; ++i) {
        cout << setw(2) << i << ' ';

        for (int j = 0; j < 26; ++j) {
            cout << char('A' + (j + i) % 26) << ' ';
        }

        cout << '\n';
    }

    return 0;
}

实际运行记录

屏幕截图 2026-10-20 222834

实验任务7 算术题生成与自动评测

程序源代码 task7.cpp

#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <ctime>
#include <algorithm>
#include <limits>

int main() {
    std::srand(static_cast<unsigned>(std::time(nullptr)));
    const int total = 10;
    const char symbols[] = {'+', '-', '*', '/'};
    int correct = 0;
    for (int i = 1; i <= total; ++i) {
        int a = std::rand() % 10 + 1;
        int b = std::rand() % 10 + 1;
        int op = std::rand() % 4;
        int expected = 0;
        if (op == 0) {
            expected = a + b;
        } else if (op == 1) {
            if (a < b) std::swap(a, b);
            expected = a - b;
        } else if (op == 2) {
            expected = a * b;
        } else {
            // 两个数始终在[1, 10]内,重新抽取直到能够整除。
            while (a % b != 0) {
                a = std::rand() % 10 + 1;
                b = std::rand() % 10 + 1;
            }
            expected = a / b;
        }
        std::cout << "第" << i << "题: " << a << ' '
                  << symbols[op] << ' ' << b << " = ";
        int answer;
        while (!(std::cin >> answer)) {
            if (std::cin.eof()) return 0;
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            std::cout << "请输入整数答案: ";
        }
        if (answer == expected) ++correct;
    }
    std::cout << "答对 " << correct << " 道,共 " << total << " 道。\n"
              << "正确率: " << std::fixed << std::setprecision(2)
              << 100.0 * correct / total << "%\n";
    return 0;
}

实际运行记录

屏幕截图 2026-10-20 223513

实验总结

本实验的几个算法都通过迭代器区间操作数据,区间通常采用左闭右开形式 [first, last)。把数据存储交给 string 和 vector,把反转、排序、变换等操作交给标准库,可以让程序更直接地表达目的。使用前仍应明确算法是否修改原数据、目标空间是否足够,以及输入是否满足前提。

回文判断可以通过“复制、变换、比较”组合实现;进制转换需要注意余数的产生顺序和零值情况;随机算术题则需要分别处理减法和除法的约束。平均值和正确率应确保使用浮点运算,多组输入应明确结束方式。

posted @ 2026-10-20 22:43  睦紫米  阅读(5)  评论(0)    收藏  举报