当我们写交换两个元素的函数时,通常会这样写:
void swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}但是,如果要交换 long long 类型、double 类型,甚至自定义类型,就需要写多个函数了。
为了解决这个问题,C++ 引入了模板这个概念。
template<class T>
void swap(T& a1, T& a2)
{
T tmp = a1;
a1 = a2;
a2 = tmp;
}注意点:
class 可以改成 typename,现阶段两者没有区别
T 不能代表不同类型
int a = 1;
double b = 2.0;
swap(a, b); // 报错:"swap": 未找到匹配的重载函数template<class T>
T add(const T& a, const T& b)
{
return a + b;
}
cout << add(a, (int)b) << endl;这种方法有很多局限性。
add<int>(a, b); // 在前面加上类型template<class T1, class T2>
T1 add(const T1& a, const T2& b)
{
return a + b;
}有些时候,编译器无从推断 T 的类型:
template<class T>
T* func(int n)
{
return new T[n];
}这个时候就必须显式实例化:
int* a = func<int>(5);template<class T1, class T2>
T1 add(const T1& a, const T2& b)
{
return a + b;
}
int add(const int a, const int b)
{
return a + b;
}根据调试结果,优先调用对应的普通函数。

为方便理解,先写一个简单的栈类:
template<class T>
class stack
{
public:
stack(int n = 4)
: capacity(n)
, size(0)
, _arr(new T[n])
{ }
~stack()
{
delete[] _arr;
_arr = nullptr;
capacity = 0;
size = 0;
}
void push(const T& s)
{
if (capacity == size)
{
T* tmp = new T[2 * capacity];
memcpy(tmp, _arr, sizeof(T) * size);
delete[] _arr;
capacity *= 2;
_arr = tmp;
}
_arr[size++] = s;
}
private:
size_t capacity;
size_t size;
T* _arr;
};注意: 类模板必须显式实例化
stack<int> st1;
stack<double> st2;这样,就可以让栈中存储不同类型的元素了。
// 声明
template<class T>
class stack
{
public:
// ...
void push(const T& s);
// ...
};
// 定义
template<class T>
void stack<T>::push(const T& s)
{
if (capacity == size)
{
T* tmp = new T[2 * capacity];
memcpy(tmp, _arr, sizeof(T) * size);
delete[] _arr;
capacity *= 2;
_arr = tmp;
}
_arr[size++] = s;
}甚至可以使用不同的模板参数名:
template<class X>
void stack<X>::push(const X& s)
{
// ...
}这也可以证明模板实质上是编译器自动生成特定元素类型的类。
有了模板,C++ 就可以写一份代码,无伤兼容各种类型,这就诞生了 STL:让顺序表、栈、队列等数据结构由编译器帮你生成。
在认识 string 之前,先介绍两个有用的网站:
在接下来的内容中,我们将使用非官方文档进行讲解。
注意: 这里的一些代码不需要彻底理解,我们在模拟实现部分会详细讲解。
在 C++98 中,string 构造函数有 7 个,先讲前 6 个:

// 1. 默认构造
string s0;
// 2. 拷贝构造
string s3(s1);
// 3. 子串构造
string s4(s1, 6); // 索引6到末尾
string s(s1, 6, 2); // 索引6开始,接下去2个字符
// 4. C风格字符串构造
string s1("hello world");
// 5. C风格字符串部分构造
string s2("hello world", 5); // 前5个字母
// 6. 填充构造
string s5(11, 'X');由于很多string的接口都是一个套路:string(string s3(s1)),string的一部分(string s(s1, 6, 2)),char*(string s1("hello world")),char*的一部分(string s2("hello world", 5)),迭代器(下文会讲),因此接下去就只演示传string的方法,其它的同理

我们注意到在 substring 的构造中:
string (const string& str, size_t pos, size_t len = npos);npos 是一个缺省参数,表示最后一个字符。查看 npos 的定义,可以看到它是一个静态成员,表面值是 -1,实际是极大值。
// npos 可以用 string::npos 指定调用
static const size_t npos = -1;因此,我们不写,或写一个较大的值都是没问题的,都会截取至末尾。
string s1("hello world");
cout << s1[2] << endl;
上面的语句会像数组一样,可以访问并修改对应元素。原因就是 [] 被重载了。
大概逻辑:
char& operator[](size_t t)
{
return _str[t];
}我们返回引用值,就可以查询并且修改了。
for (int i = 0; i < s.size(); i++)
{
cout << s[i] << " ";
}string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}我们可以用指针去理解:begin 指向开头,* 重载后有类似于解引用的功能,后置 ++ 重载有指向下一个元素的功能。这个方法在 C++ 标准容器中都可以用。
for (char e : s)
{
cout << e << " ";
}在范围 for 中,e 自动递增,自动停止。但其本质还是迭代器,只是多了一步将字符赋给 e。
迭代器接近于指针,我们可以给取到的字符加减:
string::iterator it = s.begin();
while (it != s.end())
{
*it += 2;
cout << *it << " ";
it++;
}
cout << endl;
for (char e : s)
{
e -= 2;
cout << e << " ";
}
在这个代码结果中,我们发现成功加了 2,但没有成功减去,因为 e 是字符的拷贝。当我们加一个引用,就可以用范围 for 修改了:
for (char& e : s)
{
e -= 2;
cout << e << " ";
}
同时,数组也可以用范围 for 遍历:
int arr[] = {0, 3, 2, 4};
for (int e : arr)
{
cout << e << " ";
}刚才的 begin、end 为正向迭代器,但我们要反向遍历数组时,由于迭代器为左闭右开区间,就需要先减,再打印:
string::iterator it = s.end();
while (it != s.begin())
{
it--;
cout << *it << " ";
}
cout << *s.begin() << endl;但是,还有反向迭代器:
string::reverse_iterator rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << " ";
rit++;
}这样,依旧用 ++ 就可以遍历。
此外,还有 const 版本的正向、反向迭代器,因此共 4 组:

// const 正向迭代器
string::const_iterator cit = s.cbegin();
while (cit != s.cend())
{
cout << *cit << " ";
cit++;
}
// const 反向迭代器
string::const_reverse_iterator crit = s.crbegin();
while (crit != s.crend())
{
cout << *crit << " ";
crit++;
}在刚才迭代器名字要敲疯了?我们可以将那一长串单词改成 auto,交给编译器自动推断:
auto crit = s.crbegin();
while (crit != s.crend())
{
cout << *crit << " ";
crit++;
}同样,范围 for 也可以用 auto:
for (auto& e : s)
{
cout << e << " ";
}同时,auto 不能做参数,但能做返回值。但用 auto 的缺点是代码可读性会变差。
string s;
cin >> s;
cout << s;由于输入、输出都被重载过了,因此可以用流进行输入、输出。

s.size(); // 推荐使用
s.length(); // 为了向前兼容而保留为了向前兼容,因此 length 依旧保留,但是 STL 的容器都用 size(毕竟树的大小不能用 length 吧),因此推荐使用 size。
由于 string 会不断扩容,我们用 capacity 观察一下它的扩容规律(VS2022):
int main()
{
string a;
int cap = a.capacity();
cout << cap << endl;
for (int i = 1; i <= 500; i++)
{
a += 'c';
if (a.capacity() != cap)
{
cap = a.capacity();
cout << cap << endl;
}
}
return 0;
}
由于这个容量是不算 \0 的,因此应该是:16 → 32 → 48 → 71...
开始扩 2 倍,之后扩 1.5 倍。
由于扩容的消耗比较大,因此在一些情况下,我们可以先估计需要多少容量,这就用到 reserve 了。
a.reserve(500);可以看到,加上这个语句,在加入 500 个字符时,扩容次数就减少了。

那会不会缩容?
a.reserve(500);
cout << a.capacity() << endl;
a += "aaa";
a.reserve(200);
cout << a.capacity() << endl;
可以看到,并没有缩容。
string a = "aaa";
cout << a.capacity() << endl;
a.clear();
cout << a.capacity() << endl;可以看到,清除后容量依旧不变,因此只是清数据。


缩容至 size:
a.shrink_to_fit();这一块用的很少的接口可以要用到时查文档。
为了方便使用,很多运算符都进行了重载:

string a = "aaa";
string b = "bbb";
b += a;只需要简洁的运算符,就可实现 string 的追加。同时,和构造函数一样,+= 的对象可以是 string、char* 等。

可以追加其它字符串,为 string 的冗余接口,可以用 += 的重载替代,没必要专门使用。

插入字符串,同样用的少,可以插入的对象多:
string a = "abcde";
string b = "fghij";
a.insert(3, b, 2, 4); // 将b从2下标开始取4个字符插入到a的3下标后面
cout << a << endl;
擦除,同样用的较少:
string a = "abcde";
a.erase(2, 2); // 擦除a从2下标开始2个字母
同样较少用,演示 string 的替换,其它方式与构造函数类似:
string a = "abcde";
string b = "fghij";
a.replace(2, string::npos, b); // 第二个数不能空着
// 从a的2个字符开始,数到末尾,替换成b将空格替换为 %%:
string p = "ab cd e f ghi";
size_t pos = p.find(" ");
while (pos != string::npos)
{
p.replace(pos, 1, "%%");
pos = p.find(" ", pos);
}
cout << p << endl;但是,由于是对 p 中间修改,需要后面字符挪动位置,因此时间复杂度较高。
我们新开数组,空间换时间:
string p = "ab cd e f ghi";
string tmp;
for (int i = 0; i < p.size(); i++)
{
if (p[i] != ' ')
tmp += p[i];
else
tmp += "%%";
}
cout << tmp << endl;因此,虽然这些接口可以一句话解决修改问题,但是如果修改位置不在结尾,时间复杂度是较高的(可达 O(n²)),因此如果要调用较多次,推荐优先修改算法。
返回 string 底层 char* 的指针,即为了和 C 兼容:
string b = "fghij";
printf("%p\n", b.c_str());string a = "abcde";
char arr[20];
a.copy(arr, a.size());缺点:要留足空间,不会自动扩容。

string a = "abcde";
size_t p = a.find('c'); // 找第一个c字符的位置
string a = "abcde";
size_t p = a.find("cd"); // 找含有的"cd"字符串位置
string a = "abede";
size_t p = a.find('e', 3); // 找3下标以后的e的位置接下来 rfind、find_first_of 等用法类似。
string p = "E:\\博客\\C++\\模板\\blog.docx"; // Windows路径需要转义
size_t pos = p.rfind(".");
cout << p.substr(pos + 1) << endl;只需要 rfind,从后面找 . 就可以了。
void getpath(string p)
{
size_t f = p.find_last_of("\\/");
cout << "path:" << p.substr(0, f + 1) << endl;
}
string p1 = "E:\\博客\\C++\\模板\\blog.docx";
string p2 = "/home/user/documents/file.txt";
getpath(p1);
getpath(p2);find_first_of:找到最前面在括号里的字符
find_last_of:找到最后面在括号里的字符
首先,我们通过 cin 读取 "hello world":

string a;
cin >> a; // 只能读到"hello"由于 cin 遇到空格、换行就会停止,因此只能读到一个单词。
如果要读整行,就需要 getline:

string a;
getline(cin, a);同时,可以加第三个参数,读到哪个字符终止:
string a;
getline(cin, a, 'r'); // 读到r终止private:
char* _str;
size_t _capacity;
size_t _size;const char* c_str() const
{
return _str;
}string()
:_str(nullptr)
,_size(0)
,_capacity(0)
{ }
string(const char* str)
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}注意点:
但是加入c_str返回指针以后,以下会程序崩溃:
string s1;
string s2 = "abc";
std::cout << s1.c_str() << std::endl;
std::cout << s2.c_str() << std::endl;原因: s1用默认构造,用nullptr,之后cout解引用,崩溃
string()
:_str(new char[1]{'\0'})
,_size(0)
,_capacity(0)
{ }
string(const char* str)
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}将默认构造改为直接开一个有\0的空间
将两个函数合为一个默认构造:
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}可以缺省值为""原因: strcpy会将\0一起拷贝(先拷贝再判断) 因此结尾自动有\0
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
}size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}不可外界修改,直接const版本
有无const版本都要
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}但显然,由于还没有写迭代器,因此不支持范围for
由于之前迭代器类似于指针,因此我们先写最简单的指针改名的方法
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;
}这样,就初步支持范围for了
for (auto& e : s2)
{
std::cout << e;
}迭代器本质: 将细节底层屏蔽,统一typedef,提供统一访问方式,不用关心底层实现细节
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0'; // 注意尾插后要在最后补\0
}在定义的private里加入这句话:
static const size_t npos = -1;注意:
两个版本,字符和字符串
void insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_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(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);
}
int end = _size + len;
while (end >= pos + len)
{
_str[end] = _str[end - len];
end--;
}
for (int i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;
}可以直接用之前的函数
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}注意,由于缺省值规定只用在声明中用一次,因此
声明:
void erase(size_t pos, size_t len = npos);定义:
void erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
for (int i = pos + len; i <= _size; i++)
{
_str[i - len] = _str[i];
}
_size -= len;
}
}但是,我们erase时出bug了:
a.erase(2);我们调试,看到明明应该是 len >= _size - pos 的分支,却进入了第二个分支
原因: 由于 len + pos 会溢出,因此这个值大小是比 _size 小的
改后就没事了。
此外,如果 int -1 和 size_t 1 比较,依旧会 -1 > 1 的神奇一幕,因为 C++ 兼容 C,会将范围小向范围大的提升,因此 int -1 提升为 2^64-1,出问题。
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* c = strstr(_str + pos, str);
if (c == nullptr)
{
return npos;
}
else
{
return (c - _str);
}
}string substr(size_t pos, size_t len = npos)
{
assert(pos < _size);
string tmp;
if (len > _size - pos)
{
len = _size - pos;
}
for (size_t i = 0; i < len; i++)
{
tmp += _str[i + pos];
}
return tmp;
}但是,我们测试时程序崩溃了:
string a = "hello";
a = a.substr(2);原因: 没有写拷贝构造,与赋值重载,默认浅拷贝,结束直接析构,无法打印
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
string& operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
_capacity = s._capacity;
_size = s._size;
strcpy(_str, s._str);
}
return *this;
}由于前一位不一定是string,我们重载成全局
bool operator<(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator<=(const string& s1, const string& s2)
{
return s1 < s2 || s1 == s2;
}
bool operator>(const string& s1, const string& s2)
{
return !(s1 <= s2);
}
bool operator>=(const string& s1, const string& s2)
{
return !(s1 < s2);
}
bool operator==(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator!=(const string& s1, const string& s2)
{
return !(s1 == s2);
}同时 "sdf" < a 这样子也是可以的
原因: 单参数构造会隐式类型转换,因此 "sdf" 会直接转为 string 并比较
std::ostream& operator<<(std::ostream& out, const string& s)
{
for (auto& e : s)
{
std::cout << e;
}
return out;
}
std::istream& operator>>(std::istream& in, string& s)
{
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}由于 cin、scanf 会忽略空格和换行,因此用 get
那为什么可以不用友元函数?
回顾上一篇博客,我们的方法有:设置成公有(绝对不行),get函数架桥获取私有,友元
但是,由于我们重载了 [] 和设置了迭代器,因此这两个东西可以扮演get函数,以访问私有
但是,这样写刚开始程序会较为频繁的扩容,我们将扩容时打印出来
而扩容,多为异地扩,复杂度较高。对此,我们可以存一个临时字符组buff,存满了再放进去,减少扩容
std::istream& operator>>(std::istream& in, string& s)
{
s.clear();
char ch;
ch = in.get();
const int N = 256;
char buff[N];
int i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i + 1 == N)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}这样扩容次数就少了
void clear()
{
_size = 0;
_str[0] = '\0';
}这样,一个较为基础的string就实现好了
现代C++,就是C++11及以后的写法,开销较少,这一准则的典范就是交换,让别人帮我做事
我们先将拷贝构造换成普通构造
string(const string& s)
{
string tmp(s._str); // 普通构造
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}同样,可以重载一个swap函数交换类
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& s)
{
string tmp(s._str); // 普通构造
swap(tmp);
}赋值重载也可以这么写
string& operator=(string tmp)
{
swap(tmp);
return *this;
}那这么写什么时候能优化呢?
我们看一下库中的swap函数:
不得了,如果不做任何优化,交换string需要进行3次拷贝
但是,用交换以后,就不需要拷贝,交换指针指向即可
并且,编译器也知道这么做很浪费,因此,为string单独重载了一个swap函数,当交换string时直接调用交换指针指向的写法
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<assert.h>
#include<string.h>
namespace bit
{
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()
:_str(new char[1]{'\0'})
,_size(0)
,_capacity(0)
{ }
string(const char* str)
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}*/
string(const char* 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)
{
string tmp(s._str);//普通构造
swap(tmp);
/*_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;*/
}
void clear()
{
_size = 0;
_str[0] = '\0';
}
//string& operator=(const string& s)
//{
// if (this != &s)
// {
// delete[] _str;
// _str = new char[_capacity + 1];
// _capacity = s._capacity;
// _size = s._size;
// strcpy(_str, s._str);
// }
// return *this;
//}
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
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];
}
void reserve(size_t n);
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos, size_t len = npos);
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
string substr(size_t pos = 0, size_t len = npos);
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
}
private:
char* _str=nullptr;
size_t _capacity=0;
size_t _size=0;
static const size_t npos = -1;
};
bool operator<(const string& s1, const string& s2);
bool operator<=(const string& s1, const string& s2);
bool operator>(const string& s1, const string& s2);
bool operator>=(const string& s1, const string& s2);
bool operator==(const string& s1, const string& s2);
bool operator!=(const string& s1, const string& s2);
std::ostream& operator<<(std::ostream& out, const string& s);
std::istream& operator>>(std::istream& in, string& s);
}#define _CRT_SECURE_NO_WARNINGS
#include"string.h"
namespace bit
{
void string::reserve(size_t n)
{
if (n > _capacity)
{
//std::cout << "reserve:" << n << std::endl;
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size == _capacity)
{
reserve(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
void string::insert(size_t pos, char ch)
{
assert(pos < _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[end] = ch;
++_size;
}
void string::insert(size_t pos, const char* str)
{
assert(pos < _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);
}
int end = _size + len;
while (end >= pos + len)
{
_str[end] = _str[end - len];
end--;
}
for (int i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
for (int i = pos + len; i <= _size; i++)
{
_str[i - len] = _str[i];
}
_size -= len;
}
}
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
for (int i = pos; i < _size; i++)
{
if (_str[i] == ch)return i;
}
return npos;
}
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
const char* c = strstr(_str + pos, str);
if (c == nullptr)
{
return npos;
}
else
{
return (c - _str);
}
}
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
string tmp;
if (len > _size - pos)
{
len = _size - pos;
}
for (int i = 0; i < len; i++)
{
tmp += _str[i + pos];
}
return tmp;
}
bool operator<(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator<=(const string& s1, const string& s2)
{
return s1 < s2 || s1 == s2;
}
bool operator>(const string& s1, const string& s2)
{
return !(s1 <= s2);
}
bool operator>=(const string& s1, const string& s2)
{
return !(s1 < s2);
}
bool operator==(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator!=(const string& s1, const string& s2)
{
return !(s1 == s2);
}
std::ostream& operator<<(std::ostream& out, const string& s)
{
for (auto& e : s)
{
std::cout << e;
}
return out;
}
std::istream& operator>>(std::istream& in, string& s)
{
s.clear();
char ch;
ch = in.get();
const int N = 256;
char buff[N];
int i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i + 1 == N)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
return in;
}
}由于深拷贝是较浪费的,浅拷贝又会有多次析构的问题,能不能将两者结合呢?
因此,我们可以加一个数字,记录了拷贝了多少次,只有要用到了再进行深拷贝,其它时候都是挂着个名头,析构时也会根据数字决定是否析构
但这个方式已经几乎被更好的方式替代,我们未来的博客会讲到