Go語(yǔ)言基本語(yǔ)法 (下)

      網(wǎng)友投稿 910 2022-05-30

      Golang 中文文檔地址:https://studygolang.com/pkgdoc

      Go語(yǔ)言基本語(yǔ)法 (中)

      1. fmt 標(biāo)準(zhǔn)庫(kù)介紹

      fmt 包實(shí)現(xiàn)了類似C語(yǔ)言printf和scanf的格式化I/O。主要分為向外輸出內(nèi)容和獲取輸入內(nèi)容兩大部分。

      1.1 向外輸出

      標(biāo)準(zhǔn)庫(kù)fmt提供了以下幾種輸出相關(guān)函數(shù)。

      Print系列函數(shù)會(huì)將內(nèi)容輸出到系統(tǒng)的標(biāo)準(zhǔn)輸出,區(qū)別在于Print函數(shù)直接輸出內(nèi)容,Printf函數(shù)支持格式化輸出字符串,Println函數(shù)會(huì)在輸出內(nèi)容的結(jié)尾添加一個(gè)換行符。

      func Print(a ...interface{}) (n int, err error) func Printf(format string, a ...interface{}) (n int, err error) func Println(a ...interface{}) (n int, err error)

      1

      2

      3

      舉個(gè)簡(jiǎn)單的例子:

      func main() { fmt.Print("在終端打印該信息。") name := "沙河小王子" fmt.Printf("我是:%s\n", name) fmt.Println("在終端打印單獨(dú)一行顯示") }

      1

      2

      3

      4

      5

      6

      執(zhí)行上面的代碼輸出:

      在終端打印該信息。我是:沙河小王子 在終端打印單獨(dú)一行顯示

      1

      2

      Fprint系列函數(shù)會(huì)將內(nèi)容輸出到一個(gè)io.Writer接口類型的變量w中,我們通常用這個(gè)函數(shù)往文件中寫(xiě)入內(nèi)容。

      func Fprint(w io.Writer, a ...interface{}) (n int, err error) func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) func Fprintln(w io.Writer, a ...interface{}) (n int, err error)

      1

      2

      3

      舉個(gè)例子:

      // 向標(biāo)準(zhǔn)輸出寫(xiě)入內(nèi)容 fmt.Fprintln(os.Stdout, "向標(biāo)準(zhǔn)輸出寫(xiě)入內(nèi)容") fileObj, err := os.OpenFile("./xx.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { fmt.Println("打開(kāi)文件出錯(cuò),err:", err) return } name := "沙河小王子" // 向打開(kāi)的文件句柄中寫(xiě)入內(nèi)容 fmt.Fprintf(fileObj, "往文件中寫(xiě)如信息:%s", name)

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      注意,只要滿足io.Writer接口的類型都支持寫(xiě)入。

      Sprint系列函數(shù)會(huì)把傳入的數(shù)據(jù)生成并返回一個(gè)字符串。

      func Sprint(a ...interface{}) string func Sprintf(format string, a ...interface{}) string func Sprintln(a ...interface{}) string

      1

      2

      3

      簡(jiǎn)單的示例代碼如下:

      s1 := fmt.Sprint("沙河小王子") name := "沙河小王子" age := 18 s2 := fmt.Sprintf("name:%s,age:%d", name, age) fmt.Println(s2) s3 := fmt.Sprintln("沙河小王子") fmt.Println(s1, s2, s3)

      1

      2

      3

      4

      5

      6

      7

      Errorf函數(shù)根據(jù)format參數(shù)生成格式化字符串并返回一個(gè)包含該字符串的錯(cuò)誤。

      func Errorf(format string, a ...interface{}) error

      1

      通常使用這種方式來(lái)自定義錯(cuò)誤類型,例如:

      err := fmt.Errorf("這是一個(gè)錯(cuò)誤")

      1

      Go1.13版本為fmt.Errorf函數(shù)新加了一個(gè)%w占位符用來(lái)生成一個(gè)可以包裹Error的Wrapping Error。

      e := errors.New("原始錯(cuò)誤e") w := fmt.Errorf("Wrap了一個(gè)錯(cuò)誤%w", e)

      1

      2

      1.2 格式化占位符

      *printf系列函數(shù)都支持format格式化參數(shù),在這里我們按照占位符將被替換的變量類型劃分,方便查詢和記憶。

      示例代碼如下:

      fmt.Printf("%v\n", 100) fmt.Printf("%v\n", false) o := struct{ name string }{"小王子"} fmt.Printf("%v\n", o) fmt.Printf("%#v\n", o) fmt.Printf("%T\n", o) fmt.Printf("100%%\n")

      1

      2

      3

      4

      5

      6

      7

      輸出結(jié)果如下:

      100 false {小王子} struct { name string }{name:"小王子"} struct { name string } 100%

      1

      2

      3

      4

      5

      6

      示例代碼如下:

      n := 65 fmt.Printf("%b\n", n) fmt.Printf("%c\n", n) fmt.Printf("%d\n", n) fmt.Printf("%o\n", n) fmt.Printf("%x\n", n) fmt.Printf("%X\n", n)

      1

      2

      3

      4

      5

      6

      7

      輸出結(jié)果如下:

      1000001 A 65 101 41 41

      1

      2

      3

      4

      5

      6

      示例代碼如下:

      f := 12.34 fmt.Printf("%b\n", f) fmt.Printf("%e\n", f) fmt.Printf("%E\n", f) fmt.Printf("%f\n", f) fmt.Printf("%g\n", f) fmt.Printf("%G\n", f)

      1

      2

      3

      4

      5

      6

      7

      輸出結(jié)果如下:

      6946802425218990p-49 1.234000e+01 1.234000E+01 12.340000 12.34 12.34

      1

      2

      3

      4

      5

      6

      示例代碼如下:

      s := "小王子" fmt.Printf("%s\n", s) fmt.Printf("%q\n", s) fmt.Printf("%x\n", s) fmt.Printf("%X\n", s)

      1

      2

      3

      4

      5

      輸出結(jié)果如下:

      小王子 "小王子" e5b08fe78e8be5ad90 E5B08FE78E8BE5AD90

      1

      2

      3

      4

      示例代碼如下:

      a := 10 fmt.Printf("%p\n", &a) fmt.Printf("%#p\n", &a)

      1

      2

      3

      輸出結(jié)果如下:

      0xc000094000 c000094000

      1

      2

      寬度通過(guò)一個(gè)緊跟在百分號(hào)后面的十進(jìn)制數(shù)指定,如果未指定寬度,則表示值時(shí)除必需之外不作填充。精度通過(guò)(可選的)寬度后跟點(diǎn)號(hào)后跟的十進(jìn)制數(shù)指定。如果未指定精度,會(huì)使用默認(rèn)精度;如果點(diǎn)號(hào)后沒(méi)有跟數(shù)字,表示精度為0。舉例如下:

      示例代碼如下:

      n := 12.34 fmt.Printf("%f\n", n) fmt.Printf("%9f\n", n) fmt.Printf("%.2f\n", n) fmt.Printf("%9.2f\n", n) fmt.Printf("%9.f\n", n)

      1

      2

      3

      4

      5

      6

      輸出結(jié)果如下:

      12.340000 12.340000 12.34 12.34 12

      1

      2

      3

      4

      5

      舉個(gè)例子:

      s := "小王子" fmt.Printf("%s\n", s) fmt.Printf("%5s\n", s) fmt.Printf("%-5s\n", s) fmt.Printf("%5.7s\n", s) fmt.Printf("%-5.7s\n", s) fmt.Printf("%5.2s\n", s) fmt.Printf("%05s\n", s)

      1

      2

      3

      4

      5

      6

      7

      8

      輸出結(jié)果如下:

      小王子 小王子 小王子 小王子 小王子 小王 00小王子

      1

      2

      3

      4

      5

      6

      7

      1.3 獲取輸入

      Go語(yǔ)言fmt包下有fmt.Scan、fmt.Scanf、fmt.Scanln三個(gè)函數(shù),可以在程序運(yùn)行過(guò)程中從標(biāo)準(zhǔn)輸入獲取用戶的輸入。

      函數(shù)定簽名如下:

      func Scan(a ...interface{}) (n int, err error)

      1

      Scan從標(biāo)準(zhǔn)輸入掃描文本,讀取由空白符分隔的值保存到傳遞給本函數(shù)的參數(shù)中,換行符視為空白符。

      本函數(shù)返回成功掃描的數(shù)據(jù)個(gè)數(shù)和遇到的任何錯(cuò)誤。如果讀取的數(shù)據(jù)個(gè)數(shù)比提供的參數(shù)少,會(huì)返回一個(gè)錯(cuò)誤報(bào)告原因。

      具體代碼示例如下:

      func main() { var ( name string age int married bool ) fmt.Scan(&name, &age, &married) fmt.Printf("掃描結(jié)果 name:%s age:%d married:%t \n", name, age, married) }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      將上面的代碼編譯后在終端執(zhí)行,在終端依次輸入小王子、28和false使用空格分隔。

      $ ./scan_demo 小王子 28 false 掃描結(jié)果 name:小王子 age:28 married:false

      1

      2

      3

      fmt.Scan從標(biāo)準(zhǔn)輸入中掃描用戶輸入的數(shù)據(jù),將以空白符分隔的數(shù)據(jù)分別存入指定的參數(shù)。

      函數(shù)簽名如下:

      func Scanf(format string, a ...interface{}) (n int, err error)

      1

      Scanf從標(biāo)準(zhǔn)輸入掃描文本,根據(jù)format參數(shù)指定的格式去讀取由空白符分隔的值保存到傳遞給本函數(shù)的參數(shù)中。

      本函數(shù)返回成功掃描的數(shù)據(jù)個(gè)數(shù)和遇到的任何錯(cuò)誤。

      代碼示例如下:

      func main() { var ( name string age int married bool ) fmt.Scanf("1:%s 2:%d 3:%t", &name, &age, &married) fmt.Printf("掃描結(jié)果 name:%s age:%d married:%t \n", name, age, married) }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      將上面的代碼編譯后在終端執(zhí)行,在終端按照指定的格式依次輸入小王子、28和false。

      $ ./scan_demo 1:小王子 2:28 3:false 掃描結(jié)果 name:小王子 age:28 married:false

      1

      2

      3

      fmt.Scanf不同于fmt.Scan簡(jiǎn)單的以空格作為輸入數(shù)據(jù)的分隔符,fmt.Scanf為輸入數(shù)據(jù)指定了具體的輸入內(nèi)容格式,只有按照格式輸入數(shù)據(jù)才會(huì)被掃描并存入對(duì)應(yīng)變量。

      例如,我們還是按照上個(gè)示例中以空格分隔的方式輸入,fmt.Scanf就不能正確掃描到輸入的數(shù)據(jù)。

      $ ./scan_demo 小王子 28 false 掃描結(jié)果 name: age:0 married:false

      1

      2

      3

      函數(shù)簽名如下:

      func Scanln(a ...interface{}) (n int, err error)

      1

      Scanln類似Scan,它在遇到換行時(shí)才停止掃描。最后一個(gè)數(shù)據(jù)后面必須有換行或者到達(dá)結(jié)束位置。

      本函數(shù)返回成功掃描的數(shù)據(jù)個(gè)數(shù)和遇到的任何錯(cuò)誤。

      具體代碼示例如下:

      func main() { var ( name string age int married bool ) fmt.Scanln(&name, &age, &married) fmt.Printf("掃描結(jié)果 name:%s age:%d married:%t \n", name, age, married) }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      將上面的代碼編譯后在終端執(zhí)行,在終端依次輸入小王子、28和false使用空格分隔。

      $ ./scan_demo 小王子 28 false 掃描結(jié)果 name:小王子 age:28 married:false

      1

      2

      3

      fmt.Scanln遇到回車就結(jié)束掃描了,這個(gè)比較常用。

      有時(shí)候我們想完整獲取輸入的內(nèi)容,而輸入的內(nèi)容可能包含空格,這種情況下可以使用bufio包來(lái)實(shí)現(xiàn)。示例代碼如下:

      func bufioDemo() { reader := bufio.NewReader(os.Stdin) // 從標(biāo)準(zhǔn)輸入生成讀對(duì)象 fmt.Print("請(qǐng)輸入內(nèi)容:") text, _ := reader.ReadString('\n') // 讀到換行 text = strings.TrimSpace(text) fmt.Printf("%#v\n", text) }

      1

      2

      3

      4

      5

      6

      7

      這幾個(gè)函數(shù)功能分別類似于fmt.Scan、fmt.Scanf、fmt.Scanln三個(gè)函數(shù),只不過(guò)它們不是從標(biāo)準(zhǔn)輸入中讀取數(shù)據(jù)而是從io.Reader中讀取數(shù)據(jù)。

      func Fscan(r io.Reader, a ...interface{}) (n int, err error) func Fscanln(r io.Reader, a ...interface{}) (n int, err error) func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)

      1

      2

      3

      這幾個(gè)函數(shù)功能分別類似于fmt.Scan、fmt.Scanf、fmt.Scanln三個(gè)函數(shù),只不過(guò)它們不是從標(biāo)準(zhǔn)輸入中讀取數(shù)據(jù)而是從指定字符串中讀取數(shù)據(jù)。

      func Sscan(str string, a ...interface{}) (n int, err error) func Sscanln(str string, a ...interface{}) (n int, err error) func Sscanf(str string, format string, a ...interface{}) (n int, err error)

      1

      2

      3

      2. time 包

      time包提供了時(shí)間的顯示和測(cè)量用的函數(shù)。日歷的計(jì)算采用的是公歷。

      2.1 時(shí)間類型

      time.Time類型表示時(shí)間。我們可以通過(guò)time.Now()函數(shù)獲取當(dāng)前的時(shí)間對(duì)象,然后獲取時(shí)間對(duì)象的年月日時(shí)分秒等信息。示例代碼如下:

      func timeDemo() { now := time.Now() //獲取當(dāng)前時(shí)間 fmt.Printf("current time:%v\n", now) year := now.Year() //年 month := now.Month() //月 day := now.Day() //日 hour := now.Hour() //小時(shí) minute := now.Minute() //分鐘 second := now.Second() //秒 fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second) }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      2.2 時(shí)間戳

      時(shí)間戳是自1970年1月1日(08:00:00GMT)至當(dāng)前時(shí)間的總毫秒數(shù)。它也被稱為Unix時(shí)間戳(UnixTimestamp)。

      基于時(shí)間對(duì)象獲取時(shí)間戳的示例代碼如下:

      func timestampDemo() { now := time.Now() //獲取當(dāng)前時(shí)間 timestamp1 := now.Unix() //時(shí)間戳 timestamp2 := now.UnixNano() //納秒時(shí)間戳 fmt.Printf("current timestamp1:%v\n", timestamp1) fmt.Printf("current timestamp2:%v\n", timestamp2) }

      1

      2

      3

      4

      5

      6

      7

      使用time.Unix()函數(shù)可以將時(shí)間戳轉(zhuǎn)為時(shí)間格式。

      func timestampDemo2(timestamp int64) { timeObj := time.Unix(timestamp, 0) //將時(shí)間戳轉(zhuǎn)為時(shí)間格式 fmt.Println(timeObj) year := timeObj.Year() //年 month := timeObj.Month() //月 day := timeObj.Day() //日 hour := timeObj.Hour() //小時(shí) minute := timeObj.Minute() //分鐘 second := timeObj.Second() //秒 fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second) }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      2.3 時(shí)間間隔

      time.Duration是time包定義的一個(gè)類型,它代表兩個(gè)時(shí)間點(diǎn)之間經(jīng)過(guò)的時(shí)間,以納秒為單位。time.Duration表示一段時(shí)間間隔,可表示的最長(zhǎng)時(shí)間段大約290年。

      time包中定義的時(shí)間間隔類型的常量如下:

      const ( Nanosecond Duration = 1 Microsecond = 1000 * Nanosecond Millisecond = 1000 * Microsecond Second = 1000 * Millisecond Minute = 60 * Second Hour = 60 * Minute )

      1

      2

      3

      4

      5

      6

      7

      8

      例如:time.Duration表示1納秒,time.Second表示1秒。

      2.4 時(shí)間操作

      我們?cè)谌粘5木幋a過(guò)程中可能會(huì)遇到要求時(shí)間+時(shí)間間隔的需求,Go語(yǔ)言的時(shí)間對(duì)象有提供Add方法如下:

      func (t Time) Add(d Duration) Time

      1

      舉個(gè)例子,求一個(gè)小時(shí)之后的時(shí)間:

      func main() { now := time.Now() later := now.Add(time.Hour) // 當(dāng)前時(shí)間加1小時(shí)后的時(shí)間 fmt.Println(later) }

      1

      2

      3

      4

      5

      求兩個(gè)時(shí)間之間的差值:

      func (t Time) Sub(u Time) Duration

      1

      返回一個(gè)時(shí)間段t-u。如果結(jié)果超出了Duration可以表示的最大值/最小值,將返回最大值/最小值。要獲取時(shí)間點(diǎn)t-d(d為Duration),可以使用t.Add(-d)。

      func (t Time) Equal(u Time) bool

      1

      判斷兩個(gè)時(shí)間是否相同,會(huì)考慮時(shí)區(qū)的影響,因此不同時(shí)區(qū)標(biāo)準(zhǔn)的時(shí)間也可以正確比較。本方法和用t==u不同,這種方法還會(huì)比較地點(diǎn)和時(shí)區(qū)信息。

      func (t Time) Before(u Time) bool

      1

      如果t代表的時(shí)間點(diǎn)在u之前,返回真;否則返回假。

      func (t Time) After(u Time) bool

      1

      如果t代表的時(shí)間點(diǎn)在u之后,返回真;否則返回假。

      2.5 定時(shí)器

      使用time.Tick(時(shí)間間隔)來(lái)設(shè)置定時(shí)器,定時(shí)器的本質(zhì)上是一個(gè)通道(channel)。

      func tickDemo() { ticker := time.Tick(time.Second) //定義一個(gè)1秒間隔的定時(shí)器 for i := range ticker { fmt.Println(i)//每秒都會(huì)執(zhí)行的任務(wù) } }

      1

      2

      3

      4

      5

      6

      2.6 時(shí)間格式化

      時(shí)間類型有一個(gè)自帶的方法Format進(jìn)行格式化,需要注意的是Go語(yǔ)言中格式化時(shí)間模板不是常見(jiàn)的Y-m-d H:M:S而是使用Go的誕生時(shí)間2006年1月2號(hào)15點(diǎn)04分(記憶口訣為2006 1 2 3 4)。也許這就是技術(shù)人員的浪漫吧。

      補(bǔ)充:如果想格式化為12小時(shí)方式,需指定PM。

      func formatDemo() { now := time.Now() // 格式化的模板為Go的出生時(shí)間2006年1月2號(hào)15點(diǎn)04分 Mon Jan // 24小時(shí)制 fmt.Println(now.Format("2006-01-02 15:04:05.000 Mon Jan")) // 12小時(shí)制 fmt.Println(now.Format("2006-01-02 03:04:05.000 PM Mon Jan")) fmt.Println(now.Format("2006/01/02 15:04")) fmt.Println(now.Format("15:04 2006/01/02")) fmt.Println(now.Format("2006/01/02")) }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      now := time.Now() fmt.Println(now) // 加載時(shí)區(qū) loc, err := time.LoadLocation("Asia/Shanghai") if err != nil { fmt.Println(err) return } // 按照指定時(shí)區(qū)和指定格式解析字符串時(shí)間 timeObj, err := time.ParseInLocation("2006/01/02 15:04:05", "2019/08/04 14:15:20", loc) if err != nil { fmt.Println(err) return } fmt.Println(timeObj) fmt.Println(timeObj.Sub(now))

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      3. Go 語(yǔ)言中的結(jié)構(gòu)體

      3.1 類型別名和自定義類型

      在Go語(yǔ)言中有一些基本的數(shù)據(jù)類型,如string、整型、浮點(diǎn)型、布爾等數(shù)據(jù)類型, Go語(yǔ)言中可以使用type關(guān)鍵字來(lái)定義自定義類型。

      自定義類型是定義了一個(gè)全新的類型。我們可以基于內(nèi)置的基本類型定義,也可以通過(guò)struct定義。例如:

      //將MyInt定義為int類型 type MyInt int

      1

      2

      通過(guò)type關(guān)鍵字的定義,MyInt就是一種新的類型,它具有int的特性。

      類型別名是Go1.9版本添加的新功能。

      類型別名規(guī)定:TypeAlias只是Type的別名,本質(zhì)上TypeAlias與Type是同一個(gè)類型。就像一個(gè)孩子小時(shí)候有小名、乳名,上學(xué)后用學(xué)名,英語(yǔ)老師又會(huì)給他起英文名,但這些名字都指的是他本人。

      type TypeAlias = Type

      1

      我們之前見(jiàn)過(guò)的rune和byte就是類型別名,他們的定義如下:

      type byte = uint8 type rune = int32

      1

      2

      類型別名與類型定義表面上看只有一個(gè)等號(hào)的差異,我們通過(guò)下面的這段代碼來(lái)理解它們之間的區(qū)別。

      //類型定義 type NewInt int //類型別名 type MyInt = int func main() { var a NewInt var b MyInt fmt.Printf("type of a:%T\n", a) //type of a:main.NewInt fmt.Printf("type of b:%T\n", b) //type of b:int }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      結(jié)果顯示a的類型是main.NewInt,表示main包下定義的NewInt類型。b的類型是int。MyInt類型只會(huì)在代碼中存在,編譯完成時(shí)并不會(huì)有MyInt類型。

      3.2 結(jié)構(gòu)體

      Go語(yǔ)言中的基礎(chǔ)數(shù)據(jù)類型可以表示一些事物的基本屬性,但是當(dāng)我們想表達(dá)一個(gè)事物的全部或部分屬性時(shí),這時(shí)候再用單一的基本數(shù)據(jù)類型明顯就無(wú)法滿足需求了,Go語(yǔ)言提供了一種自定義數(shù)據(jù)類型,可以封裝多個(gè)基本數(shù)據(jù)類型,這種數(shù)據(jù)類型叫結(jié)構(gòu)體,英文名稱struct。 也就是我們可以通過(guò)struct來(lái)定義自己的類型了。

      Go語(yǔ)言中通過(guò)struct來(lái)實(shí)現(xiàn)面向?qū)ο蟆?/p>

      使用type和struct關(guān)鍵字來(lái)定義結(jié)構(gòu)體,具體代碼格式如下:

      type 類型名 struct { 字段名 字段類型 字段名 字段類型 … }

      1

      2

      3

      4

      5

      其中:

      類型名:標(biāo)識(shí)自定義結(jié)構(gòu)體的名稱,在同一個(gè)包內(nèi)不能重復(fù)。

      字段名:表示結(jié)構(gòu)體字段名。結(jié)構(gòu)體中的字段名必須唯一。

      字段類型:表示結(jié)構(gòu)體字段的具體類型。

      舉個(gè)例子,我們定義一個(gè)Person(人)結(jié)構(gòu)體,代碼如下:

      type person struct { name string city string age int8 }

      1

      2

      3

      4

      5

      同樣類型的字段也可以寫(xiě)在一行,

      type person1 struct { name, city string age int8 }

      1

      2

      3

      4

      這樣我們就擁有了一個(gè)person的自定義類型,它有name、city、age三個(gè)字段,分別表示姓名、城市和年齡。這樣我們使用這個(gè)person結(jié)構(gòu)體就能夠很方便的在程序中表示和存儲(chǔ)人信息了。

      語(yǔ)言內(nèi)置的基礎(chǔ)數(shù)據(jù)類型是用來(lái)描述一個(gè)值的,而結(jié)構(gòu)體是用來(lái)描述一組值的。比如一個(gè)人有名字、年齡和居住城市等,本質(zhì)上是一種聚合型的數(shù)據(jù)類型

      只有當(dāng)結(jié)構(gòu)體實(shí)例化時(shí),才會(huì)真正地分配內(nèi)存。也就是必須實(shí)例化后才能使用結(jié)構(gòu)體的字段。

      結(jié)構(gòu)體本身也是一種類型,我們可以像聲明內(nèi)置類型一樣使用var關(guān)鍵字聲明結(jié)構(gòu)體類型。

      var 結(jié)構(gòu)體實(shí)例 結(jié)構(gòu)體類型

      1

      舉個(gè)例子:

      type person struct { name string city string age int8 } func main() { var p1 person p1.name = "沙河娜扎" p1.city = "北京" p1.age = 18 fmt.Printf("p1=%v\n", p1) //p1={沙河娜扎 北京 18} fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"沙河娜扎", city:"北京", age:18} }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      我們通過(guò).來(lái)訪問(wèn)結(jié)構(gòu)體的字段(成員變量),例如p1.name和p1.age等。

      在定義一些臨時(shí)數(shù)據(jù)結(jié)構(gòu)等場(chǎng)景下還可以使用匿名結(jié)構(gòu)體。

      package main import ( "fmt" ) func main() { var user struct{Name string; Age int} user.Name = "小王子" user.Age = 18 fmt.Printf("%#v\n", user) }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      我們還可以通過(guò)使用new關(guān)鍵字對(duì)結(jié)構(gòu)體進(jìn)行實(shí)例化,得到的是結(jié)構(gòu)體的地址。 格式如下:

      var p2 = new(person) fmt.Printf("%T\n", p2) //*main.person fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}

      1

      2

      3

      從打印的結(jié)果中我們可以看出p2是一個(gè)結(jié)構(gòu)體指針。

      需要注意的是在Go語(yǔ)言中支持對(duì)結(jié)構(gòu)體指針直接使用.來(lái)訪問(wèn)結(jié)構(gòu)體的成員。

      var p2 = new(person) p2.name = "小王子" p2.age = 28 p2.city = "上海" fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"小王子", city:"上海", age:28}

      1

      2

      3

      4

      5

      使用&對(duì)結(jié)構(gòu)體進(jìn)行取地址操作相當(dāng)于對(duì)該結(jié)構(gòu)體類型進(jìn)行了一次new實(shí)例化操作。

      p3 := &person{} fmt.Printf("%T\n", p3) //*main.person fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"", city:"", age:0} p3.name = "七米" p3.age = 30 p3.city = "成都" fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"七米", city:"成都", age:30}

      1

      2

      3

      4

      5

      6

      7

      p3.name = "七米"其實(shí)在底層是(*p3).name = "七米",這是Go語(yǔ)言幫我們實(shí)現(xiàn)的語(yǔ)法糖。

      沒(méi)有初始化的結(jié)構(gòu)體,其成員變量都是對(duì)應(yīng)其類型的零值。

      type person struct { name string city string age int8 } func main() { var p4 person fmt.Printf("p4=%#v\n", p4) //p4=main.person{name:"", city:"", age:0} }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      使用鍵值對(duì)對(duì)結(jié)構(gòu)體進(jìn)行初始化時(shí),鍵對(duì)應(yīng)結(jié)構(gòu)體的字段,值對(duì)應(yīng)該字段的初始值。

      p5 := person{ name: "小王子", city: "北京", age: 18, } fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"小王子", city:"北京", age:18}

      1

      2

      3

      4

      5

      6

      也可以對(duì)結(jié)構(gòu)體指針進(jìn)行鍵值對(duì)初始化,例如:

      p6 := &person{ name: "小王子", city: "北京", age: 18, } fmt.Printf("p6=%#v\n", p6) //p6=&main.person{name:"小王子", city:"北京", age:18}

      1

      2

      3

      4

      5

      6

      當(dāng)某些字段沒(méi)有初始值的時(shí)候,該字段可以不寫(xiě)。此時(shí),沒(méi)有指定初始值的字段的值就是該字段類型的零值。

      p7 := &person{ city: "北京", } fmt.Printf("p7=%#v\n", p7) //p7=&main.person{name:"", city:"北京", age:0}

      1

      2

      3

      4

      初始化結(jié)構(gòu)體的時(shí)候可以簡(jiǎn)寫(xiě),也就是初始化的時(shí)候不寫(xiě)鍵,直接寫(xiě)值:

      p8 := &person{ "沙河娜扎", "北京", 28, } fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"沙河娜扎", city:"北京", age:28}

      1

      2

      3

      4

      5

      6

      使用這種格式初始化時(shí),需要注意:

      必須初始化結(jié)構(gòu)體的所有字段。

      初始值的填充順序必須與字段在結(jié)構(gòu)體中的聲明順序一致。

      該方式不能和鍵值初始化方式混用。

      3.3 結(jié)構(gòu)體內(nèi)存布局

      結(jié)構(gòu)體占用一塊連續(xù)的內(nèi)存。

      type test struct { a int8 b int8 c int8 d int8 } n := test{ 1, 2, 3, 4, } fmt.Printf("n.a %p\n", &n.a) fmt.Printf("n.b %p\n", &n.b) fmt.Printf("n.c %p\n", &n.c) fmt.Printf("n.d %p\n", &n.d)

      1

      2

      3

      4

      5

      6

      7

      8

      Go語(yǔ)言基本語(yǔ)法 (下)

      9

      10

      11

      12

      13

      輸出:

      n.a 0xc0000a0060 n.b 0xc0000a0061 n.c 0xc0000a0062 n.d 0xc0000a0063

      1

      2

      3

      4

      【進(jìn)階知識(shí)點(diǎn)】關(guān)于Go語(yǔ)言中的內(nèi)存對(duì)齊推薦閱讀:在 Go 中恰到好處的內(nèi)存對(duì)齊

      空結(jié)構(gòu)體是不占用空間的。

      var v struct{} fmt.Println(unsafe.Sizeof(v)) // 0

      1

      2

      3.4 構(gòu)造函數(shù)

      Go語(yǔ)言的結(jié)構(gòu)體沒(méi)有構(gòu)造函數(shù),我們可以自己實(shí)現(xiàn)。 例如,下方的代碼就實(shí)現(xiàn)了一個(gè)person的構(gòu)造函數(shù)。 因?yàn)閟truct是值類型,如果結(jié)構(gòu)體比較復(fù)雜的話,值拷貝性能開(kāi)銷會(huì)比較大,所以該構(gòu)造函數(shù)返回的是結(jié)構(gòu)體指針類型。

      func newPerson(name, city string, age int8) *person { return &person{ name: name, city: city, age: age, } }

      1

      2

      3

      4

      5

      6

      7

      調(diào)用構(gòu)造函數(shù)

      p9 := newPerson("張三", "沙河", 90) fmt.Printf("%#v\n", p9) //&main.person{name:"張三", city:"沙河", age:90}

      1

      2

      3.5 方法和接收者

      Go語(yǔ)言中的方法(Method)是一種作用于特定類型變量的函數(shù)。這種特定類型變量叫做接收者(Receiver)。接收者的概念就類似于其他語(yǔ)言中的this或者 self。

      方法的定義格式如下:

      func (接收者變量 接收者類型) 方法名(參數(shù)列表) (返回參數(shù)) { 函數(shù)體 }

      1

      2

      3

      其中,

      接收者變量:接收者中的參數(shù)變量名在命名時(shí),官方建議使用接收者類型名稱首字母的小寫(xiě),而不是self、this之類的命名。例如,Person類型的接收者變量應(yīng)該命名為 p,Connector類型的接收者變量應(yīng)該命名為c等。

      接收者類型:接收者類型和參數(shù)類似,可以是指針類型和非指針類型。

      方法名、參數(shù)列表、返回參數(shù):具體格式與函數(shù)定義相同。

      舉個(gè)例子:

      //Person 結(jié)構(gòu)體 type Person struct { name string age int8 } //NewPerson 構(gòu)造函數(shù) func NewPerson(name string, age int8) *Person { return &Person{ name: name, age: age, } } //Dream Person做夢(mèng)的方法 func (p Person) Dream() { fmt.Printf("%s的夢(mèng)想是學(xué)好Go語(yǔ)言!\n", p.name) } func main() { p1 := NewPerson("小王子", 25) p1.Dream() }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      方法與函數(shù)的區(qū)別是,函數(shù)不屬于任何類型,方法屬于特定的類型。

      指針類型的接收者由一個(gè)結(jié)構(gòu)體的指針組成,由于指針的特性,調(diào)用方法時(shí)修改接收者指針的任意成員變量,在方法結(jié)束后,修改都是有效的。這種方式就十分接近于其他語(yǔ)言中面向?qū)ο笾械膖his或者self。 例如我們?yōu)镻erson添加一個(gè)SetAge方法,來(lái)修改實(shí)例變量的年齡。

      // SetAge 設(shè)置p的年齡 // 使用指針接收者 func (p *Person) SetAge(newAge int8) { p.age = newAge }

      1

      2

      3

      4

      5

      調(diào)用該方法:

      func main() { p1 := NewPerson("小王子", 25) fmt.Println(p1.age) // 25 p1.SetAge(30) fmt.Println(p1.age) // 30 }

      1

      2

      3

      4

      5

      6

      當(dāng)方法作用于值類型接收者時(shí),Go語(yǔ)言會(huì)在代碼運(yùn)行時(shí)將接收者的值復(fù)制一份。在值類型接收者的方法中可以獲取接收者的成員值,但修改操作只是針對(duì)副本,無(wú)法修改接收者變量本身。

      // SetAge2 設(shè)置p的年齡 // 使用值接收者 func (p Person) SetAge2(newAge int8) { p.age = newAge } func main() { p1 := NewPerson("小王子", 25) p1.Dream() fmt.Println(p1.age) // 25 p1.SetAge2(30) // (*p1).SetAge2(30) fmt.Println(p1.age) // 25 }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      需要修改接收者中的值

      接收者是拷貝代價(jià)比較大的大對(duì)象

      保證一致性,如果有某個(gè)方法使用了指針接收者,那么其他的方法也應(yīng)該使用指針接收者。

      3.6 任意類型添加方法

      在Go語(yǔ)言中,接收者的類型可以是任何類型,不僅僅是結(jié)構(gòu)體,任何類型都可以擁有方法。 舉個(gè)例子,我們基于內(nèi)置的int類型使用type關(guān)鍵字可以定義新的自定義類型,然后為我們的自定義類型添加方法。

      //MyInt 將int定義為自定義MyInt類型 type MyInt int //SayHello 為MyInt添加一個(gè)SayHello的方法 func (m MyInt) SayHello() { fmt.Println("Hello, 我是一個(gè)int。") } func main() { var m1 MyInt m1.SayHello() //Hello, 我是一個(gè)int。 m1 = 100 fmt.Printf("%#v %T\n", m1, m1) //100 main.MyInt }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      注意事項(xiàng): 非本地類型不能定義方法,也就是說(shuō)我們不能給別的包的類型定義方法。

      3.7 結(jié)構(gòu)體的匿名字段

      結(jié)構(gòu)體允許其成員字段在聲明時(shí)沒(méi)有字段名而只有類型,這種沒(méi)有名字的字段就稱為匿名字段。

      //Person 結(jié)構(gòu)體Person類型 type Person struct { string int } func main() { p1 := Person{ "小王子", 18, } fmt.Printf("%#v\n", p1) //main.Person{string:"北京", int:18} fmt.Println(p1.string, p1.int) //北京 18 }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      **注意:**這里匿名字段的說(shuō)法并不代表沒(méi)有字段名,而是默認(rèn)會(huì)采用類型名作為字段名,結(jié)構(gòu)體要求字段名稱必須唯一,因此一個(gè)結(jié)構(gòu)體中同種類型的匿名字段只能有一個(gè)。

      3.8 嵌套結(jié)構(gòu)體

      一個(gè)結(jié)構(gòu)體中可以嵌套包含另一個(gè)結(jié)構(gòu)體或結(jié)構(gòu)體指針,就像下面的示例代碼那樣。

      //Address 地址結(jié)構(gòu)體 type Address struct { Province string City string } //User 用戶結(jié)構(gòu)體 type User struct { Name string Gender string Address Address } func main() { user1 := User{ Name: "小王子", Gender: "男", Address: Address{ Province: "山東", City: "威海", }, } fmt.Printf("user1=%#v\n", user1)//user1=main.User{Name:"小王子", Gender:"男", Address:main.Address{Province:"山東", City:"威海"}} }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      上面user結(jié)構(gòu)體中嵌套的Address結(jié)構(gòu)體也可以采用匿名字段的方式,例如:

      //Address 地址結(jié)構(gòu)體 type Address struct { Province string City string } //User 用戶結(jié)構(gòu)體 type User struct { Name string Gender string Address //匿名字段 } func main() { var user2 User user2.Name = "小王子" user2.Gender = "男" user2.Address.Province = "山東" // 匿名字段默認(rèn)使用類型名作為字段名 user2.City = "威海" // 匿名字段可以省略 fmt.Printf("user2=%#v\n", user2) //user2=main.User{Name:"小王子", Gender:"男", Address:main.Address{Province:"山東", City:"威海"}} }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      當(dāng)訪問(wèn)結(jié)構(gòu)體成員時(shí)會(huì)先在結(jié)構(gòu)體中查找該字段,找不到再去嵌套的匿名字段中查找。

      嵌套結(jié)構(gòu)體內(nèi)部可能存在相同的字段名。在這種情況下為了避免歧義需要通過(guò)指定具體的內(nèi)嵌結(jié)構(gòu)體字段名。

      //Address 地址結(jié)構(gòu)體 type Address struct { Province string City string CreateTime string } //Email 郵箱結(jié)構(gòu)體 type Email struct { Account string CreateTime string } //User 用戶結(jié)構(gòu)體 type User struct { Name string Gender string Address Email } func main() { var user3 User user3.Name = "沙河娜扎" user3.Gender = "男" // user3.CreateTime = "2019" //ambiguous selector user3.CreateTime user3.Address.CreateTime = "2000" //指定Address結(jié)構(gòu)體中的CreateTime user3.Email.CreateTime = "2000" //指定Email結(jié)構(gòu)體中的CreateTime }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      3.9 結(jié)構(gòu)體的“繼承”

      Go語(yǔ)言中使用結(jié)構(gòu)體也可以實(shí)現(xiàn)其他編程語(yǔ)言中面向?qū)ο蟮睦^承。

      //Animal 動(dòng)物 type Animal struct { name string } func (a *Animal) move() { fmt.Printf("%s會(huì)動(dòng)!\n", a.name) } //Dog 狗 type Dog struct { Feet int8 *Animal //通過(guò)嵌套匿名結(jié)構(gòu)體實(shí)現(xiàn)繼承 } func (d *Dog) wang() { fmt.Printf("%s會(huì)汪汪汪~(yú)\n", d.name) } func main() { d1 := &Dog{ Feet: 4, Animal: &Animal{ //注意嵌套的是結(jié)構(gòu)體指針 name: "樂(lè)樂(lè)", }, } d1.wang() //樂(lè)樂(lè)會(huì)汪汪汪~(yú) d1.move() //樂(lè)樂(lè)會(huì)動(dòng)! }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      3.10 結(jié)構(gòu)體字段的可見(jiàn)性

      結(jié)構(gòu)體中字段大寫(xiě)開(kāi)頭表示可公開(kāi)訪問(wèn),小寫(xiě)表示私有(僅在定義當(dāng)前結(jié)構(gòu)體的包中可訪問(wèn))。

      3.11 結(jié)構(gòu)體與JSON序列化

      JSON(JavaScript Object Notation) 是一種輕量級(jí)的數(shù)據(jù)交換格式。易于人閱讀和編寫(xiě)。同時(shí)也易于機(jī)器解析和生成。JSON鍵值對(duì)是用來(lái)保存JS對(duì)象的一種方式,鍵/值對(duì)組合中的鍵名寫(xiě)在前面并用雙引號(hào)""包裹,使用冒號(hào):分隔,然后緊接著值;多個(gè)鍵值之間使用英文,分隔。

      //Student 學(xué)生 type Student struct { ID int Gender string Name string } //Class 班級(jí) type Class struct { Title string Students []*Student } func main() { c := &Class{ Title: "101", Students: make([]*Student, 0, 200), } for i := 0; i < 10; i++ { stu := &Student{ Name: fmt.Sprintf("stu%02d", i), Gender: "男", ID: i, } c.Students = append(c.Students, stu) } //JSON序列化:結(jié)構(gòu)體-->JSON格式的字符串 data, err := json.Marshal(c) if err != nil { fmt.Println("json marshal failed") return } fmt.Printf("json:%s\n", data) //JSON反序列化:JSON格式的字符串-->結(jié)構(gòu)體 str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}` c1 := &Class{} err = json.Unmarshal([]byte(str), c1) if err != nil { fmt.Println("json unmarshal failed!") return } fmt.Printf("%#v\n", c1) }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      3.12 結(jié)構(gòu)體標(biāo)簽(Tag)

      Tag是結(jié)構(gòu)體的元信息,可以在運(yùn)行的時(shí)候通過(guò)反射的機(jī)制讀取出來(lái)。 Tag在結(jié)構(gòu)體字段的后方定義,由一對(duì)反引號(hào)包裹起來(lái),具體的格式如下:

      `key1:"value1" key2:"value2"`

      1

      結(jié)構(gòu)體tag由一個(gè)或多個(gè)鍵值對(duì)組成。鍵與值使用冒號(hào)分隔,值用雙引號(hào)括起來(lái)。同一個(gè)結(jié)構(gòu)體字段可以設(shè)置多個(gè)鍵值對(duì)tag,不同的鍵值對(duì)之間使用空格分隔。

      注意事項(xiàng): 為結(jié)構(gòu)體編寫(xiě)Tag時(shí),必須嚴(yán)格遵守鍵值對(duì)的規(guī)則。結(jié)構(gòu)體標(biāo)簽的解析代碼的容錯(cuò)能力很差,一旦格式寫(xiě)錯(cuò),編譯和運(yùn)行時(shí)都不會(huì)提示任何錯(cuò)誤,通過(guò)反射也無(wú)法正確取值。例如不要在key和value之間添加空格。

      例如我們?yōu)镾tudent結(jié)構(gòu)體的每個(gè)字段定義json序列化時(shí)使用的Tag:

      //Student 學(xué)生 type Student struct { ID int `json:"id"` //通過(guò)指定tag實(shí)現(xiàn)json序列化該字段時(shí)的key Gender string //json序列化是默認(rèn)使用字段名作為key name string //私有不能被json包訪問(wèn) } func main() { s1 := Student{ ID: 1, Gender: "男", name: "沙河娜扎", } data, err := json.Marshal(s1) if err != nil { fmt.Println("json marshal failed!") return } fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"男"} }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      3.13 結(jié)構(gòu)體和方法補(bǔ)充知識(shí)點(diǎn)

      因?yàn)閟lice和map這兩種數(shù)據(jù)類型都包含了指向底層數(shù)據(jù)的指針,因此我們?cè)谛枰獜?fù)制它們時(shí)要特別注意。我們來(lái)看下面的例子:

      type Person struct { name string age int8 dreams []string } func (p *Person) SetDreams(dreams []string) { p.dreams = dreams } func main() { p1 := Person{name: "小王子", age: 18} data := []string{"吃飯", "睡覺(jué)", "打豆豆"} p1.SetDreams(data) // 你真的想要修改 p1.dreams 嗎? data[1] = "不睡覺(jué)" fmt.Println(p1.dreams) // ? }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      正確的做法是在方法中使用傳入的slice的拷貝進(jìn)行結(jié)構(gòu)體賦值。

      func (p *Person) SetDreams(dreams []string) { p.dreams = make([]string, len(dreams)) copy(p.dreams, dreams) }

      1

      2

      3

      4

      同樣的問(wèn)題也存在于返回值slice和map的情況,在實(shí)際編碼過(guò)程中一定要注意這個(gè)問(wèn)題。

      AI Go

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:Python基礎(chǔ)數(shù)據(jù)類型(一)
      下一篇:3維圖形學(xué)基礎(chǔ)(三維基本圖形)
      相關(guān)文章
      亚洲成a人片在线观看日本| 亚洲资源最新版在线观看| 亚洲中文字幕久久无码| 亚洲无限乱码一二三四区| 亚洲精品福利视频| 亚洲av永久无码制服河南实里| 久久亚洲高清综合| 久久久久亚洲?V成人无码| 亚洲精品在线视频| 久久亚洲欧洲国产综合| 中文字幕精品无码亚洲字| 亚洲精品自在在线观看| 亚洲va无码va在线va天堂| 亚洲avav天堂av在线不卡| 亚洲国产国产综合一区首页| 亚洲av鲁丝一区二区三区| 久久亚洲精品无码AV红樱桃| 91嫩草私人成人亚洲影院| 亚洲黄色在线观看| 亚洲国产成人精品激情| 亚洲欧洲日产国码久在线| 亚洲av永久中文无码精品综合| 欧洲亚洲国产精华液| 国产亚洲精彩视频| 亚洲欧洲日产国码一级毛片| 日日噜噜噜噜夜夜爽亚洲精品 | 日韩亚洲人成在线| 亚洲欧美成人av在线观看| 337P日本欧洲亚洲大胆精品| 亚洲国产成人精品女人久久久| 久久精品国产精品亚洲人人 | 亚洲综合色婷婷在线观看| 亚洲欧美日韩中文高清www777| 亚洲日韩精品无码专区加勒比| 亚洲一区动漫卡通在线播放| 亚洲色精品VR一区区三区| 小说专区亚洲春色校园| 国产亚洲成归v人片在线观看| 亚洲精品一品区二品区三品区| 亚洲综合在线视频| 国产精品亚洲午夜一区二区三区|