2014-04-04 115 views
3

我感到困惑下面的代码中的数据:返回为const char *为char *,然后改变

string _str = "SDFDFSD"; 
char* pStr = (char*)_str.data(); 
for (int i = 0; i < iSize; i++) 
    pStr[i] = ::tolower(pStr[i]); 

这里_str.data()回报const char*。但我们将其分配给char*。我的问题是,

_str.data()正在返回指向常量数据的指针。如何将它存储在指向数据的指针中?数据一直是正确的?如果我们将它指定给char指针,而不是像我们在for语句中所做的那样改变它,这对于常量数据来说应该是不可能的。

回答

3

您在做什么在标准库级别无效(您违反了std::string contract),但在C++核心语言级别有效。

data返回的char *不应该写入,因为例如它可能在理论上(*)在具有相同值的不同字符串之间共享。

如果你想修改一个字符串,只需使用std::string::operator[],它会通知对象这个意图,并且在该字符串最初被共享的情况下,将负责为特定实例创建一个私有缓冲区。

从技术上讲,你可以从指针或引用丢弃const,但是如果它是一个有效的操作,则取决于特定情况的语义。允许操作的原因是C++的主要哲学是程序员不会犯错误并且知道他们在做什么。例如,从C++语言的角度来看,在技术上是合法的做memcpy(&x, "hello", 5)其中x是一个类实例,但结果很可能是“未定义的行为”。

如果您认为自己的代码“有效”,那是因为您对“作品”的真正含义应该有错误的理解(提示:“作品”并不意味着某人曾经观察过代码,但是在任何情况下都可以)。如果你运行该程序,一个有效的C++实现可以自由地做任何事情:你观察到你认为没问题的东西并不意味着任何事情,可能你看起来不够接近,或者你可能是幸运的实际上)不会发生事故。 (*)在现代,std :: string的COW(copy-on-write)实现的流行度很低,因为它们带来了很多问题(例如使用多线程)并且内存现在便宜很多。仍然std::string合约表示您不允许更改返回值data()指向的内存;如果你做任何事情可能发生。

+0

单独第一段+1。我会将其添加到我的曲目中。 –

+0

+1不错的答案。应该注意的是,如果被引用或指向的*原始对象*本身*非const,那么在C++中简要提到的const-casting是唯一可行的。即声明一个'Object s',将它传递给一个函数,该函数采用'const Object&',该函数知道原始对象是非const的,可以对该引用进行const转换并坚果。如果它声明为“const Object s;'它将成为UB来在函数中强制转换引用。不能保证'std :: string :: data()'和/或'std :: string :: c_str()'的结果最初是非const-ilk的指针。因此,*坏主意*。 – WhozCraig

5

不要这样做。这可能是罚款,这种情况下,但作为data()的文件说:

返回可以通过进一步呼吁该修改的对象等 成员函数是无效的指针。

程序不得更改此序列中的任何字符。

因此,如果将指针放在指针周围,您可能会非常意外地写入无效内存。或者,实际上,破坏了std :: string的实现。我几乎可以说,这个功能不应该暴露。

std :: string为此提供了一个非const operator[]

string _str = "SDFDFSD"; 
for (int i = 0; i < iSize; i++) 
    _str[i] = ::tolower(_str[i]); 
+0

我在做什么是有效的。但我想知道为什么它是有效的?对我来说这似乎是错误的。谢谢。 – Tahlil

+0

@Tahlil:你所做的是无效的。在C++中,成功的编译并不能保证有效;该标准充满了未定义的行为。 –

+0

这是无效的;这是未定义的行为,它可以在任何时候停止工作(包括简单和默默地放弃对字符串的更改,设置你的房子着火等) – Massa

0

字符串总是在堆上分配内存,所以这实际上不是const数据,它只是标记为(在方法data()签名中)以防止修改。

但是在C++中没有什么是不可能的,所以通过简单的转换,虽然不安全,但现在可以将可修改的内存空间对待。

+0

更正:“always”意味着默认字符串在这里,你当然可以编写自己的分配器。 – berkus

+0

它不需要在堆上分配。它也可以分配[例如短]字符串或硬盘上的大字符串。 –

+0

@ phresnel它实际上并没有在所有情况下分配,所以有可能会出现这种情况。虽然n3290在效果部分提到,对于所有这些情况,“指向数组的第一个元素的第一个元素**,其第一个元素由s指向” – berkus

-2

C/C++程序中的所有常量(如下面的"SDFDFSD")将存储在单独的区段.rodata中。在执行期间将二进制文件加载到内存中时,此部分映射为只读。

int main() 
{ 
    char* ptr = "SDFDFSD"; 
    ptr[0]='x'; //segmentation fault!! 
    return 0; 
} 

因此任何试图在该位置来修改数据将导致运行时错误即段故障


即将对上述问题,创造一个串并分配一个字符串到它,一个new copy in memory now exists(存储器用于保存字符串对象_str的属性)时。这是在堆上,而不是映射到只读部分。成员函数_str.data()指向映射为读/写的内存中的位置。

const预选赛到返回类型的保证,此功能是不小心传递给期望一个非const char*指针字符串处理函数。

在您当前的迭代中,对于保存字符串对象数据的内存位置本身没有限制;即它被映射为具有读/写许可。因此,使用另一个非const指针修改位置,即在作业的左侧工作,即pStr[i]不会导致运行时错误,因为对存储器位置本身没有固有的限制。

再次这是不是保证工作,只是你观察到的实现特定行为(即它只是适合你),并不总是依赖于此。

+0

然后,代码如何通过改变常量数据首先分配一个指向非常量数据的指针,然后改变它?该任务应该是错误的吗? – Tahlil

+0

这是不正确的。编译器当然可以这么做,但它不是标准的要求。另外,临时的'std :: string'返回的常量可能不是一个真正的常量。 –

+0

“构造字符串,其内容初始化为**,并带有由s **指向的以空字符结尾的字符串的副本。字符串的长度由第一个空字符确定。如果s不指向在CharT的至少Traits :: length(s)+1元素的数组中。“ – berkus

2

您绝对不可以直接更改从std::string::data()std::string::c_str()返回的数据。在一个字符串

std::string str1 = "test"; 
std::string str2 = str1; // copy. 

更改字符:

要创建std::string副本

std::string str1 = "test" 
str1[0] = 'T'; 
+0

+1:但是让我编辑你的第一个短语 –

1

“正确” 的方法是使用std::transform代替:

std::transform(_str.begin(), _str.end(), _str.begin(), ::tolower); 
+0

但是我想知道为什么将'指向常量数据'指向数据指针是合法的吗? – Tahlil

+1

@Tahlil不是。Don这很可能会导致未定义的行为 –

+1

@Tahlil:我已经在几分钟前告诉过你,编译成功并不意味着有效,你为什么不理会我的建议? - 编辑:我知道你只是现在太忙了:D –