2009-02-10 166 views
8

我有关于C++中的字符串的以下问题C++中的字符串

1 >>哪一个更好的选择(考虑性能)以及为什么?

1.

string a; 
a = "hello!"; 

OR

2.

string *a; 
a = new string("hello!"); 
... 
delete(a); 

2 >>

string a; 
a = "less"; 
a = "moreeeeeee"; 

究竟存储器管理在C++中处理时更大的串是复制到一个更小的区域NG? C++字符串是否可变?

+0

你使用的是特定的字符串类,还是只是标准的以null结束的cstrings? – tsellon 2009-02-10 20:20:41

+0

我会假设问题是关于std :: string。 – rmeador 2009-02-10 20:23:34

回答

8

以下所有内容都是朴素的编译器所能做的。当然,只要它不改变程序的行为,编译器可以自由地进行任何优化。

string a; 
a = "hello!"; 

首先你初始化一个包含空字符串。 (将长度设置为0,以及一两个其他操作)。然后,您分配一个新值,覆盖已设置的长度值。它可能还必须执行检查以查看当前缓冲区有多大,以及是否应分配更多内存。

string *a; 
a = new string("hello!"); 
... 
delete(a); 

调用new需要操作系统和内存分配器来查找空闲的内存块。这很慢。然后你立即初始化它,所以你不会分配任何东西两次或者需要缓冲区的大小,就像你在第一个版本中一样。 然后发生了一些不好的事情,并且你忘记了调用delete,并且你有一个内存泄漏,另外给一个分配非常慢的字符串。所以这是不好的。

string a; 
a = "less"; 
a = "moreeeeeee"; 

就像第一种情况一样,首先初始化a以包含空字符串。然后你分配一个新的字符串,然后另一个。这些中的每一个都可能需要来调用new来分配更多的内存。每一行也需要长度,并可能分配其他内部变量。

通常情况下,你分配这样的:

string a = "hello"; 

一号线,一旦执行初始化,而不是第一个默认初始化,然后指定你想要的值。

它还最大限度地减少了错误,因为您的程序中没有任何空缺的空字符串。如果字符串存在,它包含您想要的值。

关于内存管理,google RAII。 简而言之,字符串在内部调用new/delete来调整其缓冲区大小。这意味着你永远不会需要分配一个新的字符串。字符串对象具有固定的大小,并被设计为在堆栈上分配,以便在超出作用域时自动调用。析构函数保证任何分配的内存都被释放。这样,您不必在用户代码中使用新的/删除,这意味着您不会泄漏内存。

2
string a; 
a = "hello!"; 

2操作:调用默认的构造函数STD:字符串(),然后调用操作:: =

string *a; a = new string("hello!"); ... delete(a); 

只需一个人操作:调用构造STD:字符串(为const char *),但你不应该忘记释放你的指针。

string a(“hello”);

+0

你确定吗?你看了编译器生成的代码吗?空的ctor真的叫了吗? – Tim 2009-02-10 20:23:34

+0

当然编译器可以优化它,如果它足够聪明以确定它没有副作用,那么声明和初始化可以合并回来。但这不是一个给定的。 – jalf 2009-02-10 20:26:30

14

这是几乎从来没有必要或适宜的说

string * s = new string("hello"); 

毕竟,你会(几乎)从来不说:

int * i = new int(42); 

而应该说

string s("hello"); 

string s = "hello"; 

是的,C++字符串是可变的。

4

是否有一个特定的原因,你为什么经常使用赋值而不是初始化?也就是说,你为什么不写

string a = "Hello"; 

等?这避免了默认构造,并且在语义上更有意义。仅仅为了在堆上分配字符串而创建一个指向字符串的指针是没有意义的,即你的情况2没有意义并且效率稍低。

至于你的最后一个问题,是的,C++中的字符串是可变的,除非被声明为const

0

在1.1的情况下,你的字符串成员(包括指向数据的指针)在stack保持,并且当a超出范围由类实例占用的存储器被释放。

在情况1.2中,成员的内存也是从堆中动态分配的。

当您将char*常量分配给字符串时,将包含数据的内存将为realloc'ed以适应新数据。

您可以通过调用string::capacity()来查看分配了多少内存。

当您拨打string a("hello")时,内存将在构造函数中分配。

构造函数和赋值操作符都在内部调用相同的方法来分配内存并在那里复制新数据。

0

如果你看看STL字符串类的docs(我相信SGI文档符合规范),许多方法列出了复杂性保证。我相信许多复杂性保证都是故意保留模糊的,以允许不同的实现。我认为一些实现实际上使用了修改时复制的方法,以便将一个字符串分配给另一个字符串是一个常量操作,但是当您尝试修改其中一个实例时,您可能会产生意外的成本。不知道在现代STL中是否仍然如此。

您还应该查看capacity()函数,该函数将告诉您在强制重新分配内存之前,您可以放入给定字符串实例的最大字符串长度。如果您知道您将在以后将大型字符串存储在变量中,您还可以使用reserve()来重新分配一个特定的金额。

正如其他人所说,就您的示例而言,您应该确实比其他方法更倾向于初始化,以避免创建临时对象。

0

直接在堆中创建字符串通常不是一个好主意,就像创建基类型一样。这是不值得的,因为对象可以轻松地停留在堆栈上,并且它拥有高效复制所需的所有拷贝构造函数和赋值操作符。

std:string本身在堆中有一个缓冲区,可以由多个字符串共享,具体取决于实现。

对于intsance,与微软的STL实现,你可以这样做:

string a = "Hello!"; 
string b = a; 

直到你改变了它两个字符串将共享相同的缓冲区:

a = "Something else!"; 

这就是为什么这是非常不好存储c_str()以供后期使用; c_str()只有在对该字符串对象进行另一个调用时才会生效。

这导致,需要这种共享功能非常讨厌的并发错误被关闭了定义,如果你最有可能

string a("hello!"); 

比什么都更快地使用他们在多线程应用程序

0

0

你来自Java,对不对?在C++中,对象与基本值类型相同(在大多数情况下)。对象可以存放在堆栈或静态存储中,并可以通过值传递。当你在一个函数中声明一个字符串时,它会在栈上分配字符串对象所需的许多字节。字符串对象本身确实使用动态内存来存储实际字符,但这对您来说是透明的。另外要记住的是,当函数退出并且您声明的字符串不在范围内时,它使用的所有内存都将被释放。无需垃圾收集(RAII是你最好的朋友)。

在您的例子:

string a; 
a = "less"; 
a = "moreeeeeee"; 

这使得内存块堆栈和名字就可以了,然后调用构造函数和初始化为空字符串。编译器将“less”和“moreeeeeee”的字节存储在(我认为)exe的.rdata部分中。字符串a将有几个字段,如长度字段和char *(我大大简化了)。当您将“less”分配给a时,将调用operator =()方法。它动态地分配内存来存储输入值,然后将其复制进来。当您稍后为a分配“moreeeeeee”时,将再次调用operator =()方法,并且在必要时重新分配足够的内存以保存新值,然后将其复制进入内部缓冲区。

当字符串a的作用域退出时,将调用字符串析构函数并释放动态分配的用于存放实际字符的内存。然后堆栈指针递减,并且保存a的内存不再“在”堆栈上。