Dart - 语法糖(持续更新)

news2024/11/18 7:28:57

文章目录

  • 前言
  • 开发环境
  • 中间表示
  • 语法糖
    • 1. 操作符/运算符(?./??/??=/../?../.../...?)
    • 2. 循环(for-in)
    • 3. 函数/方法(=>)
    • 4. 关键字(await for)
  • 最后


前言

通过将dill文件序列化为可读文本查看Dart语法糖的中间表示(IR),并尝试反推大致的等价源码,便于进一步理解和使用这些语法糖。

开发环境

  • macOS: 13.4
  • Dart: 3.0.5

中间表示

中间表示序列化文本的本质是解析抽象语法树(AST)的各个节点并打印拼接成文本,节点打印以及内容拼接格式请参考ast_to_text.dart。以下列出一些常见的格式方便后面阅读理解中间表示序列化为文本后的内容:

注意,这些不是官方说明,只是通过阅读和调试源码得到的个人经验总结,仅供参考。如果需要调试源码,请参考这篇文章Dart - dill文件序列化为可读文本。

  • ::

用于指定库或类的成员。例如core::String表示dart.core库中的String类型,Dart常见类型都属于这个核心库,详见core.dart。

如果你遇到奇怪的库名称,那一般是因为成员找不到所属的具体库,比如你自己自定义的类或者顶级函数等,这时的库名称是成员所在dart文件的文件名(有所缩减)。例如下文中经常出现的syn,刚开始我也以为是Dart中的某个库,结果搜遍了Dart SDK都没找到定义的源码,没办法直接开启调试,结果原来是文件名缩写。

screenshot1

关键源码(位于ast_to_text.dart):

class NameSystem {
  ...
  String nameCanonicalNameAsLibraryPrefix(Reference? node, CanonicalName? name,
      {String? proposedName}) {
    return prefixes.disambiguate(node, name, () {
      if (proposedName != null) return proposedName;
      CanonicalName? canonicalName = name ?? node?.canonicalName;
      if (canonicalName?.name != null) {
        String path = canonicalName!.name;
        int slash = path.lastIndexOf(pathSeparator);
        if (slash >= 0) {
          path = path.substring(slash + 1);
        }
        if (path.endsWith('.dart')) {
          path = path.substring(0, path.length - '.dart'.length);
        }
        return abbreviateName(path);
      }
      return 'L';
    });
  }

  final RegExp punctuation = new RegExp('[.:]');

  String abbreviateName(String name) {
    int dot = name.lastIndexOf(punctuation);
    if (dot != -1) {
      name = name.substring(dot + 1);
    }
    if (name.length > 4) {
      return name.substring(0, 3);
    }
    return name;
  }
}

文件名为syntactic_sugar.dart时,会先截掉文件扩展名,然后截取前三个字符,得到syn。如果文件名是aa.bb.dart,会得到bb

  • let表达式

let表达式通过定义一个局部变量避免一些重复计算,一般长这样(来源Let节点的文档注释):

let v = x in y

let表达式的返回值取决于in子句中表达式y的值。举个下文中的例子🌰:

let final core::String? #t1 = syn::a in #t1 == null ?{core::int?} null : #t1{core::String}.{core::String::length}{core::int}
  • vfinal core::String? #t1
  • xsyn::a
  • y#t1 == null ?{core::int?} null : #t1{core::String}.{core::String::length}{core::int}

#t1是一个局部变量,命名规则是#t加上索引值,参考NameSystem类。

  • @vm.call-site-attributes.metadata=receiverType:library

这是由Dart VM使用的一种特殊的元数据,用于存储调用接收者的静态类型,以便优化后端编译。以上是个人理解,可能有偏差。其实这个不了解也不太影响阅读序列化的文本。

语法糖

1. 操作符/运算符(?./??/??=/…/?../…/…?)

  • ?.(条件访问成员)

使用示例:

String? a;
var b = a?.length;

中间表示:

static field core::String? a;
static field core::int? b = let final core::String? #t1 = syn::a in #t1 == null ?{core::int?} null : #t1{core::String}.{core::String::length}{core::int};

?.语法糖通过条件表达式(条件 ? 表达式 1 : 表达式 2)做了一次判空操作,当对象为空时返回null,不为空时继续访问成员。

  • ??(空感知运算符)

使用示例:

String? a;
var b = a ?? 'c';

中间表示:

static field core::String? a;
static field core::String b = let final core::String? #t1 = syn::a in #t1 == null ?{core::String} "c" : #t1{core::String};

??语法糖和?.语法糖类似,也是通过条件表达式做了一次判空操作,当对象为空时返回提供的默认值,不为空时返回对象。

  • ??=(空感知赋值运算符)

使用示例:

void test() {
  String? a;
  // 作用域有限制,需要在方法中使用
  a ??= 'b';
  // 大致等价源码
  a == null ? a = 'b' : null;
}

中间表示:

static method test() → void {
  core::String? a;
  a == null ?{core::String} a = "b" : null;
  a{core::String} == null ?{core::String?} a = "b" : null;
}

??=语法糖通过判断对象是否为空决定是否要给对象赋值。

  • ..(级联运算符)

使用示例:

void test() {
  List<String> l = [];
  var l1 = l
    ..add('a')
    ..[0] = 'b'
    ..length;
  // 大致等价源码
  l.add('a');
  l[0] = 'b';
  l.length;
  var l2 = l;
}

中间表示:

static method test() → void {
  core::List<core::String> l = core::_GrowableList::•<core::String>(0);
  core::List<core::String> l1 = let final core::List<core::String> #t1 = l in block {
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] #t1.{core::List::add}("a"){(core::String) → void};
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] #t1.{core::List::[]=}(0, "b"){(core::int, core::String) → void};
    #t1.{core::List::length}{core::int};
  } =>#t1;
  [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] l.{core::List::add}("a"){(core::String) → void};
  [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] l.{core::List::[]=}(0, "b"){(core::int, core::String) → void};
  l.{core::List::length}{core::int};
  core::List<core::String> l2 = l;
}

..语法糖通过let表达式创建一个临时变量并赋值为,然后在代码块中依次调用方法,最后返回临时变量。

  • ?..(空感知级联运算符)

使用示例:

void test() {
  List<String>? l;
  var l1 = l
    ?..add('a')
    ..[0] = 'b'
    ..length;
}

中间表示:

static method test() → void {
  core::List<core::String>? l;
  core::List<core::String>? l1 = let final core::List<core::String>? #t1 = l in #t1 == null ?{core::List<core::String>?} null : block {
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] #t1{core::List<core::String>}.{core::List::add}("a"){(core::String) → void};
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] #t1{core::List<core::String>}.{core::List::[]=}(0, "b"){(core::int, core::String) → void};
    #t1{core::List<core::String>}.{core::List::length}{core::int};
  } =>#t1;
}

?..语法糖和..语法糖相比,主要区别在于执行代码块之前通过条件表达式做了一次判空操作。

  • ...(扩展操作符)

使用示例:

void test() {
  List<String> l = ['a', 'b'];
  var l1 = [...l, 'c'];
  // 大致等价源码
  var l2 = List.of(l);
  l2.add('c');
}

中间表示:

static method test() → void {
  core::List<core::String> l = core::_GrowableList::_literal2<core::String>("a", "b");
  core::List<core::String> l1 = block {
    final core::List<core::String> #t1 = core::List::of<core::String>(l);
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] #t1.{core::List::add}{Invariant}("c"){(core::String) → void};
  } =>#t1;
  core::List<core::String> l2 = core::List::of<core::String>(l);
  [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] l2.{core::List::add}("c"){(core::String) → void};
}

...语法糖通过List.of方法从已有数组中创建一个新的数组,然后调用add方法添加其他元素。如果语法糖用于Map,和用于数组时所做的操作类似。

  • ...?(空感知扩展操作符)

使用示例:

void test() {
  List<String>? l;
  var l1 = [...?l, 'c'];
}

中间表示:

static method test() → void {
  core::List<core::String>? l;
  core::List<core::String> l1 = block {
    final core::List<core::String> #t1 = core::_GrowableList::•<core::String>(0);
    final core::Iterable<core::String>? #t2 = l;
    if(!(#t2 == null))
      [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] #t1.{core::List::addAll}{Invariant}(#t2{core::Iterable<core::String>}){(core::Iterable<core::String>) → void};
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] #t1.{core::List::add}{Invariant}("c"){(core::String) → void};
  } =>#t1;
}

...?语法糖和...语法糖相比,主要区别在于增加了临时变量用于判断已有数组是否为空,如果为空则不处理已有数组,不为空则通过addAll方法添加已有数组。

2. 循环(for-in)

  • for-in

使用示例:

void test() {
  List<String> params = [];
  for (var param in params) {}
  // 大致等价源码
  Iterator<String> iterator = params.iterator;
  for (; iterator.moveNext();) {
    String param = iterator.current;
  }
}

中间表示:

static method test() → void {
  core::List<core::String> params = core::_GrowableList::•<core::String>(0);
  {
    synthesized core::Iterator<core::String> :sync-for-iterator = params.{core::Iterable::iterator}{core::Iterator<core::String>};
    for (; :sync-for-iterator.{core::Iterator::moveNext}(){() → core::bool}; ) {
      core::String param = :sync-for-iterator.{core::Iterator::current}{core::String};
      {}
    }
  }
  core::Iterator<core::String> iterator = params.{core::Iterable::iterator}{core::Iterator<core::String>};
  for (; iterator.{core::Iterator::moveNext}(){() → core::bool}; ) {
    core::String param = iterator.{core::Iterator::current}{core::String};
  }
}

for-in语法糖是由标准的for循环Iterator实现的。每次循环调用moveNext方法会迭代到下一个元素,通过current方法获取当前迭代的元素。如果已经迭代结束,moveNext方法会返回false结束循环。

3. 函数/方法(=>)

  • =>(箭头函数)

使用示例:

bool test() => true;
// 大致等价源码
bool test1() {
  return true;
}

中间表示:

static method test() → core::bool
  return true;
static method test1() → core::bool {
  return true;
}

=>语法糖补全了return,同时因为是单行函数,所以不加{}

4. 关键字(await for)

  • await for

使用示例:

void test() async {
  var server = await HttpServer.bind(
    InternetAddress.anyIPv4,
    8080,
  );
  await for (HttpRequest request in server) {}
  // 大致等价源码
  var stream = server;
  StreamIterator<HttpRequest>? iterator = StreamIterator<HttpRequest>(stream);
  try {
    while (await iterator.moveNext()) {
      var request = iterator.current;
    }
  } finally {
    if (iterator != null) {
      await iterator.cancel();
    }
  }
}

中间表示:

static method test() → void async /* futureValueType= void */ {
  _ht::HttpServer server = await _ht::HttpServer::bind(io::InternetAddress::anyIPv4, 8080);
  {
    synthesized _ht::HttpServer :stream = server;
    synthesized asy::_StreamIterator<_ht::HttpRequest>? :for-iterator = new asy::_StreamIterator::•<_ht::HttpRequest>(:stream);
    try
      while (let dynamic #t1 = asy::_asyncStarMoveNextHelper(:stream) in await :for-iterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>}) {
        _ht::HttpRequest request = :for-iterator.{asy::_StreamIterator::current}{_ht::HttpRequest};
        {}
      }
    finally
      if(!(:for-iterator.{asy::_StreamIterator::_subscription}{asy::StreamSubscription<_ht::HttpRequest>?} == null))
        await :for-iterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>};
  }
  _ht::HttpServer stream = server;
  asy::StreamIterator<_ht::HttpRequest>? iterator = asy::StreamIterator::•<_ht::HttpRequest>(stream);
  try {
    while (await iterator{asy::StreamIterator<_ht::HttpRequest>}.{asy::StreamIterator::moveNext}(){() → asy::Future<core::bool>}) {
      _ht::HttpRequest request = iterator{asy::StreamIterator<_ht::HttpRequest>}.{asy::StreamIterator::current}{_ht::HttpRequest};
    }
  }
  finally {
    if(!(iterator{asy::StreamIterator<_ht::HttpRequest>} == null)) {
      await iterator{asy::StreamIterator<_ht::HttpRequest>}.{asy::StreamIterator::cancel}(){() → asy::Future<dynamic>};
    }
  }
}

await for语法糖用于处理流(Stream),在服务端开发中比较常见,以上使用示例是对HTTP请求进行处理。从中间表示可见,该语法糖先用HttpServer对象创建流迭代器StreamIterator,然后开启一个while循环迭代流的值。

这里简单讲讲流的迭代原理,如果不感兴趣可以略过。如果一个Future对象有await关键字修饰但又一直不调用complete方法,那么Future对象就会一直处于未完成的状态,程序会被阻塞无法继续往下执行。while循环中的判断语句就是利用这个实现对流的迭代,具体实现源码位于_StreamIterator类。

_StreamIterator类中的moveNext方法:

Future<bool> moveNext() {
  var subscription = _subscription;
  if (subscription != null) {
    if (_hasValue) {
      var future = new _Future<bool>();
      _stateData = future;
      _hasValue = false;
      subscription.resume();
      return future;
    }
    throw new StateError("Already waiting for next.");
  }
  return _initializeOrDone();
}

StreamIterator对象首次调用moveNext方法时,因为还未订阅流,所以会执行_initializeOrDone方法。如果已经订阅且当前有值(不是在等待下一个值)则恢复被暂停的订阅并返回一个新的Future对象,这时因为while循环中的判断语句有await关键字修饰,程序将被阻塞,直到下一个值的到来(在_onData方法中接收处理)。

_StreamIterator类中的_initializeOrDone方法:

Future<bool> _initializeOrDone() {
  assert(_subscription == null);
  var stateData = _stateData;
  if (stateData != null) {
    Stream<T> stream = stateData as dynamic;
    var future = new _Future<bool>();
    _stateData = future;
    // The `listen` call may invoke user code, and it might try to emit
    // events.
    // We ignore data events during `listen`, but error or done events
    // are used to asynchronously complete the future and set `_stateData`
    // to null.
    // This ensures that we do no other user-code callbacks during `listen`
    // than the `onListen` itself. If that code manages to call `moveNext`
    // again on this iterator, then we will get here and fail when the
    // `_stateData` is a future instead of a stream.
    var subscription = stream.listen(_onData,
        onError: _onError, onDone: _onDone, cancelOnError: true);
    if (_stateData != null) {
      _subscription = subscription;
    }
    return future;
  }
  return Future._falseFuture;
}

_initializeOrDone方法的作用是订阅流,监听流的各个事件,当有新的值时会调用_onData方法。返回Future对象的作用和moveNext方法一样,是为了阻塞程序。

_StreamIterator类中的_onData方法:

void _onData(T data) {
  // Ignore events sent during the `listen` call
  // (which can happen if misusing synchronous broadcast stream controllers),
  // or after `cancel` or `done` (for *really* misbehaving streams).
  if (_subscription == null) return;
  _Future<bool> moveNextFuture = _stateData as dynamic;
  _stateData = data;
  _hasValue = true;
  moveNextFuture._complete(true);
  if (_hasValue) _subscription?.pause();
}

_stateData变量指向的是moveNext方法或initializeOrDone方法返回的Future对象,新的值到来后_stateData变量改为指向新的值,后续可以通过current方法获取。Future对象调用complete方法结束while循环中的判断语句阻塞,同时订阅对象调用pause方法暂停订阅直到循环体执行结束再次调用moveNext方法恢复订阅,如此往复循环,只要流不关闭,可以一直迭代处理。

从以上流的迭代原理可知,如果有并发要求则不能在循环体内阻塞程序,不然会导致流的迭代被阻塞,实测确实如此,所以在处理HTTP请求时请尽量使用异步。

最后

如果这篇文章对你有所帮助,请不要吝啬你的点赞👍加星🌟,谢谢~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/799968.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【时间序列预测 2023 ICLR】TimesNet

【时间序列预测 2023 ICLR】TimesNet 论文题目&#xff1a;TIMESNET: TEMPORAL 2D-VARIATION MODELING FOR GENERAL TIME SERIES ANALYSIS 中文题目&#xff1a;TimesNet:用于一般时间序列分析的时态二维变异建模 论文链接&#xff1a;https://arxiv.org/abs/2210.02186 论文代…

大专同事一个人7天完成CRM系统开发,怪不得月薪3w

在当今的快节奏商业环境中&#xff0c;成功的关键是敏捷和高效。这使得开发自己的客户关系管理系统&#xff08;CRM&#xff09;成为许多企业的迫切需求。CRM有助于跟踪和优化销售线索&#xff0c;管理客户交互&#xff0c;并提高业务效率。但是&#xff0c;对于许多企业来说&a…

大学毕业后,我就去当了2个月外卖骑手,哭了一整晚

先简单介绍一下自己&#xff0c;我来自长沙&#xff0c;大学学的的物流管理专业&#xff0c;现在就职于一家互联网公司&#xff0c;从事软件测试工作。 我来自长沙县的一个偏远农村&#xff0c;家里兄弟姐妹多&#xff0c;父母无力负担我的学费&#xff0c;很多时候学费都是靠姐…

智慧工厂4G+蓝牙+UWB+RTK人员定位系统解决方案

人员定位在智慧工厂的应用正逐渐受到重视&#xff0c;通过使用现代化的技术和智能终端设备&#xff0c;工厂管理者能够实时定位和跟踪员工的位置&#xff0c;方便进行人员调度管理和监督人员的工作情况&#xff1b;人员遇到紧急情况&#xff0c;可通过定位设备一键报警求救&…

智慧消防:如何基于视频与智能分析技术搭建可视化风险预警平台?

一、背景分析 消防安全是一个重要的话题&#xff0c;涉及到每个人的生活和安全。每年都会发生大量的火灾&#xff0c;给人们带来极大的危害&#xff0c;摧毁了大量的财产&#xff0c;甚至造成了可怕的人员伤亡。而消防安全监督管理部门人员有限&#xff0c;消防安全监管缺乏有…

2.2 模型与材质基础

一、渲染管线与模型基础 1. 渲染管线 可编程阶段&#xff08;蓝色区域&#xff09;&#xff1a; 1顶点着色器 2几何着色器 3片元着色器 2. 模型的实现原理 UV&#xff1a;在建模软件中&#xff0c;进行UV展开&#xff0c;UV会放在一个横向为U纵向为V&#xff0c;范围&#xff0…

TEE GP(Global Platform)功能认证实验室

TEE之GP(Global Platform)认证汇总 GP认证实验室主要面向功能认证、SE安全认证、TEE安全认证&#xff0c;对于TEE来说&#xff0c;则分为TEE功能认证和TEE安全认证。本文对功能认证相关实验室机构进行总结和介绍。 一、国内3家 二、国外3家 参考&#xff1a; GlobalPlatform …

回归预测 | MATLAB实现POA-CNN-BiLSTM鹈鹕算法优化卷积双向长短期记忆神经网络多输入单输出回归预测

回归预测 | MATLAB实现POA-CNN-BiLSTM鹈鹕算法优化卷积双向长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现POA-CNN-BiLSTM鹈鹕算法优化卷积双向长短期记忆神经网络多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLA…

安全杂记 - js中的this关键字

javascript里什么是this this是js中的一个关键字&#xff0c;它是函数在运行时生成的一个内部对象&#xff0c;是属性和方法。 this就是属性或方法“当前”所在的对象&#xff0c;也就是调用函数的那个对象 this的使用场合 1.函数调用 <script>var a100;function test…

jenkins部署vue项目

文章目录 1 先安装node.js上传到linux并解压 jenkins 创建任务执行脚本 build Stepsvue jenkins shell 脚本 (jenkins-web.sh)启动 如何在linux上安装jenkins可以看上一篇 linux安装jenkins(详细步骤) 1 先安装node.js 建议最好跟你本地安装版本一致,减少没有必要的麻烦:node.…

UCloud上线可商用LLaMA2镜像,助力AGI应用发展

随着人工智能技术的快速发展&#xff0c;大模型应用在自然语言处理、图像识别、智能交互等领域展现出了巨大的潜力&#xff0c;为企业带来了更多创新和商机。众多企业纷纷将大模型应用于产品开发和业务优化中&#xff0c;希望通过提升智能化水平和用户体验来赢得竞争优势。近日…

【黑马头条之文章详情-静态文件生成】

本笔记内容为黑马头条项目的文章详情-静态文件生成部分 目录 一、思路分析 二、实现步骤 基础知识可参考下面两篇文章 【黑马头条之对象存储服务MinIO】_蛋饼吧的博客-CSDN博客 【黑马头条之freemarker入门】_蛋饼吧的博客-CSDN博客 一、思路分析 文章端创建app相关文章时…

如何获得SOLIDWORKS全认证资格证书!

SolidWorks认证考试 完成SolidWorks认证考试并获得专业SolidWorks认证是一项伟大的成就。它不仅在你的简历上自豪地闪耀&#xff0c;而且还向雇主、决策者和同事展示了你对软件的技能和知识水平。公司通常会将某些策略和标准设定为一项业务&#xff0c;以帮助保持在竞争中的领头…

桃子叶片病害数据集

1.分为三类 健康的桃子叶片 &#xff0c;251张 桃疮痂病一般&#xff0c;857张 桃疮痂病严重&#xff0c;770 张 数据集链接 import os import jsonimport torch from PIL import Image from torchvision import transforms import tkinter as tk from tkinter import file…

MySQL 日志管理

目录 一、四种日志介绍 1.1 错误日志 1.2 通用查询日志 1.3 二进制日志 1.4 慢查询日志 三、查询日志是否已开启 四、分割二进制日志 MySQL的日志默认保存在数据库文件的存储目录&#xff08;一般为/usr/local/mysql/data/&#xff09;。也可以修改配置文件&#xff0c;…

低代码可视化开发,是时候去学一学了!

低代码一词&#xff0c;有人认为它是第四代编程语言&#xff0c;有人认为它是开发模式的颠覆&#xff0c;也有人认为它是企业管理模式的变革……有很多声音&#xff0c;社区讨论很热烈。 即使这样&#xff0c;至今也有不少人还不知道这项技术&#xff0c;今天笼统的介绍一下低代…

认识需求不得不思考的5大因素

需求对项目非常重要&#xff0c;如果对需求认识不清晰不明确&#xff0c;往往会导致需求不完整&#xff0c;需求重复等问题&#xff0c;引起很多不必要的风险&#xff0c;因此如何认识需求&#xff0c;需要思考哪些因素&#xff0c;就显得尤为重要。以下5大因素&#xff0c;是我…

Spring 6【Lombok、基于注解方式的IoC】(六)-全面详解(学习总结---从入门到深化)

目录 十一、Lombok 十二、基于注解方式的IoC 十一、Lombok 在上面的课程讲解过程中&#xff0c;每次修改类的属性&#xff0c;都需要重新生成Getter/Setter方法及toString()方法。我 们可以借助Lombok来简化代码。 1 环境准备 下载Lombok插件 IDEA 2022.3.2 默认集成Lomb…

ICASSP 2023 | MCROOD: MULTI-CLASS RADAR OUT-OF-DISTRIBUTION DETECTION

原文链接&#xff1a;https://mp.weixin.qq.com/s?__bizMzg4MjgxMjgyMg&mid2247486484&idx1&snd43f92ca0230753e77f54557054653d6&chksmcf51beedf82637fb27d4cbb9279f273298779dabe25f7775cb93469787bcc12c1b6b2caec979#rd ICASSP 2023 | MCROOD: MULTI-CLASS…

golang利用go mod巧妙替换使用本地项目的包

问题 拉了两个项目下来&#xff0c;其中一个项目依赖另一个项目&#xff0c;因为改动了被依赖的项目&#xff0c;想重新导入测试一下。 解决办法 go.mod文件的require中想要被代替的包名在replace中进行一个替换&#xff0c;注意&#xff1a;用来替换的需要用绝对路径&#xf…