👑作者主页:@安 度 因
🏠学习社区:StackFrame
📖专栏链接:C++修炼之路
文章目录
- 一、默认成员函数
- 1、全缺省构造
- 2、析构
- 3、拷贝构造(深拷贝)
- 4、赋值重载(深拷贝)
- 二、访问
- 1、[ ] 重载
- 2、迭代器
- 三、容量操作
- 1、size
- 2、clear
- 3、reserve
- 4、resize
- 四、修改
- 1、push_back
- 2、append
- 3、+=
- 4、c_str
- 5、find
- 6、substr
- 7、insert
- 8、erase
- 五、重载符号
- 六、流插入/流提取
如果无聊的话,就来逛逛 我的博客栈 吧! 🌹
一、默认成员函数
1、全缺省构造
string(const char* str = "") // 缺省参数
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1]; // 开辟空间
// strcpy(_str, str);
memcpy(_str, str, _size + 1); // 拷贝包括 \0
}
2、析构
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
}
delete 释放空是没有问题的。但是释放空也没有意义,所以可以加个条件。
3、拷贝构造(深拷贝)
- 自己开辟空间,拷贝数据,释放空间
string(const string& s)
{
_str = new char[s._capacity + 1];
// strcpy(_str, s._str);
memcpy(_str, s._str, s._size + 1); // 需要完整拷贝
_size = s._size;
_capacity = s._capacity;
}
- 把开辟空间和拷贝数据给构造函数做
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str); // 构造函数调用
swap(tmp);
}
初始化列表必须写,因为对于类中内置类型一般来说是不处理的, 这里虽然处理了,但是编译器不一定会每次处理,到时候交换可能会出现随机值的情况,析构会出问题。
缺点:当对象进行 += ‘\0’ ,后拷贝出错,建议用第一种写法。
4、赋值重载(深拷贝)
- 自己开辟空间,拷贝数据,释放空间
string& operator=(const string& s)
{
// 防止自己给自己赋值
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
memcpy(tmp, s._str, s._capacity + 1);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
- 拷贝构造 tmp 对象,交换数据
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s); // 拷贝构造 s
std::swap(_str, tmp._str); // 改变指向
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
// err
// std::swap(*this, tmp); // 无限递归调用赋值
}
return *this;
}
通过 tmp 完成数据交换。
注意:不可以直接交换 *this
和 tmp
,会引起无限递归调用赋值。
- 参数为对象的拷贝构造,直接进行交换
// 自己写份 swap
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string& operator=(string tmp) // 拷贝构造 tmp ,交换 tmp h和 _str
{
swap(tmp);
return *this;
}
二、访问
1、[ ] 重载
const char& operator[](size_t pos) const
{
assert(pos < _size); // 检查越界
return _str[pos];
}
char& operator[](size_t pos)
{
assert(pos < _size); // 检查越界
return _str[pos];
}
2、迭代器
string 的迭代器就是原生指针,普通迭代器和 const 迭代器取法相同:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const iterator begin() const
{
return _str;
}
const iterator end() const
{
return _str + _size;
}
迭代器使用方法:
void test()
{
anduin::string s1("hello world");
anduin::string::iterator it = s1.begin();
while (it != s1.end())
{
*it += 1;
cout << *it << ' ';
it++;
}
cout << endl;
for (auto& e : s1)
{
e -= 1;
cout << e << ' ';
}
}
对于 范围 for 其实就是在迭代器遍历上 “套了层壳子” ,本质还是迭代器的使用。
三、容量操作
1、size
对 string 的 size 进行操作:
size_t size() const
{
return _size;
}
2、clear
void clear()
{
_str[0] = '\0';
_size = 0;
}
3、reserve
提前设置容量:
void reserve(size_t n) // n 为容量大小
{
if (n > _capacity)
{
char* tmp = new char[n + 1]; // 1 给 \0
// strcpy(tmp, _str);
memcpy(tmp, _str, _size + 1); // 拷贝 \0
delete[] _str;
_str = tmp;
_capacity = n;
cout << "reserve -> " << _capacity << endl;
}
}
new 空间,拷贝数据,释放原空间,改变指向。
4、resize
void resize(size_t n, char ch = '\0')
{
// 删
if (n < _size)
{
_size = n;
_str[_size] = '\0';
}
else // 增
{
reserve(n); // reserve 会自己检查是否需要扩容
for (size_t i = _size; i < n; i++) // 从之前的 _size 开始,移一直到 n
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
两种情况:
- 设置 size 比之前小,不改变容量,把大小设定为 n ,补 ‘\0’ 。
- 设置 size 比之前大,重新
reserve
,从原先最后一个位置_size
处填内容,填满 n 个,设定 _size ,补 ‘\0’ 。
四、修改
1、push_back
void push_back(char ch)
{
// 插入一个字符
if (_size + 1 > _capacity)
{
int newcapacity = _capacity == 0 ? 4 : (2 * _capacity);
reserve(newcapacity);
}
_str[_size++] = ch;
_str[_size] = '\0';
}
2、append
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
int newcapacity = _size + len; // 至少扩到 _size + len
reserve(newcapacity);
}
// strcpy(_str + _size, str); // 会把 \0 拷贝过去
memcpy(_str + _size, str, len + 1);
_size += len;
}
3、+=
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
复用
4、c_str
const char* c_str() const
{
return _str; // 返回 _str 的地址
}
5、find
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); // 在 _str 中查找 str
if (ptr)
{
return ptr - _str; // 指针 - 指针,无论 pos 在哪里,都是显示的整体位置
}
else
{
return npos;
}
}
6、substr
string substr(size_t pos = 0, size_t len = npos)
{
assert(pos < _size);
size_t n = len; // 取 len 个字符
if (len == npos || len + pos >= _size) // 直接截完
{
n = _size - pos; // 实际长度
}
string tmp;
tmp.reserve(n); // 开好空间
for (size_t i = pos; i < n + pos; i++)
{
tmp += _str[i];
}
return tmp; // 返回需要拷贝构造
}
7、insert
void insert(size_t pos, size_t n, char ch) // pos 位置 n 个字符
{
assert(pos <= _size); // 断言
if (_size + n > _capacity)
{
int newcapacity = _size + n; // 至少扩容到 _size + n
reserve(newcapacity);
}
// 挪动数据
// plan 1
size_t end = _size;
while (end >= pos && end != npos) // 一直挪到 pos,判断是否等于 npos
{
_str[end + n] = _str[end];
--end;
}
for (size_t i = pos; i < n + pos; i++)
{
_str[i] = ch;
}
// plan2: 将 end 位置改变
/*size_t end = _size + n;
while (end > pos)
{
_str[end] = _str[end - n];
--end;
}
for (size_t i = pos; i < n + pos; i++)
{
_str[i] = ch;
}
*/
_size += n;
}
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
int newcapacity = _size + len;
reserve(newcapacity);
}
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];
}
_size += len;
}
第一个 insert 有两种版本:
第一个版本是将 end 设定在 _size
位置,每次移动会把数据从后往前移动到指定位置,当 end >= pos
时,循环继续,当 end == npos
,即 -1 时,循环需要停止。npos 为 size_t 类型的 -1 。
第二个版本是将 end 往后偏移,将数据移动到指定位置。当还剩 n 个数据时,其实数据已经移动完成。之后移动的都是随机值,虽然越界,但是是越界读,也不会报错,这时当 end > pos
时就可以停止。
第二个 insert 重载第一个类似。
8、erase
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || len + pos >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t end = pos + len;
while (end <= _size) // == 是为了移动 \0
{
_str[pos++] = _str[end++];
}
_size -= len;
}
}
end 从 pos 开始偏移 len 个位置,将数据从后往前移动,直到移动到 ‘\0’ 。
五、重载符号
// 自己写
//bool operator<(const string& s)
//{
// size_t i1 = 0, i2 = 0;
// while (i1 < _size && i2 < s._size)
// {
// if (_str[i1] < _str[i2])
// {
// return true;
// }
// else if (_str[i1] > s._str[i2])
// {
// return false;
// }
// else
// {
// i1++, i2++;
// }
// }
// // hello hello
// // helloxx hello
// // hello helloxx
// return i1 == _size && i2 != s._size;
// return _size < s._size;
//}
// 复用 memcmp
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);
}
六、流插入/流提取
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();
}
char buf[128];
int i = 0;
while (ch != ' ' && ch != '\n')
{
buf[i++] = ch;
if (i == 127) // 满了
{
buf[i] = '\0';
s += buf;
i = 0; // 从头开始覆盖
}
ch = in.get();
}
// 若 i 不为 0,则去尾,并且加到 s 中
if (i != 0)
{
buf[i] = '\0';
s += buf;
}
return in;
}
流插入需要完整打印出 string 对象,所以用循环打印。
流提取注意点:
- clear 每次输入前清空数据
get()
可以读取空白字符- 需要去前导空白字符
- 设定
buf
数组,可以避免频繁扩容