文章目录
- 1、遍历
-
- [1、1 循环+下标](#1、1 循环+下标)
- [1、2 迭代器遍历](#1、2 迭代器遍历)
- [1、3 范围for遍历](#1、3 范围for遍历)
- 2、string模拟实现
-
- [2、1 构造和销毁](#2、1 构造和销毁)
-
- [2、1、1 成员声明](#2、1、1 成员声明)
- [为什么static const修饰整型成员要在声明位置就初始化?](#为什么static const修饰整型成员要在声明位置就初始化?)
- [2、1、2 C 串构造](#2、1、2 C 串构造)
- [2、1、3 拷贝](#2、1、3 拷贝)
- [2、1、4 赋值运算重载](#2、1、4 赋值运算重载)
- [2、1、5 析构](#2、1、5 析构)
- [2、2 迭代器](#2、2 迭代器)
-
- [2、2、1 迭代器类型](#2、2、1 迭代器类型)
- [2、2、2 迭代器实现](#2、2、2 迭代器实现)
- [2、3 容量](#2、3 容量)
-
- [2、3、1 size和capactiy](#2、3、1 size和capactiy)
- [2、3、2 empty 判空](#2、3、2 empty 判空)
- [2、3、3 reserve 预留空间](#2、3、3 reserve 预留空间)
- [2、3、4 resize 重新定义有效空间大小](#2、3、4 resize 重新定义有效空间大小)
- [2、4 访问 operator[]](#2、4 访问 operator[])
- [2、5 修改器](#2、5 修改器)
-
- [2、5、1 push_back](#2、5、1 push_back)
- [2、5、2 append](#2、5、2 append)
- [2、5、3 operator+=](#2、5、3 operator+=)
- [2、5、4 insert](#2、5、4 insert)
- [2、5、5 erase](#2、5、5 erase)
- [2、5、6 clear](#2、5、6 clear)
- [2、6 字符操作](#2、6 字符操作)
-
- [2、6、1 c_str](#2、6、1 c_str)
- [2、6、2 find](#2、6、2 find)
- [2、7 比较运算符重载](#2、7 比较运算符重载)
- [2、8 流插入和流提取](#2、8 流插入和流提取)
-
- [2、8、1 流插入](#2、8、1 流插入)
- [2、8、2 流提取](#2、8、2 流提取)
- 3、现代拷贝赋值写法
经过前面两期调用了一遍string接口,是时候实现我们自己的string了。在实现之前,这里补充一下遍历的知识。
1、遍历
1、1 循环+下标
试想一下,当我们遍历数据,对于线性的数据我们可以用循环+下标来访问。string的底层是顺序表,也是线性的那么也是可以用上述方式。
代码演示:
cpp
string s = "hello string";
string s = "hello string";
//for遍历
for (int i = 0; i < s.length(); i++)
{
cout << s[i] << " ";
//可修改
//注意不支持 *(s + i) 访问
}
cout << endl;
//for遍历 + 修改
for (int i = 0; i < s.length(); i++)
{
//ASCII码+1
s[i] += 1;
cout << s[i] << " ";
}

1、2 迭代器遍历
STL也提供了一种新的遍历方式:迭代器遍历。遍历不仅支持线性的还支持非线性的,为STL容器提供了统一的遍历方式。
cpp
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << "$";
//可修改
it++;
}

1、3 范围for遍历
C++11引入了一种新的遍历方式范围for,它减少了遍历的代码量,其底层就是迭代器遍历。
cpp
//范围for遍历,底层迭代器
//auto 自动推导类型
for (auto ch : s)
{
cout << ch << "#";
//ch可修改,但不影响s,ch是数据的拷贝
}
cout << endl;

2、string模拟实现
由于string的接口较多,这里只实现一些重要且常用的。
展开标准库(using namespace std) 含有 string 头文件会和我们的 string 起冲突,建议定义自己的命名空间放我们的 string。
2、1 构造和销毁
2、1、1 成员声明
string 的成员属于私有,在类外不能用随便访问,注意npos也是成员,npos由 static const 修饰,其初始化在成员声明中
cpp
private:
char* _str;
size_t _capacity;
size_t _size;
static const size_t npos = -1;
为什么static const修饰整型成员要在声明位置就初始化?
一般而言,成员变量的的声明和初始化是分离的,而static const成员声明位置位置已经初始化好了,其原因是为了满足一些特殊情况,如定义数组的边界等,这是特例。
2、1、2 C 串构造
cpp
string(const char* str = "")
{
//返回\0之前的字节数
_size = strlen(str);
//_size + 1 多一个空间存\0
_str = new char[_size + 1];
strcpy(_str, str);
_capacity = _size;
}
2、1、3 拷贝
string有资源的申请,编译器的浅拷贝不满足我们的需求,我们要手动实现深拷贝。
cpp
//拷贝
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
2、1、4 赋值运算重载
cpp
//赋值
string& operator=(const string& s)
{
delete[] _str;
if (&s != this)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
2、1、5 析构
cpp
//析构
~string()
{
delete[] _str;
_str = nullptr;
}
2、2 迭代器
2、2、1 迭代器类型
迭代器并不是一个类型,而是封装的一个标识符,string 的迭代器可以用字符指针来封装。
cpp
public:
typedef char* iterator;
2、2、2 迭代器实现
迭代器实现非常的简单,我们可以直接返回对应位置的指针。
cpp
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
iterator begin() const
{
return _str;
}
iterator end() const
{
return _str + _size;
}
2、3 容量
2、3、1 size和capactiy
cpp
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
2、3、2 empty 判空
cpp
bool empty()const
{
return _size == 0;
}
2、3、3 reserve 预留空间
cpp
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
2、3、4 resize 重新定义有效空间大小
cpp
//声明 c 填充字符
//void resize(size_t n, char c = '\0');
void string::resize(size_t n, char c)
{
//需要填充
if (n > _size)
{ //扩容
if (n > _capacity)
{
reserve(n > 2 * _capacity ? n : 2 * _capacity);
}
for (size_t i = _size; i < n; i++)
{
_str[i] = c;
}
}
_size = n;
//补\0
_str[n] = '\0';
}
2、4 访问 operator[]
cpp
char& operator[](size_t index)
{
return *(_str + index);
}
const char& operator[](size_t index)const
{
return *(_str + index);
}
2、5 修改器
2、5、1 push_back
cpp
void string::push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity = _capacity == 0 ? 4 : 2 * _capacity);
char* tmp = new char[_capacity];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
}
_str[_size++] = c;
_str[_size] = '\0';
}
2、5、2 append
cpp
void string::append(const char* str)
{
size_t len = strlen(str);
size_t n = len + _size;
if (n > _capacity)
{
reserve(_capacity + len > 2 * _capacity ? _capacity + len : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;
}
2、5、3 operator+=
cpp
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
2、5、4 insert
cpp
//插入字符
string& string::insert(size_t pos, char c)
{
assert(pos <= _size);
size_t i = _size + 1;
if (_size == _capacity)
{
reserve(2 * _capacity);
}
while (pos < i)
{
_str[i] = _str[i - 1];
i--;
}
_size++;
_str[pos] = c;
return *this;
}
//插入 C 串
string& string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t n = strlen(str);
size_t i = _size + n;
//检查容量
if (_size + n > _capacity)
{
reserve(_size + n > 2 * _capacity ? _size + n : 2 * _capacity);
}
//挪移
while (pos + n < i + 1)
{
_str[i] = _str[i - n];
i--;
}
_size += n;
memcpy(_str + pos, str, n);
return *this;
}
2、5、5 erase
cpp
// 删除pos位置上的元素,并返回该元素的下一个位置
string& erase(size_t pos, size_t len = npos);
string& string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len == 0 || pos == _size)
{
return *this;
}
if (len > _size - pos)
{
len = _size - pos;
}
for (size_t i = pos ; i < _size - len; i++)
{
_str[i] = _str[i + len];
}
_size -= len;
_str[_size] = '\0';
return *this;
}
2、5、6 clear
cpp
void clear()
{
_size = 0;
*_str = '\0';
}
2、6 字符操作
2、6、1 c_str
cpp
const char* c_str()const
{
return _str;
}
2、6、2 find
cpp
// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const;
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const;
size_t string::find(char c, size_t pos) const
{
assert(pos < _size);
if (_size <= pos)
{
return npos;
}
for (size_t i = pos; i < _size; i++)
{
if(_str[i] == c)
{
return i;
}
}
return npos;
}
size_t string::find(const char* s, size_t pos) const
{
assert(pos < _size);
char* ch = strstr(_str + pos, s);
if (ch == nullptr)
{
return npos;
}
for (size_t i = 0; i < _size; i++)
{
if (_str + i == ch)
{
return i;
}
}
return npos;
}
2、7 比较运算符重载
cpp
bool operator<(const string& s);
bool operator<=(const string& s);
bool operator>(const string& s);
bool operator>=(const string& s);
bool operator==(const string& s);
bool operator!=(const string& s);
//比较
bool string::operator<(const string& s)
{
return strcmp(this->c_str(), s.c_str()) < 0;
}
bool string::operator<=(const string& s)
{
return *this == s || *this < s;
}
bool string::operator>(const string& s)
{
return !(*this <= s);
}
bool string::operator>=(const string& s)
{
return (*this > s);
}
bool string::operator==(const string& s)
{
return strcmp(this->c_str(), s.c_str()) == 0;
}
bool string::operator!=(const string& s)
{
return !(*this == s);
}
2、8 流插入和流提取
2、8、1 流插入
cpp
ostream& operator<<(ostream& _cout, const string& s)
{
//要用const版本的begin和end
for (auto ch : s)
{
_cout << ch;
}
return _cout;
}
2、8、2 流提取
如果我们直接用C的字符串来取数据,是不行的,因为遇到 '\n' 和 ' ' ,就结束了。因此用 get 函数取,get函数的作用是取一个字符,没有结束标志。但一个一个地尾插效率低,而外开一个数组可以提高效率。
cpp
istream& operator>>(istream& _cin, string& s)
{
s.clear();
const int N = 256;
char arr[N];
size_t i = 0;
char ch;
ch = _cin.get();
while (ch != ' ' && ch != '\n')
{
arr[i++] = ch;
if (i == N - 1)
{
arr[i] = '\0';
s += arr;
i = 0;
}
ch = _cin.get();
}
if (i >= 0)
{
arr[i] = '\0';
s += arr;
}
return _cin;
}
3、现代拷贝赋值写法
cpp
//交换
void swap(string& s)
{
std::swap(this->_str, s._str);
std::swap(this->_size, s._size);
std::swap(this->_capacity, s._capacity);
}
//现代拷贝
string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
//现代赋值
string& operator=(string s)
{
swap(s);
return *this;
}