让我们说我的文件是这样的:
#include <iostream>
#include <string>
using namespace std;
class testclass{
public: string name;
//testclass(const string& sref){
// name = sref;
//}
testclass(string str){
name = str;
}
~testclass(){}
};
int main(){
testclass t1("constStringRef");
cout << t1.name << '\n';
}
给定以下构造函数调用,构造函数1和2之间有什么区别:
testclass tobj("tmemberstring");
以下是我的想法:
我知道通过引用传递意味着你没有传递副本但是由于字符串参数最初有一个字符串初始化(在两种情况下都被视为局部变量,我假设),然后在情况1中初始化对它的引用或在情况2中复制到新的字符串str。最后,两个构造函数都将值复制到成员字符串名称。如果我的想法是正确的,我会跳过一步(复制到字符串str)如果使用第一个构造函数。
Sidequestions: 参数是否存储在堆栈区域中? 如果是这样,这个特定的字符串引用或对任何基本数据类型的引用会占用多少空间?
希望得到你的建议, 提前谢谢
答案 0 :(得分:2)
回答问题的最简单方法是分解两种情况下发生的情况。
testclass(const string& sref)
testclass t1("constStringRef");
首先从string
const char*
对象
string
对象绑定到构造函数的const string&
参数name
是无用的默认构造的,因为你没有使用构造函数的初始化列表(以后更多)string::operator =
,制作const string&
参数总计: 1份。
testclass(string str)
testclass t1("constStringRef");
首先从string
const char*
对象
string
对象复制到构造函数的参数name
是无用的默认构造string::operator =
,制作string
参数总计: C ++ 03中的2个副本,C ++ 11中的1个副本。
由此,我们可以相信const string&
更好。但是这只适用于C ++ 03 。
在C ++ 11中,最好(在这种情况下)将字符串按值传递给构造函数,然后将参数移动到类成员中:
testclass(string str){
name = std::move(str);
}
让我们看看现在发生了什么:
testclass t1("constStringRef");
首先从string
const char*
对象
name
是无用的默认构造string::operator =
被调用,但这次将<{1}}参数移动到string
总计: 0副本!
对于左值,这一切都很好,但对于左值这仍然适用吗?
name
string s = "..."; // s has already been constructed some time ago
testclass t1(s); // what happens during this call?
(在C ++ 03和C ++ 11中)的构造函数,:
const string&
绑定到s
参数const string&
是无用的默认构造name
,制作string::operator =
参数 获取const string&
然后移动的构造函数(仅限C ++ 11):
string
被复制到s
参数string
是无用的默认构造name
被调用,但这次将<{1}}参数移动到string::operator =
在C ++ 03 中,无论是传递左值还是右值都无关紧要,使用string
总是更有效率。正如其他人所提到的,您可能希望重载构造函数以获取name
参数,从而避免使用无用的副本。
在C ++ 11 中,如果将参数移动到成员变量中,const string&
参数与lvalues的const char*
参数相同,但它是对于右值更有效(根本不需要复制)。所以你应该使用pass-by-value,然后将参数移动到成员变量。
最后但并非最不重要的是,您注意到我坚持使用无用的默认构建string
。要避免它,请使用构造函数的初始化列表而不是构造函数体中的赋值:
const string&
答案 1 :(得分:1)
在这两种情况下,构造函数都接受std::string
。由于您使用字符串文字(const char*
)调用构造函数,因此将构造临时std::string
以调用构造函数。这两种方法的区别在于接下来会发生什么:
如果testclass(const string& sref)
const
对您刚刚创建的临时string
进行了const&
引用。在第二种情况下,字符串是按值获取的,因此 second 临时需要+才能创建。
作为一般经验法则,我建议尽可能使用std::string
。
但请注意,只需通过模板接受字符串文字,即可完全避免构造临时template <size_t N> testclass(const char (&str)[N])
{
name = str;
}
:
name
另请注意,当您的构造函数被调用时,会发生两件事。 1)构造name
成员。 2)name
成员的值被更改。您可以使用初始化列表在一个步骤中初始化和构造template <size_t N> testclass(const char (&str)[N])
:
name (str, N-1) // minus one to not copy the trailing `\0`. Optional, depending
{
name = str;
}
成员:
{{1}}
答案 2 :(得分:0)
首先,使用“std :: endl”而非“'\ n'”更好地完成流线。 一个构造函数需要一个引用,另一个需要一个值,但是您传递的是一个值为“const char *”的C-String。 第三,在调用Constructor-Code之前初始化你的成员。 我会推荐以下内容;
testclass(const char* name) : name(name) {};
答案 3 :(得分:0)
我知道通过引用传递意味着您没有传递副本
这是对的,所以你通常应该更喜欢testclass::testclass(const std::string&)
,因为它避免了这个副本
但是由于字符串参数,首先是字符串初始化
是的,有一个临时字符串被创建并作为const引用传递,或者参数是直接创建的。
但是还有另一个字符串:您的name
成员默认初始化,只是因为您没有使用初始化列表。这样:
testclass::testclass(const std::string &s) : name(s) {}
直接初始化name
,而不先默认初始化它,然后在构造函数体中更改它。
Sidequestions:参数是否存储在堆栈区域?
参数可以保存在堆栈中,也可以保存在寄存器中,或者以编译器发现符合标准的任何其他方式保存。这是一个实现细节。
...如果是这样,这个特定的字符串引用或对任何基本数据类型的引用会占用多少空间?
一般来说,引用可能最多指针的大小(并且可能完全被省略)。
你没有考虑过的事情是std::string
拥有动态分配的字符数组副本,所以你传递的每个std::string
都可以执行动态分配,然后是超出范围时的分配。 (有些编译器可能会通过引用计数来避免这种情况,但我们会重新讨论实现细节。)