有什么区别
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 语言,第二种是构造函数风格);原则上,它们在处理类时略有不同(第一个暗示 - 可能省略 - 对复制构造函数的调用,第二个没有),但对于int
s 它们实际上是相同的。
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
通常仅用于自动存储持续时间不是一个好选择的情况(例如,您希望那些int
s 超过当前范围,或者在多个对象之间共享或其他),或者当您分配的东西太大而无法留在一个局部变量中(在典型的实现中,局部变量在堆栈上,它的大小是有限的;仍然,对于两个int
s 这不是一个有效的问题)。
对于“常规”,int
必须在当前范围内“消亡”的局部变量new
通常不是一个好主意。
不过,有关动态分配的更多信息,您应该查看您的 C++ 书籍。
new
意味着堆内存分配(可能会泄漏),所以不,您不想一直这样做。
int i=0;
和int i(0);
是等价的,但是根据实现的不同,第一个可以使用赋值运算符,而第二个可以用一个值构造。这可以让编译器针对目标架构进行优化。在类的情况下,赋值方法可能会更慢,因为它会创建一个类(通常使用默认值),然后它会进行赋值并清除它刚刚花时间分配的所有默认值。
有人可能会插话并参考语言规范以获得更准确的答案。