C++利剑string类(详解)

news2025/1/24 2:16:05

前言:大家都知道在C语言里面的有  char  类型,我接下来要讲的 string 类功能是使用 char  类型写的类,当然这个是C++官方写的,接下来我们将会学会使用它,我们会发现原来  char  这种类型是还能这么好用,授人以鱼不如授人以渔,接下来我将会讲不少干货,不仅仅是教会我们使用,还会教我们如何模拟实现一个  string  类,我也会教大家如何去读英文文档,话不多说,正文开始。

目录

一,string  类初识

1)英文文档的查找和阅读

2)string类初步使用

string 如何创建对象

string  成员函数的使用

1)size()

2)  length() 

3)  max_size() 

4)resize()

 5)  capacity()

6)    reserve() 

7)  clear() 

8)    at() 

9)  back() /front()

 10)append()

11)  push_back() 

10)    insert() 

13)erase

14)c_str()

15)find()/rfind

16)substr()

17)getline()

二,模拟实现


一,string  类初识

1)英文文档的查找和阅读

想要了解一个语言里面的一个语法,有什么比直接去它的官网看原汁原味的英文文档更加得劲呢?我现在贴出C++的官网cplusplus.com/reference/

打开之后我们会看到这个页面

因为新版本不具有搜索功能,为了我们方便使用,我们切回老版本,点击图上的这个

之后我们就能在上面的reserch进行搜索了

我们搜索  string  就会显示 string 类的提供的所有接口和功能,我们先看它对string类的描述,也就是第一大段

这上面明确说明了,string类是一串char字符,并且提供了一系列的接口,在下面是它的接口或者成员函数,让我们来看看,如何查看成员函数的功能,使用方法。

我们阅读英文文献不一定要全部明白意思,我们可以从上面的四个方面入手,就能大概理解功能和使用方法,碰到不会的单词我们可以用搜索引擎,但不建议使用翻译软件直接翻译,因为这样即不准确也不利用我们的成长,以后我们还会阅读不少的英文文献,只有自身硬才能笑到最后。

2)string类初步使用

string 如何创建对象

在使用时,我们首先可以把它定义的对象当作一个字符串,但是这个对象拥有很多C语言字符串没有的特性,比如不需要我们考虑它的空间够不够,我们可以使用运算符对它进行操作,也就是进行+,+=,=等操作,并且提供了许多接口帮助我们减少手搓的代码量

话不多说,我们先定义一个string 对象吧

string c1;      //里面只有一个\0
string c2("wzdhxhn");   //里面是"wzdxhn\0"
string c3=("wzdxhn");   //里面是"wzdxhn\0"
string c4=c3;           //里面也是"wzdxhn\0"
string c5(c4);          //里面还是"wzdxhn\0"
c1=c5+c4;              //里面可是"wzdxhnwzdxhn\0"哦
c5+=c5;                  //里面是"wzdxhnwzdxhn\0"哦
for(int i=0;i<7;i++)
c2[i]=6;                  //c2里面的内容将会全部变成6
cout<<c1;
cin>>c1;                    //支持输入输出流

没错,真的是一场酣畅淋漓的初始化和赋值,string都支持这种操作,并且拷贝都是深拷贝哦,如果在C里面我们就需要循坏进行赋值了,刚开始是不是就感受到了它的便利。编译器:最终还是我承受了一切。

string  成员函数的使用

前提知识:size_t npos = -1这个是C++库里面的,代表整形的最大值,如果它是默认参数,代表知道\0等结束标志结尾

1)size()

sizeicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/size/

 没有函数参数,这个函数可以获取string里面字符串的长度,不包括\0,然后返回

string s("hello");
cout<<s.size();      //输出s的长度5
2)  length() 

string::length - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/length/

 功能类似size,没有参数,也是返回字符串长度,不包括\0

string s("hello");
cout<<s.length();      //输出s的长度5
3)  max_size() 

 
string::max_size - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/max_size/

 没有参数,返回字符串里面ASCLL码值的最大值这个可以改变字符串的大小,

4)resize()

string::resize - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/resize/

 只有一个参数就是字符串调整后的大小(以\0为标注),没有返回值,可以扩大和缩小字符串长度,扩大可以指定一个参数填充,也可以选择不指定,这个参数有默认参数,但要注意这个如果指定大小小于字符串的大小则会将数据覆盖

string s("hello");
cout<<s.resize(10,'6');    //输出:hello66666
cout<<s.resize(2);         //输出:he
 5)  capacity()

string::capacity - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/capacity/

没有参数,返回一个无符号整形,代表调整后储存字符串空间的大小,这个会返回string类的字符串空间的容量,要注意的是,字符串空间的容量不等于长度,可能有一些空间存在,但是我们没使用,相当于多开了空间

6)    reserve() 

string::reserve - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/reserve/一个参数没有返回值,这个可以重新定义容量,但是要注意只能扩大,不能缩小容量,因为编译器会做判断,如果重定义的容量小于之前的字符串空间的容量就不会进行任何操作,也不会报错。只有一个参数,就是你再次定义的容量大小

7)  clear() 

string::clear - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/clear/没有参数,没有返回值,顾名思义,清理数据,大小和长度。,但是不会改变开的空间大小,也就是字符串空间大小

8)    at() 

string::at - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/at/这个相当于下标访问,只有一个参数就是你要访问的字符串位置 ,最后返回你要的下标元素的引用

string s("hello");
s.at(2);    //等价于s[2]
9)  back() /front()

 string::back - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/back/

 没有参数,返回最后一个字符,front()则是返回第一个字符

string s("hello");
cout<<s.back();       //输出:o
 10)append()

 string::append - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/append/

 这个有两个函数参数,一个是插入的字符串或者字符,第二个参数有默认参数0,代表插入的位置,返回类型是插入后的string类

string s("hello");
s.append(" world");
cout<<s;      //输出:hello world
11)  push_back() 

string::push_back - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/push_back/这个成员函数有一个参数,没有返回值,是要注意插入的只能是字符,或者ASCLL码值而且是尾插,插入字符串请使用append

string s("nihao");
s.push_back('h');  
cout<<s;         //输出:nihaoh
10)    insert() 

string::insert - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/insert/

这个函数支持的参数很多

从图什么我们能知道,返回类型是插入后的string类或者迭代器,这个insert功能强大,不仅支持插入字符,也支持字符串,支持各种各样的插入方式,即带来了遍历也变得复杂了

13)erase

string::erase - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/erase/

这个函数功能很明确了,就是删除字符串,没有参数,返回值是删除后的string类或者迭代器,因此不做举例了

14)c_str()

string::c_str - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/c_str/这个会将string类型 转化为C语言的char* 类型,返回类型是  const char*   ,但要注意返回类型是const类型,只读不写

15)find()/rfind

string::find - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/find/

这个函数的作用就是查找字符或者字符串里面的字符,返回它在字符串里面的位置,注意找字符串的时候不是找子字符串而是找属于这个子串里面的字符

 find()是从前往后找,rfind()则是从后往前找,参数差不多

string s("hello,world");
cout<<s.find('l',5);  结果是:10,返回为world里面的l,因为的从5 o开始查找的
16)substr()

string::substr - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/substr/这个函数的作用就是返回原字符串中的一个子字符串,返回参数为char* ,参数很简单,应该是子字符串的位置,一个是长度,如果不指定长度,默认到\0

string s("hello world");
cout<<s.substr(0,5);  //输出:hello
17)getline()

getline (string) - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/string/string/getline/这个函数的作用就是cin的一个拓展,没有参数和没有返回值,众所周知,cin输入字符串碰到空白会暂停,但是getline要碰到\0才会停止

string s;
cin>>s;     //输入:jascsahjas jscasjbsnc,但最后只有jascsahjas成功输出了
s.getline(); //输入:jascsahjas jscasjbsnc    s里面是:jascsahjas jscasjbsnc

二,模拟实现

备注:博主想偷懒了,答应大家的模拟实现不能再给大家做详细的解释了,但我会贴出源码供大家参考,里面只有一点点的注释,如果大家有不懂的,可以评论区@我,我会给大家一一解答,大家记得先看下面的私有成员,不要直接从头看到尾哦

namespace bit     //使用命名空间,防止于库的string类型冲突

{

  class string

  {

  public:

    typedef char* iterator;      //string类的迭代器可以使用这个代替

  public:

    string(const char* str = "") //当无参数时,默认只有一个\0

    {

      _size = strlen(str);

      _capacity = _size;

      _str = new char[_capacity+1];

      strcpy(_str, str);

    }

    string(const string& s): _str(nullptr), _size(0), _capacity(0) //初始化列表

    {

      string tmp(s._str);

      this->swap(tmp);

    }

    string& operator=(const string &s)

    {

         if(this != &s)

         {

             string temp(s);

             this->swap(temp);

        }

       return *this;

    }

    ~string()

    {

      if (_str)

      {

        delete[] _str;

        _str = nullptr;

      }

    }



    //

    // iterator

    iterator begin()

    {

      return _str;      //通过地址可以判断迭代器的位置

    }

    iterator end()

    {

      return _str + _size;

    }



    /

    // modify

    void push_back(char c)

    {

      if (_size == _capacity)

      reserve(_capacity*2);

      _str[_size++] = c;

      _str[_size] = '\0';

    }

    string& operator+=(char c)

    {

      push_back(c);            //嘻嘻,复用已有的功能,偷懒

      return *this;

    }

    void append(const char* str);

    string& operator+=(const char* str);

    void clear()    //清除数据,但空间不变,尽量减少开空间的频率

    {

      _size = 0;

      _str[_size] = '\0';

    }

    void swap(string& s)

    {

      std::swap(_str, s._str);  //官方库的swap函数,又是一个偷懒小技巧

      std::swap(_size, s._size);

      std::swap(_capacity, s._capacity);

    }

    const char* C_Str()const     //哈哈,是不是很简单

    {

      return _str;

    }



    /

    // capacity

    size_t size()const   //有手就行

    {

      return _size;

    }

    size_t capacity()const

    {

      return _capacity;

    }

    bool empty()const

    {

      return _size == 0;

    }

    void resize(size_t newSize, char c = '\0')

    {

      if (newSize > _size)

      {

        // 如果newSize大于底层空间大小,则需要重新开辟空间

        if (newSize > _capacity)

        {

          reserve(newSize);

        }

        memset(_str + _size, c, newSize - _size); //调用C官方库,初始化,不懂,参考我以前的博客

      }

      _size = newSize;

      _str[newSize] = '\0';

    }

    void reserve(size_t newCapacity)

    {

      // 如果新容量大于旧容量,则开辟空间

      if (newCapacity > _capacity)

      {

        char* str = new char[newCapacity + 1];

        strcpy(str, _str);

        // 释放原来旧空间,然后使用新空间

        delete[] _str;

        _str = str;

        _capacity = newCapacity;

      }

    }



    /

    // access

    char& operator[](size_t index)//很容易实现吧

    {

      assert(index < _size);

      return _str[index];

    }

    const char& operator[](size_t index)const  //要记住const类型要单独处理

    {

      assert(index < _size);

      return _str[index];

    }



    /

    //relational operators

    bool operator<(const string& s)const

    {

      int res = strcmp(_str, s._str); //C官方库,以前博客有这种函数的模拟实现和讲解

      if(res < 0)

        return true;

      return false;

    }

    bool operator<=(const string& s)const

    {

       return !(*this > s);

    }

    bool operator>(const string& s)const

    {

      int res = strcmp(_str, s._str);

      if(res > 0)

        return true;

      return false;

    }

    bool operator>=(const string& s)const

    {

      return !(*this < s);

    }

    bool operator==(const string& s)const

    {

      int res = strcmp(_str, s._str);

      if(res == 0)

        return true;

      return false;

    }

    bool operator!=(const string& s)const

    {

      return !(*this == s);

    }



    // 返回c在string中第一次出现的位置

    size_t find (char c, size_t pos = 0) const   //只读不改,最好const,同时支持非const类型

    {

for (size_t i = pos; i < _size; ++i)

{

if (_str[i] == c)

return i;//找到,返回下标

}

return -1;//未找到

}

    // 返回子串s在string中第一次出现的位置

    size_t find (const char* s, size_t pos = 0) const

    {

      assert(s);          //断言,如果条件为假就报错,结束程序并报错

assert(pos < _size);



const char* src = _str + pos;

while (*src)

{

const char* match = s;//如果不匹配,返回子串起始处重新查找

const char *cur = src;

while (*match && *match==*cur)//结束条件

{

++match;

++cur;

}

if (*match == '\0')//找到子串

{

return src - _str;//返回下标

}

else

{

++src;

}

}

return -1;//未找到

    }

    // 在pos位置上插入字符c/字符串str,并返回该字符的位置

    string& insert(size_t pos, char c)

    {

      assert(pos <= _size);

if (_size > _capacity)

{

//扩容

char *newstr = new char[_capacity * 2 + 1];//开空间

strcpy(newstr, _str);

delete[] _str;

_str = newstr;

_capacity *= 2;



//Expand(_capacity * 2);

}

//移数据

for (int i = _size; i >= (int)pos; --i)

{

_str[i + 1] = _str[i];

}

_str[pos] = c;

_size++;

return *this;

    }

    string& insert(size_t pos, const char* str)

    {

      size_t len = strlen(str);

if (_size + len > _capacity)//扩容

{

//扩容

char *newstr = new char[_capacity * 2 + 1];//开空间

strcpy(newstr, _str);

delete[] _str;

_str = newstr;

_capacity *= 2;

//Expand(_size + len);

}

//后移数据

for (int i = _size; i >= (int)pos; --i)

{

_str[len + i] = _str[i];

}

//拷贝字符串

while (*str != '\0')

{

_str[pos++] = *str++;

}

_size += len;

return *this;

    }

    // 删除pos位置上的元素,并返回该元素的下一个位置

    string& erase(size_t pos, size_t len)

    {

      assert(pos < _size);

if (pos + len >= _size)//pos位置之后全为0

{

_str[pos] = '\0';

_size = pos;

}

else

{

strcpy(_str + pos, _str + pos + len);

_size -= len;

}

return *this;

    }

  private:

    friend ostream& operator<<(ostream& _cout, const bit::string& s);

    friend istream& operator>>(istream& _cin, bit::string& s);

  private:

    char* _str;

    size_t _capacity;

    size_t _size;

  };

};



//输入流重载

istream& bit::operator>>(istream& _cin, bit::string& s)

{

  //预分配100个空间

  char *str = (char *)malloc(sizeof(char)*100);

  char *buf = str;        //buf可以防止频繁开空间

  int i = 1;

  //预处理:跳过流里面的所有空格和回车

  while ((*buf = getchar()) == ' ' || (*buf == '\n'));

  

  for ( ; ; ++i)

  {

    if (*buf == '\n') //回车跳出

    {

      *buf = '\0';

      break;

    }

    else if (*buf == ' ') //空格跳出

    {

      *buf = '\0';

      break;

    }

    else if (i % 100 == 0) //空间不足

    {

      i += 100; //追加100个空间

      str = (char *)realloc(str,i);

    }

    else  //每次getchar()一个值

    {

      buf = (str+i);//为了避免realloc返回首地址改变,不使用++buf,而是用str加上偏移.

      //每次读取一个字符

      *buf = getchar();

    }

  }

  //输入完成,更新s

s._str = str;

s._capacity = s._size = i;

  

  return _cin;

}

//输出流重载

ostream& bit::operator<<(ostream& _cout, const bit::string& s)

{

for (size_t i = 0; i < s.size(); ++i)

{

_cout << s[i];

}

return _cout;

}

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

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

相关文章

结构体精讲1

这一期我们讲一讲C语言中的自定义类型中的其中一种类型--->结构体 相信大家对于结构体都听了很久了吧&#xff0c;下面就由我来带领大家一起学习学习&#xff01; 结构体类型是一种自定义的数据类型&#xff0c;用来组织不同类型的数据成员。结构体可以包含多个不同类型的…

【Proteus】绘制简单的电路图

参考书籍&#xff1a;微机原理与接口技术——基于8086和Proteus仿真&#xff08;第3版&#xff09;&#xff08;作者&#xff1a;顾晖等&#xff09;&#xff0c;p111 1.放置元件 以8086为例&#xff1a; 确保处于元件模式&#xff0c;点击对应的按钮&#xff1a; 在元件库中…

【创意坊】技术征战:揭秘CSDN博客等级与原力等级的战略大计

目录 &#x1f4cb; 前言 1. 博客等级 1.1 博客积分规则 1.2 博客等级划分 1.3 博客等级权益 2. 原力等级 2.1 原力积分规则 2.2 原力等级划分 2.3 原力等级权益 3. 文章点赞评论规则 &#x1f4dd;总结 &#x1f4cb; 前言 &#x1f308;个人主页&#xff1a;Sarapi…

PostgreSQL有意思的现象:支持不带列的表

1、前言 以前从没有试过建一张表&#xff0c;不带任何列。在PG中却支持这种语法。这是个什么鬼? 最近&#xff0c;把PG源码扒了下&#xff0c;简单浏览了下最近的一些merge。其中有一个fix&#xff1a; eeb0ebad79 ("Fix the initial sync tables with no columns.&qu…

【动手学深度学习】(六)权重衰退

文章目录 一、理论知识二、代码实现2.1从零开始实现2.2简洁实现 【相关总结】 主要解决过拟合 一、理论知识 1、使用均方范数作为硬性限制&#xff08;不常用&#xff09; 通过限制参数值的选择范围来控制模型容量 通常不限制偏移b 小的意味着更强的正则项 使用均方范数作为柔…

自定义登录页面模板(移动端)

login/index <script setup lang"ts"> </script><template><div class"login-page">//组件 由于配置了自动注册&#xff0c;所以无需引入<cp-nav-barright-text"注册"click-right"$router.push(/register)&quo…

elk:filebeat

elk:filebeat日志收集工具和logstash相同 filebeat是一个轻量级的日志收集工具&#xff0c;所使用的系统资源比logstash部署和启动时使用的资源要小的多。 filebeat可以运行在非java环境&#xff0c;他可以代替logstash在非java环境上收集日志。 filebeat无法实现数据的过滤…

外置固态硬盘配置

1、插上usb外置硬盘盒 2、邮件我的此“电脑”选择“管理” 3、例如新增的固态硬盘如下&#xff1a; 4、这里我选择mrb(旧模式)而没选guid(新模式) 因为mrb兼容模式更加适合windows、ios等系统 5、右击未分区磁盘&#xff0c;选择新增卷区&#xff0c;一路下一步即可

顶级设计师力荐的界面设计软件,设计新选择

即时设计 作为专业的在线协作UI设计软件&#xff0c;即时设计可以实现视觉效果、交互效果、体验效果一站成型&#xff0c;为你的目标用户创造流畅体验。 轻松绘制原型&#xff1a;借助社区设计资源和原型模板的即时设计&#xff0c;开始敏捷高效的工作。与产品经理分解用户需…

fastapi.templating与HTMLResponse

要声明一个模板对象&#xff0c;应将存储html模板的文件夹作为参数提供。在当前工作目录中&#xff0c;我们将创建一个 “templates “目录。 templates Jinja2Templates(directory“templates”) 我们现在要把这个页面的HTML代码渲染成HTMLResponse。让我们修改一下hello()函…

轻量封装WebGPU渲染系统示例<41>- 前向渲染的雾(Fog)效果(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/FogTest.ts 当前示例运行效果: 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如下&#xff1a; export class FogTest {private mRscene new Rend…

TrustZone之数据、指令和统一缓存(unified caches)

在Arm架构中,data caches是物理标记(physically tagged)的。物理地址包括该行来自哪个地址空间,如下所示: 对于NP:0x800000的缓存查找永远不会命中使用SP:0x800000标记的缓存行。这是因为NP:0x800000和SP:0x800000是不同的地址。 这也影响缓存维护操作。考虑前面图表中的示…

kubernetes详解——从入门到入土(更新中~)

k8s简介 编排工具&#xff1a;系统层面ansible、saltstackdocker容器docker compose docker swarm docker machinedocker compose&#xff1a;实现单机容器编排docker swarm&#xff1a;实现多主机整合成为一个docker machine&#xff1a;初始化新主机mesos marathonmesos …

8、Broker进一步了解

1、Broker消息分发服务以及构建ConsumeQueue和IndexFile与消息清除 前面分析如何进行刷盘&#xff0c;本章分析Broker的消息分发以及构建ConsumerQueue和IndexFile&#xff0c;两者构建是为了能够提高效率&#xff0c;减少消息查找时间以及减少网络带宽与存储空间。 ConsumeQ…

软著项目推荐 深度学习图像风格迁移 - opencv python

文章目录 0 前言1 VGG网络2 风格迁移3 内容损失4 风格损失5 主代码实现6 迁移模型实现7 效果展示8 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习图像风格迁移 - opencv python 该项目较为新颖&#xff0c;适合作为竞赛课题…

鸿蒙4.0开发笔记之ArkTS语法基础之条件渲染和循环渲染的使用(十五)

文章目录 一、条件渲染&#xff08;if&#xff09;二、循环渲染&#xff08;ForEach&#xff09; 一、条件渲染&#xff08;if&#xff09; 1、定义 正如其他语言中的if…else…语句&#xff0c;ArkTS提供了渲染控制的能力&#xff0c;条件渲染可根据应用的不同状态&#xff0…

数独训练APP -->>穿山甲SDK接入收益·android广告接入·app变现·广告千展收益·eCPM收益(2023.11.01 -2023.11.30)

接入穿山甲SDK的app 全屏文字滚动APP 数独训练APP 广告接入示例: Android 个人开发者如何接入广告SDK&#xff0c;实现app流量变现 接入穿山甲SDK app示例&#xff1a; android 数独小游戏 经典数独休闲益智 2023 11月份收益总结 – 数独训练APP app接入上架有一段时间了&a…

SQL server 根据已有数据库创建相同的数据库

文章目录 用导出的脚本创建相同的数据库导出建表脚本再次建表 一些sql语句 用导出的脚本创建相同的数据库 导出建表脚本 首先&#xff0c;右击要导出的数据库名&#xff0c;依次选择任务-生成脚本。 简介&#xff08;第一页&#xff09;处选择下一步&#xff0c;然后来到选择…

TCP协议实现一对一聊天

服务端代码&#xff1a; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; /** * 发送消息线程 */ class…

【开源】基于Vue和SpringBoot的音乐偏好度推荐系统

项目编号&#xff1a; S 012 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S012&#xff0c;文末获取源码。} 项目编号&#xff1a;S012&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、系统设计2.1 功能模块设计2.1.1 音乐档案模块2.1…