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

Dart const构造函数实际上是如何工作的?,dart,Dart,我注意到可以在Dart中创建常量构造函数。在文档中,它说constword用于表示编译时常量 我想知道当我使用const构造函数创建对象时会发生什么。这像是一个不可变的对象,它总是相同的并且在编译时可用吗?const构造函数的概念实际上是如何工作的?常量构造函数与常规构造函数有何不同?常量构造函数创建“规范化”实例 也就是说,所有常量表达式都开始标准化,之后这些“标准化”符号用于识别这些常量的等价性 规范化: 将具有多个可能表示形式的数据转换为“标准”规范表示形式的过程。这可以用来比较不同的等价

我注意到可以在Dart中创建常量构造函数。在文档中,它说
const
word用于表示编译时常量


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

常量构造函数创建“规范化”实例

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

规范化:

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


这意味着像
constfoo(1,1)
这样的常量表达式可以表示在虚拟机中用于比较的任何可用形式

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=constbaz(constfoo(1,1),“你好”);//$Baz$Foo$int$1$int$1$String$hello
var baz2=constbaz(constfoo(1,2),“你好”);//$Baz$Foo$int$1$int$2$String$hello
不会每次都重新创建常量。它们在编译时被规范化,并存储在特殊的查找表中(在那里它们通过其规范签名进行散列),然后从中重用

附言

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


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

我在Chris Storms博客上找到了Lasse的答案,这是一个很好的解释

我希望他们不介意我复制内容

这是对final字段的一个很好的解释,但实际上并不是这样 解释常量构造函数。这些示例中没有实际使用的内容 构造函数是常量构造函数。任何班级都有期末考试 字段,是否为常量构造函数

Dart中的字段实际上是一个匿名存储位置,与 一个自动创建的getter和setter,用于读取和更新 它也可以在构造函数的初始值设定项中初始化 名单

最后一个字段是相同的,只是没有setter,所以 设置它的值在构造函数初始值设定项列表中,并且没有 之后更改值的方法-因此为“最终”

const构造函数的要点不是初始化最终字段,任何 生成构造函数可以做到这一点。关键在于创造 编译时常量值:包含所有字段值的对象 在编译时已知,不执行任何语句

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

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

例如:

类点{
静态终点原点=常量点(0,0);
最终整数x;
最终INTY;
常数点(此.x,此.y);
Point.clone(Point-other):x=other.x,y=other.y;//[2]
}
main(){
//将编译时常数指定给p0。
p0点=原点;
//使用常量构造函数创建新点。
点p1=新点(0,0);
//使用非常量构造函数创建新点。
p2点=新点克隆(p0);
//将(相同的)编译时间常数赋给p3。
点p3=常数点(0,0);
打印(相同(p0,p1));//错误
打印(相同(p0,p2));//错误
打印(相同(p0,p3));//正确!
}
编译时常量被规范化。这意味着无论怎样 很多时候写“常量点(0,0)”,只创建一个对象。 这可能是有用的,但不像看上去那么有用,因为你可以 只需创建一个常量变量来保存该值并使用该变量即可 相反

那么,编译时常量到底有什么好处呢

  • 它们对于枚举非常有用
  • 可以使用编译时常量
        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');
            },
          ),
        );
      }
    }