c++:string类的基本了解以及模拟实现
文章目录
前言
srting类的出现本质上是为了复用性(reusability)的提升,它位于头文件中,用于表示和操作字符串。string类提供了丰富的成员函数和运算符重载,使得字符串的处理变得既简单又高效。
string类虽然没有存在于STL中,但它拥有着STL里面容器的身影,两者相得益彰。所以学习STL的开篇便是从string类当作打开STL世界的钥匙
一、标准库中的string类
1.1 介绍
想要了解string必须知道它的解释以及成员函数。
让我们来看看官方对于string的解释如何:
可以看出它是由basic_string类模板重命名过的:typedef basic_string string;
,这是由其编码方式原因造成的不做过多赘述。
1.2 部分常用成员函数
1.2.1构造函数
以下介绍常用的三种构造函数以及解释应用
1.默认构造:string();
行为解释:Constructs an empty string, with a length of zero characters.
2.拷贝构造:string (const string& str);
行为解释:Constructs a copy of str.
3.字符串构造 :string (const char* s);
行为解释:Copies the null-terminated character sequence (C-string) pointed by s.
上面的特殊单词解释:
str:
Another string object, whose value is either copied or acquired.
s
Pointer to an array of characters (such as a c-string).
1.2.3 容量操作函数
函数名称 | 功能说明 |
---|---|
size | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty | 检测字符串释放为空串,是返回true,否则返回false |
clear | 清空有效字符 |
reserve | 为字符串预留空间 |
resize | 将有效字符的个数改成n个,多出的空间用字符c填充 |
注意:
注意:1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
2. clear()只是将string中有效字符清空,不改变底层空间大小。
3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
1.2.4 对象访问及遍历操作
函数名称 | 功能说明 |
---|---|
operator[pos] | 返回pos位置的字符,const string类对象调用 |
1.2.5 对象修改操作
函数名称 | 功能作用 |
---|---|
operator+= | 在字符串后追加字符串str |
c_str | 返回C格式字符串 |
find + npos | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
注意:注意:
- 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
- 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
1.2.6 非成员函数
函数名称 | 功能作用 |
---|---|
operator>> | 输入运算符重载 |
operator<< | 输出运算符重载 |
二.模拟实现string类
2.1 string类的数据结构
想要实现string类,首先要做的就是对其数据组成进行了解
class string
{
private:
//char buff[16];
char* _str;//存储数据的变量
size_t _capacity;//容量
size_t _size;//大小
};
由此可以看出,string类的数据结构是线性的。
2.2 构造函数的实现
2.2.1默认构造函数:
class string
{
public:
string()
:_str(new char[1]{'/0'})//这里尽量不用nullptr去初始化_str,因为实现是跟库string保存一致,如果用空来初始化那么用cout输出初始化后的字符串时会发生输出崩溃行为
,_capacity(0)
,_size(0)
{}
private:
//char buff[16];
char* _str;//存储数据的变量
size_t _capacity;//容量
size_t _size;//大小
};
2.2.2 带参构造函数
class string
{
public:
string(const char* str)//带参构造函数
{
_size=strlen(str);//这里我们使用库函数来简化操作获取需要构造的字符串长度
_capacity=_size;
//由于_capacity是没将字符串默认结束字符/0包含进去的,所以我们在开辟空间的时候需要自动加1来存储/0
_str=new char[_capacity+1];
strcpy(_str,str);//目标字符串拷贝到新开的空间_str中去
}
//带资源管理的记得写析构函数
~string()
{
delete[] _str;
_str=nullptr;
_size=_capacity=0;
}
private:
//char buff[16];
char* _str;//存储数据的变量
size_t _capacity;//容量
size_t _size;//大小
};
两者可以合并为一个函数
class string
{
public:
string(const char* str="")//只需要使其重载为带缺省值的函数即可
//当我们将前面学到的知识融合到一起来实现的时候,会感到妙不可言
{
_size=strlen(str);
_capacity=_size;
_str=new char[_capacity+1];
strcpy(_str,str);
}
//带资源管理的记得写析构函数
~string()
{
delete[] _str;
_str=nullptr;
_size=_capacity=0;
}
private:
//char buff[16];
char* _str;//存储数据的变量
size_t _capacity;//容量
size_t _size;//大小
};
2.2.3 拷贝构造函数
class string
{
public:
string(const char& str)
{
_str=new char[str._capacity+1];
strcpy(_str,str);
_size=str._size;
_capacity=str.capacity;
}
//带资源管理的记得写析构函数
~string()
{
delete[] _str;
_str=nullptr;
_size=_capacity=0;
}
private:
//char buff[16];
char* _str;//存储数据的变量
size_t _capacity;//容量
size_t _size;//大小
};
2.3 容量操作函数
本人理解下容量操作函数其实就是给外部提供可以访问对象的私有变量数据的接口
以下是代码实现:
class string
{
public:
void reserve(size_t n)//开辟新大小的空间
{
if(n>_size)
{
//开辟新的空间删除旧空间
char* tmp=new char[n+1];
strcpy(tmp,_str);
delete[] _str;
_str=tmp;
_capacity=n;
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
char& operator[](size_t pos)
{
assert(pos<_szie);
return _str[pos];
}
const char& operator[](size_t pos)
{
assert(pos<_szie);
return _str[pos];
}
string operator=(const string& s)
{
delete[] _str;
_str=new char[s._capacity+1];
strcpy(str,s);
_size=s._size;
_capacity=s._capacity;
}
private:
//char buff[16];
char* _str;//存储数据的变量
size_t _capacity;//容量
size_t _size;//大小
};
2.4 迭代器简单实现
这里的迭代器是模拟指针行为来进行操作的
class string
{
public:
typedef char* iterator;//看似神秘的iterator实则也不过是经过重重typedef出来的
iterator begin()
{
reeturn _str;
}
iterator end()
{
return _str+_size;
}
private:
//char buff[16];
char* _str;//存储数据的变量
size_t _capacity;//容量
size_t _size;//大小
};
2.5 数据操作函数
这里的函数实现主要是实现内存管理上的一些操作,主要操作方式反而相对简单
class string
{
public:
void reserve(size_t n)//开辟新大小的空间
{
if(n>_size)
{
//开辟新的空间删除旧空间
char* tmp=new char[n+1];
strcpy(tmp,_str);
delete[] _str;
_str=tmp;
_capacity=n;
}
void push_back(char ch)
{
if(_capacity==_size)
{
reserve(_capacity==0?4:_capacity*2);
}
_str[_size]=ch;
_size++;
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
void append(const char* str)
{
int len=strlen(str);
if(_size+len>_capacity)
{
//这里使用三木操作符的实现是为了保证内存上对齐
reserve((_size+len)>2*_capacity?_size+len:2*_capacity);
}
strcpy(_str+_size,str);
_size+=len;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
private:
//char buff[16];
char* _str;//存储数据的变量
size_t _capacity;//容量
size_t _size;//大小
//staic const size_t npos=-1;//记得声明和定义分离防止编译链接错误
};
指定位置进行插入操作
class string
{
public:
void reserve(size_t n)//开辟新大小的空间
{
if(n>_size)
{
//开辟新的空间删除旧空间
char* tmp=new char[n+1];
strcpy(tmp,_str);
delete[] _str;
_str=tmp;
_capacity=n;
}
void insert(size_t pos,const char ch)
{
assert(pos<_size);
if(_capacity==_size)
{
reserve(_capacity==0?4:_capacity*2);
}
//下面挪动数据主要采用将前一个数据给到后一个,这样可以避免一些无符号整型出现的问题
size_t end=_size+1;
while(end>pos)
{
_str[end]=str[end-1];
--end;
}
_str[pos]=ch;
_size++;
}
void insert(size_t pos,const char* str)
{
int len=strlen(str);
if(_size+len>_capacity)
{
//这里使用三木操作符的实现是为了保证内存上对齐
reserve((_size+len)>2*_capacity?_size+len:2*_capacity);
}
size_t end=_size+len;
while(end > pos+len-1)
{
_str[end]=_str[end-len];
end--;
}
for(size_t i=0;i<len;i++)
{
_str[pos+i]=str[i];
}
_size+=len;
}
private:
//char buff[16];
char* _str;//存储数据的变量
size_t _capacity;//容量
size_t _size;//大小
};
查找函数
class string
{
public:
void find(char ch){
for(size_t i=0;i<_size;i++)
{
if(_str[i]==ch)
{
return i
}
}
private:
//char buff[16];
char* _str;//存储数据的变量
size_t _capacity;//容量
size_t _size;//大小
};
返回指定位置之后指定大小的子串
class string
{
public:
string& substr(size_t pos,size_t len)
{
assert(pos<_size);
if(len>_size-pos)
{
len=_size-pos;
}
string sub;
sub.reserve(len);
for(size_t i=0;i<len;i++)
{
sub+=_str[pos+i];
}
return sub;
}
private:
//char buff[16];
char* _str;//存储数据的变量
size_t _capacity;//容量
size_t _size;//大小
};
2.6 流插入和流提取operator重载
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
const int N = 256;
char buff[N];
int i = 0;
char ch;
//in >> ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == N-1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
//in >> ch;
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
}
总结
以上便是部分string类函数模拟实现,其中仍有诸多不足。