有什么区别
int i=0;
和
int i(0);
int *p=new int;
和
int *p=new int(0);
还是int *p=new int复制初始样式?
什么时候int i=0;不用new int(0)?
有什么区别
int i=0;
和
int i(0);
int *p=new int;
和
int *p=new int(0);
还是int *p=new int复制初始样式?
什么时候int i=0;不用new int(0)?
我要先回答一个稍微不同的问题。
假设您有一个class或struct(它们几乎相同),如下所示:
struct Foo {
int value; // Foo stores an int, called value
Foo(int v):value(v) {}; // Foo can be constructed (created) from an int
explicit Foo(double d):value(d) {}; // Foo can be constructed (created) from a double
// ^^^ note that keyword. It says that it can only be EXPLICITLY created from a double
};
现在,这很像int,但有一些区别。
Foo i = 0;
上面创建了一个int文字,然后使用构造函数Foo i对其进行Foo(int v)构造。
Foo i(0);
上面创建了一个int文字,然后使用构造函数Foo i对其进行Foo(int v)构造。请注意,我只是重复了自己。
Foo i = Foo(0);
上面创建了一个int文字,然后使用构造函数Foo i对其进行构造,然后从中Foo(int v)复制构造。Foo i但是,该标准允许编译器“省略”(跳过)复制构造函数,而是Foo i直接从int文字构造0。
Foo* i = new Foo;
这进入空闲存储区(通常实现并称为堆),获得足够的内存来存储 a Foo,然后默认构造它。然后它返回这个Foo对象的地址,然后用它来初始化指针Foo* i。现在,请注意未初始化Foo的默认构造函数。这是我在上面的实现中的一个缺陷(在我看来),除非在特殊情况下,你很少想要这样做。value Foo
令人讨厌的是,整数(char、int、long、unsigned char等)、浮点(double或float)字面量和指针字面量都共享这个属性——默认情况下,它们不会初始化为任何值。
所以你应该确保它们被显式初始化。在 的情况下Foo,添加一行:
Foo():value() {}
就够了。
Foo* i = new Foo(0);
这进入自由存储区(通常实现并称为堆),获得足够的内存来存储 a Foo,然后用整数文字 0 构造它。然后它返回该Foo对象的地址,然后用于初始化指针Foo* i.
现在,一旦您要求,免费存储中的内存通常会保留供您使用,直到您有时间归还它,或者您的程序关闭。为了返回它,你调用delete了同一个指针,这两种情况下对象(Foo在这种情况下)都调用了它的析构函数(Foo没有析构函数,所以这个被跳过),然后内存被交还给空闲存储供以后调用new.
跟踪这一点真的很痛苦,而且是一大堆错误的根源,所以你应该避免调用new. 有很多方法可以避免调用new,包括使用std::vector来管理内存块,或者在堆上使用shared_ptr和make_shared创建对象,这些对象通过一种称为 RAII 的技术来管理它们自己的生命周期,或者unique_ptr在您想要密切控制指针的生命周期时使用(可悲的是,make_unique不存在)。
现在让我们更进一步。
Foo i = 0.0;
这无法编译。我说for的构造函数Foo(double)是显式的,上面只选择调用非显式的构造函数。另一方面:
Foo i(0.0);
Foo i = Foo(0.0);
这些都愿意调用显式构造函数,并且工作正常。
接下来,C++11 为我们带来了统一初始化。与其把你想用 in 初始化的东西放进去,不如()把它放进去{}——一个波浪形的大括号。
Foo i{0.0};
Foo i = {0};
etc. 与基于语法的语法{}相比有一些不同()——最重要的是它避免了最令人头疼的解析问题。其他差异包括初始化列表行为,处理显式构造一些东西(int x()不构造int命名x,但int x{}确实如此)。
说到这,是时候回到你的实际问题了。
int在某些方面与我struct Foo的不同。
首先,它不是 aclass或 a struct。因此,它的行为不是由您编写的某些代码决定的,而是由标准广泛描述的。碰巧的是,C++ 试图让基本类型 likeint的行为很像简单的用户定义类型 like Foo,这很有用。
因此,虽然没有调用“复制构造函数”,并且int根本没有构造函数或析构函数,但int几乎完全“好像”在这些构造函数的位置。
int i = 0;
创建整数文字 0,然后int i用它初始化。编译器可能会忽略这一点,直接创建int i值为 0 的整数。对于int,没有办法直接观察到差异。
int i(0);
与 相同int i = 0,因为int没有非显式构造函数。这只是一种不同的语法。
现在,出现了最令人头疼的解析问题。如果你输入
int i = int();
你会得到相同的int i = 0,但如果你输入
int i();
会发生什么,编译器会说“这可能是一个名为 i 的函数,它接受零参数并返回int”,并且由于各种烦人的原因,更喜欢这种解释而不是默认初始化的int. 所以你得到一个名为i而不是整数的前向声明函数。
如前所述,避免这种情况的方法是始终使用以下语法:
int i{};
在 C++11 编译器中。
接下来,我们有
int* p = new int;
它在自由存储上创建一个默认构造(未初始化)int,并将指向它的指针分配给变量int *p. 已发生副本,但它是指针的副本,而不是int.
int *p=new int(0);
大致相同,但int创建的免费存储的值为 0 而不是未初始化。
在这两种情况下,您有责任对delete返回的值调用一次且仅一次new。不这样做被称为内存泄漏。这样做后使用指针值会导致未定义的行为,通常是内存损坏。程序几乎肯定不会警告您所做的事情是危险的,但是您的程序现在可以做完全随机的事情,这些事情毫无意义,仍然是一个有效的 C++ 程序。new将每个都与一个 精确对齐delete,并确保没有人在 之后使用指针delete,并尽可能早地调用delete,这是一个令人讨厌的程序,以至于已经开发了整个类别的编程语言,其主要卖点是它们使开发人员不必处理用它。
所以避免调用new.
哦,因为这还不够长,请注意我使用上面的“最令人烦恼的解析”并不完全正确。“最令人头疼的解析”实际上是:
int x(int());
而不是
int x();
因为第一个可以是构造函数调用或函数,而第二个不能是构造函数调用,因为标准不允许这样做。
但是,我和其他大多数人都发现了 make 的解析规则int x();,因此将其称为倒数第二个 vexing 解析,您不会错太多。它仍然没有做你天真地认为它应该做的事情(创建一个默认的构造函数x),所以它很烦人。
在 C 和早期 C++ 中,您只能使用 int i=0;
该int i(0);模式与通用类型的构造函数相同
T i(0);
因此,它被添加为int i=0;看起来不像一般构造函数模式的替代方案。这在使用模板时很有用。所以模板可以使用 int 和 classes。
int i=0;
int i(0);
它们是相同的,具有两种不同的初始化语法(第一种是 C 语言,第二种是构造函数风格);原则上,它们在处理类时略有不同(第一个暗示 - 可能省略 - 对复制构造函数的调用,第二个没有),但对于ints 它们实际上是相同的。
int *p=new int;
int *p=new int(0);
在第一个中,int未初始化(它将具有恰好在该内存位置的任何值);在第二种情况下,int初始化为 0。
但最重要的是,与第一个相比,这是两个完全不同的野兽。您不是在声明 type 的自动变量int,而是指向的指针int,它指向两个动态分配 int的 s。
区别很深:int i=0内存是自动管理的(它具有自动存储持续时间),并且变量在超出范围时被销毁;与new您从 freestore(所谓的堆)分配内存,它没有自动释放方法 - 您必须显式释放它(delete尽管在现代 C++ 中,智能指针通常用于自动管理动态的生命周期对象)。
new通常仅用于自动存储持续时间不是一个好选择的情况(例如,您希望那些ints 超过当前范围,或者在多个对象之间共享或其他),或者当您分配的东西太大而无法留在一个局部变量中(在典型的实现中,局部变量在堆栈上,它的大小是有限的;仍然,对于两个ints 这不是一个有效的问题)。
对于“常规”,int必须在当前范围内“消亡”的局部变量new通常不是一个好主意。
不过,有关动态分配的更多信息,您应该查看您的 C++ 书籍。
new意味着堆内存分配(可能会泄漏),所以不,您不想一直这样做。
int i=0;和int i(0);是等价的,但是根据实现的不同,第一个可以使用赋值运算符,而第二个可以用一个值构造。这可以让编译器针对目标架构进行优化。在类的情况下,赋值方法可能会更慢,因为它会创建一个类(通常使用默认值),然后它会进行赋值并清除它刚刚花时间分配的所有默认值。
有人可能会插话并参考语言规范以获得更准确的答案。