原文地址:http://www.cppblog.com/mildcat1982/archive/2010/07/14/120390.aspx
1、在dev c++ IDE下,输入下面代码,检测string的实现机制。
1 #include < string> 2 using namespace std; 3 4 int main() 5 { 6 string str1 = "hello"; 7 string str2(str1); 8 9 printf( "str1 address:%p\n", str1.c_str());10 printf( "str2 address:%p\n", str2.c_str());11 printf( "\n" );12 13 str1[0];14 printf( "str1 address:%p\n", str1.c_str());15 printf( "str2 address:%p\n", str2.c_str());16 printf( "\n" );17 18 string str3(str1);19 printf( "str1 address:%p\n", str1.c_str());20 printf( "str3 address:%p\n", str3.c_str());21 printf( "\n" );22 23 string str4(str2);24 printf( "str2 address:%p\n", str2.c_str());25 printf( "str4 address:%p\n", str4.c_str());26 printf( "\n" );27 28 system("PAUSE");29 return EXIT_SUCCESS;30}
2、输出结果为:
1 /* output: 2 str1 address:0036101C 3 str2 address:0036101C 4 5 str1 address:0036103C 6 str2 address:0036101C 7 8 str1 address:0036103C 9 str3 address:0036105C10 11 str2 address:0036101C12 str4 address:0036101C 13*/
3、结果解析
3.1 见代码第9行和第10行,为什么str1和str2的地址一致? 因为string内部的实现机制为引用计数,string的拷贝构造函数并非真正的去进行字符串拷贝,只是将字符串的引用数加1。 str1.c_str()和str2.c_str()实际上指的还是同一个字符串,同一块内存。 3.2 见代码第14行和第15行,为什么经过str1[0]这个看似无关痛痒的一行代码后,str1和str2的地址不再一致了? 具体而言,是str1的地址变化了,str2的地址保持不变。str1[0]到底做了什么? string重载了[],一个是non-const版本,一个是const版本 char& operator[](size_t idx); const char& operator[](size_t idx)const; 在上面的代码中,调用的是non-const版本,由于这个函数版本身是无法得知代码上下文中调用自己是为了读还是为了写,也就是无法区分以下两种情况: cout << str[0];//读 str[0] = 'w'; // 写 理论上而言,在读时不需要在string内部对原字符串拷贝一份,只有在写时才需要对原字符串拷贝,由于无法区分读和写,或者说区分读和写比较困难, 所以我想DEV C++在string的实现上,应该是不区分读和写上下文,统一认为是写操作。 这样就造成一旦调用operator[],肯定在该函数内部进行了字符串拷贝操作。 从这个角度理解,str1的地址发生了变化,也是在情理之中的事情啦。 3.3、见代码第19行和第20行,为什么str1拷贝给str3后,两者的地址不一致? 是的,这个比较令人费解,尤其是考虑到str1刚拷贝给str2后,str1与str2的地址却是一致?貌似这是相悖的。 其实不然,仔细看一下,不难发现,str1是在调用完operator[]后才变成这个样子的。 看来operator[]不仅是进行了字符串拷贝,还对str1对象做了其他手脚。究竟是什么呢? 让我们先暂时撇开这个问题,看一下以下代码: 1 string s1( " test " ); 2 char * p = & s1[ 0 ]; 3 string s2(s1); 4 * p = ' T';
上述代码中,第2行取了s1[0]的地址,然后在未来的某一刻再通过指针更改s1[0]的取值,见第4行代码。
在未来的某一刻到来之前,所有对于是s1的拷贝,都不能进行引用计数,只能老老实实的进行字符串赋值,
否则一旦通过指针更改s1[0]的取值,那么所有s1的拷贝对象的值将都被修改。
假如operator[]的实现中只是进行了字符串拷贝,那么在上述第4行代码执行后,s1与s2的值将都被改变,即s1 = s2 = 'Test';
这显然是与事实不符的,正确的结果应该是s1 = 'Test',s2 = 'test';所以这就需要operator[]的实现中增加一个共享标志位,
用来表示该字符串是否可还可以被共享,每当调用operator[],operator[]都会将这个标志位置成非共享状态
(默认值为共享状态),一旦置成了非共享状态,那么将永远不会再还原!
所以凡是调用了operator[]函数的string对象,都将从共享状态转变成非共享状态,对于这种对象的拷贝,都无法进行引用计数。
再回到原来的那个问题,就可以理解为什么str1拷贝给str3之后,str1和str3的地址不一致了。