Debug小错记录

Debug记录

注:记录自己在编程时犯的各种错误,以便未来快速编程(不痛哭流涕)

2026/04/11


一、语法 & 基础概念类错误

这是最底层的问题,会导致编译直接失败或逻辑完全偏离。

  1. 迭代器解引用的「运算符优先级」错误

    • 错误写法:*it.first
    • 正确写法:(*it).firstit->first
    • 原因:. 的优先级高于 *,不加括号会先访问迭代器本身的成员(不存在)。
  2. multimap 插入函数参数错误

    • 错误写法:l.insert(键, 值)
    • 正确写法:l.insert({键, 值})l.emplace(键, 值)
    • 原因:insert 只接受一个键值对对象(pair),不接受两个独立参数。
  3. 自定义比较器的参数类型完全错误

    • 错误写法:bool operator()(pair<ll,ll> a, pair<ll,ll> b)
    • 正确写法:bool operator()(ll a, ll b) (如果 Key 是 ll)
    • 原因:multimap 的比较器只比较 Key(键),不比较整个键值对。
  4. 比较器的访问权限错误

    • 错误:在 class 里定义 operator() 但没加 public:
    • 解决:要么加 public:,要么直接用 struct(默认 public)。
  5. Typedef 乱写(你最后一版的笔误)

    • 错误:typedef pair<ll,ll,pc> m;
    • 原因:pair 只有两个模板参数,且这根本不是容器。

二、迭代器 & 内存安全类错误

这是最致命的问题,会导致程序直接崩溃(Segmentation Fault)。

  1. 不检查 end() 就直接解引用

    • 场景:auto it = l.lower_bound(val); 后直接写 it->first
    • 后果:如果 val 最大,itend(),访问野指针,程序崩溃。
  2. 不检查 begin() 就直接 it--

    • 场景:it 已经是 begin() 了,还执行 it_min--
    • 后果:迭代器越界,程序崩溃。

三、逻辑判断 & 条件矛盾类错误

这是最绕人的问题,会导致代码进入错误分支或死循环。

  1. if 条件自相矛盾

    • 错误写法:if(it_min!=l.begin() && (... || it_min==l.begin()))
    • 问题:要求“不是开头”同时“又是开头”,这个条件永远为假。
  2. while 循环条件用错 ||&&

    • 错误写法:while(it_min!=l.begin() || it_min->first==tmp_val)
    • 问题:用 || 会导致即使到了开头还会继续循环,直接越界。
    • 正确:必须用 &&
  3. 循环结束后多查了一个元素

    • 场景:循环因 it_min->first != tmp_val 停止,你还去比较 it_min->second
    • 问题:把不属于这一组的元素也比较了。

四、算法 & 题意理解类错误

这是最可惜的问题,会导致答案错误(WA)。

  1. 最佳选择的比较逻辑写反

    • 错误写法:if(bigger-val > val-lower) ... 选右边
    • 问题:右边距离更远,为什么要选右边?逻辑完全颠倒。
  2. 只在一边找最小 ID,另一边不找

    • 场景:左边花大力气找相同 Key 下的最小 ID,右边直接取 it->second
    • 问题:不公平,且不符合题意(距离相同时选 ID 最小的)。
  3. 遇到等于 val 的元素时,直接输出第一个

    • 问题:如果有多个相同的 Key,必须在这一堆里找 ID 最小的,不能直接输出第一个。
  4. 变量名误导

    • 比如把 upper_bound 的返回值命名为 it_max,容易让人误解为“最大值”,实际上它只是“第一个大于 val 的位置”。

💡 建议

  1. 写代码前先画逻辑流程图,不要上来就写补丁。
  2. 凡是用迭代器,先想边界情况(是 begin() 吗?是 end() 吗?)。
  3. 变量名要准确,叫 it_rightit_max 更不容易让人糊涂。
  4. 逻辑要“正交”:左边和右边分开处理,最后再一起比较,不要混在一个 if-else 里绕来绕去。

1. unique 去重前未排序(最严重错误)

  • 错误代码
    1
    2
    // 第一次版本
    all[id].erase(unique(all[id].begin(),all[id].end()),all[id].end());
  • 错误原因std::unique 只能去除“相邻”的重复元素,如果数组未排序,非相邻的重复元素会被保留,导致去重失败。
  • 修复方案:必须先 sortunique
    1
    2
    sort(all[id].begin(), all[id].end()); // 先排序
    all[id].erase(unique(all[id].begin(),all[id].end()),all[id].end()); // 再去重

2. 输出空格的判断逻辑完全错误

  • 错误代码
    1
    2
    // 第一次版本
    if(id) cout<<' '; // 错误:判断的是数组编号id是否非零
  • 错误原因:题目要求“第一个元素前无空格,后面元素前有空格”,应该判断当前元素的索引 i,而不是数组编号 id
  • 修复方案
    1
    if(i > 0) cout << ' '; // 或者简写为 if(i)

3. merge 命令未判断 id1 == id2

  • 错误代码
    1
    2
    3
    // 第二次版本(未加判断)
    all[id1].insert(all[id1].end(), all[id2].begin(), all[id2].end());
    all[id2].clear();
  • 错误原因:题目明确要求“如果id1等于id2,不做任何事”。如果不加判断,执行 merge 5 5 会把数组自己插入自己,然后清空,导致数据丢失。
  • 修复方案
    1
    2
    3
    4
    if(id1 != id2){ // 只有id不同才合并
    all[id1].insert(all[id1].end(), all[id2].begin(), all[id2].end());
    all[id2].clear();
    }

一、容器选择:固定数组 vs 关联容器

知识点回顾

  • 固定大小数组(如 bool isadd[100005]):

    • 内存连续,访问速度快,但大小必须在编译时确定,且无法动态扩展。
    • 若下标超出范围(如 x >= 100005x < 0),会导致未定义行为(内存越界,可能篡改其他数据或程序崩溃)。
  • 关联容器(如 set<int> / unordered_set<int>):

    • 动态管理内存,可自动处理任意范围的键值(只要在 int 合法范围内),无越界风险。
    • set 基于红黑树(有序,O(log n) 插入/查找);unordered_set 基于哈希表(无序,平均 O(1) 插入/查找)。

之前的错误

isadd[100005] 存储“是否添加过 x”,若输入 x 超出数组范围,直接触发越界。

正确做法

set<int>unordered_set<int> 替代数组,如代码中的 memory,既安全又简洁。

二、multiseterase 函数重载(核心易错点)

multiset 提供 3 个 erase 重载版本,功能完全不同,必须严格区分:

重载形式功能迭代器失效影响
erase(iterator pos)删除迭代器 pos 指向的单个元素仅使 pos 失效,其他迭代器有效
erase(iterator first, iterator last)删除区间 [first, last) 内的所有元素区间内的迭代器全部失效
erase(const value_type& val)删除容器中所有等于 val 的元素无迭代器失效问题(内部自动处理)

之前的错误

1
2
3
4
5
// 错误代码:删除从 p 到末尾的所有元素,误删了其他数据!
multiset<int>::iterator p = arr.find(x);
if (p != arr.end()) {
arr.erase(p, arr.end());
}

误用了区间删除版本,导致从 p 开始的所有元素(包括不等于 x 的)都被删除。

正确做法

  1. 简洁高效版(推荐):直接用 erase(val) 删除所有等于 x 的元素:

    1
    arr.erase(x); // 自动删除所有 x,无需循环
  2. 循环删除版(你后来的修改,逻辑正确但稍繁琐):

    1
    2
    3
    4
    5
    multiset<int>::iterator p = arr.find(x);
    while (p != arr.end()) {
    arr.erase(p); // 删除单个元素,p 失效
    p = arr.find(x); // 重新查找下一个 x(安全)
    }

三、迭代器失效规则

知识点回顾

当调用容器的 erase 函数时,被删除元素的迭代器会立即失效,但不同容器的失效范围不同:

  • 序列容器(如 vectordeque):
    • vector:删除点之后的所有迭代器/引用失效(因内存移动)。
    • deque:删除首尾元素时,仅被删元素的迭代器失效;删除中间元素时,所有迭代器失效。
  • 关联容器(如 setmultisetmap):
    • 被删除元素的迭代器失效,其他元素的迭代器/引用保持有效。

之前的正确操作

你在循环中每次 erase(p) 后,立即通过 arr.find(x) 重新获取下一个迭代器,完全符合关联容器的迭代器失效规则,逻辑安全。

四、count() 函数的时间复杂度

知识点回顾

  • multiset::count(val):返回容器中等于 val 的元素个数。
  • 时间复杂度:O(log n + k),其中 n 是容器总元素数,k 是等于 val 的元素个数(先通过 log n 找到第一个 val,再遍历 k 个元素计数)。

题目中的合理使用

题目要求 del 命令先输出删除前 x 的个数,因此必须先调用 count(x)erase(x),这是题目逻辑要求,无法避免两次遍历(一次计数、一次删除)。

五、STL 容器选择的最佳实践

需求推荐容器原因
需要“有序存储 + 快速查找/删除”set / multiset红黑树实现,O(log n) 时间复杂度
仅需“快速去重/查找”,无需有序unordered_set哈希表实现,平均 O(1) 时间复杂度
存储“键值对”map / unordered_map同上,支持 key-value 映射

2026/04/05

一、自定义输出流迭代器(myostream_iterator

1. 迭代器运算符的职责错配

  • 坑/错误/片面写法: 误解输出流迭代器运算符语义,试图在 operator* 中执行流输出,或在 operator++ 中做状态变更。

  • 错误成因(逻辑误判): 未遵循C++迭代器协议,输出流迭代器的核心是“赋值触发输出”,解引用和自增仅需满足语法兼容性。

  • 正确逻辑:

    • operator*:返回自身引用,仅作为赋值操作的左值中介;
    • operator=:执行实际的流输出 os << val << sep
    • operator++:无状态变更,仅返回自身以适配算法循环;
    • 成员变量必须用 ostream& 引用,避免流拷贝导致的编译错误。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    template<class T>class myostream_iterator {
    ostream& os; // 必须用引用,确保操作同一输出缓冲区
    string sep;
    public:
    myostream_iterator(ostream& o, string s) : os(o), sep(s) {}
    myostream_iterator& operator*() { return *this; } // 仅做左值中介
    myostream_iterator& operator++() { return *this; } // 无状态变更
    myostream_iterator& operator=(const T& val) {
    os << val << sep; // 赋值才是输出核心逻辑
    return *this;
    }
    };

二、内存重叠拷贝

1. 内存重叠导致的拷贝覆盖

  • 坑/错误/片面写法: 统一从前向后拷贝,未区分源/目标内存位置,导致后重叠场景下未读取的源数据被提前覆盖。

  • 考虑场景(逻辑错误): 当目标地址 target 在源区间 [start, end) 后方时,正向拷贝会先覆盖源区间前部元素,造成数据丢失。

  • 正确逻辑: 仅通过 targetstart 的位置判断拷贝方向:

    • target < start:正向拷贝(前重叠/无重叠);
    • target > start:反向拷贝(后重叠);
    • 空间复杂度 O(1)O(1),无中转数组开销,避免默认构造冗余。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    template <class T>struct GoodCopy {
    void operator()(T *start, T *end, T *target) {
    if (start == target) return;

    ptrdiff_t len = end - start;
    if (target < start) {
    // 正向拷贝:前交叉/无重叠
    for (ptrdiff_t i = 0; i < len; ++i) {
    target[i] = start[i];
    }
    } else {
    // 反向拷贝:后交叉/无重叠
    for (ptrdiff_t i = len - 1; i >= 0; --i) {
    target[i] = start[i];
    }
    }
    }
    };

三、浅拷贝引发的栈内存非法释放(myclass 构造/析构陷阱)

1. 指针浅拷贝导致的运行时崩溃

  • 坑/错误/片面写法: 构造函数初始化列表直接赋值 p(ptr),析构函数执行 delete[] p

  • 错误成因(致命隐患):

    • 浅拷贝使类成员与外部指针指向同一块内存;
    • 若外部指针指向栈内存(如 char line[]),析构时对栈内存执行 delete[] 触发崩溃。
  • 正确逻辑: 析构含 delete[] 时,构造必须执行深拷贝:

    1. 申请独立内存:p = new T[size]
    2. 逐元素同步数据:遍历赋值 p[i] = ptr[i]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    template<class T>
    class myclass {
    public:
    T* p;
    int size;
    myclass(T* ptr, int n) : size(n) {
    p = new T[size]; // 独立内存分配
    for (int i = 0; i < size; ++i) {
    p[i] = ptr[i]; // 深拷贝数据
    }
    }
    ~myclass() { delete[] p; } // 安全释放堆内存
    };

四、输入流迭代器(CMyistream_iterator)的预取机制与迭代器协议

1. 输入流迭代器的时序与状态错误

  • 坑/错误/片面写法: 构造函数未预取数据,或在 operator* 中触发流读取,导致解引用结果不一致。
  • 错误成因(逻辑误判): 忽略输入流“单向不可逆”特性,未遵循“预取+解耦”的迭代器协议。
  • 正确逻辑:
    • 预取机制:构造函数立即执行 is >> buffer,确保初始化后可直接解引用;
    • 运算符分工:
      • operator*:仅返回 buffer(幂等,无副作用);
      • operator++:执行 is >> buffer 更新数据(副作用仅在此处);
    • 流存储:成员用 istream& 保存,确保操作指定流对象。

2. MyCin 类 operator>> 的时序错位

  • 坑/错误/片面写法: 先判断旧值 if(n != -1) 再执行 cin >> n,导致 -1 拦截滞后。
  • 错误成因(逻辑断层): 判定基于旧数据,新输入的 -1 无法触发 stop = true,甚至引发链式读取溢出。
  • 正确逻辑: 先执行修改(读入),后做状态判定:
    1. 熔断检查:if(stop) return *this;
    2. 执行读取:cin >> n;
    3. 同步状态:if(n == -1) stop = true;

五、模板累加函数的边界防御(空区间崩溃问题)

1. 空区间解引用导致崩溃

  • 坑/错误/片面写法: 直接 T tmp = *a 初始化累加值,未检查区间是否为空。

  • 考虑场景(边界漏洞): 若传入空区间(如 SumArray(a, a)),*a 访问不存在的元素,触发段错误。

  • 正确逻辑:

    • 安全初始化:用 T() 生成零值(int→0,string→"");
    • 边界检查:循环开头判断 begin != end,避免空区间操作;
    • 性能优化:用 += 替代 +,减少临时对象构造。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    template<class T>
    T SumArray(T* begin, T* end) {
    T res = T(); // 安全初始化,适配所有类型
    while (begin != end) { // 边界检查,防止空区间
    res += *begin; // 高效累加,减少临时对象
    begin++;
    }
    return res;
    }

六、C++ Lambda 表达式

1. Lambda 语法混用与捕获错误

Lambda 核心结构 [捕获列表](参数列表)->返回值{函数体}

1
2
3
4
5
6
7
8
// 核心结构示例
auto sort_asc = [](int x, int y) -> bool { return x < y; };
// 捕获外部变量示例
int x = 10;
auto modify_x = [&x]() { x = 20; }; // &x 可修改
// 排序实战(最常用)
vector<int> v = {3,1,2};
sort(v.begin(), v.end(), [](int x, int y) { return x < y; });

七、数组形式带参构造对象的语法陷阱(new A(4)[3] 错误)

1. 数组构造的语法混用

  • 坑/错误/片面写法: 直接编写 new A(4)[3],试图同时实现数组分配与带参构造。

  • 错误成因(语法错误): C++ 不支持该写法——new A[3] 调用默认构造,new A(4) 调用带参构造,两者无法混用。

  • 正确逻辑(优先级从高到低):

    1. 推荐:使用 vector 直接构造(安全、简洁);
    2. 常规:先分配数组再循环赋值;
    3. 底层:定位 new + 内存池(需手动析构)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 方案1:vector(强烈推荐)
    vector<A> vec(3, A(4));
    // 方案2:循环赋值(常规用法)
    A* arr = new A[3];
    for (int i = 0; i < 3; ++i) {
    arr[i] = A(4);
    }
    // 方案3:定位new(底层写法,不推荐新手)
    A* arr = (A*)malloc(sizeof(A) * 3);
    for (int i = 0; i < 3; ++i) {
    new (&arr[i]) A(4); // 就地构造
    }

2026/04/03魔兽大作业(开战!)

此文档总结了《魔兽世界三(开战)》大模拟作业从 initial WA 到最终 AC 过程中遇到的核心坑点、逻辑漏洞、C++开发规范问题以及审题失误。请下次做大模拟题时引以为戒!

一、战斗机制与核心逻辑漏洞 (最致命的埋伏)

1. 战斗平局(死局)的误判

  • 坑/错误/片面写法: 只要一回合(双方各出手一次)血量和武器列表不变,直接 break 宣布平局。if (elements_unchanged && arms_unchanged) break;
  • 考虑场景(逻辑错误): 当一方打出一件 00 伤害的武器(如攻击力过低导致的废剑)时,双方血量和武器列表瞬间没有变化,代码借此跳出。这剥夺了后续高伤武器(如 Bomb)出手的机会。
  • 正确逻辑: 引入 int no_change_count,只有当连续 >=50 次交替(确保双方把所有武器全抡完一圈以上)状态仍无极微小变化时,才断定死循环并宣告平局。判断平局需解引用存入 vector<Arm> 以对比实体属性(id, hurt, times),切忌只比指针对比

2. 反伤武器 (Bomb) 导致的“死尸战斗”漏洞

  • 坑/错误/片面写法: 仅判定防守方是否死亡或同归于尽:if (c.bluesol->elements <= 0),遗漏攻击方被反噬炸死的情况。
  • 考虑场景(逻辑错误): Bomb 会对敌人造成伤害,同时反噬可能把攻击者自己炸死。若无单独判定,死人会继续留在战场发武器或被攻击。
  • 正确逻辑: 在发生攻击的逻辑下方,三层堵漏全面判断:1)同归于尽 、2) 防守方单死、3) 攻击方单死。若攻击方单死,直接让对方收缴战利品(不要等回合外):
    if (c.redsol->elements <= 0) { c.bluesol->Seize(c.redsol); c.redsol->isdead=true; break; }

3. 隐蔽规则:战利品易主后的属性重算 (动态攻击力)

  • 错误类型:题目读错
  • 具体情况: 将武器伤害值彻底封死在构造函数中(如最初拥有者攻击力的20%)。但规则要求“sword的攻击力是使用者当前攻击力的20%”。
  • 正确逻辑: 为武器引入虚函数 virtual void caculate_hurt(int hh)。每当时间来到 00:40 开战前,统一遍历存活士兵,并重新计算他们手中武器的实时伤害。

4. Dragon 的龙啸 (Yell) 与主动进攻条件

  • 错误类型:逻辑误判
  • 具体情况: 只要参战没死就欢呼,或者随便出击就欢呼。
  • 正确语法: 必须满足两个条件才欢呼:1) 战斗 2) 存活;

二、时序轴与输出规范 (大模拟的噩梦)

1. 地点同步发生事件的输出乱序

  • 坑/错误/片面写法: 遇到行军、抢夺、发声明时,直接在遍历红军或蓝军的循环里 printf

  • 考虑场景(逻辑错误): 题目严格要求“同一时间发生的事件,按发生地点从西向东依次输出(同一地点先红后蓝)”。原代码这会导致时空撕裂,永远先打印红方再打印蓝方。

  • 正确架构: 引入事件缓冲器:struct TimedEvent { int city; int color; string text; };。所有宏观事件先收集到 vector<TimedEvent> 中,调用 stable_sort 强制按 citycolor 升序排序后再集中打印。

  • 注意: 当10分钟发生达阵与占领时,必须先 push reached 再 push taken 给同城同色的记录,并结合 stable_sort 防止乱序。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    struct TimedEvent{ //add: 分钟内事件缓存,先收集再排序输出
    int city;
    int color;
    string text;
    };

    void PrintEventsByCity(vector<TimedEvent> &events){ //add: 统一按地点升序、同地红先蓝后输出
    stable_sort(events.begin(),events.end(),[](const TimedEvent &a,const TimedEvent &b){
    if(a.city!=b.city) return a.city<b.city;
    return a.color<b.color;
    });
    for(const TimedEvent &e:events){
    printf("%s",e.text.c_str());
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    vector<TimedEvent> minute5_events; //add: 先收集 05 分事件,再统一按地点排序输出
    for(Soldier*s:red.warriors){
    if(s->isdead) continue;
    if(s->checkflee()){
    char buf[128];
    //当你发现顺序输出错的时候,你。。。必须得采取某些方式自定义顺序!
    snprintf(buf,sizeof(buf),"%03d:%02d red lion %d ran away\n",
    curtime_h,curtime_m,s->id);
    minute5_events.push_back({s->location,0,string(buf)}); //add: 同城红方优先
    }
    }
    for(Soldier*s:blue.warriors){
    if(s->isdead) continue;
    if(s->checkflee()){
    char buf[128];
    snprintf(buf,sizeof(buf),"%03d:%02d blue lion %d ran away\n",
    curtime_h,curtime_m,s->id);
    minute5_events.push_back({s->location,1,string(buf)}); //add: 同城蓝方后输出
    }
    }
    PrintEventsByCity(minute5_events); //add: 满足“同一时刻从西到东”

2. 分钟级边界的细粒度拦截与跳出阈值

  • 坑/错误/片面写法:
    • 循环开头用 while(!canstop())。若此时在处理 55 分事件而限制时间恰好在此,可能遗漏。
    • 时间条件写成 60*h + m >= T_stop,漏印恰好在 T 分钟发生的事件。
    • 司令部被占领时立即 return 0 或跳出当分钟。
  • 正确逻辑:
    • 主循环改 while(true),每个操作前(如 curtime_m = 10; 前)插拔 if(canstop()) break;
    • 停止阈值为 60*h+m > T_stop(包含T执行)。
    • 若突发占领 game_over=true,务必完整输出该分钟所有其它并列事件后,在分钟末尾再依此 Flag 退出。

三、C++语法陷阱与内存管理 (工业隐患)

1. 武器列表的所有权转移(Seize / Snatch)与销毁

  • 坑/错误/片面写法: 使用泛型容器 vector<Arm*> 存储指针。抢夺时不 erase 原所有者指针,或扔武器时不先 delete 内存。
  • 考虑场景(导致编译崩溃或逻辑错误等): 浅拷贝造成两个单位指向相同武器,兵死武器遭析构引发野指针/二次释放(Double Free);不 delete 箭只 erase 导致内存泄漏MLE。
  • 正确写法:
    • 转移:this->arms.push_back(loser->arms[0]); loser->arms.erase(loser->arms.begin()); (严禁抢夺时先 delete!)
    • 销毁废器:必须先 delete arms[i]arms.erase(arms.begin() + i);。删除遍历务必从后往前跑 (for(int i=arm_num-1; i>=0; i--)) 避免索变短导致越界。

2. 负数取模的侥幸(极度危险的野路子)

  • 坑/错误/片面写法: 清理用完的武器后,用 (moment - 1) % arm_num 同步游标。

  • 考虑场景(C++隐患): C++ 中 -1 % n 的结果是 -1。若再因删除使得 arm_num==0 则产生除零崩溃。

  • 正确语法: 必须先判 arm_num > 0,并统一使用标准环形正数退位公式:(moment - 1 + arm_num) % arm_num

    反正就是要考虑到arm_num==0就是了。。。

3. 多态陷阱:缺失基类虚析构与虚实现

  • 坑/错误/片面写法: Soldier 基类未写带 {} 的析构函数;纯虚声明不加实现 virtual void get_arm();
  • 考虑场景(导致编译崩溃): 多态删除 delete soldier 时无法执行子类析构(武器指针内存泄漏)。arm64下链接器报错 Undefined symbols(无实现虚函数无跳表)。
  • 正确语法: 基类方法如不要纯虚给足 {},基类提供 virtual ~Soldier() { for(Arm* a:arms) delete a; }

4. 驻军清除引用的幽灵拷贝

  • 坑/错误/片面写法: for(City c: city) { c.redsol = nullptr; }
  • 考虑场景: C++范围循环默认值拷贝,实际未修改城市数组里的真实指针,导致"幽灵战斗"。
  • 正确写法: 加上引用以就地修改:for(City &c: city)

四、夺命眼瞎与细节笔误 (低级但致命)

1. 极度复杂的武器排序机制 (没用过的优先抢!)

  • 错误类型:题目读错
  • 具体情况: 战前与易主抢夺的 Arrow 排序逻辑完全相反。且不知 Wolf 劫掠有上限。
  • 正确逻辑:
    • 战前重排:用过的 Arrow 优先排前(早用早抛)。
    • 缴获/抢夺排序没用过的 Arrow(times 多的)优先被抢! (排序对比 condition2 单立规则)。
    • 贪婪上限:Wolf 每拿到一把武器,都必须立刻 if(wolf->arms.size() < 10) push_back; 并动态同步 arm_num

2. 永远谨防题目挖坑——上一代It’s,这一代Its谁看得出来……以及多用例初始化

  • 错误类型:打字打错 / 题目读错
  • 具体情况: Lion 逃跑的字符串为 Its loyalty is,而非 It's;连冒号后的空格 Case 1: 也手敲打错;漏读第一行输入 K (忠诚度降低值) 致使输入流全乱。
  • 正确做法: 标点符号与输出长句严禁手打,永远粘贴题面要求字串。
  • 多用例,每次 T 循环开端必须复位全局变标志(game_over=0、时间等)

3. 浮点数截断问题 (30% 伤害计算)

  • 坑/错误/片面写法: 箭伤 30%,使用 int(hurt * 0.3)。由于精度截断可能少扣血。
  • 正确逻辑: 大模拟一切百分比一律遵循**“先乘后除的整型运算”**:hurt = attack * 3 / 10;

4. 被遗漏的特征动作触发

  • 错误类型:流程漏处理 / 复制粘贴错误
  • 具体情况:
    • 遗漏了50分定点报告双司令部的生命元情况(哪怕司令部停止造兵也要报)。
    • 同城遇到同门 Wolf 忘记了“不发生抢夺”特例 (if(c.redsol->name==4 && c.blue...==4) continue;)。
    • 行军时大范围把判定黏在一起,甚至发生蓝军往西复制红代码导致再往东走的悲剧。
  • 正确架构: 取出 virtual void afterMarch() 多态分发,只给确实改变坐标的兵调。其余细枝末节逐字对照实现。
  • 具体情况: 蓝方行军代码是从红方复制来的,但循环体仍是 for(Soldier*s : red.warriors)!导致红方自己走完又替蓝方向西走,全域逻辑瘫痪!不要手滑!

2. 手滑Wolf 构造器虚空占位

  • 具体情况: Wolf 参数传入时没防备上游传来的 arm_num=1,导致它 arms 是空的但系统认为有武器,一打就报 SIGFPE 或越界除 0。必须在出生处严格初始化限制!

3. 浮点数截半

  • 具体情况: hurt0.3hurt * 0.3 强制转 int 计算 30% 伤害会导致 1 滴血差值!大模拟全盘 先乘后除 val * 3 / 10

4. 输入截断与格式硬伤

  • 具体情况:
    • 读输入 M N K T 少写一个读,全盘数据大乱!
    • 第一代是 It's 本代变成 Its loyalty is,绝不要闭眼沿用!
    • 小时分钟输出别去配字符串,老老实实 %03d:%02d 补零大法!
    • Case 标题:Case %d: 必须要有空格防 WA。

2026/03/26

第一弹

一、最开始的编译报错(根源问题)

错误:自定义函数名 strlen/strcpy/strcmp 和 C++ 标准库重名,编译器冲突(vscode自己的问题)
修改:全部改名 → sstrlen / sstrcpy / sstrcmp


二、字符串类核心致命错误(内存/深拷贝)

错误1:浅拷贝(直接赋值指针,多个对象共用一块内存,程序崩溃)
所有构造函数、拷贝构造、赋值运算符都只是 s=other.s
修改:全部改为深拷贝

  1. 每个对象自己 new 独立的字符数组
  2. sstrcpy 复制内容,不共用指针

错误2:缺少析构函数(内存泄漏)
修改:添加析构函数 ~MyString() { delete[] s; }

错误3:数组越界
new char[l+1] 却给 s[l] 赋值,下标越界
修改sstrcpy 自动补结束符,删除多余的越界赋值


三、构造函数语法错误

错误MyString(char* sk) 无法接收字符串常量("abcd"
修改:参数改为 const char* sk


四、赋值运算符优化

错误:未释放旧内存,未处理自赋值
修改:先 delete[] 旧内存 → 申请新内存 → 复制内容
优化:参数改为 const MyString& 常量引用


五、子串函数 operator() 崩溃错误

错误1char* k 未申请内存就赋值(野指针)
错误2:判断条件写反(合法子串返回空)
修改

  1. new char[length+1] 申请内存
  2. 条件改为:start+length > 总长度 才返回 nullptr

六、比较运算符(唯一写对的逻辑!)

❌ 题目自带的 sstrcmp 有缺陷(不比较长度)
做法:在 < / > / ==手动补充长度判断

第二弹

一、核心构造函数坑(最开始的一堆报错根源)

错误:空类继承 string,没有写任何构造函数

  • 后果:MyString s1("abcd-")MyString s2MyString s4(s1) 全部编译报错,因为编译器不知道怎么创建 MyString 对象
    修正:补充 3 个基础构造函数,全部在初始化列表里调用基类 string 的对应构造:
    1. 默认构造:MyString() : string() {}
    2. const char* 构造:MyString(const char* s) : string(s) {}
    3. 拷贝构造:MyString(const MyString& other) : string(other) {}

二、赋值运算符 = 冗余+死循环坑

错误:手动重载 operator=,还写成了 *this = a 这种无限递归

  • 后果:赋值语句编译报错,甚至运行时栈溢出
    修正直接删除这段代码
    继承 string 后,编译器会自动生成赋值运算符,直接复用基类 string 的赋值逻辑,完全不需要手写。

三、operator() 子串函数的类型转换坑

错误1:直接用 MyString(substr(...)) 构造,但没有接受 string 的构造函数

  • 后果:编译器报错「无法从 string 转换为 MyString
    修正方案(二选一)
    1. 补构造函数:新增 MyString(const string& s) : string(s) {},让 MyString 可以直接从 string 构造
    2. 绕路写法:用 substr(...).c_str() 转成 const char*,再调用已有的 MyString(const char*) 构造

四、连锁报错的误区

很多 = 报错、拼接报错,都是假报错

  • **根源是「string构造函数没写全」**或「类型转换失败」,导致编译器无法正确识别对象操作
  • 只要把构造函数补全、类型转换理顺,所有连锁报错会自动消失

五、这道题和上一道「手写字符串类」的区别

  • 上一道:从零造轮子,必须自己管 char*、深拷贝、内存释放
  • 这一道:继承扩展,所有核心功能(赋值、拼接、比较、排序)全靠 string 白嫖,只需要补构造和 operator()

第三弹

魔兽大世界2

1. 野指针崩溃

❌ 错误代码(局部变量取地址)

1
case 0:{Dragon dragon(1.0*th/strength[0],wc%3);warriors.push_back(&dragon);break;}

✅ 正确代码(new 堆对象)

1
case 0:{warriors.push_back(new Dragon(1.0*th/strength[0],wc%3));break;}

2. switch 作用域报错

❌ 错误代码(case 无花括号)

1
case 0:warriors.push_back(new Dragon(...)); break;

✅ 正确代码(case 加花括号)

1
case 0:{warriors.push_back(new Dragon(...));break;}

3. 定义武士类型变量

❌ 遗漏代码

1
2
// 漏写这行
int type=order[hr][mi];

✅ 补全代码

1
2
int type=order[hr][mi];
switch(type){...}

4. 多态容器

1
vector<Soldier*> warriors;

5. 调用刚生成武士的打印函数

1
warriors.back()->print();

2026/03/16

1. C风格内存分配与“+1”原则

  • 错误现象strcpy 报错、输出乱码、字符串末尾出现奇怪字符。

  • 错误根源:忽略了 C 风格字符串的结束符 \0

  • ⚠️ Debug 经验

    • 凡是使用 new char[len] 存储字符串,必须改为 new char[len + 1]

    • 在填充完字符后,务必手动封口:array[len] = '\0';

    • strlen只能处理有\0` 结尾的字符串,否则会因越界而返回错误长度。

2. 构造函数不能 delete!

  • 错误现象:程序刚运行就崩溃(Segmentation Fault)。

  • 错误根源:在构造函数(包括拷贝构造)里使用了 delete

  • Debug 经验

    • 构造函数是对象的“出生”,此时成员指针 array 里是随机垃圾值。绝对不能 delete 随机指针

    • 拷贝构造函数中,直接 new 即可;只有在 operator=(赋值运算符)中,才需要先 delete 旧内存。

    • 习惯在构造函数初始化列表中将指针置为 NULL

3. 赋值运算符(operator=)的“自杀”

  • 错误现象:执行 a = a 后,对象数据全部丢失或崩溃。

  • 错误根源:没有检查“自我赋值”。

  • Debug 经验

    • delete 自己的内存前,必须先判断 if (this == &other) return *this;

    • 忘记更新 length 成员:在拷贝 array 的同时,务必同步更新所有的元数据(如 length)。

4. 大数运算的“进位与对齐”

  • 错误现象:计算结果出现前导零(如 013)或进位丢失(如 9+1=0)。

  • 错误根源:逻辑过于复杂,进位(Carry)处理不统一。

  • Debug 经验

    • ⚠️ 统一存储方向:内部一律逆序存储array[0] 是个位),(正序拷贝,逆序输入)只有在 cout 时才倒序输出

    • 先算后存:先用一个 int 数组或足够大的临时 char 数组处理完所有的 sumcarry,最后再根据 carry 是否为 0 确定 length

    • 不要在循环内部直接改 length

一个关于加法参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
friend CHugeInt operator+(const CHugeInt &a, const CHugeInt &b) {
int maxl = a.length > b.length ? a.length : b.length;
char *tmp = new char[maxl + 2]; // 留出 1 位给进位,1 位给 \0

// 一个比较好的实现方式
int carry = 0;
for (int i = 0; i < maxl; i++) {
int moment = carry + (i >= a.length ? 0 : (a.array[i] - '0'))
+ (i >= b.length ? 0 : (b.array[i] - '0'));
tmp[i] = moment % 10 + '0';
carry = moment / 10;
}

int final_l = maxl;
if (carry > 0) {
tmp[final_l] = carry + '0';
final_l++;
}

// --- 关键修改点:手动封口 ---
tmp[final_l] = '\0';
// -------------------------

CHugeInt tt(final_l, tmp); // 这里的构造函数内部现在可以安全使用 strcpy 了
delete []tmp;
return tt;
}

// 关注:1.三目运算符的应用,补0
// 2.手动封口\0
// 3. carry 专门拿出来看(虽然不拿出来也行,但当时有未知错误,就是可能出现014等等多0现象,比较麻烦)
// 4.多开内存

5. 运算符重载的“返回类型”陷阱

  • 错误现象:加法结果无法传递,或者出现内存泄漏。

  • 错误根源operator+ 返回了局部变量的引用(CHugeInt&)。

  • Debug 经验

    • ⚠️ + 必须返回对象值CHugeInt),因为它产生的是一个临时的新值。

    • +=++(前置)必须返回**引用**(CHugeInt&),即return *this;`,以支持链式操作。

6. C 风格字符串与指针的“老派”脾气

  • 错误现象strcpy 拷贝结果不对。
  • 错误根源:数组名不能直接赋值,必须使用 memcpystrcpy
  • Debug 经验
  • 如果已经知道确切长度,memcpy 通常比 strcpy 更安全。
  • ⚠️ 在使用 tmp 数组中转后,记得 delete []tmp 释放临时空间,防止内存泄漏。