Debug小错记录

Debug小错记录
flowwalkerDebug记录
注:记录自己在编程时犯的各种错误,以便未来快速编程(不痛哭流涕)
2026/04/11
一、语法 & 基础概念类错误
这是最底层的问题,会导致编译直接失败或逻辑完全偏离。
迭代器解引用的「运算符优先级」错误
- 错误写法:
*it.first - 正确写法:
(*it).first或it->first - 原因:
.的优先级高于*,不加括号会先访问迭代器本身的成员(不存在)。
- 错误写法:
multimap插入函数参数错误- 错误写法:
l.insert(键, 值) - 正确写法:
l.insert({键, 值})或l.emplace(键, 值) - 原因:
insert只接受一个键值对对象(pair),不接受两个独立参数。
- 错误写法:
自定义比较器的参数类型完全错误
- 错误写法:
bool operator()(pair<ll,ll> a, pair<ll,ll> b) - 正确写法:
bool operator()(ll a, ll b)(如果 Key 是ll) - 原因:
multimap的比较器只比较 Key(键),不比较整个键值对。
- 错误写法:
比较器的访问权限错误
- 错误:在
class里定义operator()但没加public:。 - 解决:要么加
public:,要么直接用struct(默认 public)。
- 错误:在
Typedef 乱写(你最后一版的笔误)
- 错误:
typedef pair<ll,ll,pc> m; - 原因:
pair只有两个模板参数,且这根本不是容器。
- 错误:
二、迭代器 & 内存安全类错误
这是最致命的问题,会导致程序直接崩溃(Segmentation Fault)。
不检查
end()就直接解引用- 场景:
auto it = l.lower_bound(val);后直接写it->first。 - 后果:如果
val最大,it是end(),访问野指针,程序崩溃。
- 场景:
不检查
begin()就直接it--- 场景:
it已经是begin()了,还执行it_min--。 - 后果:迭代器越界,程序崩溃。
- 场景:
三、逻辑判断 & 条件矛盾类错误
这是最绕人的问题,会导致代码进入错误分支或死循环。
if条件自相矛盾- 错误写法:
if(it_min!=l.begin() && (... || it_min==l.begin())) - 问题:要求“不是开头”同时“又是开头”,这个条件永远为假。
- 错误写法:
while循环条件用错||和&&- 错误写法:
while(it_min!=l.begin() || it_min->first==tmp_val) - 问题:用
||会导致即使到了开头还会继续循环,直接越界。 - 正确:必须用
&&。
- 错误写法:
循环结束后多查了一个元素
- 场景:循环因
it_min->first != tmp_val停止,你还去比较it_min->second。 - 问题:把不属于这一组的元素也比较了。
- 场景:循环因
四、算法 & 题意理解类错误
这是最可惜的问题,会导致答案错误(WA)。
最佳选择的比较逻辑写反
- 错误写法:
if(bigger-val > val-lower) ... 选右边 - 问题:右边距离更远,为什么要选右边?逻辑完全颠倒。
- 错误写法:
只在一边找最小 ID,另一边不找
- 场景:左边花大力气找相同 Key 下的最小 ID,右边直接取
it->second。 - 问题:不公平,且不符合题意(距离相同时选 ID 最小的)。
- 场景:左边花大力气找相同 Key 下的最小 ID,右边直接取
遇到等于
val的元素时,直接输出第一个- 问题:如果有多个相同的 Key,必须在这一堆里找 ID 最小的,不能直接输出第一个。
变量名误导
- 比如把
upper_bound的返回值命名为it_max,容易让人误解为“最大值”,实际上它只是“第一个大于 val 的位置”。
- 比如把
💡 建议
- 写代码前先画逻辑流程图,不要上来就写补丁。
- 凡是用迭代器,先想边界情况(是
begin()吗?是end()吗?)。 - 变量名要准确,叫
it_right比it_max更不容易让人糊涂。 - 逻辑要“正交”:左边和右边分开处理,最后再一起比较,不要混在一个
if-else里绕来绕去。
1. unique 去重前未排序(最严重错误)
- 错误代码:
1
2// 第一次版本
all[id].erase(unique(all[id].begin(),all[id].end()),all[id].end()); - 错误原因:
std::unique只能去除“相邻”的重复元素,如果数组未排序,非相邻的重复元素会被保留,导致去重失败。 - 修复方案:必须先
sort再unique:1
2sort(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
4if(id1 != id2){ // 只有id不同才合并
all[id1].insert(all[id1].end(), all[id2].begin(), all[id2].end());
all[id2].clear();
}
一、容器选择:固定数组 vs 关联容器
知识点回顾
固定大小数组(如
bool isadd[100005]):- 内存连续,访问速度快,但大小必须在编译时确定,且无法动态扩展。
- 若下标超出范围(如
x >= 100005或x < 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,既安全又简洁。
二、multiset 的 erase 函数重载(核心易错点)
multiset 提供 3 个 erase 重载版本,功能完全不同,必须严格区分:
| 重载形式 | 功能 | 迭代器失效影响 |
|---|---|---|
erase(iterator pos) | 删除迭代器 pos 指向的单个元素 | 仅使 pos 失效,其他迭代器有效 |
erase(iterator first, iterator last) | 删除区间 [first, last) 内的所有元素 | 区间内的迭代器全部失效 |
erase(const value_type& val) | 删除容器中所有等于 val 的元素 | 无迭代器失效问题(内部自动处理) |
之前的错误
1 | // 错误代码:删除从 p 到末尾的所有元素,误删了其他数据! |
误用了区间删除版本,导致从 p 开始的所有元素(包括不等于 x 的)都被删除。
正确做法
简洁高效版(推荐):直接用
erase(val)删除所有等于x的元素:1
arr.erase(x); // 自动删除所有 x,无需循环
循环删除版(你后来的修改,逻辑正确但稍繁琐):
1
2
3
4
5multiset<int>::iterator p = arr.find(x);
while (p != arr.end()) {
arr.erase(p); // 删除单个元素,p 失效
p = arr.find(x); // 重新查找下一个 x(安全)
}
三、迭代器失效规则
知识点回顾
当调用容器的 erase 函数时,被删除元素的迭代器会立即失效,但不同容器的失效范围不同:
- 序列容器(如
vector、deque):vector:删除点之后的所有迭代器/引用失效(因内存移动)。deque:删除首尾元素时,仅被删元素的迭代器失效;删除中间元素时,所有迭代器失效。
- 关联容器(如
set、multiset、map):- 仅被删除元素的迭代器失效,其他元素的迭代器/引用保持有效。
之前的正确操作
你在循环中每次 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
12template<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)后方时,正向拷贝会先覆盖源区间前部元素,造成数据丢失。正确逻辑: 仅通过
target与start的位置判断拷贝方向:target < start:正向拷贝(前重叠/无重叠);target > start:反向拷贝(后重叠);- 空间复杂度 ,无中转数组开销,避免默认构造冗余。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18template <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[]时,构造必须执行深拷贝:- 申请独立内存:
p = new T[size]; - 逐元素同步数据:遍历赋值
p[i] = ptr[i]。
1
2
3
4
5
6
7
8
9
10
11
12
13template<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,甚至引发链式读取溢出。 - 正确逻辑: 先执行修改(读入),后做状态判定:
- 熔断检查:
if(stop) return *this;; - 执行读取:
cin >> n;; - 同步状态:
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
9template<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 | // 核心结构示例 |
七、数组形式带参构造对象的语法陷阱(new A(4)[3] 错误)
1. 数组构造的语法混用
坑/错误/片面写法: 直接编写
new A(4)[3],试图同时实现数组分配与带参构造。错误成因(语法错误): C++ 不支持该写法——
new A[3]调用默认构造,new A(4)调用带参构造,两者无法混用。正确逻辑(优先级从高到低):
- 推荐:使用
vector直接构造(安全、简洁); - 常规:先分配数组再循环赋值;
- 底层:定位 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; - 考虑场景(逻辑错误): 当一方打出一件 伤害的武器(如攻击力过低导致的废剑)时,双方血量和武器列表瞬间没有变化,代码借此跳出。这剥夺了后续高伤武器(如 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强制按city和color升序排序后再集中打印。注意: 当10分钟发生达阵与占领时,必须先 push
reached再 pushtaken给同城同色的记录,并结合stable_sort防止乱序。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15struct 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
21vector<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. 浮点数截半
- 具体情况: 强制转 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
✅ 修改:全部改为深拷贝
- 每个对象自己
new独立的字符数组 - 用
sstrcpy复制内容,不共用指针
❌ 错误2:缺少析构函数(内存泄漏)
✅ 修改:添加析构函数 ~MyString() { delete[] s; }
❌ 错误3:数组越界new char[l+1] 却给 s[l] 赋值,下标越界
✅ 修改:sstrcpy 自动补结束符,删除多余的越界赋值
三、构造函数语法错误
❌ 错误:MyString(char* sk) 无法接收字符串常量("abcd")
✅ 修改:参数改为 const char* sk
四、赋值运算符优化
❌ 错误:未释放旧内存,未处理自赋值
✅ 修改:先 delete[] 旧内存 → 申请新内存 → 复制内容
✅ 优化:参数改为 const MyString& 常量引用
五、子串函数 operator() 崩溃错误
❌ 错误1:char* k 未申请内存就赋值(野指针)
❌ 错误2:判断条件写反(合法子串返回空)
✅ 修改:
- 先
new char[length+1]申请内存 - 条件改为:
start+length > 总长度才返回nullptr
六、比较运算符(唯一写对的逻辑!)
❌ 题目自带的 sstrcmp 有缺陷(不比较长度)
✅ 做法:在 < / > / == 中手动补充长度判断
第二弹
一、核心构造函数坑(最开始的一堆报错根源)
❌ 错误:空类继承 string,没有写任何构造函数
- 后果:
MyString s1("abcd-")、MyString s2、MyString s4(s1)全部编译报错,因为编译器不知道怎么创建MyString对象
✅ 修正:补充 3 个基础构造函数,全部在初始化列表里调用基类string的对应构造:- 默认构造:
MyString() : string() {} const char*构造:MyString(const char* s) : string(s) {}- 拷贝构造:
MyString(const MyString& other) : string(other) {}
- 默认构造:
二、赋值运算符 = 冗余+死循环坑
❌ 错误:手动重载 operator=,还写成了 *this = a 这种无限递归
- 后果:赋值语句编译报错,甚至运行时栈溢出
✅ 修正:直接删除这段代码!
继承string后,编译器会自动生成赋值运算符,直接复用基类string的赋值逻辑,完全不需要手写。
三、operator() 子串函数的类型转换坑
❌ 错误1:直接用 MyString(substr(...)) 构造,但没有接受 string 的构造函数
- 后果:编译器报错「无法从
string转换为MyString」
✅ 修正方案(二选一):- 补构造函数:新增
MyString(const string& s) : string(s) {},让MyString可以直接从string构造 - 绕路写法:用
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 | // 漏写这行 |
✅ 补全代码
1 | int type=order[hr][mi]; |
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数组处理完所有的sum和carry,最后再根据carry是否为 0 确定length。不要在循环内部直接改
length。
一个关于加法参考:
1 | friend CHugeInt operator+(const CHugeInt &a, const CHugeInt &b) { |
5. 运算符重载的“返回类型”陷阱
错误现象:加法结果无法传递,或者出现内存泄漏。
错误根源:
operator+返回了局部变量的引用(CHugeInt&)。Debug 经验:
⚠️
+必须返回对象值(CHugeInt),因为它产生的是一个临时的新值。+=
和++(前置)必须返回**引用**(CHugeInt&),即return *this;`,以支持链式操作。
6. C 风格字符串与指针的“老派”脾气
- 错误现象:
strcpy拷贝结果不对。 - 错误根源:数组名不能直接赋值,必须使用
memcpy或strcpy。 - Debug 经验:
- 如果已经知道确切长度,
memcpy通常比strcpy更安全。 - ⚠️ 在使用
tmp数组中转后,记得delete []tmp释放临时空间,防止内存泄漏。










