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++ 教程目录