目录
2、初识迭代器与begin() \ end() \ rbegin() \ rend()
1、string赋值与拼接push_back() append() operator+=的用法
3、追查字符位置的 find() 和生成子字符串的 substr()
4、string字符串比较和替换 - replace() compare()
5、 string插入和删除 - insert() erase()
STL简介
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架
其中包括六大组件:
- 算法
- 容器
- 迭代器
- 空间适配器
- 仿函数
- 配接器
而我们这个系列,实现的是各种各样的容器
网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。
string类简介
string是c++中表示字符串的容器,其底层的字符串表示方式仍然是以’\0’表示的字符串集合但是提供了比c语言更多的接口
在使用string类时,必须包含string头文件以及using namespace std
#include <string> // 使用string类时,需引入头文件 <string>
using namespace std; // 展开std
string是C++风格的字符串,而string本质上是一个类
string和char * 区别:
- char * 是一个指针
- string是一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器。
string成员中包含了以下几个成员:
- 字符串数组
- 字符串的有效字符大小
- 字符串容量
- 迭代器
对以上点做出以下解释:
迭代器提供了遍历整个字符串的方式,虽然string中的遍历没有迭代器会更加方便。但因为与STL中其它难以遍历的容器统一,所以string中并没有舍弃迭代器。
容量指的是类为我们的字符串数组开辟的空间,而有效字符个数指的是我们已经使用已经存储了元素的容量大小。
有效字符和容量都不包括’\0’
所以,我们模拟实现的字符串类型中,有以下成员:
char* _str;//字符串类型
size_t size;//字符串有效字符大小
size_t capacity;//字符串容量
size_t 是 unsigned long long,传-1的实际值是2^64-1
官方文本文档:
string类的文档介绍 - C++ Reference (cplusplus.com)
① string 是表示字符串的字符串类。
② 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作 string 的常规操作。
③ string在底层上实际是:basic_string 模板类的别名:
typedef basic_string<char, char_traits, allocator>string;
④ 不能操作多字节或者变长字符的序列。
Ⅰ. string类的常用接口
(constructor) 函数名称 | 功能说明 |
string() (重点) | 构造空的string类对象,即空字符 |
string(const char* s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string& s) (重点) | 拷贝构造函数 |
string() // 构造空的string类对象,即非空字符串。
string(cosnt char* s) // 用C-string来构造string类对象
string(size_t n, char c) // string类对象中包含n个字符c
string(const string&s) // 拷贝构造函数
string库函数中的构造
库函数构造主要有默认构造,含参构造和拷贝构造
默认构造是构造出一个空字符串
string s1;
//构造出一个空字符串
含参构造中我们可以传入c语言中的字符串指针或字符串数组类型
string s2("hello world!");
//构造出"hello world!字符串"
string(const char* s = "")
:_size(strlen(s)), _capacity(strlen(s))
{
_str = new char[_capacity + 1];
strcpy(_str, s);
}
也可以在string中构造n个相同的字符c
//string(size_t n,char c)
string s3(10,'a');
//第一个参数是个数n,第二个参数是字符c
//构造出的字符串:aaaaaaaaaa
string(size_t n, char c)
:_size(n), _capacity(n)
{
_str = new char[_capacity + 1];
memset(_str, c, _size);
_str[_size] = '\0';
}
拷贝构造使用某个其它stirng类的引用
string s4(s1);//拷贝一份s1到s4中
string 的拷贝构造:
/* s2(s1) */
string(const string& s)
: _str(new char[strlen(s._str) + 1]) {
strcpy(_str, s._str);
}
演示:
#include<iostream>
#include<string>
using namespace std;
//string构造
void test01()
{
string s1; //创建空字符串,调用无参构造函数
cout << "str1 = " << s1 << endl;
const char* str = "hello world";
string s2(str); //把c_string转换成了string
cout << "str2 = " << s2 << endl;
string s3(s2); //调用拷贝构造函数
cout << "str3 = " << s3 << endl;
string s4(10, 'a');
cout << "str3 = " << s4 << endl;
}
int main() {
test01();
return 0;
}
补充:拷贝构造支持从pos开始,初始化npos个字符
我们发现长度 len 缺省值给的是 npos:
string (
const string& str,
size_t pos, // 起始位置
size_t len = npos // 长度(缺省值给的npos)
);
这个 npos 你可以理解为是 string 里的一个静态变量,它是 -1。
当你没有指定 len 时,因为 npos 是 -1,而又是 size_t 无符号整型,他长度将会是整形的最大值。
也就是说,如果你不传,它就相当于取整型最大值 2147483647,所以也就相当于取所有字符串了
int main(void) { string s1("abcdef123456"); string s2(s1, 2); // 从第2个位置开始,初始化npos个长度的字符 cout << s2 << endl; return 0; }
Ⅱ. sting类对象的容量操作
函数名称 | 功能说明 |
size (重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty (重点) | 检测字符串是否为空串,是返回true,否则返回 flase |
clear (重点) | 清空有效字符 |
reserve(重点) | 为字符串预留空间 |
resize (重点) | 将有效字符的个数改成n个,多出的空间用字符c填充 |
1、size()与length()的区别
① size() 和 length() 的计算不包含 \0。
解释:它不包含最后作为结尾标识符的 \0,告诉你的是有效的字符长度。
② size() 和 length() 的功能都是返回字符串有效长度,功能上没有区别。
解释: 这是一个 "历史包袱" 问题。
总结:size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
2、返回空间总大小的 capacity()
返回当前为字符串分配的存储空间的大小,以字节表示。
此容量不一定等于字符串长度。它可以等于或更大,额外的空间允许对象在向字符串添加新字符时优化其操作。请注意,此容量并不假定字符串的长度有限制。当此容量耗尽并且需要更多容量时,它由对象自动扩展(重新分配其存储空间)。字符串长度的理论限制由成员max_size给出。
总结:在算法中没什么用处,因为每个编译器分配空间方式都不同,所以值都有所出入
3、清空有效字符的 clear()
clear:擦除字符串的内容,该字符串将成为空字符串(长度为 0 个字符)
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s1("abcdef");
cout << "清空前: " << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.clear();
cout << "清空后: " << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
return 0;
}
总结:clear()只是将string中有效字符清空,不改变底层空间大小。
4、 调整字符串大小 resize()
void resize (size_t n);
void resize (size_t n, char c);
将字符串大小调整为 n 个字符的长度。
如果 n 小于当前字符串长度,则当前值将缩短为其前 n 个字符,并删除超出 n 的字符。
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s1("abcdef");
s1.resize(3);
cout<<s1<<endl;
s1.resize(30,'1');
cout<<s1<<endl;
return 0;
}
总结: resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。
注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
resize() 的实现
/* resize */
void resize(size_t new_capacity, char init_ch = '\0') {
// 如果欲增容量比_size小
if (new_capacity <= _size) {
_str[new_capacity] = '\0'; // 拿斜杠零去截断
_size = new_capacity; // 更新大小
}
// 欲增容量比_size大
else {
if (new_capacity > _capacity) {
reserve(new_capacity);
}
// 起始位置,初始化字符,初始化个数
memset(_str + _size, init_ch, new_capacity - _size);
_size = _capacity;
_str[_size] = '\0';
}
}
5、请求更改容量reserve()
void reserve (size_t n = 0);
请求将字符串容量适应计划的大小更改为最多 n 个字符的长度。
如果 n 大于当前字符串容量,则该函数会导致容器将其容量增加到 n 个字符(或更大)。
在所有其他情况下,它被视为收缩字符串容量的非约束性请求:容器实现可以自由地进行优化,并使字符串的容量大于n。
此函数对字符串长度没有影响,并且无法更改其内容。
与resize()的对比:
可见reserve()仅仅改变容量,不改变长度
总结: reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
reserve() 的实现
/* 增容:reverse */
void reserve(size_t new_capacity) {
if (new_capacity > _capacity) {
char* tmp = new char[new_capacity + 1]; // 开新空间
strcpy(tmp, _str); // 搬运
delete[] _str; // 释放原空间
_str = tmp; // 没问题,递交给_str
_capacity = new_capacity; // 更新容量
}
}
Ⅲ. string类对象的访问及遍历操作
函数名称 | 功能说明 |
operator[] | 返回pos位置的字符,const string类对象调用 |
begin + end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
rbegin + rend | rbegin获取一个字符的反向迭代器 + rend获取最后一个字符下一个位置的迭代器 |
范围 for | C++11支持更简洁的范围 for 新遍历方式 |
1、访问字符串字符的 operator[] 和 at()
如果访问这个字符串的每一个字符,会有:
for (size_t i = 0; i < s1.size(); i++) {
cout << s1[i] << " ";
}
cout << s1[i] << " ";
cout << s1.operator[](i) << " "; // 取字符串第i个位置的字符的引用
string 这里它作为函数调用,这就是 operator[] ,并且等价于:cout << s1.operator[](i) << " ";
operator[] 的底层是这样设计的:
char& operator[] (size_t pos)
{
return _str[pos];
}
所以
at()
除了 operator[] 还有一个 at() ,at() 也是早期支持的一个接口
它和 operator[] 用处是一样的,at() 是像函数一样去使用的:
int main(void)
{
string s1("hello world");
for (size_t i = 0; i < s1.size(); i++) {
s1.at(i) += 1;
}
cout << s1 << endl;
return 0;
}
at() 和 operator[] 的区别 —— 它们检查越界的方式不一样。
operator[] 是使用断言处理的,断言是一种激进的处理手段。
而 at() 是比较温柔的处理方式,如果 pos >= size 就 throw 一个异常对象出去。
2、初识迭代器与begin() \ end() \ rbegin() \ rend()
迭代器是 STL 六大组件之一,是用来访问和修改容器的。
同理:
#include<iostream>
#include<string>
using namespace std;
int main(void)
{
string s1("hello");
// 迭代器
for (string::reverse_iterator rit=s1.rbegin(); rit!=s1.rend(); ++rit)
cout << *rit;
return 0;
}
输出:olleh
迭代器遍历的意义:
- 对于 string,你得会用迭代器,但是一般我们还是喜欢用 下标 + [] 遍历;
- 比如 list、map / set 不支持 下标 + [] 遍历,迭代器就排上用场了,这就是迭代器存在的意义。
- 迭代器是通用的遍历方式。
3、范围for
#include<iostream>
#include<string>
using namespace std;
int main(void)
{
string s1("hello");
for (auto e : s1) {
cout << e << " "; //遍历
}
cout << endl;
for (auto e : s1) { // 无引用修改
e += 1;
}
cout << "无引用 " << s1 << endl;
for (auto& e : s1) { // 引用修改
e += 1;
}
cout << "引用修改 " << s1 << endl;
return 0;
}
详情可看下方连接的 Ⅴ. auto关键字(C++11)
Ⅳ.string 类对象的修改操作
函数名称 | 功能说明 |
push_back | 在字符串后尾插字符c |
append | 在字符后追加一个字符串 |
operator+= (重点) | 在字符串后追加字符串str |
c_str (重点) | 返回c格式字符串 |
find + npos (重点) | 从字符串pos位置开始往后找字符c,返回该字符自字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
1、string赋值与拼接push_back() append() operator+=的用法
push_back:插入一个字符,但只能插入一个字符
append:可以插入字符串
operator+=:可以添加字符、字符串、对象
string& operator=(const char* s);
//char*类型字符串 赋值给当前的字符串string& operator=(const string &s);
//把字符串s赋给当前的字符串string& operator=(char c);
//字符赋值给当前的字符串
演示:
#include<iostream>
#include<string>
using namespace std;
string s="C++";
string s1="";
int main()
{
s1.push_back('I');//只能单字符
cout<<s1<<endl;
s1.append(" Love ");//插入字符串
cout<<s1<<endl;
s1+='C';
cout<<s1<<endl;
s1+=" and ";
cout<<s1<<endl;
s1+=s;
cout<<s1<<endl;
//可以添加字符、字符串、对象
return 0;
}
push_back() 的实现
/* 字符串尾插:push_back */
void push_back(char append_ch) {
if (_size == _capacity) { // 检查是否需要增容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = append_ch; // 插入要追加的字符
_size++;
_str[_size] = '\0'; // 手动添加'\0'
}
append() 的实现
/* 字符串追加:append */
void append(const char* append_str) {
size_t len = strlen(append_str); // 计算要追加的字符串的长度
if (_size + len > _capacity) { // 检查是否需要增容
reserve(_size + len);
}
strcpy(_str + _size, append_str); // 首字符+大小就是\0位置
_size += len; // 更新大小
}
operator+= 的实现
/* operator+= */
string& operator+=(char append_ch) {
push_back(append_ch); // 复用push_back
return *this;
}
string& operator+=(const char* append_str) {
append(append_str); // 复用append
return *this;
}
2、返回C格式字符串的 c_str
当我们用printf输出string类型时,就必须用c_str
#include<iostream>
#include<string>
using namespace std;
int main() {
string s1("hello string");
cout << s1 << endl; // 调用重载的流插入运算符打印的
printf("%s\n",s1.c_str());
//printf("%s\n",s1);
cout << s1.c_str() << endl;
// 这是调字符串打印的,c_str 是遇到 \0 结束的,
}
补充:转成字符串才能取地址时
// 获取file后缀
void Test() {
string file("test.txt");
FILE* pf = fopen(file.c_str(), "w");
}
比如这里需要打开文件,fopen 第一个参数要求是 const char*,
所以这里怎么能直接放 string 是不行的,这时候可以用 .c_str() 就可以把字符串的地址返回出来。
3、追查字符位置的 find() 和生成子字符串的 substr()
find:从字符串 pos 位置开始往后找字符 c ,返回该字符在字符串中的位置。
size_t find (const char* s, size_t pos = 0) const;
find('字符',开始位置); (不给开始位置默认从0开始)
substr:在 str 中从 pos 位置开始,截取 n 个字符,然后返回一个新构造的对象,其值初始化为此对象的子字符串的副本。
string substr (size_t pos = 0, size_t len = npos) const;
substr(开始位置,截取长度);(不给截取长度默认缺省值无穷大npos)
演示:
#include<iostream>
#include<string>
using namespace std;
int main() {
//创建字符串记录网址
string url("http://www.cplusplus.com/reference/string/string/find/");
// 取出协议
size_t pos1 = url.find(':'); //默认从头找
string protocol = url.substr(0, pos1);
cout << protocol << endl;
// 取出域名
size_t pos2 = url.find('/', pos1 + 3);
// 冒号位置+3开始往后找(w位置)
string domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));
cout << domain << endl;
// 取出路径
string uri = url.substr(pos2 + 1);
cout << uri << endl;
}
反向追查字符串的 rfind()
#include<iostream>
#include<string>
using namespace std;
void test01()
{
//查找
string str1 = "abcdefgde";
int pos = str1.find("de");
if (pos == -1)
{
cout << "未找到" << endl;
}
else
{
cout << "pos = " << pos << endl;
}
pos = str1.rfind("de");
cout << "pos = " << pos << endl;
}
int main() {
test01();
return 0;
}
pos = 3
pos = 7
与find()同理,只是从右往左找而已
find() 的实现
find:查找字符
/* find */
size_t find(char aim_ch) {
for (size_t i = 0; i < _size; i++) {
if (aim_ch == _str[i]) {
// 找到了
return i; // 返回下标
}
}
// 找不到
return npos;
}
find:查找字符串
size_t find(const char* aim_str, size_t pos = 0) {
const char* ptr = strstr(_str + pos, aim_str);
if (ptr == nullptr) {
return npos;
}
else {
return ptr - _str; // 减开头
}
}
4、string字符串比较和替换 - replace() compare()
string& replace(int pos, int n, const string& str);
//替换从pos开始n个字符为字符串strstring& replace(int pos, int n,const char* s);
//替换从pos开始的n个字符为字符串s
int compare(const string &s) const;
//与字符串s比较int compare(const char *s) const;
//与字符串s比较
replace():
void test02()
{
//替换
string str1 = "abcdefgde";
str1.replace(1, 3, "1111");
cout << "str1 = " << str1 << endl;
}
int main() {
test02();
return 0;
}
str1 = a1111efgde
compare():
#include<iostream>
#include<string>
using namespace std;
//字符串比较
void test01()
{
string s1 = "hello";
string s2 = "aello";
int ret = s1.compare(s2);
if (ret == 0) {
cout << "s1 等于 s2" << endl;
}
else if (ret > 0)
{
cout << "s1 大于 s2" << endl;
}
else
{
cout << "s1 小于 s2" << endl;
}
}
int main() {
test01();
return 0;
}
5、 string插入和删除 - insert() erase()
string& insert(int pos, const char* s);
//插入字符串string& insert(int pos, const string& str);
//插入字符串string& insert(int pos, int n, char c);
//在指定位置插入n个字符cstring& erase(int pos, int n = npos);
//删除从Pos开始的n个字符
#include<iostream>
#include<string>
using namespace std;
//字符串插入和删除
void test01()
{
string str = "hello";
str.insert(1, "111");
cout << str << endl;
str.erase(1, 3); //从1号位置开始3个字符
cout << str << endl;
}
int main() {
test01();
return 0;
}
h111ello
hello
erase() 的实现
/* 删除:erase */
string& erase(size_t pos, size_t len = npos) {
assert(pos < _size);
if (len == pos || pos + len >= _size) {
_str[pos] = '\0'; // 放置\0截断
_size = pos;
}
else {
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
insert() 的实现
insert:字符
/* 插入:insert */
string& insert(size_t pos, char append_ch) {
assert(pos <= _size);
// 检查是否需要增容
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
// 向后挪动数据
//size_t end = _size;
//while (end >= (int)pos) {
// _str[end + 1] = _str[end];
// end--;
//}
size_t end = _size + 1;
while (end > pos) {
_str[end] = _str[end - 1];
end--;
}
// 插入
_str[pos] = append_ch;
_size++;
return *this;
}
insert:字符串
string& insert(size_t pos, const char* append_str) {
assert(pos <= _size);
size_t len = strlen(append_str);
// 检查是否需要增容
if (_size + len > _capacity) {
reserve(_size + len);
}
// 向后挪动数据
size_t end = _size + len;
while (end > pos + len - 1) {
_str[end] = _str[end - len];
end--;
}
// 插入
strncpy(_str + pos, append_str, len);
_size += len;
return *this;
}
Ⅴ.string 类非成员函数的实现
1.operator 运算符重载
大型造轮子现场,核心为复用与调用
operator<
/* s1 < s2*/
bool operator<(const string& s1, const string& s2) {
size_t i1 = 0, i2 = 0;
while (i1 < s1.size() && i1 < s2.size()) {
if (s1[i1] < s2[i2]) {
return true;
}
else if (s1[i1] > s2[i2]) {
return false;
}
else {
i1++;
i2++;
}
}
return i2 < s2.size() ? true : false;
}
当然,我们还可以实现的更简单些,直接用 strcmp 偷个懒:
/* s1 < s2*/
bool operator<(const string& s1, const string& s2) {
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
operator==
/* s1 == s2 */
bool operator==(const string& s1, const string& s2) {
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
接下来的操作可以复用 == 和 <
高端的程序员,往往只需要最简单的编程方式~~
operator<=
/* s1 <= s2 */
bool operator<=(const string& s1, const string& s2) {
return s1 < s2 || s1 == s2;
}
operator>
/* s1 > s2 */
bool operator>(const string& s1, const string& s2) {
return !(s1 <= s2);
}
operator>=
/* s1 >= s2 */
bool operator>=(const string& s1, const string& s2) {
return !(s1 < s2);
}
operator!=
/* s1 != s2 */
bool operator!=(const string& s1, const string& s2) {
return !(s1 == s2);
}
2.流插入和流提取
operator<< 的实现
// cout << s1 → operator<<(cout, s1)
ostream& operator<<(ostream& out, const string& s) {
//for (auto ch : s) {
// out << ch;
//}
for (size_t i = 0; i < s.size(); i++) {
out << s[i];
}
return out;
}
operator>> 的实现
// cin >>
istream& operator<<(istream& in, string& s) {
char ch = in.get();
while (ch == '\n') {
s += ch;
ch = in.get();
}
return in;
}
完整代码
#include <iostream>
#include <assert.h>
using namespace std;
namespace chaos
{
class string {
public:
/* 构造函数 */
string(const char* str = "")
: _size(strlen(str)) // 计算出字符串str的大小
, _capacity(_size) { // 初始容量等于字符串大小
_str = new char[_capacity + 1]; // 开辟一块 "容量+1" 大小的空间 (_capacity存的是有效字符)
strcpy(_str, str); // 将传入的字符串str复制到 _str中
}
void Swap(string& tmp) {
swap(_str, tmp._str);
swap(_size, tmp._size);
swap(_capacity, tmp._capacity);
}
/* 拷贝构造函数:s2(s1)
string(const string& src)
: _size(src._size) // 拷贝string大小
, _capacity(src._capacity) { // 拷贝string容量
// 拷贝string内容
_str = new char[src._capacity + 1]; // 开辟一块和src相同容量的空间
strcpy(_str, src._str); // 将src中的_str内容拷贝到自己的_str中
}
*/
string(const string& src)
: _str(nullptr)
, _size(0)
, _capacity(0) {
string tmp(src._str); // 拷贝构造一个src
Swap(tmp); // 现代写法:交换
}
/* 赋值重载:s1 = s3
string& operator=(const string& src) {
// 防止自己跟自己赋值
if (this != &src) {
// 1. 暂时用tmp开辟一块相同的空间
char* tmp = new char[src._capacity + 1];
// 2. 把src的值复制给tmp
strcpy(tmp, src._str);
// 3. 释放this原空间
delete[] _str;
// 4. 没翻车,把tmp交付给_src
_str = tmp;
_size = src._size;
_capacity = src._capacity;
}
return *this;
}
string& operator=(const string& src) {
// 防止自己跟自己赋值
if (this != &src) {
string tmp(src); // 复用拷贝构造
Swap(tmp);
}
return *this;
}
*/
string& operator=(string src) {
Swap(src); // 正好调用拷贝构造,不如让形参充当tmp
return *this;
}
/* 返回C格式的字符串:c_str */
const char* c_str() const {
return _str;
}
/* 求字符串大小:size() */
size_t size() const {
return _size;
}
/* operator[] */
char& operator[](size_t pos) {
assert(pos < _size);
return _str[pos]; // 返回字符串对应下标位置的元素
}
const char& operator[](size_t pos) const {
assert(pos < _size);
return _str[pos];
}
/* 迭代器 */
typedef char* iterator;
iterator begin() {
return _str; // 返回第一个字符的位置
}
iterator end() {
return _str + _size; // 返回最后一个字符的位置
}
/* const迭代器 */
typedef const char* const_iterator;
const_iterator begin() const {
return _str;
}
const_iterator end() const {
return _str + _size;
}
/* reserve() */
void reserve(size_t new_capacity) {
if (new_capacity > _capacity) { // 检查是否真的需要扩容
char* tmp = new char[new_capacity + 1]; // 开空间
strcpy(tmp, _str); // 先搬运数据到tmp
_str = tmp; // 没翻车,递交给_str
_capacity = new_capacity; // 更新容量
}
}
/* 字符尾插:push_back() */
void push_back(char append_ch) {
/*
if (_size == _capacity) { // 检查是否需要扩容
reserve(_capacity == 0 ? 4 : _capacity * 2); // 首次给4,其他情况默认扩2倍
}
_str[_size] = append_ch; // 插入要追加的字符
_size++;
_str[_size] = '\0'; // 手动添加'\0'
*/
insert(_size, append_ch);
}
/* 字符串追加:append() */
void append(const char* append_str) {
/*
size_t len = strlen(append_str); // 计算出要追加的字符串的长度
if (_size + len > _capacity) { // 检查是否需要扩容
reserve(_size + len);
}
strcpy(_str + _size, append_str); // 首字符+大小,就是'\0'位置
_size += len; // 更新大小
*/
insert(_size, append_str);
}
/* operator+= */
string& operator+=(char append_ch) {
push_back(append_ch);
return *this;
}
string& operator+=(const char* append_str) {
append(append_str);
return *this;
}
/* insert */
string& insert(size_t pos, char append_ch) {
assert(pos <= _size);
if (_size == _capacity) { // 检查是否需要扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
// 向后挪动数据
size_t end = _size + 1;
while (end > pos) {
_str[end] = _str[end - 1];
end--;
}
// 插入
_str[pos] = append_ch;
_size++;
return *this;
}
string& insert(size_t pos, const char* append_str) {
assert(pos <= _size);
size_t len = strlen(append_str);
if (_size + len > _capacity) { // 检查是否需要增容
reserve(_size + len);
}
// 向后挪动数据
size_t end = _size + len;
while (end > pos + len - 1) {
_str[end] = _str[end - len];
end--;
}
// 插入
strncpy(_str + pos, append_str, len);
_size += len;
return *this;
}
/* resize */
void resize(size_t new_capacity, char init_ch = '\0') {
// 如果欲增容量比_size小
if (new_capacity <= _size) {
_str[new_capacity] = '\0'; // 拿斜杠零去截断
_size = new_capacity; // 更新大小
}
// 欲增容量比_size大
else {
if (new_capacity > _capacity) {
reserve(new_capacity);
}
// 起始位置,初始化字符,初始化个数
memset(_str + _size, init_ch, new_capacity - _size);
_size = _capacity;
_str[_size] = '\0';
}
}
/* find */
size_t find(char aim_ch) {
for (size_t i = 0; i < _size; i++) {
if (aim_ch == _str[i]) {
// 找到了
return i; // 返回下标
}
}
// 找不到
return npos;
}
size_t find(const char* aim_str, size_t pos = 0) {
const char* ptr = strstr(_str + pos, aim_str);
if (ptr == nullptr) {
return npos;
}
else {
return ptr - _str; // 减开头
}
}
/* 删除:erase */
string& erase(size_t pos, size_t len = npos) {
assert(pos < _size);
if (len == pos || pos + len >= _size) {
_str[pos] = '\0'; // 放置\0截断
_size = pos;
}
else {
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
/* 析构函数 */
~string() {
if (_str != nullptr) {
delete[] _str;
_str = nullptr;
}
_size = _capacity = 0;
}
private:
/* 成员变量 */
char* _str;
size_t _size;
size_t _capacity;
public:
static const size_t npos;
};
/* 初始化npos */
const size_t string::npos = -1; // 无符号整型的-1,即整型最大值
/* s1 < s2*/
bool operator<(const string& s1, const string& s2) {
/*
size_t i1 = 0, i2 = 0;
while (i1 < s1.size() && i1 < s2.size()) {
if (s1[i1] < s2[i2]) {
return true;
}
else if (s1[i1] > s2[i2]) {
return false;
}
else {
i1++;
i2++;
}
}
return i2 < s2.size() ? true : false;
*/
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
/* s1 == s2 */
bool operator==(const string& s1, const string& s2) {
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
/* s1 <= s2 */
bool operator<=(const string& s1, const string& s2) {
return s1 < s2 || s1 == s2;
}
/* s1 > s2 */
bool operator>(const string& s1, const string& s2) {
return !(s1 <= s2);
}
/* s1 >= s2 */
bool operator>=(const string& s1, const string& s2) {
return !(s1 < s2);
}
/* s1 != s2 */
bool operator!=(const string& s1, const string& s2) {
return !(s1 == s2);
}
// cout << s1 → operator<<(cout, s1)
ostream& operator<<(ostream& out, const string& s) {
/*
for (auto ch : s) {
out << ch;
}
*/
for (size_t i = 0; i < s.size(); i++) {
out << s[i];
}
return out;
}
// cin >>
istream& operator<<(istream& in, string& s) {
char ch = in.get();
while (ch == '\n') {
s += ch;
ch = in.get();
}
return in;
}
/* 测试用 */
void test_string1() {
string s1("hello world");
string s2(s1);
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
string s3("pig");
cout << s3.c_str() << endl;
s1 = s3;
cout << s1.c_str() << endl;
}
void test_string2() {
string s1("hello world");
string s2;
for (size_t i = 0; i < s1.size(); i++) {
cout << s1[i] << " ";
}
cout << endl;
}
void test_string3() {
string s1("hello world");
string s2;
s1[0] = 'F';
for (size_t i = 0; i < s1.size(); i++) {
cout << s1[i] << " ";
}
cout << endl;
}
void test_string4() {
string s1("hello world");
// 迭代器写
string::iterator it = s1.begin();
while (it != s1.end()) {
*it += 1;
it++;
}
// 迭代器读
it = s1.begin(); // 重置起点
while (it != s1.end()) {
cout << *it << " ";
it++;
}
}
void test_string5() {
string s1("hello world");
cout << s1.c_str() << endl;
s1.push_back('!');
cout << s1.c_str() << endl;
s1.push_back('A');
cout << s1.c_str() << endl;
}
void test_string6() {
string s1("hello world");
cout << s1.c_str() << endl;
s1 += '!';
cout << s1.c_str() << endl;
s1 += "this is new data";
cout << s1.c_str() << endl;
}
void test_string7() {
string s1("hello world");
cout << s1.c_str() << endl;
s1.insert(0, 'X');
cout << s1.c_str() << endl;
s1.insert(0, "hahahaha");
cout << s1.c_str() << endl;
}
void test_string8() {
string s1("hello world");
cout << s1.c_str() << endl;
s1.erase(5, 2); // 从第五个位置开始,删两个字符
cout << s1.c_str() << endl;
s1.erase(5, 20); // 从第五个位置开始,删完
cout << s1.c_str() << endl;
}
}
终于写完了!!
不是,这还拿不下你?