string的内存分配引发的思考

    xiaoxiao2021-03-25  75

    今天突然在想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<std::allocator<char> >::value_type *) 0x7fffffffe310 "1234" (gdb) p &s[1] $3 = (const__gnu_cxx::__alloc_traits<std::allocator<char> >::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.h> 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<std::allocator<char> >::value_type *) 0x55555576bc20 "0123456789ABCDEF" (gdb) x &s2 0x7fffffffe2e0: 0xffffe2f0 (gdb) p &s2[0] $4 = (const__gnu_cxx::__alloc_traits<std::allocator<char> >::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 <sstream> stringstream stream; string s="123"; int i; stream<<s; stream>>i; //i=123 stream.clear(); //清除流状态 stream<<s[1]; stream>>i; //i=2
    转载请注明原文地址: https://ju.6miu.com/read-33078.html

    最新回复(0)