Swift學(xué)習(xí)總結(jié)
語(yǔ)言基礎(chǔ)
程序是指令的集合规惰,寫程序就是寫一系列的指令去控制計(jì)算機(jī)做我們想做的事情。
編譯:將程序設(shè)計(jì)語(yǔ)言轉(zhuǎn)換成計(jì)算機(jī)能夠理解的機(jī)器語(yǔ)言或者某種中間代碼的過程庐船。
馮諾依曼體系結(jié)構(gòu)的計(jì)算機(jī):
1.使用二進(jìn)制
2.程序存儲(chǔ)執(zhí)行
變量和常量
定義變量和常量是為了保存數(shù)據(jù)。變量和常量就是某種類型的值的存儲(chǔ)空間嘲更。
var a:Int = 10
a = 100
var b:Int
b = 1000
var c = 10000
let d:Int = 10
//d = 100 //compiler error
let e = 1000
說(shuō)明:1.Swift有非常強(qiáng)大的類型推斷筐钟,所以定義變量或常量時(shí)如果可以的話應(yīng)該直接使用類型推斷不用手動(dòng)指定類型 2.如果可以的話應(yīng)該盡可能使用常量而不是變量。
語(yǔ)言元素
var a:Int = 10
關(guān)鍵字:有特殊含義的單詞
標(biāo)識(shí)符:始變量赋朦、常量篓冲、函數(shù)、類宠哄、結(jié)構(gòu)壹将、枚舉、協(xié)議毛嫉、方法屬性等起的名字
- 數(shù)字诽俯、字母、下劃線承粤,數(shù)字不能開頭
- 大小寫敏感(區(qū)分大小寫)
- 不能使用關(guān)鍵字做標(biāo)識(shí)符
- 使用駝峰命名法(命名變量暴区、常量、函數(shù)辛臊、方法仙粱、屬性第一個(gè)單詞小寫,從第二個(gè)單詞開始每個(gè)單詞首字母大寫彻舰;命名類伐割、結(jié)構(gòu)候味、協(xié)議、枚舉每個(gè)單詞首字母都要大寫)
- 見名知意
- 命名私有的屬性和方法時(shí)以下劃線開頭
運(yùn)算符:Swift中的運(yùn)算符其實(shí)都是函數(shù)
1.賦值運(yùn)算符:=隔心、+=白群、-=
2.算術(shù)運(yùn)算符:+、-济炎、*川抡、/、%
3.比較運(yùn)算符:==须尚、!=崖堤、<、<=耐床、>密幔、>=
4.邏輯運(yùn)算符:&&、||撩轰、!
5.條件運(yùn)算符:?:
6.其它運(yùn)算符:[]胯甩、.、??堪嫂、?偎箫、!
字面(常)量
1.整數(shù)字面量:10、1_234_567皆串、0x10淹办、0o10、0b10
2.小數(shù)字面量:123.45恶复、1.2345e2怜森、0xab.cdp2
3.字符字面量:"c"、"\n"谤牡、"\u{41}"副硅、"\u(9a86)"
4.字符串字面量:"Hello"、"caf\u{e9}"
5.布爾字面量:true翅萤、false
6.空值字面量:nil
7.類型字面量:String.self恐疲、UILable.self
分隔符:將不同的語(yǔ)言元素符號(hào)分開
說(shuō)明:Swift中每個(gè)語(yǔ)句后面的分號(hào)可寫可不寫的,寫代碼時(shí)盡量保證一行只有一條語(yǔ)句這樣就可以省略掉分號(hào)断序。
分支和循環(huán)
分支
- if...else...
下面的程序?qū)崿F(xiàn)了對(duì)是否是閏年問題的判斷流纹。
let year = 2005
if year % 4 == 0 && year % 100 != 0 || year % 400 == 0{
print("\(year)是閏年")
}
else{
print("\(year)不是閏年")
}
- switch...case...default...
下面的程序?qū)崿F(xiàn)了將百分制的成績(jī)轉(zhuǎn)換成不同級(jí)別。
let score = 85.5
let msg:String
switch score{
case 90...100:msg = "優(yōu)秀"
case 80..<90:msg = "良好"
case 70..<80:msg = "中等"
case 60..<70:msg = "及格"
default:msg = "不及格"
}
print{msg}
循環(huán)
- for
下面的程序?qū)崿F(xiàn)了1-100的求和违诗。
var sum = 0
for i in 1...100{
sum += i
}
print(sum)
- while
下面的程序?qū)崿F(xiàn)了1-100的求和。
var sum = 0
var i = 1
while i <= 100{
sum += i
i += 1
}
print(sum)
- repeat...while...
下面的程序?qū)崿F(xiàn)了1-100的求和疮蹦。
var sum = 0
var j = 1
repeat{
sum += j
j += 1
}while j <= 100
print(sum)
窮舉法:窮盡所有可能性直到找到正確答案诸迟。
下面的程序?qū)崿F(xiàn)了"百錢百雞"問題的求解
for x in 0...20{
for y in 0...33{
let z = 100 - x - y
if 5 * x + 3 * y + z / 3 == 100 && z % 3 == 0{
print("公雞:\(x),母雞:\(y),小雞:\(z)")
}
}
}
說(shuō)明 在循環(huán)中可以使用break關(guān)鍵字來(lái)提前終止循環(huán),也可以使用continue關(guān)鍵字使循環(huán)直接進(jìn)入下一輪,但是應(yīng)該盡量減少對(duì)break和continue的使用阵苇,因?yàn)樗鼈儾粫?huì)讓你的程序變得更好壁公。
綜合案例:Craps賭博游戲.
游戲規(guī)則:玩家搖兩顆色子,如果第一次搖出了7點(diǎn)或11點(diǎn)绅项,玩家勝紊册;如果搖出了2點(diǎn)、3點(diǎn)快耿、12點(diǎn)囊陡,莊家勝;搖出其它點(diǎn)數(shù)游戲繼續(xù)掀亥。在繼續(xù)的過程中玩家重新?lián)u色子撞反,如果搖出了第一次搖的點(diǎn)數(shù),玩家勝搪花;搖出7點(diǎn)遏片,莊家勝;其它點(diǎn)數(shù)游戲繼續(xù)直到一方獲得勝利撮竿。
func roll() -> Int{
return Int(arc4random_uniform(6)) + 1
}
let firstPoint = roll() + roll()
print("玩家搖出了\(firstPoint)點(diǎn)")
var needsGoOn = false
switch firstPoint{
case 7,11:print("玩家勝")
case 2,3,12:print("莊家勝")
default:needsGoOn = true
}
while needsGoOn{
let currentPoint = roll() + roll()
print("玩家搖出了\(currentPoint)")
if currentPoint == 7{
print("莊家勝")
needsGoOn = false
}
else if currentPoint = firstPoint{
print("玩家勝")
needsGoOn = false
}
else{
needsGoOn = true
}
}
容器
數(shù)組
數(shù)組是使用連續(xù)的內(nèi)存空間保存多個(gè)同類型的元素的容器吮便,因?yàn)閿?shù)組中的元素在內(nèi)存中是連續(xù)的,所以可以使用下標(biāo)運(yùn)算來(lái)訪問數(shù)組中的元素幢踏,實(shí)現(xiàn)隨機(jī)存取髓需。
- 創(chuàng)建數(shù)組
var array1:[Int] = []
var array2:Array<Int> = []
var array3 = [1,2,3,4,5]
var array4 = [Int](count:5,repeatedValue:0)
var array5 = Array<Int>(count:5,repeatedValue:0)
- 添加元素
array1.append(2)
array1.append(3)
array1.insert(1,atIndex:0)
array1.insert(4,atIndex:array1.count)
array1 += [5]
array1 += [6,7,8]
- 刪除元素
array1.removeAtIndex(2)
arrar1.removeFirst()
array1.removeFirst(2)
array1.removeLast()
array1.removeRange(1...2)
array1.removeAll()
- 修改元素
array3[0] = 100
array3.[array.count-1] = 500
- 遍歷數(shù)組
- 方式一
for i in 0..<array3.count{
print(array3[i])
}
- 方式二
for temp in array3{
print(temp)
}
for temp in array3[1...3]{
print(temp)
}
說(shuō)明for-in循環(huán)是一個(gè)只讀循環(huán),這也就意味著在循環(huán)的過程中不能對(duì)數(shù)組中的元素進(jìn)行修改
- 方式三
for(i,temp) in array3.enumerate(){
if i == 0{
array[i] = 1
}
print("\(i).\(temp)")
}
提醒:操作數(shù)組時(shí)最重要的是不要越界訪問元素惑折。數(shù)組對(duì)象的count屬性表明了數(shù)組中有多少個(gè)元素授账,那么在有效的索引范圍是0到count-1
數(shù)組中的元素也可以是數(shù)組,因此我們可以構(gòu)造多維數(shù)組惨驶,最常見的是二維數(shù)組白热,它相當(dāng)于是一個(gè)有行有列的數(shù)組,數(shù)組中的每個(gè)元素代表一行粗卜,該數(shù)組中的每個(gè)元素代表行里面的列屋确。二維數(shù)組可以模擬現(xiàn)實(shí)世界中的表格、數(shù)學(xué)上的矩陣续扔、棋類游戲的棋盤攻臀、2D游戲中的地圖,所以在實(shí)際開發(fā)中應(yīng)用廣泛纱昧。
下面的程序是用二維數(shù)組模擬表格的例子刨啸。
func randomInt(min:UInt32,max:UInt)->Int{
return Int(arc4random_uniform(max - min + 1) + min)
}
let namesArray = ["趙","錢","孫","李","周"]
let coursesArray = ["語(yǔ)文","數(shù)學(xué)","英語(yǔ)"]
var scoresArray = [[Double]](count:namesArray.count,repeatedValue: [Double](count:coursesArray.count,repeatedValue:0))
for i in 0..<scoresArray.count{
for j in 0..<scoresArray[i].count{
scoresArray[i][j] = Double(randomInt(50,max:100))
}
}
for (index,array) in scoresArray.enumerate(){
var sum = 0.0
for score in array{
sum += score
}
let avg = sum / Double(coursesArray.count)
print("\(namesArray[index])的平均成績(jī)?yōu)?\(avg)")
}
for i in 0..<coursesArray.count{
var sum = 0.0
for row in 0..<coursesArray.count{
sum += scoresArray[row][i]
}
let avg = sum/Double(namesArray.count)
print("\(coursesArray[i])課的平均成績(jī)?yōu)?\(avg)")
}
集合
集合在內(nèi)存中是離散的,集合中的元素通過計(jì)算Hash Code(哈希碼或散列碼)來(lái)決定存放在內(nèi)存中的什么位置识脆,集合中不允許有重復(fù)元素设联。
- 創(chuàng)建集合
let set:Set<Int> = [1,2,1,2,3,5,10,200]
- 添加和刪除元素
set.insert(100)
set.removeFirst()
set.remove(5)
set.removeAll()
- 集合運(yùn)算(交集善已、并集、差集)
var set1:Set<Int> = [1,2,1,2,3,4,5]
var set2:Set<Int> = [1,3,5,7]
set1.intersect(set2)
set1.union(set2)
set1.subtract(set2)
字典
字典是以鍵值對(duì)的方式保存數(shù)據(jù)的容量离例,字典中的每個(gè)元素都是鍵值對(duì)組合换团,通過鍵可以找到對(duì)應(yīng)的值。
- 創(chuàng)建字典
var dict [Int:String] = [1:"hello"
2:"good"
3:"wonderful"
5:"delicious"]
- 添加宫蛆、刪除艘包、修改元素
dict[3]= "terrible"
dict[4] = "shit"
dict[5] = nil
- 遍歷元素
for key in dict.keys{
print("\(key)--->\(dict[key]!)")
}
for value in dict.values{
print(value)
}
for (index,value) in dict.enumerate(){
print("\(index).\(value.0)--->\(value.1)")
}
重要操作
- 排序
1.sort
2.sortInPlace
let array = [23,45,12,89,98,55,7]
array.sort({(one:Int,two:Int) -> Bool in return one < two })
array.sort({(one,two) in one < two })
array.sort({one,two in one < two })
array.sort({$0 < $1})
array.sort{$0 < $1}
array.sort(<)
說(shuō)明:排序方法的參數(shù)是一個(gè)閉包(closure),該閉包的作用是比較數(shù)組中兩個(gè)元素的大小
- 過濾:篩選掉不滿足條件的數(shù)據(jù)
let array = [23,45,12,89,98,55,7]
let newArray = array.filter{ $0 > 50}
print(newArray) //[89,98,55]
- 映射:通過映射對(duì)數(shù)據(jù)進(jìn)行變換處理
let array = [23,45,12,89,98,55,7]
let newArray = array.map{ sqrt(Double($0))}
print(newArray)
- 歸約
let array = [23,45,12,89,98,55,7]
let newArray = array.reduce(0,combine:+)
print(newArray)
函數(shù)和閉包
函數(shù)是獨(dú)立的可重復(fù)使用的功能模塊耀盗,如果程序中出現(xiàn)了大量的重復(fù)代碼想虎,通常都可以將這部分功能封裝成一個(gè)獨(dú)立的函數(shù)。在Swift中袍冷,函數(shù)是"一等公民"磷醋;函數(shù)可以作為類型來(lái)使用,也就是說(shuō) 函數(shù)可以賦值給一個(gè)變量或常量胡诗,可以將函數(shù)作為函數(shù)的參數(shù)或者返回值邓线,還可以使用高階函數(shù)。
func 函數(shù)名([參數(shù)1:類型煌恢,參數(shù)2:類型骇陈,...])[throws][rethrows][->返回類型]{
函數(shù)的執(zhí)行體
[return 表達(dá)式]
}
- 外部參數(shù)名
函數(shù)名(外部參數(shù)名 內(nèi)部參數(shù)名: 類型, 外部參數(shù)名 內(nèi)部參數(shù)名: 類型)
如果不寫外部參數(shù)名那么內(nèi)部參數(shù)名也是外部參數(shù)名,可以使用_來(lái)作為外部參數(shù)名表示省略外部參數(shù)名
func myMin(a x: Int, b y: Int) -> Int {
return x < y ? x : y
}
func myMin( x: Int, _ y: Int) -> Int {
return x < y ? x : y
}
- inout參數(shù)
inout - 輸入輸出參數(shù)(不僅將數(shù)據(jù)傳入函數(shù)還要從函數(shù)中取出數(shù)據(jù)),inout類型的參數(shù)前要加上&符號(hào)
func createX(inout x: Int) {
x = 1000
}
var x = 1
createX(&x)
print(x)
- 可變參數(shù)列表
Swift中函數(shù)的參數(shù)列表可以是可變參數(shù)列表(參數(shù)的個(gè)數(shù)是任意多個(gè))
func sum(nums: Int...) -> Int {
var total = 0
for num in nums {
total += num
}
return total
}
閉包就是沒有名字的函數(shù)或者稱之為函數(shù)表達(dá)式(Lambda表達(dá)式),Objective-C與之對(duì)應(yīng)的概念叫block.如果一個(gè)函數(shù)的參數(shù)類型是函數(shù)我們可以傳入一個(gè)閉包瑰抵;如果一個(gè)函數(shù)的返回類型是函數(shù)我們可以返回一個(gè)閉包你雌;如果一個(gè)類的某種屬性是函數(shù)我們也可以將一個(gè)閉包表達(dá)式賦值給它。
{ ([參數(shù)列表]) [->返回類型] in 代碼 }
面向?qū)ο缶幊?OOP)
基本概念
對(duì)象:接收消息的單元二汛。
類:對(duì)象的藍(lán)圖和模板婿崭,類是一個(gè)抽象概念。
消息:對(duì)象之間通信的方式肴颊,通過給對(duì)象發(fā)消息可以讓對(duì)象執(zhí)行對(duì)應(yīng)的操作來(lái)解決問題
四大支柱
抽象:定義類的過程就是一個(gè)抽象的過程氓栈,需要做數(shù)據(jù)抽象和行為抽象,數(shù)據(jù)抽象找到對(duì)象的屬性(保存對(duì)象狀態(tài)的存儲(chǔ)屬性),行為抽象找到對(duì)象的方法(可以給對(duì)象發(fā)的消息)婿着。
封裝:
觀點(diǎn)一:我們?cè)陬愔袑懙姆椒ㄆ鋵?shí)就是在封裝API授瘦,方法的內(nèi)部實(shí)現(xiàn)可能會(huì)很復(fù)雜,但是這些對(duì)調(diào)用者來(lái)說(shuō)是不可見的竟宋,調(diào)用只能看到方法有一個(gè)簡(jiǎn)單清晰的接口提完,這就是封裝。
觀點(diǎn)二:將對(duì)象的屬性和操作這些屬性的方法綁定在一起丘侠。
觀點(diǎn)三:隱藏一切可以隱藏的實(shí)現(xiàn)細(xì)節(jié)徒欣,只提供簡(jiǎn)單清晰的編程口(界面)。繼承:從已有的類創(chuàng)建新類的過程蜗字,提供繼承信息的稱為父類(超類/基類帚称,得到繼承信息的稱為子類(派生類/衍生類)官研。通常子類除了得到父類的繼承信息還會(huì)增加一些自己特有的東西秽澳,所以子類的能力一定比父類更強(qiáng)大闯睹。繼承的意義在于子類可以復(fù)用父類的代碼并且增強(qiáng)系統(tǒng)現(xiàn)有的功能。
多態(tài):同樣的引用調(diào)用相同的方法但是做了不同的事情担神。
三個(gè)步驟
1.定義類
- 數(shù)據(jù)抽象
- 存儲(chǔ)屬性:類自身具有的屬性(找名詞)
- 行為抽象
- 方法:寫到類里面的函數(shù)或者說(shuō)跟對(duì)象綁定的行為就是方法
- 對(duì)象方法:給對(duì)象發(fā)的消息
- 類方法:給類發(fā)的消息楼吃,與對(duì)象的狀態(tài)無(wú)關(guān)的方法
- 計(jì)算屬性:通過對(duì)存儲(chǔ)屬性做運(yùn)算得到的屬性,通常獲得某個(gè)計(jì)算出的值的方法都可以設(shè)計(jì)成計(jì)算屬性。
- 方法:寫到類里面的函數(shù)或者說(shuō)跟對(duì)象綁定的行為就是方法
- 構(gòu)造器
- 指派構(gòu)造器:被其他初始化方法調(diào)用的初始化方法妄讯。
- 便利構(gòu)造器(convenience):調(diào)用了其他的初始化方法的初始化方法孩锡。
- 必要構(gòu)造器(required):必要構(gòu)造器意味著子類也要提供一模一樣的構(gòu)造器.
2.創(chuàng)建對(duì)象
3.給對(duì)象發(fā)消息
class Triangle {
var a: Double
var b: Double
var c: Double
init(a: Double, b: Double, c: Double) {
self.a = a
self.b = b
self.c = c
}
// 類方法(發(fā)給類的消息與對(duì)象狀態(tài)無(wú)關(guān))
// 此處的static也可以換成class作用相同
static func isValid(a: Double, _ b: Double, _ c: Double) -> Bool {
return a + b > c && b + c > a && c + a > b
}
// 對(duì)象方法(發(fā)給對(duì)象的消息與對(duì)象狀態(tài)有關(guān))
func perimeter() -> Double {
return a + b + c
}
}
let a = 1.0
let b = 2.0
let c = 3.0
// 在創(chuàng)建對(duì)象前先調(diào)用類方法判定給定的三條邊能否構(gòu)成三角形
// 類方法是發(fā)給類的消息所以不用創(chuàng)建對(duì)象直接通過類名調(diào)用
if Triangle.isValid(a, b, c) {
let t = Triangle(a: a, b: b, c: c)
// 對(duì)象方法是發(fā)給對(duì)象的消息要先創(chuàng)建對(duì)象才能調(diào)用
print(t.perimeter())
}
else {
print("無(wú)法創(chuàng)建三角形")
}
相關(guān)內(nèi)容
- 枚舉:枚舉是定義符號(hào)常量的最佳方式, 符號(hào)常量總是優(yōu)于字面常量。
- 結(jié)構(gòu)(體)
總結(jié):類和結(jié)構(gòu)的區(qū)別亥贸,什么時(shí)候使用類躬窜,什么時(shí)候使用結(jié)構(gòu)
區(qū)別1: 結(jié)構(gòu)的對(duì)象是值類型, 類的對(duì)象是引用類型,值類型在賦值的時(shí)候會(huì)在內(nèi)存中進(jìn)行對(duì)象的拷貝,引用類型在賦值的時(shí)候不會(huì)進(jìn)行對(duì)象拷貝只是增加了一個(gè)引用.
區(qū)別2: 結(jié)構(gòu)會(huì)自動(dòng)生成初始化方法
區(qū)別3: 結(jié)構(gòu)中的方法在默認(rèn)情況下是不允許修改結(jié)構(gòu)中的屬性除非加上mutating關(guān)鍵字.
結(jié)論: 我們自定義新類型時(shí)優(yōu)先考慮使用類而不是結(jié)構(gòu)除非我們要定義的是一種底層的數(shù)據(jù)結(jié)構(gòu)(保存其他數(shù)據(jù)的類型)
- 擴(kuò)展(extension)
如果在某個(gè)特定的應(yīng)用場(chǎng)景中你發(fā)現(xiàn)現(xiàn)有的類缺少了某項(xiàng)功能,那么可以通過類擴(kuò)展(extension)的方式現(xiàn)場(chǎng)添加這項(xiàng)功能
- 運(yùn)算符重載:為自定義的類型定義運(yùn)算符
class Student{
var name:String
init(name:String){
self.name = name
}
}
func <(one: Student, two: Student) -> Bool {
return one.name < two.name
}
下標(biāo)運(yùn)算(subscript)
-
訪問修飾符
- private(私有的):存儲(chǔ)屬性通常是private的,因?yàn)閿?shù)據(jù)要保護(hù)起來(lái)炕置。
- internal(內(nèi)部的):如果自定義的類沒有打算在其他項(xiàng)目中使用荣挨,可以不寫訪問修飾符。直接使用默認(rèn)的internal修飾符表示在本項(xiàng)目中公開對(duì)其他項(xiàng)目私有朴摊。
- public(公共的):方法一般是public的默垄,因?yàn)榉椒ㄊ菍?duì)象接受的消息。
面向協(xié)議編程(POP)
協(xié)議
protocal 協(xié)議名[父協(xié)議1甚纲,父協(xié)議二...]{
//方法的集合(計(jì)算屬性相當(dāng)于就是方法)
}
協(xié)議是方法的集合(計(jì)算屬性相當(dāng)于就是方法),可以把看似不相關(guān)的對(duì)象的公共行為放到一個(gè)協(xié)議中口锭。協(xié)議在Swift開發(fā)中大致有三種作用:
1.能力:遵循了協(xié)議就意味著具備了某種能力
2.約定:遵循了協(xié)議就一定要實(shí)現(xiàn)協(xié)議中的方法
3.角色:一個(gè)類可以遵循多個(gè)協(xié)議, 一個(gè)協(xié)議可以被多個(gè)類遵循, 遵循協(xié)議就意味著扮演了某種角色, 遵循多個(gè)協(xié)議就意味著可以扮演多種角色
依賴倒轉(zhuǎn)原則
用協(xié)議實(shí)現(xiàn)委托回調(diào)
一個(gè)對(duì)象想做某件事情但是自身沒有能力做這件事就可以使用委托回調(diào),具體步驟是:
1.設(shè)計(jì)協(xié)議介杆,讓被委托方遵循協(xié)議并實(shí)現(xiàn)協(xié)議中的方法
2.委托方有一個(gè)屬性是協(xié)議類型的鹃操,通過該屬性可以調(diào)用協(xié)議中的方法
注意:委托方的協(xié)議類型的屬性通常是可空類型,要寫成弱引用(weak)
其他
- 協(xié)議組合:protocal<協(xié)議1春哨,協(xié)議2,...>
- 可選方法
- 協(xié)議擴(kuò)展:對(duì)協(xié)議中的方法給出默認(rèn)實(shí)現(xiàn)荆隘,可以設(shè)計(jì)出更通用的代碼
泛型
讓類型不再是程序中的硬代碼
- 泛型函數(shù)
- 泛型類/結(jié)構(gòu)/枚舉
定義一個(gè)虛擬類型T, 調(diào)用函數(shù)時(shí)根據(jù)傳入的參數(shù)類型來(lái)決定T到底是什么
func mySwap<T>(inout a: T, inout _ b: T) {
let temp = a
a = b
b = temp
}
注意:Swift中的類、結(jié)構(gòu)和枚舉都可以使用泛型.
相關(guān)知識(shí)
- 泛型限定:<T: Comparable>限定T類型必須是遵循了Comparable協(xié)議的類型
- where字句
錯(cuò)誤處理
enum MyError:ErrorType{
case A
case B
case C
}
- throw:
- throws/rethrows:
- do
- catch
- try:
邊角知識(shí)
- ARC
- 正則表達(dá)式
- 嵌套類型