线性表初始化int InitList(LinkList *L)、int InitList(LinkList L)及int InitList(LinkList &L)

  • 单链表的存储结构

   typedef struct LNode

{
      int data;
      struct Node * next
}LNode, *LinkList,*ptr;

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
  1.  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;

                   while(p = L->next)
                         free(p);
           }
  • 销毁链表L,释放链表L申请的内存,使L的值重新变为NULL,所以会改变L的值,得用*L 
  3. void DestroyList(LinkList *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 LL本身就已经是指针了,为何初始化还要这里为何还要定义指针的指针 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++编译环境。

 

posted @   师大无雨  阅读(3447)  评论(3编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示