回顾前文我们学习了,string的初步实现,但是学习STL过程中我们不仅需要会用,更需要了解底层来造轮子,现在我们开始string造轮子
首先我们开始准备个框架
// 放在头文件中定义类
namespace myString(){
class my_string{
public:
// 类中需要的成员函数
private:
// 定义成员变量
};
}
构造与析构函数
首先创建一个string类,然后就是构造和析构函数,析构函数没有什么可说的就是空间释放和成员变量置为0。而构造函数中,通过初始化列表,可以得到_size(strlen不计算' \0' ),然后再在构造函数体里面来给_str开辟空间,值得注意的有两点:1.给的新空间容量刻意大了一,是给 '\0' 准备的;2.传入的缺省函数值为空字符
my_string(const char* str = "") // 构造函数
// 传入缺省值为空字符
:_size(strlen(str))
,_capacity(_size)
{
// 需要分配空间(_str任没开辟出来)
_str = new char[_capacity + 1];
// 将str给_str
// strcpy(_str, str); 这里用安全版本的来替代
strcpy_s(_str, strlen(str) + 1, str);
}
my_string(const my_string& s) { // 拷贝构造函数
_size = s._size;
_capacity = s._capacity;
_str = new char[_capacity];
// 复制
strcpy_s(_str, _size + 1, s._str);
}
~my_string() { // 析构
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
那么通过前期的准备,我们现在就来实现string中主要的成员函数!
从外部读取类的成员变量
一般来说类的成员变量都需要封装,所以需要自己写读取成员变量的函数
// 从外部调用获取封装变量
size_t capacity()const { // 获取容量
return _capacity;
}
size_t size() const { // 获取大小
return _size;
}
const char* c_str() const { // 获取地址
return _str;
// 读取地址的时候
// printf("c_str address: %p\n", str.c_str());
}
接下来就是对操作符进行的重载
string中的操作符重载
具体的用法可以去Reference - C++ Reference 阅读文档,来看看相应的重载函数
// 重载[]
char& operator[] ( size_t pos) { // 可读可写,使得通过对象直接读取 第 i 位置字符
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos)const { // 只读
assert(pos < _size);
return _str[pos];
}
// 重载=
my_string& operator=(const my_string& s) { // 便于用=实现对象拷贝构造
if (this != &s) {
_size = s._size;
_capacity = s._capacity;
_str = new char[_size + 1];
// 复制
strcpy_s(_str, _size + 1, s._str);
}
return *this;
}
// 重载+=
my_string& operator+=(const char ch) { // 对字符,的尾插
push_back(ch);
return *this;
}
my_string& operator+=(const char* str) { // 对字符串
append(str);
return *this;
}
// 重载比大小
bool operator<(const my_string& s) const {
return strcmp(_str, s._str) < 0;
}
bool operator==(const my_string& s) const {
return strcmp(_str, s._str) == 0;
}
bool operator>(const my_string& s) const {
return !(*this == s) && !(*this < s);
}
bool operator>=(const my_string& s) const {
return !(*this < s);
}
bool operator<=(const my_string& s) const {
return !(*this > s);
}
那么现在开始增删查改的操作
string的增删查改
在增删查改之前,我们知道对这个数组的操作我们常常需要考虑,容量大小是否足够,所以我们先写一个扩容函数
// 扩容
void reserve(size_t n) {
if (n > _capacity) {
char* tmp = new char[n + 1]; // n+1为了给'\0'准备空间
strcpy_s(tmp, strlen(_str) + 1, _str); // strlen()不计算'\0'
// 释放旧空间
delete[] _str;
_str = tmp;
_capacity = n;
}
}
接下来是增删查改,我们需要注意的是增删操作后我们可能需要对最后一个有效位置的下一个位置补上 '\0',一般就是_size这个位置 ,并且里面的strcpy_s这个函数的使用需要仔细研究
// 插入函数
void push_back(char ch) { // 尾插单个字符
if (_size == _capacity) { // 扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
// 这里插入的位置刚好是有效位置的后一个(也就是\0)的为位置 替换了\0
_str[_size] = ch;
_size++;
// 这里的新的最后一个有效位置变成了ch,ch后一个位置需要补上\0
_str[_size] = '\0';
}
void append(const char* str) { // 尾插插字符串
size_t length = strlen(str) ;
if (length + _size > _capacity) {
reserve(length + _size);
}
// 这里的_str + _size相当于走到了_str最后有效位的下一位开始复制
strcpy_s(_str + _size, strlen(str)+1, str);
_size = _size + length;
}
void insert(size_t pos, char ch) { // 从pos处插入单个字符
assert(pos <= _size);
size_t end = _size;
if (_size == _capacity) { // 扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
// 这里有类型提升的机制,导致需要i = end + 1
for (size_t i = end + 1; i > pos; i--) {
// 后一位放前一位的数据
_str[i] = _str[i - 1];
}
_str[pos] = ch;
_size++;
}
void insert(size_t pos, const char* str) { // 从pos处插入一串字符
assert(pos <= _size);
size_t end = _size;
size_t length = strlen(str);
if (length + _size > _capacity) { // 扩容
reserve(length + _size);
}
// 需要与插入单个字符做比较
for (size_t i = end + length ; i >= pos + length; i--) {
_str[i] = _str[i - length];
}
// 不可行 原因未知
// 因为_str+pos总长为length此时的数据已经完成挪动,就可直接将str的length部分复制,且不用担心\0
// strcpy_s(_str + pos, length + 1, str);
for (size_t i = 0; i < length; i++) {
_str[pos] = str[i];
pos++;
}
_size = _size + length;
// return *this; 可以为my_string& 类型
}
// 抹除数据
my_string& erase(size_t pos, size_t len = npos) {
assert(pos < _size);
if (len == npos || pos + len > _size) {
_size = pos;
_str[_size] = '\0';
}
else {
strcpy_s(_str + pos, _size - pos - len + 1, _str + pos + len);
_size = _size - len;
}
return *this;
}
void clear() { // 清除数据
_str[0] = '\0';
_size = 0;
}
// 查
size_t find(const char ch, size_t pos = 0) const {
assert(pos < _size);
for (size_t i = pos; i < _size; i++) {
if (_str[i] == ch)
return i;
}
// 未找到就返回
// cout << "return nops: ";
return npos;
}
size_t find(const string& str, size_t pos = 0) const {
assert(pos < _size);
const char* pch = strstr(_str + pos, str.c_str());
if (pch != nullptr)
return pch - _str;
else
return npos;
}
char& at(size_t pos) { // 查找某个位置的字符
assert(pos < _size);
return _str[pos];
}
const char& at(size_t pos) const { // 在at基础上返回字符不可修改
assert(pos < _size);
return _str[pos];
}
char& front() { // 返回第一个字符
assert(empty() != true);
return _str[0];
}
const char& front() const { // 左值不可修改
assert(empty() != true);
return _str[0];
}
char& back() {
assert(empty() != true);
return _str[_size - 1];
}
const char& back() const {
assert(empty() != true);
return _str[_size - 1];
}
// 改
my_string& replace(size_t pos, size_t len, const string& str) {
assert(pos < _size );
}
void resize(size_t n, char ch = '\0') { // _str大小的改变,也可以插入字符
if (n < _size) {
// 第n个位置用\0替代直接就结束
_str[n] = '\0';
_size = n;
}
else {
reserve(n);
for (; _size < n;/* 这里就靠着_size就能完成循环 */) {
_str[_size] = ch;
_size++;
}
_str[_size] = '\0';
}
}
string中其他的成员函数
字符串的截取
// 返回截取字符串
my_string substr(size_t pos = 0, size_t len = npos) const {
assert(_size > 0);
my_string s;
size_t end = pos + len;
if (len == npos || pos + len >= _size) {
len = _size - pos;
end = _size;
}
s.reserve(len);
for (size_t i = pos; i < end; i++) {
s += _str[i];
}
return s;
}
如图这个函数就是截取某个字符串对象的一部分,然后赋值给另一个对象
判断字符是否为空
// 判定字符串是否为空
bool empty() const {
if (_size == 0)
return true;
else
return false;
}
判断字符串是否为空,我们知道true为非0 ,false为0,这里只是测试,而对于string的删除和修改就需要判断是否为空,因为这两个操作不能对空字符串操作,因此empty这个函数用于判断
迭代器的模拟
迭代器的作用就是遍历,而begin,end则是为了找到前后位置(指针)
// 迭代器模拟
iterator begin() { // 返回字符串的首地址
return _str;
}
iterator end() { // 返回尾地址
return _str + _size;
}
迭代器的使用如下,首先先定义一个迭代器对象,然后读取对象的头尾指针最后借助地带器来完成对字符串的遍历
全局的函数
流输出以及流提取
// 全局 流输出my_string类型
ostream& operator<<(ostream& out, const my_string& s) {
for (size_t i = 0; i < s.size(); i++) {
cout << s[i];
}
return out;
}
// 流提取
istream& operator>>(istream& in, my_string& s) {
char ch;
// 清除原数据 不清除的话变成尾插
s.clear();
in >> ch; // 先输入一次判断是否为' '或者 '\0'
while (ch != ' ' && ch != '\0') {
s += ch;
in >> ch;
}
return in;
}
对于cout<<是只对内置类型能够输出,而这里我们定义的my_string类为自定义类型就不在重载的范围内,所以我们需要重载一下 << 流输出,使得能够输出字符串,同理流提取也是如此
对string的优化
swap函数
我们在读取string文档时发现有个swap函数,而这个swap我们好像并没有怎么使用,因为在上述中我们基本完成了string基础的成员函数,但是事实上swap使我们用来使得string代码更加简洁的工具
当我们需要有拷贝的操作时,我们就可以调用swap函数,这样就省去大部分相同的代码块,也让编程更加简洁
汇总的my_string代码
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace myString{
class my_string { // 模拟实现string
public:
typedef char* iterator;
// 迭代器模拟
iterator begin() { // 返回字符串的首地址
return _str;
}
iterator end() { // 返回尾地址
return _str + _size;
}
// 构造与析构
my_string(const char* str = "") // 构造函数
// 传入缺省值为空字符
:_size(strlen(str))
,_capacity(_size)
{
// 需要分配空间(_str任没开辟出来)
_str = new char[_capacity + 1];
// 将str给_str
// strcpy(_str, str); 这里用安全版本的来替代
strcpy_s(_str, strlen(str) + 1, str);
}
my_string(const my_string& s) { // 拷贝构造
/*_size = s._size;
_capacity = s._capacity;
_str = new char[_size + 1];
// 复制
strcpy_s(_str, _size + 1, s._str);
*/
// 现代写法
my_string s1(s._str); // 通过交换函数
swap(s1);
}
~my_string() { // 析构
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
// 交换
void swap(my_string& s) {
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
// 重载[]
char& operator[] ( size_t pos) { // 可读可写
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos)const { // 只读
assert(pos < _size);
return _str[pos];
}
// 重载=
my_string& operator=(const my_string& s) {
if (this != &s) {
_size = s._size;
_capacity = s._capacity;
_str = new char[_size + 1];
// 复制
strcpy_s(_str, _size + 1, s._str);
}
return *this;
}
// 重载+=
my_string& operator+=(const char ch) { // 对字符
push_back(ch);
return *this;
}
my_string& operator+=(const char* str) { // 对字符串
append(str);
return *this;
}
// 重载比大小
bool operator<(const my_string& s) const {
return strcmp(_str, s._str) < 0;
}
bool operator==(const my_string& s) const {
return strcmp(_str, s._str) == 0;
}
bool operator>(const my_string& s) const {
return !(*this == s) && !(*this < s);
}
bool operator>=(const my_string& s) const {
return !(*this < s);
}
bool operator<=(const my_string& s) const {
return !(*this > s);
}
// 从外部调用获取封装变量
size_t capacity()const { // 获取容量
return _capacity;
}
size_t size() const { // 获取大小
return _size;
}
const char* c_str() const { // 获取内部数组
return _str;
}
// 扩容
void reserve(size_t n) {
if (n > _capacity) {
// 存n个字符,在留一个给\0
char* tmp = new char[n + 1];
strcpy_s(tmp, strlen(_str) + 1, _str);
// 释放旧空间
delete[] _str;
_str = tmp;
_capacity = n;
}
}
// 判定字符串是否为空
bool empty() const {
if (_size == 0)
return true;
else
return false;
}
// 返回截取字符串
my_string substr(size_t pos = 0, size_t len = npos) const {
assert(_size > 0);
my_string s;
size_t end = pos + len;
if (len == npos || pos + len >= _size) {
len = _size - pos;
end = _size;
}
s.reserve(len);
for (size_t i = pos; i < end; i++) {
s += _str[i];
}
return s;
}
// 插入函数
void push_back(char ch) { // 尾插单个字符
if (_size == _capacity) { // 扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
// 这里插入的位置刚好是有效位置的后一个(也就是\0)的为位置 替换了\0
_str[_size] = ch;
_size++;
// 这里的新的最后一个有效位置变成了ch,ch后一个位置需要补上\0
_str[_size] = '\0';
}
void append(const char* str) { // 尾插插字符串
size_t length = strlen(str) ;
if (length + _size > _capacity) {
reserve(length + _size);
}
// 这里的_str + _size相当于走到了_str最后有效位的下一位开始复制
strcpy_s(_str + _size, strlen(str)+1, str);
_size = _size + length;
}
void insert(size_t pos, char ch) { // 从pos处插入单个字符
assert(pos <= _size);
size_t end = _size;
if (_size == _capacity) { // 扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
// 这里有类型提升的机制,导致需要i = end + 1
for (size_t i = end + 1; i > pos; i--) {
// 后一位放前一位的数据
_str[i] = _str[i - 1];
}
_str[pos] = ch;
_size++;
}
void insert(size_t pos, const char* str) { // 从pos处插入一串字符
assert(pos <= _size);
size_t end = _size;
size_t length = strlen(str);
if (length + _size > _capacity) { // 扩容
reserve(length + _size);
}
// 需要与插入单个字符做比较
for (size_t i = end + length ; i >= pos + length; i--) {
_str[i] = _str[i - length];
}
// 不可行 原因未知
// 因为_str+pos总长为length此时的数据已经完成挪动,就可直接将str的length部分复制,且不用担心\0
// strcpy_s(_str + pos, length + 1, str);
for (size_t i = 0; i < length; i++) {
_str[pos] = str[i];
pos++;
}
_size = _size + length;
// return *this; 可以为my_string& 类型
}
// 抹除数据
my_string& erase(size_t pos, size_t len = npos) {
assert(pos < _size);
if (len == npos || pos + len > _size) {
_size = pos;
_str[_size] = '\0';
}
else {
strcpy_s(_str + pos, _size - pos - len + 1, _str + pos + len);
_size = _size - len;
}
return *this;
}
void clear() { // 清除数据
_str[0] = '\0';
_size = 0;
}
// 查
size_t find(const char ch, size_t pos = 0) const {
assert(pos < _size);
for (size_t i = pos; i < _size; i++) {
if (_str[i] == ch)
return i;
}
// 未找到就返回
// cout << "return nops: ";
return npos;
}
size_t find(const string& str, size_t pos = 0) const {
assert(pos < _size);
const char* pch = strstr(_str + pos, str.c_str());
if (pch != nullptr)
return pch - _str;
else
return npos;
}
char& at(size_t pos) { // 查找某个位置的字符
assert(pos < _size);
return _str[pos];
}
const char& at(size_t pos) const { // 在at基础上返回字符不可修改
assert(pos < _size);
return _str[pos];
}
char& front() { // 返回第一个字符
assert(empty() != true);
return _str[0];
}
const char& front() const { // 左值不可修改
assert(empty() != true);
return _str[0];
}
char& back() {
assert(empty() != true);
return _str[_size - 1];
}
const char& back() const {
assert(empty() != true);
return _str[_size - 1];
}
// 改
my_string& replace(size_t pos, size_t len, const string& str) {
assert(pos < _size );
}
void resize(size_t n, char ch = '\0') { // _str大小的改变,也可以插入字符
if (n < _size) {
// 第n个位置用\0替代直接就结束
_str[n] = '\0';
_size = n;
}
else {
reserve(n);
for (; _size < n;/* 这里就靠着_size就能完成循环 */) {
_str[_size] = ch;
_size++;
}
_str[_size] = '\0';
}
}
private:
char* _str;
size_t _size;
size_t _capacity;
public:
const static size_t npos;
};
// my_string类外部 myString命名空间内全局
const size_t my_string:: npos = -1; // npos赋值为-1
// 全局 流输出my_string类型
ostream& operator<<(ostream& out, const my_string& s) {
for (size_t i = 0; i < s.size(); i++) {
cout << s[i];
}
return out;
}
// 流提取
istream& operator>>(istream& in, my_string& s) {
char ch;
// 清除原数据 不清除的话变成尾插
s.clear();
in >> ch; // 先输入一次判断是否为' '或者 '\0'
while (ch != ' ' && ch != '\0') {
s += ch;
in >> ch;
}
return in;
}
my_string to_string(int x)
{
my_string ret;
while (x)
{
int val = x % 10;
x /= 10;
ret += ('0' + val);
}
reverse(ret.begin(), ret.end());
return ret;
}
// 命名空间结束
}