欢迎光临
我们一直在努力

Day1 初识线性表

线性表 ADT 里 *L / L / *e / e 到底怎么记?——一次把 C 语言“值传递/指针传递”讲透(学习日志)

最近在学《数据结构》线性表(SqList)时,我被教材里 ADT 操作的写法搞得很乱:

有时候写 ListInsert(*L, i, e),有时候又是 ListLength(L)

有时候 GetElem(L, i, *e),有时候 LocateElem(L, e)

在 union(并集)示例代码里又出现 ListLength(*La)、GetElem(Lb, i, &e)、ListInsert(La, ++La_len, e)

这导致我一直在纠结:

什么时候要加 *?什么时候不加?调用时到底要传 L 还是 &L?e 到底是本体还是指针?

这篇日志就是把这些疑惑一次性梳理干净。

1. 我最开始的核心困惑:是不是“要改动就传指针,不改动就传值”?

我一开始的理解是:

ListLength(*La) 用 *La 是因为 La 会改变

Lb_len = ListLength(Lb) 不用指针是因为 Lb 不变

GetElem(Lb, i, &e) 用 &e 是因为要给 e 赋值

所以“容易混淆的地方”就是:要被改动就传指针,不改动就传参数?

这个结论方向是对的,但我后来发现还缺了一个关键点:

有时候出现 *La / La 并不是因为“改不改”,而是因为“函数形参类型需要什么”,你手里拿的变量类型又是什么。

换句话说:

你到底该写 * 还是 &,最终要看“函数原型”和“你当前变量的类型”。

2. ADT 里写的 *L,并不是让你调用时写 *L

教材 ADT 操作经常写:

ListInsert(*L, i, e)

ListDelete(*L, i, *e)

ListLength(L)

GetElem(L, i, *e)

我以为:既然 ADT 写了 *L,那我调用 ListInsert 时也应该写 *La。

但这是误解。

正确理解:ADT 里的 *L 是一种“标记”

它在告诉你:

这个操作会修改线性表 L,所以实现时要用“指针参数”来传表。

也就是实际 C 代码的函数原型更像这样:

C

Status ListInsert(SqList *L, int i, ElemType e);

Status ListDelete(SqList *L, int i, ElemType *e);

所以:

ADT 的 *L ≈ “会修改 L”(应当传地址)

不是在告诉你“调用时要写星号”

3. 为什么示例代码里 ListInsert(La, …) 前面没有 *?

示例代码里的并集函数:

C

void unionL(SqList *La, SqList Lb)

{

    …

    ListInsert(La, ++La_len, e);

}

我疑惑:书上不是写 ListInsert(*L, i, e) 吗?这里为什么是 ListInsert(La, …) 而不是 ListInsert(*La, …)?

原因:La 本身已经是指针了

unionL 的形参写的是:

C

SqList *La

所以 La 的类型就是 SqList*(地址)。

而 ListInsert 需要的也是 SqList*:

C

Status ListInsert(SqList *L, int i, ElemType e);

因此调用时应当直接传:

C

ListInsert(La, …);

如果你写成 ListInsert(*La, …),那传进去的是 SqList 本体(结构体值),类型反而不匹配(除非函数原型不同)。

4. 我又问:L 是线性表,为什么不能直接代表地址?

我曾经觉得:

“L 既然是一个线性表,那我写 L 也能代表它的地址吧?为什么还要 &L?”

关键点:结构体变量不会“自动退化”为指针

在 C 里:

SqList L; 是一个结构体变量(本体)

L 不是地址

&L 才是地址

我把“数组名能当地址”的特例套到了结构体上。

数组的特例(容易误导人)

C

int a[10];

很多场景下 a 会退化为 &a[0],所以你会感觉 a 就像地址。

但结构体不这样:

复制代码

C

SqList L;

L 绝不会自动变成 &L。

5. 于是我开始想要一个“记忆法”:为什么 L 有时带星号,有时不带?e 有时带星号,有时不带?

这是我最后最想解决的问题。

终极判断原则(强烈推荐背下来)

函数里要改动谁,就传谁的地址(指针)。

只读谁,就按值传。

但要更精确一点:

*L(ADT 里)表示:操作会修改线性表

L 表示:只读线性表

*e(ADT 里)表示:e 是输出参数,要把结果写回给调用者

e 表示:e 只是输入值

6. 把所有 operation 按“是否修改/是否输出”分类(最不容易混)

✅ 修改线性表 L(所以 ADT 写 *L,实现用 SqList *L)

InitList(*L):初始化会改 L

ClearList(*L):清空会改 L

ListInsert(*L, i, e):插入会改长度/内容

ListDelete(*L, i, *e):删除会改表(还会输出被删元素)

✅ 不修改线性表 L(所以 ADT 写 L)

ListEmpty(L):只判断不改

ListLength(L):只求长度不改

LocateElem(L, e):只查找不改

GetElem(L, i, *e):表不改,但要输出元素值

7. e 到底什么时候要 *e?

这是我最容易搞混的一点。最终我用一句话就能判断:

如果这个函数要“把一个 ElemType 的值带回给你”,它就需要 *e(输出参数)。

因此:

输入参数:e

LocateElem(L, e)(拿 e 去比较)

ListInsert(*L, i, e)(把 e 插进去)

输出参数:*e

GetElem(L, i, *e)(把元素写到 e 里)

ListDelete(*L, i, *e)(把被删元素写到 e 里)

8. 调用时到底写不写 & / *?——看你手里变量的类型

情况 A:你手里是本体 SqList L;

如果操作需要“修改表”(需要指针),你就传 &L:

C

SqList L;

ElemType x;

 

ListInsert(&L, 1, x);

GetElem(L, 1, &x);

情况 B:你手里已经是指针 SqList *La;

那就直接传指针本身:

C

SqList *La;

 

ElemType x;

 

ListInsert(La, 1, x);

GetElem(*La, 1, &x); // 注意:如果 GetElem 形参是 SqList L(本体),就需要 *La

这里顺带解释了示例代码里的一个细节:

La_len = ListLength(*La);

因为 ListLength(L) 可能定义成接收 本体,而 La 是 指针,所以要 *La 来匹配类型。

这再次说明:

出现 *La 有时是“类型匹配”而不是“改不改”。

9. 我还问过:ElemType e 那句“声明与 La 和 Lb 相同的数据元素 e”到底是什么意思?本体还是指针?

在 union 示例里常见:

C

ElemType e;

GetElem(Lb, i, &e);

我当时疑惑:“e 既然要传 &e,那 e 是不是指针?”

结论:ElemType e; 的 e 是本体(值变量)

判断方法极简单:看声明。

ElemType e; ✅ e 是本体(一个值)

ElemType *e; ✅ e 才是指针

GetElem(…, &e) 只是把 e 的地址传进去让函数写值而已。

也就是:

e:装元素值的“盒子”

&e:告诉函数“盒子在哪里”,让它把值放进去

教材里那句话“与 La 和 Lb 相同的数据元素 e”,说的是:

e 的类型要和线性表里存放的元素类型一致(即 ElemType),这样才能接住从表里取出的元素。

它不是说 e 和 La/Lb 的类型相同,更不是说 e 是 La/Lb 的指针。

10. 最终记忆口诀(我现在就靠这个不再混)

我给自己总结了四句口诀:

改表传地址:ADT 写 *L(实现用 SqList *L)

取值回传地址:ADT 写 *e(实现用 ElemType *e)

只读就不带星:L

只是输入值就不带星:e

再配一个“调用规则”:

我手里是本体 L → 要指针就写 &L

我手里已是指针 La → 直接传 La

输出参数一律传 &变量

11. 一个极简小抄(贴在笔记本第一页)

假设函数原型风格是常见教材版:

C

Status GetElem(SqList L, int i, ElemType *e);

int LocateElem(SqList L, ElemType e);

Status ListInsert(SqList *L, int i, ElemType e);

Status ListDelete(SqList *L, int i, ElemType *e);

int ListLength(SqList L);

那么调用时就按:

插入:ListInsert(&L, i, e)

删除:ListDelete(&L, i, &e)

取值:GetElem(L, i, &e)

查找:LocateElem(L, e)

长度:ListLength(L)

如果你在函数里拿到的是 SqList *La,那把 &L 换成 La 即可。

总结

我最开始纠结的所有“星号问题”,归根结底都来自两件事:

ADT 写法里的 *L / *e 是“会被修改/输出”的标记,不等于调用时直接写星号。

C 里到底写 * 还是 &,最终要看:函数形参类型需要什么 + 我手里变量类型是什么。

现在我用“改谁就传谁地址 + 看原型匹配”这两条,基本不会再混了。

赞(0)
未经允许不得转载:171主机测评 » Day1 初识线性表
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址