深入理解并实现自定义 unordered_map 和 unordered_set

news2025/3/3 10:02:45

亲爱的读者朋友们😃,此文开启知识盛宴与思想碰撞🎉。

快来参与讨论💬,点赞👍、收藏⭐、分享📤,共创活力社区。


          在 C++ 的标准模板库(STL)中,unordered_mapunordered_set是非常实用的容器,它们基于哈希表实现,能够提供高效的查找、插入和删除操作。在 SGI - STL30 版本中,虽然没有标准的unordered_mapunordered_set,但有类似功能的hash_maphash_set,它们作为非标准容器存在。

 本文将基于哈希表来模拟实现自定义的unordered_mapunordered_set。


目录

💯模拟实现的基础 —— 哈希表

1. HashTable 类

1.1 迭代器相关函数

Iterator Begin()

Iterator End()

ConstIterator Begin() const 和 ConstIterator End() const

1.2 插入函数 Insert

1.3 查找函数 Find

1.4 删除函数 Erase

1.5 辅助函数 __stl_next_prime

2. HTIterator 类 

2.1 构造函数 HTIterator(Node* node, const HashTable * pht)

2.2 解引用操作符 operator*()

2.3 箭头操作符 operator->()

2.4 不等于操作符 operator!=

2.5 前置递增操作符 operator++()

💯基于哈希表实现 unordered_set

💯基于哈希表实现 unordered_map

 operator[] 函数

💯测试代码


💯模拟实现的基础 —— 哈希表

首先,定义哈希表的节点结构HashNode。每个节点包含数据_data和指向下一个节点的指针_next,用于解决哈希冲突(链地址法)。

namespace zdf {
// 哈希表节点定义
template<class T>
struct HashNode {
    T _data;
    HashNode<T>* _next;
    // 构造函数,初始化数据和指针
    HashNode(const T& data) : _data(data), _next(nullptr) {}
};
}
  • 功能:这是哈希表中节点的基本结构,用于存储数据和指向下一个节点的指针,采用链表法解决哈希冲突。
  • 构造函数 HashNode(const T& data)
    • 参数data 是要存储在节点中的数据,类型为 T
    • 作用:初始化节点的数据成员 _data 为传入的 data,并将指向下一个节点的指针 _next 初始化为 nullptr

1. HashTable 类

 

接着,定义哈希表类HashTable。它包含多个成员变量和成员函数,以实现哈希表的各种功能。

namespace zdf {
template<class K, class T, class KeyOfT, class Hash>
class HashTable {
    // 定义节点类型
    typedef HashNode<T> Node;
public:
    // 定义迭代器类型
    typedef HTIterator<K, T, T*, T&, KeyOfT, Hash> Iterator;
    // 定义常量迭代器类型
    typedef HTIterator<K, T, const T*, const T&, KeyOfT, Hash> ConstIterator;

    // 返回哈希表起始位置的迭代器
    Iterator Begin() {
        if (_n == 0) {
            return End();
        }
        for (size_t i = 0; i < _tables.size(); ++i) {
            Node* cur = _tables[i];
            if (cur) {
                return Iterator(cur, this);
            }
        }
        return End();
    }

    // 返回哈希表结束位置的迭代器
    Iterator End() {
        return Iterator(nullptr, this);
    }

    // 返回常量哈希表起始位置的迭代器
    ConstIterator Begin() const {
        if (_n == 0) {
            return End();
        }
        for (size_t i = 0; i < _tables.size(); ++i) {
            Node* cur = _tables[i];
            if (cur) {
                return ConstIterator(cur, this);
            }
        }
        return End();
    }

    // 返回常量哈希表结束位置的迭代器
    ConstIterator End() const {
        return ConstIterator(nullptr, this);
    }

    // 插入数据到哈希表
    pair<Iterator, bool> Insert(const T& data) {
        KeyOfT kot;
        Iterator it = Find(kot(data));
        if (it != End()) {
            return make_pair(it, false);
        }
        Hash hs;
        size_t hashi = hs(kot(data)) % _tables.size();
        // 负载因子达到1时进行扩容
        if (_n == _tables.size()) {
            vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
            for (size_t i = 0; i < _tables.size(); ++i) {
                Node* cur = _tables[i];
                while (cur) {
                    Node* next = cur->_next;
                    // 计算在新表中的位置并插入
                    size_t newHashi = hs(kot(cur->_data)) % newtables.size();
                    cur->_next = newtables[newHashi];
                    newtables[newHashi] = cur;
                    cur = next;
                }
                _tables[i] = nullptr;
            }
            _tables.swap(newtables);
        }
        // 在当前位置插入新节点
        Node* newnode = new Node(data);
        newnode->_next = _tables[hashi];
        _tables[hashi] = newnode;
        ++_n;
        return make_pair(Iterator(newnode, this), true);
    }

    // 在哈希表中查找数据
    Iterator Find(const K& key) {
        KeyOfT kot;
        Hash hs;
        size_t hashi = hs(key) % _tables.size();
        Node* cur = _tables[hashi];
        while (cur) {
            if (kot(cur->_data) == key) {
                return Iterator(cur, this);
            }
            cur = cur->_next;
        }
        return End();
    }

    // 从哈希表中删除数据
    bool Erase(const K& key) {
        KeyOfT kot;
        Hash hs;
        size_t hashi = hs(key) % _tables.size();
        Node* prev = nullptr;
        Node* cur = _tables[hashi];
        while (cur) {
            if (kot(cur->_data) == key) {
                if (prev == nullptr) {
                    _tables[hashi] = cur->_next;
                }
                else {
                    prev->_next = cur->_next;
                }
                delete cur;
                --_n;
                return true;
            }
            prev = cur;
            cur = cur->_next;
        }
        return false;
    }
private:
    // 计算下一个素数,用于扩容
    inline unsigned long __stl_next_prime(unsigned long n) {
        static const int __stl_num_primes = 28;
        static const unsigned long __stl_prime_list[__stl_num_primes] = {
            53, 97, 193, 389, 769,
            1543, 3079, 6151, 12289, 24593,
            49157, 98317, 196613, 393241, 786433,
            1572869, 3145739, 6291469, 12582917, 25165843,
            50331653, 100663319, 201326611, 402653189, 805306457,
            1610612741, 3221225473, 4294967291
        };
        const unsigned long* first = __stl_prime_list;
        const unsigned long* last = __stl_prime_list + __stl_num_primes;
        const unsigned long* pos = lower_bound(first, last, n);
        return pos == last? *(last - 1) : *pos;
    }
    // 哈希表存储数据的桶,是一个指针数组
    vector<Node*> _tables;
    // 哈希表中存储数据的个数
    size_t _n = 0;
};
}
1.1 迭代器相关函数
Iterator Begin()
Iterator Begin() {
    if (_n == 0) {
        return End();
    }
    for (size_t i = 0; i < _tables.size(); ++i) {
        Node* cur = _tables[i];
        if (cur) {
            return Iterator(cur, this);
        }
    }
    return End();
}
  • 功能:返回哈希表起始位置的迭代器。
  • 步骤
    1. 检查哈希表中元素数量 _n 是否为 0,如果是则直接返回表示结束位置的迭代器。
    2. 遍历哈希表的每个桶(_tables),找到第一个不为空的桶。
    3. 如果找到不为空的桶,返回指向该桶第一个节点的迭代器。
    4. 如果遍历完所有桶都没有找到非空桶,则返回表示结束位置的迭代器。
Iterator End()

 

Iterator End() {
    return Iterator(nullptr, this);
}
  • 功能:返回哈希表结束位置的迭代器,通常用一个指向 nullptr 的迭代器表示。
  • 步骤:创建一个指向 nullptr 的迭代器并返回。
ConstIterator Begin() const 和 ConstIterator End() const

  • 功能:与上述 Begin() 和 End() 类似,但用于常量哈希表,返回常量迭代器。
  • 实现细节:实现逻辑与非常量版本相同,只是返回的是常量迭代器。
1.2 插入函数 Insert
pair<Iterator, bool> Insert(const T& data) {
    KeyOfT kot;
    Iterator it = Find(kot(data));
    if (it != End()) {
        return make_pair(it, false);
    }
    Hash hs;
    size_t hashi = hs(kot(data)) % _tables.size();
    if (_n == _tables.size()) {
        vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
        for (size_t i = 0; i < _tables.size(); ++i) {
            Node* cur = _tables[i];
            while (cur) {
                Node* next = cur->_next;
                size_t newHashi = hs(kot(cur->_data)) % newtables.size();
                cur->_next = newtables[newHashi];
                newtables[newHashi] = cur;
                cur = next;
            }
            _tables[i] = nullptr;
        }
        _tables.swap(newtables);
    }
    Node* newnode = new Node(data);
    newnode->_next = _tables[hashi];
    _tables[hashi] = newnode;
    ++_n;
    return make_pair(Iterator(newnode, this), true);
}
  • 功能:向哈希表中插入一个新的数据项。
  • 步骤
    1. 使用 Find 函数检查数据是否已经存在于哈希表中,如果存在则返回该元素的迭代器和 false
    2. 计算数据的哈希值,确定其在哈希表中的桶位置 hashi
    3. 检查负载因子(元素数量 _n 与桶数量 _tables.size() 相等),如果达到阈值则进行扩容操作:
      • 创建一个新的更大的哈希表 newtables,桶的数量为下一个素数。
      • 遍历原哈希表的每个桶,将每个节点重新计算哈希值并插入到新的哈希表中。
      • 交换原哈希表和新哈希表的指针。
    4. 创建一个新的节点 newnode,将其插入到对应桶的头部。
    5. 元素数量 _n 加 1。
    6. 返回新插入节点的迭代器和 true
1.3 查找函数 Find

 

Iterator Find(const K& key) {
    KeyOfT kot;
    Hash hs;
    size_t hashi = hs(key) % _tables.size();
    Node* cur = _tables[hashi];
    while (cur) {
        if (kot(cur->_data) == key) {
            return Iterator(cur, this);
        }
        cur = cur->_next;
    }
    return End();
}
  • 功能:在哈希表中查找指定键 key 对应的数据项。
  • 步骤
    1. 计算键的哈希值,确定其在哈希表中的桶位置 hashi
    2. 遍历该桶对应的链表,检查每个节点的数据是否与键匹配。
    3. 如果找到匹配的节点,则返回指向该节点的迭代器。
    4. 如果遍历完链表都没有找到匹配的节点,则返回表示结束位置的迭代器。
1.4 删除函数 Erase

bool Erase(const K& key) {
    KeyOfT kot;
    Hash hs;
    size_t hashi = hs(key) % _tables.size();
    Node* prev = nullptr;
    Node* cur = _tables[hashi];
    while (cur) {
        if (kot(cur->_data) == key) {
            if (prev == nullptr) {
                _tables[hashi] = cur->_next;
            }
            else {
                prev->_next = cur->_next;
            }
            delete cur;
            --_n;
            return true;
        }
        prev = cur;
        cur = cur->_next;
    }
    return false;
}
  • 功能:从哈希表中删除指定键 key 对应的数据项。
  • 步骤
    1. 计算键的哈希值,确定其在哈希表中的桶位置 hashi
    2. 遍历该桶对应的链表,查找键匹配的节点。
    3. 如果找到匹配的节点:
      • 如果该节点是链表的第一个节点,则将桶的头指针指向该节点的下一个节点。
      • 否则,将前一个节点的 _next 指针指向该节点的下一个节点。
      • 删除该节点,并将元素数量 _n 减 1。
      • 返回 true 表示删除成功。
    4. 如果遍历完链表都没有找到匹配的节点,则返回 false 表示删除失败。
1.5 辅助函数 __stl_next_prime

inline unsigned long __stl_next_prime(unsigned long n) {
    static const int __stl_num_primes = 28;
    static const unsigned long __stl_prime_list[__stl_num_primes] = {
        53, 97, 193, 389, 769,
        1543, 3079, 6151, 12289, 24593,
        49157, 98317, 196613, 393241, 786433,
        1572869, 3145739, 6291469, 12582917, 25165843,
        50331653, 100663319, 201326611, 402653189, 805306457,
        1610612741, 3221225473, 4294967291
    };
    const unsigned long* first = __stl_prime_list;
    const unsigned long* last = __stl_prime_list + __stl_num_primes;
    const unsigned long* pos = lower_bound(first, last, n);
    return pos == last? *(last - 1) : *pos;
}
  • 功能:计算大于等于 n 的下一个素数,用于哈希表扩容时确定新的桶数量。
  • 步骤
    1. 定义一个素数列表 __stl_prime_list
    2. 使用 lower_bound 函数在素数列表中查找第一个大于等于 n 的素数。
    3. 如果找到则返回该素数,否则返回素数列表中的最后一个素数。

为了实现哈希表的迭代器功能,定义迭代器类HTIterator

2. HTIterator 类 

namespace zdf {
template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>
struct HTIterator {
    // 定义节点类型
    typedef HashNode<T> Node;
    // 定义自身类型
    typedef HTIterator<K, T, Ptr, Ref, KeyOfT, Hash> Self;

    Node* _node;
    const HashTable<K, T, KeyOfT, Hash>* _pht;

    // 构造函数,初始化节点指针和哈希表指针
    HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht) : _node(node), _pht(pht) {}

    // 解引用操作符重载,返回节点数据的引用
    Ref operator*() {
        return _node->_data;
    }

    // 箭头操作符重载,返回节点数据的指针
    Ptr operator->() {
        return &_node->_data;
    }

    // 不等于操作符重载,用于比较两个迭代器
    bool operator!=(const Self& s) {
        return _node != s._node;
    }

    // 前置递增操作符重载,移动到下一个节点
    Self& operator++() {
        if (_node->_next) {
            _node = _node->_next;
        }
        else {
            KeyOfT kot;
            Hash hs;
            size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();
            ++hashi;
            while (hashi < _pht->_tables.size()) {
                if (_pht->_tables[hashi]) {
                    break;
                }
                ++hashi;
            }
            if (hashi == _pht->_tables.size()) {
                _node = nullptr;
            }
            else {
                _node = _pht->_tables[hashi];
            }
        }
        return *this;
    }
};
}
2.1 构造函数 HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht)

 

HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht) : _node(node), _pht(pht) {}

  • 功能:初始化迭代器,将节点指针 node 和哈希表指针 pht 赋值给成员变量。
  • 参数
    • node:指向当前节点的指针。
    • pht:指向哈希表的指针。
2.2 解引用操作符 operator*()

Ref operator*() {
    return _node->_data;
}
  • 功能:返回当前迭代器指向节点的数据的引用。
  • 步骤:直接返回节点的数据成员 _data
2.3 箭头操作符 operator->()

Ptr operator->() {
    return &_node->_data;
}
  • 功能:返回当前迭代器指向节点的数据的指针。
  • 步骤:返回节点数据成员 _data 的地址。
2.4 不等于操作符 operator!=

 

bool operator!=(const Self& s) {
    return _node != s._node;
}
  • 功能:比较两个迭代器是否不相等。
  • 步骤:比较两个迭代器指向的节点指针是否不同。
2.5 前置递增操作符 operator++()

Self& operator++() {
    if (_node->_next) {
        _node = _node->_next;
    }
    else {
        KeyOfT kot;
        Hash hs;
        size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();
        ++hashi;
        while (hashi < _pht->_tables.size()) {
            if (_pht->_tables[hashi]) {
                break;
            }
            ++hashi;
        }
        if (hashi == _pht->_tables.size()) {
            _node = nullptr;
        }
        else {
            _node = _pht->_tables[hashi];
        }
    }
    return *this;
}
  • 功能:将迭代器移动到下一个节点。
  • 步骤
    1. 如果当前节点有下一个节点,则直接将迭代器指向该下一个节点。
    2. 如果当前节点没有下一个节点,则需要找到下一个非空的桶:
      • 计算当前节点所在的桶位置 hashi
      • 从下一个桶开始查找,直到找到一个非空的桶。
      • 如果遍历完所有桶都没有找到非空桶,则将迭代器指向 nullptr
      • 否则,将迭代器指向该非空桶的第一个节点。
    3. 返回迭代器自身的引用。

 


💯基于哈希表实现 unordered_set

   unordered_set是一个无序的集合容器,它存储唯一的键值。通过复用前面实现的哈希表,定义unordered_set类。

namespace zdf {
template<class K, class Hash = HashFunc<K>>
class unordered_set {
    // 仿函数,用于从键值中提取键
    struct SetKeyOfT {
        const K& operator()(const K& key) {
            return key;
        }
    };
public:
    // 定义迭代器类型
    typedef typename HashTable<K, const K, SetKeyOfT, Hash>::Iterator iterator;
    // 定义常量迭代器类型
    typedef typename HashTable<K, const K, SetKeyOfT, Hash>::ConstIterator const_iterator;

    // 返回容器起始位置的迭代器
    iterator begin() {
        return _ht.Begin();
    }

    // 返回容器结束位置的迭代器
    iterator end() {
        return _ht.End();
    }

    // 返回常量容器起始位置的迭代器
    const_iterator begin() const {
        return _ht.Begin();
    }

    // 返回常量容器结束位置的迭代器
    const_iterator end() const {
        return _ht.End();
    }

    // 插入键值到unordered_set
    pair<iterator, bool> insert(const K& key) {
        return _ht.Insert(key);
    }

    // 在unordered_set中查找键值
    iterator Find(const K& key) {
        return _ht.Find(key);
    }

    // 从unordered_set中删除键值
    bool Erase(const K& key) {
        return _ht.Erase(key);
    }
private:
    // 使用哈希表存储数据
    HashTable<K, const K, SetKeyOfT, Hash> _ht;
};
}

💯基于哈希表实现 unordered_map

   unordered_map是一个无序的键值对容器,它允许通过键快速查找对应的值。同样复用哈希表来实现unordered_map类。

namespace zdf {
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map {
    // 仿函数,用于从键值对中提取键
    struct MapKeyOfT {
        const K& operator()(const pair<K, V>& kv) {
            return kv.first;
        }
    };
public:
    // 定义迭代器类型
    typedef typename HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;
    // 定义常量迭代器类型
    typedef typename HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::ConstIterator const_iterator;

    // 返回容器起始位置的迭代器
    iterator begin() {
        return _ht.Begin();
    }

    // 返回容器结束位置的迭代器
    iterator end() {
        return _ht.End();
    }

    // 返回常量容器起始位置的迭代器
    const_iterator begin() const {
        return _ht.Begin();
    }

    // 返回常量容器结束位置的迭代器
    const_iterator end() const {
        return _ht.End();
    }

    // 插入键值对到unordered_map
    pair<iterator, bool> insert(const pair<K, V>& kv) {
        return _ht.Insert(kv);
    }

    // 通过键访问对应的值,若键不存在则插入默认值
    V& operator[](const K& key) {
        pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
        return ret.first->second;
    }

    // 在unordered_map中查找键值对
    iterator Find(const K& key) {
        return _ht.Find(key);
    }

    // 从unordered_map中删除键值对
    bool Erase(const K& key) {
        return _ht.Erase(key);
    }
private:
    // 使用哈希表存储数据
    HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};
}
 operator[] 函数
V& operator[](const K& key) {
    pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
    return ret.first->second;
}
  • 功能:通过键访问对应的值,如果键不存在则插入一个默认值。
  • 步骤
    1. 调用底层哈希表的 Insert 函数插入键值对(如果键不存在)。
    2. 返回插入或已存在的键值对的值的引用。

 


💯测试代码

为了验证自定义的unordered_mapunordered_set的功能正确性,编写测试函数。

using namespace std;
// 测试unordered_set的功能
void test_set() {
    zdf::unordered_set<int> s;
    int a[] = { 4,2,6,1,3,5,15,7,16,14,3,3,15 };
    for (auto e : a) {
        s.insert(e);
    }
    for (auto e : s) {
        cout << e << " ";
    }
    cout << endl;
    zdf::unordered_set<int>::iterator it = s.begin();
    while (it != s.end()) {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}

// 测试unordered_map的功能
void test_map() {
    zdf::unordered_map<string, string> dict;
    dict.insert({ "sort", "排序" });
    dict.insert({ "Left", "左边" });
    dict.insert({ "right", "右边" });
    dict["left"] = "左边,剩余";
    dict["insert"] = "插入";
    dict["string"];
    zdf::unordered_map<string, string>::iterator it = dict.begin();
    while (it != dict.end()) {
        it->second += 'x';
        cout << it->first << ":" << it->second << endl;
        ++it;
    }
    cout << endl;
}

int main() {
    test_set();
    test_map();
    return 0;
}

        通过上述代码,我们基于哈希表成功模拟实现了自定义的unordered_mapunordered_set,并对其功能进行了测试验证。这不仅有助于深入理解哈希表的工作原理以及 STL 容器的实现机制,也为在实际开发中根据特定需求进行容器的定制化提供了思路和参考。

 如果文章对你有帮助,欢迎关注我👉【A charmer】

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

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

相关文章

228页PPT丨制造业核心业务流程优化咨询全案(战略营销计划生产研发质量),附核心系统集成架构技术支撑体系,2月26日资料已更新

一、订单全生命周期管理优化 1. 智能订单承诺&#xff08;CTP&#xff09;系统 ●集成ERP/APS/MES数据&#xff0c;实时计算产能可视性 ●应用蒙特卡洛模拟评估订单交付风险 ●建立动态插单评估模型&#xff08;基于边际贡献与产能占用系数&#xff09; 2. 跨部门协同机制…

6.6.5 SQL访问控制

文章目录 GRANT授予权限REVOKE回收权限 GRANT授予权限 GRANT语句可以给用户授予权限&#xff0c;基本格式是GRANT 权限 TO 用户。在授权时&#xff0c;WITH GRANT OPTION是可选项&#xff0c;有此句话&#xff0c;被授予权限的用户还能把权限赋给其他用户。 REVOKE回收权限 RE…

【语法】C++中string类中的两个问题及解答

贴主在学习string类时遇到过两个困扰我的问题&#xff0c;今天拿出来给大家分享一下我是如何解决的 一、扩容时capacity的增长问题 在string的capacity()接口中&#xff0c;调用的是这个string对象的容量(可以存多少个有效字符)&#xff0c;而size()是调用的string对象现在有…

智慧校园平台在学生学习与生活中的应用

随着科技的发展&#xff0c;教育领域也在不断探索新的模式与方法。智慧校园平台作为教育信息化的重要组成部分&#xff0c;正逐渐成为推动教育改革、提高教学质量的关键工具。 一.智慧校园平台概述 智慧校园平台是一种集成了教学管理、资源服务、数据分析等多功能于一体的数字…

AtCoder Beginner Contest 001(A - 積雪深差、B - 視程の通報、C - 風力観測、D - 感雨時刻の整理)题解

由于我发现网上很少有人会发很久之前AtCoder Beginner Contes的题&#xff0c;所以我打算从AtCoder Beginner Contest 001开始写。大约两周一更&#xff0c;需要的可以订阅专栏&#xff0c;感谢支持Thanks♪(&#xff65;ω&#xff65;)&#xff89; →题目翻译 A - 積雪深差…

Windows本地Docker+Open-WebUI部署DeepSeek

最近想在自己的电脑本地部署一下DeepSeek试试&#xff0c;由于不希望污染电脑的Windows环境&#xff0c;所以在wsl中安装了ollama&#xff0c;使用ollama拉取DeepSeek模型。然后在Windows中安装了Docker Desktop&#xff0c;在Docker中部署了Open-WebUI&#xff0c;最后再在Ope…

WSBDF レクチア 定义2 引理3 wsbdf的乘子

定义2 引理3 wsbdf的乘子 ここまで 寝みます❓

Odoo免费开源CRM技术实战:从商机线索关联转化为售后工单的应用

文 / 开源智造 Odoo金牌服务 Odoo&#xff1a;功能强大且免费开源的CRM Odoo 引入了一种高效的客户支持管理方式&#xff0c;即将 CRM 线索转换为服务台工单。此功能确保销售和支持团队能够无缝协作&#xff0c;从而提升客户满意度并缩短问题解决时间。通过整合 CRM 模块与服…

C语言(3)—循环、数组、函数的详解

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、函数二、循环与数组 1.循环2.数组 总结 前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、函数 在C语言中&#xff0c;函数…

架构师论文《论面向对象设计的应用与实现》

软考论文-系统架构设计师 摘要 我所在的公司是国内一家专注于智慧城市建设的科技企业&#xff0c;为适应城市数字化转型中对于高内聚、低耦合、可扩展性的技术需求&#xff0c;2021年3月&#xff0c;公司立项开发“智慧社区综合管理平台”&#xff0c;旨在整合物业管理、安防监…

现代未来派品牌海报设计液体装饰英文字体安装包 Booster – Liquid Font

CS Booster – 具有动态流的液体显示字体 具有液体美感的现代显示字体 CS Booster 是一种未来主义的显示字体&#xff0c;采用流畅和有机的形式设计&#xff0c;赋予其流畅、灵活和不断移动的外观。独特的液体灵感形状和非刚性边缘使这款字体脱颖而出&#xff0c;提供一种既俏…

(十 四)趣学设计模式 之 策略模式!

目录 一、 啥是策略模式&#xff1f;二、 为什么要用策略模式&#xff1f;三、 策略模式的实现方式四、 策略模式的优缺点五、 策略模式的应用场景六、 总结 &#x1f31f;我的其他文章也讲解的比较有趣&#x1f601;&#xff0c;如果喜欢博主的讲解方式&#xff0c;可以多多支…

kkfileview部署

kkfileview部署 链接: 官方文档 链接: gitee 链接: github 首先打开官网如下&#xff1a; OK&#xff0c;我们从官方文档的教程中看到&#xff0c;部署步骤如下: 是不是很简单&#xff0c;没错&#xff0c;于是我们按照步骤从码云上下载&#xff0c;然后解压&#xff0c;然…

文件描述符(File Descriptor)

一、介绍 内核&#xff08;kernel&#xff09;利用文件描述符&#xff08;file descriptor&#xff09;来访问文件。文件描述符是非负整数。打开现存文件或新建文件时&#xff0c;内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。 二、功能 文件…

钉钉MAKE AI生态大会思考

1. 核心特性 1.1 底层模型开放 除原有模型通义千问外,新接入猎户星空、智普、MinMax、月之暗面、百川智能、零一万物。 1.2 AI搜索 AI搜索贯通企业和个人散落在各地的知识(聊天记录、文档、会议、日程、知识库、项目等),通过大模型对知识逻辑化,直接生成搜索的答案,并…

[操作系统] 文件的软链接和硬链接

文章目录 引言硬链接&#xff08;Hard Link&#xff09;什么是硬链接&#xff1f;硬链接的特性硬链接的用途 软链接&#xff08;Symbolic Link&#xff09;什么是软链接&#xff1f;软链接的特性软链接的用途 软硬链接对比文件的时间戳实际应用示例使用硬链接节省备份空间用软链…

【TI毫米波雷达】DCA1000的ADC原始数据C语言解析及FMCW的Python解析2D-FFT图像

【TI毫米波雷达】DCA1000的ADC原始数据C语言解析及FMCW的Python解析2D-FFT图像 文章目录 ADC原始数据C语言解析Python的2D-FFT图像附录&#xff1a;结构框架雷达基本原理叙述雷达天线排列位置芯片框架Demo工程功能CCS工程导入工程叙述Software TasksData PathOutput informati…

LeeCode题库第三十九题

39.组合总和 项目场景&#xff1a; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同…

B/B+树与mysql索引

数据结构操作网站&#xff1a;https://www.cs.usfca.edu/~galles/visualization/Algorithms.html B树 算法平均最差空间O(n)O(n)搜索O(log n)O(log n)插入O(log n)O(log n)删除O(log n)O(log n) B树 算法平均最差空间O(n)O(n)搜索O(log n)O(log n)插入O(log n)O(log n)删除O(…

1.2.3 使用Spring Initializr方式构建Spring Boot项目

本实战概述介绍了如何使用Spring Initializr创建Spring Boot项目&#xff0c;并进行基本配置。首先&#xff0c;通过Spring Initializr生成项目骨架&#xff0c;然后创建控制器HelloController&#xff0c;定义处理GET请求的方法hello&#xff0c;返回HTML字符串。接着&#xf…