目录
前沿
一、标准库中的string类
二、string类的常用接口说明
1、string类对象的常见构造
2、string类对象的容量操作
3、string类对象的访问及遍历操作
4、string类对象的修改操作
5、string类非成员函数
6、vs下string结构的说明
三、string类的模拟实现
1、构造函数
2、析构函数
3、拷贝构造函数
4、赋值运算符重载
5、比较运算符重载
6、push_back ,append,+=
7、容量(resize,reserve)(重点)
8、插入和删除(insert,erase)
9、迭代器
STL (standard template libaray-标准模板库)是 C++ 标准库的重要组成部分,由六大部分构成:仿函数,空间配置器,算法,容器,迭代器和配接器,其中包含了各式各样的容器,方便我们以后编写程序,比如今天要总结的 string 容器。
- 字符串是表示字符序列的类
- 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作 单字节字符字符串的设计特性。
- string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信 息,请参阅basic_string)。
- string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits 和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
- 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
总结:
- string是表示字符串的字符串类。
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
- string在底层实际是:basic_string模板类的别名,typedef basic_string string。
- 不能操作多字节或者变长字符的序列。
注意:在使用string类时,必须包含#include头文件以及using namespace std;
(constructor)函数名称 | 功能说明 |
string()(重点) | 构造一个空字符串,长度为0个字符。 |
string (const char* s)(重点) | 通过C字符串构造string类对象 |
string (const string& str)(重点) | 拷贝构造 |
string (const string& str, size_t pos, size_t len = npos) | 复制str从pos位置开始往后npos个字符 |
string (const char* s, size_t n) | 复制C字符串前n个字符 |
string (size_t n, char c) | string类对象包含n个字符c |
template < class InputIterator > string (InputIterator first, InputIterator last) | 通过一个字符串区间构造对象 |
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello bit"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}
函数名称 | 功能说明 |
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty(重点) | 检测字符串释放为空串,是返回true,否则返回false |
clear(重点) | 清空有效字符 |
reserve(重点) | 为字符串预留空间**(只改变capacity的值) |
resize(重点) | 将有效字符的个数该成n个,多出的空间用字符c填充(改变size和capacity的值) |
注意:
- size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()。
- clear()只是将string中有效字符清空,不改变底层空间大小。
- resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字 符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的 元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大 小,如果是将元素个数减少,底层空间总大小不变。
- reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于 string的底层空间总大小时,reserver不会改变容量大小。
#include
#include
using namespace std;
int main()
{
string s1("hello world");
s1.reserve(10);
s1.resize(6, '0');
return 0;
}
当运行到 reserve 时:
我们发现当参数小于size时,改操作对参数几乎没有影响。
当运行resize时:
我们发现这里参数 size 发生了改变,但是没有改变容量的大小。
注意:当 reserve 参数大于 size 时就会开出大于参数的空间,具体是多少由编译器决定,resize 的第一个参数大于 size 时也会扩大空间,同时还将 size 扩大到 n,在原始数据上将后面数据初始化为ch.
函数名称 | 功能说明 |
operator[] | 返回pos位置的字符,const string类对象调用 |
begin+ end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
rbegin + rend | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
(1)可以通过[]+下标的方式来获取该字符串某个位置的字符。
void Test()
{
string s1("hello World!");
cout << s1[0] << " " << s2[0] << endl;
s1[0] = 'H';
cout << s1 << endl;
}
int main()
{
Test();
return 0;
}
运行结果:
void Test()
{
string s("hello world!");
//begin代表字符串第一个位置
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it;
++it;
}
cout << endl;
// string::reverse_iterator rit = s.rbegin();
// C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型
auto rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit;
rit++;
}
cout << endl;
}
int main()
{
Test();
return 0;
}
运行结果:
(3) 还可以用范围 for 的方式访问。
void Test()
{
string s("hello world!");
for (auto ch : s)
cout << ch;
cout << endl;
}
int main()
{
Test();
return 0;
}
运行结果:
函数名称 | 功能说明 |
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= | 在字符串后追加字符串str |
c_str | 返回C格式字符串 |
find + npos | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
insert | 在源字符串某一位置插入字符串str |
erase | 在某一位置开始进行删除 |
swap | 字符串交换 |
find_first_of | 搜索字符串中与参数中指定的任何字符匹配的第一个字符 |
find_last_of | 在字符串中搜索与参数中指定的任何字符匹配的最后一个字符 |
find_first_not_of | 搜索字符串中与参数中指定的任何字符不匹配的第一个字符 |
find_last_not_of | 在字符串中搜索不匹配其参数中指定的任何字符的最后一个字符 |
void Test()
{
string str;
str.push_back(' '); // 在str后插入空格
str.append("hello"); // 在str后追加一个字符"hello"
str += 'w'; // 在str后追加一个字符'w'
str += "orld"; // 在str后追加一个字符串"orld"
cout << str << endl;
cout << str.c_str() << endl; // 以C语言的方式打印字符串
string s("world");
s.insert(0, "hello");
cout << s << endl;
s.insert(2, "abcdef", 3);//插入该字符串的三个字符在二号位置
cout << s << endl;
string s("abcd");
s.insert(s.find('b'), "abcdef");//找到字符b的位置然后插入"abcdef"
cout << s << endl;
s.replace(s.find('f'), 1, "20%");//找到f的位置用20%代替
cout << s << endl;
string s("hello world");
size_t pos = s.find_first_of("eo", 0);//从位置0处找到字符e或字符o第一次出现的位置
while (pos != string::npos)//如果没有找到匹配项,函数返回string::npos。
{
s[pos] = '*';
pos = s.find_first_of("eo", pos + 1);//将整个字符串中的字符e或字符o换成*
}
//将不是e或o的字符都替换成*
string s("hello world");
size_t pos = s.find_first_not_of("eo", 0);
while (pos != string::npos)
{
s[pos] = '*';
pos = s.find_first_not_of("eo", pos + 1);
}
cout << s << endl;
string s("hello world");
size_t pos = s.find_last_of("eo", s.size() - 1);//从后开始找
while (pos != string::npos)
{
s[pos] = '*';
pos = s.find_last_of("eo", pos - 1);
}
cout << s << endl;
}
注意:
- 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
- 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
函数 | 功能说明 |
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> | 输入运算符重载 |
operator<< | 输出运算符重载 |
getline(重要) | 获取一行字符串 |
relational operators | 大小比较 |
这里面的 getline 函数是获取一行字符串的,和单纯的流插入是不一样的,因为字符串中很可能会包含空格的,而 cin 会认为这是结束的标志,进而不再读取字符,因此才有了这个 getline 函数。
具体用法如下:
#include
#include
using namespace std;
int main()
{
string s;
getline(cin, s);
cout << s << endl;
return 0;
}
运行结果:
注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。
关于string对象应该占多少字节这个问题,我们先大胆猜一下为12个字节(一个指向字符串的指针,一个是整形的 size,一个整形的 capacity),但明显不是这样的,string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 string 中字符串的存储空间:
(1)当字符串长度小于16时,使用内部固定的字符数组来存放;
(2)当字符串长度大于等于16时,从堆上开辟空间。
union _Bxty
{
// storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
// to permit aliasing
char _Alias[_BUF_SIZE];
}_Bx;
因为大多数情况下字符串的长度都小于16,那 string 对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高;还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量;还有一个指针做一些其他事情。因此总共占16+4+4+4=28个字节。
class string
{
public:
string(const char* str = "")
:_size(strlen(str))
{
_capcity = _size == 0 ? 4 : _size;
_str = new char[_capcity + 1];
strcpy(_str, str);
}
private:
char* _str;
size_t _size;
size_t _capcity;
};
~String()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//传统版写法
string(const string& s)
:_size(s._size)
, _capcity(s._capcity)
{
_str = new char[_capcity + 1];
strcmp(_str, s._str);
}
//现代版写法
String(const string& s)
:_str(nullptr)
{
string str(s._str);
swap(_str, str);
}
注意:第二种现代版写法, 一定要先构造一份对象然后再交换,要不然直接交换的话就把传过来的对象变空了。
//传统版写法
string& operator=(const string& s)
{
_size = s._size;
_capcity = s._capcity;
// 防止new失败,我们先创建一个临时变量开辟空间
// 拷贝完后,在给_str
char* tmp = new char[_capcity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
return *this;
}
//现代版写法
String& operator=(String s)
{
swap(_str, s._str);
return *this;
}
bool operator==(const string& s)
{
return strcmp(_str, s._str) == 0;
}
bool operator>(const string& s)
{
return strcmp(_str, s._str) > 0;
}
bool operator>=(const string& s)
{
return *this > s || *this == s;
}
bool operator<(const string& s)
{
return !(*this >= s);
}
bool operator<=(const string& s)
{
return *this < s && *this == s;
}
bool operator!=(const string& s)
{
return !(*this == s);
}
比较运算符的重载可以先写一个大于和等于的函数,其他的比较可以直接用这两个进行复用。
//增加一个字符
void push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity * 2);
}
_str[_size++] = c;
_str[_size] = '\0';
}
//增加一个字符串
void append(const char* str)
{
int 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;
}
void resize(size_t n, char c = '\0')
{
if (n > _size)
{
if (n > _capacity)
{
reserve(n);
}
memset(_str + _size, c, n - _size);
}
_size = n;
_str[n] = '\0';
}
void reserve(size_t n)
{
if (n > _capcity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capcity = n;
}
}
//插入字符
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
//插入前检查是否需要扩容
if (_size + 1 > _capcity)
reserve(_size * 2);
//挪动数据
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
_size++;
return *this;
}
//插入字符串
string& insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t lens = strlen(s);
if (_size + lens > _capcity)
reserve(_size + lens);
//挪动数据
size_t end = _size + lens;
while (end > pos + lens - 1)
{
_str[end] = _str[end - lens];
--end;
}
strncpy(_str, s, lens);
_size += lens;
return *this;
}
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
//挪动数据
if (len == npos || _size - pos <= len)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t begin = pos + len;
while (begin <= _size)
{
_str[begin - len] = _str[begin];
++begin;
}
_size -= len;
}
return *this;
}
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;
}
本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。