手撕容器string(了解底层) 1
目录
基本前提:
我们知道string类是模板basic_string的实例化对象,那它就应该包含相应的成员变量。

本期内容在此基础上手打代码,自己实现string的各种接口。
1构造函数的实现
1.1默认构造函数(无参)
当默认构造函数将_str初始化成空指针时,无法对其进行输入输出。
头文件
#pragma once
#include<iostream>
using namespace std;
namespace xxj//创建命名空间,防止string关键词冲突
{
class string
{
public:
string()
:_str(nullptr)//nullptr解决了NULL的指针类型转换问题
, _size(0)
, _capacity(0)
{
}
const char* c_str()
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
测试源文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace xxj
{
void test_string1()
{
string s1;//不用写明类域,优先在本域里找string,不回和库里的string发生冲突。
cout << s1.c_str() << endl;
}
}
int main()
{
xxj::test_string1();
return 0;
}
这里c_str()返回一个const char*类型的空指针,cout接受时自动识别char*类型,会对其进行解引用,而后报错。
运行结果:

1.2有参构造函数
头文件
#pragma once
#include<iostream>
using namespace std;
namespace xxj//创建命名空间,防止string关键词冲突
{
class string
{
public:
string()//默认
:_str(new char[1]{'\0'})//nullptr解决了NULL的指针类型转换问题
, _size(0)
, _capacity(0)
{
}
string(const char* str)//有参
:_str(new char[strlen(str)+1])//1.strlen接口会遍历,O(N),用3次,没效率
, _size(strlen(str)) //2.与函数strlen不同,sizeof操作符只能计算数组的字节
, _capacity(strlen(str)) //数,如果传指针,根据环境返回对应的字节数,比如32位
//就是4个字节,且在编译阶段就已经执行完成,不可计算动
strcpy(_str, str); //态分配的内存
}
const char* c_str()
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
既然使用3次strlen会降低代码效率,那么把代码改成下边这样可行吗?
string(const char* str)
:_size(strlen(str))
,_str(new char[_size+1])
, _capacity(_size)
{
strcpy(_str, str);
}
不可行!改变之后的代码初始化列表内容顺序改变了,但是声明处的内容顺序没变,而初始化列表的初始化顺序与变量被声明的顺序相同,也就是说_str是第一个被初始化的,但是这是后还没有_size,此时new出来的内存是随机的。因此如果使用有参的构造函数来创建对象程序可能会崩。
如果非要这样写,需要改变声明顺序,但是这样做程序的可维护性不强,别人顺序稍微一换就出问题。
那怎么办
string(const char* str)
:_size(strlen(str))
{
_str=new char[_size];
_capacity = _size;
strcpy(_str, str);
}
把_str和_capacity放到函数体里定义就行。所谓“两害取其轻”。
1.3规范化编程
上述项目中我们可以把较长的函数做声明和定义分离,较短的函数就直接放在.h文件中,直接内联展开。整理如下:
string.h
#pragma once
#include<iostream>
using namespace std;
namespace xxj//创建命名空间,防止string关键词冲突
{
class string
{
public:
string(const char* str = "");//缺省参数,放在声明处,给空默认结尾有\0,直接给也没问题。
~string();
const char* c_str()
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
string.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace xxj//创建命名空间,防止string关键词冲突
{
string::string(const char* str )
:_size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
string::~string()
{
delete[]_str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
}
Test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace xxj
{
void test_string1()
{
string s1;
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
}
}
int main()
{
try
{
xxj::test_string1();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
2.字符转的遍历的实现
2.1[ ]操作符重载
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const//const修饰this指针,变只读,无法改变对象成员变量
{
assert(pos < _size);
return _str[pos];
}
2.2迭代器(指针实现)
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;
}
2.3整理
将上述代码(短)添加到头文件中,在加上size,和c_str接口,我们就能用两种方式实现自定string类对象的遍历。
string.h
#pragma once
#include<iostream>
using namespace std;
namespace xxj//创建命名空间,防止string关键词冲突
{
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;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const//const修饰this指针,变只读,无法改变对 //象私有成员变量
{
assert(pos < _size);
return _str[pos];
}
string(const char* str = "");//缺省参数,放在声明处,给空默认结尾有\0,直接给也没 //问题
~string();
size_t size() const
{
return _size;
}
const char* c_str()const
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
Test.cpp
#include"string.h"
namespace bit
{
void test_string1()
{
bit::string s1;
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
s2[0] = 'x';
for (size_t i = 0; i < s2.size(); i++)
{
s2[i]++;
}
cout << s2.c_str() << endl;
string s3 = "hello world"; // 隐式类型转换,构造+拷贝构造->优化为构造
string s4("hello world");
const string s5("hello world");
for (size_t i = 0; i < s2.size(); i++)
{
cout << s5[i] << "-";
}
cout << endl;
for (auto ch : s4)//范围for需要迭代器的支持,需要手动实现迭代器
{
cout << ch << " ";
}
cout << endl;
string::iterator it4 = s4.begin();
while (it4 != s4.end())
{
*it4 += 1;
cout << *it4 << " ";
++it4;
}
cout << endl;
for (auto ch : s5)
{
cout << ch << " ";
}
cout << endl;
string::const_iterator it5 = s5.begin();
while (it5 != s5.end())
{
// *it5 += 1;
cout << *it5 << " ";
++it5;
}
cout << endl;
}
3.插入功能的实现
3.1扩容
实现插入功能,首先要扩容,标准string容器的resreve接口是会缩容的,我们自己实现的reserve不会。
void string::reserve(size_t n)
{
if (n > _capacity)
{
//
char* tmp = new char[n + 1];//真正的容器,是直接向内存池申请内存的,这里直接用new了
//记得号多申请一个,size和capacity的位置是倒数第二个
//最后还有一个\0
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
3.2尾插
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
3.3尾部插入字符串
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(std::max(_size + len, _capacity * 2));//二倍不一定够
//要多少扩多少,每次只插入一点,就得
//频繁扩,很麻烦
}
strcpy(_str + _size, str);
_size += len;
}
3.4+=操作符重载
string& operator+=(const char* str)//字符串
{
append(str);
return *this;
}
string& operator+=(char ch)//字符
{
push_back(ch);
return *this;
}
3.5指定位置插入字符
这里要注意隐式类型转换
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size;
while (end >= pos)
{
_str[end + 1] = _str[end];
--end;
}
_str[pos] = ch;
_size++;
}
上述代码的第一个问题:头插,会出现end>=pos的情况恒成立(end是size_t类型,大于等于0),导致程序崩溃
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
int end = _size;//把size_t类型改成int,使之能出现负数的情况,跳出循环
while (end >= pos)
{
_str[end + 1] = _str[end];
--end;
}
_str[pos] = ch;
_size++;
}
第二个问题:运算符进行运算的时候,如果操作数类型不同,会先转换成一样类型,再进行运算,默认有符号整型会转换成无符号整型。那么这里的end又会被转换成size_t类型。
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
int end = _size;//把size_t类型改成int,使之能出现负数的情况,跳出循环
while (end >= (int)pos)//强制类型转换
{
_str[end + 1] = _str[end];
--end;
}
_str[pos] = ch;
_size++;
}
4.整理
string.h
#pragma once
#include<iostream>
using namespace std;
namespace xxj//创建命名空间,防止string关键词冲突
{
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;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const//const修饰this指针,变只读,无法改变对 //象私有成员变量
{
assert(pos < _size);
return _str[pos];
}
string(const char* str = "");//缺省参数,放在声明处,给空默认结尾有\0,直接给也没 //问题
~string();
size_t size() const
{
return _size;
}
const char* c_str()const
{
return _str;
}
void reserve(size_t n);
void push_back(char ch);
void append(const char* str);
string& operator+=(const char* str)
{
append(str);
return *this;
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
void insert(size_t pos, char ch);
private:
char* _str;
size_t _size;
size_t _capacity;
// const static整形可以这么用,特殊处理
const static size_t npos = -1;
// double不支持
// const static double x = 1.1;
}
string.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace bit
{
string::string(const char* str)
:_size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
string::~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
void string::reserve(size_t n)
{
if (n > _capacity)
{
//
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(std::max(_size + len, _capacity * 2));
}
strcpy(_str + _size, str);
_size += len;
}
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
int end = _size;
while (end >= (int)pos)
{
_str[end + 1] = _str[end];
--end;
}
_str[pos] = ch;
_size++;
}
//const size_t string::npos = -1;
}
Test.cpp
#include"string.h"
namespace bit
{
void test_string1()
{
bit::string s1;
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
s2[0] = 'x';
for (size_t i = 0; i < s2.size(); i++)
{
s2[i]++;
}
cout << s2.c_str() << endl;
string s3 = "hello world"; // 隐式类型转换,构造+拷贝构造->优化为构造
string s4("hello world");
const string s5("hello world");
for (size_t i = 0; i < s2.size(); i++)
{
cout << s5[i] << "-";
}
cout << endl;
for (auto ch : s4)
{
cout << ch << " ";
}
cout << endl;
string::iterator it4 = s4.begin();
while (it4 != s4.end())
{
*it4 += 1;
cout << *it4 << " ";
++it4;
}
cout << endl;
for (auto ch : s5)
{
cout << ch << " ";
}
cout << endl;
string::const_iterator it5 = s5.begin();
while (it5 != s5.end())
{
// *it5 += 1;
cout << *it5 << " ";
++it5;
}
cout << endl;
}
void test_string2()
{
bit::string s1;
cout << s1.c_str() << endl;
s1.push_back('x');
s1.push_back('x');
s1.push_back('x');
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
s2.push_back('x');
s2.push_back('y');
s2.push_back('z');
cout << s2.c_str() << endl;
string s3("hello");
s3.append("xxxxxxxxxxxxxxxxxxxxxxxxx");
cout << s3.c_str() << endl;
string s4("hello");
s4.append("xx");
s4.append("xx");
cout << s4.c_str() << endl;
s4 += '*';
s4 += "hello bit";
cout << s4.c_str() << endl;
}
void test_string3()
{
string s1("hello world");
cout << s1.c_str() << endl;
s1.insert(5, 'x');
cout << s1.c_str() << endl;
s1.insert(0, 'x');
cout << s1.c_str() << endl;
}
}
int main()
{
try
{
bit::test_string3();
//cout << typeid(bit::string::iterator).name() << endl;
//cout << typeid(std::string::iterator).name() << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
更多推荐

所有评论(0)