【C++】深入剖析C++11中右值引用和左值引用

目录

一、左值引用 && 右值引用

二、左值引用于右值引用的比较

三、 右值引用使用场景和意义

 1、函数返回值

①移动赋值

②移动构造

2、STL容器插入接口

​3、完美转发


一、左值引用 && 右值引用

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们 之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名

什么是左值?什么是左值引用?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋 值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左 值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名

什么是右值?什么是右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引 用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能 取地址右值引用就是对右值的引用,给右值取别名

⭕【注意】

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可 以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地 址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用

二、左值引用于右值引用的比较

左值引用总结:

🟢左值引用只能引用左值,不能引用右值。

🟢const左值引用既可引用左值,也可引用右值。 

右值引用总结:

🟢右值引用只能右值,不能引用左值。

🟢右值引用可以move以后的左值。 (move可以将左值转换位右值,也就是使用move后它会传回一个右值。)

三、 右值引用使用场景和意义

前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么C++11还要提出右值引 用呢?是不是化蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!

左值引用的核心价值就是减少拷贝,提高效率。但也有局限场景:
①当作为函数参数时,用左值引用非常好,可以减少拷贝。
②当作为函数返回值时,这里的前提就是变量要么是静态变量要么是全局变量,反正在函数结束后,该引用变量仍然存在,这个场景下才可以使用左值引用,减少拷贝。
③当变量是局部变量时,就无法使用左值引用作为函数返回值了,必须使用传值返回!但在传值返回时,函数结束后变量就销毁了,所以需要拷贝一个临时变量存储返回值。这里就存在拷贝。当返回值是内置类型,拷贝代价低,当返回值是自定义类型,那么拷贝的代价就很大了。因为拷贝都是深拷贝,需要开空间。

右值引用都应用在哪呢?
右值引用的核心也是为了减少拷贝,并且是进一步减少拷贝,弥补左值引用中没有解决的场景:比如上面所说的函数传值返回需要拷贝。那么右值引用是如何解决的呢?转移资源!直接将资源转移

 1、函数返回值

就是对于那些自定义类型中需要深拷贝的类,并且需要传值返回的类。对象如果是内置类型那么拷贝的代价很低,所以主要考虑的是自定义类型。而如果在自定义类型中不存在深拷贝的操作,那么也不需要考虑,这些操作的消耗不是很大。但是如果是自定义类型中深拷贝的话,那么这个消耗就巨大了,不仅需要开跟对象一样大的空间,将数据拷贝过来,最后还要释放空间。

接下来用自己手搓的 string 来演示【手把手教你手搓string类】

#define  _CRT_SECURE_NO_WARNINGS
#pragma once
#include<assert.h>
#include<string>namespace zhou
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str+_size;}iterator begin() const{return _str;}iterator end() const{return _str + _size;}//无参的构造函数/*string():_str(new char[1]),_size(0),_capacity(0){ _str[0] = '\0';}*///带参的构造函数//str是被const修饰的,是库里面决定的,不能改变//string(const char* str=nullptr) 不可以,strlen遇到'\0'才停止,遇到空指针会崩溃	//string(const char* str = '\0')  不可以,类型不匹配,左边是char类型的//string(const char* str = "\0")  //可以,是常量字符串,strlen是0,可以正常运算。string(const char* str = "")  //可以,不写默认是'\0':_size(strlen(str)){_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}//返回c形式的字符串const char* c_str(){return _str;}string(const string& s):_size(s._size), _capacity(s._capacity){_str = new char[s._capacity + 1];strcpy(_str, s._str);}//无 const 修饰char& operator[](size_t pos){assert(pos < _size);return _str[pos];}//有 const 修饰const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}size_t size(){return _size;}//赋值string& operator=(const string s){if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[]_str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}//不修改成员变量数据的函数,最好都加上constbool operator>(const string& s) const{return strcmp(_str, s._str) > 0;}bool operator==(const string& s) const{return strcmp(_str, s._str) == 0;}bool operator>=(const string& s) const{//return *this > s || *this == s;return *this > s || s == *this;}bool operator<(const string& s) const{return !(*this >= s);}bool operator<=(const string& s) const{return !(*this > s);}bool operator!=(const string& s) const{return !(*this == s);}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void resize(size_t n, char ch = '\0'){if (n <= _size){_size = n;_str[_size] = '\0';}else {if (n > _capacity){reserve(n);}size_t i = _size;while (i < n){_str[i] = ch;i++;}_size = n;_str[_size] = '\0';}}void push_back(char ch){//要判断内存if (_size + 1 > _capacity){reserve(2 * _capacity);}_str[_size] = ch;_size++;//不要忘'\0'_str[_size] = '\0';}void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size += len;}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}string& insert(size_t pos, char ch){assert(pos <=_size);if (_size + 1 > _capacity){reserve(2 * _capacity);}//问题代码,会发现头插时会崩溃/*size_t end = _size;while (end >=pos){_str[end + 1] = _str[end];end--;}*/size_t end = _size + 1;while (end > pos){_str[end] = _str[end-1];end--;}_str[pos] = ch;_size++;return *this;}string& insert(size_t pos, const char* str){size_t len = strlen(str);assert(pos <= _size);if (_size + len > _capacity){reserve(_size + len);}size_t end = _size + len;while (end > pos + len-1){_str[end] = _str[end-len];end--;}strncpy(_str + pos, str,len);_size += len;return *this;}string& erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}void swap(string& s){std::swap(_str, s._str);std::swap(_capacity, s._capacity);std::swap(_size, s._size);}size_t find(char ch, size_t pos = 0){assert(pos < _size);for (size_t i = pos; i < _size; ++i){if (_str[i] == ch){return i;}}return  npos;}size_t find(const char* str, size_t pos = 0){assert(pos < _size);char* p = strstr(_str + pos, str);if (p == nullptr){return  npos;}else{return p - _str;}}void clear(){_str[0] = '\0';_size = 0;}~string(){delete[] _str;_str = nullptr;_capacity =_size= 0;}private:char* _str;size_t _size;size_t _capacity;static const size_t npos;};const size_t string::npos = -1;ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}istream& operator>>(istream& in, string& s){s.clear();char ch = in.get();char buff[128];size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[127] = '\0';s += buff;i = 0;}ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in;}void test_string1(){string s1;string s2("hello world");cout << s1.c_str() << endl;cout << s2.c_str() << endl;}void test_string2(){string s("hello world");//下标遍历for (size_t i = 0; i < s.size(); i++){cout<<s[i];}cout << endl;//iterator遍历string::iterator it = s.begin();while (it < s.end()){cout << *it;it++;}cout << endl;//范围for for (auto ch : s){cout << ch;}}}

①移动赋值

这里我们再对右值进一步分类,右值也称为将亡值。为什么叫将亡值呢?一般有的右值的生命周期只有一行,下一行,这个右值就销毁了,所以称为将亡值,就比如函数的返回值就是将亡值。对于内置类型呢,右值称呼为纯右值,对于自定义类型,称为将亡值。

这样必须调用两次深拷贝,代价太大了。我们注意到 to_string 函数的地址是无法获取到的,也就是说 to_string 函数的返回值是右值,而 to_string 函数的返回值又是自定义类型,所以这个右值是个将亡值,生命周期就在这一行,我们可以利用这个将要销毁的将亡值的特性,将这个将亡值的资源全部吸走,再将自己的不要的给它,这样不需要开辟空间,也不需要深度拷贝,ret 这个变量就获取到了想要的资源。

⭕【总结】当要赋值的对象是右值时,就调用移动赋值,当拷贝的对象是左值时,就调用普通重载赋值 。因为左值不会立即销毁,如果将左值的资源全部抢走明显是不合理的。所以当拷贝左值时,该深拷贝就深拷贝。

②移动构造

我们不仅可以重载赋值运算符的移动赋值,还可以重载拷贝构造的移动拷贝,因为重载后,对整体来说是没有问题的,当拷贝的对象是左值那么就调用拷贝构造,当拷贝的对象是右值那么就调用移动拷贝。

 to_string 的返回值是一个右值,用右值构造s,如果没有移动构造,调用就会匹配调用拷贝构造,因为 const 左值引用是可以引用右值的,这里就是一个深拷贝。

移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不 用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己

1.连续的构造/拷贝构造,会合二为一。
2.编译器会将 ret 识别成右值,即将亡值。
【问题①】如何合二为一的?
在函数结束之前,就让 ret 作为拷贝对象,s 调用拷贝构造。而不是在函数结束之后再赋值,因为函数结束后,ret 就销毁了,所以需要在函数结束之前拷贝,也就是在函数结束之前将 ret 返回,再将 ret 看成将亡值,这一步是编译器做的,我们看不到。
【问题②】为什么将 ret 识别成将亡值?
因为 ret 识别成将亡值更符合概念,编译器不优化的话, to_string 函数的返回值也是将亡值,编译器优化后, to_string 返回值是 ret ,那这样一对, ret 理论上就应该被识别成将亡值,并且将 ret 看成将亡值并没有什么问题,反正 ret 也快销毁了。

这样的话最后的过程就只调用了移动构造。由原来的会调用拷贝构造进行深拷贝变成了现在的只调用移动拷贝.这里移动拷贝直接就将 ret 的资源转载到了 s,中间没有开辟空间.to_string 的返回值是一个右值,用这个右值构造s,如果既有拷贝构造柱又有移动构造,调用就会匹配调用移动构造,因为编译器会选择最匹配的参数调用。

2、STL容器插入接口

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能 真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move 数将左值转化为右值。C++11中,std::move()函数位于头文件中,该函数名字具有迷惑性, 它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

 我们接下来用 list 来实现:

❓【问题】为什么只有一个移动拷贝呢?按我们上面方法to_string 应该是移动拷贝才对啊。

✅【解答】因为右值被右值引用以后得属性是左值。to_string 返回的是右值,但右值经过函数引用之后又变成了左值,左值拷贝就变成了深拷贝

❓【问题】那为什么要这么设计呢?

✅【解答】因为右值不能直接修改,但是右值被右值引用之后,需要被修改,否则无法实现移动赋值和移动构造。

为了解决上面的问题我们可以通过 move 函数将左值修改为右值

 3、完美转发

定义一个模板,既可以接收左值,又可以接受右值。

【模板验证】

 ❓【问题】为什么全是左值引用呢?

✅【解答】因为左值引用返回左值引用,右值被引用之后的属性也是左值。

❓【问题】怎么保持值的原有属性呢?保持T属性

✅【解答】使用 forward<T>,当实参是左值时,它就是左值引用,当实参是右值时,它就是右值引用。

所以我们也可以将 forward<T> 来取代 move() 函数在list之间的应用。

 【list 完整代码】

#pragma once#include<set>namespace zhou
{template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T()):_data(x), _next(nullptr), _prev(nullptr){}list_node(T&& x):_data(move(x)), _next(nullptr), _prev(nullptr){}template <class... Args>list_node(Args&&... args): _data(args...), _next(nullptr), _prev(nullptr){}};// T T& T*// T cosnt T& const T*template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _node;__list_iterator(Node* node):_node(node){}self& operator++(){_node = _node->_next;return *this;}self& operator--(){_node = _node->_prev;return *this;}self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const self& s){return _node != s._node;}bool operator==(const self& s){return _node == s._node;}};template<class T>class list{typedef list_node<T> Node;public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;//typedef __list_const_iterator<T> const_iterator;const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}iterator begin(){//return iterator(_head->_next);return _head->_next;}iterator end(){//return iterator(_head->_next);return _head;}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}// lt2(lt1)list(const list<T>& lt){empty_init();for (auto e : lt){push_back(e);}}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}// lt3 = lt1list<int>& operator=(list<int> lt){swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}void push_back(const T& x){insert(end(), x);}void push_back(T&& x){insert(end(), forward<T>(x));}template <class... Args>void emplace_back(Args&&... args){Node* newnode = new Node(args...);// 链接节点}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}void pop_back(){erase(--end());}iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* newnode = new Node(x);Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}iterator insert(iterator pos, T&& x){Node* cur = pos._node;Node* newnode = new Node(forward<T>(x));Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}iterator erase(iterator pos){Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;delete cur;prev->_next = next;next->_prev = prev;--_size;return iterator(next);}size_t size(){return _size;}private:Node* _head;size_t _size;};
}

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

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

相关文章

pygame鼠标绘制

pygame鼠标绘制 Pygame鼠标绘制效果代码 Pygame Pygame是一个开源的Python库&#xff0c;专为电子游戏开发而设计。它建立在SDL&#xff08;Simple DirectMedia Layer&#xff09;的基础上&#xff0c;允许开发者使用Python这种高级语言来实时开发电子游戏&#xff0c;而无需被…

淘宝霸屏必备!了解淘宝商品评论电商API接口

淘宝商品评论电商API接口是指用于获取淘宝商品评论信息的一种接口&#xff0c;通过该接口可以获取淘宝网上商品的评价内容、评价等级、评价数量等信息。通过了解并使用该接口&#xff0c;联讯数据能够帮助电商了解消费者对商品的评价情况&#xff0c;做好商品的推广和销售工作。…

力扣刷题:四数相加Ⅱ

题目详情&#xff1a; 解法一&#xff1a;暴力枚举 对于这道题&#xff0c;我们的第一思路就是暴力枚举&#xff0c;我们可以写一个四层的for循环进行暴力匹配&#xff0c;只要相加的结果等于0就进行统计。但是我们会发现&#xff0c;我们的事件复杂度为O(N^4)事件复杂度非常大…

【Word】写论文,参考文献涉及的上标、尾注、脚注 怎么用

一、功能位置 二、脚注和尾注区别 1.首先脚注是一个汉语词汇&#xff0c;论文脚注就是附在论文页面的最底端&#xff0c;对某些内容加以说明&#xff0c;印在书页下端的注文。脚注和尾注是对文本的补充说明。 2.其次脚注一般位于页面的底部&#xff0c;可以作为文档某处内容的…

机器人系统ros2内部接口介绍

内部 ROS 接口是公共 C API &#xff0c;供创建客户端库或添加新的底层中间件的开发人员使用&#xff0c;但不适合典型 ROS 用户使用。 ROS客户端库提供大多数 ROS 用户熟悉的面向用户的API&#xff0c;并且可能采用多种编程语言。 内部API架构概述 内部接口主要有两个&#x…

tcping的安装,ping和tcping的区别

ping和tcping的区别 功能不同&#xff1a; Ping&#xff1a;Ping是一种基于ICMP协议的网络工具&#xff0c;用于测试主机之间的连通性。它发送ICMP回显请求&#xff08;Echo Request&#xff09;到目标主机&#xff0c;并等待目标主机返回ICMP回显应答&#xff08;Echo Reply…

架构师:搭建Spring Security、OAuth2和JWT 的安全认证框架

1、简述 Spring Security 是 Spring 生态系统中的一个强大的安全框架,用于实现身份验证和授权。结合 OAuth2 和 JWT 技术,可以构建一个安全可靠的认证体系,本文将介绍如何在 Spring Boot 中配置并使用这三种技术实现安全认证,并分析它们的优点。 2、Spring Security Spri…

Java 笔记 13:Java 数组内容,数组的声明、创建、初始化、赋值等,以及内存分析

一、前言 记录时间 [2024-05-03] 系列文章简摘&#xff1a; Java 笔记 01&#xff1a;Java 概述&#xff0c;MarkDown 常用语法整理 Java 笔记 02&#xff1a;Java 开发环境的搭建&#xff0c;IDEA / Notepad / JDK 安装及环境配置&#xff0c;编写第一个 Java 程序 Java 笔记 …

会声会影电影片头怎么做 会声会影电影质感调色技巧 会声会影视频制作教程 会声会影下载免费中文版

片头通常通过一系列的图像、音乐和文字等元素来引入电影的主题和氛围。通过视觉和音频的呈现方式&#xff0c;给观众留下深刻的第一印象&#xff0c;为电影的故事铺设基础。这篇文章来学习一下会声会影电影片头怎么做&#xff0c;会声会影电影质感调色技巧。 一、会声会影电影…

ASP.NET视频点播系统的设计与实现

摘 要 本文阐述了基于WEB的交互式视频点播系统的协议原理、软件结构和设计实现。本视频点播系统根据流媒体传输原理&#xff0c;在校园局域网的基础上模拟基于Web的视频点播系统&#xff0c;实现用户信息管理、视频文件的添加、删除、修改及在线播放和搜索功能。本系统是一个…

XMall-Front:基于Vue.js的XMall商城前台页面的开发实践

XMall-Front&#xff1a;基于Vue.js的XMall商城前台页面的开发实践 摘要 随着电子商务的蓬勃发展&#xff0c;用户体验逐渐成为决定电商平台成功与否的关键因素。作为XMall商城项目的一部分&#xff0c;XMall-Front是基于Vue.js的前端页面开发&#xff0c;其目标是为用户提供…

关于地盘的紧固连接技术——SunTorque智能扭矩系统

底盘作为汽车的重要组成部分&#xff0c;其材料的选择和连接技术也日益受到关注。尤其是随着新能源汽车的兴起&#xff0c;底盘轻量化已成为一种趋势。在这一背景下&#xff0c;底盘新材料与紧固连接技术的研究和应用显得尤为重要。今天SunTorque智能扭矩系统和大家一起了解地盘…

Go通过CRUD实现学生管理系统

虽然这个项目没有什么含金量&#xff0c;但是可以熟悉go的语法和go开发项目的一般流程 项目结构 项目实现了五个功能&#xff1a; &#xff08;1)增加一个学生 &#xff08;2&#xff09;删除一个学生 &#xff08;3&#xff09;修改一个学生的信息 &#xff08;4&#xf…

9.4k Star!MemGPT:伯克利大学最新开源、将LLM作为操作系统、无限上下文记忆、服务化部署自定义Agent

9.4k Star&#xff01;MemGPT&#xff1a;伯克利大学最新开源、将LLM作为操作系统、无限上下文记忆、服务化部署自定义Agent 原创 Aitrainee | 公众号&#xff1a;AI进修生&#xff1a;AI算法工程师 / Prompt工程师 / ROS机器人开发者 | 分享AI动态与算法应用资讯&#xff0c;提…

N7552A是德科技N7552A电子校准件

181/2461/8938产品概述&#xff1a; 更小巧轻便的 2 端口模块&#xff0c;支持 3.5 mm 或 N 型 50 Ω 连接器&#xff0c;能够将校准时间缩短一半 特点 频率范围&#xff1a;直流至 9 GHz 使用 N 型或 3.5 mm 连接器 更小巧轻便的 2 端口电子校准件&#xff08;ECal&#xff…

力扣刷题--数组--第一天

一、数组 数组特点&#xff1a; 连续内存空间存储得数据元素类型一致数组可以通过下标索引查找数据元素&#xff0c;可以删除、替换、添加元素等 1.1 二分查找 使用二分查找需满足得条件&#xff1a; 数组是有序的&#xff1b;数组中没有重复元素&#xff1b;查找的target…

[Docker]容器的网络类型以及云计算

目录 知识梗概 1、常用命令2 2、容器的网络类型 3、云计算 4、云计算服务的几种主要模式 知识梗概 1、常用命令2 上一篇已经学了一些常用的命令&#xff0c;这里补充两个&#xff1a; 导出镜像文件&#xff1a;[rootdocker ~]# docker save -o nginx.tar nginx:laster 导…

关于Oracle 23ai 你要知道的几件事情

1.版本生命周期 23ai发布后的Oracle版本生命周期图&#xff0c;可以看到23ai是长期支持版本可以到2032年。 引申 Oracle版本分为两类 Innovation Release--创新版本&#xff0c;一般提供至少两年技术支持 Long Term Release --长期支持版本&#xff0c;一般提供5年premier和…

护眼灯排名前十的品牌有哪些?护眼灯品牌排行前十名推荐

近视在儿童中愈发普遍&#xff0c;许多家长开始认识到&#xff0c;除了学业成绩之外&#xff0c;孩子的视力健康同样重要。毕竟&#xff0c;学业的落后可以逐渐弥补&#xff0c;而一旦孩子近视&#xff0c;眼镜便可能成为长期伴随。因此&#xff0c;专业的护眼台灯对于每个家庭…

MySQL 中的HASH详解

MySQL中的哈希索引&#xff08;Hash Index&#xff09;是一种特殊的数据库索引类型&#xff0c;它利用哈希表&#xff08;Hash Table&#xff09;的数据结构来存储索引项。哈希表通过哈希函数&#xff08;Hash Function&#xff09;将索引列的值转化为一个固定长度的哈希码&…