当前位置: 首页 > 知识库问答 >
问题:

const构造函数实际上是如何工作的?

史景铄
2023-03-14

我注意到在Dart中可以创建一个const构造函数。在文档中,它说< code>const一词用于表示编译时常数。

我想知道当我使用const构造函数创建对象时会发生什么。这是否像一个不可变对象,它总是相同的并且在编译时可用?const构造函数的概念实际上是如何工作的?const构造函数与常规构造函数有何不同?

共有3个答案

农明辉
2023-03-14

详细解释得很好,但对于实际寻找const构造函数用法的用户来说

它用于提高颤振性能,因为它可以帮助Flutter仅重建应更新的小部件。在 StateFulWidgets 中使用 setState() 时,将仅重建那些非构造函数的组件

可以用例子来解释——

    class _MyWidgetState extends State<MyWidget> {

  String title = "Title";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Column(
        children: <Widget>[
          const Text("Text 1"),
          const Padding(
            padding: const EdgeInsets.all(8.0),
            child: const Text("Another Text widget"),
          ),
          const Text("Text 3"),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          setState(() => title = 'New Title');
        },
      ),
    );
  }
}

在此示例中,只有文本标题应该更改,因此只有此小部件应该重新生成,因此将所有其他小部件设置为 const 构造函数将有助于 flutter 做同样的事情以提高性能

况唯
2023-03-14

我发现Lasse在Chris Storms博客上的答案是一个很好的解释。

飞镖常数构造函数

希望他们不介意我抄袭内容。

这是对final字段的一个很好的解释,但它并没有真正解释const构造函数。在这些例子中,实际上没有使用构造函数是常量构造函数。任何类都可以有final字段,不管是不是const构造函数。

Dart中的字段实际上是一个匿名存储位置,结合了自动创建的getter和setter来读取和更新存储,它也可以在构造函数的初始化器列表中初始化。

final字段也是一样的,只是没有设置器,所以设置它的值的唯一方法是在构造函数初始化列表中,之后就没有办法更改值了——因此有了“final”。

const构造函数的目的不是初始化最终字段,任何生成构造函数都可以这样做。关键是创建编译时常量值:在编译时已知所有字段值的对象,而不执行任何语句。

这对类和构造函数施加了一些限制。const构造函数不能有主体(不执行语句!)并且它的类不能有任何非最终字段(我们在编译时“知道”的值以后不能更改)。初始化列表还必须仅将字段初始化为其他编译时常量,因此右侧仅限于“编译时常量表达式”[1]。并且它必须以“const”为前缀——否则您只会得到一个恰好满足这些要求的普通构造函数。这很好,只是不是const构造函数。

为了使用const构造函数实际创建编译时常量对象,您然后在“new”-表达式中将“new”替换为“const”。您仍然可以将“new”与const构造函数一起使用,它仍然会创建一个对象,但它只是一个普通的新对象,而不是编译时常量值。也就是说:const构造函数也可以用作普通构造函数来在运行时创建对象,以及在编译时创建编译时常量对象。

因此,作为一个例子:

class Point { 
  static final Point ORIGIN = const Point(0, 0); 
  final int x; 
  final int y; 
  const Point(this.x, this.y);
  Point.clone(Point other): x = other.x, y = other.y; //[2] 
}

main() { 
  // Assign compile-time constant to p0. 
  Point p0 = Point.ORIGIN; 
  // Create new point using const constructor. 
  Point p1 = new Point(0, 0); 
  // Create new point using non-const constructor.
  Point p2 = new Point.clone(p0); 
  // Assign (the same) compile-time constant to p3. 
  Point p3 = const Point(0, 0); 
  print(identical(p0, p1)); // false 
  print(identical(p0, p2)); // false 
  print(identical(p0, p3)); // true! 
}

编译时常量被规范化。这意味着无论你写多少次“const Point(0,0)”,你只创建一个对象。这可能很有用 - 但并不像看起来那么多,因为你可以创建一个const变量来保存值并使用变量。

那么,编译时间常数有什么好处呢?

  • 它们对枚举很有用。
  • 您可以在切换情况下使用编译时常量值。
  • 它们被用作注释。

在Dart切换到延迟初始化变量之前,编译时常量曾经更加重要。在此之前,您只能声明一个初始化的全局变量,例如“var x=foo;”,如果“foo”是编译时常量。没有这个要求,大多数程序都可以在不使用任何const对象的情况下编写

所以,简而言之:Const构造函数只是用来创建编译时常量值的。

/L

[1]或者真的:“潜在的编译时常量表达式”,因为它也可能引用构造函数参数。[2]所以是的,一个类可以同时有常量和非常量构造函数。

https://github.com/dart-lang/sdk/issues/36079也讨论了这个话题,并有一些有趣的评论。

臧欣怿
2023-03-14

Const 构造函数创建一个“规范化”实例。

也就是说,所有常量表达式都开始标准化,之后这些“标准化”符号用于识别这些常量的等价性。

规范化:

将具有多个可能表示形式的数据转换为“标准”规范表示的过程。这样做可以比较不同的表示等效性,计算不同数据结构的数量,通过消除重复计算来提高各种算法的效率,或者可以施加有意义的排序顺序。

这意味着像const Foo(1,1)这样的const表达式可以表示任何对虚拟机中的比较有用的可用形式。

VM只需要按照值类型和参数在常量表达式中出现的顺序来考虑它们。当然,它们是为了优化而减少的。

具有相同规范化值的常数:

var foo1 = const Foo(1, 1); // #Foo#int#1#int#1
var foo2 = const Foo(1, 1); // #Foo#int#1#int#1

具有不同规范化值的常数(因为签名不同):

var foo3 = const Foo(1, 2); // $Foo$int$1$int$2
var foo4 = const Foo(1, 3); // $Foo$int$1$int$3

var baz1 = const Baz(const Foo(1, 1), "hello"); // $Baz$Foo$int$1$int$1$String$hello
var baz2 = const Baz(const Foo(1, 2), "hello"); // $Baz$Foo$int$1$int$2$String$hello

常数不会每次都重新创建。它们在编译时被规范化,并存储在特殊的查找表中(在那里它们被规范化签名散列),以后可以从这些表中重用它们。

附笔。

这些示例中使用的形式< code>#Foo#int#1#int#1仅用于比较目的,它不是Dart VM中规范化(表示)的真实形式;

但真正的规范化形式必须是“标准”的规范表示。

 类似资料:
  • 在Dart中,工厂构造函数需要编码器提供更多逻辑,但与常量构造函数没有太大区别,只是它们允许“非最终”实例变量。 与const Constructor相比,它们有哪些优点? 谢谢大家。 编辑 下面是关于Seth Ladd博客“Dart-试图理解“工厂”构造函数的价值”中工厂构造函数的用法。 恕我直言,使用通用构造函数,可以通过细微的差异实现相同的效果,但相当简单。 如上所示,尽管这两个实例 所以,

  • 使用Java8泛型,可以使用构造函数引用初始化该变量,如下所示: Java编译器如何将其转换为字节码? 我知道对于其他类型,比如,它可以使用一个指令,该指令指向字符串构造函数,这只是一个具有特殊含义的方法。

  • 问题内容: 编译器或OS如何区分sig_atomic_t类型和普通的int类型变量,并确保操作是原子的?两者都使用的程序具有相同的汇编代码。如何特别注意使操作原子化? 问题答案: 不是原子数据类型。仅仅是允许您在信号处理程序的上下文中使用的数据类型,仅此而已。因此最好将其名称理解为“相对于信号处理而言是原子的”。 为了保证与信号处理程序之间的通信,仅需要原子数据类型的属性之一,即读取和更新将始终看

  • 问题内容: 我有一个Applet,它使用URLConnection通过HTTP连接加载图像。我正在为所有连接设置setUseCaches(true),但仍然看不到任何缓存行为。我图像的HTTP标头具有合理的缓存设置。如果您查看错误4528599,则有一个相当神秘的陈述: Java插件的当前版本(1.3.1)仅检查浏览器缓存中名称以.jar或.class结尾的文件。我被告知,对于Java Plug-

  • 问题内容: 首先,我要说我知道它是什么,做什么以及如何使用它。这个问题关系到它在引擎盖下的工作方式,我不希望出现“这就是如何用” 循环数组”的答案。 很长时间以来,我一直认为该方法可用于数组本身。然后,我发现了很多关于它可以与数组 副本 一起使用的事实的引用,从那时起,我一直以为这是故事的结尾。但是我最近对此事进行了讨论,经过一番实验后发现这实际上并非100%正确。 让我表明我的意思。对于以下测试

  • 问题内容: 假设我们有一个类型为变量的变量,它返回一个整数数组: 使用Java 8泛型,可以使用如下构造函数引用来初始化此变量: Java编译器如何将其转换为字节码? 我知道对于其他类型,例如,它可以使用指向String构造函数的指令,这只是一种具有特殊含义的方法。 看到有构造数组的特殊说明,这对数组如何工作? 问题答案: 您可以通过反编译Java字节码来发现自己: 编译器对数组lambda()进