python官方网站什么是淘宝搜索关键词
在 C C C++ 中, s t r i n g string string 是标准库提供的一个类,用于处理字符串。它是基于模板的容器类,提供了许多成员函数和操作符,用于对字符串进行各种操作,比如插入、删除、查找、连接等。与 C C C 风格的字符串相比, s t r i n g string string 类更加安全和方便,它负责自动管理字符串内存,具有动态大小调整的能力。
文章目录
- 前言 —— STL 简介
- 1. 什么是 STL ?
- 2. STL 的版本(了解)
- 3. STL 的六大组件
- 一、为什么学习 string 类?
- 1. C语言中的字符串
- 2. OJ 题
- 二、如果学习 string 类?(查文档)
- 三、补充 2 个 C++11 的知识点
- 1. auto 关键字
- 2. 范围 for
- 四、迭代器(iterator)
- 五、string 类的使用(常用接口)
- 1. 常见构造
- 2. 容量操作
- 3. 访问及遍历操作
- 4. 修改操作
- 5. 非成员函数
- 六、vs 和 g++ 下 string 结构的说明(了解)
- 1. vs 下 string 的结构
- 2. g++ 下 string 的结构
- 七、string 类的拷贝问题
- 1. 浅拷贝
- 2. 深拷贝
- 3. 写时拷贝(了解)
- 八、string 类的模拟实现
- 总结
前言 —— STL 简介
本篇博客作为 S T L STL STL 系列的开篇,首先要介绍一下什么是 S T L STL STL。不过要注意的是, s t r i n g string string 类不属于 S T L STL STL 的六大组件,而是在 S T L STL STL 出现之前就已经存在了。不过 s t r i n g string string 在实现和使用方式和 S T L STL STL 容器极为相似,因此把 s t r i n g string string 类暂且归为 S T L STL STL 部分学习。
1. 什么是 STL ?
S T L STL STL( s t a n d a r d t e m p l a t e l i b a r a y standard\ template\ libaray standard template libaray - 标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
2. STL 的版本(了解)
- 原始版本
A l e x a n d e r S t e p a n o v 、 M e n g L e e Alexander Stepanov、Meng Lee AlexanderStepanov、MengLee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 H P HP HP 版本 —— 所有 S T L STL STL 实现版本的始祖。
- P . J . P. J. P.J. 版本
由 P . J . P l a u g e r P.\ J.\ Plauger P. J. Plauger 开发,继承自 H P HP HP 版本,被 W i n d o w s V i s u a l C + + Windows\ Visual\ C++ Windows Visual C++ 采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。
- R W RW RW 版本
由 R o u g e W a g e Rouge\ Wage Rouge Wage 公司开发,继承自 H P HP HP 版本,被 C + + B u i l d e r C++\ Builder C++ Builder 采用,不能公开或修改,可读性一般。
- S G I SGI SGI 版本
由 S i l i c o n G r a p h i c s C o m p u t e r S y s t e m s Silicon\ Graphics\ Computer\ Systems Silicon Graphics Computer Systems, I n c Inc Inc 公司开发,继承自 H P HP HP 版本。被 G C C ( L i n u x ) GCC(Linux) GCC(Linux) 采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面 S T L STL STL 要阅读部分源代码,主要参考的就是这个版本。
3. STL 的六大组件
可以看出, S T L STL STL 的主要内容是一个包罗数据结构与算法的软件框架。模板的出现大大提高了代码的复用性, S T L STL STL 使我们方便了非常多,不用再像 C C C 语言一样手搓算法和数据结构了。在算法竞赛中, S T L STL STL 也是一个常用且有力的工具。
一、为什么学习 string 类?
1. C语言中的字符串
C C C 语言中,字符串是以 ‘ \ 0 ’ ‘\backslash 0’ ‘\0’ 结尾的一些字符的集合。为了操作方便, C C C 标准库中提供了一些 s t r str str 系列的库函数,但是这些库函数与字符串是分离开的,不太符合 O O P OOP OOP( O b j e c t O r i e n t e d P r o g r a m m i n g Object\ Oriented\ Programming Object Oriented Programming)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
因此,在 C++ 中,采用面向对象的思想,把 s t r i n g string string 这个常用的字符串封装成一个 s t r i n g string string 类,这样我们用户想使用 s t r i n g string string 这个字符串类型的时候,只需要调用其接口即可。
2. OJ 题
在 O J OJ OJ 中,有关字符串的题目基本以 s t r i n g string string 类的形式出现。而且在常规工作中,为了简单、方便、快捷,基本都使用 s t r i n g string string 类,很少有人去使用 C C C 库中的字符串操作函数。
以下面一道面试题为例:
【题目信息】 L e e t C o d e 415. LeetCode\ 415. LeetCode 415. 字符串相加
【题目解析】
这道题是高精度加法,当我们遇到很大的两个数(即使开 l o n g l o n g long\ long long long 也存不下)做加法运算的时候,我们就要用字符串来存下数字的每一位,然后模拟竖式运算,通过各位相加、进位加 1 1 1 等操作,来实现大整数的加法。题目的具体做法思路如下:
- 字符串转化为数字数组:
首先,我们知道两个高精度整数是以字符串的形式存储的,因此为了方便逐位相加,我们需要将字符串转换成倒序的数组,使得个位对齐,这样可以从低位向高位依次相加。
- 逐位相加并处理进位:
对于每一位数字,两个数组中的对应位相加,记录进位。进位部分会加到下一位的相加过程中。如果当前位的和大于等于 10 10 10,则需要将该位的值取模 10 10 10,余数保留,进位值加到下一位。
- 处理进位后的结果:
在完成所有位的相加后,如果最高位有进位,则需要在结果中保留该位。否则,最高位是 0 0 0 时则忽略。
- 将结果转回字符串输出:
最后,将结果数组从高位到低位依次转换为字符串,并输出最终结果。
【代码示例】
class Solution {
public:string addStrings(string a, string b) {int x[10000] = {0}, y[10000] = {0}, z[10000] = {0};// 将字符串 a, b 转换为倒序数组for (int i = 0; i < a.size(); i++) x[a.size() - 1 - i] = a[i] - '0';for (int i = 0; i < b.size(); i++) y[b.size() - 1 - i] = b[i] - '0';// 求和并处理进位int len = max(a.size(), b.size());for (int i = 0; i < len; i++) {z[i] += x[i] + y[i]; // 当前位相加z[i + 1] += z[i] / 10; // 处理进位z[i] %= 10; // 保留个位}// 检查最高位是否有进位if (z[len] != 0) len++;// 构造返回结果字符串string ans;for (int i = len - 1; i >= 0; i--)ans.push_back(z[i] + '0');return ans;}
};
二、如果学习 string 类?(查文档)
不只是 s t r i n g string string 类,我们在学习 S T L STL STL 的时候一定要勤于查 C++ 官方文档: s t r i n g string string 类的文档介绍。
注意:在使用 s t r i n g string string 类时,必须包含 # i n c l u d e include include < s t r i n g string string > 头文件以及 u s i n g n a m e s p a c e s t d ; using\ namespace\ std; using namespace std;
三、补充 2 个 C++11 的知识点
1. auto 关键字
在 C++11 中, a u t o auto auto 是一个类型指示符来指示编译器, a u t o auto auto 声明的变量必须由编译器在编译时期推导而得。(自动识别类型)
注意:只有C++11以上的版本才支持auto。
实际上, a u t o auto auto 最大的用途是为了简化代码:(但是一定程度上牺牲了可读性)
string s;string::iterator it = s.begin();//像迭代器这种长类型可以直接用auto代替,来简化代码
auto it = s.begin();
除此之外,还有一些注意事项:
- 用 a u t o auto auto 声明指针类型时,用 a u t o auto auto 和 a u t o ∗ auto^* auto∗ 没有任何区别(自动识别类型),但用 a u t o auto auto 声明引用类型时则必须加 & \& &(加 & \& & 代表可修改)。
auto x = 1; //intauto y = &x; //int*
auto* z = &x; //int*cout << typeid(x).name() << endl; //输出x的类型
cout << typeid(y).name() << endl; //输出y的类型
cout << typeid(z).name() << endl; //输出z的类型//int
auto m = x;
m = 2;
cout << x << endl;//int&
auto& n = x;
n = 2;
cout << x << endl;
运行结果为:
- 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
auto a = 1, b = 2;//error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
auto c = 3, d = 4.0;
- a u t o auto auto 不能作为函数的参数,可以做返回值,但是建议谨慎使用。
auto add(int a, int b)
{return a + b;
}//error C3533: 参数不能为包含“auto”的类型
int sub(auto b, int c)
{return b - c;
}int main()
{add(2, 1);sub(4, 3);
}
- a u t o auto auto 不能直接用来声明数组 。
//error C3318 : “auto[]” : 数组不能具有其中包含“auto”的元素类型
auto a[] = { 1,2,3 };
2. 范围 for
在 C++11 中,引入了基于范围的 f o r for for 循环。 f o r for for 循环后的括号由冒号 : : : 分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。(范围 f o r for for 的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到)
范围 f o r for for 可以作用到数组和容器对象上进行遍历:
int a[] = { 1,2,3,4,5 };//C++98的遍历
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{cout << a[i] << " ";
}
cout << endl;//C++11的遍历//a不可修改(传值)
for (auto i : a)
{cout << i << " ";
}
cout << endl;//加上&代表可修改(传引用)
for (auto& i : a)
{i *= 2;cout << i << " ";
}
cout << endl;
运行结果为:
四、迭代器(iterator)
- i t e r a t o r iterator iterator
string s("hello world");for (string::iterator it = s.begin(); it != s.end(); it++)
{cout << *it; //hello world
}
- r e v e r s e _ i t e r a t o r reverse\_iterator reverse_iterator
string s("hello world");for (string::reverse_iterator it = s.rbegin(); it != s.rend(); ++it)
{cout << *it; //dlrow olleh
}
- c o n s t _ i t e r a t o r const\_iterator const_iterator
const string s = "hello world";for (string::const_iterator it = s.cbegin(); it != s.cend(); it++)
{//error C3892: “it”: 不能给常量赋值//*it += 1;cout << *it; //hello world
}
- c o n s t _ r e v e r s e _ i t e r a t o r const\_reverse\_iterator const_reverse_iterator
const string s = "hello world";for (string::const_reverse_iterator it = s.crbegin(); it != s.crend(); it++)
{//error C3892: “it”: 不能给常量赋值//*it += 1;cout << *it; //dlrow olleh
}
五、string 类的使用(常用接口)
下面只列举了 s t r i n g string string 最常用的几个接口,更多详细信息可以自行查官方文档: s t r i n g string string。
1. 常见构造
构造( c o n s t r u c t o r constructor constructor)函数:
函数名称 | 功能说明 |
---|---|
s t r i n g ( ) string() string() (重点) | 构造空的 s t r i n g string string 类对象,即空字符串 |
s t r i n g ( c o n s t c h a r ∗ s ) string(const\ char^*\ s) string(const char∗ s) (重点) | 用 C − s t r i n g C-string C−string (字符数组)来构造 s t r i n g string string 类对象 |
s t r i n g ( s i z e _ t n , c h a r c ) string(size\_t\ n,\ char\ c) string(size_t n, char c) | s t r i n g string string 类对象中包含 n n n 个字符 c c c |
s t r i n g ( c o n s t s t r i n g & s ) string(const\ string\&\ s) string(const string& s) (重点) | 拷贝构造函数 |
void Member_functions()
{string s1; //1.构造空的string类对象s1string s2("hello bit"); //2.用C格式字符串构造string类对象s2string s3(s2); //3.拷贝构造s3
}
2. 容量操作
函数名称 | 功能说明 |
---|---|
s i z e size size(重点) | 返回字符串有效字符长度 |
l e n g t h length length (重点) | 返回字符串有效字符长度 |
c a p a c i t y capacity capacity | 返回空间总大小 |
e m p t y empty empty (重点) | 检测字符串是否为空串,是返回 t r u e true true,否则返回 f a l s e false false |
c l e a r clear clear(重点) | 清空有效字符 |
r e s e r v e reserve reserve (重点) | 为字符串预留空间 |
r e s i z e resize resize(重点) | 将有效字符的个数改成 n n n 个,多出的空间用字符 c c c 填充 |
// 测试string容量相关的接口
// size/clear/resize
void Capacity1()
{string s("hello, ybc!!!");cout << "s.size(): " << s.size() << endl;cout << "s.length(): " << s.length() << endl;cout << "s.capacity():" << s.capacity() << endl;cout << "s: " << s << endl;cout << endl;// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小s.clear();cout << "s.size(): " << s.size() << endl;cout << "s.capacity():" << s.capacity() << endl;cout << "s: " << s << endl;cout << endl;// 将s中有效字符个数增加到10个,多出位置用'a'进行填充// “aaaaaaaaaa”s.resize(10, 'a');cout << "s.size(): " << s.size() << endl;cout << "s.capacity():" << s.capacity() << endl;cout << "s: " << s << endl;cout << endl;// 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充// "aaaaaaaaaa\0\0\0\0\0"// 注意此时s中有效字符个数已经增加到15个s.resize(15);cout << "s.size(): " << s.size() << endl;cout << "s.capacity():" << s.capacity() << endl;cout << "s: " << s << endl;cout << endl;// 将s中有效字符个数缩小到5个s.resize(5);cout << "s.size(): " << s.size() << endl;cout << "s.capacity():" << s.capacity() << endl;cout << "s: " << s << endl;cout << endl;
}
void Capacity2()
{string s;// 测试reserve是否会改变string中有效元素个数s.reserve(100);cout << "s.size(): " << s.size() << endl;cout << "s.capacity():" << s.capacity() << endl;cout << endl;// 测试reserve参数小于string的底层空间大小时,是否会将空间缩小s.reserve(50);cout << "s.size(): " << s.size() << endl;cout << "s.capacity():" << s.capacity() << endl;cout << endl;
}
如果 s t r i n g string string 为空,那么我们当我们插入数据的时候, s t r i n g string string 会不断扩容:
void TestPushBack()
{string s;size_t sz = s.capacity();cout << "making s grow:\n";for (int i = 0; i < 100; ++i){s.push_back('c');if (sz != s.capacity()){sz = s.capacity();cout << "capacity changed: " << sz << '\n';}}
}
构建 s t r i n g string string 时,如果提前已经知道 s t r i n g string string 中大概要放多少个元素,可以提前将 s t r i n g string string 中空间设置好:
void TestPushBackReserve()
{string s;s.reserve(100);size_t sz = s.capacity();cout << "making s grow:\n";for (int i = 0; i < 100; ++i){s.push_back('c');if (sz != s.capacity()){sz = s.capacity();cout << "capacity changed: " << sz << '\n';}}
}
因此,利用 r e s e r v e reserve reserve 提高插入数据的效率,可以避免增容带来的开销。
总结:
-
s i z e ( ) size() size() 与 l e n g t h ( ) length() length() 方法底层实现原理完全相同,引入 s i z e ( ) size() size() 的原因是为了与其他容器的接口保持一致,一般情况下基本都是用 s i z e ( ) size() size()。
-
c l e a r ( ) clear() clear() 只是将 s t r i n g string string 中有效字符清空,不改变底层空间大小( c a p a c i t y capacity capacity)。
-
r e s i z e ( s i z e _ t n ) resize(size\_t\ n) resize(size_t n) 与 r e s i z e ( s i z e _ t n , c h a r c ) resize(size\_t\ n, char\ c) resize(size_t n,char c) 都是将字符串中有效字符个数改变到 n n n 个。
不同的是:当字符个数增多时, r e s i z e ( n ) resize(n) resize(n) 用 0 0 0 来填充多出的元素空间, r e s i z e ( s i z e _ t n , c h a r c ) resize(size\_t\ n, char\ c) resize(size_t n,char c) 用字符 c c c 来填充多出的元素空间。
注意: r e s i z e resize resize 在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小(扩容);如果是将元素个数减少,底层空间总大小不变(不缩)。
- r e s e r v e ( s i z e _ t r e s _ a r g = 0 ) reserve(size\_t\ res\_arg=0) reserve(size_t res_arg=0):为 s t r i n g string string 预留空间,不改变有效元素个数,当 r e s e r v e reserve reserve 的参数小于 s t r i n g string string 的底层空间总大小时, r e s e r v e r reserver reserver 不会改变容量大小。
3. 访问及遍历操作
函数名称 | 功能说明 |
---|---|
o p e r a t o r [ ] operator[\ ] operator[ ] (重点) | 返回 p o s pos pos 位置的字符, c o n s t s t r i n g const\ string const string 类对象调用 |
b e g i n + e n d begin+ end begin+end | b e g i n begin begin 获取第一个字符的迭代器 + + + e n d end end 获取最后一个字符下一个位置的迭代器 |
r b e g i n + r e n d rbegin + rend rbegin+rend | r b e g i n rbegin rbegin 获取最后一个字符的迭代器 + + + r e n d rend rend 获取第一个字符下一个位置的迭代器 |
范围 f o r for for | C C C++ 11 11 11 支持更简洁的范围 f o r for for 的新遍历方式 |
s t r i n g string string 的遍历一般是:
-
b e g i n ( ) + e n d ( ) begin()+end() begin()+end()
-
f o r + o p e r a t o r [ ] for+operator[\ ] for+operator[ ] / / / 范围 f o r for for
注意: s t r i n g string string 遍历时使用最多的还是 f o r for for + + + 下标,或者范围 f o r for for( C C C ++ 11 11 11 后才支持); b e g i n ( ) + e n d ( ) begin()+end() begin()+end() 大多数使用在需要使用 S T L STL STL 提供的算法操作 s t r i n g string string 时,比如:采用 r e v e r s e reverse reverse 逆置 s t r i n g string string。
1. 访问操作:
void Element_access1()
{string s1("hello ybc");const string s2("Hello ybc");cout << s1 << " " << s2 << endl;cout << s1[0] << " " << s2[0] << endl;s1[0] = 'H';cout << s1 << endl;// s2[0] = 'h'; 代码编译失败,因为const类型对象不能修改
}
2. 遍历操作:
void Element_access2()
{string s("hello ybc");// 1. for + operator[]for (auto i = 0; i < s.size(); ++i)cout << s[i];cout << endl << endl;// 2.迭代器string::iterator it = s.begin();while (it != s.end()){cout << *it;++it;}cout << endl << endl;// string::reverse_iterator rit = s.rbegin();// C++11之后,直接使用auto定义迭代器,让编译器推导迭代器的类型auto rit = s.rbegin();while (rit != s.rend()){cout << *rit;++rit;}cout << endl << endl;// 3.范围forfor (auto ch : s)cout << ch;cout << endl << endl;
}
4. 修改操作
函数名称 | 功能说明 |
---|---|
p u s h _ b a c k push\_back push_back | 在字符串后尾插字符 c c c |
a p p e n d append append | 在字符串后追加一个字符串 |
o p e r a t o r + = operator\ += operator +=(重点) | 在字符串后追加字符串 s t r str str |
c _ s t r c\_str c_str(重点) | 返回 C C C 格式字符串 |
f i n d + n p o s find + npos find+npos(重点) | 从字符串 p o s pos pos 位置开始往后找字符 c c c,返回该字符在字符串中的位置 |
r f i n d + n p o s rfind + npos rfind+npos (重点) | 从字符串 p o s pos pos 位置开始往前找字符 c c c,返回该字符在字符串中的位置 |
s u b s t r substr substr(重点) | 在 s t r str str 中从 p o s pos pos 位置开始,截取 n n n 个字符,然后将其返回 |
i n s e r t insert insert(重点) | 从字符串 p o s pos pos 位置开始,插入 n n n 个字符 c c c / / / 字符串 s t r i n g string string |
e r a s e erase erase(重点) | 从字符串 p o s pos pos 位置开始,删除长度为 n n n 的字符串 |
r e p l a c e replace replace(重点) | 从字符串 p o s pos pos 位置开始,将 n n n 个字符替换成字符串 s t r i n g string string |
注意: n p o s npos npos 是 s t r i n g string string 里面的一个静态成员变量:
static const size_t npos = -1;
。
测试 s t r i n g string string:
-
插入(拼接)方式: p u s h _ b a c k / a p p e n d / o p e r a t o r + = / i n s e r t push\_back / append / operator+= / insert push_back/append/operator+=/insert
-
正向和反向查找: f i n d ( ) + r f i n d ( ) find() + rfind() find()+rfind()
-
截取子串: s u b s t r ( ) substr() substr()
-
删除: e r a s e erase erase
void Modifiers()
{string str;str.push_back(' '); // 在str后插入空格str.append("hello "); // 在str后追加一个字符"hello "str += 'y'; // 在str后追加一个字符'y' str += "bc"; // 在str后追加一个字符串"bc"cout << str << endl; // 以string方式打印字符串cout << str.c_str() << endl; // 以C语言的方式打印字符串
}
【应用1】获取 f i l e file file 的后缀
string file("string.cpp");//1.从file中找到.的下标pos
size_t pos = file.rfind('.');
//2.从pos开始,截取pos及pos后面的字符子串
string suffix(file.substr(pos, file.size() - pos));cout << suffix << endl;
【应用2】取出 u r l url url 中的域名
string url("http://www.cplusplus.com/reference/string/string/find/");
cout << url << endl;//找到域名的协议前缀
size_t start = url.find("://");
//如果找不到协议前缀(说明不是协议)
if (start == string::npos)
{cout << "invalid url" << endl;return 0;
}
//找到域名的起始位置
start += 3;
//找到域名的终止位置
size_t finish = url.find('/', start);
//获取域名子串
string address = url.substr(start, finish - start);cout << address << endl;
【应用3】删除 u r l url url 的协议前缀
string url("http://www.cplusplus.com/reference/string/string/find/");//找到域名的协议前缀
size_t pos = url.find("://");
//删除协议前缀
url.erase(0, pos + 3);cout << url << endl;
string s = "o world ";//头插
s.insert(0, "hell");
//尾插10个字符a
s.insert(s.size(), 10, 'a');、cout << s << endl;//删除空格后的所有元素(到npos结束)
auto pos = s.find(' ');
s.erase(pos + 1);cout << s << endl;//把hello替换成nihao
s.replace(0, s.size(), "nihao");cout << s << endl;
总结:
-
在 s t r i n g string string 尾部追加字符时,
s.push_back(c)
/s.append(1, c)
/s += 'c'
三种的实现方式差不多,一般情况下 s t r i n g string string 类的+=
操作用的比较多,+=
操作不仅可以连接单个字符,还可以连接字符串。 -
对 s t r i n g string string 操作时,如果能够大概预估到放多少字符,可以先通过 r e s e r v e reserve reserve 把空间预留好。
5. 非成员函数
这里主要介绍的是 s t r i n g string string 类的运算符重载:
函数名称 | 功能说明 |
---|---|
o p e r a t o r + operator\ + operator + | 尽量少用,因为传值返回,导致深拷贝效率低 |
o p e r a t o r > > operator\ >> operator >>(重点) | 输入运算符重载 |
o p e r a t o r < < operator\ << operator <<(重点) | 输出运算符重载 |
g e t l i n e getline getline(重点) | 获取一行字符串,遇到 d e l i m delim delim 停止读入(默认是 ’ \0 ’ ) |
r e l a t i o n a l o p e r a t o r s relational\ operators relational operators(重点) | 大小比较 |
g e t l i n e getline getline 这个函数可以获取一整行字符串,而 c i n cin cin 输入的字符串自动到空格结束。
因此, g e t l i n e getline getline 的也是非常有用的,要注意其第一个参数为输入流。
void Non_member_function_overloads()
{// 注意:string类对象支持直接用cin和cout进行输入和输出string s1, s2;getline(cin, s1);getline(cin, s2);cout << "s1:" << s1 << endl << "s2:" << s2 << endl;if (s1 < s2) cout << "s1 < s2" << endl;else if (s1 > s2) cout << "s1 > s2" << endl;else if (s1 == s2) cout << "s1 == s2" << endl;string s3 = s1 + " " + s2; cout << "s3:" << s3 << endl;
}
注意:这里字符串比较大小是按照字典序比较的(不是按照长度比较)。
六、vs 和 g++ 下 string 结构的说明(了解)
注意:下述结构是在 32 32 32 位平台下进行验证, 32 32 32 位平台下指针占 4 4 4 个字节。
1. vs 下 string 的结构
s t r i n g string string 总共占 28 28 28 个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 s t r i n g string string 中字符串的存储空间:
- 当字符串长度小于 16 16 16 时,使用内部固定的字符数组( B u f Buf Buf)来存放。
- 当字符串长度大于等于 16 16 16 时,从堆上开辟空间(动态申请空间)。
union _Bxty
{ // storage for small buffer or pointer to larger onevalue_type _Buf[_BUF_SIZE];pointer _Ptr;char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
这种设计也是有一定道理的:
1. 首先:大多数情况下字符串的长度都小于 16 16 16,那 s t r i n g string string 对象创建好之后,内部已经有了 16 16 16 个字符数组的固定空间,不需要通过堆创建,效率高。
2. 其次:还有一个 s i z e _ t size\_t size_t 字段保存字符串长度,一个 s i z e _ t size\_t size_t 字段保存从堆上开辟空间总的容量。
3. 最后:还有一个指针做一些其他事情。
4. 故总共占 16 + 4 + 4 + 4 = 28 16+4+4+4=28 16+4+4+4=28 个字节。
2. g++ 下 string 的结构
G G G++下, s t r i n g string string 是通过写时拷贝实现的, s t r i n g string string 对象总共占 4 4 4 个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
- 空间总大小( l e n g t h length length)
- 字符串有效长度( c a p a c i t y capacity capacity)
- 引用计数( r e f c o u n t refcount refcount)
- 指向堆空间的指针,用来存储字符串。
struct _Rep_base
{size_type _M_length;size_type _M_capacity;_Atomic_word _M_refcount;
};
七、string 类的拷贝问题
1. 浅拷贝
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
2. 深拷贝
用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
1. 传统写法:
//拷贝构造
string(const string& s)
{_str = new char[s._size + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}//赋值重载
string& operator = (const string& s)
{if (this != &s){delete[] _str;_str = new char[s._size + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}return *this;
}
2. 现代写法:
void swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}//拷贝构造
string(const string& s)
{string tmp(s._str); //调用构造函数swap(tmp);
}//赋值重载
string& operator = (string tmp) //直接传拷贝
{swap(tmp);return *this;
}
3. 写时拷贝(了解)
写时拷贝:在浅拷贝的基础之上增加了引用计数的方式来实现的。(一种拖延症)
由于深拷贝需要开空间,消耗大;而浅拷贝会有以下两个问题(以两个对象指向同一块空间为例):
-
同一块空间会被析构两次
-
一个修改会影响另一个
因此,引入了引用计数来优化深浅拷贝:
引用计数:用来记录资源使用者的个数。(有几个对象指向这个资源)
在构造时,将资源的计数给成 1 1 1,每增加一个对象使用该资源,就给计数增加 1 1 1,当某个对象被销毁时,先给该计数减 1 1 1,然后再检查是否需要释放资源,如果计数为 1 1 1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。
总结:写时拷贝使其只有在被用到的时候才会进行深拷贝,避免了许多不必要的消耗。(博弈:拷贝后,如果不写就赚了)
八、string 类的模拟实现
对于 C C C++ 官方文档中,对于 s t r i n g string string 的实现是对 b a s i c _ s t r i n g basic\_string basic_string 的重命名,主要是用来处理 U T F − 8 UTF-8 UTF−8 的 c h a r char char 类型数据的。这里模拟实现的是 s t r i n g string string 类( c h a r char char 类型顺序表)的大部分基本功能,依然采用声明与定义分离的形式,最后进行测试(一些短小且频繁调用的函数不用分离,相当于设置成内联函数)。
总共分为三个文件: s t r i n g . h string.h string.h 用来存放 s t r i n g string string 类的声明; s t r i n g . c p p string.cpp string.cpp 用来定义声明的函数; t e s t . c p p test.cpp test.cpp 用来测试。
注意:这里每个文件中的 s t r i n g string string 都是自己定义的,为了和 s t d : : s t r i n g std::string std::string 作区分,我们要单独创建一个命名空间,这里我用的是 n a m e s p a c e y b c namespace\ ybc namespace ybc 。
- s t r i n g . h string.h string.h
#define _CRT_SECURE_NO_WARNINGS #pragma once#include<iostream>#include<assert.h>#include<string>using namespace std;namespace ybc
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin() const{return _str;}iterator end() const{return _str + _size;}const_iterator cbegin() const{return _str;}const_iterator cend() const{return _str + _size;}//短小且频繁调用的函数,直接定义到类里面,默认为内联inline/*string():_str(new char[1] {'\0'}),_size(0),_capacity(0){}*/string(const char* str = ""){_size = strlen(str);_str = new char[strlen(str) + 1];_capacity = _size; //capacity不包含'\0'strcpy(_str, str);}/*string(const string& s){_str = new char[s._size + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}*/void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}string(const string& s){string tmp(s._str); //调用构造函数swap(tmp);}/*string& operator = (const string& s){if (this != &s){delete[] _str;_str = new char[s._size + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}return *this;}*/string& operator = (string tmp){swap(tmp);return *this;}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const char* c_str() const{return _str;}void clear(){_str[0] = '\0';_size = 0;}size_t size() const{return _size;}size_t length() const{return _size;}size_t capacity() const{return _size;}bool empty() const{return _size == 0;} char& operator [] (size_t pos){assert(pos < _size);return _str[pos];}const char& operator [] (size_t pos) const{assert(pos < _size);return _str[pos];}//声明和定义分离void reserve(size_t n);void push_back(char c);void append(const char* s);string& operator += (char c);string& operator += (const char* s);void insert(size_t pos, char c);void insert(size_t pos, const char* s);void erase(size_t pos, size_t len = npos);size_t find(char c, size_t pos = 0);size_t find(const char* s, size_t pos = 0);string substr(size_t pos = 0, size_t len = npos);private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;static const size_t npos;};bool operator < (const string& s1, const string& s2);bool operator <= (const string& s1, const string& s2);bool operator > (const string& s1, const string& s2);bool operator >= (const string& s1, const string& s2);bool operator == (const string& s1, const string& s2);bool operator != (const string& s1, const string& s2);ostream& operator << (ostream& out, const string& s);istream& operator >> (istream& in, string& s);istream& getline(istream& in, string& s);
}
- s t r i n g . c p p string.cpp string.cpp
#include"string.h"namespace ybc
{const size_t string::npos = -1;void string::reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void string::push_back(char c){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = c;_str[_size] = '\0';
}void string::append(const char* s){size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}strcpy(_str + _size, s);_size += len;}string& string::operator += (char c){push_back(c);return *this;}string& string::operator += (const char* s){append(s);return *this;}void string::insert(size_t pos, char c){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}//挪动数据size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];end--;}_str[pos] = c;_size++;}void string::insert(size_t pos, const char* s){assert(pos <= _size);size_t len = strlen(s);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] = s[i];}_size += len;}void string::erase(size_t pos, size_t len){assert(pos < _size);if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{for (size_t i = pos + len; i < _size; i++){_str[i - len] = _str[i];}_size -= len;}}size_t string::find(char c, size_t pos){assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;}}return npos;}size_t string::find(const char* s, size_t pos){assert(pos < _size);const char* ptr = strstr(_str + pos, s);if (ptr == nullptr){return npos;}else{return ptr - _str;}}string string::substr(size_t pos, size_t len){assert(pos < _size);//更新一下lenif (len > _size - pos){len = _size - pos;}string sub;reserve(len);for (size_t i = 0; i < len; i++){sub += _str[pos + i];}return sub;}bool operator < (const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) < 0;}bool operator == (const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) == 0;}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);}bool operator != (const string& s1, const string& s2){return !(s1 == s2);}ostream& operator << (ostream& out, const string& s){for (auto c : s){out << c;}return out;}istream& operator >> (istream& in, string& s){s.clear();//避免多次扩容的优化const int N = 1024;char buff[N];int i = 0;char c; in.get(c);while (c != ' ' && c != '\n'){buff[i++] = c;if (i == N - 1){buff[i] = '\0';s += buff;i = 0;}in.get(c);}if (i > 0){buff[i] = '\0';s += buff;}return in;}istream& getline(istream& in, string& s){s.clear();//避免多次扩容的优化const int N = 1024;char buff[N];int i = 0;char c; in.get(c);while (c != '\n'){buff[i++] = c;if (i == N - 1){buff[i] = '\0';s += buff;i = 0;}in.get(c);}if (i > 0){buff[i] = '\0';s += buff;}return in;}
}
- t e s t . c p p test.cpp test.cpp
#include"string.h"namespace ybc
{void test1(){string s("hello world");cout << s.c_str() << endl;for (int i = 0; i < s.size(); i++){s[i] += 2;}cout << s.c_str() << endl;for (auto& i : s){i -= 2;cout << i;}cout << endl;for (string::iterator it = s.begin(); it != s.end(); ++it){cout << *it;}cout << endl;}void test2(){string s("hello ");s.append("ybc ");s += "nb plus !";s.insert(0, "--------");s += "--------";s.erase(0, 5);s.erase(s.size() - 5);cout << s.c_str() << " " << s.find(" ", 5) << endl;}void test3(){string s = "hello world";string ss = s.substr(s.find("w"));cout << ss.c_str() << endl;//析构2次string copy(s);cout << copy.c_str() << endl;string sss;sss = s;cout << sss.c_str() << endl;}void test4(){string s1 = "hello", s2 = "world";cout << "s1:" << s1 << endl;cout << "s2:" << s2 << endl;if (s1 < s2) cout << "s1 < s2" << endl;if (s1 > s2) cout << "s1 > s2" << endl;if (s1 <= s2) cout << "s1 <= s2" << endl;if (s1 >= s2) cout << "s1 >= s2" << endl;if (s1 == s2) cout << "s1 == s2" << endl;if (s1 != s2) cout << "s1 != s2" << endl;}void test5(){string s = "hello world";cin >> s;getline(cin, s);cout << s << endl;}void test6(){string s = "hello world";string ss = s;string sss;sss = ss;cout << s << endl << ss << endl << sss << endl;}void test7(){string s = "hello world";string ss = "xxxxxxxxxxxxxxxxxxxxxxx";std::swap(s, ss); //C++98:3次深拷贝(不推荐)cout << s << endl << ss << endl;s.swap(ss);cout << s << endl << ss << endl;}
}int main()
{//ybc::test1();//ybc::test2();//ybc::test3();//ybc::test4();//ybc::test5();//ybc::test6();ybc::test7();return 0;
}
总结
以上就是对 C C C++ 标准库 S T L STL STL 的引入,以及介绍了 C C C++ 标准库中 s t r i n g string string 类的基本使用以及一些 C C C++11 的新特性,为后续学习正式的 S T L STL STL 容器做好铺垫。最后,将 s t r i n g string string 类的常用功能进行了模拟实现,对 s t r i n g string string 类的使用和底层有了更深刻的认识。