go語言入門


title: go語言入門
date: 2019-02-12 22:03:27


前言

因項(xiàng)目需要和個(gè)人喜好概行,決定系統(tǒng)入門go語言。

go是由Google開發(fā)、開源、強(qiáng)類型的編譯型語言徘公。與c語言類似,不同的是哮针,go中每行語句結(jié)束不用加 ; :-)

本筆記主要參考 《Go語言實(shí)戰(zhàn)》 关面。

后加:本文所基于的GO語言版本較低(1.10),當(dāng)時(shí)還并未支持 Go Modules十厢。

一等太、Hello world

編寫hello.go文件:

package main    // 程序入口包

import (
    "fmt"
)

// 程序入口函數(shù)
func main() {
    fmt.Println("Hello world")
}

在命令行中輸入:go run hello.go

二、基礎(chǔ)語言

1. 變量

1.1. 基本變量類型

// 布爾型
bool

// 字符串型
string

// 整型
int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

// byte型
byte // uint8

// 表示一個(gè) Unicode 碼點(diǎn)
rune // int32 的別名

// 浮點(diǎn)型
float32 float64

// 復(fù)數(shù)型
complex64 complex128

1.2. 變量聲明

特性

  • 使用var關(guān)鍵字聲明(像js)寿烟,變量類型在變量名后
  • 短變量聲明 (像python)
  • 使用()一次聲明多個(gè)變量
  • 若聲明的變量沒被使用澈驼,會(huì)報(bào)錯(cuò)
// 一般聲明
var v_name1 int
var v_name2 = 1     // 根據(jù)值自行判別變量類型

// 短變量聲明辛燥。不能用于聲明全局變量
v_name3 := "hello"

// 多變量聲明
var vname1, vname2, vname3 int
var (
    ToBe   bool       = false
    MaxInt uint64     = 1<<64 - 1
)

// 指針變量聲明
var p *int
v_p := &v_name1
*v_p = 233

:和c類似筛武,go也分 全局變量(函數(shù)外、包內(nèi)) 和 局部變量(函數(shù)內(nèi)/控制語句內(nèi))

1.3. 變量零值

變量聲明時(shí)沒有賦予初始值挎塌,則默認(rèn)被賦予零值徘六。

  • 布爾型零值:false
  • 字符串型零值:""
  • 數(shù)值型零值:0
  • 指針型零值:nil

1.4. 強(qiáng)制類型轉(zhuǎn)換

表達(dá)式T(v)將值v轉(zhuǎn)換為類型T

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

1.5. 類型推導(dǎo)

當(dāng)不指定數(shù)據(jù)類型時(shí)榴都,系統(tǒng)會(huì)自行推導(dǎo)變量類型待锈。如下:

var i int
j := i // j 也是一個(gè) int

// 初始值為常量,則取決于常量的精度
i := 42           // int
f := 3.142        // float64
g := 0.867 + 0.5i // complex128

1.6. 變量輸出

使用fmt包中的函數(shù):fmt.Printffmt.Println嘴高。

格式化輸出fmt.Printf == c語言中的printf竿音。%T輸出數(shù)據(jù)的類型和屎,%v輸出任意數(shù)據(jù)的值,%p輸出地址數(shù)據(jù)春瞬,%d輸出整型數(shù)據(jù)柴信,等等。

直接輸出fmt.Println == python中的print宽气。

2. 常量

const關(guān)鍵字常量随常。可以不指定常量的數(shù)據(jù)類型萄涯。

const b string = "abc"
const b = "abc"
const Pi = 3.14

3. 運(yùn)算符

運(yùn)算符與c語言類似绪氛。具體如下,優(yōu)先級從高到低:

分類 描述 關(guān)聯(lián)性
后綴 () [] -> . ++ -- 左到右
一元 + - ! ~ (type) * & sizeof() 右到左
乘法 * / % 左到右
加法 + - 左到右
移位 << >> 左到右
關(guān)系 < <= > >= 左到右
相等 == != 左到右
按位AND & 左到右
按位XOR ^ 左到右
按位OR ` ` 左到右
邏輯AND && 左到右
邏輯OR ` ` 左到右
條件 ?: 右到左
分配 = += -= *= /= %= >>= <<= &= ^= ` =` 右到左
逗號 , 左到右

4. 語句

go中iffor語句不需要加()涝影;語句大括號{不需要換行枣察。

4.1. if語句

特性

  • 可以初始化變量,僅在if語句中使用袄琳。
// if...else...
if numA < 20 {
    fmt.Printf("a小于20\n" );
} else {
    fmt.Printf("numA 不小于 20\n" );
}

// if語句的分號前面询件,相當(dāng)于初始化一個(gè)變量,僅在if語句內(nèi)使用
if i := 30; numA < i {
    fmt.Println(i)
}

4.2. switch語句

特性

  • case語句結(jié)束自動(dòng)break
  • case可以同時(shí)匹配多個(gè)值唆樊,如:case v1,v2,v3:
  • 匹配一個(gè)case成功后宛琅,可以使用fallthrough強(qiáng)制匹配下一個(gè)case
  • switch可以沒有條件
var grade string
var marks int = 90

// case語句結(jié)束自動(dòng)break。
// 可以同時(shí)case多個(gè)值逗旁,case v1,v2,v3:
switch marks {
    case 90: grade = "A"
    case 80: grade = "B"
    case 50,60,70 : grade = "C"
    default: grade = "D"
}
fmt.Printf("你的等級是 %s\n", grade );

t := time.Now()
switch {
    case t.Hour() < 12:
        fmt.Println("Good morning!")
    case t.Hour() < 17:
        fmt.Println("Good afternoon.")
    default:
        fmt.Println("Good evening.")
}

switch {
    case false:
        fmt.Println("1嘿辟、case 條件語句為 false")
        fallthrough
    case true:
        fmt.Println("2、case 條件語句為 true")
        fallthrough
    case false:
        fmt.Println("3片效、case 條件語句為 false")
        fallthrough
    case true:
        fmt.Println("4红伦、case 條件語句為 true")
    case false:
        fmt.Println("5、case 條件語句為 false")
        fallthrough
    default:
        fmt.Println("6淀衣、默認(rèn) case")
}
/*
輸出:
2昙读、case 條件語句為 true
3、case 條件語句為 false
4膨桥、case 條件語句為 true
*/

4.3. select語句

select語句類似于switch語句蛮浑。

但是,區(qū)別在于:

  • 每個(gè)case必須是一個(gè)通信操作(數(shù)據(jù)結(jié)構(gòu)通道的操作)只嚣,要么是發(fā)送要么是接收沮稚。
  • select隨機(jī)執(zhí)行一個(gè)未堵塞的case。如果所有case都堵塞册舞,它將等待蕴掏,直到有case可以通行。
var c1, c2, c3 chan int
var i1, i2 int
// 隨機(jī)執(zhí)行一個(gè)case,若所有case都堵塞盛杰,則直到有case可以通行為止
select {
    case i1 = <-c1:
        fmt.Println("received ", i1, " from c1")
    case c2 <- i2:
        fmt.Println("sent ", i2, " to c2")
    case i3, ok := (<-c3):  // same as: i3, ok := <-c3
        if ok {
            fmt.Println("received ", i3, " from c3")
        } else {
            fmt.Println("c3 is closed")
        }
    default:
        fmt.Println("no communication")
}
/*
輸出:
no communication
*/

4.4. for語句

特性

  • for只帶條件判斷相當(dāng)于while
  • for中使用range關(guān)鍵字挽荡,可以遍歷順序結(jié)構(gòu),返回keyvalue
for i := 0; i < 10; i++ {
    fmt.Printf("i 的值為: %d\n", i)
    if i > 5{
        break
    }
}
// 相當(dāng)于c的while
for numA < numB {
    numA++
    fmt.Printf("numA 的值為: %d\n", numA)
}
numbers := [6]int{1, 2, 3, 5}
// range關(guān)鍵字即供,可以對 slice徐伐、map、數(shù)組募狂、字符串等進(jìn)行迭代
for key, value := range numbers {
    fmt.Printf("第 %d 位 x 的值 = %d\n", key, value)
}

5. 函數(shù) 引用-數(shù)據(jù)類型

go語言的一大“神奇”特點(diǎn)办素,就是喜歡把原本前面的東西放到后面,函數(shù)也不例外祸穷。

特性

  • 函數(shù)以關(guān)鍵字func進(jìn)行聲明
  • 返回類型(和值)性穿,放在參數(shù)項(xiàng)的后面
  • 允許先聲明返回值
  • 多值返回
  • deferdefer語句會(huì)將指定函數(shù)推遲到外層函數(shù)返回之后再執(zhí)行雷滚。并且算谈,被推遲的函數(shù)將被壓入一個(gè)棧中篮迎。
//==========函數(shù)============
func add(x int, y int) int {
    return x + y
}

// 先聲明返回值蕉毯,直接用return返回蚓挤。適用于短函數(shù)
func subtract(x int, y int) (z int) {
    z = x - y
    return
}

// 返回多值
func swap(x, y string) (string, string) {
    return y, x
}
/*
使用:
stringA, stringB = swap(stringA, stringB)
*/

// 傳指針
func swap_p(x, y *int) {
    var temp int
    temp = *x    /* 保持 x 地址上的值 */
    *x = *y      /* 將 y 值賦給 x */
    *y = temp    /* 將 temp 值賦給 y */
}

// defer棧
func deferTest() {
    fmt.Println("counting")

    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }

    fmt.Println("done")
}
/*
輸出
counting
done
9
8
7
6
5
4
3
2
1
0
*/

另外,跟JavaScript一樣车份,go語言也有:

  • 函數(shù)變量谋减,函數(shù)也是屬于一類數(shù)據(jù)類型。
  • 函數(shù)做參扫沼。多態(tài)性的一種體現(xiàn)出爹。
  • 函數(shù)作為返回值(函數(shù)閉包)。得以使用函數(shù)的局部變量缎除。
  • 匿名函數(shù)
  • 函數(shù)自調(diào)用
// 函數(shù)做參严就。函數(shù)參數(shù),要指明函數(shù)參數(shù)的參數(shù)類型和返回值類型
func handleNum(num float64, fn func (float64) float64) {
    fmt.Println("函數(shù)做參開始")
    fmt.Println(fn(num))
    fmt.Println("函數(shù)做參結(jié)束")
}

// 閉包函數(shù)(將函數(shù)作為返回值)
func getSequence() func() int {
    i:=0
    return func() int {
        i+=1
        return i
    }
}

func main() {
    // 函數(shù)變量
    getSquareRoot := func(x float64) float64 {
        return math.Sqrt(x)
    }
    fmt.Println(getSquareRoot(9))

    // 函數(shù)做參
    handleNum(16, getSquareRoot)

    // 函數(shù)閉包
    nextNumber := getSequence()     // nextNumber 為一個(gè)函數(shù)器罐,函數(shù) i 為 0
    fmt.Println(nextNumber())       // 調(diào)用 nextNumber 函數(shù)梢为,i 變量自增 1 并返回
    fmt.Println(nextNumber())
    fmt.Println(nextNumber())

    nextNumber1 := getSequence()    // 創(chuàng)建新的函數(shù) nextNumber1,并查看結(jié)果
    fmt.Println(nextNumber1())      // 輸出 1
    fmt.Println(nextNumber1())      // 輸出 2

    // 匿名函數(shù)和函數(shù)自調(diào)用
    func (count int) {
        fmt.Println("匿名函數(shù)開始")
        for i := 0; i < count; i++ {
            fmt.Println(i)
        }
        fmt.Println("匿名函數(shù)結(jié)束")
    } (3)
}

輸出:

函數(shù)做參開始
4
函數(shù)做參結(jié)束
1
2
3
1
2
匿名函數(shù)開始
0
1
2
匿名函數(shù)結(jié)束

6. 包

6.1. 包的特性

java相似轰坊,每個(gè).go文件開頭需要用package關(guān)鍵字聲明文件所屬于的铸董。并且,包的名字需要與目錄名字相同衰倦。main包除外袒炉。

例如旁理,項(xiàng)目中有一個(gè)routers/目錄樊零,routers/目錄下有一個(gè)router.go文件,那router.go文件開頭必須聲明所屬于的包:

package routers

6.2. main包

每個(gè)程序(項(xiàng)目)都必須有一個(gè)main包,編譯器會(huì)根據(jù)main包找到main()函數(shù)驻襟,這是程序的入口函數(shù)夺艰。若沒找到main()函數(shù),程序則不會(huì)執(zhí)行沉衣。

6.3. 導(dǎo)入包

import關(guān)鍵字用于導(dǎo)入一個(gè)外包郁副。格式如下:

import "fmt"

或者導(dǎo)入多個(gè)包時(shí):

import (
    "fmt"
    "math"
    "net/http"
)

根據(jù)以上包名,編譯器會(huì)依次在以下目錄中查找:

1)GOROOT/src豌习,安裝路徑下的src目錄
2)GOPATH/src存谎,工作空間下的src目錄
3)若以上都沒找到,且包路徑中包含URL肥隆,那么會(huì)從網(wǎng)上獲取包既荚,并保存到GOPATH/src目錄下。比如:

import "github.com/99MyCql/chatRoom/routers"

6.4. 命名導(dǎo)入

導(dǎo)入包的名字默認(rèn)為包名栋艳,但如果出現(xiàn)重名情況恰聘,我們可以通過給包重新命名來化解。

import (
    "fmt"
    myfmt "mylib/fmt"   // myfmt為該包的新名字
)

go語言中若導(dǎo)入了某包(會(huì)調(diào)用該包中的init()函數(shù))吸占,而又沒使用該包晴叨,編譯器則會(huì)報(bào)錯(cuò)。

解決這個(gè)問題可以使用空白標(biāo)識符_來重命名這個(gè)包矾屯,表明導(dǎo)入該包卻不使用該包兼蕊。如:

import "github.com/99MyCql/chatRoom/routers"

6.5. init()函數(shù)

一個(gè)包中,可以有一個(gè)或多個(gè)init()函數(shù)(多個(gè)init()函數(shù)不能在同一個(gè).go文件中)件蚕。

init()函數(shù)會(huì)在main()函數(shù)執(zhí)行前被調(diào)用遍略。

每個(gè)被導(dǎo)入的包(不管有沒有被使用),都會(huì)調(diào)用包中的所有init()函數(shù)骤坐。通常绪杏,init()函數(shù)被用來進(jìn)行一些初始化操作。

使用空白標(biāo)識符_纽绍,可以讓包中的init()函數(shù)被調(diào)度使用蕾久,同時(shí)編譯器不會(huì)因?yàn)榘鼪]被使用而報(bào)錯(cuò)。

6.6. 包中名的可見性(special)

在一個(gè)包內(nèi)拌夏,所有文件的全局變量是共享的僧著。

對于包外,以大寫字母開頭的全局變量和函數(shù)是公開的障簿,以小寫字母開頭的私有的盹愚。如:

  • fmt.Println() 是調(diào)用fmt包中公開的Println()函數(shù)。
  • fmt.Println(math.pi) 輸出math包中變量站故,會(huì)報(bào)錯(cuò)皆怕,因?yàn)樵撟兞渴撬接械摹?/li>

6.7. 使用另一個(gè)包中的變量和函數(shù)

通過 <package name>.<var/fun> 格式(跟C++使用類中變量函數(shù)相似)毅舆,來使用另一個(gè)包中公開的變量和函數(shù)。如:

fmt.Println()   // 使用 fmt 包中 Println() 函數(shù)
math.PI         // 使用 math 包中 PI 變量

三愈腾、進(jìn)階數(shù)據(jù)結(jié)構(gòu)

1. 指針 值-數(shù)據(jù)類型

與c語言指針類似憋活,go指針指向?qū)?yīng)類型的變量。

但不同的是:

  • go語言指針不能進(jìn)行運(yùn)算

  • 指針變量的聲明中虱黄,標(biāo)識符*必須貼近變量類型悦即,而不貼近變量名。如:var name *T

  • 指針類型的零值為nil

// 指針變量聲明
var p *int
v_p := &v_name1
*v_p = 233

2. 數(shù)組 值-數(shù)據(jù)類型

go語言數(shù)組跟c語言的相似橱乱,但也有不同辜梳。

特性

  • 格式var name [len]T,如:var a [2]string

  • go語言的數(shù)組是一種數(shù)據(jù)類型泳叠,而且是一種值類型冗美。即數(shù)組名是一個(gè)值,包含著整個(gè)數(shù)組的數(shù)據(jù)

  • 需要編譯器自己識別數(shù)組長度時(shí)析二,不能使[]中空閑粉洼,而必須使用[...]

  • 指向數(shù)組的指針格式為:var name *[len]T,如:var arrp *[5]int

//====================數(shù)組=====================
// 變量 a 是一個(gè)值類型叶摄,而不是引用類型属韧。包含著整個(gè)數(shù)組的數(shù)據(jù)
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)

// 使用字面量聲明數(shù)組
array1 := [5]int{10,20,30,40,50}
array2 := [...]int{1,2,3,4,5}   // ... 可以使編譯器根據(jù)元素?cái)?shù)量,自動(dòng)確定數(shù)組長度
fmt.Println(array1, array2)

// 數(shù)組元素類型為指針
array3 := [5]*int{0:new(int), 1:new(int)}   // 用 下標(biāo):... 進(jìn)行特定位置的初始化
*array3[0] = 10
*array3[1] = 20
fmt.Println(*array3[0])
fmt.Println(array3)

// 多維數(shù)組
var array4 [4][2]int    // 4行2列的二維數(shù)組蛤吓,即有4行宵喂,每行有2個(gè)int

// 指向數(shù)組變量的指針
arrP := &a
fmt.Printf("a 's type is %T\n", a)
fmt.Printf("arrp 's type is %T\n", arrP)
(*arrP)[1] = "Today"    // arrP[1] = "Today" 也可以
fmt.Println(*arrP)

// new 指向數(shù)組的指針
var arrp *[5]int    // 此時(shí)為nil
fmt.Println(arrp)   // 輸出 nil
arrp = new([5]int)  // 分配相應(yīng)的數(shù)組空間,并返回指針
fmt.Println(arrp)   // 輸出 &[0 0 0 0 0]

// 與c語言的不同会傲,go的數(shù)組名是值而不是指針锅棕。以下按照c語言的思路,在go中是錯(cuò)誤的
// var p *int
// p = a

// 數(shù)組是值類型而不是引用類型的示例
arrA := [2]int{1,2}
arrB := arrA    // 相當(dāng)于賦值了整個(gè)數(shù)組的值
arrB[1] = 3
fmt.Println(arrA, arrB) // 輸出 [1 2] [1 3]

輸出:

Hello World
[Hello World]
[10 20 30 40 50] [1 2 3 4 5]
10
[0xc42008a018 0xc42008a030 <nil> <nil> <nil>]
[[0 20] [0 0] [0 60] [0 0]]
a 's type is [2]string
arrp 's type is *[2]string
[Hello Today]
<nil>
&[0 0 0 0 0]
[1 2] [1 3]

注意

由于在go語言中淌山,數(shù)組類型是值類型而不是引用類型裸燎。所以,在函數(shù)傳參時(shí)泼疑,我們需要傳入指向數(shù)組的指針德绿,而不是數(shù)組值。但退渗,若是希望拿到該數(shù)組的副本移稳,則可以選擇使用傳入值。

同時(shí)会油,指向數(shù)組的指針還必須指明數(shù)組的長度个粱,這其實(shí)十分不方便。但切片可以很好地解決這個(gè)問題翻翩。

// 10的6次方數(shù)組
var array [1e6]int
foo(&array)

// 函數(shù)接受一個(gè)指向包含100萬個(gè)整型值數(shù)組的指針
func foo(array *[1e6]int) {
    ...
}

3. 切片 引用-數(shù)據(jù)類型

切片是go自帶的數(shù)據(jù)類型都许,圍繞動(dòng)態(tài)數(shù)組的概念來構(gòu)建稻薇。

同時(shí),切片是一個(gè)引用類型梭稚,所以切片的零值為nil

3.1. 切片的內(nèi)部實(shí)現(xiàn)

切片其實(shí)是一個(gè)很小的結(jié)構(gòu)體絮吵,對底層數(shù)組進(jìn)行了抽象弧烤。“切片結(jié)構(gòu)體”包含三個(gè)屬性:

  • 指向底層數(shù)組的指針蹬敲。底層數(shù)組會(huì)一直存在暇昂,直到?jīng)]有指向它的切片

  • 切片的長度。動(dòng)態(tài)數(shù)組的長度

  • 切片的容量伴嗡。容量相當(dāng)于動(dòng)態(tài)數(shù)組的長度上限

3.2. 切片的創(chuàng)建和初始化

  • 格式為:name []T急波。注意[]中無值,有值為數(shù)組

  • 未初始化的切片為“空指針”瘪校,零值為nil

  • make關(guān)鍵字創(chuàng)建澄暮,還可以聲明切片的長度和容量。推薦

  • 通過數(shù)組或切片[x:y]來創(chuàng)建切片(包含x位元素阱扬,排除y位元素)泣懊,可以使用[x:][:y]等麻惶,還可以通過[x:y:z]規(guī)定切片的容量(z)

// 切片的創(chuàng)建和初始化
// 注意切片和數(shù)組的區(qū)別
var slice1 []int                // nil切片(空指針)馍刮,指向底層數(shù)組的指針為空
if slice1 == nil {
    fmt.Println("slice1 is nil")
}
slice2 := []int{0,1,2,3,5:6}    // 創(chuàng)建并初始化,跟數(shù)組很像
slice3 := make([]int, 0)        // 空切片(不是nil切片)窃蹋,長度和容量為0
slice4 := make([]string, 5)     // 用make創(chuàng)建字符串切片卡啰,長度和容量都為5
slice5 := make([]int, 3, 5)     // 長度為3,容量為5
fmt.Println(slice1, slice2, slice3, slice4, slice5)

arr := [5]int{0,1,2,3,4}
arrSlice1 := arr[1:3]           // 用數(shù)組創(chuàng)建切片警没。此時(shí)切片指向該數(shù)組下標(biāo)1位置匈辱,并且長度為2、容量為4(下標(biāo)1到原數(shù)組結(jié)束)
arrSlice2 := arr[1:3:3]         // 規(guī)定容量為3(容量不可超過原數(shù)組)杀迹。此時(shí)梅誓,arrSlice1和arrSlice2共享同一個(gè)底層數(shù)組
newSlice1 := slice2[1:3]        // 用切片構(gòu)建切片。兩個(gè)切片共享一個(gè)底層數(shù)組
newSlice2 := newSlice1
fmt.Println(arr, arrSlice1, arrSlice2, newSlice1, newSlice2)

輸出:

slice1 is nil
[] [0 1 2 3 0 6] [] [    ] [0 0 0]
[0 1 2 3 4] [1 2] [1 2] [1 2] [1 2]

3.3. 切片的使用

  • len()函數(shù)獲取切片長度佛南,cap()函數(shù)獲取切片容量

  • func copy(dst, src []T) intsrc切片的內(nèi)容拷貝到dst切片中梗掰,拷貝的長度為兩個(gè)slice中長度較小的長度值

  • func append(s []T, x ...T) []T 返回一個(gè)新切片。當(dāng)原切片容量不足時(shí)嗅回,append函數(shù)會(huì)創(chuàng)建一個(gè)新的容量更大的底層數(shù)組及穗,并將原切片的底層數(shù)組復(fù)制到新數(shù)組里,再追加新的值绵载。append(dist, x, y)追加多個(gè)值(x,y...)到dist切片埂陆。append(dist, src...)將整個(gè)src切片追加到dist切片尾苛白。

  • 切片的多維和遍歷/迭代,與數(shù)組一樣

// len() 和 cap()
fmt.Printf("slice5 is %v, len is %d, capacity is %d\n", slice5, len(slice5), cap(slice5))
fmt.Printf("arrSlice1 is %v, len is %d, capacity is %d\n", arrSlice1, len(arrSlice1), cap(arrSlice1))

// func copy(dst, src []T) int
copy(slice5, arrSlice1)     // copy()函數(shù)會(huì)根據(jù)長度復(fù)制
fmt.Printf("slice5 is %v, len is %d, capacity is %d\n", slice5, len(slice5), cap(slice5))

// func append(s []T, x ...T) []T   返回一個(gè)新切片
// 當(dāng)追加后焚虱,目標(biāo)切片長度超過容量時(shí)购裙,append函數(shù)會(huì)創(chuàng)建一個(gè)新的容量更大的底層數(shù)組,將原本數(shù)組復(fù)制到新數(shù)組中鹃栽,再追加新的值
slice1 = append(slice1, 10)
fmt.Printf("slice1 is %v, len is %d, capacity is %d\n", slice1, len(slice1), cap(slice1))
slice1 = append(slice1, slice2...)  // 用標(biāo)識符 ... 將整個(gè)切片追加到另一個(gè)切片
fmt.Printf("slice1 is %v, len is %d, capacity is %d\n", slice1, len(slice1), cap(slice1))

輸出:

slice5 is [0 0 0], len is 3, capacity is 5
arrSlice1 is [1 2], len is 2, capacity is 4
slice5 is [1 2 0], len is 3, capacity is 5
slice1 is [10], len is 1, capacity is 1
slice1 is [10 0 1 2 3 0 6], len is 7, capacity is 8

3.4. 切片的“陷阱”

切片賦值后躏率,兩個(gè)切片會(huì)共享同一個(gè)底層數(shù)組,一個(gè)切片修改值時(shí)會(huì)影響到另一個(gè)數(shù)組民鼓。切片共享底層數(shù)組示例圖:

[圖片上傳失敗...(image-b37feb-1601651171851)]

// 切片賦值后薇芝,會(huì)共用一個(gè)底層數(shù)組
sliceA := []string{"hello", "world", "!", "!", "!"}
fmt.Printf("sliceA is %v\n", sliceA)
sliceB := sliceA[:3]
sliceB[2] = "?"         // 由于sliceB和sliceA共享一個(gè)底層數(shù)組,通過sliceB修改底層數(shù)組丰嘉,會(huì)影響到sliceA
fmt.Printf("sliceB is %v\n", sliceB)
fmt.Printf("sliceA changs : %v\n", sliceA)

輸出:

sliceA is [hello world ! ! !]
sliceB is [hello world ?]
sliceA changs : [hello world ? ! !]

4. 映射 引用-數(shù)據(jù)類型

映射又稱map夯到、鍵值對,基于特定的hash函數(shù)/散列函數(shù)饮亏。

映射也是引用類型耍贾,零值為nil

4.1. 映射的創(chuàng)建和初始化

  • 格式:name map[keyT]valueT

  • 未初始化的聲明會(huì)創(chuàng)建nil映射路幸。nil 映射既沒有鍵逼争,也不能添加鍵

  • make()函數(shù)進(jìn)行創(chuàng)建,產(chǎn)生空映射而非nil映射

  • 用字面量初始化聲明映射劝赔,采用換行的形式誓焦,需要在最后一個(gè)鍵值對后加 ,

// 映射的創(chuàng)建和初始化
var dictNil map[string]int  // 聲明了一個(gè)nil映射,nil 映射既沒有鍵着帽,也不能添加鍵
if dictNil == nil {
    fmt.Println("dictNil is nil")
}
// dictNil["red"] = 1   運(yùn)行時(shí)會(huì)報(bào)錯(cuò)
dict1 := make(map[string]int)    // 用make()函數(shù)創(chuàng)建的map杂伟,是空映射,而不是nil映射仍翰。映射/鍵值對中鍵的類型不能是切片赫粥、函數(shù)等引用數(shù)據(jù)類型
dict1["red"] = 1
dict2 := map[string]string {
    "Red": "#da1337",
    "Orange": "#e95a22",    // 最后一行需要加 ,
}
fmt.Println(dict1, dict2)

輸出:

dictNil is nil
map[red:1] map[Orange:#e95a22 Red:#da1337]

4.2. 映射的操作

  • 獲取值:value=map[key]。通過雙賦值檢測某個(gè)鍵是否存在:value, ok = map[key]予借,若keymap中越平,oktrue;否則灵迫,okfalse秦叛;若key不在映射中,那么value是該映射元素類型的零值瀑粥。

  • 增加鍵值對:map[key]=value

  • 刪除鍵值對挣跋,用delete()函數(shù):delete(map, key)

fmt.Println(dict1["red"])   // 獲取鍵值對
dict1["blue"] = 2           // 增加鍵值對
fmt.Println(dict1)
delete(dict1, "red")        // 刪除鍵值對
fmt.Println(dict1)

輸出:

1
map[red:1 blue:2]
map[blue:2]

5. 結(jié)構(gòu)體 值-數(shù)據(jù)類型

  • 結(jié)構(gòu)體類型使用關(guān)鍵字struct進(jìn)行定義,用type進(jìn)行命名狞换,如:type user struct{}避咆。

結(jié)構(gòu)體定義:

type user struct {
    name    string
    email   string
    age     int
}

type admin struct {
    person  user    // 嵌套另一個(gè)結(jié)構(gòu)體
    level   int
}

結(jié)構(gòu)體使用:

var bill user       // 初始化后舟肉,結(jié)構(gòu)體中所有成員都會(huì)被賦零值
// 短變量聲明
lisa := user{
    name:   "Lisa",
    email:  "lisa@email.com",
    age:    19,     // 采用換行形式時(shí)最后一個(gè)也需要加 ,
}
tom := user{name:"Tom"} // 單獨(dú)聲明某一個(gè)成員
fmt.Println(bill, lisa, tom)

ad := admin{lisa, 10}   // 直接按照結(jié)構(gòu)體成員順序,傳入對應(yīng)的值
adP := &ad              // 獲取指向結(jié)構(gòu)體的指針
fmt.Println(ad, adP)

fmt.Println(ad.person.name)  // 結(jié)構(gòu)體值訪問內(nèi)部成員
fmt.Println(adP.person.name) // 結(jié)構(gòu)體指針訪問內(nèi)部成員

輸出:

{  0} {Lisa lisa@email.com 19} {Tom  0}
{{Lisa lisa@email.com 19} 10} &{{Lisa lisa@email.com 19} 10}
Lisa
Lisa

6. new 和 make

在go語言中有兩個(gè)用于內(nèi)存分配的函數(shù):newmake查库。

6.1. new

函數(shù)原型:

func new(Type) *Type

說明:

主要給值類型數(shù)據(jù)分配空間路媚,并返回指向該數(shù)據(jù)空間的指針。這與c語言中malloc類似樊销。但是整慎,new()函數(shù)不能指定個(gè)數(shù)和大小,只能傳入指定的數(shù)據(jù)類型(包括用戶自定義的數(shù)據(jù)類型)现柠。

示例:

// var i *int
// *i=10    錯(cuò)誤院领,野指針
var i *int
i = new(int)
arrP := new([5]int)     // 分配長度為5的數(shù)組空間弛矛,并返回?cái)?shù)組指針

6.2. make

函數(shù)原型:

func make(t Type, size ...IntegerType) Type

說明:

make也是用于內(nèi)存分配的够吩,但是和new不同,它只用于chan丈氓、map以及slice的內(nèi)存創(chuàng)建周循,而且它返回的類型值,而不是他們的指針万俗。同時(shí)湾笛,make()函數(shù)還能對這三個(gè)類型的相關(guān)屬性進(jìn)行初始化。

示例:

slice := make([]int, 3, 5)     // 長度為3闰歪,容量為5嚎研,單位為int的slice
dict := make(map[string]int)   // 鍵為string,值為int類型的map
buffer := make(chan string, 10)// 緩沖為10的字符串類型通道

7. 淺談引用類型

在Go語言中库倘,引用類型可以看作一個(gè)指針临扮,它并不包含實(shí)際數(shù)據(jù)。比如教翩,切片 slice 只是一個(gè)如下的類型:

type Slice struct {
    point Point // 指向底層數(shù)據(jù)的指針
    len int     // 底層數(shù)據(jù)的長度
    cap int     // 底層數(shù)據(jù)的容量(最大長度)
}

當(dāng)引用類型作為函數(shù)參數(shù)時(shí)杆勇,你可以通過引用類型修改所指向的數(shù)據(jù)(退出函數(shù)后依然有效)。但是饱亿,你不可以修改引用類型本身(退出函數(shù)后修改無效)蚜退。

map 為例:

func mapAdd(m map[string]interface{}) {
    m["name"] = "dounine"
}

func mapAdd2(m map[string]interface{}) {
    mp := map[string]interface{}{
        "name": "dounine",
    }
    m = mp
}

func main() {
    data3 := make(map[string]interface{})
    mapAdd(data3)
    fmt.Println("type:", reflect.TypeOf(data3), "; value:", reflect.ValueOf(data3))

    data4 := make(map[string]interface{})
    mapAdd2(data4)
    fmt.Println("type:", reflect.TypeOf(data4), "; value:", reflect.ValueOf(data4))
}

輸出:

type: map[string]interface {} ; value: map[name:dounine]
type: map[string]interface {} ; value: map[]

再以 slice 為例:

func sliceAdd(s []int) {
    s = append(s, 6)
}

func sliceUpdate(s []int) {
    s[0] = 1
}

func main() {
    slice1 := []int{0, 1, 2, 3, 5}
    sliceAdd(slice1)
    fmt.Println(slice1)

    sliceUpdate(slice1)
    fmt.Println(slice1)
}

輸出:

[0 1 2 3 5]
[1 1 2 3 5]

原因是:append 函數(shù)會(huì)修改 slice 類型本身的 len 屬性,退出函數(shù)后失效彪笼;而修改 slice 類型指向的數(shù)組的值钻注,退出函數(shù)后依然有效。

四配猫、面向?qū)ο?/h2>

go語言中是沒有關(guān)鍵字class的队寇,也就是說,go語言中沒有類也沒有繼承章姓。但佳遣,go卻是一個(gè)面向?qū)ο蟮恼Z言识埋,那它究竟如何實(shí)現(xiàn)面向?qū)ο竽兀?/p>

首先,go通過結(jié)構(gòu)體的成員來定義類的屬性零渐,結(jié)構(gòu)體名即類名窒舟;

其次,通過語法格式讓函數(shù)與結(jié)構(gòu)體關(guān)聯(lián)诵盼,實(shí)現(xiàn)類方法惠豺;

然后,通過關(guān)鍵字interface與結(jié)構(gòu)體結(jié)合风宁,實(shí)現(xiàn)接口和多態(tài)洁墙;

接著,通過結(jié)構(gòu)體實(shí)名內(nèi)嵌的形式戒财,來實(shí)現(xiàn)對象內(nèi)嵌另一個(gè)對象的has-a模式热监;

最后,通過結(jié)構(gòu)體匿名域內(nèi)嵌的形式饮寞,來實(shí)現(xiàn)“繼承”孝扛,即is-a模式。

各功能的具體實(shí)現(xiàn)幽崩,下文一一講解苦始。

1. 類方法

1.1. 定義

格式:

// 用戶類型
type user struct {
    name  string
    email string
}

// notify 方法,以值為接收者
func (u user) notify() {
    fmt.Printf("Sending User Email To %s<%s>\n",
        u.name,
        u.email)
}

// changeEmail 方法慌申,以指針為接收者
func (u *user) changeEmail(email string) {
    u.email = email
}

說明:

  • 一個(gè)類型的方法的聲明陌选,必須跟類型在同一個(gè)包內(nèi)。

  • 方法的聲明與函數(shù)類似蹄溉,不同的是咨油,需要在func與方法名之間加上接收者參數(shù),指明方法所從屬的類型类缤。接收者有兩種:值接收者指針接收者臼勉。

1.2. 調(diào)用類方法

調(diào)用類型的方法:<類型值/指針>.<方法>,如:boss.notify()餐弱。不管是值類型宴霸,還是指向類型的指針,都使用這種格式膏蚓。

示例:

// 值類型
boss := user{
    name: "aaaaa",
    email: "123456",
    age: 20,
}
boss.notify()
boss.changeEmail("2222")    // go語言隱式轉(zhuǎn)換瓢谢,(&boss).changeEmail()

// 指針類型
bossP := &boss
bossP.notify()              // 隱式轉(zhuǎn)換,(*bossP).notify()
bossP.changeEmail("1111")

1.3. 指針接收者和值接收者

類型的值 使用 指針接收者聲明的方法驮瞧,和 類型的指針使用 值接收者聲明的方法時(shí)氓扛,go語言都會(huì)進(jìn)行隱式轉(zhuǎn)換。所以,不管是以什么接收者聲明的方法采郎,值類型和指針類型都能調(diào)用千所。

示例:

boss := user{
    name: "aaaaa",
    email: "123456",
    age: 20,
}
boss.notify()
boss.changeEmail("2222")    // go語言隱式轉(zhuǎn)換,(&boss).changeEmail()
fmt.Println(boss)

bossP := &boss
bossP.notify()              // 隱式轉(zhuǎn)換蒜埋,(*bossP).notify()
bossP.changeEmail("1111")
fmt.Println(bossP)

值接收者指針接收者 的區(qū)別:

  • 值接收者得到類型的副本淫痰,修改副本值不會(huì)對原本值起作用;
  • 指針接收者得到指向類型值的指針整份,所以待错,在指針接收者的方法中修改類型數(shù)據(jù),會(huì)影響到原本的值烈评。

示例:

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

// 值接收者
func (v Vertex) Scale1(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

// 指針接收者
func (v *Vertex) Scale2(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v)      // 輸出 {3,4}
    v.Scale1(10)
    fmt.Println(v)      // 輸出 {3,4}
    v.Scale2(10)
    fmt.Println(v)      // 輸出 {30,40}
}

2. 接口 引用-數(shù)據(jù)類型

2.1. 聲明

接口是一系列方法的集合火俄。它的格式如下:

type <接口名> interface {
    方法1名(方法參數(shù)) 方法返回值
    ...
}

同時(shí),也可以組合(嵌入)其它接口形成新接口

type <接口名> interface {
    接口名1
    接口名2
    ...
    方法1名(方法參數(shù)) 方法返回值
    ...
}

嵌入進(jìn)來的接口讲冠,相當(dāng)于把它的方法都復(fù)制到新接口中瓜客。

2.2. 實(shí)現(xiàn)接口

如果想要某個(gè)類型實(shí)現(xiàn)某個(gè)接口,只需要將接口中所有方法實(shí)現(xiàn)為類方法即可沟启。示例:

type I interface {
    M()
    N()
}

type T struct {
    S string
}

// 以下兩個(gè)方法意味著: type T implements the interface I
func (t T) M() {
    fmt.Println(t.S)
}
func (t T) N() {
    fmt.Println(t.S)
}

2.3. 使用接口

如果某個(gè)類型實(shí)現(xiàn)了某個(gè)接口類型的所有方法忆家,那么就可以將該類型的值或指針賦給這個(gè)接口類型的值犹菇。

但要注意

  • 值類型 和 指針類型 能使用 值接收者 實(shí)現(xiàn)的方法德迹;
  • 但是,值類型 不能使用 指針接收者 實(shí)現(xiàn)的方法揭芍,指針類型 才能使用胳搞。

示例如下:

// 接口 I
type I interface {
    M()
}

// 類型 T
type T struct {
    S string
}

// 值接收者 實(shí)現(xiàn)的方法
func (t T) M() {
    fmt.Println(t.S)
}

func main() {
    // 值類型 使用 值接收者 實(shí)現(xiàn)的方法
    var i I = T{"hello"}
    i.M()
    // 指針類型 使用 值接收者 實(shí)現(xiàn)的方法
    var i I = &T{"hello"}
    i.M() // 隱式轉(zhuǎn)換:(*i).M()
}
// 接口 I
type I interface {
    M()
}

// 類型 T
type T struct {
    S string
}

// 指針接收者 實(shí)現(xiàn)的方法
func (t *T) M() {
    fmt.Println(t.S)
}

func main() {
    // 值類型 不能使用 指針接收者 實(shí)現(xiàn)的方法
    // Error: cannot use T literal (type T) as type I in assignment:
    // T does not implement I (M method has pointer receiver)
    var i1 I = T{"hello"}
    i1.M()
    // 指針類型 才能使用 指針接收者 實(shí)現(xiàn)的方法
    var i2 I = &T{"hello"}
    i2.M()
}

因此,我們通常定義類型指針來操作類型称杨。

2.4. nil接口

如果沒有為接口賦值肌毅,而調(diào)用接口中的方法,那將會(huì)報(bào)錯(cuò)姑原,因?yàn)榻涌谥禐?code>nil:

type I interface {
    M()
}

func main() {
    var i I
    // panic: runtime error: invalid memory address or nil pointer dereference
    i.M()
}

2.5. 空接口 - 泛型

空接口相當(dāng)于C++中的泛型悬而。格式如下:

interface{}

使用示例:

package main

import "fmt"

func main() {
    var i interface{}
    describe(i) // 輸出:(<nil>, <nil>)

    i = 42
    describe(i) // 輸出:(42, int)

    i = "hello"
    describe(i) // 輸出:(hello, string)
}

func describe(i interface{}) {
    fmt.Printf("(%v, %T)\n", i, i) // %v:變量的值,%T:變量的類型
}

2.6. 類型斷言與空接口(泛型)

類型斷言可以判斷變量是否為該類型锭汛。格式如下:

t := i.(T)

若不是笨奠,將報(bào)錯(cuò)中斷程序。如果不想中斷唤殴,則使用如下格式:

t, ok := i.(T)

若不是般婆,則ok值為false

使用示例:

func main() {
    var i interface{} = "hello"

    s := i.(string)
    fmt.Println(s)

    s, ok := i.(string)
    fmt.Println(s, ok)

    f, ok := i.(float64)
    fmt.Println(f, ok)

    f = i.(float64) // panic: interface conversion: interface {} is string, not float64
    fmt.Println(f)
}

實(shí)現(xiàn)一個(gè)類型判斷函數(shù):

func do(i interface{}) {
    switch v := i.(type) {
        case int:
            fmt.Printf("Twice %v is %v\n", v, v*2)
        case string:
            fmt.Printf("%q is %v bytes long\n", v, len(v))
        default:
            fmt.Printf("I don't know about type %T!\n", v)
    }
}

2.7. 自定義輸出格式 - Stringers 接口

Stringers接口定義如下:

type Stringer interface {
    String() string
}

它允許用戶自定義變量打印格式朵逝。示例如下:

type Person struct {
    Name string
    Age  int
}

// type Person implements the interface Stringer
func (p Person) String() string {
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age) // 返回一個(gè)字符串
}

func main() {
    a := Person{"Arthur Dent", 42}
    z := Person{"Zaphod Beeblebrox", 9001}
    fmt.Println(a, z) // 輸出:Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)
}

2.8. 自定義錯(cuò)誤處理 - error 接口

error接口定義如下:

type error interface {
    Error() string
}

使用如下:

import (
    "fmt"
    "time"
)

type MyError struct {
    When time.Time
    What string
}

// type MyError implements the interface error
func (e *MyError) Error() string {
    return fmt.Sprintf("at %v, %s",
        e.When, e.What)
}

// 返回一個(gè) error 接口類型變量
func run() error {
    return &MyError{
        time.Now(),
        "it didn't work",
    }
}

func main() {
    if err := run(); err != nil {
        fmt.Println(err) // at 2009-11-10 23:00:00 +0000 UTC m=+0.000000001, it didn't work
    }
}

3. 嵌入類型 - 繼承

3.1. 聲明

嵌入類型將已有類型直接聲明在新的結(jié)構(gòu)里蔚袍,新的類型被稱為外部類型,被嵌入的類型被稱為內(nèi)部類型配名。如下:

type <類型名> struct {
    內(nèi)部類型1名
    ...
    屬性1名
    ...
}

3.2. 創(chuàng)建

注意:創(chuàng)建時(shí)啤咽,依然需要區(qū)分出內(nèi)部類型晋辆。因?yàn)橥獠款愋陀锌赡軙?huì)覆蓋內(nèi)部類型中的標(biāo)識符抵知。 示例如下:

type user struct {
    name  string
    email string
}

type admin struct {
    user    // 嵌入 user 類型按脚,相當(dāng)于 admin 繼承了 user 類型
    level string
}

func main() {
    // Error: cannot use promoted field user.name in struct literal of type admin
    // Error: cannot use promoted field user.email in struct literal of type admin
    // ad := admin{
    //     name:  "john",
    //     email: "qq.com",
    //     level: "1",
    // }

    // 創(chuàng)建類型,需要區(qū)別出內(nèi)部類型
    ad := admin{
        user: user{
            name:  "john",
            email: "qq.com",
        },
        level: "1",
    }
}

3.2. 繼承屬性和方法

內(nèi)部類型中的標(biāo)識符(屬性和方法)都會(huì)提升到外部類型中授段,就像直接在外部類型中聲明了一樣没陡。

延續(xù)3.1中的例子:

// 可以通過內(nèi)部類型訪問內(nèi)部類型的屬性
fmt.Println(ad.user.name)   // 輸出:john
// 也可以直接訪問內(nèi)部類型的屬性
fmt.Println(ad.name)        // 輸出:john

3.3. 覆蓋屬性和方法

外部類型也可以通過聲明與內(nèi)部類型同名的標(biāo)識符涩哟,來覆蓋內(nèi)部標(biāo)識符的屬性或方法。這樣盼玄,內(nèi)部類型中對應(yīng)的標(biāo)識符將不會(huì)被提升贴彼,但其值依然存在。

示例:

type user struct {
    name  string
    email string
}

type admin struct {
    user
    name string // 覆蓋 user 類型中的 name 屬性
    level string
}

func main() {
    // cannot use promoted field user.name in struct literal of type admin
    // cannot use promoted field user.email in struct literal of type admin
    ad := admin{
        user: user{
            name:  "john",
            email: "qq.com",
        },
        name: "tom",
        level: "1",
    }

    fmt.Println(ad.name)        // 輸出:tom
    fmt.Println(ad.user.name)   // 輸出:john
}

4. 公開或未公開的標(biāo)識符 - 私有與公有

要使用另一個(gè)包中的類型時(shí)埃儿,類型名首字母需要大寫器仗,調(diào)用格式為:<package>.<name>(package為包名,name為類型名)童番。

若要調(diào)用公開類型中的屬性和方法時(shí)精钮,屬性和方法名的首字母也必須是大寫。

示例:

package main

import (
    "fmt"
    "study/my_study/obj" // 導(dǎo)入另一個(gè)包
)

func main()  {
    // Person 為 study/my_study/obj 包中的類型
    boss := obj.Person{
        Name: "aaaaa",
        Email: "123456",
    }

    // 調(diào)用 Person 類型的公開方法
    boss.Notify()
    boss.ChangeEmail("2222")
    fmt.Println(boss)
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末剃斧,一起剝皮案震驚了整個(gè)濱河市轨香,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幼东,老刑警劉巖臂容,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異根蟹,居然都是意外死亡脓杉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門简逮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來球散,“玉大人,你說我怎么就攤上這事散庶〗堆撸” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵督赤,是天一觀的道長嘁灯。 經(jīng)常有香客問我,道長躲舌,這世上最難降的妖魔是什么丑婿? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上羹奉,老公的妹妹穿的比我還像新娘秒旋。我一直安慰自己,他們只是感情好诀拭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布迁筛。 她就那樣靜靜地躺著,像睡著了一般耕挨。 火紅的嫁衣襯著肌膚如雪细卧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天筒占,我揣著相機(jī)與錄音贪庙,去河邊找鬼。 笑死翰苫,一個(gè)胖子當(dāng)著我的面吹牛止邮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播奏窑,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼导披,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了埃唯?” 一聲冷哼從身側(cè)響起撩匕,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎筑凫,沒想到半個(gè)月后滑沧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體并村,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡巍实,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哩牍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棚潦。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖膝昆,靈堂內(nèi)的尸體忽然破棺而出丸边,到底是詐尸還是另有隱情,我是刑警寧澤荚孵,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布妹窖,位于F島的核電站,受9級特大地震影響收叶,放射性物質(zhì)發(fā)生泄漏骄呼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蜓萄。 院中可真熱鬧隅茎,春花似錦、人聲如沸嫉沽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绸硕。三九已至堂竟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間玻佩,已是汗流浹背跃捣。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留夺蛇,地道東北人疚漆。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像刁赦,于是被迫代替她去往敵國和親娶聘。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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