大家好我是沐曦希💕
文章目录
一.前言
注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节.。
1.VS下string的结构
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:
- 当字符串长度小于16时,使用内部固定的字符数组来存放
- 当字符串长度大于等于16时,从堆上开辟空间
union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量
最后:还有一个指针做一些其他事情。
故总共占16+4+4+4=28个字节。
2.g++下string的结构
g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
- 空间总大小
- 字符串有效长度
- 引用计数
- 指向堆空间的指针,用来存储字符串。
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};
二、string基本框架
1.构造函数
string的构造函数分为无参和带参数,无参需要给一个缺省值“”。
string(const char* str = "")
{
// 构造String类对象时,如果传递nullptr指针,可以认为程序非法
if (nullptr == str)
{
assert(false);
return;
}
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
这里的capacity取值是字符串的有效个数,不算’\0’,所以在进行扩容操作时候需要多扩容一个字节位置。
2.拷贝构造
因为拷贝构造函数和赋值函数都是默认构造函数,所以我们不写,编译器会自动生成。对于内置类型完成浅拷贝,对于自定义类型调用拷贝构造。
对于string类型来说,如果不写拷贝构造会导致浅拷贝问题(只完成值拷贝)
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
所以一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。
- 传统写法
string(const string& str)
{
_size = str._size;
_capacity = str._capacity;
_str = new char[_capacity + 1];
strcpy(_str, str._str);
}
- 现代写法
通过构造出tmp,然后把tmp和s2进行交换(swap)
注意:我们需要把s2的_str置为nullptr,如果不置为空,tmp会变成随机值,tmp是局部变量出作用域时会调用析构函数,会导致崩溃
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);
swap(tmp);//this->swap(tmp)
}
对于现代手法的swap有两个:标准库有一个swap,string也有一个swap。
swap(s1,s2);
s1.swap(s2);
对于第一个swap交换代价比较大,需要三次深拷贝(拷贝+赋值+赋值),造成空间损耗,所以我们可以提供一个成员函数swap交换string,直接交换,swap中的swap要指定作用域std::,否则需要从局部找,再去全局找,发现参数不匹配。
3.赋值重载
默认生成的赋值重载也会导致浅拷贝,所以我们需要实现深拷贝。同时,对于赋值重载,我们不要直接去进行销毁,有可能自己给自己赋值,导致自身进行销毁。最好利用tmp来进行赋值
- 传统写法
string& operator=(const string& str)
{
if (this != &str)
{
char* tmp = new char[str._capacity + 1];
strcpy(tmp, str._str);
delete[] _str;
_str = tmp;
_size = str._size;
_capacity = str._capacity;
}
return *this;
}
- 现代写法
string& operator=(const string& str)
{
if (this != &str)
{
string tmp(str);
swap(tmp);
}
return *this;
}
另外一种写法:直接传值传参
string& operator=(string str)
{
swap(str);
return *this;
}
4.析构函数
需要用到delete[]放空间
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
三、常用接口
1.迭代器和范围for
- 迭代器
迭代器有普通迭代器以及const修饰的迭代器,所以我们可以实现两种不同的迭代器,其中,const迭代器可读不可写。
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
- 范围for
范围fro的底层原理就是迭代器,实现范围for后我们可以直接使用:
void test_string1()
{
string s1("hello world");
string::iterator it = s1.begin();
while (it < s1.end())
{
cout << *it;
it++;
}
cout << endl;
for (auto e : s1)
{
cout << e;
}
cout << endl;
}
2.[]重载
- 普通[]重载
适于普通对象,可读可写
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
- const[]重载
适于const对象,只可读,不可写
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
3.c_str
const char* c_str() const
{
return _str;
}
四、容量操作函数
1.size和capacity函数
size_t size()
{
return _size;
}
size_t capacity()
{
return _capacity;
}
2.empty函数
用于判断string类对象是否为空
bool empty()const
{
return _size == 0;
}
3.reserve函数
在已知开多少空间是调用,避免频繁扩容,具体实现要开辟新的空间,在进行拷贝,对旧空间进行释放。
只有n大于原来的_capacity时候,才进行扩容,不进行缩容。
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
4.resize函数
resize需要分情况:
1.元素个数大于容量,需要扩容,多出来的用’\0’(默认情况下)来进行填充
2.元素个数小于原有的,需要删除
void resize(size_t n, char c = '\0')
{
if (n > _size)
{
reserve(n);
size_t i = 0;
for (i = _size; i < n; ++i)
{
_str[i] = c;
}
_size += n;
_str[_size] = '\0';
}
else
{
_str[n] = '\0';
_size = n;
}
}
五、修改操作函数
1.push_back
尾插一个字符,我们需要考虑扩容问题,我们需要判断capacity是否为0的情况,同时,尾插之后’\0’要重新处理。
void push_back(const char ch = '\0')
{
if (_capacity == _size)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 4;
reserve(newCapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
2.append
尾插字符串,这里开多少空间取决于插入字符串的长度。
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
3.+=重载
可以复用push_back和append来实现
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
4.insert
- 插入字符
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_capacity == _size)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 4;
reserve(newCapacity);
}
//挪动数据
size_t end = _size;
while (end >= pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
return *this;
}
可以看到当pos是0时候,进行插入,–end会变成-1,进程崩溃,(但是不要忽略了,end的类型是size_t,怎么可能是-1。可以把end改成int类型,但是实际上这样子会发生隐式类型提升,范围小往大的提升,也就是int会提升为size_t,还是没解决问题)
- 1.强转
- 2.去掉=
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_capacity == _size)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 4;
reserve(newCapacity);
}
//挪动数据
size_t end = _size;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
return *this;
}
- 插入字符串
要把插入的字符串拷贝过来,但是不要把’\0’顺便拷贝过来,所以不要用strcpy而是要用strncpy,并且要注意检查是否越界。
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
//挪动数据
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
_size += len;
return *this;
}
六、删除
1.clear
清空string类对象,但不改变string类对象的_capacity
void clear()
{
_str[0] = '\0';
_size = 0;
}
2.erase
1.如果len太长,直接把pos之后的删除即可
2.只需要删除部分,挪动数据
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
//挪动数据
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
七、查找
从pos处开始查找字符或者字符串,找到返回下标值,没找到则返回npos,可以调用C接口strstr函数
size_t find(const char ch, size_t pos = 0)
{
assert(pos < _size);
while (pos < _size)
{
if (_str[pos] == ch)
{
return pos;
}
++pos;
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
八、运算符重载
1.流插入<<
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}
<<和c_str()的区别:<<按照size进行打印,跟\0没有关系,而c_str()遇到\0结束。
2.流提取>>
scanf和cin一样,都拿不到’ ‘和’\0’
所以要读取一个一个的字符,我们可以用get函数
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
另外一种:
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[128] = { '\0' };
size_t i = 0;
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
if (i == 127)
{
s += buff;
i = 0;
}
buff[i++] = ch;
ch = in.get();
}
if (i >= 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
九、完整代码
#include<assert.h>
#include<iostream>
#include<string.h>
using namespace std;
namespace lj
{
size_t npos = -1;
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
//基本框架
string(const char* str = "")
{
// 构造String类对象时,如果传递nullptr指针,可以认为程序非法
if (nullptr == str)
{
assert(false);
return;
}
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//传统写法
/*string(const string& str)
{
_size = str._size;
_capacity = str._capacity;
_str = new char[_capacity + 1];
strcpy(_str, str._str);
}*/
//现代写法
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);
swap(tmp);//this->swap(tmp)
}
//传统写法
/*string& operator=(const string& str)
{
if (this != &str)
{
char* tmp = new char[str._capacity + 1];
strcpy(tmp, str._str);
delete[] _str;
_str = tmp;
_size = str._size;
_capacity = str._capacity;
}
return *this;
}*/
//现代写法
string& operator=(const string& str)
{
if (this != &str)
{
string tmp(str);
swap(tmp);
}
return *this;
}
/*string& operator=(string str)
{
swap(str);
return *this;
}*/
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//迭代器
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
{
assert(pos < _size);
return _str[pos];
}
//容器操作函数
size_t size()
{
return _size;
}
size_t capacity()
{
return _capacity;
}
bool empty()const
{
return _size == 0;
}
void resize(size_t n, char c = '\0')
{
if (n > _size)
{
reserve(n);
size_t i = 0;
for (i = _size; i < n; ++i)
{
_str[i] = c;
}
_size += n;
_str[_size] = '\0';
}
else
{
_str[n] = '\0';
_size = n;
}
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
//修改操作函数
void push_back(const char ch = '\0')
{
if (_capacity == _size)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 4;
reserve(newCapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
const char* c_str() const
{
return _str;
}
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_capacity == _size)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 4;
reserve(newCapacity);
}
//挪动数据
size_t end = _size;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
return *this;
}
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
//挪动数据
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
_size += len;
return *this;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
//挪动数据
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
size_t find(const char ch, size_t pos = 0)
{
assert(pos < _size);
while (pos < _size)
{
if (_str[pos] == ch)
{
return pos;
}
++pos;
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[128] = { '\0' };
size_t i = 0;
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
if (i == 127)
{
s += buff;
i = 0;
}
buff[i++] = ch;
ch = in.get();
}
if (i >= 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
/*istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}*/
}