【C++精华铺】10.STL string模拟实现

1. 序言

        STL(标准模板库)是一个C++标准库,其中包括一些通用的算法、容器和函数对象。STL的容器是C++ STL库的重要组成部分,它们提供了一种方便的方式来管理同类型的对象。其中,STLstring是一种常用的字符串类型。

        STLstring是一个类,它封装了字符串的操作,并提供了一组成员函数。STLstring的实现使用了动态的内存分配技术,这意味着字符串的大小可以随时改变。STLstring还提供了一些高效的成员函数,例如substr、find、replace等,这些函数可以对字符串进行快速的操作。

        STLstring的实现主要基于字符数组。字符数组是一种固定大小的数组,其中每个元素包含一个字符。STLstring使用一个字符数组来存储字符串,并通过动态的内存分配技术来管理数组的大小。当向一个空的STLstring对象中添加字符时,STLstring会自动调整数组的大小。

        STLstring还实现了一些常见的字符串操作,例如连接字符串、查找字符串、替换字符串和分割字符串等。这些操作使用了C++ STL库中的algorithm算法,可以高效地处理字符串。同时,STLstring也提供了迭代器的支持,允许用户使用STL算法来处理字符串。

2. string类的接口实现

        在实现接口之前先要给出我们的初始类,包括三个私有成员(_size,_capacity,_str),接下来我们会对这个初始类一步一步的完善(为了文章易读后续接口函数不会展示类的全貌,只会展示实现的接口函数内容,在文章的末尾会给出完整的string类代码)如下:

namespace zybjs
{class string{public:private:size_t _size;size_t _capacity;char* _str;};
}

2.1 构造函数

(1)默认构造和C串构造

        在STL库中将C串构造和默认构造分开实现,但是我们在模拟实现string类的时候可以将这俩个构造函数合并,这样的代码会更简洁,也方便我们自己的使用。在此之前我们先按库里面的思路走一趟:在我们给初始容量的时候即便字符串长度是0我们也不能给0,避免后续无法倍数扩容,这里我们给的是4。并且给字符串开空间的时候要比容量多一个,最后一个留给'\0'。(VS下实现思路不同,VS下给了一个16字节的字符数组_buf,如果要存储的字符串小于16字节就存放在字符数组_buf中,否则就重新开一个空间)

default (1)            string();

from c - string(2)  string(const char* s);

string():_size(0),_capacity(4)  //不能给0,如果给0后续无法倍数扩容 下同
{_str = new char[_capacity + 1]; //开空间的时候要比容量多一个字节留给'\0',因为容量的大小不算'\0',下同_str[0] = '\0';
}
string(const char* str)  //const类型接收右值:_size(strlen(str))
{_capacity = _size == 0 ? 4 : _size;_str = new char[_capacity + 1];strcpy(_str, str);
}

         但是我们在自己实现的时候大可不必这样去写,我们之前学习了缺省参数,并且全缺省的构造函数也可以作为默认构造,所以我们可以对上述代码进行优化,给str一个空串作为缺省参数,这样当我们调用的时候不给实参就会默认使用空串进行构造来完成库中”string();“的功能。如下:

string(const char* str = "")   //const类型接收右值:_size(strlen(str))
{_capacity = _size == 0 ? 4 : _size;_str = new char[_capacity + 1];strcpy(_str,str);  
}

(2)拷贝构造

        string类的拷贝构造很简单,要注意的点有俩个:其一,string类涉及到空间管理,所以在拷贝的时候要深拷贝,否则会导致同一空间多次析构导致报错;其二,传参不能传值传参,要使用传引用传参,否则会导致无穷递归,

string(const string& s):_size(s._size),_capacity(s._capacity)
{      //深拷贝_str = new char[_capacity + 1];  //重新开空间strcpy(_str,s._str);     //字符序列拷贝
}

2.2 析构函数

        string类因为涉及到内存的管理,所以析构函数不能使用默认生成的析构,需要我们自己去实现析构。在实现析构的时候将_size和_capacity全部置零,然后通过delete[]释放_str就可以了。如下:

~string()
{delete[] _str;   //释放_str_str = nullptr;_size = _capacity = 0;
}

2.3 size()、capacity()

        size()和capacity()很多人在实现的时候都觉得很简单反而会忽略一个点:就是const对象访问的时候是否会权限放大。因为这个俩个函数仅涉及到读取,没有修改的操作,所以我们只需要实现const版本来兼容const对象和非const对象。如下

size_t size() const  //兼容const对象和非const对象
{return _size;
}
size_t capacity() const//兼容const对象和非const对象
{return _capacity;
}

2.4 c_str()

        返回指向一个数组的指针,该数组包含以 null 结尾的字符序列(即 C 字符串),该序列表示字符串对象的当前值。只需完成const版本来兼容const对象和非const对象。指针的返回值也需要是const char *类型,防止通过指针对对象进行修改。

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

2.5 operator[]

        因为要实现类似于数组的访问方式,所以我们要实现[]的重载形式。[]的特性:能够随机访问元素,对于非const对象能够修改元素。所以operator[]要实现const版本和非const版本,const版本的返回值必须是const引用。

const char& operator[](size_t pos) const
{assert(pos < _size&& pos >= 0);return _str[pos];
}
char& operator[](size_t pos)
{assert(pos < _size && pos >= 0);return _str[pos];
}

2.6 operator=

        赋值运算符的重载是对字符串对象的深拷贝,和拷贝构造的过程相同,但是‘=’的特性支持连续的赋值,所以我们在实现赋值重载的时候需要返回一个string对象来支持赋值重载的连续赋值。因为我们不涉及对传入参数的修改,所以我们需要传入一个const string& 类型。注意在赋值之前需要释放原来的空间。如下:

string& operator=(const string& s)
{if (s._str != _str){_size = s._size;_capacity = s._capacity;//深拷贝char* _tmp = new char[_capacity + 1]; strcpy(_tmp,s._str);//先释放原来的空间delete[] _str;_str = _tmp;}return *this;
}

测试:

zybjs::string s1("cacaca");
zybjs::string s2("dadada");
zybjs::string s3("bababa");
s3 = s2 = s1;
std::cout << s1.c_str() << std::endl;
std::cout << s2.c_str() << std::endl;
std::cout << s3.c_str() << std::endl;

2.7 字符串比较 

        字符串比较我们通过strcmp来进行比较(strcmp(srt1,str2)),返回的值为0,字符串相同;返回的值大于0,str1>str2;返回的值小于0,str1<str2。基于此便可以实现字符串的比较函数。

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

2.8 迭代器实现

        string的迭代器底层是一个指针,string类的迭代器是一种用于访问字符串中字符的对象,可以通过迭代器的运算符访问字符串中的字符。迭代器为C++容器提供了一种通用的访问手段。

typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{//指向第一个字符的位置return _str;
}
iterator end()
{//指向最后一个字符的后一个位置return _str + _size;
}const_iterator begin() const
{return _str;
}
const_iterator end() const
{return _str + _size;
}

2.9 reserve

        reserve()是为了给对象预留空间,如果我们提前得知字符串需要的空间我们就可以提前开好,避免频繁扩容带来的性能消耗。当reserve的参数小于string底层空间大小的时候,reserve就不会对容量进行处理。

void reserve(size_t n)
{if (n > _capacity){char* _tmp = new char[n+1];strcpy(_tmp, _str);delete[] _str;_str = _tmp;_capacity = n;}
}

2.10 resize

          修改字符有效个数为n,多出的空间用字符ch填充。如果n小于有效字符数,本质就是删字符操作。如果容量不够会扩容。

void resize(size_t n, char ch = '\0')
{//当n<有效字符数的时候本质上就是删字符//但是我们一般不会进行缩容,在原来的空间上将n位置的字符设置为'\0'if (n < _size){_size = n;_str[_size] = '\0';}else if (n > _size){if (n > _capacity)  //n>_capacity就进行扩容{reserve(n);        }size_t i = _size;while (i < n)       //将非有效字符初始化为ch{_str[i] = ch;i++;}_size = n;_str[_size] = '\0'; //设置终止位}
}

2.11 push_back、append、operator+=

        push_back尾插,append是在字符串后面追加一个字符串,实现比较简单,但要注意检查容量。

void push_back(char ch)
{if (_size + 1 > _capacity){reserve(2 * _capacity);}_str[_size] = ch;_size++;_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;
}

        operator+=的功能可以由push_back和append的复用来实现:

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

2.12 insert

        insert是string类中支持pos位插入字符或者字符串的函数。其中,pos表示插入的位置,返回string的引用表示插入后的新字符串。

string& insert(size_t pos, char ch)
{if (_size + 1 > _capacity){reserve(2 * _capacity);}size_t n = pos;size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size += 1;_str[_size] = '\0';return *this;
}string& insert(size_t pos, const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){reserve(_capacity + len);}size_t n = pos;size_t end = _size + 1;while (end > pos){_str[end - 1 + len] = _str[end - 1];end--;}strncpy(_str + pos, str, len);_size += len;_str[_size] = '\0';return *this;
}

2.13 erase

        C++中的string类提供了一个名为erase()的成员函数,用于删除字符串中的一部分字符并修改该字符串。该函数可以接受1个或2个参数,具体取决于要删除的字符数。如果没有显式的指定删除字符数,会使用默认的npos也就是无符号整型-1来作为缺省参数(65535),就会默认删除pos位后面所有的字符。然后返回删除字符后生成的新字符。

        首先我们要定义一个和库里相同的npos:

string& erase(size_t pos,size_t len = npos)
{assert(pos < _size);if (len >= _size - pos - 1){_size = pos;_str[_size] = '\0';}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;
}

2.14 流插入和流提取

        

	std::ostream& operator<<(std::ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}std::istream& operator>>(std::istream& in, string& s){s.clear();        //输入之前要清空字符串char ch = in.get();//获取字符包括'\n'char buff[32];  //设置缓冲区来防止频繁扩容size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i] = ch;if (i == 30){buff[31] = '\0';s += buff;i = 0;}i++;ch = in.get();}buff[i] = '\0';s += buff;}

3. 完整代码(均调试通过)

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<cassert>
namespace zybjs
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){//指向第一个字符的位置return _str;}iterator end(){//指向最后一个字符的后一个位置return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}//string()//	:_size(0)//	,_capacity(4)  //不能给0,如果给0后续无法倍数扩容 下同//{//	_str = new char[_capacity + 1]; //开空间的时候要比容量多一个字节留给'\0',因为容量的大小不算'\0',下同//	_str[0] = '\0';//}//string(const char* str)//	:_size(strlen(str))//{//	_capacity = _size == 0 ? 4 : _size;//	_str = new char[_capacity + 1];//	strcpy(_str, str);//}string(const char* str = "")   //const类型接收右值:_size(strlen(str)){_capacity = _size == 0 ? 4 : _size;_str = new char[_capacity + 1];strcpy(_str,str);  }string(const string& s):_size(s._size),_capacity(s._capacity){      //深拷贝_str = new char[_capacity + 1];  //重新开空间strcpy(_str,s._str);     //字符序列拷贝}~string(){delete[] _str;   //释放_str_str = nullptr;_size = _capacity = 0;}size_t size() const  //兼容const对象和非const对象{return _size;}size_t capacity() const//兼容const对象和非const对象{return _capacity;}const char* c_str() const {return _str;}const char& operator[](size_t pos) const{assert(pos < _size&& pos >= 0);return _str[pos];}char& operator[](size_t pos){assert(pos < _size && pos >= 0);return _str[pos];}string& operator=(const string& s){if (s._str != _str){_size = s._size;_capacity = s._capacity;//深拷贝char* _tmp = new char[_capacity + 1]; strcpy(_tmp,s._str);//先释放原来的空间delete[] _str;_str = _tmp;}return *this;}//字符串比较bool 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'){//当n<有效字符数的时候本质上就是删字符//但是我们一般不会进行缩容,在原来的空间上将n位置的字符设置为'\0'if (n < _size){_size = n;_str[_size] = '\0';}else if (n > _size){if (n > _capacity)  //n>_capacity就进行扩容{reserve(n);        }size_t i = _size;while (i < n)       //将非有效字符初始化为ch{_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++;_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){if (_size + 1 > _capacity){reserve(2 * _capacity);}size_t n = pos;size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size += 1;_str[_size] = '\0';return *this;}string& insert(size_t pos, const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_capacity + len);}size_t n = pos;size_t end = _size + 1;while (end > pos){_str[end - 1 + len] = _str[end - 1];end--;}strncpy(_str + pos, str, len);_size += len;_str[_size] = '\0';return *this;}string& erase(size_t pos,size_t len = npos){assert(pos < _size);if (len >= _size - pos - 1){_size = pos;_str[_size] = '\0';}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}void clear(){_size = 0;_str[_size] = '\0';}private:size_t _size;size_t _capacity;char* _str;static const size_t npos;};const size_t string::npos = -1;std::ostream& operator<<(std::ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}std::istream& operator>>(std::istream& in, string& s){s.clear();        //输入之前要清空字符串char ch = in.get();//获取字符包括'\n'char buff[32];  //设置缓冲区来防止频繁扩容size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i] = ch;if (i == 30){buff[31] = '\0';s += buff;i = 0;}i++;ch = in.get();}buff[i] = '\0';s += buff;}
}

 

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

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

相关文章

既然有 HTTP 协议,为什么还要有 RPC

HTTP和RPC 什么是HTTP HTTP协议&#xff08;Hyper Text Transfer Protocol&#xff09;&#xff0c;又叫做超文本传输协议。平时上网在浏览器上敲个网址就能访问网页&#xff0c;这里用到的就是HTTP协议。 什么是RPC RPC&#xff08;Remote Procedure Call&#xff09;&…

VLAN间路由:单臂路由与三层交换

文章目录 一、定义二、实现方式单臂路由三层交换 三、单臂路由与三层路由优缺点对比四、常用命令 首先可以看下思维导图&#xff0c;以便更好的理解接下来的内容。 一、定义 VLAN间路由是一种网络配置方法&#xff0c;旨在实现不同虚拟局域网&#xff08;VLAN&#xff09;之…

ssprompt:一个LLM Prompt分发管理工具

阅读顺序 &#x1f31f;前言&#x1f514;ssprompt介绍命令介绍Metafile介绍版本依赖规则 &#x1f30a; PromptHubGitHub Token &#x1f680; Quick Install系统依赖pip安装Linux, macOS, Windows (WSL)Windows (Powershell) &#x1f6a9; Roadmap&#x1f30f; 项目交流讨论…

Android手机防沉迷软件的基本原理

(现在手机游戏、短视频等不仅对小孩子负面影响巨大&#xff0c;连很多成年人都沉迷其中难以自拔&#xff0c;影响工作、生活、学习。这已经造成全社会性的巨大影响&#xff0c;长此以往&#xff0c;国将不国。本人仅在此以自己掌握的些许技术略尽绵薄之力&#xff0c;希望能抛砖…

一、了解[mysql]索引底层结构和算法

目录 一、索引1.索引的本质2.mysql的索引结构 二、存储引擎1.MyISAM2.InnoDB3.为什么建议InnoDB表要建立主键并且推荐int类型自增&#xff1f;4.innodb的主键索引和非主键索引&#xff08;二级索引&#xff09;区别5.联合索引 一、索引 1.索引的本质 索引:帮助mysql高效获取数…

ClickHouse 存算分离改造:小红书自研云原生数据仓库实践

ClickHouse 作为业界性能最强大的 OLAP 系统&#xff0c;在小红书内部被广泛应用于广告、社区、直播和电商等多个业务领域。然而&#xff0c;原生 ClickHouse 的 MPP 架构在运维成本、弹性扩展和故障恢复方面存在较大局限性。为应对挑战&#xff0c;小红书数据流团队基于开源 C…

Vue + Element UI 前端篇(八):管理应用状态

使用 Vuex 管理应用状态 1. 引入背景 像先前我们是有导航菜单栏收缩和展开功能的&#xff0c;但是因为组件封装的原因&#xff0c;隐藏按钮在头部组件&#xff0c;而导航菜单在导航菜单组件&#xff0c;这样就涉及到了组件收缩状态的共享问题。收缩展开按钮触发收缩状态的修改…

MT9700 80mΩ,可调快速响应限流配电开关芯片

MT9700 80mΩ&#xff0c;可调快速响应限流配电开关芯片 特征 符合USB规范 集成80mΩ电源MOSFET 低电源电流 15μA典型开启状态 1μA典型关闭状态 宽输入电压Range&#xff1a;2.4V到5.5V 快速瞬态响应&#xff1a;<2μs 反向电流流阻塞 热关机保护 热插件应…

JAVA 比较两个区间是否存在交集

最近遇到一个开发问题&#xff0c;判断两个价格的大小&#xff0c;听着很简单&#xff0c;但其实价格是浮动的&#xff0c;也就是说价格是一个范围&#xff0c;比如物品A的价格是5&#xff5e;10&#xff0c;现在我们通过筛选条件&#xff0c;把价格符合在8&#xff5e;20之前的…

2023 年高教社杯全国大学生数学建模竞赛题目 A 题 定日镜场的优化设计

A 题 定日镜场的优化设计 构建以新能源为主体的新型电力系统&#xff0c;是我国实现“碳达峰”“碳中和”目标的一项重要措施。塔式太阳能光热发电是一种低碳环保的新型清洁能源技术[1]。 定日镜是塔式太阳能光热发电站&#xff08;以下简称塔式电站&#xff09;收集太阳能的基…

【力扣周赛】第 357 场周赛(⭐反悔贪心)

文章目录 竞赛链接Q1&#xff1a;6925. 故障键盘解法1——直接模拟解法2——双端队列 Q2&#xff1a;6953. 判断是否能拆分数组&#xff08;贪心&#xff09;Q3&#xff1a;2812. 找出最安全路径⭐解法1——多源BFS瓶颈路模型&#xff1f;解法2——多源BFS 倒序枚举答案 并查…

Java 基于SpringBoot+Vue的社区医院管理系统的实现

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,Csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 1、效果演示2、 前言介绍3. 技术栈4系统设计4.1数据库设计4.2系统整体设计4.2.1 系统设计思想4.2.…

Win10如何找回图片查看器

近期有小伙伴反映在将Win10升级之后发现电脑自带的图片查看器没有了&#xff0c;这是怎么回事&#xff0c;该怎么找回呢&#xff0c;下面小编就给大家详细介绍一下Win10找回图片查看器的方法&#xff0c;有需要的小伙伴快来和小编一起阅读看看吧。 win10找回windows照片查看器…

降噪音频转录 Krisp: v1.40.7 Crack

主打人工智能降噪服务的初创公司「Krisp」近期宣布推出音频转录功能&#xff0c;能对电话和视频会议进行实时设备转录。该软件还整合的ChatGPT&#xff0c;以便快速总结内容&#xff0c;开放测试版于今天上线。 随着线上会议越来越频繁&#xff0c;会议转录已成为团队工作的重…

Python 实现单例模式的五种写法!

单例模式&#xff08;Singleton Pattern&#xff09; 是一种常用的软件设计模式&#xff0c;该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中&#xff0c;某个类只能出现一个实例时&#xff0c;单例对象就能派上用场。 比如&#xff0c;某个服务器程序的…

数据集学习笔记(六):目标检测和图像分割标注软件介绍和使用,并转换成YOLO系列可使用的数据集格式

文章目录 一、目标检测1.1 labelImg1.2 介绍1.3 安装1.4 使用1.5 转换1.6 验证 二、图像分割2.1 labelme2.2 介绍2.3 安装2.4 使用2.5 转换2.6 验证 一、目标检测 1.1 labelImg 1.2 介绍 labelImg是一个开源的图像标注工具&#xff0c;用于创建图像标注数据集。它提供了一个…

OSI与TCP IP各层的结构与功能,都有哪些协议

分析&回答 OSI七层模型 层功能TCP/IP协议族应用层文件传输&#xff0c;电子邮件&#xff0c;文件服务&#xff0c;虚拟终端TFTP&#xff0c;HTTP&#xff0c;SNMP&#xff0c;FTP&#xff0c;SMTP&#xff0c;DNS&#xff0c;Telnet表示层数据格式化&#xff0c;代码转换…

数学建模--Seaborn库绘图基础的Python实现

目录 1.绘图数据导入 2. sns.scatterplot绘制散点图 3.sns.barplot绘制条形图 4.sns.lineplot绘制线性图 5.sns.heatmap绘制热力图 6.sns.distplot绘制直方图 7.sns.pairplot绘制散图 8.sns.catplot绘制直方图 9.sns.countplot绘制直方图 10.sns.lmplot绘回归图 1.绘图数…

在外SSH远程连接macOS服务器【cpolar内网穿透】

文章目录 前言1. macOS打开远程登录2. 局域网内测试ssh远程3. 公网ssh远程连接macOS3.1 macOS安装配置cpolar3.2 获取ssh隧道公网地址3.3 测试公网ssh远程连接macOS 4. 配置公网固定TCP地址4.1 保留一个固定TCP端口地址4.2 配置固定TCP端口地址 5. 使用固定TCP端口地址ssh远程 …

2023高教社杯 国赛数学建模C题思路 - 蔬菜类商品的自动定价与补货决策

1 赛题 在生鲜商超中&#xff0c;一般蔬菜类商品的保鲜期都比较短&#xff0c;且品相随销售时间的增加而变差&#xff0c; 大部分品种如当日未售出&#xff0c;隔日就无法再售。因此&#xff0c; 商超通常会根据各商品的历史销售和需 求情况每天进行补货。 由于商超销售的蔬菜…