之间有什么区别
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)
?
答案 0 :(得分:5)
我先回答一个稍微不同的问题。
假设您有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
。但是,该标准允许编译器" elide" (跳过)复制构造函数,而只是直接从Foo i
文字int
构建0
。
Foo* i = new Foo;
这将转到免费存储(通常实现并称为堆),获取足够的内存来存储Foo
,然后默认构造它。然后它返回此Foo
对象的地址,然后用于初始化指针Foo* i
。现在,请注意Foo
的默认构造函数使value
未初始化。这是我在Foo
上面实现的一个缺陷(在我看来),除非在特殊情况下你很少想要这样做。
恼人地,整数(char
,int
,long
,unsigned char
等),浮点(double
或float
)文字和指针文字都共享此属性 - 默认情况下,它们不会初始化为任何值。
因此,您应确保明确初始化它们。如果是Foo
,请添加一行:
Foo():value() {}
就足够了。
Foo* i = new Foo(0);
这将转到免费存储(通常实现并称为堆),获取足够的内存来存储Foo
,然后使用整数文字0构造它。然后返回此{{的地址1}} object,然后用于初始化指针Foo
。
现在,免费商店中的内存通常一直保留供您使用,直到您回复它或程序关闭为止。为了返回它,你在相同的指针上调用Foo* i
,这两个情况下的对象(在这种情况下为delete
)使其析构函数被调用(Foo
没有析构函数,所以这被跳过),然后将内存传回免费商店,供以后调用Foo
时使用。
跟踪这是一个真正的痛苦,并且是一大堆错误的来源,所以你应该避免调用new
。有许多方法可以避免调用new
,包括使用new
来管理内存块,或使用std::vector
和shared_ptr
在堆上创建管理其自身生命周期的对象通过一种称为RAII的技术,或者当你想要对指针的生命周期进行严密控制时使用make_shared
(遗憾的是,unique_ptr
不存在)。
现在让我们走得更远。
make_unique
此无法编译。我说Foo i = 0.0;
的构造函数是显式的,上面只选择调用非显式的构造函数。另一方面:
Foo(double)
这两者都愿意调用显式构造函数,并且工作正常。
接下来,C ++ 11为我们带来了统一的初始化。你可以把它放在Foo i(0.0);
Foo i = Foo(0.0);
- 一个波浪形的大括号里,而不是把你想要的东西用()
初始化。
{}
等。 Foo i{0.0};
Foo i = {0};
与基于{}
的语法相比有一些差异 - 最重要的是它避免了most vexing parse个问题。其他差异包括初始化列表行为,处理显式琐碎构建的内容(()
不会构建名为int x()
的{{1}},但int
会这样做。
说到这一点,是时候回到你的实际问题了。
x
在某些方面与我的int x{}
不同。
首先,它不是int
或struct Foo
。因此,它的行为而不是由您编写的某些代码确定,而是由标准广泛描述。碰巧的是,C ++尝试让像class
这样的原始类型表现得像struct
之类的简单用户定义类型,这很有用。
所以虽然没有"复制构造函数"被调用,int
根本没有构造函数或析构函数,Foo
几乎完全表现为"好像"那里有这样的建设者。
int
创建整数文字0,然后用它初始化int
。编译器可能会忽略它,只需直接创建值为0的整数int i = 0;
。对于int i
,无法直接观察差异。
int i
与int
相同,因为int i(0);
没有非显式构造函数。这只是一种不同的语法。
现在,存在最令人烦恼的解析问题。如果你输入
int i = 0
您与int
相同,但如果您输入
int i = int();
将会发生的事情是编译器会说"这可能是一个名为i的函数,它带有零参数并返回int i = 0
",并且出于各种令人讨厌的原因,优先选择解释为默认值 - 初始化int i();
。因此,您将获得一个名为int
的前向声明函数,而不是整数。
如前所述,避免这种情况的方法是始终使用以下语法:
int
在C ++ 11编译器中。
接下来,我们有
i
在免费商店中创建默认构造(未初始化)int i{};
,并将指针指向变量int* p = new int;
。已发生副本,但它是指针的副本,而不是int
。
int *p
大致相同,但创建的免费商店int
的值为0而不是未初始化。
在这两种情况下,您都有责任仅对int *p=new int(0);
返回的值调用一次int
。不这样做被称为内存泄漏。执行此操作后使用指针值会导致未定义的行为,通常会导致内存损坏。你几乎肯定不会被程序警告你所做的事情是危险的,但是你的程序现在可以做完全随机的事情,这些事情毫无意义,仍然是一个有效的C ++程序。将每个delete
与一个new
对齐,并确保没有人在new
之后使用指针,并尽早调用delete
,这是一个令人烦恼的程序,整个已经开发了各种编程语言,其主要销售特征是它们使开发人员不必处理它。
因此,请避免致电delete
。
哦,因为这还不够长,请注意我使用最烦人的解析"以上不太正确。最令人烦恼的解析"实际上是:
delete
而不是
new
因为第一个可能是构造函数调用或函数,而第二个不能是构造函数调用,因为标准不允许它。
然而,我和其他大多数人都发现了int x(int());
的解析规则,所以称之为倒数第二个烦恼的解析,你就不会错。它仍然没有做你天真以为它应该做的事情(创建一个默认的构造int x();
),所以这很麻烦。
答案 1 :(得分:2)
在C和早期C ++中,你只能使用int i = 0;
int i(0);
模式与一般类型
T i(0);
所以它被添加为int i=0;
的替代,它看起来不像一般的构造函数模式。这在使用模板时很有用。所以模板可以使用int和类。
答案 2 :(得分:2)
int i=0;
int i(0);
这些是相同的,有两种不同的初始化语法(C-类似第一种,构造函数式第二种);根据原则,它们在处理类时略有不同(第一个暗示 - 可能是省略的 - 调用复制构造函数,第二个不是),但对于int
s它们实际上是相同的。 / p>
int *p=new int;
int *p=new int(0);
在第一个中,int
未初始化(它将具有在该内存位置发生的任何值);在第二种情况下,int
被初始化为0.
但最重要的是,这些是与第一种完全不同的两种野兽。您没有声明int
类型的自动变量,而是指针到int
,它指向两个动态分配的 int
s
区别很深:使用int i=0
自动管理内存(它具有自动存储持续时间),当变量超出范围时,变量将被销毁;使用new
从freestore(所谓的堆)中分配内存,它没有自动释放方法 - 你必须显式释放它(使用delete
,尽管在现代C ++智能指针中通常用于自动管理动态对象的生命周期。)
new
通常用于自动存储持续时间不是一个好选择的情况(例如,您希望那些int
比当前范围更长,或者在多个对象之间共享或无论什么)或当你分配的东西太大而无法保留在局部变量中时(在典型的实现中,局部变量会进入堆栈,这在大小上是有限的;对于两个int
来说,这不是一个有效的问题)。
对于“常规”,必须与当前范围int
“死”的本地new
变量通常不是一个好主意。
尽管如此,有关动态分配的更多信息,请查看您的C ++书籍。
答案 3 :(得分:-1)
new
意味着堆内存分配(可能泄漏),所以不,你不想一直这样做。
int i=0;
和int i(0);
是等价的,但是根据实现,第一个可以使用赋值运算符,而第二个可以使用值构造。这可以让编译器针对目标体系结构进行优化。在类的情况下,赋值方法可能较慢,因为它将创建一个类(通常使用默认值),然后它将通过一个赋值并清除它刚刚花费时间分配的所有默认值。
有人可能会参与语言规范并获得更准确的答案。