欢迎光临
我们一直在努力

深入理解string:通过模拟实现探讨其内部机制

一、auto和范围for

. auto

在早期C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来C++11中,为auto赋予了全新的含义:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。

需要注意的是,用auto声明指针类型时,右边可以给指针类型,也可以给其它类型,但如果用auto*,就只能给指针类型。

在这里插入图片描述
在这里插入图片描述

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其它变量。

在这里插入图片描述
在这里插入图片描述

auto不能用来定义数组类型。

在这里插入图片描述
在这里插入图片描述

从C++14,auto可以作为函数返回类型,C++20开始,auto可以作为函数参数类型。

. 范围for

C++11中引入了基于范围的 for 循环,for循环后的括号由冒号分为两部分,第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围(自动迭代,自动取数据,自动判断结束)。

范围 for 可以作用到数组和容器对象上进行遍历。

范围 for 的底层其实就是替换为迭代器。

在这里插入图片描述

二、String的模拟实现

1. string的成员变量

class string
{
private:
//声明
char* _str;
size_t _size;//字符串存储字符的个数
size_t _capacity;//字符串的容量
static size_t npos;
};

2. 构造函数

size_t string::npos = 1;//后面要用到
string::string()
:_str(new char[1])
, _size(0)
, _capacity(0)
{
_str[0] = '\\0';//初始化
}

3. reserve

void string::reserve(size_t n)
{
if (n > _capacity)
{
cout << "reserve()" << n << endl;
char* tmp = new char[n + 1];//开辟新空间
strcpy(tmp, _str);//拷贝数据
delete[] _str;//释放旧空间
_str = tmp;//指向新空间
_capacity = n;//更新容量
}
}

在这里插入图片描述

异地扩容,开辟新空间,将旧空间的内容拷贝到新空间里,释放旧空间,让指针指向新空间,更新容量的大小。

也有可能是原地扩容,但大概率都是异地扩容,这里不考虑原地扩容。

4. push_back

void string::push_back(char c)
{
if (_size == _capacity)
{
//扩容
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = c;
_size++;
_str[_size] = '\\0';
}

首先检查是否还有容量存储数据,若没有就需要扩容,由于构造时初始容量为0,所以,需要判断,如果容量为0,我们就开4个空间,否则,开2倍的空间。此时添加数据,但是数组空间最后一个元素是\\0,所以添加完数据之后,先更新_size,再在数组最后添加\\0字符。

5. append

string& string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
//扩容
if (_size + len > 2 * _capacity)
{
reserve(_size + len);
}
else
{
reserve(2 * _capacity);
}
}
strcpy(_str + _size, str);
_size += len;
return *this;
}

append函数添加一个字符串,如果还有存储空间,就直接存储,若存储空间不够,就需要开辟空间。那么,问题来了,我们需要开辟多大的空间呢?直接开2倍的空间,字符串太长怎么办,难道需要不断开辟新空间吗?那直接开辟一块很大的空间呢?字符串太短怎么办,浪费了许多空间。

所以,我们可以先求出字符串的长度,如果存储空间足够,直接存储就可以了,否则,先判断开2倍的空间是否足够,不够就直接开辟_size + len 个字符空间,反之,开辟2倍的空间就足够了。

最后更新_size的值。

6. insert

string& string::insert(size_t pos, size_t n, char c)
{
assert(pos >= 0 && pos <= _size);
assert(n > 0);
if (_size + n > _capacity)
{
//扩容
if (_size + n > 2 * _capacity)
{
reserve(_size + n);
}
else
{
reserve(2 * _capacity);
}
}
size_t end = _size + n;
while (end > pos + n 1)
{
_str[end] = _str[end n];
end;
}
for (size_t i = 0; i < n; ++i)
{
_str[pos + i] = c;
}
_size += n;
return *this;
}

首先判断要插入的字符的个数是否大于0,字符要插入的位置是否合法。

接着判断存储空间是否足够存储 n 个字符,不够就考虑开2倍的空间还是直接开 _size + n 个字符的空间,原因同append函数,反之足够的话就开始插入字符。

在这里插入图片描述

紧接着就挪动数据,为插入数据预留位置空间。

string& string::insert(size_t pos, const char* str)
{
assert(pos >= 0 && pos <= _size);
size_t len = strlen(str);
insert(pos, len, 'x');
for (size_t i = 0; i < len; ++i)
{
_str[pos + i] = str[i];
}
return *this;
}

插入一个字符串,可以复用插入 n 个字符的操作,先随便插入一个指定的字符,然后在从 pos 位置开始覆盖。

7. erase

string& string::erase(size_t pos, size_t len)
{
assert(pos >= 0 && pos <= _size);
assert(len > 0);
//剩余字符的个数
size_t leftSize = _size pos;
//pos位置及之后的数据删完了
if (len >= leftSize)
{
len = leftSize;
_str[pos] = '\\0';
}
else
{
size_t start = pos + len;
while (start <= _size)
{
_str[pos++] = _str[start++];
}
}
_size -= len;
return *this;
}

分为两种情况:第一种删除字符的个数大于等于 pos 位置开始剩余的字符个数,从 pos 位置全部删除,有一种简便操作就是把 pos 位置的字符设置为 \\0 字符。

否则,就要挪动数据,更新 _size的值。

8. find

size_t string::find(const char* s, size_t pos)
{
assert(pos >= 0 && pos < _size);
char* ptr = strstr(this->_str + pos, s);
if (ptr == nullptr)
{
return npos;
}
return ptr _str;
}

查找一个字符串,我们可以使用C语言库的 strstr 库函数查找,成功的话,会返回字符串在string对象中第一次匹配成功位置的指针。要得到匹配成功的下标,只需要让 ptr – _str就可以了。

size_t string::find(char c, size_t pos)
{
assert(pos < _size);
for (int i = pos; i < _size; ++i)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}

查找一个字符,只需要从 pos 位置开始遍历数组,判断是否有与字符 c 相等的字符就可以了。

9. substr

string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
size_t leftSize = _size pos;
if (len >= leftSize)
{
len = leftSize;
}
string tmp;
tmp.reserve(len);
for (size_t i = 0; i < len; ++i)
{
tmp += _str[pos + i];
}
//tmp是一个局部变量,出了作用域便会销毁
//这里需要一个拷贝构造函数(深拷贝)
//浅拷贝会存在多次析构的问题以及间接修改对象
return tmp;
}

如果从pos位置开始提取的 len 个字符大于剩余字符的个数,那么就将剩余字符全部提取。我们可以调用reserve函数,提前开辟好空间,这样就不用频繁申请空间。

但是,这里需要注意的一个点是 tmp 是一个局部对象,出了作用域便会销毁,构建一个临时对象去拷贝构造另一个 string 对象。由于未显示实现拷贝构造函数,所以拷贝构造函数的默认行为是浅拷贝。

这就会导致出现问题,两个string对象的_str指向了同一块空间,那么在析构时,就会将同一块空间进行多次释放,这不就造成野指针问题了吗!所以,我们需要显示实现拷贝构造函数。

10. 拷贝构造函数

//拷贝构造函数
string::string(const string& s)
{
clear();
_str = new char[s.capacity() + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}

开辟一个和拷贝对象一样大小的空间,将拷贝对象的内容拷贝进新开辟的空间内,更新_size和_capacity。

11. clear

void clear()
{
_str[0] = '\\0';
_size = 0;
}

清空数据,直接修改0号下标字符为 \\0字符,更新_size。

12. 赋值运算符重载

//s2=s1;
string& string::operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s.capacity() + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}

赋值运算符默认也是浅拷贝的行为,所以,对于有指向资源的成员是非常危险的行为,因此,我们需要实现一个深拷贝的赋值运算符重载。

为了避免自己给自己赋值,从而导致自己的空间被释放,所以需要进行判断。如果不是自己给自己赋值,那就先释放原来的空间,在开辟一个新的空间,拷贝数据,更新_size和_capacity。

13. operator[]运算符重载

const char& string::operator[](size_t pos)const
{
assert(pos >= 0 && pos < _size);
return _str[pos];
}

底层是数组,直接利用下标访问即可。

14. operator+=运算符重载

string& string::operator+=(char c)
{
push_back(c);
return *this;
}

直接复用push_back操作即可。

string& string::operator+=(const char* str)
{
append(str);
return *this;
}

+=一个字符串,复用append函数。

15. 判断两个string对象之间的大小关系

bool string::operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
bool string::operator!=(const string& s) const
{
return !(_str == s._str);
}
bool string::operator<(const string& s) const
{
return strcmp(_str, s._str) < 0;
}
bool string::operator<=(const string& s) const
{
return !(_str > s._str);
}
bool string::operator>(const string& s) const
{
return strcmp(_str, s._str) > 0;
}
bool string::operator>=(const string& s) const
{
return !(_str < s._str);
}

直接调用C语言中的strcmp函数,比较两个string对象中的字符串即可。

16. getline函数

istream& string::getline(istream& in, string& s, char delim)
{
s.clear();
char ch;
//阻塞
//in >> ch;
ch = in.get();
const int N = 1024;
int i = 0;
//避免空间给大,造成浪费
//空间给小,频繁扩容
char buff[N];
while (ch != delim)
{
buff[i++] = ch;
if (i == N 1)
{
buff[i] = '\\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\\0';
s += buff;
}
return in;
}

delim是分割符,get是获取字符的库函数,只要输入的字符串中不包含delim分割符,就要一直输入。为了避免频繁扩容,我们创建了一个char buff 的数组,获取到的字符先存储在字符数组里,如果数组满了,就存储到 string 对象中,将 i 置为0,从头开始继续存储。如果字符串全部读取到数组里,数组未满,就需要判断数组里是否还有内容,有的话就添加到 string 对象中。

17. operator << 运算符重载

ostream& operator<<(ostream& out, const string& str)
{
for (auto e : str)
{
out << e;
}
return out;
}

out是ostream类的对象,直接遍历string对象输出即可。

18. operator >> 运算符重载

istream& operator>>(istream& in, string& s)
{
s.clear();
char ch;
//阻塞
//in >> ch;
ch = in.get();
const int N = 1024;
int i = 0;
//避免空间给大,造成浪费
//空间给小,频繁扩容
char buff[N];
while (ch != ' ' && ch != '\\n')
{
buff[i++] = ch;
if (i == N 1)
{
buff[i] = '\\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\\0';
s += buff;
}
return in;
}

原理和上面的getline函数是一样的,只不过换了一个分割符而已。

19. Iterator

iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}

这里的 iterator 和 const_iterator 是被 typedef 命名过的,实际是 char* 和 const char*。

20. size、capacity、c_str、clear、~string

size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
char* c_str()const
{
return _str;
}
void clear()
{
_str[0] = '\\0';
_size = 0;
}
//析构函数
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}

三、完整代码

. string.h

#pragma once

#include<iostream>
#include<assert.h>
#include<string.h>
#include<string>
using namespace std;

namespace LC
{
class string
{
typedef char* iterator;
typedef const char* const_iterator;
public:
//默认构造函数
string();
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
char* c_str()const
{
return _str;
}
void clear()
{
_str[0] = '\\0';
_size = 0;
}
//拷贝构造函数
string(const string& s);
string& insert(size_t pos, const char* str);
string& insert(size_t pos, size_t n, char c);
void reserve(size_t n);
void push_back(char c);
string& append(const char* str);
string& erase(size_t pos, size_t len = npos);
const char& operator[](size_t pos)const;
string& operator+=(char c);
string& operator+=(const char* str);
size_t find(const char* s, size_t pos = 0);
size_t find(char c, size_t pos = 0);
string substr(size_t pos, size_t len = npos);

char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
bool operator==(const string& s) const;
bool operator!=(const string& s) const;
bool operator<(const string& s) const;
bool operator<=(const string& s) const;
bool operator>(const string& s) const;
bool operator>=(const string& s) const;
string& operator=(const string& s);
istream& getline(istream& is, string& str, char delim);
//析构函数
~string();
private:
//声明
char* _str;
size_t _size;//字符串存储字符的个数
size_t _capacity;//字符串的容量
static size_t npos;
};
ostream& operator<<(ostream& out, const string& str);
istream& operator>>(istream& in, string& str);
}

. string.cc

#define _CRT_SECURE_NO_WARNINGS

#include"string.h"
namespace LC
{
size_t string::npos = 1;
string::string()
:_str(new char[1])
, _size(0)
, _capacity(0)
{
_str[0] = '\\0';
}
//拷贝构造函数
string::string(const string& s)
{
clear();
_str = new char[s.capacity() + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
void string::reserve(size_t n)
{
if (n > _capacity)
{
cout << "reserve()" << n << endl;
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back(char c)
{
if (_size == _capacity)
{
//扩容
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = c;
_size++;
_str[_size] = '\\0';
}
string& string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
//扩容
if (_size + len > 2 * _capacity)
{
reserve(_size + len);
}
else
{
reserve(2 * _capacity);
}
}
strcpy(_str + _size, str);
_size += len;
return *this;
}
string& string::insert(size_t pos, const char* str)
{
assert(pos >= 0 && pos <= _size);
size_t len = strlen(str);
insert(pos, len, 'x');
for (size_t i = 0; i < len; ++i)
{
_str[pos + i] = str[i];
}
return *this;
}
string& string::insert(size_t pos, size_t n, char c)
{
assert(pos >= 0 && pos <= _size);
assert(n > 0);
if (_size + n > _capacity)
{
//扩容
if (_size + n > 2 * _capacity)
{
reserve(_size + n);
}
else
{
reserve(2 * _capacity);
}
}
size_t end = _size + n;
while (end > pos + n 1)
{
_str[end] = _str[end n];
end;
}
for (size_t i = 0; i < n; ++i)
{
_str[pos + i] = c;
}
_size += n;
return *this;
}
ostream& operator<<(ostream& out, const string& str)
{
for (auto e : str)
{
out << e;
}
return out;
}
string& string::erase(size_t pos, size_t len)
{
assert(pos >= 0 && pos <= _size);
assert(len > 0);
//剩余字符的个数
size_t leftSize = _size pos;
//pos位置及之后的数据删完了
if (len >= leftSize)
{
len = leftSize;
_str[pos] = '\\0';
}
else
{
size_t start = pos + len;
while (start <= _size)
{
_str[pos++] = _str[start++];
}
}
_size -= len;
return *this;
}
const char& string::operator[](size_t pos)const
{
assert(pos >= 0 && pos < _size);
return _str[pos];
}
string& string::operator+=(char c)
{
push_back(c);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
size_t string::find(const char* s, size_t pos)
{
assert(pos >= 0 && pos < _size);
char* ptr = strstr(this->_str + pos, s);
if (ptr == nullptr)
{
return npos;
}
return ptr _str;
}
size_t string::find(char c, size_t pos)
{
assert(pos < _size);
for (int i = pos; i < _size; ++i)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
size_t leftSize = _size pos;
if (len >= leftSize)
{
len = leftSize;
}
string tmp;
tmp.reserve(len);
for (size_t i = 0; i < len; ++i)
{
tmp += _str[pos + i];
}
//tmp是一个局部变量,出了作用域便会销毁
//这里需要一个拷贝构造函数(深拷贝)
//浅拷贝会存在多次析构的问题以及间接修改对象
return tmp;
}

bool string::operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
bool string::operator!=(const string& s) const
{
return !(_str == s._str);
}
bool string::operator<(const string& s) const
{
return strcmp(_str, s._str) < 0;
}
bool string::operator<=(const string& s) const
{
return !(_str > s._str);
}
bool string::operator>(const string& s) const
{
return strcmp(_str, s._str) > 0;
}
bool string::operator>=(const string& s) const
{
return !(_str < s._str);
}
//s2=s1;
string& string::operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s.capacity() + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch;
//阻塞
//in >> ch;
ch = in.get();
const int N = 1024;
int i = 0;
//避免空间给大,造成浪费
//空间给小,频繁扩容
char buff[N];
while (ch != ' ' && ch != '\\n')
{
buff[i++] = ch;
if (i == N 1)
{
buff[i] = '\\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\\0';
s += buff;
}
return in;
}
istream& string::getline(istream& in, string& s, char delim)
{
s.clear();
char ch;
//阻塞
//in >> ch;
ch = in.get();
const int N = 1024;
int i = 0;
//避免空间给大,造成浪费
//空间给小,频繁扩容
char buff[N];
while (ch != delim)
{
buff[i++] = ch;
if (i == N 1)
{
buff[i] = '\\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\\0';
s += buff;
}
return in;
}
//析构函数
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
}

. test.cc

#define _CRT_SECURE_NO_WARNINGS

#include"string.h"

void TestString1()
{
LC::string s1;
s1.push_back('h');
s1.push_back('e');
s1.push_back('l');
s1.push_back('l');
s1.push_back('o');
s1.push_back(' ');

s1.append("world");
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;

s1.insert(11, 5, 'z');
cout << s1 << endl;
s1.insert(4, 3, 'y');
cout << s1 << endl;
s1.insert(0, 5, 'x');
cout << s1 << endl;

s1.insert(s1.size(), "welcome");
cout << s1 << endl;
s1.insert(10, "china");
cout << s1 << endl;
s1.insert(0, "to");
cout << s1 << endl;
}

void TestString2()
{
LC::string s1;
s1.push_back('h');
s1.push_back('e');
s1.push_back('l');
s1.push_back('l');
s1.push_back('o');
s1.push_back(' ');

s1.append("world");
s1.erase(11, 5);
cout << s1 << endl;
s1.erase(8, 5);
cout << s1 << endl;
s1.erase(3, 5);
cout << s1 << endl;
s1.erase(0, 2);
cout << s1 << endl;
}
void TestString3()
{
LC::string s1;
s1.push_back('h');
s1.push_back('e');
s1.push_back('l');
s1.push_back('l');
s1.push_back('o');
s1.push_back(' ');

s1.append("world");
cout << s1[3] << endl;
cout << s1[0] << endl;
s1 += ' ';
s1 += "welcome";
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
LC::string s2;
s2 += "china";
cout << s2.c_str() << endl;
cout << s1.find("come", 3) << endl;
cout << s1.find("hello", 2) << endl;
cout << s1.find("hello") << endl;
cout << s1.find("to", 5) << endl;

cout << s1.find('w', 10) << endl;
cout << s1.find('o') << endl;
cout << s1.find('z') << endl;
//编译器进行了优化,隐藏有BUG
//拷贝构造
/*LC::string s3 = s1.substr(3);
cout << s3 << endl;*/

//2022编译器会进行优化,这种写法会打破编译器的优化
LC::string s3;
//赋值重载
s3 = s1.substr(3);
cout << s3 << endl;
}

void TestString4()
{
LC::string s1;
s1.push_back('h');
s1.push_back('e');
s1.push_back('l');
s1.push_back('l');
s1.push_back('o');

LC::string s2;
s2.push_back('w');
s2.push_back('e');
s2.push_back('l');
s2.push_back('c');
s2.push_back('o');
cout << (s1 == s2) << endl;
cout << (s1 != s2) << endl;
cout << (s1 < s2) << endl;
cout << (s1 <= s2) << endl;
cout << (s1 > s2) << endl;
cout << (s1 >= s2) << endl;
LC::string s3;
cin >> s3;
cout << s3 << endl;

LC::string s4;
s4.getline(cin, s4, '#');
cout << s4 << endl;
}
int main()
{
TestString1();
//TestString2();
//TestString3();
//TestString4();
return 0;
}

赞(0)
未经允许不得转载:171主机测评 » 深入理解string:通过模拟实现探讨其内部机制
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址