数据结构初阶之栈和队列(C语言版)

加粗样式

数据结构初阶之栈和队列(C语言版)

  • ✍栈
    • ♈栈的结构设计
    • ♈栈的各个接口的实现
      • 👺StackInit(初始化)
      • 👺push(入栈)
      • 👺pop(出栈)
      • 👺获取栈顶元素
      • 👺获取栈中有效元素的个数
      • 👺判断栈是否为空
      • 👺销毁栈
  • ✍队列
    • 👻队列的结构的设计
    • 👻队列的各个接口实现
      • 🐷Init(初始化队列)
      • 🐷队尾入队列
      • 🐷队头出队列
      • 🐷获取队列队头元素
      • 🐷获取队列队尾元素
      • 🐷判断队列是否为空
      • 🐷获取队列的有效元素个数
      • 🐷销毁队列
  • ✍OJ题之用两个队列实现栈
  • ✍OJ题之用两个栈实现队列
  • ⭕总结

✍栈

栈是数据结构的一种,一个栈可以用来对数据进行增删查改,但是它遵循一个原则,就是数据必须是后入先出,什么意思呢?就是先入栈的数据后出栈,后入栈的数据先出栈。我们可以使用链表和动态数组来实现栈。

我们使用动态数组来实现栈,为了实现后入先出,我们可以将栈看作只有尾插和尾删功能(也就是没有头插头删)的动态顺序表,其它都很相似。

♈栈的结构设计

// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
	STDataType* _a;
	int _top;		// 栈顶
	int _capacity;  // 容量 
}Stack;

各个接口:

// 初始化栈 
void StackInit(Stack* ps);

// 入栈 
void StackPush(Stack* ps, STDataType data);

// 出栈 
void StackPop(Stack* ps);

// 获取栈顶元素 
STDataType StackTop(Stack* ps);

// 获取栈中有效元素个数 
int StackSize(Stack* ps);

// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
bool StackEmpty(Stack* ps);

// 销毁栈 
void StackDestroy(Stack* ps);

♈栈的各个接口的实现

这里我们使用动态增长的数组来实现栈尾插和尾删对应我们的入栈和出栈,利用链表实现也可以,但是动态数组操作起来更加简单,也不需要挪动数据,出栈更是只需要简单的_top--就可以完成,何乐而不为呢。

👺StackInit(初始化)

栈对象在主程序已经创建好了,传结构体指针可以修改结构体里面的成员,初始化栈只需要把动态数组、栈的元素个数、栈的空间大小初始化就可以了。

// 初始化栈 
void StackInit(Stack* ps)
{
	ps->_a = NULL;
	ps->_top = ps->_capacity = 0;
}

👺push(入栈)

入栈其实就是动态顺序表里的尾插,如果小伙伴有疑惑,可以看数据结构初阶之顺序表(C语言实现)

// 入栈 
void StackPush(Stack* ps, STDataType data)
{
	assert(ps);
	
	if (ps->_top == ps->_capacity)//判断是否需要扩容
	{
		STDataType* tmp = ps->_capacity == 0 ? (STDataType*)realloc(ps->_a, sizeof(STDataType) * 4) : (STDataType*)realloc(ps->_a, sizeof(STDataType)* (ps->_capacity * 2));
		if (tmp == NULL)
		{
			perror("realloc Failed");
			exit(-1);
		}
		ps->_a = tmp;
		ps->_capacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;
	}
	
	ps->_a[ps->_top++] = data;//尾插新的元素,_top++
}

上述代码用到了三目操作符,如果不知道这个操作符的话可能就看不懂代码,这里博主来简单的说一下。它的构成是这样的:

表达式1?语句1: 语句二;
如果表达式1成立则执行语句1,否则执行语句二。

👺pop(出栈)

出栈操作就更不用说了,由于入栈是尾插由于要满足栈后进先出的原则,我们直接把最后一个入栈的删除就可以了,等价于元素个数_top--

// 出栈 
void StackPop(Stack* ps)
{
	assert(ps);
	assert(ps->_top > 0);
	ps->_top--;
}

👺获取栈顶元素

栈的元素个数减一就是栈顶元素的下标位置,返回该下标位置的值就可以了。

// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->_top > 0);
	
	return ps->_a[ps->_top-1];
}

👺获取栈中有效元素的个数

直接返回栈的元素个数。

// 获取栈中有效元素个数 
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->_top;
}

👺判断栈是否为空

直接看栈的元素个数是否为0,为空返回true,反之返回false。

// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
bool StackEmpty(Stack* ps)
{
	assert(ps);
	return ps->_top == 0;
}

👺销毁栈

这里栈对象是在外面就创建了,不一定是动态开辟的,所以我们只释放栈里面动态数组的空间就可以。

// 销毁栈 
void StackDestroy(Stack* ps)
{
	assert(ps);
	
	free(ps->_a);
	ps->_a = NULL;
	ps->_capacity = 0;
	ps->_top = 0;
}

✍队列

队列也是数据结构的一种,它和栈很像,区别就是队列数据必须是先进先出,什么意思呢?就是先入队列的数据先出队列,后入队列的数据后出队列。我们可以使用链表和动态数组来实现栈。

这里我们使用链表来实现队列,为了实现队列先入先出的功能,我们可以将我们实现的队列看成出数据只能头删、入数据只能尾插的单链表。

可能有小伙伴说,那我将最后面的数据视作队头,最前面的数据视作队尾,利用单链表的头插和尾删也可以实现队列呀,这也是可行的,只要保证先入先出的原则就可以,下面的图是两者的区别。

在这里插入图片描述

无论怎么样,要保证先入先出,队头始终是先进队列的。这里可能就有小伙伴突发奇想了,既然队列可以这样,栈可不可以把栈顶放在动态数组的左边去,使用头插和头删来维护栈呢?由于动态顺序表的头删和头插都需要挪动数据,时间开销太大,我们一般不这样做。

👻队列的结构的设计

typedef  int QDataType;//typedef重命名数据类型,下次改数据类型时改这个地方就可以了。
// 链式结构:表示队列 
typedef struct QListNode
{
	struct QListNode* _next;
	QDataType _data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* _front;
	QNode* _rear;
}Queue;

队列和栈的区别就体现出来了:即出队列的是队头,最后入队列的会变成队尾。最后入栈的会变成栈顶,出栈的也是栈顶。

由于我们使用链表实现队列,出对队头对应头删,入队尾对应尾插,所以为了避免重复的遍历找队尾,我们干脆在设计队列结构的时候,定义尾节点和头节点,在入队列和出队列的时候不断维护。

这里使用链表实现队列会比使用动态数组实现队列更优,因为动态数组头删和头插都需要挪动数据。

各个接口:

// 初始化队列 
void QueueInit(Queue* q);

// 队尾入队列 
void QueuePush(Queue* q, QDataType data);

// 队头出队列 
void QueuePop(Queue* q);

// 获取队列头部元素 
QDataType QueueFront(Queue* q);

// 获取队列队尾元素 
QDataType QueueBack(Queue* q);

// 获取队列中有效元素个数 
int QueueSize(Queue* q);

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q);

// 销毁队列 
n'/void QueueDestroy(Queue* q);

👻队列的各个接口实现

队列用到了很多基础的链表的知识,如果你不太懂链表,请看这篇博客。

🐷Init(初始化队列)

初始化,将头节点指针和尾节点置空就可以了。

// 初始化队列 
void QueueInit(Queue* q)
{
	assert(q);
	
	q->_front = NULL;
	q->_rear = NULL;
}

🐷队尾入队列

和单链表里的尾插的区别是,这里我们已经维护了尾节点,不用循环遍历去找尾节点了,没有数据的情况需要特殊判断一下。

// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{
	assert(q);

	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc Failed\n");
		exit(-1);
	}
	newnode->_data = data;
	newnode->_next = NULL;

	if (q->_rear == NULL)//没有节点的情况
	{
		q->_front = q->_rear = newnode;
	}
	else//一般情况
	{
		q->_rear->_next = newnode;
		q->_rear = newnode;
	}
}

🐷队头出队列

等价于链表中的头删,但是需要特判一下只有一个数据的情况。

// 队头出队列 
void QueuePop(Queue* q)
{
	assert(q);
	assert(q->_front);
	
	QNode* new_front = q->_front->_next;
	free(q->_front);
	q->_front = new_front;
	if (q->_front == NULL)//特判一下一个节点的情况
		q->_rear = NULL;
}

🐷获取队列队头元素

直接返回头节点的节点值。

// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(q->_front);
	
	return q->_front->_data;
}

🐷获取队列队尾元素

直接返回尾节点的节点值。

// 获取队列队尾元素 
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(q->_rear);
	
	return q->_rear->_data;
}

🐷判断队列是否为空

等价于判断头节点或者尾节点是否为空。

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q)
{
	assert(q);
	
	return q->_front == NULL;
}

🐷获取队列的有效元素个数

循环遍历累加到临时变量中去,然后返回。

// 获取队列中有效元素个数 
int QueueSize(Queue* q)
{
	assert(q);
	int size = 0;
	QNode* cur = q->_front;
	while (cur)
	{
		size++;
		cur = cur->_next;
	}
	return size;
}

注意:这里计算队列的大小不能使用指针减指针,因为链表每一个节点都是随机开的空间,而不是像动态数组一样连续的空间。

🐷销毁队列

由于链表的节点的空间是一个一个开的,所以需要一个一个依次销毁,最后不要忘了将队列的两个指针置空。

// 销毁队列 
void QueueDestroy(Queue* q)
{
	assert(q);
	assert(q->_front);//如果队列已经为空,就不用销毁了

	QNode* cur = q->_front;
	while (cur)
	{
		QNode* next = cur->_next;
		free(cur);
		cur = next;
	}
	q->_front = q->_rear = NULL;

下面我们用两道力扣上的简单题来熟悉一下栈和队列:

✍OJ题之用两个队列实现栈

在这里插入图片描述
这是题目链接
先贴C语言ak代码:

typedef  int QDataType;  // 定义队列中数据类型为整数

// 链式结构:表示队列节点
typedef struct QListNode {
    struct QListNode* _next;  // 指向下一个节点的指针
    QDataType _data;          // 节点存储的数据
} QNode;

// 队列的结构
typedef struct Queue {
    QNode* _front;  // 指向队列头部节点的指针
    QNode* _rear;   // 指向队列尾部节点的指针
} Queue;

// 初始化队列
void QueueInit(Queue* q) {
    assert(q);  // 确保指针不为空
    q->_front = NULL;  // 将队列头部指针置空
    q->_rear = NULL;   // 将队列尾部指针置空
}

// 队尾入队列
void QueuePush(Queue* q, QDataType data) {
    assert(q);  // 确保指针不为空

    // 创建新的节点并分配内存空间
    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    if (newnode == NULL) {
        perror("malloc Failed\n");
        exit(-1);
    }
    newnode->_data = data;  // 存储数据到新节点中
    newnode->_next = NULL;  // 新节点的下一个节点置空

    // 如果队列为空,则新节点即为队列的唯一节点
    if (q->_rear == NULL) {
        q->_front = q->_rear = newnode;
    } else {  // 否则将新节点接入队列尾部,并更新队列尾部指针
        q->_rear->_next = newnode;
        q->_rear = newnode;
    }
}

// 队头出队列
void QueuePop(Queue* q) {
    assert(q);        // 确保指针不为空
    assert(q->_front); // 确保队列不为空

    // 移动队列头部指针至下一个节点,并释放原头部节点的内存空间
    QNode* new_front = q->_front->_next;
    free(q->_front);
    q->_front = new_front;

    // 如果队列为空,则同时更新队列尾部指针为空
    if (q->_front == NULL)
        q->_rear = NULL;
}

// 获取队列头部元素
QDataType QueueFront(Queue* q) {
    assert(q);        // 确保指针不为空
    assert(q->_front); // 确保队列不为空
    return q->_front->_data;  // 返回队列头部节点存储的数据
}

// 获取队列队尾元素
QDataType QueueBack(Queue* q) {
    assert(q);        // 确保指针不为空
    assert(q->_rear); // 确保队列不为空
    return q->_rear->_data;  // 返回队列尾部节点存储的数据
}

// 获取队列中有效元素个数
int QueueSize(Queue* q) {
    assert(q);  // 确保指针不为空
    int size = 0;
    QNode* cur = q->_front;
    while (cur) {  // 遍历队列节点计算节点数目
        size++;
        cur = cur->_next;
    }
    return size;
}

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q) {
    assert(q);  // 确保指针不为空
    return q->_front == NULL;  // 判断队列是否为空
}

// 销毁队列
void QueueDestroy(Queue* q) {
    assert(q);  // 确保指针不为空

    // 释放队列中所有节点的内存空间,并将队列头部和尾部指针置空
    QNode* cur = q->_front;
    while (cur) {
        QNode* next = cur->_next;
        free(cur);
        cur = next;
    }
    q->_front = q->_rear = NULL;
}

// 定义一个栈结构,使用两个队列实现
typedef struct {
    Queue* q1;  // 第一个队列
    Queue* q2;  // 第二个队列
} MyStack;

// 创建一个新的栈结构
MyStack* myStackCreate() {
    MyStack* obj = (MyStack*)malloc(sizeof(MyStack));  // 分配栈结构内存空间
    obj->q1 = (Queue*)malloc(sizeof(Queue));  // 初始化第一个队列
    QueueInit(obj->q1);
    obj->q2 = (Queue*)malloc(sizeof(Queue));  // 初始化第二个队列
    QueueInit(obj->q2);
    return obj;
}

// 元素入栈
void myStackPush(MyStack* obj, int x) {
    Queue* noempty = obj->q1;  // 初始化非空队列指针为q1
    if (QueueEmpty(noempty))  // 如果q1为空,则指向q2
        noempty = obj->q2;
    QueuePush(noempty, x);  // 将元素入队到非空队列中
}

// 元素出栈
int myStackPop(MyStack* obj) {
    Queue* noempty = obj->q1;  // 初始化非空队列指针为q1
    Queue* empty = obj->q2;    // 初始化空队列指针为q2
    if (QueueEmpty(noempty)) {  // 如果q1为空,则交换指针
        noempty = obj->q2;
        empty = obj->q1;
    }

    // 将非空队列中的数据放入空队列中,并留下最后一个数据
    while (QueueSize(noempty) > 1) {
        QueuePush(empty, QueueFront(noempty));
        QueuePop(noempty);
    }
    int top = QueueFront(noempty);  // 获取栈顶元素
    QueuePop(noempty);  // 将栈顶元素出队
    return top;
}

// 获取栈顶元素
int myStackTop(MyStack* obj) {
    int top = myStackPop(obj);  // 获取栈顶元素
    Queue* noempty = obj->q1;   // 初始化非空队列指针为q1
    if (QueueEmpty(noempty))    // 如果q1为空,则指向q2
        noempty = obj->q2;
    QueuePush(noempty, top);    // 将栈顶元素重新入栈
    return top;
}

// 检测栈是否为空
bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(obj->q1) && QueueEmpty(obj->q2);  // 判断两个队列是否均为空
}

// 释放栈结构内存空间
void myStackFree(MyStack* obj) {
    QueueDestroy(obj->q1);  // 销毁队列1
    QueueDestroy(obj->q2);  // 销毁队列2
    free(obj->q1);  // 释放队列1的内存空间
    obj->q1 = NULL;
    free(obj->q2);  // 释放队列2的内存空间
    obj->q2 = NULL;
    free(obj);      // 释放栈结构的内存空间
}

ak截图:

在这里插入图片描述

这里由于C语言没有现成的轮子(也就是写好的队列)给我们用,所以只能自己手撕一个队列上去,所以代码比较长,不过小伙伴如果刚开始接触编程不用担心,等以后学了C++/java,上面那段代码可以减少很多,下面是C++代码示例:

#include <queue>

class MyStack {
private:
    std::queue<int>* q1;  // 第一个队列指针
    std::queue<int>* q2;  // 第二个队列指针

public:
    // 构造函数初始化两个队列
    MyStack() {
        q1 = new std::queue<int>;
        q2 = new std::queue<int>;
    }
    
    // 元素入栈
    void push(int x) {
        std::queue<int>* noempty = q1;  // 初始化非空队列指针为q1
        if (q1->empty())  // 如果q1为空,则指向q2
            noempty = q2;
        noempty->push(x);  // 将元素入队到非空队列中
    }
    
    // 元素出栈
    int pop() {
        std::queue<int>* noempty = q1;  // 初始化非空队列指针为q1
        std::queue<int>* empty = q2;    // 初始化空队列指针为q2
        if (q1->empty()) {  // 如果q1为空,则交换指针
            noempty = q2;
            empty = q1;
        }
     
        // 将非空队列中的数据放入空队列中,并留下最后一个数据
        while (noempty->size() > 1) {
            empty->push(noempty->front());
            noempty->pop();
        }
        int top = noempty->front();  // 获取栈顶元素
        noempty->pop();  // 将栈顶元素出队
        return top;
    }
    
    // 获取栈顶元素
    int top() {
        int top = pop();  // 获取栈顶元素
        std::queue<int>* noempty = q1;  // 初始化非空队列指针为q1
        if (q1->empty())  // 如果q1为空,则指向q2
            noempty = q2;
        noempty->push(top);  // 将栈顶元素重新入栈
        return top;
    }
    
    // 检测栈是否为空
    bool empty() {
        return q1->empty() && q2->empty();  // 判断两个队列是否均为空
    }
    
    // 析构函数释放队列内存空间
    ~MyStack() {
        delete q1;
        delete q2;
    }
};

ak截图:

在这里插入图片描述

C++的代码看不懂不要紧,关键我们要把这题的思路学会:

在这里插入图片描述

相信如果小伙伴了解了本题的思路,再去看代码就很简单了。
这里就C语言代码的几个关键点作一下说明:

  1. myStackCreate()函数部分,这个函数是用来初始化栈的,返回值是指针(地址),所以我们必须使用动态开辟函数在堆上申请空间,否则出了这个函数,那片地址就会被回收,另外栈的两个指针成员也要初始化,同样需要在其它函数中使用,需要动态开辟申请空间,如果不初始化他们就是野指针了。
  2. 在pop、push栈函数部分我们都使用到了noempty或者是empty,这样做就不用考虑谁是空,谁不是空了,不容易出错,这也是我们设计栈的成员的时候使用指针的原因,如果这里empty、noempty不是指针就无法改变原队列的数据,就需要去具体的考虑谁是空,谁不是空了。
  3. myStackFree()释放内存函数部分,由于我们给两个队列的节点和队列对象q1、q2、栈对象都在堆上申请了空间,需要把他们一一释放,注意顺序即可。

✍OJ题之用两个栈实现队列

这是题目:

在这里插入图片描述

原题点这里

这里我们也是先贴ak代码:

// 定义栈结构
typedef int STDataType; // 定义栈中数据类型为整数
typedef struct Stack {
    STDataType* _a; // 存储栈元素的数组指针
    int _top;       // 栈顶位置索引
    int _capacity;  // 栈的容量
} Stack;

// 初始化栈
void StackInit(Stack* ps) {
    ps->_a = NULL;
    ps->_top = ps->_capacity = 0;
}

// 入栈
void StackPush(Stack* ps, STDataType data) {
    assert(ps); // 确保栈指针不为空
    if (ps->_top == ps->_capacity) { // 当栈满时扩容
        STDataType* tmp = ps->_capacity == 0 ? (STDataType*)realloc(ps->_a, sizeof(STDataType) * 4) : (STDataType*)realloc(ps->_a, sizeof(STDataType) * (ps->_capacity * 2));
        if (tmp == NULL) {
            perror("realloc Failed");
            exit(-1);
        }
        ps->_a = tmp;
        ps->_capacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;
    }
    ps->_a[ps->_top++] = data; // 将元素入栈并更新栈顶位置
}

// 出栈
void StackPop(Stack* ps) {
    assert(ps); // 确保栈指针不为空
    assert(ps->_top > 0); // 确保栈不为空
    ps->_top--; // 出栈操作,栈顶位置减一
}

// 获取栈顶元素
STDataType StackTop(Stack* ps) {
    assert(ps); // 确保栈指针不为空
    return ps->_a[ps->_top - 1]; // 返回栈顶元素
}

// 获取栈中有效元素个数
int StackSize(Stack* ps) {
    assert(ps); // 确保栈指针不为空
    return ps->_top; // 返回栈中元素个数即栈顶位置索引
}

// 检测栈是否为空
bool StackEmpty(Stack* ps) {
    assert(ps); // 确保栈指针不为空
    return ps->_top == 0; // 返回栈是否为空
}

// 销毁栈
void StackDestroy(Stack* ps) {
    assert(ps); // 确保栈指针不为空
    free(ps->_a); // 释放栈数组内存空间
    ps->_a = NULL;
    ps->_capacity = 0;
    ps->_top = 0;
}

// 定义一个队列结构,使用两个栈实现
typedef struct {
    Stack* st1; // 入队列栈
    Stack* st2; // 出队列栈
} MyQueue;

// 创建一个新的队列结构
MyQueue* myQueueCreate() {
    MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue)); // 分配队列结构内存空间
    obj->st1 = (Stack*)malloc(sizeof(Stack)); // 初始化入队列栈
    StackInit(obj->st1);
    obj->st2 = (Stack*)malloc(sizeof(Stack)); // 初始化出队列栈
    StackInit(obj->st2);
    return obj;
}

// 元素入队
void myQueuePush(MyQueue* obj, int x) {
    while (!StackEmpty(obj->st2)) { // 将出队列栈中的元素全部转移到入队列栈中
        StackPush(obj->st1, StackTop(obj->st2));
        StackPop(obj->st2);
    }
    StackPush(obj->st1, x); // 将新元素入栈到入队列栈中
}

// 元素出队
int myQueuePop(MyQueue* obj) {
    while (!StackEmpty(obj->st1)) { // 将入队列栈中的元素全部转移到出队列栈中
        StackPush(obj->st2, StackTop(obj->st1));
        StackPop(obj->st1);
    }
    int peek = StackTop(obj->st2); // 获取队头元素
    StackPop(obj->st2); // 出队操作
    return peek;
}

// 获取队头元素
int myQueuePeek(MyQueue* obj) {
    int peek = myQueuePop(obj); // 获取队头元素
    StackPush(obj->st2, peek); // 将队头元素重新入队
    return peek;
}

// 检测队列是否为空
bool myQueueEmpty(MyQueue* obj) {
    return StackEmpty(obj->st1) && StackEmpty(obj->st2); // 判断两个栈是否均为空
}

// 释放队列结构内存空间
void myQueueFree(MyQueue* obj) {
    StackDestroy(obj->st1); // 销毁入队列栈
    StackDestroy(obj->st2); // 销毁出队列栈
    free(obj->st1); // 释放入队列栈的内存空间
    obj->st1 = NULL;
    free(obj->st2); // 释放出队列栈的内存空间
    obj->st2 = NULL;
    free(obj); // 释放队列结构的内存空间
}

这道题队列的成员可以不用Stack指针(这样的话在使用Stack函数时就需要取地址了),因为没有涉及到使用其它变量更改原栈变量的情况。

ak截图:

在这里插入图片描述


关键思路:

在这里插入图片描述

代码部分很多都和第一道题相似这里我们不再做过多的叙述。

⭕总结

本篇博客主要讲到了数据结构中栈和队列的一些知识,并给出了他们的C语言模拟实现,最后以两道OJ题强化了对后入先出、先入先出特性的了解,欢迎小伙伴指出不足和提出宝贵的建议,下面给出本篇博客思维导图,希望本篇博客对你有所帮助。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

案例081:基于微信小程序的移动平台的远程在线诊疗系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

2024年金融变革RWA的7大展望

作者&#xff1a;SanjayRoofstock onChain的副总裁 编译&#xff1a;秦晋 碳链价值 稳定币、代币化国债、去中心化私人信贷、实物支持的NFTs、气候和再生金融DeFi - 这些只是未来一年将重塑资本市场的部分趋势。 在不断变化的金融格局中&#xff0c;过去两年给我们带来了一系列…

玩转贝启科技BQ3588C开源鸿蒙系统开发板 —— 开发板详情与规格

本文主要参考&#xff1a; BQ3588C_开发板详情-开源鸿蒙技术交流-Bearkey-开源社区 BQ3588C_开发板规格-开源鸿蒙技术交流-Bearkey-开源社区 厦门贝启科技有限公司-Bearkey-官网 1. 开发板详情 RK3588 核心板是一款由贝启科技自主研发的基于瑞芯微 RK3588 AI 芯片的智能核心…

专题一:递推与递归

递归 例题 递归实现指数型枚举 从 1∼n这 n个整数中随机选取任意多个&#xff0c;输出所有可能的选择方案。 输入格式 输入一个整数 n。 输出格式 每行输出一种方案。 同一行内的数必须升序排列&#xff0c;相邻两个数用恰好 1 个空格隔开。 对于没有选任何数的方案&#xff0c…

认识SpringBoot项目中的Starter

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 循序渐进学SpringBoot ✨特色专栏&…

webshell检测方式深度剖析 --- Pixy系列二(数据流分析)

开篇 书接上文&#xff0c;这次我们来聊聊数据流分析&#xff0c;数据流分析的内容非常广泛&#xff0c;我们力求深入浅出通俗易懂&#xff0c;在简短的篇幅内将这一概念描述清楚。 简单来说&#xff0c;数据流分析是一种用来获取相关数据沿着程序执行路径流动的信息分析技术…

ROS学习笔记(7)进一步深入了解ROS第一步

0.前提 最近在学习宾夕法尼亚大学工程学院的ROS公开课&#xff0c;在尽力的去融入全英语的环境&#xff08;哪怕我的英语水准并不是很高&#xff09;。既然是在学习&#xff0c;笔记也就是必须的了&#xff0c;当然这些笔记都是课程当中提出的问题&#xff0c;我去寻找后得出的…

EDI 项目推进流程

EDI 需求确认 交易伙伴发来EDI对接邀请&#xff0c;企业应该如何应对&#xff1f; 首先需要确认EDI需求&#xff0c;通常包括传输协议和报文标准以及传输的业务单据类型。可以向交易伙伴发送以下内容&#xff1a; &#xff08;中文版&#xff09; 与贵司建立EDI连接需要使用…

案例086:基于微信小程序的影院选座系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

七功能遥控编解码芯片

一、基本概述 TT6/TR6 是一对为遥控玩具车设计的 CMOS LSI 芯片。TT6 为发射编码芯片&#xff0c;TR6 为接收解码芯片。TT6/TR6 提供七个功能按键控制前进、后退、左转、右转、加速、独立功能 F1,独立功能 F2 的动作。除此以外&#xff0c;还有这五种常规小车功能&#xff08;…

Spring MVC - Controller的创建与使用

控制器Controller是处理器&#xff0c;是真正处理请求的组件 1 创建Controller 一般在src/main/java/com/qdu下建立一个controller包用来存放所有控制器。当创建一个控制器时&#xff0c;首先要记得使用Controller标记将该类注册成为一个控制器类。 然后在SpringMVCConfig类…

PostgreSQL数据库的json操作

1.操作符 select json字段::json->key值 from order -- 对象域 select json字段::json->>key值 from order -- 文本 select json字段::json#>{key值} from order -- 对象域 select json字段::json#>>{key值} from order -- 文本对象域表示还能继续操作&#…

《MySQL系列-InnoDB引擎02》InnoDB存储引擎介绍

文章目录 第二章 InnoDB存储引擎1 InnoDB存储引擎概述2 InnoDB存储引擎的版本3 InnoDB体系架构3.1 后台线程3.2 内存 4 Checkpoint技术5 Master Thread 工作方式5.1 InnoDB 1.0.x版本之前的Master Thread5.2 InnoDB 1.2.x版本之前的Master Thread5.3 InnoDB 1.2.x版本的Master …

Windows下使用wireshark抓取usb数据

参考&#xff1a;使用Wireshark获取USB数据&#xff08;https://blog.csdn.net/2301_76293276/article/details/133791136&#xff09; 文章目录 安装wireshark运行wireshark筛选所需连接设备数据 安装wireshark 直接官网下载wireshark&#xff08;https://www.wireshark.org…

关于“Python”的核心知识点整理大全57

目录 3. 模板edit_entry edit_entry.html 4. 链接到页面edit_entry topic.html 19.2 创建用户账户 19.2.1 应用程序 users 1. 将应用程序users添加到settings.py中 settings.py 2. 包含应用程序users的URL urls.py 19.2.2 登录页面 urls.py 1. 模板login.html log…

Git原理与使用(二):分支管理

Git原理与使用[二]:分支管理 一.分支的基本操作1.理解分支2.创建分支3.切换分支4.删除分支5.补充:创建并切换分支 二.合并分支1.合并分支的基础操作2.分支冲突 三.分支管理策略1.Fast-forward模式2.--no--ff(即:禁用Fast-forward模式)3.分支策略 四.创建临时分支修复bug1.git s…

NSSCTF 1zjs

开启环境: 搞就完事了,别玩魔法! 源码打开 点击访问:./dist/index.umd.js" 搜索php,找到23条相关的,注意到有一个特别的信息: PERFORMANCE OF THIS SOFTWARE.Your gift just take it : /fk3f1ag.php 访问: node4.anna.nssctf.cn:28325/fk3f1ag.php 得到这样: ([![]…

【Java 21 新特性】顺序集合(Sequenced Collections)

1 摘要 引入新的接口表示具有定义的遇到顺序的集合。每个这样的集合都有一个明确定义的第一个元素、第二个元素&#xff0c;依此类推&#xff0c;直到最后一个元素。提供统一的API来访问它的第一个和最后一个元素&#xff0c;并以相反的顺序处理它的元素。 "生活只能向后…

【算法与数据结构】968、LeetCode监控二叉树

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;本题的一共有两个难点&#xff0c;一个在于如何遍历二叉树&#xff08;前中后遍历&#xff0c;选择什么…

MySQL 时间日期函数,流程控制函数,加密解密函数以及聚合查询函数

注:本文仅作为查找函数和部分理解使用,希望能给大家带来帮助 以下函数均可以使用 SELECT NOW()等函数 FROM DUAL;来测试 //其中dual是一个准们用来测试的测试表 1.时间日期函数 1.1 获取时间的函数 重点记忆前三个红色标注的函数, 第一个函数返回值如2024-01-02的形式 第二个如…