本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本压缩包包含多个基于C++语言的实用程序项目,涵盖自守数判断、回文数检测、两个日期间间隔天数计算、学生信息管理系统及蛇形矩阵生成等典型编程任务。项目充分展示了C++在基础算法实现、数据结构应用、面向对象设计以及实际问题建模中的强大能力。通过这些经过验证的实战代码,学习者可深入掌握循环控制、字符串处理、类与对象设计、数组操作和逻辑思维构建,全面提升C++编程水平,为后续复杂系统开发奠定坚实基础。

自守数与回文数的数学判定理论及C++实现基础

在数字的世界里,有些数天生就带着“对称”或“自指”的神秘气质。比如你有没有想过:为什么 121 正着读反着读都一样?或者 25 的平方是 625 ,末尾竟然还藏着自己?🤯 这些看似巧合的现象背后,其实是 回文数 自守数 这两种特殊整数在悄悄起作用!

今天我们就来深入挖一挖这两位“数学明星”,从最朴素的直觉出发,一步步用 C++ 把它们揪出来。不只是写代码,更要搞清楚——怎么写得更快、更稳、更有工程味儿!


回文数判断:字符串法 vs 数值重构法 🧩

先说 回文数(Palindrome Number) :它就是那种无论正着读还是倒着读都一样的数字,像 121 1331 12321 都是经典代表。

方法一:字符串反转 —— 简单粗暴但有点“重”

最容易想到的办法?把数字变成字符串,然后翻个个儿,看看是不是一样👇

#include <string>
#include <algorithm>

bool isPalindromeString(int n) {
    if (n < 0) return false; // 负数带负号,肯定不对称
    std::string s = std::to_string(n);
    std::string rev = s;
    std::reverse(rev.begin(), rev.end());
    return s == rev;
}

优点 :逻辑清晰,一看就懂,适合新手入门
缺点 :要分配内存存字符串,对于高频调用或嵌入式场景不太友好

我们来跑个流程图感受一下它的执行路径:

graph TD
    A[输入整数n] --> B{n >= 0?}
    B -- 否 --> D[返回false]
    B -- 是 --> C[转为字符串s]
    C --> E[复制副本rev]
    E --> F[reverse(rev)]
    F --> G{s == rev?}
    G -- 是 --> H[true]
    G -- 否 --> I[false]

虽然简洁,但这招本质上是“换了个赛道打架”——把数值问题强行转成文本处理,中间多了一层转换成本。

方法二:纯数值逆序重构 —— 数学派登场!

真正的高手,从来不用字符串 😎

我们可以只靠 %10 /10 操作,逐位取出数字并重建它的“镜像体”。核心思想很简单:

不断取当前数的个位,加到新数末尾;原数不断除以10直到归零。

举个栗子: n = 121

  • 第一次: digit = 1 , rev = 1 , n = 12
  • 第二次: digit = 2 , rev = 12 , n = 1
  • 第三次: digit = 1 , rev = 121 , n = 0

最后比较 original == reversed 就行了!

完整实现如下:

bool isPalindromeNumeric(int n) {
    if (n < 0 || (n == 0 && n % 10 == 0)) return false;
    if (n < 10) return true;

    int original = n;
    int reversed = 0;

    while (n > 0) {
        reversed = reversed * 10 + n % 10;
        n /= 10;
    }

    return original == reversed;
}

🧠 小技巧提醒
- n == 0 单独处理是因为循环中 n/=10 会让它提前退出。
- n % 10 == 0 && n != 0 排除 10 , 100 这类以0结尾但不是回文的数。

这个方法完全避开了字符串操作,空间复杂度降到 O(1),性能提升明显 ✅

方法三:半程逆序优化 —— 工业级首选方案!

更进一步!其实我们不需要重建整个逆序数,只要处理一半就够了。

想象一个回文数,前半部分和后半部分是对称的。那我只需要把后半部分逆过来,跟前半部分比就行啦!

bool isPalindromeOptimized(int n) {
    if (n < 0 || (n % 10 == 0 && n != 0)) return false;

    int reversedHalf = 0;
    while (n > reversedHalf) {
        reversedHalf = reversedHalf * 10 + n % 10;
        n /= 10;
    }

    // 偶数位:两半相等;奇数位:忽略中间那位
    return n == reversedHalf || n == reversedHalf / 10;
}

🎯 关键点解析

  • 循环条件 n > reversedHalf 控制只处理一半
  • 结束时有两种情况:
  • n == reversedHalf → 偶数位回文(如 1221
  • n == reversedHalf / 10 → 奇数位回文(如 12321 ,此时 reversedHalf=123 , n=12

✨ 这个版本不仅省空间,连比较次数都砍了一半,常数因子极小,是工业项目中的首选策略!

下面这张表总结了三种方法的对比:

方法 时间复杂度 空间复杂度 是否使用字符串 场景推荐
字符串反转 O(d) O(d) 教学演示、快速原型
完全逆序重构 O(d) O(1) 通用场景
半程逆序优化 O(d) O(1) 高频调用、性能敏感

再来看个直观建议分布图:

pie
    title 回文数判断方法选择建议
    “字符串反转” : 20
    “完全逆序重构” : 35
    “半程逆序优化” : 45

看到没?近一半的开发者都应该优先考虑“半程逆序法”!


自守数检测:平方截尾匹配的艺术 🔍

接下来出场的是另一位数学界的“魔术师”—— 自守数(Automorphic Number)

啥叫自守?就是一个数的平方,末尾几位刚好等于它自己!

🌰 例子来了:
- $ 5^2 = 25 $ → 末尾是 5 ✔️
- $ 25^2 = 625 $ → 末尾是 25 ✔️
- $ 76^2 = 5776 $ → 末尾是 76 ✔️

这种“自我映射”的特性,在密码学和数论研究中还挺有用的。

核心公式:模运算定乾坤!

判断的关键在于一句话:

若 $ n^2 \mod 10^k = n $,则 $ n $ 是自守数
其中 $ k $ 是 $ n $ 的位数

翻译成代码就是:

bool isAutomorphic(int n) {
    if (n < 0) return false;
    if (n == 0 || n == 1) return true;

    int temp = n;
    int k = 0;
    while (temp) {
        k++;
        temp /= 10;
    }

    long long mod = 1;
    for (int i = 0; i < k; ++i) mod *= 10;

    long long square = (long long)n * n;
    return (square % mod) == n;
}

📌 细节说明
- 用循环算位数是为了避免浮点误差(别用 log10 啊兄弟们!)
- mod = pow(10, k) 手动计算,防止精度丢失
- 强转 long long 是为了防溢出(想想 99999^2 ≈ 1e10 ,超 int 啦)

💡 经验法则 :凡是涉及大数平方的操作,一定要上 long long !否则调试半小时才发现是溢出……

批量查找:遍历区间找宝藏 💎

想找出某个范围内的所有自守数?封装一下就好:

void findAutomorphicsInRange(int start, int end) {
    int count = 0;
    std::cout << "🔍 区间 [" << start << ", " << end << "] 内的自守数:\n";
    std::cout << std::left << std::setw(8) << "Number"
              << std::setw(15) << "Square"
              << std::setw(12) << "Digits" << "\n";
    std::cout << std::string(35, '-') << "\n";

    for (int i = start; i <= end; ++i) {
        if (isAutomorphic(i)) {
            long long sq = (long long)i * i;
            int k = 0, t = i;
            while (t) { k++; t /= 10; }
            std::cout << std::left << std::setw(8) << i
                      << std::setw(15) << sq
                      << std::setw(12) << k << "\n";
            count++;
        }
    }
    std::cout << "🎉 共找到 " << count << " 个自守数。\n";
}

运行结果示例:

Number  Square         Digits    
-----------------------------------
5       25             1         
6       36             1         
25      625            2         
76      5776           2         
376     141376         3         
625     390625         3         

哇哦~是不是有种发现隐藏规律的快感?😎


综合测试框架:打造可靠验证体系 🛡️

光有函数还不够,咱们得建立一套完整的测试机制,确保代码经得起考验。

统一接口 + 枚举控制

定义一个通用测试入口:

enum CheckType { PALINDROME_STR, PALINDROME_OPT, AUTOMORPHIC };

void runTests(const std::vector<std::pair<int, bool>>& cases,
              CheckType type) {
    int passed = 0, total = cases.size();
    for (const auto& [input, expected] : cases) {
        bool result = false;
        switch (type) {
            case PALINDROME_STR:
                result = isPalindromeString(input); break;
            case PALINDROME_OPT:
                result = isPalindromeOptimized(input); break;
            case AUTOMORPHIC:
                result = isAutomorphic(input); break;
        }
        if (result == expected) passed++;
        else 
            std::cout << "❌ 失败: 输入=" << input 
                      << " (期望=" << expected << ", 实际=" << result << ")\n";
    }
    std::cout << "✅ 测试汇总: " << passed << "/" << total << " 通过\n\n";
}

加入边界测试用例 ⚠️

别忘了这些“刁钻”的输入:

std::vector<std::pair<int, bool>> palindromeCases = {
    {121, true},      // 普通回文
    {123, false},     // 非回文
    {0, true},        // 特殊值0
    {-121, false},    // 负数
    {1221, true},     // 偶数位
    {12321, true}     // 奇数位
};

std::vector<std::pair<int, bool>> automorphicCases = {
    {5, true},        // 小自守
    {25, true},       // 中等
    {76, true},       // 又一个
    {10, false},      // 易错点
    {1, true},        // 边界
    {99999, false}    // 大数非自守
};

运行测试后输出:

✅ 测试汇总: 6/6 通过
✅ 测试汇总: 6/6 通过

🎯 工程提示
- 在生产环境中务必加入 assert() 断言保护
- 对接近 INT_MAX 的输入做预警处理
- 建议设置上限(如 n <= 1e6 ),防止 long long 也撑不住


日期类设计:精准时间建模实战 🗓️

搞定数字游戏之后,我们升级难度——来做一个真正的系统级组件: 日期类(Date Class)

无论是日志分析、金融结算还是用户行为追踪,精确的时间计算都是刚需。

类结构设计:封装先行!

先定义基本成员变量,私有化保护数据一致性:

class Date {
private:
    int year;
    int month;
    int day;

public:
    Date(int y, int m, int d);
    void display() const;
    bool isValid() const;
    int getYear() const { return year; }
    int getMonth() const { return month; }
    int getDay() const { return day; }
};

各字段含义如下:

成员 类型 含义 合法范围
year int 年份 [1, 9999]
month int 月份 [1, 12]
day int [1, 31](动态调整)

注意: day 的最大值不是固定的!得看月份和是否闰年。

构造函数 + 合法性校验:宁可拒杀也不放过!

对象创建阶段就要拦截非法输入:

Date::Date(int y, int m, int d) : year(y), month(m), day(d) {
    if (!isValid()) {
        throw std::invalid_argument("无效日期: " + std::to_string(y) + "-" 
                                  + std::to_string(m) + "-" + std::to_string(d));
    }
}

配合 isValid() 函数进行全方位检查:

bool Date::isValid() const {
    if (year < 1 || year > 9999) return false;
    if (month < 1 || month > 12) return false;

    static const int baseDays[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
    int maxDay = baseDays[month];

    if (month == 2 && isLeapYear(year)) maxDay = 29;

    return day >= 1 && day <= maxDay;
}

闰年判断必须严谨:

bool isLeapYear(int y) {
    return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0);
}

✅ 验证几个典型年份:
- 1900:%100=0 且 %400≠0 → ❌ 非闰年
- 2000:%400=0 → ✅ 闰年
- 2024:%4=0 且 %100≠0 → ✅ 闰年

构造流程可视化:

graph TD
    A[开始构造] --> B{输入年月日}
    B --> C[初始化成员]
    C --> D[调用 isValid()]
    D --> E{合法?}
    E -- 是 --> F[成功创建]
    E -- 否 --> G[抛异常终止]

这样就能保证每个 Date 对象生下来就是健康的!


跨年天数差计算:分段累加策略 📆

现在挑战来了:如何计算两个日期之间的天数差?

直接减不行,因为日期不是线性标量。我们得拆解任务。

同年情形:查表加速

如果两日期同一年,可以直接计算“年内累计天数”之差:

int daysFromStartOfYear(int year, int month, int day) {
    static const int cumulative[] = {0,0,31,59,90,120,151,181,212,243,273,304,334};
    int total = cumulative[month] + day;
    if (month > 2 && isLeapYear(year)) total++;
    return total;
}

int daysBetweenSameYear(const Date& d1, const Date& d2) {
    return abs(daysFromStartOfYear(d1.getYear(), d1.getMonth(), d1.getDay()) -
               daysFromStartOfYear(d2.getYear(), d2.getMonth(), d2.getDay()));
}

利用预计算的 cumulative 表,时间复杂度 O(1),非常高效!

跨年情形:三段式逐年累加

将整个时间段分为三块:

  1. 起始年剩余天数
  2. 中间完整年份
  3. 结束年已过天数
long long daysBetween(const Date& early, const Date& late) {
    long long total = 0;
    int y1 = early.getYear(), m1 = early.getMonth(), d1 = early.getDay();
    int y2 = late.getYear(), m2 = late.getMonth(), d2 = late.getDay();

    if (y1 == y2) return daysBetweenSameYear(early, late);

    // 首年剩余天数
    total += 365 - daysFromStartOfYear(y1, m1, d1) + (isLeapYear(y1) ? 1 : 0);

    // 中间整年
    for (int y = y1 + 1; y < y2; ++y) {
        total += isLeapYear(y) ? 366 : 365;
    }

    // 末年已过天数
    total += daysFromStartOfYear(y2, m2, d2);

    return total;
}

这种分治思想大大降低了计算复杂度,避免逐日遍历的低效做法。

精度验证:std::chrono 来背书!

C++17 提供了强大的 std::chrono::sys_days ,我们可以拿它来做交叉验证:

#include <chrono>
using namespace std::chrono;

sys_days start = year_month_day{year{y1}/m1/d1};
sys_days end   = year_month_day{year{y2}/m2/d2};
auto diff = (end - start).count(); // 天数差

对比自定义算法结果,一致才算过关!


学生信息管理系统:面向对象工程实践 👨‍🎓

最后压轴大戏:做一个完整的学生管理系统(SMS),融合前面所学。

类设计:Student + Manager 双剑合璧

class Student {
private:
    int id;
    std::string name;
    float score;
    int age;

public:
    Student(int id, const std::string& name, float score, int age)
        : id(id), name(name), score(score), age(age) {}

    int getId() const { return id; }
    const std::string& getName() const { return name; }
    float getScore() const { return score; }
    int getAge() const { return age; }
    void display() const;
};

Manager 类负责管理学生集合:

struct Node {
    Student data;
    Node* next;
    Node(const Student& s) : data(s), next(nullptr) {}
};

class Manager {
private:
    Node* head;
    int count;

public:
    Manager() : head(nullptr), count(0) {}
    ~Manager(); // 记得释放链表!

    bool addStudent(const Student& s);
    bool deleteStudent(int id);
    Student* findStudent(int id);
    void queryByScoreRange(float min, float max);
    void displayAll();
};

功能实现:增删改查全齐活

添加时检测重复学号:

bool Manager::addStudent(const Student& s) {
    if (findStudent(s.getId()) != nullptr) {
        std::cout << "🚫 学号 " << s.getId() << " 已存在!\n";
        return false;
    }
    Node* newNode = new Node(s);
    newNode->next = head;
    head = newNode;
    count++;
    return true;
}

删除按主键匹配:

bool Manager::deleteStudent(int id) {
    Node* curr = head, *prev = nullptr;
    while (curr) {
        if (curr->data.getId() == id) {
            if (prev) prev->next = curr->next;
            else head = curr->next;
            delete curr;
            count--;
            return true;
        }
        prev = curr;
        curr = curr->next;
    }
    return false;
}

查询支持成绩区间筛选,并格式化输出表格:

void Manager::queryByScoreRange(float min, float max) {
    printf("\n+------+--------+-------+-----+\n");
    printf("| 学号 | 姓名   | 成绩  | 年龄 |\n");
    printf("+------+--------+-------+-----+\n");

    Node* curr = head;
    while (curr) {
        auto& s = curr->data;
        if (s.getScore() >= min && s.getScore() <= max) {
            printf("| %4d | %-6s | %5.1f | %3d |\n",
                   s.getId(), s.getName().c_str(), s.getScore(), s.getAge());
        }
        curr = curr->next;
    }
    printf("+------+--------+-------+-----+\n");
}

输出效果杠杠的:

+------+--------+-------+-----+
| 学号 | 姓名   | 成绩  | 年龄 |
+------+--------+-------+-----+
| 1001 | Alice  |  87.5 |  20 |
| 1002 | Bob    |  90.0 |  21 |
+------+--------+-------+-----+

工程化组织:Makefile + 单元测试加持

目录结构清晰分离:

sms/
├── student.h/cpp
├── manager.h/cpp
├── main.cpp
└── Makefile

Makefile 实现一键编译:

CXX = g++
CXXFLAGS = -std=c++11 -Wall
TARGET = sms
SOURCES = main.cpp student.cpp manager.cpp
OBJECTS = $(SOURCES:.cpp=.o)

$(TARGET): $(OBJECTS)
    $(CXX) $(CXXFLAGS) -o $@ $^

%.o: %.cpp
    $(CXX) $(CXXFLAGS) -c $< -o $@

clean:
    rm -f $(OBJECTS) $(TARGET)

.PHONY: clean

运行 make 就能生成可执行文件,开发效率拉满⚡

最后加上单元测试保障稳定性:

void runTests() {
    Manager mgr;
    Student s1(1001, "Alice", 85.5, 20);
    Student s2(1002, "Bob", 90.0, 21);

    assert(mgr.addStudent(s1) == true);
    assert(mgr.addStudent(s1) == false); // 重复检测
    assert(mgr.findStudent(1001)->getName() == "Alice");
    assert(mgr.deleteStudent(1002) == false); // 不存在
    std::cout << "✅ 所有单元测试通过\n";
}

整个系统的类关系图如下:

classDiagram
    class Student {
        -int id
        -string name
        -float score
        -int age
        +Student(int, string, float, int)
        +getId() int
        +display() void
    }

    class Manager {
        -Node* head
        -int count
        +addStudent(Student) bool
        +deleteStudent(int) bool
        +findStudent(int) Student*
        +displayAll() void
    }

    class Node {
        -Student data
        -Node* next
        +Node(Student)
    }

    Manager --> Node : 包含多个
    Node --> Student : 封装

总结:从数学到工程的跃迁之路 🚀

这一路走来,我们经历了:

  • 回文数、自守数 的数学定义出发,动手实现了多种高性能判断算法;
  • 掌握了 数值逆序、模运算、位数控制 等底层技巧;
  • 构建了健壮的 Date 类,解决了跨年天数差的复杂计算;
  • 最终落地到一个完整的 学生信息管理系统 ,涵盖了 OOP 设计、内存管理、工程组织全流程。

🎯 关键收获:
1. 能用数值就不转字符串 ,减少类型转换开销
2. 边界测试不能少 ,异常输入早拦截
3. 模块化 + 单元测试 是大型项目的基石
4. Makefile 自动化构建 提升开发幸福感

这些技能不仅能帮你拿下 LeetCode 题目,更能让你写出真正可靠的工业级代码!

所以,下次面试官问你:“你会写日期类吗?”
你可以微微一笑:“要不要顺便看看我的学生管理系统?” 😎

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本压缩包包含多个基于C++语言的实用程序项目,涵盖自守数判断、回文数检测、两个日期间间隔天数计算、学生信息管理系统及蛇形矩阵生成等典型编程任务。项目充分展示了C++在基础算法实现、数据结构应用、面向对象设计以及实际问题建模中的强大能力。通过这些经过验证的实战代码,学习者可深入掌握循环控制、字符串处理、类与对象设计、数组操作和逻辑思维构建,全面提升C++编程水平,为后续复杂系统开发奠定坚实基础。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐