智能合約編程語(yǔ)言 - solidity快速入門(mén)(上)

solidity簡(jiǎn)介

本文默認(rèn)讀者已掌握至少一種面向?qū)ο缶幊陶Z(yǔ)言卫枝,所以文中一些概念會(huì)借助其他語(yǔ)言進(jìn)行類比掂榔。

solidity是用于實(shí)現(xiàn)智能合約的一種面向合約的高級(jí)編程語(yǔ)言视卢,solidity受到C++酥泛、Python和JavaScript的影響月趟,被設(shè)計(jì)為可運(yùn)行在以太坊虛擬機(jī)(EVM)上杨幼,所以用戶無(wú)需擔(dān)心代碼的可移植性和跨平臺(tái)等問(wèn)題撇簿。solidity是一種靜態(tài)類型的語(yǔ)言聂渊,支持繼承、庫(kù)引用等特性四瘫,并且用戶可自定義復(fù)雜的結(jié)構(gòu)類型汉嗽。

目前嘗試 Solidity 編程的最好的方式是使用 Remix (由于是網(wǎng)頁(yè)IDE可能加載起來(lái)需要一定的時(shí)間)。Remix 是一個(gè)基于 Web 的 IDE找蜜,它可以讓你編寫(xiě) Solidity 智能合約饼暑,然后部署并運(yùn)行該智能合約,它看起來(lái)是這樣子的:

image.png

也可以使用sublime或vs code等編輯器編寫(xiě) Solidity 代碼洗做,然后復(fù)制粘貼到Remix上部署運(yùn)行弓叛。

solidity官網(wǎng)地址如下:

https://solidity.readthedocs.io/en/latest/index.html


合約文件

本小節(jié)我們來(lái)說(shuō)說(shuō)合約文件,眾所周知任何語(yǔ)言所編寫(xiě)的代碼都需要存儲(chǔ)在一個(gè)文件里诚纸,并且該文件都有一個(gè)特定的后綴名撰筷,我們一般將這種文件稱之為代碼文件。

solidity代碼文件的后綴名為.sol畦徘,但我們通常會(huì)把使用solidity編寫(xiě)的文件稱之為合約文件毕籽,一個(gè)合約文件通常會(huì)包含四個(gè)部分,其實(shí)與我們平時(shí)所編寫(xiě)其他語(yǔ)言的代碼文件是類似的井辆,如下圖所示:

image.png

版本聲明的代碼需寫(xiě)在合約文件的開(kāi)頭关筒,接著可以根據(jù)實(shí)際情況導(dǎo)入一些合約,所謂導(dǎo)入合約也就類似于其他面向?qū)ο蟮恼Z(yǔ)言導(dǎo)入某個(gè)類的概念掘剪。然后就是聲明一個(gè)合約平委,在合約里編寫(xiě)具體的代碼,其實(shí)這里的合約與我們所熟悉的類的概念基本上是一樣的夺谁,可以暫時(shí)將它們當(dāng)做同一個(gè)東西廉赔。

我們先來(lái)對(duì)一個(gè)較為完整的合約代碼進(jìn)行一個(gè)預(yù)覽,在之后會(huì)對(duì)代碼中的每個(gè)部分進(jìn)行逐一介紹:

// 版本聲明
pragma solidity ^0.4.0;

// 導(dǎo)入一個(gè)合約
import "solidity_for_import.sol";

// 定義一個(gè)合約
contract ContractTest {
    // 定義一個(gè)無(wú)符號(hào)整型變量
    uint a;

    // 定義一個(gè)事件
    event Set_A(uint a);
    
    // 定義一個(gè)函數(shù)
    function setA(uint x) public {
        a = x;
        // 觸發(fā)一個(gè)事件
        emit Set_A(x);
    }

    // 定義一個(gè)具有返回值的函數(shù)
    function getA() public returns (uint) {
        return a;
    }
    
    // 自定義一個(gè)結(jié)構(gòu)類型
    struct Pos {
        // 定義一個(gè)有符號(hào)整型變量
        int lat;
        int lng;
    }

    // 定義一個(gè)地址類型匾鸥,每個(gè)合約都運(yùn)行在一個(gè)特定的地址上
    address public addr;

    // 定義一個(gè)函數(shù)修改器
    modifier owner () {
        require(msg.sender == addr);
        _;
    }

    // 讓函數(shù)使用函數(shù)修改器
    function mine() public owner {
        a += 1;
    }
}

這里對(duì)函數(shù)修改器做一個(gè)簡(jiǎn)單的說(shuō)明:

函數(shù)修改器的概念類似于python中的裝飾器蜡塌,其核心目的都是給函數(shù)增加函數(shù)內(nèi)沒(méi)有定義的功能,也就是對(duì)函數(shù)進(jìn)行增強(qiáng)

從以上代碼中勿负,可以看到owner 函數(shù)修改器里定義了一句條件代碼馏艾,其意義為:

當(dāng)msg.sender等于addr地址變量時(shí),才繼續(xù)往下執(zhí)行奴愉,因?yàn)檫@個(gè)require函數(shù)是solidity校驗(yàn)條件用的琅摩,若不符合條件就會(huì)拋出異常

mine函數(shù)使用了owner函數(shù)修改器后,那么mine函數(shù)在執(zhí)行之前锭硼,會(huì)先執(zhí)行owner函數(shù)修改器里的條件代碼房资,也就是說(shuō)當(dāng)msg.sender等于addr成立的話,才會(huì)執(zhí)行mine函數(shù)里a += 1;的代碼檀头,否則就不會(huì)執(zhí)行轰异。從中也可以看出函數(shù)修改器里的_;語(yǔ)句岖沛,其實(shí)表示的就是mine函數(shù)里的代碼,如此一來(lái)在不修改mine函數(shù)的前提下搭独,給mine函數(shù)增加了額外的功能婴削。


solidity 類型

Solidity是一種靜態(tài)類型語(yǔ)言,意味著每個(gè)變量(本地或狀態(tài)變量)需要在編譯時(shí)指定變量的類型(或至少可以推導(dǎo)出類型)牙肝,Solidity提供了一些基本類型可以用來(lái)組合成復(fù)雜類型唉俗。

Solidity和大多數(shù)語(yǔ)言一樣,有兩種類型:

  • 值類型(Value Type) - 變量在賦值或傳參時(shí)惊奇,總是進(jìn)行值拷貝互躬。
  • 引用類型(Reference Types)

solidity所包含的值類型如下:


image.png

注:其中標(biāo)紅的是最常用的類型

官網(wǎng)關(guān)于solidity類型的文檔地址如下:

https://solidity.readthedocs.io/en/latest/types.html

1.布爾類型取值范圍是true和false播赁,使用bool關(guān)鍵字進(jìn)行聲明颂郎,聲明方式如下:

// 版本聲明
pragma solidity ^0.4.0;

// 定義一個(gè)合約
contract ContractTest {
    bool b1 = true;
    bool b2 = false;
}   

2.solidity中有兩種整型的定義方式,一種是無(wú)符號(hào)整型容为,另一種則是有符號(hào)整型乓序。并且支持關(guān)鍵字uint8 到 uint256 (以8步進(jìn)),uint 和 int 默認(rèn)對(duì)應(yīng)的是 uint256 和 int256坎背。如下示例:

// 版本聲明
pragma solidity ^0.4.0;

// 定義一個(gè)合約
contract ContractTest {
    // 定義一個(gè)無(wú)符號(hào)的整型變量
    uint a;
    // 定義一個(gè)有符號(hào)的整型變量
    int i;
}

solidity常量

在solidity里使用constant關(guān)鍵字來(lái)聲明常量替劈,但并非所有的類型都支持常量,當(dāng)前支持的僅有值類型和字符串:

pragma solidity ^0.4.0;

contract C {
    uint constant x = 32**22 + 8;
    string constant text = "abc";
    bytes32 constant myHash = keccak256("abc");
}

在solidity中還可以將函數(shù)聲明為常量得滤,該函數(shù)的返回值就是常量值陨献,這類函數(shù)將承諾自己不修改區(qū)塊鏈上任何狀態(tài):

// 定義有理數(shù)常量
function testLiterals() public constant returns (int) {
    return 1;
}

// 定義字符串常量
function testStringLiterals() public constant returns (string) {
    return "string";
}

// 定義16進(jìn)制常量,以關(guān)鍵字hex打頭懂更,后面緊跟用單或雙引號(hào)包裹的字符串眨业,內(nèi)容是十六進(jìn)制字符串
function testHexLiterals() public constant returns (bytes2) {
    return hex"abcd";
}

有理數(shù)常量函數(shù)里的運(yùn)算可以是任意精度的,不會(huì)有溢出的問(wèn)題:

// 定義有理數(shù)常量
function testLiterals() public constant returns (int) {
    return 1859874861811128585416.0 + 123.0;
}

科學(xué)符號(hào)也支持沮协,基數(shù)可以是小數(shù)龄捡,但指數(shù)必須是整數(shù),如下:

// 定義有理數(shù)常量
function testLiterals() public constant returns (int) {
    return 2e10;
}

solidity地址類型

solidity中使用address關(guān)鍵字聲明地址類型變量慷暂,該類型屬于值類型聘殖,地址類型主要用于表示一個(gè)賬戶地址,一個(gè)以太坊地址的長(zhǎng)度為20字節(jié)的16進(jìn)制數(shù)行瑞,地址類型也有成員奸腺,地址是所有合約的基礎(chǔ)。

地址類型的主要成員:

  • 屬性:balance血久,用來(lái)查詢賬戶余額
  • 函數(shù):transfer()突照,用來(lái)轉(zhuǎn)移以太幣(默認(rèn)以wei為單位)

代碼示例如下:

pragma solidity ^0.4.7;

contract AddrTest {

    // payable關(guān)鍵字定義一個(gè)可接受以太幣的函數(shù)
    function deposit() public payable {

    }

    // 查詢賬戶余額
    function getBalance() public constant returns (uint) {
        return this.balance;
    }

    // 轉(zhuǎn)移以太幣
    function transferEther(address towho) public {
        towho.transfer(10);
    }
}

然后我們將這段代碼復(fù)制粘貼到remix中編譯運(yùn)行看看,首先需要在Compile選項(xiàng)卡中將代碼進(jìn)行編譯:


image.png

編譯成功后洋魂,到Run選項(xiàng)卡中绷旗,部署該合約:


image.png

部署成功后喜鼓,可以查看到合約中的各個(gè)函數(shù),并且只需要點(diǎn)擊就可以運(yùn)行指定的函數(shù):


image.png

此時(shí)我們來(lái)點(diǎn)擊執(zhí)行一下getBalance函數(shù):


image.png

可以看到衔肢,此時(shí)該合約的賬戶余額為0庄岖,現(xiàn)在我們來(lái)存儲(chǔ)10個(gè)wei的以太幣到合約中:


image.png

此時(shí)再執(zhí)行g(shù)etBalance函數(shù),合約余額為10個(gè)wei:


image.png

然后我們?cè)賮?lái)看看轉(zhuǎn)移/發(fā)送以太幣的transferEther函數(shù)角骤,此時(shí)我們這個(gè)合約地址的余額為10個(gè)wei隅忿,當(dāng)我將這10個(gè)wei的以太轉(zhuǎn)移到另一個(gè)地址后,當(dāng)前合約的余額為0:


image.png

在solidity中一個(gè)能通過(guò)地址合法性檢查(address checksum test)的十六進(jìn)制常量就會(huì)被認(rèn)為是地址邦尊,如:

0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF

而不能通過(guò)地址合法性檢查的39到41位長(zhǎng)的十六進(jìn)制常量背桐,會(huì)提示一個(gè)警告,被視為普通的有理數(shù)常量蝉揍。

關(guān)于賬戶地址的合法性檢查定義參考如下提案:

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md


solidity數(shù)組

在上文中我們提到Solidity 類型分為值類型和引用類型链峭,以上小節(jié)介紹了常見(jiàn)的值類型,接下來(lái)會(huì)介紹一下引用類型又沾。

引用類型是一個(gè)復(fù)雜類型弊仪,占用的空間通常超過(guò)256位, 拷貝時(shí)開(kāi)銷很大杖刷,因此我們需要考慮將它們存儲(chǔ)在什么位置励饵,是存儲(chǔ)在memory(內(nèi)存,數(shù)據(jù)不是永久存在)中還是存儲(chǔ)在storage(永久存儲(chǔ)在區(qū)塊鏈)中滑燃。

所有的復(fù)雜類型如數(shù)組和結(jié)構(gòu)體都有一個(gè)額外的屬性:數(shù)據(jù)的存儲(chǔ)位置(data location)役听,可為memory和storage。根據(jù)上下文的不同表窘,大多數(shù)時(shí)候數(shù)據(jù)存儲(chǔ)的位置有默認(rèn)值典予,也可以通過(guò)指定關(guān)鍵字storage和memory修改它。

函數(shù)參數(shù)(包含返回的參數(shù))默認(rèn)是memory蚊丐。而局部復(fù)雜類型變量(local variables)和狀態(tài)變量(state variables) 默認(rèn)是storage熙参。局部變量即部作用域(越過(guò)作用域即不可被訪問(wèn),等待被回收)的變量麦备,如函數(shù)內(nèi)的變量孽椰,狀態(tài)變量則是合約內(nèi)聲明的公有變量。

除此之外凛篙,還有一個(gè)存儲(chǔ)位置是:calldata黍匾,用來(lái)存儲(chǔ)函數(shù)參數(shù),是只讀的呛梆,不會(huì)永久存儲(chǔ)的一個(gè)數(shù)據(jù)位置锐涯。外部函數(shù)的參數(shù)(不包括返回參數(shù))被強(qiáng)制指定為calldata。效果與memory差不多填物。還有一個(gè)存儲(chǔ)位置是:calldata纹腌,用來(lái)存儲(chǔ)函數(shù)參數(shù)霎终,是只讀的,不會(huì)永久存儲(chǔ)的一個(gè)數(shù)據(jù)位置升薯。外部函數(shù)的參數(shù)(不包括返回參數(shù))被強(qiáng)制指定為calldata莱褒。效果與memory差不多。

數(shù)組是一種典型的引用類型涎劈,在solidity中數(shù)組的定義方式如下:

  • T[k]:元素類型為T(mén)广凸,固定長(zhǎng)度為k的數(shù)組
  • T[]:元素類型為T(mén),長(zhǎng)度可動(dòng)態(tài)調(diào)整的數(shù)組
  • bytes和string 是一種特殊的數(shù)組蛛枚,string 可轉(zhuǎn)為 bytes谅海,而bytes則類似于byte[]

數(shù)組類型有兩個(gè)主要成員:

  • 屬性:length
  • 函數(shù):push()

具體的示例代碼如下:

pragma solidity ^0.4.7;

contract ArrayTest {
    // 定義一個(gè)無(wú)符號(hào)整型的變長(zhǎng)數(shù)組
    uint[] public numbers = [1, 2, 3];

    // 定義一個(gè)字符串
    string str = "abcdefg";

    function getNumbersLength() public returns (uint) {
        // 往數(shù)組中添加一個(gè)元素
        numbers.push(4);

        // 返回?cái)?shù)組的長(zhǎng)度
        return numbers.length;
    }

    function getStrLength() public constant returns (uint) {
        // 將字符串轉(zhuǎn)換為bytes并返回長(zhǎng)度
        return bytes(str).length;
    }

    function getFirst() public constant returns (byte) {
        // 將字符串轉(zhuǎn)換為bytes后,通過(guò)下標(biāo)訪問(wèn)元素
        return bytes(str)[0];
    }

    function newMemory(uint len) public constant returns (uint) {
        // 定義一個(gè)定長(zhǎng)數(shù)組并通過(guò)memory指定數(shù)組的存儲(chǔ)位置
        uint[] memory memoryArr = new uint[] (len);
        return memoryArr.length;
    }

    function changeFirst(uint[3] _data) public constant returns (uint[3]) {
        // 通過(guò)索引操作元素
        _data[0] = 0;
        return _data;
    }
}

solidity結(jié)構(gòu)體和映射

Solidity提供struct關(guān)鍵字來(lái)定義自定義類型也就是結(jié)構(gòu)體蹦浦,自定義的類型屬于引用類型扭吁,如果學(xué)習(xí)過(guò)go語(yǔ)言的話應(yīng)該對(duì)其不會(huì)陌生。如下示例:

// 版本聲明
pragma solidity ^0.4.7;

// 定義一個(gè)合約
contract ContractTest {
    // 聲明一個(gè)結(jié)構(gòu)體
    struct Funder {
        address addr;
        uint amount;
    }

    // 將自定義的結(jié)構(gòu)體聲明為狀態(tài)變量
    Funder funder;

    // 使用結(jié)構(gòu)體
    function newFunder() public {
        funder = Funder({addr: msg.sender, amount: 10});
    }
}

solidity擁有映射類型白筹,映射類型是一種鍵值對(duì)的映射關(guān)系存儲(chǔ)結(jié)構(gòu)智末,有點(diǎn)類似于python語(yǔ)言中的字典谅摄。定義方式為mapping(_KeyType => _KeyValue)徒河。鍵類型允許除映射、變長(zhǎng)數(shù)組送漠、合約顽照、枚舉、結(jié)構(gòu)體外的幾乎所有類型值類型沒(méi)有任何限制闽寡,可以為任何類型包括映射類型代兵。

映射可以被視作為一個(gè)哈希表,所有可能的鍵會(huì)被虛擬化的創(chuàng)建爷狈,映射到一個(gè)類型的默認(rèn)值(二進(jìn)制的全零表示)植影。在映射表中,并不存儲(chǔ)鍵的數(shù)據(jù)涎永,僅僅存儲(chǔ)它的keccak256哈希值思币,這個(gè)哈希值在查找值時(shí)需要用到。正因?yàn)榇讼畚ⅲ成涫菦](méi)有長(zhǎng)度的谷饿,也沒(méi)有鍵集合或值集合的概念。

映射類型有一點(diǎn)比較特殊妈倔,它僅能用來(lái)作為狀態(tài)變量博投,或在內(nèi)部函數(shù)中作為storage類型的引用。

可以通過(guò)將映射標(biāo)記為public盯蝴,來(lái)讓Solidity創(chuàng)建一個(gè)訪問(wèn)器毅哗。通過(guò)提供一個(gè)鍵值做為參數(shù)來(lái)訪問(wèn)它听怕,將返回對(duì)應(yīng)的值。映射的值類型也可以是映射虑绵,使用訪問(wèn)器訪問(wèn)時(shí)叉跛,要提供這個(gè)映射值所對(duì)應(yīng)的鍵,不斷重復(fù)這個(gè)過(guò)程蒸殿。

示例代碼如下:

// 版本聲明
pragma solidity ^0.4.7;

// 定義一個(gè)合約
contract ContractTest {
    // 定義一個(gè)映射類型筷厘,key類型為address,value類型為uint
    mapping(address => uint) public balances;

    function updateBalance(uint newBalance) public {
        // msg.sender作為鍵宏所,newBalance作為值酥艳,將這對(duì)鍵值添加到該映射中
        balances[msg.sender] = newBalance;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市爬骤,隨后出現(xiàn)的幾起案子充石,更是在濱河造成了極大的恐慌,老刑警劉巖霞玄,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骤铃,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡坷剧,警方通過(guò)查閱死者的電腦和手機(jī)惰爬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)惫企,“玉大人撕瞧,你說(shuō)我怎么就攤上這事∧” “怎么了丛版?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)偏序。 經(jīng)常有香客問(wèn)我页畦,道長(zhǎng),這世上最難降的妖魔是什么研儒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任豫缨,我火速辦了婚禮,結(jié)果婚禮上殉摔,老公的妹妹穿的比我還像新娘州胳。我一直安慰自己,他們只是感情好逸月,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布栓撞。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瓤湘。 梳的紋絲不亂的頭發(fā)上瓢颅,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音弛说,去河邊找鬼挽懦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛木人,可吹牛的內(nèi)容都是我干的信柿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼醒第,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼渔嚷!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起稠曼,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤形病,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后霞幅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體漠吻,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年司恳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了途乃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抵赢,死狀恐怖欺劳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铅鲤,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布枫弟,位于F島的核電站邢享,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏淡诗。R本人自食惡果不足惜骇塘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望韩容。 院中可真熱鬧款违,春花似錦、人聲如沸群凶。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至赠尾,卻和暖如春力穗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背气嫁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工当窗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人寸宵。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓崖面,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親梯影。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嘶朱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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