我试图理解字符串在C ++中是如何工作的,因为我在遇到意外行为后感到非常困惑。
考虑一个字符串,我使用append()
运算符插入一个字符(不使用[]
):
string str;
str[0] = 'a';
让我们打印字符串:
cout << "str:" << str << endl;
我输出NULL:
str:
好的,让我们尝试打印字符串中唯一的字符:
cout << "str[0]:" << str[0] << endl;
输出:
str[0]:a
Q1。那里发生了什么?为什么在第一种情况下不打印a
?
现在,我做了一些应该抛出编译错误的事情,但它没有,我的问题又是,为什么。
str = 'ABC';
Q2。那不是一个不正确的语义,即将一个字符(实际上不是一个字符,但实际上是单引号中的字符串)分配给一个字符串?
现在,更糟糕的是,当我打印字符串时,它总是打印出最后一个字符,即C(我期待第一个字符,即A):
cout << "str:" << str << endl;
输出:
str:C
Q3。为什么打印最后一个字符,而不是第一个?
答案 0 :(得分:5)
考虑一个字符串,我使用[]运算符插入一个字符(不使用append()):
string str; str[0] = 'a';
你没有插入一个角色。 operator[](size_type pos)
会在pos
返回对已存在字符的引用。如果pos == size()
则行为未定义。您的字符串为空,因此size() == 0
因此str[0]
具有未定义的行为。
Q1。那里发生了什么?为什么在第一种情况下没有印刷?
行为未定义。
现在,我做了一些应该抛出编译错误的事情,但它没有,我的问题又是,为什么。
str =&#39; ABC&#39;;
Q2。如何不是一个不正确的语义,即将一个字符......分配给一个字符串?
将字符分配给字符串的语义不正确。它将字符串的内容设置为该单个字符。
Q2。 ...一个字符(实际上不是一个字符,但实际上是单引号中的字符串)......
这是一个多字符文字。多字符文字的类型是int
。如果编译器支持多字符文字,那么语义不正确。
没有接受int
字符串的赋值运算符。但是,int
可以隐式转换为char
,因此转换后会使用接受char
的赋值运算符。
char
不一定代表int
可以包含的所有值,因此转换可能会溢出。如果char
是签名类型,则此溢出具有未定义的行为。
Q3。为什么打印最后一个字符,而不是第一个?
多字符文字的值是实现定义的。您需要查阅编译器的手册,以了解是否支持多字符文字,以及您应该期望的值。此外,您需要考虑这样一个事实,即转换为值的char
可能无法代表int
的所有值。
但我没有得到任何警告
然后考虑获得更好的编译器。这是GCC警告的:
警告:多字符字符常量[-Wmultichar]
str = 'ABC';
警告:隐式常量转换溢出[-Woverflow]
str[0] = 'a'
应该像使用char str[] = ""
一样使用字符串(但它并不像我们所见)。你能帮助我理解为什么[]运算符在处理字符数组时会有不同于字符串的行为吗?
因为标准如何定义了std::string
的行为和要求。
char str[] = "";
创建一个大小为1的数组,由null终止符组成。数组的这个元素就像任何其他元素一样,你可以自由地修改它:
str[0] = 'a';
这是明确定义的,没问题。但是现在str
不再包含以null结尾的字符串,因此尝试使用它本身具有未定义的行为:
out << "str:" << str << endl; // oops, str is not a null terminated string
因此,std::string
的设计使您无法使用最终的空终止符 - 只要您遵守std::string
的要求即可。不允许触摸空终止符也允许实现永远不为空字符串分配内存缓冲区。不分配内存可能比分配内存更快,所以这是一件好事。
答案 1 :(得分:2)
你应该看看http://en.cppreference.com/w/cpp/string/basic_string/operator_at。即,关于&#34;如果pos == size()的部分,行为是未定义的。&#34;
以下行创建一个空字符串:
string str;
所以size()将返回0.
答案 2 :(得分:2)
你的陈述str string; str[0]='a'
是未定义的行为,尽管其原因在C ++ 11&#34之前的&#34;之间有所不同。 &#34;来自C ++ 11 on&#34;。请注意,str
是非const字符串。在C ++ 11之前,str[pos]
(pos == size()
和str
之类的(读取)访问是非const字符串会产生未定义的行为。从C ++ 11开始,将允许读访问(产生对'\0'
- 字符的引用。但是,修改在其行为中也未定义。
到目前为止,关于std::basic_string::operator_at的Cpp参考文献。
但是现在让我们来解释一个类似于你的程序的行为但具有明确的行为; (我将其用作类比来描述程序的行为):
string str = "bbbb";
const char* cstr = str.data();
printf("adress: %p; content:%s\n", cstr, cstr);
// yields "adress: 0x7fff5fbff5d9; content:bbbb"
str[0] = 'a';
const char* cstr2 = &str[0];
printf("adress: %p; content:%s\n", cstr2, cstr2);
// yields "adress: 0x7fff5fbff5d9; content:abbb"
cout << "str:" << str << endl;
// yields "str:abbb"
该程序几乎是自我解释,但请注意str.data()
给出了指向内部数据缓冲区的指针,str.data()
返回与&str[0]
相同的地址。
如果我们现在使用string str = ""
将相同的程序更改为您的设置,那么行为中甚至没有太大变化(尽管此行为未定义,不安全,无法保证,并且可能因编译器而异编译器):
string str; // is the same as string str = ""
const char* cstr = str.data();
printf("adress: %p; content:%s\n", cstr, cstr);
// yields "adress: 0x7fff5fbff5c1; content:"
str[0] = 'a';
const char* cstr2 = &str[0];
printf("adress: %p; content:%s\n", cstr2, cstr2);
// yields "adress: 0x7fff5fbff5c1; content:a"
cout << "str:" << str << endl;
// yields "str:"
请注意,str.data()
返回与&str[0]
相同的地址,'a'
实际上已写入该地址(如果运气好,我们不会访问未分配的内存,因为空字符串不能保证准备好缓冲区;也许我们真的好运)。因此,打印str.data()
实际上会给你a
(如果我们有额外的运气,'a'
之后的字符是终止字符串的字符串)。无论如何,语句str[0]='a'
不会增加字符串大小,这仍然是0
,这样cout << str
会给出一个空字符串。
希望这会有所帮助。
答案 3 :(得分:1)
string str;
制作长度为0的字符串。
str[0] = 'a';
将字符串的第一个元素设置为“a”。请注意,字符串的长度仍为0.另请注意,可能没有空间分配来保存此“a”,此时程序已被破坏,因此进一步分析是最好的猜测。
cout << "str:" << str << endl;
打印字符串的内容。该字符串的长度为0,因此不打印任何内容。
cout << "str[0]:" << str[0] << endl;
进入未定义的区域并尝试回读先前存储的“a”。这不起作用,结果未定义。在这种情况下,它给出了工作的外观,可能是未定义行为可以做的最恶劣的事情。
str = 'ABC';
不一定是错误,因为那里有多字节字符,但这很可能会,但不是必需的,会导致编译器发出警告,因为它可能是一个错误。
cout << "str:" << str << endl;
你的猜测与我的编译将会做的一样好,因为str = 'ABC';
在逻辑上是不正确的(尽管语法上有效)。编译器似乎已将ABC
截断为最后一个字符,就像将257放入8位整数可能会导致仅保留最低有效位。