目录
💕1.模拟string类构造函数
💕2.模拟构造函数实现
💕3.拷贝构造函数模拟实现
💕4.析构函数模拟实现
💕5.size函数,capacity函数模拟实现
💕6.begin函数,end函数,模拟实现
💕7.=赋值运算符重载模拟实现
💕8.访问运算符重载 模拟实现
💕9.reserve函数模拟实现
💕10.push_back函数模拟实现
💕11.append函数模拟实现
💕 12.+=运算符重载
💕13.find函数模拟实现
💕14.insert函数指定位置插入字符模拟实现
💕15.insert函数指定位置插入字符串模拟实现
💕16.erase函数模拟实习
💕17.判断运算符重载
💕18.输出/输入运算符重载
💕19.clear函数
💕20.总体代码
💕21.完结
(最新更新时间——2025.1.23——各位小年快乐)
不过是卑贱的下人 杀了就杀了
💕1.模拟string类构造函数
我们知道string在C++中是一个单独的名,如果我们想要模拟string类的实现,我们就需要使用命名空间域将string重命名
那么在.h文件中我们可以这样写
blogstring.h的初始模板
#pragma once #include<iostream> include<assert.h> using namespace std; namespace blog {class string{private:char* _str;//字符串首地址size_t _size;//有效个数size_t _capacity;//容量}; }
将string的初始成员变量安置后,我们先进行构造函数的实现
💕2.模拟构造函数实现
构造函数分为有参构造函数,无参构造函数,以及拷贝构造函数,因此,有参构造函数和无参构造函数可以使用全缺省,合并为一个构造函数,拷贝构造函数单独进行实现
首先思考,我们有参构造函数与无参构造函数的形参是字符串,因此,我们的形参应当也是const char* str,const char* str的意义就是字符串类型
再次思考,既然要实现构造函数,那就要对成员变量进行更改,有效个数可以使用strlen函数,容量的话可以先使用size的数值,_str可以使用size的数值进行开辟空间
但需要注意的是capacity开辟出来的容量应该要多一个,多出来的是'\0'的位置
因此,我们可以这样写->:
string.cpp的构造函数
#define _CRT_SECURE_NO_WARNINGS #include"blogstring.h" namespace blog {string::string(const char* str):_size(strlen(str)){_capacity = _size;//更新容量_str = new char[_capacity+1];//开辟capacity+1个char类型的空间,因为有一个是'\0'的strcpy(_str, str);//strcpy(char* 目标字符串,char* 源字符串)函数,传入两个地址,将源字符串的内容复制到目标字符串中} }
注意我们的string.c文件中,使用的是命名空间域,这样是因为我们的成员变量是私有的,而不同文件的命名空间域会合并在一起,因此,我们在实现string.c文件时,可以直接让形参访问成员变量
同时我们的全缺省需要写到头文件中,如果我们什么都没传该怎么写,传空字符串,size应该是0。如何让size是0?
我们只需要在头文件处更新一下缺省值即可,如下->:
blogstring.h更新
namespace blog {class string{public:string(const char* str = "");//有参&&无参构造函数private:char* _str;//字符串首地址size_t _size;//有效个数size_t _capacity;//容量}; }
💕3.拷贝构造函数模拟实现
如何实现拷贝构造函数,我们回想一下如何使用,string s2(s1),我们的形参应该是一个字符串类型的,那么,我们的头文件可以这样写->:
blogstring.h
#pragma once #include<iostream> using namespace std; namespace blog {class string{string(const char* str = "");//有参&&无参构造函数string(const string& str);//拷贝构造函数private:char* _str;//字符串首地址size_t _size;//有效个数size_t _capacity;//容量}; }
思考一下,拷贝构造函数的实现应该实现深拷贝,也就是需要开辟新的空间,那么既然要开辟新的空间,我们就需要知道形参字符串的有效之,因此我们可以这样写->:
这里只写拷贝构造函数的实现了,往后也是如此
string::string(const string& str){_str = new char[str._capacity+1];//开辟新的空间_size = str._size;_capacity = str._capacity;strcpy(_str, str._str);}
如此,就显示出我们之前使用命名空间域合并的优势了
💕4.析构函数模拟实现
想要实现析构函数,我们就要释放内存并进行空指针化,这点很简单,代码如下->:
string.h
~string();//析构函数
string.c
string:: ~string(){_size = _capacity = 0;delete[] _str;_str = nullptr;}
💕5.size函数,capacity函数模拟实现
实现size函数与capacity函数的实现,就是实现返回字符串的size和capacity是多少,这一点也简单容易实现,如下->:
string.h
size_t size();//返回_size size_t capacity();//返回_capacity
string.c
//返回_sizesize_t string::size(){return _size;}//返回_capacitysize_t string::capacity(){return _capacity;}
💕6.begin函数,end函数,模拟实现
在我们模拟实现时,迭代器的类型iterator不可以直接使用,我们需要使用typedef将其重命名一下才可以使用,那么我们的头文件需要先整体更新一下->:
string.h
#pragma once #include<iostream> using namespace std; namespace blog {class string{typedef char* iterator ;typedef const char* const_iterator;public:string(const char* str = "");//有参&&无参构造函数string(const string& str);//拷贝构造函数~string();//析构函数size_t size();//返回_sizesize_t capacity();//返回_capacityiterator begin();//begin函数iterator end();//end函数const_iterator begin() const;//const begin函数const_iterator end() const;//const end函数private:char* _str;//字符串首地址size_t _size;//有效个数size_t _capacity;//容量}; }
因为原本的begin与end函数有const与非const类型,所以我们这里也模仿上
begin函数返回的是字符串首地址,end函数返回的是'\0'的位置,那么就很好实现了
string.cpp
//begin函数 string::iterator string::begin() {return _str; }//end函数 string::iterator string::end() {return _str+_size; }//const begin函数 string::const_iterator string::begin()const {return _str; }//const end函数 string::const_iterator string::end()const {return _str + _size; }
💕7.=赋值运算符重载模拟实现
想要实现赋值运算符,首先想想它可以怎样用,它可以,string s1 = s2这样用,也可以s2 = s3
这样用,这就说明我们传过去的形参,可能已经被初始化了,也可能没有被初始化,这就会导致capacity可能不够的问题,因此,需要先判断一下,再进行深拷贝
如果像string s1 = s2还容易弄,毕竟就像拷贝构造函数一样,但如果是s2 = s3呢?我们需要更改s2的capacity,那么,可以这样写->:
string.h
string& operator=(const string& s);//赋值运算符重载
string.cpp
//赋值运算符重载string& string::operator=(const string& s){_capacity = s._capacity;//更改capacity_size = s._size;//更改_sizechar* tep = new char[_capacity+1];//开辟一样的空间strcpy(tep, s._str);//将内容复制过去delete[] _str;_str = tep;return *this;}
我们对于.cpp文件也可以进行更一步的优化,如果两个对象不相等的话,进行更改,否则直接返回,如下->:
//赋值运算符重载string& string::operator=(const string& s){//这里是取地址操作符if (this != &s) {_capacity = s._capacity;//更改capacity_size = s._size;//更改_sizechar* tep = new char[_capacity+1];//开辟一样的空间strcpy(tep, s._str);//将内容复制过去delete[] _str;_str = tep;}return *this;}
💕8.访问运算符重载 模拟实现
想要实现访问运算符,我们想想arr[1],返回的是一个确切的值,那么这里返回的就应该是char类型的,避免内存重复开辟我们可以使用char&来作为返回值(堆区开辟的内存,不会主动释放)
string.h
string类中有const与非const,我们也这样实现
char& operator[](size_t pos);//访问运算符重载 const char& operator[](size_t pos) const;//const 访问运算符重载
string.cpp
//访问运算符重载char& string::operator[](size_t pos){assert(pos >= 0 && pos < _size);return *(_str + pos);}//const 访问运算符重载const char& string::operator[](size_t pos) const{assert(pos >= 0 && pos < _size);return *(_str + pos);}
💕9.reserve函数模拟实现
reserve函数是用来预留空间的,而且只可以扩不可以缩,这一点我们之前有讲,所以只需要重新开一段空间并将值拷贝过去即可
string.h
void reserve(size_t n);//预留空间
string.cpp
//预留空间模拟实现void string::reserve(size_t n){if (n > _capacity){_capacity = n;char* tep = new char[_capacity + 1];strcpy(tep, _str);delete[] _str;_str = tep;}}
💕10.push_back函数模拟实现
想要实现push_bakc进行尾插,首先需要判断的就是是否需要扩容,以及是否是空容量的问题,之后就是插入的问题,不要忘记'\0'的处理
string.h
void push_back(char ch);//尾插一个字符
string.cpp
//尾插字符void string::push_back(char ch){//判断是否需要扩容if (_size == _capacity) {size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}_str[_size] = ch;_str[_size + 1] = '\0';++_size; }
💕11.append函数模拟实现
attend函数是尾插字符串,依旧需要先判断是否扩容,接着我们可以使用一个c语言中学习的string类型的函数进行尾插
string.h
void append(const char* str);//尾插一个字符串
string.cpp
//尾插字符串void string::append(const char* str){ size_t len = strlen(str);//计算字符串多长,方便修改size//判断是否需要扩容if (_size == _capacity) {size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}strcpy(_str + _size, str);_size += len;}
💕 12.+=运算符重载
+=运算符重载可以既尾插一个字符,也可以尾插一个字符串,是两种类型,这里非常容易实现,直接套现push_back和append函数即可,代码如下->:
string.h
string& operator+=(char ch);//+=运算符重载,尾插字符 string& operator+=(const char* str);//+=运算符重载,尾插字符串
string.cpp
//+=运算符重载,尾插字符string& string::operator+=(char ch){push_back(ch);return *this;}//+=运算符重载,尾插字符串string& string::operator+=(const char* str){append(str);return *this;}
💕13.find函数模拟实现
find函数的实现也很简单,代码如下->:
string.h
size_t find(char ch, size_t pos = npos);//查找某个字符 size_t find(const char* str, size_t pos = npos);//查找某个字符串 static const size_t npos;//我们需要定义静态npos设为找不到的情况
查找某个字符串时可以直接用函数来实现,并通过指针-指针返回中间元素的个数,进而直接反馈出下标是多少
string.cpp
const size_t string::npos = -1;//查找某个字符size_t string::find(char ch, size_t pos){ //方法一:/*while (_str + pos < _str + _size){if (*(_str + pos) == ch){return (_str + pos) - _str;}pos++;}return npos;*///方法二:for (size_t i = pos; i < _size; i++){if (*(_str + i) == ch){return i;}}return npos;}//查找某个字符串size_t string::find(const char* str, size_t pos){if (p == nullptr)return npos;char* p = strstr(_str + pos, str);return p - _str;}
💕14.insert函数指定位置插入字符模拟实现
insert函数的模拟实现,其实就像数据结构实现顺序表的样子
先讲插入位置后面的数据全部后移,再进行擦插入。需要先判断是否需要扩容,因为有可能直接使用insert插入,导致capacity是0
string.h
void insert(size_t pos, char ch);//指定位置插入字符
string.cpp
//指定位置插入字符 void string::insert(size_t pos, char ch) {assert(pos <= _size);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; }
💕15.insert函数指定位置插入字符串模拟实现
插入字符串是类似的,只不过用到了memcpy函数进行了将字符串复制上去
string.h
void insert(size_t pos, const char* str);//指定位置插入字符串
string.cpp
//指定插入字符串void 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 + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}memcpy(_str + pos, str, len);_size += len;}
💕16.erase函数模拟实习
指定位置删除字符需要注意的是删去的长度,大于字符串就全删。
删去后使用strcpy进行覆盖,并进行size的减去
string.h
void erase(size_t pos = 0, size_t len = npos);//指定位置删除字符串
string.cpp
void string::erase(size_t pos , size_t len ) {assert(pos < _size);// len大于前面字符个数时,有多少删多少if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);//用strcpy进行覆盖_size -= len;}}
💕17.判断运算符重载
这里很简单,不进行解释了
string.h
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;
string.cpp
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) == 0; }bool string::operator!=(const string& s) const {return !(*this == s); }
💕18.输出/输入运算符重载
输出输入运算符的重载是要在类外写的,但是不要出了命名空间域,否则会无法访问成员变量(不使用友元)
string.h
istream& operator>> (istream& is, string& str); ostream& operator<< (ostream& os, const string& str);
string.cpp
istream& operator>> (istream& is, string& str) {str.clear();//下面讲char ch = is.get();while (ch != ' ' && ch != '\n'){str += ch;ch = is.get();}return is; }ostream& operator<< (ostream& os, string& str) {for (size_t i = 0; i < str.size(); i++){os << str[i];}return os; }
💕19.clear函数
我们在cout重载时,如果不将对象清理,那就会变成尾插,因此,我们需要先进行初始化清理一下
clear函数把字符串首字符设为字符串结束符
'\0'
,这意味着字符串内容为空。同时将字符串的大小_size
置为 0
string.h
void clear();//清理函数
string.cpp
void string::clear(){_str[0] = '\0';_size = 0;}
💕20.总体代码
string.h
#pragma once #include<iostream> #include<assert.h> using namespace std; namespace blog {class string{typedef char* iterator ;typedef const char* const_iterator;public://test1string(const char* str = "");//有参&&无参构造函数string(const string& str);//拷贝构造函数~string();//析构函数size_t size() const;//返回_sizesize_t capacity();//返回_capacity//test2iterator begin();//begin函数iterator end();//end函数const_iterator begin() const;//const begin函数const_iterator end() const;//const end函数//test3string& operator=(const string& s);//赋值运算符重载char& operator[](size_t pos);//访问运算符重载const char& operator[](size_t pos) const;//const 访问运算符重载//test4void reserve(size_t n);//预留空间void push_back(char ch);//尾插一个字符void append(const char* str) ;//尾插一个字符串string& operator+=(char ch);//+=运算符重载,尾插字符string& operator+=(const char* str);//+=运算符重载,尾插字符串//test5size_t find(char ch, size_t pos = 0);//查找某个字符size_t find(const char* str, size_t pos = 0);//查找某个字符串//test6void insert(size_t pos, char ch);//指定位置插入字符void insert(size_t pos, const char* str);//指定位置插入字符串void erase(size_t pos = 0, size_t len = npos);//指定位置删除字符串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 clear();//清理函数private:char* _str;//字符串首地址size_t _size;//有效个数size_t _capacity;//容量static const size_t npos;};istream& operator>> (istream& is, string& str);//重载输入ostream& operator<< (ostream& os, string& str);//重载输出 }
string.cpp
#define _CRT_SECURE_NO_WARNINGS #include"blogstring.h" namespace blog {const size_t string::npos = -1;//构造函数string::string(const char* str):_size(strlen(str)){_capacity = _size;//更新容量_str = new char[_capacity + 1];//开辟size个char类型的空间strcpy(_str, str);//strcpy(char* 目标字符串,char* 源字符串)函数,传入两个地址,将源字符串的内容复制到目标字符串中}//拷贝构造函数string::string(const string& str){_str = new char[str._capacity + 1];//开辟新的空间_size = str._size;_capacity = str._capacity;strcpy(_str, str._str);}//析构函数string:: ~string(){_size = _capacity = 0;delete[] _str;_str = nullptr;}//返回_sizesize_t string::size() const{return _size;}//返回_capacitysize_t string::capacity(){return _capacity;}//begin函数string::iterator string::begin(){return _str;}//end函数string::iterator string::end(){return _str + _size;}//const begin函数string::const_iterator string::begin()const{return _str;}//const end函数string::const_iterator string::end()const{return _str + _size;}//赋值运算符重载string& string::operator=(const string& s){//这里是取地址操作符if (this != &s) {_capacity = s._capacity;//更改capacity_size = s._size;//更改_sizechar* tep = new char[_capacity + 1];//开辟一样的空间strcpy(tep, s._str);//将内容复制过去delete[] _str;_str = tep;}return *this;}//访问运算符重载char& string::operator[](size_t pos){assert(pos >= 0 && pos < _size);return *(_str + pos);}//const 访问运算符重载const char& string::operator[](size_t pos) const{assert(pos >= 0 && pos < _size);return *(_str + pos);}//预留空间模拟实现void string::reserve(size_t n){if (n > _capacity){_capacity = n;char* tep = new char[_capacity + 1];strcpy(tep, _str);delete[] _str;_str = tep;}}//尾插字符void string::push_back(char ch){//判断是否需要扩容if (_size == _capacity) {size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}_str[_size] = ch;_str[_size + 1] = '\0';++_size;}//尾插字符串void string::append(const char* str){size_t len = strlen(str);//计算字符串多长,方便修改size//判断是否需要扩容if (_size == _capacity) {size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}strcpy(_str + _size, str);_size += len;}//+=运算符重载,尾插字符string& string::operator+=(char ch){push_back(ch);return *this;}//+=运算符重载,尾插字符串string& string::operator+=(const char* str){append(str);return *this;}//查找某个字符size_t string::find(char ch, size_t pos){for (size_t i = pos; i < _size; i++){if (*(_str + i) == ch){return i;}}return npos;}//查找某个字符串size_t string::find(const char* str, size_t pos){char* p = strstr(_str + pos, str);if (p == nullptr)return npos;return p - _str;}//指定位置插入字符void string::insert(size_t pos, char ch){assert(pos <= _size);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;}//指定插入字符串void 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){_str[end + len] = _str[end];--end;}memcpy(_str + pos, str, len);_size += len;}//指定位置删除void string::erase(size_t pos, size_t len){assert(pos < _size);// len 大于剩余字符个数时,有多少删多少if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{// 使用 memmove 避免内存重叠问题memmove(_str + pos, _str + pos + len, _size - pos - len + 1);_size -= len;}}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) == 0;}bool string::operator!=(const string& s) const{return !(*this == s);}//清理函数void string::clear(){_str[0] = '\0';_size = 0;}//输入重载istream& operator>> (istream& is, string& str){str.clear();char ch = is.get();while (ch != ' ' && ch != '\n'){str += ch;ch = is.get();}return is;}//输出重载ostream& operator<< (ostream& os,string& str){for (size_t i = 0; i < str.size(); i++){os << str[i];}return os;} }
test.cpp
这里包含了测试用例
#define _CRT_SECURE_NO_WARNINGS #include"blogstring.h" void test1() {blog::string s1("hello world");//测试构造函数cout << s1<<endl;blog::string s2(s1);//测试拷贝构造函数cout << s2 << endl;blog::string s3(s2);s1.~string();//测试析构函数cout << s1.capacity()<<' ' << s1.size() << endl;//测试size与capacity函数 }void test2() {blog::string s1("hello world");printf("%p\n", s1.begin());//测试beginprintf("%p\n", s1.end());//测试endconst blog::string s2(s1);printf("%p\n", s2.begin());//测试beginprintf("%p\n", s2.end());//测试end }void test3() {blog::string s1("hello world");s1 = "ming ri fang zhou";//测试赋值运算符重载cout << s1 << endl;for (size_t i = 0; i < s1.size(); i++){cout << s1[i] << ' ';//测试[]运算符重载}cout << endl;const blog::string s2("nihao");for (size_t i = 0; i < s2.size(); i++){cout << s2[i] << ' ';//测试[]运算符重载} }void test4() {blog::string s1("hello world");cout << s1.capacity() << endl;s1.reserve(200);//测试reservecout << s1.capacity() << endl;s1.push_back('x');//测试push_backs1.append("oppo");//测试appends1 += 'p';//测试+=运算符重载s1 += "lll";//测试+=运算符重载cout << s1 << endl;}void test5() {blog::string s1("hello world");int tep1 = s1.find('l');int tep2 = s1.find("or");cout << tep1<<' '<<tep2<<endl; }void test6() {blog::string s1;s1.insert(0, 'x');cout << s1 << endl;s1.insert(1, "xxxx");cout << s1 << endl;s1.erase(1);cout << s1 << endl; } int main() {//test1();//test2();//test3();//test4();//test5();//test6(); }