我对 C++ 中的字符串有以下疑问
1>>哪个是更好的选择(考虑性能),为什么?
1.
string a;
a = "hello!";
或者
2.
string *a;
a = new string("hello!");
...
delete(a);
2>>
string a;
a = "less";
a = "moreeeeeee";
当一个较大的字符串被复制到一个较小的字符串中时,c++ 中如何准确地处理内存管理?c++ 字符串是可变的吗?
几乎从来没有必要或可取的说
string * s = new string("hello");
毕竟,你(几乎)永远不会说:
int * i = new int(42);
你应该说
string s( "hello" );
或者
string s = "hello";
是的,C++ 字符串是可变的。
以下所有内容都是天真的编译器会做的事情。当然,只要不改变程序的行为,编译器就可以自由地进行任何优化。
string a;
a = "hello!";
首先,您初始化 a 以包含空字符串。(将长度设置为 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 来调整其缓冲区的大小。这意味着您永远不需要使用 new 分配字符串。字符串对象具有固定大小,并且被设计为在堆栈上分配,以便在超出范围时自动调用析构函数。然后析构函数保证释放所有分配的内存。这样,您不必在用户代码中使用 new/delete,这意味着您不会泄漏内存。
您经常使用赋值而不是初始化是否有特定原因?也就是说,你为什么不写
string a = "Hello";
ETC。?这避免了默认构造,并且在语义上更有意义。仅仅为了在堆上分配它而创建指向字符串的指针是没有意义的,即您的案例 2 没有意义并且效率稍低。
至于你的最后一个问题,是的,C++ 中的字符串是可变的,除非声明const
。
string a;
a = "hello!";
2操作:调用默认构造函数std:string()然后调用operator::=
string *a; a = new string("hello!"); ... delete(a);
只有一个操作:调用构造函数 std:string(const char*) 但你不应该忘记释放你的指针。
那么字符串 a("hello");
在案例 1.1 中,您的字符串成员(包括指向数据的指针)被保留,并且当超出范围stack
时释放类实例占用的内存。a
在案例 1.2 中,成员的内存也是从堆中动态分配的。
当您将char*
常量分配给字符串时,将包含数据的内存将被realloc
'ed 以适应新数据。
您可以通过调用来查看分配了多少内存string::capacity()
。
当您调用 时string a("hello")
,内存会在构造函数中分配。
构造函数和赋值运算符都在内部调用相同的方法来分配内存并在那里复制新数据。
如果您查看 STL 字符串类的文档(我相信 SGI 文档符合规范),许多方法都列出了复杂性保证。我相信许多复杂性保证都故意含糊其辞,以允许不同的实现。我认为某些实现实际上使用了修改时复制的方法,这样将一个字符串分配给另一个字符串是一个恒定时间的操作,但是当您尝试修改其中一个实例时,您可能会产生意想不到的成本。不确定在现代 STL 中是否仍然如此。
您还应该查看该capacity()
函数,它将告诉您在强制重新分配内存之前可以放入给定字符串实例的最大长度字符串。reserve()
如果您知道稍后将在变量中存储一个大字符串,您也可以使用导致重新分配到特定数量。
正如其他人所说,就您的示例而言,您应该真正支持初始化而不是其他方法,以避免创建临时对象。
直接在堆中创建字符串通常不是一个好主意,就像创建基类型一样。这是不值得的,因为对象可以很容易地留在堆栈上,并且它具有高效复制所需的所有复制构造函数和赋值运算符。
std:string 本身在堆中有一个缓冲区,可以由多个字符串共享,具体取决于实现。
例如,使用 Microsoft 的 STL 实现,您可以这样做:
string a = "Hello!";
string b = a;
并且两个字符串将共享相同的缓冲区,直到您更改它:
a = "Something else!";
这就是为什么存储 c_str() 以供以后使用非常糟糕的原因;c_str() 仅保证在对该字符串对象进行另一次调用之前有效。
这会导致非常讨厌的并发错误,如果您在多线程应用程序中使用它们,则需要使用定义关闭此共享功能。
最有可能的
string a("hello!");
比其他任何东西都快。
你来自Java,对吧?在 C++ 中,对象被视为(在大多数情况下)与基本值类型相同。对象可以存在于堆栈或静态存储中,并按值传递。当您在函数中声明字符串时,无论字符串对象占用多少字节,它都会在堆栈上分配。字符串对象本身确实使用动态内存来存储实际字符,但这对您来说是透明的。要记住的另一件事是,当函数退出并且您声明的字符串不再在范围内时,它使用的所有内存都将被释放。不需要垃圾收集(RAII 是你最好的朋友)。
在您的示例中:
string a;
a = "less";
a = "moreeeeeee";
这会将一块内存放在堆栈上并将其命名为 a,然后调用构造函数并将 a 初始化为空字符串。编译器将“less”和“moreeeeeee”的字节存储在(我认为)您的 exe 的 .rdata 部分中。字符串 a 将有几个字段,例如长度字段和 char*(我正在大大简化)。当您将“less”分配给 a 时,将调用 operator=() 方法。它动态分配内存来存储输入值,然后将其复制进去。当您稍后将“moreeeeeee”分配给 a 时,将再次调用 operator=() 方法,并在必要时重新分配足够的内存来保存新值,然后复制它进入内部缓冲区。
当字符串 a 的作用域退出时,将调用字符串析构函数并释放为保存实际字符而动态分配的内存。然后堆栈指针递减,保存 a 的内存不再“在”堆栈上。