C++ -- string类的模拟实现
// 欢迎来到 aramae 的博客,愿 Bug 远离,好运常伴! //
博主的Gitee地址:阿拉美 (aramae) - Gitee.com
![]()
时代不会辜负长期主义者,愿每一个努力的人都能达到理想的彼岸。
- string类的模拟实现
- 扩展阅读
引言: 本章模拟实现string类,包括string 的构造函数,析构函数的部分接口,等到C++学习到一定程度上后会逐渐补充STL源码剖析书中的内容,敬请期待。另外补充部分扩展阅读。
1.string类的模拟实现
注意:本部分的string的模拟实现是根据string标准库来逐步实现的,目的不是为了实现一个更好的string类,只是通过其底层的实现,加深对string的理解,只实现部分重要的函数。
这里的实现顺序和上一章介绍的顺序一致,避免混乱;文章末尾会给出完整的实现代码+测试代码通过本章的模拟,加深类和对象的理解和运用,如有遗忘,请及时复习。
C++ string 类(标准库中的string类)-CSDN博客
C++ 类和对象(上)(类的定义,类的访问限定及封装...)-CSDN博客
C++ 类和对象(中) (默认成员函数,构造函数,析构函数......)-CSDN博客
C++ 类和对象(下)(初始化列表,static成员,友元......)-CSDN博客
一.成员函数(Member functions):
1.1 构造函数 construcotrs
-
string();
-
string(const char* str);
-
char* c_str();
-
string(const string& str);
这里我们的模拟实现包含 无参默认构造,全缺省构造,拷贝构造函数
代码实现如下:
//我们知道默认构造函数包括三部分://无参构造函数,全缺省构造函数,编译器自己生成的默认构造//第一种:无参构造(实现)string():_size(0),_capacity(0),_str(new char[1]{'\0'}){ }//C++风格:string() : _size(0), _capacity(0), _str(new char[1] {'\0'}) {}// 第二种:全缺省构造函数(实现)string(const char* str=""):_size(strlen(str)), _capacity(_size), _str(new char[_capacity + 1]){strcpy(_str, str);}//拷贝构造函数string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}
那么看上面的代码,你会发现我们使用string标准库里的默认构造后初始化后的字符串是没有 ‘\0’的但这里的实现是在初始化后结尾加‘\0’的,为什么?这里是为了简化了 c_str() 的实现(无需动态添加 '\0'),通过预分配 1 字节存储 '\0'
,确保 _str
始终是合法的 C 风格字符串自定义实现的选择:这里的代码选择了一种保守实现,确保 _str
始终指向一个合法的 C 风格字符串。
总结:自定义 string
类的默认构造函数和标准库 std::string
的行为差异源于实现方式的不同
其中差异体现在:
- 自定义实现需要分配空间并存储
'\0'
- 标准库的
std::string
在默认构造后不包含'\0'
标准库的优化:std::string
追求效率,在空字符串时不存储任何字符,仅在调用 c_str()
时动态添加 '\0'
。这样可以节省内存并减少初始化开销
那么我们 c_str 的实现就变得十分容易简洁:
//实现c_str接口const char* c_str() const{return _str;}
可以看一下具体的差异体现:
当然,你也可以模拟标准库的实现,但是C 风格接口兼容性需额外处理
- 若直接传递空字符串的指针(未调用
c_str()
),可能导致野指针问题,需确保接口正确处理
下面是模仿标准库的实现:
class string {
private:char* _str;size_t _size;size_t _capacity;public:string() : _str(nullptr), _size(0), _capacity(0) {} // 初始化为空指针~string() {delete[] _str; // 处理 nullptr 的情况}const char* c_str() const {if (_size == 0) return ""; // 空字符串返回静态常量 ""return _str;}
};
对应的 c_str 接口:
const char* c_str() const {if (_size == 0) {return ""; // 返回静态常量 ""(即 "\0")}return _str; // 非空字符串直接返回内部指针}
1.2 析构函数destructor
-
~string();
//析构函数~string(){delete[] _str;_str = nullptr;_capacity = _size = 0;}
1.3 operator=
- string& operator=(const string& str)
传统写法:
string& operator=(const string& s){if (this == &s){return *this;}delete[] _str;_size = s._size;_capacity = s._capacity;_str = new char[_capacity + 1];strcpy(_str, s._str);}
现代写法:
void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}/*string& operator=(const string& s){if (this != &s){string tmp(s);//this->swap(tmp);swap(tmp);}return *this;}*/string& operator=(string tmp){swap(tmp);return *this;}
二. string遍历操作(iterators):
这里简单实现一下就行
//迭代器 + 遍历操作(含下标[]访问)接口typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}const_iterator begin()const{return _str;}iterator end(){return _str + _size;}const_iterator end() const{return _str + _size;}size_t size()const{return _size;}char& operator[](size_t pos){assert( pos < _size);return _str[pos];}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}
void test2()
{aramae::string s1("hello world!!!");for (size_t i = 0; i < s1.size(); i++){cout << s1[i] << " ";}cout << endl;const aramae::string s3("aramae");//aramae::string::const_iterator cit = s3.begin();auto cit = s3.begin();while (cit != s3.end()){cit += 1;cout << *cit << " ";++cit;}cout << endl;aramae::string::iterator it = s1.begin();while (it != s1.end()){*it += 1;cout << *it << " ";++it;}cout << endl;for (auto ch : s1){cout << ch << " ";}cout << endl;}
三. string类对象的容量操作 (Capacity):
- size_t size()const;
size_t size()const{return _size;}
- void resize(size_t n, char ch = '\0');
void resize(size_t n, char ch = '\0'){if (n < _size){_size = n;_str[_size] = '\0';}else{reserve(n);for (size_t i = _size; i < n; i++){_str[i] = ch;}_size = n;_str[_size] = '\0';}}
- void reserve(size_t n);
void reserve(size_t n){if (n > _capacity){cout << "reserve()->" << n << endl;char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}
- void clear();
void clear(){_str[0] = '\0';_size = 0;}
四. string类对象的修改操作(Modifiers):
- void push_back(char ch);
void push_back(char ch){if (_size == _capacity){//2倍扩容reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0';}
- void append(const char* str);
void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){//至少扩容到_size+lenreserve(_size + len);}strcpy(_str + _size, str);_size += len;}
- string& operator+=(char ch);
string& operator+=(char ch) {push_back(ch);return *this;}
- string& operator+=(const char* str);
string& operator+=(const char* str){append(str);return *this;}
- void insert(size_t pos, size_t n, char ch);
void insert(size_t pos, size_t n, char ch){assert(pos <= _size);if (_size + n > _capacity){//至少扩容到_size+lenreserve(_size + n);}//挪动数据/* int end = _size;while (end >= (int)pos){_str[end + n] = _str[end];--end;}*/size_t end = _size;while (end >= pos && end != npos){_str[end + 1] = _str[end];--end;}for (size_t i = 0; i < n; i++){_str[pos + 1] = ch;}_size += n;}
- void insert(size_t pos, const char* str);
void insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){//至少扩容到_size+lenreserve(_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];}_str += len;}
- void erase(size_t pos, size_t len = npos);
void erase(size_t pos, size_t len = npos){assert(pos <= _size);if (len == npos || pos + len >= _size){_size = pos;_str[_size] = '\0';}else{size_t end = pos + len;while (end <= _size){_str[pos++] = _str[end++];}_size -= len;}}
- size_t find(char ch, size_t pos = 0);
size_t find(char ch, size_t pos = 0){assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}
- size_t find(const char* str , size_t pos = 0);
size_t find(const char* str, size_t pos = 0){assert(pos < _size);const char* ptr = strstr(_str + pos, str);if (ptr){return ptr - _str;}else{return npos;}}
- string substr(size_t pos = 0, size_t len = npos);
string substr(size_t pos = 0, size_t len = npos){assert(pos < _size);size_t n = len;if (len == npos || pos + len > _size){n = _size - pos;}string tmp;tmp.reserve(n);for (size_t i = pos; i < pos + n; i++){tmp += _str[i];}return tmp;}
五. string类非成员函数(Non-member function overloads):
- bool operator<(const string& s) const;
bool operator<(const string& s) const{int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);return ret == 0 ? _size < s._size : ret < 0;}
- bool operator==(const string& s) const;
bool operator==(const string& s) const{return _size == s._size&& memcmp(_str, s._str, _size) == 0;}
- bool operator<=(const string& s) const;
bool operator<=(const string& s) const{return *this < s || *this == s;}
- bool operator>(const string& s) const;
bool operator>(const string& s) const{return !(*this <= s);}
- bool operator>=(const string& s) const;
bool operator>=(const string& s) const{ return !(*this < s);}
- bool operator!=(const string& s) const;
bool operator!=(const string& s) const{ return !(*this == s);}
- ostream& operator<<(ostream& out, const string& s);
ostream& operator<<(ostream& out, const string& s){/*for (size_t i = 0; i < s.size(); i++){out << s[i];}*/for (auto ch : s){out << ch;}return out;}
-
istream& operator>>(istream& in, string& s);
istream& operator>>(istream& in, string& s){s.clear();char ch = in.get();// 处理前缓冲区前面的空格或者换行while (ch == ' ' || ch == '\n'){ch = in.get();}//in >> ch;char buff[128];int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}//in >> ch;ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in;}
string.h .cpp
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<assert.h>
#include<iostream>
using namespace std;namespace aramae
{class string {public://我们知道默认构造函数包括三部分://无参构造函数,全缺省构造函数,编译器自己生成的默认构造//第一种:无参构造(实现)/* string():_size(0),_capacity(0),_str(new char[1]{'\0'}){ }*///C++风格:string() : _size(0), _capacity(0), _str(new char[1] {'\0'}) {}// 第二种:全缺省构造函数(实现)string(const char* str=""):_size(strlen(str)), _capacity(_size), _str(new char[_capacity + 1]){strcpy(_str, str);}//实现c_str接口const char* c_str() const{return _str;}// 转换构造函数//string(const char* str)// :_size(strlen(str))// , _capacity(_size)// , _str(new char[_capacity + 1])//{// strcpy(_str, str);//}//拷贝构造函数string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}//析构函数~string(){delete[] _str;_str = nullptr;_capacity = _size = 0;}string& operator=(const string& s){if (this == &s){return *this;}delete[] _str;_size = s._size;_capacity = s._capacity;_str = new char[_capacity + 1];strcpy(_str, s._str);}//void swap(string& s)//{// std::swap(_str, s._str);// std::swap(_size, s._size);// std::swap(_capacity, s._capacity);//}//string& operator=(const string& s)//{// if (this != &s)// {// string tmp(s);// //this->swap(tmp);// swap(tmp);// }// return *this;//}//string& operator=(string tmp)//{// swap(tmp);// return *this;//}//迭代器 + 遍历操作(含下标[]访问)typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}const_iterator begin()const{return _str;}iterator end(){return _str + _size;}const_iterator end() const{return _str + _size;}size_t size()const{return _size;}char& operator[](size_t pos){assert( pos < _size);return _str[pos];}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}//string 容器操作void resize(size_t n, char ch = '\0'){if (n < _size){_size = n;_str[_size] = '\0';}else{reserve(n);for (size_t i = _size; i < n; i++){_str[i] = ch;}_size = n;_str[_size] = '\0';}}void reserve(size_t n){if (n > _capacity){cout << "reserve()->" << n << endl;char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void clear(){_str[0] = '\0';_size = 0;}//string 修改操作void push_back(char ch){if (_size == _capacity){//2倍扩容reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0';}void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){//至少扩容到_size+lenreserve(_size + len);}strcpy(_str + _size, str);_size += len;}string& operator+=(char ch) {push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}void insert(size_t pos, size_t n, char ch){assert(pos <= _size);if (_size + n > _capacity){//至少扩容到_size+lenreserve(_size + n);}//挪动数据/* int end = _size;while (end >= (int)pos){_str[end + n] = _str[end];--end;}*/size_t end = _size;while (end >= pos && end != npos){_str[end + 1] = _str[end];--end;}for (size_t i = 0; i < n; i++){_str[pos + 1] = ch;}_size += n;}void insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){//至少扩容到_size+lenreserve(_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];}_str += len;}void erase(size_t pos, size_t len = npos){assert(pos <= _size);if (len == npos || pos + len >= _size){_size = pos;_str[_size] = '\0';}else{size_t end = pos + len;while (end <= _size){_str[pos++] = _str[end++];}_size -= len;}}size_t find(char ch, size_t pos = 0){assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}size_t find(const char* str, size_t pos = 0){assert(pos < _size);const char* ptr = strstr(_str + pos, str);if (ptr){return ptr - _str;}else{return npos;}}string substr(size_t pos = 0, size_t len = npos){assert(pos < _size);size_t n = len;if (len == npos || pos + len > _size){n = _size - pos;}string tmp;tmp.reserve(n);for (size_t i = pos; i < pos + n; i++){tmp += _str[i];}return tmp;}//string 非成员函数//bool operator<(const string& s)//{// size_t i1 = 0;// size_t i2 = 0;// while (i1 < _size && i2 < s._size)// {// if (_str[i1] < s._str[i2])// {// return true;// }// else if (_str[i1] > s._str[i2])// {// return false;// }// else// {// ++i1;// ++i2;// }// }// /*if (i1 == _size && i2 != s._size)// {// return true;// }// else// {// return false;// }*/// //return i1 == _size && i2 != s._size;// return _size < s._size;//}bool operator<(const string& s) const{int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);return ret == 0 ? _size < s._size : ret < 0;}bool operator==(const string& s) const{return _size == s._size&& memcmp(_str, s._str, _size) == 0;}bool operator<=(const string& s) const{return *this < s || *this == s;}bool operator>(const string& s) const{return !(*this <= s);}bool operator>=(const string& s) const{ return !(*this < s);}bool operator!=(const string& s) const{ return !(*this == s);}private:size_t _size;size_t _capacity;char* _str;public://const static size_t npos = -1; // 虽然可以这样用,但是不建议const static size_t npos;};const size_t string::npos = -1;ostream& operator<<(ostream& out, const string& s){/*for (size_t i = 0; i < s.size(); i++){out << s[i];}*/for (auto ch : s){out << ch;}return out;}istream& operator>>(istream& in, string& s){s.clear();char ch = in.get();// 处理前缓冲区前面的空格或者换行while (ch == ' ' || ch == '\n'){ch = in.get();}//in >> ch;char buff[128];int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}//in >> ch;ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in;}};
test.cpp
#include"string.h"void test1()
{aramae::string s1("hello world!!!");//aramae::string s4();//cout << s1.c_str() << endl;//std::string s2;//cout << s2.c_str() << endl;//aramae::string s3;//cout << s3.c_str() << endl;//aramae::string s5(s1);//cout << s5.c_str() << endl;aramae::string s6 = s1;cout << s6.c_str() << endl;}void test2()
{aramae::string s1("hello world!!!");for (size_t i = 0; i < s1.size(); i++){cout << s1[i] << " ";}cout << endl;const aramae::string s3("aramae");//aramae::string::const_iterator cit = s3.begin();auto cit = s3.begin();while (cit != s3.end()){cit += 1;cout << *cit << " ";++cit;}cout << endl;aramae::string::iterator it = s1.begin();while (it != s1.end()){*it += 1;cout << *it << " ";++it;}cout << endl;for (auto ch : s1){cout << ch << " ";}cout << endl;}void test3()
{aramae::string s1("hello world");cout << s1.c_str() << endl;s1.push_back(' ');s1.push_back('#');s1.append("hello bit");cout << s1.c_str() << endl;aramae::string s2("hello world");cout << s2.c_str() << endl;s2 += ' ';s2 += '#';s2 += "hello bit";cout << s2.c_str() << endl;
}void test4()
{aramae::string s1("helloworld");cout << s1.c_str() << endl;s1.insert(5, 3, '#');cout << s1.c_str() << endl;s1.insert(0, 3, '#');cout << s1.c_str() << endl;aramae::string s2("helloworld");s2.insert(5, "%%%%%");cout << s2.c_str() << endl;
}void test5()
{aramae::string s1("helloworld");cout << s1.c_str() << endl;s1.erase(5, 3);cout << s1.c_str() << endl;s1.erase(5, 30);cout << s1.c_str() << endl;s1.erase(2);cout << s1.c_str() << endl;
}void test6()
{aramae::string url = "ftp://www.baidu.com/?tn=65081411_1_oem_dg";size_t pos1 = url.find("://");if (pos1 != aramae::string::npos){aramae::string protocol = url.substr(0, pos1);cout << protocol.c_str() << endl;}size_t pos2 = url.find('/', pos1 + 3);if (pos2 != aramae::string::npos){aramae::string domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));aramae::string uri = url.substr(pos2 + 1);cout << domain.c_str() << endl;cout << uri.c_str() << endl;}
}void test7()
{aramae::string s("hello world");s.resize(8);cout << s.c_str() << endl;cout << s << endl;s.resize(13, 'x');cout << s.c_str() << endl;cout << s << endl;s.resize(20, 'y');cout << s.c_str() << endl;cout << s << endl;// c的字符数组,以\0为终止算长度// string不看\0,以size为终止算长度aramae::string ss("hello world");ss += '\0';ss += "!!!!!!!!!!";cout << ss.c_str() << endl;cout << ss << endl;aramae::string copy(ss);cout << ss << endl;cout << copy << endl;ss += "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";cout << ss << endl;
}void test8()
{aramae::string s;cin >> s;cout << s << endl;cin >> s;cout << s << endl;//char buff[128];//for (size_t i = 0; i < 5; i++)//{// cin >> buff[i];//}//for (size_t i = 0; i < 5; i++)//{// cout << buff[i] << endl;//}
}void test9()
{//string s1("bb");//string s2("aaa");//cout << (s1 < s2) << endl;aramae::string s1("hello");aramae::string s2("hello");cout << (s1 < s2) << endl;cout << (s1 > s2) << endl;cout << (s1 == s2) << endl << endl;aramae::string s3("hello");aramae::string s4("helloxxx");cout << (s3 < s4) << endl;cout << (s3 > s4) << endl;cout << (s3 == s4) << endl << endl;aramae::string s5("helloxxx");aramae::string s6("hello");cout << (s5 < s6) << endl;cout << (s5 > s6) << endl;cout << (s5 == s6) << endl << endl;
}void test10()
{aramae::string s1("hello");aramae::string s2(s1);cout << s1 << endl;cout << s2 << endl;aramae::string s3("xxxxxxxxxxxxx");s1 = s3;cout << s1 << endl;cout << s3 << endl;
}int main()
{//test1();//test2();//test3();//test4();//test5();//test5();//test6();//test7();//test8();//test9();test10();return 0;
}
2.扩展阅读
C++面试中string类的一种正确写法 | 酷 壳 - CoolShellhttps://coolshell.cn/articles/10478.htmlSTL 的string类怎么啦?_stl 字符串-CSDN博客文章浏览阅读4.5w次,点赞50次,收藏60次。 STL 的string类怎么啦?陈皓 前言 上个周末在和我的同学爬香山闲聊时,同学说到STL中的string类曾经让他备受折磨,几年前他开发一个系统前对string类还比较清楚,然后随着程序的复杂度的加深,到了后期,他几乎对string类失去了信心和信任,他觉得他对string类一头雾水。老实说,我几年前也有同样的痛苦(就是当我写下《标准C++类string的Copy-O_stl 字符串
https://blog.csdn.net/haoel/article/details/1491219
结语:感谢相遇
/// 高山仰止,景行行止。虽不能至,心向往之 ///