一 結構體
1 簡介
1 結構體介紹
Go 語言通過自定義方式形成新的類型,結構體是類型中帶有成員的符合類型,Go語言使用結構體和結構體成員來描述真實世界的實體和實體對應的各種屬性。
2 字段
結構體成員是由一系列成員變量構成,這些成員變量稱為"字段"
字段特征如下:
1 字段必須有自己的名稱和類型
2 字段名必須唯一
3 字段的類型一般是基本資料類型,數組,也可以是引用類型,甚至可以是字段所在的結構體的類型。
Go 語言中不但結構體可以擁有自己的方法,且每種自定義類型都可以擁有自己的方法
2 定義結構體
1 基本定義
Go語言使用關鍵字type 可以定義各種類型,包括将各種基本類型定義為自定義類型,自然也可以定義結構體
2 基本格式
type 類型名稱 struct {
字段1 字段1類型
字段2 字段2類型
}
類型名: 同一個包中不能重複,辨別自定義結構體名稱
struct{}: 辨別其類型是結構體類型
字段: 表示字段名稱,在該結構體中必須唯一
字段類型:該字段的類型,可以是多種類型
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)
}
結果如下

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)
}
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)
}
結構體注意事項和使用細節
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)
}
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)
}
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)
}
說明:
其中b和c 方式傳回的是結構體指針,其标準的通路字段形式是(*x.field).Go語言為了進行簡化,支援結構體指針.字段名,更加符合程式員的使用習慣,Go的底層進行了相關的優化操作
3 取位址執行個體化應用-工廠函數
某種情況下,需要調用某些結構體,但又不想對外暴露,此時便可使用工廠模式來解決此種情況
目錄結構如下
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)
}
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)
}
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資料為大寫
}
}
通過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資料為大寫
}
}
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)
}
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)
}
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()
}
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 面向對象程式設計---封裝
封裝就是把抽象出的字段和堆字段的操作封裝在一起,資料被保護在内部,程式的其他包隻能通過被授權的操作,才能實作對字段的操作
2 封裝實作的步驟
1 将結構體字段的首字母大寫
2 給結構體所在的包提供一個工廠模式的函數,首字母大寫,類似于一個構造函數
3 提供一個首字母大寫的Set方法,用于對屬性判斷和指派
4 提供一個Get方法,用于擷取屬性的值
3 封裝的好處
1 隐藏實作細節
2 可對資料進行驗證,保證安全合理
4 如何展現封裝
1 對結構體中的方法進行封裝
2 通過方法,包實作封裝
3 面向對象--繼承
類型内嵌和結構體内嵌
結構體允許其成員字段在聲明時沒有字段名而隻有類型,這種形式被稱為類型内嵌或者匿名字段類型内嵌
寫法如下
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)
}
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)
}
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()
}
3 結構體嵌套入兩個匿名結構體時,若兩個匿名結構體具有相同的字段和方法,則在通路時,就必須指明匿名結構體名稱,否則會出現編譯錯誤
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()
}
4 如果一個struct嵌套了一個有名稱的結構體,此稱為組合,若組合關聯,那麼在通路結構體字段或方法時,就必須有結構體的名稱
5 嵌套名結構體後,也可以建立結構體變量時,直接指定各個匿名結構體字段的值
4 面向對象程式設計之多态
變量(執行個體)具有多種形态,面向對象的第三大特征,在Go語言中,多态特征是通過接口實作的,可以按照統一的接口來調用不同的實作,這時接口就會呈現不同的形态
三 接口(interface)
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")
}
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)
}
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")
}
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")
}
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)
}
2 通過接口實作
官網得知,欲使用Sort接口,需實作三個方法
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)
}
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("失敗")
}
}
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)
}
在進行斷言時,需要確定類型比對,否則會報錯
可進行處理,不報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)
}
}
}
8 空接口類型 interface{}
空接口是接口類型的特殊形式,空接口沒有任何方法,是以任何類型都無需實作空接口,從實作的角度看,任何值都滿足這個接口的需求,是以空接口類型可儲存任何值,也可以從空接口中取出原值。
空接口的内部實作了儲存對象的類型和指針,使用空接口儲存一個資料的過程比直接使用資料對應類型的變量儲存稍慢。
package main
import "fmt"
func main() {
var str interface{}
str = 1
fmt.Println(str)
str = "hello"
fmt.Println(str)
str = false
fmt.Println(str)
}
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)
}
編譯器傳回接口,不能将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)
}
3 空接口值的比較
空接口在儲存不同的值後,可以和其他變量值一樣使用"==" 進行比較操作,空接口比較有以下幾種特性
1 類型不同的空接口之間的比較結果不同
package main
import "fmt"
func main() {
var a interface{} = 100
var b interface{} = "abc"
fmt.Println(a == b)
}
2 不能比較空接口中的動态值
package main
import "fmt"
func main() {
var c interface{} = []int{10}
var d interface{} = []int{20}
fmt.Println(c == d)
}
9 接口和結構體 優缺點
1 接口的優點
1 結構體實作繼承過程中不能選擇性繼承,而當基類結構體需要其他擴充功能不被子類繼承時,則需要使用接口來補充
2 接口比繼承更靈活 接口一定程度上實作了代碼的解耦
2 接口和繼承解決的問題
1 繼承的價值在于解決代碼中的複用性和可維護性問題
2 接口的價值在于設計好各種規範,讓其他自定義類型實作這些方法
接口提現的多态特性
1 多态參數
2 多态數組
四 檔案介紹
檔案,資料源,其主要作用就是儲存資料,其就是資料源
檔案在程式中以流的方式操作的
流:資料在資料源和程式之間經曆的路徑
輸入流:從資料源到程式的路徑,讀取檔案
輸出流:從程式到資料源的路徑,寫檔案
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()
}
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()
}
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("重新整理成功")
}
}
}
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("檔案寫入成功")
}
}
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)進行拷貝。
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)
}
}
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)
}
五 Json簡介
1 簡介
介紹詳情見: https://blog.51cto.com/11233559/2411552
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()
}
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)
}