167

在 C#、C++ 和 Java 中,当您创建一个带参数的构造函数时,默认的无参数构造函数就会消失。我一直只是接受这个事实,但现在我开始想知道为什么。

这种行为的原因是什么?它只是一种“安全措施/猜测”,说“如果你已经创建了自己的构造函数,你可能不希望这个隐含的构造函数存在”?或者它是否有技术原因导致编译器无法在您自己创建构造函数后添加一个?

4

11 回答 11

224

如果您添加了自己的构造函数,编译器没有理由无法添加构造函数 - 编译器几乎可以做任何它想做的事情!然而,你必须看看什么是最有意义的:

  • 如果我没有为非静态类定义任何构造函数,我很可能希望能够实例化该类。为了实现这一点,编译器必须添加一个无参数构造函数,它没有任何作用,但允许实例化。这意味着我不必为了让它工作而在我的代码中包含一个空的构造函数。
  • 如果我定义了自己的构造函数,尤其是带有参数的构造函数,那么我很可能有自己的逻辑,必须在创建类时执行。如果编译器在这种情况下创建一个空的、无参数的构造函数,它将允许某人跳过我编写的逻辑,这可能会导致我的代码以各种方式中断。如果在这种情况下我想要一个默认的空构造函数,我需要明确地说出来。

因此,在每种情况下,您都可以看到当前编译器的行为在保留代码的可能意图方面最有意义。

于 2012-08-03T08:43:15.447 回答
71

语言必须以这种方式设计当然没有技术原因。

我可以看到四个有点现实的选项:

  1. 根本没有默认构造函数
  2. 目前的情景
  3. 默认情况下始终提供默认构造函数,但允许显式抑制它
  4. 始终提供默认构造函数不允许它被抑制

选项 1 有点吸引人,因为我编写的代码越多,我真正想要一个无参数构造函数的频率就越低。总有一天,我应该计算一下我实际上最终使用默认构造函数的频率......

选项2我很好。

对于语言的其余部分,选项 3 与 Java 和 C# 的流程背道而驰。从来没有任何东西是你明确“删除”的,除非你明确地让事情变得比 Java 中的默认设置更私密。

选项 4 太可怕了——您绝对希望能够使用某些参数强制构造。new FileStream()甚至意味着什么?

因此,基本上,如果您接受提供默认构造函数完全有意义的前提,我相信一旦您提供自己的构造函数就禁止它是很有意义的。

于 2012-08-03T08:43:21.857 回答
20

编辑。实际上,虽然我在第一个答案中所说的是有效的,但这才是真正的原因。:

一开始是 C。C 不是面向对象的(您可以采用 OO 方法,但它对您没有帮助或强制执行任何操作)。

然后是 C With Classes,后来改名为 C++。C++ 是面向对象的,因此鼓励封装,并确保对象的不变性——在构造以及任何方法的开始和结束时,对象都处于有效状态。

与此相关的自然做法是强制类必须始终具有构造函数以确保它以有效状态启动 - 如果构造函数不需要做任何事情来确保这一点,那么空构造函数将记录这一事实.

但是 C++ 的目标是尽可能地与 C 兼容,所有有效的 C 程序也是有效的 C++ 程序(不再是一个活跃的目标,而 C 与 C++ 分离的演变意味着它不再适用)。

这样做的一个影响是 和 之间的功能struct重复class。前者以 C 方式做事(默认情况下所有内容都是公开的),后者以良好的 OO 方式做事(默认情况下所有内容都是私有的,开发人员主动公开他们想要公开的内容)。

另一个原因是,为了使 C struct(由于 C 没有构造函数而不能具有构造函数)在 C++ 中有效,那么对于 C++ 的看待方式来说,它必须具有意义。因此,虽然没有构造函数会违背积极确保不变量的 OO 实践,但 C++ 认为这意味着存在一个默认的无参数构造函数,它的行为就像它有一个空的主体一样。

现在所有的 Cstructs都是有效的 C++ structs(这意味着它们与 C++ 相同classes,所有内容 - 成员和继承 - 公共)从外部被视为具有单个无参数构造函数。

但是,如果您确实在 a 中放置了构造函数classstruct那么您正在以 C++/OO 方式而不是 C 方式进行操作,并且不需要默认构造函数。

由于它是一种速记,即使在其他情况下无法兼容时,人们也继续使用它(它使用了 C 语言中没有的其他 C++ 特性)。

因此,当 Java 出现(在许多方面基于 C++)和后来的 C#(以不同的方式基于 C++ 和 Java)时,他们保留了这种方法,因为编码人员可能已经习惯了这种方法。

Stroustrup 在他的 The C++ Programming Language中写到了这一点,甚至更多,在C++ 的设计和演变中更加关注语言的“为什么” 。

===原始答案===

假设这没有发生。

假设我不想要一个无参数的构造函数,因为没有它我无法将我的类置于有意义的状态。事实上,这是struct在 C# 中可能发生的事情(但如果你不能在 C# 中有意义地使用全零和空值struct,那么你充其量只能使用非公开可见的优化,否则会有使用中的设计缺陷struct)。

为了让我的类能够保护它的不变量,我需要一个特殊的removeDefaultConstructor关键字。至少,我需要创建一个私有的无参数构造函数,以确保没有调用代码调用默认值。

这使语言更加复杂。最好不要这样做。

总而言之,最好不要将添加构造函数视为删除默认值,最好将根本没有构造函数视为添加不做任何事情的无参数构造函数的语法糖。

于 2012-08-03T15:48:23.623 回答
13

如果您自己不做任何事情来控制对象的创建,则会添加默认的无参数构造函数。一旦您创建了一个构造函数来获得控制权,编译器就会“退出”并让您拥有完全的控制权。

如果不是这种方式,如果您只想通过带参数的构造函数构造对象,则需要某种显式方法来禁用默认构造函数。

于 2012-08-03T08:43:19.707 回答
3

我认为问题应该是相反的:如果您没有定义任何其他构造函数,为什么不需要声明默认构造函数?

对于非静态类,构造函数是必需的。
所以我认为如果你没有定义任何构造函数,生成的默认构造函数只是 C# 编译器的一个方便特性,如果没有构造函数,你的类也将无效。所以隐式生成一个什么都不做的构造函数没有错。它看起来肯定比到处都是空的构造函数更干净。

如果您已经定义了一个构造函数,那么您的类是有效的,那么为什么编译器要假定您需要一个默认构造函数呢?如果你不想要一个怎么办?实现一个属性来告诉编译器不生成该默认构造函数?我认为这不是一个好主意。

于 2012-08-03T08:44:42.200 回答
3

这是编译器的一个便利功能。如果你定义了一个带参数的构造函数,但没有定义一个无参数的构造函数,那么你不想允许一个无参数的构造函数的可能性要高得多。

对于许多使用空构造函数进行初始化没有意义的对象来说就是这种情况。

否则,您必须为要限制的每个类声明一个私有无参数构造函数。

在我看来,允许一个需要参数才能起作用的类的无参数构造函数是不好的风格。

于 2012-08-03T08:45:21.617 回答
1

只有当类没有构造函数时,才能构造默认构造函数。编译器的编写方式仅将其作为备份机制提供。

如果您有参数化构造函数,您可能不希望使用默认构造函数创建对象。如果编译器提供了默认构造函数,您将不得不编写一个无参数构造函数并将其设为私有,以防止使用无参数创建对象。

此外,您更有可能忘记禁用或“私有化”默认构造函数,从而导致难以捕获的潜在功能错误。

现在,如果您希望以默认方式或通过传递参数创建对象,则必须显式定义无参数构造函数。这是经过严格检查的,否则编译器会抱怨,从而确保这里没有漏洞。

于 2012-08-03T20:11:32.700 回答
1

前提

这种行为可以看作是类具有默认公共无参数构造函数的决定的自然扩展。基于所提出的问题,我们将此决定作为前提,并假设我们在这种情况下不会质疑它。

删除默认构造函数的方法

因此,必须有一种方法可以删除默认的公共无参数构造函数。这种移除可以通过以下方式完成:

  1. 声明一个非公共的无参数构造函数
  2. 声明带参数的构造函数时自动移除无参数构造函数
  3. 一些关键字/属性指示编译器删除无参数构造函数(很尴尬,很容易排除)

选择最佳解决方案

现在我们扪心自问:如果没有无参构造函数,它必须被什么替代?以及在什么类型的场景下,我们希望移除默认的公共无参数构造函数?

事情开始到位。首先,它必须要么用带参数的构造函数替换,要么用非公共构造函数替换。其次,您不想要无参数构造函数的场景是:

  1. 我们根本不希望类被实例化,或者我们想控制构造函数的可见性:声明一个非公共构造函数
  2. 我们要强制在构造时提供参数:声明带参数的构造函数

结论

我们有了它——正是 C#、C++ 和 Java 允许删除默认公共无参数构造函数的两种方式。

于 2012-08-25T23:59:15.863 回答
1

我认为这是由编译器处理的。如果您在其中打开.net程序集,ILDASM您将看到默认构造函数,即使它不在代码中。如果您定义参数化构造函数,则不会看到默认构造函数。

Actually when you define the class (non static), the compiler provides this feature thinking that you will be just creating an instance. And if you want any specific operation to perform you surely will be having your own constructor.

于 2012-08-27T17:38:56.430 回答
0

这是因为当您不定义构造函数时,编译器会自动为您生成一个不带任何参数的构造函数。当你想从构造函数中得到更多东西时,你会覆盖它。这不是函数重载。因此,编译器现在看到的唯一构造函数是带有参数的构造函数。为了解决这个问题,如果构造函数没有传递值,您可以传递一个默认值。

于 2012-08-26T05:35:58.780 回答
0

A class need a constructor. It is mandatory requirement.

  • If you don't create one, parameterless constructor will be given to you automatically.
  • If you don't want a parameterless constructor, then you need to create your own.
  • If you need both, parameterless constructor and parameter based-constructor you can add them manually.

I will answer you question with another, why do we want always a default parameterless constructor? there are case where this is not desired, so the developer has the control to add or remove it as is need.

于 2019-01-14T20:52:02.760 回答