初阶数据结构之---顺序表和链表(C语言)

news2025/1/9 18:22:35

引言-线性表

线性表:

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构。线性表在逻辑上是线性结构,也就是说是连续的一条直线。但在物理上并不一定是连续的。线性表在物理上存储时,通常以数组链式结构的形式存储。

我们今天的主角,顺序表和链表,其实都是线性表,当然线性表不止包含这两个

线性表:

  • 顺序表
  • 链表
  • 队列
  • 字符串
  • ……

再次声明:线性表的逻辑结构是线性的,物理结构不一定是线性

顺序表

概念及结构

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

顺序表一般可以分为:

1.静态顺序表:使用定长存储元素

2.动态顺序表:使用动态开辟数组的存储

来跟我一起手搓个顺序表吧

静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们手搓动态顺序表。

我们先写一个头文件,里面写好我们维护的动态顺序表以及要实现的接口函数

结构及接口Sqlist.h

//Sqlist.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<assert.h>
#define INIT_CAPACITY 4
typedef int SLDataType;
// 动态顺序表 -- 按需申请
typedef struct SeqList
{
    SLDataType* a;//指向动态开辟数组
    int size;     // 有效数据个数
    int capacity; // 空间容量
}SL;

//初始化和销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
//顺序表打印
void SLPrint(SL* ps);
//扩容
void SLCheckCapacity(SL* ps);
//头部插入删除 / 尾部插入删除
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
//指定位置之前插入/删除数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
//顺序表查找数据
int SLFind(SL* ps,SLDataType x);

往下就可以开始实现我们的顺序表内容了,下面对于接口的实现放在 Sqlist.c 中

初始化和销毁

void SLInit(SL* ps)
{
    ps->a = NULL; //开始时,给一个空指针
    ps->capacity = ps->size = 0;
}

void SLDestroy(SL* ps)
{
    assert(ps); //断言,防止ps为空指针
    ps->capacity = ps->size = 0;
    free(ps->a);
    ps->a = NULL;
}

顺序表打印

void SLPrint(SL* ps)
{
    assert(ps);
    for (int i = 0; i < ps->size; i++) {
        printf("%d\n",ps->a[i]);
    }
    printf("\n");
}

 这里需要注意的是,在打印过程中,往顺序表中放置的数据类型不同,所打印的方式也会有所不同,在头文件Sqlist.h中

typedef int SLDataType;

这句代码说明放入的数据类型是int,所以我这里就使用int的打印方式了。

扩容

void SLCheckCapacity(SL* ps)
{
    if (ps->size == ps->capacity) {
        int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
        SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType));
        //防止开辟空间失败返回空指针
        if (tmp == NULL) {
            perror("malloc fail:");
            exit(1);
        }
        ps->a = tmp;
        //更新容量
        ps->capacity = newCapacity;
    }
}

扩容的部分在整个动态顺序表中占据非常重要的地位,关系到堆中空间的开辟,保证后续数据操作的顺利进行。

头部插入删除和尾部插入删除数据

//头部插入删除
void SLPushFront(SL* ps, SLDataType x)
{
    assert(ps);
    SLCheckCapacity(ps);//保证插入时不会越界
    for (int i = ps->size; i > 0; i--) {
        ps->a[i] = ps->a[i - 1];
    }
    ps->a[0] = x;
    ps->size++;
}
void SLPopFront(SL* ps)
{
    assert(ps);
    assert(ps->size);
    for (int i = 0; i < ps->size - 1; i++) {
        ps->a[i] = ps->a[i + 1];
    }
    ps->size--;
}
//尾部插入删除
void SLPushBack(SL* ps, SLDataType x)
{
    assert(ps);
    SLCheckCapacity(ps);
    ps->a[ps->size++] = x;
}

void SLPopBack(SL* ps)
{
    assert(ps);
    assert(ps->size != 0);
    ps->size--;
}

这里要注意的是,头部插入删除的实现方式,是将整个后面的数据做了一个移动操作,时间耗费比较大,所以顺序表在实际应用当中,尽量避免使用头插头删。

指定位置之前插入数据和指定位置删除数据

void SLInsert(SL* ps, int pos, SLDataType x)
{
    assert(ps);
    assert(pos >= 0 && pos <= ps->size);
    SLCheckCapacity(ps);
    for (int i = ps->size; i > pos; i--) {
        ps->a[i] = ps->a[i - 1];
    }
    ps->a[pos] = x;
    ps->size++;
}

void SLErase(SL* ps, int pos)
{
    assert(ps);
    assert(pos >= 0 && pos < ps->size);
    for (int i = pos; i < ps->size - 1; i++) {
        ps->a[i] = ps->a[i + 1];
    }
    ps->size--;
}

这里的插入和删除操作在顺序表中其实也避免不了数据的移动,这也体现了顺序表的一个缺陷,中间部分数据的插入删除的时间复杂度较高。

查找数据

最后就是查找列表中数据,返回找到的下标

int SLFind(SL* ps,SLDataType x)
{
    assert(ps);
    for (int i = 0; i < ps->size; i++) {
        if (ps->a[i] == x)return i;
    }
    return -1;
}

这里注意一下,数据的匹配查找其实也要匹配 a 动态数组中的的数据类型,这里我们定义的数据类型为int,就以int的查找方式查找。

体验体验手搓的动态顺序表

以下是体验码

#include"Sqlist.h"
int main()
{
	struct SeqList sq;
	SLInit(&sq);
	SLPushBack(&sq, 1);
	SLPushBack(&sq, 2);
	SLPushBack(&sq, 5);
	SLPushBack(&sq, 6);
	SLPushBack(&sq, 3);
	SLPushFront(&sq, 4);
	SLPrint(&sq);//4 1 2 5 6 3 

	SLPopBack(&sq);
	SLPrint(&sq);//4 1 2 5 6

	SLPopFront(&sq);
	SLPrint(&sq);//1 2 5 6

	int pos1 = SLFind(&sq, 5);
	SLErase(&sq, pos1);
	SLPrint(&sq);//1 2 6

	int pos2 = SLFind(&sq, 6);
	SLInsert(&sq, pos2, 100);
	SLPrint(&sq);//1 2 100 6
    SLDestroy(&sq);
	return 0;
}

 以上就是手搓的动态顺序表以及使用了。

链表

链表的概念及结构

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 

大概是这样一个东西

注:

  1. 从图上可以看出,链式结构在逻辑上是连续的,但是在物理上不一定连续
  2. 现实中的结点一般都是从堆上申请出来的
  3. 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续

链表的分类

其实链表不止我刚刚展示的一种,以下情况组合起来就有8种链表结构

1.单像或者双向

图中,上面的是单向,下面为双向

2.带头或者不带头

图中,上面是不带头,下面是带头

3.循环或者非循环

图中,上面是,不循环,下面是循环

它们两两排列组合 2 * 2 * 2 刚好就为8

虽然有这么多结构,但是实际上最常用的只有两种结构:

  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
    构的子结构
    ,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多
  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了

这次手搓个单链表怎样

这里的单链表当然指的是无头单项非循环链表喽。

SList.h存放了单链表结点结构和函数声明

//SList.h
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

//打印单向链表内容
void SLTPrint(SLTNode* phead);
//创建新节点
SLTNode* CreatNewNode(SLTDataType x);
//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);

下面来实现函数声明的源代码

链表打印

void SLTPrint(SLTNode* phead)
{
	assert(phead);
	SLTNode* pcur = phead;
	while (pcur != NULL) {
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

动态申请一个结点

SLTNode* CreatNewNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL) {
		perror("malloc fail:");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

这里创建新结点的重要性不亚于顺序表中的扩容,结点的内存也是开辟在堆上的。

头部的插入删除和尾部的插入删除

//头部插入删除
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = CreatNewNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* prev = *pphead;
	*pphead = (*pphead)->next;
	free(prev);
	prev = NULL;
}
//尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = CreatNewNode(x);
	if (*pphead == NULL) {
		*pphead = newnode;
		return;
	}
	SLTNode* ptail = *pphead;
	while (ptail->next) {
		ptail = ptail->next;
	}
	ptail->next = newnode;
}
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	if ((*pphead)->next == NULL) {
		free(*pphead);
		*pphead == NULL;
		return;
	}
	SListNode* ptail = *pphead;
	SListNode* prev = NULL;
	while (ptail->next) {
		prev = ptail;
		ptail = ptail->next;
	}
	prev->next = NULL;
	free(ptail);
	ptail = NULL;
}

这里链表头插头删的时间复杂度相比顺序表就大大降低了,可是尾插尾删还是有一定缺陷的,其操作必须走到链表末尾才能进行。

查找

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* pcur = phead;
	while (pcur) {
		if (pcur->data == x) {
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

这里查找的逻辑非常简单,就是遍历链表匹配元素,如果没找到返回一个空指针。

删除pos结点

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);
	if (*pphead == pos) {
		SLTNode* del = *pphead;
		*pphead = (*pphead)->next;
		free(del);
		del = NULL;
	}
	SLTNode* pcur = *pphead;
	while (pcur&&pcur->next != pos) {
		pcur = pcur->next;
	}
	pcur->next = pos->next;
	free(pos);
	pos = NULL;
}

这里稍微注意传入的pos是一个指针,指向链表中的元素

这里你是否注意到pphead是一个二级指针,是的,当pos指向头结点时,需要改变外部phead结点的指向,改变phead指针指向就需要使用二级指针pphead了。

指定位置之后插入数据

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = CreatNewNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

为什么不提供在指定数据之前插入数据呢?是由于此单链表的无头和单向性,使其很难确定前驱节点的位置和情况,不过硬要提供其实也是可实现的。

同时这里的pos也是一个指针

删除pos之后的结点

void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

删除链表释放空间

void SListDesTroy(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* next = (*pphead)->next;
	SLTNode* pcur = (*pphead);
	while (pcur) {
		next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

注:这里的链表是一个结点一个结点释放的。

体验下手搓的单链表

int main()
{
	SLTNode* phead = NULL;
	SLTPushBack(&phead, 1);
	SLTPushBack(&phead, 2);
	SLTPushBack(&phead, 3);
	SLTPushBack(&phead, 4);
	SLTPushFront(&phead, 5);
	SLTPrint(phead);//5->1->2->3->4->NULL

	SLTPopBack(&phead);
	SLTPopFront(&phead);
	SLTPrint(phead);//1->2->3->NULL

	SLTNode* ret1 = SLTFind(phead, 3);
	SLTInsert(&phead, ret1, 100);
	SLTPrint(phead);//1->2->100->3->NULL

	SLTNode* ret2 = SLTFind(phead, 2);
	SLTErase(&phead, ret2);
	SLTPrint(phead);//1->100->3->NULL

    SLTDestroy(phead);
	return 0;
}

以上就是单项不循环链表的内容了。

来来来,再手搓个双向链表可否?

这里的双向链表便是带头循环双向链表,复杂了些,但用起来确实不知道比单链表爽多少倍。

下面放到LTList.h中

//LTList.h
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

//创建双向链表结点
LTNode* LTBuyNode(LTDataType x);
//下面有两种初始化方式,这里我们选择第二种,两个其实差别不大
//void LTInit(LTNode** pphead);
LTNode* LTInit();
//销毁链表
void LTDestroy(LTNode* phead);
//打印链表
void LTPrint(LTNode* phead);
//判断链表是否为空
bool LTEmpty(LTNode* phead);
//双向链表的尾插和尾删
void LTPushBack(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);
//双向链表的头插和头删
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);
//在pos位置之后插入和删除数据
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);
//查找
LTNode* LTFind(LTNode* phead, LTDataType x);

然后就可以实现我们函数声明的源代码了,放到LTList.c中

创建双向链表结点

LTNode* LTBuyNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	newnode->data = x;
	newnode->next = newnode->prev = newnode;
	return newnode;
}

初始化链表

LTNode* LTInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL) {
		perror("malloc phead fail:");
		exit(1);
	}
	phead->data = -1;
	phead->next = phead->prev = phead;
	return phead;
}

头节点data里其实放什么值都无所谓

销毁链表

void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	LTNode* pnext = pcur->next;
	while (pcur != phead) {
		free(pcur);
		pcur = pnext;
		pnext = pnext->next;
	}
	free(phead);
	pcur = pnext = phead = NULL;
}

这里和单链表销毁同理

打印链表

void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead) {
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

判断链表是否为空

bool LTEmpty(LTNode* phead)
{
	assert(phead);
	if (phead->next == phead)
		return true;
	else
		return false;
}

链表头和链表末尾的插入删除

//链表头的插入和删除
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->next = phead->next;
	newnode->prev = phead;
	phead->next->prev = newnode;
	phead->next = newnode;
}
void LTPopFront(LTNode* phead)
{
	assert(phead);
	LTNode* del = phead->next;
	phead->next = del->next;
	del->next->prev = phead;
	free(del);
	del = NULL;
}
//链表末尾的插入和删除
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->next = phead;
	newnode->prev = phead->prev;
	phead->prev->next = newnode;
	phead->prev = newnode;
}

void LTPopBack(LTNode* phead)
{
	assert(phead);
	LTNode* del = phead->prev;
	phead->prev = del->prev;
	del->prev->next = phead;
	free(del);
	del = NULL;
}

这里链表尾的插入删除就和单链表尾的插入删除不一样了,双向链表可以直接通过head->prev直接找到链表末尾,因此时间复杂度大大降低。

在pos元素之后插入和删除结点

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = pos;
	newnode->next = pos->next;
	pos->next->prev = newnode;
	pos->next = newnode;
}
void LTErase(LTNode* pos)
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

查找

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead) {
		if (pcur->data == x) {
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

以上便是双向链表源代码实现的全部内容了

 再来试试我们写的双向链表

int main()
{
	LTNode* phead = LTInit();
	LTPushBack(phead, 1);
	LTPushBack(phead, 2);
	LTPushBack(phead, 3);
	LTPushFront(phead, 4);
	LTPushFront(phead, 100);
	LTPrint(phead);//100->4->1->2->3->

	LTPopFront(phead);
	LTPopBack(phead);
	LTPrint(phead);//4->1->2->

	LTNode* ret = LTFind(phead, 2);
	LTInsert(ret, 120);
	LTPrint(phead);//4->1->2->120->

	LTErase(ret->next);
	LTPrint(phead);//4->1->2->
	LTDestroy(phead);
	return 0;
}

很好,到这里,双向链表的内容也就差不多了。

顺序表和链表小结

顺序表和链表虽然在物理上都是线性的,在实际包装好使用时差别也不大,但是底层却天差地别

合理运用顺序表和链表各自的优势很有利于一些项目的开发,下面是对顺序表和链表的对比总结

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续

随机访问

支持:O(1)不支持:O(N)
任意位置插入或者删除元素可能需要搬移元素,效率低:O(N)只需修改指针指向
插入动态顺序表,空间不够需要扩容没有容量的概念
应用场景元素高效存储+频繁访问让人难以位置插入和删除频繁
缓存利用率

如果你想了解缓存利用率相关的知识,可以看看下面博客

  ​​​​​ 与程序员相关的CPU缓存知识

结语

今天的内容到这里就结束了,本来想着把这篇博客分成三部分的,不知咋回事一口气给写完了,一万多字其实很多一部分是代码。后续博主还会继续产出数据结构系列的内容。如果本篇博客对你有帮助的话,还请多多支持博主,感谢大家♥

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

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

相关文章

基于EasyCVR视频汇聚系统的公安网视频联网共享视频云平台建设思路分析(一)

随着社会的发展和科技的进步&#xff0c;视频监控系统在各个领域的应用越来越广泛&#xff0c;视频云平台建设已经成为了行业数字化转型的重要一环。公安网视频汇聚联网共享云的建设需要充分考虑技术、架构、安全、存储、计算等多方面因素&#xff0c;以确保平台的稳定性和可用…

深度学习图像处理基础

这里写目录标题 分辨率是什么 视网膜屏视网膜屏人眼的视觉视力 像素密度设置合适的PPI&#xff0c;制造视网膜屏 色彩是什么色匹配实验色彩匹配的意义量化色彩匹配白色合为1色度图 总结 HDR光亮度&#xff08;尼特&#xff09;灰阶亮度范围HDR显示技术总结 数字图像化概览 人脸…

Excel SUMPRODUCT函数用法(乘积求和,分组排序)

SUMPRODUCT函数是Excel中功能比较强大的一个函数&#xff0c;可以实现sum,count等函数的功能&#xff0c;也可以实现一些基础函数无法直接实现的功能&#xff0c;常用来进行分类汇总&#xff0c;分组排序等 SUMPRODUCT 函数基础 SUMPRODUCT函数先计算多个数组的元素之间的乘积…

German Prepositions

German Prepositions 一, ab and auerhalb1, ab2,auerhalb 二, an三&#xff0c;auf 一, ab and auerhalb 1, ab 2,auerhalb 二, an 静三 动四 时间&#xff08;节日省略dem&#xff09; 表近似 三&#xff0c;auf 静三 动四 meas wann 加一段时间 表方式 固定搭…

若依前后端分离版如何集成的mybatis以及修改集成mybatisplus实现Mybatis增强

场景 若依前后端分离版手把手教你本地搭建环境并运行项目&#xff1a; 若依前后端分离版手把手教你本地搭建环境并运行项目_本地运行若依前后端分离-CSDN博客 SpringBoot中使用PageHelper插件实现Mybatis分页&#xff1a; SpringBoot中使用PageHelper插件实现Mybatis分页-C…

本地创建Git仓库

在 Windows 下&#xff0c;可以通过以下步骤在本地创建一个 并模拟远程Git 仓库。 1、在命令行中打开模拟远程Git 仓库目标文件夹&#xff1a; 打开命令提示符或 PowerShell。例如&#xff1a; 创建裸仓库&#xff08;模拟远程仓库&#xff09;&#xff1a;创建一个裸仓库&am…

【Flutter】底部导航BottomNavigationBar的使用

常用基本属性 属性名含义是否必须items底部导航栏的子项List是currentIndex当前显示索引否onTap底部导航栏的点击事件&#xff0c; Function(int)否type底部导航栏类型&#xff0c;定义 [BottomNavigationBar] 的布局和行为否selectedItemColor选中项图标和label的颜色否unsel…

Java学习小记——设计模式

设计模式 设计模式简介Singleton模式Singleton模式简介Singleton的创建双重锁模式Double checked locking作为Java类的静态变量 变继承关系为组合关系组合模式装饰器模式 如何创建对象抽象工厂模式 设计模式简介 设计模式&#xff08;Design pattern&#xff09;代表了最佳的实…

2024 年了,如何 0 基础开始学习 Vue ?

最近 5 个月&#xff0c;我都在忙着构建我的第一开源项目 HexoPress&#xff0c;这个项目是使用 Electron Vue 3 TypeScript 等技术实现的&#xff0c;一方面&#xff0c;我真的很需要一款合自己心意的博客编辑器&#xff0c;另一方面&#xff0c;我也是真心想学习 Electron …

QT应用软件【协议篇】周立功CAN接口卡代码示例

文章目录 USBCAN系列CAN接口卡规格参数资料下载QT引用周立功的库安装sdk代码USBCAN系列CAN接口卡 USBCAN系列CAN接口卡兼容USB2.0全速规范,可支持1/2/4/8路CAN接口。采用该接口卡,PC机可通过USB连入CAN网络,进行CAN总线数据采集和处理,主要具备以下几大优势特点: 支持车载…

【HarmonyOS应用开发】三方库(二十)

三方库的基本使用 一、如何获取三方库 目前提供了两种途径获取开源三方库&#xff1a; 通过访问Gitee网站开源社区获取 在Gitee中&#xff0c;搜索OpenHarmony-TPC仓库&#xff0c;在tpc_resource中对三方库进行了资源汇总&#xff0c;可以供开发者参考。 通过OpenHarmony三…

Day 30 标准IO

文章目录 1.什么是标准IO1.1 概念1.2 特点1.3 操作 2.缓存区3.函数接口3.1 打开文件fopen3.2 关闭文件 fclose3.3 读写文件操作3.3.1 每次读写一个字符&#xff1a;fgetc()、fputc()每次读一个字符fgetc()每次写一个字符fputc()(1)针对文件(2)针对终端feof和ferror 3.3.2 每次一…

Java+SpringBoot+Vue的大学生就业信息管理系统

一、项目介绍 基于Java (spring-boot)的大学生就业信息管理系统分为三个角色&#xff1a;管理员、企业、求职者。 功能:登录、注册功能、学生信息管理、企业信息管理、岗位分类管理、学历信息管理、应聘信息管理、求职者信息管理、招聘信息管理。 二、作品包含 三、项目技术 后…

srs集群下行edge处理逻辑

官方关于源站集群的介绍&#xff1a; Origin Cluster | SRS 下行边缘是指观众端从边缘edge拉流&#xff0c;边缘edge回源到源站origin节点拉流&#xff0c;然后再 把流转给客户端 边缘处理类SrsPlayEdge 当服务器收到播放请求时&#xff0c;创建对应的consumer消费者。在创…

2.1_1 进程的概念、组成、特征

2.1_1 进程的概念、组成、特征 &#xff08;一&#xff09;进程的概念 程序&#xff1a;是静态的&#xff0c;就是一个存放在磁盘里的可执行文件&#xff0c;就是一系列的指令集合。 进程&#xff08;Process&#xff09;&#xff1a;是动态的&#xff0c;是程序的一次执行过程…

Java学习--黑马SpringBoot3课程个人总结-2024-02-16

1.添加文章 1.1 富文本编辑器 文章内容需要使用到富文本编辑器&#xff0c;这里咱们使用一个开源的富文本编辑器 Quill 官网地址&#xff1a; https://vueup.github.io/vue-quill/ 安装&#xff1a; npm install vueup/vue-quilllatest --save导入组件和样式&#xff1a; …

基于springboot+vue的B2B平台的医疗病历交互系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

【git 使用】git 中head、工作树、和索引分别是什么,有什么关系和区别

HEAD 定义&#xff1a;HEAD 是指向当前所在分支&#xff08;或者是某个特定的提交&#xff09;的指针&#xff0c;它表示当前工作目录正在处于哪个提交或分支上。作用&#xff1a;HEAD 指示了当前工作目录的状态&#xff0c;可以通过 HEAD 来确定当前处于哪个分支上&#xff0…

【已解决】PPT无法复制内容怎么办?

想要复制PPT文件里的内容&#xff0c;却发现复制不了&#xff0c;怎么办&#xff1f; 这种情况&#xff0c;一般是PPT文件被设置了以“只读方式”打开&#xff0c;“只读方式”下的PPT无法进行编辑更改&#xff0c;也无法进行复制粘贴的操作。 想要解决这个问题&#xff0c;我…

百度地图接口 | 实现校验收货地址是否超出配送范围

目录 1. 环境准备 2. 代码开发 2.1 application.yml 2.2 OrderServiceImpl &#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家博主&#xff0c;专注于Java领域学习&#xff0c;擅长web应用开发、数据结构和算法&#xff0c;初步涉猎Py…