学习了c++, 其中STL是当之无愧的佼佼者。简单实现一些STL容器-string,只写了部分功能。
/*
模拟实现一个简单的stirng容器
*/
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<string.h>
#include<iostream>
#include<assert.h>
using namespace std;
class String {
public:
//string中迭代器很简单, 不用实现它都可以完成++、--、*、->操作。
typedef char* iterator; //定义迭代器
typedef const char* const_iterator;
//声明一个cout友元函数
friend ostream& operator<<(ostream& _cout, const String& str);
//构造函数
String(const char* str = "") {
_size = strlen(str);
_capacity = _size;
//size只是表示有效元素的个数, 但必须需要存放一个'\0', 所以多开一个。
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//拷贝构造函数
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)
//{
// //现代写法, 创建一个临时对象,然后交换, 最后临时对象被析构。
// //但是请注意现在写法,交换后,临时对象最后会被析构,所以交换以前_str要存放nullptr。
// String tmp(s._str);
// Swap(tmp);
//}
//类与类成员交换函数 - 浅拷贝。
void Swap(String& str) {
swap(_str, str._str);
swap(_size, str._size);
swap(_capacity, str._capacity);
}
//赋值运算符重载
String& operator=(const String& s) {
if (this != &s) {
//先释放原有的, 然后在开辟新的,把s类值中拷贝过来。
delete[] _str;
_size = s._size;
_capacity = s._capacity;
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
return *this;
}
//赋值运算符重载 - 现代写法
//String& operator=(String str) {
// //现在写法-赋值运算符重载
// Swap(str);
// return *this;
//}
//析构函数
~String() {
if (_str) {
delete[] _str;
}
}
//访问
char& operator[](size_t pos) {
//可读可写
assert(pos < _size);
return _str[pos];
}
//与上面operator[]函数构成重载,上面可读可写, 这个只读不能写。
//主要是搭配const String型的对象。
const char& operator[](size_t pos) const {
//只能读不能写。
//匿名的对象具有常性。
assert(pos < _size);
return _str[pos];
}
iterator begin() {
return _str;
}
iterator end() {
return _str + _size;
}
const_iterator begin() const {
return _str;
}
const_iterator end() const {
return _str + _size;
}
const iterator cbegin() {
return _str;
}
const iterator cend() {
return _str + _size;
}
//修改
void push_back(char c) {
//插入之前是需要判断的。
if (_size == _capacity) {
//容量满了, 需要扩容
size_t newC = _capacity == 0 ? 15 : 2 * _capacity;
reserve(newC); //调用了增容。
}
_str[_size] = c;
++_size;
_str[_size] = '\0';
}
void append(const char* str) {
//插入一个字符串
int len = strlen(str);
//如果_size+len<_capacity,我们是不需要扩容的, 否则则需要扩容。
if (_size + len >= _capacity) {
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
_str[_size] = 0;
}
String& operator+=(char c) {
//只能加字符
push_back(c);
return *this;
}
String& operator+=(const char* str) {
//加字符串
Append(str);
return *this;
}
String& operator+=(const String& str) {
//加一个类
Append(str._str);
return *this;
}
//insert插入一个字符
void insert(size_t pos, char c) {
if (_size == _capacity) {
size_t newC = _capacity == 0 ? 15 : 2 * _capacity;
reserve(newC);
}
//先后移再插入 - 建议画图理解!
size_t end = _size + 1;
for (end; end > pos; --end) {
_str[end] = _str[end - 1];
}
_str[pos] = c;
_size++;
}
//insert插入一个字符串
void insert(size_t pos, const char* str) {
int len = strlen(str);
if (_size + len >= _capacity) {
reserve(_size + len);
}
size_t end = _size + len;
size_t end1 = _size;
for (end; end > pos; --end) {
_str[end] = _str[end1];
--end1;
}
int i = 0;
while (str[i] != '\0') {
_str[pos + i] = str[i];
++i;
}
_size += len;
}
//earse删除函数
void erase(size_t pos, int len) {
size_t begin = pos;
size_t begin1 = pos + len;
for (begin; begin < _size; ++begin) {
_str[begin] = _str[begin1];
++begin1;
}
--_size;
}
//删除迭代器对应的值
void erase(iterator it)
{
assert(it < end() && it >= begin());
while (it != end())
{
*it = *(it + 1);
++it;
}
--_size;
}
//查找一个字符串, 找到后返回下标。 没有返回npos
size_t find(const char* str, size_t pos = 0){
char* start = strstr(_str + pos, str);
if (start == nullptr)
return npos;
else
return start - _str;
}
//查找一个字符, 找到后返回下标。 没有返回npos
size_t find(char ch, size_t pos = 0){
for (int i = pos; i < _size; ++i){
if (ch == _str[i])
return i;
}
return npos;
}
//容量
//reserve接口, 只增不减!
void reserve(size_t n) {
if (n > _capacity) {
char* buffer = new char[n + 1];
strcpy(buffer, _str);
delete[] _str;
_str = buffer;
_capacity = n;
}
}
//resize重新设置
void resize(size_t n, char c = '\0') {
//重新设置capacity容量的大小
//分为3种情况:
// 1、 n > _capacity -- 增容
// 2、 _size < n < _capacity -- 赋值
// 3、 sz < _size == --改变_size
//下面的代码小巧精炼, 值得参考!
if (n > _capacity) {
//如果n>容量, 则需要扩容
reserve(n);
}
if (n > _size) {
//不管是否扩容过, 只要n > _size, 我们就必须要赋初始值
memset(_str + _size, c, n - _size);
}
//最后修改_size
_size = n;
_str[_size] = '\0';
}
//字符串的大小
size_t size() const{
return _size;
}
//当前动态开辟的容量大小
size_t capacity() const{
return _capacity;
}
//提供一个char*的指针。 只读不写
const char* c_str() const {
return _str;
}
private:
static const size_t npos;
char* _str;
int _size;
int _capacity;
};
//对静态成员赋值。
const size_t String::npos = -1;
//友元函数实现
ostream& operator<<(ostream& _cout, const String& str) {
for (const auto& ch : str) {
_cout << ch;
}
return _cout;
}
代码中我尽可能多地表达每段函数的信息。 一些简单的我就不做详细的解读。
在写源码时, 需要掌握几点:
1、熟悉并知道容器中每个接口的功能
2、知道每个容器的优缺点
分析:
1、我们可以看出string容器是线性存储的, 因此这种容器就像线性表一样会有一个缺点–不易于任意位置插入删除(效率不高),更偏向于遍历。
2、当然也有优点: 那就是对内存使用率是比较好的。遍历效率高!
3、还有一点增容这块代价还是很大的!