下面两段代码的输出是一样的,但是本质区别是什么?
import 'dart:math';
class Point {
final num x;
final num y;
final num distanceFromOrigin;
Point(x, y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
main() {
var p = Point(3, 4);
print(p.distanceFromOrigin);
}
我的代码
Point(this.x, this.y)
: distanceFromOrigin = sqrt(x * x + y * y);
两个输出相同 5.
致以诚挚的问候,
没有什么区别,结果是相同的,只是你可以利用不同类型的构造函数。
如果您不想公开 Point 中定义的变量并将其标记为私有,则初始化器将是一个不错的选择。
class Point {
final num _x;
final num _y;
final num _distanceFromOrigin;
Point(x, y)
: _x = x,
_y = y,
_distanceFromOrigin = sqrt(x * x + y * y);
}
还要查看带有可选参数的构造函数或工厂构造函数。
无论是坐火车还是婴儿车,一旦到达车站,只要到达目的地就没有什么“本质”的区别了。
使用“构造函数”和“初始化器列表”,您确实获得了相同的输出,但该示例并不是 Dart 中这些功能支持的唯一用例。此外,较短的形式(使用“构造函数”作为“初始化列表”的替代)只是一种常见的表现形式,Dart 将其提供为“语法糖”:
将构造函数参数分配给实例的模式 变量是如此常见,Dart 有语法糖来使它变得简单:
class Point {
double x = 0;
double y = 0;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
我已经根据您的问题/示例使用了术语“构造函数”和“初始化列表”,但为了清楚地提及差异,我将使用以下术语来获得更广泛的视角:
ClassName[.<constructor-name>](<constructor-args>)
: <initializer-list> {
<constructor-body>
}
在答案的这一部分之前,对“构造函数”的关注在很大程度上忽略了构造函数的主体,而事实正是如此,因为该示例可能旨在引入初始值设定项列表,这从最终成员的参与中可以明显看出。
构造函数的主体不能用于初始化不可为空和/或最终成员。是可以做到的
this.
语法糖),或者class Demo {
int x; // non-nullable member
final int? y; // final member
// ERROR (observe <constructor-body>'s limitation)
Demo.constructor1(int x, int y) {
this.x = x;
this.y = y;
}
// Allowed
Demo.constructor2(int x, int y)
: x = x,
y = y;
// Better (observe <constructor-args>'s syntactic sugar)
Demo.constructor3(this.x, this.y);
// ERROR
Demo.constructor4(this.x, this.y = this.x + 1);
// Allowed (observe <initializer-list>'s rescue)
Demo.constructor5(this.x) : y = x + 1;
}
除此之外,这并不会使初始化器列表成为构造函数体的替代品,因为初始化器列表非常 功能有限。
另一个重要的区别是它们的执行顺序,从设计角度来看这可能很重要,尤其是涉及继承。它恰如其分地记录在语言之旅中:
[...]执行顺序如下:
- 初始化列表
- 超类的无参数构造函数
- 主类的无参数构造函数
如果超类没有未命名、无参数的构造函数, 那么您必须手动调用超类中的构造函数之一。 在冒号 (:) 之后、在 构造函数主体(如果有)。
Jamesdlin 的评论 包含一个很好的总结。
我应该如何正确使用构造函数和初始化列表?
牢记上述差异,“正确”用法客观上遵守此类语言限制,但在两种用法都合适的情况下,我认为您使用语法糖来编写惯用的 Dart 代码是正确的,除非类似的东西另有要求强制风格指南。
在 Dart 中,
constructors
和 initializer lists
都用于初始化类的实例变量,但它们的用途不同,并且具有不同的语法结构。
构造函数
构造函数是创建对象时调用的特殊方法。它可以有参数,并且可以包含设置对象状态的逻辑。
class Person {
String name;
int age;
// Constructor
Person(this.name, this.age);
}
void main() {
var person = Person('Alice', 30);
print('${person.name}, ${person.age}');
}
初始化器列表
初始化列表是一种在构造函数体运行之前初始化实例变量的机制。它对于初始化最终字段或更复杂的初始化逻辑特别有用。
class Person {
final String name;
final int age;
// Constructor with initializer list
Person(String name, int age)
: this.name = name.toUpperCase(), // Using initializer list
this.age = age;
@override
String toString() => '$name, $age';
}
void main() {
var person = Person('Alice', 30);
print(person);
}
主要差异 执行顺序:
初始化列表在构造函数主体之前运行。这意味着您可以在执行构造函数中的任何代码之前初始化字段。
最终字段的使用:
初始化列表对于初始化最终字段特别有用,这些字段只能分配一次。您不能将值分配给构造函数主体中的最终字段。
语法:
在构造函数中,您可以使用初始值设定项列表语法(:fieldName = value)直接设置字段,而在构造函数主体中,您通常使用 this.fieldName。