今天来看一下在面试笔试中经常会出错的地方。
我们先来看一个代码:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s = "12345";
const char* p = s.c_str();
cout << p << endl;
s.append("abced");
cout << p << endl;
return 0;
}
看一下这个代码,第一感觉是没有什么问题的,指针p指向了S这个字符串,那么当执行 s.append(“abced”);这句话之后,字符串S会增加“abced”这借个字符,那么打印输出的结果应该为:12345和12345abced。但是我们运行程序发现(纠错:使用gcc 4.4.7版本编译器),两条打印语句都是12345.这是为什么呢?下面我们看图详细分析一下:
一开始只是字符串S指向0xFF112233这个内存空间,然后让P指向这个空间,执行这句话后:s.append(“abced”);S这个字符串变成了0x12345abced,同时这个字符串所对应的地址空间却变成了:0xFF445566,但是此时指针P依然指向之前的0xFF112233,里面的值是没有变化的,为什么出现这种情况呢?
因为:string对象维护了一个指向数据的char*指针,这个指针在程序运行阶段,有可能会发生突变。
所以那个P指针还是指向原来的0xFF112233这个地址,打印输出的内容就还是12345了。
纠错:
- 针对上述的说明,有网友提出质疑,在vs2013上运行结果就是12345和12345abced。我也做了实验,在vs2017上运行结果也是12345和12345abced。而在我的Linux中,使用gcc
4.4.7版本编译器,运行结果就是12345和12345。使用gcc 7.3.0 编译器,就得到12345和12345abced 这个结果。很明显,这与编译器实现有关,比较新版本的编译器可以得到正常的运行结果。- 很明显,我一开始的分析,也是有一些错误的。string对象维护了一个指向数据的char* 指针,这个指针在程序的运行阶段是有可能发生突变,也有可能不发生突变。比较新的编译器编译都没有发生突变,说明比较新的编译器解决了那个bug。
- 在我下面的评论中,我之前说可以从另一个角度理解为什么打印结果一样,就是const,这种说法也是不对的,const修饰的变量,则该变量不能出现在赋值符号的左边,不能被直接改变,但是可以被间接改变。
下面再看一个程序:
#include <iostream>
#include <string>
using namespace std;
int main()
{
const char* p = "12345";
string s = "";
s.reserve(10); //分配内存大小为10
// 不要使用 C 语言中的方式操作 C++ 中的字符串
for(int i=0; i<5; i++)
{
s[i] = p[i];
}
cout << s << endl;
return 0;
}
这个程序运行结果为:空!!!
为什么呢?难道对S的赋值没有成功么?我们给出分析,用C语言描述C++中的字符串,会出现一些异常,看图:
这里可以看出,m_cstr是指向字符串的内容,m_length是string类的成员变量,它指向字符串的长度,经过for循环后,我们操作的不是对象的整体,m_cstr所指向的字符串确实有了,但是m_length却依然为0,所以最后打印出来的是空,我们应该直接操作这个对象,才能让m_length随着赋值而改变。
我们把程序改成这样:
#include <iostream>
#include <string>
using namespace std;
int main()
{
const char* p = "12345";
string s = "";
s.reserve(10);
s = p; //直接操作对象,不要像C语言那样进行for循环赋值。
// 不要使用 C 语言中的方式操作 C++ 中的字符串
/* for(int i=0; i<5; i++) { s[i] = p[i]; } */
cout << s << endl;
return 0;
}
打印结果为:
这下就是我们期待的结果了。
总结一下:
-string类通过一个数据空间保存字符串数据
-string类通过一个成员变量保存当前字符串的长度
-C++开发时,尽量避免C语言的一些惯用的编程思想
想一起探讨以及获得各种学习资源加我:
qq:1126137994
微信:liu1126137994
可以共同交流关于嵌入式,操作系统,C++语言,C语言,数据结构等技术问题。