目录
前言:
1. string框架构造
2. 默认函数
2.1 构造函数
2.2 析构函数
2.3 拷贝构造
2.4 赋值重载
3. 迭代器
4. 整体程序
前言:
本篇文章模拟实现了C++中string的部分功能,有助于大家了解和熟悉string类,虽然这个类不难实现,但是其中有些小细节还是可以细细品味的。
1. string框架构造
我们得知道编写一个类的首要步骤不是直接开写代码,而是需要思考这个类需要用什么样的数据结构?需要准备哪些参数,这个类的作用域在哪里等等内容。
首先,咱们知道string类是有多个版本的,不同版本下的string类的变量是不同的,就我所知,在vs下string的实现是下方的格式,实际上比我写的要复杂,但是能够表示。
class string{
private:size_t _size;
size_t _capacity;
char* _buf;
char _arr[16];
};
这样做的用处主要是空间换取时间,当我们只是一个小字符串,那么就直接将数据存到了_arr的数组当中,因为是直接写在类里面的数组,所以就省去了向堆申请空间这一过程,如果数据大于了_arr数组大小,那么系统才会向堆申请空间。所以在vs下的string类有28个字节。
在Linux下则不同,string类有8个字节,结构如下:至于为什么是8个字节,那是因为Linux下跑C++代码默认是64位机哦,我现在还不会改为32位运行,请原谅博主。
class string{
private:
m_string* _point;
}
其中m_string是一个自定义类型,里面存了和vs下差不多样式的变量,只不过没有数组的存在,如下:
class m_string{
size_t _size;
size_t _capacity;
char* _buf;
};
这样做是因为Linux有一个特性,那就是赌,它赌我们在拷贝时不需要更改,不改那么他就直接将指针指向这个空间,一旦需要更改,那他就会触发写时拷贝这个牛技术。如下图:
对此我只能是表示,这些大佬们写代码是真牛哇,为了提升代码效率都能卷成这样了,我们自己的实现就不用这么高级的操作了,太难写了。既然是为了理解string类,所以我决定用下方的结构:(简单清楚好写)
class string{
private:
char* _buf;
size_t _size;
size_t _capacity;
};
还有一点,我们的这个类尽量写在一个自己的命名空间当中,防止与库中的string起冲突。
如下:
namespace yf
{
class string
{
private:
char* _buf;
size_t _size;
size_t _capacity;
};
}
2. 默认函数
2.1 构造函数
先看一下C++库中提供的构造函数:
真是有够恐怖的,这提供的接口有点猛,不过我们呢也不需要写这么多,写几个常用的就好。还有就是这些接口设计得有一些不合理,比如说default和from c-string两个函数很明显是可以写为一个函数的,以缺省参数来表示的,不过也能理解,毕竟C++是为了兼容C的。
代码:
//构造函数
string(const char* str = "")
:_size(strlen(str))
{
_capacity = _size;
_buf = new char[_capacity + 1];
strcpy(_buf, str);
}
上方我讲解了可以用缺省参数来合并两个函数接口,但是大家再不看我的代码之前能想出来吗?除此之外,我还想了两个方法,一个是 ‘ ’,另一个是“\0”,有人看出不对了吗,这两个方式都是不行的,第二种倒也不是不行,而是太多余了,本身“”里面就已经有了一个‘\0’了显得我们不够专业,那这可不行。第一种用字符方式缺省那是真的错得离谱,我们之后可是要用字符串呢,用字符接收?这很明显不行哇,所以这种情况是一定要避免的。不然以后去公司面试,让我们写个类都乱写,简历上还写着熟悉C++,这不扯淡嘛。还有一点,为了保持new空间和delete保持一致,
然后我们的构造函数当中还用了初始化列表这一概念,通过我们传入的字符串长度去初始化_size,然后_capacity又在下方被赋值,再为_buf向堆上申请_capacity+1个字节大小的空间,为什么加1,当然是为了存我们的'\0'哇,小笨蛋,最后再使用strcpy函数将字符拷贝到我们的空间当中。
测试用例:
void test1()
{
yf::string str1;
yf::string str2("hello");
yf::string str3("good morning xxxxxxxxxxxxx");
}
2.2 析构函数
大伙们回忆一下,为什么我们要有析构函数?因为我们向堆空间申请了空间了,所以需要释放,避免造成内存泄漏的过程。
//析构
~string()
{
delete[] _buf;
_size = 0;
_capacity = 0;
}
看着这里释放内存的方式是delete[] _buf,也就与我们的构造函数对应起来了,如果构造函数的缺省参数是‘’的话,这里就不好析构,就得分情况,这不是脱了裤子放屁——多此一举嘛。
2.3 拷贝构造
拷贝构造和构造差不多,我也就不多做解释了,只是要注意参数得用引用。
string(const string& str)
{
_capacity = str._capacity;
_size = str._size;
_buf = new char[str._capacity + 1];
strcpy(_buf, str._buf);
}
2.4 赋值重载
如果是没有数据的重载,好,简单,但是呢,我的这个字符串本来是有数据的,另外被拷贝对象有大于的情况,有小于的情况,有等于的情况,如果拷贝错误怎么办?原来的数据这么办?这一些列的问题问下来还简单嘛?依然简单,只不过多数人在一时间想不到完全实现功能的代码罢了。
//赋值重载
string& operator=(const string& str)
{
//不能自己拷贝自己
if (this != &str)
{
//当前容量大于被拷贝对象并不多时
if (_capacity - str._capacity < 10)
{
strcpy(_buf, str._buf);
_capacity = str._capacity;
_size = str._size;
}
else
{
//考虑到new失败的情况
char* temp = new char[str._capacity + 1];
strcpy(temp, str._buf);
delete[] _buf;
_capacity = str._capacity;
_size = str._size;
}
}
return *this;
}
请看我的代码,首先我们不能直接赋值给自己,这样会让我们把数据给delete掉了,导致整个程序出BUG了还不知道,这是很危险的。然后呢,我还考虑到了,当当前对象的容量大于被拷贝对象,并且大于的值并不多时,可以直接拷贝,不用delete后再整个赋值,节省了时间。
在下方需要delete的过程中,我们需要考虑到当new失败之后会怎么样,所以不能先释放掉原空间,需要创建一个变量去接收被拷贝对象的数据,如果new失败了,外部的try会直接接收到。
3. 迭代器
说起string类等一系列库类,里面都是提供了迭代器的,我们也模仿着在我们的类里面实现迭代器,我们呢还是从简,用指针来实现迭代器,不过我们不能将迭代器理解为指针,只是指针能够实现。
定义:
typedef char* iterator;
typedef const char* const_iterator;
对应接口:
//迭代器begin
iterator begin()
{
return _buf;
}
const_iterator begin() const
{
return _buf;
}
//迭代器end
iterator end()
{
return _buf + _size;
}
const_iterator end() const
{
return _buf + _size;
}
此时,我们的string类就能实现范围for这个语法糖了,至于为什么能实现,我只能说,范围for底层就是迭代器。
测试:
void test2()
{
const yf::string str1("hello world");
yf::string::const_iterator it = str1.begin();
while (it != str1.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : str1)
{
cout << e << " ";
}
cout << endl;
}
4. 整体程序
其余的函数都是对于string类的补充,比如reserve、resize、+=、[]、<<、>>函数等等,博主很懒,并且认为这些大家都是有基础能实现的所以就不做讲解了,附下代码:
#pragma once
#include<iostream>
#include<string>
#include<assert.h>
using std::cout;
using std::cin;
using std::endl;
namespace yf
{
class string
{
friend std::ostream& operator<<(std::ostream& out, const string& str);
friend std::istream& operator>>(std::istream& in, yf::string& str);
public:
typedef char* iterator;
typedef const char* const_iterator;
//构造函数
string(const char* str = "")
:_size(strlen(str))
{
_capacity = _size;
_buf = new char[_capacity + 1];
strcpy(_buf, str);
}
//拷贝构造
string(const string& str)
{
_capacity = str._capacity;
_size = str._size;
_buf = new char[str._capacity + 1];
strcpy(_buf, str._buf);
}
//赋值重载
string& operator=(const string& str)
{
//不能自己拷贝自己
if (this != &str)
{
//当前容量大于被拷贝对象并不多时
if (_capacity - str._capacity < 10)
{
strcpy(_buf, str._buf);
_capacity = str._capacity;
_size = str._size;
}
else
{
//考虑到new失败的情况
char* temp = new char[str._capacity + 1];
strcpy(temp, str._buf);
delete[] _buf;
_capacity = str._capacity;
_size = str._size;
}
}
return *this;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _buf[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _buf[pos];
}
//tidy_capacity
void reserve(size_t n);
void resize(size_t n, char c = '\0');
//add
void push_back(char c);
void append(const string& str);
void append(const char* s);
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const string& str)
{
append(str);
return *this;
}
string& operator+=(const char* s)
{
append(s);
return *this;
}
//insert
string& insert(size_t pos, size_t n, char c);
string& insert(size_t pos, const char* s);
string& insert(size_t pos, const string& str);
//迭代器begin
iterator begin()
{
return _buf;
}
const_iterator begin() const
{
return _buf;
}
//迭代器end
iterator end()
{
return _buf + _size;
}
const_iterator end() const
{
return _buf + _size;
}
//输出
void print() const
{
cout << _buf << endl;
}
const char* c_str() const
{
return _buf;
}
//大小
size_t size() const
{
return _size;
}
//容量
size_t capacity() const
{
return _capacity;
}
//匹配字符
size_t find_first_of(char c, size_t pos = 0)
{
const_iterator it = begin();
it += pos;
while (it != end())
{
if (*it == c)
{
return (it - begin());
}
it++;
}
return -1;
}
//获取字串
string substr(size_t pos = 0, size_t len = -1) const;
//析构
~string()
{
delete[] _buf;
_size = 0;
_capacity = 0;
}
const size_t npos = -1;
private:
char* _buf;
size_t _size;
size_t _capacity;
};
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
//tidy_capacity->reserve
void yf::string::reserve(size_t n)
{
if (n > _capacity)
{
char* temp = new char[n + 1];
strcpy(temp, _buf);
delete[] _buf;
_buf = temp;
_capacity = n;
}
}
//tidy_capacity->resize
void yf::string::resize(size_t n, char c)
{
//长度小于_size时,需要删除n位置后的数据
if (n < _size)
{
_size = n;
_buf[_size] = '\0';
}
else
{
//当n>_size有两种情况,大于capacity和小于capacity
if (n > _capacity)
{
//大于需要扩容操作
reserve(n); //复用reserve
}
//二者都有同样的需求,对后面的数据初始化为c
while (_size != n)
{
_buf[_size] = c;
++_size;
}
_buf[_size] = '\0';
}
}
//add
void yf::string::push_back(char c)
{
if (_size + 1 > _capacity)
{
//加一预防_capacity初始为0
reserve(2 * _capacity + 1);
}
_buf[_size++] = c;
_buf[_size] = '\0';
}
void yf::string::append(const string& str)
{
if (_size + str._size > _capacity)
{
reserve(_size + str._size);
}
size_t len = str._size;
int i = 0;
while (i!=len)
{
_buf[_size++] = str[i];
i++;
}
_buf[_size] = '\0';
}
void yf::string::append(const char* s)
{
size_t len = strlen(s);
if (_size + len > _capacity)
{
reserve(_size + len);
}
while (*s != '\0')
{
_buf[_size++] = *s;
s++;
}
_buf[_size] = '\0';
}
//insert
yf::string& yf::string::insert(size_t pos, size_t n, char c)
{
if (_size + n > _capacity)
{
reserve(_size + n);
}
size_t end = _size + n;
while (end - n + 1 > pos)
{
_buf[end] = _buf[end - n];
--end;
}
for (size_t i = 0; i < n;++i)
{
_buf[pos + i] = c;
}
_size += n;
return *this;
}
//插入字符串
yf::string& yf::string::insert(size_t pos, const char* s)
{
size_t len = strlen(s);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (end - len + 1 > pos)
{
_buf[end] = _buf[end - len];
--end;
}
strncpy(_buf + pos, s, len);
_size += len;
return *this;
}
yf::string& yf::string::insert(size_t pos, const yf::string& str)
{
size_t len = str._size;
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (end - len + 1 > pos)
{
_buf[end] = _buf[end - len];
--end;
}
strncpy(_buf + pos, str._buf, len);
_size += len;
return *this;
}
//获取子串
yf::string yf::string::substr(size_t pos, size_t len) const
{
yf::string::const_iterator it = begin();
yf::string temp;
it += pos;
if (len > size())
len = size();
while (it != begin() + len)
{
temp += *it;
++it;
}
return temp;
}
//流插入
std::ostream& yf::operator<<(std::ostream& out, const yf::string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
//流提取
std::istream& yf::operator>>(std::istream& in, yf::string& s)
{
//从缓冲区获取字符
char ch = in.get();
while (ch != '\n')
{
s += ch;
//记录上一次位置,拿到下一个字符
ch = in.get();
}
return in;
}