C++ string实现原理

C++程序员编码过程中经常会使用string(wstring)类,你是否思考过它的内部实现细节。比如这个类的迭代器是如何实现的?对象占多少字节的内存空间?内部有没有虚函数?内存是如何分配的?构造和析构的成本有多大?笔者综合这两天阅读的源代码及个人理解简要介绍之,错误的地方望读者指出。

首先看看string和wstring类的定义:

typedef basic_string<char, char_traits<char>, allocator<char> > string;
typedef basic_string<wchar_t, char_traits<wchar_t> allocator<wchar_t> > wstring;

从这个定义可以看出string和wstring分别是模板类basic_string对char和wchar_t的特化。

再看看basic_string类的继承关系(类方法未列出):

最顶层的类是_Container_base,它也是STL容器的基类,包含一个_Iterator_base*的成员,指向容器的最开始的元素,这样就能遍历容器了。

这个类其实只定义了两个函数

void _Orphan_all() const;	// orphan all iterators
void _Swap_all(_Container_base_secure&) const;	// swaps all iterators

_String_base类没有数据成员,只定义了异常处理的三个函数:

static void _Xlen();	// report a length_error
static void _Xran();	// report an out_of_range error
static void _Xinvarg();

_String_val包含一个alloctor的对象,这个类也非常简单,除了构造函数没有定义其它函数。

上面三个基类都定义得很简单,而basic_string类的实现非常复杂。不过它的设计和大多数标准库一样,把复杂的功能分成几部分去实现,充分体现了模块的低耦合。

迭代器有关的操作交给_String_iterator类去实现,元素相关的操作交给char_traits类去实现,内存分配交给allocator类去实现。

_String_iterator类的继承关系如下图:

这个类实现了迭代器的通用操作,比如:

reference  operator*() const;
pointer operator->() const
_String_iterator & operator++()
_String_iterator operator++(int)
_String_iterator& operator--()
_String_iterator operator--(int)
_String_iterator& operator+=(difference_type _Off)
_String_iterator operator+(difference_type _Off) const
_String_iterator& operator-=(difference_type _Off)
_String_iterator operator-(difference_type _Off) const
difference_type operator-(const _Mybase& _Right) const
reference operator[](difference_type _Off) const

有了迭代器的实现,就可以很方便的使用算法库里面的函数了,比如将所有字符转换为小写:

string s("Hello String");
transform(s.begin(), s.end(), s.begin(), tolower);

char_traits类图如下:

这个类定义了字符的赋值,拷贝,比较等操作,如果有特殊需求也可以重新定义这个类。

allocator类图如下:

这个类使用new和delete完成内存的分配与释放等操作。你也可以定义自己的allocator,msdn上有介绍哪些方法是必须定义的。

再看看basic_string类的数据成员:

_Mysize表示实际的元素个数,初始值为0;

_Myres表示当前可以存储的最大元素个数(超过这个大小就要重新分配内存),初始值是_BUF_SIZE-1;

_BUF_SIZE是一个enum类型:

enum
{	// length of internal buffer, [1, 16]
	_BUF_SIZE = 16 / sizeof (_Elem) < 1 ? 1: 16 / sizeof(_Elem)
};

从这个定义可以得出,针对char和wchar_t它的值分别是16和8。

_Bxty是一个union:

union _Bxty
{	// storage for small buffer or pointer to larger one
	_Elem _Buf[_BUF_SIZE];
	_Elem *_Ptr;
} _Bx;

为什么要那样定义_Bxty呢,看下面这段代码:

_Elem * _Myptr()
{	// determine current pointer to buffer for mutable string
	return (_BUF_SIZE <= _Myres ? _Bx._Ptr : _Bx._Buf);
}

这个函数返回basic_string内部的元素指针(c_str函数就是调用这个函数)。

所以当元素个数小于_BUF_SIZE时不用分配内存,直接使用_Buf数组,_Myptr返回_Buf。否则就要分配内存了,_Myptr返回_Ptr。

不过内存分配策略又是怎样的呢,像vector那样每次增加一倍?答案是否定的,看下面这段代码:

void _Copy(size_type _Newsize, size_type _Oldlen)
{	// copy _Oldlen elements to newly allocated buffer
	size_type _Newres = _Newsize | _ALLOC_MASK;
	if (max_size() < _Newres)
		_Newres = _Newsize;	// undo roundup if too big
	else if (_Newres / 3 < _Myres / 2 && _Myres <= max_size() - _Myres / 2)
		_Newres = _Myres + _Myres / 2;	// grow exponentially if possible
	//other code
}

_ALLOC_MASK的值是_BUF_SIZE-1。这段代码看起来有点复杂,简单描述就是:最开始_Myres每次增加_BUF_SIZE,当值达到一定大小时每次增加一半。

针对char和wchar_t,每次分配内存的临界值分别是(超过这些值就要重新分配):

char:15,31,47,70,105,157,235,352,528,792,1188,1782。。。

wchar_t:7, 15, 23, 34, 51, 76, 114, 171, 256, 384, 576, 864, 1296, 1944。。。

重新分配后都会先将旧的元素拷贝到新的内存地址。所以当处理一个长度会不断增长而又大概知道最大大小时可以先调用reserve函数预分配内存以提高效率。

string类占多少字节的内存空间呢?

_Container_base含有一个指针,4字节。_String_val类含有一个allocator对象。string类使用默认的allocator类,这个类没有数据成员,不过按字节对齐的原则,它占4字节。basic_string类的成员加起来是24,所以总共是32字节。wstring也是32字节,至于原因文中已经分析。

综上所述:string和wstring类借助_String_iterator实现迭代器操作,都占32字节的内存空间,没有虚函数,构造和析构开销较低,内存分配比较灵活。

实际使用string类时也有很多不方便的地方,笔者写了一个扩展类,欢迎提出宝贵意见。

扩展类链接:http://blog.csdn.net/passion_wu128/article/details/38354541

C++ string实现原理,布布扣,bubuko.com

时间: 2024-12-20 20:02:49

C++ string实现原理的相关文章

String类原理分析及部分方法

//String类原理分析及部分方法 //http://www.cnblogs.com/vamei/archive/2013/04/08/3000914.html //http://www.cnblogs.com/YSO1983/archive/2009/12/07/1618564.html //String类包含在java.lang包中,这个包在java的时候就自动import //String类是唯一一个不需要new关键词来创建对象的类. public class Test{ public

Java中String创建原理深入分析

1.  使用new关键字 String s1 = new String("ab");  // 2.  使用字符串常量直接赋值 String s2 = "abc"; 3.  使用"+"运算符进行字符串连接 String s3 = "abc" + "d"; String s4 = s3 + 5;  //abcd5 常量池概念: Java运行时会维护一个String Pool(String池), 也叫"

Python string interning原理

原文链接:The internals of Python string interning 由于本人能力有限,如有翻译出错的,望指明. 这篇文章是讲Python string interning是如何工作的,代码基于CPython2.7.7这个版本. 前一段时间,我向同事解释了python的buil-in函数 intern背后到底做了什么.我给他看了下面这个例子: >>> s1 = 'foo!' >>> s2 = 'foo!' >>> s1 is s2

第17章 string基本字符序列容器

/* 第17章 string基本字符序列容器 17.1 string技术原理 17.2 string应用基础 17.3 本章小结 */ // 第17章 string基本字符序列容器 // 17.1 string技术原理 -------------------------------------------------------------------------------------- // 17.2 string应用基础 ----------------------------------

Delphi String

Delphi结构类型包含String字符串使用需要注意的地方 有些人提倡当在结构里面包含字符串类型的时候最好采用定长的方式. 比如像下面这样: TCSInfo = record Cs_Str    :String; Count :Integer; CS_Str2 :String; end; 如果Cs_Str在这里不采用定长的方式,那么当采用SizeOf()获取结构的大小的时候,可能得不到正确的大小值. 另外这个首位成员变量的值,很有可能出现随机值的情况,就是你已经给它赋了值,在当前的显示是正确的

Java中String的设计

String应用简介 前言 String字符串在Java应用中使用非常频繁,只有理解了它在虚拟机中的实现机制,才能写出健壮的应用,本文使用的JDK版本为1.8.0_111. 常量池 Java代码被编译成class文件时,会生成一个常量池(Constant pool)的数据结构,用以保存字面常量和符号引用(类名.方法名.接口名和字段名等). 很简单的一段代码,通过命令 javap -verbose 查看class文件中 Constant pool 实现: 通过反编译出来的字节码可以看出字符串 "t

android开发 WriteUTF与readUTF 原理

今晚上写代码玩,用到java.io.RandomAccessFile.writeUTF(String)函数,而文件默认保存为gbk,显然是乱码.突然想起来去看看存储编码规则,就去找了些文章了解writeUTF(String)的原理,在此记录. 首先需要弄明白unicode与utf8的表示规则,搜到@Feng哥的一篇文章<字符编码笔记:ASCII,Unicode和UTF-8>,写的很明白,在此招录一段: | Unicode符号范围 | UTF-8编码方式 | 0000 0000-0000 007

C++基础和STL,Effective C++笔记

C++基础 static static变量存储在静态数据区 相对于function:在函数内,变量,内存只被分配一次,多次调用值相同 相对于其他模块(.c文件):变量和函数,不能被模块外其他函数访问(private) 相对于类:类中的static变量和函数属于整个类,而不是对象 全局变量 VS 全局静态变量 若程序由一个源文件构成时,全局变量与全局静态变量没有区别. 若程序由多个源文件构成时,全局变量与全局静态变量不同:全局静态变量使得该变量成为定义该变量的源文件所独享,即:全局静态变量对组成该

转载--C++的反思

转载自http://blog.csdn.net/yapian8/article/details/46983319 最近两年 C++又有很多人出来追捧,并且追捧者充满了各种优越感,似乎不写 C++你就一辈子是低端程序员了,面对这种现象,要不要出来适时的黑一下 C++呢?呵呵呵. 咱们要有点娱乐精神,关于 C++的笑话数都数不清: 笑话:C++是一门不吉祥的语言,据说波音公司之前用ADA为飞机硬件编程,一直用的好好的,后来招聘了一伙大学生,学生们说我靠还在用这么落后的语言,然后换成C++重构后飞机就