【C++】string的模拟实现
1.头文件介绍
1.类定义及成员变量
class string { public: typedef char* iterator; typedef const char* const_iterator; private: char* _str; size_t _size; size_t _capacity; char _buff[16]; public: static const size_t npos; };1.定义迭代器类型,便于进行字符串遍历。
2.成员变量:
1._str:指向动态分配字符串内存的指针
2._size:字符串的当前长度
3._capacity:字符串容量
4._buff[16]:小字符串优化(SSO)的缓冲区,当字符串长度小于16时使用栈内存
3.静态常量npos:通常定义为-1,因为它为size_t类型,所以是size_t最大值
拓展:buff的作用
第一种:短字符串(SSO启用)
std::string s ="short"长度为5<16,存在栈上_buff[16]中,没有在堆开辟空间,速度极快
第二种:长字符串(SSO关闭)
std::string s = "this is a long string more than 15 chars";长度大于15,会调用new[]/delete[]进行动态内存管理,在堆上开辟空间,速度较慢。
2.迭代器接口
iterator begin(); const_iterator cbegin() const; iterator end(); const_iterator cend() const;提供迭代器接口,就可以使用范围for遍历字符串,如for(auto e : s1)
3.构造函数、析构函数、拷贝赋值
string(const char* str = ""); string(const string& s); ~string(); string& operator=(const string& s); string& operator=(string tmp); void swap(string& s);构造函数:支持C字符串构造,默认构造(不传参会使用缺省值)
析构函数:释放动态申请的内存
拷贝构造:实现深拷贝,防止浅拷贝造成的程序崩溃
拷贝赋值运算符:使用swap函数拷贝并交换内容
4.容量和访问操作
size_t size() const; size_t capacity() const; const char* c_str() const; void reserve(size_t n=0); char& operator[](size_t pos); const char& operator[](size_t pos) const;size():返回字符串长度,有这个重载<<就不需要变成友元函数。
capacity():返回字符串容量
c_str():返回char* 指针
reserve():可以提前预留合适空间,避免反复扩容
operator[]:支持读写和只读字符串内容
5.字符串修改
void push_back(char c); void append(const char* str); string& operator+=(char c); string& operator+=(const char* str); void pop_back(); string& insert(size_t pos, char ch); string& insert(size_t pos, const char* str); string& erase(size_t pos, size_t len = npos); void clear();功能介绍
追加操作:push_back(),append(),operator+=(),在字符串末尾添加字符或者字符串
删除操作:pop_back()尾删一个字符,erase()在指定位置,删除指定字符,不指定默认删完插入操作:insert()在指定位置插入一个字符,或者一个字符串、
清空操作:clear()清空字符串内容
6.查找及提取子串操作
size_t find(char ch, size_t pos = 0) const; size_t find(const char* str, size_t pos = 0) const; string substr(size_t pos = 0, size_t len = npos) const;查找:find()查找指定位置的字符或者字符串,找到返回pos,否则返回npos
提取字串:substr()在指定位置,提权n个字符
7.比较运算符
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;实现字符串的比较,跟C语言的strcmp类似
8.流操作符及全局函数
ostream& operator<<(ostream& out, const string& s); istream& operator>>(istream& in, string& s); istream& getline(istream& in, string& s, char delim = '\n'); void swap(string& x, string& y);流操作符:用于输出/输入
getline():读取一行代码,遇到\n停止,可以改变delim,改变停止位置
全局swap():转发调用成员函数,提高效率
2.函数实现
1.构造函数和析构函数
string::string(const char* str) :_size(strlen(str)) { _capacity = _size; _str = new char[_size + 1]; memcpy(_str, str, _size + 1); }默认构造,用字符串常量构造string,不传参默认只有一个\0
在初始化列表定义,是按声明顺序进行定义的。所以为了提升效率,只使用一次strlen,只定义_size,_capacity直接用_size的值,_str要多开辟一个空间,存放\0,因为_size和_capacity都是不包括\0的。memcpy复制字符串给string,因为要有\0所以+1
string::string(const string& s) { _str = new char[s._capacity + 1]; memcpy(_str, s._str, s._size + 1); _size = s._size; _capacity = s._capacity; }拷贝构造,为了防止浅拷贝,所以需要开空间,进行深拷贝,否则程序会崩溃。但是这个是传统写法。
string::string(const string& s) { string tmp(s._str); swap(tmp); } void string::swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); }这个是现代写法,用s._str构造一个tmp,交换内容后,tmp会调用析构销毁自己,但是效率没有什么区别,都需要深拷贝,但是注意!不能写string tmp(s),这样会无限递归死循环。
string::~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; }析构函数:释放空间,指针置空,容量大小归零
string& string::operator=(const string& s) { if (this != &s) { char* tmp = new char[s._capacity + 1]; memcpy(tmp, s._str, s._size + 1); delete[] _str; _str = tmp; _size = s._size; _capacity = s._capacity; } return *this; }重载赋值运算符:深拷贝赋值,防止浅拷贝问题,将目标字符串完整拷贝给当前对象。这个是传统写法。
string& string::operator=(const string& s) { if (this != &s) { string tmp(s); swap(tmp); } return *this; } string& string::operator=(string tmp) { swap(tmp); return *this; }这是现代写法,代码更短
memcpy、memmove、strcpy的对比
对比维度 strcpy memcpy memmove 核心功能 专门用于字符串拷贝 通用内存块拷贝 通用内存块拷贝(支持重叠) 拷贝依据 遇到 \0(字符串结束符)停止按指定长度 n 拷贝,不识别 \0按指定长度 n 拷贝,不识别 \0内存重叠处理 不处理(重叠拷贝结果未定义) 不处理(重叠拷贝结果未定义) 完美处理(支持源和目标内存重叠) 适用数据类型 仅字符串(char*) 任意数据类型(int、char、结构体等) 任意数据类型(int、char、结构体等) 结束标志 自动拷贝到 \0并追加\0无结束标志,必须指定长度 无结束标志,必须指定长度 安全性 易缓冲区溢出(不检查长度) 需手动指定长度,可控性强 需手动指定长度,可控性强 典型场景 字符串赋值、拼接 数组、结构体、二进制数据拷贝 内存区域有重叠的数据移动 函数原型 char* strcpy(char* dest, const char* src);void* memcpy(void* dest, const void* src, size_t n);void* memmove(void* dest, const void* src, size_t n);
2.迭代器函数
string::iterator string::begin() { return _str; } string::const_iterator string::cbegin() const { return _str; } string::iterator string::end() { return _str + _size; } string::const_iterator string::cend() const { return _str + _size; }STL风格的迭代器,支持范围for
3.基本访问和容量函数
const char* string::c_str() const { return _str; } size_t string::size() const { return _size; } size_t string::capacity() const { return _capacity; } 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]; }
4.扩容函数
void 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; cout << "capacity:" << _capacity << endl; } }根据输入的值,创建新空间,并复制和释放旧空间,所以不要频繁扩容,效率低。
5.字符串修改函数
void string::push_back(char c) { if (_size >= _capacity) { size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity; reserve(newcapacity); } _str[_size] = c; ++_size; _str[_size] = '\0'; } void string::append(const char* str) { size_t len = strlen(str); if (len + _size > _capacity) { size_t newcapacity = 2 * _capacity > len + _size ? 2 * _capacity : len + _size; reserve(newcapacity); } memcpy(_str + _size, str, len + 1); _size += len; } void string::clear() { _str[0] = '\0'; _size = 0; } string& string::operator+=(char c) { push_back(c); return *this; } string& string::operator+=(const char* str) { append(str); return *this; } void string::pop_back() { assert(_size > 0); --_size; _str[_size] = '\0'; }push_back:末尾追加单个字符,容量不足则自动扩容,补字符串结束符
append:末尾拼接 C 风格字符串,按需扩容后批量拷贝字符
clear:清空字符串内容,重置长度为 0
operator+=(char):重载 +=,实现单个字符尾部追加
operator+=(const char):重载 +=,实现字符串尾部拼接
pop_back:删除末尾字符,非空才可执行,重置结束符位置
6.插入和删除函数
string& string::insert(size_t pos, char ch) { assert(pos <= _size); if (_size >= _capacity) { size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity; reserve(newcapacity); } size_t end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; --end; } _str[pos] = ch; ++_size; _str[_size] = '\0'; return *this; } string& string::insert(size_t pos, const char* str) { assert(pos <= _size); size_t len = strlen(str); if (len + _size >= _capacity) { size_t newcapacity = 2 * _capacity > len + _size ? 2 * _capacity : len + _size; reserve(newcapacity); } size_t end = _size + len; while (end > pos + len - 1) { _str[end] = _str[end - len]; --end; } for (size_t i = 0; i < len; i++) { _str[pos + i] = str[i]; } _size += len; return *this; } string& string::erase(size_t pos, size_t len) { assert(pos < _size); if (len == npos || len >= (_size - pos)) { _size = pos; _str[_size] = '\0'; } else { size_t i = pos + len; memmove(_str + pos, _str + i, _size + 1 - i); _size -= len; } return *this; }insert(pos, char):指定位置插入单个字符,按需扩容,后移字符,更新长度
insert(pos, str):指定位置插入字符串,按需扩容,整体后移,拷贝插入
erase(pos, len):删除指定位置字符,删至末尾 / 指定长度,前移覆盖,更新结束符
7.查找及提取字串
size_t string::find(char ch, size_t pos) const { 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) const { const char* p = strstr(_str + pos, str); if (p == nullptr) { return npos; } else { return p - _str; } }find(char, pos):从指定位置查找字符,找到返回下标,找不到返回 npos
find(const char, pos):从指定位置查找子串,找到返回首字符下标,找不到返回 nposstring string::substr(size_t pos, size_t len) const { if (len == npos || len >= _size - pos) { len = _size - pos; } string ret; ret.reserve(len); for (size_t i = 0; i < len; i++) { ret += _str[pos + i]; } return ret; }从pos位置开始,截取len个字符返回新字符串;长度不足则截取到末尾
8.比较运算符
bool string::operator<(const string& s) const { size_t i1 = 0, i2 = 0; while (i1 < _size && i2 < s._size) { if (_str[i1] < s[i2]) { return true; } else if (_str[i1] > s[i2]) { return false; } else { ++i1; ++i2; } } return i2 < s._size; }逐字符字典序比较:逐个对比字符,当前字符串更小时返回 true;前缀相同时,短字符串更小。
bool string::operator==(const string& s) const { size_t i1 = 0, i2 = 0; while (i1 < _size && i2 < s._size) { if (_str[i1] != s[i2]) { return false; } else { ++i1; ++i2; } } return i1 == _size && i2 == s._size; }判断字符串是否相等:逐字符比较,长度和所有字符都相同才返回 true。
一般来说,只用写小于和等于,其他复用即可,如下
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); } bool string::operator!=(const string& s) const { return !(*this == s); }
9.流操作符及全局函数
以下都是全局函数
ostream& operator<<(ostream& out, const string& s) { //有效防止无法打印字符串中有效\0后的字符 //out << s.c_str; for (size_t i = 0; i < s.size(); i++)//可以不用友元,访问内部_size了 { out << s[i]; } return out; }重载输出运算符:逐字符打印字符串有效内容,不受\0影响,完整输出。
istream& operator>>(istream& in, string& s) { s.clear(); char buff[128]; int i = 0; char ch = in.get(); while (ch != '\n' && ch != ' ') { 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; }重载输入 >>,清空字符串后用 128 字节缓冲区批量读取字符,以空格 / 回车结束,支持高效读取任意长度字符串并赋值。
设置 128 字节缓冲区用来暂存读取字符,避免逐个字符频繁拼接触发多次内存扩容,有效提升数据读取效率,同时限制单次存储长度规避数组越界风险,分段缓存的方式也能够正常处理超长输入内容。
istream& getline(istream& in, string& s, char delim ) { s.clear(); char buff[128]; int i = 0; char ch = in.get(); while (ch != delim) { 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; }功能:
1.自定义实现按指定分隔符读取整行字符串,支持读取带空格的完整一行内容。
2.执行流程:先清空字符串原有内容,通过 128 字节缓冲区暂存读取的字符,逐字符读取输入直到遇到指定分隔符停止,缓冲区满时自动拼接字符串并重置,读取结束后将缓冲区剩余字符追加到字符串,最终返回输入流对象。
3.核心特点:以指定字符作为读取结束标志,支持读取包含空格的字符串,通过缓冲区提升读取效率、减少扩容,兼容超长字符串读取。void swap(string& x, string& y) { x.swap(y); }全局交换函数,调用自身的交换方法,高效交换两个字符串对象的所有内容。
3.文件
头文件
#pragma once #include <iostream> #include<string> #include<algorithm> #include<assert.h> using namespace std; namespace ray { class string { public: typedef char* iterator; typedef const char* const_iterator; iterator begin(); const_iterator cbegin() const; iterator end(); const_iterator cend() const; string(const char* str = ""); string(const string& s); ~string(); size_t size() const; size_t capacity() const; const char* c_str() const; void reserve(size_t n=0); char& operator[](size_t pos); const char& operator[](size_t pos) const; void push_back(char c); void clear(); void append(const char* str); string& operator+=(char c); string& operator+=(const char* str); string& operator=(const string& s); string& operator=(string tmp); void swap(string& s); void pop_back(); string& insert(size_t pos, char ch); string& insert(size_t pos, const char* str); string& erase(size_t pos, size_t len = npos); size_t find(char ch, size_t pos = 0) const; size_t find(const char* str, size_t pos = 0) const; string substr(size_t pos = 0, size_t len = npos) 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; bool operator!=(const string& s) const; private: char* _str; size_t _size; size_t _capacity; char _buff[16]; public: static const size_t npos; }; ostream& operator<<(ostream& out, const string& s); istream& operator>>(istream& in, string& s); istream& getline(istream& in, string& s, char delim = '\n'); void swap(string& x, string& y); }源文件
#define _CRT_SECURE_NO_WARNINGS 1 #include"string.h" using namespace std; namespace ray { const size_t string::npos = -1; string::string(const char* str) :_size(strlen(str)) { _capacity = _size; _str = new char[_size + 1]; memcpy(_str, str, _size + 1); } /*string::string(const string& s) { _str = new char[s._capacity + 1]; memcpy(_str, s._str, s._size + 1); _size = s._size; _capacity = s._capacity; }*/ string::string(const string& s) { string tmp(s._str); swap(tmp); } string::~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; } const char* string::c_str() const { return _str; } size_t string::size() const { return _size; } size_t string::capacity() const { return _capacity; } string::iterator string::begin() { return _str; } string::const_iterator string::cbegin() const { return _str; } string::iterator string::end() { return _str + _size; } string::const_iterator string::cend() const { return _str + _size; } void 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; cout << "capacity:" << _capacity << endl; } } 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]; } void string::push_back(char c) { if (_size >= _capacity) { size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity; reserve(newcapacity); } _str[_size] = c; ++_size; _str[_size] = '\0'; } void string::append(const char* str) { size_t len = strlen(str); if (len + _size > _capacity) { size_t newcapacity = 2 * _capacity > len + _size ? 2 * _capacity : len + _size; reserve(newcapacity); } memcpy(_str + _size, str, len + 1); _size += len; } void string::clear() { _str[0] = '\0'; _size = 0; } string& string::operator+=(char c) { push_back(c); return *this; } string& string::operator+=(const char* str) { append(str); return *this; } void string::pop_back() { assert(_size > 0); --_size; _str[_size] = '\0'; } string& string::insert(size_t pos, char ch) { assert(pos <= _size); if (_size >= _capacity) { size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity; reserve(newcapacity); } size_t end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; --end; } _str[pos] = ch; ++_size; _str[_size] = '\0'; return *this; } string& string::insert(size_t pos, const char* str) { assert(pos <= _size); size_t len = strlen(str); if (len + _size >= _capacity) { size_t newcapacity = 2 * _capacity > len + _size ? 2 * _capacity : len + _size; reserve(newcapacity); } size_t end = _size + len; while (end > pos + len - 1) { _str[end] = _str[end - len]; --end; } for (size_t i = 0; i < len; i++) { _str[pos + i] = str[i]; } _size += len; return *this; } string& string::erase(size_t pos, size_t len) { assert(pos < _size); if (len == npos || len >= (_size - pos)) { _size = pos; _str[_size] = '\0'; } else { size_t i = pos + len; memmove(_str + pos, _str + i, _size + 1 - i); _size -= len; } return *this; } size_t string::find(char ch, size_t pos) const { 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) const { const char* p = strstr(_str + pos, str); if (p == nullptr) { return npos; } else { return p - _str; } } string string::substr(size_t pos, size_t len) const { if (len == npos || len >= _size - pos) { len = _size - pos; } string ret; ret.reserve(len); for (size_t i = 0; i < len; i++) { ret += _str[pos + i]; } return ret; } bool string::operator<(const string& s) const { size_t i1 = 0, i2 = 0; while (i1 < _size && i2 < s._size) { if (_str[i1] < s[i2]) { return true; } else if (_str[i1] > s[i2]) { return false; } else { ++i1; ++i2; } } return i2 < s._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); } bool string::operator==(const string& s) const { size_t i1 = 0, i2 = 0; while (i1 < _size && i2 < s._size) { if (_str[i1] != s[i2]) { return false; } else { ++i1; ++i2; } } return i1 == _size && i2 == s._size; } bool string::operator!=(const string& s) const { return !(*this == s); } //传统写法 /*string& string::operator=(const string& s) { if (this != &s) { char* tmp = new char[s._capacity + 1]; memcpy(tmp, s._str, s._size + 1); delete[] _str; _str = tmp; _size = s._size; _capacity = s._capacity; } return *this; }*/ //现代写法 string& string::operator=(const string& s) { if (this != &s) { string tmp(s); swap(tmp); } return *this; } string& string::operator=(string tmp) { swap(tmp); return *this; } void string::swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } ostream& operator<<(ostream& out, const string& s) { //有效防止无法打印字符串中有效\0后的字符 //out << s.c_str; for (size_t i = 0; i < s.size(); i++) { out << s[i]; } return out; } istream& operator>>(istream& in, string& s) { s.clear(); char buff[128]; int i = 0; char ch = in.get(); while (ch != '\n' && ch != ' ') { 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; } istream& getline(istream& in, string& s, char delim ) { s.clear(); char buff[128]; int i = 0; char ch = in.get(); while (ch != delim) { 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 swap(string& x, string& y) { x.swap(y); } }
大家可以试试自己写一下,对类和对象的一些知识和命名空间的使用会有所了解。希望大家一键三连。谢谢!
更多推荐
所有评论(0)