1

有什么区别

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)

4

4 回答 4

5

我要先回答一个稍微不同的问题。

假设您有一个classstruct(它们几乎相同),如下所示:

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

令人讨厌的是,整数(charintlongunsigned char等)、浮点(doublefloat)字面量和指针字面量都共享这个属性——默认情况下,它们不会初始化为任何值。

所以你应该确保它们被显式初始化。在 的情况下Foo,添加一行:

Foo():value() {}

就够了。

Foo* i = new Foo(0);

这进入自由存储区(通常实现并称为堆),获得足够的内存来存储 a Foo,然后用整数文字 0 构造它。然后它返回该Foo对象的地址,然后用于初始化指针Foo* i.

现在,一旦您要求,免费存储中的内存通常会保留供您使用,直到您有时间归还它,或者您的程序关闭。为了返回它,你调用delete了同一个指针,这两种情况下对象(Foo在这种情况下)都调用了它的析构函数(Foo没有析构函数,所以这个被跳过),然后内存被交还给空闲存储供以后调用new.

跟踪这一点真的很痛苦,而且是一大堆错误的根源,所以你应该避免调用new. 有很多方法可以避免调用new,包括使用std::vector来管理内存块,或者在堆上使用shared_ptrmake_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),所以它很烦人。

于 2013-03-12T02:04:17.453 回答
2

在 C 和早期 C++ 中,您只能使用 int i=0;

int i(0);模式与通用类型的构造函数相同

T i(0);

因此,它被添加为int i=0;看起来不像一般构造函数模式的替代方案。这在使用模板时很有用。所以模板可以使用 int 和 classes。

于 2013-03-12T01:58:48.640 回答
2
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++ 书籍。

于 2013-03-12T02:05:09.963 回答
-1

new意味着堆内存分配(可能会泄漏),所以不,您不想一直这样做。

int i=0;int i(0);是等价的,但是根据实现的不同,第一个可以使用赋值运算符,而第二个可以用一个值构造。这可以让编译器针对目标架构进行优化。在类的情况下,赋值方法可能会更慢,因为它会创建一个类(通常使用默认值),然后它会进行赋值并清除它刚刚花时间分配的所有默认值。

有人可能会插话并参考语言规范以获得更准确的答案。

于 2013-03-12T01:54:22.360 回答