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

C/C++ string模拟实现

1.模拟准备

1.1因为是模拟string,防止与库发生冲突,所以需要命名空间namespace隔离一下,我们来看一下基本内容

namespace yx
{class string{private://char _buff[16];  lunix下小于16字节就存buff里char* _str;size_t _size;size_t _capacity;};
}

1.2我们这里声明和定义分离,分为string.h和string.cpp

最简单的是:把声明和定义都往命名空间里包就可以

        

2.模拟实现

2.1遇到一个类,先来写构造和析构

构造和析构

我们来看一下下面代码对不对

namespace yx
{ string::string(const char* str):_str(str){}
}

既然这么问了,那肯定是不对的,

因为你初始化string的时候,可能为常量字符串初始化的,它是不可以作为初始化对象的,当你扩容、修改就没法改了

yx::string s1("hello world");

应该这样玩:

namespace yx
{ string::string(const char* str):_str(new char[strlen(str) + 1]){}
}

我和你开一样的空间

完整写法👇 

string::string(const char* str):_str(new char[strlen(str) + 1]), _size(strlen(str)), _capacity(strlen(str))
{strcpy(_str, str);
}

但还是有点问题的,strlen的效率还是有点底的,它和sizeof不一样,sizeof是在编译时运行,根据存储规则,内存对齐规则来算,strlen是运行时算的,三个strlen就重复运算了。

我们可以把size放在初始化列表,把其他的放在函数体初始化

string::string(const char* str): _size(strlen(str))
{_str = new char[_size + 1];_capacity = _size;strcpy(_str, str);
}

这是比较传统的写法,我们来看一下同样手法

tring::string(const string& s)
{string tmp(s._str);std::swap(tmp._str, _str);std::swap(tmp._size, _size);std::swap(tmp._capacity, _capacity);
}

创建一个临时变量tmp,s1给给tmp,然后把s2指向tmp,tmp指向s2的位置.

c_str

const char* c_str() const;
const char* string::c_str() const
{return _str;
}

加const是为了让const或非const成员都能访问到,c_str()的类型为const char*,相当于常量字符串,遇到\0就会停止

无参string

我们可不可以这样写呢?

string::string()
{_str = nullptr;_size = 0;_capacity = 0;
}

看起来可以,但实际不可以,这里的delete和free是不会出问题的,因为delete是可以空指针的

我们看例子,后定义的先析构,程序崩溃了,为什么呢?

c_str ,类型为const char* 不会按指针打印,是常量字符串,就会解引用找到\0才停止。

但库里不会崩溃

为什么?

因为这个地方不是没有空间,库里面开了一个空间

所以初始化要改为

string::string()
{_str = new char[1] {'\0'};_size = 0;_capacity = 0;
}

new一个空间,而实践中不会这样写,无参的和带参的是可以和成一个的,就是全缺省

如下👇,冒号里的\0是可以不写的 默认就是\0 , 给的是字符串

namespace yx
{class string{public://string(); //无参构造  string(const char* str = "\0");~string();const char* c_str() const;//加上const,const成员和非const成员都可以调用private://char _buff[16];  lunix下小于16字节就存buff里char* _str;size_t _size;size_t _capacity;};
}

遍历:运算符重载[] 和 size()

char& string::operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}
size_t string::size() const
{return _size;
}

测试:可读可写

迭代器:begin 和 end

实现方式有两种,自定义类型的和简单点的,我们这里使用简单的

typedef char* iterator;iterator begin();
iterator end();
string::iterator string::begin()
{return _str;
}
string::iterator string::end()
{return _str + _size;
}

为什么这样写?

注:

        这里必须用typedef,为什么?迭代器体现的是一种封装

        这里的typedef相当于把char*用iterator封装起来,而且这里的迭代器一定是char*吗,在lunix下是char*吗,不同平台是不同的。

把不同类型,不同平台的类型都封装成Iterator,隐藏的底层的细节。它是一个像指针的东西。iterator的原生类型是不确定的,给了一种简单通用访问容器的方式。

无论哪个容器都重命名为iterator,各个类域也不会发生冲突。

const迭代器

定义及实现

const_iterator begin() const;
const_iterator end() const;
string::const_iterator string::begin() const
{return _str;
}

这里可以直接返回_str,_str类型为char*,begin为const char* ,相当于权限缩小。

string::const_iterator string::end() const
{return _str + _size;
}

这里也是权限缩小,下面我们测试一下

测试成功,而且是不能给常量赋值的,如下👇

如果我们用方括号加size来遍历呢?

这里会报错,为啥?因为s3是常量,所以还需要重载一个const修饰的operator[]

const char& operator[](size_t pos) const;​
const char& string::operator[](size_t pos) const
{assert(pos < _size);return _str[pos];
}

测试通过。

push_back()  、 append()  和 reserve()

完成了string的基本功能,下面我们来进行string的插入。

push_back()是尾插一个字符,append()插入一个字符串,这里我们会遇到一个问题,push_back()尾插扩容一倍或两倍,不会太大;append()是插入一个字符串,有可能需要扩容,但扩容多少呢?插入字符串长短不定,万一非常长,你扩容二倍?显然扩容多少倍是不确定的。这里我们引入reserve().

reserve()请求保留空间,一般不会缩容,如果空间比capacity大了就会扩容

void string::reserve(size_t n)
{if (n > _capacity){char* tmp = new char[n + 1];//为什么预留一个空间? 因为\0是不算在里面的strcpy(tmp,_str);delete[] _str;//释放旧空间_str = tmp;//指向新空间_capacity = n;}
}

push_back()

​
void string::push_back(char ch)//插入字符
{if (_size == _capacity)//如果size 等于 容量大小才会扩容{size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);//传给reserve,如果比当前capacity大,就扩容}_str[_size] = ch;//_size是最后一个字符的下一个位置_str[_size + 1] = '\0';++_size;
}​

满足扩容条件才扩容,_size是最后一个字符的下一个位置,也就是\0的位置,所以需要把\0也处理一下。

append()

我们来看一下下面代码正确否?

void string::append(const char* str)//插入字符串
{size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);//现在有_size个,我们需要插入长度为len的字符串}strcat(_str, str);_size += len;
}

使用了strcat(),直接在原字符串后面追加,我们测试一下

测试没问题。但是我们用strcat的时候要谨慎一些,他会从头开始找\0,效率低,这里我们采用strcpy(),从指定位置开始拷贝,带\0

void string::append(const char* str)//插入字符串
{size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);//现在有_size个,我们需要插入长度为len的字符串}strcpy(_str + _size, str);_size += len;
}

同样测试通过,但效率很高

直接从\0位置开始插入

 

operator+=

说到尾插字符串,那必须得提到+=运算符重载,这里提供俩个版本

这里引用返回,返回对象本身,出了作用域对象还存在。

string& operator+=(char ch);//+=是需要返回值的,用引用返回减少拷贝
string& operator+=(const char ch);

这里我们直接调用push_bakc和append

string& string::operator+=(char ch)
{push_back(ch);return *this;
}
string& string::operator+=(const char* str)
{append(str);return *this;
}

测试

inset() 和 erase()

size_t insert(size_t pos,char ch); //pos位插入字符
size_t insert(size_t pos,const char* ch);//插入字符串
void erase(size_t pos, size_t len = npos);//pos开始删除len个字符
private://char _buff[16];  lunix下小于16字节就存buff里char* _str;size_t _size;size_t _capacity;const static size_t npos;

对于npos,静态成员如何初始化在类里面声明,在类外定义。

类里的静态成员遍历,相当于全局变量,如果在string.h里定义,在预处理以后,string

.h会在string.cpp和test.cpp里展开,展开两份,俩文件最后生成.o文件,再一链接就出问题了,

声明和定义分离的时候把定义放在.cpp,声明放在.h。

insert()插入一个字符

size_t string::insert(size_t pos, char ch)
{if (_size == _capacity)//空间不够,就扩容{size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}size_t end = _size;while (end >= pos){_str[end + 1] = _str[end];--end;}_str[pos] = ch;++_size;
}

测试

如果我们在0出插入一个字符呢?

显然报错了,为什么?

因为edn >= 0 继续交换,把0之前的哪个未知值也交换进去了,

当end >= pos = 0 时,已将交换完了,但又进入循环了,为什么?因为当操作符两边的操作数类型不同的时候,会发生隐式类型转化,当有符号遇到无符号,有符号转换为无符号,end变为非常大的数,end依据大于pos。那有什么方法来修改呢?

我们是否可以把pos改为int类型呢?答案是不建议,因为我们要与库里的类型一样。

直接把pos强制转为int,运行测试提供。

void string::insert(size_t pos, char ch)
{if (_size == _capacity)//空间不够,就扩容{size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}size_t end = _size;while (end >= (int)pos){_str[end + 1] = _str[end];--end;}_str[pos] = ch;//把字符放进去++_size;
}

那么我们可以不强转可以修改吗?可以

这里最根本的问题点在于end >= pos,只要是无符号遇到>=绝对是坑,无符号最小就是0,无法停止,非常扯淡。

这里我们去掉= ,把end的位置变为_size + 1,把前一个往后挪。

代码修改为:

void string::insert(size_t pos, char ch)
{if (_size == _capacity)//空间不够,就扩容{size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;//把字符放进去++_size;
}

测试通过

insert()插入一个字符串

void insert(size_t pos,const char* str);//插入字符串

首先pos不能越界,检查是否需要扩容,

把xxx拷贝进去,不能用strcpy,它携带了\0,我们可以用strncpy或memcpy,这里我们采用memcpy

代码:

void string::insert(size_t pos, const char* str)
{ assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}int end = _size;while (end >= pos){_str[end + len] = _str[end];--end;}memcpy(_str + pos, str, len);_size += len;}

测试通过

这是我们在0出插入,还是会遇到之前的问题

我们只需要把pos强制类型转化为int即可。

如果我们不想用这种方式,我们把=去掉,把前一个往后挪,

到pos + len依旧需要挪动,可以写成end>=pos+len

void string::insert(size_t pos, const char* str)
{ assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}/*int end = _size;while (end >=(int) pos){_str[end + len] = _str[end];--end;}*/size_t end = _size + len;//len插入字符串长度while (end > pos + len - 1){_str[end] = _str[end - len];--end;}memcpy(_str + pos, str, len);_size += len;}

erase()

void erase(size_t pos, size_t len = npos);//pos开始删除len个字符

代码:第一种情况,删除的长度大于pos后的长度,直接全部删除;第二种情况,直接把不删除的字符覆盖在要删除的字符上。

void string::erase(size_t pos = 0, size_t len = npos)
{assert(pos < _size);if (len == npos || len >= _size - pos)//删除的字符大于pos后的字符{_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len; }}

find()

查找一个字符

size_t string::find(char ch, size_t pos = 0)
{for (size_t i = 0; i < _size; i++){if (_str[i] == ch){return i;}}return npos;
}

查找一个子串


size_t string::find(const char* sub, size_t pos = 0)//从pos位置开始查找
{const char* p = strstr(_str + pos, sub);//在_str 种匹配sub子串,,返回对应位置指针return p - _str;//指针相减获取下标
}

测试通过

运算符重载 = 

看下面代码,s1赋值拷贝给s2,s3赋值给s1,下面程序有没有问题呢?

void test5()
{yx::string s1("hello world");yx::string s2(s1);cout << s1.c_str() << endl;cout << s2.c_str() << endl;yx::string s3("yyyy");s1 = s3; // s3赋值给s1cout << s1.c_str() << endl;cout << s3.c_str() << endl;
}

当然是有问题的,当我们没有运算符重载=时,编译器会默认提供一个版本,会执行浅拷贝,当 s2 被创建为 s1 的副本时,s2 实际上可能仅仅是指向 s1 所指向的字符串常量的另一个指针。这意味着,如果改变 s2 所指向的内容,s1 的内容也会随之改变。所以需要我们实现深拷贝版本。

修正:

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

重新开一个空间tmp,把需要拷贝的字符串拷贝进去,然后释放旧空间,把new的空间给给原空间

而且为了避免自己给自己赋值,需要再套一个判断 this != &s

优化写法:

s1 = s3,s是s3的别名

	string& string::operator=(const string& s){if (this != &s){string tmp(s._str);this->swap(tmp);}return *this;}

s3拷贝给s1,设置一个临时变量tmp,s3拷贝构造tmp,然后再交换s1和tmp的空间。

而且tmp是个局部对象,出了作用域会析构,免去了释放空间这一步

最简化的写法:

s1 = s3

string& operator=(string tmp);string& string::operator=(string tmp)
{swap(tmp);return *this;
}

和上面最大的区别在于没有引用,s3拷贝构造tmp,然后s1和tmp交换,和上面的道理一样,tmp是临时对象,出了作用域自动销毁,效率并没有提示,只是简化了代码

swap()

如果我们swap一下s1 和 s2呢?库里的swap能否帮我们完成任务呢?答案是可以的,因为他是模板,但是它的代价非常大,

a 拷贝给 c,b 赋值给 a,c赋值给b,都会开空间,拷贝数据,传值传参代价都非常大。

自己实现:

void string::swap(string& s)
{std::swap(_str, s._str);//交换char*指针,size capacitystd::swap(_size, s._size);std::swap(_capacity, s._capacity);
}

通过命名空间,来使用库里的swap,如果不写std,swap就会调用自己,而且这里面交换的都是内置类型

strsub()

从pos位置开始,取len个字符的子串

代码实现:

string string::substr(size_t pos, size_t len)
{if (len > _size - pos)//如果len大于pos后面的长度,有多少取多少{string sub(_str + pos);//_str(开始的指针 ) + pos 从pos位置开始取,然后构造一个子串返回return sub;}else{string sub;sub.reserve(len);//扩容for (size_t i = 0;i< len ;i++){sub += _str[pos + i];}return sub;}
}

运算符重载 < > <= >= == !=

bool string::operator<(const string& s) const //比较大小
{return strcmp(_str, s._str) < 0;
}
bool string::operator>(const string& s) const
{return !(*this <= s);
}
bool string::operator<=(const string& s) const
{return *this < s || *this == s;
}
bool string::operator>=(const string& s) const
{return !(*this < s);
}
bool string::operator==(const string& s) const
{return strcmp(_str, s._str);
}
bool string::operator!=(const string& s) const
{!(*this == s);
}

其实这里写两个,然后取反复用就可以。

流插入和流提取operator<< >> 

istream& operator>>(istream& is, string& str)// is - cin
{char ch = is.get();is >> ch;while (ch != ' ' || ch != '\n')//不等于空格或换行{str += ch;//在io流里提取一个一个的char += 到str里char ch = is.get();}return is;
}
ostream& operator<<(ostream& os, const string& str)//os - cout
{for (size_t i = 0; i < str.size(); i++){os << str[i];//一个一个插入}return os;
}

流插入和流提取的运算符重载不能写成成员函数,且不一定写成友元。

cin是不能获取空格的,而scanf可以 当输入 y 空格 y 时, 写两个cin ,两个cin都会获得y,而忽略空格。

clear()

void string::clear() //请调当前的数据
{_str[0] = '\0';_size = 0;
}

相关文章:

C/C++ string模拟实现

1.模拟准备 1.1因为是模拟string&#xff0c;防止与库发生冲突&#xff0c;所以需要命名空间namespace隔离一下&#xff0c;我们来看一下基本内容 namespace yx {class string{private://char _buff[16]; lunix下小于16字节就存buff里char* _str;size_t _size;size_t _capac…...

微信小程序学习(八):behaviors代码复用

小程序的 behaviors 方法是一种代码复用的方式&#xff0c;可以将一些通用的逻辑和方法提取出来&#xff0c;然后在多个组件中复用&#xff0c;从而减少代码冗余&#xff0c;提高代码的可维护性。 如果需要 behavior 复用代码&#xff0c;需要使用 Behavior() 方法&#xff0c…...

【The design pattern of Attribute-Based Dynamic Routing Pattern (ADRP)】

In ASP.NET Core, routing is one of the core functionalities that maps HTTP requests to the corresponding controller actions. While “Route-Driven Design Pattern” is a coined name for a design pattern, we can construct a routing-centric design pattern base…...

2713. 矩阵中严格递增的单元格数

题目 给定一个 m x n 的整数矩阵 mat&#xff0c;我们需要找出从某个单元格出发可以访问的最大单元格数量。移动规则是可以从当前单元格移动到同一行或同一列的任何其他单元格&#xff0c;但目标单元格的值必须严格大于当前单元格的值。需要返回最大可访问的单元格数量。 示例…...

git创建子模块

有种情况我们经常会遇到&#xff1a;某个工作中的项目需要包含并使用另一个项目。 也许是第三方库&#xff0c;或者你独立开发的&#xff0c;用于多个父项目的库。 现在问题来了&#xff1a;你想要把它们当做两个独立的项目&#xff0c;同时又想在一个项目中使用另一个。 Git …...

把Deepin塞进U盘,即插即用!Deepin To Go来袭

前言 小伙伴之前在某篇文章下留言说&#xff1a;把Deepin塞进U盘的教程。 这不就来了吗&#xff1f; 事实是可以的。这时候你要先做点小准备&#xff1a; 一个大小为8GB或以上的普通U盘 一个至少64GB或以上的高速U盘 一个Deepin系统镜像文件 普通U盘的大概介绍&#xff1…...

​​给【AI硬件】创业者的论文、开源项目和产品整理

一、AI 硬件精选论文 《DrEureka: Language Model Guided Sim-To-Real Transfer》 瑜伽球上遛「狗」这项研究由宾夕法尼亚大学、 NVIDIA 、得克萨斯大学奥斯汀分校的研究者联合打造&#xff0c;并且完全开源。他们提出了 DrEureka&#xff08;域随机化 Eureka&#xff09;&am…...

模拟面试题卷二

1. 什么是JavaEE框架&#xff0c;你能列举一些常用的JavaEE框架吗&#xff1f; 答&#xff1a;JavaEE框架是一套用于开发企业级应用的技术规范和工具集合。常用的JavaEE框架有Spring、Hibernate、Struts、JSF等。 2. 请解释一下面向对象技术和设计原则是什么&#xff0c;你能…...

22种常用设计模式示例代码

文章目录 创建型模式结构型模式行为模式 仓库地址https://github.com/Xiamu-ssr/DesignPatternsPractice 参考教程 refactoringguru设计模式-目录 创建型模式 软件包复杂度流行度工厂方法factorymethod❄️⭐️⭐️⭐️抽象工厂abstractfactory❄️❄️⭐️⭐️⭐️生成器bui…...

Java面试题:对比ArrayList和LinkedList的内部实现,以及它们在不同场景下的适用性

ArrayList和LinkedList是Java中常用的两个List实现&#xff0c;它们在内部实现和适用场景上有很大差异。下面是详细的对比分析&#xff1a; 内部实现 ArrayList 数据结构&#xff1a;内部使用动态数组&#xff08;即一个可变长的数组&#xff09;实现。存储方式&#xff1a;…...

ping: www.baidu.com: 未知的名称或服务(IP号不匹配)

我用的是VMware上的Red Hat Enterprise Linux 9&#xff0c;出现了能联网但ping不通外网的情况。 问题描述&#xff1a;设置中显示正常连接&#xff0c;而且虚拟机右上角有联网的图标&#xff0c;但不能通外网。 按照网上教程修改了/etc/resolv.conf和/etc/sysconfig/network-…...

谷神前端组件增强:子列表

谷神Ag-Grid导出Excel // 谷神Ag-Grid导出Excel let allDiscolumns detailTable.getAllDisColumns() let columnColIds columns.map(column > column.colId) let columnKeys columnColIds.filter(item > ![select, "_OPT_FIELD_"].includes(item)) detailT…...

测试cudaStream队列的深度

测试cudaStream队列的深度 一.代码二.编译运行[得出队列深度为512] 以下代码片段用于测试cudaStream队列的深度 方法: 主线程一直发任务,启一个线程cudaEventQuery查询已完成的任务,二个计数器的值相减 一.代码 #include <iostream> #include <thread> #include …...

​海康威视 isecure center 综合安防管理平台任意文件上传漏洞

文章目录 前言声明一、漏洞描述二、影响版本三、漏洞复现四、修复方案 前言 海康威视是以视频为核心的智能物联网解决方案和大数据服务提供商,业务聚焦于综合安防、大数据服务和智慧业务。 海康威视其产品包括摄像机、多屏控制器、交通产品、传输产品、存储产品、门禁产品、消…...

shadertoy-安装和使用

一、安装vscode 安装vscode流程 二、安装插件 1.安装glsl编辑插件 2.安装shader toy插件 三、创建glsl文件 test.glsl文件 float Grid(float size, vec2 fragCoord) {vec2 r fragCoord / size;vec2 grid abs(fract(r - 0.5) - 0.5) / fwidth(r);float line min(grid…...

matlab线性多部法求常微分方程数值解

用Adamas内差二步方法&#xff0c;内差三步方法&#xff0c;外差二步方法&#xff0c;外差三步方法这四种方法计算。 中k为1和2. k为2和3 代码 function chap1_adams_methodu0 1; T 2; h 0.1; N T/h; t 0:h:T; solu exact1(t);f f1; u_inter_2s adams_inter_2steps(…...

前端页面实现【矩阵表格与列表】

实现页面&#xff1a; 1.动态表绘制&#xff08;可用于矩阵构建&#xff09; <template><div><h4><b>基于层次分析法的权重计算</b></h4><table table-layout"fixed"><thead><tr><th v-for"(_, colI…...

GPT4v和Gemini-Pro调用对比

要调用 GPT-4 Vision (GPT-4V) 和 Gemini-Pro&#xff0c;以下是详细的步骤分析&#xff0c;包括调用流程、API 使用方法和两者之间的区别&#xff0c;以及效果对比和示例。 GPT-4 Vision (GPT-4V) 调用步骤 GPT-4 Vision 主要通过 OpenAI 的 API 进行调用&#xff0c;用于处…...

破布叶(Microcos paniculata)单倍型染色体级别基因组-文献精读22

Haplotype-resolved chromosomal-level genome assembly of Buzhaye (Microcos paniculata) 破布叶、布渣叶&#xff08;Microcos paniculata&#xff09;单倍型解析染色体级别基因组组装 摘要 布渣叶&#xff08;Microcos paniculata&#xff09;是一种传统上用作民间药物和…...

浅谈RC4

一、什么叫RC4&#xff1f;优点和缺点 RC4是对称密码&#xff08;加密解密使用同一个密钥&#xff09;算法中的流密码&#xff08;一个字节一个字节的进行加密&#xff09;加密算法。 优点&#xff1a;简单、灵活、作用范围广&#xff0c;速度快 缺点&#xff1a;安全性能较差&…...

uniapp微信小程序开发物料

开发工具 HBuilder&#xff1a; HBuilderX-高效极客技巧 vscode 1、在vscode中新建一个项目npx degit dcloudio/uni-preset-vue#vite-ts 项目名称 2、在HBuilder中可以可视化进行新建项目 路由 在app.json文件中配置pages路由路径 路由跳转方法 uni.navigateTo(OBJECT)…...

大数据工程师如何做到数据可视化?

好的数据可视化作品都是通过不断的数据对比分析实战出来的。 今天给大家带来一篇大数据工程师干货&#xff0c;从多角度解析做数据可视化的重要性&#xff0c;并解读一些适用的应用场景。大数据工程师们刷到这篇文章时一定要进来看看&#xff0c;满满的干货。 目录 1. 什么是数…...

Java 序列化与反序列化

Java 序列化是一种将对象的状态转换为字节流的机制&#xff0c;以便可以将该对象的状态保存到文件、数据库或通过网络传输。在反序列化过程中&#xff0c;这些字节流可以被重新转换为对象。序列化主要用于以下几种情况&#xff1a; 持久化存储&#xff1a;将对象的状态保存到文…...

自定义防抖注解

问题场景 在开发中由于可能存在的网络波动问题导致用户重复提交&#xff0c;所以自定义一个防抖注解。设计思路&#xff1a;自定义注解加在接口的方法上&#xff0c;注解中设置了SPEL表达式&#xff0c;可以通过SPEL表达式从接口参数中提取Redis的Key&#xff0c;以这个Key作为…...

【尚庭公寓SpringBoot + Vue 项目实战】登录管理(十八)

【尚庭公寓SpringBoot Vue 项目实战】登录管理&#xff08;十八&#xff09; 文章目录 【尚庭公寓SpringBoot Vue 项目实战】登录管理&#xff08;十八&#xff09;1、登录业务介绍2、接口开发2.1、获取图形验证码2.2、登录接口2.3、获取登录用户个人信息 1、登录业务介绍 登…...

【html】用html+css做地表最强王者荣耀辅助工具

源码&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title><style>* {margin: 0;padding: 0;}body{background-color: blue;}.con {width: 300px;height: 500px;background-color: rgba(230,…...

TF-IDF、BM25传统算法总结

1. TF-IDF算法 F-IDF&#xff08;词频-逆文档频率&#xff09;是一种用于衡量文本中词语重要性的方法&#xff0c;特别适用于信息检索和文本挖掘任务。下面会拆分为两部分深入讲解TF-IDF的计算过程&#xff0c;以便更好地理解。 TF-IDF的计算过程可以分为两个主要部分&#xf…...

项目五 OpenStack镜像管理与制作

任务一 理解OpenStack镜像服务 1.1 •什么是镜像 • 镜像通常 是指一系列文件或一个磁盘驱动器的精确副本 。 • 虚拟机 所使用的虚拟磁盘&#xff0c; 实际上是 一种特殊格式的镜像文件 。 • 云 环境下尤其需要 镜像。 • 镜像 就是一个模板&#xff0c;类似于 VMware 的虚拟…...

LabVIEW回热系统热经济性分析及故障诊断

开发了一种利用LabVIEW软件的电厂回热系统热经济性分析和故障诊断系统。该系统针对火电厂回热加热器进行优化&#xff0c;通过实时数据监控与分析&#xff0c;有效提高机组的经济性和安全性&#xff0c;同时降低能耗和维护成本。系统的实施大幅提升了火电厂运行的效率和可靠性&…...

设计模式-迭代器模式

目录 一:基本介绍 二:原理说明 三:案例说明 四:优点 五:缺点 一:基本介绍 1)属于行为模式 2)如果我们的集合元素是用不同的方式实现的,有数组,还有java的集合类,或者还有其他方式,当客户 端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以…...

什么是网站分析/seo

Powered by:NEFU AB-IN Link 文章目录1275. 最大数题意思路代码1275. 最大数 题意 给定一个正整数数列 a1,a2,…,an&#xff0c;每一个数都在 0∼p−1之间。 可以对这列数进行两种操作&#xff1a; 添加操作&#xff1a;向序列后添加一个数&#xff0c;序列长度变成 n1&#x…...

美妆网站怎么做/网络销售新手入门

本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理以下文章来源于腾讯云 作者&#xff1a;keinYe( 想要学习Python&#xff1f;Python学习交流群&#xff1a;1039649593&#xff0c;满足你的需求&#xff0c…...

哪些网站开发/友情链接交换平台源码

桔妹导读&#xff1a;随着计算机技术和工程架构的发展&#xff0c;微服务变得越来越热。如今&#xff0c;绝大多数服务都处于分布式环境中&#xff0c;其中&#xff0c;数据一致性是我们一直关注的重点。分布式锁到底是什么&#xff1f;经过了哪些发展演进&#xff1f;工程上有…...

有没有个人做的网站赚流量费/产品宣传推广方案

初次使用anaconda安装tensorflow的心路历程 anaconda已安装完毕 第一次尝试&#xff1a; 直接在base中安装tensorflow&#xff0c;conda install tensorflow,等待数分钟后无果&#xff0c;报错&#xff08;具体错误未记录&#xff09;。 查找原因&#xff1a; 安装anacond…...

对于网站反爬虫如何做/电脑优化系统的软件哪个好

有时候我们在使用pycharm进行python编程的时候&#xff0c;遇到了no python interpreter for the project&#xff0c;怎么解决呢&#xff0c;下面来分享一下方法 工具/原料 pycharm no python interpreter for the project解决方法 方法/步骤 1 第一步我们在使用pycharm进行py…...

巩义网站建设方案表/ui设计公司

本次项目主要需要改进的方面&#xff1a; 1.界面 界面的优化是其中的一大项&#xff0c;群众和使用者第一眼看到的是软件的界面&#xff0c;能不能让大家感觉到眼前一亮的感觉是十分重要的&#xff0c;我们在界面上没有什么经验&#xff0c;不知道通过怎样的方式去进行修改&…...