“ 静水流深,只是无意起波澜。”
好久没有更新公众号了,是因为最近一直被需求压着喘不过气了,连续做了两个大需求,一直在加班,不过也积累很多要和大家分享的素材。今天想和大家分享的是标准库的string,因为最近有同事遇到string的问题,问我比较多,也就顺势看了一下string的底层实现,自己记录一下。
我希望大家读完这篇文章能够清楚一下问题:
string的常见的实现方式有几种?
string类的内部结构是什么样子?
string内部使用的内存是如何分配管理的?
string是如何拷贝构造,如何析构的,有引用计数的概念吗?
string都给我们提供了哪些关键的API?
string的data()和c_str()函数有什么区别?
std::to_string()是如何实现的?
0. 字符串常量
在开始string的源码分析之前,我们需要明确一些和字符串相关的概念。首先就是字符常量与字符串常量。
字符常量
字符常量指的是用单撇号括起来的一个字符就是字符型常量。如'a', '#', '%', 'D'都是合法的字符常量,在内存中占一个字节。
字符常量只能包括一个字符,如'AB' 是不合法的。
字符常量区分大小写字母,如'A'和'a'是两个不同的字符常量。
撇号(')是定界符,而不属于字符常量的一部分。如cout<
将一个字符常量存放到内存单元时,实际上并不是把该字符本身放到内存单元中去,而是将该字符相应的ASCII代码放到存储单元中。如果字符变量c1的值为'a',c2的值为'b',则在变量中存放的是'a'的ASCII码97,'b' 的ASCII码98。
既然字符数据是以ASCII码存储的,它的存储形式就与整数的存储形式类似。这样,在C++中字符型数据和整型数据之间就可以通用。一个字符数据可以赋给一个整型变量,反之,一个整型数据也可以赋给一个字符变量。也可以对字符数据进行算术运算,此时相当于对它们的ASCII码进行算术运算。
字符串常量
字符串常量指的是用双撇号括起来字符,字符串常量存储在静态存储区中,如"abc","Hello!","a+b","Li ping"等都是字符串常量。编译系统会在字符串最后自动加一个'\0'作为字符串结束标志。但'\0'并不是字符串的一部分,它只作为字符串的结束标志。例如,字符串常量"abc"在内存中占4个字节(而不是3个字节)。
要区分如下两种写法。第一个是将静态存储区的"abc"的地址赋给了s1,第二个是将静态存储区的"abc"赋值给了栈区的字符数组
char* s1 = "abc";char s2[4] = "abc";
1. STL string源码分析
string的内容主要在gcc源码的三个文件中:、、>。我们首先要明白,STL中的string是一个类型,我们用string声明的变量都是一个string类型的对象。因为中对string是这样定义的
typedef basic_string<char> string;// 其中basic_string的定义如下template<typename _CharT, typename _Traits = char_traits<_chart>, typename _Alloc = allocator<_chart> >class basic_string;
看到了吧,我们使用的string其实是模板类basic_string的特化版本basic_string<char>。对string的源码分析实际上就是对basic_string的源代码分析。basic_string源代码的简化版本如下
template<typename _CharT, typename _Traits, typename _Alloc>class basic_string{
// 保存字符串对象的内容的地址 _Alloc_hider _M_dataplus; // 字符串对象内容的长度 size_type _M_string_length; // _S_local_capacity是默认存储能力,存储数组的最大能存储字符数 // 区别于_M_string_length enum { _S_local_capacity = 15 / sizeof(_CharT) }; /** * 这里有个小技巧,用了union *