Go語言之結構體,接口和文件操做

一 結構體

1 簡介

1 結構體介紹

Go 語言經過自定義方式造成新的類型,結構體是類型中帶有成員的符合類型,Go語言使用結構體和結構體成員來描述真實世界的實體和實體對應的各類屬性。前端

2 字段

結構體成員是由一系列成員變量構成,這些成員變量稱爲"字段"mysql


字段特徵以下:
1 字段必須有本身的名稱和類型
2 字段名必須惟一
3 字段的類型通常是基本數據類型,數組,也能夠是引用類型,甚至能夠是字段所在的結構體的類型。程序員


Go 語言中不但結構體能夠擁有本身的方法,且每種自定義類型均可以擁有本身的方法 golang

2 定義結構體

1 基本定義

Go語言使用關鍵字type 能夠定義各類類型,包括將各類基本類型定義爲自定義類型,天然也能夠定義結構體 sql

2 基本格式

type   類型名稱 struct {
    字段1  字段1類型
    字段2  字段2類型
}

類型名: 同一個包中不能重複,標識自定義結構體名稱數據庫

struct{}: 標識其類型是結構體類型編程

字段: 表示字段名稱,在該結構體中必須惟一 json

字段類型:該字段的類型,能夠是多種類型數組

3 實例化結構體

結構體定義只是一種內存佈局的描述,只有當結構體實例化後,纔會真正的分配內存,所以必須在定義結構體並進行實例化後才能使用。安全

實例化:根據結構體定義的格式建立一份與格式一致的區域

結構體實例和實例之間內存是獨立的,由於其值類型


1 初始化基本結構體

package main

import "fmt"

type A struct {
    //定義一個結構體,其中包含三個字段
    X int
    Y string
    Z int
}

func main() {
    a := A{X: 10, Y: "234", Z: 10}
    b := A{10, "123", 100}
    c := A{}
    c.X = 100
    c.Y = "789"
    c.Z = 1000
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
}

結果以下

Go語言之結構體,接口和文件操做

2 初始化化匿名結構體

package main

import (
    "fmt"
)

func PrintMsgType(msg *struct {
    //此處定義入參類型爲如此結構體
    id   int
    data string
}) {
    fmt.Printf("其類型爲:%T", msg) // 此處打印其對應的類型
}

func main() {
    msg := struct {
        // 此處使用匿名指針
        id   int
        data string
    }{ // 此處調用該匿名指針,直接進行賦值操做
        10,
        "12234",
    }
    PrintMsgType(&msg)

}

結果以下

Go語言之結構體,接口和文件操做

3 其餘格式的實例化操做

結構體自己是一種類型,其也可經過var 方式聲明並進行實例化,基本以下

package main

import "fmt"

type A struct {
    //定義一個結構體,其中包含三個字段
    X int
    Y string
    Z int
}

func main() {
    var a A  // 實例化結構體
    a.X = 10 //結構體賦值,及其成員訪問是經過"." 點號來訪問的,其賦值方式和普通變量相同
    a.Y = "abcd"
    a.Z = 10
    fmt.Println(a)
    fmt.Printf("結構體實例地址:%p\n", &a) // 取出實例地址
    fmt.Println("第一個參數地址:", &a.X)
    fmt.Println(&a.Y)
    fmt.Println(&a.Z)
}

結果以下

Go語言之結構體,接口和文件操做

結構體注意事項和使用細節
1 結構體的全部字段在內存中都是連續的
2 結構體是用戶單獨定義的類型,和其餘類型進行轉換時須要有徹底相同的字段(名稱,個數和類型一一對應)

4 其餘類型建立和初始化

package main

import "fmt"

type A struct {
    X int               //定義整形數據
    Y string            // 定義字符串類型數據
    Z float64           //定義浮點類型數據
    W []int             //定義切片
    M map[string]string //定義map
    N [3]string         //定義數組
    P *int              // 定義指針
}

func main() {
    var a A
    a.X = 10
    a.Y = "abcd"
    a.Z = 10.0000
    a.W = []int{1, 2, 3, 4}
    a.M = make(map[string]string)
    a.M["a"] = "abcd"
    a.M["b"] = "1234"
    a.N = [3]string{"1", "2", "3"}
    a.P = &a.X

    a.W = append(a.W, 1, 3, 4, 6, 7, )
    fmt.Println(a)
}

結果以下

Go語言之結構體,接口和文件操做

4 指針類型結構體

1 基本初始化

Go語言中,也可以使用new關鍵字對類型進行實例化,結構體在實例化後會造成指針類型的結構體

package main

import "fmt"

type A struct {
    //定義一個結構體,其中包含三個字段
    X int
    Y string
    Z int
}

func main() {
    a := new(A) //此處返回爲一個指針類型,其須要使用*a 來進行取值,其類型爲*A
    a.X = 10
    a.Y = "123"
    a.Z = 20
    fmt.Printf("a的類型爲%T,a的值爲%v\n", a, *a)
    b := new(int) // 其類型爲*int
    fmt.Printf("b的類型爲%T,b的值爲%v\n", b, *b)
}

結果以下

Go語言之結構體,接口和文件操做

2 結構體指針基本使用

package main

import "fmt"

type A struct {
    X int    //定義整形數據
    Y string // 定義字符串類型數據
}

func main() {
    var a A
    a = A{X: 10, Y: "abcd"}
    fmt.Printf("a 的類型爲:%T", a)
    fmt.Println(a)
    var b *A = new(A) //使用值傳遞進行處理
    fmt.Printf("b 的類型爲:%T", b)
    (*b).X = 100 //此處應該使用值方式訪問,其* 表示經過指針獲取其值
    (*b).Y = "1234"
    fmt.Println(*b)
    b.X = 200 // 此處也修改了,其實際上應該是(*b).X ,其是Go語言底層對其進行了優化和配置
    b.Y = "mysql"
    fmt.Println(*b)
    var c = &A{}// 取地址操做,其可視爲對該類型進行一次new的實例化操做。
    fmt.Printf("c 的類型爲:%T", c)
    c.X = 300
    c.Y = "abcd1234"
    fmt.Println(*c)
}

結果以下

Go語言之結構體,接口和文件操做

說明:
其中b和c 方式返回的是結構體指針,其標準的訪問字段形式是(*x.field).Go語言爲了進行簡化,支持結構體指針.字段名,更加符合程序員的使用習慣,Go的底層進行了相關的優化操做

3 取地址實例化應用-工廠函數

某種狀況下,須要調用某些結構體,但又不想對外暴露,此時即可使用工廠模式來解決此種狀況

目錄結構以下

Go語言之結構體,接口和文件操做

test01.go中的配置

package test

type student struct {
    Name  string
    Score float64
}

func NewStudent(n string, s float64) *student {  //此處用於返回一個student的指針類型數據,其被封裝在對應的函數中
    return &student{
        Name:  n,
        Score: s,
    }
}

main.go 調用以下

package main

import (
    "fmt"
    "gocode/project01/test"
)

func main() {
    t1 := test.NewStudent("zhangsan", 100.00)
    fmt.Println(*t1)
}

結果以下

Go語言之結構體,接口和文件操做

5 擴展

1 type重定義

結構體進行type 從新定義(至關於取別名),golang任務是新的數據類型,可是相互之間可強制轉換

package main

import "fmt"

type A struct {
    X int //定義整形數據
}

type B A // 從新定義

func main() {
    var a A
    var b B
    a.X = 10
    b = B(a) // 強制轉換
    fmt.Println(a, b)
}

結果以下

Go語言之結構體,接口和文件操做

2 struct 反射機制

問題:
Json 要處理數據,其必須調用對應的Mashal方法,若結構體的各個字段都是小寫,則其不能被調用,如果大寫,則可能會致使返回給前端爲大寫,不符合規範,此時可以使用tag 進行處理

package main

import (
    "encoding/json"
    "fmt"
)

type A struct {
    X int //定義整形數據
}

func main() {
    var a A
    a.X = 10
    data, err := json.Marshal(a)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("%c", data) //其默認返回的Json數據爲大寫
    }
}

結果以下

Go語言之結構體,接口和文件操做

經過tag 進行處理

package main

import (
    "encoding/json"
    "fmt"
)

type A struct {
    X int `json:"x"` //定義整形數據,定義tag,其中不能有空格
}

func main() {
    var a A
    a.X = 10
    data, err := json.Marshal(a)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("%c", data) //其默認返回的Json數據爲大寫
    }
}

結果以下

Go語言之結構體,接口和文件操做

4 struct 方法及構造函數

1 介紹

Go語言中的方法是一種做用於特定類型變量的函數,這種特定變量叫作接收器

若是將特定類型理解爲結構體或"類"時,接收器的概念就相似於this 或 self

Go語言中,接收器的類型能夠是任何類型,而不只僅是結構體,任何類型均可擁有方法

面嚮對象語言中,類擁有的方法通常被理解爲類能夠作的事情,在Go語言中方法給概念和其餘一致,只是Go語言創建的"接收器"強調方法做用對象是接收器,也就是類實例,而函數是沒有做用對象的

Go語言的類型或結構體的類型沒有構造函數的功能,結構體的初始化過程可使用函數封裝實現

每一個類能夠添加構造函數,多個構造函數使用函數重載實現
構造函數通常與類名同名,且沒有返回值
構造函數有一個靜態構造函數,通常用這個特性來調用父類的構造函數


對於C++來講,還有默認構造函數,拷貝構造函數等

在某些狀況下,咱們須要定義方法,及數據能夠幹什麼操做,所以,自定義的不只僅是類型,還能夠是方法

2 方法的調用和傳參機制原理

說明:方法的調用和傳參機制和函數基本同樣,不同的地方是方法調用,會將調用方法的變量,當作實參傳遞給方法

1 方法的聲明(定義)

func  (receiver  type)  methodName(參數列表)   (返回值列表)  {
        方法體 
                return   返回值 
}
其中receiver和type統稱爲接收器。其類型有指針類型和非指針類型兩種。通常的,非指針類型都有返回值的。

1 type: 表示這個方法和這個type類型進行綁定,或者說方法用於type類型,其type能夠是結構體,也能夠是其餘自定義類型 
2  receive: 就是type 類型的一個變量(實例),其稱爲接收器變量,其在命名時,官方建議使用接收器類型名的第一個小寫字母,而不是self,this之類的名稱。
3 methodName:表示該結構體綁定的方法名稱
4 參數列表: 表示方法的輸入 
5 返回值列表,其可爲多個 
6 方法主體:表示爲了實現某一功能的代碼塊 
7 return: 返回值,非必須

2 基本實例

package main

import "fmt"

type Bag struct {
    items []int
}

func (b Bag) Insert(n int) {  // 非指針類型
    b.items = append(b.items, n)
}

func (b *Bag) Test(n int) {  // 指針類型
    (*b).items = append((*b).items, n)
}

func main() {
    bag := Bag{}
    bag.Insert(10) // 此處調用方法
    bag.Insert(20)   
    fmt.Println(bag)

    bag1 := &Bag{} // 此處也可以使用new(Bag) 進行建立
    bag1.Test(30)  // 此處調用方法
    bag1.Test(40)
    bag1.Test(40)
    fmt.Println(*bag1)
}

結果以下

Go語言之結構體,接口和文件操做

3 指針類型和非指針類型接收器的使用

在計算機中,小對象因爲值複製時很快,所以適合非指針接收器,大對象由於複製性能較低,適用於指針接收器,在接收器和參數之間傳遞時不進行復制,只進行指針傳遞

具體的結構體的方法其第一個字段只有結構體

package main

import (
    "fmt"
)

type A struct {
    X int `json:"x"` //定義整形數據,定義tag,其中不能有空格
}

func (a A) Test() { //定義方法,其中a 表示A的形參,後面的Test表示值
    a.X += a.X
    fmt.Println(a.X)
}

func (a A) Test1(n1 int) int { //此中可在對應方法中傳值,更加具體的描述其方法的靈活性
    return a.X + n1
}

func main() {
    a := A{10}
    a.Test()
    res := a.Test1(100)
    fmt.Println(res)
}

結果以下

Go語言之結構體,接口和文件操做

4 方法注意事項和細節

1 結構體類型是值類型,在方法調用過程當中,遵循值傳遞機制,是值拷貝傳遞方式
2 若程序員但願在訪問中修改結構體變量的值,可經過結構體指針方式來完成
3 golang中方法做用在指定的數據類型上,及和數據類型綁定,只要是數據類型,都可綁定方法,而不必定是struct
4 方法的訪問訪問控制規則和函數同樣,方法首字母小寫,只能在本包中使用,方法首字母大寫,可在本包和其餘包中訪問使用
5 若是一個變量實現了string()這個方法,則fmt.Println() 會默認調用這個變量的string() 進行輸出

5 方法和函數的區別

1 調用方式不一樣

函數的調用方式  函數名(實參列表)
   方法的調用方式  變量.方法名(實參列表)

2 傳遞實參

對於普通函數,接受者爲值類型,不能將指針類型的數據直接傳遞,反之亦然,對於方法,接受者爲值類型,可以使用指針類型的變量調用方法,反之也能夠
package main

import "fmt"

type User struct {
    Name string `json:"name"` //定義整形數據,定義tag,其中不能有空格
    Age  int    `json:"age"`
}

func (user *User) Test() {
    fmt.Printf("user 的類型爲:%T,其對應的值爲:%v\n", user, *user)
}

func (user User) Test1() {
    fmt.Printf("user 的類型爲:%T,其對應的值爲:%v\n", user, user)
}
func main() {
    user := User{"golang", 13}
    user.Test()
    user.Test1()
}

結果以下

Go語言之結構體,接口和文件操做

3 總結

無論是任何形式,真正決定是值拷貝仍是地址包括的,是看方法是哪一種類型的綁定,如果值類型綁定(user User) 指針類型綁定(user *User),則是指針拷貝。

二 面向對象編程

1 基本介紹

1 Go 語言面向對象編程和其餘語言區別

1 golang 也支持面向對象編程(OOP),但和傳統的面向對象編程有區別,其並非純粹的面嚮對象語言,因此說golang支持面向對象編程特性是比較準確的

2 golang沒有class,golang語言結構體(struct)和其餘語言的class有同等的地位,可理解爲使用struct實現了OOP特性

3 golang面向對象很是簡潔,去掉了傳統的繼承,重載,構造和析構,隱藏了this和self等

4 golang仍然有面向對象編程的繼承,封裝和多態,只是其實現方式和其餘OOP語言不一樣,如繼承golang沒有extends關鍵字,繼承法是經過匿名字段來實現的

5 golang面向對象很優雅,OOP自己就是語言系統的一部分,經過接口interface實現,耦合性第,也很是靈活

golang仍然有面向對象編程的繼承,封裝和多態,只是實現方式和其餘COP語言不一樣

2 面向對象編程---封裝

1 簡介

封裝就是把抽象出的字段和堆字段的操做封裝在一塊兒,數據被保護在內部,程序的其餘包只能經過被受權的操做,才能實現對字段的操做

2 封裝實現的步驟

1 將結構體字段的首字母大寫
2 給結構體所在的包提供一個工廠模式的函數,首字母大寫,相似於一個構造函數
3 提供一個首字母大寫的Set方法,用於對屬性判斷和賦值
4 提供一個Get方法,用於獲取屬性的值

3 封裝的好處

1 隱藏實現細節
2 可對數據進行驗證,保證安全合理

4 如何體現封裝

1 對結構體中的方法進行封裝

2 經過方法,包實現封裝

3 面向對象--繼承

1 簡介

類型內嵌和結構體內嵌

結構體容許其成員字段在聲明時沒有字段名而只有類型,這種形式被稱爲類型內嵌或者匿名字段類型內嵌


寫法以下

package main

import "fmt"

type Data struct {
    int //定義匿名字段
    float32
    bool
}

func main() {
    ins := &Data{
        10,
        3.14,
        true,
    }
    fmt.Println(*ins)
    ins1 := Data{
        10,
        20.0,
        false,
    }
    fmt.Println(ins1)
    ins2 := new(Data)
    ins2.float32 = 30.20 // 類型內嵌其實也有本身的字段名,只是字段名就是其類型自己,結構體要求字段名必須惟一,所以結構體中同種類型的匿名字段只能有一個
    ins2.int = 10
    ins2.bool = false
    fmt.Println(*ins2)
}

結果以下

Go語言之結構體,接口和文件操做

2 繼承做用和實例

繼承可解決代碼複用的問題
當多個結構體存在相同的屬性和方法時,能夠從這些結構體中抽象出基礎結構體,該結構體中定義這些相同的屬性和方法,其餘的結構體不須要從新定義該屬性和方法,只須要嵌套基本結構體便可,也就是說,在golang中,若是一個struct嵌套了另外一個匿名結構體,那麼這個結構體能夠直接訪問匿名結構體中的字段和方法,從而實現了繼承的特性


代碼以下

package main

import "fmt"

type Person struct {
    //老師和學生都是人類
    Name string
    Age  int
}

type Teacher struct {
    //老師具備對應的職業
    Person
    Occupation string
}

type Student struct {
    //學生具備對應的座位號
    Person
    Seat_Number map[int]int
}

func main() {
    student := Student{}
    student.Name = "小旺"
    student.Age = 12
    student.Seat_Number = make(map[int]int)
    student.Seat_Number[10] = 20
    teacher := Teacher{}
    teacher.Name = "王老師"
    teacher.Age = 30
    teacher.Occupation = "teacher"

    fmt.Println(student)
    fmt.Println(teacher)

}

結果以下

Go語言之結構體,接口和文件操做

3 繼承討論

1 結構體可使用嵌套匿名結構體複用字段和方法
2 當結構體和匿名結構體具備相同的字段或方法時,則採用就近原則,及個該實例最接近的結構體的屬性或方法就是其返回的結果的條件

package main

import "fmt"

type Person struct {
    //老師和學生都是人類
    Name string
    Age  int
}

type Teacher struct {
    //老師具備對應的職業
    Person
    Occupation string
}

type Student struct {
    //學生具備對應的座位號
    Person
    Seat_Number map[int]int
}

func (p Person) Get() {
    fmt.Printf("名字爲:%s的年齡爲:%d\n", p.Name, p.Age)
}

func (s Student) Get() {
    fmt.Printf("此方法只針對學生,名字爲:%s的年齡爲:%d\n", s.Name, s.Age)

}
func main() {
    student := Student{}
    student.Name = "小旺"
    student.Age = 12
    student.Seat_Number = make(map[int]int)
    student.Seat_Number[10] = 20
    teacher := Teacher{}
    teacher.Name = "王老師"
    teacher.Age = 30
    teacher.Occupation = "teacher"

    fmt.Println(student)
    fmt.Println(teacher)

    student.Get()
    teacher.Get()
}

結果以下

Go語言之結構體,接口和文件操做

3 結構體嵌套入兩個匿名結構體時,若兩個匿名結構體具備相同的字段和方法,則在訪問時,就必須指明匿名結構體名稱,不然會出現編譯錯誤

Go語言之結構體,接口和文件操做

package main

import (
    "fmt"
)

type Person struct {
    Name string
    Age  int
}
type Teacher struct {
    Name string
    Age  int
}
type Student struct {
    //此結構體繼承了上面兩個結構體
    Person
    Teacher
}

func (p Person) Get() {
    fmt.Printf("名字爲:%s的年齡爲:%d\n", p.Name, p.Age)
}

func (s Teacher) Get() {
    fmt.Printf("此方法只針對學生,名字爲:%s的年齡爲:%d\n", s.Name, s.Age)

}
func main() {
    student := Student{}
    student.Teacher.Name = "小王"
    student.Teacher.Age = 20
    student.Person.Get()
    student.Teacher.Get()
}

結果以下

Go語言之結構體,接口和文件操做

4 若是一個struct嵌套了一個有名稱的結構體,此稱爲組合,若組合關聯,那麼在訪問結構體字段或方法時,就必須有結構體的名稱
5 嵌套名結構體後,也能夠建立結構體變量時,直接指定各個匿名結構體字段的值

4 面向對象編程之多態

變量(實例)具備多種形態,面向對象的第三大特徵,在Go語言中,多態特徵是經過接口實現的,能夠按照統一的接口來調用不一樣的實現,這時接口就會呈現不一樣的形態

三 接口(interface)

1 基本介紹

interface 類型能夠定義一組方法,可是這些不須要實現,而且interface不能包含任何變量,到某個自定義類型的時候要使用,根據具體狀況將這些方法寫出來,接口自己調用方法和實現均須要遵照一種協議,你們按照統一的方法來命名參數類型和數量來協調邏輯處理的過程


Go語言中使用組合實現獨享特性的描述,對象的內部使用結構體內嵌組合對象應該具備的特性,對外經過接口暴露能使用的特性。

2 聲明接口

接口是雙方約定的一種合做協議,是一種類型,也是一種抽象結構,不會暴露全部含數據的格式,類型及結構

1 基本語法

type  接口名稱   interface  {
                method1 (參數列表)  返回值列表
                method2 (參數列表)  返回值列表
}

func (t 自定義類型)  method1(參數列表)  返回值列表  {
        方法體 
        return  返回數據
}

接口名稱: 使用type將接口定義爲自定義的類型名,Go語言的接口命名時,通常在單詞後面加上er,如寫操做叫作writer,關閉叫作closer等

method1: 當方法名首字母是大寫時,且這個接口類型名首字母也是大寫時,這個方法能夠被接口所在的包(package)以外的代碼訪問

參數列表,返回值列表:參數列表和返回值列表中的參數變量可被忽略

2 小結

1 接口中全部的方法都沒方法體,及接口的方法都是沒實現的方法,接口體現了程序設計的多態和高內聚低耦合的思想

2 golang中的接口,不須要顯示實現,只要一個變量,含有接口類型中的全部方法,那麼這個變量就實現了這個接口,所以,golang中沒有implement這樣的關鍵字

2 接口實現的條件

接口定義後,須要實現調用接口,調用方能正確編譯並經過使用接口,接口的實現須要遵循兩條規則才能讓接口可用。

1 接口被實現的條件一

接口的方法與實現接口的類型方法一致,及在類型中添加與接口簽名一致的方法就能夠實現該方法,簽名包括方法中的名稱、參數列表、返回值列表,其實現接口中方法的名稱,參數列表,返回參數列表中的任意一項和接口要實現的方法不一致,那麼就扣的這個方法就不能被實現。

package main

import "fmt"

type DataWriter interface {
    WriteData(data interface{}) error
}
type file struct {
}

func (d *file) WriteData(data interface{}) error {
    fmt.Println("WriteData:", data)
    return nil
}

func main() {
    f := new(file) //實例化file

    var write DataWriter // 聲明一個接口,用於讀取數據
    write = f            //將接口賦值f,也就是file類型,及關聯接口和實例,雖然其變量類型不一致,但writer是一個接口,且f已經徹底實現了DataWriter()的全部方法,所以賦值成功

    write.WriteData("data")
}

結果以下

Go語言之結構體,接口和文件操做

2 接口中的全部方法均被實現

當一個接口有多個方法時,只有這些方法都被實現了,接口才能被正確編譯並使用

Go 語言實現的接口是隱式的,無需讓實現接口的類型寫出實現了那些接口,這種設計被稱爲非侵入式設計。

package main

import (
    "fmt"
)

type Test interface {
    Start()
    Stop()
}

type A struct {
    X string
    Y int
}
type B struct {
    A
}

type C struct {
}

func (a A) Start() {
    fmt.Println(a.X)
}

func (a A) Stop() {
    fmt.Println(a.Y)
}

func (b B) Start() {
    fmt.Println(b.X)
}

func (b B) Stop() {
    fmt.Println(b.Y)
}

func (c C) Usb(usb Test) {
    usb.Start()
    usb.Stop()
}

func main() {
    a := A{"golang", 20}
    b := B{}
    b.X = "goland"
    b.Y = 10
    c := C{}
    //接口調用對應實例a
    c.Usb(a)
    fmt.Println("-----------------------------")
    c.Usb(b)
}

結果以下

Go語言之結構體,接口和文件操做

3 類型和接口的關係

1 一個類型能夠實現多個接口

package main

import (
    "fmt"
)

type DataWriter interface {
    WriteData(data interface{}) error
}

type DataRead interface {
    ReadData(data interface{}) error
}

type file struct {
}

// 定義實現該方法的結構體
func (d *file) WriteData(data interface{}) error {
    fmt.Println("WriteData:", data)
    return nil
}

func (d *file) ReadData(data interface{}) error {
    fmt.Println("READ DATA", data)
    return nil
}

func main() {
    f := new(file) //實例化file

    var write DataWriter // 聲明一個接口,用於讀取數據

    write = f //將接口賦值f,也就是file類型,及關聯接口和實例,雖然其變量類型不一致,但writer是一個接口,且f已經徹底實現了DataWriter()的全部方法,所以賦值成功
    write.WriteData("write data")
    var read DataRead
    //
    read = f
    read.ReadData("read data")
}

上述中file類型實現了DdataWriter 和 DataRead 的兩個接口,這兩個接口之間沒聯繫。

2 多個類型可實現相同的接口

一個接口的方法,不必定須要由一個類型徹底實現,接口的方法能夠經過在類型中嵌套其餘類型或結構體實現,也就是說,使用者並不關係某個接口的方法是經過一個類型徹底實現的,仍是經過多個結構嵌套到一個結構體中拼湊起來共同實現的。

package main

import (
    "fmt"
)

type DataWriter interface {
    WriteData(data interface{}) error
    ReadData(data interface{}) error
}

type fileread struct {
}

type file struct {
    fileread //此類型包含此類型
}

// 定義實現該方法的結構體
func (d *file) WriteData(data interface{}) error {
    fmt.Println("WriteData:", data)
    return nil
}

func (d *fileread) ReadData(data interface{}) error { // 此類型實現了此方法
    fmt.Println("READ DATA", data)
    return nil
}

func main() {
    f := new(file) //實例化file

    var write DataWriter // 聲明一個接口,用於讀取數據

    write = f //將接口賦值f,也就是file類型,及關聯接口和實例,雖然其變量類型不一致,但writer是一個接口,且f已經徹底實現了DataWriter()的全部方法,所以賦值成功
    write.WriteData("write data")
    write.ReadData("read  data")
}

結果以下

Go語言之結構體,接口和文件操做

4 接口的嵌套組合

在Go語言中,不只結構體體和結構體體之間能夠嵌套,接口和接口之間也能夠經過創造出新的接口

接口與接口嵌套組合而造成了新接口,只要接口的全部方法都被實現,則這個接口中全部嵌套接口的方法都可以被調用

package main

import (
    "fmt"
)

type Data interface {
    DataWriter
    DataRead
}
type DataWriter interface {
    WriteData(data interface{}) error
}
type DataRead interface {
    ReadData(data interface{}) error
}

type file struct {
}

// 定義實現該方法的結構體
func (d *file) WriteData(data interface{}) error {
    fmt.Println("WriteData:", data)
    return nil
}

func (d *file) ReadData(data interface{}) error { // 此類型實現了此方法
    fmt.Println("READ DATA", data)
    return nil
}

func main() {
    f := new(file) //實例化file

    var data Data // 聲明一個接口,用於讀取數據

    data = f //將接口賦值f,也就是file類型,及關聯接口和實例,雖然其變量類型不一致,但writer是一個接口,且f已經徹底實現了DataWriter()的全部方法,所以賦值成功
    data.WriteData("write data")
    data.ReadData("read  data")
}

結果以下

Go語言之結構體,接口和文件操做

5 定義接口和注意細節

1 接口自己不能建立實例,可是能夠指向一個實現了接口的自定義類型的變量
2 接口中全部的方法都沒有方法體,及沒實現該方法
3 在golang中,一個自定義類型須要將某個接口的全部方法倒都實現,咱們才說這個自定義接口實現了該方法
4 一個自定義類型只有實現了某個接口,才能將該自定義類型賦值給接口類型
5 只要是自定義類型,均可以實現接口,而不只僅是結構體
6 一個自定義類型能夠實現多個接口
7 golang接口中不能有任何變量
8 一個接口能夠繼承多個個別接口,這時若是要實現接口A,則必須實現接口B和接口C
9 interface 類型默認是一個指針,若是沒有對interface初始化就使用,則會出現輸出nil
10 空接口interface{} 沒有實現任何方法,因此全部類型都實現了空接口

6 應用

1 基本排序處理方式

package main

import (
    "fmt"
    "math/rand"
    "sort"
    "time"
)

func Test(n int) []int {
    var l1 []int
    for i := 1; i <= n; i++ {
        rand.NewSource(time.Now().UnixNano())
        num := rand.Intn(500)
        l1 = append(l1, num)
    }
    return l1
}

func main() {
    var l1 []int
    l1 = Test(10)
    fmt.Println("排序前的值:", l1)
    sort.Ints(l1)
    fmt.Println("排序後的值:", l1)
}

結果以下

Go語言之結構體,接口和文件操做

2 經過接口實現

官網得知,欲使用Sort接口,需實現三個方法

Go語言之結構體,接口和文件操做

Len() 獲取數據長度,返回爲int 類型數據
Less() 元素比大小,經過i,j 進行比較返回bool
Swap() 元素交換,經過i,j 交換來處理元素


具體代碼實現以下

package main

import (
    "fmt"
    "math/rand"
    "sort"
    "time"
)

// 此處用於構造元數據
type Base struct {
    Name string
    Age  int
}

type SliceBase []Base //此方法需實現對應的interface規定的功能,其才能調用具體的interface sort.Sort()接口

// 此函數用於返回列表,其列表中的元素是上述struct中的元素

func CreateSlice(n int, base SliceBase) []Base {
    for i := 1; i <= n; i++ {
        rand.NewSource(time.Now().UnixNano())
        num := rand.Intn(100)
        value := Base{"goland" + string(num), num}
        base = append(base, value)
    }
    return base
}

func (t SliceBase) Len() int {
    return len(t) //此處返回對應類型長度便可
}

func (t SliceBase) Less(i, j int) bool {
    if t[i].Age > t[j].Age { //此處表示降序排列,且此處爲age的比較
        return true
    } else {
        return false
    }
}

func (t SliceBase) Swap(i, j int) {
    t[i], t[j] = t[j], t[i] //此處標識交換
}
func main() {
    var l1 SliceBase
    l1 = CreateSlice(10, l1)
    fmt.Println("排序前的值:", l1)
    sort.Sort(l1)
    fmt.Println("排序後的值:", l1)
}

結果以下

Go語言之結構體,接口和文件操做

7 類型斷言

Go 語言使用接口端來將接口轉換成另外一個接口,也能夠將接口轉換成另一種類型。

1 類型斷言的格式

t:=i.(T)

i 表明接口變量
T 表明轉換的目標類型
t 表明轉換後變量

若是i 沒有徹底實現T接口的方法,此語句將會致使觸發宕機,所以可經過 t,ok:=i.(T),這種寫法下,若是發生接口未實現,則將會把ok置爲false,t置爲T類型的值爲0,ok的含義是i接口是否實現T類型的結果。

2 基本使用場景

應用場景:因爲接口是通常類型,不知道具體類型,若是要轉換成具體類型,就須要斷言

package main

import "fmt"

// 此處用於構造元數據

func main() {
    var a interface{}
    var f float64 = 10.00
    a = f
    if y, ok := a.(float64); ok {
        fmt.Println(y)
    } else {
        fmt.Println("失敗")
    }
}

結果以下

Go語言之結構體,接口和文件操做

package main

import "fmt"

// 此處用於構造元數據
type Base struct {
    Name string
    Age  int
}

func main() {
    var a interface{}
    base := Base{"golang", 30}
    a = base
    var b Base
    b = a.(Base) //此處若不使用斷言,則會報錯
    fmt.Println(b)
}

Go語言之結構體,接口和文件操做

在進行斷言時,須要確保類型匹配,不然會報錯

可進行處理,不報panic

3 綜合應用

package main

import (
    "fmt"
)

type DataWriter interface {
    WriteData(data interface{}) error
}
type DatawRead interface {
    ReadData(data interface{}) error
}

type readwritefile struct {
}

// 定義實現該方法的結構體
func (d *readwritefile) WriteData(data interface{}) error {
    fmt.Println("WriteData:", data)
    return nil
}

func (d *readwritefile) ReadData(data interface{}) error { // 此類型實現了此方法
    fmt.Println("READ DATA", data)
    return nil
}

type readfile struct {
}

func (d *readfile) ReadData(data interface{}) error { // 此類型實現了此方法
    fmt.Println("read dta:", data)
    return nil
}

func main() {
    //建立實例
    file := map[string]interface{}{
        "readfile":      new(readfile),
        "readwritefile": new(readwritefile),
    }
    //遍歷映射

    for name, obj := range file {
        fmt.Println("-------------------")

        rw, ok := obj.(DataWriter)
        if ok {
            rw.WriteData("write data")
            fmt.Println(name)
        }
        r, ok := obj.(DatawRead)
        if ok {
            r.ReadData("read  data")
            fmt.Println(name)
        }
    }

}

結果以下

Go語言之結構體,接口和文件操做

8 空接口類型 interface{}

1 簡介

空接口是接口類型的特殊形式,空接口沒有任何方法,所以任何類型都無需實現空接口,從實現的角度看,任何值都知足這個接口的需求,所以空接口類型可保存任何值,也能夠從空接口中取出原值。


空接口的內部實現了保存對象的類型和指針,使用空接口保存一個數據的過程比直接使用數據對應類型的變量保存稍慢。

package main

import "fmt"

func main() {
    var str interface{}
    str = 1
    fmt.Println(str)
    str = "hello"
    fmt.Println(str)
    str = false
    fmt.Println(str)
}

結果以下

Go語言之結構體,接口和文件操做

2 空接口獲取值

package main

import "fmt"

func main() {
    var a int = 1
    var i interface{} = a
    fmt.Println(i)
    var b int = i
    fmt.Println(b)
}

結果以下

Go語言之結構體,接口和文件操做

編譯器返回接口,不能將i 變量視爲int類型賦值給b。
修改結果以下

package main

import "fmt"

func main() {
    var a int = 1
    var i interface{} = a
    fmt.Println(i)
    var b int = i.(int)
    fmt.Println(b)
}

結果以下

Go語言之結構體,接口和文件操做

3 空接口值的比較

空接口在保存不一樣的值後,能夠和其餘變量值同樣使用"==" 進行比較操做,空接口比較有如下幾種特性

1 類型不一樣的空接口之間的比較結果不一樣

package main

import "fmt"

func main() {
    var a interface{} = 100
    var b interface{} = "abc"
    fmt.Println(a == b)

}

結果以下

Go語言之結構體,接口和文件操做

2 不能比較空接口中的動態值

package main

import "fmt"

func main() {
    var c interface{} = []int{10}
    var d interface{} = []int{20}
    fmt.Println(c == d)
}

結果以下

Go語言之結構體,接口和文件操做

9 接口和結構體 優缺點

1 接口的優勢

1 結構體實現繼承過程當中不能選擇性繼承,而當基類結構體須要其餘擴展功能不被子類繼承時,則須要使用接口來補充
2 接口比繼承更靈活 接口必定程度上實現了代碼的解耦

2 接口和繼承解決的問題

1 繼承的價值在於解決代碼中的複用性和可維護性問題
2 接口的價值在於設計好各類規範,讓其餘自定義類型實現這些方法

接口提現的多態特性
1 多態參數
2 多態數組

四 文件介紹

1 簡介

文件,數據源,其主要做用就是保存數據,其就是數據源
文件在程序中以流的方式操做的
流:數據在數據源和程序之間經歷的路徑
輸入流:從數據源到程序的路徑,讀取文件
輸出流:從程序到數據源的路徑,寫文件

2 基本打開文件操做

1 os.Open

1 基本形式

func Open(name string) (file *File, err error)

Open打開一個文件用於讀取。若是操做成功,返回的文件對象的方法可用於讀取數據;對應的文件描述符具備O_RDONLY模式。若是出錯,錯誤底層類型是*PathError。

2 os.openFile

1 基本形式

func OpenFile(name string, flag int, perm FileMode) (*File, error) {
    testlog.Open(name)
    f, err := openFileNolog(name, flag, perm)
    if err != nil {
        return nil, err
    }
    f.appendMode = flag&O_APPEND != 0

    return f, nil
}

2 相關解析以下

name :表示文件路徑

flag:表示文件打開模式

const (

 O_RDONLY int = syscall.O_RDONLY // 只讀模式打開文件
    O_WRONLY int = syscall.O_WRONLY // 只寫模式打開文件
    O_RDWR   int = syscall.O_RDWR   // 讀寫模式打開文件
    O_APPEND int = syscall.O_APPEND // 寫操做時將數據附加到文件尾部
    O_CREATE int = syscall.O_CREAT  // 若是不存在將建立一個新文件
    O_EXCL   int = syscall.O_EXCL   // 和O_CREATE配合使用,文件必須不存在
    O_SYNC   int = syscall.O_SYNC   // 打開文件用於同步I/O
    O_TRUNC  int = syscall.O_TRUNC  // 若是可能,打開時清空文件
)

perm FileMode:解析結果以下

const (
    // 單字符是被String方法用於格式化的屬性縮寫。
    ModeDir        FileMode = 1 << (32 - 1 - iota) // d: 目錄
    ModeAppend                                     // a: 只能寫入,且只能寫入到末尾
    ModeExclusive                                  // l: 用於執行
    ModeTemporary                                  // T: 臨時文件(非備份文件)
    ModeSymlink                                    // L: 符號連接(不是快捷方式文件)
    ModeDevice                                     // D: 設備
    ModeNamedPipe                                  // p: 命名管道(FIFO)
    ModeSocket                                     // S: Unix域socket
    ModeSetuid                                     // u: 表示文件具備其建立者用戶id權限
    ModeSetgid                                     // g: 表示文件具備其建立者組id的權限
    ModeCharDevice                                 // c: 字符設備,需已設置ModeDevice
    ModeSticky                                     // t: 只有root/建立者能刪除/移動文件
    // 覆蓋全部類型位(用於經過&獲取類型位),對普通文件,全部這些位都不該被設置
    ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice
    ModePerm FileMode = 0777 // 覆蓋全部Unix權限位(用於經過&獲取類型位)
)

3 文件讀取操做

1 不帶緩衝的文件讀取ioutil.ReadFile

func ReadFile(filename string) ([]byte, error) {  
    f, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer f.Close()
package main

import (
    "fmt"
    "io/ioutil"
)

func main() {

    str := "D:\\go\\project\\src\\gocode\\project01\\test\\test01.go"
    l1, err := ioutil.ReadFile(str)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("%s", l1) //此處輸出類型爲字符串
    }
}

結果和上述相同

2 帶緩衝的文件讀取bufio

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
)

func main() {

    str := "D:\\go\\project\\src\\gocode\\project01\\test\\test01.go"
    file, err := os.Open(str) //此處獲取到一個file句柄,一個err錯誤,如有錯誤返回,則此處不爲nil
    if err != nil {
        fmt.Println(err)
    } else {
        reader := bufio.NewReader(file) // 此處用於生成一個緩衝,默認是4096,其讀取數據選擇部分讀取
        for {
            str, err := reader.ReadString('\n') // 此處是經過\n進行分割的
            if err == io.EOF { // 此處如果io.EOF,則代表其進入文件底部
                break
            }
            fmt.Println(str)
        }
    }
    defer file.Close()
}

結果以下

Go語言之結構體,接口和文件操做

3 寫入操做

1 基本寫操做 file.WriteString

package main

import (
    "fmt"
    "os"
)

func main() {

    str := "D:\\go\\project\\src\\gocode\\project01\\test\\test02.txt"

    file, err := os.OpenFile(str, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0640)
    if err != nil {
        fmt.Println(err)
    } else {
        _, err := file.WriteString(  
            `package test
type student struct {
    Name  string
    Score float64
}
func NewStudent(n string, s float64) *student {
    return &student{
    Name:  n,
    Score: s,
}
}`)
        if err == nil {
            fmt.Println("數據寫入成功")
        } else {
            fmt.Println("數據寫入失敗")
        }
    }
    defer file.Close()
}

結果以下

Go語言之結構體,接口和文件操做

2 帶緩衝的數據寫入

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {

    str := "D:\\go\\project\\src\\gocode\\project01\\test\\test02.txt"

    file, err := os.OpenFile(str, os.O_CREATE|os.O_WRONLY, 0777)
    if err != nil {
        fmt.Println(err)
        return
    } else {
        writer := bufio.NewWriter(file)
        for i := 'a'; i <= 'z'; i++ {
            _, err := writer.WriteString("mysql" + string(i) + "\n")
            if err != nil {
                fmt.Println(err)
            }
        }
        err := writer.Flush()
        if err != nil {
            fmt.Println(err)
        } else {
            fmt.Println("刷新成功")
        }
    }
}

結果以下

Go語言之結構體,接口和文件操做

4 文件複製操做

1 基本形式複製文件

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {

    str1 := "D:\\go\\project\\src\\gocode\\project01\\test\\test02.txt"
    str2 := "D:\\go\\project\\src\\gocode\\project01\\test\\test03.txt"
    data, err := ioutil.ReadFile(str1) //此處使用讀文件,而後再寫文件的方式完成對應操做
    if err != nil {
        fmt.Println("read file error", err)
        return
    }
    err = ioutil.WriteFile(str2, data, 0666)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("文件寫入成功")
    }
}

結果以下

Go語言之結構體,接口和文件操做

2 Copy 方式複製文件

func Copy(dst Writer, src Reader) (written int64, err error)

將src的數據拷貝到dst,直到在src上到達EOF或發生錯誤。返回拷貝的字節數和遇到的第一個錯誤。
對成功的調用,返回值err爲nil而非EOF,由於Copy定義爲從src讀取直到EOF,它不會將讀取到EOF視爲應報告的錯誤。若是src實現了WriterTo接口,本函數會調用src.WriteTo(dst)進行拷貝;不然若是dst實現了ReaderFrom接口,本函數會調用dst.ReadFrom(src)進行拷貝。

Go語言之結構體,接口和文件操做

package main

import (
    "fmt"
    "io"
    "os"
)

func CopyTest(srcfilepath, desfilepath string) (n int64, err error) {
    srcfile, err := os.Open(srcfilepath)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer srcfile.Close()
    desfile, err := os.Create(desfilepath)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer desfile.Close()
    return io.Copy(desfile, srcfile)
}
func main() {

    srcfilepath := "D:\\go\\project\\src\\gocode\\project01\\test\\test02.txt"
    desfilepath := "D:\\go\\project\\src\\gocode\\project01\\test\\test04.txt"

    n, err := CopyTest(srcfilepath, desfilepath)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("成功", n)
    }
}

結果以下

Go語言之結構體,接口和文件操做

結果以下

Go語言之結構體,接口和文件操做

5 判斷文件是否存在

golang判斷文件或文件夾是否存在的方法是使用os.Stat() 函數返回錯誤進行判斷

1 若返回錯誤爲nil,代表文件或文件夾存在
2 若返回類型使用os.IsNotExist() 判斷爲true,則代表文件或文件夾不存在
3 若返回錯誤爲其餘類型,則不肯定是否存在

func PathExists(path string) (bool, error) {
    _, err := os.Stat(path) //此處返回錯誤爲nil,則表示存在文件或文件夾
    if err == nil {
        return true, nil
    }
    if os.IsNotExist(err) { //若此處返回使用此判斷爲true,則表示爲不存在
        return false, nil
    }
    return false, nil
}

6 golang 命令行參數

1 os.Args 命令處理

os.Args 是一個string的切片,用來存儲全部的命令行參數

package main

import (
    "fmt"
    "os"
)

func main() {

    param1 := os.Args[1]  // 此處表示獲取命令行的第一個參數
    param2 := os.Args[2]  // 此處表示獲取命令行的第二個參數
    fmt.Printf("第一個參數爲:%s,第二個參數爲:%s", param1, param2)

}

2 flag 機制

flag包用來解析命令行參數

說明: 前面的方式比較原生,對解析參數不方便,特別是帶有指定參數形式的命令行

如 mysql.exe -p 5000 -u root -proot -h localhost ,對於此種形式,Go語言提供了flag包,能夠方便解析命令行參數,並且其順序書寫也能夠隨意。


相關代碼以下

package main

import (
    "flag"
    "fmt"
)

var (
    user     string
    password string
    port     int
    hostip   string
    db       string
)

func main() {
    flag.StringVar(&user, "u", "", "用戶名,默認爲空")
    flag.StringVar(&password, "p", "", "密碼,默認爲空")
    flag.IntVar(&port, "P", 3306, "端口,默認爲3306")
    flag.StringVar(&hostip, "h", "localhost", "遠端IP地址,默認爲localhost")
    flag.StringVar(&db, "db", "test", "數據庫,默認爲test")
    flag.Parse()  // 此處表示方法轉換,必須存在
    fmt.Printf("user=%v,password=%v,port=%v,hostip=%v,db=%v\n", user, password, port, hostip, db)

}

結果以下

Go語言之結構體,接口和文件操做

五 Json簡介

1 簡介

介紹詳情見: http://www.javashuo.com/article/p-nzztpswu-kw.html

2 JSON 序列化操做

package main

import (
    "encoding/json"
    "fmt"
)

type A struct {
    Name string  `json:"name"`
    Age  int     `json:"age"`
    Sal  float32 `json:"sal"`
}

// 結構體反序列化操做
func SerA() {
    a := A{
        Name: "golang",
        Age:  13,
        Sal:  30.00,
    }

    data, err := json.Marshal(a)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("%s\n", data)
    }
}

//Map 序列化

func SerM() {
    m := make(map[string]interface{})
    m["name"] = "golang"
    m["age"] = 40
    m["sal"] = 100.0
    data, err := json.Marshal(m)
    if err != nil {
        fmt.Println(err)
        return
    } else {
        fmt.Printf("%s\n", data)
    }
}

//切片序列化
func SerS() {
    var l1 []map[string]interface{}
    m := make(map[string]interface{})
    m["name"] = "golang"
    m["age"] = 40
    m["sal"] = 100.0
    m1 := make(map[string]interface{})
    m1["name"] = "goland"
    m1["age"] = 10
    m1["sal"] = 200.0
    l1 = append(l1, m)
    l1 = append(l1, m1)
    data, err := json.Marshal(l1)
    if err != nil {
        fmt.Println(err)
        return
    } else {
        fmt.Printf("%s\n", data)
    }
}

func main() {
    SerA()
    SerM()
    SerS()

}

結果以下

Go語言之結構體,接口和文件操做

3 JSON 反序列化

將Json 字符串反序列化成對應的數據類型

package main

import (
    "encoding/json"
    "fmt"
)

type A struct {
    Name string  `json:"name"`
    Age  int     `json:"age"`
    Sal  float32 `json:"sal"`
}

// 結構體反序列化操做
func SerA() []byte {
    a := A{
        Name: "golang",
        Age:  13,
        Sal:  30.00,
    }

    data, err := json.Marshal(a)
    if err != nil {
        fmt.Println(err)
    } else {
    }
    return data
}

//Map 序列化

func SerM() []byte {
    m := make(map[string]interface{})
    m["name"] = "golang"
    m["age"] = 40
    m["sal"] = 100.0
    data, err := json.Marshal(m)
    if err != nil {
        fmt.Println(err)
    } else {
    }
    return data
}

func DesA(by []byte) A {
    a := A{}
    err := json.Unmarshal(by, &a)
    if err != nil {
        fmt.Println(err)
    } else {
    }
    return a
}

func DesM(by []byte) map[string]interface{} {
    m := make(map[string]interface{})
    err := json.Unmarshal(by, &m)
    if err != nil {
        fmt.Println(err)
    } else {
    }
    return m
}

func main() {
    b := SerA()
    b1 := DesA(b)
    fmt.Println(b1)
    m := SerM()
    m1 := DesM(m)
    fmt.Println(m1)
}

結果以下

Go語言之結構體,接口和文件操做

4 注意事項

1 在反序列化一個json字符串時,要確保反序列化後的數據類型和原來序列化前的數據類型一致 2 若是json字符串是經過程序獲取的,則不須要對其進行轉義和處理

相關文章
相關標籤/搜索