#pragma once
#include <iostream>
#include <assert.h>
namespace bit
{
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 = "")
:_str(new char[strlen(str) + 1])
, _size(strlen(str))
, _capacity(strlen(str))
{
strcpy(_str, str);
}*/
//不建议这样使用构造函数,的确这样可以减少两次strlen的复杂度,但是此处的初始化的顺序要和下面声明的顺序要一致,不利于维护与修改,否则就会出现随机值的问题,所以最终建议是将构造的内容一并写入函数体内
/*string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
,_str(new char[_capacity + 1])
{
strcpy(_str, str);
}*/
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string()
:_str(new char[1])//这里不能用空指针来初始化,空对象可能会解引用空指针导致崩溃,这里开一个是因为如果是空字符串也有一个\0来标识结束
, _size(0)
, _capacity(0)
{
_str[0] = '\0';
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
//传统写法
/*string(const string& s)
:_str(new char[s._capacity+1])
,_size(s._size)
,_capacity(s._capacity)
{
strcpy(_str, s._str);
}
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
*/
//现代写法:复用
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}
return *this;
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[]_str;
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
string& operator +=(char ch)
{
push_back(ch);
return *this;
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(len + _size);
}
strcpy(_str + len, str);
_size += len;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
一,构造函数
这里我们将讨论几种构造函数的实现方法的可行性,并给出理由。
首先我们这里采用最简单的char* ,size和capacity的总体框架来设计整个类。
第一种:构造函数主要用于c语言风格的string类的构造,先传入一个c语言的char类型指针,再构造一个c++类并使用初始化列表进行初始化,注意这里new的时候要多加一个\0的位子,这种方法的缺陷就是使用了三次o(n)的strlen,复杂度比较高。
大概的使用场景如下:
const char* cStyleString = "Hello, World!"; // A C-style string
string myString(cStyleString); // Creating a custom string object with the content of the C-style string
第二种:为了减少使用strlen的次数,我们进行了顺序调换,避免了重复的使用。但是不建议这样使用构造函数,的确这样可以减少两次strlen的复杂度,但是此处的初始化的顺序要和下面声明的顺序要一致,不利于维护与修改,否则就会出现随机值的问题,所以最终建议是将构造的内容一并写入函数体内,也就是第三种方法。
无参的默认构造函数:注意这里不能用空指针来初始化,空对象可能会解引用空指针导致崩溃,这里开一个是因为如果是空字符串也有一个\0来标识结束。
二,析构函数
这里的析构函数比较简单,只是要注意这里的delete后面要加上[],和前面的new[]对应上即可
三,函数接口
1.c_str()
这个函数非常常用,尤其在需要与C风格字符串兼容的代码或库进行交互时很有用。一些C/C++库和函数(尤其是老旧的库)可能要求以C风格字符串作为输入或者返回C风格字符串,这时候你可以使用这个函数来获取类对象中存储的C风格字符串。
2.size()
返回该string的_size,注意这里的c_str和size都要用const来修饰,防止修改
3.[]的符号重载
首先判断传入参数是否超过_size的大小,然后返回指定位置的字母。注意这里要单独写两种,一种是const修饰的一种是不带const修饰的,普通对象调用不带const,const对象就要调用带const,主要原因就是权限只能缩小而不能放大。
三,迭代器
这里由于是string类,所以直接用指针来代替,但不是所有的类的迭代器都是指针,只有类似于连续数组的对象才能用指针代替。定义一个begin和end就行,不过同样的我们需要定义一个带const的迭代器。
四,拷贝构造函数
这里没有写拷贝构造,编译器会自动生成,对于内置类型是完成值拷贝或者浅拷贝,而这里所有的成员变量都是内置类型。所以在浅拷贝的时候,s2也会指向字符串。当出了函数作用域时调用析构函数就会析构两次,从而会发生崩溃。
所以为了解决浅拷贝的问题,应该定义自己的拷贝构造函数使用深拷贝,自己开辟一块空间,然后再把值拷贝下来就不会发生指向同一块地址的问题。
这里我们的传统写法先判断是不是给自己赋值,如果是就直接return,如果不是我们直接开辟新空间,然后拷贝数据,最后释放原空间,并把变量赋值一遍。
而现代写法为了方便起见,我们先定义一个tmp用来存放赋值内容,然后将tmp与需要赋值的对象和tmp进行交换就完成了拷贝构造。同理=的符号重载也可以用相似的方法实现。
五,一些函数接口
尾插reserve函数用来开辟一个新的tmp对象,尾插函数在_size的地方替换要插入的字符,最后处理一下_size和\0
+=的符号重载直接复用push_back就行。
append函数首先判断字符串是否能够装下,如果装不下就调用reserve函数,然后再把值拷贝过去就行,+=直接调用append就行。