顺序表:实现 q_addpro 任意位置插入,记录已知问题
1.c: - q_addpro 完成平移 + 插入的基本逻辑(从后往前挪,避免覆盖未搬元素) - 修掉 realloc 尺寸算错导致的堆越界(sizeof(q->max) → q->max * sizeof(int)) - 补上遗漏的写入动作 q->start[num] = x 仍存在的问题(详见 调试记录.md): - 平移循环多搬一次,把垃圾值带进表 - 没有检查插入位置是否合法,非法位置会静默越界写 - q->length 从未初始化,但 q_add 的扩容判断在用它 - q_reduce / q_reducepro 未完成 新增 调试记录.md 记录以上问题与待办测试用例。
This commit is contained in:
@@ -3,51 +3,27 @@
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
int max; //列表最大长度
|
||||
int length; //列表现在的长度
|
||||
int *start; //内存地址起始位置
|
||||
int front; //列表起始索引
|
||||
int end; //列表结束索引
|
||||
int max; //列表最大长度
|
||||
int length; //列表现在的长度
|
||||
int *start; //内存地址起始位置
|
||||
int front; //列表起始索引
|
||||
int end; //列表结束索引
|
||||
|
||||
}queue;
|
||||
|
||||
|
||||
int main(){
|
||||
queue q;
|
||||
printf("输入整个列表长度");
|
||||
int length;
|
||||
scanf("%d",&length);
|
||||
int b;
|
||||
printf("接下来依次输入放进列表的东西");
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
/* code */
|
||||
scanf("%d",&b);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int q_init(queue *q,int length){ //就是普通的初始化。要给他什么参数呢?首先就是queue的地址,然后是一个初始长度。然后初始化全0就行了。
|
||||
int q_init(queue *q,int length){ //就是普通的初始化。要给他什么参数呢?首先就是queue的地址,然后是一个初始长度。然后初始化全0就行了。
|
||||
q->start=malloc(length*sizeof(int));
|
||||
q->max=length;
|
||||
q->front=0;
|
||||
q->end=0;
|
||||
q->max=0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int q_add(queue *q,int num){ //最后边加上元素
|
||||
if(q->length==q->max){ //容量不够了的自动扩容
|
||||
int q_add(queue *q,int num){ //最后边加上元素
|
||||
if(q->length==q->max){ //容量不够了的自动扩容
|
||||
q->max+=5;
|
||||
q->start=realloc(q->start,q->max*sizeof(int));
|
||||
}
|
||||
@@ -56,17 +32,80 @@ q->end++;
|
||||
q->length++;
|
||||
}
|
||||
|
||||
int q_addpro(){ //任意地方加上元素,要进去一个索引编号和数值
|
||||
int q_addpro(queue *q,int num,int x){ //num是插入位置,x是插入的元素。任意地方加上元素,要进去一个索引编号和数值
|
||||
//先判断够不够位置加上去先
|
||||
if(q->end>=q->max){
|
||||
q->max=q->max+5;
|
||||
q->start=realloc(q->start,q->max*sizeof(int));//这里就是扩容代码,把max也一起增加了
|
||||
}
|
||||
//然后写平移的代码段,把加入的位置的东西到end的所有东西往后平移一个
|
||||
for (size_t i = q->end; i > num-1; i--)
|
||||
{
|
||||
/* code */
|
||||
q->start[i+1]=q->start[i];
|
||||
|
||||
|
||||
}
|
||||
q->end++;
|
||||
q->start[num]=x;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
int q_reduce(){ //最前面拿走元素
|
||||
int q_reduce(){ //最前面拿走元素
|
||||
|
||||
|
||||
}
|
||||
|
||||
int q_reducepro(){ //任意地方拿走元素,也是索引号+数值
|
||||
int q_reducepro(queue *q,int num ,int *x){ //任意地方拿走元素,也是索引号+数值
|
||||
*x=q->start[num];
|
||||
for (size_t i = num; i < q->end+1; i++)
|
||||
{
|
||||
/* code */
|
||||
q->start[i]=q->start[i+1];
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int printall(queue *q){ //打印全部东西
|
||||
printf("下面是依次打印");
|
||||
for (size_t i = 0; i < q->end; i++)
|
||||
{
|
||||
/* code */
|
||||
|
||||
printf("\n%d\n",q->start[i]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int main(){
|
||||
queue q;
|
||||
printf("输入整个列表长度");
|
||||
int length;
|
||||
scanf("%d",&length);
|
||||
int b;
|
||||
q_init(&q,length);
|
||||
printf("接下来依次输入放进列表的东西");
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
/* code */
|
||||
scanf("%d",&b);
|
||||
q_add(&q,b);
|
||||
}
|
||||
printall(&q);
|
||||
printf("\n接下来测试中间插入: 3号位置插入666,5号位置插入-77,11号位置插入1919810");
|
||||
q_addpro(&q,3,666);
|
||||
q_addpro(&q,5,-77);
|
||||
q_addpro(&q,11,1919810);
|
||||
printall(&q);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
# 调试记录
|
||||
|
||||
记录顺表练习过程中遇到的问题和当时的想法。
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-17 当前进度
|
||||
|
||||
| 函数 | 状态 |
|
||||
|---|---|
|
||||
| `q_init` | 可用(但见问题 3) |
|
||||
| `q_add` | 可用 |
|
||||
| `printall` | 可用 |
|
||||
| `q_addpro` | 能跑,但有 bug(见问题 1、2) |
|
||||
| `q_reduce` | 空函数,未写 |
|
||||
| `q_reducepro` | 写了,未测试(见问题 4) |
|
||||
|
||||
---
|
||||
|
||||
## 问题 1:`q_addpro` 平移时多搬了一次,把垃圾值带进了表
|
||||
|
||||
**现象**:插入之后,数据末尾会多出一个来源不明的值。
|
||||
|
||||
**复现**:表长 3、元素 `1 2 3`,执行 `q_addpro(&q, 3, 666)`,
|
||||
打印结果为 `1 2 3 666 3801155 ...` —— `3801155` 不是任何一次输入的数据。
|
||||
|
||||
**待查**:循环的**起点**取的应该是哪一个下标?
|
||||
|
||||
> 补充:同样的问题在表长 6 时也存在,只是那次垃圾值落到了打印范围之外,
|
||||
> 所以没被发现。**同一个 bug,换个尺寸才现形。**
|
||||
|
||||
---
|
||||
|
||||
## 问题 2:`q_addpro` 不检查插入位置是否合法
|
||||
|
||||
**现象**:`q_addpro(&q, 11, 1919810)` 在表长只有 5 时被接受,
|
||||
程序不报错、不崩溃,静默跑完。
|
||||
|
||||
**实际后果**:扩容后 `max = 8`(合法下标 0~7),但数据被写到了 **下标 11**。
|
||||
也就是往不属于自己的内存里写了 4 格 —— **堆越界**。
|
||||
|
||||
**记住的教训**:
|
||||
|
||||
- 越界 ≠ 崩溃。Windows 上这类写坏通常无声无息,只在特定边界才炸。
|
||||
- 防御性编程:函数应该先检查参数,发现非法就**立刻返回**,
|
||||
并用返回值告诉调用方"我失败了"。
|
||||
- 这几个函数都声明成 `int`,但从来没 `return` 过 —— 那个 `int` 就是留给这件事的。
|
||||
|
||||
**待定**:合法插入位置的范围是什么?(`num` 能不能等于表长?能不能负数?)
|
||||
检测到非法之后,函数该怎么返回?调用方该怎么处理?
|
||||
|
||||
---
|
||||
|
||||
## 问题 3:`q->length` 从未被初始化
|
||||
|
||||
`q_init` 里只设了 `max`、`front`、`end`,**没有给 `length` 赋值**。
|
||||
但 `q_add` 的扩容判断用的是 `q->length == q->max` ——
|
||||
等于拿一个栈上的随机数来决定要不要扩容。
|
||||
|
||||
目前测试没踩到,是因为运气。换个输入就会出问题。
|
||||
|
||||
**待定**:`length` 和 `end` 到底是不是同一个东西?如果循环队列要用 `length`,
|
||||
那它得先被管起来。
|
||||
|
||||
---
|
||||
|
||||
## 问题 4:`q_reducepro` 的平移方向
|
||||
|
||||
删除和插入的平移**方向是相反的**,但原理相通。
|
||||
|
||||
`q_reducepro` 目前写的是 `for (size_t i = num; i < q->end + 1; i++)`,
|
||||
还没测过。另外注意 `size_t` 是无符号类型。
|
||||
|
||||
**待测**:
|
||||
|
||||
- [ ] 删 0 号位
|
||||
- [ ] 删最后一个
|
||||
- [ ] 删中间
|
||||
- [ ] `*x` 里拿到的是不是被删掉的那个值
|
||||
- [ ] 删除后表长有没有正确减少
|
||||
|
||||
---
|
||||
|
||||
## 待办的测试用例
|
||||
|
||||
- [ ] 插入到 0 号位
|
||||
- [ ] 插入到末尾(`num == end`)
|
||||
- [ ] 表"差一格满"时(`end == max - 1`)插入
|
||||
- [ ] 非法位置:负数、大于表长
|
||||
- [ ] 往空表里插入
|
||||
|
||||
---
|
||||
|
||||
## 环境和工具备注
|
||||
|
||||
- 源文件目前是 **GBK** 编码,PowerShell 里中文能正常显示。
|
||||
(注意:不要再加 `-fexec-charset=GBK`,会转两次反而乱码)
|
||||
- 编译命令:`gcc -Wall 1.c -o 1.exe`
|
||||
- 本机 **没有装 ASan**(AddressSanitizer),想抓内存越界得用别的办法。
|
||||
- `printall` 用的边界是 `q->end` —— 如果 `end` 本身不可信,打印出来的就是假象。
|
||||
|
||||
---
|
||||
|
||||
## 已经解决的(记录一下思路,别忘了)
|
||||
|
||||
**扩容时 `realloc` 的大小算错了**
|
||||
|
||||
写成了 `realloc(q->start, sizeof(q->max))`,而 `sizeof(q->max)` 是
|
||||
"一个 `int` 占几字节"(4),不是"总共要几字节"。
|
||||
|
||||
正确写法是 `个数 × 每个的大小`。当时误申请到了 4 字节,
|
||||
后面的数据全被堆的元数据覆盖,打印出来是一堆每次都不同的随机数。
|
||||
|
||||
> **识别技巧**:数据每次运行都不一样 → 读到的不是自己的数据,是别人的内存。
|
||||
|
||||
**忘了把 `x` 真正写进去**
|
||||
|
||||
平移做完了,但没执行插入动作,参数 `x` 从头到尾没被用过。
|
||||
编译器的 warning 里其实点了名(`unused parameter 'x'`)。
|
||||
|
||||
> **识别技巧**:编译器说某个参数"没被用过",八成是漏写了什么。
|
||||
Reference in New Issue
Block a user