手撕string类
1.string类的成员变量 1指针指向申请的空间 2.size字符串长度 3.capacity字符串容量
注意 申请的空间=capacity容量+1 1是留给 \0
2.STL中的string 字符串长度<=15 在栈上申请空间 >15才会在堆上申请空间。但我们统一在堆上申请空间。
3.要实现的函数
1.构造函数: 1.默认构造 2.带参构造 (有两种 1.C风格字符串构造 带\0结尾 2.二进制字符串构造 不带\0传入size大小 划定界限) 3.拷贝构造 4.移动构造 5.析构函数
2.赋值运算符重载 1.拷贝赋值运算符重载 2.移动赋值运算符重载
3.预分配内存reserve 释放多余内存shrink_to_fit
4.尾加字符串append函数(1.C风格 2.二进制)
5.其它函数 1.c_str 2.size 3.capacity 4.empty判空
1.内存管理
申请一块大小为newcap+1大小的空间,把原数据拷贝过去,并把申请的原空间释放
1.newcap<_size+1 说明新空间不能存放所有的原数据 抛异常
invalid_argument表示传入参数错误 runtime_error程序运行过程中遇到问题
2.申请的字符串容量newcap最小为15
因为我们不知道要复制的原空间字符串是否以\0结尾,
所有用memcpy(目标空间起始地址,原空间起始地址,拷贝字节数)控制要拷贝的字节数,只拷贝size字符串长度大小的空间,最后自己手动加上\0
2.构造函数
1.默认构造
容量最小15
2.C风格字符串构造
1.带参构造函数加上 explicit禁止隐式类型转换
3.C风格以\0结尾 memcpy拷贝的时候可以把\0也拷贝进去
注意:sizeof(str):算的是指针的大小
sizeof(*str):*str表示字符串数组第一个字符 也就是大小为1
strlen(str):算字符串的长度 但以\0为分界线,只能算以\0结尾的字符串
3.二进制风格字符串
1.explicit 禁止隐式转换
2.判空
3.拷贝完len大小的字符串后,手动加\0
4.拷贝构造
5.移动构造
1.移动语义(移动构造 移动赋值)+x析构函数 加noexcept,表示不会进行抛异常,这样容器在移动元素时会选择移动语义 减少拷贝 提高效率。
2.记得把原资源置为空
6.析构函数
1. 析构函数默认是
noexcept(true)
,但手动写时最好明确虽然编译器默认会将析构函数视为不抛异常,但你自己定义的析构函数如果不加
noexcept
,某些标准库操作会认为它可能抛异常,从而影响:
标准容器如
std::vector<T>
是否能使用移动优化。
std::swap
等泛型算法是否使用移动而非拷贝。
2. 不加
noexcept
会影响标准库优化路径例如在
std::vector<string>
中:
如果
string
的析构函数不是noexcept
,移动操作可能回退为拷贝,降低性能。编译器会避免在
noexcept
不确定的情况下做 move 优化。
3. 析构函数本身不该抛异常
C++ 的一个基本原则是:析构函数绝不能抛出异常,否则程序会在异常传播时“再次抛出”导致
std::terminate()
被调用。因此,加上
noexcept
是对这个约定的明确表达,也是一种安全保障。
3.赋值运算符重载
1.拷贝运算符重载
1.先判断是否是自身,是就直接返回*this
2.把申请的原空间进行释放
3.申请新空间进行拷贝操作
2.移动赋值运算符
1.赋值运算符 先判断自身
2.移动 加noexcept
3.释放原空间 但不用申请新空间直接指向other的空间 让other指向空
4.扩容 缩容操作
5.append函数
1.二进制风格
在原字符串后面加上长度为len为字符串
1.先判空
2.先判断capacity空间是否还够 不够就进行扩容 *2+len +len防止扩容后仍不够
3.在原字符串结尾处即\0处进行memcpy拷贝
4.最后加上\0结尾
2.C风格
进行复用
6.其它函数
... c_str() const
表示该函数是一个常量成员函数。
它不修改当前对象的成员变量(除非是
mutable
变量)。这意味着你可以在
const string
对象上调用它:
位置 作用 示例 const char*
返回值指向的是 const 数据(不能改) const char* p = str.c_str();
... const
修饰成员函数,表示不修改对象 const string s; s.data();