深入哈希结构

news2024/11/25 5:00:39

目录

一、哈希结构概念

二、哈希冲突

三、哈希函数

3.1 哈希函数设计原则

3.2 常见哈希函数设计方法

1. 直接定址法--(常用)

2. 除留余数法--(常用)

3. 平方取中法

4. 折叠法

5. 随机数法

6. 数学分析法

3.3 处理key的局限性问题

四、通过闭散列解决哈希冲突

4.1 闭散列概念

4.2 基础操作

4.2.1 插入操作

4.2.2 删除操作

4.2.3 扩容机制

4.3 线性探测

4.4 二次探测

五、通过开散列解决哈希冲突

5.1 概念

5.2 扩容机制

5.3 完整代码

六、 闭散列与开散列对比


一、哈希结构概念

在顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素
时必须要经过关键码的多次比较。
顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(log_2 N),搜索的效率取决于搜索过程中元素的比较次数。

理想的搜索方法: 可以不经过任何比较,一次直接从表中得到要搜索的元素
若构造一种存储结构,通过某种函数(哈希函数)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素

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

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

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称
哈希表(Hash Table)(或者称散列表
 

二、哈希冲突

不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突哈希碰撞
把具有不同关键码而具有相同哈希地址的数据元素称为"同义词"。

引起哈希冲突的一个重要原因可能是:哈希函数设计不够合理

下图为线性探测情况下发生哈希冲突:

 

三、哈希函数

3.1 哈希函数设计原则

1. 哈希函数的定义域必须包括需要存储的全部关键码。若散列表允许有m个地址时,其值域必须在0到m-1之间

2. 哈希函数计算出来的地址能均匀分布在整个空间中

3. 哈希函数应该比较简单
 

3.2 常见哈希函数设计方法

1. 直接定址法--(常用)

取关键字的某个线性函数为散列地址:Hash(Key) = A*Key + B
优点:简单、均匀 、不存在哈希冲突
缺点:需要事先知道关键字的分布情况
使用场景:适合查找比较小且连续的情况

2. 除留余数法--(常用)

设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p (p<=m),将关键码转换成哈希地址

3. 平方取中法

假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址;
再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址
平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况

4. 折叠法

折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这
几部分叠加求和,并按散列表表长,取后几位作为散列地址。
折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况

5. 随机数法

选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中
random为随机数函数。
通常应用于关键字长度不等时采用此法,要求random结果固定。

6. 数学分析法

设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定
相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只
有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散
列地址。
 

3.3 处理key的局限性问题

当哈希函数采用除留余数法时,被模的key必须要为整型才可以处理。

unordered_map<string, string> dict;

但unordered_map又为何可以用string类型的数据作为key呢?

template<class K>//默认仿函数
struct hash {
	size_t operator()(const K& key) {
		return (size_t)key;
	}
};
template<>//特化
struct hash<string> {
	//BKDR算法
	size_t operator()(const string& key) {
		size_t sum = 0;
		for (auto& e : key) {
			sum = sum * 131 + e;
		}
		return sum;
	}
};

通过提供模板仿函数,利用仿函数处理key后即可得到整型类型的数据。

具体可参考: 各种字符串Hash函数 - clq - 博客园 (cnblogs.com)

四、通过闭散列解决哈希冲突

4.1 闭散列概念

即开放定址法。当发生哈希冲突时,若哈希表未被装满,说明在哈希表中必然存在空位置,那么可将key存放到冲突位置中的"下一个"空位置中去。
 

4.2 基础操作

4.2.1 插入操作

1. 通过哈希函数获取待插入元素在哈希表中的位置

2. 若该位置中没有元素则直接插入新元素,若该位置中有元素发生哈希冲突,使用线性探测或者二次探测找到下一个空位置,插入新元素

4.2.2 删除操作

采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。因此线性探测采用标记的伪删除法来删除一个元素。

4.2.3 扩容机制

 

4.3 线性探测

从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止

优点: 简单且易于实现

缺点: 一旦发生哈希冲突,所有的冲突连在一起,容易产生数据"堆积",即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。

namespace CloseHash {
	enum State {
		EMPTY,
		EXIST,
		DELETE
	};
	template<class K, class V>
	struct HashData {
		pair<K, V> _kv;
		State _state = EMPTY;
	};

	template<class K>//默认仿函数
	struct hash {
		size_t operator()(const K& key) {
			return (size_t)key;
		}
	};
	template<>//特化
	struct hash<string> {
		//BKDR算法
		size_t operator()(const string& key) {
			size_t sum = 0;
			for (auto& e : key) {
				sum = sum * 131 + e;
			}
			return sum;
		}
	};

	template<class K, class V, class Hash = hash<K>>
	class HashTable
	{
	public:
		bool insert(const pair<K, V>& kv) {
			if (find(kv.first) != nullptr) return false;//不允许键值冗余

			if (_table.size() == 0 || 10 * _size / _table.size() >= 7) {//扩容
				size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
				HashTable<K, V, Hash> new_table;
				new_table._table.resize(newSize);
				//旧表数据映射到新表
				for (auto& e: _table){
					if (e._state == EXIST) {
						new_table.insert(e._kv);
					}
				}
				_table.swap(new_table._table);
			}
			Hash hash;
			size_t index = hash(kv.first) % _table.size();//int提升为size_t
			while (_table[index]._state == EXIST) {//线性探测
				++index;
				index %= _table.size();
			}
			_table[index]._kv = kv;
			_table[index]._state = EXIST;
			++_size;
			return true;
		}

		bool erase(const K& key) {
			HashData<K, V>* ret = find(key);
			if (ret == nullptr) {
				return false;
			}
			else {
				ret->_state = DELETE;
				--_size;
				return true;
			}
		}

		HashData<K, V>* find(const K& key) {
			if (_table.size() == 0) return nullptr;
			Hash hash;
			size_t start = hash(key) % _table.size();//int提升为size_t
			size_t index = start;
			while (_table[index]._state != EMPTY) {
				if (_table[index]._state != DELETE && _table[index]._kv.first == key) {
					return &_table[index];
				}
				++index;
				index %= _table.size();
				if (index == start) {//当哈希表中全为DELETE 和 EXIST时避免死循环
					break;
				}
			}

			}
			return nullptr;
		}

	private:
		vector<HashData<K, V>> _table;
		size_t _size = 0;//有效数据
	};
}

4.4 二次探测

线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置的方法有关(逐个往后去找)。
因此二次探测为了避免该问题,找下一个空位置的方法为: H_i = (H_0 + i ^ 2) % m, 或者: H_i = (H_0 - i ^ 2) % m。其中: i = 0,1,2,3……
H_0是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小。

namespace CloseHash {
#define LINEAR
	enum State {
		EMPTY,
		EXIST,
		DELETE
	};
	template<class K, class V>
	struct HashData {
		pair<K, V> _kv;
		State _state = EMPTY;
	};

	template<class K>//默认仿函数
	struct hash {
		size_t operator()(const K& key) {
			return (size_t)key;
		}
	};
	template<>//特化
	struct hash<string> {
		//BKDR算法
		size_t operator()(const string& key) {
			size_t sum = 0;
			for (auto& e : key) {
				sum = sum * 131 + e;
			}
			return sum;
		}
	};

	template<class K, class V, class Hash = hash<K>>
	class HashTable
	{
	public:
		bool insert(const pair<K, V>& kv) {
			if (find(kv.first) != nullptr) return false;//不允许键值冗余

			if (_table.size() == 0 || 10 * _size / _table.size() >= 5) {//扩容
				size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
				HashTable<K, V, Hash> new_table;
				new_table._table.resize(newSize);
				//旧表数据映射到新表
				for (auto& e : _table) {
					if (e._state == EXIST) {
						new_table.insert(e._kv);
					}
				}
				_table.swap(new_table._table);
			}

			Hash hash;
			size_t start = hash(kv.first) % _table.size();//int提升为size_t
			size_t index = start, i = 0;
			while (_table[index]._state == EXIST) {//二次探测
				++i;
				index = start + i * i;
				index %= _table.size();
			}

			_table[index]._kv = kv;
			_table[index]._state = EXIST;
			++_size;
			return true;
		}

		bool erase(const K& key) {
			HashData<K, V>* ret = find(key);
			if (ret == nullptr) {
				return false;
			}
			else {
				ret->_state = DELETE;
				--_size;
				return true;
			}
		}

		HashData<K, V>* find(const K& key) {
			if (_table.size() == 0) return nullptr;
			Hash hash;
			size_t start = hash(key) % _table.size();//int提升为size_t
			size_t index = start, i = 0;
			while (_table[index]._state == EXIST) {//二次探测
				if (_table[index]._state != DELETE && _table[index]._kv.first == key) {
					return &_table[index];
				}
				++i;
				index = start + i * i;
				index %= _table.size();
			}
			return nullptr;
		}

	private:
		vector<HashData<K, V>> _table;
		size_t _size = 0;//有效数据
	};
}

研究表明: 当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次
因此只要表中有一半的空位置,在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,若超出必须考虑增容。

五、通过开散列解决哈希冲突

5.1 概念

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

开散列中每个桶中放的都是发生哈希冲突的元素

5.2 扩容机制

桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多。极端情况下,可
能会导致一个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希
表进行增容。那该条件怎么确认呢?开散列最好的情况是:每个哈希桶中刚好挂一个节点,
再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可
以给哈希表增容

 

但是该如何扩容呢?采用除留余数法的情况下,除数(即哈希表的长度)最好为质数,且每次扩容最好近似之前的两倍大小。这里采用SGI版本的方案(开散列、闭散列都可以使用该种扩容方式):

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
};

5.3 完整代码

namespace OpenHash {
	template<class K>//默认仿函数
	struct hash {
		size_t operator()(const K& key) {
			return (size_t)key;
		}
	};
	template<>//特化
	struct hash<string> {
		//BKDR算法
		size_t operator()(const string& key) {
			size_t sum = 0;
			for (auto& e : key) {
				sum = sum * 131 + e;
			}
			return sum;
		}
	};

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

	template<class K, class V, class Hash = hash<K>>
	class HashBucket
	{
		typedef HashNode<K, V> Node;
		
		inline size_t __stl_next_prime(unsigned long n)
		{
			static const size_t __stl_num_primes = 28;
			static const size_t __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
			};
			for (size_t i = 0; i < __stl_num_primes; ++i) {
				if (__stl_prime_list[i] > n) return __stl_prime_list[i];
			}
			return -1;
		}
	public:
		bool insert(const pair<K, V>& kv) {
			Hash hash;
			if (find(kv.first) != nullptr) return false;//不允许键值冗余

			//荷载因子到达1进行扩容
			if (_table.size() == 0 || _size == _table.size()) {
				vector<Node*> new_table;
				new_table.resize(__stl_next_prime(_table.size()), nullptr);
				for (size_t i = 0; i < _table.size(); ++i) {
					Node* cur = _table[i];
					while (cur != nullptr) {
						Node* next = cur->_next;
						size_t hashi = hash(cur->_kv.first) % new_table.size();
						//头插
						cur->_next = new_table[hashi];
						new_table[hashi] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(new_table);
			}
			
			size_t hashi = hash(kv.first) % _table.size();
			//头插
			Node* newNode = new Node(kv);
			newNode->_next = _table[hashi];
			_table[hashi] = newNode;
			++_size;
			return true;
		}

		bool erase(const K& key) {
			Hash hash;

			if (_table.size() == 0) return false;
			size_t hashi = hash(key) % _table.size();
			Node* cur = _table[hashi];
			Node* prev = nullptr;
			while (cur != nullptr) {
				if (cur->_kv.first == key) {
					if (prev == nullptr) {//头删
						_table[hashi] = cur->_next;
					}
					else {
						prev->_next = cur->_next;
					}
					delete cur;
					--_size;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}


		Node* find(const K& key) {
			Hash hash;
			if (_table.size() == 0) return nullptr;
			size_t hashi = hash(key) % _table.size();
			Node* cur = _table[hashi];
			while (cur != nullptr) {
				if (cur->_kv.first == key) {
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

		~HashBucket(){
			for (size_t i = 0; i < _table.size(); ++i) {
				Node* cur = _table[i];
				while (cur != nullptr) {
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
		}

		//存储的元素个数
		size_t size() { return _size; }

		// 表的长度
		size_t table_size()
		{
			return _tables.size();
		}

		// 桶的个数
		size_t bucket_num(){
			size_t num = 0;
			for (size_t i = 0; i < _tables.size(); ++i) {
				if (_tables[i]) {
					++num;
				}
			}
			return num;
		}

		size_t max_bucket_length() {
			size_t maxLen = 0;
			for (size_t i = 0; i < _tables.size(); ++i) {
				size_t len = 0;
				Node* cur = _tables[i];
				while (cur){
					++len;
					cur = cur->_next;
				}
				if (len > maxLen) maxLen = len;
			}
			return maxLen;
		}

	private:
		vector<Node*> _table;
		size_t _size = 0;
	};

}

六、 闭散列与开散列对比

使用链地址法处理溢出时,需要增设链接指针,似乎增加了存储开销。事实上:由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子α <= 0.7且最好是α <= 0.5,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间。

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

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

相关文章

GeoServer安装MBTiles插件

GeoServer安装MBTiles插件 之前介绍过GeoServer使用mvt插件来生成矢量切片&#xff0c;这种mvt切片本质上还是前台加载GeoJson数据&#xff0c;数据是由前端进行渲染加载的&#xff0c;数据存储在浏览器内存中&#xff0c;前台会承担渲染的压力&#xff0c;数据量过大的话&…

数字电子技术(六)时序逻辑电路

时序逻辑电路时序逻辑概述时序逻辑电路特点基本方程组时序逻辑电路的分类自启动同步时序逻辑电路的分析方法&#xff08;*考点&#xff09;分析步骤例题分析常用的时序逻辑电路模块寄存器与移位寄存器计数器二进制计数器十进制计数器任意进制计数器&#xff08;*考点&#xff0…

Python -- 高阶函数

目录 1.递归函数 2.匿名函数 3.高阶函数 3.1 定义一个变量指向函数 3.2 函数作为另一个函数的参数 3.3 函数作为另一个函数的返回值 1.递归函数 什么是递归函数&#xff1f; 如果一个函数在内部不调用其它的函数&#xff0c;而是自己本身的话&#xff0c;这个函数就是递归…

【SVM时序预测】基于matlab粒子群算法优化支持向量机PSO-SVM期贷时序数据预测【含Matlab源码 2289期】

⛄一、PSO-SVM介绍 1 SVM SVM是Vapnik提出的一种分类技术&#xff0c;这一技术具有坚实的统计理论基础。SVM可以将原始的数据映射到高维且线性可分的空间&#xff0c;扩展了线性不可分的样本数据&#xff0c;它是使用核函数将线性不可分转换为线性可分。 如果问题为线性不可分…

计算机毕业设计springboot+vue+elementUI进销存管理信息系统

项目介绍 本次系统设计是一个纺织企业进销存管理信息系统,主要目的是为了提升绿 岩纺织科技有限公司的进销存管理能力,实现绿岩纺织科技有限公司的采购/加 工管理、仓库管理、销售管理等功能。 1.基本信息模块 ;1;商品管理&#xff1a;主要包含商品信息的展示以及添加商品功能。…

Java+MySQL基于SSM的在线论坛交流系统

信息和交流是一个一直伴随着人类一生的话题,从人类诞生开始就已经拥有了这一概念,无论是远古时候的打猎还是现代化的多样化合作都离不开信息和交流。随着时代的积累,人们要面对的信息量也越来越大,任何一个人体单位的人类都不可能涉及到全部的知识,这就需要大家不断的进行交流才…

springboot之多数据源---1

一、多数据源的典型使用场景 在实际开发中&#xff0c;经常可能遇到在一个应用中可能需要访问多个数据库的情况。以下是两种典型场景&#xff1a; 1 业务复杂&#xff08;数据量大&#xff09; 数据分布在不同的数据库中&#xff0c;数据库拆了&#xff0c; 应用没拆。 一个公司…

Git 分布式版本控制工具 05SSH免登陆:如何使用SSH如何免密登录服务器~

6 ssh免登陆 SSH是目前比较可靠的专为远程登录会话和其他网络服务提供安全的协议。不同主机之间在进行通信时&#xff0c;一般都是需要输入密码进行验证&#xff0c; ssh免密码之后&#xff0c;只要通过指定主机地址和端口号就可以实现不同的计算机之间访问时&#xff0c;不需…

基于web的网上图书商城的设计与实现/图书购物系统

摘 要 本毕业设计的内容是设计并且实现一个基于JSP技术的网上图书商城。它是在Windows下&#xff0c;以MYSQL为数据库开发平台&#xff0c;Tomcat网络信息服务作为应用服务器。网上图书商城的功能已基本实现&#xff0c;主要包括个人中心、图书分类管理、用户管理、图书信息管…

Android12指纹框架完全解析(一)

前言&#xff1a;自从Android6.0开始Google官方出了标准的Android指纹框架&#xff0c;结束了各家指纹厂商各自为政的局面&#xff0c;推动了电容指纹在Android的发展&#xff1b;自从2017年新思的屏下指纹方案横空出世&#xff0c;后边汇顶等指纹厂商跟进&#xff0c;Android …

【Linux】shell命令以及运行原理

shell命令1.用户不能直接使用操作系统2.Linux是一个操作系统3.在软件层面操作Linux系统4.shell运行原理1.用户不能直接使用操作系统 首先在讲shell命令之前&#xff0c;我们要知道 “用户是不能直接在操作系统上操作的&#xff0c;只能在操作系统之上&#xff0c;也就是软件层面…

基于两阶段鲁棒优化算法的微网多电源容量配置(Matlab)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

风控模型应聘,80%会被问到的面试题

模型过拟合&#xff0c;对经常建模的小伙伴来说是非常熟悉的&#xff0c;稍不留神&#xff0c;模型就出现过拟合了&#xff0c;这让我们在解决模型过拟合问题上花费了不少功夫。同样这个也是在面试中最高频会被Q到的问题。因此&#xff0c;在平日里建立模型的过程中&#xff0c…

视觉BEV语义分割模型 Simple-BEV: What Really Matters for Multi-Sensor BEV Perception?

A Simple Baseline for BEV Perception Without LiDAR本文介绍一篇简单的BEV语义分割模型&#xff0c;这篇文章出彩的是消融实验部分&#xff0c;分析了哪些因素对BEV语义分割影响比较大&#xff0c;此外还分析了如何更好使用Radar数据提高感知性能。 项目链接为&#xff1a;h…

fastjson 反序列化RCE,远程命令执行漏洞CVE、CNVD(2022年12月最新)

1、前言 Fastjson 是一个 Java 库&#xff0c;可以将 Java 对象转换为 JSON 格式&#xff0c;也可以将 JSON 字符串转换为 Java 对象。Fastjson 可以操作任何 Java 对象&#xff0c;即使是一些预先存在的没有源码的对象。 2、漏洞原理 漏洞利用fastjson autotype在处理json对…

GCD和LCM算法

目录 一 整除 定义 性质 二 GCD 1&#xff09;定义 2&#xff09;性质 3&#xff09;GCD编程 ①暴力法 ②欧几里得算法 ③更相减损术 ④Stein算法 三 LCM ①暴力法 ②最大公约数法 四 裴蜀定理 例题&#xff1a;裴蜀定理 五 算法实践 [蓝桥杯 2019 省 B] 等差…

【AIOT】语音学习

声音以波的形式传播&#xff0c;即声波&#xff08;Sound Wave&#xff09;。当我们以波的视角来理解声音时&#xff0c;却又大繁若简起来&#xff1a;仅凭频率&#xff08;Frequency&#xff09;、幅度&#xff08;Magnitude&#xff09;、相位&#xff08;Phase&#xff09;便…

19 CPP拷贝构造函数

函数以值的方式返回对象时&#xff0c;可能会调用拷贝构造函数&#xff08;VS会调用&#xff0c;Linux不会&#xff0c;g编译器做了优化&#xff09; 代码说明 #include <iostream>using namespace std;//函数以值的方式返回对象时&#xff0c;可能会调用拷贝构造函数&a…

C++对C的语法增强

1、语法检查增强 声明int a; 定义int a 10; c语言的弱语法类型 2、严格的类型转换 枚举类型不能赋值整形 3、struct类型加强 c中定义结构体变量需要加上struct关键字&#xff0c;c不需要 c中结构体只能定义成员变量&#xff0c;不能定义函数。c可以定义成员函数 4、bool类…

聊聊优惠卷秒杀的下单功能

前言 案例来自黑马程序员视频&#xff1a;https://www.bilibili.com/video/BV1cr4y1671t/?spm_id_from333.999.0.0 案例分析 下单时需要判断两点&#xff1a; 秒杀是否开始或者结束&#xff0c;如果尚未开始或者已经结束则无法下单&#xff1b;库存是否充足&#xff0c;不足…