赞
踩
ans->next= cur->next
问:struct ListNode *head = (struct ListNode *)malloc(sizeof(struct ListNode));是什么意思 并讲解其涉及到的基本语法?
答:这个代码是用于创建一个链表的,具体来说:
1. struct ListNode head = (struct ListNode )malloc(sizeof(struct ListNode));
这行代码是创建链表的头节点。它使用malloc函数在堆上动态分配了一个struct ListNode类型的内存空间,并将其首地址赋值给头指针head。
其中:
- struct ListNode 是之前定义的链表节点的结构体类型。
- malloc函数可以在堆上动态分配指定大小的内存块,返回值为void*,需要强制类型转换为对应类型的指针。
- sizeof(struct ListNode) 计算struct ListNode类型的大小,以分配正确大小的内存空间。
- 将malloc的返回值强制类型转换为struct ListNode*,并赋值给头指针head。
2. 关键的语法知识点包括:
- struct定义结构体类型
- malloc和free用于在堆上动态分配和释放内存
- 指针变量的定义和使用
- sizeof计算类型或变量的大小
- 强制类型转换(type casting)
所以这行代码的作用是使用malloc在堆上动态分配一个结构体类型的内存空间,用于存储链表的头节点,并将其地址赋值给头指针head,后面可以通过这个头指针来访问和操作这个链表。
struct ListNode* getKthfromend(struct ListNode* head, int k)这句话的语法是表示创建了一个专门函数用来实现相关功能吗?如果是 为什么需要struct ListNode*呢?
因为函数声明 的定义是:
C语言中函数声明主要有以下几种形式:
1. 完整函数原型声明
格式:
返回值类型 函数名(参数表){函数体}
例如:
int add(int a, int b) {
return a + b;
}
这是推荐的标准函数声明格式。包含返回值类型、函数名、参数表、函数体。
2. 只有函数原型声明
格式:
返回值类型 函数名(参数表);
例如:
int add(int a, int b);
这种只声明函数原型,不包含函数体。通常在头文件中使用。
3. 只有函数名称声明
格式:
返回值类型 函数名();
例如:
int add();
这种只包含返回类型和函数名,参数未知。早期C语言常用,现在不推荐。
4. 隐式函数声明
不进行函数声明,直接使用函数名。编译器会默认函数返回int。不安全,应尽量避免。综上所述:之所以使用struct listnode*函数声明
1. 返回链表节点
这个函数需要返回倒数第k个链表节点,所以函数返回值类型是 struct ListNode* 。
2. 输入链表头节点
函数的参数需要输入一个链表的头节点用于遍历,所以有 struct ListNode* head 参数。
3. 与节点结构匹配
ListNode 是表示链表节点的结构体,返回值和参数类型与该结构体匹配。
4. 访问节点指针
函数内部需要通过指针访问节点的成员,所以用结构体指针类型。
5. 修改节点指针
在获取倒数第k个节点时,可能需要修改节点的指针指向,所以需要指针。
综上,使用 struct ListNode* 与函数的输入和输出匹配,并可以在函数内部直接操作节点指针,使代码逻辑更清晰正确。所以这个函数声明需要使用 ListNode 结构体指针类型。
struct ListNode {
int val; //代表数据
struct ListNode *next; //代表指针
};为什么 struct ListNode *next; 必须用struct类型?
答:struct ListNode next 在结构体struct ListNode中使用自己的类型指针struct ListNode,有以下几个原因:
1. 链表的每个节点需要通过next指针指向下一个节点,使用结构体自己的指针可以更清晰地表示这种引用关系。
2. C语言中,结构体内的指针如果不初始化,会包含随机值,所以最好将其指向具体的类型,而不是void*。
3. 定义具体的类型可以在访问链表节点时,直接使用next指针访问节点成员,更加直观。
4. 可以方便地通过next指针遍历整个链表,不需要强制类型转换。
5. 使用具体类型指针还能够调用该类型的函数,提高代码的可读性和可维护性。
6. 如果不使用结构体自己的指针类型,则需要使用类似struct ListNode* next; 在各处代码中进行类型转换,增加代码的复杂性。
所以对于链表这样的数据结构,使用自己的类型指针可以更好地表示节点之间的逻辑关系,降低编码复杂度,提高可读性和可维护性。这是结构体指针使用自己类型的主要原因。
问:struct ListNode* initLink() {
int i;
//
struct ListNode* p = NULL;
//这一步是用来创造头指针 也就是说先创造指针域,再创造数据域?
struct ListNode* temp = (struct ListNode*)malloc(sizeof(struct ListNode));
temp->val = 0;
temp->next = NULL;//这里不是 ListNode*next 一个是指针一个是temp 不一样
p = temp;
为什么说p=temp是让头节点指向头指针?为什么不是p->next=temp?
答:
在这里p和temp都代表指针变量,所以p = temp表示将temp指针的值赋值给p,使p也指向这个头节点,而不是通过p->next去更改p所指向的节点的next指针。
如果是p->next = temp,效果就不一样了:
1. 一开始p和temp都未初始化,都是空指针:
p temp
NULL NULL
2. 然后初始化temp指向头节点:
temp -> 头节点
p
NULL
3. 如果执行p->next = temp,意思是把p所指向的节点的next指针指向temp指向的头节点:
p
NULL
|
v
temp -> 头节点
由于p本身为空指针,所以这条语句其实没有真正执行,p还是NULL。
4. 如果执行p = temp,则:。
p
NULL
|
temp -> 头节点
这才让p和temp都指向了头节点,实现了头指针的效果。
所以p = temp是正确的让头指针p指向头节点的方式,而p->next = temp在这里不会产生预期的效果。
主要区别在于p = temp改变的是p自身的指针值,而p->next = temp改变的是p所指向节点的next指针值。
自我理解:首先它是创造了一个头指针,然后又创造了一个头节点,这个投节点指向了后面,此时只需要把头指针也就是p=temp,这样就可以让头指针指向头节点,又或者说此时头指针等于二头节点。
涉及到的指针知识点:
设计到了以下关于C语言指针的知识点和语法:
1. 指针变量的定义
定义指针变量的语法是在变量类型前加上*,如:
struct ListNode* p;
这就定义了一个名为p的指针变量,其类型是struct ListNode*。
2. &和*的含义
&用于获取变量的地址,如&a会得到变量a的地址。
*用于declare pointer变量,也用于从指针变量中获取它所指向的内容,比如*p会得到p所指向的结构体变量的值。
3. 指针赋值
可以使用=直接将一个指针变量赋值给另一个指针变量,会将指向的地址复制过来。
如p = temp会将temp的地址值赋给p。
4. 结构体指针操作
可以使用指针变量访问结构体中的字段,比如p->val。
也可以通过指针修改结构体中的字段,如p->next = XXX。
5. malloc函数
malloc用于在堆上动态分配内存,需要包含stdlib.h头文件。
malloc的返回值是void*,需要强制类型转换为具体的指针类型。
6. NULL值
NULL表示空指针,可以赋值给指针对其进行初始化。
int32_t getLength(struct ListNode* p) {
struct ListNode* temp = p;//temp指针用来遍历链表
int length=0;
//只要temp指向结点的next值不是NULL,就执行输出语句。
while (temp) {
// struct ListNode* f = temp;//准备释放链表中的结点
length++;
temp = temp->next;
// free(f);
}
return length;
}为什么要 struct ListNode* temp = p;这一步?
答:1. 不影响原始的头指针p
如果直接使用p来遍历链表,会改变p的值,不能保证返回后p还指向头节点。
而使用临时指针temp,可以避免改变头指针p。
2. 从头开始遍历链表
头指针p指向头节点,使用p初始化temp,可以让temp从链表头开始遍历。
如果不使用p初始化temp,则temp会是一个空指针,无法遍历整个链表。
3. 可重复利用temp遍历链表
temp初始化为p后,可以被重复使用,每次循环让temp = temp->next来遍历下一个节点。
如果每次循环里都重新定义指针,则不能重复利用。
4. p仍可用来直接访问头节点
因为p没有被修改,所以返回后还可以通过p访问头节点,方便后续操作。
5. 避免修改实参
p作为函数入参,不应在函数中被修改,使用temp拷贝一份可以防止修改实参。
链表增加元素 首先要考虑到是否有元素还有是否溢出的问题,然后需要注意,插入之后需要将newnode换成head
struct ListNode* insertNode(struct ListNode* head, struct ListNode* nodeInsert, int position) {
if (head == NULL) {
// 这里可以认为待插入的节点就是链表的头节点,也可以抛出不能插入的异常
return nodeInsert;
}
int size = getLength(head);
if (position > size + 1 || position < 1) {
printf("位置参数越界");
return head;
} // 插入节点到头部
if (position == 1) {
nodeInsert->next = head;
head = nodeInsert;
return head;
} struct ListNode* pNode = head;
int count = 1;
// 遍历链表,找到插入位置的前一个节点
while (count < position - 1) {
pNode = pNode->next;
count++;
}
nodeInsert->next = pNode->next;
pNode->next = nodeInsert; return head;
}这个position是什么意思?
在这个插入节点的函数中,position 参数表示希望插入新节点的位置。
具体来说:
1. position 的含义是索引位置
position=1 表示插入到链表头部
position=2 表示插入到第一个节点的后面
position=n 表示插入到第n-1个节点的后面
2. position 的取值范围是 1 ~ 链表长度+1
位置编号从 1 开始,最大不能超过原链表长度+1
如果越界会报错并返回原链表
3. 根据 position 将新节点插入链表
如果是插入头部,直接更新头节点
否则需要遍历找到位置,插入新节点到前一个节点后面
4. 返回值是插入后新的链表头指针
需要返回头指针是因为头节点可能被插入函数修改
5. position 的作用是明确指定新节点的插入位置
综上,这个position参数可以明确指定用户希望的新节点的插入位置,使得函数更加灵活,可以将新节点插入到链表的任意位置。
为什么return head?
return head是为了将插入后新的头指针返回,以便调用者可以通过头指针继续操作这个链表。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。