单双链表及其反转

一,空指针的补充

1. 空指针的定义

在 C 语言中,空指针通常被定义为 NULL,或者在 C++ 中为 nullptr。它的本质是一个指针,指向无效的地址,用来表示一个指针当前没有指向有效的内存空间。空指针并不指向实际的内存地址,因此可以用于表示指针没有被初始化或者没有指向任何有效的对象。

例如:

 
  

int *ptr = NULL; // ptr 是一个空指针

在许多编译器中,空指针通常会被定义为 0,或者一个特定的常量值(例如 0x0 或 0xFFFFFFFF 等)。

2. 空指针在内存中的表示

空指针通常被定义为某个特定的内存地址,最常见的就是 0x0(即地址为 0)。这意味着空指针指向的是内存地址 0。内存地址 0 通常被保留,不能被程序正常使用。当程序试图访问内存地址 0 时,CPU 会触发一个 段错误(segmentation fault)或 访问违规(access violation)错误。

为什么选择地址 0 作为空指针?
  1. 地址 0 被保留:在大多数系统中,地址 0 通常不映射到任何实际的物理内存。将空指针定义为指向地址 0 可以确保在任何程序试图使用一个未初始化的指针时,都会触发错误。

  2. 便于检查:使用地址 0 作为空指针的标准,使得程序可以通过简单的检查 ptr == NULL 来判断指针是否有效。

  3. 硬件限制:早期的计算机系统(尤其是 16 位和 32 位架构)在设计时就规定了地址 0 不被用于普通的数据存储或代码,因此在这些系统中,空指针指向地址 0 成为了一种标准做法。

3. 空指针在操作系统中的处理

空指针本身并没有实际的内存位置(即它不映射到任何有效的内存块),但操作系统和硬件会特别处理试图访问空指针的情况:

  • 访问控制:现代操作系统通常会通过内存管理单元(MMU)来管理虚拟内存地址。当程序访问一个空指针时,它会试图访问地址 0。如果操作系统的虚拟内存管理设置了保护,访问地址 0 会导致一个 页错误(page fault)或 段错误

  • 段错误(Segmentation Fault):当程序试图访问空指针时,操作系统会触发一个段错误,表示程序尝试访问一个它无权访问的内存地址。这种错误通常会导致程序崩溃,并生成一个错误报告或堆栈跟踪信息。

4. 空指针的作用和使用场景

空指针通常用于以下几个场景:

  • 初始化指针:用来标识指针尚未指向任何有效内存。
  • 函数返回值:有时函数会返回一个空指针来表示错误或无法返回有效数据。例如,C 标准库中的 malloc 函数在内存分配失败时返回 NULL
  • 标记结束条件:例如,链表的尾节点的 next 指针通常指向 NULL,以标志链表的结束。

5. 总结

  • 空指针本质上是一个特殊的指针值,通常定义为 NULL,并在底层映射为 0x0(地址 0)。
  • 空指针并没有指向有效的内存地址,而是用来表示指针未初始化、无法访问有效数据,或者指向一个错误的内存区域。
  • 操作系统和硬件通过内存保护和错误检测机制(如段错误、访问违规)来处理对空指针的访问,防止程序继续运行或崩溃。
  • 由于空指针的特殊性,它在程序中被广泛用于表示无效的指针或作为函数返回值来指示错误或未找到数据。

在系统的设计中,空指针是一个非常有用且关键的概念,能够帮助开发者管理指针的生命周期和程序的内存访问安全。

二,复习怎么写链表

1,第一尝试

改了几个错误,

#include

struct ListNode
{
	int value;
	ListNode* next;
};

ListNode* create_list_node(int num);
void printlist(ListNode*);

int main()
{
	ListNode* head = create_list_node(5);
	printlist(head);
	return 0;
}

ListNode* create_list_node(int num)
{
	//create the head node 
	ListNode* head = (ListNode*)malloc(sizeof(ListNode));
	head->value = 0;
	head->next = NULL;

	//create a copy of the head node
	ListNode* current = head;


	for (int i = 1; i < num + 1; i++)
	{
		ListNode* p = (ListNode*)malloc(sizeof(ListNode));
		p->value = i;
		p->next = NULL;
		current->next = p;
		current = p;
	}

	return head;
}


void printlist(ListNode* head)
{
	ListNode* current = head;
	while (current->next != NULL)
	{
		std::cout << current->value << std::endl;
		current = current->next;
	}

}

2,改进

单双链表及其反转_第1张图片

单双链表及其反转_第2张图片

修改后的代码

#include

struct ListNode
{
	int value;
	ListNode* next;
};

ListNode* create_list_node(int num);
void printlist(ListNode*);
void deletenode(ListNode*);

int main()
{
	ListNode* head = create_list_node(5);
	printlist(head);
	delete(head);
	return 0;
}



//the definition of function
ListNode* create_list_node(int num)
{
	//create the head node 
	ListNode* head = new ListNode;
	head->value = 0;
	head->next = NULL;

	//create a copy of the head node
	ListNode* current = head;


	for (int i = 1; i < num + 1; i++)
	{
		ListNode* p = new ListNode;
		p->value = i;
		p->next = NULL;
		current->next = p;
		current = p;
	}

	return head;
}


void printlist(ListNode* head)
{
	ListNode* current = head;
	while (current != NULL)
	{
		std::cout << current->value << std::endl;
		current = current->next;
	}

}

void deletenode(ListNode* head)
{
	ListNode* current = head;
	while (current != NULL)
	{
		ListNode* nextNode = current->next;
		delete current;
		current = nextNode;
	}
}

增加了删除节点和使用new来分配内存空间。

三,反转链表

1,反转单链表

看完左神的视频后第一次尝试:

#include

struct ListNode
{
	int value;
	ListNode* next;
};

ListNode* create_list_node(int num);
void printlist(ListNode*);
void deletenode(ListNode*);
ListNode* reverse_nodelist(ListNode*);

int main()
{
	ListNode* head = create_list_node(5);
	printlist(head);
	std::cout << "the nodelist will be reversed !" << std::endl;
	head = reverse_nodelist(head);
	printlist(head);
	delete(head);
	return 0;
}



//the definition of function
ListNode* create_list_node(int num)
{
	//create the head node 
	ListNode* head = new ListNode;
	head->value = 0;
	head->next = NULL;

	//create a copy of the head node
	ListNode* current = head;


	for (int i = 1; i < num + 1; i++)
	{
		ListNode* p = new ListNode;
		p->value = i;
		p->next = NULL;
		current->next = p;
		current = p;
	}

	return head;
}


void printlist(ListNode* head)
{
	ListNode* current = head;
	while (current != NULL)
	{
		std::cout << current->value << std::endl;
		current = current->next;
	}

}

void deletenode(ListNode* head)
{
	ListNode* current = head;
	while (current != NULL)
	{
		ListNode* nextNode = current->next;
		delete current;
		current = nextNode;
	}
}


ListNode* reverse_nodelist(ListNode* head)
{
	ListNode* current = head;
	ListNode* pre = NULL;
	ListNode* next = NULL;
	while (current != NULL)
	{
		next = current->next;
		current->next = pre;
		pre = current;
		current = next;
	}

	return pre;
}

2,改进

  1. 内存释放

    • 确保在 main 函数中调用 delete_list 来释放链表,而不是直接 delete head
  2. 代码优化

    • 重命名变量 pre 为 prev,更符合常见命名习惯。
    • 修改 deletenode 为更具语义的名称 delete_list
  3. 反转链表函数

    • 保持反转链表的代码简洁,命名清晰,功能完备。
  4. 内存管理

    • 使用 delete_list 来释放链表的内存,确保避免内存泄漏。

2,反转双链表

第一次尝试,

Double_node* reverse_nodelist(Double_node* head)
{
	Double_node* current = head, *prev = NULL, *next = NULL;
	current = head;

	while (current != NULL)
	{
		prev = current->last;
		current->last = current->next;
		next = current->next;
		current->next = prev;
		current = next;

	}
	return prev;
}

会发现当current是空指针时,prev仍然指的是倒数第二个节点,无法返回头节点。

我看半天才看出来为什么没有输出结果,我问AI也没有给我说明白。就是current->next = current->last;

最初current是赋值为head,而head的last是NULL,所以导致后面的全是NULL,

Double_node* reverse_nodelist(Double_node* head)
{
	Double_node* current = head;
	Double_node* prev = NULL;
	Double_node* next = NULL;
	current = head;

	while (current != NULL)
	{
		next = current->next;
		current->next = prev;
		current->last = next;
		prev = current;
		current = next;

	}
	return prev;
}

我感到很奇怪的一件事就是我的代码在VS上运行时就是没有翻转后的输出,但在其他DEV上就行。奇怪。

你可能感兴趣的:(开发语言)