string的内存分配引发的思考

今天突然在想string的内存分配是怎样的呢?通过查阅《C++primer》和网上资料以及做实验,对string这个家伙更有进一步的认识了。

一、初始化string对象的方式

如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去。与之相反,如果不使用等号,则执行的是直接初始化。

string s1 = "hello";    //拷贝初始化
string s2("hello");     //直接初始化

通常当我们从一个const char*创建string时,指针指向数组必须以空字符结尾,拷贝操作遇到空字符时停止。如果我们还传递给构造函数一个计数值,数组就不必以空字符结尾。如果我们未传递计数值且数组也未以空字符结尾 ,或者给定计数值大于数组大小,则构造函数的行为是未定义的。

//n、len2和pos2都是无符号值
string s(cp,n)     //s是cp指向的数组中前n个字符的拷贝。此数组至少应该包含n个字符 
string s(s2,pos2)  //s是string s2从下标pos2开始的字符的拷贝。若pos2>s2.size(),构造函数的行为未定义
string s(s2,pos2,len2)   //s是string s2从下标pos2开始len2个字符的拷贝。若pos2>s2.size(),构造函数的行为未定义。不管len2的值是多少,构造函数至多拷贝s2.size()-pos2个字符。

e.g.

    const char* cp = "hello world!!!";  //以空字符结束的数组
    char noNull[] = {'h', 'i'};         //不是以空字符结束
    string s1(cp);  //拷贝cp中的字符直到遇到空字符;s1=="hello world!!!"
    string s2(noNull);  //未定义:noNull不是以空字符结束
    string s3(noNull,2);    //从noNull拷贝2个字符;s2=="hi"
    string s4(cp+6,5);      //从cp[6]开始拷贝5个字符;s4=="world"
    string s5(s1,6,5);      //从s1[6]开始拷贝5个字符;s5=="world"
    string s6(s1,6);        //从s1[6]开始拷贝,直至s1末尾;s6=="world!!!"
    string s7(s1,6,20);     //正确,只拷贝到s1末尾;s7=="world!!!"
    string s8(s1,16);       //抛出一个out_of_range异常

二、构造过程

还没看过string类的源码,不过通过实验大致可以知道经过string s = “1234”这个过程后,s维护着一个指针,这个指针的值是”1234”的地址。实验如下:

string s = "1234";
(gdb) x &s
0x7fffffffe300: 0xffffe310
(gdb) p &s[0]
$2 = (const__gnu_cxx::__alloc_traits::allocator >::value_type *) 0x7fffffffe310 "1234"
(gdb) p &s[1]
$3 = (const__gnu_cxx::__alloc_traits::allocator >::value_type *) 0x7fffffffe311 "234"

具体的本质是怎样的,需要等以后开始学习源码之后才能更熟悉了

三、string类的字符操作:
const char &operator[](int n)const;
const char &at(int n)const;
char &operator[](int n);
char &at(int n);
operator[]和at()均返回当前字符串中第n个字符的位置,但at函数提供范围检查,当越界时会抛出out_of_range异常,下标运算符[]不提供检查访问。
const char *data()const;//返回一个非null终止的c字符数组
const char *c_str()const;//返回一个以null终止的c字符串
int copy(char *s, int n, int pos = 0) const;//把当前串中以pos开始的n个字符拷贝到以s为起始位置的字符数组中,返回实际拷贝的数目

注意,针对c_str(),正确用法如下:

#include 
string s = "1234";
char c[s.length()+1];
strcpy(c,s.c_str());

最好不要这样使用

const char* c;
string s = "1234";
c = s.c_str();

因为s.c_str()返回的是一个指向”1234”的指针,若直接将指针赋给c,如果s被析构了(比如因为栈的销毁),那么c将会变成一个野指针,此时再使用*c将是非常危险的。

四、内存分配

在string对象构造后,内部就具有一个16Bytes的小缓存区,当符号中内容很小的时候,先使用这个16Bytes的小缓存区,当字符串内容增长的更大时,它才会在堆上重新分配新的缓存区。因此在栈上构造的string,当字符串尺寸小于16Bytes时,string.c_str()也是位于栈上的空间。当字符串尺寸不小于16Bytes时,则c_str就会位于堆上了。

通过实验就能知道了:

 string s1 = "0123456789ABCDEF";    //16个字符
 string s2 = "0123456789ABCDE";     //15个字符
(gdb) x &s1
0x7fffffffe300: 0x5576bc20
(gdb) p &s1[0]
$3 = (const__gnu_cxx::__alloc_traits::allocator >::value_type *) 0x55555576bc20 "0123456789ABCDEF"
(gdb) x &s2
0x7fffffffe2e0: 0xffffe2f0
(gdb) p &s2[0]
$4 = (const__gnu_cxx::__alloc_traits::allocator >::value_type *) 0x7fffffffe2f0 "0123456789ABCDE"

五、数值转换

最后再来看来string与其他数值数据之间的转换吧。

《C++primer》中提供了如下的数值转换函数:

to_string(val)        //一组重载函数,返回数值val的string表示,val可以是任何算术类型。

stoi(s,p,b)           //返回s的起始子串(表示整数内容)的数值,返回值
stol(s,p,b)           //类型分别是int、long、unsigned long、long 
stoul(s,p,b)          //long、unsigned long long。b表示转换所用的
stoll(s,p,b)          //基数,默认值为10。p是size_t指针,用来保存s中
stoull(s,p,b)         //第一个非数值字符的下标,p默认为0,即,函数不保存下标。

stof(s,p)             //返回s的起始子串(表示浮点数内容)的数值
stod(s,p)            //类型分别是float、double、long double。
stold(s,p)           //参数p的作用与整数转换函数中一样。

e.g.

    string s="hello1234h";
    int i = stoi((string(&s[6]))); //i = 234

除了以上方法,还可以通过stringstream来实现数值转换。

#include 
stringstream stream;
string s="123";    
int i;            
stream<>i;            //i=123
stream.clear();       //清除流状态
stream<1];
stream>>i;            //i=2

你可能感兴趣的:(C/C++)