fmt
fmt包实现了类似C语言printf和scanf的格式化I/O。主要分为向外输出内容和获取输入内容两大部分。
向外输出
标准库fmt提供了以下几种输出相关函数。
Print
<font style="color:rgb(51, 51, 51);">Print</font>系列函数会将内容输出到系统的标准输出,区别在于<font style="color:rgb(51, 51, 51);">Print</font>函数直接输出内容,<font style="color:rgb(51, 51, 51);">Printf</font>函数支持格式化输出字符串,<font style="color:rgb(51, 51, 51);">Println</font>函数会在输出内容的结尾添加一个换行符。
func Print(a ...interface{}) (n int, err error)func Printf(format string, a ...interface{}) (n int, err error)func Println(a ...interface{}) (n int, err error)
Fprint
Fprint系列函数会将内容输出到一个io.Writer接口类型的变量w中,我们通常用这个函数往文件中写入内容。
只要满足io.Writer接口的类型(存在Write方法)都支持写入。__例如:标准输入输出、文件、
func Fprint(w io.Writer, a ...interface{}) (n int, err error)func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
// 向标准输出写入内容fmt.Fprintln(os.Stdout, "向标准输出写入内容")fileObj, err := os.OpenFile("./xx.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)if err != nil { fmt.Println("打开文件出错,err:", err) return}name := "枯藤"// 向打开的文件句柄中写入内容fmt.Fprintf(fileObj, "往文件中写如信息:%s", name)
Sprint
<font style="color:rgb(51, 51, 51);">Sprint</font>系列函数会把传入的数据生成并返回一个字符串。
func Sprint(a ...interface{}) stringfunc Sprintf(format string, a ...interface{}) stringfunc Sprintln(a ...interface{}) string
简单的示例代码如下:
s1 := fmt.Sprint("枯藤")name := "枯藤"age := 18s2 := fmt.Sprintf("name:%s,age:%d", name, age)s3 := fmt.Sprintln("枯藤")fmt.Println(s1, s2, s3)// 输出 枯藤 name:枯藤,age:18 枯藤
Errorf
Errorf函数根据format参数生成格式化字符串并返回一个包含该字符串的错误。通常使用这种方式来自定义错误类型。
func Errorf(format string, a ...interface{}) error
例如:
// 自定义错误类型err := fmt.Errorf("这是一个错误")fmt.Println(err) // 这是一个错误err := fmt.Errorf("这是一个错误%d",2)fmt.Println(err) // 这是一个错误2
格式化占位符
***printf**系列函数都支持format格式化参数,在这里我们按照占位符将被替换的变量类型划分,方便查询和记忆。
通用占位符
| 占位符 |
说明 |
| %v |
值的默认格式表示 |
| %+v |
类似%v,但输出结构体时会添加字段名 |
| %#v |
值的Go语法表示 |
| %T |
打印值的类型 |
| %% |
百分号 |
fmt.Printf("%v\n", 100) // 100fmt.Printf("%v\n", false) // falseo := struct{ name string }{"枯藤"} fmt.Printf("%v\n", o) // {枯藤}fmt.Printf("%#v\n", o) // struct { name string }{name:"枯藤"}fmt.Printf("%T\n", o) // struct { name string }fmt.Printf("100%%\n") // 100%
布尔型
整型
| 占位符 |
说明 |
| %b |
表示为二进制 |
| %c |
该值对应的unicode码值 |
| %d |
表示为十进制 |
| %o |
表示为八进制 |
| %x |
表示为十六进制,使用a-f |
| %X |
表示为十六进制,使用A-F |
| %U |
表示为Unicode格式:U+1234,等价于”U+%04X” |
| %q |
该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示 |
n := 65fmt.Printf("%b\n", n) //1000001fmt.Printf("%c\n", n) //Afmt.Printf("%d\n", n) //65fmt.Printf("%o\n", n) //101fmt.Printf("%x\n", n) //41fmt.Printf("%X\n", n) //41
浮点数与复数
| 占位符 |
说明 |
| %b |
无小数部分、二进制指数的科学计数法,如-123456p-78 |
| %e |
科学计数法,如-1234.456e+78 |
| %E |
科学计数法,如-1234.456E+78 |
| %f |
有小数部分但无指数部分,如123.456 |
| %F |
等价于%f |
| %g |
根据实际情况采用%e或%f格式(以获得更简洁、准确的输出) |
| %G |
根据实际情况采用%E或%F格式(以获得更简洁、准确的输出) |
f := 12.34fmt.Printf("%b\n", f) //6946802425218990p-49fmt.Printf("%e\n", f) //1.234000e+01fmt.Printf("%E\n", f) //1.234000E+01fmt.Printf("%f\n", f) //12.340000fmt.Printf("%g\n", f) //12.34fmt.Printf("%G\n", f) //12.34
字符串和[]byte
| 占位符 |
说明 |
| %s |
直接输出字符串或者[]byte |
| %q |
该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示 |
| %x |
每个字节用两字符十六进制数表示(使用a-f |
| %X |
每个字节用两字符十六进制数表示(使用A-F) |
s := "枯藤"fmt.Printf("%s\n", s) //枯藤fmt.Printf("%q\n", s) //"枯藤"fmt.Printf("%x\n", s) //e69eafe897a4fmt.Printf("%X\n", s) //E69EAFE897A4
指针
| 占位符 |
说明 |
| %p |
表示为十六进制,并加上前导的0x |
示例代码如下:
a := 18fmt.Printf("%p\n", &a) // 0xc000054058fmt.Printf("%#p\n", &a) // c000054058
宽度标识符
宽度通过一个紧跟在百分号后面的十进制数指定,如果未指定宽度,则表示值时除必需之外不作填充。精度通过(可选的)宽度后跟点号后跟的十进制数指定。如果未指定精度,会使用默认精度;如果点号后没有跟数字,表示精度为0。举例如下
| 占位符 |
说明 |
| %f |
默认宽度,默认精度 |
| %9f |
宽度9,默认精度 |
| %.2f |
默认宽度,精度2 |
| %9.2f |
宽度9,精度2 |
| %9.f |
宽度9,精度0 |
n := 88.88fmt.Printf("%f\n", n) // fmt.Printf("%9f\n", n) // fmt.Printf("%.2f\n", n) // fmt.Printf("%9.2f\n", n) //fmt.Printf("%9.f\n", n) //
输出结果如下:
88.880000 88.880000 88.88 88.88 89
其他falg
| 占位符 |
说明 |
| ‘+’ |
总是输出数值的正负号;对%q(%+q)会生成全部是ASCII字符的输出(通过转义); |
| ‘ ‘ |
对数值,正数前加空格而负数前加负号;对字符串采用%x或%X时(% x或% X)会给各打印的字节之间加空格 |
| ‘-‘ |
在输出右边填充空白而不是默认的左边(即从默认的右对齐切换为左对齐); |
| ‘#’ |
八进制数前加0(%#o),十六进制数前加0x(%#x)或0X(%#X),指针去掉前面的0x(%#p)对%q(%#q),对%U(%#U)会输出空格和单引号括起来的go字面值; |
| ‘0’ |
使用0而不是空格填充,对于数值类型会把填充的0放在正负号后面; |
s := "枯藤"fmt.Printf("%s\n", s)fmt.Printf("%5s\n", s)fmt.Printf("%-5s\n", s)fmt.Printf("%5.7s\n", s)fmt.Printf("%-5.7s\n", s)fmt.Printf("%5.2s\n", s)fmt.Printf("%05s\n", s)
输出结果如下:
枯藤 枯藤 枯藤 枯藤 枯藤 枯藤 000枯藤
获取输入
Go语言fmt包下有<font style="color:rgb(51, 51, 51);">fmt.Scan、fmt.Scanf、fmt.Scanln</font>三个函数,可以在程序运行过程中从标准输入获取用户的输入。
fmt.Scan
- Scan从标准输入扫描文本,读取由空白符分隔的值保存到传递给本函数的参数中,换行符视为空白符。
- 本函数返回成功扫描的数据个数和遇到的任何错误。如果读取的数据个数比提供的参数少,会返回一个错误报告原因。
函数定签名如下:
func Scan(a ...interface{}) (n int, err error)
func main() { var ( name string age int married bool ) fmt.Scan(&name, &age, &married) fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)}/*$ ./scan_demo 枯藤 18 false 扫描结果 name:枯藤 age:18 married:false*/
fmt.Scanf
函数签名如下:
func Scanf(format string, a ...interface{}) (n int, err error)
- Scanf从标准输入扫描文本,根据format参数指定的格式去读取由空白符分隔的值保存到传递给本函数的参数中。
- 本函数返回成功扫描的数据个数和遇到的任何错误。
func main() { var ( name string age int married bool ) fmt.Scanf("1:%s 2:%d 3:%t", &name, &age, &married) fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)}
将上面的代码编译后在终端执行,在终端按照指定的格式依次输入枯藤、18和false。
$ ./scan_demo 1:枯藤 2:18 3:false扫描结果 name:枯藤 age:18 married:false
fmt.Scanf不同于fmt.Scan简单的以空格作为输入数据的分隔符,fmt.Scanf为输入数据**指定了具体的输入内容格式**,只有按照格式输入数据才会被扫描并存入对应变量。
例如,我们还是按照上个示例中以空格分隔的方式输入,fmt.Scanf就不能正确扫描到输入的数据。
$ ./scan_demo 枯藤 18 false扫描结果 name: age:0 married:false
fmt.Scanln
函数签名如下:
func Scanln(a ...interface{}) (n int, err error)
- Scanln类似Scan,它在遇到换行时才停止扫描。最后一个数据后面必须有换行或者到达结束位置。
- 本函数返回成功扫描的数据个数和遇到的任何错误。
func main() { var ( name string age int married bool ) fmt.Scanln(&name, &age, &married) fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)}
将上面的代码编译后在终端执行,在终端依次输入枯藤、18和false使用空格分隔。
$ ./scan_demo 枯藤 18 false扫描结果 name:枯藤 age:18 married:false
fmt.Scanln遇到回车就结束扫描了,这个比较常用。
bufio.NewReader
有时候我们想完整获取输入的内容,而**输入的内容可能包含空格,这种情况下可以使用bufio包来实现,遇到换行时停止扫描**。示例代码如下:
func main() { reader := bufio.NewReader(os.Stdin) // 从标准输入生成读对象,读到所有 fmt.Print("请输入内容:") text, _ := reader.ReadString('\n') // 读到换行 fmt.Printf("%#v\n", text) text = strings.TrimSpace(text) // 去掉字符串前后的空格 fmt.Printf("%#v\n", text) }/*请输入内容:sdfsfsf\r\n \r \n asdf\0sdf"sdfsfsf\\r\\n \\r \\n asdf\\0sdf\r\n""sdfsfsf\\r\\n \\r \\n asdf\\0sdf"*/
Fscan系列
这几个函数功能分别类似于<font style="color:rgb(51, 51, 51);">fmt.Scan、fmt.Scanf、fmt.Scanln</font>三个函数,只不过它们不是从标准输入中读取数据而是从io.Reader中读取数据。
func Fscan(r io.Reader, a ...interface{}) (n int, err error)func Fscanln(r io.Reader, a ...interface{}) (n int, err error)func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)
Sscan系列
这几个函数功能分别类似于fmt.Scan、fmt.Scanf、fmt.Scanln三个函数,只不过它们不是从标准输入中读取数据而是从指定字符串中读取数据。
func Sscan(str string, a ...interface{}) (n int, err error)func Sscanln(str string, a ...interface{}) (n int, err error)func Sscanf(str string, format string, a ...interface{}) (n int, err error)
IO操作
输入输出的底层原理
- 终端其实是一个文件,相关实例如下:
**os.Stdin**:标准输入的文件实例,类型为*File**os.Stdout**:标准输出的文件实例,类型为*File**os.Stderr**:标准错误输出的文件实例,类型为*File
以文件的方式操作终端:
package mainimport "os"func main() { var buf [16]byte os.Stdin.Read(buf[:]) os.Stdin.WriteString(string(buf[:]))}
文件操作相关API
func Create(name string) (file *File, err Error)// 根据提供的文件名创建新的文件,返回一个文件对象,默认权限是0666func NewFile(fd uintptr, name string) *File// 根据文件描述符创建相应的文件,返回一个文件对象func Open(name string) (file *File, err Error)// 只读方式打开一个名称为name的文件func OpenFile(name string, flag int, perm uint32) (file *File, err Error)// 打开名称为name的文件,flag是打开的方式,只读、读写等,perm是权限func (file *File) Write(b []byte) (n int, err Error)// 写入byte类型的信息到文件func (file *File) WriteAt(b []byte, off int64) (n int, err Error)// 在指定位置开始写入byte类型的信息func (file *File) WriteString(s string) (ret int, err Error)// 写入string信息到文件func (file *File) Read(b []byte) (n int, err Error)// 读取数据到b中func (file *File) ReadAt(b []byte, off int64) (n int, err Error)// 从off开始读取数据到b中func Remove(name string) Error// 删除文件名为name的文件
打开和关闭文件
os.Open()函数能够打开一个文件,返回一个*File和一个err。对得到的文件实例调用close()方法能够关闭文件。
package mainimport ( "fmt" "os")func main() { // 只读方式打开当前目录下的main.go文件 file, err := os.Open("./main.go") if err != nil { fmt.Println("open file failed!, err:", err) return } // 关闭文件 file.Close()}
写文件
package mainimport ( "fmt" "os")func main() { // 新建文件 file, err := os.Create("./xxx.txt") if err != nil { fmt.Println(err) return } defer file.Close() for i := 0; i < 5; i++ { file.WriteString("ab\n") file.Write([]byte("cd\n")) }}
读文件
文件读取可以用file.Read()和file.ReadAt(),读到文件末尾会返回io.EOF的错误;
package mainimport ( "fmt" "io" "os")func main() { // 打开文件 file, err := os.Open("./xx.txt") if err != nil { fmt.Println("open file err :", err) return } defer file.Close() // 定义接收文件读取的字节数组 var buf []byte = make([]byte, 100) var content []byte var i = 0 // 用于计数循环多少次 for { n, err := file.Read(buf) fmt.Println(i) // 循环的次数,发现读到最后的io.EOF时会先将最后一个包的数据返回,下次再单独读io.EOF if err == io.EOF { // 读取结束 break } if err != nil { fmt.Println("read file err ", err) return } content = append(content, buf[:n]...) fmt.Println(i, n) fmt.Println(string(buf[:n])) i++ }}/* 文件内容往文件中写如信息:枯藤往文件中写如信息:枯藤往文件中写如信息:枯藤guafsdfsdfdsfsdf----------af454651435465465/*00 100往文件中写如信息:枯藤往文件中写如信息:枯藤往文件中写如信息:枯藤g11 44uafsdfsdfdsfsdf----------af454651435465465 2*/
拷贝文件
package mainimport ( "fmt" "io" "os")func main(){ srcfile, err := os.Open("./xx.txt") if err != nil { fmt.Println(err) return } dstfile, err := os.Create("./copy.txt") if err != nil { fmt.Println(err) return } var buf = make([]byte, 100) for{ n, err := srcfile.Read(buf) if err == io.EOF { fmt.Println("read over!!") break } fmt.Println(n) dstfile.Write(buf[:n]) // 注意这里,要有n,要不最后会写入脏文件 } srcfile.Close() dstfile.Close()}
bufio(按行读取)
- bufio包实现了带缓冲区的读写,是对文件读写的封装
- bufio缓冲写数据
| 模式 |
含义 |
| os.O_WRONLY |
只写 |
| os.O_CREATE |
创建文件 |
| os.O_RDONLY |
只读 |
| os.O_RDWR |
读写 |
| os.O_TRUNC |
清空 |
| os.O_APPEND |
追加 |
package mainimport ( "bufio" "fmt" "io" "os")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) i := 0 for { line, _, err := reader.ReadLine() fmt.Println(i) if err == io.EOF { break } if err != nil { return } fmt.Println(string(line)) i++ }}func main() { re()}/* re0hello1 hello2hello3hello4hello5hello6hello7hello8hello9hello10*/
ioutil工具包(整个文件读入)
package mainimport ( "fmt" "io/ioutil")func wr() { err := ioutil.WriteFile("./yyy.txt", []byte("www.5lmh.com"), 0666) if err != nil { fmt.Println(err) return }}func re() { content, err := ioutil.ReadFile("./yyy.txt") if err != nil { fmt.Println(err) return } fmt.Println(string(content))}func main() { re()}/* 读取整个文件www.5lmh.com*/
例子
实现一个cat命令
使用文件操作相关知识,模拟实现linux平台cat命令的功能。
package mainimport ( "bufio" "flag" "fmt" "io" "os")// cat命令实现func cat(r *bufio.Reader) { for { buf, err := r.ReadBytes('\n') //注意是字符 if err == io.EOF { break } fmt.Fprintf(os.Stdout, "%s", buf) }}func main() { flag.Parse() // 解析命令行参数 if flag.NArg() == 0 { // 如果没有参数默认从标准输入读取内容 cat(bufio.NewReader(os.Stdin)) } // 依次读取每个指定文件的内容并打印到终端 for i := 0; i < flag.NArg(); i++ { f, err := os.Open(flag.Arg(i)) if err != nil { fmt.Fprintf(os.Stdout, "reading from %s failed, err:%v\n", flag.Arg(i), err) continue } cat(bufio.NewReader(f)) }}