学完栈的静态存储也⇔以静态数组的方式来实现栈这种抽象数据结构之后,就是栈的链式存储,这里稍微发散一下思维:其实在ADT里的栈不是CO里物理意义上的栈,CO中的栈、堆、常量区、代码区来区分了内存的存储模式,是计算机在物理意义(内存)上的存储逻辑的应用,虽然都是stack⇔栈、heap⇔堆、但实际所指的含义还是有所区别的,这个在我学到ADT中的队列时候猛然想到,所以顺嘴一提。
刚才有点跑题,现在我回归正题,链栈这种ADT其实就是链表的应用,为什么这么说呢?我们都知道,学习一种ADT的时候,要从逻辑实现、物理实现、操作(创销增删改查空)、以及操作的时空复杂度这些方面去学习,而栈的逻辑实现FILO(first in last out)可以通过链表和数组这两种物理方式实现,而链表对应栈的逻辑实现要点就是在链表的头结点去完成增删改差(其实这样还比普通的链表更简单因为我们操作只在链表头部因此不会进入链表头部(头结点)往后,而对于链表这种数据结构不支持随机访问,只能顺序访问,因此需要使用循环来对链表访问链表的内部结点,而在链栈中由于栈这种ADT的逻辑实现只要求在顶部完成对其操作,因此在增删改查空不需要访问除了栈顶之外的结点,所以便不用循环,而访问栈顶对应的操作是常量1,所以增删改查空的时间复杂度为O(1)。
当然具体的实现是分为带头节点的链表和不带头结点的链表,这里是链栈很有玩味的地方,也是这个玩意搞得我怀疑自己链表那里学的没有扎实,便在学完队之后返回复习检验链表的知识点,不过实话实说感谢链栈让我去发现问题,不然就这么囫囵吞枣的学下去早晚得出事。
下面为链栈基本操作的源码,这里要说明的是,由于C中不支持引用操作,当然如果要用C的话用指针替代引用就好,或者从原理层出发将,创建常指针(因为引用实际上是创建了一个常指针只不过是换了符号&⇔const 类型 *)来替代引用,但是由于这只是第二遍复习(还没到第三遍),加之最近在学java没太多时间所以暂时以C++为源码语言。
//问题:如果定义一个结构,结构内的元素属性为指针,那么在函数中形参直接传结构(不使用结构指针或者引用),那么在函数中使用结构内的指针并对其进行更改会不会在结构中完成?
//检验:如果是在结构中完成,则第二次Display会无法正常输出,因为第一次display是直接使用的结构内的链表头指针进行遍历链表打印,没有对其进行复制保存
// InitialLLS(stack1);
// Display(stack1);
// Display(stack1);
//结果:第二次打印正常,所以函数中形参传结构名并且函数中使用结构内的指针使用结果没有保存,所以是当作临时变量对待,所以在函数中使用结构内的指针并对其进行更改不会在结构中完成,所以函数传参只要不是指针类传参方式都是编译器使用栈内存进行复制参数包含的全部信息然后对复制的进行修改,函数完成后编译器自动进行清除。
❓昨天在C上也是遇到这个问题,但是C上编译结果是会发生更改,打算下午再检查一下如果真是不同的话,C与C++的函数传参模式也有些许不同。
//
// main.cpp
// 栈_链栈(链式存储)_带头节点
// Created by 郭宸羽 on 1/8/2022.
//
/*⬇️
📖链栈(单链表表头⇔栈顶)
·链栈⇔链表的一种应用
·头插法建立单链表⇔链栈进栈
·单链表头删除结点⇔链栈出栈操作
⬆️*/
#include <iostream>
typedef struct Linknode
{
int data;
struct Linknode *next_p;
}Linknode,*LiStack_p;//Create node struct Linknode, and also alias Linknodepointer's name LiStack_p.
typedef struct InformOfSLL
{
int Stack_length;
LiStack_p stack_top_p;
int top_index;
}InformOfSLL;//Create a struct which has the information of stack and the pointer to (head of linklist) ⇔ (stack top).
//Initialize memery to Linkliststack
int InitialLLS(InformOfSLL &stack)
{
if(stack.stack_top_p == NULL)
{
return -1;//if stack has not success creat,means heap memory has full.
}
else
{
Linknode* tem_p=NULL;
Linknode* control_p=NULL;
//创建头节点
tem_p = (LiStack_p)malloc(sizeof(Linknode));
tem_p->next_p=NULL;
control_p=tem_p;
//外部指针接入链表(外部指针链接头节点)
stack.stack_top_p = tem_p;
//循环创建结点并连接形成链表:
for(int i=0;i<stack.Stack_length;i++)
{
tem_p = (LiStack_p)malloc(sizeof(Linknode));
tem_p->data=i;
tem_p->next_p = NULL;
control_p->next_p = tem_p;
control_p = tem_p;
}
stack.top_index=-1;//top_index=-1 ⇔ stack_top_p = stack's head node,Thus when change the stack not only change the stack_top_p but also change the top_index;
return 1;
}
}
int pops(InformOfSLL &stack,int element_data)
{
//1)创建结点
Linknode* tem_p = (LiStack_p)malloc(sizeof(Linknode));
if(tem_p==NULL)//if heap is full the tem_p will not be create and initialize it will equal to NULL
{
printf("Heap have full please clean the heap\n");
return -1;
}
else
{
tem_p->data = element_data;
//2)连接结点,完成压栈(这也是有无头节点不同的直接体现)
Linknode* control_p = stack.stack_top_p;
tem_p->next_p=control_p->next_p;//set the new node into Linklist
control_p->next_p = tem_p;//set the head node next point to new node(new linklist)
}
return 1;
}
int Display(InformOfSLL stack)
{
printf("Stack Top:");
while (stack.stack_top_p != NULL)
{
printf("[%d]->",stack.stack_top_p->data);
stack.stack_top_p = stack.stack_top_p->next_p;
}
printf("|\n");
return 1;
}
int main()
{
InformOfSLL stack1;
stack1.Stack_length = 12;//create a stack with 12 nodes length(12 means whithout head node)
//问题:如果定义一个结构,结构内的元素属性为指针,那么在函数中形参直接传结构(不使用结构指针或者引用),那么在函数中使用结构内的指针并对其进行更改会不会在结构中完成?
//检验:如果是在结构中完成,则第二次Display会无法正常输出,因为第一次display是直接使用的结构内的链表头指针进行遍历链表打印,没有对其进行复制保存
// InitialLLS(stack1);
// Display(stack1);
// Display(stack1);
//结果:第二次打印正常,所以函数中形参传结构名并且函数中使用结构内的指针使用结果没有保存,所以是当作临时变量对待,所以在函数中使用结构内的指针并对其进行更改不会在结构中完成,所以函数传参只要不是指针类传参方式都是编译器使用栈内存进行复制参数包含的全部信息然后对复制的进行修改,函数完成后编译器自动进行清除。
InitialLLS(stack1);
pops(stack1, 22);
Display(stack1);
}
昨天在C++下以同样的思路跑了一遍,发现结论与这篇文章的结论完全相反,越想越不对劲,今天重看一遍,Debug了下果然又闹笑话了😂。也是我没脑子,这个Display写了没有个几十遍也得有个十几遍了,为啥还犯这种printf数值之后不去移动指针到下一个节点再去打印呢?这还能叫遍历链表吗😓,对自己也是很无语,这次犯错一定铭记,言归正传,稍微细心的小伙伴肯定能发现Display函数的逻辑实现是:用结点指针遍历链表每到一个节点访问该节点的数值并输出到控制台,直到指针走了逻辑长度步,也就是个人设置的for循环内的i从0++到链表信息结构数据类型里的length数值为止,其实这样很容易搞出岔子,因为在写插入和删除结点的时候在末尾如果忘记对length++或lenght--那么插入或者删除后调用其他操作肯定是要出问题的,所以在这里display可以尝试用while循环控制条件是(结点指针!=NULL)也就是节点指针指向NULL(表尾)是停止。
以下是改了之后的源码:
// main.c
// 单链表(不带头结点)_插入与删除
//
// Created by 郭宸羽 on 30/7/2022.
//
#include <stdio.h>
#include <stdlib.h>
//创建结点属性
typedef struct SLNode
{
int data;
struct SLNode *next;
}SLNode,*SingleLinkList_p;
//创建链表信息属性
typedef struct InfomOfSLL
{
int ll_length;
SingleLinkList_p SLL_p;
}InfomOfSLL;
//初始化单链表(不带头结点)
int InitialSLL(InfomOfSLL*SLL,int arr[])
{
//for the strong of the function test the legal number of linklist length before create
if(SLL->ll_length>=1)
{
//o先创建第一个结点,然后创建控制节点指针指向第一个结点,then loop for create SingleLinklist
//·声明结点指针tem用于创建结点,并将单链表的SingleLinkList_p指向(初始化)第一个结点
SLNode*tem=(SingleLinkList_p)malloc(sizeof(SLNode));//从堆区开辟内存用于结点
tem->next=NULL;//初始化结点指针域
//·通过数组进行赋值,变量i在循环创建链表时用于赋值
int i=0;
tem->data=arr[i];
SLL->SLL_p=tem;//将链表头与外部指针链接
SLNode* control_p=tem;//创建临时控制指针,用于循环中创建链表时节点的链接
//o循环控制创建链表
for(i=1;i<SLL->ll_length;i++)//已将完成了第一个节点的创建故将i初始值不设为0而是1
{
tem =(SingleLinkList_p)malloc(sizeof(SLNode));
//·creat and initialize new node
tem->data=arr[i];
tem->next=NULL;//This will be more easily for create the end of the linklist pointer arrange in the loopcreate;
//·link the older Linklist end node to the new node
control_p->next=tem;
//·switch control_p to next looptime link
control_p=tem;
}
return 1;
}
else
{
printf("The legal arrange number of length is[1,∞],and please check you number");
return -1;
}
return 1;
}
void Display(InfomOfSLL SLL)
{
for(int i=0;i < SLL.ll_length;i++)
{
SLL.SLL_p->data++;
//Test——在函数中,参数使用结构,那么该结构内的指针指向的内存被修改是否会修改。猜测:不会,∵结构内的指针也是结构的一部分其传参传得是结构其内部置针也会被复制,所以更改的只是复制部分.
//AN:会改变,虽然复制了一个结构但是该结构内的指针变量和原结构指针变量相同,所以通过指针指向的数据内存都是同一个,所以同样可以达到更改数据的目的。就相当于指针的指针
printf("[%d]->",SLL.SLL_p->data);
//⚠️⬆️❌没有遍历链表而进行了打印,应该加上⬇️指针移动的code
SLL.SLL_p = SLL.SLL_p->next;
}
printf("\n");
}
int main()
{
InfomOfSLL SLL1;
SLL1.ll_length=6;
int arry1[6]={1,2,3,4,5,6};
InitialSLL(&SLL1, arry1);
Display(SLL1);
Display(SLL1);
}
结果是:第二次打印发生变化。
所以结论是: 所以函数中形参传结构名并且函数中使用结构内的指针使用结果会进行保存和更改,虽然是临时变量,但是可以通过这个临时变量所存储的内存地址(指针存储16进制的内存地址)来访问和更改堆区的动态内存数据,进而达到了永久更改的结果,所以在函数中使用结构内的指针并对其进行更改会在结构变量中完成,所以函数传参只要含有指针类传参方式都可以指针所存储的内存地址指向的内存中的数据进行修改。