📃博客主页: 小镇敲码人
💚代码仓库,欢迎访问
🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌏 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎
❤️ 什么?你问我答案,少年你看,下一个十年又来了 💞 💞 💞
【C++STL】string类的简单模拟实现
- 🚏 string类的成员变量及相关函数设计
- 🚏 string类的构造函数的实现
- ⛽️ 无参构造函数
- ⛽️ string类的析构函数的实现
- ⛽️ string类的其它构造函数
- 🚀 赋值运算符重载函数
- 🚀 拷贝构造函数和其它构造函数
- 🚏 string类迭代器的实现
- 🚏 string类Capacity函数的实现
- ⛽️ 较为基础的几个函数
- ⛽️ clear清除函数
- ⛽️ resize函数
- ⛽️ shrink_to_fit 函数
- 🚏 string类访问操作符的实现
- 🚏 string类Modifiers函数的实现
- ⛽️ operator+=函数
- ⛽️ append函数
- ⛽️ insert函数
- ⛽️ push_back函数
- 🚏 string类operations
- ⛽️ c_str函数
- ⛽️ substr函数
- ⛽️ find函数
- 🚏 非成员函数的实现
- ⛽️ operator+系列
- 🚏 测试函数
前言:本篇博客用到的C语言字符串操作函数和内存函数比较多,可以看一下博主这篇博客内存函数、字符串函数。代码仓库自取
🚏 string类的成员变量及相关函数设计
最重要的私有的成员变量只有三个:char*
用来存字符串、size_
保存有效字符的个数、capacity_
表示当前申请的空间的大小。还有一个是类公有的静态成员常数npos
,我们先照着文档和源码把string
类的一些函数接口和类型敲出来:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<map>
#include<string>
#include<stack>
#include<assert.h>using namespace std;namespace my_string
{class string{public:typedef char* iterator;//定义普通的正向迭代器类型typedef const char* const_iterator;//定义const的正向迭代器类型iterator begin(){return str_;//返回字符串首地址即可}iterator end(){return str_+size_;//返回字符串终止字符的地址即可}const_iterator begin() const{return str_;}const_iterator end() const{return str_+size_;}string()//无参数的构造函数{}string(const string& str);//拷贝构造函数string(const char* s);//使用c字符串构造string(size_t n, char c);//使用n个字符c来构造string类size_t size() const{return size_;}size_t length() const{return size_;}size_t capacity() const{return capacity_;}void resize(size_t n, char c = '\0');void shrink_to_fit();void clear();char& operator[] (size_t pos);const char& operator[] (size_t pos) const;string& operator+= (const string& str);string & operator+= (const char* s);string& operator+= (char c);string& append(const string& str);string& append(const char* s);string& append(size_t n, char c);string& insert(size_t pos, const string& str);iterator insert(iterator p, char c);void push_back(char c);iterator erase(iterator p);string& erase(size_t pos = 0, size_t len = npos);const char* c_str() const{return this->str_;}size_t find(const char* str, size_t pos = 0);size_t find(const string& s, size_t pos = 0);string substr(size_t pos = 0, size_t len = npos);void swap(string& s);string& operator= (const string& str);void swap(string& x, string& y);~string();static const size_t npos = -1;private:char* str_ = nullptr;//保存字符串的个数,给缺省值size_t size_ = 0;size_t capacity_ = 0;};istream& operator>> (istream& is, string& str);ostream& operator<< (ostream& os, const string& str);string operator+ (const string& lhs, const string& rhs);string operator+ (const string & lhs, const char* rhs);string operator+ (const char* lhs, const string& rhs);string operator+ (const string& lhs, char rhs);string operator+ (char lhs, const string& rhs);istream& getline(istream& is, string& str);void swap(string& x, string& y);void Test_string1();//测试函数
}
类的分文件编程,较短的函数可以在头文件里面直接实现,类里面的函数默认是隐式内联函数。但分文件编程,其它非内联函数要老老实实定义和实现分离。
类的份文件编程,要在.cpp
文件的函数名前面加上对于的类名,因为类对它的函数有类域的限制和命名空间类似,同样的如果你之前整个类在一个命名空间
里声明,在.cpp
文件中同样要加上那命名空间,或者是打开那个命名空间
。
🚏 string类的构造函数的实现
⛽️ 无参构造函数
string()//无参数的构造函数{}
⛽️ string类的析构函数的实现
直接释放空间即可。
string::~string(){size_ = 0;capacity_ = 0;delete[] str_;str_ = nullptr;}
⛽️ string类的其它构造函数
🚀 赋值运算符重载函数
我们实现了1个版本:
这里我们使用了现代版本,去复用刚刚实现的拷贝构造函数(深拷贝),再把深拷贝出来的类和我们自己的类交换一下内容即可,效率上相差不大,因为我们没有使用引用计数的方式,深拷贝是避不开的。
代码实现:
string& string::operator= (const string& str){string tmp(str);tmp.swap(*this);return *this;}
🚀 拷贝构造函数和其它构造函数
1.拷贝构造函数。
string::string(const string& str)//拷贝构造函数{str_ = new char[str.capacity_+1];//给`\0`多开一个空间strcpy(str_, str.str_);capacity_ = str.capacity_;size_ = str.size_;}
2.其它两个构造函数的实现:
string::string(const char* s)//使用c字符串构造{size_ = strlen(s);capacity_ = size_;str_ = new char[capacity_ + 1];memcpy(str_, s, size_ + 1);//\0也要拷贝进去}string::string(size_t n, char c)//使用n个字符c来构造string类{size_ = n;capacity_ = size_;str_ = new char[capacity_ + 1];//给\0预留一个空间memset(str_, c, n);//逐字节初始化str_[size_] = '\0';}
🚏 string类迭代器的实现
string类的迭代器比较简单,不需要重新创建一个类,当然如果我们想要实现反向迭代器的话需要自己重新实现一个迭代器的类,这里我们为了省事,就直接用原生指针代替了,因为string
类型本质是使用字符数组存储的,空间连续,++和–操作不需要我们重新来设计。
typedef char* iterator;//定义普通的正向迭代器类型typedef const char* const_iterator;//定义const的正向迭代器类型iterator begin(){return str_;//返回字符串首地址即可}iterator end(){return str_+size_;//返回字符串终止字符的地址即可}const_iterator begin() const{return str_;}const_iterator end() const{return str_+size_;}
const迭代器不能修改其指向的内容,直接加上const
即可。
🚏 string类Capacity函数的实现
这几个函数我们只实现了一部分,因为有些比较简单亦或是比较困难,我们自己手撕string
是为了更好的了解这个类。
⛽️ 较为基础的几个函数
加const
是为了防止这些string
类的变量被修改。
size_t size() const{return size_;}size_t length() const{return size_;}size_t capacity() const{return capacity_;}
⛽️ clear清除函数
这个函数没有清理空间,仅仅将size_置为了0。
void string::clear(){size_ = 0;str_[0] = '\0';}
⛽️ resize函数
这个函数的功能我们上一篇博客已经介绍过了,就是改变类的size_
,同时可能会给其初始化,但是只能初始化原先没有存在的位置,可能会存在扩容,但是不会缩容。
实现代码:
void string::resize(size_t n, char c){if (n > size_){//先考虑是否扩容if (n > capacity_)//需要扩容{char* tmp = new char[n+1];memcpy(tmp, str_,size_);//把之前的内容拷贝进来memset(tmp + size_, c, n - size_);//初始化其余的空间size_ = n;capacity_ = n;tmp[size_] = '\0';delete[] str_;//把之前空间的内存释放掉str_ = tmp;}else//不需要扩容,直接初始化后面空间的内容即可{memset(str_ + size_,c, n - size_);size_ = n;str_[size_] = '\0';}}else//n < size_,只需要改size_和\0{size_ = n;str_[size_] = '\0';}}
⛽️ shrink_to_fit 函数
库里面的这个函数是用来让size_和capacity_的值保持基本一致的,但是库里面的容量的值基本都定了,所以有时候当我们的size_
和capacity
接近时是不会缩容的,但是我们自己实现就不用这么细致了。
直接缩容就完事。
void string::shrink_to_fit(){char* tmp = new char[size_ + 1];capacity_ = size_;memcpy(tmp, str_,size_ + 1);//把原先的字符串(包括\0)都拷贝给tmpdelete[] str_;//释放原来空间的内存str_ = tmp;}
``
🚏 string类访问操作符的实现
operator[]
函数的实现,我们实现了const
和非const
两个版本。
代码实现
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];}
at
函数需要抛异常,其实和operator[]
函数类似,只不过at
函数的检查更严格,这里我们不再实现。
🚏 string类Modifiers函数的实现
⛽️ operator+=函数
这个函数和append
追加函数类似,都是在字符串结尾添加内容,本质上就是一些内容的拷贝,代码不难理解,如果重新开空间不要忘了将原先的内容拷贝进来,并释放空间。
我们实现了三个版本:
string& string::operator+= (const string& str){//看是否需要扩容if (str.size() + size_ > capacity_)//需要{char* tmp = new char[str.size() + size_ + 1];//保存之前的字符串if(str_)memcpy(tmp, str_, size_);if(str.str_)memcpy(tmp+size_, str.str_, str.size_+1); //str(包括\0)都拷贝给tmp//更新成员变量size_ += str.size();capacity_ = size_;delete[] str_;//释放之前申请的的空间str_ = tmp;}else//不需要{memcpy(str_ + size_, str.str_, str.size_+1);//把\0也copy进去size_ += str.size();str_[size_] = '\0';}return *this;}string& string::operator+= (const char* s){assert(s != NULL);int length = strlen(s);//看是否需要扩容if (length + size_ > capacity_)//需要{char* tmp = new char[size_ + length + 1];//保存之前的字符串if(str_ != nullptr)memcpy(tmp,str_, size_);memcpy(tmp + size_,s, length + 1);字符串s(包括\0)都拷贝给tmp//更新成员变量size_ += length ;capacity_ = size_;delete[] str_;//释放之前申请的的空间str_ = tmp;}else//不需要{memcpy(str_ + size_,s, length + 1);//把\0也copy进去size_ += length;}return *this;}string& string::operator+= (char c){//先看是否需要扩容if (size_ + 1 > capacity_){capacity_ = capacity_ == 0 ? 4 : capacity_ * 1.5;//扩容到原先的1.5倍char* tmp = new char[capacity_ + 1];if(str_ != nullptr)//不能把空地址的内容拷贝到tmp中strcpy(tmp,str_);tmp[size_++] = c;tmp[size_] = '\0';if(str_ != nullptr)delete[] str_;str_ = tmp;}else{str_[size_++] = c;str_[size_] = '\0';}return *this;}
strcpy
函数在这里和memcpy
没有太大区别,但是内存拷贝函数memcpy
函数的应用范围更广。
⛽️ append函数
这个函数和operator+=
函数没有声明太大区别,都是字符串追加函数,我们同样实现了三个版本,对operator+=
函数进行了复用。
string& string::append(const string& str)//{*this += str;return *this;}string& string::append(const char* s){*this += s;return *this;}string& string::append(size_t n, char c){string tmp(n,c);*this += tmp;return *this;}
⛽️ insert函数
这个插入函数我们只实现了两个版本。
在某个位置之前插入string
类和在某个迭代器前插入一个字符:
string& string::insert(size_t pos, const string& str){//是否需要扩容if (size_ + str.size() > capacity_){capacity_ = size_ + str.size();//更新capacity_char* tmp = new char[capacity_ + 1];//重新申请空间strcpy(tmp,str_);//把之前的内容拷贝进来先把pos位置及其之后的字符后移memcpy(tmp + pos+str.size(), tmp + pos, size_-pos+1);//把\0也拷贝进去memcpy(tmp + pos, str.str_, str.size());delete[] str_;str_ = tmp;size_ += str.size();}else{size_ += str.size();//先把pos位置及其之后的字符后移memcpy(str_ + pos + str.size(),str_ + pos,size_ - pos + 1);//把\0也拷贝进去memcpy(str_ + pos, str.str_, str.size());}return *this;}string::iterator string::insert(iterator p, char c){//需要扩容int pos_ = p - begin();//提前保存,防止迭代器失效if (size_ == capacity_){capacity_ = capacity_ == 0 ? 4 : capacity_ * 1.5;char* tmp = new char[capacity_+1];strcpy(tmp, str_);int pos = pos_;for (int i = size_; i >= pos; --i)//包括\0都后移一位tmp[i + 1] = tmp[i];tmp[pos] = c;delete[] str_;str_ = tmp;size_++;}else{int pos = pos_;for (int i = size_; i >= pos; --i)//包括\0都后移一位str_[i + 1] = str_[i];size_++;}return str_+pos_;}
对第一个不是迭代器版本的关键代码做一下解释:
⛽️ push_back函数
在字符串结尾尾插一个字符,实现价值不大。但我们还是实现了一下,复用了一下insert
函数:
void string::push_back(char c){insert(end(), c);}
🚏 string类operations
这上面的函数我们只实现了比较重要的几个:
⛽️ c_str函数
返回c字符串,比较简单:
const char* c_str() const{return this->str_;}
因为这是string
类的字符串,所以返回值是const
类型防止你随便修改。
⛽️ substr函数
这个函数的功能我们再上篇博客已经谈到了,这里主要阐述一下这个string
切割函数应该如何实现。
实现很简单,分为两种情况即可,
1、len
的长度大于pos
后面的字符,这种情况把后面的字符都copy
给我们新开的字符数组
2、len
的长度小于剩余的字符长度,copy
对应长度即可。
代码实现:
string string::substr(size_t pos, size_t len){string s;if (len == string::npos || pos+len > size_)//如果{char* tmp = new char[size_ - pos + 1];//给\0也要开空间memcpy(tmp, str_ + pos, size_ - pos + 1);//把pos及其后面的字符复制给tmps = tmp;//赋值}else{char* tmp = new char[len + 1];memcpy(tmp, str_ + pos, len);tmp[len] = '\0';s = tmp;}return s;}
⛽️ find函数
我们只实现了两个版本,复用了一下substr
函数,并使用了字符串比较函数strcmp
,逻辑比较简单,就是简单的遍历。
代码实现:
size_t string::find(const char* str, size_t pos){int len = strlen(str);//计算出str字符串的长度int pos_ = -1;for (int i = pos; i < size_ - len; ++i)//从pos位置开始遍历,找接下来的子串是否含有str{string tmp = substr(i, len);//复用string切割函数if (strcmp(tmp.str_, str) == 0)//字符串比较函数来比较{pos_ = i;break;}}return pos_ == -1 ? string::npos : pos_;//如果没有找到,就返回npos}size_t string::find(const string& s, size_t pos){int pos_ = -1;//为-1,代表不存在for (int i = pos; i < size_ - s.size(); ++i)//从pos开始遍历找{string tmp = substr(i,s.size());if (strcmp(s.str_,tmp.str_) == 0)//比较{pos_ = i;//找到了返回起始位置,breakbreak;}}return pos_ == -1 ? string::npos : pos_;}
🚏 非成员函数的实现
⛽️ operator+系列
这个函数是运算符+
的重载,我们可以复用operator+=
函数。
代码实现:
string operator+ (const string& lhs, const string& rhs){string tmp(lhs);//拷贝构造tmp += rhs;//赋值运算符重载return tmp;}string operator+ (const string& lhs, const char* rhs){string tmp(lhs);//拷贝构造tmp += rhs;//赋值运算符重载return tmp;}string operator+ (const char* lhs, const string& rhs){string tmp(lhs);//拷贝构造tmp += rhs;//赋值运算符重载return tmp;}string operator+ (const string& lhs, char rhs){string tmp(lhs);//拷贝构造tmp += rhs;//赋值运算符重载return tmp;}string operator+ (char lhs, const string& rhs){string tmp(1, lhs);//调用构造函数tmp += rhs;//赋值运算符重载return tmp;}
注意:这里返回值不能传引用,因为tmp
出来作用域就销毁了。
🚏 测试函数
void Test_string1()//测试迭代器和构造函数以及赋值运算符重载{string s1;//默认构造string s2("520");string s3(s2);//拷贝构造string s4(4, 'a');cout << "s1: " << s1 << "s2: " << s2 << " s3: " << s3 << " s4:" << s4 << endl;//<< 操作符重载s1 = s3;//赋值运算符cout << "s1: " << s1 << "s2:" << s2 << " s3: " << s3 << " s4:" << s4 << endl;//<< 操作符重载cin >> s1;cout << "s1: " << s1 << "s2:" << s2 << " s3: " << s3 << " s4:" << s4 << endl;//<< 操作符重载string s5;//+=操作符重载s5 += '1';s5 += "314";s5 += s2;cout << "s5: " << s5 << endl;//+操作符重载string firstlevel("com");string secondlevel("cplusplus");string scheme("http://");string hostname;string url;hostname = "www." + secondlevel + '.' + firstlevel;url = scheme + hostname;cout << "url: " << url << '\n';//insert、erase函数string s6("1520");string s7("314");s6.insert(1, s7);cout << "s6: " << s6 << endl;s6.insert(s6.begin(), 'w');cout << "s6: " << s6 << endl;s6.erase(s6.begin());cout << "s6: " << s6 << endl;s6.erase(s6.end());cout << "s6: " << s6 << endl;s6.erase(0, 4);cout << s6 << endl;//find函数string s8("xxxxxlovexxxxxlovexxxxlove");string str1("lovexxxxxx");const char* str2 = "xxl";size_t pos1 = s8.find(str1, 0);//从下标7开始往后找if (pos1 != string::npos){cout << "找到了!!!!首次出现的位置是" << pos1 << endl;}else{cout << "没有找到" << endl;}size_t pos2 = s8.find(str2, 0);//从下标7开始往后找if (pos2 != string::npos){cout << "找到了!!!!位置是" << pos2 << endl;}else{cout << "没有找到" << endl;}//substrstring s9("love and help");cout << "s9:" << s9.substr(9, 4) << endl;//从s的下标9位置开始的连续4个字符,help//append函数string str;string str4("Writing ");string str5("print 10 and then 5 more");// used in the same order as described above:str.append(str4); // "Writing "str.append("here: "); // "here: "str.append(10u, '.'); // ".........."cout << "str:" << str << '\n';//swap函数cout << "s1: " << s1 << " s2: " << s2 << endl;swap(s1, s2);cout << "s1: " << s1 << " s2: " << s2 << endl;//resize、shrink_to_fitstring s10 = "reserve";cout << "size: " << s10.size() << "capacity: " << s10.capacity() << endl;s10.shrink_to_fit();cout << "size: " << s10.size() << "capacity: " << s10.capacity() << endl;s10.resize(1000, '5');cout << "size: " << s10.size() << "capacity: " << s10.capacity() << endl;s10.shrink_to_fit();cout << "size: " << s10.size() << "capacity: " << s10.capacity() << endl;//size、lengthstring s11 = "xxxxxx";cout << "size: " << s11.size() << endl;cout << "length: " << s11.length() << endl;//capacitystring s12("xxxxxxxxxxxxx");int pre = s12.capacity();for (int i = 0; i < 100000; i++){s12 += 'x';if (pre < s12.capacity()){cout << "i: " << i << "capacity: " << "增加的字节倍数:" << 1.0 * s12.capacity() / pre << " " << s12.capacity() << endl;pre = s12.capacity();}}//clear函数s12.clear();cout << "s12: " << s12 << endl;}
运行结果: