#include <bits/stdc++.h>
using namespace std;
namespace wjl
{
class string
{
public:
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;
}
string(const char *str = "") // '\0'
/* :_str(str) */ // 不能直接给 - 权限放大
:_size(strlen(str))
,_capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
void swap(string &s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string &s)
:_str(nullptr)
,_size(0)
,_capacity(0) // 初始化列表,把 this 初始化以防 swap 之后出现问题
{
// 古法
/*
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
*/
// 现代写法
string tmp(s._str);
swap(tmp);// 默认 this 调用
}
/*
string()
:_str(new char[4])
,_size(0)
,_capacity(0)
{
_str[0] = '\0';
_str[1] = '\0';
_str[2] = '\0';
_str[3] = '\0';
}
*/
const char *c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _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];
}
string &operator +=(char ch)
{
push_back(ch);
return *this;
}
string &operator +=(const char *str)
{
append(str);
return *this;
}
void reserve(size_t n) // 扩容
{
if (n <= _capacity)
{
return;
}
// 扩容 - 开新空间,拷贝数据,删除旧空间,改变指针指向
char *tmp = new char[n + 1];// 多的那个放 '\0'
strcpy(tmp, _str);// 会拷贝 '\0'
delete[] _str;
_str = tmp;
_capacity = n;
}
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);// _capacity 是 0 怎么办?
}
_str[_size++] = ch;
_str[_size] = '\0';
}
void append(const char *str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
void insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
memmove(_str + pos + 1, _str + pos, _size - pos);
_str[pos] = ch;
_size++;
}
void insert(size_t pos, const char *str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
memmove(_str + pos + len, _str + pos, _size - pos);
memcpy(_str + pos, str, len);
_size += len;
}
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
len = _size - pos;
}
memmove(_str + pos, _str + pos + len, _size - pos - len);
memset(_str + _size - len, 0, len);
_size -= len;
}
void resize(size_t n, char ch = '\0')
{
if (n <= _size)
{
_str[n] = '\0';
_size = n;
}
else
{
reserve(n);// 不管空间是大于还是小于 capacity,只需要 reserve 就可以了
while (_size < n)
{
_str[_size++] = ch;
}
_str[_size] = '\0';
}
}
size_t find(char ch, size_t pos = 0) // 从 pos 位置开始查找
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t find(const char *sub, size_t pos = 0)
{
const char *p = strstr(_str + pos, sub);
if (p)
{
return p - _str;// 指针相减
}
else
{
return npos;
}
}
string substr(size_t pos, size_t len = npos)
{
assert(pos < _size);
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;// 注意浅拷贝:_str 地址不变,返回时被析构
}
bool operator <(const string &s) const
{
return strcmp(_str, s._str) < 0;
}
bool operator ==(const string &s) const
{
return strcmp(_str, s._str) == 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);
}
// string &operator =(const string &s)
// {
// if (this != &s)
// {
// // 古法
// /*
// char *tmp = new char[s._capacity + 1];
// strcpy(tmp, s._str);
// delete[] _str;
// _str = tmp;
// _size = s._size;
// _capacity = s._capacity;
// */
//
// // 现代方法
// string temp(s);
// swap(temp);
// // 注意原本的 this 被交换给了 temp,自动析构
// }
//
// return *this;
// }
// 极致现代写法
string &operator =(string s)
{
swap(s);
return *this;
}
void clear()
{
_str[0] = '\0';
_size = _capacity = 0;
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
private:
char *_str;
size_t _size;
size_t _capacity;
public:
const static size_t npos; /* = -1; */ // 特例:const static 整形 可以在类里面初始化
};
ostream &operator <<(ostream &out, const string &s);
istream &operator >>(istream &in, string &s);
ostream &operator <<(ostream &out, const string &s) // 流插入
{
for (const char &i : s)
{
out << i;
}
return out;
}
istream &operator >>(istream &in, string &s) // 流提取
{
s.clear();
char buffer[129];// 缓冲空间
size_t i = 0;
char ch;
/* in >> ch; */ // 流插入拿不到空格和换行
begin:
ch = in.get();
if (ch == ' ' || ch == '\n')
{
goto begin;
}
while (ch != ' ' && ch != '\n') // 空格自动结束
{
buffer[i++] = ch;
if (i == 128)
{
buffer[i] = '\0';
s += buffer;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buffer[i] = '\0';
s += buffer;// 减少扩容次数
}
return in;
}
}
const size_t wjl::string::npos = -1;// static 成员要在类外初始化
void func(const string& s) // 传值传参会进行深拷贝,所以一般用 const & 传参
{
auto it = s.begin();// cbegin 也行
auto rit = s.rbegin();// crbegin 也行
while (it != s.end())
{
/* *it = 'a'; */ // err - 只读
cout << *it;
it++;
}
cout << endl;
while (rit != s.rend())
{
cout << *rit;
rit++;
}
cout << endl;
}
void test_string1()
{
wjl::string s1("hello world");
cout << s1.c_str() << endl;
wjl::string s2;
cout << s2.c_str() << endl;
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i];
}
cout << endl;
auto it = s1.begin();
while (it != s1.end())
{
cout << *it;
++it;
}
cout << endl;
for (auto &ch : s1) // 底层是迭代器,begin 换成大写都调用不到
{
cout << ch;
}
cout << endl;
}
void test_string2()
{
wjl::string s1("hello world");
cout << s1.c_str() << endl;
s1.push_back(' ');
s1.append("hello bit hello bit");
cout << s1.c_str() << endl;
s1 += '#';
s1 += "******************";
cout << s1.c_str() << endl;
}
void test_string3()
{
wjl::string s1("hello world");
cout << s1.c_str() << endl;
s1.insert(5, '%');
cout << s1.c_str() << endl;
s1.insert(s1.size(), '%');
cout << s1.c_str() << endl;
s1.insert(0, '%');// 注意类型提升:在多操作数的操作符两端,类型会自动提升至高等级
cout << s1.c_str() << endl;
s1.insert(0, "awa");
cout << s1.c_str() << endl;
s1.insert(5, "awa");
cout << s1.c_str() << endl;
s1.insert(s1.size(), "qwq");
cout << s1.c_str() << endl;
s1.erase(0, 3);
cout << s1.c_str() << endl;
s1.erase(2);
cout << s1.c_str() << endl;
wjl::string s2("awa");
cin >> s1;
cin >> s2;
cout << s1 << endl;
cout << s2 << endl;
}
void test_string4()
{
wjl::string s1("hello world");
cout << s1 << endl;
s1.resize(5);
cout << s1 << endl;
s1.resize(10, 'x');
cout << s1 << endl;
}
void test_string5()
{
wjl::string s5("https://legacy.cplusplus.com/reference/string/string/rfind/");
// 协议,域名,资源名
wjl::string sub1, sub2, sub3;
size_t i1 = s5.find(':');
if (i1 != wjl::string::npos)
{
sub1 = s5.substr(0, i1);
}
else
{
cout << "没有找到 i1" << endl;
}
size_t i2 = s5.find('/', i1 + 3);
if (i2 != wjl::string::npos)
{
sub2 = s5.substr(i1 + 3, i2 - (i1 + 3));// 构造 + 赋值,不能优化
// 左闭右开区间 - i2 - i1 就是字符个数
// 闭区间 - i2 - i1 + 1
sub3 = s5.substr(i2 + 1);
}
else
{
cout << "没有找到 i2" << endl;
}
cout << sub1 << endl;
cout << sub2 << endl;
cout << sub3 << endl;
}
void test_string6()
{
wjl::string s1("hello world");
wjl::string s2 = s1;// 拷贝构造
cout << s1 << endl;
cout << s2 << endl;
wjl::string s3("xxxxxxxxxxx");
s2 = s3;
cout << s2 << endl;
string s4;
cout << sizeof(s4) << endl;// 8
string s5("hello world");
cout << sizeof(s5) << endl;// 8
// 里面存储指向堆空间的指针,前面的空间包含三个变量 _size _capacity _refcount(引用计数)
// 后面存储字符串
string s6(s5);
cout << (void *)s5.c_str() << endl;// 0xa00022908
cout << (void *)s6.c_str() << endl;// 0xa00022908
s6[0]++;
cout << (void *)s6.c_str() << endl;// 0xa00022938
// 浅拷贝的问题:
// 1.析构两次
// 2.更改一个变量同时会更改其他变量
// 解决浅拷贝,但是不用深拷贝:引用计数 -> 智能指针
// 记录有几个对象指向这块空间
// 拷贝的时候 ++ 引用计数,析构的时候 -- 引用计数
// 引用计数减到 0 时,说明是最后一个对象,再析构
// 一个修改数据会影响另一个 - 写时拷贝(延迟拷贝)
// 在写入的时候再拷贝
// 检测引用计数,如果不为 1,就要深拷贝,并且更新原来的引用计数,创建新的引用计数
}
int main()
{
{
// string 基本用法
// string 是模板类 basic_string 的 char 类型的显式实例化
string s1;// 无参构造
string s2("hello");// 带参构造
// 重载了流插入和流提取 - 按需分配空间(自动扩容),缓解了 C 的不足
cin >> s1;
cout << s1 << endl;
cout << s2 << endl;
// cin >> s2;
// cout << s2 << endl;
// 字符串拼接
string ret1 = s1 + s2;
cout << ret1 << endl;
string ret2 = s1 + "我来了";
cout << ret2 << endl;
cout << s1 + s1 << endl;
// string 构造
string s3("hello world");
string s4 = "hello world";// 构造 + 拷贝构造 + 优化
// 体验:遍历 string
// 方法 1
for (size_t i = 0; i < s3.size(); i++)
{
// 读
cout << s3[i];
}
cout << endl;
for (size_t i = 0; i < s3.size(); i++)
{
// 写
s3[i]++;
cout << s3[i];
s3[i]--;
}
cout << endl;
// 优化版本(范围 for) - 自动判断结束,自动 ++
for (const auto &item: s3) // 底层是迭代器
{
cout << item;
}
cout << endl;
// 方法 2:迭代器
// 迭代器:像指针一样的东西
string::iterator it = s3.begin();// begin - 开始位置的指针
// string 类里的 _size 不包括 '\0' - 有效字符
// end 指向最后一个有效字符的下一个位置 - '\0'
while (it != s3.end()) // 不要用 '<' -
{
// 读
cout << *it;
++it;
}
cout << endl;
it = s3.begin();
while (it != s3.end())
{
// 写
*it = 'a';
++it;
}
cout << s3 << endl;
// 迭代器的优势:通用性 - 可以用于任何结构,并且代码结构很相似(访问方式相同)
// 原因 - 运算符重载
// list 不支持 [] 访问
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
auto lit = lt.begin();
while (lit != lt.end()) // 不能使用 '<' - 空间不连续,后面空间的地址不一定比前面大
{
cout << *lit << " ";
++lit;
}
cout << endl;
// 反向迭代器
string s5 = "hello world";
auto rit = s5.rbegin();
while (rit != s5.rend())
{
cout << *rit;
++rit;// 还是 ++
}
cout << endl;
// 常量迭代器
func(s5);
// 子串的拷贝构造
string s6(s5, 6, 5);
cout << s6 << endl;
string s7(s5, 6);// 缺省参数,如果不给值,为 -1
// npos 是 size_t 类型,所以是 4G 字节(2 ^ 32)
// 如果字符串太短,就会取到字符串结束 - 有多少取多少
cout << s7 << endl;
string s8(10, 'a');// 10 个 a 初始化
string s9(++s5.begin(), --s5.end());// 迭代器区间初始化
// 赋值运算符重载
s8 = s7;
s8 = "xxx";
s8 = 'y';
// size 和 length
string s10 = "hello world";
cout << s10.size() << s10.length() << endl;// 不包括 '\0'
// size 具有通用性,但是 length 没有,因为 string 是早期 C++ 设计的产物,所以先用 length,之后加上了 size
// clear - 清除数据,但是不释放空间
cout << s10.capacity() << endl;
s10.clear();
cout << s10.capacity() << endl;
// capacity - 容量
// empty - 判空
// max_size() - 最大可开辟空间,没什么用,只有理论意义
// c_str - 将字符串转换成字符数组,返回常量字符指针
}
{
// string 的扩容
string s1;
size_t old = s1.capacity();
cout << "开始容量:" << old << endl;
for (size_t i = 0; i < 100; i++) {
s1.push_back('x');
if (s1.capacity() != old) {
cout << "扩容:" << s1.capacity() << endl;
old = s1.capacity();
}
}
// 0 - 1 - 2 - 4 - 8 - 16 - 32 - 64 - 128
// reserve - 重新分配空间
string s2;
s2.reserve(100);
cout << "开始容量:" << s2.capacity() << endl;
// 有可能只给 100,也有可能给一个大于 100 的数
// 价值:体现在已知大概需要多少空间,提前开好空间 - 减少扩容次数,提高效率
s2.reserve(10);
cout << "更改容量:" << s2.capacity() << endl;// 没有缩小,还是 100
// 标准没有规定不能缩小空间,当写出收缩的指令的时候,编译器会把它当做一个不具有约束力的请求
// resize - 改变长度
// void resize(size_t n)
// void resize(size_t n, char c)
// 如果不给第二个参数,那么多出来的 size 用 '\0' 填充
// 假设 size = 7,capacity = 10
// 有三种情况:
// 1.更改后的 size > 10
// 2.更改后的 size 在 7 - 10 之间
// 3.更改后的 size < 7
string s3 = "hello world";
cout << s3.size() << endl;// 11
cout << s3.capacity() << endl;// 11 - 编译器优化:知道你要开多少字节空间,直接开好
s3.resize(13);
cout << s3.size() << endl;// 13
cout << s3.capacity() << endl;// 22 - 两倍,自动扩容
s3.resize(15, 'x');// 会把加上的空间赋值成 'x'
cout << s3 << endl;
cout << s3.size() << endl;
cout << s3.capacity() << endl;
s3.resize(5);// 删除 - 保留前 n 个字符
cout << s3 << endl;
cout << s3.size() << endl;// 5
cout << s3.capacity() << endl;// 22 - 不变
// 用途:初始化(?)但是好像构造函数也能直接初始化
}
{
// at - []
string s = "hello world";
cout << s.at(1) << endl;// 越界 - 抛出异常
}
{
// push_back() - 尾插
string s;
s.push_back('#');
// append() - 尾插字符串/字符
s.append("hello");
string ss(" world");
s.append(ss);
cout << s << endl;
// += - 运算符重载
s += '#';
s += "hello";
s += ss;
cout << s << endl;
// + (全局函数,非成员函数)
string ret = ss + "hello";
cout << ret << endl;
// 少用 +,因为代价很大
}
{
// assign - 赋值
string str = "xxx";
string base = "hello world";
str.assign(base);
cout << str << endl;// 会把原来的内容清除
str.assign(base, 5, 5);
cout << str << endl;
}
{
// insert - 在 pos 位置之前插入
string str = "hello world";
str.insert(0, 1, 'x');// n 是字符个数
str.insert(str.begin(), 'x');
cout << str << endl;
}
{
// erase
// string &erase(size_t pos = 0, size_t len = npos);
// 把第 pos 个位置之后的 len 个字符删除
string s = "hello world";
s.erase(5, 6);
cout << s << endl;
}
{
// replace - 替换
// string &replace(size_t pos, size_t len, const string &str);
// pos 位置开始的 len 个字符替换成 str
string s = "hello world";
s.replace(5, 1, "%%20");// 也可以替换成字符
cout << s << endl;
}
// insert,erase,replace 能不用就尽量不用,因为需要挪动数据,效率不高
// 接口设计复杂,需要时查一下文档
{
// 空格替换为 %20
string s = "The quick brown fox jumps over a lazy dog";
// 1.find + replace
// 2.swap
// 3.以空间换时间 - 再开一个
string s1;
for (const auto &item: s)
{
if (item != ' ')
{
s1 += item;
}
else
{
s1 += "20%";
}
}
// s = s1;
// s.assign(s1);
/* swap(s, s1); */ // 虽然可以,但是效率低 - 一次拷贝构造,两次深拷贝
// 但是库里提供了一个 string 类型的参数 swap,避免调用到模板类
printf("s:%p\n", s.c_str());// s:0xa00012828
printf("s1:%p\n", s1.c_str());// s1:0xa00022958
s.swap(s1);// 只是交换成员函数,效率高很多
printf("s:%p\n", s.c_str());// s:0xa00022958
printf("s1:%p\n", s1.c_str());// s1:0xa00012828
cout << s << endl;
// 尾删 pop_back
}
{
// shrink_to_fit - 缩容
// capacity 减少到 size
}
{
// c_str 和 data - 把 string 类里面的 _str 拿出来
// 类似于 size 和 length
}
{
// copy - 复制
// 用处不大
}
{
// find 系列
// size_t find(const string &str, size_t pos = 0) const;
// const char *s, size_t pos = 0
// const char *s, size_t pos, size_t n
// char c, size_t pos = 0
// 读取文件的后缀
string s1("test.c");
string s2("test.cpp.tar.zip");
size_t i = s1.find('.');// 4
// substr - 取子串
// string substr(size_t pos = 0, size_t len = npos) const;
// 从 pos 位置(包括 pos)开始取 len 个字符
string s3 = s1.substr(i);
string s4 = s2.substr(s2.rfind('.'));// 从后面开始找
cout << s3 << endl;
cout << s4 << endl;
string s5("https://legacy.cplusplus.com/reference/string/string/rfind/");
// 协议,域名,资源名
string sub1, sub2, sub3;
size_t i1 = s5.find(':');
if (i1 != string::npos)
{
sub1 = s5.substr(0, i1);
}
else
{
cout << "没有找到 i1" << endl;
}
size_t i2 = s5.find('/', i1 + 3);
if (i2 != string::npos)
{
sub2 = s5.substr(i1 + 3, i2 - (i1 + 3));
// 左闭右开区间 - i2 - i1 就是字符个数
// 闭区间 - i2 - i1 + 1
sub3 = s5.substr(i2 + 1);
}
else
{
cout << "没有找到 i2" << endl;
}
cout << sub1 << endl;
cout << sub2 << endl;
cout << sub3 << endl;
// find_first_of
// size_t find_first_of(const string &str, size_t pos = 0)
// 查找主串中的字符是否匹配 str 里面的每个字符,如果有一个匹配,就返回位置下标
string str ("Please, replace the vowels in this sentence by asterisks.");
size_t found = str.find_first_of("aeiou");
while (found != string::npos)
{
str[found] = '*';
found = str.find_first_of("aeiou", found + 1);
}
cout << str << '\n';
// find_last_of 从后往前找
// find_first_not_of - 找主串中第一个不匹配 str 里的任意字符,返回其下标
string st("Please, replace the vowels in this sentence by asterisks.");
size_t fond = st.find_first_not_of("aeiou");
while (fond != string::npos)
{
st[fond] = '*';
fond = st.find_first_not_of("aeiou", fond + 1);
}
cout << st << '\n';
}
{
// getline - 获取一行
// 输入带有空格的文本时,cin 不能解决问题
string s;
getline(cin, s);
cout << s << endl;
getline(cin, s, '!');// 自己定义结束符,直到 delim 字符才会结束
cout << s << endl;
}
{
// 字符编码介绍
// ASCII 码不足以表示汉字 - Unicode 万国码
// Unicode 编码分为 UTF-8 UTF-16 UTF-32
// UTF-8 是变长的 - 不同字符使用的空间可能不同 - 1 ~ 4 个字节
// UTF-16 和 UTF-32 把所有字符统一用 16 / 32 比特位存储
char str1[] = "hello world";
char str2[] = "比特 hello";
cout << sizeof(str1) << endl;// 12
cout << sizeof(str2) << endl;// 13 - 每个汉字占三个字节
wchar_t ch1;// wide char - 两个字节的字符
cout << sizeof(ch1) << endl;
char16_t ch2;// 2 字节 - 对应 UTF-16
char32_t ch3;// 4 字节 - 对应 UTF-32
cout << sizeof(ch2) << endl;
cout << sizeof(ch3) << endl;
string s;// UTF-8
u16string us1;// UTF-16
u32string us2;// UTF-32
// 上面三个 class 的区别就是模板参数不同 - char,char16_t,char32_t
// 乱码 - 写入字符的编码和读取字符的编码不一样
// GBK - 国标 扩展,国家规定标准编码表
str2[2]++;
cout << str2 << endl;
str2[2]++;
cout << str2 << endl;
str2[2]++;
cout << str2 << endl;
str2[2]++;
cout << str2 << endl;
str2[5]--;
cout << str2 << endl;
str2[5]--;
cout << str2 << endl;
str2[5]--;
cout << str2 << endl;
str2[5]--;
cout << str2 << endl;
}
{
// string 模拟实现
test_string1();
test_string2();
test_string3();
test_string4();
test_string5();
test_string6();
}
return 0;
}