## 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. 相关资源