一、简单介绍
①【STL简介】
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
- STL的六大组件
②【string简介】
std::string 是 C++ 标准模板库 (STL) 中用于处理字符串的类(定义在 <string> 头文件中),它封装了字符序列的操作,提供丰富的成员函数,支持动态内存管理,比 C 风格字符数组更安全高效。
二、模拟实现
①【成员变量】
_str:字符数组的指针
_size:字符串的字符个数
_capacity:字符数组可容纳的字符个数(这里设定字符串最后一位为’\0’,故实际占用空间为_capacity+1)
npos:size_t类型的最大值,常表示作用至字符串最后一个字符,可参考https://legacy.cplusplus.com/reference/string/string/npos/char* _str = nullptr; size_t _size = 0; size_t _capacity = 0; static const size_t npos = -1; // 无符号的-1非常大!
②【成员函数】
1. 构造函数
string:将根据字符长度申请空间,并拷贝形参至指针_str中,若不传参则默认形参为空字符串。
string::string(const char* str = "") :_size(strlen(str)) { _capacity = _size; _str = new char[_capacity + 1]; memcpy(_str, str, _capacity + 1); }
_string_iterator:因为string底层可以用数组实现,这里作为练习就不配置迭代器类了,直接开辟数组空间,使用指针作为简易的迭代器。(迭代器这部分将在【STL之list模拟实现】中具体演示!)
2. 拷贝构造函数
string:string的拷贝函数可以直接写,也可以在拷贝构造函数中调用构造函数构造tmp类,this再与之交换成员变量,函数调用完毕后析构的是原this的空间内存。(同理,再利用拷贝构造函数也可以实现赋值重载运算符)
基础写法:手动配置空间。
string::string(const string& str) :_size(str._size) , _capacity(str._capacity) { _str = new char[_capacity + 1]; memcpy(_str, str._str, _capacity + 1); }
进阶写法:利用构造函数来配置空间。
void string::swap(string& tmp) // 自定义string类的swap成员函数 { std::swap(_capacity, tmp._capacity); std::swap(_size, tmp._size); std::swap(_str, tmp._str); } string::string(const string& str) // 现代C++的拷贝构造函数写法,利用构造函数省去手动深拷>>贝,执行效率区别不大 { string tmp(str._str); // 调用构造函数 swap(tmp); std::cout << "拷贝构造函数" << std::endl; }
_string_iterator:使用指针作为简易的迭代器,若想学习移步至【STL之list模拟实现】。
3. 析构函数
string:释放指针_str的空间并置为nullptr,_size与_capacity清零。
string::~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; }
_string_iterator:使用指针作为简易的迭代器,若想学习移步至【STL之list模拟实现】。
③【迭代器】
1. 读写迭代器(iterator)
iterator:迭代器对应的数据可读写。
typedef char* iterator; string::iterator string::begin() { return _str; } string::iterator string::end() { return _str + _size; }
2. 只读迭代器(const_iterator)
const_iterator:迭代器对应的数据只读,不可被修改。(使用指针作为简易的迭代器,故可将只读迭代器视作const char*,在【STL之list模拟实现】中会演示正规做法!)
typedef const char* const_iterator; string::const_iterator string::begin() const { return _str; } string::const_iterator string::end() const { return _str + _size; }
④【运算符重载】
1. 赋值运算符重载(operator=)
operator=:上面在拷贝构造函数部分中提到可以利用拷贝构造函数+自定义的swap方式提高代码书写效率,这里展示三种方法!
基础写法:手动配置空间。
string& string::operator=(const string& str) { if (this != &str) { _capacity = str._capacity; _size = str._size; char* tmp = new char[_capacity + 1]; memcpy(tmp, str._str, _size + 1); delete[] _str; _str = tmp; } return *this; }
进阶写法:利用拷贝构造函数来配置空间。(成员函数swap参照上方拷贝构造函数)
string& string::operator=(const string& str) // 现代C++写法(函数内调用拷贝构造,再swap掠夺"资本") { if (this != &str) { string tmp(str); swap(tmp); } return *this; }
进阶写法Pro版:利用拷贝构造函数来配置空间,传参调用拷贝构造函数,无需手动调用拷贝构造函数,更简洁。(成员函数swap参照上方拷贝构造函数的进阶写法)
string& string::operator=(string tmp) // 现代C++写法(传参调用拷贝构造得到tmp,在函数内用swap掠夺"资本") { swap(tmp); std::cout << "=" << std::endl; return *this; }
2. 复合赋值运算符(operator+=)
operator+=:直接调用append成员函数。(涉及空间扩容)
string& string::operator+=(const char* str) { append(str); return *this; }
3. 下标访问运算符(operator[])
oeprator[]:下标访问字符,含读写、只读两种版本。
char& string::operator[](size_t i) // 读写版本 { assert(i < _size); return _str[i]; } const char& string::operator[](size_t i) const // 只读版本 { assert(i < _size); return _str[i]; }
4. 输入输出运算符(cin>>与cout<<)
cout<<:由于语法限制,需为全局函数,因为类的成员函数第一个参数默认为this指针。
std::ostream& operator<<(std::ostream& out, const string& s) { for (auto ch : s) // 这里的end()与_size有关,故可代替for (size_t i = 0; i < _size; i++) { out << ch; } return out; }
cin>>:由于语法限制,需为全局函数,因为类的成员函数第一个参数默认为this指针。
std::istream& operator>>(std::istream& in, string& str) { str.clear(); char buff[128]; size_t i = 0; char ch = in.get(); while (ch != '\n' && ch != ' ') // 截断条件 { buff[i++] = ch; if (i == 127) { buff[i] = '\0'; str += buff; i = 0; } ch = in.get(); } if (i > 0) // 处理未被流提取的字符 { buff[i] = '\0'; str += buff; } return in; }
⑤【功能函数】
1. getline
getline:与输入运算符重载相似,仅修改了delim截断条件。
std::istream& getline(std::istream& in, string& str, char delim = '\n') { str.clear(); char buff[128]; size_t i = 0; char ch = in.get(); while (ch != delim) // 截断条件 { buff[i++] = ch; if (i == 127) { buff[i] = '\0'; str += buff; i = 0; } ch = in.get(); } if (i > 0) // 处理未被流提取的字符 { buff[i] = '\0'; str += buff; } return in; }
2. swap
swap:已在拷贝构造函数的进阶写法中介绍过。
void string::swap(string& tmp) // 自定义string类的swap成员函数 { std::swap(_capacity, tmp._capacity); std::swap(_size, tmp._size); std::swap(_str, tmp._str); }
3. c_str
c_str:以C语言的字符数组指针类型作为返回,内容为const只读。
const char* string::c_str() const { return _str; }
4. size
size:返回string的字符个数。
size_t string::size() const { return _size; }
5. reserve
reserve:为字符数组扩容至n。
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; //std::cout << "执行扩容成功 :> " << _capacity << std::endl; } }
6. push_back
push_back:尾插字符。
void string::push_back(char ch) { if (_capacity == _size) { size_t new_capacity = _capacity == 0 ? 4 : 2 * _capacity; // 扩至两倍 reserve(new_capacity); } _str[_size++] = ch; _str[_size] = '\0'; }
7. pop_back
pop_back:尾删字符。
void string::pop_back() { assert(_size > 0); _str[_size] = '\0'; --_size; }
8. append
append:在尾部插入C语言中的字符串。
string& string::append(const char* str) { size_t len = strlen(str); if (_size + len > _capacity) { size_t new_capacity = 2 * _capacity >= _size + len ? 2 * _capacity : _size + len; reserve(new_capacity); } memcpy(_str + _size, str, len + 1); _size += len; return *this; }
9. insert
insert:在pos处插入字符,原字符串pos及pos后的字符向后移动一格。
string& string::insert(size_t pos, char ch) { assert(pos <= _size); if (_capacity == _size) { size_t new_capacity = _capacity == 0 ? 4 : 2 * _capacity; reserve(new_capacity); } size_t end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; --end; } _str[pos] = ch; ++_size; return *this; }
10. erase
erase:从pos处删除len个字符(包括pos处),若len超出范围则删至末尾,若未传入len参数也删至末尾(npos在成员变量中有解释!)。
string& string::erase(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || len >= _size - pos) // 后面全删的情况 { _str[pos] = '\0'; _size = pos; } else { size_t pos2 = pos + len; memmove(_str + pos, _str + pos2, _size + 1 - pos2); _size -= len; } return *this; }
11. find
find:从pos处开始查找字符or字符串的下标,pos默认为0,若未查询则返回npos。
size_t string::find(char ch, size_t pos = 0) const // 查找字符为ch的下标 { 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 = 0) const // 查找字符串为*str的下标 { const char* p = strstr(_str + pos, str); if (p == nullptr) return npos; else return p - _str; }
12. substr
substr:截取字符or字符串,若无传递len参数则截取至字符串末尾。
string string::substr(size_t pos, size_t len = npos) const { assert(pos < _size); if (len == npos || len > _size - pos) // 需修改len的情况,确保len个数正确 len = _size - pos; string tmp; tmp._size = len; tmp.reserve(len); memcpy(tmp._str, _str + pos, len); return tmp; }
13. clear
clear:清空字符串,代码复用方面在输入重载运算符>>、全局函数getline中使用过。
void string::clear() { _size = 0; _str[0] = '\0'; }
三、整体代码
①【string.h】
#pragma once
#include <iostream>
#include <string.h>
#include <assert.h>
namespace wyx
{
class string
{
public:
// 构造函数、拷贝构造函数、析构函数
string(const char* str = "");
string(const string& str);
~string();
// 迭代器
typedef char* iterator;
typedef const char* const_iterator;
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
// 运算符重载
char& operator[](size_t i);
const char& operator[](size_t i) const;
string& operator+=(char ch);
string& operator+= (const char* str);
bool operator<(const string& str) const;
bool operator==(const string& str) const;
bool operator<=(const string& str) const;
bool operator>(const string& str) const;
bool operator>=(const string& str) const;
//string& operator=(const string& str);
string& operator=(string tmp);
// string功能
const char* c_str() const;
size_t size() const;
void reserve(size_t n);
void push_back(char ch);
void pop_back();
string& append(const char* str);
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, size_t len = npos) const;
void clear();
void Print();
void swap(string& tmp);
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
static const size_t npos = -1; // 无符号的-1非常大!
};
std::ostream& operator<<(std::ostream& out, const string& s);
std::istream& operator>>(std::istream& in, string& s);
std::istream& getline(std::istream& in, string& str, char delim = '\n');
void swap(string& s1, string& s2);
}
②【string.cpp】
#include "string.h"
namespace wyx
{
// 构造函数、拷贝构造函数、析构函数
string::string(const char* str)
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_capacity + 1];
memcpy(_str, str, _capacity + 1);
}
/*string::string(const string& str)
:_size(str._size)
, _capacity(str._capacity)
{
_str = new char[_capacity + 1];
memcpy(_str, str._str, _capacity + 1);
}*/
string::string(const string& str) // 现代C++的拷贝构造函数写法,利用构造函数省去手动深拷贝,执行效率区别不大
{
string tmp(str._str); // 调用构造函数
swap(tmp);
std::cout << "拷贝构造函数" << std::endl;
}
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
// 迭代器
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
string::const_iterator string::begin() const
{
return _str;
}
string::const_iterator string::end() const
{
return _str + _size;
}
// 运算符重载
char& string::operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
const char& string::operator[](size_t i) const
{
assert(i < _size);
return _str[i];
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
bool string::operator<(const string& str) const
{
size_t i = 0, j = 0;
while (i < _size && j < str._size)
{
if (_str[i] < str._str[j])
return true;
else if (_str[i] > str._str[j])
return false;
else
++i, ++j;
}
return _size < str._size;
}
bool string::operator==(const string& str) const
{
size_t i = 0, j = 0;
while (i < _size && j < str._size)
{
if (_str[i] != str._str[j])
return false;
}
return _size == str._size;
}
bool string::operator<=(const string& str) const
{
return *this < str || *this == str;
}
bool string::operator>(const string& str) const
{
return !(*this <= str);
}
bool string::operator>=(const string& str) const
{
return !(*this < str);
}
/*string& string::operator=(const string& str)
{
if (this != &str)
{
_capacity = str._capacity;
_size = str._size;
char* tmp = new char[_capacity + 1];
memcpy(tmp, str._str, _size + 1);
delete[] _str;
_str = tmp;
}
return *this;
}*/
/*string& string::operator=(const string& str) // 现代C++写法(函数内调用拷贝构造,再swap掠夺"资本")
{
if (this != &str)
{
string tmp(str);
swap(tmp);
}
return *this;
}*/
string& string::operator=(string tmp) // 现代C++写法(传参调用拷贝构造得到tmp,在函数内用swap掠夺"资本")
{
swap(tmp);
std::cout << "=" << std::endl;
return *this;
}
// string功能
const char* string::c_str() const
{
return _str;
}
size_t string::size() const
{
return _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;
//std::cout << "执行扩容成功 :> " << _capacity << std::endl;
}
}
void string::push_back(char ch)
{
if (_capacity == _size)
{
size_t new_capacity = _capacity == 0 ? 4 : 2 * _capacity; // 扩至两倍
reserve(new_capacity);
}
_str[_size++] = ch;
_str[_size] = '\0';
}
void string::pop_back()
{
assert(_size > 0);
_str[_size] = '\0';
--_size;
}
string& string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t new_capacity = 2 * _capacity >= _size + len ? 2 * _capacity : _size + len;
reserve(new_capacity);
}
memcpy(_str + _size, str, len + 1);
_size += len;
return *this;
}
string& string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_capacity == _size)
{
size_t new_capacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(new_capacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
return *this;
}
string& string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t new_capacity = 2 * _capacity >= _size + len ? 2 * _capacity : _size + len;
reserve(new_capacity);
}
size_t end = _size + len;
while (end > pos - 1 + len)
{
_str[end] = _str[end - len];
--end;
}
memcpy(_str + pos, str, len);
_size += len;
return *this;
}
string& string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len == npos || len >= _size - pos) // 后面全删的情况
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t pos2 = pos + len;
memmove(_str + pos, _str + pos2, _size + 1 - pos2);
_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
{
assert(pos < _size);
if (len == npos || len > _size - pos) // 需修改len的情况,确保len个数正确
len = _size - pos;
string tmp;
tmp._size = len;
tmp.reserve(len);
memcpy(tmp._str, _str + pos, len);
return tmp;
}
void string::clear()
{
_size = 0;
_str[0] = '\0';
}
void string::Print()
{
std::cout << "size=" << _size << " capacity=" << _capacity << " p=" << this << std::endl;
}
void string::swap(string& tmp)
{
std::swap(_capacity, tmp._capacity);
std::swap(_size, tmp._size);
std::swap(_str, tmp._str);
}
// 非成员函数
std::istream& getline(std::istream& in, string& str, char delim)
{
str.clear();
char buff[128];
size_t i = 0;
char ch = in.get();
while (ch != delim) // 截断条件
{
buff[i++] = ch;
if (i == 127)
{
buff[i] = '\0';
str += buff;
i = 0;
}
ch = in.get();
}
if (i > 0) // 处理未被流提取的字符
{
buff[i] = '\0';
str += buff;
}
return in;
}
void swap(string& s1, string& s2)
{
s1.swap(s2);
}
// 非成员重载运算符
std::ostream& operator<<(std::ostream& out, const string& s)
{
for (auto ch : s) // 这里的end()与_size有关,故可代替for (size_t i = 0; i < _size; i++)
{
out << ch;
}
return out;
}
std::istream& operator>>(std::istream& in, string& str)
{
str.clear();
char buff[128];
size_t i = 0;
char ch = in.get();
while (ch != '\n' && ch != ' ') // 截断条件
{
buff[i++] = ch;
if (i == 127)
{
buff[i] = '\0';
str += buff;
i = 0;
}
ch = in.get();
}
if (i > 0) // 处理未被流提取的字符
{
buff[i] = '\0';
str += buff;
}
return in;
}
}
③【Test.cpp】
#include "string.h"
using namespace std;
namespace wyx
{
void test_01()
{
string str("123qwe");
cout << str[5] << endl;
for (auto& ch : str)
{
ch++;
cout << ch << ' ';
}
cout << endl;
cout << str.c_str() << endl;
cout << typeid(wyx::string::iterator).name() << endl;
cout << typeid(std::string::iterator).name() << endl;
}
void test_02()
{
string s1("qwer1234");
cout << s1.size() << endl;
cout << s1.c_str() << endl;
s1.push_back('x');
cout << s1.c_str() << endl;
s1.append(" hello!xiaoxiong!");
cout << s1.c_str() << endl;
s1 += ' ';
s1 += "123";
cout << s1.c_str() << endl;
}
void test_03()
{
string s = "qwer";
cout << s << endl;
s += '\0';
s += 'x';
cout << s << endl;
s += "\0 123";
cout << s << endl;
s += "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";
cout << s << endl;
}
void test_04()
{
string s = "qwer";
cout << s << endl;
s.insert(0, "xxxxxxxxxxxxxxxxxxxxx");
cout << s << endl;
}
void test_05()
{
string s = "0123qwer";
s.erase(0, 3);
cout << s << endl;
string s2 = "qwer12345";
cout << s2 << endl;
s2.push_back('x');
s2.push_back('\0');
s2.push_back('x');
cout << s2 << endl;
cout << s2.erase(4, 3) << endl;
string s3 = "12";
s3.pop_back();
cout << s3 << endl;
}
void test_06()
{
string s = "0123456789";
cout << s.find("3456789") << endl;
cout << s.substr(0, 3) << endl;
}
void test_07()
{
string s = "012345";
string s2 = "012347";
cout << (s <= s2) << endl;
cout << (s > s2) << endl;
}
void test_08()
{
string s, s2;
cin >> s >> s2;
cout << s << ' ' << s2 << endl;
s.Print(); s2.Print();
/*string s3;
getline(cin, s3);
cout << s3;*/
s = s2;
cout << s << ' ' << s2 << endl;
s.Print(); s2.Print();
}
void test_09()
{
string s("12345");
string s2("45678");
std::swap(s, s2); // 需要一次拷贝构造,两次赋值拷贝(每个赋值拷贝函数会调用一次拷贝构造)
cout << "//////////////" << endl;
swap(s, s2); // 直接交换变量值,无拷贝构造与赋值拷贝
}
}
int main()
{
//wyx::test_01();
//wyx::test_02();
//wyx::test_03();
//wyx::test_04();
//wyx::test_05();
//wyx::test_06();
//wyx::test_07();
//wyx::test_08();
wyx::test_09();
return 0;
}