Flutter基礎(chǔ)篇(2)-- 老司機(jī)用一篇博客帶你快速熟悉Dart語法

版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載姓言。http://www.reibang.com/p/3d927a7bf020

轉(zhuǎn)載請標(biāo)明出處:
http://www.reibang.com/p/3d927a7bf020
本文出自 AWeiLoveAndroid的博客


Flutter系列博文鏈接 ↓:

工具安裝:

Flutter基礎(chǔ)篇:

Flutter進(jìn)階篇:

Dart語法系列博文鏈接 ↓:

Dart語法基礎(chǔ)篇:

Dart語法進(jìn)階篇:


【前言】Dart語言是使用flutter框架開發(fā)時候必備的語言搜骡,flutter是一個跨平臺的框架,一套代碼就可以完美實(shí)現(xiàn)安卓和ios兩個平臺佑女,適配也很不錯记靡,Dart語言很友好,和java很類似团驱,學(xué)習(xí)成本也是很低的摸吠。所以這也是我推薦學(xué)習(xí)Dart語言的一個原因。

從本篇文章開始講解Dart語言的基本使用嚎花,我將會連續(xù)推出好幾篇文章詳解寸痢,希望幫助大家快速掌握Dart語言。


本文目錄:

一紊选、注釋
二啼止、關(guān)鍵字(56個)
三道逗、變量和常量
四、特殊數(shù)據(jù)類型
五献烦、運(yùn)算符
六憔辫、控制流程語句
七、異常


本文代碼同步發(fā)布在Github:
https://github.com/AweiLoveAndroid/Flutter-learning/tree/master/projects/dart_demo

怎么運(yùn)行代碼仿荆?

如果你使用IDEA或者Android Studio:

打開IDEA或者Android Studio,新建一個Flutter項(xiàng)目坏平,然后在test目錄運(yùn)行我的代碼拢操;或者里面去寫你自己的dart文件,然后右鍵run就可以運(yùn)行了舶替。(注意:需要連接手機(jī)或者模擬器)令境。

如果你使用vscode:

打開vscode,執(zhí)行菜單欄運(yùn)行顾瞪,就可以了(確保只有一個dart文件舔庶,免得運(yùn)行的文件不是你想要的,就很尷尬了陈醒,vscode也可以設(shè)置默認(rèn)運(yùn)行的文件是哪個惕橙,但是新手不建議去設(shè)置,很麻煩钉跷。因?yàn)槟銈兿胱羁斓倪\(yùn)行效果弥鹦,所有建議只有一個dart文件是最好的)。


一爷辙、注釋

Dart的注釋分為3種:單行注釋彬坏、多行注釋、文檔注釋膝晾。

1栓始、單行注釋以//開頭。Dart編譯器會忽略//和行尾之間的所有內(nèi)容血当。

例如:// todo:待完成

2幻赚、多行注釋以/*開頭,以*/結(jié)尾歹颓。介于/**/兩者之間的內(nèi)容會被編譯器忽略(除非該注釋是一個文檔注釋)坯屿。多行注釋可以嵌套。

例如:/* todo:待完成 */

3巍扛、文檔注釋以///或者/**開頭领跛。可以通過dartdoc命令導(dǎo)出文檔撤奸。

文檔注釋的使用吠昭,例如:/// todo:待完成

文檔的導(dǎo)出如圖所示:


文檔的導(dǎo)出

導(dǎo)出的結(jié)果在我的工程根路徑的/doc/api/文件夾里面喊括,如圖所示:

導(dǎo)出的結(jié)果

然后瀏覽器打開index.html就可以看到文檔了。如圖所示:

本地的文檔

二矢棚、關(guān)鍵字(60個)

5個上下文關(guān)鍵字(僅在特定位置具有含義郑什。它們在任何地方都是有效的標(biāo)識符)

關(guān)鍵字 - - -
async hide on show
sync - - -

其中內(nèi)置標(biāo)志符有:(20個)

關(guān)鍵字 - - -
abstract as covariant defered
dynamic export external factory
Function get implements import
interface library mixin operator
part set static typedef

Dart新增的,有限的保留字,支持異步功能的關(guān)鍵字有:(2個)

關(guān)鍵字 - - -
await yield

33個保留字(不能使用保留字作為標(biāo)識符)

關(guān)鍵字 - - -
assert break case catch
class const continue default
do else enum extends
false final finally for
if in is new
null rethrow return super
switch this throw true
try var void while
with - - -

跟java相比蒲肋,Dart特有的關(guān)鍵字有:(27個)

關(guān)鍵字 - - -
as async await covariant
deferred dynamic export external
factory Function get hide
in is library mixin
on operator part rethrow
set show sync typedef
var with yield

三蘑拯、變量和常量

(一)變量的聲明,可以使用 var兜粘、Object 或 dynamic 關(guān)鍵字申窘。

創(chuàng)建變量并初始化變量實(shí)例:

var name = '張三' ;

變量存儲引用。

    1. 使用Object或dynamic關(guān)鍵字
dynamic name = '張三';

調(diào)用的變量name包含對String值為“張三” 的對象的引用孔轴。
name推斷變量的類型是String剃法,但可以通過指定它來更改該類型。
如果對象不限于單一類型(沒有明確的類型)路鹰,請使用Object或dynamic關(guān)鍵字

Object name = '張三';
dynamic name = '李四';
    1. 顯式聲明將被推斷的類型

比如String,int等贷洲。

//可以使用String顯示聲明字符串類型
String name = '張三' ; //代替var name = '張三';

這個類型有很多,具體在下文有介紹晋柱。

(二)默認(rèn)值

未初始化的變量的初始值為null(包括數(shù)字)优构,因此數(shù)字、字符串都可以調(diào)用各種方法雁竞。

//測試 數(shù)字類型的初始值是什么?
int intDefaultValue;
// assert 是語言內(nèi)置的斷言函數(shù)俩块,僅在檢查模式下有效
// 在開發(fā)過程中, 除非條件為真浓领,否則會引發(fā)異常玉凯。(斷言失敗則程序立刻終止)
assert(intDefaultValue == null);
print(intDefaultValue);//打印結(jié)果為null,證明數(shù)字類型初始化值是null

(三)Final 和 Const的用法

如果您從未打算更改一個變量联贩,那么使用 final 或 const漫仆,不是var,也不是一個類型泪幌。
一個 final 變量只能被設(shè)置一次;const 變量是一個編譯時常量盲厌。(Const變量是隱式的final。)
final的頂級或類變量在第一次使用時被初始化祸泪。

  • 1吗浩、被final或者const修飾的變量,變量類型可以省略没隘。
//可以省略String這個類型聲明
final name1 = "張三";
//final String name1  = "張三";
    
const name2 = "李四";
//const String name2 = "李四";
  • 2懂扼、被 final 或 const 修飾的變量無法再去修改其值。
final name1 = "張三";
// 這樣寫,編譯器提示:a final variable, can only be set once
// 一個final變量阀湿,只能被設(shè)置一次赶熟。
//name1 = "zhangsan";
    
const name2 = "李四";

// 這樣寫,編譯器提示:Constant variables can't be assigned a value
// const常量不能賦值
// name2 = "lisi";
  • 3陷嘴、注意:flnal 或者 const 不能和 var 同時使用
//這樣寫都會報錯
//final var name1 = "張三";
//const var name2 = "李四";
  • 4映砖、常量如果是類級別的,請使用 static const
static const speed = 100;
  • 5灾挨、常量的運(yùn)算
const speed = 100; //速度(km/h)
const double distance = 2.5 * speed; // 距離 = 速度 * 時間

final speed2 = 100; //速度(km/h)
final double distance2 = 2.5 * speed2; // 距離 = 速度 * 時間
  • 6邑退、const關(guān)鍵字不只是聲明常數(shù)變量。您也可以使用它來創(chuàng)建常量值劳澄,以及聲明創(chuàng)建常量值的構(gòu)造函數(shù)瓜饥。 任何變量都可以有一個常量值。
// 注意: [] 創(chuàng)建的是一個空的list集合
// const []創(chuàng)建一個空的浴骂、不可變的列表(EIL)。
var varList = const []; // varList 當(dāng)前是一個EIL
final finalList = const []; // finalList一直是EIL
const constList = const []; // constList 是一個編譯時常量的EIL

// 可以更改非final,非const變量的值
// 即使它曾經(jīng)具有const值
varList = ["haha"];

// 不能更改final變量或const變量的值
// 這樣寫宪潮,編譯器提示:a final variable, can only be set once
// finalList = ["haha"];
// 這樣寫溯警,編譯器提示:Constant variables can't be assigned a value  
// constList = ["haha"];
  • 7、只要任何插值表達(dá)式是一個計算結(jié)果為null或數(shù)字狡相,字符串或布爾值的編譯時常量梯轻,那么文字字符串就是編譯時常量。(關(guān)于$表達(dá)式和不同的數(shù)據(jù)類型后面會講解尽棕。)
// 這些是常量字符串
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';

// 這些不是常量字符串

var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = const [1, 2, 3];

const validConstString = '$aConstNum $aConstBool $aConstString';

//這樣用就會報錯:Const variables must be initialized with a constant value
// const常量必須用conat類型的值初始化喳挑。
// const invalidConstString = '$aNum $aBool $aString $aConstList';

四、特殊數(shù)據(jù)類型

Dart支持以下特殊類型:

numbers 數(shù)字
strings 字符串
booleans 布爾
lists list集合(也稱為數(shù)組)
maps map集合
runes 字符(用于在字符串中表示Unicode字符)

(一)num數(shù)字類型

num是數(shù)字類型的父類滔悉,有兩個子類 intdouble伊诵。
num類型包括基本的運(yùn)算符,如+,-,/和*回官,位運(yùn)算符曹宴,如>>,在int類中定義歉提。如果num和它的子類沒有你要找的東西笛坦,math庫可能會找到。比如你會發(fā)現(xiàn)abs(),ceil()和floor()等方法苔巨。

(1)int類型

int表示整數(shù)版扩,int默認(rèn)是64位二進(jìn)制補(bǔ)碼整數(shù),int的取值不大于64位侄泽,具體取決于平臺礁芦。編譯為JavaScript時,整數(shù)僅限于valus悼尾,可以用雙精度浮點(diǎn)值精確表示宴偿∠嫔樱可用的整數(shù)值包括-253和253之間的所有整數(shù),以及一些幅度較大的整數(shù)窄刘。這包括一些大于2^63的整數(shù)窥妇。 因此,在編譯為JavaScript的Dart VM和Dart代碼之間娩践,int類中的運(yùn)算符和方法的行為有時會有所不同活翩。例如,當(dāng)編譯為JavaScript時翻伺,按位運(yùn)算符將其操作數(shù)截斷為32位整數(shù)材泄。
示例如下:

int intNum1 = 10 ;
print(intNum1);//結(jié)果是10
int intNum2 = 0xDEADBEEF ;
print(intNum2);//結(jié)果是3735928559

判斷一個int值需要多少bit(位),可以使用bitLength吨岭,例如:

// bitLength 返回存儲此int整數(shù)所需的最小位數(shù)
int a1 = 1; // 占了1個bit     相當(dāng)于二進(jìn)制數(shù)字 00000000 00000001
int a2 = 12; // 占了4個bit    相當(dāng)于二進(jìn)制數(shù)字 00000000 00001100
int a3 = 123; // 占了7個bit   相當(dāng)于二進(jìn)制數(shù)字 00000000 01111011
int a4 = 1234; // 占了11個bit 相當(dāng)于二進(jìn)制數(shù)字 00000100 11010010
print('${a1.bitLength}'); //  1
print('${a2.bitLength}');  // 4
print('${a3.bitLength}'); // 7
print('${a4.bitLength}'); // 11
(2)double類型

Dart的double是IEEE 754標(biāo)準(zhǔn)中規(guī)定的64位浮點(diǎn)數(shù)拉宗。double的最大值是:1.7976931348623157e+308,double類里面有一個常量maxFinite辣辫,我們通過語句print(double. maxFinite)可以得到double的最大值旦事。
如果一個數(shù)字包含一個小數(shù),那么它就是一個double類型急灭。示例如下:

double doubleNum1 = 1.1;
print(doubleNum1); //結(jié)果是1.1
double doubleNum2 = 1.42e5;
print(doubleNum2); //結(jié)果是142000.0
(3)Dart2.1里面新增特性姐浮,當(dāng)double的值為int值時,int自動轉(zhuǎn)成double葬馋。

例如:double test = 12;//打印結(jié)果是12.0

(4)Dart2.1,int也有api轉(zhuǎn)成double卖鲤。

例如:

int test = 10;
print(test.toDouble()); // 結(jié)果是: 10.0
(5)Dart2.1,double也有api轉(zhuǎn)成int,會把小數(shù)點(diǎn)后面的全部去掉。

例如:

double test2 = 15.1;
double test3 = 15.1234;
print(test2.toInt());// 結(jié)果是15
print(test3.toInt());// 結(jié)果是15

(二)String字符串類型

Dart里面的String是一系列 UTF-16代碼單元畴嘶。

(1)您可以使用單引號或雙引號來創(chuàng)建一個字符串蛋逾。
String str1 = '單引號基本使用demo.';
String str2 = "雙引號基本使用demo.";
(2)單引號或者雙引號里面嵌套使用引號。

單引號里面嵌套單引號窗悯,或者//雙引號里面嵌套雙引號换怖,必須在前面加反斜杠。

// 單引號里面有單引號蟀瞧,必須在前面加反斜杠
String str3 = '單引號里面有單引號it\'s沉颂,必須在前面加反斜杠.';
//雙引號里面嵌套單引號(正常使用)
String str4 = "雙引號里面有單引號it's.";
//單引號里面嵌套雙引號(正常使用)
String str5 = '單引號里面有雙引號,"hello world"';
//雙引號里面嵌套雙引號悦污,必須在前面加反斜杠
String str6 = "雙引號里面有雙引號铸屉,\"hello world\"";

print(str3);// 雙引號里面有單引號it's,必須在前面加反斜杠
print(str4);// 雙引號里面有單引號it's.
print(str5);// 單引號里面有雙引號切端,hello world"
print(str6);//雙引號里面有雙引號彻坛,"hello world"
(3)多個字符串相鄰中間的空格問題:

除了單引號嵌套單引號或者雙引號嵌套雙引號不允許出現(xiàn)空串之外,其余的幾種情況都是可以運(yùn)行的。
示例如下:

這個會報錯
//String blankStr1 = 'hello''''world';
//這兩個運(yùn)行正常
String blankStr2 = 'hello'' ''world'; //結(jié)果: hello world
String blankStr3 = 'hello''_''world'; //結(jié)果: hello_world


// 這個會報錯
//String blankStr4 = "hello""""world";
這兩個運(yùn)行正常
String blankStr5 = "hello"" ""world"; //結(jié)果: hello world
String blankStr6 = "hello""_""world"; //結(jié)果: hello_world

單引號里面有雙引號昌屉,混合使用運(yùn)行正常

String blankStr7 = 'hello""""world'; //結(jié)果: hello""""world
String blankStr8 = 'hello"" ""world'; //結(jié)果: hello"" ""world
String blankStr9 = 'hello""_""world'; //結(jié)果: hello""_""world

雙引號里面有單引號钙蒙,混合使用運(yùn)行正常

String blankStr10 = "hello''''world"; //結(jié)果: hello''''world
String blankStr11 = "hello'' ''world"; //結(jié)果: hello'' ''world
String blankStr12 = "hello''_''world"; //結(jié)果: hello''_''world
(4)您可以使用相鄰字符串文字或+ 運(yùn)算符連接字符串:
  1. 直接把相鄰字符串寫在一起,就可以連接字符串了间驮。
  String connectionStr1 =  '字符串連接''甚至可以在''換行的時候進(jìn)行躬厌。';
  print(connectionStr1);// 字符串連接甚至可以在換行的時候進(jìn)行。
  1. 用+把相鄰字符串連接起來竞帽。
  String connectionStr2 =  '字符串連接'+ '甚至可以在'+ '換行的時候進(jìn)行扛施。';
  print(connectionStr2);// 字符串連接甚至可以在換行的時候進(jìn)行。
  1. 使用單引號或雙引號的三引號:
String connectionStr3 = ''' 
  這是用單引號創(chuàng)建的
  多行字符串屹篓。
  ''' ;
print(connectionStr3);
String connectionStr4 = """這是用雙引號創(chuàng)建的
  多行字符串疙渣。""";
print(connectionStr4);

print(connectionStr3)輸出結(jié)果如下:

  這是用單引號創(chuàng)建的
  多行字符串。

print(connectionStr4)的輸出結(jié)果如下:

這是用雙引號創(chuàng)建的
  多行字符串堆巧。
(5)關(guān)于轉(zhuǎn)義符號的使用

聲明raw字符串(前綴為r)妄荔,在字符串前加字符r,或者在\前面再加一個\谍肤,
可以避免“\”的轉(zhuǎn)義作用啦租,在正則表達(dá)式里特別有用。

舉例如下:

print(r"換行符:\n"); //這個結(jié)果是 換行符:\n
print("換行符:\\n"); //這個結(jié)果是 換行符:\n
print("換行符:\n");  //這個結(jié)果是 換行符:
(6)使用$可以獲得字符串中的內(nèi)容谣沸,使用${表達(dá)式}也可以將表達(dá)式的值放入字符串中。使用${表達(dá)式}時可以使用字符串拼接笋颤,也可以使用String類或者Object里面的某些方法獲得相關(guān)字符串屬性乳附。

1、使用$+字符串

var height = 48.0;
print('當(dāng)前標(biāo)題的高度是$height'); //當(dāng)前標(biāo)題的高度是48.0

2伴澄、使用$+字符串赋除,以及字符串拼接

String name = "張三";
print("$name"+"是我們的部門經(jīng)理"); // 張三是我們的部門經(jīng)理

3、這里使用了字符串的拼接非凌,以及使用了String類里面的toUpperCase()函數(shù)举农,把字母全部變成大寫。

String replaceStr = 'Android Studio';
assert('你知道' +
'${replaceStr.toUpperCase()}'
+ '最新版本是多少嗎敞嗡?' ==
'你知道ANDROID STUDIO最新版本是多少嗎颁糟?'); 

注:==操作符測試兩個對象是否相等。assert是斷言喉悴,如果條件為true棱貌,繼續(xù)進(jìn)行,否則拋出異常箕肃,中端操作婚脱。

(三)bool布爾類型

Dart表示布爾值的類型叫做bool,它有兩個值,分別是:truefalse障贸,它們都是編譯時常量错森。
Dart使用的是顯式的檢查值,檢查值的類型篮洁,如下所示:

  // 檢查是否為空字符串
  var emptyStr = '';
  assert(emptyStr.isEmpty);

  // 檢查是否小于等于0
  var numberStr = 0;
  assert(numberStr <= 0);  

  // 檢查是否為null
  var nullStr;
  assert(nullStr == null);

  // 檢查是否為NaN
  var value = 0 / 0;
  assert(value.isNaN);

assert 是Dart語言里的的斷言函數(shù)涩维,僅在Debug模式下有效
在開發(fā)過程中嘀粱, 除非條件為真激挪,否則會引發(fā)異常。(斷言失敗則程序立刻終止)锋叨。

(四)list集合垄分,也成為數(shù)組

在Dart中,數(shù)組是List對象娃磺,因此大多數(shù)人只是將它們稱為List薄湿。
以下是一個簡單的Dart的List:

創(chuàng)建一個int類型的list

List list = [10, 7, 23];
print(list);// 輸出結(jié)果  [10, 7, 23]

要創(chuàng)建一個編譯時常量const的list,示例如下:

List constantList = const[10,3,15];
print(constantList);// 輸出結(jié)果  [10, 3, 15]

注意事項(xiàng):

1.可以直接打印list包括list的元素偷卧,list也是一個對象豺瘤。但是java必須遍歷才能打印list,java若直接打印list听诸,結(jié)果是地址值坐求。
2.和java一樣list里面的元素必須保持類型一致,不一致就會報錯晌梨。
3.和java一樣list的角標(biāo)從0開始桥嗤。

Dart的list集合給我們提供了很多api,示例如下仔蝌,api太多就不逐個展示了:

操作 代碼 含義 輸出結(jié)果
新增 list.add(1);print(list); 把數(shù)字1添加到list中泛领,默認(rèn)是添加到末尾 [10, 7, 23, 1]
移除 list.remove(1);print(list); 移除數(shù)字1 [10, 7, 23]
插入 list.insert(0, 5);print(list); 在索引為0的地方插入數(shù)字5 [5, 10, 7, 23]
查找某個索引的值 int value = list.indexOf(10);print(value); 查找10在list中的索引 1
判斷元素是否包含 bool result = list.contains(5);print(result); 查找list中是否包含數(shù)字5 true

(五)map集合

Dart中的map是將鍵和值相關(guān)聯(lián)的對象。鍵和值都可以是任何類型的對象敛惊。每個鍵只出現(xiàn)一次渊鞋,但您可以多次使用相同的值。

(1)創(chuàng)建方式:
    1. 直接聲明瞧挤,用{}表示锡宋,里面寫key和value,每組鍵值對中間用逗號隔開特恬。
Map companys = {'first': '阿里巴巴', 'second': '騰訊', 'fifth': '百度'};
print(companys);//打印結(jié)果 {first: 阿里巴巴, second: 騰訊, fifth: 百度}
    1. 先聲明员辩,再去賦值。
  Map companys1 = new Map();
  companys1['first'] = '阿里巴巴';
  companys1['second'] = '騰訊';
  companys1['fifth'] = '百度';
  print(companys1);
  //打印結(jié)果 {first: 阿里巴巴, second: 騰訊, fifth: 百度}
    1. 要創(chuàng)建一個編譯時常量const的map鸵鸥,請?jiān)趍ap文字之前添加const:
final fruitConstantMap = const {2: 'apple',10: 'orange',18: 'banana'};
// 打印結(jié)果{second: 騰訊, fifth: 百度, 5: 華為}
(2)添加元素奠滑。格式: 變量名[key] = value,其中key可以是不同類型丹皱。
//添加一個新的元素,key為“5”宋税,value為“華為”
  companys[5] = '華為';
  print(companys);//打印結(jié)果 {first: 阿里巴巴, second: 騰訊, fifth: 百度, 5: 華為}
(3)修改元素摊崭。格式:變量名[key] = value

例如:把key為first的元素對應(yīng)的value改成 alibaba

  companys['first'] = 'alibaba';
  print(companys);//打印結(jié)果 {first: alibaba, second: 騰訊, fifth: 百度, 5: 華為}
(4)查詢元素
  bool mapKey = companys.containsKey('second');
  bool mapValue = companys.containsValue('百度');
  print(mapKey); //結(jié)果為:true
  print(mapValue); //結(jié)果為:true
(5)刪除元素.可以使用map的remove或者clear方法。
  companys.remove('first');// 移除key為“first”的元素杰赛。
  print(companys);// 打印結(jié)果{second: 騰訊, fifth: 百度, 5: 華為}

  companys.clear();// 清空map集合的數(shù)據(jù)呢簸。
  print(companys);// 打印結(jié)果{}
(6)關(guān)于map集合的小結(jié):
1.創(chuàng)建map有兩種方式。
2.map的key類型不一致也不會報錯乏屯。
3.添加元素的時候根时,會按照你添加元素的順序逐個加入到map里面,哪怕你的key不連續(xù)辰晕。
比如key分別是 1,2,4蛤迎,看起來有間隔,事實(shí)上添加到map的時候{1:value,2:value,4:value} 這種形式含友。
4.添加的元素的key如果是map里面某個key的英文替裆,照樣可以添加到map里面,
比如可以為3和key為three可以同時存在窘问。
5.map里面的key不能相同辆童,但是value可以相同,value可以為空字符串或者為null。

(六)runes 字符(用于在字符串中表示Unicode字符)

Unicode為世界上所有的書寫系統(tǒng)中使用的每個字母惠赫,數(shù)字和符號定義了唯一的數(shù)值把鉴。
Dart字符串是UTF-16代碼單元的序列,所以在字符串中表達(dá)32位Unicode值需要特殊的語法儿咱。
Unicode代碼點(diǎn)的常用方法是\uXXXX庭砍,其中XXXX是一個4位十六進(jìn)制值。

例如概疆,心形字符()是\u2665逗威。要指定多于或少于4個十六進(jìn)制數(shù)字峰搪,請將該值放在大括號中岔冀。 例如,笑的表情符號是\u{1f600}概耻。

String類有幾個屬性可以用來提取符文信息使套。 codeUnitAt和codeUnit屬性返回16位代碼單元。
以下示例說明了符文鞠柄,16位代碼單元和32位代碼點(diǎn)之間的關(guān)系侦高。

var clapping = '\u{1f44f}';
print(clapping);
print(clapping.codeUnits);
print(clapping.runes.toList());

//使用String. fromCharCodes顯示字符圖形 
Runes input = new Runes(
        '\u2665  \u{1f605}  \u{1f60e}  \u{1f47b}  \u{1f596}  \u{1f44d}');
print(new String.fromCharCodes(input));

五掰茶、運(yùn)算符

運(yùn)算符在每一種語言中都很常見暂筝,Dart的運(yùn)算符如下表所示:

我這里不詳細(xì)去講解每個運(yùn)算符的用法搓彻,我這里主要講一下Dart里面比較有代表性的以及有特點(diǎn)的一些運(yùn)算符相關(guān)用法。

(一)?..一樣暖混,但最左邊的操作數(shù)可以為空。

比如:Test?.funs從表達(dá)式Test中選擇屬性funs庆揪,除非Test為空(當(dāng)Test為空時境析,Test?.funs的值為空)。

class Test {
  static int funs = 5;

  Test() {
    print('構(gòu)造函數(shù) Test');
  }
  static fun() {
    print('Test fun函數(shù)');
  }
}
void main(){
  print(Test?.funs); // 打印5
}

(二)..級聯(lián)符號..

級聯(lián)符號..允許您在同一個對象上進(jìn)行一系列操作咆槽。 除了函數(shù)調(diào)用之外陈轿,還可以訪問同一對象上的字段。其實(shí)相當(dāng)于java的鏈?zhǔn)秸{(diào)用秦忿。
例如:

String s = (new StringBuffer()
        ..write('test1 ')
        ..write('test2 ')
        ..write('test3 ')
        ..write('test4 ')
        ..write('test5'))
      .toString();
print(s); // test1 test2 test3 test4 test5

(三)?? 三目運(yùn)算符的一種形式麦射。

expr1 ?? expr2 表示如果expr1非空,則返回其值; 否則返回expr2的值灯谣。

//普通三元運(yùn)算符
int a = 10;
var values = a > 5 ? a : 0;
//??操作符
print('a ??=3 : ${a ??= 3}');  // 3

(四)~/ 除法潜秋,返回一個整數(shù)結(jié)果,其實(shí)就是取商酬屉。

小學(xué)都學(xué)過:被除數(shù) ÷ 除數(shù) = 商 ... 余數(shù)半等,在Dart里面A ~/ B = C,這個C就是商呐萨,這個語句相當(dāng)于Java里面的A / B = C杀饵。Dart與java不同的是,Dart里面如果使用A / B = D語句谬擦,這個結(jié)果計算出來的是真實(shí)的結(jié)果切距。示例如下:

  var result1 = 15/7;
  print(result1); // 結(jié)果是:2.142857...
  var result2 = 15~/7;
  print(result2); // 結(jié)果是:2

順便提一下取模操作,在Dart里面A % B = E惨远,這個E就是余數(shù)谜悟,%符號表示取模,例如:

 var result3 = 15%7;
  print(result3); // 結(jié)果是:1

(五)as北秽、is與is!

as 判斷屬于某種類型
is 如果對象具有指定的類型葡幸,則為true
is! 如果對象具有指定的類型,則為false

例如:

class Test {
  static int funs = 5;

  Test() {
    print('構(gòu)造函數(shù) Test');
  }
  static fun() {
    print('Test fun函數(shù)');
  }
}

class Test2 extends Test {
  Test2() {
    print('構(gòu)造函數(shù) Test2');
  }
  void fun() {
    print('Test2 fun函數(shù)');
  }
}

void main(){
  print(test2 is Test);  // true
  print(test is! Test2);  // true

  (test2 as Test2).fun();  // Test2 fun函數(shù)
  // 相當(dāng)于
  // if (test2 is Test) {
  //   test2.fun();
  // }

六贺氓、控制流程語句

控制流程語句和Java語言差不多蔚叨,有這些語句:

(一)if else

if(條件語句){
    內(nèi)容體
}else{
內(nèi)容體
}

(二)for循環(huán)

1.簡單for循環(huán)

for(初始值;判斷條件;循環(huán)后的語句){
    內(nèi)容體
}

例如:

for(int i=0;i<10;i++){
    print(i);
}

也可以通過for循環(huán)內(nèi)部的閉包獲取索引的值。

var array = [];
for(var i=0;i<10;i++){
    array.add(()=> print(i));
}

2.使用foreach循環(huán)辙培,一般List和Set都可以使用foreach遍歷元素蔑水。

如果要迭代的對象是Iterable,或者你不想知道當(dāng)前的迭代次數(shù)扬蕊,可以使用foreach()方法搀别。

var numbers = [1,2,3,4,5,6,7,8,9];
numbers.foreach((number)=> print(number));

3.使用for in循環(huán),一般List和Set使用for-in遍歷元素尾抑。

var list = [1,2,3];
for(var data in list){
    print(data);
}

4.Dart的for循環(huán)里面可以使用標(biāo)記:(比較有特色的地方)

Dart的標(biāo)記:標(biāo)記是后面跟著冒號的標(biāo)識符歇父。帶標(biāo)記的陳述是以標(biāo)記 L為前綴的陳述蒂培。帶標(biāo)簽的case子句是標(biāo)簽L前綴的switch語句中的case子句。標(biāo)簽的唯一作用是為“break”和“continue”聲明提供對象榜苫。
大多數(shù)此功能與其他語言類似毁渗,因此以下大部分內(nèi)容可能對讀者來說都很熟悉。Dart的switch聲明中處理continue是比較獨(dú)特的单刁,所以這一部分需要一點(diǎn)時間去閱讀和熟悉灸异。

  • 1.循環(huán)(Loops)

標(biāo)簽最常用作breakcontinue內(nèi)部循環(huán)。假設(shè)你有嵌套的循環(huán)羔飞,并要跳轉(zhuǎn)到breakcontinue到外部循環(huán)肺樟。如果沒有標(biāo)記,這不可能(輕松)實(shí)現(xiàn)逻淌。

以下示例使用continue 標(biāo)記名稱從內(nèi)部循環(huán)直接跳轉(zhuǎn)到外部循環(huán)的下一輪循環(huán):

// 返回具有最小總和的內(nèi)部列表(正整數(shù))么伯。
/// Returns the inner list (of positive integers) with the smallest sum.
List<int> smallestSumList(List<List<int>> lists) {
  var smallestSum = 0xFFFFFFFF; //已知list的總和較小。
  var smallestList = null;
  outer: // 這就是標(biāo)記
  for (var innerList in lists) {
    var sum = 0;
    for (var element in innerList) {
      assert(element >= 0);
      sum += element;
      // 無需繼續(xù)迭代內(nèi)部列表卡儒。它的總和已經(jīng)太高了田柔。
      if (sum > smallestSum) continue outer; // continue 跳出到標(biāo)記處(outer)
    }
    smallestSum = sum;
    smallestList = innerList;
  }
  return smallestList;
}

此函數(shù)在所有l(wèi)ist中運(yùn)行,但只要總和過高骨望,就會停止累加變量硬爆。

同理,可以使用break跳出到外部循環(huán):

// 計算第一個非空list
List<int> firstListWithNullValueList(List<List<int>> lists) {
  var firstListWithNullValues = null;
  outer:
  for (var innerList in lists) {
    for (var element in innerList) {
      if (element == null) {
        firstListWithNullValues = innerList;
        break outer;  // break 返回到標(biāo)記處
      }
    }
  }
  // 現(xiàn)在繼續(xù)正常的工作流程
  if (firstListWithNullValues != null) {
    // ...
  }
  return firstListWithNullValues;
}
  • 2.跳出代碼塊

標(biāo)記也可以用于跳出代碼塊擎鸠。假設(shè)我們想要統(tǒng)一處理錯誤條件缀磕,但有多個條件(可能是深度嵌套)來揭示(reveal)錯誤。標(biāo)簽可以幫助構(gòu)建此代碼劣光。

void doSomethingWithA(A a) {
  errorChecks: {
    if (a.hasEntries) {
      for (var entry in a.entries) {
        if (entry is Bad) break errorChecks;   // 跳出代碼塊
      }
    }
    if (a.hasB) {
      var b = new A.b();
      if (b.inSomeBadState()) break errorChecks;  // 跳出代碼塊
    }
    // 一些看起來都正常
    use(a);
    return;
  }
  // 錯誤的情況袜蚕,執(zhí)行這里的代碼:
  print("something bad happened");
}

class A{
  bool hasEntries = false;
  bool hasB = true;
  List<Bad> entries = [new Bad2(),new Bad2()];
  A.b(){

  }

  bool inSomeBadState(){
    return false;
  }
  
}

void use(A a){}

abstract class Bad{}
class Bad1 extends Bad{}
class Bad2 extends Bad{}

對代碼塊的使用break指令,使得Dart繼續(xù)執(zhí)行塊之后的語句绢涡。從某個角度來看牲剃,它是一個結(jié)構(gòu)化的goto,它只允許跳轉(zhuǎn)到當(dāng)前指令之后的嵌套較少的位置雄可。

雖然聲明標(biāo)簽在代碼塊中最有用凿傅,但它們可以用在在每個語句中。
例如滞项,foo: break foo;是一個有效的聲明狭归。

請注意:continue上面的循環(huán)可以通過將循環(huán)體包裝到帶標(biāo)記的代碼塊中并使用break來實(shí)現(xiàn)夭坪。
也就是說文判,以下兩個循環(huán)是等效的:

//以下兩種描述是等價的:

// 使用 continue
for (int i = 0; i < 10; i++) {
  if (i.isEven) continue;
  print(i);
}

// 使用 break.
for (int i = 0; i < 10; i++) {
  labels: {
    // isEven 當(dāng)且僅當(dāng)該整數(shù)為偶數(shù)時才返回true
    if (i.isEven) break labels;
    print(i);
  }
}
  • 3.Switch中的標(biāo)記(label)

標(biāo)記也可以用于switch內(nèi)部。
Switch中的標(biāo)記允許continue 使用其它的case 子句室梅。在最簡單的形式中戏仓,這可以作為一種方式來實(shí)現(xiàn)下一個子句:

void switchExample(int foo) {
  switch (foo) {
    case 0:
      print("foo is 0");
      break;
    case 1:
      print("foo is 1");
      continue shared; // Continue使用在被標(biāo)記為shared的子句中
    shared:
    case 2:
      print("foo is either 1 or 2");
      break;
  }
}

有趣的是, Dart沒有要求continue的目標(biāo)子句是當(dāng)前case子句之后的子句疚宇。
帶標(biāo)記的任何case子句都是有效的目標(biāo)。這意味著赏殃,Dart的switch語句實(shí)際上是狀態(tài)機(jī)(state machines)敷待。

以下示例演示了這種濫用,其中整個switch實(shí)際上只是用作狀態(tài)機(jī)(state machines)仁热。

void main() {
  runDog();
}

void runDog() {
  int age = 0;
  int hungry = 0;
  int tired = 0;

  bool seesSquirrel() => new Random().nextDouble() < 0.1;
  bool seesMailman() => new Random().nextDouble() < 0.1;

  switch (1) {
    start:
    case 0:
      print("dog 方法已經(jīng)開始");
      print('case 0 ==> age: $age');
      print('case 0 ==> hungry: $hungry');
      print('case 0 ==> tired: $tired');
      continue doDogThings;

    sleep:
    case 1:
      print("sleeping");
      tired = 0;
      age++;
      if (age > 20) break;
      print('case 1 ==> age: $age');
      print('case 1 ==> hungry: $hungry');
      print('case 1 ==> tired: $tired');
      continue doDogThings;

    doDogThings:
    case 2:  
      if (hungry > 2) continue eat;
      if (tired > 3) continue sleep;
      if (seesSquirrel()) continue chase;
      if (seesMailman()) continue bark;
      print('case 2 ==> age: $age');
      print('case 2 ==> hungry: $hungry');
      print('case 2 ==> tired: $tired');
      continue play;

    chase:
    case 3:  
      print("chasing");
      hungry++;
      tired++;
      print('case 3 ==> age: $age');
      print('case 3 ==> hungry: $hungry');
      print('case 3 ==> tired: $tired');
      continue doDogThings;

    eat:
    case 4:  
      print("eating");
      hungry = 0;
      print('case 4 ==> age: $age');
      print('case 4 ==> hungry: $hungry');
      print('case 4 ==> tired: $tired');
      continue doDogThings;

    bark:
    case 5: 
      print("barking");
      tired++;
      print('case 5 ==> age: $age');
      print('case 5 ==> hungry: $hungry');
      print('case 5 ==> tired: $tired');
      continue doDogThings;

    play:
    case 6: 
      print("playing");
      tired++;
      hungry++;
      print('case 6 ==> age: $age');
      print('case 6 ==> hungry: $hungry');
      print('case 6 ==> tired: $tired');
      continue doDogThings;
  }
}

這個函數(shù)從一個switch子句跳到另一個子句榜揖,模擬了狗的生命。
在Dart中抗蠢,標(biāo)簽只允許在case子句中使用举哟,因此我必須添加一些case永遠(yuǎn)不會到達(dá)的行。

這個功能很酷迅矛,但很少使用妨猩。由于我們的編譯器增加了復(fù)雜性,我們經(jīng)常討論它的刪除秽褒。到目前為止壶硅,它已經(jīng)在我們的審查中幸存下來,但我們最終可能會簡化我們的規(guī)范并讓用戶自己添加一個while(true)循環(huán)(帶有標(biāo)記)销斟。這個dog的示例可以重寫如下:

var state = 0;
loop:
while (true)
  switch (state) {
    case 0:
      print("dog has started");
      state = 2; continue;

    case 1:  // sleep.
      print("sleeping");
      tired = 0;
      age++;
      // The inevitable... :(
      if (age > 20) break loop;  // 跳出循環(huán)
      // Wake up and do dog things.
      state = 2; continue;
    
    case 2:  // doDogThings.
      if (hungry > 2) { state = 4; continue; }
      if (tired > 3) { state = 1; continue; }
      if (seesSquirrel()) { state = 3; continue; }
      ...

如果狀態(tài)值被命名為常量庐椒,那么它將與原始版本一樣具有可讀性,但不需要switch語句來支持狀態(tài)機(jī)蚂踊。

(三)while 和do while

while(判斷條件){
    內(nèi)容體
}
do{
內(nèi)容體
} while(判斷條件);
while(a>5){
    print(a);
}
do{
print(a);
} while(a>5);

(四)break continue

break 停止循環(huán)

while(a>5){
  if(b>5){
  print(a);
    break;
  }
}

continue 跳到下一個循環(huán)

while(a>5){
  if(b<10){
  print(b);
    continue;
  }
}

如果使用Iterable(list或者set)扼睬,則可以使用下面這種方式:

// 第一個是滿足條件就進(jìn)入  第二個是foreach遍歷
arrays
  .when((c)=>c.counts >=5)
  .foreach((c)=>c.next());

(五)switch case

比較integer, string,編譯時常量 使用==。比較對象必須是同一個類的實(shí)例(不是其子類的實(shí)例)悴势,并且該類不能重寫==窗宇。枚舉類型在switch也可以運(yùn)行。
每一條非空case字子句以break結(jié)束特纤,也可以使用其他的方式結(jié)束:continue,throw或者return军俊。

var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}

(六)assert

如果布爾條件為false,則使用assert語句來中斷正常執(zhí)行捧存。例如:

// 確保變量具有非空值 
assert(text != null);
// 確保值小于100
assert(number < 100);
// 確保這是一個 https 網(wǎng)址
assert(urlString.startsWith('https'));

要將消息附加到斷言粪躬,請?zhí)砑右粋€字符串作為第二個參數(shù)。

assert(urlString.startsWith('https'),'URL ($urlString) should start with "https".');

上例中assert的第一個參數(shù)可以是任何解析為布爾值的表達(dá)式昔穴。如果表達(dá)式的值為true镰官,則斷言成功并繼續(xù)執(zhí)行。如果為false吗货,則斷言失敗并拋出異常


七泳唠、異常

Dart代碼可以拋出并捕獲異常。Exception是指示發(fā)生意外事件的錯誤宙搬。如果未捕獲異常笨腥,則會暫停引發(fā)異常的isolate 拓哺,并且通常會終止isolate及其程序。

與Java相比脖母,Dart的所有異常都是未經(jīng)檢查的異常士鸥。方法不會聲明它們可能引發(fā)的異常,并且您不需要捕獲任何異常谆级。

Dart提供了ExceptionError 類型烤礁,以及許多預(yù)定義的子類型。當(dāng)然肥照,您可以定義自己的Exception鸽凶。但是,Dart程序可以拋出任何非null對象建峭,作為Exception(不僅僅是Exception和Error對象)玻侥。

(一)throw

以下是拋出或引發(fā)異常的示例:

throw FormatException('Expected at least 1 section');

你也可以拋出任意對象,例如:throw '格式不正確!';
通常在開發(fā)中會拋出Error或者Exception類型亿蒸。

因?yàn)閽伋霎惓J且粋€表達(dá)式凑兰,所以可以在=>語句中以及允許表達(dá)式的任何其他地方拋出異常:

void distanceTo(Point other) => throw UnimplementedError();   

(二)try catch

捕獲或捕獲異常會阻止異常傳遞(除非您重新拋出異常)。捕獲異常使您有機(jī)會處理它:

try {
    breedMoreLlamas();
} on OutOfLlamasException {
    buyMoreLlamas();
}

要處理可能拋出多種類型異常的代碼边锁,可以指定多個catch子句姑食。與拋出對象的類型匹配的第一個catch子句處理異常。如果catch子句未指定類型茅坛,則該子句可以處理任何類型的拋出對象音半。
您可以使用on或catch兩者兼而有之。使用on時需要指定異常類型贡蓖。使用catch時曹鸠,你的異常處理程序需要異常對象。
示例:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}

您可以指定一個或兩個參數(shù)catch()斥铺。第一個是拋出的異常彻桃,第二個是堆棧跟蹤(StackTrace對象)。
示例:

try {
  // ···
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

要部分處理異常晾蜘,同時允許它傳遞邻眷,請使用rethrow關(guān)鍵字。
示例:

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // 運(yùn)行時異常
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // 允許調(diào)用者查看exception.
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

(三)finally

無論是否拋出異常剔交,要確保某些代碼運(yùn)行肆饶,請使用finally子句。如果沒有catch子句匹配該異常岖常,則在finally子句運(yùn)行后傳遞異常驯镊。
示例:

try {
  breedMoreLlamas();
} finally {
  // 即使拋出異常  也會執(zhí)行這句代碼.
  cleanLlamaStalls();
}
該finally子句在任何匹配的catch子句之后運(yùn)行:
try {
  breedMoreLlamas();
} catch (e) {
    // 首先會處理異常
  print('Error: $e'); 
} finally {
  // 然后執(zhí)行這句語句
  cleanLlamaStalls(); 
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子阿宅,更是在濱河造成了極大的恐慌,老刑警劉巖笼蛛,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洒放,死亡現(xiàn)場離奇詭異,居然都是意外死亡滨砍,警方通過查閱死者的電腦和手機(jī)往湿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惋戏,“玉大人领追,你說我怎么就攤上這事∠旆辏” “怎么了绒窑?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長舔亭。 經(jīng)常有香客問我些膨,道長,這世上最難降的妖魔是什么钦铺? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任订雾,我火速辦了婚禮,結(jié)果婚禮上矛洞,老公的妹妹穿的比我還像新娘洼哎。我一直安慰自己,他們只是感情好沼本,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布噩峦。 她就那樣靜靜地躺著,像睡著了一般抽兆。 火紅的嫁衣襯著肌膚如雪壕探。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天郊丛,我揣著相機(jī)與錄音李请,去河邊找鬼。 笑死厉熟,一個胖子當(dāng)著我的面吹牛导盅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播揍瑟,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼白翻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起滤馍,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤岛琼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后巢株,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體槐瑞,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年阁苞,在試婚紗的時候發(fā)現(xiàn)自己被綠了困檩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡那槽,死狀恐怖悼沿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情骚灸,我是刑警寧澤糟趾,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站甚牲,受9級特大地震影響拉讯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鳖藕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一魔慷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧著恩,春花似錦院尔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至伍茄,卻和暖如春栋盹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背敷矫。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工例获, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人曹仗。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓榨汤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親怎茫。 傳聞我的和親對象是個殘疾皇子收壕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

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