前言
本篇博客主要内容:实现string类的基本功能。
string使用很快就讲完了,但是光会用string还不够,在面试中,面试官总喜欢让我们自己来模拟实现string类,包括string类的构造、拷贝构造、赋值运算符重载以及析构函数等等内容。所以,我认为string类的自实现还是有必要讲一下的。
🔥string类的接口函数
我们本次并不会将string类的所有接口函数逐一讲解,讲一些常用的和重点的。本次string的实现分成了两个文件,一份是string.h
,一份string.cpp
。
看看string.h的内容:
# define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iostream>
#include<cstring>
#include<cassert>
using std::cout;
using std::cin;
using std::endl;
namespace ForcibleBugMaker
{
class string
{
public:
// 交换,非成员函数
friend void swap(string& s1, string s2);
// 定义迭代器
typedef char* iterator;
typedef const char* const_iterator;
//迭代器获取
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
// string默认成员函数
string(const char* str = "");
string(const string& s);
string& operator=(string tmp);
~string();
// 获取只读字符串
const char* c_str() const;
// 获取容量
size_t size() const;
size_t capaity() const;
// []获取元素重载
char& operator[](size_t pos);
const char& operator[](size_t pos) const;
// 开辟空间
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 = 0, size_t len = npos);
// 查找字符或字串
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
// string对象比较
bool operator>(const string& str) const;
bool operator==(const string& str) const;
bool operator>=(const string& str) const;
bool operator<(const string& str) const;
bool operator<=(const string& str) const;
bool operator!=(const string& str) const;
// 获取string对象字串
string substr(size_t pos, size_t len);
// 交换函数,成员函数
void swap(string& str);
// 清除串中内容
void clear();
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
// 常量成员
const static size_t npos;
};
// 交换函数,非成员函数
void swap(string& s1, string s2);
// 流插入和流提取重载,非成员函数
std::ostream& operator<<(std::ostream& so, const string& str);
std::istream& operator>>(std::istream& is, string& str);
}
在实现string类添加和拷贝的一些函数中,使用了C语言中的一些库函数,以此方便实现,这些函数在C++中统一存放在<cstring>
。
如果对C语言的一些字符串函数不太了解,可以看看我之前写的一篇博客:C语言-字符串函数,相信会对你有所帮助。
🔥string类的模拟实现
接下来进入主要内容,按照string.h的接口开始实现。
以下接口都是放在命名空间里的,不同文件的相同命名空间在编译时会自动合并。
==swap交换==
如果你仔细观察,会发现存在两个swap交换函数,一个string中的成员函数,另一个是非成员函数。
在std的默认swap当中,是这样的:template <class T> void swap ( T& a, T& b ) { T c(a); a=b; b=c; }
当我们使用这个成员函数进行交换时,会造成拷贝消耗,我们提供对应的非成员函数重载是为了防止C++程序员掉坑。
成员函数的swap:void string::swap(string& str) { std::swap(_str, str._str); std::swap(_size, str._size); std::swap(_capacity, str._capacity); }
非成员函数的swap:
void swap(string& s1, string& s2) { std::swap(s1._str, s2._str); std::swap(s1._size, s2._size); std::swap(s1._capacity, s2._capacity); }
==默认成员函数==
默认成员函数,就是不提供编译器自动会生成的一些函数。我们这里实现构造函数,析构函数和赋值运算符重载。
构造函数:
string::string(const char* str)
:_size(strlen(str))
{
_capacity = _size + 1;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string::string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
构造函数提供了两个,分别支持了字符串构造和string对象构造。_str指向通过new开辟空间,这个空间需要比实际的capacity大,因为需要在字符串末尾多存放一个'\0'
。
析构函数:
string::~string()
{
delete[] _str;
_str = nullptr;
_capacity = 0;
_size = 0;
}
这个没什么好说,释放空间,指针置空,_size和_capacity置0。
赋值运算符重载:
string的赋值属于深拷贝。
拷贝分为深拷贝和浅拷贝:
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。
深拷贝:如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
浅拷贝:
深拷贝:
如果中规中矩的来写(传统版赋值重载):
String& operator=(const String& s)
{
if (this != &s)
{
char* pStr = new char[strlen(s._str) + 1];
strcpy(pStr, s._str);
delete[] _str;
_str = pStr;
}
return *this;
}
但如果你能对之前实现的代码进行复用,你会发现这个过程可以简化非常多(现代版赋值重载):
string& string::operator=(string tmp)
{
// 调用成员函数的swap,交换*this和tmp
swap(tmp);
return *this;
}
在C++的编写中,学会复很有必要。
==迭代器==
之前讲过,迭代器不一定是指针,但是你可以把它想象成指针,可以通过正常的运算操作来控制其指向的元素。在string中,我们使用指针来模拟实现迭代器。
迭代器在不同编译器下的实现方式有所不同,比如VS下的迭代器就不是一个指针,而是一个类模板。
在类中需要typedef一下,可以看到我们所实现的string迭代器的本质:
// 定义迭代器
typedef char* iterator;
typedef const char* const_iterator;
获取迭代器的接口:
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;
}
这时,我们已经可以使用我们自己的迭代器了:
string str("hello world!");
string::iterator it = str.begin();
while (it != str.end()) {
cout << *it << " ";
++it;
}
cout << endl;
==获取容量和内容信息==
这些都是返回str类型状态和内容的函数。
const char* string::c_str() const
{
return _str;
}
size_t string::size() const
{
return _size;
}
size_t string::capaity() const
{
return _capacity;
}
// 运算符重载
char& string::operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& string::operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
都比较简单易懂。