C语言中,字符串是以
‘\0’结尾的一些字符的集合,
为了操作方便,C标准库提供了一些str系列的函数,但是这些库函数是与字符串分开的,不符合OOP思想,而且底层空间需要用户自己管理,稍不留神就会发生越界访问。
string是一个类,对于底层空间自己管理,不会发生越界访问。string使用起来方便、快捷、简单。
函数名称 | 功能说明 |
---|---|
string() (重点) |
构造空的string类对象,即空字符串 |
string(const char* s) (重点) |
用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) |
拷贝构造函数 |
函数名称 | 功能说明 |
---|---|
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的底层空间总大小时,reserve不会改变容量大小。
函数名称 | 功能说明 |
---|---|
operator[] (重 点) | 返回pos位置的字符,const string类对象调用 |
begin+ end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器 |
rbegin + rend | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
函数名称 | 功能说明 |
---|---|
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= (重点) | 在字符串后追加字符串str |
c_str(重点) | 返回C格式字符串 |
find + npos(重点) | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
对string的操作时,如果能够大概预估到放多少字符,可以先通过
reserve
把空间预留好。这样可以有效的避免开辟多次重新开辟空间,拷贝数据时消耗的时间。
函数 | 功能说明 |
---|---|
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> (重点) | 输入运算符重载 |
operator<< (重点) | 输出运算符重载 |
getline (重点) | 获取一行字符串 |
下面我们来实现一个简单的string要求能够正确的实现内存管理,要求实现
string类的构造、拷贝构造、赋值运算符重载以及析构函数。
需要注意的时,string类中包含了指针,存在深浅拷贝的问题,有关深浅拷贝的文章参考我另外一篇博客:C++入门–构造函数、拷贝构造函数、析构函数
class string{
public:
string(const char *str="")
{
if(str==nullptr)
{
assert(false);
return;
}
_str=new char[strlen(str)+1];
strcpy(_str,str._str);
}
string(const string& s)
:_str(new char[strlen(s._str)+1])
{
strcpy(_str,s._str);
}
string& operator=(const string& s)
{
if(this!=&s) //判断是否自己给自己赋值
{
delete[] _str; //删除_str原有的空间
_str=new char[strlen(s._str)+1];
strcpy(_str,s._str);
}
return *this;
}
~string()
{
delete[] _str;
_str=nullptr;
}
private:
char *_str;
}
class string{
public:
string(const char* str=“”)
{
if(str==nullptr)
{
str="";
}
_str=new char[strlen(str)+1];
strcpy(_str,str);
}
string(const string& s)
:_str(nullptr);
{
string tempStr(s._str);//通过构造函数去生成临时变量,然后让_str和临时变量的_str交换,然后出函数作用域后临时变量析构,析构nullptr合法。
swap(tempStr._str,_str);
}
string& operator=(string s) //传值生成临时变量,
{
swap(s._str,_str);
return *this;
}
~string()
{
delete[] _str;
_str=nullptr;
}
private:
char *_str;
}
要实现string类的模拟实现,我们来对string类分解成一个一个的小模块。
namespace Mystring{
class string{
public:
void swap(string& s){
std::swap(_str,s._str);
std::swap(_size,s._size);
std::swap(_capacity,s._capacity);
}
//构造函数
string(const char* str){
_size=strlen(str);
_capacity=_size;
_str=new char[_capacity+1];
strcpy(_str,str);
}
//拷贝构造
string(const string& s)
:_str(nullptr),_size(0),_capacity(0)
{
string temp(s._str);//构造一个临时对象,然后和临时对象交换,出作用域临时对象析构。
swap(temp);
}
//赋值运算符重载使用现代写法
string& operator(string s){
swap(s);
return *this;
}
//析构函数
~string(){
delete[] _str;
_str=nullptr;
}
private:
char *_str;
size_t _size;
size_t _capacity;
public:
static const size_t npos;
}
//标记结尾
const size_t string::npos=-1;
}
namespace Mystring{
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 i){
assert(i<_size);
return _str[i];
}
const char& operator[](size_t i) const{
assert(i<_size);
return _str[i];
}
private:
char *_str;
size_t _size;
size_t _capacity;
public:
static const size_t npos;
}
//标记结尾
const size_t string::npos=-1;
}
namespace Mystring{
class string{
public:
//If n is greater than the current string capacity, the function causes the container to increase its capacity to n characters (or greater).当n>_capacity时,将_capacity的大小扩大为n
void reserve(size_t n){
if(n>_capacity){
char *temp=new char[n+1]; //还有一个标志位'\0';所以要多申请一个空间
strcpy(temp,_str);
delete[] _str;//释放原来的空间
_str=temp;
_capacity=n;
}
}
//放n<_size时,将_size改为n,当n>_size时,将索引为[_size,n-1]区间字符改为ch
void resize(size_t n,char ch='\0'){
if(n<_size){
_str[n]='\0';
_size=n;
}else{
if(n>_capacity){
//当n>_capacity时需要扩容
reserve(n);
}
for(size_t i=_size;i<n;++i){
_str[i]=ch;
}
_str[n]='\0';
_size=n;
}
}
//插入一个字符
void insert(size_t pos,char ch){
assert(pos<=_size);//当pos==_size时即插入到最后
if(pos==_capacity){
size_t newcapacity = _capacity == 0 ? 8 : _capacity * 2;//当字符串为空时,给默认长度为8,当不为空时,_capacity扩大为原来的2倍
reserve(newcapacity);
}
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){
assert(pos<=_size);//当pos==_size时即插入到最后
size_t len = strlen(str);
if (len + _size > _capacity) {
reserve(len+_size);
}
size_t end = len + _size;
while (end > pos + len) {
_str[end] = _str[end - len];
--end;
}
_str[end] = _str[end - len];
strncpy(_str+pos,str,len);
_size += len;
}
private:
char *_str;
size_t _size;
size_t _capacity;
public:
static const size_t npos;
}
//标记结尾
const size_t string::npos=-1;
}
namespace Mystring{
class string{
public:
//从pos下标开始删除长度为len的字符串
void 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;
}
}
//查找字符
size_t find(char ch,size_t pos=0) {
for (size_t i = pos; i < _size; ++i) {
if (_str[i] == ch) {
return i;
}
}
return npos;
}
//查找字符串
size_t find(const char* sub,size_t pos=0) {
const char* ret = strstr(_str+pos,sub);
if (ret == nullptr) {
return npos;
}
else {
return ret - _str;
}
}
private:
char *_str;
size_t _size;
size_t _capacity;
public:
static const size_t npos;
}
//标记结尾
const size_t string::npos=-1;
}
namespace Mystring{
class string{
public:
void push_back(char ch) {
insert(_size,ch);
}
void append(const char* str) {
insert(_size, str);
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
string& operator+=(const string& s)
{
append(s._str);
return *this;
}
const char* c_str() const {
return _str;
}
size_t size() const {
return _size;
}
private:
char *_str;
size_t _size;
size_t _capacity;
public:
static const size_t npos;
}
//标记结尾
const size_t string::npos=-1;
}
//注意:operator<<不一定是类的友元函数
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.reserve(0);
char ch;
while (1) {
in.get(ch);
if (ch == '\n' || ch == ' ') break;
else s += ch;
}
return in;
}
bool operator<(const string& s1,const string& s2) {
size_t i1 = 0, i2 = 0;
while (i1 < s1.size() && i2 < s2.size()) {
if (s1[i1] < s2[i2]) {
return true;
}
else if (s1[i1] > s2[i2]) {
return false;
}
++i1, ++i2;
}
return s1.size() < s2.size();
}
bool operator==(const string& s1, const string& s2) {
if (s1.size() != s2.size()) return false;
size_t i = 0;;
while (i < s1.size()) {
if (s1[i] != s2[i]) {
return false;
}
++i;
}
return true;
}
bool operator!=(const string& s1, const string& s2) {
return !(s1==s2);
}
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);
}