【C++】哈希结构

目录

一,哈希结构的认识

1-1,哈希思想

1-2,哈希函数

1-3,哈希冲突

1-3-1,闭散列

1-3-2,开散列

二,哈希结构的封装实现

2-1,闭散列封装实现

​编辑

2-2,开散列封装实现


一,哈希结构的认识

1-1,哈希思想

        C++存储结构中,我们接触的顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(log N)。其实,C++STL中还有一种平均情况下的常数时间复杂度(O(1))的查找、插入和删除操作的哈希结构,它是一种理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素,在许多应用中都非常高效。

        C++11底层库中有 unordered_map、unordered_multimap、unordered_set、unordered_multiset等关联式哈希容器,都是通过键key与值value一一眏射的关系进行存储,与map、multimap、set、multiset等容器不同的是哈希存储结构内部都是无序的。

        哈希常用于先构造一种哈希表数据结构,然后通过某种函数(哈希函数)计算键(key)获得一个数值(哈希值),此值对应元素的存储位置,这样一来元素的存储位置与它的关键码(键key)之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。具体如下:

        当向该结构中插入元素时:根据待插入元素的关键码(键key值),以哈希函数计算出该元素的存储位置并按此位置进行存放。

        当向该结构中搜索元素时:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。

1-2,哈希函数

        运用以上方式进行增删查改叫哈希(或散列)方法,哈希方法中使用的转换函数称为哈希(或散列)函数,构造出来的结构称为哈希表(或者称散列表)。例如:数据集合{1,7,6,4,5,9};哈希函数设置为:hash(key) = key % capacity;其中 capacity 为存储元素底层空间总的大小,这里假设为10,对应的效果图如下:

        类似于以上哈希函数的设置叫做除留余数法,也是最常用的一种方法。哈希函数的设置还有一种常用的方法叫做直接定址法。若使用直接定址法,如上哈希函数的设置为:hash(key) = key或hash(Key)= a*key + b或类似于取关键字的某个线性函数为散列地址。不难看出,此方法非常消耗空间。若存在类似于{1,1000,60000}数据时,哈希表必须设置非常大,也就是说当关键码非常大时,空间开辟也必须同样大,这将导致空间利用率极低,所以这种方法存在局限性,只有适合查找比较小且连续的情况才适用。

1-3,哈希冲突

        哈希函数的设置其实还有很多种方法,但是大多数都不常用,这里我们只说明除留余数法。用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快。但是,按照上述的哈希函数进行插入时,再向表中插入元素44、16、47,类似于这些关键码通过哈希函数计算出的相同的哈希值时,将会产生空间冲突,该现象叫做哈希冲突。

        哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是这里无法避免哈希冲突。直接定址法和除留余数法彻底解决哈希冲突的方式相同,常见的两种方法是:闭散列开散列

1-3-1,闭散列

        闭散列:也叫开放定址法。当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,此方法是将数据存放到冲突位置中的 “下一个” 空位置中去,若找到表尾位置,就要往头往后继续寻找。哈希冲突中,若连续存在的数据越多,哈希效率就越低,因此又有了负载因子(或载荷因子)的概念。负载因子=实际存储数据的个数/表的大小。负载因子越大,哈希冲突概率就越高,发生冲突时,连续存在多个数据的概率越大;负载因子越小,综合性能就越好,但是空间效率就越差,也就是说负载因子是拿空间换时间的做法。因此,通常将负载因子控制一定大小,一旦负载因子超出范围时就对哈希表进行扩容,其中这里的负载因子大于0,小于等于1。

        控制负载因子还不能完全解决连续存在多个数据导致哈希效率低的情况,通常这里发生哈希冲突寻找下一个空位置时采用线性探测或二次探测。线性探测在寻找下一个位置时一次跳一步寻找下一个空位置,即:hash + i(i > 0),二次探测是以二次方的跳跃式探测下一个空位置,即:hash + i^2(i > 0)。二次探测可能会存在查找不到空位置的情况。研究表明:当表的长度为质数且负载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。

        线性探测实现简单,但是当连续存在多个数据时,会影响哈希效率。二次探测虽可以解决效率问题,但会降低空间利用率。

1-3-2,开散列

        开散列:又叫拉链地址法(或哈希桶法)——首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

        从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。

        无论是哪种方法,若哈希冲突越多,哈希效率就越低。因此,这里的负载因子一定要合理设置,通常闭散列开放定址法将负载因子设置为0.7,开散列拉链法将负载因子设置为1。


二,哈希结构的封装实现

2-1,闭散列封装实现

        由于哈希结构是在关联式容器基础上操作的(不完全是依靠关联式容器),不同的是哈希是无序,关联式容器是有序,所以这里也不支持修改操作。下面我们模拟实现插入、删除、修改操作。

插入操作:插入操作的哈希函数是通过键key值作为哈希码来计算哈希值,但问题是若键key值不是整数将会导致失败,因此在使用哈希函数时需要将哈希码转换为正整数,这里采用伪函数实现。至于在插入时的扩容操作,当负载因子超出或等于0.7时进行扩容,而且扩容时不能在原表上扩容,因为眏射位置会不同。比如:原本表的大小是10,根据哈希函数数据14应插入在4的位置,但是在原表上扩容后假设大小变为20,14应在14的位置上,眏射位置不同,导致后面插入或眏射关系混乱。扩容时需要重新建立哈希表,将全部数据重新确定眏射关系进行插入。

template<class K>   
struct Conversion
{
    size_t operator()(const K& data)
    {
        return data;
    }
};
template<>  //处理键是串的情况
struct Conversion<std::string>
{
    size_t operator()(const std::string& data)
    {
        size_t count = 0;
        for (auto e : data)
        {
            count += e;
            //以下进行专门乘法运算防止出现类似"ab","ba"的串,这里专门乘131而不是其它数据是大佬研究出来最终很小概率不会面临重复数字,这里不做研究
            count *= 131;
        }
        return count;
    }
};

//注意:只要是串,这里无论乘什么数据都会面临重复,因为size_t存储的数据大小有限,而string串的长度很大,当数据大到一定程度时将面临溢出,会出现数值相同现象

查找操作:直接通过计算哈希函数进行查找即可,思想跟插入操作一样。
删除操作:删除数据后要进行标记,表示此位置没有被占用,以便下次进行插入或查找。这里采用枚举表示数据状态,当存储数据时顺便也把数据状态一并存储。删除数据时,只讲状态进行更新即可,表示此数据已被删除。

//数据状态
enum State
{
    EXIT,        //存在数据——已插入
    DELETE, //数据被删除——已删除
    EMPTY    //不存在数据——空位置
};

//存储数据
template <class K, class V>
struct HashData
{
    std::pair<K, V> _kv = std::pair<K, V>(); //数据
    State _state = EMPTY;   //状态
};

封装思路:

        哈希表是一个vector顺序存储容器,存储数据类型为HashData。其次这里还需封装数据个数,即便计算负载因子。

//K—键        V—值     Con—转换size_t的类
template<class K, class V, class Con = Conversion<K>>
class HashTable
{
public:

    //........

private:
    std::vector<HashData<K, V>> _table;  //哈希表
    size_t _count;    //数据个数
};

代码封装:

template<class K>
struct Conversion
{
    size_t operator()(const K& data)
    {
        return data;
    }
};
template<>
struct Conversion<std::string>
{
    size_t operator()(const std::string& data)
    {
        size_t count = 0;
        for (auto e : data)
        {
            count += e;
            count *= 131;
        }
        return count;
    }
};
enum State
{
    EXIT, DELETE, EMPTY   
};
template <class K, class V>
struct HashData
{
    std::pair<K, V> _kv = std::pair<K, V>(); 
    State _state = EMPTY;   
};
//K—键        V—值     Con—转换size_t的类
template<class K, class V, class Con = Conversion<K>>
class HashTable
{
public:
    HashTable(size_t n = 10)
        : _count(0)
    {
        _table.resize(n); //默认开辟10大小空间
    }
    //查找: 当没有查找到数据时返回空,查找到数据返回此数据的指针
    std::pair<K, V>* Find(const K& data)
    {
        Con conversion;
        size_t pos = conversion(data) % _table.size();
        while (_table[pos]._state == EXIT && _table[pos]._kv.first != data)
        {
            pos++;
            pos %= _table.size();
        }
        if (_table[pos]._state != EXIT)    return nullptr;
        return &_table[pos]._kv;
    }
    //插入: 分扩容和插入两大步骤
    bool Insert(const std::pair<K, V>& kv)
    {
        if (Find(kv.first)) return false;
        //扩容,当负载因子超出0.7时进行扩容
        if (_count * 1.0 / _table.size() >= 0.7)
        {
            //扩容时不能在原表上扩容,会导致眏射关系混乱
            //_table.resize(2 * _table.size()); 
            

            //注意: 建立新表时需要运用封装的专门算法,方便起见这里最好不要直接创建表vector,直接新创建对象
            HashTable<K, V, Con> newtable;
            newtable._table.resize(2 * _table.size());
            for (auto& e : _table)
            {
                if (e._state == EXIT) newtable.Insert(e._kv);
            }
            _table.swap(newtable._table);
        }
        //插入数据
        Con conversion;
        size_t pos = conversion(kv.first) % _table.size();
        while (_table[pos]._state == EXIT)
        {
            //不断往后面遍历,当遍历到表尾时从头遍历
            pos++;
            pos %= _table.size();
        }
        _table[pos]._kv = kv;
        _table[pos]._state = EXIT;
        _count++;
        return true;
    }
    //删除: 找到数据后更新数据状态
    bool Erase(const K& data)
    {
        if (!Find(data)) return false;
        Con conversion;
        size_t pos = conversion(data) % _table.size();
        while (_table[pos]._kv.first != data)
        {
            pos++;
            pos %= _table.size();
        }
        _table[pos]._state = DELETE; //更新数据状态
        _count--;
        return true;
    }

    void Print()//测试输出
    {
        std::cout << "Size: " << _count << std::endl;
        for (auto& e : _table)
        {
            if (e._state == EXIT)
            {
                std::cout << e._kv.first << ":" << e._kv.second << std::endl;
            }
        }
        std::cout << std::endl;
    }
private:
    std::vector<HashData<K, V>> _table;
    size_t _count;
};

样例测试1:

void HashTest1()
{
    int a[] = { 1,4,24,34,7,44,17,37 };
    HashTable<int, int> h;
    for (auto e : a)
    {
        h.Insert(std::make_pair(e, e));
    }

    h.Print();

    std::pair<int, int>* it = h.Find(17);
    if (it)
        std::cout << it->first << std::endl;
    else
        std::cout << "无此数据" << std::endl;
    std::cout << std::endl;

    h.Erase(17);
    h.Erase(44);

    h.Print();
}

样例测试2:

void HashTest2()
{
    HashTable<std::string, int> h;
    h.Insert(std::make_pair("string", 0));
    h.Insert(std::make_pair("vector", 0));
    h.Insert(std::make_pair("list", 0));
    h.Insert(std::make_pair("ingstr", 0));
    h.Insert(std::make_pair("torvect", 0));
    h.Insert(std::make_pair("stli", 0));

    h.Print();

    std::pair<std::string, int>* it = h.Find("ingstr");
    if (it)
        std::cout << it->first << std::endl;
    else
        std::cout << "无此数据" << std::endl;
    std::cout << std::endl;

    h.Erase("stli");
    h.Erase("torvect");

    h.Print();
}

2-2,开散列封装实现

        开散列插入的数据类型是结点,发生哈希冲突时使用链表结构连接,跟闭散列不同,不需要使用状态。有些人想到链表结构可能会首选库中list,即vector<list>。但是list是双向链表,双向链表的优势在于可在任意位置插入删除,这里显然没多大优势,因此采取这种方式会降低哈希性能,使用单链表足以。

        开散列的查找和删除操作简单,插入操作时的扩容需注意不要使用闭散列的方式建立哈希表进行插入,因为这里的插入数据操作是结点,需要动态开辟空间,若原本数据量过多,这样持续的开辟删除会大大降低性能。这里直接创建表来重新搬运结点数据以重新建立哈希关系。封装如下:

//结点
template<class K, class V>
struct HashNode
{
    pair<K, V> _kv;
    HashNode<K, V>* _next;

    HashNode(const pair<K, V>& kv = pair<K, V>())
        : _kv(kv)
        , _next(nullptr)
    {    }
};

//哈希表

template <class K, class V, class Con = Conversion<K>>
class HashTable
{
    typedef HashNode<K, V> Node;
public:
    HashTable(const size_t n = 10)
        : _count(0)
    {
        _table.resize(n, nullptr);
    }

    ~HashTable()
    {
        for (size_t i = 0; i < _table.size(); i++)
        {
            Node* cur = _table[i];
            while (cur)
            {
                _table[i] = cur->_next;
                delete cur;
                _count--;
                cur = _table[i];
            }
        }
    }
    //查找
    Node* Find(const K& data)
    {
        Con conversion;
        size_t pos = conversion(data) % _table.size();
        Node* cur = _table[pos];
        while (cur)
        {
            if (cur->_kv.first == data)    return cur;
            cur = cur->_next;
        }
        return nullptr;
    }
    //插入
    bool Insert(const pair<K, V>& kv)
    {
        //这里不允许插入相同数据
        if (Find(kv.first)) return false;
        //扩容: 负载因子到1就扩容
        if (_count == _table.size())
        {
            //直接创建表来重新搬运结点数据
            vector<Node*> newtable;
            newtable.resize(2 * _table.size(), nullptr);
            for (size_t i = 0; i < _table.size(); i++)
            {
                Node* cur = _table[i];
                while (cur)
                {
                    Node* next = cur->_next;
                    Con conversion;
                    size_t pos = conversion(cur->_kv.first) % newtable.size();
                    cur->_next = newtable[pos];
                    newtable[pos] = cur;
                    cur = next;
                }
            }
            _table.swap(newtable);
        }
        //插入
        Con conversion;
        size_t pos = conversion(kv.first) % _table.size();
        Node* node = new Node(kv);
        //这里进行头插
        node->_next = _table[pos];
        _table[pos] = node;
        _count++;
        return true;
    }
    //删除
    bool Erase(const K& data)
    {
        if (!Find(data)) return false;
        Con conversion;
        size_t pos = conversion(data) % _table.size();
        Node* cur = _table[pos];
        Node* prv = nullptr;
        while (cur->_kv.first != data)
        {
            prv = cur;
            cur = cur->_next;
        }
        if (cur == _table[pos]) _table[pos] = cur->_next;
        else prv->_next = cur->_next;
        delete cur;
        cur = nullptr;
        _count--;
        return true;
    }

    void Print() //测试输出
    {
        int num = 0, Maxbucketlength = 0, Bucketlength = 0;
        for (size_t i = 0; i < _table.size(); i++)
        {
            Bucketlength = 0;
            Node* cur = _table[i];
            if (cur)
            {
                num++;
                cout << "Bucket " << i << ": ";
            }
            else
            {
                cout << "Bucket " << i << ": " << "nullptr";
            }
            while (cur)
            {
                Bucketlength++;
                cout << cur->_kv.first << ":" << cur->_kv.second << "  ";
                cur = cur->_next;
            }
            if (Bucketlength > Maxbucketlength) Maxbucketlength = Bucketlength;
            cout << endl;
        }
        cout << "Bucket Node Count: " << _count << endl;
        cout << "Bucket Group: " << num << endl;
        cout << "Table Size: " << _table.size() << endl;
        cout << "load factor: " << _count * 1.0 / _table.size() << endl;
        cout << "Maxbucketlength: " << Maxbucketlength << endl;
        cout << endl;
    }
private:
    vector<Node*> _table;  //哈希表
    size_t _count;  //数据个数
};

        注意:若哈希桶连接过长,哈希效率就接近于n。由于这里哈希表存储的都是桶,桶结构是链表,因此,极端情况下若存在桶长度过大,我们可以将此桶改为红黑树结构来提高哈希效率。

测试样例:

void HashTest1()
{
    int a[] = { 1,4,24,34,7,44,17,37 };
    HashTable<int, int> h;
    for (auto e : a)
    {
        h.Insert(make_pair(e, e));
    }

    h.Print();

    HashNode<int, int>* Node = h.Find(17);
    if (Node)
        cout << Node->_kv.first << ":" << Node->_kv.second << endl;
    else
        cout << "无此数据" << endl;
    cout << endl;

    h.Erase(17);
    h.Erase(44);

    h.Print();
}

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

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

相关文章

genetic algorithm

genetic algorithm 遗传算法

C++入门5.内联函数,auto关键字,基于范围的for循环(C++11),指针空值nullptr(C++11)

本篇是C过度C初始的最后一篇&#xff0c;快快对入门须知的知识有个印象后&#xff0c;就可以顺顺利利的学习C的类了。 目录 内联函数&#xff1a; 内联函数的特性&#xff1a; auto关键字(C11)&#xff1a; auto简介&#xff1a; 使用细则&#xff1a; auto不能推导的场…

基于java+springboot+vue实现的物业管理系统(文末源码+Lw+ppt)23-23

摘 要 快速发展的社会中&#xff0c;人们的生活水平都在提高&#xff0c;生活节奏也在逐渐加快。为了节省时间和提高工作效率&#xff0c;越来越多的人选择利用互联网进行线上打理各种事务&#xff0c;通过线上物业管理系统也就相继涌现。与此同时&#xff0c;人们开始接受方…

K8S基础概念

一、MASTER Kubernetes里的Master指的是集群控制节点&#xff0c;在每个Kubernetes集群里都需要有一个Master来负责整个集 群的管理和控制&#xff0c;基本上 Kubernetes的所有控制命令都发给它&#xff0c;它负责具体的执行过程&#xff0c;我们后 面执行的所有命 令基本都…

idea2024.1发布,lambda多语句的内联断点,增强spring图标等新特性,你没玩过的全新版本

这里是weihubeats,觉得文章不错可以关注公众号小奏技术 简述 2024-04-04 idea官方宣布发布了 一些重大更新 随后我便下载了你没玩过的全新版本IntelliJ IDEA Ultimeate版本试玩 然后脑子里面想到这个 开玩笑 实际下载完是这样 更新内容 更新的内容比较多 关键亮点主要有如下…

Redis入门到通关之数据结构解析-RedisObject

文章目录 ☃️概述☃️源码 ☃️概述 RedisObject 是 Redis 中表示数据对象的结构体&#xff0c;它是 Redis 数据库中的基本数据类型的抽象。在 Redis 中&#xff0c;所有的数据都被存储为 RedisObject 类型的对象。 RedisObject 结构体定义如下&#xff08;简化版本&#xf…

MDC搭配ttl

1.MDC 1.简介 MDC 介绍​ MDC&#xff08;Mapped Diagnostic Context&#xff0c;映射调试上下文&#xff09;是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的Map&#xff0c;可以往其中添加键值对。MDC 中包含的内容可…

CSS实现广告自动轮播

实现原理 该广告轮播功能的实现主要依靠HTML和CSS。HTML负责搭建轮播框架&#xff0c;而CSS则控制样式和动画效果。通过CSS中的关键帧动画&#xff08;Keyframes&#xff09;&#xff0c;我们可以定义图片在容器内的滚动效果&#xff0c;从而实现轮播功能。 HTML结构 首先&am…

如何搭建线下陪玩系统(本地伴游、多玩圈子)APP小程序H5多端前后端源码交付,支持二开!

一、卡顿的优化方法 1、对陪玩系统源码中流媒体传输的上行进行优化&#xff0c;通过提升推流端的设备性能配置、推流边缘CDN节点就近选择等方式解决音视频数据源流的卡顿。 2、对陪玩系统源码中音视频数据的下载链路进行优化&#xff0c;通过选择更近更优质的CDN边缘节点来减少…

Navicat导入sql文件图文教程

本文使用的MySQL工具为:Navicat.默认已经连接数据库!! 步骤: 1.右键自己的数据库,选择新建数据库. 2.输入数据库名称&#xff0c;字符集选择“utf8”&#xff0c;排序规则选择“ utf8_general_ci”,确定. 3.双击新建好的“数据库”。右键点击“运行SQL文件”。 4.选择本地的s…

linux信号相关概念

signal 信号引入什么是信号&#xff1f;如何产生信号&#xff1f;通过按键产生信号调用系统函数向进程发信号系统调用函数发送信号的流程: 由软件条件产生信号软件发送信号的流程&#xff1a; 硬件异常产生信号硬件异常的流程&#xff1a; Deliver、Pending、Block概念信号在内…

Vue 查看真实请求地址

当你在项目中配置了proxy代理&#xff0c;前端在浏览器开发调试的时候&#xff0c;是看不到真是的请求地址的。 这时候&#xff0c;后端要说话了&#xff1a;你这连的是我的地址吗&#xff1f;网络里这显示的也不对吧~ 前端: 额、不是在这里看的。既然你不相信我&#xff0c;…

替代普通塑料吸头的PFA移液吸头

目前市场上的规格&#xff1a;0.01ml、0.05ml、0.1ml、0.2ml、0.5ml、1ml、2ml、5ml、10ml等均可定制加工PFA材质枪头&#xff0c;可以适配市场上大部分移液枪&#xff0c;普兰德&#xff0c;大龙&#xff0c;赛默飞&#xff0c;赛多利斯&#xff0c;力辰、吉尔森&#xff0c;瑞…

K8S哲学 - probe 探针

探针分类&#xff1a; liveness probe readiness probe startup probe Liveness Probe&#xff1a;用于检查容器是否还在运行。如果 Liveness Probe 失败&#xff0c;Kubernetes 会杀死容器&#xff0c;然后根据你的重启策略来决定是否重新启动容器。常见的做法是使用与 Readin…

error解决expression before ‘static‘

问题现象 报警如下 跳转到提示第125行&#xff0c;但是这行明显是没有问题的。 问题分析 经过排查可以看到&#xff0c;是120行的末尾\在S32DS编译器里面被认为是“接下一行”的意思&#xff0c;120行注释掉之后&#xff0c;后面的121行、122行、123行均被注释掉&#xff0c;…

2024年3月 青少年软件编程(图形化) 等级考试试卷(一级)

2024.3青少年软件编程&#xff08;图形化&#xff09; 等级考试试卷&#xff08;一级&#xff09; 一、 单选题(共 25 题&#xff0c; 共 50 分) 1.单击下列哪个按钮&#xff0c; 能够让舞台变为“全屏模式” &#xff1f; &#xff08; &#xff09; A. B. C. D. 标准答案&am…

Redis系列3:高可用之主从架构

1 主从复制介绍 上一篇《Redis系列2&#xff1a;数据持久化提高可用性》中&#xff0c;我们介绍了Redis中的数据持久化技术&#xff0c;包括 RDB快照 和 AOF日志 。有了这两个利器&#xff0c;我们再也不用担心机器宕机&#xff0c;数据丢失了。 但是持久化技术只是解决了Redi…

Redis进阶——相互关注Feed流推送

目录 关注和取消关注业务需求实现步骤效果如下 共同关注业务需求实现步骤效果如下 Feed流实现方案Feed流简介三种Timeline方式三种模式对比 推送到粉丝收件箱业务需求Feed流的滚动分页 实现分页查询收件箱业务需求具体步骤如下 关注和取消关注 业务需求 当我们进入到笔记详情…

如何用C++写一个日期计算器

目录 前言 代码的布局 设计数据 方法声明 方法的实现 获取某年某月的天数 *全缺省的构造函数 * 拷贝构造函数 *赋值运算符重载 *析构函数 日期天数 日期天数 日期-天数 日期-天数 前置 后置 后置-- 前置-- 实现比较大小运算符重载思路 >运算符重载 运算…

互联网通信原理

互联网通信原理 ISO/OSI(开放系统互连)的七层模型 注意事项 上三层是为用户提供服务的&#xff0c;下四层负责实际数据传输下四层的传输单位 传输层&#xff08;数据段&#xff09;、网络层&#xff08;数据包&#xff09;、数据链路层&#xff08;数据帧&#xff09;、物理层…