C++ 语言练习题(15 题)

学了语法得动手写才算真的会。这一页给 15 道精心挑选的练习题,按难度分三级——基础 6 题(变量 / 运算 / 控制流 / 函数)、进阶 5 题(模板 / STL / 智能指针)、实战 4 题(综合应用)。每题都含题目描述、提示、可折叠的参考答案,答案都是完整的、本地 g++ 一编就能跑的程序。

使用建议:先别急着展开答案。读题后自己用本地编辑器(或 在线 C++ 编译器)试着写,卡住了再看提示,实在写不出再对照参考答案。C++ 既有 C 的底层又有现代语言的高级抽象——同一题能用三种风格(C 风格、面向对象、模板泛型)写出,推荐都试一遍。

基础(6 题):变量 / 运算 / 控制流 / 函数

这一组覆盖最基础的语言元素,目标是用 STL 容器(std::string、std::vector)替代 C 风格的裸数组,训练"现代 C++"的肌肉记忆。

第 1 题:翻转字符串

题目:用 std::string 写一个翻转字符串的函数。比如 "hello" → "olleh"。

提示:std::reverse(s.begin(), s.end()) 原地翻转(需要 #include <algorithm>);也可以下标循环用 std::swap 交换首尾。std::string 比裸 char[] 安全得多——长度自动管理、支持赋值与拷贝。

参考答案
#include <iostream>
#include <string>
#include <algorithm>

// 方法 1:std::reverse 原地翻转(std::string 是可变容器)
std::string reverse1(std::string s) {
    std::reverse(s.begin(), s.end());
    return s;
}

// 方法 2:下标循环手写(双指针交换)
std::string reverse2(std::string s) {
    for (size_t i = 0; i < s.size() / 2; i++)
        std::swap(s[i], s[s.size() - 1 - i]);
    return s;
}

int main() {
    std::cout << reverse1("hello") << "\n";      // olleh
    std::cout << reverse2("你好世界") << "\n";    // 界世好你
    return 0;
}

第 2 题:判断素数

题目:写一个函数 bool is_prime(int n),判断正整数 n 是否为素数。

提示:小于 2 的都不是素数;只需检查从 2 到 sqrt(n) 的整除性——超过平方根的因子必然配对一个更小的、已经检查过的因子。用 #include <cmath> 引入 std::sqrt,返回类型用 bool(C++ 原生支持)。

参考答案
#include <iostream>
#include <cmath>

bool is_prime(int n) {
    if (n < 2) return false;
    for (int i = 2; i <= static_cast<int>(std::sqrt(n)); i++) {
        if (n % i == 0) return false;
    }
    return true;
}

int main() {
    std::cout << is_prime(7) << "\n";    // 1
    std::cout << is_prime(10) << "\n";   // 0
    std::cout << is_prime(2) << "\n";    // 1
    return 0;
}

第 3 题:FizzBuzz

题目:用 std::cout 打印 1 到 15。3 的倍数打印 Fizz,5 的倍数打印 Buzz,同时是 3 和 5 的倍数打印 FizzBuzz,其余打印数字本身。

提示:if / else if / else 的判断顺序很关键——必须先判"同时是 3 和 5 的倍数"(即 15 的倍数),否则会被前面的分支截胡。std::cout << i 比 C 的 printf("%d", i) 更安全(类型自动推导)。

参考答案
#include <iostream>

int main() {
    for (int i = 1; i <= 15; i++) {
        if (i % 15 == 0)        // 必须先判 15
            std::cout << "FizzBuzz\n";
        else if (i % 3 == 0)
            std::cout << "Fizz\n";
        else if (i % 5 == 0)
            std::cout << "Buzz\n";
        else
            std::cout << i << "\n";
    }
    return 0;
}
// 输出:
// 1
// 2
// Fizz
// 4
// Buzz
// Fizz
// 7
// 8
// Fizz
// Buzz
// 11
// Fizz
// 13
// 14
// FizzBuzz

第 4 题:vector 最大值(遍历)

题目:不使用 std::max_element,写一个函数 int find_max(const std::vector<int>& v),返回 vector 中的最大值。

提示:引用传参 const std::vector<int>& 避免拷贝;范围 for 循环 for (int x : v) 比 C 的下标循环更简洁;做完后也可以试试 STL 的 *std::max_element(v.begin(), v.end())。

参考答案
#include <iostream>
#include <vector>
#include <algorithm>

int find_max(const std::vector<int>& v) {
    int m = v[0];
    for (size_t i = 1; i < v.size(); i++)
        if (v[i] > m) m = v[i];
    return m;
}

int main() {
    std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
    std::cout << find_max(v) << "\n";      // 9(手写遍历)
    std::cout << *std::max_element(v.begin(), v.end()) << "\n";  // 9(STL)
    return 0;
}

第 5 题:vector 求和(循环或 std::accumulate)

题目:写一段代码,对 std::vector<int> 求和。要求用循环和 std::accumulate 两种写法。

提示:循环版用范围 for;std::accumulate(v.begin(), v.end(), 初值) 在 <numeric> 头文件——注意初值的类型决定返回类型(初值 0 是 int,初值 0LL 才会得到 long long)。

参考答案
#include <iostream>
#include <vector>
#include <numeric>      // std::accumulate

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

    // 方法 1:循环
    int sum1 = 0;
    for (int x : v) sum1 += x;

    // 方法 2:std::accumulate(初值 0 决定返回类型)
    int sum2 = std::accumulate(v.begin(), v.end(), 0);

    std::cout << sum1 << "\n";    // 15
    std::cout << sum2 << "\n";    // 15
    return 0;
}

第 6 题:计算阶乘

题目:写函数 long long factorial(int n),返回 n!。要求 factorial(0) == 1。同时用循环和递归两种写法实现。

提示:int 装不下 13!,所以返回类型用 long long;递归版用三目运算 return n <= 1 ? 1 : n * factorial_rec(n-1) 更紧凑。

参考答案
#include <iostream>

// 方法 1:循环
long long factorial(int n) {
    long long r = 1;
    for (int i = 2; i <= n; i++) r *= i;
    return r;
}

// 方法 2:递归
long long factorial_rec(int n) {
    return n <= 1 ? 1 : n * factorial_rec(n - 1);
}

int main() {
    std::cout << factorial(5) << "\n";      // 120
    std::cout << factorial(0) << "\n";      // 1
    std::cout << factorial_rec(6) << "\n";  // 720
    return 0;
}

进阶(5 题):模板 / STL / 智能指针

这一组是 C++ 区别于 C 的核心——模板泛型、STL 容器算法、RAII 智能指针。掌握它们才能写出真正的现代 C++ 代码,而不是"C with Classes"。

第 7 题:函数模板 my_max

题目:写一个函数模板 template<typename T> T my_max(T a, T b),对 int、double、std::string 都能工作。

提示:模板由编译器根据实参类型在调用处自动实例化(my_max(3, 7) 生成 my_max<int>);只需写一份代码,类型安全且没有 C 宏 #define max(a,b) 的副作用。

参考答案
#include <iostream>
#include <string>

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

int main() {
    std::cout << my_max(3, 7) << "\n";                                  // 7
    std::cout << my_max(3.14, 2.71) << "\n";                            // 3.14
    std::cout << my_max(std::string("apple"), std::string("banana")) << "\n";  // banana
    return 0;
}
// 编译器会按实参类型自动生成 my_max<int>/my_max<double>/my_max<std::string> 三个版本。

第 8 题:类模板 Stack<T>

题目:实现一个栈类模板 Stack<T>,支持 push / pop / top / empty。栈空时 pop 抛 std::out_of_range。

提示:内部用 std::vector<T> 当底层容器(不要自己管理内存);push_back / pop_back / back 对应栈操作;抛异常用 throw std::out_of_range("...")(<stdexcept>)。

参考答案
#include <iostream>
#include <vector>
#include <stdexcept>

template<typename T>
class Stack {
public:
    void push(const T& v) { data_.push_back(v); }
    void pop() {
        if (data_.empty()) throw std::out_of_range("pop on empty stack");
        data_.pop_back();
    }
    T top() const {
        if (data_.empty()) throw std::out_of_range("top on empty stack");
        return data_.back();
    }
    bool empty() const { return data_.empty(); }
private:
    std::vector<T> data_;     // 用 vector 当底层容器
};

int main() {
    Stack<int> s;
    s.push(1); s.push(2); s.push(3);
    while (!s.empty()) {
        std::cout << s.top() << "\n";   // 后进先出:3 2 1
        s.pop();
    }
    return 0;
}
// 输出:
// 3
// 2
// 1

第 9 题:STL vector + sort + find

题目:对一个 std::vector<int> 做排序、查找:先升序排序打印,再用 std::find 找某个值的下标。

提示:std::sort(v.begin(), v.end()) 默认升序,时间复杂度 O(n log n);std::find 返回迭代器,找不到等于 v.end(),下标用 it - v.begin() 计算。

参考答案
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> v = {5, 2, 8, 1, 9, 3};

    std::sort(v.begin(), v.end());    // 默认升序
    std::cout << "排序后:";
    for (int x : v) std::cout << x << " ";
    std::cout << "\n";

    auto it = std::find(v.begin(), v.end(), 8);    // find 返回迭代器
    if (it != v.end())
        std::cout << "找到 8 在下标 " << (it - v.begin()) << "\n";
    return 0;
}
// 输出:
// 排序后:1 2 3 5 8 9
// 找到 8 在下标 4

第 10 题:std::map 词频统计

题目:给定一段英文文本,统计每个单词的出现次数,按字典序打印 word: count。

提示:std::map<std::string, int> 自动按 key 升序;用 std::istringstream 按空格分词;freq[word]++ 中的 operator[] 会在 key 不存在时自动插入 0;遍历可用 C++17 结构化绑定 for (const auto& [w, n] : freq)。

参考答案
#include <iostream>
#include <map>
#include <sstream>
#include <string>

int main() {
    std::string text = "the quick brown fox the lazy dog the fox";

    // map<string,int> 会自动按 key(string)字典序排序
    std::map<std::string, int> freq;
    std::istringstream iss(text);
    std::string word;
    while (iss >> word) freq[word]++;      // operator[] 自动初始化为 0

    for (const auto& [w, n] : freq)         // C++17 结构化绑定
        std::cout << w << ": " << n << "\n";
    return 0;
}
// 输出(已按 key 字典序):
// brown: 1
// dog: 1
// fox: 2
// lazy: 1
// quick: 1
// the: 3

第 11 题:std::unique_ptr 管理动态数组

题目:用 std::make_unique<int[]>(n) 分配一个长度为 5 的动态数组,填入平方值并打印。不要写一行 delete。

提示:RAII——资源获取即初始化;std::unique_ptr<int[]> 离开作用域时自动调用 delete[],永远不会内存泄漏;也能用下标 arr[i] 访问(特化版本重载了 operator[])。

参考答案
#include <iostream>
#include <memory>

int main() {
    // make_unique<int[]>(5) 分配并默认初始化 5 个 int
    std::unique_ptr<int[]> arr = std::make_unique<int[]>(5);

    for (int i = 0; i < 5; i++) arr[i] = i * i;
    for (int i = 0; i < 5; i++) std::cout << arr[i] << " ";
    std::cout << "\n";
    return 0;
    // 离开作用域,unique_ptr 析构自动 free,无需 delete[],也不会内存泄漏
}
// 输出: 0 1 4 9 16
// 提示:RAII——资源获取即初始化;智能指针把"new/delete 配对"交给编译器管理。

实战(4 题):综合应用

这一组每题都会综合用到多个知识点——面向对象、STL、模板、lambda——做出一个接近真实场景的小程序。建议每题都完整跑一遍,再想想怎么扩展。

第 12 题:学生管理(class Student + std::vector)

题目:定义 class Student(姓名、数学、语文、成员函数 average()),用 std::vector 存储,按均分降序排序后打印。

提示:构造函数用初始化列表 : name(std::move(n)), math(m), ...;std::sort 第三参数传 lambda [] (const Student& a, const Student& b) { return a.average() > b.average(); };输出小数用 std::fixed << std::setprecision(1)。

参考答案
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <iomanip>

class Student {
public:
    std::string name;
    int math, chinese;
    Student(std::string n, int m, int c) : name(std::move(n)), math(m), chinese(c) {}
    double average() const { return (math + chinese) / 2.0; }
};

int main() {
    std::vector<Student> students = {
        {"小明", 90, 80},
        {"小红", 95, 88},
        {"小刚", 70, 100}
    };

    // 用 lambda 作为排序准则:按均分降序
    std::sort(students.begin(), students.end(),
              [](const Student& a, const Student& b) { return a.average() > b.average(); });

    for (size_t i = 0; i < students.size(); i++)
        std::cout << i + 1 << ". " << students[i].name
                  << " 均分 " << std::fixed << std::setprecision(1) << students[i].average() << "\n";
    return 0;
}
// 输出:
// 1. 小红 均分 91.5
// 2. 小明 均分 85.0
// 3. 小刚 均分 85.0

第 13 题:STL 单词频率统计(map + vector+sort 取 TopN)

题目:统计一段文本中每个单词的出现次数,输出频次最高的 Top 3(频次相同按字典序)。

提示:std::map 默认按 key 排,要按 value 排需把 map 拷到 std::vector<std::pair<string, int>> 再 std::sort;比较 lambda 用二级判断——先比频次降序,再比 key 升序。

参考答案
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>

int main() {
    std::string text = "the quick brown fox the lazy dog the fox brown dog dog";

    // 1. 用 map 计数
    std::map<std::string, int> freq;
    std::istringstream iss(text);
    std::string w;
    while (iss >> w) freq[w]++;

    // 2. 拷到 vector 用 sort 取 TopN(map 默认按 key 排,这里要按 value 排)
    std::vector<std::pair<std::string, int>> v(freq.begin(), freq.end());
    std::sort(v.begin(), v.end(), [](const auto& a, const auto& b) {
        if (a.second != b.second) return a.second > b.second;   // 频次降序
        return a.first < b.first;                                // 频次相同按字典序
    });

    std::cout << "Top 3:\n";
    int n = std::min<int>(3, v.size());
    for (int i = 0; i < n; i++)
        std::cout << "  " << v[i].first << ": " << v[i].second << "\n";
    return 0;
}
// 输出:
// Top 3:
//   dog: 3
//   the: 3
//   brown: 2

第 14 题:模板矩阵计算器(二维 vector 模板相加)

题目:定义二维矩阵类型 template<typename T> using Matrix = std::vector<std::vector<T>>;,实现 Matrix<T> add(const Matrix<T>&, const Matrix<T>&),使其对 int 和 double 都能工作。

提示:模板别名 using ... = ... 比 typedef 更现代、支持模板化;模板函数有实参推导,调用时不用写 add<int>;返回类型直接是 Matrix<T>。

参考答案
#include <iostream>
#include <vector>

template<typename T>
using Matrix = std::vector<std::vector<T>>;

template<typename T>
Matrix<T> add(const Matrix<T>& a, const Matrix<T>& b) {
    size_t rows = a.size(), cols = a[0].size();
    Matrix<T> c(rows, std::vector<T>(cols));
    for (size_t i = 0; i < rows; i++)
        for (size_t j = 0; j < cols; j++)
            c[i][j] = a[i][j] + b[i][j];
    return c;
}

int main() {
    Matrix<int> a = {{1, 2}, {3, 4}};
    Matrix<int> b = {{5, 6}, {7, 8}};
    auto c = add(a, b);            // 模板实参推导为 int

    for (const auto& row : c) {
        for (int x : row) std::cout << x << " ";
        std::cout << "\n";
    }
    return 0;
}
// 输出:
// 6 8
// 10 12
// 提示:换成 Matrix<double> 调用 add 同样工作,这就是模板的威力。

第 15 题:简易计算器(lambda 或 std::function 分派)

题目:实现一个支持 + - * / 的简易计算器。要求用 std::map<char, std::function> 把运算符映射到 lambda,通过 map 查表分派。

提示:std::function<double(double, double)> 是类型擦除的函数包装器,可以装下任何签名匹配的 lambda / 函数指针 / 仿函数;#include <functional>;遍历 map 时用结构化绑定 for (const auto& [op, fn] : ops)。

参考答案
#include <iostream>
#include <functional>
#include <map>
#include <string>

int main() {
    // map 的 value 类型用 std::function 统一 lambda 的签名
    std::map<char, std::function<double(double, double)>> ops = {
        {'+', [](double a, double b) { return a + b; }},
        {'-', [](double a, double b) { return a - b; }},
        {'*', [](double a, double b) { return a * b; }},
        {'/', [](double a, double b) { return b == 0 ? 0 : a / b; }}
    };

    double x = 10, y = 3;
    // map 按键升序遍历: * + - /(ASCII 序)
    for (const auto& [op, fn] : ops)
        std::cout << x << " " << op << " " << y << " = " << fn(x, y) << "\n";
    return 0;
}
// 输出:
// 10 * 3 = 30
// 10 + 3 = 13
// 10 - 3 = 7
// 10 / 3 = 3.33333
// 提示:std::function 是"类型擦除"的函数包装器;map + lambda 比一长串 switch 更易扩展。

练习建议

不要只看答案。把每道题在本地或在线环境亲手敲一遍,改一改输入试试边界情况(空 vector、负数、超长字符串)。C++ 编译错误信息可能很长——学会看编译器指出的第一处错误位置和类型,这是 C++ 程序员的硬通货。

试着扩展:做完第 11 题智能指针,能不能改成 std::shared_ptr 让多个持有者共享?做完第 14 题矩阵加法,能不能加上减法和乘法重载 operator+?做完第 15 题计算器,能不能解析字符串表达式(逆波兰式)?每个扩展都是一次小型的真实开发。

更多练习资源:推荐 LeetCode 用 C++ 刷算法(STL 在面试里是加分项);现代 C++ 特性参考 cppreference 的 C++ 文档(有中文版)。

← 上一篇 C++ 实战项目

返回 C++ 教程目录

✈️💬