1. 接口
在Go語言中接口(interface)是一種類型,一種抽象的類型。
interface是一組method的集合,接口做的事情就像是定義一個協議(規則),只要一臺機器有洗衣服和甩干的功能,我就稱它為洗衣機。不關心屬性(數據),只關心行為(方法)。
接口(interface)是一種類型
接口類型是對其它類型行為的抽象和概括;因為接口類型不會和特定的實現細節綁定在一起,通過這種抽象的方式我們可以讓我們的函數更加靈活和更具有適應能力。
接口是雙方約定的一種合作協議。接口實現者不需要關心接口會被怎樣使用,調用者也不需要關心接口的實現細節。接口是一種類型,也是一種抽象結構,不會暴露所含數據的格式、類型及結構。
1.2 接口定義
Go語言提倡面向接口編程。
每個接口類型由數個方法組成。接口的形式代碼如下:
type 接口類型名 interface{方法名1( 參數列表1 ) 返回值列表1方法名2( 參數列表2 ) 返回值列表2…
}
對各個部分的說明:
- 接口類型名:使用 type 將接口定義為自定義的類型名。Go語言的接口在命名時,一般會在單詞后面添加 er,如有寫操作的接口叫 Writer,有字符串功能的接口叫 Stringer,有關閉功能的接口叫 Closer 等。
- 方法名:當方法名首字母是大寫時,且這個接口類型名首字母也是大寫時,這個方法可以被接口所在的包(package)之外的代碼訪問。
- 參數列表、返回值列表:參數列表和返回值列表中的參數變量名可以被忽略
type Writer interface {//接口名和方法首字母大寫,意味著可以被其他包訪問Write([]byte)string
}
1.3 接口實現條件
如果一個任意類型 T 的方法集為一個接口類型的方法集的超集,則我們說類型 T 實現了此接口類型。
T 可以是一個非接口類型,也可以是一個接口類型。
實現關系在Go語言中是隱式的。兩個類型之間的實現關系不需要在代碼中顯式地表示出來。Go語言中沒有類似于 implements 的關鍵字。 Go編譯器將自動在需要的時候檢查兩個類型之間的實現關系。
接口定義后,需要實現接口,調用方才能正確編譯通過并使用接口。
接口的實現需要遵循兩條規則才能讓接口可用:
- 接口的方法與實現接口的類型方法格式一致在類型中添加與接口簽名一致的方法就可以實現該方法。簽名包括方法中的名稱、參數列表、返回參數列表。也就是說,只要實現接口類型中的方法的名稱、參數列表、返回參數列表中的任意一項與接口要實現的方法不一致,那么接口的這個方法就不會被實現。
// 定義一個數據寫入器
type DataWriter interface {Write(interface{}) error
}// 定義文件結構,用于實現DataWriter
type file struct {
}// 實現DataWriter接口的WriteData方法
func (f *file) Write(b interface{}) error {return fmt.Sprintf("writer:", b)
}func main() {// 實例化filef := new(file)// 聲明一個DataWriter的接口var write DataWriter// 將接口賦值f,也就是*file類型write = f// 使用DataWriter接口進行數據寫入write.Write("hhhhhhhh")
}
- 當類型無法實現接口時,編譯器會報錯:
-
- 函數名不一致導致的報錯
- 實現接口的方法簽名不一致導致的報錯
- 接口中所有方法均被實現當一個接口中有多個方法時,只有這些方法都被實現了,接口才能被正確編譯并使用。
// 定義一個數據寫入器
type DataWriter interface {Write(interface{}) error//上述代碼中新增一個方法Content() bool
}
運行結果
.\main.go:28:10: cannot use f (variable of type *file) as DataWriter value in assignment: *file does not implement DataWriter (missing method Content)
Go語言的接口實現是隱式的,無須讓實現接口的類型寫出實現了哪些接口。
這個設計被稱為非侵入式設計。
1.4 類型與接口的關系
在Go語言中類型和接口之間有一對多和多對一的關系
一個類型可以實現多個接口
一個類型可以同時實現多個接口,而接口間彼此獨立,不知道對方的實現。
例如,狗可以叫,也可以動。
我們就分別定義Sayer接口和Mover接口,如下:
type Sayer interface {Say()
}type Mover interface {Move()
}type Dog struct {name string
}// dog實現say和move接口
func (d Dog) Say() {fmt.Println(d.name, " saying......")
}func (d Dog) Move() {fmt.Println(d.name, "moving ......")
}func main() {var x Sayervar y Movervar dog = Dog{"wangwang"}x = dogy = dogx.Say() //wangwang saying......y.Move() //wangwang moving ......}
多個類型實現同一接口
type Mover interface {Move()
}type Dog struct {name string
}type Car struct {name string
}//dog 和 car都實現mover接口func (d Dog) Move() {fmt.Println(d.name, "moving,....")
}func (c Car) Move() {fmt.Println(c.name, "moving .....")
}func main() {var d = Dog{"旺財"}var c = Car{"小米"}var move Movermove = dmove.Move() //旺財 moving,....move = cmove.Move() //小米 moving .....}
接口嵌套
接口與接口間可以通過嵌套創造出新的接口
// Sayer 接口
type Sayer interface {say()
}// Mover 接口
type Mover interface {move()
}// 接口嵌套
type animal interface {SayerMover
}
嵌套得到的接口的使用與普通接口一樣,這里我們讓cat實現animal接口:
type cat struct {name string
}func (c cat) say() {fmt.Println("喵喵喵")
}func (c cat) move() {fmt.Println("貓會動")
}func main() {var x animalx = cat{name: "花花"}x.move()x.say()
}
1.5 空接口
空接口是指沒有定義任何方法的接口。
因此任何類型都實現了空接口。
空接口類型的變量可以存儲任意類型的變量。
func main() {var x interface{}var i = 100x = ifmt.Println(x) //100var name = "hhhhh"x = namefmt.Println(x) //hhhhh
}
1.5.1 空接口的應用
空接口作為函數的參數
使用空接口實現可以接收任意類型的函數參數。
func show(a interface{}) {fmt.Println(a)
}func main() {//空接口作為函數參數show("空接口傳參") //空接口傳參}
空接口作為map的值
使用空接口實現可以保存任意值的字典。
func main() {var student = make(map[string]interface{}, 3)student["小明"] = 100student["小紅"] = "hahah"student["小高"] = falsefmt.Printf("%+v", student) //map[小明:100 小紅:hahah 小高:false]}
1.5.2 類型斷言
空接口可以存儲任意類型的值,那我們如何獲取其存儲的具體數據呢?
接口值
一個接口的值(簡稱接口值)是由一個具體類型和具體類型的值兩部分組成的。
這兩部分分別稱為接口的動態類型和動態值。
想要判斷空接口中的值這個時候就可以使用類型斷言,其語法格式:
x.(T)
1
其中:
- x:表示類型為interface{}的變量
- T:表示斷言x可能是的類型。
該語法返回兩個參數,第一個參數是x轉化為T類型后的變量,第二個值是一個布爾值,若為true則表示斷言成功,為false則表示斷言失敗。
func main() {var student = make(map[string]interface{}, 3)student["小明"] = 100student["小紅"] = "hahah"student["小高"] = falsefmt.Printf("%+v\n", student) //map[小明:100 小紅:hahah 小高:false]_, ok := student["小明"].(bool)if ok != true {fmt.Println("student[\"小明\"]不是bool") }
}
2. I/O操作
I/O操作也叫輸入輸出操作。其中I是指Input,O是指Output,用于讀或者寫數據的,有些語言中也叫流操作,是指數據通信的通道。
Golang 標準庫對 IO 的抽象非常精巧,各個組件可以隨意組合,可以作為接口設計的典范。
io包中提供I/O原始操作的一系列接口。
它主要包裝了一些已有的實現,如 os 包中的那些,并將這些抽象成為實用性的功能和一些其他相關的接口。
由于這些接口和原始的操作以不同的實現包裝了低級操作,客戶不應假定它們對于并行執行是安全的。
io庫比較常用的接口有三個,分別是Reader,Writer和Closer。
2.1 Reader
Reader接口的定義,Read()方法用于讀取數據。
type Reader interface {Read(p []byte) (n int, err error)
}
io.Reader 表示一個讀取器,它將數據從某個資源讀取到傳輸緩沖區。在緩沖區中,數據可以被流式傳輸和使用。
- 對于要用作讀取器的類型,它必須實現 io.Reader 接口的唯一一個方法 Read(p []byte)。
- 換句話說,只要實現了 Read(p []byte) ,那它就是一個讀取器。
- Read() 方法有兩個返回值,一個是讀取到的字節數,一個是發生錯誤時的錯誤。
通過 string.NewReader(string) 創建一個字符串讀取器,然后流式地按字節讀取:
func main() {reader := strings.NewReader("this is a reader")// 每次讀取4個字節p := make([]byte, 4)for {n, err := reader.Read(p)if err != nil {if err == io.EOF {log.Println("讀完了")break}log.Fatalln("read error", err)os.Exit(2)}log.Println("讀取到的字節數:", n)}}
- 最后一次返回的 n 值有可能小于緩沖區大小。
- io.EOF 來表示輸入流已經讀取到頭
2.1.1 文件操作相關API
func Create(name string) (file *File, err Error)
1
-
- 根據提供的文件名創建新的文件,返回一個文件對象,默認權限是0666
func NewFile(fd uintptr, name string) *File
1
-
- 根據文件描述符創建相應的文件,返回一個文件對象
func Open(name string) (file *File, err Error)
1
-
- 只讀方式打開一個名稱為name的文件
func OpenFile(name string, flag int, perm uint32) (file *File, err Error)
1
-
- 打開名稱為name的文件,flag是打開的方式,只讀、讀寫等,perm是權限
func (file *File) Write(b []byte) (n int, err Error)
1
-
- 寫入byte類型的信息到文件
func (file *File) WriteAt(b []byte, off int64) (n int, err Error)
1
-
- 在指定位置開始寫入byte類型的信息
func (file *File) WriteString(s string) (ret int, err Error)
1
-
- 寫入string信息到文件
func (file *File) Read(b []byte) (n int, err Error)
1
-
- 讀取數據到b中
func (file *File) ReadAt(b []byte, off int64) (n int, err Error)
1
-
- 從off開始讀取數據到b中
func Remove(name string) Error
1
-
- 刪除文件名為name的文件
2.1.2 讀文件
type Closer interface {Close() error
}
os.Open()函數能夠打開一個文件,返回一個*File和一個err。對得到的文件實例調用Close()方法能夠關閉文件。
文件讀取可以用file.Read(),讀到文件末尾會返回io.EOF的錯誤
func main() {// 打開文件file, err := os.Open("C:\\Users\\Administrator\\Desktop\\新建 文本文檔 (2).txt")if err != nil {log.Println("打開失敗")}defer file.Close()// 定義接收文件讀取的字節數組buff := make([]byte, 128)var content []bytefor {_, err := file.Read(buff)if err == io.EOF {log.Println("讀完了")break}if err != nil {log.Println("讀取失敗:", err)return}}content = append(content, buff...)fmt.Sprintln(content)
}
Writer
type Writer interface {//Write() 方法有兩個返回值,一個是寫入到目標資源的字節數,一個是發生錯誤時的錯誤。Write(p []byte) (n int, err error)
}
- io.Writer 表示一個寫入器,它從緩沖區讀取數據,并將數據寫入目標資源。
- 對于要用作編寫器的類型,必須實現 io.Writer 接口的唯一一個方法 Write(p []byte)
- 同樣,只要實現了 Write(p []byte) ,那它就是一個編寫器。
func main() {// 打開文件file, err := os.Open("C:\\Users\\Administrator\\Desktop\\新建 文本文檔 (2).txt")if err != nil {log.Println("打開失敗")}defer file.Close()// 定義接收文件讀取的字節數組buff := make([]byte, 128)var content []bytefor {_, err := file.Read(buff[:])if err == io.EOF {log.Println("讀完了")break}if err != nil {log.Println("讀取失敗:", err)return}}content = append(content, buff...)fmt.Println(string(content))
}
2.2 Writer
type Writer interface {//Write() 方法有兩個返回值,一個是寫入到目標資源的字節數,一個是發生錯誤時的錯誤。Write(p []byte) (n int, err error)
}
- io.Writer 表示一個寫入器,它從緩沖區讀取數據,并將數據寫入目標資源。
- 對于要用作編寫器的類型,必須實現 io.Writer 接口的唯一一個方法 Write(p []byte)
- 同樣,只要實現了 Write(p []byte) ,那它就是一個編寫器。
寫文件:
func main() {file, err := os.Create("test.txt")if err != nil {log.Println("create error")}defer file.Close()b := make([]byte, 0)for i := 0; i < 10; i++ {b = append(b, byte(i))}file.WriteString(string(b))
}
2.3 bufio
- bufio包實現了帶緩沖區的讀寫,是對文件讀寫的封裝
- bufio緩沖寫數據
模式 | 含義 |
os.O_WRONLY | 只寫 |
os.O_CREATE | 創建文件 |
os.O_RDONLY | 只讀 |
os.O_RDWR | 讀寫 |
os.O_TRUNC | 清空 |
os.O_APPEND | 追加 |
bufio讀寫數據
func wr() {// 參數2:打開模式,所有模式d都在上面// 參數3是權限控制// w寫 r讀 x執行 w 2 r 4 x 1//特殊權限位,擁有者位,同組用戶位,其余用戶位file, err := os.OpenFile("./xxx.txt", os.O_CREATE|os.O_WRONLY, 0666)if err != nil {return}defer file.Close()// 獲取writer對象writer := bufio.NewWriter(file)for i := 0; i < 10; i++ {writer.WriteString("hello\n")}// 刷新緩沖區,強制寫出writer.Flush()
}func re() {file, err := os.Open("./xxx.txt")if err != nil {return}defer file.Close()reader := bufio.NewReader(file)for {line, _, err := reader.ReadLine()if err == io.EOF {break}if err != nil {return}fmt.Println(string(line))}}func main() {re()
}
2.5 實現一個cat命令
使用文件操作相關知識,模擬實現linux平臺cat命令的功能。