当前位置: 首页 > news >正文

精通C++ STL(六):list的模拟实现

目录

类及其成员函数接口总览

结点类的模拟实现

  构造函数

迭代器类的模拟实现

  迭代器类存在的意义

  迭代器类的模板参数说明

  构造函数

  ++运算符的重载

  --运算符的重载

  ==运算符的重载

  !=运算符的重载

  *运算符的重载

  ->运算符的重载

list的模拟实现

  默认成员函数

        构造函数

        拷贝构造函数

        赋值运算符重载函数

        析构函数

  迭代器相关函数

        begin和end

  访问容器相关函数

        front和back

  插入、删除函数

        insert

        erase

        push_back和pop_back

        push_front和pop_front

  其他函数

        size

        resize

        clear

        empty

        swap


类及其成员函数接口总览

namespace RussLeo
{// 模拟实现 list 当中的结点类template<class T>struct _list_node{// 构造函数_list_node(const T& val = T()); // 默认构造函数,初始化数据域// 成员变量T _val;                 // **数据域**:存储节点的值_list_node<T>* _next;   // **后继指针**:指向下一个节点_list_node<T>* _prev;   // **前驱指针**:指向前一个节点};// 模拟实现 list 迭代器template<class T, class Ref, class Ptr>struct _list_iterator{typedef _list_node<T> node;       // 节点类型别名typedef _list_iterator<T, Ref, Ptr> self;  // 迭代器自身类型别名// 构造函数_list_iterator(node* pnode);  // 初始化迭代器,指向节点// 各种运算符重载函数self operator++();    // 前置递增self operator--();    // 前置递减self operator++(int); // 后置递增self operator--(int); // 后置递减bool operator==(const self& s) const; // 相等比较bool operator!=(const self& s) const; // 不等比较Ref operator*();      // 解引用Ptr operator->();     // 成员访问// 成员变量node* _pnode; // **一个指向结点的指针**,用于迭代};// 模拟实现 listtemplate<class T>class list{public:typedef _list_node<T> node;      // 节点类型别名typedef _list_iterator<T, T&, T*> iterator;  // 迭代器类型别名typedef _list_iterator<T, const T&, const T*> const_iterator;  // 常量迭代器类型别名// 默认成员函数list();                         // 默认构造函数list(const list<T>& lt);        // 拷贝构造函数list<T>& operator=(const list<T>& lt);  // 拷贝赋值操作符~list();                        // 析构函数// 迭代器相关函数iterator begin();               // 返回指向头部的迭代器iterator end();                 // 返回指向尾部的迭代器const_iterator begin() const;   // 返回常量迭代器指向头部const_iterator end() const;     // 返回常量迭代器指向尾部// 访问容器相关函数T& front();                     // 返回容器第一个元素的引用T& back();                      // 返回容器最后一个元素的引用const T& front() const;         // 返回容器第一个元素的常量引用const T& back() const;          // 返回容器最后一个元素的常量引用// 插入、删除函数void insert(iterator pos, const T& x);   // 在指定位置插入元素iterator erase(iterator pos);            // 删除指定位置的元素void push_back(const T& x);              // 在末尾添加元素void pop_back();                        // 删除末尾元素void push_front(const T& x);             // 在头部添加元素void pop_front();                       // 删除头部元素// 其他函数size_t size() const;         // 返回容器中元素的个数void resize(size_t n, const T& val = T()); // 调整容器大小void clear();                // 清空容器bool empty() const;          // 判断容器是否为空void swap(list<T>& lt);      // 交换两个容器的内容private:node* _head; // **指向链表头结点的指针**};
}

结点类的模拟实现

        list 类在底层实现时确实是一个链表,更确切地说,它通常是一个带有头结点的双向循环链表。因此,要实现 list,首先需要实现一个结点类。

        一个链表结点需要存储的信息包括:数据前一个结点的地址后一个结点的地址。因此,该结点类的成员变量就包括了数据、前驱指针和后继指针。

        对于结点类的成员函数,我们只需要实现一个 构造函数。这个构造函数用于根据给定的数据初始化结点。结点的释放则由 list 类的析构函数负责处理。

  构造函数

结点类的构造函数 直接根据所提供的数据来创建一个结点。构造出来的结点将具有以下特点:

  • 数据域:存储传入的数据。
  • 前驱指针后继指针:均初始化为空指针 (nullptr)。
// 构造函数
_list_node(const T& val = T()): _val(val)        // 初始化数据域为传入的值, _prev(nullptr)  // 初始化前驱指针为空, _next(nullptr)  // 初始化后继指针为空
{}

注意:若构造结点时未传入数据,则构造函数会使用 list 容器存储类型的默认构造函数 构造出的值作为传入数据。

迭代器类的模拟实现

  迭代器类存在的意义

        当我们模拟实现 stringvector 时,并没有提到需要实现一个迭代器类,这是因为 stringvector 的数据都存储在连续的内存空间中。我们可以通过原生指针来实现迭代器的功能,指针支持自增、自减和解引用等操作,因此可以直接用于访问和操作数据。

        然而,对于 list 类而言,它的内部结构与 stringvector 不同。list 通常采用链表结构来存储数据,这意味着每个元素并不是存储在一个连续的内存块中,而是通过指针链接在一起。因此,原生指针无法直接用于链表中的迭代,因为在链表中元素的存储位置并不连续。

        为了能够遍历和操作链表中的元素,我们需要实现一个专门的迭代器类,该类需要管理链表节点的连接关系,并支持类似于指针的操作,如自增、自减和解引用。 

迭代器的意义在于让使用者能够以统一的方式访问容器中的数据,而无需关心底层实现。

        对于 list 类,它的节点指针行为不符合标准迭代器的定义。为了使 list 的迭代器能够像 stringvector 的迭代器一样使用,我们需要对节点指针进行封装。通过重载节点指针的各种运算符,我们可以实现与原生指针类似的操作。例如,当使用 list 的迭代器进行自增操作时,实际上执行的是 p = p->next,但用户不需要知道底层的具体实现细节。

总结: list 迭代器类本质上是对节点指针的封装,通过重载运算符,使得节点指针的行为看起来像普通指针一样。例如,节点指针自增操作能够指向下一个节点。

  迭代器类的模板参数说明

        我们所实现的迭代器类的模板参数列表当中为什么有三个模板参数?

template<class T, class Ref, class Ptr>

        这里的模板参数 RefPtr 分别代表了引用类型指针类型。在 list 的模拟实现中,我们定义了两个迭代器类型: 

typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, const T&, const T*> const_iterator;

这表明迭代器类的模板参数 RefPtr 用于区分普通迭代器和 const 迭代器。具体来说:

  • 普通迭代器iterator)使用 T& 作为引用类型,T* 作为指针类型。
  • const 迭代器const_iterator)使用 const T& 作为引用类型,const T* 作为指针类型。

总结: 迭代器类的三个模板参数(TRefPtr)使得迭代器能够适应不同的引用和指针类型,从而可以很好地区分普通迭代器和 const 迭代器。如果只设计两个模板参数,无法实现对普通迭代器和 const 迭代器的有效区分。因此,使用三个模板参数可以确保迭代器在不同的使用场景下具有正确的行为和类型。

  构造函数

        迭代器类实际上是对节点指针的封装,它的主要成员变量就是一个节点指针。迭代器的构造函数直接使用传入的节点指针来初始化迭代器对象。

// 构造函数
_list_iterator(node* pnode): _pnode(pnode)  // 初始化节点指针
{}

说明: self 是当前迭代器对象的类型:  

typedef _list_iterator<T, Ref, Ptr> self;

  ++运算符的重载

        前置自增(++ 的作用是将迭代器指向下一个节点,并返回自增后的迭代器对象。实现如下:

// 前置自增
self& operator++()
{_pnode = _pnode->_next; // 让节点指针指向下一个节点return *this; // 返回自增后的迭代器对象
}

        后置自增(++ 的作用是返回自增前的迭代器对象,然后将迭代器指向下一个节点。实现如下: 

// 后置自增
self operator++(int)
{self tmp(*this); // 记录当前迭代器的状态_pnode = _pnode->_next; // 让节点指针指向下一个节点return tmp; // 返回自增前的迭代器对象
}

  --运算符的重载

        前置自减(-- 的作用是将迭代器指向前一个节点,并返回自减后的迭代器对象。实现如下:

// 前置自减
self& operator--()
{_pnode = _pnode->_prev; // 让节点指针指向前一个节点return *this; // 返回自减后的迭代器对象
}

        后置自减(-- 的作用是返回自减前的迭代器对象,然后将迭代器指向前一个节点。实现如下:

// 后置自减
self operator--(int)
{self tmp(*this); // 记录当前迭代器的状态_pnode = _pnode->_prev; // 让节点指针指向前一个节点return tmp; // 返回自减前的迭代器对象
}

  ==运算符的重载

        当使用 == 运算符比较两个迭代器时,我们实际上是检查这两个迭代器是否指向同一个位置。具体来说,就是判断这两个迭代器中的节点指针是否相同。实现代码如下:

// 比较两个迭代器是否相等
bool operator==(const self& s) const
{return _pnode == s._pnode; // 判断两个节点指针是否相同
}

  !=运算符的重载

        != 运算符 的作用是判断两个迭代器是否不相等。这可以通过检查这两个迭代器中的节点指针是否不同来实现。实现代码如下:

// 比较两个迭代器是否不相等
bool operator!=(const self& s) const
{return _pnode != s._pnode; // 判断两个节点指针是否不同
}

  *运算符的重载

        当使用解引用操作符(*)时,我们想要获取当前迭代器指向节点的数据。为了支持对数据的修改,解引用操作符应返回一个引用。实现代码如下:

// 解引用操作符,返回节点指针所指节点的数据
Ref operator*()
{return _pnode->_val; // 返回节点指针所指节点的数据
}

  ->运算符的重载

        -> 运算符 用于通过迭代器访问节点中的数据。

        当节点存储的不是内置类型而是自定义类型时。例如日期类,我们可能会使用 -> 运算符来访问对象的成员。为了实现这一点,我们需要重载 -> 运算符,使其返回节点数据的地址。实现代码如下:

// 访问节点数据的地址
Ptr operator->()
{return &_pnode->_val; // 返回节点指针所指节点数据的地址
}

示例:

list<Date> lt;
Date d1(2024, 8, 12);
Date d2(2002,11, 2);
Date d3(1949, 10, 1);
lt.push_back(d1);
lt.push_back(d2);
lt.push_back(d3);
list<Date>::iterator pos = lt.begin();
cout << pos->_year << endl; // 输出第一个日期的年份

 在这个示例中,pos->_year 语句实际上涉及两个箭头操作:

  1. 第一个箭头 pos-> 调用重载的 operator->,返回 Date* 的指针。
  2. 第二个箭头 Date*-> 访问 Date 对象的成员变量 _year

        解释:虽然需要两个箭头操作,但为了提高程序的可读性,编译器在实现时做了优化,使得只有一个箭头操作是可见的。这是因为编译器会自动处理 -> 操作的特殊情况,简化用户代码中的箭头访问。

list的模拟实现

  默认成员函数

        构造函数

        list 是一个带头双向循环链表。在构造一个 list 对象时,我们通常会创建一个头结点,并将其前驱指针和后继指针都指向自身。这样可以简化链表的操作,避免在插入和删除节点时需要处理空链表的特殊情况。

// 构造函数
list()
{_head = new node; // 申请一个头结点_head->_next = _head; // 头结点的后继指针指向自己_head->_prev = _head; // 头结点的前驱指针指向自己
}

        拷贝构造函数

        拷贝构造函数 用于根据现有 list 容器创建一个新的对象。首先创建一个头结点,并将其前驱指针和后继指针都指向自己。然后,将原容器中的数据通过遍历方式逐一尾插到新构造的容器中。实现代码如下:

// 拷贝构造函数
list(const list<T>& lt)
{_head = new node; // 申请一个头结点_head->_next = _head; // 头结点的后继指针指向自己_head->_prev = _head; // 头结点的前驱指针指向自己// 遍历原容器,将数据逐一尾插到新容器中for (const auto& e : lt){push_back(e); // 将容器 lt 中的数据一个个尾插到新构造的容器后面}
}

        赋值运算符重载函数

        赋值运算符重载 有两种写法:传统写法和现代写法。两者各有优缺点,具体如下:

传统写法中,我们首先清空原容器,然后将新容器的数据逐个尾插到当前容器中。实现代码如下:

// 传统写法
list<T>& operator=(const list<T>& lt)
{if (this != &lt) // 避免自己给自己赋值{clear(); // 清空容器for (const auto& e : lt){push_back(e); // 将容器 lt 中的数据一个个尾插到链表后面}}return *this; // 支持连续赋值
}

现代写法利用了编译器的拷贝构造函数,使用右值参数创建临时对象,然后通过 swap 函数交换当前对象和临时对象的内容。实现代码如下:

// 现代写法
list<T>& operator=(list<T> lt) // 编译器接收右值时自动调用拷贝构造函数
{swap(lt); // 交换这两个对象return *this; // 支持连续赋值
}

总结

  • 传统写法 更为直接,但需要额外的清理和插入操作。
  • 现代写法 更简洁,通过 swap 函数避免了显式的清理操作,利用了资源管理的自动化机制。

        析构函数

        析构函数 在对象销毁时负责清理容器中的数据,释放头结点,并将头指针置空。实现代码如下:

// 析构函数
~list()
{clear(); // 清理容器中的所有数据delete _head; // 释放头结点_head = nullptr; // 将头指针置空
}

  迭代器相关函数

        begin和end

        beginend 函数 的实现用于获取迭代器,分别指向容器的第一个有效数据和最后一个有效数据的下一个位置。以下是其实现代码:

普通迭代器

  • begin() 返回指向第一个有效数据的迭代器,即头结点后一个结点的地址。
  • end() 返回指向最后一个有效数据的下一个位置的迭代器,即头结点的地址。
    // 返回指向第一个有效数据的迭代器
    iterator begin()
    {return iterator(_head->_next); // 使用头结点后一个结点的地址构造普通迭代器
    }// 返回指向最后一个有效数据的下一个位置的迭代器
    iterator end()
    {return iterator(_head); // 使用头结点的地址构造普通迭代器
    }
    

常量迭代器

  • begin() const 返回指向第一个有效数据的常量迭代器,即头结点后一个结点的地址。
  • end() const 返回指向最后一个有效数据的下一个位置的常量迭代器,即头结点的地址。
// 返回指向第一个有效数据的常量迭代器
const_iterator begin() const
{return const_iterator(_head->_next); // 使用头结点后一个结点的地址构造常量迭代器
}// 返回指向最后一个有效数据的下一个位置的常量迭代器
const_iterator end() const
{return const_iterator(_head); // 使用头结点的地址构造常量迭代器
}

说明

  • iteratorconst_iterator 分别用于非 const 和 const 对象。
  • begin 函数返回的是指向第一个实际数据的迭代器,end 函数返回的是指向“末尾”的迭代器,通常用来表示容器的结束位置。

  访问容器相关函数

        front和back

        frontback 函数 用于获取链表中第一个有效数据和最后一个有效数据的引用。实现时需要处理普通对象和常量对象的情况。以下是其实现代码:

普通对象

  • front() 返回第一个有效数据的引用。
  • back() 返回最后一个有效数据的引用。
// 返回第一个有效数据的引用
T& front()
{return *begin(); // 通过 begin() 获取第一个有效数据的引用
}// 返回最后一个有效数据的引用
T& back()
{return *(--end()); // 通过 --end() 获取最后一个有效数据的引用
}

常量对象

  • front() const 返回第一个有效数据的常量引用。
  • back() const 返回最后一个有效数据的常量引用。
// 返回第一个有效数据的常量引用
const T& front() const
{return *begin(); // 通过 begin() 获取第一个有效数据的常量引用
}// 返回最后一个有效数据的常量引用
const T& back() const
{return *(--end()); // 通过 --end() 获取最后一个有效数据的常量引用
}

说明

  • front() 函数返回第一个有效数据的引用或常量引用,back() 函数返回最后一个有效数据的引用或常量引用。
  • --end() 操作将 end() 迭代器向前移动一个位置,指向最后一个有效数据。

  插入、删除函数

        insert

        insert 函数 用于在给定迭代器的位置之前插入一个新结点。

        实现步骤包括定位插入位置、调整指针以及插入新结点。以下是 insert 函数的实现代码:

// 在给定迭代器位置之前插入一个新结点
void insert(iterator pos, const T& x)
{assert(pos._pnode); // 检测 pos 的合法性,确保迭代器指向有效的结点node* cur = pos._pnode; // 取得迭代器 pos 所指结点的指针node* prev = cur->_prev; // 取得 pos 前一个位置的结点指针node* newnode = new node(x); // 根据所给数据 x 构造一个新的结点// 建立 newnode 与 cur 之间的双向关系newnode->_next = cur;cur->_prev = newnode;// 建立 newnode 与 prev 之间的双向关系newnode->_prev = prev;prev->_next = newnode;
}

        erase

        erase 函数 用于删除给定迭代器位置的结点。

        实现步骤包括获取当前结点及其相邻结点的指针,调整相邻结点的双向关系,然后释放当前结点的内存。

// 删除所给迭代器位置的结点
iterator erase(iterator pos)
{assert(pos._pnode); // 检测 pos 的合法性,确保它指向一个有效结点assert(pos != end()); // 删除的结点不能是头结点node* cur = pos._pnode; // 取得要删除的结点指针node* prev = cur->_prev; // 取得前一个结点指针node* next = cur->_next; // 取得后一个结点指针delete cur; // 释放 cur 结点的内存// 建立 prev 与 next 之间的双向关系prev->_next = next;next->_prev = prev;return iterator(next); // 返回删除结点后的下一个迭代器
}

        push_back和pop_back

        push_backpop_back 函数的实现用于 list 的尾插和尾删操作。

        在已经实现了 inserterase 函数的情况下,可以通过复用这两个函数来实现 push_backpop_back。以下是这两个函数的实现代码及说明:

// 尾插函数
void push_back(const T& x)
{insert(end(), x); // 在头结点前插入新结点
}// 尾删函数
void pop_back()
{erase(--end()); // 删除头结点的前一个结点
}

        push_front和pop_front

        push_frontpop_front 函数用于 list 的头插和头删操作。

        可以通过复用已经实现的 inserterase 函数来实现这两个功能。以下是这两个函数的实现代码及说明:

// 头插函数
void push_front(const T& x)
{insert(begin(), x); // 在第一个有效结点前插入新结点
}// 头删函数
void pop_front()
{erase(begin()); // 删除第一个有效结点
}

  其他函数

        size

        size 函数用于获取当前容器中的有效数据个数。

        由于 list 是链表,无法直接通过索引访问元素,所以需要通过遍历的方式逐个统计有效数据的个数。以下是 size 函数的实现代码及说明:

// 获取当前容器中有效数据的个数
size_t size() const
{size_t sz = 0; // 用于统计有效数据的个数const_iterator it = begin(); // 获取第一个有效数据的迭代器while (it != end()) // 遍历直到容器的末尾{sz++; // 计数器加一it++; // 迭代器前移到下一个结点}return sz; // 返回有效数据的个数
}

        resize

        resize 函数用于调整 list 容器的大小,以适应给定的大小 n

根据当前容器的大小和目标大小 nresize 的规则如下:

  1. 若当前容器的大小小于 n
    • 尾部添加元素,直到容器大小等于 n
  2. 若当前容器的大小大于 n
    • 只保留前 n 个元素,删除其余元素。

        实现 resize 时避免直接调用 size 函数,因为 size 函数会遍历整个容器,而我们希望在遍历过程中完成调整大小的操作。以下是 resize 函数的实现代码及说明:

// 调整容器的大小为n,如果当前大小小于n则尾插,若当前大小大于n则删除多余元素
void resize(size_t n, const T& val = T())
{iterator i = begin(); // 获取第一个有效数据的迭代器size_t len = 0; // 记录当前遍历的数据个数// 遍历容器,直到len达到n或遍历完所有元素while (len < n && i != end()){len++; // 增加计数器i++; // 迭代器前移}if (len == n) // 如果当前有效数据个数已达到或超过n{// 删除从第n个元素开始的所有元素while (i != end()){i = erase(i); // 删除当前元素并更新迭代器}}else // 当前有效数据个数小于n{// 尾插元素直到容器大小达到nwhile (len < n){push_back(val); // 尾插元素len++; // 增加计数器}}
}

        clear

        clear 函数会逐个删除所有结点,最终只保留头结点。

// 清空容器,逐个删除所有结点,只保留头结点
void clear()
{iterator it = begin(); // 获取第一个有效数据的迭代器while (it != end()) // 逐个删除结点,直到遍历完所有有效数据{it = erase(it); // 删除当前结点并更新迭代器}
}

        empty

        empty函数用于判断容器是否为空。我们可以通过比较容器的begin函数和end函数返回的迭代器来实现这个判断。如果beginend返回的迭代器相同,那么说明容器中只有一个头结点,即容器为空。

// empty函数用于判断容器是否为空
bool empty() const
{return begin() == end(); // 判断begin和end返回的迭代器是否是同一个位置的迭代器,若相同则说明容器中只有头结点
}

        swap

        swap函数用于交换两个容器。在list容器中,实际上存储的仅仅是链表的头指针,因此我们只需交换两个容器的头指针即可实现交换操作。

// 交换当前容器和指定容器的内容
void swap(list<T>& lt)
{::swap(_head, lt._head); // 交换两个容器的头指针
}

注意: 在此处调用库中的 swap 函数时,需要加上 ::(作用域限定符),以确保编译器在全局范围内查找 swap 函数,避免编译器错误地调用当前类中的 swap 函数。

相关文章:

精通C++ STL(六):list的模拟实现

目录 类及其成员函数接口总览 结点类的模拟实现 构造函数 迭代器类的模拟实现 迭代器类存在的意义 迭代器类的模板参数说明 构造函数 运算符的重载 --运算符的重载 运算符的重载 !运算符的重载 *运算符的重载 ->运算符的重载 list的模拟实现 默认成员函数 构造函数 拷贝…...

《雅思口语真经总纲1.0》话题实战训练笔记part1——6. Music

《雅思口语真经总纲1.0》笔记——第四章&#xff1a;口语素材大全&#xff08;part1、part2、part3回答准则及练习方法&#xff0c;不包括范例答案&#xff09;★★★★★ 文章目录 MusicWhen do you listen to music?20240804答评价注意事项1、在说到“no music”时&#xff…...

Python之赋值语句(多重赋值和交换赋值)

这是《Python入门经典以解决计算问题为导向的Python编程实践》73-74页关于赋值的内容。讲了Python中几种赋值方式。 赋值语句 1、最简单的赋值&#xff1a;ab2、多重赋值&#xff1a;a,b,c1,2,33、交换&#xff1a;a,bb,a 1、最简单的赋值&#xff1a;ab b可以是数字、字符串…...

网络协议七 应用层 HTTP 协议

应用层常见的协议 HTTP协议 1. 如何查看我们的http 协议全部的内容有哪些呢&#xff1f; 一种合理的方法是 通过 wireshark 软件&#xff0c;找到想要查看的HTTP --->追踪流--->HTTP流 来查看 结果如下&#xff1a;红色部分 为 发送给服务器的&#xff0c;蓝色部分为服务…...

uniapp vue 在适配百度小程序平台动态:style

uniapp vue 在适配百度小程序平台动态:style踩坑报错Unexpected string concatenation of literals 抖快平台动态style写法基本是 <view :style"{width: 686rpx, height: (setHeight 96) rpx}"> </view>这种写法在百度上会又解析报错&#xff1a; Une…...

【最小生成树】(二) Kruskal 算法

题目: 寻宝 题目描述 在世界的某个区域&#xff0c;有一些分散的神秘岛屿&#xff0c;每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路&#xff0c;方便运输。 不同岛屿之间&#xff0c;路途距离不同&#xff0c;国王希望你可以规划建公路的方案&#xf…...

haproxy最强攻略

1、负载均衡 负载均衡&#xff08;Load Balance&#xff0c;简称 LB&#xff09;是高并发、高可用系统必不可少的关键组件&#xff0c;目标是 尽力将网络流量平均分发到多个服务器上&#xff0c;以提高系统整体的响应速度和可用性。 负载均衡的主要作用如下&#xff1a; 高并发…...

XetHub 加入 Hugging Face!

我们非常激动地正式宣布&#xff0c;Hugging Face 已收购 XetHub &#x1f525; XetHub 是一家位于西雅图的公司&#xff0c;由 Yucheng Low、Ajit Banerjee 和 Rajat Arya 创立&#xff0c;他们之前在 Apple 工作&#xff0c;构建和扩展了 Apple 的内部机器学习基础设施。XetH…...

在编程学习的海洋中,如何打造高效的知识宝库

目录 在编程学习的海洋中&#xff0c;如何打造高效的知识宝库一、笔记记录的重要性&#xff1a;为知识设立灯塔二、快速记录的策略&#xff1a;抓住知识的核心三、系统化的整理&#xff1a;构建个人知识体系四、实用工具推荐&#xff1a;为知识管理添砖加瓦五、保持条理性的秘诀…...

string详解(1)

1.C语言中的字符串 C语言中&#xff0c;字符串是以\0结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;C标准库中提供了一些str系列的库函数&#xff0c;但是这些库函数与字符串是分离开的&#xff0c;不太符合OOP的思想&#xff0c;而且底层空间需要用户自己管理&…...

Linux云计算 |【第二阶段】NETWORK-DAY4

主要内容&#xff1a; NAT 原理与配置&#xff08;私有IP地址、静态NAT转换、Easy IP&#xff09;、VRRP解析&#xff08;主路由器、备份路由器、虚拟路由器、优先级&#xff09; 一、NAT概述 NAT 网络地址转换&#xff08;Network Address Translation&#xff09;是一种网络…...

amazon linux使用密码登录或者root登陆

1. 首先要把创建root密码&#xff0c;如果原来的密码不记得了&#xff0c;可以直接用 sudo passwd -d root 删除原来的密码 然后创建root密码 sudo passwd root 2. 修改 sshd_config 文件 vim /etc/ssh/sshd_config 允许使用密码登录 PasswordAuthentication yes 允许root…...

集智书童 | CNN 与 Transformer 的强强联合:AResNet-ViT在图像分析中的优势 !

本文来源公众号“集智书童”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;CNN 与 Transformer 的强强联合&#xff1a;AResNet-ViT在图像分析中的优势 &#xff01; 作者针对残差CNN分支的注意力引导设计进行了消融实验。同时&a…...

Ubuntu基础使用指南

Ubuntu基础使用指南 Ubuntu作为一款流行的开源操作系统&#xff0c;以其稳定性、安全性和易用性著称。无论是作为服务器操作系统还是桌面操作系统&#xff0c;Ubuntu都能满足用户的各种需求。下面&#xff0c;我们将从Ubuntu的基础使用开始&#xff0c;带你深入了解这个强大的…...

怎样才算精通 Excel?

最强AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频百万播放量https://aitools.jurilu.com/ 高赞回答很系统&#xff0c;但普通人这么学&#xff0c;没等精通先学废了&#xff01; 4年前&#xff0c;我为了学数据分析&#…...

怎么学算法并找到工作

1.基础 找一本基础的内容看一遍 时间复杂度、空间复杂度计算方式数组、队列、栈、树、图结构十大排序算法 2.《hello算法》 动画图解算法 https://www.hello-algo.com/chapter_hello_algo/ 3.《剑指Offer》 一些面试的高频有年度的题解 里么的题很有特色&#xff0c;而…...

【实时建图】MapTR(1)------ 论文详解

作者们提出了一种有效构建高清地图的方法(MapTR),该地图为自动驾驶系统的规划提供丰富且精确的环境信息。这是一种结构化端到端变换器,用于高效在线矢量化地图构建。作者提出了一种统一的置换等价建模方法,即将地图元素建模为一个具有一组等价置换的点集,这准确地描述了地…...

通用Builder工具类

假设有一个Java实体类定义&#xff1a; public class Request {private String type;private String op;private PageInfo pageInfo;public static class PageInfo {private Integer pageNum;private Integer pageSize;}// 省略getter和setter... }在代码中创建这个对象&#…...

开源免费的海报设计器vue-fabric-editor

vue-fabric-editor 是一个基于 Vue.js 和 Fabric.js 的创新前端富文本编辑器&#xff0c;它将传统的文本输入体验与图形设计元素相结合&#xff0c;为用户提供了全新的内容创作方式。 以下是关于 vue-fabric-editor 的详细介绍&#xff1a; 一、技术特点 框架结合&#xff1a;…...

【学习笔记】Day 4 - Day 5

一、进度概述 1、新机环境配置 2、《地震勘探原理》第一章 3、对 “DL-FWI 研究方向展望” 组会的一些想法 二、详情 1、新机环境配置 配置新机环境着实耗了较多时间&#xff0c;导致理论进度推进较慢。 2、《地震勘探原理》第一章学习笔记 相关笔记在另一篇…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代&#xff0c;情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现&#xff0c;消费者对内容的“有感”程度&#xff0c;正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

基于 TAPD 进行项目管理

起因 自己写了个小工具&#xff0c;仓库用的Github。之前在用markdown进行需求管理&#xff0c;现在随着功能的增加&#xff0c;感觉有点难以管理了&#xff0c;所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD&#xff0c;需要提供一个企业名新建一个项目&#…...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...

Python Einops库:深度学习中的张量操作革命

Einops&#xff08;爱因斯坦操作库&#xff09;就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库&#xff0c;用类似自然语言的表达式替代了晦涩的API调用&#xff0c;彻底改变了深度学习工程…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.

ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #&#xff1a…...