## 4.9\. I/O包
接下來我們使用open/close/read/write等基本的系統調用實現一個用于文件IO的包。 讓我們從文件file.go開始:
```
05 package file
07 import (
08 "os"
09 "syscall"
10 )
12 type File struct {
13 fd int // file descriptor number
14 name string // file name at Open time
15 }
```
文件的第一行聲明當前代碼對應—"file"—包,然后導入os和syscall兩個包。 包os封裝了不同操作系統底層的實現,例如將文件抽象成相同的類型。我們將在系統接口基礎 上封裝一個基本的文件IO接口。
另外還有其他一些比較底層的syscall包,它提供一些底層的系統調用(system's calls)。
接下來是一個類型(type)定義:用"type"這個關鍵字來聲明一個類。在這個例子里數據結構(data structure) 名為"File"。為了讓這事變的有趣些,我們的File包含了一個這個文件的名字(name)用來描述這個文件。
因為結構體名字"File"的首字母是大寫,所以這個類型包(package)可以被外部訪問。在GO中訪問規則的處理 是非常簡單的:如果頂極類型名字首字母(包括:function, method, constant or variable, or of a structure field or method)是大寫,那么引用了這個包(package)的使用者就可以訪問到它。不然 名稱和被命名的東西將只能有package內部看到。這是一個要嚴格遵循的規則,因為這個訪問規則是由 編譯器(compiler)強制規范的。在GO中,一組公開可見的名稱是"exported"。
在這個File例子中,所有的字段(fields)都是小寫所以從包外部是不能訪問的,不過我們在下面將會一個 一個對外訪問的出口(exported) —— 一個以大寫字母開頭的方法。
首先是一個創建File結構體的函數:
```
17 func newFile(fd int, name string) *File {
18 if fd < 0 {
19 return nil
20 }
21 return &File{fd, name}
22 }
```
這將返回一個指向新File結構體的指針,結構體存有文件描述符和文件名。這段代碼使用了GO的復合變量(composite literal)的概念,和創建內建的maps和arrays類型變量一樣。要創建在堆(heap-allocated)中創建一個新的 對象,我們可以這樣寫:
```
n := new(File);
n.fd = fd;
n.name = name;
return n
```
如果結構比較簡單的話,我們可以直接在返回結構體變量地址的時候初始化成員字段,如前面例子的 21行代碼所示。
我們可以用前面的函數(newFile)構造一些File類型的變量,返回File:
```
24 var (
25 Stdin = newFile(0, "/dev/stdin")
26 Stdout = newFile(1, "/dev/stdout")
27 Stderr = newFile(2, "/dev/stderr")
28 )
```
這里的newFile是內部函數,真正包外部可以訪問的函數是Open:
```
30 func Open(name string, mode int, perm uint32) (file *File, err os.Error) {
31 r, e := syscall.Open(name, mode, perm)
32 if e != 0 {
33 err = os.Errno(e)
34 }
35 return newFile(r, name), err
36 }
```
在這幾行里出現了一些新的東西。首先,函數Open返回多個值(multi-value):一個File指針和一個error( 等一下會介紹errors)》我們用括號來表來聲明返回多個變量值(multi-value),語法上它看 起來像第二個參數列表。syscall.Open系統調用同樣也是返回多個值multi-value。接著我們能在31行 創建了r和e兩個變量用于保存syscall.Open的返回值。函數最終也是返回2個值,分別為File指針和一個error。 如果syscall.Open打開失敗,文件描述r將會是個負值,newFile將會返回nil。
關于錯誤:os包包含了一些常見的錯誤類型。在用戶自己的代碼中也盡量使用這些通用的錯誤。 在Open函數中,我們用os.Error函數將Unix的整數錯誤代碼轉換為go語言的錯誤類型。
現在我們可以創建Files,我們為它定義了一些常用的方法(methods)。要給一個類型定義一個方法(method), 需要在函數名前增加一個用于訪問當前類型的變量。這些是為*File類型創建的一些方法:
```
38 func (file *File) Close() os.Error {
39 if file == nil {
40 return os.EINVAL
41 }
42 e := syscall.Close(file.fd)
43 file.fd = -1 // so it can't be closed again
44 if e != 0 {
45 return os.Errno(e)
46 }
47 return nil
48 }
50 func (file *File) Read(b []byte) (ret int, err os.Error) {
51 if file == nil {
52 return -1, os.EINVAL
53 }
54 r, e := syscall.Read(file.fd, b)
55 if e != 0 {
56 err = os.Errno(e)
57 }
58 return int(r), err
59 }
61 func (file *File) Write(b []byte) (ret int, err os.Error) {
62 if file == nil {
63 return -1, os.EINVAL
64 }
65 r, e := syscall.Write(file.fd, b)
66 if e != 0 {
67 err = os.Errno(e)
68 }
69 return int(r), err
70 }
72 func (file *File) String() string {
73 return file.name
74 }
```
這些并沒有隱含的this指針(參考C++類),而且類型的方法(methods)也不是定義在struct內部——struct結構 只聲明數據成員(data members)。事實上,我們可以給任意數據類型定義方法,例如:整數(integer),數組(array) 等。后面我們會有一個給數組定義方法的例子。
String這個方法之所以會被調用是為了更好的打印信息,我們稍后會詳細說明。
方法(methods)使用os.EINVAL來表示(os.Error的版本)Unix錯誤代碼EINVAL。 在os包中針對標準的error變量定義各種錯誤常量。
現在我們可以使用我們自己創建的包(package)了:
```
05 package main
07 import (
08 "./file"
09 "fmt"
10 "os"
11 )
13 func main() {
14 hello := []byte("hello, world\n")
15 file.Stdout.Write(hello)
16 file, err := file.Open("/does/not/exist", 0, 0)
17 if file == nil {
18 fmt.Printf("can't open file; err=%s\n", err.String())
19 os.Exit(1)
20 }
21 }
```
這個"./"在導入(import)"./file"時告訴編譯器(compiler)使用我們自己的package,而不是在 默認的package路徑中找。
最后,我們來執行這個程序:
```
$ 6g file.go # compile file package
$ 6g helloworld3.go # compile main package
$ 6l -o helloworld3 helloworld3.6 # link - no need to mention "file"
$ helloworld3
hello, world
can't open file; err=No such file or directory
$
```
- 1. 關于本文
- 2. Go語言簡介
- 3. 安裝go環境
- 3.1. 簡介
- 3.2. 安裝C語言工具
- 3.3. 安裝Mercurial
- 3.4. 獲取代碼
- 3.5. 安裝Go
- 3.6. 編寫程序
- 3.7. 進一步學習
- 3.8. 更新go到新版本
- 3.9. 社區資源
- 3.10. 環境變量
- 4. Go語言入門
- 4.1. 簡介
- 4.2. Hello,世界
- 4.3. 分號(Semicolons)
- 4.4. 編譯
- 4.5. Echo
- 4.6. 類型簡介
- 4.7. 申請內存
- 4.8. 常量
- 4.9. I/O包
- 4.10. Rotting cats
- 4.11. Sorting
- 4.12. 打印輸出
- 4.13. 生成素數
- 4.14. Multiplexing
- 5. Effective Go
- 5.1. 簡介
- 5.2. 格式化
- 5.3. 注釋
- 5.4. 命名
- 5.5. 分號
- 5.6. 控制流
- 5.7. 函數
- 5.8. 數據
- 5.9. 初始化
- 5.10. 方法
- 5.11. 接口和其他類型
- 5.12. 內置
- 5.13. 并發
- 5.14. 錯誤處理
- 5.15. Web服務器
- 6. 如何編寫Go程序
- 6.1. 簡介
- 6.2. 社區資源
- 6.3. 新建一個包
- 6.4. 測試
- 6.5. 一個帶測試的演示包
- 7. Codelab: 編寫Web程序
- 7.1. 簡介
- 7.2. 開始
- 7.3. 數據結構
- 7.4. 使用http包
- 7.5. 基于http提供wiki頁面
- 7.6. 編輯頁面
- 7.7. template包
- 7.8. 處理不存在的頁面
- 7.9. 儲存頁面
- 7.10. 錯誤處理
- 7.11. 模板緩存
- 7.12. 驗證
- 7.13. 函數文本和閉包
- 7.14. 試試!
- 7.15. 其他任務
- 8. 針對C++程序員指南
- 8.1. 概念差異
- 8.2. 語法
- 8.3. 常量
- 8.4. Slices(切片)
- 8.5. 構造值對象
- 8.6. Interfaces(接口)
- 8.7. Goroutines
- 8.8. Channels(管道)
- 9. 內存模型
- 9.1. 簡介
- 9.2. Happens Before
- 9.3. 同步(Synchronization)
- 9.4. 錯誤的同步方式
- 10. 附錄
- 10.1. 命令行工具
- 10.2. 視頻和講座
- 10.3. Release History
- 10.4. Go Roadmap
- 10.5. 相關資源