✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨
✨ 个人主页:余辉zmh–CSDN博客
✨文章所属专栏:c++篇–CSDN博客
文章目录
前言一.`string`类的默认成员函数以及深拷贝1.基本框架2.默认成员函数1.构造函数2.析构函数3.拷贝构造函数(深拷贝)4.赋值运算符重载(深拷贝) 3.什么是深拷贝深拷贝的必要性深拷贝的实现 4.测试 二.`string`类的访问和迭代器相关函数1.访问相关函数2.迭代器相关函数1.普通`string`对象的迭代器2.`const` `string`对象的迭代器 3.测试 三.`string`类的容量相关函数1.容量大小相关函数2.扩容相关函数3.测试 四.`string`类的修改相关函数1.拼接操作相关函数2.插入操作相关函数3.删除操作相关函数4.测试 五.`string`类的流插入和流提取函数六.`string`类完整代码1.`string.h`头文件代码2.`string.cpp`函数定义文件代码3.`test.cpp`测试文件代码
前言
在上一篇文章中,我们了解到了std::string
类的常用接口函数以及如何熟练使用。在这片文章中,我们将深入探讨如何模拟实现一个基本的string
类。我们的目的不是创建一个功能完整的string
库,而是通过这个过程来学习字符串处理的基本原理和常见的实现技巧。通过模拟实现string
类,我们不仅能够深入理解字符串的内部工作原理,还能锻炼我们的编程能力,提高解决问题的能力,希望这篇文章能过为你的编程之旅提供有价值的参考和启发。
注意:模拟实现string类需要用到三个文件
test.cpp
文件用来进行测试string.cpp
文件用来定义接口函数(部分较为短小的函数将会直接在string.h
文件中定义)string.h
文件用来声明头文件和string类 一.string
类的默认成员函数以及深拷贝
1.基本框架
为了和库里面的std::string
类进行区分,我们首先定义一个命名空间Mystring
用来封装我们自己模拟实现的string
类。
基本框架如下:
namespace Mystring{ class string{ Public: static const size_t npos=-1; //成员函数 private: //成员变量 char* _str; size_t _size; size_t capacity; };}
_st
r在堆上开辟动态内存用来存储string对象的数据。_size
用来记录string对象的实际大小。_capacity
用来记录当前string对象可存储的最大容量。npos
是一个无符号整形的最大值,在查找等相关函数会用到。 2.默认成员函数
前面我们知道,一个类中有六个默认成员函数(在我之前的文章类和对象(二)中有关于默认成员函数的详细讲解,不清楚的可以看我之前的文章),在模拟实现string类时,我们只需要实现常用的四个(构造函数,析构函数,拷贝构造函数,赋值运算符重载)即可。
1.构造函数
代码实现:
//string.h中声明string(const char* str = "");//string.cpp中定义Mystring::string::string(const char* str):_size(strlen(str)),_capacity(_size),_str(new char[_size+1]){ memcpy(_str,str,_size+1);}
实现原理:
str字符串作为常量参数用来创建string对象,缺省值为空字符串(当没有参数时就是创建一个空对象),缺省值要在声明中给,不能再定义中给。_size字符串大小和_capacity容量初始化值为参数str字符串的大小(strlen(str))。_str指针用来指向存储string对象的动态内存,开辟空间的大小为参数str字符串的大小加一,加一是为了存放结尾的’\0’,开辟空间后要将str字符串内容拷贝到开辟的动态内存。2.析构函数
代码实现:
//string.h文件中声明~string();//string.cpp文件中定义Mystring::string::~string() { delete[] _str; _str=nullptr; _size=0; _capacity=0;}
实现原理:
释放动态内存空间,再将_str指针置为空指针。_size字符串大小和_capacity容量置为0。3.拷贝构造函数(深拷贝)
代码实现:
//string.h文件中声明string(const string&s);//string.cpp文件中定义Mystring::string::string(const string&s){ _str=new char[s._capacity+1]; strcpy(_str,s._str); _size=s._size; _capacity=s._capacity;}
实现原理:
用一个string对象拷贝构造一个新的string对象,拷贝构造函数需要完成深拷贝。先用字符串s的容量作为新空间的大小申请一个新的动态内存空间,再将s字符串的数据拷贝到新的内存空间中。新的string对象的_size和_capacity分别是原string对象的_size和_capacity.4.赋值运算符重载(深拷贝)
代码实现:
//string.h文件中声明string& operator=(const string&s);//string.cpp文件中定义Mystring::string& Mystring::string::operator=(const string&s){ char* tmp=new char[s._capacity+1]; memcpy(tmp,s._str,s._size+1); delete[] _str; _str=tmp; _capacity=s._capacity; _size=s._size; return *this;}
实现原理:
赋值和拷贝构造的不同点在于,拷贝构造是用已有的对象拷贝构造一个新的对象;而赋值是用一个已存在的对象赋值给另一个已存在的对象,赋值也是需要完成深拷贝。先用赋值对象s的容量大小申请一个新的动态内存空间,设置一个新的tmp指针先指向这块空间,再将赋值对象s的数据拷贝到新的内存空间中。释放被赋值对象的原有空间,再将被赋值对象的_str指针指向新的内存空间。被赋值对象的_size和_capacity分别是赋值对象s的_size和_capacity.3.什么是深拷贝
深拷贝(Deep Copy)是对象复制操作中的一种,它不仅仅复制对象的表层数据(如指针或引用),还会递归地复制对象内部所有动态分配的内存、引用的其他对象或其他资源。这样,复制出来的新对象与原始对象在内存中是完全独立的,对它们的修改不会影响到彼此。
深拷贝的必要性
当对象包含指向动态分配内存的指针或其他需要管理的资源时,浅拷贝(仅复制指针值)会导致两个对象共享同一块内存。这可能会引发以下问题:
数据损坏:一个对象修改了它共享的内存中的数据,导致另一个对象看到的数据也发生了变化。内存泄漏:如果两个对象都认为自己拥有这块内存,并在析构时尝试释放它,就会导致重复释放内存的错误(double free),进而可能导致程序崩溃。资源管理混乱:如果对象还管理其他资源(如文件句柄、网络连接等),共享这些资源可能会导致资源被意外关闭或重复访问。深拷贝的实现
实现深拷贝通常涉及以下几个步骤:
为新对象分配内存:如果原始对象包含动态分配的内存,深拷贝的第一步是为新对象分配相应的内存空间。复制数据:将原始对象中的数据复制到新分配的内存中。如果数据本身也是对象(即对象包含指向其他对象的指针),则需要递归地应用深拷贝。更新指针:将新对象的指针成员指向新分配的内存,而不是原始对象的内存。处理其他资源:如果对象管理其他资源(如文件、网络连接等),则需要确保新对象也能正确地获取或创建这些资源的副本。4.测试
测试代码如下:
void test1(){ //创建s1对象 Mystring::string s1("hello world"); //用s1对象拷贝构造s2对象 Mystring::string s2(s1); //将s1对象赋值给s3对象 Mystring::string s3("hello"); s3=s1;}
测试结果如下:
二.string
类的访问和迭代器相关函数
1.访问相关函数
at()
函数代码实现:
//string文件中声明和定义char& at(size_t pos){ //断言检查pos是否符合字符串的范围 assert(pos<_size); //直接返回字符数组对应下标上的字符即可 return _str[pos];}
operator[]
函数代码实现:
//string文件中声明和定义//和at()函数同理char& operator[](size_t pos){ assert(pos<_size); return _str[pos];}
2.迭代器相关函数
1.普通string
对象的迭代器
类型定义:
//普通string对象的迭代器类型typedef char* iterator;
begin()
函数代码实现:
//string文件中声明和定义iterator begin()const{ //返回_str首元素的地址 return _str;}
end()
函数代码实现:
//string文件中声明和定义iterator end()const{ //返回_str最后一个元素的下一个位置的地址 return _str+_size;}
2.const
string
对象的迭代器
类型定义:
//const string对象的迭代器类型typedef const char* const_iterator;
cbegin()
函数代码实现:
//string文件中声明和定义const_iterator begin()const{ //返回_str首元素的地址 return _str;}
cend()
函数代码实现:
//string文件中声明和定义const_iterator end()const{ //返回_str最后一个元素的下一个位置的地址 return _str+_size;}
3.测试
测试代码如下:
void test2(){ Mystring::string s1("hello world"); //使用opreator[]打印s1 for(size_t i=0;i<s1.size();i++){ cout<<s1[i]<<" "; } cout<<endl; //使用普通对象的迭代器打印s1 Mystring::string::iterator it=s1.begin(); while(it!=s1.end()){ cout<<*it<<" "; it++; } cout<<endl; //使用const对象的迭代器打印s2 const Mystring::string s2("hello"); Mystring::string::const_iterator rit=s2.begin(); while(rit!=s2.end()){ cout<<*rit<<" "; rit++; } cout<<endl;}
测试结果如下:
三.string
类的容量相关函数
1.容量大小相关函数
获取string对象的大小需要使用size()函数,获取容量则需要使用capacity()函数
size()
函数代码实现:
//string.h文件中声明和定义//直接返回_size即可size_t size(){ return _size;}
capacity()
函数代码实现:
//string.h文件中声明和定义//直接返回_capacity即可size_t capacity(){ return _capacity;}
2.扩容相关函数
string
类扩容相关的函数主要是reserve()
和resize()
。
reserve()
函数代码实现:
//string.h文件中声明void reserve(size_t);//string.cpp文件中定义void Mystring::string::reserve(size_t n){ if(n>_capacity){ char*tmp=new char[n+1]; memcpy(tmp,_str,_size+1); delete[] _str; _str=tmp; _capacity=n; }}
实现原理:
首先判断需要扩容的大小n是否大于原容量大小_capacity,如果小于则不进行扩容,大于时就需要扩容。扩容时先开辟内存大小为(n+1)的动态内存空间,在设置一个新的字符指针tmp指向新的内存空间,然后将原string对象的数据拷贝到新的内存空间中。释放原string对象的_str指针,再从新指向开辟的新内存空间,最后更改内存大小为n。resize()
函数代码实现:
//string.h文件中声明void resize(size_t n,char ch='\0');//string.cpp文件中定义void Mystring::string::resize(size_t n,char ch){ if(n>_capacity){ reserve(n); } if(n>_size){ for(size_t i=_size;i<n;i++){ _str[i]=ch; } } _size=n; _str[_size]='\0';}
实现原理:
首先依然是需要判断扩容的大小n是否大于原容量大小_capacity,和reserve()函数不同的是,resize对于扩容大小n小于原容量大小时会发生截断,大于时就会进行扩容操作,这里扩容操作直接调用reserve()函数即可。
如果扩容大小n大于原string对象的_size,需要将多余的空间初始化为参数ch,参数ch的缺省值设置为’\0’(要在声明中给),没有传参时,默认初始化为0。
3.测试
测试代码如下:
void test3(){ Mystring::string s1("hello world"); cout<<s1.size()<<" "<<s1.capacity()<<endl; //s1容量扩为15 s1.reserve(15); cout<<s1.size()<<" "<<s1.capacity()<<endl; //s1容量扩为20,多余空间用字符a填充 s1.resize(20,'a'); cout<<s1.size()<<" "<<s1.capacity()<<endl; cout<<s1<<endl; //s1缩容为8,发生截断 s1.resize(8); cout<<s1.size()<<" "<<s1.capacity()<<endl; cout<<s1<<endl;}
测设结果如下:
四.string
类的修改相关函数
1.拼接操作相关函数
1.push_back()函数实现:
//string.h文件中声明void push_back(char ch);//string.cpp文件中定义void Mystring::string::push_back(char ch){ if(_size==_capacity){ reserve(_capacity==0?4:2*_capacity); } _str[_size]=ch; _size++; _str[_size]='\0';}
2.实现原理:
push_back()函数的功能是尾插单个字符,在尾插之前要先判断是否需要扩容,在扩容时,如果需要尾插的string对象是空对象扩容 大小先设置为4,如果不是空对象则扩容大小是原容量的二倍,扩容直接调用reserve()函数即可。插入时,将原来字符串的’\0’位置存放插入的字符ch,再将大小_size增加一,最后重新在字符串的结尾加上’\0’。1.append()
函数实现:
append()函数有两种不同的实现,一个是在原string
对象后增加单个字符,一个是增加字符串。
//string.h文件中声明void append(char ch); void append(const char* str); //string.cpp文件中定义//增加单个字符void Mystring::string::append(char ch){ push_back(ch);}//增加字符串void Mystring::string::append(const char* str){ size_t len=strlen(str); if(_size+len>_capacity){ reserve(_size+len); } //_str加上_size就是原字符串结尾的位置,插入的字符串拷贝到原字符串后面 memcpy(_str+_size,str,len+1); _size+=len;}
2.实现原理:
增加单个字符直接调用push_back函数。增加字符串时,先获取插入字符串的大小len,然后判断原字符串的大小加上插入字符串的大小是否大于容量_capacity,如果大于需要先进行扩容,扩容大小为原字符串的大小加上插入字符串的大小。将插入的字符串拷贝到原字符串后面,拷贝大小为len+1,然后修改_size值。1.operator+=
函数实现:
operator+=
函数和append()
函数一样有两种不同的实现,在原string
对象后增加单个字符和字符串。
//string.h文件中声明string& operator+=(char ch); string& operator+=(const char* str); //string.cpp文件中定义//增加单个字符Mystring::string& Mystring::string::operator+=(char ch){ push_back(ch); return *this;}//增加字符串Mystring::string& Mystring::string::operator+=(const char* str){ append(str); return *this;}
2.实现原理:
增加单个字符直接调用push_back()函数,最后要返回this指针。增加字符串直接调用append(const char* str)函数,最后返回this指针。2.插入操作相关函数
insert()
函数实现:
1.插入n个字符:
//string.h文件中声明void insert(size_t pos,size_t n,char ch);//string.cpp文件中定义void Mystring::string::insert(size_t pos,size_t n,char ch){ assert(pos<_size); if((_size+n)>_capacity){ reserve(_size+n); } size_t end=_size; while(end>=pos&&end!=npos){ _str[end+n]=_str[end]; end--; } for(size_t i=0;i<n;i++){ _str[pos+i]=ch; } _size+=n;}
实现原理:
如果原字符串大小加上插入的个数大于容量大小时,需要先进行扩容,扩容大小为原字符串大小加上插入的个数。将插入位置后面的数据依次往后移动n个位置。再将移动后空出来的位置插入n个字符ch。最后修改_size大小。2.插入字符串:
//string.h文件中声明void insert(size_t pos,const char*str);//string.cpp文件中定义void Mystring::string::insert(size_t pos,const char*str){ assert(pos<_size); size_t len=strlen(str); if(_size+len>_capacity){ reserve(_size+len); } size_t end=_size; while(end>=pos&&end!=npos){ _str[end+len]=_str[end]; end--; } for(size_t i=0;i<len;i++){ _str[pos+i]=str[i]; } _size+=len;}
实现原理:
插入字符串和插入n个字符原理相同,不同点是,最后插入时,需要依次插入字符串的字符。3.删除操作相关函数
erase()
函数实现:
//string.h中声明void erase(size_t pos,size_t len=npos);//string.cpp文件中定义void Mystring::string::erase(size_t pos,size_t len){ assert(pos<_size); if(len==npos||pos+len>=_size){ _size=pos; _str[_size]='\0'; } else{ size_t end=len+pos; while(end<=_size){ _str[pos++]=_str[end++]; } _size-=len; }}
实现原理:
删除要分为两种情况,第一种,如果删除的个数大于_size或者len等于npos值,直接从pos位置将后面全部删除。第二种,依次将后面的字符往前移动,从而达到删除的目的。4.测试
测试代码如下:
void test4(){ //创建一个空对象s1 Mystring::string s1; //s1追加字符串hello s1.append("hello"); cout<<s1<<endl; //s1追加字符串world s1+="world"; cout<<s1<<endl; //s1尾插字符! s1.push_back('!'); cout<<s1<<endl; //在s1下标为6的位置插入*** s1.insert(6,"***"); cout<<s1<<endl; //删除s1下标为10后面的所有字符 s1.erase(10); cout<<s1<<endl; //删除s1下标为5后面的三个字符 s1.erase(5,3); cout<<s1<<endl;}
测试结果如下:
五.string
类的流插入和流提取函数
1.operator<<
流插入函数实现:
//string.h文件中声明ostream& operator<<(ostream& out,const string&s);//string.cpp文件中定义ostream& Mystring::operator<<(ostream& out,const string&s){ cout<<"operator<<"<<endl; for(auto e:s){ out<<e; } return out;}
1.operator>>
流提取函数实现:
//string.h文件中声明istream& operator>>(istream &in,string &s);//string.cpp文件中定义istream& Mystring::operator>>(istream &in, string &s){ cout<<"operator>>"<<endl; s.clear(); char ch = in.get();// 处理前缓冲区前面的空格或者换行while (ch == ' ' || ch == '\n'){ch = in.get();}char buff[128];int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in;}
测试代码如下:
void test5(){ Mystring::string s1; //输入 cin>>s1; //输出 cout<<s1<<endl;}
六.string
类完整代码
1.string.h
头文件代码
#include<iostream>#include<string>#include<string.h>#include<assert.h>using namespace std;namespace Mystring{ class string{ public: static const size_t npos=-1; //构造函数 string(const char* str = ""); //析构函数 ~string(); //拷贝构造函数 string(const string&s); //赋值运算符重载 string& operator=(const string&s); //c格式打印 const char* c_str(){ return _str; } //容量大小 size_t size() { return _size; } size_t capacity() { return _capacity; } //扩容 void reserve(size_t n); void resize(size_t n,char ch='\0'); void clear(){ _size=0; _str[_size]='\0'; } //访问方式 char& operator[](size_t pos){ assert(pos<_size); return _str[pos]; } char& at(size_t pos){ assert(pos<_size); return _str[pos]; } //迭代器 typedef char* iterator; iterator begin() { return _str; } iterator end() { return _str+_size; } typedef const char* const_iterator; const_iterator begin()const { return _str; } const_iterator end()const { return _str+_size; } //修改 void push_back(char ch); void append(char ch); void append(const char* str); string& operator+=(char ch); string& operator+=(const char* str); void insert(size_t pos,size_t n,char ch); void insert(size_t pos,const char*str); void erase(size_t pos,size_t len=npos); //查找 size_t find(char ch,size_t pos=0); size_t find(const char*str,size_t pos=0); //友元函数 friend ostream& operator<<(ostream& out,const string&s); friend istream& operator>>(istream& in,string&s); private: int _size; int _capacity; char* _str; }; //非string成员函数 ostream& operator<<(ostream& out,const string&s); istream& operator>>(istream& in,string&s);}
2.string.cpp
函数定义文件代码
#include"string.h"Mystring::string::string(const char* str):_size(strlen(str)),_capacity(_size),_str(new char[_size+1]){ cout<<"string()"<<endl; memcpy(_str,str,_size+1);}Mystring::string& Mystring::string::operator=(const string&s){ cout<<"operator="<<endl; char* tmp=new char[s._capacity+1]; memcpy(tmp,s._str,s._size+1); delete[] _str; _str=tmp; _capacity=s._capacity; _size=s._size; return *this;} Mystring::string::~string() { cout<<"~string()"<<endl; delete[] _str; _str=nullptr; _size=0; _capacity=0;}Mystring::string::string(const string&s){ cout<<"string()"<<endl; _str=new char[s._capacity+1]; strcpy(_str,s._str); _size=s._size; _capacity=s._capacity;}void Mystring::string::reserve(size_t n){ if(n>_capacity){ char*tmp=new char[n+1]; memcpy(tmp,_str,_size+1); delete[] _str; _str=tmp; _capacity=n; }}void Mystring::string::resize(size_t n,char ch){ if(n>_capacity){ reserve(n); } if(n>_size){ for(size_t i=_size;i<n;i++){ _str[i]=ch; } } _size=n; _str[_size]='\0';}void Mystring::string::push_back(char ch){ if(_size==_capacity){ reserve(_capacity==0?4:2*_capacity); } _str[_size]=ch; _size++; _str[_size]='\0';}void Mystring::string::append(char ch){ push_back(ch);}void Mystring::string::append(const char* str){ size_t len=strlen(str); if(_size+len>_capacity){ reserve(_size+len); } memcpy(_str+_size,str,len+1); _size+=len;}Mystring::string& Mystring::string::operator+=(char ch){ push_back(ch); return *this;}Mystring::string& Mystring::string::operator+=(const char* str){ append(str); return *this;}size_t Mystring::string::find(char ch,size_t pos){ assert(pos<_size); for(size_t i=pos;i<_size;i++){ if(_str[i]==ch){ return i; } } return npos;}size_t Mystring::string::find(const char*str,size_t pos){ assert(pos<_size); const char* ptr=strstr(_str+pos,str); if(ptr){ return ptr-_str; } else{ return npos; }}void Mystring::string::insert(size_t pos,size_t n,char ch){ assert(pos<_size); if((_size+n)>_capacity){ reserve(_size+n); } size_t end=_size; while(end>=pos&&end!=npos){ _str[end+n]=_str[end]; end--; } for(size_t i=0;i<n;i++){ _str[pos+i]=ch; } _size+=n;}void Mystring::string::insert(size_t pos,const char*str){ assert(pos<_size); size_t len=strlen(str); if(_size+len>_capacity){ reserve(_size+len); } size_t end=_size; while(end>=pos&&end!=npos){ _str[end+len]=_str[end]; end--; } for(size_t i=0;i<len;i++){ _str[pos+i]=str[i]; } _size+=len;}void Mystring::string::erase(size_t pos,size_t len){ assert(pos<_size); if(len==npos||pos+len>=_size){ _size=pos; _str[_size]='\0'; } else{ size_t end=len+pos; while(end<=_size){ _str[pos++]=_str[end++]; } _size-=len; }}ostream& Mystring::operator<<(ostream& out,const string&s){ //cout<<"operator<<"<<endl; for(auto e:s){ out<<e; } return out;}istream& Mystring::operator>>(istream &in, string &s){ cout<<"operator>>"<<endl; s.clear(); char ch = in.get();// 处理前缓冲区前面的空格或者换行while (ch == ' ' || ch == '\n'){ch = in.get();}char buff[128];int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in;}
3.test.cpp
测试文件代码
#include"string.h"void test1(){ //创建s1对象 Mystring::string s1("hello world"); //用s1对象拷贝构造s2对象 Mystring::string s2(s1); //将s1对象赋值给s3对象 Mystring::string s3("hello"); s3=s1;}void test2(){ Mystring::string s1("hello world"); //使用opreator[]打印s1 for(size_t i=0;i<s1.size();i++){ cout<<s1[i]<<" "; } cout<<endl; //使用普通对象的迭代器打印s1 Mystring::string::iterator it=s1.begin(); while(it!=s1.end()){ cout<<*it<<" "; it++; } cout<<endl; //使用const对象的迭代器打印s2 const Mystring::string s2("hello"); Mystring::string::const_iterator rit=s2.begin(); while(rit!=s2.end()){ cout<<*rit<<" "; rit++; } cout<<endl;}void test3(){ Mystring::string s1("hello world"); cout<<s1.size()<<" "<<s1.capacity()<<endl; //s1容量扩为15 s1.reserve(15); cout<<s1.size()<<" "<<s1.capacity()<<endl; //s1容量扩为20,多余空间用字符a填充 s1.resize(20,'a'); cout<<s1.size()<<" "<<s1.capacity()<<endl; cout<<s1<<endl; //s1缩容为8,发生截断 s1.resize(8); cout<<s1.size()<<" "<<s1.capacity()<<endl; cout<<s1<<endl;}void test4(){ //创建一个空对象s1 Mystring::string s1; //s1追加字符串hello s1.append("hello"); cout<<s1<<endl; //s1追加字符串world s1+="world"; cout<<s1<<endl; //s1尾插字符! s1.push_back('!'); cout<<s1<<endl; //在s1下标为6的位置插入*** s1.insert(6,"***"); cout<<s1<<endl; //删除s1下标为10后面的所有字符 s1.erase(10); cout<<s1<<endl; //删除s1下标为5后面的三个字符 s1.erase(5,3); cout<<s1<<endl;}void test5(){ Mystring::string s1; //输入 cin>>s1; //输出 cout<<s1<<endl;}int main(){ //test1(); //test2(); //test3(); //test4(); test5(); return 0;}
以上就是关于如何模拟实现string类的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!