#pragma once
#include<iostream>
using namespace std;
#include<assert.h>
namespace bite
{
class string
{
public:
//迭代器 //像指针 底层不一定是指针
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
//const 版本
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
/* string()
:_str(nullptr)
, _size(0)
,_capacity(0)
{
}*/
//string(char* str) //常量字符串不能直接赋值
// :_str(new char[strlen(str) + 1]) //不是很好,strlen是在运行时,所以不适宜多调用
// , _size(strlen(str))
// ,_capacity(strlen(str))
//{
// strcpy(_str, str);
//}
// string(const char* str="\0"); 可以但是实际不推荐 因为\0的后面还有\0
//string(const char* str=nullptr) 缺省参数,strlen不能初始化为空
const char* c_str() const
{
return _str;
}
//二合一
//构造
string(const char* str="")
:_size(strlen(str))
{
_str = new char[_size+1];
_capacity = _size;
strcpy(_str, str);
}
//拷贝构造s2(s1)
//传统写法
//string(const string &s)
//{
// _str = new char[s._capacity+1];
// strcpy(_str, s._str);
// _size = s._size;
// _capacity = s._capacity;
//}
//
s1=s3
//string& operator=(const string& 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)
{
//调用构造
string stmp(s._str);
//this和tmp进行交换
swap(stmp);
}
//s1==s3
//string& operator=(const string&ss)
//{
// //调用拷贝构造
// string tmp(ss);
// swap(tmp);
// return *this;
//}
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
//析构
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//下标+【】遍历
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//const 构造
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
void resize(size_t n, char ch = '\0')
{
if (n < _size)
{
_str[n] = '\0';
_size = n;
}
else
{
reserve(n);
for (size_t i = _size; i < n; ++i)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
}
void reserve(size_t n)
{
if (n > _capacity)
{
char*tmp = new char[n+1];//多开一个‘\0’
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
//扩容2倍
/* if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';*/
insert(_size,ch);
}
void append(const char* str)
{
扩容
//int len = strlen(str);
//if (_size + len > _capacity)
//{
// reserve(_size + len);
//}
//strcpy(_str + _size, str);
//_size += len;
insert(_size, str);
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
//pos位置插入
void insert(size_t pos, char ch)
{
//等于_size就是尾插
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size+1;
//
//这么写头插时因为end是size_t会发生整形提升
//while (end>=pos)
/*while (end >= pos)
{
_str[end + 1] = _str[end];
--end;
}*/
while (end>pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
}
void 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;
}
strncpy(_str+pos,str,len);
_size += len;
}
//pos位置删除len个字符
void erase(size_t pos,size_t len = npos)
{
assert(pos < _size);
//pos+len len处于接近阈值的时候会出现溢出的风险
//if (len == npos || pos + len >= _size)
//解决溢出风险
if (len == npos || _size-pos <=len)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str+pos,_str+pos+len);
_size -=len;
}
}
void swap(string& s)
{
//就近原则,去全局找
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//查找位置 ,返回下标
size_t find(char ch,size_t pos=0) const
{
assert(pos < _size);
for (int i = pos; i < _size; ++i)
{
if (_str[i]==ch)
{
return i;
}
}
return npos;
}
size_t find(const char*sub, size_t pos = 0) const
{
assert(pos < _size);
const char* p = strstr(_str+pos, sub);
if (pos)
{
return p - _str;
}
else
{
return npos;
}
}
string substr(size_t pos=0, size_t len = npos)
{
string sub;
//if(pos==npos||len>=_size-pos)
if (len >= _size - pos)
{
for (size_t i = pos; i < _size; ++i)
{
sub += _str[i];
}
}
else
{
for (size_t i = pos; i < pos + len; ++i)
{
sub += _str[i];
}
}
return sub;
}
void clear()
{
_size = 0;
_str[_size] = '\0';
}
private:
char* _str;
size_t _size;
size_t _capacity;
public:
//共有的静态成员变量
static const int npos;
};
const int string::npos = -1;
bool operator==(const string& s1, const string& s2)
{
int ret = strcmp(s1.c_str(), s2.c_str());
return ret == 0;
}
bool operator>(const string& s1, const string& s2)
{
int ret = strcmp(s1.c_str(), s2.c_str());
return ret > 0;
}
bool operator>=(const string& s1, const string& s2)
{
return s1 > s2 || s1 == s2;
}
bool operator<(const string & s1, const string & s2)
{
return !(s1 >= s2 );
}
bool operator<=(const string& s1, const string& s2)
{
return !(s1 > s2);
}
void swap(string& x, string& y)
{
x.swap(y);
}
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch;
//会出现死循环的情况 scanf和cin默认空格和换行做分割符无法取到
//in >> ch;
//get遇到一个字符娶一个字符
ch = in.get();
while (ch!=' '&&ch!='\n')
{
s += ch;
ch = in.get();
}
return in;
}
void test_string1()
{
string s1("hello world");
for (size_t i = 0; i < s1.size(); ++i)
{
cout << s1[i] << " ";
}
cout << endl;
string s2("hello worrld");
string::iterator it = s2.begin();
while (it!=s2.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto ch : s2)
{
cout << ch << " ";
}
cout << endl;
s2.push_back('s');
for (auto ch : s2)
{
cout << ch << " ";
}
cout << endl;
s2.append("xxxxx");
for (auto ch : s2)
{
cout << ch << " ";
}
cout << endl;
const string s3("hello world");
for (auto ch : s3)
{
cout << ch << " ";
}
cout << endl;
//迭代器不一定是原生指针,typeid可以查看原生类型
cout << typeid(std::string::iterator).name() << endl;
}
void test_string2()
{
string s1("hello world");
s1 += 'x';
s1 += "ssss";
s1.insert(0,'o');
for (auto ch : s1)
{
cout << ch << " ";
}
cout << endl;
}
void test_string3()
{
string s3("hello world");
for (auto ch : s3)
{
cout << ch << " ";
}
cout << endl;
s3.erase(5, 3);
cout << s3.c_str() << endl;
}
void test_string4()
{
string s1("hello world");
cout << s1.c_str() << endl;
//没实现拷贝构造时
//程序崩溃,析构的时候对同一块空间多次析构
//发生浅拷贝,值拷贝
string s2(s1);
cout << s2.c_str() << endl;
s2.insert(5, "xxxxx");
cout << s2.c_str() << endl;
}
void test_string5()
{
string s1("hello world");
string s2("hello world");
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
//深拷贝代价:三次拷贝加一次析构
//swap(s1, s2);
//s1.swap(s2);
//全局重载
swap(s1, s2);
cout << s1 << endl;
cout << s2 << endl;
cout << (s1 == s2) << endl;
string s4;
string s5;
cin >> s4 >> s5;
cout << s4 << endl;
cout << s5 << endl;
}
}
vs和g++下string结构的说明
注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节
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个字节。
g++下string的结构
G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指 针将来指向一块堆空间,内部包含了如下字段:
- 空间总大小
- 字符串有效长度
- 引用计数 (解决浅拷贝析构多次的问题) 有引用计数以后,先--引用技术 ,如果减完以后,引用技术==0 代表当前是最后一个管理资源的对象,那就释放。
浅拷贝的修改会影响其他对象的问题 :
检查引用计数,如果等于1,说明资源是自己独占的,不用拷贝
如果大于1,先拷贝再写,这就是写时拷贝反正都要拷贝,意义是什么?
不是每个对象都会修改,拷贝后不修改就赚
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};
指向堆空间的指针,用来存储字符串。
写时拷贝
写时拷贝在读取时的缺陷
扩展阅读
面试中string的一种正确写法
STL中的string怎么了