目录
2.10 Non-member function overloads
2.10.3 relational operators(string)
一、模拟实现接口总览
Member functions
Member functions
//构造函数
string(const char* str = "")
//析构函数
~string()
//拷贝构造 -- 现代写法
string(const string& s)
//赋值重载 -- 现代写法2
string& operator=(string s)
//iterators
iterator begin()
iterator end()
//Capacity
size_t size()const
size_t capacity()const
bool empty()const
//更改容量大小
void reserve(size_t n)
//调整字符串大小
void resize(size_t n, char ch = '\0')
//清空字符串
void clear()
//Element access
char& operator[](size_t pos)
const char& operator[](size_t pos)const
//modify
//尾插一个字符
void push_back(char c)
//尾插一个字符串
void append(const char* str)
//+= 一个字符
string& operator+=(char c)
//+= 一个字符串
string& operator+=(const char* str)
//交换两个字符串
void swap(string& s)
//String operations
const char* c_str()const
// 返回字符c 在string中第一次出现的位置
size_t find(char c, size_t pos = 0)const
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0)const
//在pos位置上插入字符c,并返回该字符的位置
string& insert(size_t pos, char c)
//在pos位置上插入字符串s,并返回该字符的位置
string& insert(size_t pos, const char* s)
//删除pos位置上的元素,并返回该元素的下一个位置
string& erase(size_t pos, size_t len = npos)
Non-member function overloads
Non-member function overloads
ostream& operator<<(ostream& out, const string& s)
istream& operator>>(istream& in, string& s)
上面只是挑一些常用的进行模拟实现,要把自己实现的写在自己命名的命名空间里面,否则与库中的 string 会产生冲突
注:string 类模拟实现,最主要是实现 string 类的构造、拷贝构造、赋值运算符重载以及析构函数,面试也经常喜欢考这几个
二、string模拟实现
2.1 构造函数
构造函数的参数设置成缺省参数,不传参默认构造空字符串
//构造函数
string(const char* str = "")
{
_size = strlen(str);//字符串大小
_capacity = _size;//构造时,容量大小默认与字符串大小相同
_str = new char[_capacity + 1];//为字符串开辟空间(多开一个用于存放'\0')
strcpy(_str, str);//将C字符串拷贝到已开好的空间
}
2.2 析构函数
string 类的析构函数需要我们自己写,因为每个 string 类对象都开辟有一块空间,不释放会造成内存泄漏
//析构函数
~string()
{
delete[] _str; //释放_str指向的空间
_str = nullptr;
_size = _capacity = 0;
}
2.3 拷贝构造函数
这里需要注意浅拷贝和深拷贝的问题
浅拷贝:
也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规
简单说就是 新拷贝出来的对象的指针 和 原对象的指针 指向的内存空间是同一块空间,其中一个对象的改动会对另一个对象造成影响,进行析构的时候会对同一份空间释放两次,造成非法访问
深拷贝:
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出,一般情况都是按照深拷贝方式提供
深拷贝:原对象 与 新拷贝对象 互相独立,其中任何一个对象的改动不会对另外一个对象造成影响
很明显,string 类的拷贝构造需要深拷贝,拷贝构造有两种写法,传统写法和现代写法,一般比较推荐现代写法
2.3.1 传统写法
开辟一块和原对象空间大小一样的空间,然后将原对象的字符串拷贝过去,接着把原对象的其他成员变量也赋值过去即可
//拷贝构造函数
//传统写法
string(const string& s)
{
_size = s._size;
_capacity = s._capacity;
_str = new char[_capacity + 1];//申请空间
strcpy(_str, s._str);//拷贝字符串到新空间
}
2.3.2 现代写法
现代写法与传统写法的思想不同:复用构造函数构造一个 tmp对象,对 tmp 初始化的对象是原对象的字符串,然后再将 tmp对象 与 新拷贝对象的数据交换即可,新拷贝对象先用初始化列表设置为空,新拷贝对象并没有开辟空间。新拷贝对象的 _str 与原对象的 _str 指向的也不是同一块空间,是互相独立的
//拷贝构造 -- 现代写法
string(const string& s)
:_str(nullptr)
, _size(0)
,_capacity(0)
{
string tmp(s._str);//复用构造函数,构造 tmp对象
swap(tmp);//交换
}
swap 函数是后面实现的,并不是使用库中的 swap
2.4 赋值运算符重载
与拷贝构造函数类似,赋值运算符重载函数的模拟实现也涉及深浅拷贝问题,赋值运算符重载也是进行深拷贝
2.4.1 传统写法
赋值运算符重载函数的传统写法与拷贝构造函数的传统写法类似,只是左值的 _str 在开辟新空间之前需要先将原来的空间释放掉,并且在进行操作之前还需判断是否是自己给自己赋值,若是自己给自己赋值,则无需进行任何操作
//赋值重载
//传统写法
string& operator=(const string& s)
{
if (this == &s)//检查自我赋值
{
return *this;
}
delete[] _str;
_size = s._size;
_capacity = s._capacity;
_str = new char[_capacity + 1];
strcpy(_str, s._str);
return *this;//返回左值,目的是为了支持连续赋值
}
2.4.2 现代写法
现代写法 -- 1
复用拷贝构造函数,拷贝构造一个 tmp对象,对 tmp 初始化的对象是原对象,然后将该 tmp对象与 新拷贝对象进行交换交换,图就不画了,与上面的拷贝构造现代写法图类似
//赋值重载 -- 现代写法1
string& operator=(const string& s)
{
if (this == &s)//检查自我赋值
{
return *this;
}
string tmp(s);//复用拷贝构造函数,用s拷贝构造出对象tmp
swap(tmp);
return *this;
}
现代写法 -- 2
也是复用拷贝构造,但是参数不再是传引用传参,而是传值形参,而传值传参则会自动调用拷贝构造函数,但是这种写法无法检查自我赋值的情况,但是这种情况几乎不会出现,推荐这种写法
//赋值重载 -- 现代写法2
string& operator=(string s)//传值传参自动调用拷贝构造,比如,s1 = s2,先把s2拷贝到s,s再交换给this,也就是s1
{
swap(s);
return *this;
}
2.5 iterator
string 类中的迭代器实际上就是指针,只是给指针起了一个别名叫 iterator 而已
typedef char* iterator;//迭代器
2.5.1 begin
直接返回字符串第一个元素的地址
iterator begin()
{
return _str;
}
2.5.2 end
直接返回字符串中最后一个字符的后一个字符的地址(即’\0’的地址)
iterator end()
{
return _str + _size;
}
范围 for 的底层就是迭代器
2.6 Capacity
2.6.1 size
size 用于获取字符串当前的有效长度(不包括’\0’)
size_t size()const
{
return _size;
}
2.6.2 capacity
capacity函数用于获取字符串当前的容量
size_t capacity()const
{
return _capacity;
}
2.6.2 empty
判断有效字符串是否为空,为空返回 true,否则返回 false
bool empty()const
{
return _size == 0;
}
2.6.3 reserve
reserve 规则:
- n 大于原字符串的 capacity,此时 reserve 函数会将 capacity 扩容到 n
- n 小于等于原字符串的 capacity,capacity 容量不变 (不缩容)
//更改容量大小
void reserve(size_t n)
{
if (n > _capacity)//n大于现有容量,增容,n小于现有容量则不改变容量
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;//更新容量
}
}
2.6.4 resize
resize 规则:
- n 小于原字符串的 size,此时 resize 函数会将原字符串的 size 改为 n,但不会改变 capacity
- 大于原字符串的 size,但小于其 capacity,此时 resize 函数会将 size 后面的空间全部设置为字符 c 或默认的‘\0’
- 大于原字符串的 capacity,此时 resize 函数会将原字符串扩容,然后将size 后面的空间全部设置为字符 c 默认的‘\0’
//调整字符串大小
void resize(size_t n, char ch = '\0')
{
if (n > _size)//n大于原字符串长度,增容,末尾追加字符
{
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
else//小于等于
{
_str[n] = '\0';
_size = n;
}
}
2.6.5 clear
clear 函数用于清空有效字符串
//清空有效字符串
void clear()
{
_str[0] = '\0';
_size = 0;
}
2.7 Element access
2.7.1 operator[ ]
[ ]运算符的重载是为了让 string对象能像C字符串一样,通过 [ ] +下标 的方式获取字符串对应位置的字符,就像数组可以可以通过下标访问元素
//可读可写
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//只读
const char& operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
2.8 Modify
2.8.1 push_back
push_back函数的作用就是在当前字符串的后面尾插上一个字符,尾插之前首先需要判断是否需要增容,若需要,则调用reserve函数进行增容,然后再尾插字符,注意尾插完字符后需要在该字符的后方设置上’\0’,否则打印字符串的时候会出现非法访问,因为尾插的字符后方不一定就是’\0’
//尾插一个字符
void push_back(char c)
{
if (_size == _capacity)//判断是否需要增容
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newCapacity);
}
_str[_size] = c;
++_size;
_str[_size] = '\0';
}
2.8.2 append
append 函数的作用是在当前字符串的后面尾插一个字符串
//尾插一个字符串
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)//判断是否需要增容
{
reserve(_size + len);
}
strcpy(_str + _size, str);//将str尾插到字符串后面
_size += len;
}
2.8.3 operator+=
+= 一个字符直接复用 push_back
//+= 一个字符
string& operator+=(char c)
{
push_back(c);
return *this;
}
+= 一个字符串直接复用 append
//+= 一个字符串
string& operator+=(const char* str)
{
append(str);
return *this;
}
2.8.4 swap
swap 函数用于交换两个对象的数据,直接调用库里的 swap 模板函数将对象的各个成员变量进行交换即可
//交换两个字符串
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
2.9 String operations
2.9.1 c_str
c_str 函数用于获取对象C类型的字符串
const char* c_str()const
{
return _str;
}
2.9.2 find
在字符串中查找一个字符
// 返回字符c 在string中第一次出现的位置
size_t find(char c, size_t pos = 0)const
{
assert(pos < _size);
while (pos < _size)
{
if (_str[pos] == c)
{
return pos;//找到目标字符,返回其下标
}
pos++;
}
return npos;//没有找到目标字符,返回npos
}
在字符串中查找一个子串
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0)const
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, s);
if (ptr == nullptr)//没有找到
{
return npos;
}
else
{
return ptr - _str;//返回字符串第一个字符的下标
}
}
2.9.3 insert
insert函数的作用是在字符串的任意位置插入字符或是字符串
任意位置插入字符:
//在pos位置上插入字符c,并返回该字符的位置
string& insert(size_t pos, char c)
{
assert(pos <= _size);
if (_size == _capacity)//判断是否需要增容
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newCapacity);
}
size_t end = _size + 1;
while (pos < end)//挪动数据
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
_size++;
return *this;
}
任意位置插入字符串:
/在pos位置上插入字符串s,并返回该字符的位置
string& insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t len = strlen(s);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (pos + len - 1 < end)
{
_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, s, len);
_size += len;
return *this;
}
2.9.3 erase
erase函数的作用是删除字符串任意位置开始的 len 个字符,分两种情况:
- pos位置及其之后的有效字符都需要被删除
- pos位置及其之后的有效字符只需删除一部分
//删除pos位置上 len 个的元素
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)//说明pos位置及其后面的字符都被删除
{
_str[pos] = '\0';
_size = pos;
}
else//说明pos位置及其后方的有效字符需要保留一部分
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
2.10 Non-member function overloads
2.10.1 operator<<
重载 <<运算符是为了让string对象能够像内置类型一样使用 <<运算符直接输出打印,实现直接使用对象进行遍历即可输出
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}
2.10.2 operator>>
重载 >>运算符是为了让 string对象能够像内置类型一样使用>>运算符直接输入,输入前我们需要先将对象的C字符串置空,然后从标准输入流读取字符,直到读取到 ’ ‘ 或是 ’\n’ 便停止读取
istream& operator>>(istream& in, string& s)
{
s.clear();//清空
char buff[128] = { '\0' };
size_t i = 0;
char ch = in.get();//读取一个字符
while (ch != ' ' && ch != '\n')//当读取到的字符不是空格或'\n'的时候继续读取
{
if (i == 127)//满了
{
s += buff;
i = 0;
}
buff[i++] = ch;
ch = in.get();//继续读取字符
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
2.10.3 relational operators(string)
关系运算符有 >、>=、<、<=、==、!= 这六个,但是对于 C++中任意一个类的关系运算符重载,我们均只需重载其中的两个,剩下的四个关系运算符可以通过复用已经重载好了的两个关系运算符来实现
三、string模拟实现代码
(1)string.h
#pragma once
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;
namespace fy
{
class string
{
public:
//构造函数
string(const char* str = "")
{
_size = strlen(str);//字符串大小
_capacity = _size;//构造时,容量大小默认与字符串大小相同
_str = new char[_capacity + 1];//为字符串开辟空间(多开一个用于存放'\0')
strcpy(_str, str);//将C字符串拷贝到已开好的空间
}
//析构函数
~string()
{
delete[] _str; //释放_str指向的空间
_str = nullptr;
_size = _capacity = 0;
}
//拷贝构造函数
传统写法
//string(const string& s)
//{
// _size = s._size;
// _capacity = s._capacity;
// _str = new char[_capacity + 1];//申请空间
// strcpy(_str, s._str);//拷贝字符串到新空间
//}
//拷贝构造 -- 现代写法
string(const string& s)
:_str(nullptr)
, _size(0)
,_capacity(0)
{
string tmp(s._str);//复用构造函数,构造 tmp对象
swap(tmp);//交换
}
//赋值重载
传统写法
//string& operator=(const string& s)
//{
// if (this == &s)//检查自我赋值
// {
// return *this;
// }
// delete[] _str;
// _size = s._size;
// _capacity = s._capacity;
// _str = new char[_capacity + 1];
// strcpy(_str, s._str);
//
// return *this;//返回左值,目的是为了支持连续赋值
//}
赋值重载 -- 现代写法1
//string& operator=(const string& s)
//{
// if (this == &s)//检查自我赋值
// {
// return *this;
// }
// string tmp(s);//复用拷贝构造函数,用s拷贝构造出对象tmp
// swap(tmp);
// return *this;
//}
//赋值重载 -- 现代写法2
string& operator=(string s)//传值传参自动调用拷贝构造,比如,s1 = s2,先把s2拷贝到s,s再交换给this,也就是s1
{
swap(s);
return *this;
}
//-------------------------------------------------------------------
//iterators
typedef char* iterator;//迭代器
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
//-------------------------------------------------------------------
//Capacity
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
bool empty()const
{
return _size == 0;
}
//更改容量大小
void reserve(size_t n)
{
if (n > _capacity)//n大于现有容量,增容,n小于现有容量则不改变容量
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;//更新容量
}
}
//调整字符串大小
void resize(size_t n, char ch = '\0')
{
if (n > _size)//n大于原字符串长度,增容,末尾追加字符
{
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
else//小于等于
{
_str[n] = '\0';
_size = n;
}
}
//清空有效字符串
void clear()
{
_str[0] = '\0';
_size = 0;
}
//-------------------------------------------------------------------
//Element access
//可读可写
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//只读
const char& operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
//-------------------------------------------------------------------
//modify
//尾插一个字符
void push_back(char c)
{
if (_size == _capacity)//判断是否需要增容
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newCapacity);
}
_str[_size] = c;
++_size;
_str[_size] = '\0';
}
//尾插一个字符串
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)//判断是否需要增容
{
reserve(_size + len);
}
strcpy(_str + _size, str);//将str尾插到字符串后面
_size += len;
}
//+= 一个字符
string& operator+=(char c)
{
push_back(c);
return *this;
}
//+= 一个字符串
string& operator+=(const char* str)
{
append(str);
return *this;
}
//交换两个字符串
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//-------------------------------------------------------------------
//String operations
const char* c_str()const
{
return _str;
}
// 返回字符c 在string中第一次出现的位置
size_t find(char c, size_t pos = 0)const
{
assert(pos < _size);
while (pos < _size)
{
if (_str[pos] == c)
{
return pos;//找到目标字符,返回其下标
}
pos++;
}
return npos;//没有找到目标字符,返回npos
}
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0)const
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, s);
if (ptr == nullptr)//没有找到
{
return npos;
}
else
{
return ptr - _str;//返回字符串第一个字符的下标
}
}
//在pos位置上插入字符c,并返回该字符的位置
string& insert(size_t pos, char c)
{
assert(pos <= _size);
if (_size == _capacity)//判断是否需要增容
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newCapacity);
}
size_t end = _size + 1;
while (pos < end)//挪动数据
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
_size++;
return *this;
}
//在pos位置上插入字符串s,并返回该字符的位置
string& insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t len = strlen(s);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (pos + len - 1 < end)
{
_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, s, len);
_size += len;
return *this;
}
//删除pos位置上 len 个的元素
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)//说明pos位置及其后面的字符都被删除
{
_str[pos] = '\0';
_size = pos;
}
else//说明pos位置及其后方的有效字符需要保留一部分
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
private:
char* _str;
size_t _size;
size_t _capacity;
const static size_t npos = -1;
};
//-------------------------------------------------------------------
//Non-member function overloads
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();//清空
char buff[128] = { '\0' };
size_t i = 0;
char ch = in.get();//读取一个字符
while (ch != ' ' && ch != '\n')//当读取到的字符不是空格或'\n'的时候继续读取
{
if (i == 127)//满了
{
s += buff;
i = 0;
}
buff[i++] = ch;
ch = in.get();//继续读取字符
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
//relational operators(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 strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator>=(const string& s1, const string& s2)
{
return operator==(s1, s2) || operator>(s1, s2);//复用
}
bool operator<(const string& s1, const string& s2)
{
return !operator>=(s1, s2);
}
bool operator<=(const string& s1, const string& s2)
{
return !operator<(s1, s2);
}
bool operator!=(const string& s1, const string& s2)
{
return !operator==(s1, s2);
}
}
(2)Test.cpp
#define _CRT_SECURE_NO_WARNINGS
#include "string.h"
void Test_string1()
{
//构造
fy::string s1;
fy::string s2("abcd");
//拷贝构造
fy::string s3 = s2;
fy::string s4(s2);
//赋值重载
s1 = s2;//编译器自动转换成 s1.operator(s2)
}
void Test_string2()//iterators
{
fy::string s1("abcdef");
fy::string::iterator it = s1.begin();
for (it; it < s1.end(); ++it)
{
cout << *(it);
}
cout << endl;
fy::string s2("12345");
fy::string::iterator it2 = s2.begin();
for (it2; it2 < s2.end(); ++it2)
{
cout << *(it2);
}
cout << endl;
}
void Test_string3()//Capacity
{
fy::string s1("hello world");
cout << s1.empty() << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.reserve(5);
cout << s1.capacity() << endl;
s1.reserve(20);
cout << s1.capacity() << endl;
fy::string s2("hello world");
cout << s2.size() << endl;
cout << s2.capacity() << endl;
//s2.resize(6, 'x');
s2.resize(15, 'x');//调试查看,<<这里暂时没有实现
fy::string s3("aaaaa");
s3.clear();
}
void Test_string4()//Element access
{
fy::string s1("hello world");
//可以使用 [] 访问字符串的每一位
for (size_t i = 0; i < s1.size(); ++i)
{
cout << s1[i];
}
cout << endl;
}
void Test_string5()//modify
{
fy::string s1("hello");
s1.push_back(' ');
s1.push_back('w');
s1.append("orld");
s1 += '!';
s1 += "xxxx";
}
void Test_string6()//String operations
{
fy::string s1("hello helloxxss");
cout << s1.find('o') << endl;
cout << s1.c_str() << endl;
cout << s1.find('xxs') << endl;
cout << s1.c_str() << endl;
s1.insert(11, 'w');
cout << s1.c_str() << endl;
s1.insert(6, "xxxxxxxx");
cout << s1.c_str() << endl;
s1.erase(6, 10);
cout << s1.c_str() << endl;
}
void Test_string7()//Non - member function overloads
{
fy::string s1("hello");
cout << s1 << endl;//测试<<
fy::string s2;
cin >> s2;//测试>>
cout << s2 << endl;
fy::string s3("aaa");
fy::string s4("aaa");
if (s3 == s4)
cout << "s3==s4" << endl;
}
int main()
{
Test_string7();
return 0;
}
----------------我是分割线---------------
文章到这里就结束了,下一篇即将更新