C++ 实战项目

学了 16 篇基础,是时候把它们串起来做点真东西了。这一章给三个完整、可编译运行的 C++ 项目——学生成绩管理系统、STL 单词频率统计、模板矩阵计算器,每个都给出完整代码、运行结果和要点讲解。三个项目分别对应三个核心方向:面向对象 + 文件 I/O 的工程化、STL 容器算法的组合威力、模板与运算符重载的泛型抽象。建议你把代码复制到本地用 g++ 亲手编译运行一遍——编程是手艺活,光看不练记不住。

实战 1:学生成绩管理系统(class + vector + fstream)

需求:用 struct 描述学生,用 vector 存储全班,用 fstream 把记录持久化到文本文件并能读回,用 algorithm::sort 按总分降序,再算出班级平均/最高/最低。

涉及知识:结构体与成员函数、vector 容器、ifstream / ofstream 文件读写、sort 算法与比较函数、iomanip 格式化输出。编译命令:g++ -std=c++11 students.cpp -o students。

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

// 学生结构体:仍然是 aggregate,可用花括号初始化
struct Student {
    int id;
    string name;
    double chinese;
    double math;
    double english;

    double total() const { return chinese + math + english; }
    double average() const { return total() / 3.0; }
};

// 比较函数:按总分降序(algorithm::sort 用)
bool cmpByTotal(const Student& a, const Student& b) {
    return a.total() > b.total();
}

// 保存到文本文件:第一行写记录数,后面每行一条
void saveToFile(const vector<Student>& v, const string& path) {
    ofstream ofs(path);
    if (!ofs) {
        cerr << "[错误] 无法写入文件: " << path << endl;
        return;
    }
    ofs << v.size() << "\n";
    for (const auto& s : v) {
        ofs << s.id << " " << s.name << " "
            << s.chinese << " " << s.math << " " << s.english << "\n";
    }
    ofs.close();
    cout << "[保存] 已写入 " << v.size() << " 条记录到 " << path << endl;
}

// 从文本文件读回:用 >> 自动按空格分割
vector<Student> loadFromFile(const string& path) {
    vector<Student> v;
    ifstream ifs(path);
    if (!ifs) {
        cerr << "[提示] 文件不存在或无法打开: " << path << endl;
        return v;
    }
    size_t n;
    ifs >> n;
    for (size_t i = 0; i < n; ++i) {
        Student s;
        ifs >> s.id >> s.name >> s.chinese >> s.math >> s.english;
        v.push_back(s);
    }
    ifs.close();
    return v;
}

// 打印所有学生:fixed + setprecision 设置一次,影响整个循环
void printAll(const vector<Student>& v) {
    cout << "ID\t姓名\t语文\t数学\t英语\t总分\t平均" << endl;
    cout << fixed << setprecision(2);
    for (const auto& s : v) {
        cout << s.id << "\t" << s.name << "\t"
             << s.chinese << "\t" << s.math << "\t" << s.english << "\t"
             << s.total() << "\t" << s.average() << endl;
    }
}

// 全班统计:平均总分、最高、最低
void statistics(const vector<Student>& v) {
    if (v.empty()) { cout << "(无数据)" << endl; return; }
    double sum = 0;
    double best = v[0].total();
    double worst = v[0].total();
    string topName = v[0].name, lowName = v[0].name;
    for (const auto& s : v) {
        double t = s.total();
        sum += t;
        if (t > best) { best = t; topName = s.name; }
        if (t < worst) { worst = t; lowName = s.name; }
    }
    cout << fixed << setprecision(2);
    cout << "全班平均总分: " << sum / v.size() << endl;
    cout << "最高: " << topName << " (" << best << ")" << endl;
    cout << "最低: " << lowName << " (" << worst << ")" << endl;
}

int main() {
    // 花括号聚合初始化:Student 是 aggregate(成员函数不影响)
    vector<Student> students = {
        {1, "张三", 85, 92, 78},
        {2, "李四", 76, 88, 90},
        {3, "王五", 92, 95, 89},
        {4, "赵六", 65, 70, 72},
    };

    cout << "=== 原始成绩 ===" << endl;
    printAll(students);

    // 按总分降序排序
    sort(students.begin(), students.end(), cmpByTotal);
    cout << "\n=== 按总分降序 ===" << endl;
    printAll(students);

    cout << "\n=== 班级统计 ===" << endl;
    statistics(students);

    // 持久化到文件,再读回验证
    saveToFile(students, "students.txt");
    vector<Student> loaded = loadFromFile("students.txt");
    cout << "\n=== 从文件重新读出的记录 ===" << endl;
    printAll(loaded);

    return 0;
}
// 输出:
// === 原始成绩 ===
// ID    姓名    语文    数学    英语    总分    平均
// 1    张三    85.00    92.00    78.00    255.00    85.00
// 2    李四    76.00    88.00    90.00    254.00    84.67
// 3    王五    92.00    95.00    89.00    276.00    92.00
// 4    赵六    65.00    70.00    72.00    207.00    69.00
//
// === 按总分降序 ===
// ID    姓名    语文    数学    英语    总分    平均
// 3    王五    92.00    95.00    89.00    276.00    92.00
// 1    张三    85.00    92.00    78.00    255.00    85.00
// 2    李四    76.00    88.00    90.00    254.00    84.67
// 4    赵六    65.00    70.00    72.00    207.00    69.00
//
// === 班级统计 ===
// 全班平均总分: 248.00
// 最高: 王五 (276.00)
// 最低: 赵六 (207.00)
// [保存] 已写入 4 条记录到 students.txt
//
// === 从文件重新读出的记录 ===
// (与上面排序后一致)

要点讲解:

实战 2:STL 单词频率统计(map + lambda 排序)

需求:读入一段文本(或文件),分词、规范化(转小写去标点),用 map 统计每个单词出现次数,先按字母序输出,再按频率降序输出 Top 5。

涉及知识:ifstream 整文件读入、istringstream 按空白切词、map 关联数组、vector<pair> 按值排序、lambda 表达式、cctype 字符分类函数。

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

// 把一个原始 token 规范化:转小写 + 只保留字母
string normalize(const string& s) {
    string out;
    for (char c : s) {
        // isalpha / tolower 要先把 char 转成 unsigned char,否则负数会 UB
        if (isalpha(static_cast<unsigned char>(c))) {
            out += static_cast<char>(tolower(static_cast<unsigned char>(c)));
        }
    }
    return out;
}

int main() {
    // 演示用:内联一段文本
    string text = "the quick brown fox jumps over the lazy dog. "
                  "The fox is quick, the dog is lazy.";

    // 实际项目里从文件读取,代码如下(取消注释即可):
    // ifstream ifs("input.txt");
    // if (!ifs) { cerr << "无法打开文件" << endl; return 1; }
    // stringstream ss;
    // ss << ifs.rdbuf();
    // string text = ss.str();

    // 1. 分词 + 频率统计:istringstream 用空白切词,map 自动按 key 排序
    map<string, int> freq;
    istringstream iss(text);
    string raw;
    while (iss >> raw) {
        string w = normalize(raw);
        if (!w.empty()) freq[w]++;
    }

    // 2. map 自带按 key(字母)升序,直接遍历即可
    cout << "=== 按字母顺序 ===" << endl;
    for (const auto& p : freq) {
        cout << p.first << " : " << p.second << endl;
    }

    // 3. 按频率降序:map 不能按 value 排,拷贝到 vector<pair> 再 sort
    vector<pair<string, int>> vec(freq.begin(), freq.end());
    sort(vec.begin(), vec.end(),
        [](const pair<string, int>& a, const pair<string, int>& b) {
            if (a.second != b.second) return a.second > b.second;  // 频率降序
            return a.first < b.first;                              // 同频再按字母升序
        });

    // 4. 输出 Top 5
    cout << "\n=== 按频率降序 Top 5 ===" << endl;
    int n = 0;
    for (const auto& p : vec) {
        cout << p.first << " : " << p.second << endl;
        if (++n >= 5) break;
    }

    return 0;
}
// 输出:
// === 按字母顺序 ===
// brown : 1
// dog : 2
// fox : 2
// is : 2
// jumps : 1
// lazy : 2
// over : 1
// quick : 2
// the : 4
//
// === 按频率降序 Top 5 ===
// the : 4
// dog : 2
// fox : 2
// is : 2
// lazy : 2

要点讲解:

实战 3:简易模板矩阵计算器(template + operator 重载)

需求:用 template <typename T> 写一个通用矩阵类,支持 M(i, j) 下标访问、+ / - / * 运算符,既能算 int 矩阵也能算 double 矩阵——一份代码,多种类型。

涉及知识:类模板、运算符重载(operator() / operator+ / operator- / operator*)、异常 throw、vector<vector<T>> 二维容器、矩阵加减乘的数学维度规则。

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

// 类模板:元素类型 T 可以是 int / double / float ...
template <typename T>
class Matrix {
private:
    vector<vector<T>> data;
    size_t rows, cols;

public:
    Matrix(size_t r, size_t c, T init = T())
        : rows(r), cols(c), data(r, vector<T>(c, init)) {}

    size_t Rows() const { return rows; }
    size_t Cols() const { return cols; }

    // operator() 重载:支持下标读写 M(i, j),自带边界检查
    T& operator()(size_t i, size_t j) {
        if (i >= rows || j >= cols) throw out_of_range("矩阵下标越界");
        return data[i][j];
    }
    const T& operator()(size_t i, size_t j) const {
        if (i >= rows || j >= cols) throw out_of_range("矩阵下标越界");
        return data[i][j];
    }

    // 矩阵加法:维度必须完全一致
    Matrix<T> operator+(const Matrix<T>& other) const {
        if (rows != other.rows || cols != other.cols)
            throw invalid_argument("维度不匹配,无法相加");
        Matrix<T> r(rows, cols);
        for (size_t i = 0; i < rows; ++i)
            for (size_t j = 0; j < cols; ++j)
                r(i, j) = data[i][j] + other(i, j);
        return r;
    }

    // 矩阵减法
    Matrix<T> operator-(const Matrix<T>& other) const {
        if (rows != other.rows || cols != other.cols)
            throw invalid_argument("维度不匹配,无法相减");
        Matrix<T> r(rows, cols);
        for (size_t i = 0; i < rows; ++i)
            for (size_t j = 0; j < cols; ++j)
                r(i, j) = data[i][j] - other(i, j);
        return r;
    }

    // 矩阵乘法:A 是 rows x cols,B 是 other.rows x other.cols,要求 cols == other.rows
    Matrix<T> operator*(const Matrix<T>& other) const {
        if (cols != other.rows)
            throw invalid_argument("维度不匹配,无法相乘");
        Matrix<T> r(rows, other.cols, T(0));
        for (size_t i = 0; i < rows; ++i)
            for (size_t j = 0; j < other.cols; ++j)
                for (size_t k = 0; k < cols; ++k)
                    r(i, j) += data[i][k] * other(k, j);
        return r;
    }

    // 打印:cout 的 operator<<  对 setw 是粘性的,这里用最简单的格式
    void print(const string& name = "") const {
        if (!name.empty()) cout << name << " =" << endl;
        for (size_t i = 0; i < rows; ++i) {
            for (size_t j = 0; j < cols; ++j)
                cout << data[i][j] << "\t";
            cout << endl;
        }
    }
};

int main() {
    // 用 int 实例化
    Matrix<int> A(2, 2);
    A(0, 0) = 1; A(0, 1) = 2;
    A(1, 0) = 3; A(1, 1) = 4;
    A.print("A");

    Matrix<int> B(2, 2);
    B(0, 0) = 5; B(0, 1) = 6;
    B(1, 0) = 7; B(1, 1) = 8;
    B.print("B");

    // 用重载的运算符写起来就像数学公式
    Matrix<int> C = A + B;
    C.print("A + B");

    Matrix<int> D = A - B;
    D.print("A - B");

    Matrix<int> E = A * B;
    E.print("A * B");

    // 模板的威力:同一份代码换成 double 也照常工作(2x3 * 3x2 = 2x2)
    Matrix<double> F(2, 3, 0.0);
    F(0, 0) = 1.5; F(0, 1) = 2.0; F(0, 2) = 3.0;
    F(1, 0) = 0.5; F(1, 1) = 1.0; F(1, 2) = 1.5;

    Matrix<double> G(3, 2, 0.0);
    G(0, 0) = 1.0; G(0, 1) = 0.0;
    G(1, 0) = 0.5; G(1, 1) = 1.0;
    G(2, 0) = 2.0; G(2, 1) = 0.5;

    Matrix<double> H = F * G;
    H.print("F * G (double)");

    return 0;
}
// 输出:
// A =
// 1    2
// 3    4
// B =
// 5    6
// 7    8
// A + B =
// 6    8
// 10    12
// A - B =
// -4    -4
// -4    -4
// A * B =
// 19    22
// 43    50
// F * G (double) =
// 8.5    3.5
// 4    1.75

要点讲解:

小结

三个项目分别展示了 C++ 三大杀手锏:工程化(类 + 文件 IO 把数据和逻辑组织成系统)、组合力(STL 容器算法像积木一样拼出强大功能)、抽象力(模板让一份代码服务任意类型)。它们都用到了前面 16 篇学的基础——只是组合起来解决真实问题。建议你至少把其中一个改造成自己的版本:成绩管理加 GUI、单词统计改成读 URL、矩阵类加求逆——做过一遍比读十遍更扎实。

恭喜!到这里 C++ 系列就真正完结了。接下来挑一个方向深入——游戏开发(Unreal Engine / SDL)、高性能计算(多线程 / OpenMP / CUDA)、嵌入式(STM32 / 树莓派)、图形渲染(OpenGL / Vulkan)——边做边学,进步最快。

← 上一篇 C++ 文件 IO

返回 C++ 教程目录

✈️💬