Dart語(yǔ)言基礎(chǔ)之類

原文:https://www.dartlang.org/guides/language/language-tour
Dart是一種面向?qū)ο蟮恼Z(yǔ)言,具有類和基于mixin的繼承。 每個(gè)對(duì)象都是一個(gè)類的實(shí)例,所有類都來自O(shè)bject众旗。 基于Mixin的繼承意味著雖然每個(gè)類(除了Object)只有一個(gè)超類呕缭,但是類體可以在多個(gè)類層次結(jié)構(gòu)中重用。
用點(diǎn)(.) 來訪問實(shí)例變量和方法:

var p = Point(2, 2);

// Set the value of the instance variable y.
p.y = 3;

// Get the value of y.
assert(p.y == 3);

// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));

?. i代替 .來避免異常當(dāng)左對(duì)象是null:

// If p is non-null, set its y value to 4.
p?.y = 4;

使用構(gòu)造函數(shù)

你可以用構(gòu)造函數(shù)來創(chuàng)建一個(gè)對(duì)象. 構(gòu)造函數(shù)可以用 類名或者類名.標(biāo)識(shí)符. 例如, 下面的代碼使用使用 Point()Point.fromJson() 構(gòu)造函數(shù)來創(chuàng)建 Point 對(duì)象:

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

下面的代碼具有同樣的效果, but uses the optional new keyword before the constructor name:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

Version note: new關(guān)鍵字在Dart 2中是可選的.

一些類提供 constant constructors. 用常量構(gòu)造函數(shù)來創(chuàng)建編譯時(shí)常量, 將 const 關(guān)鍵字放在構(gòu)造函數(shù)前面:

var p = const ImmutablePoint(2, 2);

Constructing two identical compile-time constants results in a single, canonical instance:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // They are the same instance!

常量上下文(constant context)中, 你可以省略 const :

// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

省略const :

// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

若常量構(gòu)造函數(shù)沒有在常量上下文中被調(diào)用椒楣,或者沒有用const調(diào)用, 會(huì)產(chǎn)生 non-constant object錯(cuò)誤:

var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!

Version note: const keyword became optional within a constant context in Dart 2.

獲得對(duì)象類型

在運(yùn)行時(shí)獲取對(duì)象類型, 你可以使用對(duì)象的 runtimeType 屬性, 返回 Type object.

print('The type of a is ${a.runtimeType}');

實(shí)例變量

聲明:

class Point {
  num x; // Declare instance variable x, initially null.
  num y; // Declare y, initially null.
  num z = 0; // Declare z, initially 0.
}

所有未初始化變量的值都是 null.

所有實(shí)例變量都含有有隱式的getter 方法. 非final實(shí)例變量都有隱式的 setter 方法. 細(xì)節(jié)請(qǐng)看 Getters and setters.

class Point {
  num x;
  num y;
}

void main() {
  var point = Point();
  point.x = 4; // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}

當(dāng)你在聲明實(shí)例變量的地方初始化它 (不是在構(gòu)造函數(shù)和方法內(nèi)),當(dāng)實(shí)例變量創(chuàng)建的時(shí)候就會(huì)被賦值, 在構(gòu)造方法和初始化列表執(zhí)行之前.

Dart語(yǔ)法糖:

class Point {
  num x, y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

默認(rèn)構(gòu)造函數(shù)

如果你沒有聲明構(gòu)造函數(shù), 會(huì)為你提供一個(gè)默認(rèn)構(gòu)造函數(shù). 默認(rèn)構(gòu)造函數(shù)沒有任何參數(shù)并且會(huì)調(diào)用父類的無參構(gòu)造函數(shù)。構(gòu)造函數(shù)不能被繼承牡肉。

子類不能繼承父類的構(gòu)造函數(shù). 沒有聲明構(gòu)造函數(shù)的子類只有一個(gè)默認(rèn)構(gòu)造函數(shù).

命名構(gòu)造函數(shù)

使用命名構(gòu)造函數(shù)來為類提供多個(gè)構(gòu)造函數(shù):

class Point {
  num x, y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin() {
    x = 0;
    y = 0;
  }
}

父類的命名構(gòu)造函數(shù)不能被子類繼承.如果你想用父類的命名構(gòu)造函數(shù)來創(chuàng)建子類, 你必須在子類中實(shí)現(xiàn)它.

調(diào)用父類的非默認(rèn)構(gòu)造函數(shù)

默認(rèn)的, 子類的構(gòu)造函數(shù)調(diào)用父類的非命名無參構(gòu)造函數(shù). 父類的構(gòu)造函數(shù)在子類構(gòu)造函數(shù)體的開始執(zhí)行之前被調(diào)用. 如果使用了初始化列表 捧灰,初始化列表會(huì)在父類構(gòu)造函數(shù)之前執(zhí)行. 總之,執(zhí)行順序如下:

  1. 初始化列表 initializer list
  2. 父類的構(gòu)造函數(shù)
  3. 主類的構(gòu)造函數(shù)

如果父類中沒有非命名無參構(gòu)造函數(shù), 你必須手動(dòng)調(diào)用父類的一個(gè)構(gòu)造函數(shù). 在冒號(hào) (:)之后指定父類構(gòu)造函數(shù).

class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}

因?yàn)楦割悩?gòu)造函數(shù)的參數(shù)會(huì)在調(diào)用父類構(gòu)造函數(shù)之前被計(jì)算出來统锤,參數(shù)可以是一個(gè)表達(dá)式毛俏,例如一個(gè)函數(shù)調(diào)用:

class Employee extends Person {
  Employee() : super.fromJson(getDefaultData());
  // ···
}

警告: 父類構(gòu)造函數(shù)的參數(shù)沒有this權(quán)限。例如參數(shù)可以調(diào)用靜態(tài)方法饲窿,但不能調(diào)用實(shí)例方法.

初始化列表

除了調(diào)用父類構(gòu)造函數(shù)煌寇,你還可以在構(gòu)造函數(shù)執(zhí)行之前初始化實(shí)例變量. 用逗號(hào)分隔初始化代碼.

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

警告: The right-hand side of an initializer does not have access to this.

在開發(fā)中,你可以在初始化列表中用assert來驗(yàn)證輸入.

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}
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 = new Point(2, 3);
  print(p.distanceFromOrigin);
}

重定位構(gòu)造函數(shù)

有時(shí)構(gòu)造函數(shù)的唯一目的就是定位到另一個(gè)同類中的其他構(gòu)造函數(shù). 重定位構(gòu)造函數(shù)的函數(shù)體是空的逾雄,with the constructor call appearing after a colon (:).

class Point {
  num x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(num x) : this(x, 0);
}

常量構(gòu)造函數(shù)

如果你的類產(chǎn)生的對(duì)象從不改變,你可以把這些對(duì)象聲明為編譯時(shí)常量. 為此阀溶,定義一個(gè)const構(gòu)造函數(shù)來確保所有實(shí)例變量都是 final.

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}

常量構(gòu)造函數(shù)并不總是創(chuàng)建常量. 細(xì)節(jié)請(qǐng)看using constructors.

工廠構(gòu)造函數(shù)

當(dāng)實(shí)現(xiàn)一個(gè)并不總是創(chuàng)建新實(shí)例對(duì)象的構(gòu)造函數(shù),使用factory關(guān)鍵詞. 例如鸦泳,一個(gè)工廠構(gòu)造函數(shù)可能從緩存中返回一個(gè)對(duì)象银锻,或者它可能返回一個(gè)子類的實(shí)例.

class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

Note: 工廠構(gòu)造函數(shù)不能訪問this.

像調(diào)用其他構(gòu)造函數(shù)一樣調(diào)用工廠構(gòu)造函數(shù):

var logger = Logger('UI');
logger.log('Button clicked');

方法

實(shí)例方法

對(duì)象的實(shí)例方法可以使用 this. :

import  'dart:math';  
class  Point  {  
num x, y;  
Point(this.x,  this.y);  
num distanceTo(Point other)  {  
var dx = x - other.x; 
 var dy = y - other.y;  
return sqrt(dx * dx + dy * dy); 
 }  
}

Getters and setters

Getters and setters 是為類屬性提供讀寫途徑的特殊方法。 重新強(qiáng)調(diào)做鹰,每個(gè)實(shí)例變量都有一個(gè)隱式的getter和 setter . 你可以通過實(shí)現(xiàn)getters 和 setters來創(chuàng)建額外的屬性, 使用 getset 關(guān)鍵詞:

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

With getters and setters, you can start with instance variables, later wrapping them with methods, all without changing client code.

Note: Operators such as increment (++) work in the expected way, whether or not a getter is explicitly defined. To avoid any unexpected side effects, the operator calls the getter exactly once, saving its value in a temporary variable.

抽象方法

getter, setter 可以是抽象的, 定義一個(gè)接口并把它的實(shí)現(xiàn)交給其他類. 抽象方法只能存在于 抽象類.

abstract class Doer {
  // Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}

隱式接口

每個(gè)類都隱式的定義了一個(gè)包含一個(gè)類和它實(shí)現(xiàn)的所有接口的所有實(shí)例變量和方法. 如果你想創(chuàng)建一個(gè)支持B類API但不繼承其實(shí)現(xiàn)的類A击纬,A類應(yīng)該實(shí)現(xiàn)B類接口.

一個(gè)類可以實(shí)現(xiàn)一個(gè)或多個(gè)接口:

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// An implementation of the Person interface.
class Impostor implements Person {
  get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}
class Point implements Comparable, Location {...}

擴(kuò)展類

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}

重寫方法

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}

可重載運(yùn)算符

< + | []
> / ^ []=
<= ~/ & ~
>= * << ==
– % >>

Note: 你可能會(huì)被通知!=不是個(gè)可重載的操作符.表達(dá)式1e1 != e2 `i只是!(e1 == e2)的語(yǔ)法糖.

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown. For details, see note below.
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

如果你重載了==, 你應(yīng)該重寫ObjecthashCode getter. 例子請(qǐng)看 Implementing map keys.

更多信息Extending a class

noSuchMethod()

為了探測(cè)和對(duì)是否調(diào)用了類中不存在的方法和變量作出反應(yīng),你可以重寫noSuchMethod():

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

不能調(diào)用 未實(shí)現(xiàn)的方法除非以下之一成立:

  • 接收者有靜態(tài)類型 dynamic.
  • 接收者是靜態(tài)類型定義了未實(shí)現(xiàn)的方法 (抽象的也行), 接收者的動(dòng)態(tài)類型有異于ObjectnoSuchMethod()的實(shí)現(xiàn).

更多請(qǐng)看noSuchMethod forwarding specification.

Enum類型

通常叫列舉枚舉, 是一種特殊的類用來代表常量值的固定數(shù)字.

使用enum

enum Color { red, green, blue }

enum中的每個(gè)值都有個(gè) index getter, 返回從0開始的值在enum中聲明的位置.例如, 第一個(gè)值的index為0, 第二個(gè)為1.

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

To get a list of all of the values in the enum, use the enum’s values constant.

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

你可以在 switch statements中使用enum, 你如果沒有處理全部值你會(huì)得到一個(gè)警告:

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // Without this, you see a WARNING.
    print(aColor); // 'Color.blue'
}

enum類型有如下限制:

  • You can’t subclass, mix in, or implement an enum.
  • You can’t explicitly instantiate an enum.

更多請(qǐng)看Dart language specification.

為類添加特性: 混入

混入是一種在多層級(jí)類關(guān)系重用類代碼的方法。

使用混入钾麸,將一個(gè)或多個(gè)mixin名放在with后面更振。

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

實(shí)現(xiàn)一個(gè)mixin炕桨,創(chuàng)建一個(gè)繼承自O(shè)bject的類,并且不聲明任何構(gòu)造函數(shù)殃饿。除非你想你的mixin可以像正常類一樣被使用谋作,否則用mixin關(guān)鍵詞來代替class,例如:

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

To specify that only certain types can use the mixin — for example, so your mixin can invoke a method that it doesn’t define — use on to specify the required superclass:

mixin MusicalPerformer on Musician {
  // ···
}

Version note: mixin關(guān)鍵詞在 Dart 2.1中引進(jìn). 早期版本的代碼使用abstract class 代替. 更多請(qǐng)看Dart SDK changelog and 2.1 mixin specification.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末乎芳,一起剝皮案震驚了整個(gè)濱河市遵蚜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌奈惑,老刑警劉巖吭净,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異肴甸,居然都是意外死亡寂殉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門原在,熙熙樓的掌柜王于貴愁眉苦臉地迎上來友扰,“玉大人,你說我怎么就攤上這事庶柿〈骞郑” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵浮庐,是天一觀的道長(zhǎng)甚负。 經(jīng)常有香客問我,道長(zhǎng)审残,這世上最難降的妖魔是什么梭域? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮搅轿,結(jié)果婚禮上病涨,老公的妹妹穿的比我還像新娘。我一直安慰自己璧坟,他們只是感情好没宾,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沸柔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铲敛。 梳的紋絲不亂的頭發(fā)上褐澎,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音伐蒋,去河邊找鬼工三。 笑死迁酸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的俭正。 我是一名探鬼主播奸鬓,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼掸读!你這毒婦竟也來了串远?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤儿惫,失蹤者是張志新(化名)和其女友劉穎澡罚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肾请,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡留搔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铛铁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隔显。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖饵逐,靈堂內(nèi)的尸體忽然破棺而出括眠,到底是詐尸還是另有隱情,我是刑警寧澤梳毙,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布哺窄,位于F島的核電站,受9級(jí)特大地震影響账锹,放射性物質(zhì)發(fā)生泄漏萌业。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一奸柬、第九天 我趴在偏房一處隱蔽的房頂上張望生年。 院中可真熱鬧,春花似錦廓奕、人聲如沸抱婉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蒸绩。三九已至,卻和暖如春铃肯,著一層夾襖步出監(jiān)牢的瞬間患亿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工押逼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留步藕,地道東北人惦界。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像咙冗,于是被迫代替她去往敵國(guó)和親沾歪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi閱讀 7,294評(píng)論 0 10
  • pyspark.sql模塊 模塊上下文 Spark SQL和DataFrames的重要類: pyspark.sql...
    mpro閱讀 9,446評(píng)論 0 13
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學(xué)習(xí)記錄文檔雾消,今天18年5月份再次想寫文章灾搏,發(fā)現(xiàn)簡(jiǎn)書還為我保存起的...
    Jenaral閱讀 2,732評(píng)論 2 9
  • 時(shí)間過得真快,不知不覺迷迷糊糊的2月份就過完了仪或。 今天聽了講服裝的斷舍離确镊,適合的不適和的該怎樣處理。去年才做過一次...
    傲雪_352a閱讀 123評(píng)論 0 1
  • 那一夜自己究竟是如何回來范删,現(xiàn)在已無法記起蕾域,燒烤攤上的壺裝烈酒讓我的思維能力幾近于低能兒。次日醒來到旦,只覺得頭痛如裂旨巷,...
    似沫漸消閱讀 671評(píng)論 0 2