学习Go语言基础类语法_Day05

目录

1、接口

接口像是一个公司里面的领导,他会定义一些通用规范,只设计规范,而不实现规范。

go语言的接口,是一种新的类型定义,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

语法格式和方法非常类似。

1.1、语法

/* 定义接口 */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
   /* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法实现*/
}

在接口定义中定义,若干个空方法。这些方法都具有通用性。

1.2、基本实例1:

下面我定义一个USB接口,有读read和写write两个方法,再定义一个电脑Computer和一个手机Mobile来实现这个接口。

USB接口

type USB interface {
	read()
	write()
}

Computer结构体

type Computer struct {
}

Mobile结构体

type Mobile struct {
}

Computer实现USB接口方法

func (c Computer) read() {
	fmt.Println("computer read...")
}

func (c Computer) write() {
	fmt.Println("computer write...")
}

Mobile实现USB接口方法

func (c Mobile) read() {
	fmt.Println("mobile read...")
}

func (c Mobile) write() {
	fmt.Println("mobile write...")
}

测试

func main() {
	c := Computer{}
	m := Mobile{}

	c.read()
	c.write()
	m.read()
	m.write()
}

运行结果

func main() {
	c := Computer{}
	m := Mobile{}

	c.read()
	c.write()
	m.read()
	m.write()
}

1.3、实现接口必须实现接口中的所有方法

下面我们定义一个OpenClose接口,里面有两个方法open和close,定义个Door结构体,实现其中一个方法。

package main

import "fmt"

type OpenClose interface {
	open()
	close()
}

type Door struct {
}

func (d Door) open() {
	fmt.Println("open door...")
}

func main() {
	var oc OpenClose
	oc = Door{} // 这里编译错误,提示只实现了一个接口
}

Caidd123理解:

就像标题所言实现接口就是实现接口中的所有方法,接口只是定义了一个规范,至于谁来做这个事情就是方法来做了,定义的结构体类型是写的这个事情包含的属性

1.4、接口值类型接收者和指针类型接收者

这个话题,本质上和方法的值类型接收者和指针类型接收者,的思考方法是一样的,值接收者是一个拷贝,是一个副本,而指针接收者,传递的是指针。

定义一个Pet接口

type Pet interface {
	eat()
}

定义一个Dog结构体

type Dog struct {
	name string
}

实现Pet接口(接收者是值类型)

func (dog Dog) eat() {
	fmt.Printf("dog: %p\n", &dog)
	fmt.Println("dog eat..")
	dog.name = "黑黑"
}

测试

func main() {
	dog := Dog{name: "花花"}
	fmt.Printf("dog: %p\n", &dog)
	dog.eat()
	fmt.Printf("dog: %v\n", dog)
}

运行结果

dog: 0xc000046240
dog: 0xc000046250
dog eat..
dog: {花花}

从运行结果,我们看出dog的地址变了,说明是复制了一份,dog的name没有变说明,外面的dog变量没有被改变。

将实现Pet接口改为指针接收者版

func (dog *Dog) eat() {
	fmt.Printf("dog: %p\n", dog)
	fmt.Println("dog eat..")
	dog.name = "黑黑"
}

再测试

func main() {
	dog := &Dog{name: "花花"}
	fmt.Printf("dog: %p\n", dog)
	dog.eat()
	fmt.Printf("dog: %v\n", dog)
}

运行结果

dog: 0xc00008c230
dog: 0xc00008c230
dog eat..
dog: &{黑黑}

1.5、接口和类型的关系

1.5.1、一个类型实现多个接口

一个类型实现多个接口,例如:有一个Player接口可以播放音乐,有一个Video接口可以播放视频,一个手机Mobile实现这两个接口,既可以播放音乐,又可以播放视频。

定义一个Player接口

type Player interface {
	playMusic()
}

定义一个Video接口

type Video interface {
	playVideo()
}

定义Mobile结构体

type Mobile struct {
}

实现两个接口

func (m Mobile) playMusic() {
	fmt.Println("播放音乐")
}

func (m Mobile) playVideo() {
	fmt.Println("播放视频")
}

测试

func main() {
	m := Mobile{}
	m.playMusic()
	m.playVideo()
}

运行结果

播放音乐
播放视频

1.5.2、多个类型实现同一个接口

比如,一个宠物接口Pet,猫类型Cat和狗类型Dog都可以实现该接口,都可以把猫和狗当宠物类型对待,这在其他语言中叫做多态

定义一个Pet接口

type Pet interface {
	eat()
}

定义一个Dog结构体

type Dog struct {
}

定义一个Cat结构体

type Cat struct {
}

实现接口

func (cat Cat) eat() {
	fmt.Println("cat eat...")
}

func (dog Dog) eat() {
	fmt.Println("dog eat...")
}

测试

func main() {
	var p Pet
	p = Cat{}
	p.eat()
	p = Dog{}
	p.eat()
}

运行结果

cat eat...
dog eat...

1.6、接口嵌套

接口可以通过嵌套,创建新的接口。例如:飞鱼,既可以飞,又可以游泳。我们创建一个飞Fly接口,创建一个游泳接口Swim,飞鱼接口有这两个接口组成。

飞Flyer接口

type Flyer interface {
	fly()
}

创建Swimmer接口

type Swimmer interface {
	swim()
}

组合一个接口FlyFish

type FlyFish interface {
	Flyer
	Swimmer
}

创建一个结构体Fish

type Fish struct {
}

实现这个组合接口

func (fish Fish) fly() {
	fmt.Println("fly...")
}

func (fish Fish) swim() {
	fmt.Println("swim...")
}

测试

func main() {
	var ff FlyFish
	ff = Fish{}
	ff.fly()
	ff.swim()
}

运行结果

fly...
swim...

1.7、接口实现OCP设计原则

而面向对象的可复用设计的第一块基石,便是所谓的”开-闭“原则(Open-Closed Principle,常缩写为OCP)。虽然,go不是面向对象语言,但是也可以模拟实现这个原则。对扩展是开放的,对修改是关闭的。

下面通过一个人养宠物的例子,来解释OCP设计原则。

定义一个宠物接口Pet

type Pet interface {
	eat()
	sleep()
}

该接口有吃和睡两个方法。

定义个Dog结构体

type Dog struct {
	name string
	age  int
}

Dog实现接口方法

func (dog Dog) eat() {
	fmt.Println("dog eat...")
}

func (dog Dog) sleep() {
	fmt.Println("dog sleep...")
}

定义一个Cat结构体

type Cat struct {
	name string
	age  int
}

Cat实现接口方法

func (cat Cat) eat() {
	fmt.Println("cat eat...")
}

func (cat Cat) sleep() {
	fmt.Println("cat sleep...")
}

定义个Person结构体

type Person struct {
	name string
}

为Person添加一个养宠物方法

func (per Person) care(pet Pet) {
	pet.eat()
	pet.sleep()
}

最后测试一下

func main() {

	dog := Dog{}
	cat := Cat{}
	per := Person{}

	per.care(dog)
	per.care(cat)

}

运行结果

dog eat...
dog sleep...
cat eat...
cat sleep...

使用接口的这种设计方法,可以很好的解耦合代码,实现软件设计的OCP原则(即开闭原则)

这样设计,如果再添加一个宠物,例如:一个鸟Bird,原有的代码不用修改,直接添加就可以。

只需要把上面的cat复制粘贴一份即可

type Bird struct {
	name string
	age  int
}

func (Bird Bird) eat() {
	fmt.Println("Bird eat...")
}

func (Bird Bird) sleep() {
	fmt.Println("Bird sleep...")
}

测试

func main() {

	dog := Dog{}
	cat := Cat{}
	Bird := Bird{}
	per := Person{}

	per.care(dog)
	per.care(cat)
	per.care(Bird)
}

运行结果

dog eat...
dog sleep...
cat eat...
cat sleep...
Bird eat...
Bird sleep...

1.8、通过接口实现OCP设计原则

而面向对象的可复用设计的第一块基石,便是所谓的”开-闭“原则(Open-Closed Principle,常缩写为OCP)。虽然,go不是面向对象语言,但是也可以模拟实现这个原则。对扩展是开放的,对修改是关闭的。

下面通过一个人养宠物的例子,来解释OCP设计原则。

定义一个宠物接口Pet

type Pet interface {
	eat()
	sleep()
}

该接口有吃和睡两个方法。

定义个Dog结构体

type Dog struct {
	name string
	age  int
}

Dog实现接口方法

func (dog Dog) eat() {
	fmt.Println("dog eat...")
}

func (dog Dog) sleep() {
	fmt.Println("dog sleep...")
}

定义一个Cat结构体

type Cat struct {
	name string
	age  int
}

Cat实现接口方法

func (cat Cat) eat() {
	fmt.Println("cat eat...")
}

func (cat Cat) sleep() {
	fmt.Println("cat sleep...")
}

定义个Person结构体

type Person struct {
	name string
}

为Person添加一个养宠物方法

func (per Person) care(pet Pet) {
	pet.eat()
	pet.sleep()
}

最后测试一下

func main() {

	dog := Dog{}
	cat := Cat{}
	per := Person{}

	per.care(dog)
	per.care(cat)

}

运行结果

dog eat...
dog sleep...
cat eat...
cat sleep...

使用接口的这种设计方法,可以很好的解耦合代码,实现软件设计的OCP原则(即开闭原则)

这样设计,如果再添加一个宠物,例如:一个鸟Bird,原有的代码不用修改,直接添加就可以。

2、OS包

2.1、文件目录相关

2.1.1、创建文件

func createFile() {
	f, err := os.Create("test.txt")
	if err != nil {
		fmt.Printf("err: %v\n", err)
	} else {
		fmt.Printf("f: %v\n", f)
	}
}

2.1.2、创建目录

func createDir() {
	// 创建单个目录
	/* err := os.Mkdir("test", os.ModePerm)
	if err != nil {
		fmt.Printf("err: %v\n", err)
	} */
	// 创建连续的目录
    err := os.MkdirAll("test/a/b", os.ModePerm)
	if err != nil {
		fmt.Printf("err: %v\n", err)
	}
}

2.1.3、删除目录

func removeDir() {
    //删除test.txt
	/* err := os.Remove("test.txt")
	if err != nil {
		fmt.Printf("err: %v\n", err)
	} */
	//删除test目录下的所有文件
	err := os.RemoveAll("test")
	if err != nil {
		fmt.Printf("err: %v\n", err)
	}
}

2.1.4、获得工作目录

func getWd() {
	dir, err := os.Getwd()
	if err != nil {
		fmt.Printf("err: %v\n", err)
	} else {
		fmt.Printf("dir: %v\n", dir)
	}
}

2.1.5、修改工作目录

func chWd() {
	err := os.Chdir("d:/")
	if err != nil {
		fmt.Printf("err: %v\n", err)
	}
	fmt.Println(os.Getwd())
}

2.1.6、获得临时目录

func getTemp() {
	s := os.TempDir()
	fmt.Printf("s: %v\n", s)
}

2.1.7、重命名文件

func renameFile() {
	err := os.Rename("test.txt", "test2.txt")
	if err != nil {
		fmt.Printf("err: %v\n", err)
	}
}

2.1.8、读文件

func readFile() {
	b, err := os.ReadFile("test2.txt")
	if err != nil {
		fmt.Printf("err: %v\n", err)
	} else {
		fmt.Printf("b: %v\n", string(b[:]))
	}
}

2.1.9、写文件

func writeFile() {
	s := "hello world"
	os.WriteFile("test2.txt", []byte(s), os.ModePerm)
}

2.2、File文件读操作

2.2.1、打开关闭文件

// 
func openCloseFile() {
	// 只能读
	f, _ := os.Open("a.txt")
	fmt.Printf("f.Name(): %v\n", f.Name())
	// 根据第二个参数 可以读写或者创建
	f2, _ := os.OpenFile("a1.txt", os.O_RDWR|os.O_CREATE, 0755)
	fmt.Printf("f2.Name(): %v\n", f2.Name())

	err := f.Close()
	fmt.Printf("err: %v\n", err)
	err2 := f2.Close()
	fmt.Printf("err2: %v\n", err2)
}

2.2.2、创建文件

func createFile() {
	// 等价于:OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
	f, _ := os.Create("a2.txt")
	fmt.Printf("f.Name(): %v\n", f.Name())
	// 第一个参数 目录默认:Temp 第二个参数 文件名前缀
	f2, _ := os.CreateTemp("", "temp")
	fmt.Printf("f2.Name(): %v\n", f2.Name())
}

2.2.3、读取

func readOps() {
	/* 	f, _ := os.Open("a.txt")
	   	for {
	   		buf := make([]byte, 6)
	   		n, err := f.Read(buf)
	   		fmt.Println(string(buf))
	   		fmt.Printf("n: %v\n", n)
	   		if err == io.EOF {
	   			break
	   		}
	   	}
	   	f.Close()
	*/
	/* buf := make([]byte, 10)
	f2, _ := os.Open("a.txt")
	// 从5开始读10个字节
	n, _ := f2.ReadAt(buf, 5)
	fmt.Printf("n: %v\n", n)
	fmt.Printf("string(buf): %v\n", string(buf))
	f2.Close() */

	// 测试 a目录下面有b和c目录
	/* f, _ := os.Open("a")
	de, _ := f.ReadDir(-1)
	for _, v := range de {
		fmt.Printf("v.IsDir(): %v\n", v.IsDir())
		fmt.Printf("v.Name(): %v\n", v.Name())
	} */

	// 定位
	f, _ := os.Open("a.txt")
	f.Seek(3, 0)
	buf := make([]byte, 10)
	n, _ := f.Read(buf)
	fmt.Printf("n: %v\n", n)
	fmt.Printf("string(buf): %v\n", string(buf))
	f.Close()
	
}

2.3、File文件写操作

2.3.1、写字节数组

func write() {
	f, _ := os.OpenFile("a.txt", os.O_RDWR|os.O_TRUNC, 0755)
	f.Write([]byte(" hello golang"))
	f.Close()
}

2.3.2、写字符串

func writeString() {
	f, _ := os.OpenFile("a.txt", os.O_RDWR|os.O_APPEND, 0755)
	f.WriteString("hello java")
	f.Close()
}

2.3.3、随机写

func writeAt() {
	f, _ := os.OpenFile("a.txt", os.O_RDWR, 0755)
	f.WriteAt([]byte("aaa"), 3)
	f.Close()
}

2.4、进程相关

package main

import (
	"fmt"
	"os"
	"time"
)

func main() {
	// 获得当前正在运行的进程id
	fmt.Printf("os.Getpid(): %v\n", os.Getpid())
	// 父id
	fmt.Printf("os.Getppid(): %v\n", os.Getppid())

	//设置新进程的属性
	attr := &os.ProcAttr{
		//files指定新进程继承的活动文件对象
		//前三个分别为,标准输入、标准输出、标准错误输出
		Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
		//新进程的环境变量
		Env: os.Environ(),
	}

	//开始一个新进程
	p, err := os.StartProcess("C:\\Windows\\System32\\notepad.exe", []string{"C:\\Windows\\System32\\notepad.exe", "D:\\a.txt"}, attr)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(p)
	fmt.Println("进程ID:", p.Pid)

	//通过进程ID查找进程
	p2, _ := os.FindProcess(p.Pid)
	fmt.Println(p2)

	//等待10秒,执行函数
	time.AfterFunc(time.Second*10, func() {
		//向p进程发送退出信号
		p.Signal(os.Kill)
	})

	//等待进程p的退出,返回进程状态
	ps, _ := p.Wait()
	fmt.Println(ps.String())
}

2.5、和环境相关的方法

package main

import (
	"fmt"
	"os"
)

func main() {
	// 获得所有环境变量
	s := os.Environ()
	fmt.Printf("s: %v\n", s)
	// 获得某个环境变量
	s2 := os.Getenv("GOPATH")
	fmt.Printf("s2: %v\n", s2)
	// 设置环境变量
	os.Setenv("env1", "env1")
	s2 = os.Getenv("aaa")
	fmt.Printf("s2: %v\n", s2)
	fmt.Println("-----------")

	// 查找
	s3, b := os.LookupEnv("env1")
	fmt.Printf("b: %v\n", b)
	fmt.Printf("s3: %v\n", s3)

	// 替换
	os.Setenv("NAME", "gopher")
	os.Setenv("BURROW", "/usr/gopher")

	fmt.Println(os.ExpandEnv("$NAME lives in ${BURROW}."))

	// 清空环境变量
	// os.Clearenv()

}

3、io包

Go 语言中,为了方便开发者使用,将 IO 操作封装在了如下几个包中:

  • io 为 IO 原语(I/O primitives)提供基本的接口 os File Reader Writer
  • io/ioutil 封装一些实用的 I/O 函数
  • fmt 实现格式化 I/O,类似 C 语言中的 printf 和 scanf format fmt
  • bufio 实现带缓冲I/O

3.1、io — 基本的 IO 接口

在 io 包中最重要的是两个接口:Reader 和 Writer 接口。本章所提到的各种 IO 包,都跟这两个接口有关,也就是说,只要实现了这两个接口,它就有了 IO 的功能

3.1.1、Reader 接口

type Reader interface {
    Read(p []byte) (n int, err error)
}

3.1.2、Writer 接口

type Writer interface {
    Write(p []byte) (n int, err error)
}

3.2、那些类型实现了Reader和Writer接口

os.File 同时实现了 io.Reader 和 io.Writer
strings.Reader 实现了 io.Reader
bufio.Reader/Writer 分别实现了 io.Reader 和 io.Writer
bytes.Buffer 同时实现了 io.Reader 和 io.Writer
bytes.Reader 实现了 io.Reader
compress/gzip.Reader/Writer 分别实现了 io.Reader 和 io.Writer
crypto/cipher.StreamReader/StreamWriter 分别实现了 io.Reader 和 io.Writer
crypto/tls.Conn 同时实现了 io.Reader 和 io.Writer
encoding/csv.Reader/Writer 分别实现了 io.Reader 和 io.Writer

4、ioutil包

封装一些实用的 I/O 函数

名称 作用
ReadAll 读取数据,返回读到的字节 slice
ReadDir 读取一个目录,返回目录入口数组 []os.FileInfo
ReadFile 读一个文件,返回文件内容(字节slice)
WriteFile 根据文件路径,写入字节slice
TempDir 在一个目录中创建指定前缀名的临时目录,返回新临时目录的路径
TempFile 在一个目录中创建指定前缀名的临时文件,返回 os.File

5、fmt包

fmt包实现了格式化的I/O函数,这点类似C语言中的printf和scanf,但是更加简单. format

5.1、Scanning

一组类似的函数通过扫描已格式化的文本来产生值。

  1. Scan、Scanf 和 Scanln 从os.Stdin 中读取;
  2. Fscan、Fscanf 和 Fscanln 从指定的 io.Reader 中读取;
  3. Sscan、Sscanf 和 Sscanln 从实参字符串中读取。
  4. Scanln、Fscanln 和 Sscanln在换行符处停止扫描,且需要条目紧随换行符之后;
  5. Scanf、Fscanf 和 Sscanf需要输入换行符来匹配格式中的换行符;其它函数则将换行符视为空格。
  6. Scanf、Fscanf 和 Sscanf 根据格式字符串解析实参,类似于 Printf。例如,%x会将一个整数扫描为十六进制数,而 %v 则会扫描该值的默认表现格式。

5.2、函数

5.2.1、Print

Print系列函数会将内容输出到系统的标准输出,区别在于Print函数直接输出内容,Printf函数支持格式化输出字符串,Println函数会在输出内容的结尾添加一个换行符。

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)

例子:

func main() {
	fmt.Print("在终端打印该信息。")
	name := "沙河小王子"
	fmt.Printf("我是:%s\n", name)
	fmt.Println("在终端打印单独一行显示")
}

执行上面的代码输出:

在终端打印该信息。我是:沙河小王子
在终端打印单独一行显示

5.2.2、Errorf

func Errorf(format string, a ...interface{}) error

Errorf 根据于格式说明符进行格式化,并将字符串作为满足 error 的值返回,其返回类型是error.

5.2.3、Fprint

Fprint系列函数会将内容输出到一个io.Writer接口类型的变量w中,我们通常用这个函数往文件中写入内容。

Fprint 使用其操作数的默认格式进行格式化并写入到 w。当两个连续的操作数均不为字符串时,它们之间就会添加空格。它返回写入的字节数以及任何遇到的错误。

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)

注意,只要满足io.Writer接口的类型都支持写入。

5.2.4、Sprint

Sprint系列函数会把传入的数据生成并返回一个字符串。

func Sprint(a ...interface{}) string
func Sprintf(format string, a ...interface{}) string
func Sprintln(a ...interface{}) string

示例代码如下:

s1 := fmt.Sprint("沙河小王子")
name := "沙河小王子"
age := 18
s2 := fmt.Sprintf("name:%s,age:%d", name, age)
s3 := fmt.Sprintln("沙河小王子")
fmt.Println(s1, s2, s3)

5.2.5、Fprintf

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)

Fprintf 根据于格式说明符进行格式化并写入到 w。它返回写入的字节数以及任何遇到的写入错误。

5.2.6、Fprintln

func Fprintln(w io.Writer, a ...interface{}) (n int, err error) 

Fprintln 使用其操作数的默认格式进行格式化并写入到 w。其操作数之间总是添加空格,且总在最后追加一个换行符。它返回写入的字节数以及任何遇到的错误。

5.3、格式化占位符

*printf系列函数都支持format格式化参数,在这里我们按照占位符将被替换的变量类型划分,方便查询和记忆。

5.3.1、通用占位符

占位符 说明
%v 值的默认格式表示
%+v 类似%v,但输出结构体时会添加字段名
%#v 值的Go语法表示
%T 打印值的类型
%% 百分号

示例代码如下:

fmt.Printf("%v\n", 100)
fmt.Printf("%v\n", false)
o := struct{ name string }{"小王子"}
fmt.Printf("%v\n", o)
fmt.Printf("%#v\n", o)
fmt.Printf("%T\n", o)
fmt.Printf("100%%\n")

输出结果如下:

100
false
{小王子}
struct { name string }{name:"小王子"}
struct { name string }
100%

5.3.2、格布尔型

占位符 说明
%t true或false

5.3.3、整型

占位符 说明
%b 表示为二进制
%c 该值对应的unicode码值
%d 表示为十进制
%o 表示为八进制
%x 表示为十六进制,使用a-f
%X 表示为十六进制,使用A-F
%U 表示为Unicode格式:U+1234,等价于”U+%04X”
%q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示

示例代码如下:

n := 65
fmt.Printf("%b\n", n)
fmt.Printf("%c\n", n)
fmt.Printf("%d\n", n)
fmt.Printf("%o\n", n)
fmt.Printf("%x\n", n)
fmt.Printf("%X\n", n)

输出结果如下:

1000001
A
65
101
41
41

5.3.4、浮点数与复数

占位符 说明
%b 无小数部分、二进制指数的科学计数法,如-123456p-78
%e 科学计数法,如-1234.456e+78
%E 科学计数法,如-1234.456E+78
%f 有小数部分但无指数部分,如123.456
%F 等价于%f
%g 根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)
%G 根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)

示例代码如下:

f := 12.34
fmt.Printf("%b\n", f)
fmt.Printf("%e\n", f)
fmt.Printf("%E\n", f)
fmt.Printf("%f\n", f)
fmt.Printf("%g\n", f)
fmt.Printf("%G\n", f)

输出结果如下:

6946802425218990p-49
1.234000e+01
1.234000E+01
12.340000
12.34
12.34

5.3.5、字符串和[]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)
fmt.Printf("%X\n", s)

输出结果如下:

小王子
"小王子"
e5b08fe78e8be5ad90
E5B08FE78E8BE5AD90

5.3.6、指针

占位符 说明
%p 表示为十六进制,并加上前导的0x

示例代码如下:

a := 10
fmt.Printf("%p\n", &a)
fmt.Printf("%#p\n", &a)

输出结果如下:

0xc000094000
c000094000

5.3.7、宽度标识符

宽度通过一个紧跟在百分号后面的十进制数指定,如果未指定宽度,则表示值时除必需之外不作填充。精度通过(可选的)宽度后跟点号后跟的十进制数指定。如果未指定精度,会使用默认精度;如果点号后没有跟数字,表示精度为0。举例如下:

占位符 说明
%f 默认宽度,默认精度
%9f 宽度9,默认精度
%.2f 默认宽度,精度2
%9.2f 宽度9,精度2
%9.f 宽度9,精度0

示例代码如下:

n := 12.34
fmt.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)

输出结果如下:

12.340000
12.340000
12.34
    12.34
       12

5.3.8、其他flag

占位符 说明
’+’ 总是输出数值的正负号;对%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)

输出结果如下:

小王子
  小王子
小王子  
  小王子
小王子  
   小王
00小王子

5.4、获取输入

Go语言fmt包下有fmt.Scanfmt.Scanffmt.Scanln三个函数,可以在程序运行过程中从标准输入获取用户的输入。

5.4.1、fmt.Scan

函数定签名如下:

func Scan(a ...interface{}) (n int, err error)
  • Scan从标准输入扫描文本,读取由空白符分隔的值保存到传递给本函数的参数中,换行符视为空白符。
  • 本函数返回成功扫描的数据个数和遇到的任何错误。如果读取的数据个数比提供的参数少,会返回一个错误报告原因。

具体代码示例如下:

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)
}

将上面的代码编译后在终端执行,在终端依次输入小王子28false使用空格分隔。

$ ./scan_demo 
小王子 28 false
扫描结果 name:小王子 age:28 married:false 

fmt.Scan从标准输入中扫描用户输入的数据,将以空白符分隔的数据分别存入指定的参数。

5.4.2、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)
}

将上面的代码编译后在终端执行,在终端按照指定的格式依次输入小王子28false

$ ./scan_demo 
1:小王子 2:28 3:false
扫描结果 name:小王子 age:28 married:false 

fmt.Scanf不同于fmt.Scan简单的以空格作为输入数据的分隔符,fmt.Scanf为输入数据指定了具体的输入内容格式,只有按照格式输入数据才会被扫描并存入对应变量。

例如,我们还是按照上个示例中以空格分隔的方式输入,fmt.Scanf就不能正确扫描到输入的数据。

$ ./scan_demo 
小王子 28 false
扫描结果 name: age:0 married:false 

5.4.3、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)
}

将上面的代码编译后在终端执行,在终端依次输入小王子28false使用空格分隔。

$ ./scan_demo 
小王子 28 false
扫描结果 name:小王子 age:28 married:false 

fmt.Scanln遇到回车就结束扫描了,这个比较常用。

5.4.4、bufio.NewReader

有时候我们想完整获取输入的内容,而输入的内容可能包含空格,这种情况下可以使用bufio包来实现。示例代码如下:

func bufioDemo() {
	reader := bufio.NewReader(os.Stdin) // 从标准输入生成读对象
	fmt.Print("请输入内容:")
	text, _ := reader.ReadString('\n') // 读到换行
	text = strings.TrimSpace(text)
	fmt.Printf("%#v\n", text)
}

5.4.5、Fscan系列

这几个函数功能分别类似于fmt.Scanfmt.Scanffmt.Scanln三个函数,只不过它们不是从标准输入中读取数据而是从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)

5.4.6、Sscan系列

这几个函数功能分别类似于fmt.Scanfmt.Scanffmt.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)

6、flag包

6.1、flag参数类型

flag包支持的命令行参数类型有boolintint64uintuint64float float64stringduration

flag参数 有效值
字符串flag 合法字符串
整数flag 1234、0664、0x1234等类型,也可以是负数。
浮点数flag 合法浮点数
bool类型flag 1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False。
时间段flag 任何合法的时间段字符串。如”300ms”、”-1.5h”、”2h45m”。 合法的单位有”ns”、”us” /“µs”、”ms”、”s”、”m”、”h”。

6.2、定义命令行flag参数

有以下两种常用的定义命令行flag参数的方法。

6.2.1、flag.Type()

基本格式如下:

flag.Type(flag名, 默认值, 帮助信息)*Type 例如我们要定义姓名、年龄、婚否三个命令行参数,我们可以按如下方式定义:

name := flag.String("name", "张三", "姓名")
age := flag.Int("age", 18, "年龄")
married := flag.Bool("married", false, "婚否")
delay := flag.Duration("d", 0, "时间间隔")

需要注意的是,此时nameagemarrieddelay均为对应类型的指针。

6.2.2、flag.TypeVar()

基本格式如下: flag.TypeVar(Type指针, flag名, 默认值, 帮助信息) 例如我们要定义姓名、年龄、婚否三个命令行参数,我们可以按如下方式定义:

var name string
var age int
var married bool
var delay time.Duration
flag.StringVar(&name, "name", "张三", "姓名")
flag.IntVar(&age, "age", 18, "年龄")
flag.BoolVar(&married, "married", false, "婚否")
flag.DurationVar(&delay, "d", 0, "时间间隔")

6.2.3、flag.Parse()

通过以上两种方法定义好命令行flag参数后,需要通过调用flag.Parse()来对命令行参数进行解析。

支持的命令行参数格式有以下几种:

  • -flag xxx (使用空格,一个-符号)
  • --flag xxx (使用空格,两个-符号)
  • -flag=xxx (使用等号,一个-符号)
  • --flag=xxx (使用等号,两个-符号)

其中,布尔类型的参数必须使用等号的方式指定。

Flag解析在第一个非flag参数(单个”-“不是flag参数)之前停止,或者在终止符”–“之后停止。

6.2.4、flag其他函数

flag.Args()  ////返回命令行参数后的其他参数,以[]string类型
flag.NArg()  //返回命令行参数后的其他参数个数
flag.NFlag() //返回使用的命令行参数个数

6.3、完整示例

6.3.1、定义

func main() {
	//定义命令行参数方式1
	var name string
	var age int
	var married bool
	var delay time.Duration
	flag.StringVar(&name, "name", "张三", "姓名")
	flag.IntVar(&age, "age", 18, "年龄")
	flag.BoolVar(&married, "married", false, "婚否")
	flag.DurationVar(&delay, "d", 0, "延迟的时间间隔")

	//解析命令行参数
	flag.Parse()
	fmt.Println(name, age, married, delay)
	//返回命令行参数后的其他参数
	fmt.Println(flag.Args())
	//返回命令行参数后的其他参数个数
	fmt.Println(flag.NArg())
	//返回使用的命令行参数个数
	fmt.Println(flag.NFlag())
}

6.3.2、使用

命令行参数使用提示:

$ ./flag_demo -help
Usage of ./flag_demo:
  -age int
        年龄 (default 18)
  -d duration
        时间间隔
  -married
        婚否
  -name string
        姓名 (default "张三")

正常使用命令行flag参数:

$ ./flag_demo -name 沙河娜扎 --age 28 -married=false -d=1h30m
沙河娜扎 28 false 1h30m0s
[]
0
4

使用非flag命令行参数:

$ ./flag_demo a b c
张三 18 false 0s
[a b c]
3
0

7、文件操作

7.1、打开和关闭文件

os.Open()函数能够打开一个文件,返回一个*File和一个err。对得到的文件实例调用close()方法能够关闭文件。

package main

import (
	"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()
}

为了防止文件忘记关闭,我们通常使用defer注册文件关闭语句。

7.2、读取文件

7.2.1、file.Read()

7.2.1.1、基本使用

Read方法定义如下:

func (f *File) Read(b []byte) (n int, err error)

它接收一个字节切片,返回读取的字节数和可能的具体错误,读到文件末尾时会返回0io.EOF。 举个例子:

func main() {
	// 只读方式打开当前目录下的main.go文件
	file, err := os.Open("./main.go")
	if err != nil {
		fmt.Println("open file failed!, err:", err)
		return
	}
	defer file.Close()
	// 使用Read方法读取数据
	var tmp = make([]byte, 128)
	n, err := file.Read(tmp)
	if err == io.EOF {
		fmt.Println("文件读完了")
		return
	}
	if err != nil {
		fmt.Println("read file failed, err:", err)
		return
	}
	fmt.Printf("读取了%d字节数据\n", n)
	fmt.Println(string(tmp[:n]))
}

7.2.1.2、循环读取

使用for循环读取文件中的所有数据。

func main() {
	// 只读方式打开当前目录下的main.go文件
	file, err := os.Open("./main.go")
	if err != nil {
		fmt.Println("open file failed!, err:", err)
		return
	}
	defer file.Close()
	// 循环读取文件
	var content []byte
	var tmp = make([]byte, 128)
	for {
		n, err := file.Read(tmp)
		if err == io.EOF {
			fmt.Println("文件读完了")
			break
		}
		if err != nil {
			fmt.Println("read file failed, err:", err)
			return
		}
		content = append(content, tmp[:n]...)
	}
	fmt.Println(string(content))
}

7.2.2、bufio读取文件

bufio是在file的基础上封装了一层API,支持更多的功能。

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

// bufio按行读取示例
func main() {
	file, err := os.Open("./xx.txt")
	if err != nil {
		fmt.Println("open file failed, err:", err)
		return
	}
	defer file.Close()
	reader := bufio.NewReader(file)
	for {
		line, err := reader.ReadString('\n') //注意是字符
		if err == io.EOF {
			if len(line) != 0 {
				fmt.Println(line)
			}
			fmt.Println("文件读完了")
			break
		}
		if err != nil {
			fmt.Println("read file failed, err:", err)
			return
		}
		fmt.Print(line)
	}
}

7.2.3、ioutil读取整个文件

io/ioutil包的ReadFile方法能够读取完整的文件,只需要将文件名作为参数传入。

package main

import (
	"fmt"
	"io/ioutil"
)

// ioutil.ReadFile读取整个文件
func main() {
	content, err := ioutil.ReadFile("./main.go")
	if err != nil {
		fmt.Println("read file failed, err:", err)
		return
	}
	fmt.Println(string(content))
}

7.3、文件写入

os.OpenFile()函数能够以指定模式打开文件,从而实现文件写入相关功能。

func OpenFile(name string, flag int, perm FileMode) (*File, error) {
	...
}

其中:

name:要打开的文件名 flag:打开文件的模式。 模式有以下几种:

模式 含义
os.O_WRONLY 只写
os.O_CREATE 创建文件
os.O_RDONLY 只读
os.O_RDWR 读写
os.O_TRUNC 清空
os.O_APPEND 追加

perm:文件权限,一个八进制数。r(读)04,w(写)02,x(执行)01。

7.3.1、Write和WriteString

func main() {
	file, err := os.OpenFile("xx.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
	if err != nil {
		fmt.Println("open file failed, err:", err)
		return
	}
	defer file.Close()
	str := "hello 沙河"
	file.Write([]byte(str))       //写入字节切片数据
	file.WriteString("hello 小王子") //直接写入字符串数据
}

7.3.2、bufio.NewWriter

func main() {
	file, err := os.OpenFile("xx.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
	if err != nil {
		fmt.Println("open file failed, err:", err)
		return
	}
	defer file.Close()
	writer := bufio.NewWriter(file)
	for i := 0; i < 10; i++ {
		writer.WriteString("hello沙河\n") //将数据先写入缓存
	}
	writer.Flush() //将缓存中的内容写入文件
}

7.3.3、ioutil.WriteFile

func main() {
	str := "hello 沙河"
	err := ioutil.WriteFile("./xx.txt", []byte(str), 0666)
	if err != nil {
		fmt.Println("write file failed, err:", err)
		return
	}
}
posted @   Caidd123  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示