1.string的框架
在string当中需要定义三个变量。
分别是str,size,capacity。
str是储存指向开辟的动态内存的地址。
size是存储现有的数据个数。
capacity是用来存储动态开辟的内存的大小。
2.构造和析构函数的实现
string需要在动态内存当中申请空间,所以必须要用自己写的构造与析构函数来申请和释放动态内存,用默认自带的会有内存泄露的风险。
(1).构造函数
string(const char* str = "")
:_size(strlen(str))
,_capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
思路:
先将数组的数据个数和大小存储好,然后根据数据大小来开辟动态内存的空间将地址赋值给str,然后将数组拷贝到开辟的动态内存当中。
(2).析构函数
~string() {
delete[] _str;
_str = nullptr;
_capacity = _size = 0;
}
思路:
析构函数就只需要将指向的动态内存空间释放掉就可以了,但是这里为了规整,还是把str置空,size和capacity赋值为0.
(3).拷贝构造函数
string(const string& str)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(str._str);
swap(tmp);
}
拷贝狗仔函数首先要进行列表的初始化,因为内置类型编译器一般不会进行初始化,会给一个随机值,可能会导致一些不必要的错误。
思路:利用构造函数基于用来拷贝的string类对象 str 来创建出一个临时的string类tmp,tmp可以视为str的拷贝。然后将tmp与被拷贝构造的string类对象 this 交换,这样一来this就成为了str的拷贝,tmp存储的是原来this所存储的数据,之后在出拷贝构造函数的作用域时,tmp会进行析构,把原来属于this的数据析构掉,这样一来拷贝构造就完成了。
这里还需要用到另一个函数swap,这边就一并列出来
void swap(string& s) {
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
思路:
直接将两个string类当中存储的三个数据str,size,capacity进行交换,可以用库函数当中的swap来实现。
3.基础函数的实现
先来实现一些string当中较为常用的功能。
(1).size()
size_t size()const {
return _size;
}
返回数据个数。
(2).capacity()
size_t capacity()const {
return _capacity;
}
返回动态内存大小
(3).operator[]
char& operator[](size_t pos) {
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos)const {
assert(pos < _size);
return _str[pos];
}
下标访问函数的实现分为可修改和不可修改。
(4).reserve(int n)
void reserve(int n) {
if (n > _capacity) {
char* newnode = new char[n+1];
strcpy(newnode, _str);
delete[] _str;
_str = newnode;
_capacity = n;
}
}
reserve可以提前开辟想要的动态内存空间,可以很好的减少扩容的次数。
思路:
首先检查需要的大小是否大于原来的空间,大于则直接在动态内存当中开辟想要的大小,然后将原本存储的数据拷贝到新的动态内存当中,将原本的动态内存释放,str的地址修改为新的动态内存地址,然后修改size和capacity的大小。
(5).resize(int n, char ch = '\0')
void resize(int n, char ch = '\0') {
if (n <= _size) {
_str[n] = '\0';
_size = n;
}
else {
reserve(n);
while (_size < n) {
_str[_size] = ch;
_size++;
}
_str[_size] = '\0';
}
}
resize可以减少和增加动态内存当中存储的数据个数,如果内存不足可以开辟更多的动态内存,但是不能释放内存,因为原则上动态内存不能只释放一部分。
思路:首先判断需要的数据个数n,小于或等于则直接将字符串第n个位置改为\0,并修改size的大小为n。如果大于,则需要面对两种情况,需要的内存大于现有的动态内存和不大于。但是这里可以忽视掉,直接使用reserve,因为reserve当中有关于大小的检查,只有大于时才会发生扩容,之后从下标size开始直到n-1都修改为字符ch,将下标为n修改为\0,如果没有传字符过来,默认ch为\0。
(6).push_back(char n)
void push_back(char n) {
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = n;
_size++;
_str[_size] = '\0';
}
尾部插入数据
思路:
先查看内存是否足够,不足则用reserve进行扩容,之后将下标为size的数据修改为n,++size,添加\0。
(7).append(const char* arr)
sting当中尾部插入数据,字符和字符串并没有用函数重载,而是使用了两个不同的函数,因为是模拟实现,所以也是用两个函数来实现尾部插入。
void append(const char* arr) {
int len = strlen(arr);
if (_size + len > _capacity) {
reserve(_size + len);
}
strcpy(_str + _size, arr);
_size += len;
}
思路:
先取出arr字符串的长度,判断内存是否足够,不足则扩容,之后从原字符串末尾开始将arr拷贝进去,修改size。
(8).测试
将上面实现的函数都测试一下是否正确。
void test_string1() {
string s1("hello world");
cout << s1.c_str() << endl;
for (int i = 0; i < s1.size(); i++) {
cout << s1[i];
}
cout << endl;
string s2(s1);
cout << s2.c_str() << endl;
s1.push_back(' ');
s1.append("hello");
cout << s1.c_str() << endl;
}
在这里为了方便查看字符串还需要实现一个函数c_str用来提取字符串
const char* c_str()const {
return _str;
}
运行
没有问题。
4.运算符的重载以及迭代器
(1).operator+=(char n)
string& operator+=(char n) {
push_back(n);
return *this;
}
思路:
直接复用push_back就可以,然后返回+=后的值。
(2).operator+=(const char* arr)
string& operator+=(const char* arr) {
append(arr);
return *this;
}
直接复用append就可以,然后返回+=后的值。
(3).operator<(const string& s)const
bool operator<(const string& s)const {
return strcmp(_str, s._str)<0;
}
思路:
直接复用标准库里的strcmp就可以,并判断返回值。
(4).operator==(const string& s)const
bool operator==(const string& s)const {
return strcmp(_str, s._str) == 0;
}
思路:
直接复用标准库里的strcmp就可以,并判断返回值。
(5).operator<=(const string& s)const
bool operator<=(const string& s)const {
return *this<s||*this==s;
}
思路:
复用小于和等于,只要有一个为真便返回真。
(6). operator>(const string& s)const
bool operator>(const string& s)const {
return !(*this <= s);
}
思路:
复用小于等于并取反。
(7).operator>=(const string& s)const
bool operator>=(const string& s)const {
return !(*this < s);
}
思路:
复用小于并取反。
(8).operator!=(const string& s)const
bool operator!=(const string& s)const {
return !(*this == s);
}
思路:
复用等于并取反
(9).operator=( string s)
string& operator=( string s) {
if (this != &s) {
swap(s);
}
return *this;
}
思路:
先判断是否为同一个数,不是同一个则复用swap交换,这边使用的时传值调用,所以交换不会影响到原本的数,并且出了这个函数会将换过来的this的数据进行析构。
(9).迭代器的实现
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;
}
所有的迭代器都要命名为iterator,并定义begin和end函数来表示第一个数据的指针和最后一个数据的指针,以此来完成迭代。在string当中迭代器就是指针,但在其他类当中不一定是,到时候就需要运算符重载等方法来实现迭代器的遍历。
(9).clear()
用来清除所有数据,但不会释放空间
void clear() {
_str[0] = '\0';
_size = 0;
}
思路:
将字符串的首个字符修改为\0,并且修改size为0.
(10).operator<<(ostream& out, string& s)
ostream& operator<<(ostream& out, string& s) {
for (auto e : s) {
out << e;
}
return out;
}
思路:
将字符依次取出来投在屏幕上,最后返回流插入
(11).operator>>( istream& in, string& s)
istream& operator>>( istream& in, string& s) {
s.clear();
size_t i = 0;
char buff[129];
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n') {
buff[i++] = ch;
if (i == 128) {
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0) {
buff[i] = '\0';
s += buff;
}
return in;
}
思路:
先使用clear清除原本的数据,然后一个个提取到ch上,如果遇到空格或\n就截止,但正常的流提取无法提取空格和\n,也就是提取不到结束的条件。所以需要用到一个函数,get(),这是一个库函数,可以让流提取能够取到空格和\n。但同时还有另一个问题,扩容太过频繁,所以需要用到一个缓冲区,先建立一个数组,将提取到的字符先保存的数组当中,当数组满了或者遇到结束条件时再进行插入,可以有效的减少扩容的次数。
(12).测试
void test_string2() {
string s1 = "hello world";
string s2 = "hello world";
cout << (s1 == s2) << endl;
cout << (s1 != s2) << endl;
cout << (s1 >= s2) << endl;
cout << (s1 <= s2) << endl;
cout << (s1 < s2) << endl;
cout << (s1 > s2) << endl;
s1[0] = 'z';
cout << (s1 == s2) << endl;
cout << (s1 != s2) << endl;
cout << (s1 >= s2) << endl;
cout << (s1 <= s2) << endl;
cout << (s1 < s2) << endl;
cout << (s1 > s2) << endl;
for (auto e: s1) {
cout << e;
}
cout << endl;
s1 += '*';
s1 += "########";
cout << s1 << endl;
}
将实现的所有函数进行一下测试
运行
5.所有代码
#include <iostream>
#include<assert.h>
using namespace std;
#pragma once
namespace world {
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(const char* str = "")
:_size(strlen(str))
,_capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string(const string& str)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(str._str);
swap(tmp);
}
~string() {
delete[] _str;
_str = nullptr;
_capacity = _size = 0;
}
void swap(string& s) {
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
size_t size()const {
return _size;
}
size_t capacity()const {
return _capacity;
}
const char* c_str()const {
return _str;
}
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(int n) {
if (n > _capacity) {
char* newnode = new char[n+1];
strcpy(newnode, _str);
delete[] _str;
_str = newnode;
_capacity = n;
}
}
void resize(int n, char ch = '\0') {
if (n <= _size) {
_str[n] = '\0';
_size = n;
}
else {
reserve(n);
while (_size < n) {
_str[_size] = ch;
_size++;
}
_str[_size] = '\0';
}
}
void push_back(char n) {
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = n;
_size++;
_str[_size] = '\0';
}
void append(const char* arr) {
int len = strlen(arr);
if (_size + len > _capacity) {
reserve(_size + len);
}
strcpy(_str + _size, arr);
_size += len;
}
string& operator+=(char n) {
push_back(n);
return *this;
}
string& operator+=(const char* arr) {
append(arr);
return *this;
}
bool operator<(const string& s)const {
return strcmp(_str, s._str)<0;
}
bool operator==(const string& s)const {
return strcmp(_str, s._str) == 0;
}
bool operator<=(const string& s)const {
return *this<s||*this==s;
}
bool operator>(const string& s)const {
return !(*this <= s);
}
bool operator>=(const string& s)const {
return !(*this < s);
}
bool operator!=(const string& s)const {
return !(*this == s);
}
string& operator=( string s) {
if (this != &s) {
swap(s);
}
return *this;
}
void clear() {
_str[0] = '\0';
_size = 0;
}
private:
char* _str;
int _size;
int _capacity;
};
ostream& operator<<(ostream& out, string& s) {
for (auto e : s) {
out << e;
}
return out;
}
istream& operator>>( istream& in, string& s) {
s.clear();
size_t i = 0;
char buff[129];
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n') {
buff[i++] = ch;
if (i == 128) {
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0) {
buff[i] = '\0';
s += buff;
}
return in;
}
void test_string1() {
string s1("hello world");
cout << s1.c_str() << endl;
for (int i = 0; i < s1.size(); i++) {
cout << s1[i];
}
cout << endl;
string s2(s1);
cout << s2.c_str() << endl;
s1.push_back(' ');
s1.append("hello");
cout << s1.c_str() << endl;
}
void test_string2() {
string s1 = "hello world";
string s2 = "hello world";
cout << (s1 == s2) << endl;
cout << (s1 != s2) << endl;
cout << (s1 >= s2) << endl;
cout << (s1 <= s2) << endl;
cout << (s1 < s2) << endl;
cout << (s1 > s2) << endl;
s1[0] = 'z';
cout << (s1 == s2) << endl;
cout << (s1 != s2) << endl;
cout << (s1 >= s2) << endl;
cout << (s1 <= s2) << endl;
cout << (s1 < s2) << endl;
cout << (s1 > s2) << endl;
for (auto e: s1) {
cout << e;
}
cout << endl;
s1 += '*';
s1 += "########";
cout << s1 << endl;
}