C++:模拟实现string

devtools/2024/12/22 14:02:40/

前言:

        为了更好的理解string底层的原理,我们将模拟实现string类中常用的函数接口。为了与std里的string进行区分,所以用命名空间来封装一个自己的strin类。

string.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<assert.h>
#include<string>
using namespace std;
namespace manbo
{class string{public://迭代器typedef char* iterator;static size_t npos;//构造函数string(const char* s);//默认构造string();//拷贝构造string(const string& s);//析构函数~string();//返回字符串const char* c_str()const ;char& operator[](size_t pos);const char& operator[](size_t pos)const ;const size_t size()const;iterator begin(){return _str;}iterator end(){return _str + _size;}const iterator begin()const {return _str;}const iterator end()const {return _str + _size;}void reserve(size_t n = 0);void push_back(char ch);string& append(const char*s);string& operator+=(const char* s);string& operator+=(char ch);string& insert(size_t pos, const char* s);string& erase(size_t pos, size_t len = npos);size_t find(const char* s, size_t pos = 0) const;size_t find(char c, size_t pos = 0) const;string substr(size_t pos, size_t len =npos) const;void resize(size_t n, char ch = '\0');void clear();bool operator<(const string& s) const;bool operator<=(const string& s) const;bool operator>(const string& s) const;bool operator>=(const string& s) const;bool operator==(const string& s) const;bool operator!=(const string& s) const;void swap(string& s2);string& operator=(string temp);private:size_t _size;size_t _capaticy;char* _str;};ostream& operator<<(ostream& out, const string& s);istream& operator>>(istream& in, string& s);
}

        string里有三个私有成员变量 size,capacity,*str,分别表示字符数组的有效数据个数,容量,以及指向字符数组的指针。另外还有一个公有的char类型的指针iterator(迭代器)以及size_t类型的公有静态成员变量npos,并初始化值为-1实则会整型的最大值。

string.cpp

namespace manbo
{string::string(const char* s):_size(strlen(s)), _capaticy(_size), _str(new char[_size + 1]){memcpy(_str, s,_size+1);}string::string():_size(0), _capaticy(0), _str(new char[1]){_str[0] = '\0';}string::string(const string& s){_str = new char[s._size + 1];_size = s._size;_capaticy = s._capaticy;memcpy(_str,s._str,s._size+1);}string::~string(){delete[] _str;_str = nullptr;_size = _capaticy = 0;}const char* string::c_str()const {return _str;}char& string::operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& string::operator[](size_t pos)const{assert(pos < _size);return _str[pos];}const size_t string ::size()const{return _size;}void string::reserve(size_t n){if (n>_capaticy){char* temp = new char[n + 1];memcpy(temp, _str,_size+1);_capaticy = n;delete[] _str;_str = temp;}}void string::push_back(char ch){if (_size==_capaticy){reserve(_capaticy == 0 ? 4 : 2 * _capaticy);}_str[_size++] = ch;_str[_size] = '\0';}string& string:: append(const char*s){size_t len = strlen(s);if (_size+len>_capaticy){reserve(2 * (_size + len));}memcpy(_str + _size,s,len+1);_size += len;return *this;}string& string::operator+=(const char* s){append(s);return *this;}string& string::operator+=(char ch){push_back(ch);return *this;}string& string::insert(size_t pos, const char* s){assert(pos >= 0 && pos <= _size);size_t n = strlen(s);size_t len = _size + n;_size += n;if (len>_capaticy){reserve(2 *len);}size_t end = _size;while (end>=pos&&end!=npos){_str[end + n] = _str[end];end--;}for (int i = 0; i <n ; i++){_str[pos + i] = s[i];}return *this;}string& string::erase(size_t pos, size_t len){assert(pos < _size);;if (len==npos||pos+len>=_size){_str[pos] = '\0';_size = pos;}else{int sum = 0;size_t begin = pos + len;while (begin<=_size){sum++;_str[pos] = _str[begin];pos++, begin++;}_size = pos + sum;}return *this;}size_t string::find(char c, size_t pos) const{for (size_t i = pos; i < _size; i++){if (_str[i] == c)return i;}return npos;}size_t string:: find(const char* s, size_t pos) const{char* temp = strstr(_str, s);if (!temp){return npos;}else{return temp-_str ;}}string string::substr(size_t pos, size_t len) const{assert(pos < _size);string temp;if (len == npos || pos + len >= _size){temp.reserve(_capaticy);for (size_t i = pos; i < _size; i++){temp += _str[i];}}else{temp.reserve(len + 1);for (size_t i = pos; i < len+pos; i++){temp += _str[i];}}return temp;}size_t string::npos = -1;void string::resize(size_t n, char ch){if (n<_size){_str[n] = '\0';_size = n;}else{reserve(n);for (int i = _size; i < n; i++){_str[i] = ch;}_str[n] = '\0';_size = n;}}void string::clear(){_str[0] = '\0';_size = 0;}// hello hello		false// hello*** hello	false// hello hello**	truebool string:: operator<(const string& s) const{size_t s1, s2;s1 = s2 = 0;while (s1<_size&&s2<s._size){if (_str[s1] < s._str[s2])return true;else if (_str[s1] > s._str[s2])return false;else{s1++, s2++;}}if (_size>s.size()){return false;}else if(_size == s.size()){return false;}return true;}bool string::operator==(const string& s) const{return _size == s._size && 0 == (memcmp(s._str, _str, _size));}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 *this == s || *this > s;}bool string::operator!=(const string& s) const{return !(*this == s);}void string:: swap( string& s){std::swap(_str, s._str);std::swap(_capaticy, s._capaticy);std::swap(_size, s._size);}//实现深拷贝string& string::operator=(string temp){swap(temp);return *this;}
}ostream& manbo:: operator<<(ostream& out, const string& s)
{for (auto ch:s){out << ch;}cout << endl;return out;
}
istream&manbo:: operator>>(istream& in, string& s)
{s.clear();char ch;ch = in.get();while (ch == '\0' || ch == '\n'){ch = in.get();}char buff[128];int i = 0;while (ch!='\0'&&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;
}

string::string(const char* s)

        以下是函数的解释:

        1.这是string类的构造函数,它接受一个const char*类型的参数s。这个参数代表一个 C语言 风格的字符串(即以 null 终止的字符数组)。

        2.初始化列表用于在构造函数体执行之前初始化类的成员变量。这里对三个成员变量进行了初始化:

                2.1strlen(s)计算字符串s的长度(不包括 null 终止符)。将这个长度赋值给_size成员变量,表示字符串的实际字符数。

                2.2_capaticy是字符串的容量。在这个实现中,容量被设置为与字符串长度相同,即_size

                2.3new char[_size + 1]分配了一块动态内存,大小为_size+1。这里+1是为了存储字符串的 null 终止符('\0')。将这块内存的指针赋值给_str,_str用于存储实际的字符串内容。

        3.构造函数体的内容是将源字符串 s 的内容复制到新分配的内存区域_str中。memcpy函数用于内存块的复制:复制从s开始的_size+1字节到_str。这里的_size+1是因为我们要复制整个字符串,包括 null 终止符。

    string::string()        

        以下是函数解释

        1.这是string类的默认构造函数。它不接受任何参数,并用于创建一个空的 string对象。    

        2.初始化列表用于在构造函数体执行之前初始化类的成员变量。这里对三个成员变量进行了初始化:

                2.1初始化_size成员变量为 0,表示字符串的长度为零。因为这是一个空字符串,所以长度为零。

                2.2初始化_capaticy成员变量为 0,表示字符串的容量也为零。

                2.3使用new char[1]分配了一块大小为 1 字节的动态内存。这块内存用于存储字符串及其终止符'\0'。

        

string::string(const string& s)

        以下是关于函数的解释

        这段代码是一个拷贝构造函数,用于创建一个string类的新对象,它是现有string对象的副本

  string::~string()      

        以下是函数的解释

        1.释放_str指针在堆上申请的空间,并将_str置为空指针

        2.将_size与_capaticy重新置为0;

const char* string::c_str()const 

         以下是函数的解释

        1.返回_str所指向的字符串。

char& string::operator[](size_t pos)

        以下是函数的解释

        1.这段代码定义了string类的一个成员函数operator[ ],用于访问字符串中的字符。这个函数是一个重载的下标运算符,允许通过下标直接访问字符串中的字符。

        2.assert是一个宏,用于在调试阶段检查条件是否为真。如果条件不为真,程序会中断并输出错误信息。这里检查pos是否在有效范围内,如果是则程序继续执行。

        3.return _str[pos];如果索引pos合法,函数返回_str[pos]。 _str是一个指向字符数组的指针,因此_str[pos] 表示数组中第 pos个位置的字符,并且返回的是引用所以可以对返回的值进行修改会影响到_str[pos]里的值。

const char& string::operator[](size_t pos)const

        以下是函数的解释

        与上个函数类似,但传入的对象以及返回的引用都被const进行修饰,此函数可以传const对象,因为这是一个权限的平移,而上个函数不能传const对象因为会产生权限的放大。并且对返回的引用只能进行读取而不能进行修改。

 const size_t string ::size()const   

   

        以下是函数的解释

        因为_size是成员变量默认是私有的所以不能直接返回,并且也不能随意修改,通过size() 函数来获取_size的值。

void string::reserve(size_t n)

                以下是函数的解释

                1.这段代码是一个成员函数reserve,用于调整string类的内部字符数组的容量,以便容纳至少n个字符的数据;

                2.判断n > _capaticy,检查n是否大于当前已分配的容量_capaticy,如果大于就扩容;

                3.new char[n + 1],在堆上分配一个新的字符数组,大小为n+1。因为额外的 1 个字符位置用于存储字符串的终止符'\0',并temp指针进行接收.

                4.memcpy(temp, _str, _size + 1)使用memcpy函数将旧字符数组_str的内容(包括终止符所以要+1)复制到新分配的内存temp中

                5.释放原_str的内容

                6.将_str重新指向新分配内存的指针temp的地址

void string::push_back(char ch)

         以下是函数的解释

        1.这个push_back函数用于向string对象的末尾添加一个字符,并处理容量的扩展。

        2.if (_size == _capaticy):检查当前字符串的大小是否等于当前容量。如果等于,说明字符串需要扩展容量,那么则调用创建好的reserve函数进行扩容,如果一开始是给空字符串那么_capacity会等于0,0乘任何数都得0,所以要加以进行判断。

        3.将字符ch添加到当前字符串的末尾,在赋值后递增_size,更新字符串的当前长度;

        4.在字符串末尾添加终止符'\0',标志着字符串的结束

    string& string:: append(const char*s)

        以下是关于函数的解释

        1.append函数用于将一个 C 风格的字符串(const char* s)追加到string对象的末尾

        2.strlen(s):计算要追加的字符串s的长度(不包括终止符 '\0'),并且赋值给len

        3.检查当前字符串的总长度(包括要追加的部分)是否超过当前容量,如果超过则调用reserve函数进行扩容

        4.拷贝字符串s从原string对象末尾('\0')的位置,并更新_size的值。

    string& string::operator+=(const char* s)

         以下是关于函数的解释

        operatir+=运算符重载,其参数为字符串,使用此operatir+=会复用append函数,在原字符串末尾追加字符串s,并返回*this(原对象)的引用。

        string& string::operator+=(char ch)

          以下是关于函数的解释

          operatir+=运算符重载,其参数为单个字符,使用此operatir+=会复用push_backd函数,在原字符串末尾追加字符ch,并返回*this(原对象)的引用。      

        string& string::insert(size_t pos, const char* s)

                以下是关于函数的解释

                1.insert函数用于在string对象中的指定位置插入字符串

                2.assert:检查插入位置pos是否在有效范围内(即不小于 0 且不大于当前字符串的_size)

                3.strlen(s)计算待插入的字符串s的长度,并将其赋值给n

                4.更新当前字符串的大小_size,将更新后的_size赋值给len,并判断是否需要扩容

                5.end:初始化为新的字符串长度_size,这是待会插入操作开始前的字符串末尾。        

                6.while循环:从字符串的末尾向插入位置pos移动字符,为腾出空间插入新字符串,_str[end + n] = _str[end]:将字符移动到新的位置并将end--.。注意,npos是一个静态成员变量,通常定义为size_t(-1),表示无效位置。在这里,它的作用是防止end变成负值,从而避免无限循环。

                7.for循环:将待插入的字符串s 的字符逐个复制到目标位置pos开始的位置

                8.返回 *this(当前对象)的引用,以支持链式调用。

string& string::erase(size_t pos, size_t len)

        以下是关于函数的介绍

        1.erase函数用于从string对象中删除指定位置的字符,删除的长度由 len 参数指定(缺省值为npos)。它的实现包括了处理不同情况的逻辑,以确保删除操作正确地更新字符串的内容和大小。

        2.assert:检查pos 是否在有效范围内(即 pos 应小于当前字符串的大小_size)

        3.if语句:处理特殊情况

                3.1如果len的值是npos(表示删除到字符串的末尾)或者pos + len超过了 _size,则删除从pos位置到字符串末尾的所有字符。

                3.2_str[pos] = '\0';:将删除位置设置为字符串结束符'\0',这会将字符串从pos位置截断,并更新字符串的大小 _size,以表示新的字符串长度。

        4.else语句:处理len小于字符串剩余长度的情况

                4.1 int sum = 0;:初始化一个计数器sum,用于记录实际移动的字符数。

                4.2 size_t begin = pos + len;:确定开始移动的字符位置。

                4.3 while (begin <= _size)遍历从begin到字符串末尾的所有字符,并将它们向前移动到删除的区域(这里字符'\0'也会向前移动,所以不需要再添加'\0'),以覆盖掉删除的字符。

        5._size = pos + sum;:更新字符串的大小_size,它应该等于新末尾的位置加上实际移动的字符数。

    

size_t string::find(char c, size_t pos) const

        以下是关于函数的介绍

        通过遍历来查找传入的字符c,如果找到就返回下标,如果没找到则返回npos。

size_t string:: find(const char* s, size_t pos) const

        以下是关于函数的介绍

        1.运用c语言的库函数strstr查找子串并用char类型的指针temp进行接受

        2.如果temp是NULL那么就没找到返回npos,如果找到了则将temp的地址-_str的首地址,最终会算出子串的第一个字符的下标位置。

    string string::substr(size_t pos, size_t len) const

                

        以下是关于函数的介绍

        1.substr函数用于生成当前字符串对象的子字符串。它的实现包括对子字符串的提取和处理

        2.assert(pos < _size);确保pos 在有效范围内。这里_size表示当前字符串的实际大小

        3.string temp;创建一个名为 temp的 string 对象,用于存储提取的子字符串。

        4.if (len == npos || pos + len >= _size),如果len是npos(表示提取从pos到字符串末尾)或者pos + len 超过了当前字符串的大小 _size,则处理这两种情况:

                4.1预先为temp对象开好_capaticy个空间,目的是避免频繁的扩容

                4.2 for循环,从pos位置开始遍历到字符串末尾,将每个字符添加到temp字符串中。

        5 当len小于等于字符串的剩余长度时,提取从pos开始的长度为len的子字符串

                5.1为temp字符串扩容,len+1是为了确保有足够的空间来存储len个字符和一个'\0'

                5.2 for循环从pos位置开始,遍历len个字符,将每个字符添加到 temp 字符串中。

注意:这里并不需要专门再结尾添加'\0',因为temp +=会去调用operator+=的运算符重载,而operator+=里又会调用push_back函数,再push_back函数里会对字符末尾进行添加'\0'的操作

void string::resize(size_t n, char ch)

        以下是关于函数的解释

        1.resize函数用于调整string对象的大小,并且可以用特定的字符填充新增的部分。

        2.如果传入的n是小于_size的那么直接将字符串_str[n]替换为'\0',并将_size更新为n;

        3.如果是n大于_size那么先将string对象的内存进行扩容,确保字符串的存储空间足够,然后从_size位置到n 位置填充字符ch。接着在_str[n]插入终结符 '\0',并更新_size为n。

void string::clear()

         以下是关于函数的解释

        clear顾名思义是清理的意思,那么直接在_str[0]的位置插入终止符'\0',并把_size更新为0,这里没有将_capacity置空是为了怕对象还要进行插入数据,防止多次扩容消耗性能。

bool string:: operator<(const string& s) const

        以下是关于函数的解释

        .operator<函数定义了一个 <操作符,用于比较当前 string 对象与另一个 string对象的大小。它主要用于ASCII比较(即按字母顺序比较)

        

其他的operator比较符重载

string& string::operator=(string temp)

        以下是关于函数的解释  

        1.接受一个string对象temp的形参

        2.在函数体内调用swap(temp),这将当前对象的内容与temp对象的内容交换。

        3.交换后,当前对象就变成了 temp 的内容,temp变成了当前对象的旧内容,并且因为temp是局部参数,在函数调用的时候自动进行析构,防止了内存泄漏

ostream& manbo:: operator<<(ostream& out, const string& s)

        以下是关于函数的解释

        1.operator<<函数是一个重载的输出流操作符,用于将string对象的内容输出到输出流

        2.范围for循环,auto ch自动推导ch的类型为s中的字符类型,循环遍历s中的每一个字符,并将其逐个输出到流out中

        3.返回流out以支持链式操作。比如:out<<s1<<s2;

istream&manbo:: operator>>(istream& in, string& s)

        以下是关于函数的解释

        1.operator>>函数是一个重载的输入流操作符,用于从输入流读取字符串,并将其存储到string对象s中。

        2.s.clear();使s变为空字符串,以确保读取的内容覆盖之前的内容.

        3.char ch;ch = in.get();使用in.get()从流中读取一个字符并存储在ch中。

        4.while循环跳过开头无用的字符如'\0'或'\n',直到读取到第一个有用的字符

        5.使用一个字符数组buff作为缓冲区来临时存储字符,i用于确认buff数组的位置。

        6.while循环, 只要字符不是'\0'和'\n',就将字符存入缓冲区buff。缓冲区buff里有 127 个字符时(留一个位置给终止符'\0'),将缓冲区内容+=到s中,并重置i。

        7.如果i>0表示buff里有剩余的字符,将这些字符+=到s对象中

        8. 返回流in的引用,以支持链式输入操作。比如:cin>>s1>>s2;

        

测试文件:

void test01()
{manbo::string s1;manbo::string s2="Hello world";cout << s2.c_str() << endl;cout << s1.c_str() << endl;manbo::string const s3 = "asdddd";
}void test02()
{manbo::string const s2 = "Hello world";manbo::string::iterator it = s2.begin();while (it!=s2.end()){cout << *it;it++;}cout << endl;
}void test03()
{manbo::string  s2 = "Hello world";s2.insert(6, "******");cout << s2.c_str() << endl;s2.erase(0);cout << s2.c_str() << endl;
}void test04()
{manbo::string  s2 = "Hello world";size_t pos1 = s2.find("world");size_t pos2 = s2.find('e');cout << pos2 << endl;
}void test05()
{manbo::string s1 = "https://www.bilibili.com/video/BV1fW421X7gD/?spm_id_from=333.1007.tianma.6-1-19.click";//解析网址,分协议   域名  资源size_t pos1 = s1.find("://");if (pos1 == manbo::string::npos){cout << "No Find";exit(1);}manbo::string agreement = s1.substr(0, pos1);cout << "协议:" << agreement.c_str()<< endl;size_t pos2 = s1.find('/', pos1 + 3);if (pos2 == manbo::string::npos){cout << "No Find";exit(1);}manbo::string domain = s1.substr(pos1 + 3, pos2 - (pos1 + 3));cout << "域名:" << domain.c_str()<< endl;manbo::string resource = s1.substr(pos2 + 1);cout << "资源:" << resource.c_str() << endl;
}void test06()
{string s1 = "hello world";manbo::string s2 = "hello world";//s1.resize(4,'x');//s2.resize(4,'x');s1 += '\0';s1 += "******";s2 += '\0';s2 += "******";cout << s1 << endl;cout << s2<< endl;manbo::string s3;cin >> s3;cout << s3;cin >> s3;cout << s3;
}void test07()
{manbo::string s1="hello";manbo::string s2="hello world";cout << (s1 < s2) << endl;cout << (s1 == s2) << endl;cout << (s1 > s2) << endl << endl;manbo::string s3 = "hello world";manbo::string s4 = "hello";cout << (s3 < s4) << endl;cout << (s3 == s4) << endl;cout << (s3 > s4) << endl<<endl;manbo::string s5 = "hello";manbo::string s6 = "hello";cout << (s5 < s6) << endl;cout << (s5 == s6) << endl;cout << (s5 > s6) << endl << endl;
}void test08()
{manbo::string s1 = "hello";manbo::string s2 = "hello world";s1 = s2;cout << s1;}


http://www.ppmy.cn/devtools/97635.html

相关文章

EmguCV学习笔记 VB.Net 4.4 图像形态学

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 教程VB.net版本请访问&#xff1a;EmguCV学习笔记 VB.Net 目录-CSDN博客 教程C#版本请访问&#xff1a;EmguCV学习笔记 C# 目录-CSD…

Java Web —— 第七天(Mybatis案例1)

环境搭建 准备数据库表(dept、emp) -- 部门管理 create table dept(id int unsigned primary key auto_increment comment 主键ID,name varchar(10) not null unique comment 部门名称,create_time datetime not null comment 创建时间,update_time datetime not null commen…

day30(8/16)——ansible

目录 一、回顾 1、mysql和python 1. mysql5.7 2. 可以使用pymysql非交互的管理mysql 2、mycat中间件 1. 独属于mysql主从的负载均衡策略 2.配置写主读从 3. 步骤 3.1 安装jdk 3.2 mycat 3.3 配置 3.4 启动和调试 二、运维自动化&#xff08;ansible&#xff09; 1、任务背…

QT emit关键字

QT的emit关键字 emit 是 Qt 框架中的一个关键字&#xff0c;用于显式地触发信号&#xff08;signals&#xff09;。信号是 Qt 中用于对象间通信的一种机制&#xff0c;通过 emit 关键字&#xff0c;程序员可以在代码中明确地触发信号&#xff0c;从而通知连接的槽&#xff08;…

Vue利用axios请求前携带令牌

请求流程 ① 发起登录请求&#xff0c;拿到后端返回的token&#xff0c;存到 localstorage 中&#xff08; 通过 localStorage.setItem(token,存入的令牌&#xff09;) ② 每一次请求发送之前都进行拦截&#xff0c;给请求添加token&#xff08;通过 localStorage.getItem(tok…

php生成json字符串,python解析json字符串

<?php $nodes []; $_tmp[title] 标题1; $_tmp[titlekey] actt; $_tmp[child] [acww.zip, acww21.zip, tta.zip]; $nodes[] $_tmp;$_tmp2[title] 标题2; $_tmp2[titlekey] kfij; $_tmp2[child] [KL7SHR47.zip, fdgfdg.zip, qweqw.zip]; $nodes[] $_tmp2;// 构建调用…

【3】AT32F437 OpenHarmony轻量系统第一个程序:点灯

在搭建好AT32F437 OpenHarmony 轻量系统之后&#xff0c;当然要尝试点一下灯了。 编写点灯程序 笔者在适配OpenHarmony轻量系统的时候&#xff0c;只对源码的device和vendor目录进行了修改&#xff0c;AT32的app目录笔者放置在了vendor/tree/master/artery/AT-START-F437/app…

同步外网YUM源-3

在企业实际应用场景中,仅仅靠光盘里面的RPM软件包是不能满足需要,我们可以把外网的YUM源中的所有软件包同步至本地,可以完善本地YUM源的软件包数量及完整性。 获取外网YUM源软件常见方法包括Rsync、Wget、Reposync,三种同步方法的区别Rsync方式需要外网YUM源支持RSYNC协议…