'string s(“hello”);' 和 'string s = “hello”;

Is there a difference between `string s("hello");` and `string s = "hello";` [duplicate]

提问人:Fabian 提问时间:1/25/2017 最后编辑:songyuanyaoFabian 更新时间:9/19/2023 访问量:983

问:

标题说明了一切。但是,请作为任何类的占位符。string

std::string s1("hello");  // construct from arguments
std::string s2 = "hello"; // ???
std::string s3;           // construct with default values
s3 = "hello";             // assign

我想知道语句 for 是否与 for 或 for 相同。s2s1s3

C++ 初始化 变量 复制构造函数 赋值运算符

评论

0赞 1/25/2017
另外:初始化和分配

答:

2赞 Bathsheba 1/25/2017 #1

在本例中,并执行完全相同的操作:它们都调用构造函数 .(为了清楚起见,有些人更喜欢使用)。s1s2const char*=

对于 ,调用默认构造函数,后跟 to 。s3operator=const char*

14赞 songyuanyao 1/25/2017 #2

的情况是复制初始化。这是初始化,而不是像 那样分配。s2s3

请注意,对于 ,效果与 和 相同,将调用 apporiate 构造函数(即 )来构造对象。但是复制初始化和直接初始化之间是有区别的(这种情况是 );对于复制初始化,将不考虑显式构造函数。假设 是声明的,这意味着不允许从 到 的隐式转换;那么第二个案例将不会再次编译。std::strings1s2std::string::string(const char*)s1std::string::string(const char*)explicitconst char*std::string

复制初始化不如直接初始化宽松:显式构造函数不会转换构造函数,因此不考虑进行复制初始化。

1赞 Pete Becker 1/25/2017 #3

如果一个类没有可访问的复制构造函数,则第二种初始化形式无效:

[temp]$ cat test.cpp
struct S {
    S(int);
private:
    S(const S&);
};

S s1(3);  // okay
S s2 = 3; // invalid
[temp]$ clang++ -std=gnu++1z -c test.cpp
test.cpp:8:3: error: calling a private constructor of class 'S'
S s2 = 3; // invalid
  ^
test.cpp:4:5: note: declared private here
    S(const S&);
    ^
1 error generated.
[temp]$ 

不同之处在于,第二个正式地创建一个 类型的临时对象,使用值 3 初始化,然后将该临时对象复制到 中。允许编译器跳过副本并直接构造,但前提是副本有效。Ss2s2

评论

0赞 songyuanyao 1/25/2017
我认为复制 ctor 在这里无关紧要。melpon.org/wandbox/permlink/2VI5MmtM8qG73qTs
0赞 Pete Becker 1/25/2017
@songyuanyao - 我已经编辑了我的答案以显示编译器输出和开关(与您的匹配)。这仍然是一个错误。
0赞 songyuanyao 1/25/2017
这可能是 C++17 问题。从这里开始,“最后一步通常是优化出来的,转换结果直接在分配给目标对象的内存中构造,但即使没有使用适当的构造函数(移动或复制),也需要可以访问。(直到 C++17)”。C++14C++17
0赞 Pete Becker 1/25/2017
@songyuanyao - C++17 还不是语言定义。即使它成为标准,拥有一个可访问的复制构造函数对于使用当今编译器的人来说也不是“无关紧要的”。
0赞 songyuanyao 1/25/2017
很公平。最好在答案中提及潜在的变化。
2赞 BusyProgrammer 1/25/2017 #4

虽然所有 3 种方法的最终结果都是相同的(字符串将被分配给变量),但存在某些比语法更深层次的基本差异。我将介绍您的 3 个字符串涵盖的所有 3 个场景:

第一种情况:s1 是直接初始化的示例。直接初始化涵盖许多不同的方案,您的方案定义如下:

使用非空括号内的表达式列表进行初始化。

这里,s1 没有类数据类型,而是数据类型,因此将进行标准转换,将括号中的数据类型转换为 s1 的 cv 非限定版本,即 .Cv-unqualified 表示变量没有附加 (const) 或 (volatile) 等限定符。请注意,在直接初始化的情况下,它比 copy-initialization 要宽松得多,这是 s2 的主题。这是因为复制初始化将仅引用用户定义的non_explicit(即隐式)的构造函数和转换函数。另一方面,直接初始化考虑隐式和显式构造函数以及用户定义的转换函数。std::stringconst *char

接下来,第二个字符串 s2 是复制初始化的示例。简单地说,它将值从左侧复制到右侧。这是一个示例:

当非引用类型 T 的命名变量(自动、静态或线程局部)声明时,初始值设定项由等号后跟表达式组成。

此方法涵盖的过程是相同的。由于 s2 没有类数据类型,而是数据类型,因此它将使用标准转换将右侧字符串的值转换为左侧的类型值。但是,如果函数是显式声明的,则无法像复制初始值设定项那样进行标准转换,并且代码的编译将失败。std::stringconst *char

请参阅与 2 种初始化类型进行比较的一些代码示例。这应该可以消除上面的任何混淆:

    struct Exp { explicit Exp(const char*) {} }; // This function has an explicit constructor; therefore, we cannot use a copy initialization here
    Exp e1("abc");  // Direct initialization is valid here
    Exp e2 = "abc"; // Error, copy-initialization does not consider explicit constructor
     
    struct Imp { Imp(const char*) {} }; // Here we have an implicit constructor; therefore, a copy initializer can be used
    Imp i1("abc");  // Direct initialization always works
    Imp i2 = "abc"; // Copy initialization works here due to implicit copy constructor 

转到第三种情况,它甚至不是初始化的情况,而是赋值的情况。就像您在评论中所说的那样,变量 s3 是用默认字符串初始化的。当您使用等号执行此操作时,该字符串将替换为“Hello”。这里发生的情况是,当 中声明 s3 时,将调用默认构造函数 ,并设置默认字符串值。当您使用 = 符号时,该默认字符串将在下一行中替换为 hellostring s3;std::string

如果我们在跑步时看哪个在速度方面更有效,那么差异是微不足道的。但是,如果我们只这样做,s1 的运行时间最快:

    int main(void)
    {
        string a("Hello");
    }

这需要以下时间和内存来编译和运行:

编译时间: 0.32 秒, 绝对运行时间: 0.14 秒, CPU 时间: 0 秒, 内存峰值: 3 Mb, 绝对使用时间: 0,46 秒

如果我们看一下按以下方式编码的字符串 s2:

    int main(void)
    {
        string a = "Hello";
    }

然后程序运行所需的总时间为:

编译时间: 0.32 秒, 绝对运行时间: 0.14 秒, CPU 时间: 0 秒, 内存峰值: 3 Mb, 绝对使用时间: 0.47 秒

使用复制初始值设定项的运行时比直接初始值设定项多运行 0.01 秒。差异是存在的,但只是微不足道的。

第 3 种情况为 s3,如果按以下方式编码:

    int main(void)
    {
        string a;
        a = "Hello";
    }

总运行、编译时间和空间占用:

编译时间: 0.32 秒, 绝对运行时间: 0.14 秒, CPU 时间: 0 秒, 内存峰值: 3 Mb, 绝对使用时间: 0.47 秒

我想在这里指出一点:第二种和第三种方法之间的运行时差异很可能为零;相反,它是一个小于 0.01 秒的时间差,第 3 种方法需要更长的时间 (S3)。那是因为它有 2 行代码可以操作;一个是变量的声明,另一个是字符串对变量的赋值。

希望这能回答你的问题。

评论

0赞 Fabian 2/1/2017
非常感谢您的详细回答。对于性能测试,您应该在循环中多次重复相同的步骤并计算平均时间。我相信这会使性能差异更加明显。此外,您必须确保编译器不会简单地优化未使用的字符串变量!
0赞 BusyProgrammer 2/1/2017
实际上@Fabian,我确实运行了多次,但由于时间精确到只有小数点后 2 位,从我使用的网站来看,时间是完全相同的!我需要的是一个更精确的时间,以便能够找到平均值。感谢您的反馈:-)