文章目录
- [一. string的默认成员函数](#一. string的默认成员函数)
-
- [1.1 构造函数](#1.1 构造函数)
- [1.2 析构函数](#1.2 析构函数)
- [1.3 拷贝构造函数](#1.3 拷贝构造函数)
- [1.4 赋值运算符重载](#1.4 赋值运算符重载)
- [二. 模拟实现string库](#二. 模拟实现string库)
-
- [2.1 c_str() 的实现](#2.1 c_str() 的实现)
- [2.2 size() 的实现](#2.2 size() 的实现)
- [2.3 capacity() 的实现](#2.3 capacity() 的实现)
- [2.4 empty() 的实现](#2.4 empty() 的实现)
- [2.5 clear() 的实现](#2.5 clear() 的实现)
- [2.6 operator[ ] 的实现](#2.6 operator[ ] 的实现)
- [2.7 reserve() 的实现](#2.7 reserve() 的实现)
- [2.8 resize() 的实现](#2.8 resize() 的实现)
- [三. 实现迭代器(iterator)](#三. 实现迭代器(iterator))
-
- [3.1 范围for](#3.1 范围for)
- [四. string的增删改查](#四. string的增删改查)
-
- [4.1 push_back() 的实现](#4.1 push_back() 的实现)
- [4.2 append() 的实现](#4.2 append() 的实现)
- [4.3 operator+= 的实现](#4.3 operator+= 的实现)
- [4.4 insert() 的实现](#4.4 insert() 的实现)
- [4.5 erase() 的实现](#4.5 erase() 的实现)
- [4.6 find() 的实现](#4.6 find() 的实现)
- [五. 传统写法和现代写法的对比](#五. 传统写法和现代写法的对比)
-
- [5.1 拷贝构造的现代写法](#5.1 拷贝构造的现代写法)
- [5.2 赋值运算符重载的现代写法](#5.2 赋值运算符重载的现代写法)
- [5.3 整体代码改进](#5.3 整体代码改进)
- [六. 其他运算符重载](#六. 其他运算符重载)
-
- [6.1 operator+ 的实现](#6.1 operator+ 的实现)
- [6.2 operator== 的实现](#6.2 operator== 的实现)
- [6.3 operator> 的实现](#6.3 operator> 的实现)
- [6.4 operator< 的实现](#6.4 operator< 的实现)
- [6.5 剩下的直接复用](#6.5 剩下的直接复用)
- [七. 流操作](#七. 流操作)
-
- [7.1 流插入](#7.1 流插入)
- [7.2 流提取](#7.2 流提取)
- [7.3 getline() 的实现](#7.3 getline() 的实现)
- [八. MyString::string源码](#八. MyString::string源码)
- [九. 写时拷贝(了解)](#九. 写时拷贝(了解))
- END
string类在我们日常写算法题和项目中经常用得到,有了string使我们对字符串的操作极其方便,那么string的底层原理到底是什么呢?接下来我们模拟一下实现string类,去了解它的本质到底是什么,只有自己尝试造一次轮子,心里才会更加清楚它和有利于加深对它的理解。
string的存储结构与顺序表很相似:
💬string.h文件
cpp
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;
namespace MyString
{
class string
{
public:
static const size_t npos = -1; //npos是size_t类型的最大值
private:
char* _str; //存储字符串的首地址
size_t _size; //存储字符串的长度
size_t _capacity; //存储字符串的容量
};
}
使用命名空间MyString将string类进行封装,是为了防止与std标准库的string冲突。
- _str:指向字符串首字符的指针
- _size:代表所存储字符串的有效个数
- _capacity:代表字符串的最大容量
- _npos:是一个静态常量的无符号整型,此常量值为定为-1,因为size_t是无符号整型,所以它代表的是无符号整型的最大值。
关于npos被定义为static const size_t。我们知道类里的static变量只能在类中声明,在类外定义 ,因为如果一开始就给初始值,这个初始值是给构造函数的初始化列表的,但是static变量不走初始化列表,那么就会报错。如果该静态变量被const修饰,那它既可以在类中定义初始化也可以在类外定义初始化,因为它的初始化只有一次,就当作编译器对它的特殊处理。
关于npos的初始化,以下方式都是可以的:
cpp
class string
{
public:
static const size_t npos = -1;
};
cpp
class string
{
public:
static const size_t npos;
};
const size_t string::npos = -1;
一. string的默认成员函数
1.1 构造函数
我们来看这样的一个代码:
cpp
string::string(char* str)
:_str(str)
{
_size = strlen(str);
_capacity = _size + 1;
}
这个构造函数存在两个问题:
1.潜在的内存管理问题
这个构造函数直接将传入的char* str 指针赋值给类内部的_str成员变量。这意味着类实例的_str指针和外部传入的char* str指向同一块内存。如果外部代码修改或释放了这块内存,那么类内部的_str指针将变成悬空指针或指向无效内存,导致未定义行为。
2.缺乏对参数的保护
如果传入的char* str 是nullptr 或者指向的内存不是以'\0'结尾的字符串,strlen(str)将导致未定义行为,可能程序崩溃。
所以每次调用构造函数时都要进行动态内存分配并复制内存,确保了对资源的拥有和管理。
💬string.h文件
cpp
string(const char* str = ""); //默认构造函数,缺省值为空字符串
string(size_t n, char c); //初始化为n个字符c
💬string.cpp文件
cpp
string::string(const char* str)
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_size + 1]; //多开一个空间用来存放'\0'
strcpy(_str, str);
}
string::string(size_t n, char c)
:_size(n)
{
_capacity = _size;
_str = new char[_size + 1];
for (size_t i = 0; i < n; i++)
{
_str[i] = c;
}
_str[n] = '\0';
}
这里解释一下为什么在string(const char* str = ""); 中字符串str给的缺省值是空字符串,不能是'\0'和nullptr吗?
- 不能是'\0'的原因
因为str是C语言风格的常量字符串,在字符串的末尾会自动添加'\0',无需我们手动写。如果写了'\0',那么该字符串str就有两个'\0'了。
- 不能是nullptr的原因
如果str指向nullptr,那么当调用函数strlen(str)时程序就会崩溃,因为strlen()函数是不会去检查空的,它是去找'\0'的。相当于直接对指针str解引用了,因为str指向空,所以引发了空指针问题。
所以我们这里给的是空字符串"",因为常量字符串默认就带有'\0',所以不会出现问题。
1.2 析构函数
注意:申请内存时用的是new[ ],那么释放内存时就要用delete[ ]。
💬string.h文件
cpp
~string();
💬string.cpp文件
cpp
string::~string()
{
delete[] _str;
_str = nullptr; //不要忘了将指针_str置为空,避免_str成为野指针
_size = _capacity = 0;
}
1.3 拷贝构造函数
我们知道,当我们不写拷贝构造函数时,编译器会自动生成一个拷贝构造函数。
💬 Test.cpp文件
cpp
#include "string.h"
void Test1()
{
MyString::string b(10, 'c');
MyString::string s1(b);
}
int main()
{
Test1();
return 0;
}
当执行上述程序时,程序就崩溃了:
上述string类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会生成默认的,当用b构造s1时,编译器会调用默认的拷贝构造。最终导致的问题是,b._str,s1._str共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝/值拷贝。
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

通过调试可以发现,b._str和s1._str确实指向了同一块内存空间。因为后定义的先析构,s1先调用析构函数将_str指向的内存空间释放掉了,b再想调用析构函数将_str指向的空间释放就会报错,因为一块空间不能被释放多次,只能释放一次。
那么如何解决这个问题呢❓
只需要将对象b和s1的_str指向两个不同的内存空间即可。

这种拷贝方式叫做深拷贝 :对于包含指针或动态分配内存的对象,深拷贝会分配新的内存并复制内容,确保两个对象不共享同一块内存。
使用场景:如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。
浅拷贝与深拷贝的区别:
特性 | 浅拷贝 | 深拷贝 |
---|---|---|
内存分配 | 不分配内存,直接复制指针 | 分配新内存并复制内容 |
对象独立性 | 两个对象共享同一块内存 | 两个对象拥有独立的内存 |
安全性 | 可能导致双重释放或悬空指针等问题 | 更安全,避免了共享内存带来的问题 |
性能 | 更快,因为不涉及内存分配和数据复制 | 较慢,因为需要内存分配和数据复制 |
适用场景 | 对象不包含动态内存分配的资源或指针 | 对象包含动态内存分配的资源或指针,需要独立副本 |
💬string的拷贝构造函数
cpp
string::string(const string& s)
{
_str = new char[s._capacity + 1]; //多开一个空间用来存放'\0'
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}

通过调试可以看到b._str和s1._str指向不同的两个内存空间。
1.4 赋值运算符重载
cpp
void Test1()
{
MyString::string s1("hello world");
MyString::string s2("hello");
s1 = s2;
}
看如上代码,试图将s2赋值给s1,结果发生了如下报错。和之前拷贝构造一样,如果自己不写,编译器会自动生成一个浅拷贝方式的赋值运算符重载函数,s1._str和s2._str又指向了同一块内存空间,当调用析构函数时一块内存空间被析构多次而引发报错。


先看一下以下这段代码有没有什么问题
cpp
string& string::operator=(const string& s) //返回当前对象的引用是为了能连线赋值
{
delete[] _str; //先将当前的内存空间释放掉
_str = new char[s._capacity + 1]; //重新开辟和s一样大的空间,多开一个空间是为了存储'\0'
strcpy(_str, s._str); //复制数据
_size = s._size;
_capacity = s._capacity;
return *this;
}

看似好像没问题,赋值后s1和s2都指向了两块不同的内存空间。
那我们再来看一下以下示例:
cpp
void Test2()
{
MyString::string s1("hello world");
s1 = s1;
}
如果一个对象给自己赋值就会发生如上问题:先将_str指向的内存空间释放掉,然后再开辟一个新的空间,然后再尝试复制数据,但是_str之前的空间已经被释放了,现在的_str指向的是新的空间,新空间里面没有存储任何数据(包括 '\0' ),自己给自己拷贝数据时就会出现随机字符。
那么怎么解决这个问题呢?------>很简单,只要判断传入的对象s的地址是否和当前对象的地址相同即可,如果相同则无需任何操作,直接返回,否则再进行对应的操作。
💬string的赋值运算符重载
cpp
string& string::operator=(const string& s) //返回当前对象的引用是为了能连线赋值
{
if (this != &s) //防止自己给自己赋值
{
delete[] _str; //先将当前的内存空间释放掉
_str = new char[s._capacity + 1]; //重新开辟和s一样大的空间,多开一个空间是为了存储'\0'
strcpy(_str, s._str); //复制数据
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
如果担心开辟空间失败,又已经把_str指向的内存空间释放了,那么可以先用临时指针去指向新开辟的空间,如果开辟成功了再将_str指向的旧空间释放,再指向新空间即可。
cpp
string& string::operator=(const string& s) //返回当前对象的引用是为了能连线赋值
{
if (this != &s) //防止自己给自己赋值
{
char* tmp = new char[s._capacity + 1]; //先用临时指针tmp接受新开辟的空间
strcpy(tmp, s._str); //如果开辟成功就将数据赋值到这个空间
delete[] _str; //释放旧空间
_str = tmp; //将_str指向新空间
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
再来看一下如下代码:
cpp
void Test2()
{
//构造+拷贝------>直接构造
MyString::string s1 = "hello world";
}
看到这段代码是不是会想到,先用字符串"hello world"构造一个临时对象(隐式类型转换),再用这个临时对象拷贝构造出s1。但是编译器将这个过程优化了,优化成直接用字符串"hello world"构造s1,调用的是string(const char* str = ""); 这个函数。
注意 :字符串"hello world"隐式类型转换为string对象时,需要有用字符串构造对象的构造函数,比如string(const char* str = "");
转到反汇编验证一下:

发现它们调用的是一模一样的函数。
二. 模拟实现string库
2.1 c_str() 的实现
cpp
const char* c_str() const;
这个函数返回的是C语言风格的字符串,不支持修改。因为不涉及对象成员的修改,所以可以用const修饰this,同时也兼并了const对象。
cpp
const char* string::c_str() const
{
return _str;
}
cpp
void Test3()
{
MyString::string s1("hello world");
cout << s1.c_str() << endl;
}

2.2 size() 的实现
cpp
size_t size() const;
这个函数的功能是返回字符串的长度,跟上面的类似,这里直接返回_size即可。
cpp
size_t string::size() const
{
return _size;
}
2.3 capacity() 的实现
cpp
size_t capacity() const;
这个函数的功能返回字符串的容量,跟上面的类似,这里直接返回_capacity即可。
cpp
size_t string::capacity() const
{
return _capacity;
}
2.4 empty() 的实现
cpp
bool empty() const;
这个函数的功能是判断字符串是否为空,如果为空返回true,否则返回false。
cpp
bool string::empty() const
{
return _size == 0;
}
2.5 clear() 的实现
cpp
void clear();
该函数的功能是清空一个字符串,但是不释放空间,只是在下标位置为0的字符置为'\0',_size置为0即可。
cpp
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
2.6 operator[ ] 的实现

cpp
char& operator[](size_t pos);
const char& operator[](size_t pos) const;
这里不仅要写普通对象的版本,还要写const对象的版本。因为普通对象指定位置的字符是可以通过该函数进行修改的,但是const对象不行,它的字符串是不能修改的。
cpp
char& string::operator[](size_t pos)
{
assert(pos < _size); //确保pos不能越界,越界了就断言
return _str[pos];
}
const char& string::operator[](size_t pos) const
{
assert(pos < _size); //确保pos不能越界,越界了就断言
return _str[pos];
}
cpp
void Test3()
{
MyString::string s1("hello world");
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;
for (size_t i = 0; i < s1.size(); i++)
{
s1[i] += 1;
cout << s1[i] << " ";
}
}

2.7 reserve() 的实现

cpp
void reserve(size_t n = 0);
该函数的功能是调整字符串容量大小为n,如果n大于字符串容量则将其扩容到n个字节,否则不会进行任何操作。
cpp
void string::reserve(size_t n)
{
//检查n是否大于原有的容量,必须大于才能调整字符串的容量
if (n > _capacity) {
char* tmp = new char[n + 1]; //用指针tmp去接受新开的内存空间
strcpy(tmp, _str); //如果开辟成功,将数据拷贝到新空间
delete[] _str; //将旧空间释放掉
_str = tmp; //将_str指向新空间
_capacity = n; //更改容量值
}
}
注意 :不要忘记多开一个空间存储 \0
接下来测试一下案例:
cpp
void Test4()
{
MyString::string s1("hello world");
s1.reserve(20);
s1.reserve(5);
}
当前字符串容量小于20,新开一个内存空间的大小为20,并将数据拷贝给这个新空间。
当前字符串容量大于5,则不会进行任何操作。
2.8 resize() 的实现

cpp
void resize(size_t n);
void resize(size_t n, char c);
功能:将字符串的长度调整为n个字符。
解释:
- 如果n小于当前字符串的长度,则字符串缩短为前n个字符,并删除第n个字符以外的字符。
- 如果n大于当前字符串的长度,则通过在字符串末尾插入所需数量的字符来增加字符串的长度,以达到n的大小。如果函数参数指定了字符c,则在字符串末尾插入的字符为c的副本,否则,插入的字符为空字符。
- 缩小长度:将_size赋值为n,并在下标为_size的字符设置为\0
- 增加长度:1. 增加后的长度在容量范围内:如果指定了字符c,则在[_size,n-1]的位置插入字符c,再将_size赋值为n,并在n的位置插入\0,如果没有指定字符c,就执行后两步。2. 增加后的长度超过了容量范围:先对字符串扩容,再执行以上操作。
cpp
void string::resize(size_t n)
{
if (n > _capacity) {
reserve(n); //扩容
}
_size = n;
_str[_size] = '\0';
}
void string::resize(size_t n, char c)
{
if (n > _capacity) {
reserve(n); //扩容
}
for (size_t i = _size; i < n; i++)
{
//如果是增加长度就会进入循环,在字符串末尾插入所需数量的字符c
_str[i] = c;
}
_size = n;
_str[_size] = '\0';
}
测试:
创建string类型的对象为s1
当前字符串容量小于20,先扩容再执行操作,因为没有给定补全的字符,所以给空字符
调整字符串长度为5,_size赋值为5,并在位置为_size的字符赋为\0
创建string类型的对象为s2
当前字符串容量小于20,先扩容再执行操作,因为给定了补全的字符,所以在字符串末尾插入所需数量的字符('x')
调整字符串长度为5,_size赋值为5,并在位置为_size的字符赋为\0
三. 实现迭代器(iterator)
迭代器的本质
- 指针或指针封装:迭代器的底层本质是一个指针,或者对指针进行了封装。例如,vector的迭代器就是一个原生态指针T*,而其他容器如list、map等的迭代器则是对指针进行了封装,以适应其内部复杂的数据结构。
- 运算符重载:迭代器通过重载解引用运算符(operator*)、递增运算符(operator++)等,使得用户可以像操作指针一样操作迭代器,实现对容器元素的访问和遍历。
在C++中,std::string 的迭代器底层实现本质上是一个指针。std::string 内部通常使用连续的内存块来存储字符序列,因此其迭代器可以直接使用字符指针char*(对于非const字符串)或const char*(对于const字符串)来实现。
迭代器的声明:
cpp
typedef char* iterator;
iterator begin();
iterator end();
typedef const char* const_iterator;
const_iterator begin() const;
const_iterator end() const;
迭代器的定义:
cpp
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
string::const_iterator string::begin() const
{
return _str;
}
string::const_iterator string::end() const
{
return _str + _size;
}
测试:
cpp
void Test5()
{
MyString::string s1("hello world");
MyString::string::iterator it1 = s1.begin();
while (it1 != s1.end())
{
cout << *it1;
it1++;
}
cout << endl;
const MyString::string s2("hello hello");
MyString::string::const_iterator it2 = s2.begin();
while (it2 != s2.end())
{
cout << *it2;
it2++;
}
cout << endl;
}

3.1 范围for
cpp
MyString::string s1("hello world");
for (auto e : s1)
{
cout << e;
}
cout << endl;

因为范围for的的底层实现本质是迭代器,编译时范围for会被替换成迭代器,所以只要把迭代器实现好了,范围for就可以直接使用。
转到反汇编就可以验证:
注意:
如果将迭代器begin()替换成Begin(),范围for还能正常使用吗?
cpp
string::iterator string::Begin()
{
return _str;
}
string::const_iterator string::Begin() const
{
return _str;
}
程序发生了以下报错:
这说明范围for是按照迭代器固定名称(begin和end)去查找并替换的,如果自己实现的迭代器没有按固定名称去命名,那范围for也不再支持。
四. string的增删改查
4.1 push_back() 的实现

该函数的功能是将字符c插入到字符串末尾,使字符串长度加一。
cpp
void string::push_back(char c)
{
//首先检查是否需要扩容
if (_size == _capacity) {
//扩容
reserve(_size == 0 ? 4 : 2 * _size);
}
_str[_size] = c;
_size++;
_str[_size] = '\0'; //最后不要忘了在末尾字符的下一个位置放\0
}
测试:
cpp
void Test6()
{
MyString::string s1;
s1.push_back('a');
s1.push_back('b');
s1.push_back('c');
}

字符串s1被初始化为空串,此时_size和_capacity都为0
开辟了4个大小的空间,将a,b,c都插入到了字符串的末尾
4.2 append() 的实现

append有以上这些函数,在这里实现前五个。
cpp
//尾部追加一个string对象
string& append(const string& str);
//从对象str的subpos位置开始获取sublen个字符,如果sublen大于剩余字符串的最大长度则将其剩余字符串全部追加
string& append(const string& str, size_t subpos, size_t sublen = npos);
//尾部追加字符串s
string& append(const char* s);
//尾部追加字符串s的前n个字符
string& append(const char* s, size_t n);
//尾部追加n个字符c
string& append(size_t n, char c);
- 尾部追加一个string对象
cpp
string& string::append(const string& str)
{
size_t len = str._size;
//首先检查是否需要扩容
if (_size + len > _capacity) {
size_t newCapacity = 2 * _capacity;
if (newCapacity < _size + len) {
newCapacity = _size + len;
}
reserve(newCapacity);
}
strcpy(_str + _size, str._str); //拷贝数据,同时\0也拷贝过去了,不用手动设置\0
_size += len;
return *this;
}

- 尾部追加字符串s
cpp
string& string::append(const char* s)
{
size_t len = strlen(s);
if (_size + len > _capacity) {
size_t newCapacity = 2 * _capacity;
if (newCapacity < _size + len) {
newCapacity = _size + len;
}
reserve(newCapacity);
}
strcpy(_str + _size, s);
_size += len;
return *this;
}

- 尾部追加字符串s的前n个字符
cpp
string& string::append(const char* s, size_t n)
{
size_t len = strlen(s);
if (n > len) {
//如果n大于待插入的字符串长度,则赋值为该字符串的长度值
n = len;
}
//检查扩容
if (_size + n > _capacity) {
size_t newCapacity = 2 * _capacity;
if (newCapacity < _size + n) {
newCapacity = _size + n;
}
reserve(newCapacity);
}
for (size_t i = 0; i < n; i++)
{
_str[_size++] = s[i];
}
_str[_size] = '\0'; //需要手动设置\0
return *this;
}

- 从对象str的subpos位置开始获取sublen个字符,如果sublen大于剩余字符串的最大长度则将其剩余字符串全部追加
cpp
string& string::append(const string& str, size_t subpos, size_t sublen)
{
size_t len = str._size;
assert(subpos < len); //检查subpos是否合法
if (subpos + sublen >= len) {
append(str._str + subpos); //复用append(const char* s)
}
else {
append(str._str + subpos, sublen); //复用append(const char* s, size_t n)
}
return *this;
}
测试:
cpp
MyString::string s1("hello world");
cout << s1.c_str() << endl;
MyString::string s2("xxxxxxxx");
s1.append(s2, 0);
cout << s1.c_str() << endl;
MyString::string s3("abcdefg");
s1.append(s3, 0, 3);
cout << s1.c_str() << endl;
MyString::string s4;
s4.append(s3, 0);
cout << s3.c_str() << endl;

- 尾部追加n个字符c
cpp
string& string::append(size_t n, char c)
{
//检查扩容
if (_size + n > _capacity) {
size_t newCapacity = 2 * _capacity;
if (newCapacity < _size + n) {
newCapacity = _size + n;
}
reserve(newCapacity);
}
for (size_t i = 0; i < n; i++)
{
_str[_size + i] = c;
}
_size += n;
_str[_size] = '\0';//手动设置\0
return *this;
}

4.3 operator+= 的实现
cpp
string& operator+=(const string& str);
string& operator+=(const char* s);
string& operator+=(char c);
因为我们之前实现了append和push_back,直接复用就可以了。
cpp
string& string::operator+=(const string& str)
{
append(str);
return *this;
}
string& string::operator+=(const char* s)
{
append(s);
return *this;
}
string& string::operator+=(char c)
{
push_back(c);
return *this;
}

4.4 insert() 的实现

这里主要实现以下几个函数:
cpp
//在指定位置pos插入字符串s,在pos以及之后的字符向后移动
string& insert(size_t pos, const char* s);
//在指定位置pos插入字符串对象str,在pos以及之后的字符向后移动
string& insert(size_t pos, const string& str);
//在指定位置pos插入字符c,在pos以及之后的字符向后移动
string& insert(size_t pos, char c);
//在指定位置pos插入n个字符c,在pos以及之后的字符向后移动
string& insert(size_t pos, size_t n, char c);
cpp
string& string::insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t len = strlen(s);
if (_size + len > _capacity) {
size_t newCapacity = 2 * _capacity;
if (newCapacity < _size + len) {
newCapacity = _size + len;
}
reserve(newCapacity);
}
size_t end = _size + len;
while (end >= pos + len)
{
_str[end] = _str[end - len];
end--;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = s[i];
}
_size += len;
return *this;
}
string& string::insert(size_t pos, const string& str)
{
insert(pos, str._str); //复用
return *this;
}
string& string::insert(size_t pos, char c)
{
assert(pos <= _size);
if (_size == _capacity) {
reserve(_size == 0 ? 4 : 2 * _size);
}
size_t end = _size + 1;
while (end >= pos + 1)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
_size++;
return *this;
}
string& string::insert(size_t pos, size_t n, char c)
{
assert(pos <= _size);
if (_size + n > _capacity) {
size_t newCapacity = 2 * _capacity;
if (newCapacity < _size + n) {
newCapacity = _size + n;
}
reserve(newCapacity);
}
size_t end = _size + n;
while (end >= pos + n)
{
_str[end] = _str[end - n];
end--;
}
for (size_t i = 0; i < n; i++)
{
_str[pos + i] = c;
}
_size += n;
return *this;
}
测试:
cpp
void Test7()
{
MyString::string s1("hello world");
MyString::string s2("xxx");
s2.insert(0, s1);
cout << s2.c_str() << endl;
s2.insert(10, "zzz");
cout << s2.c_str() << endl;
s2.insert(15, 'o');
cout << s2.c_str() << endl;
s2.insert(0, 5, 'k');
cout << s2.c_str() << endl;
}

4.5 erase() 的实现
cpp
//在指定位置pos开始删除len个字符
string& erase(size_t pos = 0, size_t len = npos);
该函数的功能是在指定位置pos开始删除len个字符,pos不写时默认为0,len不写时默认npos,表示将第pos个以及之后的字符删除。
cpp
string& string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len >= _size - pos) {
_size = pos;
_str[_size] = '\0';
}
else {
size_t end = pos + len;
while (end <= _size)
{
_str[end - len] = _str[end];
end++;
}
_size -= len;
}
return *this;
}

4.6 find() 的实现

主要实现以下两个函数:
cpp
//从指定位置pos开始查找一个字符c,并返回字符c首次出现的位置,如果找不到就返回npos
size_t find(char c, size_t pos = 0) const;
//从指定位置pos开始查找字符串s,找到后返回字符串s第一个字符的下标,否则返回npos
size_t find(const char* s, size_t pos = 0) const;
注意:要用const修饰this指针指向的对象,目的是不能通过this指针去修改对象,同时还兼并了const对象。
- 查找字符
cpp
size_t string::find(char c, size_t pos = 0) const
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c) {
return i;
}
}
return npos;
}
测试:
cpp
MyString::string s1("hello world");
cout << s1.c_str() << endl;
size_t n = s1.find(' ');
s1.erase(n, 1);
cout << s1.c_str() << endl;

- 查找字符串
首先介绍一个函数:
c
char *strstr(char *str1, const char *str2);
该函数的功能:在字符串 str1 中查找子串 str2 的首次出现位置,并返回指向该位置的指针。如果找不到子串,则返回空。
cpp
size_t string::find(const char* s, size_t pos) const
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, s);
if (ptr == nullptr) {
return npos;
}
return ptr - _str;
}
测试:
cpp
MyString::string s1("hello world");
cout << s1.c_str() << endl;
size_t n = s1.find("hello");
s1.erase(n, 5);
cout << s1.c_str() << endl;
n = s1.find("rld");
s1.erase(n);
cout << s1.c_str() << endl;

五. 传统写法和现代写法的对比
5.1 拷贝构造的现代写法
💬传统写法:
cpp
string::string(const string& s)
{
_str = new char[s._capacity + 1]; //多开一个空间用来存放'\0'
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
传统写法非常老实,先开特定大小的空间给_str再拷贝数据,最后更新_size和_capacity
💬现代写法:
cpp
string::string(const string& s)
{
string tmp(s.c_str());
swap(_str, tmp._str);
swap(_size, tmp._size);
swap(_capacity, tmp._capacity);
}
现代写法就是复用string(const char* str = ""); 这个默认构造函数,创建一个新对象tmp,再将tmp里面所有的成员变量与当前对象的成员变量进行交换,出了作用域tmp就会被析构,不会造成内存泄漏。
注意 :在类的成员函数中,可以直接访问该类的私有和保护成员,包括其他对象的私有和保护成员。
在C++中,类的成员函数在访问权限上具有以下特点:
- 访问本类的私有和保护成员:类的成员函数可以访问同一类中其他对象的私有和保护成员。这是因为成员函数属于类的内部实现细节,设计上允许它们操作类的内部状态,包括其他对象的内部状态。
- 友元函数:如果一个函数被声明为类的友元函数,那么它也可以访问该类的私有和保护成员。
5.2 赋值运算符重载的现代写法
💬传统写法:
cpp
string& string::operator=(const string& s) //返回当前对象的引用是为了能连线赋值
{
if (this != &s) //防止自己给自己赋值
{
char* tmp = new char[s._capacity + 1]; //先用临时指针tmp接受新开辟的空间
strcpy(tmp, s._str); //如果开辟成功就将数据赋值到这个空间
delete[] _str; //释放旧空间
_str = tmp; //将_str指向新空间
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
赋值运算符重载和拷贝构造是差不多的,传统写法也是类似,开新空间------>拷贝数据------>释放旧空间------>指向新空间------>更新其他的成员变量。
💬现代写法:
cpp
string& string::operator=(const string& s) //返回当前对象的引用是为了能连线赋值
{
if (this != &s) //防止自己给自己赋值
{
string tmp(s);
swap(_str, tmp._str);
swap(_size, tmp._size);
swap(_capacity, tmp._capacity);
}
return *this;
}
现代写法就是创建一个临时对象,再与其成员变量进行交换即可。
还有一个更简单的写法:
cpp
string& string::operator=(string s) //返回当前对象的引用是为了能连线赋值
{
swap(_str, s._str);
swap(_size, s._size);
swap(_capacity, s._capacity);
return *this;
}
进入该函数之前会先调用拷贝构造函数(将s2传给s),构造对象s,再将s所有的成员变量与当前对象的成员变量进行交换。这样看起来就非常简洁,复用性很高。
5.3 整体代码改进
在以上函数中,我们可以发现我们大量调用了swap函数:
cpp
swap(_str, s._str);
swap(_size, s._size);
swap(_capacity, s._capacity);
我们可以将这些内容封装成一个成员函数:
cpp
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
注意:在类成员函数swap内部要调用std库里的swap函数时,要指定std,因为编译器会先去寻找当前域里面的swap函数。
进一步改善后的代码如下:
cpp
string::string(const string& s)
{
string tmp(s.c_str());
swap(tmp);
}
string& string::operator=(string s) //返回当前对象的引用是为了能连线赋值
{
swap(s);
return *this;
}
非常的简洁!
如果想交换两个string对象(比如s1和s2),得这样写:s1.swap(s2)或s2.swap(s1),为了和std库里的swap相适应,我们可以这样写:swap(s1,s2),这就需要在类外实现一个swap函数,这样就不会通过函数模板再来创建一个swap函数了。
cpp
void swap(string& s1, string& s2)
{
s1.swap(s2);
}

很奇怪这里为什么调用的是MyString域里面的swap函数,而不是std库里的swap函数呢?
这是因为编译器识别string对象s1和s2是在MyString域里面的,会优先在MyString域里寻找swap函数,找到了就直接调用,找不到才会去调用std库里的swap函数(前提是在全局域中展开了std库)。
六. 其他运算符重载
6.1 operator+ 的实现
主要实现以下函数:
cpp
string operator+(const string& s1, const string& s2);
string operator+(const string& s1, const char* str);
string operator+(const string& s1, char c);
cpp
string operator+(const string& s1, const string& s2)
{
string tmp(s1);
tmp += s2;
return tmp;
}
string operator+(const string& s1, const char* str)
{
string tmp(s1);
tmp += str;
return tmp;
}
string operator+(const string& s1, char c)
{
string tmp(s1);
tmp.push_back(c);
return tmp;
}
- 因为operator+不会改变对象里的成员变量,所以可以使用const修饰对象。
- 因为返回的是临时对象,所以不能使用引用返回。
6.2 operator== 的实现
先来介绍一下strcmp函数:
c
int strcmp(const char *str1, const char *str2);
参数:
- str1:指向第一个 C 字符串的指针。
- str2:指向第二个 C 字符串的指针。
返回值:
- 如果 str1 和 str2 相等,返回 0。
- 如果 str1 小于 str2,返回一个负整数。
- 如果 str1 大于 str2,返回一个正整数。
比较方式 :
strcmp 按字典顺序逐个字符比较两个字符串,直到遇到不同的字符或空字符 \0 为止。比较是基于字符的 ASCII 值进行的。
cpp
bool operator==(const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}
6.3 operator> 的实现
cpp
bool operator>(const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) > 0;
}
6.4 operator< 的实现
cpp
bool operator<(const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}
6.5 剩下的直接复用
cpp
bool operator!=(const string& lhs, const string& rhs)
{
return !(lhs == rhs);
}
bool operator>=(const string& lhs, const string& rhs)
{
return !(lhs < rhs);
}
bool operator<=(const string& lhs, const string& rhs)
{
return !(lhs > rhs);
}
测试:
cpp
MyString::string s1("abc");
MyString::string s2("efg");
cout << (s1 == s2) << endl;
cout << (s1 != s2) << endl;
cout << (s1 >= s2) << endl;
cout << (s1 <= s2) << endl;
cout << (s1 > s2) << endl;
cout << (s1 < s2) << endl;

七. 流操作
7.1 流插入
cpp
ostream& operator<< (ostream& os, const string& str); //流插入
因为cout支持连续输出,所以要返回ostream对象的引用使其可以继续调用。
cpp
ostream& operator<< (ostream& os, const string& str)
{
for (auto& e : str)
{
os << e;
}
return os;
}
测试:
cpp
void Test11()
{
MyString::string s1("abcdefg");
cout << s1 << endl;
MyString::string s2("zzzllllll");
cout << s2 << endl;
}

7.2 流提取
cpp
istream& operator>> (istream& is, string& str); //流提取
为了支持连续输入,返回istream对象的引用使其可以继续调用。
因为我们无法确定输入字符串的长度,如果一个一个地将字符给str,必然会引起频繁地扩容,从而导致性能的下降。
所以我们可以定义一定长度的缓冲区buff,先将字符串输入到缓冲区中,如果缓冲区满了就将该缓冲区所有的字符写入到string字符串中,再刷新缓冲区,继续接收字符,这样就避免了频繁扩容的问题。
注意:在使用流提取前,先将string字符串清空(clear),不管该字符串有没有字符。
cpp
istream& operator>> (istream& is, string& str)
{
str.clear();//清空字符串
char buff[256];//定义缓冲区
char ch = is.get();//从输入流中获取一个字符
int i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 255) {
buff[i] = '\0'; //不要忘了在字符串末尾给\0
str += buff;
i = 0;
}
ch = is.get();//从输入流中获取一个字符
}
if (i) {
buff[i] = '\0';
str += buff;
}
return is;
}
测试:
cpp
void Test12()
{
MyString::string s1;
MyString::string s2;
cin >> s1 >> s2;
cout << s1 << endl << s2;
}

7.3 getline() 的实现
cpp
istream& getline(istream& is, string& str, char delim = '\n'); //自定义获取字符串
与流提取不同的是,流提取是遇到空格(' ')或换行符('\n')就停止提取了,getline是根据传入的参数delim来判断什么时候停止输入,即遇到字符delim就停止输入。如果不传字符delim,就默认为'\n',即遇到换行符就停止输入。
cpp
istream& getline(istream& is, string& str, char delim)
{
str.clear();//清空字符串
char buff[256];//定义缓冲区
char ch = is.get();//从输入流中获取一个字符
int i = 0;
while (ch != delim)
{
buff[i++] = ch;
if (i == 255) {
buff[i] = '\0';//不要忘了在字符串末尾给\0
str += buff;
i = 0;
}
ch = is.get();//从输入流中获取一个字符
}
if (i) {
buff[i] = '\0';
str += buff;
}
return is;
}
测试:
cpp
void Test11()
{
MyString::string s1;
MyString::string s2;
getline(cin, s1);
getline(cin, s2, '#');
cout << s1 << endl;
cout << s2;
}

八. MyString::string源码
string.h文件
cpp
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;
namespace MyString
{
class string
{
public:
static const size_t npos; //npos是size_t类型的最大值
string(const char* str = ""); //默认构造函数,缺省值为空字符串
string(size_t n, char c); //初始化为n个字符c
string(const string& s); //拷贝构造函数
string& operator=(string s);
~string();
void swap(string& s);
const char* c_str() const;
size_t size() const;
size_t capacity() const;
bool empty() const;
void clear();
char& operator[](size_t pos);
const char& operator[](size_t pos) const;
void reserve(size_t n = 0);
void resize(size_t n);
void resize(size_t n, char c);
typedef char* iterator;
iterator begin();
iterator end();
typedef const char* const_iterator;
const_iterator begin() const;
const_iterator end() const;
void push_back(char c);
//尾部追加一个string对象
string& append(const string& str);
//从对象str的subpos位置开始获取sublen个字符,如果sublen大于剩余字符串的最大长度则将其剩余字符串全部追加
string& append(const string& str, size_t subpos, size_t sublen = npos);
//尾部追加字符串s
string& append(const char* s);
//尾部追加字符串s的前n个字符
string& append(const char* s, size_t n);
//尾部追加n个字符c
string& append(size_t n, char c);
string& operator+=(const string& str);
string& operator+=(const char* s);
string& operator+=(char c);
//在指定位置pos插入字符串s,在pos以及之后的字符向后移动
string& insert(size_t pos, const char* s);
//在指定位置pos插入字符串对象str,在pos以及之后的字符向后移动
string& insert(size_t pos, const string& str);
//在指定位置pos插入字符c,在pos以及之后的字符向后移动
string& insert(size_t pos, char c);
//在指定位置pos插入n个字符c,在pos以及之后的字符向后移动
string& insert(size_t pos, size_t n, char c);
//在指定位置pos开始删除len个字符
string& erase(size_t pos = 0, size_t len = npos);
//从指定位置pos开始查找一个字符c,并返回字符c首次出现的位置,如果找不到就返回npos
size_t find(char c, size_t pos = 0) const;
//从指定位置pos开始查找字符串s,找到后返回字符串s第一个字符的下标,否则返回npos
size_t find(const char* s, size_t pos = 0) const;
private:
char* _str; //存储字符串的首地址
size_t _size; //存储字符串的长度
size_t _capacity; //存储字符串的容量
};
void swap(string& s1, string& s2);
string operator+(const string& s1, const string& s2);
string operator+(const string& s1, const char* str);
string operator+(const string& s1, char c);
bool operator==(const string& lhs, const string& rhs);
bool operator!=(const string& lhs, const string& rhs);
bool operator>=(const string& lhs, const string& rhs);
bool operator<=(const string& lhs, const string& rhs);
bool operator>(const string& lhs, const string& rhs);
bool operator<(const string& lhs, const string& rhs);
ostream& operator<< (ostream& os, const string& str); //流插入
istream& operator>> (istream& is, string& str); //流提取
istream& getline(istream& is, string& str, char delim = '\n'); //自定义获取字符串
}
string.cpp文件
cpp
#include "string.h"
namespace MyString
{
const size_t string::npos = -1;
string::string(const char* str)
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_size + 1]; //多开一个空间用来存放'\0'
strcpy(_str, str);
}
string::string(size_t n, char c)
:_size(n)
{
_capacity = _size;
_str = new char[_size + 1];
for (size_t i = 0; i < n; i++)
{
_str[i] = c;
}
_str[n] = '\0';
}
//string::string(const string& s)
//{
// _str = new char[s._capacity + 1]; //多开一个空间用来存放'\0'
// strcpy(_str, s._str);
// _size = s._size;
// _capacity = s._capacity;
//}
string::string(const string& s)
{
string tmp(s.c_str());
swap(tmp);
}
//string& string::operator=(const string& s) //返回当前对象的引用是为了能连线赋值
//{
// if (this != &s) //防止自己给自己赋值
// {
// delete[] _str; //先将当前的内存空间释放掉
// _str = new char[s._capacity + 1]; //重新开辟和s一样大的空间,多开一个空间是为了存储'\0'
// strcpy(_str, s._str); //复制数据
// _size = s._size;
// _capacity = s._capacity;
// }
// return *this;
//}
//string& string::operator=(const string& s) //返回当前对象的引用是为了能连线赋值
//{
// if (this != &s) //防止自己给自己赋值
// {
// char* tmp = new char[s._capacity + 1]; //先用临时指针tmp接受新开辟的空间
// strcpy(tmp, s._str); //如果开辟成功就将数据赋值到这个空间
// delete[] _str; //释放旧空间
// _str = tmp; //将_str指向新空间
// _size = s._size;
// _capacity = s._capacity;
// }
// return *this;
//}
//string& string::operator=(const string& s) //返回当前对象的引用是为了能连线赋值
//{
// if (this != &s) //防止自己给自己赋值
// {
// string tmp(s);
// swap(_str, tmp._str);
// swap(_size, tmp._size);
// swap(_capacity, tmp._capacity);
// }
// return *this;
//}
string& string::operator=(string s) //返回当前对象的引用是为了能连线赋值
{
swap(s);
return *this;
}
string::~string()
{
delete[] _str;
_str = nullptr; //不要忘了将指针_str置为空,避免_str成为野指针
_size = _capacity = 0;
}
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
const char* string::c_str() const
{
return _str;
}
size_t string::size() const
{
return _size;
}
size_t string::capacity() const
{
return _capacity;
}
bool string::empty() const
{
return _size == 0;
}
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
char& string::operator[](size_t pos)
{
assert(pos < _size); //确保pos不能越界,越界了就断言
return _str[pos];
}
const char& string::operator[](size_t pos) const
{
assert(pos < _size); //确保pos不能越界,越界了就断言
return _str[pos];
}
void string::reserve(size_t n)
{
//检查n是否大于原有的容量,必须大于才能调整字符串的容量
if (n > _capacity) {
char* tmp = new char[n + 1]; //用指针tmp去接受新开的内存空间
strcpy(tmp, _str); //如果开辟成功,将数据拷贝到新空间
delete[] _str; //将旧空间释放掉
_str = tmp; //将_str指向新空间
_capacity = n; //更改容量值
}
}
void string::resize(size_t n)
{
if (n > _capacity) {
reserve(n); //扩容
}
_size = n;
_str[_size] = '\0';
}
void string::resize(size_t n, char c)
{
if (n > _capacity) {
reserve(n); //扩容
}
for (size_t i = _size; i < n; i++)
{
//如果是增加长度就会进入循环,在字符串末尾插入所需数量的字符c
_str[i] = c;
}
_size = n;
_str[_size] = '\0';
}
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
string::const_iterator string::begin() const
{
return _str;
}
string::const_iterator string::end() const
{
return _str + _size;
}
void string::push_back(char c)
{
//首先检查是否需要扩容
if (_size == _capacity) {
//扩容
reserve(_size == 0 ? 4 : 2 * _size);
}
_str[_size] = c;
_size++;
_str[_size] = '\0'; //最后不要忘了在末尾字符的下一个位置放\0
}
string& string::append(const string& str)
{
size_t len = str._size;
//首先检查是否需要扩容
if (_size + len > _capacity) {
size_t newCapacity = 2 * _capacity;
if (newCapacity < _size + len) {
newCapacity = _size + len;
}
reserve(newCapacity);
}
strcpy(_str + _size, str._str); //拷贝数据,同时\0也拷贝过去了,不用手动设置\0
_size += len;
return *this;
}
string& string::append(const string& str, size_t subpos, size_t sublen)
{
size_t len = str._size;
assert(subpos < len); //检查subpos是否合法
if (subpos + sublen >= len) {
append(str._str + subpos); //复用append(const char* s)
}
else {
append(str._str + subpos, sublen); //复用append(const char* s, size_t n)
}
return *this;
}
string& string::append(const char* s)
{
size_t len = strlen(s);
if (_size + len > _capacity) {
size_t newCapacity = 2 * _capacity;
if (newCapacity < _size + len) {
newCapacity = _size + len;
}
reserve(newCapacity);
}
strcpy(_str + _size, s);
_size += len;
return *this;
}
string& string::append(const char* s, size_t n)
{
size_t len = strlen(s);
if (n > len) {
//如果n大于待插入的字符串长度,则赋值为该字符串的长度值
n = len;
}
//检查扩容
if (_size + n > _capacity) {
size_t newCapacity = 2 * _capacity;
if (newCapacity < _size + n) {
newCapacity = _size + n;
}
reserve(newCapacity);
}
for (size_t i = 0; i < n; i++)
{
_str[_size++] = s[i];
}
_str[_size] = '\0'; //需要手动设置\0
return *this;
}
string& string::append(size_t n, char c)
{
//检查扩容
if (_size + n > _capacity) {
size_t newCapacity = 2 * _capacity;
if (newCapacity < _size + n) {
newCapacity = _size + n;
}
reserve(newCapacity);
}
for (size_t i = 0; i < n; i++)
{
_str[_size + i] = c;
}
_size += n;
_str[_size] = '\0';//手动设置\0
return *this;
}
string& string::operator+=(const string& str)
{
append(str);
return *this;
}
string& string::operator+=(const char* s)
{
append(s);
return *this;
}
string& string::operator+=(char c)
{
push_back(c);
return *this;
}
string& string::insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t len = strlen(s);
if (_size + len > _capacity) {
size_t newCapacity = 2 * _capacity;
if (newCapacity < _size + len) {
newCapacity = _size + len;
}
reserve(newCapacity);
}
size_t end = _size + len;
while (end >= pos + len)
{
_str[end] = _str[end - len];
end--;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = s[i];
}
_size += len;
return *this;
}
string& string::insert(size_t pos, const string& str)
{
insert(pos, str._str); //复用
return *this;
}
string& string::insert(size_t pos, char c)
{
assert(pos <= _size);
if (_size == _capacity) {
reserve(_size == 0 ? 4 : 2 * _size);
}
size_t end = _size + 1;
while (end >= pos + 1)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
_size++;
return *this;
}
string& string::insert(size_t pos, size_t n, char c)
{
assert(pos <= _size);
if (_size + n > _capacity) {
size_t newCapacity = 2 * _capacity;
if (newCapacity < _size + n) {
newCapacity = _size + n;
}
reserve(newCapacity);
}
size_t end = _size + n;
while (end >= pos + n)
{
_str[end] = _str[end - n];
end--;
}
for (size_t i = 0; i < n; i++)
{
_str[pos + i] = c;
}
_size += n;
return *this;
}
string& string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len >= _size - pos) {
_size = pos;
_str[_size] = '\0';
}
else {
size_t end = pos + len;
while (end <= _size)
{
_str[end - len] = _str[end];
end++;
}
_size -= len;
}
return *this;
}
size_t string::find(char c, size_t pos) const
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c) {
return i;
}
}
return npos;
}
size_t string::find(const char* s, size_t pos) const
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, s);
if (ptr == nullptr) {
return npos;
}
return ptr - _str;
}
void swap(string& s1, string& s2)
{
s1.swap(s2);
}
string operator+(const string& s1, const string& s2)
{
string tmp(s1);
tmp += s2;
return tmp;
}
string operator+(const string& s1, const char* str)
{
string tmp(s1);
tmp += str;
return tmp;
}
string operator+(const string& s1, char c)
{
string tmp(s1);
tmp.push_back(c);
return tmp;
}
bool operator==(const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}
bool operator!=(const string& lhs, const string& rhs)
{
return !(lhs == rhs);
}
bool operator>=(const string& lhs, const string& rhs)
{
return !(lhs < rhs);
}
bool operator<=(const string& lhs, const string& rhs)
{
return !(lhs > rhs);
}
bool operator>(const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) > 0;
}
bool operator<(const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}
ostream& operator<< (ostream& os, const string& str)
{
for (auto& e : str)
{
os << e;
}
return os;
}
istream& operator>> (istream& is, string& str)
{
str.clear();//清空字符串
char buff[256];//定义缓冲区
char ch = is.get();//从输入流中获取一个字符
int i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 255) {
buff[i] = '\0'; //不要忘了在字符串末尾给\0
str += buff;
i = 0;
}
ch = is.get();//从输入流中获取一个字符
}
if (i) {
buff[i] = '\0';
str += buff;
}
return is;
}
istream& getline(istream& is, string& str, char delim)
{
str.clear();//清空字符串
char buff[256];//定义缓冲区
char ch = is.get();//从输入流中获取一个字符
int i = 0;
while (ch != delim)
{
buff[i++] = ch;
if (i == 255) {
buff[i] = '\0';//不要忘了在字符串末尾给\0
str += buff;
i = 0;
}
ch = is.get();//从输入流中获取一个字符
}
if (i) {
buff[i] = '\0';
str += buff;
}
return is;
}
}
九. 写时拷贝(了解)
写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。
可看一下以下两个文章:
写时拷贝的实现代码(可参考):
cpp
//构造函数(分存内存)
string::string(const char* tmp)
{
_Len = strlen(tmp);
_Ptr = new char[_Len+1+1];
strcpy( _Ptr, tmp );
_Ptr[_Len+1]=0; // 设置引用计数
}
//拷贝构造(共享内存)
string::string(const string& str)
{
if (*this != str){
this->_Ptr = str.c_str(); //共享内存
this->_Len = str.szie();
this->_Ptr[_Len+1] ++; //引用计数加一
}
}
//写时才拷贝Copy-On-Write
char& string::operator[](unsigned int idx)
{
if (idx > _Len || _Ptr == 0 ) {
static char nullchar = 0;
return nullchar;
}
_Ptr[_Len+1]--; //引用计数减一
char* tmp = new char[_Len+1+1];
strncpy( tmp, _Ptr, _Len+1);
_Ptr = tmp;
_Ptr[_Len+1]=0; // 设置新的共享内存的引用计数
return _Ptr[idx];
}
//析构函数的一些处理
~string()
{
_Ptr[_Len+1]--; //引用计数减一
// 引用计数为0时,释放内存
if (_Ptr[_Len+1]==0) {
delete[] _Ptr;
}
}