线性表初始化int InitList(LinkList *L)、int InitList(LinkList L)及int InitList(LinkList &L)
- 单链表的存储结构
typedef struct LNode
LNode L: L是结构体LNode实例化的实体,可以用.运算符来访问结构体成员,即L.elem。
LinkList L: L是指向定义的LNode结构体的指针,可以用->运算符来访问结构体成员,即L->elem,而(*L)就是个Node型结构体的实体了,可以用点运算符访问该结构体成员,即(*L).elem;
LinkList *L: L是指向定义的LNode结构体指针的指针,所以(*L)是指向LNode结构体的指针,可以用->运算符来访问结构体成员,即(*L)->elem,当然,(**L)就是LNode型结构体的实体了,所以可以用点运算符来访问结构体成员,即(**L).elem;
在链表操作中,我们常常要用链表变量作为函数的参数,这时,用LinkList L还是LinkList *L就很值得考虑深究了,一个用不好,函数就会出现逻辑错误,其准则是:
1. 如果子函数会改变指针L的值,而你也希望子函数结束调用后保存L的值(因子函数运行结束后,所有形参,含L,会被释放),那你就要用LinkList *L的形式传递参数。这样,向子函数传递的就是指针的地址,结束调用后,自然就可以去改变实参指针变量的值,让它指向新的实体;
2. 如果子函数只会修改指针所指向的内容,而不会更改指针变量的值(实体的地址),那么用LinkList L就行了。实质是,子函数修改的是形参指针 指向的内容Lnode,注意到 实参指针也指向Lnode,也就是子函数修改的就是实参指向的内容Lnode,形参指针随着子函数结束消失不影响 子函数的功能,就是要修改实参 后面指向的内容,而不是实参指针的值。
- 初始化的链表T,子函数调用完毕后,L会指向一个空的链表,即会改变指针L的值,所以要用*L
- int InitList1(LinkList *L) {
*L = (LinkList)malloc(sizeof(Node));
if(!(*L)){return 0;}
(*L)->next = NULL;
return 1;
}
- 清空链表L,使L重新变为空链表,子函数调用完后不会改变指针L的值,只会改变指针L所指向的内容(即L->next的值)
2. void ClearList(LinkList L) {
LinkList p;
- 销毁链表L,释放链表L申请的内存,使L的值重新变为NULL,所以会改变L的值,得用*L
LinkList p;
while(p = (*L)->next )
free(p);
free(*L);
*L = NULL;
}
- 主函数中调用
void main()
{
LinkList T = NULL;
InitList(&T);
ClearList(T);
DestroyList(&T);
}
有同学问,老师在未了解前也有同样疑问,链表初始化子函数中,形参 LinkList L,L本身就已经是指针了,为何初始化还要这里为何还要定义指针的指针 LinkList *L,何必多此一举呢,我先把初始化代码改成如下:
4. int InitList2(LinkList L)
L = (LinkList)malloc(sizeof(Node));// 其实只是修改指针型形参L的值(它的值是某个地址);也即: 指针型变量L,其值放的是新地址,把传递过来 实参的地址 覆盖了。
if(!L){return 0;}
L->next = NULL;
return 1;
}
虽然能通过编译,但是执行的时候却是一串乱码,反复思考,得出原因如下,给初学者一些帮助
C语言的函数参数是值调用(指针也是变量,只不过其值是地址),一定要记着!!!
例如:在main函数中有如下代码
int main()
{
LinkList T;//T只是声明,并未创建
int i;
i=InitList1(T);//目的是初始化创建T,正常情况下通过调用子函数InitList(T)即可;此处赋值给i,只是帮我们根据返回的值理解T传入子函数的值到底是什么
printf("初始化L后:%d\n",i);
}
正常的话,子函数1执行完,链表T应被初始化,打印出i的值应是1,因为InitList 返回1了。这本是子函数应实现的功能,也是正确的。
分析:主函数定义了T,它是个指向 LNode节点型数据的指针,当然也是个变量,那么不妨假设它的值是系统随机分配的一地址1000H(理论上指针变量必须引用到某一实体,即指向一实体变量,才有后续).
如果用第二种初始化方法这是调用InitList2函数,传入T的值,此时L = 1000H, 接下来就和T没有任何关系了,这时候给L又指向了一个节点(L的值为该节点的地址),然后节点的指针域有 L->next == NULL,返回1。
需要注意的是:子函数2确实初始化了一个链表L, 但L是形参,随着子函数2的结束,所有形参,包括L也会随之消失。
但T呢,还是刚刚在主函数里定义的一个指针而已,还是那个 随机分配的的地址(1000H),并没有真正的指向刚才创建的节点。
在看看第一种初始化方法方法,传入指针的指针,也就是:形参变量L(本身是指针变量)指向实参变量T的地址。
主函数定义了T,它是个指针变量,假设它的值为1000H(声明时随机分配),而T的地址,假设为500H。
此时调用初始化子函数1,值传递,T的地址传给L,即:L=500H(细心的会发现L和T指向值为 1000H的同一块内存空间,也即该内容空间有两个变量名字表示), *L = 1000H。 此时申请节点后,假设该节点地址为2000H, 其地址赋给 *L,然后返回 1。
回到主函数,在看一下 T 的地址依然为500,但是 *T呢,不错,已经是2000H了,也就是说此时的指针T真正的指向了一个节点了。
- int InitList1(LinkList *L)同int InitList1(LinkList &L),只不过前者用在C环境(&L编译不过去),后者 引用方式传递参数 用在C++环境才行(可以*L);再者,函数体中,前者(*L)->next,后者L->next. 教材上如出现&L引用方式传递参数,请用C++编译环境。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现