> 文章列表 > STL--string

STL--string

STL--string

一、string介绍

string是表示字符序列的对象。

标准字符串类通过类似于标准字节容器的接口为此类对象提供支持,但添加了专门设计用于处理单字节字符字符串的功能。

字符串类是 basic_string 类模板的实例化,该模板使用char作为其字符类型,以及默认 char_traits和 allocator 类型

C语言中,字符串是以'\\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数, 但是这些库函数与字符串是分离开的,不太符合OOP(封装,继承,多态)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问

二、string接口

详情可见string - C++ Reference (cplusplus.com)https://legacy.cplusplus.com/reference/string/string/?kw=string

1.string常见构造

函数名称 功能说明
string() 构造空的string类对象,即空字符串
string(const char* s) 用C-string来构造string类对象
string(size_t n, char c) string类对象中包含n个字符c
string(const string&s) 拷贝构造函数
void test()
{	string s1;string s2("hello world\\n");//string()string s3 = "heeeeeeelo\\n";//先构造后拷贝构造-》直接构造  深拷贝string s4(s2, 6, 8);//string (const string& str, size_t pos, size_t len = npos);
}

2.string容量操作

函数名称 功能说明
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不会改变容量大小。

void size_func()//string容量操作
{cout << "size_func" << endl;string sx = "this is string function";cout << "sx.size:" << sx.size() << endl;;cout<<"sx.length:"<<sx.length()<<endl;cout << "sx.max_size():" << sx.max_size() << endl;cout << "sx.capacity():" << sx.capacity() << endl;sx.reserve(100);//只改变capacity,不改变sizecout << "sx.reseve(100)后sx.capacity:" << sx.capacity() << endl;cout << "sx.reseve(100)后sx.size():" << sx.size() << endl;sx.resize(150);//同时改变capacity和sizecout << "sx.resize(150)后sx.capacity:" << sx.capacity() << endl;cout << "sx.resize(150)后sx.size():" << sx.size() << endl;//size大小更改之后默认将空位置填充"\\0"cout << endl;
}

3.string访问与遍历

函数名称 功能说明
operator[] 返回pos位置的字符,const string类对象调用
begin+ end begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器
rbegin + rend begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器
范围for C++11支持更简洁的范围for的新遍历方式
void string_iterator()//string迭代器
{cout << "string_iterator" << endl;string s1("hello world");string::iterator it = s1.begin();while (it != s1.end())//end指向最后一个数据的下一个位置{cout << *it << " ";++it;}cout << endl;string::reverse_iterator rit = s1.rbegin();//反向迭代器while (rit != s1.rend()){cout << *rit << " ";rit++;//相当于镜像后从前往后,因此是++}cout << endl;cout << "范围for实现" << endl;//底层仍然是迭代器,本质上是一样的for (auto ch : s1){cout << ch << " ";}cout << endl;
}

4.string修改操作

函数名称 功能说明
push_back 在字符串后尾插字符c
append 在字符串后追加一个字符串
operator+= 在字符串后追加字符串str
c_str 返回C格式字符串
find + npos 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr 在str中从pos位置开始,截取n个字符,然后将其返回

1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好

void change_func()//string修改操作
{cout << "change_func" << endl;string sx = "change_func";sx.push_back('!');cout << "sx.pushback('!'):	" << sx << endl;;sx += "xxx";cout << "sx+=xxx:	" << sx << endl;cout << endl;
}

5.string非类成员对象

函数名称 功能说明
operator+ 尽量少用,因为传值返回,导致深拷贝效率低
operator>> 输入运算符重载
operator<< 输出运算符重载
getline 获取一行字符串
relational operators 大小比较

三、string模拟实现

1.string.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS#include<assert.h>
#include<iostream>
using std::cout;
using std::endl;
using std::ostream;
using std::istream;
namespace my_string
{class string{public:typedef char* iterator;typedef const char* const_iterator;//string()//构造//	:_str(new char[1]), _size(0), _capacity(0)//这里new使用[]是为了适应更多情况,避免析构错误//{//	_str[0] = '\\0';//}//可以将无参构造和拷贝构造合并成一个缺省实现的拷贝构造//但是如下写法是错误的,strlen会读取到"\\0"位置,如果置为空,则strlen报错//string(const char* str=nullptr)//string(const char* str='\\0')//string(const char* str="\\0")//可以string(const char* str = "")//全缺省构造,常量字符串默认"\\0": _size(strlen(str)){_capacity = _size == 0 ? 3 : _size;//保障_capacity不为零_str = new char[_capacity + 1];strcpy(_str, str);//使用strcpy是因为_str是一个字符指针}string(const string& s)//拷贝构造,实现深拷贝,避免重复析构:_size(s._size), _capacity(s._capacity){_str = new char[s._capacity + 1];strcpy(_str, s._str);}~string()//析构{delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}//迭代器iterator begin();iterator end();const_iterator begin()const;const_iterator end()const;//运算符重载string& operator=(const string& s);//赋值运算符重载char& operator[](size_t pos);//下标运算符重载const char& operator[](size_t pos)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;bool operator!=(const string& s)const;string& operator+=(const char* str);string& operator+=(char ch);//由于ostream类型已经在iostream中实现,所以不能作为ostream类的成员函数重载,只能作为全局函数或友元函数重载friend ostream& operator<<(ostream& out, const string& s);//流插入friend istream& operator>>(istream& in,string& s);//流提取//成员函数const char* c_str();//输出,c_str 的 c 有两个意思,一个是: const,不能写;一个是 C style,保证null结尾。size_t size()const;//输出字符串大小,常函数size_t capacity()const;//输出字符串容量,常函数void resize(size_t n,char ch='\\0');//开空间并初始化void reserve(size_t n);//重新分配空间void append(const char* str);//尾插字符串void push_back(char ch);//尾插字符size_t find(char ch,size_t pos=0);//查字符size_t find(const char* str, size_t pos);//查字符串void insert(size_t pos,char ch);//插入字符void insert(size_t pos, const char* str);//插入字符串void erase(size_t pos, size_t len = npos);//删除void swap(string& s1);void clear();//清除private:char* _str;//这里的_str不能加const,因为需要方便扩容size_t _size;//当前存放容量size_t _capacity;//有效字符容量static const size_t npos = -1;};void test1(){string  s1;string s2("jello world");cout << s1.c_str() << endl;cout << s2.c_str() << endl;}void Print(const string& s){for (size_t i = 0; i < s.size(); i++)//这里不能使用s.size(),不能跨类调用{cout << s[i] << " ";}cout << endl;string::const_iterator it = s.begin();while (it != s.end()){cout << *it << " ";it++;}cout << endl;}void test_2(){string s1;s1.resize(10, 'z');cout << s1.c_str() << endl;s1.resize(50, 'r');cout << s1.c_str() << endl;cout << s1.find('r') << endl;cout << s1.find('x') << endl;std::string s2("hello world");cout << s2.find('x') << endl;}void test_3(){string s1;s1.resize(10, 'r');cout << s1.c_str() << endl;s1.erase(0, 5);cout << s1.c_str() << endl;}
}

2.string模拟实现.cpp

#include"string.h"
using namespace my_string;//迭代器
string::iterator string::begin()
{return _str;
}string::iterator string::end()
{return _str + _size;
}string::const_iterator string::begin()const
{return _str;
}string::const_iterator string::end()const
{return _str+_size;
}//运算符重载
string& string::operator=(const string& s)
{if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(_str, s._str);//拷贝数据delete[] _str;//释放原有空间_str = tmp;_capacity = s._capacity;_size = s._size;//下面这种方式也可以,但是如果new失败了,由于delete,原有数据被破坏//delete[] _str;//_str = new char[s._capacity = 1];//strcpy(_str, s._str);//_size = s._size;//_capacity = s._capacity;}return *this;
}char& string::operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}const char& string::operator[](size_t pos)const
{assert(pos < _size);return _str[pos];
}
bool string::operator>(const string& s)const
{return strcmp(_str, s._str) > 0;
}
bool string::operator<(const string& s)const
{return strcmp(_str, s._str) < 0;
}
bool string::operator==(const string& s)const//变成常成员函数,是为了常成员变量调用
{return strcmp(_str,s._str) == 0;
}
bool string::operator>=(const string& s)const
{return *this > s || *this == s;//当==是常成员函数时,可以实现s==*this
}
bool string::operator<=(const string& s)const
{return* this < s || *this == s;
}
bool string::operator!=(const string& s)const
{return!(*this == s);
}
string& string::operator+=(const char* str)
{append(str);return *this;
}
string& string::operator+=(char ch)
{push_back(ch);return *this;
}
ostream& my_string::operator<<(ostream& out, const string& s)
{for (auto ch : s){out << ch;}return out;
}istream& my_string::operator>>(istream& in,string& s)
{s.clear();//先将s清空//c语言与c++并不是同一个缓冲区,因此不能使用getchar()char ch = in.get();char buff[128];//目的是一段一段的开辟空间,避免空间浪费,函数结束后会销毁buffsize_t i = 0;while (ch != ' ' && ch != '\\n'){buff[i++] = ch;if (i == 127){buff[127] = '\\0';s += buff;//+=内部为append,实际上是双倍扩容,容易开辟过剩空间i = 0;}ch = in.get();}if (i != 0){buff[i] = '\\0';s += buff;}return in;
}
//成员函数
const char* string::c_str()
{return _str;
}size_t string::size()const
{return _size;
}size_t string::capacity()const
{return _capacity;
}void string::resize(size_t n, char ch)
{if (n < _size)//相当于删除数据{_size = n;_str[_size] = '\\0';}else{if (n > _capacity){reserve(n);}for (int i = _size; i < n; i++){_str[i] = ch;}_size = n;_str[_size] = '\\0';}
}void string::reserve(size_t n)
{assert(n > _capacity);//不允许缩容char* tmp = new char[n+1];//需要n个空间,但是要开n+1个空间,多出来的这个空间存放"\\0"strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;
}
void string::append(const char* str)//扩容
{size_t len = strlen(str);if (_size + len >= _capacity){reserve(_size + len);}strcpy(_str + _size, _str);//这里使用strcpy而不是strcat,strcat需要从头找"\\0"再追加,如果字符串较长需要花费很多时间//而扩容时可以知道,'\\0'就在_size位置_size += len;
}void string::push_back(char ch)//尾插字符
{if (_size + 1 > _capacity){reserve(_capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\\0';
}size_t string::find(char ch,size_t pos)//查
{for (int i=pos;i<_size;i++){if (_str[i] == ch){return i;}}return npos;
}size_t string::find(const char* str, size_t pos)
{assert(pos < _size);//const char* strstr(const char* str1, const char* str2);//返回指向 str2 中第一次出现的 str1 的指针,如果 str2 不是 str1 的一部分,则返回一个空指针char* p = strstr(_str + pos, str);if (p == nullptr){return npos;}else{return p - str;}
}void string::insert(size_t pos,char ch)//插入字符
{assert(pos <= _size);if (_size + 1 > _capacity){reserve(2 * _capacity);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;_size++;
}
void 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--;}strncpy(_str + pos, str,len);//将str中的len个字符拷贝到_str+pos的位置,并且不拷贝str中的'\\0'//方法二//size_t end = _size;//for (size_t i = 0; i < _size + 1; i++)//{//	_str[end + len] = _str[end];//	end--;//}
}
void string::erase(size_t pos, size_t len)//这里不是len=npos,否则会报错重定义默认参数
{if (len == npos || pos + len >= _size){_str[pos] = '\\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -=len;}
}void string::swap(string& s1)//相比于swap(a,b)更高效
{std::swap(_str, s1._str);std::swap(_capacity, s1._capacity);std::swap(_size, s1._size);
}void string::clear()
{_str[0] = '\\n';_size = 0;
}
int main()
{//string s1("hello world");//cout << s1.c_str() << endl;//string s2(s1);//cout << "s2.size():" << s2.size() << endl;//s2.insert(3, 's');//cout << "s2.insert(3,'s'):" << s2.c_str() << endl;//test1();//Print(s2);//test_2();test_3();system("pause");return 0;
}