文章目录
- 为什么要学习string?
- 库中的string
- 关于编码
- ASCII编码
- Unicode编码
- 迭代器Iterators
- string常用构造接口
- 接口声明与功能说明
- 接口演示
- string类对象的容量操作
- 接口声明与功能说明
- 接口演示
- reverse与resize在不同平台下的扩容与缩容机制
- string类对象的访问及遍历操作
- 接口声明与功能说明
- 接口演示
- at与operator[] 的区别
- back与front
- string类对象的修改操作
- operator+=
- append
- push_back与pop_back
- insert
- erase
- replace
- swap
- c_str
- find与rfind
- substr
- string类非成员函数
- operator+
- relational operators
- getline ()
- vs和g++下string结构的说明
- vs下string的结构
- g++下string的结构
为什么要学习string?
提高效率!提高效率!提高效率!
发展是滚滚向前的,我们是站在巨人肩膀上的一代,C++中有前辈为我们准备好的STL库,对于想要实现某种结构不再需要我们去手搓某一结构供我们使用,对于手搓某一已经被实现的结构再来使用这种行为是对生产力的浪费!
库中的string
库中的string类是由basic_string类模板实例化而来,原因是因为string是专门用于表示字符序列的对象,而为了能够适应多种语言(汉语,英语,韩语…),basic_string类模也就实例化了不同编码方式的类。
string编码规则是使用单字节编码,通常采用 ASCII 或 UTF-8 编码
u16string编码规则是UTF-16 编码
u32string编码规则是UTF-32 编码
wstring编码规则是宽字符编码,可以是 UTF-16 或 UTF-32,具体取决于平台
关于编码
大家思考在计算机中会存储像"哦豁,记得关注博主"这样的字符串内容吗?
当然不会,在计算机底层存储的是二进制序列,像00001111这样的序列(实际上计算机存储的是一种可以用来表示1或0的状态)。
ASCII编码
现在我们就来了解一下ASCII编码。
以下是ASCII编码表:
在ASCII编码规则中,一个英文字母用一个字节表示,一个汉字用多个字节表示(对于常见汉字一个汉字用两个字节表示)。
为什么出现这样的规则?
对于英文来讲,无非就是26个英文字母大小写,一个字节就够用了,对于博大精深的汉语来讲,汉字数不胜数,一个字节最多才能表示256个汉字于是就用多个字节表示一个汉字,而在世界上有那么多种语言,那他们怎么表示呢?为了解决这个问题,也是就出现了多种编码方式,这也就是为什么basic_string要实例化多种类。
我们可以通过内存存储与sizeof,验证ASCII编码规则。
#include<iostream>
using namespace std;
int main()
{
char Estr[] = "apple";
char Cstr[] = "哦豁,记得关注博主哦";
cout << sizeof(Estr) << endl;
cout << sizeof(Cstr) << endl;
return 0;
}
以下是内存窗口内容:
以下是运行结果:
(注意:字符串结尾有标识字符串末尾的’/0’)
Unicode编码
Unicode编码有多种规范UTF-8、UTF-16、UTF-32。
UTF-8为可变长度字符编码编码规则如如下:
UTF-16编码规则是两个字节表示一个字符
UTF-32编码规则是四个个字节表示一个字符
对于UTF-16与UTF-32来讲编码规则相较于UTF-8来讲更规范简单,但是空间浪费太大。
温馨提示,string的接口众多,请耐心阅读,对于过于鸡肋接口博主会一笔带过,本文重点讲解C++98的接口
为了方便大家理解,提前声明string的成员变量有哪些,以下为成员对象
size_t _capacity;//对象容量
size_t _size;//对象的有效字符数
char* _str;//字符串
为了方便大家了解string的相关接口,首先要了解string的迭代器
迭代器Iterators
对于string的迭代器来讲他可以说是STL容器中最简单的,这里提一下,他的迭代器实际为原生指针。后续我们会对string进行模拟实现,到时再详细讲解。
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
begin() 函数返回一个指向字符串首字符的迭代器,而 end() 函数返回一个指向字符串末尾后一个字符的迭代器(末尾后的一个位置)。const 修饰符版本的函数用于在 const 对象上获取 const 迭代器。
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
reverse_iterator rend();
const_reverse_iterator rend() const;
rbegin() 返回一个指向字符串末尾后一个字符的迭代器(末尾位置),而 rend() 函数返回一个指向字符串首字符前一个位置的逆向迭代器。const 修饰符版本的函数用于在 const 对象上获取 const 逆向迭代器。
string常用构造接口
接口声明与功能说明
接口声明 | 功能说明 |
---|---|
string(); | 构造空的string类对象,即空字符串 |
string (const string& str); | 拷贝构造函数,用str拷贝构造string |
string (const string& str, size_t pos, size_t len = npos); | 用str的pos位置后len长度的内容构造string对象(包括pos位置),如果len超过str长度则用pos位置后直到str末尾位置构造string对象,这里npos=-1 |
string (const char* s); | 用C-string来构造string类对象 |
string (const char* s, size_t n); | 用C-string来构造string类对象,内容为C-string前n个字符 |
string (size_t n, char c); | string类对象中包含n个字符c |
template string (InputIterator first, InputIterator last); | 使用迭代器范围 [first, last) 来构造一个新的string对象 |
string (initializer_list il); | C++11后出现的构造接口,列表初始化string对象 |
string (string&& str) noexcept; | C++11后出现的构造接口,移动构造string对象 |
接口演示
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s1;
string s2("hello world!");
string s3(s2);
string s4(s2,1,7);
string s5("hello world",8);
string s6(10,'a');
string s7(s2, 2, 3);
string s8{ "hello world" };
string s9(string("hello world"));
cout << "string();" <<" " << s1 << endl;
cout << "string (const char* s);" << " " << s2<<endl;
cout << "string (const string& str);" << " " << s3<<endl;
cout << "string (const string& str, size_t pos, size_t len = npos);" << " " << s4<< endl;
cout << "string (const char* s, size_t n);" << " " << s5<<endl;
cout << "string (size_t n, char c);" << " " << s6<< endl;
cout << "template string (InputIterator first, InputIterator last);" << " " << s7<< endl;
cout << "string (initializer_list il);" << " " << s8<< endl;
cout << "string (string&& str) noexcept;" << " " <<s9 << endl;
return 0;
}
RUN:
string类对象的容量操作
接口声明与功能说明
接口声明 | 功能说明 |
---|---|
size_t size() const; | 返回字符串有效字符长度 |
size_t length() const; | 返回字符串有效字符长度 |
size_t capacity() const; | 返回空间总大小 |
bool empty() const; | 检测字符串释放为空串,是返回true,否则返回false |
void clear(); | 清空有效字符 |
void reserve (size_t n = 0); | 为字符串预留空间 |
– | – |
void resize (size_t n); | 将容量修改成n,不修改有效字符,多余部分填入’\0’ |
void resize (size_t n, char c); | 将有效字符的个数改成n个,多出的空间用字符c填充 |
接口演示
void testCapacity()
{
string s1("hello world");
cout << "size()"<< " " << s1.size() << endl;
cout << "length()"<<" " << s1.length() << endl;
cout << "capacity()" << " " << s1.capacity() << endl;
cout << "empty()" << " " << s1.empty() << endl;
string s2("hello");
cout << "扩容前" << endl;
cout << "reverse()前有效元素数" << " " << s2.size() << endl;
cout << "reverse()前容量大小" << " " << s2.capacity() << endl;
s2.reserve(50);
cout << "reserve()扩容机制" << endl;
cout << "reverse()后有效元素数" << " " << s2.size() << endl;
cout << "reverse()后容量大小" << " " << s2.capacity() << endl;
s2.reserve(0);
cout << "reserve()缩容机制" << endl;
cout << "reverse()后有效元素数" << " " << s2.size() << endl;
cout << "reverse()后容量大小" << " " << s2.capacity() << endl;
string s3("hello");
cout << "扩容前" << endl;
cout << "resize()前有效元素数" << " " << s3.size() << endl;
cout << "resize()前容量大小" << " " << s3.capacity() << endl;
s3.resize(50);
cout << "resize()扩容机制" << endl;
cout << "resize()后有效元素数" << " " << s3.size() << endl;
cout << "resize()后容量大小" << " " << s3.capacity() << endl;
s3.resize(0);
cout << "resize()缩容机制" << endl;
cout << "resize()后有效元素数" << " " << s3.size() << endl;
cout << "resize()后容量大小" << " " << s3.capacity() << endl;
s3.clear();
cout << "clear()后有效元素数" << " " << s3.size() << endl;
cout << "clear()后容量大小" << " " << s3.capacity() << endl;
}
RUN:
注意:这是在Windows平台进行演示
RUN:
注意:这是在Linux(CentOS发行版)平台进行演示
reverse与resize在不同平台下的扩容与缩容机制
扩容:
Windows平台
每次扩容以合理倍数扩容
Linux平台
简单粗暴,扩容至指定大小
缩容:
Windows平台:
reverse无缩容效果
resize将有效字符修改至指定个数
Linux平台:
reverse把容量调整到有效字符数大小
resize将有效字符修改至指定个数
string类对象的访问及遍历操作
接口声明与功能说明
接口声明 | 功能说明 |
---|---|
char& operator[] (size_t pos); | 返回pos位置的字符,可修改pos位置字符 |
const char& operator[] (size_t pos) const; | 返回pos位置的字符,不可修改pos位置字符 |
– | – |
char& at (size_t pos); | 返回pos位置的字符,可修改pos位置字符 |
const char& at (size_t pos) const; | 返回pos位置的字符 |
– | – |
char& back(); | 获取字符串的最后一个字符,可修改 |
const char& back() const; | 获取字符串的最后一个字符,不可修改 |
– | – |
char& front(); | 获取字符串的第一个字符,可修改 |
const char& front() const; | 获取字符串的第一个字符,不可修改 |
接口演示
at与operator[] 的区别
at与operator[] 的区别在于边界检测,at检测出错抛异常,operator[]检测出错程序报错
at:
void testElementAccessAt() {
string s1("abcde");
try {
cout << s1.at(7) << endl;
}
catch (...)
{
}
}
RUN:
operator[] :
void testElementAccessOperator()
{
string s1("abcde");
cout << s1[7] << endl;
}
RUN:
熟悉的警告窗口
back与front
出现了string的鸡肋接口!对的string的鸡肋接口多的是,本文尽量较少的介绍这种鸡肋接口,大家了解一下即可。
void testElementAccess()
{
string s1("abcde");
cout << s1.front() << s1.back() << endl;
}
RUN:
string类对象的修改操作
接口名称 | 功能说明 |
---|---|
operator+= | 在字符串后追加字符串str |
append | 在字符串后追加一个字符串 |
push_back | 在字符串后尾插字符c |
insert | 在指定位置 pos 处插入字符或字符序列 |
erase | 删除指定位置的字符或字符范围 |
replace | 替换指定位置或字符范围的字符 |
swap | 交换两个对象内容 |
pop_back | 删除字符串中的最后一个字符 |
c_str | 返回C格式字符串 |
find | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
operator+=
string& operator+= (const string& str); //调用对象的末尾拼接一个str对象内容
string& operator+= (const char* s);//调用对象的末尾拼接一个s字符串内容
string& operator+= (char c);//调用对象的末尾拼接一个c字符
void test1()
{
string link("xxx");
string s1;
s1 += link;
cout << s1 << endl;
s1 += "yyy";
cout << s1 << endl;
s1 += 'z';
cout << s1 << endl;
}
RUN:
append
string& append (const string& str);//将另一个对象的内容添加到当前字符串的末尾。
string& append (const string& str, size_t subpos, size_t sublen);//将另一个对象的subpos位置(包括subpos位置)后sublen长度的内容添加到当前字符串的末尾。
string& append (const char* s);//在当前字符串末尾添加一个字符串
string& append (const char* s, size_t n);//在当前字符串末尾添加一个字符串的前n个字符
string& append (size_t n, char c);//在当前字符串末尾添加n个c字符
template string& append (InputIterator first,InputIterator last);//在当前字符串末尾添加一个对象的迭代器区间[first, last) ,这个对象不局限于string对象,只要是数据类型相同的对象均可
push_back与pop_back
void push_back (char c);//在对象字符串末尾添加一个字符c
void pop_back();//删除对象的最后一个字符
insert
string& insert (size_t pos, const string& str);//在pos位置处插入一个对象内容
string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);//在pos位置处插入一个对象的subpos位置后sublen长度的内容
string& insert (size_t pos, const char* s);//在pos位置处插入一个字符串内容
string& insert (size_t pos, const char* s, size_t n);//在pos位置处插入一个字符串的前n个字符
string& insert (size_t pos, size_t n, char c);//在pos位置处插入n个字符c
erase
string& erase (size_t pos = 0, size_t len =npos);
//删除字符串pos位置后len个字符,默认全部删除
iterator erase (iteratorp);
//删除一个迭代器位置,并返回一个指向删除后的下一个字符的迭代器
iterator erase (iterator first,iterator last);
//删除一个迭代器区间,并返回一个指向删除后的下一个字符的迭代器
replace
string& replace (size_t pos, size_t len, const string&str);
//将字符串的pos位置后len个字符替换为对象str内容
string& replace (size_t pos, size_t len, const string& str, size_t subpos, size_tsublen);
//将字符串的pos位置后len个字符替换为对象str的一个子区间内容
string& replace (size_t pos, size_t len, const char* s);
//将字符串的pos位置后len个字符替换为字符串s内容
string& replace (size_t pos, size_t len, const char* s, size_t n);
//将字符串的pos位置后len个字符替换为字符串s的前n个字符内容
swap
void swap (string& str);
//交换两个对象内容
c_str
const char* c_str() const;//这个函数返回一个指向以 null 结尾的字符数组(C 字符串)的指针,表示当前字符串的内容,不可对指针内容修改。
find与rfind
注意:这里仅对find进行讲解,rfind与find唯一区别就是查找方向不同。
size_t find (const string& str, size_t pos = 0) const;//从pos位置开始寻找一个类,返回其首个字符位置
size_t find (const char* s, size_t pos = 0) const;//从pos位置开始寻找一个字符串,返回其首个字符位置
size_t find (char c, size_t pos = 0) const;//从pos位置开始寻找一个字符,返回其字符位置
以上三个接口查找内容失败均返回npos;
substr
string substr (size_t pos = 0, size_t len = npos) const;//取子串pos位置后len个字符,返回一个string类
string类非成员函数
以下函数在模拟实现时博主会再次进行介绍
接口 | 功能说明 |
---|---|
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> () | 输入运算符重载 |
operator<<() | 输出运算符重载 |
getline () | 获取一行字符串 |
relational operators | 大小比较 |
operator+
该接口就是某一数据与某一数据相加然后返回相加结果
relational operators
getline ()
istream& getline (istream& is, string& str, char delim);
//用于从输入流is中读取一行字符串,并将其存储在str中。函数会在遇到指定的分隔符delim或者到达输入流的末尾时停止读取,delim默认是’\n’(换行符)。
istream& getline (istream& is, string& str);//用于从输入流is中读取一行字符串,并将其存储在str中。函数会在遇到换行符时停止
getline ()具体效果需要自己实践体验
#include <iostream>
#include <string>
int main() {
std::string line;
std::getline(std::cin, line); // 读取一行,直到遇到换行符
std::cout << "You entered: " << line << std::endl;
char delimiter = ':';
std::getline(std::cin, line, delimiter); // 读取一行,直到遇到冒号
std::cout << "You entered up to delimiter: " << line << std::endl;
return 0;
}
vs和g++下string结构的说明
vs下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个字节。
这种实现技术被称为短字符串优化(SSO,Short String Optimization)
短字符串优化(SSO,Short String Optimization)是 std::string 实现中常见的一种技术,用于优化小字符串的存储和性能。它的基本思想是将小字符串直接存储在 std::string 对象内部,而不是在堆上动态分配内存。这避免了小字符串在堆和栈之间频繁分配和释放的开销,同时减少了内存碎片。
实现上,std::string 对象内部通常会包含一个固定大小的缓冲区(比如15或22个字符的空间),用于直接存储短字符串。当字符串的长度小于或等于这个缓冲区的大小时,字符串的内容就直接存放在这个缓冲区里,不需要额外的动态内存分配。当字符串的长度超过缓冲区的大小时,std::string 就会在堆上动态分配内存来存储字符串的内容,并释放之前使用的缓冲区空间(如果有的话)。
g++下string的结构
g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
空间总大小
字符串有效长度
引用计数
指向堆空间的指针,用来存储字符串
本章到此结束,感谢您的阅读!