【数据结构】链表其实并不难 —— 手把手带你实现双向链表

news2024/11/25 6:38:49

文章目录

  • 0. 前言
  • 1. 双向链表的概念
  • 2. 双向链表的实现
    • 2.1 结构设计
    • 2.2 接口总览
    • 2.3 初始化
    • 2.4 创建新节点
    • 2.5 尾插
    • 2.6 头插
    • 2.7 尾删
    • 2.8 头删
    • 2.9 查找
    • 2.10 在pos位置之前插入
    • 2.11 在pos位置删除
    • 2.12 打印
    • 2.13 销毁
  • 3. 完整代码
    • List.h
    • List.c
    • test.c
  • 4. 结语

0. 前言

之前,我们已经学习了单链表,在实现单链表的过程中,也发现了单链表的缺陷。

比如在尾插时,需要找到尾结点;尾删时,需要找到尾结点的前一个节点;在任意位置删除时需要找到该位置前一个节点等等等…这些都需要用时间复杂度为 O(N)的算法来处理。

所以我们说,单链表 ,也就是单向无头非循环链表,是一个有缺陷的结构 ,它有时会作为其他数据结构的 子结构

基于单链表的这些缺陷,带头双向循环链表,也就是我们说的 双向链表 就完美的解决了 单链表 的这些问题。

1. 双向链表的概念

我们首先分析一下 双向链表 ,双向链表实际上就是 带头双向循环链表

之前我们说过链表的几种结构:带头 / 不带头,单向 / 双向,循环 / 非循环,而双向链表很明显就是这些结构中,最复杂结构 的集合。

它的主要表现就是,含有头结点 —— 有一个不存储有效数据的虚拟节点,链表永不为空,所以无需传二级指针,只需要改变节点之间的链接关系;双向 —— 可以通过一个节点直接找到上一个节点;循环 —— 链表头尾相连呈环状,链表中无空指针。

那么它的 结构 设计就必然有些特殊,由于是双向,所以要比 单链表 多一个 prev 用来找到上一个节点。同样的 单链表 有的 next、data 也必不可少;再考虑上它的循环结构,那么就需要最后一个节点的 next 能找到第一个节点,第一个节点的 prev 能找到最后一个节点。

我们可以大概画出它的 示意图

image-20221022144955761

从这幅图就可以看出,双向链表的结构十分完美,它完美解决了 单链表插入、删除数据时复杂的操作,加上存储单元之间的链接多样,让数据之间的管理变得非常简单!

那么它实现起来到底复不复杂呢,我们接下来就开始设计 双向链表 ,我们在实现的过程中感受!

2. 双向链表的实现

2.1 结构设计

双向链表单链表 的结构多一个 prev 指针,用来记录上一个节点的地址。

typedef struct ListNode
{
	LTDataType data; // 保存数据
	struct ListNode* next; // 记录下一个节点的地址
	struct ListNode* prev; // 记录上一个节点的地址
}LTNode;

2.2 接口总览

实现一个 双向链表 ,总共需要以下接口:

// 初始化
LTNode* ListInit(); // 使用返回值处理
// 打印
void ListPrint(LTNode* phead);
// 尾插
void ListPushBack(LTNode* phead, LTDataType x);
// 尾删
void ListPopBack(LTNode* phead);
// 头插
void ListPushFront(LTNode* phead, LTDataType x);
// 头删
void ListPopFront(LTNode* phead);
// 查找元素
LTNode* ListFind(LTNode* phead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(LTNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(LTNode* pos);
// 销毁双向链表
void ListDestroy(LTNode* phead);

虽然接口和 单链表 差不多,但是这些接口的实现,远远比单链表 简单 地多~

而且这里有一个特殊的地方就是函数传参时,初始化接口参数 无参 ,其他接口传的是 一级指针 ,这是为什么?

首先需要明确的一点是,双向链表是带头的,含有一个头结点,就是我们单链表中提到的 哨兵位 。哨兵位不存储 有效数据 ,存在哨兵位链表就不为空,并使实现接口时更加方便。需要注意的是 只要存在哨兵位,链表的第一个节点就是哨兵位后面第一个节点

  1. 初始化函数 无参。双向链表初始化只需要创建哨兵位,然后得到哨兵位即可。在这里我也同样可以使用二级指针来操作,但是为了配合下面的接口函数,就不那么“突出”了~
  2. 其他接口参数传 一级指针,是因为我在进行相应的操作时,由于哨兵位不存储有效数据并且我并不需要改变哨兵位,所以我只需要找到哨兵位然后改变它的链接关系就可以,所以 不需要二级指针

2.3 初始化

创建一个 哨兵位 节点。

双向链表 在只有一个哨兵位时,让它自己指向自己。哨兵位的 next 指向它自己的prev ,哨兵位的 prev 指向它自己的 next 。说白了就是一个特殊的环形链表。

image-20221022145010877

由于我们这里使用的是返回值的形式,所以只要创建返回就可以。

// 初始化
LTNode* ListInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	// 双向带头循环链表的prev指向next,next指向prev
	// 但是这里只有一个节点,所以只能让它自己指向自己
	if (phead == NULL)
	{
		perror("ListInit");
		exit(-1);
	}
	phead->next = phead;
	phead->prev = phead;
	
	return phead;
}

2.4 创建新节点

双向链表 需要插入元素时,需要创建节点。这就很简单,直接 malloc 开辟,然后把值存入,两个指针给为空指针,然后返回节点就行。

// 创建新节点
LTNode* BuyListNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("ListPushBack");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

2.5 尾插

为什么说 双向链表 很完美,实现也比单链表 简单 ,从尾插就能看出来。

我们 单链表 的尾插,在链表为空时,需要特殊处理;在平常插入时,需要找到 尾结点 ,改变尾结点的链接。

对于 双向链表 的尾结点,就是哨兵位的 prev ,将其拷贝一份,放在 tail 中,然后将 tailnext 链接至新节点 newnode ,然后将 newnodeprev 链接到 tail。在处理一下 newnodeprevtail 的链接就可以了~ 且这些步骤没有先后顺序 ~

image-20221022155719811

怎么样,单链表O(N) 的时间复杂度的尾插,在这里只需要用 O(1) 就可以完成,是不是简单多了?

// 尾插
void ListPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);// 一定不为空-->有哨兵位

	LTNode* tail = phead->prev;// 尾就是prev,由于是双向循环链表,所以头的prev就是尾
	LTNode* newnode = BuyListNode(x);

	// phead              tail            newnode
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

2.6 头插

对于 头插 来说,首先需要创建节点。

然后将哨兵位的后一个节点,即链表实际上的 第一个节点 phead->next,给定一个新节点 newnode 。然后将 newnodeprev 链接到 哨兵位。再将 newnodenext 给定为先前的第一个节点 next 。然后改变该节点 (next)的 prevnewnode 的链接关系。

image-20221022160633145

// 头插
void ListPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyListNode(x);
	LTNode* next = phead->next;
	
	phead->next = newnode;
	newnode->prev = phead;
	
	newnode->next = next;
	next->prev = newnode;
}

2.7 尾删

对于 双向链表 的尾删,只要找到尾结点的前一个节点改变它和哨兵位的连接关系即可。

如果要找到尾结点的前一个节点,那么我只需要通过 哨兵位prev 找到 ,在通过 prev 就可以找到 尾结点的前一个节点。然后调整这个节点和哨兵位的链接关系,然后 释放尾结点 就可以了。

但是要注意,当链表只有哨兵位的时候不能进行删除!!!

image-20221022162006011

// 尾删
void ListPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);// 防止把哨兵位删掉 
	LTNode* tail = phead->prev; 
	LTNode* tailprev = tail->prev;
	free(tail);
	
	phead->prev = tailprev;
	tailprev->next = phead;
}

2.8 头删

对于头删来说,我需要删除链表的第一个节点,也就是 哨兵位的 next 节点 ,我需要改变 哨兵位第二个节点 的链接关系,然后释放 第一个节点 。总体来说也很简单~

image-20221022170755020

void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
    
	LTNode* next = phead->next;
	LTNode* nextNext = next->next;

	phead->next = nextNext;
	nextNext->prev = phead;
	free(next);
}

2.9 查找

对于查找一个元素在 双向链表 中存不存在,那肯定是采用遍历链表的形式。

但是对于 双向链表 来说,它是没有指向 NULL 的节点的,它是一个环,停不下来。所以我们要把循环的截止条件设定为 != phead ,这个条件就表示,已经遍历过一遍链表了,走到哨兵位了。

如果找到,返回该节点的地址;如果找不到返回 NULL

// 查找
LTNode* ListFind(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* cur = phead->next;

	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

2.10 在pos位置之前插入

pos 位置之前插入,那么通过 posprev 找到 pos 位置的上一个节点 posPrev ,然后改变 posPrev 和 新节点 newnode 之间的链接和 newnodepos 之间的链接。和头插尾插思路大致相同。

image-20221022170928980

// 在pos位置之前插入
void ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* newnode = BuyListNode(x);

	LTNode* posPrev = pos->prev;

	newnode->prev = posPrev;
	posPrev->next = newnode;

	newnode->next = pos;
	pos->prev = newnode;
}

那么有了这个接口,那么我们就可以把它 复用尾插 和 头插

对于 尾插 来说, pos 位置就是 phead ,因为 phead 的前面就是链表的尾,在 phead 位置前插入,就是尾插:

void ListPushBack(LTNode* phead, LTDataType x)
{
    assert(phead);
	ListInsert(phead, x);
}

对于 头插 来说,pos 位置就是 phead->next ,为第一个节点的前面,在 phead->next 位置前插入,就是头插:

void ListPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	ListInsert(phead->next, x);
}

2.11 在pos位置删除

pos 位置删除,只要找到 pos 的前一个节点 posPrev ,然后找到 pos 的后一个节点 posNext ,然后将这两个节点的 prevnext 建立正确的链接关系。然后释放 pos 节点,pos 节点置空。

但是注意,删除的位置不能是哨兵位。但是由于这里设计的原因,再传 phead 就显得有点不划算了,所以我们需要注意一下,pos 不能为哨兵位,自己控制好就可以了。

image-20221022171203777

void ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;

	posPrev->next = posNext;
	posNext->prev = posPrev;

	free(pos);
	pos = NULL;
}

同样的,这个接口也能复用于 尾删 和 头删

对于 尾删 来说,pos 位置就是 phead->prev ,为链表的尾,删除 phead->prev 位置,就是尾删:

void ListPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);// 防止把哨兵位删掉 

	ListErase(phead->prev);
}

对于 头删 来说,pos 位置就是 phead->next ,为链表的头,删除 phead->next 位置,就是头删:

// 头删
void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);// 防止把哨兵位删掉 
    
	ListErase(phead->next);
}

2.12 打印

打印整个链表,就只需要遍历链表,控制好循环的停止条件:

// 打印
void ListPrint(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;

	while (cur != phead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

2.13 销毁

我需要把 哨兵位链表的节点 全部删除,那么我就要使用循环来删除。所以,销毁 双向链表 的思路 和 查找是差不多的,循环的结束条件为 != phead。在销毁的过程中,每次记住我当前节点的下一个节点,以便迭代。

但是当前接口函数中,哨兵位是不能正常删除的!!!

注意:由于在函数中,我释放了哨兵位,并要将其置空。释放是可以的,因为我知道哨兵位的地址,释放就可以,但是置空却完成不了。因为我的 哨兵位 是形参,改变形参并不能影响实参,所以我们还需要在主函数中将 哨兵位 置空(在这个部分就不展示了,马上会在完整代码中呈现)。

// 销毁
void ListDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}

	free(phead);// 只销毁了形参,没有销毁实参,这里只是象征性的销毁一下
	phead = NULL;
}

3. 完整代码

List.h

#pragma once
/*
* c++中带头双向循环链表命名方式为List,所以我们也采用这个命名方式
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;
// 初始化
LTNode* ListInit();// 使用返回值处理
// 打印
void ListPrint(LTNode* phead);
// 尾插
void ListPushBack(LTNode* phead, LTDataType x);
// 尾删
void ListPopBack(LTNode* phead);
// 头插
void ListPushFront(LTNode* phead, LTDataType x);
// 头删
void ListPopFront(LTNode* phead);
// 查找元素
LTNode* ListFind(LTNode* phead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(LTNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(LTNode* pos);
// 销毁双向链表
void ListDestroy(LTNode* phead);

List.c

#define _CRT_SECURE_NO_WARNINGS 1 
#include "List.h"
// 初始化
LTNode* ListInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	// 双向带头循环链表的prev指向next,next指向prev
	// 但是这里只有一个节点,所以只能让它自己指向自己
	if (phead == NULL)
	{
		perror("ListInit");
		exit(-1);
	}
	phead->next = phead;
	phead->prev = phead;	
	return phead;
}
// 创建新节点
LTNode* BuyListNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("ListPushBack");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}
// 打印
void ListPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

// 尾插
void ListPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);// 一定不为空-->有哨兵位
	//LTNode* tail = phead->prev;// 尾就是prev,由于是双向循环链表,所以头的prev就是尾
	//LTNode* newnode = BuyListNode(x);

	 phead              tail            newnode
	//tail->next = newnode;
	//newnode->prev = tail;
	//newnode->next = phead;
	//phead->prev = newnode;
	ListInsert(phead, x);
}

// 尾删
void ListPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);// 防止把哨兵位删掉 
	/*LTNode* tail = phead->prev; 
	LTNode* tailprev = tail->prev;
	free(tail);
	
	phead->prev = tailprev;
	tailprev->next = phead;*/
	ListErase(phead->prev);
}

// 另一种写法
//void ListPopBack(LTNode* phead)
//{
//	assert(phead);
//	assert(phead->next != phead);// 防止把哨兵位删掉 
//	LTNode* tail = phead->prev; 
//	
//	phead->prev = tail->prev;
//	tail->prev->next = phead;
//	
//	free(tail); 	
//}

// 头插
void ListPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	/*LTNode* newnode = BuyListNode(x);
	LTNode* next = phead->next;
	
	phead->next = newnode;
	newnode->prev = phead;
	
	newnode->next = next;
	next->prev = newnode;*/

	ListInsert(phead->next, x);
}

// 头删
void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	/*LTNode* next = phead->next;
	LTNode* nextNext = next->next;

	phead->next = nextNext;
	nextNext->prev = phead;
	free(next);*/
	ListErase(phead->next);
}

// 查找
LTNode* ListFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

// 在pos位置之前插入
void ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = BuyListNode(x);
	LTNode* posPrev = pos->prev;
	newnode->prev = posPrev;
	posPrev->next = newnode;
	newnode->next = pos;
	pos->prev = newnode;
}

// 在pos位置删除
void ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
	pos = NULL;
}

// 销毁
void ListDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);// 只销毁了形参,没有销毁实参
	phead = NULL;
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1 
#include "List.h"
// 测试尾插、尾删
void TestList1()
{
	LTNode* plist = ListInit();// 哨兵位
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPushBack(plist, 5);
	ListPrint(plist);

	ListPopBack(plist);
	ListPopBack(plist);
	ListPopBack(plist);
	ListPopBack(plist);
	ListPopBack(plist);
	// 双向链表的缺点
	// 当链表只剩下哨兵位时,链表为空
	// 如果这时候进行删除,会把哨兵位删掉  
	// ListPopBack(plist);
	ListPrint(plist);
}
// 测试头插、任意位置删除
void TestList2()
{
	LTNode* plist = ListInit();// 哨兵位
	ListPushFront(plist, 1);
	ListPushFront(plist, 2);
	ListPushFront(plist, 3);
	ListPushFront(plist, 4);
	ListPushFront(plist, 5);
	ListPrint(plist);

	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPushBack(plist, 5);
	ListPrint(plist);

	LTNode* pos = ListFind(plist, 2);
	if (pos)
	{
		ListErase(pos);
	}
	ListPrint(plist);

	ListDestroy(plist);
	plist = NULL;// 手动置空
}

int main()
{
	//TestList1();
	TestList2();

	return 0;
}

4. 结语

到这里,本篇博客就到此结束了。其实双向链表中还可以增加判空和计算链表大小的接口,也很简单,我这里就不实现了,大家有兴趣可以试一下。

总体来说,双向链表的结构是非常完美的,一般我们说的存储数据的链表其实也就是双向链表,单链表一般是作为其他数据结构的子结构的。所以双向链表因其结构,对于数据管理是十分高效的。而且实现起来也十分便捷~

如果觉得anduin写的还不错的话,还请一键三连!如有错误,还请指正!

我是anduin,一名C语言初学者,我们下期见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/4626.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python百日进阶-WEB开发】Day175 - Django案例:07状态保持

文章目录五、状态保持5.1 Django中状态保持5.1.1 状态保持概述5.1.2 Cookie5.1.2.1 Cookie的用处&#xff1a;5.1.2.1 Cookie的特点&#xff1a;5.1.2.1 Cookie的操作&#xff1a;5.1.3 session5.1.3.1 Session的特点&#xff1a;5.1.3.2 Session依赖于Cookie5.1.3.3 存储方式5…

网页数据抓取-网页实时数据抓取软件

网页数据抓取&#xff0c;随着社会的发展&#xff0c;互联网的普及&#xff0c;不管是企业还是个人都意识到数据的重要性。今天给大家分享一款免费的网页数据抓取软件。只要点点鼠标就能轻松采集你想要的内容不管是导出还是自动发布都支持&#xff01;详细参考图片&#xff01;…

Qlib股票数据获取与查看(Qlib学习1)

文章目录Qlib基本信息数据使用方法1. 借助Qlib下载数据2. 查看相关数据参考链接Qlib基本信息 Qlib Github主页&#xff1a;https://github.com/microsoft/qlib Qlib quickstart&#xff1a;https://qlib.readthedocs.io/en/latest/introduction/quick.html#introduction 基本…

LeetCode刷题---142. 环形链表 II(双指针-快慢指针)

文章目录一、编程题&#xff1a;142. 环形链表 II&#xff08;双指针-快慢指针&#xff09;1.题目描述2.示例1&#xff1a;3.示例2&#xff1a;4.示例3&#xff1a;5.提示&#xff1a;6.提示&#xff1a;二、解题思路1.思路2.复杂度分析&#xff1a;3.算法图解三、代码实现总结…

如何理解Linux下一切皆文件

文章目录一、问题抛出二、如何理解三、Linux源码验证一、问题抛出 Linux中普通文件、目录、字符设备、块设备、网络设备等都被当做文件来对待。虽然他们的类型不同&#xff0c;但是Linux中提供了统一的操作接口。  普通文件、目录文件显然非常好理解&#xff0c;因此在本文中&…

今日论文阅读2022-11-10

多模态预训练论文ViLBERT: Pretraining Task-Agnostic Visiolinguistic Representations for Vision-and-Language Tasksvision-and-language tasks&#xff1a; visual question answering,visual commonsense reasoning, referring expressions, and caption-based image ret…

基于DeepLabV3实践路面、桥梁、基建裂缝裂痕分割

在我前面的文章中有基于改进的模型开发的裂缝裂痕检测模型&#xff0c;感兴趣的话可以看下&#xff1a; 《基于yolov5sbifpn实践隧道裂缝裂痕检测》 今天主要是趁着有时间基于deeplabv3来实践裂缝裂痕分割。首先来看效果图&#xff1a; 为了整体直观&#xff0c;这里专门是开…

腾讯蓝鲸 API 网关如何借助 APISIX 实现产品升级与业务完善

分享嘉宾朱雷&#xff0c;腾讯 IEG 运维 PaaS 平台技术负责人。 蓝鲸&#xff08;全名“蓝鲸智云”&#xff09;是一套孵化于腾讯 IEG&#xff08;互动娱乐事业群&#xff09;内部&#xff0c;服务于多业务与各内部平台的研运一体化 PaaS。 其作用是在 CI、CD 和 CO 三个阶段&a…

Spring 概述

Spring是 Java 应用程序开发框架。 Spring 框架的目标是使 J2EE 开发变得更容易使用&#xff0c;通过启用基于 POJO编程模型来促进良好的编程实践。 Spring Framework Spring 基础框架是 Spring Framework &#xff0c;基本上任何其他 Spring 项目都是以 Spring Framework 为…

如何进入 mysql?

目录 1. win r 2. 输入cmd点确定 3. 输入 mysql -u -t 4. 点回车出现 下面的 就代表已经进入 mysql 退出 mysql的 方法&#xff1a; 1. win r 2. 输入cmd点确定 3. 输入 mysql -u -t -u &#xff1a;代表你的用户名&#xff0c;如果是本地登录 则为 -uroot-p &am…

学习python第7天

Python绘制图形库turtle 1.介绍&#xff1a; turtle库根据一组函数指令的控制&#xff0c;在平面坐标系中移动&#xff0c;从 而它爬行的路径上绘制图形。 2.原理&#xff1a;turtle(海龟&#xff09;由程序控制在画布上游走&#xff0c;走过的轨迹形成绘 制的图形&#xff0c…

子不语IPO下限定价:预计2022年全年净利润下滑,华丙如为实控人

11月10日&#xff0c;子不语集团有限公司&#xff08;HK:02420&#xff0c;下称“子不语”&#xff09;在港交所公布发售结果。公告显示&#xff0c;子不语在香港公开发售及国际配售&#xff08;不含基石部分&#xff09;阶段均获得超额认购&#xff0c;将于2022年11月11日在港…

【前端】Vue+Element UI案例:通用后台管理系统-登陆页面Login

文章目录目标代码0.路由1.结构2.校验规则3.样式总代码Login.vue效果本篇很短&#xff0c;因为只有一个页面。没有功能。 目标 登陆页面&#xff0c;路由为/login有表单验证 代码 0.路由 在router的index.js文件中的routes中添加对象&#xff1a; {path:/login,component:L…

SpringBoot+Mybatis+CRUD项目

一、项目要求 创建一个 SpringBoot 项目&#xff0c;项目名”week11_学号”&#xff1b;使用 Mybatis 框架&#xff0c;也可以时可用 MybatisPlus 框架&#xff1b;访问 myschool 数据库&#xff1b;对 student 表进行操作&#xff0c;向 student 插入自己的一条记录&#xff…

Linux学习-29-用户组信息相关命令

8.17 Linux groupadd命令&#xff1a;添加用户组 添加用户组的命令是 groupadd&#xff0c;命令格式如下: [rootCncLucZK ~]# groupadd [选项] 组名选项&#xff1a; -g GID&#xff1a;指定组 ID&#xff1b;-r&#xff1a;创建系统群组。-o 一般与-g选项同时使用&#xff0…

C++ opencv 图像色彩空间转换--色域捕获

1.API和相关知识 1.inRange 在opencv中&#xff0c;我们提取指定色彩范围的区域&#xff0c;采用inRange实现&#xff0c;这样的一块区域&#xff0c;学名叫做ROI&#xff08;region of interest&#xff09;&#xff0c;感兴趣区域。 关于inRange的提取原理 图像中&#xff0…

这是一篇用python画3D爱心的代码

浅浅写一个最近很火的爱心代码 最近你是否也被李峋的爱心跳动代码所感动&#xff0c;心动不如行动&#xff0c;相同的代码很多&#xff0c;我们今天换一个玩法&#xff01;构建一个三维的跳动爱心&#xff01;嗯&#xff01;这篇博客本着开源的思想&#xff01;不是说谁对浪漫…

开放服务担心安全?vx-api-gateway值得一用

下载地址&#xff1a;VX-API-Gateway帮助文档 下载解压打开后 Windows 下vx-api-gateway启动 bin目录下start.bat文件 启动后访问http://localhost:5256/ VX-API客户端用于查看VX-API运行的基本信息,管理应用与API及黑名单,打开客户端的方式,在浏览器中请求http://地址:端口…

被生活、房贷车贷压得喘不过气的35岁测试工程师,拿什么来谈追求~

高龄测试员现状 我有位在深圳工作的测试员朋友分享了他的工作经历&#xff0c;他今年35岁&#xff0c;前不久被公司辞退&#xff0c;面对家庭&#xff0c;房贷&#xff0c;车贷的压力&#xff0c;让他的生活都是一片灰&#xff0c;离职后最让测试员感到痛心是在面试多家公司以…

Android Studio App开发之广播组件Broadcast的讲解及实战(包括收发标准、有序、静态广播实现手机震动功能 附源码)

一、收发标准广播 Android的广播机制正式借鉴了Wifi的通信原理&#xff0c;不必搭建专门的通路&#xff0c;就能在发送方与接收方之间建立连接&#xff0c;同时广播也是Android的四大组件之一&#xff0c;它用于Android各组件之间的灵活通信&#xff0c;与活动的区别在于以下几…