探究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容器的基类,Debug下包含一个_Iterator_base*的成员,指向容器的最开始的元素,这样就能遍历容器了,并定义了了两个函数

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

Release下_Container_base只是一个空的类。

_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。

不过内存分配策略又是怎样的呢?看下面这段代码:

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 Debug下含有一个指针,4字节,Release下是空类,0字节。_String_val类含有一个allocator对象。string类使用默认的allocator类,这个类没有数据成员,不过按字节对齐的原则,它占4字节。basic_string类的成员加起来是24,所以总共是32字节(Debug)或28字节(Relase)。wstring也是32或28,至于原因文中已经分析。

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

扩展string类
在实际开发过程中,C++string类使用起来有很多不方便的地方,笔者根据根据这些不足简单的扩展了这个类,如增加与数字之间的相互转化和格式化字符串。不足的地方望指正。读者也可以根据自己需求继续扩展。

头文件:exstring.h

/*
Author: wuqiang
Email: debugroot@126.com
Description:exstring is a subclass of basic_string.It is added some userful
operations,such as toUpper,toLower,toNumber,fromNumber,format,etc.It can also
convert between basic_string seamlessly,which is very important for compatibility.
And it is almostly a wrapper of some C++ standard library,so there should be no bugs.
If you find some,please let me known.It is totally free,you can use it in any way you desire.
*/
#pragma once 

#include <string>
#include <stdarg.h>
#include <algorithm>
#include <sstream>
#include <iomanip> 

using namespace std; 

#ifndef INLINE
#define INLINE inline
#endif //INLINE 

static ios_base::fmtflags BaseFlag(int base)
{
  return (base == 16) ? (ios_base::hex) :
    ( (base == 8) ? (ios_base::oct) : (ios_base::dec) );
} 

template<class _Elem> struct ex_char_traits
{
}; 

template<> struct ex_char_traits<char>
{
  static INLINE int ct_vscprintf(const char* format, va_list argptr )
  {
    return _vscprintf(format, argptr);
  }
  static INLINE int ct_vstprintf_s(char* buffer, size_t numberOfElements,
    const char* format, va_list argptr)
  {
    return vsprintf_s(buffer, numberOfElements, format, argptr);
  }
}; 

template<> struct ex_char_traits<wchar_t>
{
  static INLINE int ct_vscprintf(const wchar_t* format, va_list argptr )
  {
    return _vscwprintf(format, argptr);
  }
  static INLINE int ct_vstprintf_s(wchar_t* buffer, size_t numberOfElements,
    const wchar_t* format, va_list argptr)
  {
    return vswprintf_s(buffer, numberOfElements, format, argptr);
  }
}; 

template<class _Elem, class _Traits, class _Ax, class Type>
Type ConvertToNumber(basic_stringstream<_Elem, _Traits, _Ax>& ss,
           Type t, int base)
{
  ss.setf(BaseFlag(base), ios_base::basefield);
  ss >> t;
  return t;
} 

template<class _Elem, class _Traits, class _Ax>
float ConvertToNumber(basic_stringstream<_Elem, _Traits, _Ax>& ss,
           float t, int/*ignore base*/)
{
  ss >> t;
  return t;
} 

template<class _Elem, class _Traits, class _Ax>
double ConvertToNumber(basic_stringstream<_Elem, _Traits, _Ax>& ss,
            double t, int/*ignore base*/)
{
  ss >> t;
  return t;
} 

template<class _Elem, class _Traits, class _Ax, class _ExTraits>
class basic_exstring : public basic_string<_Elem, _Traits, _Ax>
{
public:
  typedef basic_exstring<_Elem, _Traits, _Ax, _ExTraits> _Myt;
  typedef basic_string<_Elem, _Traits, _Ax> _Mybase; 

#pragma region "constructor" 

  //所有构造函数的行为同basic_string 

  explicit INLINE _Myt(const _Ax& al = _Ax())
    :_Mybase(al)
  {
  }
  INLINE _Myt(const _Myt& rhs)
    :_Mybase(rhs)
  {
  }
  INLINE _Myt(const _Myt& rhs, size_type pos, size_type n,const _Ax& al = _Ax())
    :_Mybase(rhs, pos, n, al)
  {
  }
  INLINE _Myt(const _Elem *s, size_type n, const _Ax& al = _Ax())
    :_Mybase(s, n, al)
  {
  }
  INLINE _Myt(const _Elem *s, const _Ax& al = _Ax())
    :_Mybase(s, al)
  {
  }
  INLINE _Myt(size_type n, _Elem c, const _Ax& al = _Ax())
    :_Mybase(n, c, al)
  {
  }
  INLINE _Myt(const_iterator first, const_iterator last,const _Ax& al = _Ax())
    :_Mybase(first, last, al)
  {
  } 

  //string(wstring)转化为exstring(exwstring)
  INLINE _Myt(const _Mybase& base)
    :_Mybase(base)
  { 

  }
#pragma endregion //constructor 

#pragma region "general operation" 

  //所有字符转为大写,改变自身
  _Myt& toUpper()
  {
    transform(begin(), end(), begin(), toupper);
    return *this;
  } 

  //所有字符转为大写,不改变自身
  _Myt toUpper() const
  {
    _Myt s;
    transform(begin(), end(), s.begin(), toupper);
    return s;
  } 

  //所有字符转为小写,改变自身
  _Myt& toLower()
  {
    transform(begin(), end(), begin(), tolower);
    return *this;
  } 

  //所有字符转为大写,不改变自身
  _Myt toLower() const
  {
    _Myt s(_Mysize, _Elem());
    transform(begin(), end(), s.begin(), tolower);
    return s;
  } 

  //将所有oldStr替换为newStr
  _Myt& replace(const _Myt& oldStr, const _Myt& newStr)
  {
    if (oldStr.empty())
      return *this;
    size_type index;
    while ( (index = find(oldStr)) != npos )
      _Mybase::replace(index, oldStr.size(), newStr);
    return *this;
  } 

  //删除左边所有包含在target中的字符
  _Myt& trimLeft(const _Myt& target)
  {
    while (!empty() && (target.find(*begin()) != npos))
      erase(begin());
    return *this;
  } 

  //删除右边所有包含在target中的字符
  _Myt& trimRight(const _Myt& target)
  {
    while (!empty() && target.find(*rbegin()) != npos)
      erase(--end());
    return *this;
  } 

  //返回左边count个字符,count大于总长度则返回整个字符串
  _Myt left(size_type count) const
  {
    return substr( 0, count );
  } 

  //返回右边count个字符,count大于总长度则返回整个字符串
  _Myt right(size_type count) const
  {
    return substr( _Mysize < count ? 0 : _Mysize - count );
  } 

  //忽略大小写判断两个字符串是否相等
  int compareNoCase(const _Myt& rhs) const
  {
    return toLower().compare(rhs.toLower());
  } 

  //判断字符串是否以制定字符串开头
  bool beginWith(const _Myt& rhs) const
  {
    return find(rhs) == size_type(0);
  } 

  //判断字符串是否以制定字符串结尾
  bool endWith(const _Myt& rhs) const
  {
    if(rhs.size() > _Mysize)
      return false;
    return compare(_Mysize - rhs.size(), rhs.size(), rhs) == 0;
  }
#pragma endregion //general operation 

#pragma region "convert between numbers" 

  //将字符串转为数字
  //base:进制数。可以为8,10,16,如果其它值则强制为10。浮点数则忽略此参数
  template<typename T>
  T toNumber (int base = 10) const
  {
    T t = T();
    basic_stringstream<_Elem, _Traits, _Ax> ss(_Myptr());
    return ConvertToNumber<_Elem, _Traits, _Ax>(ss, t, base);
  } 

  //将整数转化为字符串
  //base:进制数。可以为8,10,16,如果其它值则强制为10
  template<typename T>
  static _Myt fromNumber ( T number, int base = 10 )
  {
    basic_stringstream<_Elem, _Traits, _Ax> ss;
    ss.setf(BaseFlag(base), ios_base::basefield);
    ss << number;
    return ss.str();
  } 

  //将float转化为字符串
  //f:格式化参数。可以为'f','e','E','g','G'。'f'为定点数,'e'或'E'表示科学计数法
  // 'g'或‘G'表示格式化为定点数或科学计数法,看哪一个表示方便。
  //prec:小数点后的位数(定点数表示法)或总的有效位数(科学计数法)
  static _Myt fromNumber ( float number, _Elem f = _Elem('g'), int prec = 6 )
  {
    return fromNumber(static_cast<double>(number), f, prec);
  } 

  //将double转化为字符串,参数解释同上
  static _Myt fromNumber ( double number, _Elem f = _Elem('g'), int prec = 6 )
  {
    basic_stringstream<_Elem, _Traits, _Ax> ss;
    ss << setprecision(prec);
    if ( _Traits::eq(f, _Elem('f')) )
      ss << setiosflags(ios_base::fixed);
    else if ( _Traits::eq(f, _Elem('e')) || _Traits::eq(f, _Elem('E')) )
      ss << setiosflags(ios_base::scientific);
    ss << number;
    return ss.str();
  }
#pragma endregion //convert between numbers 

#pragma region "format string" 

  //将szFormat格式化为字符串,参数解释同sprintf
  void format(const _Elem* szFormat, ...)
  {
    if(!szFormat)
      return;
    va_list argList;
    va_start(argList, szFormat);
    formatV(szFormat, argList);
    va_end(argList);
  } 

  //将szFormat格式化为字符串,参数解释同sprintf
  void formatV(const _Elem* szFormat, va_list argList)
  {
    if(!szFormat)
      return;
    int nLength = _ExTraits::ct_vscprintf(szFormat, argList);
    if(nLength < 0)
      return;
    resize(nLength);
    _ExTraits::ct_vstprintf_s(_Myptr(), nLength + 1, szFormat, argList);
    va_end(argList);
  }
#pragma endregion //format string
}; 

typedef basic_exstring<char, char_traits<char>,
  allocator<char>, ex_char_traits<char> > exstring; 

typedef basic_exstring<wchar_t, char_traits<wchar_t>,
  allocator<wchar_t>, ex_char_traits<wchar_t> > exwstring;

使用举例:

#include <iostream>
#include <tchar.h>
#include "exstring.h" 

#ifdef _UNICODE
typedef exwstring tstring;
#define tcout wcout
#else
typedef exstring tstring;
#define tcout cout
#endif //_UNICODE 

int main(int argc, char* argv[])
{
  tstring s(_T("\t Hello ExString\r\n"));
  tcout << _T("result of triming left:") << s.trimLeft(_T("\t ")) << endl;
  tcout << _T("result of triming right:") << s.trimRight(_T("\r\n")) << endl;
  tcout << _T("result of compare") << s.compareNoCase(_T("hello exstring")) << endl;
  tcout << _T("result of converting to upper:") << s.toUpper() << endl;
  tcout << _T("result of converting to lower:") << s.toLower() << endl; 

  tcout << _T("the left 5 chars:") << s.left(5) << endl;
  tcout << _T("the right 8 chars:") << s.right(8) << endl;
  tcout << _T("result of appending:") << s.append(_T(",exstring is practical")) << endl;
  tcout << _T("result of replacing:") << s.replace(_T("exstring"), _T("Exstring")) << endl; 

  s.format(_T("sizeof(%s) is %d(0x%x)"), _T("exstring"), sizeof(exstring), sizeof(exstring));
  tcout << _T("result of formating:") << s << endl; 

  tcout << tstring(_T("0xFF")).toNumber<int>(16) << endl;
  tcout << tstring(_T("-1")).toNumber<unsigned __int64>() << endl;
  tcout << tstring(_T("12.3456789")).toNumber<float>() << endl; 

  tcout << tstring::fromNumber(255) << endl;
  tcout << _T("0x") << tstring::fromNumber(__int64(-1), 16).toUpper() << endl;
  tcout << tstring::fromNumber(12.3456789, _T('f'), 4) << endl;
  tcout << tstring::fromNumber(12.3456789, _T('E'), 4) << endl; 

  return 0;
}

输出:

(0)

相关推荐

  • 详解C++中实现继承string类的MyString类的步骤

    昨天师兄又出了道测试题,让我们实现类似于string类的没有MyString类,刚开始很头疼,可是真正在自己写代码的时候又很兴奋的发现,这个过程真的是个很宝贵的机会,让我又有机会可以很好的熟悉回顾C++的很多知识-类设计,构造析构函数,成员函数,友元函数,引用,重载,字符串操作,动态内存分布.....于是昨天花了半天时间写了300多行代码,并认真的进行了相关测试.修改和总结.因为内容有点丰富,所以想分几次写出来,条理也清楚些. 类的空间分配:类给它的每个对象都分配了独立的空间去存储它的数据成员,

  • C# 为String类型增加方法详解

    namespace MyExtensionMethods { public static class MyExtensions { public static int MyGetLength(this System.String target) { return target.Length; } } } 使用时,需要引入这个名字空间,引用如下: string str = "dafasdf"; int len = str.MyGetLength(); 以上这篇C# 为String类型增加

  • C++中的string类的用法小结

    相信使用过MFC编程的朋友对CString这个类的印象应该非常深刻吧?的确,MFC中的CString类使用起来真的非常的方便好用.但是如果离开了MFC框架,还有没有这样使用起来非常方便的类呢?答案是肯定的.也许有人会说,即使不用MFC框架,也可以想办法使用MFC中的API,具体的操作方法在本文最后给出操作方法.其实,可能很多人很可能会忽略掉标准C++中string类的使用.标准C++中提供的string类得功能也是非常强大的,一般都能满足我们开发项目时使用.现将具体用法的一部分罗列如下,只起一个

  • 一个string类的简单实现案例

    string类中使用到了赋值构造函数.复制构造函数.构造函数.默认构造函数.析构函数.重载操作符等一些类操作 class String { public: String() { data = new char[1]; //这里为什么使用new char[1]呢,虽然是一个字符,这是为了和析构函数保持对称,因为在别的构造函数中使用的char[] data[0]='\0'; length = 0; } String(const char* str) { length = strlen(str); d

  • 探究C++中string类的实现原理以及扩展使用

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

  • 代码分析c++中string类

    一:回顾 (1)c++中的string类是在面试中和笔试中经常考的题目: 工程代码免费下载 string类的自行实现 (2)c++中的string类和fstream类合起来是处理外部数据的利器: (3)string类经常用到find find_first_of find_first_not_of find_last_of find_last_not_of substr replace等,以及联合使用来达到java中的split和trim (4) 使用friend 仅仅是在类中进行声明的非内部 却

  • java String类功能、原理与应用案例【统计、判断、转换等】

    本文实例讲述了java String类功能.原理与应用.分享给大家供大家参考,具体如下: String构造方法 package cn.itcast_01; /* * 字符串:就是由多个字符组成的一串数据.也可以看成是一个字符数组. * 通过查看API,我们可以知道 * A:字符串字面值"abc"也可以看成是一个字符串对象. * B:字符串是常量,一旦被赋值,就不能被改变. * * 构造方法: * public String():空构造 * public String(byte[] by

  • 关于Java中String类字符串的解析

    目录 一.前言 二.String类概述 三.字符串的特点 四.String 构造方法 五.String类对象的特点 六.比较字符串的方法 七.判断两个字符串地址是否相等 一.前言 在java中,和C语言一样,也有关于字符串的定义,并且有他自己特有的功能,下面我们一起来学习一下. 二.String类概述 string在软件包java.lang下,所以不需要导包. String字符串是java中的重点,String 类表示字符串类 ,所有的字符串(如"adf")都属于 此类,也就是说有&q

  • 详解Java中String类的各种用法

    目录 一.创建字符串 二.字符.字节与字符串的转换 1.字符与字符串的转换 2.字节与字符串的转换 三.字符串的比较 1.字符串常量池 2.字符串内容比较 四.字符串查找 五.字符串替换 六.字符串拆分 七.字符串截取 八.String类中其它的常用方法 九.StringBuffer 和 StringBuilder 1.StringBuilder与StringBuffer的区别 2.StringBuilder与StringBuffer常用的方法 十.对字符串引用的理解 一.创建字符串 创建字符串

  • 详解C++中String类模拟实现以及深拷贝浅拷贝

    详解C++中String类模拟实现以及深拷贝浅拷贝 在C语言中/C++中,字符串是一个应用很广泛的类型,也是很基础的类型,C语言并没有直接处理字符串的操作而是采用字符指针和字符串数组进行操作,而在C++中标准库为我们封装了一个字符串的类供我们使用,使用需要#inlcude <string>头文件.我们也可以自己模拟实现一个简单的String类. 在模拟实现String类的过程中,不可避免的会遇到深拷贝浅拷贝的问题,下面就深拷贝浅拷贝做一个简介.所谓深拷贝浅拷贝,简单来说就是浅拷贝只是简单的将值

  • 分享C++面试中string类的一种正确写法

    具体来说: 能像 int 类型那样定义变量,并且支持赋值.复制. 能用作函数的参数类型及返回类型. 能用作标准库容器的元素类型,即 vector/list/deque 的 value_type.(用作 std::map 的 key_type 是更进一步的要求,本文从略). 换言之,你的 String 能让以下代码编译运行通过,并且没有内存方面的错误. 复制代码 代码如下: void foo(String x)  {  } void bar(const String& x)  {  } Strin

  • C#中String类常用方法汇总

    本文实例汇总了C#中String类常用方法.分享给大家供大家参考.具体分析如下: C#中的String类很有用,下面是一些它的常用方法的总结,如果灵活运用这些的话,String类就掌握的差不多了. .ToLower()    //转为小写字符串"AbC"-->"abc" .ToUpper()    //转为大写"AbC" -->"ABC" .Trim()       //去掉字符串首尾的空格"  abc

  • Java中String类使用方法总结

    一.Java中关于String类的常用方法 本文只用来自己做笔记,随便写写,方便自己理解,谢谢各位的指正.下面是摘抄慕课的一部分 1.使用 substring(beginIndex , endIndex) 进行字符串截取时,包括 beginIndex 位置的字符,不包括 endIndex 位置的字符. 2.字符串 str 中字符的索引从0开始,范围为 0 到 str.length()-1 3.使用 indexOf 进行字符或字符串查找时,如果匹配返回位置索引:如果没有匹配结果,返回 -1 4.整

  • Java中String类常用类型实例总结

    目录 1.创建字符串的方法 1.1构造 1.2引用对象  2.字符串的比较 3.字符串的不可改变性 4.数组转字符串  5.判断是否是数字字符串  isNumberChar(  )  6.字节变字符串  7.字符串的查找 8.字符的替换 9.字符串的分割 9.1以单个符号来分割  9.2多个分隔符分割  9.3特殊符号分割 10.提取子串str.substring(  )  11.去空格 12.字符串的拼接 13.StringBuffer   13.1字符串的拼接 13.2方法的返回类型 总结

随机推荐