STL容器-- list的模拟实现(附源码)

news2025/2/28 12:53:21

STL容器-- list的模拟实现(附源码)

List的实现主要考察我们对list这一容器的理解,和代码的编写能力,通过上节对list容器的使用,我们对list容器已经有了一些基本的了解·,接下来就让我们来实现一些list容器常见的功能函数来加深我们对c++工程的理解:

在这里插入图片描述

一、基本框架的搭建

首先我们需要将基本的框架搭建好, 从之前我们学数据结构时,对list的一些认识并结合c++ stl标准可以得出:

  • 定义节点的构造
  • 明确指针的指向
  • list的封装
    根据c++中的标准我们可以得知,list时一个双向迭代器, 所以对应的, 我们需要定义的双向循环链表:

1.1节点的构造

template <class T>
struct ListNode
{
    T _data;
    ListNode<T>* _next;
    ListNode<T>* _prev;
    // 节点的构造函数
    ListNode(const T& val)
    :_data(val)
    ,_next(nullptr)
    ,_prev(nullptr)
    {}
};

在定义一个节点的时候,我们先让这个节点的_next and _prev指针为空

1.2 list的封装

    
template <class T>
struct ListNode
{
    T _data;
    ListNode<T>* _next;
    ListNode<T>* _prev;
    // 节点的构造函数
    ListNode(const T& val = T())
    :_data(val)
    ,_next(nullptr)
    ,_prev(nullptr)
    {}
};

template<class T>
class list
{
public:
    typedef ListNode<T> node;
    void empty_init()
    {
        // zdl :: 在初始化一个空链表时, 先定义一个哨兵位
        _head = new node();
        _head->_next = _head;
        _head->_prev = _head;
    }
    list()
    {
    	empty_init();
    }
    // functon define:
    }

private:
        node* _head;
};

同时为了防止访问冲突,我们可以将我们自己实现的类,放在我们自己定义的命名域中:

namespace zdl
{
	template <class T>
	struct ListNode
	{
	    T _data;
	    ListNode<T>* _next;
	    ListNode<T>* _prev;
	    // 节点的构造函数
	    ListNode(const T& val)
	    :_data(val)
	    ,_next(nullptr)
	    ,_prev(nullptr)
	    {}
	};
	    
	template <class T>
	struct ListNode
	{
	    T _data;
	    ListNode<T>* _next;
	    ListNode<T>* _prev;
	    // 节点的构造函数
	    ListNode(const T& val = T())
	    :_data(val)
	    ,_next(nullptr)
	    ,_prev(nullptr)
	    {}
	};
	
	template<class T>
	class list
	{
	public:
	    typedef ListNode<T> node;
	    void empty_init()
	    {
	        // zdl :: 在初始化一个空链表时, 先定义一个哨兵位
	        _head = new node();
	        _head->_next = _head;
	        _head->_prev = _head;
	    }
	    list()
	    {
	    	empty_init();
	    }
	    // functon define:
	    }
	
	private:
	        node* _head;
	};
}

这样list的基本框架就搭建好了!!

二、节点的尾插和头插

2.1 节点的头插 (push_back)

在这里插入图片描述

通过前面数据结构的学习。我们已经清楚了链表的结构,在进行数据尾插时, 就只是在改变指针的指向罢了:

void push_back(const T& val)
    {
        node* creat = new node(val);
        node* tail = _head->_prev;
        tail->_next = creat;
        creat->_prev = tail;
        creat->_next = _head;
        _head->_prev = creat; 
    }

2.2 节点的头插(push_front)

节点的头插和尾插十分的相似,
这里我们就直接展示一下代码:

void push_front(const T& val)
    {
        node* fnode = new node(val);
        fnode->_next = _head->_next;
        fnode->_prev = _head;
        _head->_next->_prev = fnode;
        _head->_next = fnode;
    }

2.3 数据插入验证

为了方便起见, 我这里再再这个类里面定义一个打印链表的函数:

 void Print_list()
        {
            node* cur = _head->_next;
            while (cur != _head)
            {
                cout << cur->_data << " ";
                cur = cur->_next;
            }
            cout << endl;
        }

完成了list容器的头插和尾插操作接下来我们可以来验证一下,我们的函数实现是否有问题:

#include"list.h"

int main()
{
    zdl::list<int> l1;
    l1.push_back(1);
    l1.push_back(2);
    l1.push_front(3);
    l1.Print_list();
    return 0;
}

通过运行可知, 这里的函数没有问题:

在这里插入图片描述

三、list迭代器的实现

list迭代器的功能和vetor的功能有很多相似的地方,但是二者在底层实现时,使用的不同的方法:
基于,list迭代器的特殊性质,我们会采用类封装的方式来满足list_iterator的特殊性质:

3.1迭代器基本功能的实现和封装处理

template<class T>
    struct list_iterator
    {
        typedef ListNode<T> node;
        typedef list_iterator<T> self;
        node* _nd;
        list_iterator(node* nd)
        :_nd(nd)
        {}
        // zdl:: 实现迭代器的基本功能:
        // 解引用数值
        T& operator*()
        {
            return _nd->_data;
        }
        // zdl:: 前置++ / --
        self& operator++()
        {
            _nd = _nd->_next;
            return *this;
        }
        self& operator--()
        {
            _nd = _nd->_prev;
            return *this;
        }
        // zdl:: 后置 ++ / --
        self operator++(int)
        {
            self tmp = _nd;
            _nd = _nd->_next;
            return tmp;
        }
        self operator--(int)
        {
            self tmp = _nd;
            _nd = _nd->_prev;
            return tmp;
        }
        // zdl:: != 运算
        bool operator!=(const self& s)
        {
            return _nd != s._nd;
        }
    };

实现了list_iterator的功能后,我们就只需要将他封装道list类中就可以正常使用了:
使用typedef 封装迭器
在这里插入图片描述
基本的逻辑都是十分的简单的接下来我们来简单的验证一代码是否可行:
在这里插入图片描述

3.2 对结构体(类)的解引用

->运算符的重载

大家现在可以想象一下这样的场景,假设我现在在llist中存储的不是基础类型的元素,而是类等较为复杂的对象时,我们应当怎么正常的使用这个类对象的成员呢?
这时,我们就可以考虑对->运算符重载, 通过返回对象的地址,来访问成员变量和成员函数等
下面我们就来举个例子:
假设我现在我要在list中存储pos类:

struct pos
{
	int row;
	int col;
	//构造函数
	pos(const int& x = 0, const int& y = 0)
	:row(x)
	,col(y)
	{}
	//成员函数
	void pos_show()
	{
		printf("(%d, %d) ",row, col);
	}
};

我们就只需要重载->运算符:

T* operator->()
{
    return &_nd->_data;
}

运行后可以得到:
在这里插入图片描述
但是大家可能会觉得很奇怪,通过->重载,返回的只是_data的地址,为什么能直接访问到元素呢?不应该使用it->->解引用两次才行啊?
这个其实就只是c++便准下为我们提供的特殊语法, 通过这样的规定使我们能够直接访问到元素, 当然c++也是支持这样的写法的:
在这里插入图片描述
但是不支持这样的写法:
在这里插入图片描述

3.3const迭代器的实现与模板参数的巧用

前面我们已经实现了,可读可写的迭代器,现在我们就来实现一下只读迭代器:
const iterator,
其实从功能上看,这个迭代器与原来的迭代器十分的相似,只是不能对指向对象的值进行修改,因此我们就只需要对* 和 ->运算符重载的时候稍加修改就可以得到我们想要的结果,即再创建一个类:
在这里插入图片描述
其他的共同功能粗需要改动,只需要改动下面两个重载函数:

 const T& operator*()
{
   return _nd->_data;
}
const T* operator->()
{
	return &_nd->_data;
}

紧接着我们还需要在llist类中实现const对象专用的begin()、end()
在这里插入图片描述
在这里插入图片描述

接下来我们来验证一下效果:
在这里插入图片描述
现在consr iterator也实现好了,但是这里依然还存在问题,这样我们将相当于为迭代器实现了两个类,这两个类的攻击能还高度重合,这并没有,体现出模板函数的简洁性,因此我们还可以通过其他的方式来优化我们现在的代码。
通过对源码的分析,参照我们或许可以从中得到一些启发, 从源码中可知我们可以得知,通过对模板参数的巧用就可以的实现代码的简化:

在这里插入图片描述
接下来我们就只需要将代码稍加改动就可以实现我们的目的:
在这里插入图片描述
在这里插入图片描述
接下来,我们再次运行看看是否可以达到简化代码的效果:
在这里插入图片描述
可以发现现在的代码依然有效,代码简化成功!!

四、丰富构造函数、list增删查改的实现

现在我们已经完成了list的简单构造函数,现在我们就可以参照 c++ library完成其他的构造函数:

4.1 list(size_t n, const T& x = T())

我们要实现的这个函数和标准库中的一样,并且直接复用我峨嵋你已经实现的函数就好了:

list(size_t n, const T& x = T())
{
   for (int i = 0; i < n; i++)
   {
       push_back(x);
   }
}

直接来演示一下的效果:
在这里插入图片描述

4.2 拷贝构造

我们就只需要将被拷贝链表的元素一个一个的拷贝进链表就行了:

list(const list<T>& l1)
{
    empty_init();
    for (auto& e : l1) push_back(e);
}

我们来测试一下效果:
在这里插入图片描述

4.3 插入函数的实现(insert)

想要在这个双向链表中插入节点,我们就需要

  • 待插入的值

  • 待插入位置

所以,insert函数定义为: void insert(iterator pos, const T& val = T())

iterator insert(iterator pos, const T& val = T())
{
     node* cur = pos._nd;
     node* prev = pos._nd->_prev;
     node* insrt = new node(val);
     prev->_next = insrt;
     insrt->_prev = prev;
     insrt->_next = cur;
     cur->_prev = insrt;
     return iterator(cur->_prev);
 }

这个函数也十分的简单如果,有的uu还没有接触过链表,或者是已经忘了链表的增删查改,可以移步去看看我之前的博客:链表的介绍

4.4链表的删除(earse)

关于erase和函数的定义,我们就只需要拿到需要删除的位置就可以了, 所以定义为:void erase(iterator pos)

iterator erase(iterator pos)
{
     assert(pos != end()); //! 注意不能够将哨兵位删除!!
     node* cur = pos._nd;
     node* prev = cur->_prev;
     prev->_next = cur->_next;
     cur->_next->_prev = prev;
     delete cur;
     return iterator(prev->_next);
 }

现在我们就直接来测试一下这个函数是否可以满足我们的要求:
在这里插入图片描述
由此可知我们实现的都没有问题。

4.5链表元素的查找(find)

定义find函数时,我们就只需要给函数一个特定的需要查找到的值,然后使这个函数返回这个元素的位置(迭代器

iterator find(const T& val)
{
     auto it = begin();
     while (it != end() && *it != val)
     {
         it++;
     }
     if (*it == val) return it;
     return nullptr;
 }

在这里插入图片描述

4.6 头删和尾删操作

前面我们已经实现了earse 现在我们就只需要对这个和拿书尽心你给复用就好了:

void pop_front()
{
    erase(++end());
}
void pop_back()
{
    erase(--end());
}

接下来我们就直接来演示这个函数是否有用:

在这里插入图片描述

五、析构函数与clear函数

最后我们就来实现一下list的析构函数,我们还是继续函数的复用:

// zdl:: 析构类函数的实现:
        ~list()
        {
            // ! 不仅要将所有的数值删除还需要将哨兵位也清除!
            clear();
            delete _head;
            _head = nullptr;            
        }
        void clear()
        {
            // !! 不能删除哨兵位,就只是将所有的数值清空。
            auto it = begin();
            while (it != end()) it = erase(it);
        }

现在我们就将已有的链表清空试试:
在这里插入图片描述

六、代码展示

list.h

#pragma once 

#include<iostream>
#include<cassert>
using  namespace std;
namespace zdl
{
    
    template <class T>
    struct ListNode
    {
        T _data;
        ListNode<T>* _next;
        ListNode<T>* _prev;
        // 节点的构造函数
        ListNode(const T& val = T())
        :_data(val)
        ,_next(nullptr)
        ,_prev(nullptr)
        {}
    };
    template<class T, class Ref, class Ptr>
    struct list_iterator
    {
        typedef ListNode<T> node;
        typedef list_iterator<T, Ref, Ptr> self;
        node* _nd;
        list_iterator(node* nd)
        :_nd(nd)
        {}
        // zdl:: 实现迭代器的基本功能:
        // 解引用数值
        Ref operator*()
        {
            return _nd->_data;
        }
        Ptr operator->()
        {
            return &_nd->_data;
        }
        // zdl:: 前置++ / --
        self& operator++()
        {
            _nd = _nd->_next;
            return *this;
        }
        self& operator--()
        {
            _nd = _nd->_prev;
            return *this;
        }
        self& operator+(size_t n)
        {
            while (n--)
            {
                _nd = _nd->_next;
            }
            return *this;
        }
        self& operator-(size_t n)
        {
            while (n--)
            {
                _nd = _nd->_prev;
            }
            return *this;
        }
        // zdl:: 后置 ++ / --
        self operator++(int)
        {
            self tmp = _nd;
            _nd = _nd->_next;
            return tmp;
        }
        self operator--(int)
        {
            self tmp = _nd;
            _nd = _nd->_prev;
            return tmp;
        }
        // zdl:: != 运算
        bool operator!=(const self& s)
        {
            return _nd != s._nd;
        }
    
    };
    
    template<class T>
    class list
    {
    public:
        typedef ListNode<T> node;
        typedef list_iterator<T, T&, T*> iterator;
        typedef list_iterator<T, const T&, const T*> const_iterator;
        void empty_init()
        {
            // zdl :: 在初始化一个空链表时, 先定义一个哨兵位
            _head = new node();
            _head->_next = _head;
            _head->_prev = _head;
        }
        list()
        {
            empty_init();
        }
        list(const list<T>& l1)
        {
            empty_init();
            for (auto& e : l1) push_back(e);
        }
        list(size_t n, const T& x = T())
        {
            empty_init();
            for (size_t i = 0; i < n; i++)
            {
                push_back(x);
            }
        }
        void push_back(const T& val)
        {
            node* creat = new node(val);
            node* tail = _head->_prev;
            tail->_next = creat;
            creat->_prev = tail;
            creat->_next = _head;
            _head->_prev = creat; 
        }
        void push_front(const T& val)
        {
            node* fnode = new node(val);
            fnode->_next = _head->_next;
            fnode->_prev = _head;
            _head->_next->_prev = fnode;
            _head->_next = fnode;
        }
        void Print_list()
        {
            node* cur = _head->_next;
            while (cur != _head)
            {
                cout << cur->_data << " ";
                cur = cur->_next;
            }
            cout << endl;
        }
        // zdl:: 增删查改的实现
        iterator insert(iterator pos, const T& val = T())
        {
            node* cur = pos._nd;
            node* prev = pos._nd->_prev;
            node* insrt = new node(val);
            prev->_next = insrt;
            insrt->_prev = prev;
            insrt->_next = cur;
            cur->_prev = insrt;
            return iterator(cur->_prev);
        }
        iterator erase(iterator pos)
        {
            assert(pos != end()); //! 注意不能够将哨兵位删除!!
            node* cur = pos._nd;
            node* prev = cur->_prev;
            prev->_next = cur->_next;
            cur->_next->_prev = prev;
            delete cur;
            return iterator(prev->_next);
        }
        iterator find(const T& val)
        {
            auto it = begin();
            while (it != end() && *it != val)
            {
                it++;
            }
            if (*it == val) return it;
            return nullptr;
        }
        void pop_front()
        {
            erase(begin());
        }
        void pop_back()
        {
            erase(--end());
        }
        // zdl:: 析构类函数的实现:
        ~list()
        {
            // ! 不仅要将所有的数值删除还需要将哨兵位也清除!
            clear();
            delete _head;
            _head = nullptr;            
        }
        void clear()
        {
            // !! 不能删除哨兵位,就只是将所有的数值清空。
            auto it = begin();
            while (it != end()) it = erase(it);
        }
// zdl:: 使用类来模拟迭代器的行为
    iterator begin()
    {
        return iterator(_head->_next);
    }
    iterator end()
    {
        return iterator(_head);
    }
    const_iterator begin() const
    {
        return const_iterator(_head->_next);
    }
    const_iterator end() const
    {
        return const_iterator(_head);
    }
    private:
            node* _head;
    };
}

test.cpp


#include"list.h"
#include<list>
struct pos
{
	int row;
	int col;
	//构造函数
	pos(const int& x = 0, const int& y = 0)
	:row(x)
	,col(y)
	{}
	//成员函数
	void pos_show()
	{
		printf("(%d, %d) ",row, col);
	}
};
int main()
{
    // zdl::list<pos> l1;
    // pos p[] = {{1, 2}, {3, 4}, {5, 6}};
    // for (int i = 0; i < 3; i++) l1.push_back(p[i]);
    // auto it = l1.begin();
    // while (it != l1.end())
    // {
    //     it->pos_show();
    //     it++;
    //     cout << endl;
    // }
    // cout << endl;

    // zdl::list<int> l2(5, 4);
    // for (auto&i : l2) cout << i << " ";
    // cout << endl;

    // zdl::list<int> l4(3, 3);
    // l4.Print_list();
    // l4.insert(l4.begin() + 2, 2);
    // l4.Print_list();
    // l4.erase(l4.begin() + 2);
    // l4.Print_list();

    // zdl::list<int>l5;
    // for (int i = 1; i <= 10; i++) l5.push_back(i);
    // cout << "原数组:" << endl;
    // l5.Print_list();
    // // zdl:: 现在我们要借助 find + erase函数来删除元素:5
    // l5.erase(l5.find(5));
    // cout << "删除后:" << endl;
    // l5.Print_list();
    //  zdl:: 进行头删和尾删
    // cout << "进行尾删和头删后:" << endl;
    // l5.pop_back();
    // l5.pop_front();
    // l5.Print_list();
    // cout << "现在将所有的元素都删除!" << endl;
    // l5.clear();
    // l5.Print_list();
    zdl::list<int> l1(10, 10);
    cout <<"l1:" << endl;
    l1.Print_list();
    zdl::list<int> l2(l1);
    cout << "l2:" << endl;
    l2.Print_list();
    return 0;
}

好,常用的接口我们就实现了,list学习到此告一段落,再见!!

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

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

相关文章

【转】厚植根基,同启新程!一文回顾 2024 OpenHarmony 社区年度工作会议精彩瞬间

在数字化浪潮奔腾不息的今天&#xff0c;开源技术已成为推动科技创新与产业发展的强大引擎。2025年1月10日-11日&#xff0c;OpenAtom OpenHarmony&#xff08;开放原子开源鸿蒙&#xff0c;以下简称“OpenHarmony”或“开源鸿蒙”&#xff09;社区2024年度工作会议于深圳盛大启…

蓝桥杯备考:堆和priority queue(优先级队列)

堆的定义 heap堆是一种特殊的完全二叉树&#xff0c;对于树中的每个结点&#xff0c;如果该结点的权值大于等于孩子结点的权值&#xff0c;就称它为大根堆&#xff0c;小于等于就叫小根堆&#xff0c;如果是大根堆&#xff0c;每个子树也是符合大根堆的特征的&#xff0c;如果是…

力扣682

from typing import Listclass Solution:def calPoints(self, operations: List[str]) -> int:a [] # 用于存储有效得分的列表for op in operations:if op.isdigit() or (op[0] - and op[1:].isdigit()): # 如果是整数&#xff08;包括负数&#xff09;a.append(int(op)…

考研计算机组成原理——零基础学习的笔记

第一章 研究计算机硬件的学科。 1.计算机系统概述 计算机系统硬件软件&#xff08;系统软件&#xff1a;比如操作系统、数据库管理系统、标准程序库等&#xff0c;应用软件&#xff1a;QQ等&#xff09; 1.2计算机的层次结构 1.2.1计算机硬件的基本组成 冯诺伊曼计算机&a…

海康工业相机的应用部署不是简简单单!?

作者&#xff1a;SkyXZ CSDN&#xff1a;SkyXZ&#xff5e;-CSDN博客 博客园&#xff1a;SkyXZ - 博客园 笔者使用的设备及环境&#xff1a;WSL2-Ubuntu22.04MV-CS016-10UC 不会吧&#xff1f;不会吧&#xff1f;不会还有人拿到海康工业相机还是一脸懵叭&#xff1f;不会还有人…

计算机网络 (49)网络安全问题概述

前言 计算机网络安全问题是一个复杂且多维的领域&#xff0c;它涉及到网络系统的硬件、软件以及数据的安全保护&#xff0c;确保这些元素不因偶然的或恶意的原因而遭到破坏、更改或泄露。 一、计算机网络安全的定义 计算机网络安全是指利用网络管理控制和技术措施&#xff0c;保…

STM32 FreeRTOS中断管理

目录 FreeRTOS的中断管理 1、STM32中断优先级管理 2、FreeRTOS任务优先级管理 3、寄存器和内存映射寄存器 4、BASEPRI寄存器 5、FreeRTOS与STM32中断管理结合使用 vPortRaiseBASEPRI vPortSetBASEPRI 6、FromISR后缀 7、在中断服务函数中调用FreeRTOS的API函数需注意 F…

操作系统 期末重点复习

操作系统 期末重点复习 必会 课后题摘要 第二章&#xff1a; 在操作系统中为什么要引入进程概念&#xff1f;它会产生什么样的影响? 为了使程序在多道程序环境下能并发执行&#xff0c;并对并发执行的程序加以控制和描述&#xff0c;在操作系统中引入了进程概念。影响: 使程…

7.5.4 MVCC优化测试

作者&#xff1a; h5n1 原文来源&#xff1a; https://tidb.net/blog/4e02d900 1. 背景 由于MVCC 版本数量过多导致rocksdb扫描key数量过多影响SQL执行时间是tidb经常出现问的问题&#xff0c;tidb也一直在致力于优化该问题。 一些优化方式包括比&#xff1a; (1) 从传统…

2024年AI与大数据技术趋势洞察:跨领域创新与社会变革

目录 引言 技术洞察 1. 大模型技术的创新与开源推动 2. AI Agent 智能体平台技术 3. 多模态技术的兴起:跨领域应用的新风口 4. 强化学习与推荐系统:智能化决策的底层驱动 5. 开源工具与平台的快速发展:赋能技术创新 6. 技术安全与伦理:AI技术的双刃剑 7. 跨领域技…

vulnhub靶场【Lampiao靶机】,主要考察提权,脏牛提权

前言 靶机&#xff1a;lampiao&#xff0c;IP地址为192.168.10.11 攻击&#xff1a;kali&#xff0c;IP地址为192.168.10.2 都采用虚拟机&#xff0c;网卡为桥接模式 该靶机目前只剩下一个了&#xff0c;之前记得是有两台构成系列的。 文章中涉及的靶机&#xff0c;来源于v…

ASP .NET Core 学习(.NET9)配置接口访问路由

新创建的 ASP .NET Core Web API项目中Controller进行请求时&#xff0c;是在地址:端口/Controller名称进行访问的&#xff0c;这个时候Controller的默认路由配置如下 访问接口时&#xff0c;是通过请求方法&#xff08;GET、Post、Put、Delete&#xff09;进行接口区分的&…

构建core模块

文章目录 1.环境搭建1.sunrays-common下新建core模块2.引入依赖&#xff0c;并设置打包常规配置 2.测试使用1.启动&#xff01;1.创建模块2.引入依赖3.application.yml 配置MySQL和Minio4.创建启动类5.启动测试 2.common-web-starter1.目录2.WebController.java3.结果 3.common…

VRTK4 记录抓取错误

左手原本可以正常抓取&#xff0c;但是当右手拿起一个物体时&#xff0c;左手抓取右手的线性驱动器&#xff0c;只有部分区域可以抓取 原因是左手的判定物体的层级错误 应该在Collections下&#xff0c;之前错误的和Collections同一层级&#xff0c;导致抓取有时可以有时不可以…

游戏画质升级史的思考

画质代入感大众玩家对游戏的第一印象与评判标准 大众玩家还没到靠游戏性等内在因素来评判游戏的程度。 画面的重要性&#xff0c;任何时候都不能轻视。 行业就是靠摩尔定律来推动进步的。 NS2机能达到PS4到PS4PRO之间的水准&#xff0c;5050达到8G显存&#xff0c;都会引发连…

Windows11电脑总是一闪一闪的,黑一下亮一些怎么解决

Windows11电脑总是一闪一闪的&#xff0c;黑一下亮一些怎么解决 1. 打开设备管理器2. 点击显示适配器3. 更新下方两个选项的驱动3.1 更新驱动Inter(R) UHD Graphixs3.2 更新驱动NVIDIA GeForce RTX 4060 Laptop GPU 4. 其他文章快来试试吧&#x1f970; 1. 打开设备管理器 在电…

【RAG落地利器】向量数据库Qdrant使用教程

TrustRAG项目地址&#x1f31f;&#xff1a;https://github.com/gomate-community/TrustRAG 可配置的模块化RAG框架 环境依赖 本教程基于docker安装Qdrant数据库&#xff0c;在此之前请先安装docker. Docker - The easiest way to use Qdrant is to run a pre-built Docker i…

【逆境中绽放:万字回顾2024我在挑战中突破自我】

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 一、引言二、个人成长与盘点情感与心理成长学习与技能提升其它荣誉 三、年度创作历程回顾创作内容概…

HTTP / 2

序言 在之前的文章中我们介绍过了 HTTP/1.1 协议&#xff0c;现在再来认识一下迭代版本 2。了解比起 1.1 版本&#xff0c;后面的版本改进在哪里&#xff0c;特点在哪里&#xff1f;话不多说&#xff0c;开始吧⭐️&#xff01; 一、 HTTP / 1.1 存在的问题 很多时候新的版本的…

于灵动的变量变幻间:函数与计算逻辑的浪漫交织(下)

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。 这一节我们主要来学习单个函数的声明与定义&#xff0c;static和extern… 这里写目录标题 一、单个函数…