为什么你需要在新的char(str.length())上添加一个?

时间:2014-09-06 07:34:50

标签: c++ string

代码:

string str = "Whats up";
char *c = new char[str.length() + 1];

我仍然可以写char *c = new char[str.length()];

在长度上添加+1有什么意义?

3 个答案:

答案 0 :(得分:6)

您的代码:

string str = "Whats up";
char *c = new char[str.length() + 1];

你的问题:

  

在长度上添加+1有什么意义?

真正的问题应该是:在C ++程序中使用C风格的字符串有什么意义?你确定需要它们吗?

让我解释一下你的两个代码行中到底发生了什么:

"Whats up"是一个字符串文字,即一系列不变的字符,确切地说是char const[9]。第9个字符是编译器自动添加的空字符'\0'。所以数组实际上是这样的:

{ 'W', 'h', 'a', 't', 's', ' ', 'u', 'p', '\0' }

事实上,你也可以写:

char const array[9] = { 'W', 'h', 'a', 't', 's', ' ', 'u', 'p', '\0' };
std::string s = array;

所以你有一个char const[9]数组,用于初始化std::string。这里实际使用了std::string的哪个构造函数?如果你看一下http://en.cppreference.com/w/cpp/string/basic_string/basic_string,你会发现这个:

basic_string( const CharT* s,
              const Allocator& alloc = Allocator() );

请注意,std::string实际上是std::basic_string<char>的typedef,因此在这种情况下,您的CharTchar,构造函数为:

string( const char* s,
              const Allocator& alloc = Allocator() );

同时忽略alloc参数。向初学者解释它太复杂了,它有一个默认的参数,所以你几乎可以随时忽略它。这意味着你最终得到:

string( const char* s);

这本身就是另一种写作方式:

string(char const *s);

因此,您可以使用std::string初始化char const *,并且您的代码会将构造函数传递给char const[9]。这是有效的,因为数组会自动转换为指向其第一个元素的指针

因此std::string接受您的数组,将其视为指针并复制9个字符。数组大小信息9已丢失,但无关紧要,因为您有终止'\0',因此std::string知道停止的位置。

到目前为止,这么好。您有一个std::string对象,其中包含"Whats up"的副本。你的下一行是这样的:

char *c = new char[str.length() + 1];

首先,考虑str.length()length函数返回 字符串大小,而非数组大小。因此,尽管您传递了9个字符来构造字符串,但length返回8.这是有道理的,因为std::string旨在让您忘记指针,数组和内存操作。它是文本,这里的文字有8个字符。

因此,str.length() + 1等于 8 + 1 = 9 ,因此您的代码行相当于:

char *c = new char[9];

您创建了一个名为c的指针,初始化指向一个内存位置,其中有足够的 房间为9个字符,尽管当前存储的是什么是未定义,所以你不能尝试从那里读取:

c
|
|
+------+
       |
       v
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
...|  |  |  |  |  |  |  |  |  |  |  | ...
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       0  1  2  3  4  5  6  7  8

您创建的std::string和内存c之间没有任何关系。他们住在完全不同的地方:

 c
 |
 |
 +------+
        |
        v                                   0  1  2  3  4  5  6  7  8
 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
... |  |  |  |  |  |  |  |  |  |  |  | ... |W |h |a |t |s |  |u |p |\0| ...
 +-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        0  1  2  3  4  5  6  7  8           ^
                                            |
                                            |
           str -------( c_str() )-----------+

但是如果你使用像strcpy这样的C函数将std::string的内容复制到这9个字符,那么你就会明白为什么你需要9个字符的空格:

strcpy(c, str.c_str());

strcpy查看来源(str.c_str())并将一个字符一个接一个地复制到c,直到找到'\0'str内部以\0结尾,所以一切都很好。该功能从此图片右侧的 0 8 ,并将所有内容复制到 0 8

这最终回答了你的问题:左边必须有9个字符的空格。否则,strcpy将尝试将最终字符(\0)写入您不允许触摸的内存位置。这导致未定义的行为并且可能导致例如崩溃或随机崩溃。

有9个字符的空间,strcpy成功完成:

 c
 |
 |
 +------+
        |
        v                                   0  1  2  3  4  5  6  7  8
 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
... |  |W |h |a |t |s |  |u |p |\0|  | ... |W |h |a |t |s |  |u |p |\0| ...
 +-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        0  1  2  3  4  5  6  7  8           ^
                                            |
                                            |
           str -------( c_str() )-----------+

故事的道德:

使用std::string。复制std::string可能会在内部使用非常相似的机制,但让您(以及其他令人烦恼的事情)不必记住“+ 1”规则:

std::string s1 = "Whats up";
std::string s2 = "...";
s2 = s1;

答案 1 :(得分:2)

std::string不同,C风格的字符串使用特殊字符来表示其结尾,即空字符'\0',额外的一个字符用于存储终止'\0'

答案 2 :(得分:-6)

您的代码存在缺陷。

应该是

c* = new char[str.length()+1];

s.length()+ 1不会做任何事情。

虽然编译器会自动为你设置c字符串大小,但最好指定确切的大小,这样你就可以看到所有内容的机制。

C字符串总是需要比std :: string值多一个空格,因为c字符串是在数组末尾具有终止空值的字符数组。这就是为什么你总是在最后给空间留出空间。