Go语言基础语法

1、Go语言基础语法

1.1 Go标记

Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。如以下 GO 语句由 6 个标记组成:

fmt.Println("Hello, World!")

6 个标记是(每行一个):

fmt
.
Println
(
"Hello, World!"
)

1.2 行分隔符

在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工

作都将由 Go 编译器自动完成。

如果你打算将多个语句写在同一行,它们则必须使用 ; 人为区分,但在实际开发中我们并不鼓励这种做法。

如以下为两个语句:

fmt.Println("Hello, World!")
fmt.Println("Hello Go!")

1.3 注释

注释不会被编译,每一个包应该有相关注释。

单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /*

开头,并以 */ 结尾。如:

// 单行注释
/*
 Author
 我是多行注释
 */

1.4 标识符

标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(AZ和az)数字(0~9)、下划线_

组成的序列,但是第一个字符必须是字母或下划线而不能是数字。

以下是有效的标识符:

mahesh   kumar   abc   move_name   a_123
myname50   _temp   j   a23b9   retVal

以下是无效的标识符:

  • 1ab(以数字开头)
  • case(Go 语言的关键字)
  • a+b(运算符是不允许的)

1.5 字符串连接

Go 语言的字符串连接可以通过 + 实现。

package main

import "fmt"

func main() {
	// GoogleRunoob
	fmt.Println("Google" + "Runoob")
}

1.6 关键字

下面列举了 Go 代码中会使用到的 25 个关键字或保留字。

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符:

append bool byte cap close complex complex64 complex128 uint16
copy false float32 float64 imag int int8 int16 uint32
int32 int64 iota len make new nil panic uint64
print println real recover string true uint uint8 uintptr

程序一般由关键字、常量、变量、运算符、类型和函数组成。

程序中可能会使用到这些分隔符:括号 (),中括号 [] 和大括号 {}。

程序中可能会使用到这些标点符号:.,;:

1.7 Go 语言的空格

Go 语言中变量的声明必须使用空格隔开,如:

var age int;

语句中适当使用空格能让程序更易阅读。

无空格:

fruit=apples+oranges;

在变量与运算符间加入空格,程序看起来更加美观,如:

fruit = apples + oranges;

1.8 格式化字符串

Go 语言中使用 fmt.Sprintf 格式化字符串并赋值给新串。

package main

import (
	"fmt"
)

func main() {
	// go中格式化字符串并赋值给新串,使用fmt.Sprintf
	// %s表示字符串
	var stockcode = "000987"
	var enddate = "2020-12-31"
	var url = "Code=%s&endDate=%s"
	var target_url = fmt.Sprintf(url, stockcode, enddate)
	// Code=000987&endDate=2020-12-31
	fmt.Println(target_url)
	// 另外一个实例,%d表示整型
	const name, age = "Kim", 22
	s := fmt.Sprintf("%s is %d years old.\n", name, age)
	// Kim is 22 years old.
	fmt.Println(s)
}

Go 可以使用 fmt.Sprintf 来格式化字符串,格式如下:

fmt.Sprintf(格式化样式, 参数列表…)
  • 格式化样式:字符串形式,格式化符号以 % 开头, %s 字符串格式,%d 十进制的整数格式。

  • 参数列表:多个参数以逗号分隔,个数必须与格式化样式中的个数一一对应,否则运行时会报错。

Go 字符串格式化符号:

格 式 描 述
%v 按值的本来值输出
%+v 在 %v 基础上,对结构体字段名和值进行展开
%#v 输出 Go 语言语法格式的值
%T 输出 Go 语言语法格式的类型和值
%% 输出 % 本体
%t true或false
%b 整型以二进制方式显示(整数)或者是无小数部分,指数为二的幂的科学计数法(浮点数)
%o 整型以八进制方式显示
%d 整型以十进制方式显示
%x 整型以十六进制、字母小写方式显示或者字符串或切片显示,每字节两个字符
%X 整型以十六进制、字母大写方式显示或者字符串或切片显示,每字节两个字符
%c 相应Unicode码所表示的字符
%q 单引号围绕的字符字面值,由Go语言安全的转义(整数)或者是双引号围绕的字符串,由Go语言安全的转义(字符串)
%U Unicode 字符
%e 科学计数法
%E 科学计数法
%f 浮点数
%g 根据情况选择%e或%f产生更紧凑的无末尾0的输出
%G 根据情况选择%E或%f产生更紧凑的无末尾0的输出
%p 指针,十六进制方式显示
%s 字符串或切片显示
+ 总打印数值的正负号;对于%q(%+q)保证只输出ASCII编码的字符
- 在右侧而非左侧填充空格(左对齐该区域)
# 备用格式:对八进制添加前导0(%#o),对十六进制添加前导0x(%#x),对%p(%#p)去掉前导0x

格式化例子:

package main

import (
	"fmt"
)

type point struct {
	x, y int
}

func main() {
	p := point{1, 2}
	// {1 2}
	fmt.Printf("%v\n", p)
	// {x:1 y:2}
	fmt.Printf("%+v\n", p)
	// main.point{x:1, y:2}
	fmt.Printf("%#v\n", p)
	// main.point
	fmt.Printf("%T\n", p)
	// 100%
	fmt.Printf("%d%%\n", 100)
	// true
	fmt.Printf("%t\n", true)
	// 123
	fmt.Printf("%d\n", 123)
	// 1110
	fmt.Printf("%b\n", 14)
	// 7886928847432581p-49
	fmt.Printf("%b\n", 14.01)
	// 16
	fmt.Printf("%o\n", 14)
	// 14
	fmt.Printf("%d\n", 14)
	// e
	fmt.Printf("%x\n", 14)
	// E
	fmt.Printf("%X\n", 14)
	// 616263
	fmt.Printf("%x\n", "abc")
	// 616263
	fmt.Printf("%X\n", "abc")
	// c
	fmt.Printf("%c\n", 'c')
	// 'a'
	fmt.Printf("%q\n", 'a')
	// "abc"
	fmt.Printf("%q\n", "abc")
	// U+0061
	fmt.Printf("%U\n", 'a')
	// 1.234000e+08
	fmt.Printf("%e\n", 123400000.0)
	// 1.234000E+08
	fmt.Printf("%E\n", 123400000.0)
	// 78.900000
	fmt.Printf("%f\n", 78.9)
	// 78.9
	fmt.Printf("%g\n", 78.9)
	// 78.9
	fmt.Printf("%G\n", 78.9)
	// 0xc0000160b0
	fmt.Printf("%p\n", &p)
	// "abc"
	fmt.Printf("%s\n", "\"abc\"")
	// +的演示
	fmt.Printf("%+d\n", 10)
	fmt.Printf("%+d\n", -10)
	// '我'
	fmt.Printf("%q\n", '我')
	// "a我们b"
	fmt.Printf("%q\n", "a我们b")
	// '\u6211'
	fmt.Printf("%+q\n", '我')
	// "a\u6211\u4eecb"
	fmt.Printf("%+q\n", "a我们b")
	// -的演示
	// 默认右对齐
	// |    12|   345|
	fmt.Printf("|%6d|%6d|\n", 12, 345)
	// |  1.20|  3.45|
	fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45)
	// 加-表示左对齐
	// |1.20  |3.45  |
	fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45)
	// 默认右对齐
	//|   foo|     b|
	fmt.Printf("|%6s|%6s|\n", "foo", "b")
	// 加-表示左对齐
	// |foo   |b     |
	fmt.Printf("|%-6s|%-6s|\n", "foo", "b")
	// #的演示
	// 016
	fmt.Printf("%#o\n", 14)
	// 0xe
	fmt.Printf("%#x\n", 14)
	// c0000160b0
	fmt.Printf("%#p\n", &p)
}

宽度和精度:

宽度是%之后的值,如果没有指定,则使用该值的默认值,精度是跟在宽度之后的值,如果没有指定,也使用要打

印的值的默认精度。%9.2f,宽度9,精度2

%f default width,default precision
%9f width 9,default precision
%.2f default width,precision 2
%9.2f width 9,precision 2
%9.f width 9,precision 0

对数值而言,宽度为该数值占用区域的最小宽度;精度为小数点之后的位数。但对于 %g/%G 而言,精度为所有数

字的总数。例如,对于123.45,格式 %6.2f会打印123.45,而 %.4g 会打印123.5。%e 和 %f 的默认精度为6;但

对于 %g 而言,它的默认精度为确定该值所必须的最小位数。

对大多数值而言,宽度为输出的最小字符数,如果必要的话会为已格式化的形式填充空格。对字符串而言,精度为

输出的最大字符数,如果必要的话会直接截断。

宽度是指必要的最小宽度,若结果字符串的宽度超过指定宽度时, 指定宽度就会失效。

1.9 Go 程序的一般结构

// 当前程序的包名
package main

// 导入其他包
import . "fmt"

// 常量定义
const PI = 3.14

// 全局变量的声明和赋值
var name = "gopher"

// 一般类型声明
type newType int

// 结构的声明
type gopher struct{}

// 接口的声明
type golang interface{}

// 由main函数作为程序入口点启动
func main() {
	// Hello World!
	Println("Hello World!")
}
  • Go 程序是通过 package 来组织的。

  • 只有 package 名称为 main 的源码文件可以包含 main 函数。

  • 一个可执行程序有且仅有一个 main 包。

  • 通过 import 关键字来导入其他非 main 包。

可以通过 import 关键字单个导入:

import "fmt"
import "io"

也可以同时导入多个:

import (
    "fmt"
    "math"
)

使用 . 调用:

package main

import (
	"fmt"
	"math"
)

func main() {
	// 1024
	fmt.Println(math.Exp2(10))
}

package别名:

// 为fmt起别名为fmt2
import fmt2 "fmt"

省略调用(不建议使用):

// 调用的时候只需要Println(),而不需要fmt.Println()
import . "fmt"

前面加个点表示省略调用,那么调用该模块里面的函数,可以不用写模块名称了:

package main

import . "fmt"

func main() {
	// hello,world
	Println("hello,world")
}
  • 通过 const 关键字来进行常量的定义。

  • 通过在函数体外部使用 var 关键字来进行全局变量的声明和赋值。

  • 通过 type 关键字来进行结构(struct)和接口(interface)的声明。

  • 通过 func 关键字来进行函数的声明。

可见性规则:Go语言中,使用大小写来决定该常量、变量、类型、接口、结构或函数是否可以被外部包所调用。

函数名首字母小写即为 private :

func getId() {}

函数名首字母大写即为 public :

func Printf() {}

Go 语言的包引入一般为: 项目名/包名

import "test/controllers"

方法的调用为: 包名.方法名()

controllers.Test()

本包内方法名可为小写,包外调用方法名首字母必须为大写。

1.10 Go fmt 库

1.10.1 Printing

fmt包实现了格式化I/O函数,Print() 函数将参数列表 a 中的各个参数转换为字符串并写入到标准输出中。

非字符串参数之间会添加空格,返回写入的字节数。

func Print(a ...interface{}) (n int, err error)

Println() 函数功能类似 Print,只不过最后会添加一个换行符。

所有参数之间会添加空格,返回写入的字节数。

func Println(a ...interface{}) (n int, err error)

Printf() 函数将参数列表 a 填写到格式字符串 format 的占位符中。

填写后的结果写入到标准输出中,返回写入的字节数。

func Printf(format string, a ...interface{}) (n int, err error)

实例:

package main

import "fmt"

func main() {
	// ab1 2 3cd
	fmt.Print("a", "b", 1, 2, 3, "c", "d", "\n")
	// a b 1 2 3 c d
	fmt.Println("a", "b", 1, 2, 3, "c", "d")
	// ab 1 2 3 cd
	fmt.Printf("ab %d %d %d cd\n", 1, 2, 3)
}

以下三个函数功能同上面三个函数,只不过将转换结果写入到 w中。

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

实例:

package main

import (
	"fmt"
	"os"
)

func main() {
	// ab1 2 3cd
	fmt.Fprint(os.Stdout, "a", "b", 1, 2, 3, "c", "d", "\n")
	// a b 1 2 3 c d
	fmt.Fprintln(os.Stdout, "a", "b", 1, 2, 3, "c", "d")
	// ab 1 2 3 cd
	fmt.Fprintf(os.Stdout, "ab %d %d %d cd\n", 1, 2, 3)
}

以下三个函数功能同上面三个函数,只不过将转换结果以字符串形式返回。

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

实例:

package main

import (
	"fmt"
)

func main() {
	// ab1 2 3cd
	var s1 = fmt.Sprint("a", "b", 1, 2, 3, "c", "d", "\n")
	fmt.Print(s1)
	// a b 1 2 3 c d
	var s2 = fmt.Sprintln("a", "b", 1, 2, 3, "c", "d")
	fmt.Print(s2)
	// ab 1 2 3 cd
	var s3 = fmt.Sprintf("ab %d %d %d cd\n", 1, 2, 3)
	fmt.Print(s3)
}

以下函数功能同 Sprintf() 函数,只不过结果字符串被包装成了 error 类型。

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

实例:

package main

import (
	"fmt"
)

func main() {
	var error = fmt.Errorf("ab %d %d %d cd\n", 1, 2, 3)
	// ab 1 2 3 cd
	fmt.Print(error)
}

不考虑占位符的话,如果操作数是接口值,就会使用其内部的具体值,而非接口本身。 因此:

var i interface{} = 23
# 23
fmt.Printf("%v\n", i)

若其格式(它对于Println等函数是隐式的%v)对于字符串是有效的(%s %q %v %x %X),以下两条规则也适用:

  • 若一个操作数实现了 error 接口,Error 方法就能将该对象转换为字符串,随后会根据占位符的需要进行格

    式化。

  • 若一个操作数实现了 String() string 方法,该方法能将该对象转换为字符串,随后会根据占位符的需要进

    行格式化。

1.10.2 Formatter 接口

Formatter 由自定义类型实现,用于实现该类型的自定义格式化过程。

当格式化器需要格式化该类型的变量时,会调用其 Format 方法。

Formatter接口的定义如下:

type Formatter interface {
    // f 用于获取占位符的旗标、宽度、精度等信息,也用于输出格式化的结果
    // c 是占位符中的动词
    Format(f State, c rune)
}

由格式化器(Print之类的函数)实现,用于给自定义格式化过程提供信息:

type State interface {
    // Formatter 通过 Write 方法将格式化结果写入格式化器中,以便输出。
    Write(b []byte) (ret int, err error)
    // Formatter 通过 Width 方法获取占位符中的宽度信息及其是否被设置。
    Width() (wid int, ok bool)
    // Formatter 通过 Precision 方法获取占位符中的精度信息及其是否被设置。
    Precision() (prec int, ok bool)
    // Formatter 通过 Flag 方法获取占位符中的旗标[+- 0#]是否被设置。
    Flag(c int) bool
}

通过实现Formatter接口可以做到自定义输出格式(自定义占位符):

package main

import (
	"bytes"
	"fmt"
	"strconv"
)

type Person struct {
	Name string
	Age  int
	Sex  int
}

func (person *Person) String() string {
	buffer := bytes.NewBufferString("This is ")
	buffer.WriteString(person.Name + ", ")
	if person.Sex == 0 {
		buffer.WriteString("He ")
	} else {
		buffer.WriteString("She ")
	}
	buffer.WriteString("is ")
	buffer.WriteString(strconv.Itoa(person.Age))
	buffer.WriteString(" years old.")
	return buffer.String()
}

func (this *Person) Format(f fmt.State, c rune) {
	if c == 'L' {
		f.Write([]byte(this.String()))
		f.Write([]byte(" Person has three fields."))
	} else {
		// 没有此句,会导致 fmt.Printf("%s", p) 啥也不输出
		f.Write([]byte(fmt.Sprintln(this.String())))
	}
	f.Write([]byte("\n"))
}

func main() {
	p1 := &Person{"polaris", 28, 0}
	// This is polaris, He is 28 years old. Person has three fields.
	fmt.Printf("%L", p1)
	p2 := &Person{"marry", 38, 1}
	// This is marry, She is 38 years old.
	fmt.Printf("%s", p2)
}

这里需要解释以下几点:

1)、fmt.State是一个接口,由于Format方法是被fmt包调用的,它内部会实例化好一个fmt.State接口的实

例,我们不需要关心该接口。

2)、可以实现自定义占位符,同时fmt包中和类型相对应的预定义占位符会无效。因此例子中Format的实现加

上了else子句。

3)、实现了Formatter接口,相应的Stringer接口不起作用。但实现了Formatter接口的类型应该实现Stringer

接口,这样方便在Format方法中调用String()方法。就像本例的做法。

4)、Format方法的第二个参数是占位符中%后的字母(有精度和宽度会被忽略,只保留字母)。

一般地,我们不需要实现Formatter接口。

1.10.3 Stringer 接口

Stringer 由自定义类型实现,用于实现该类型的自定义格式化过程。

当格式化器需要输出该类型的字符串格式时就会调用其 String 方法。

Stringer接口的定义如下:

type Stringer interface {
    String() string
}

根据Go语言中实现接口的定义,一个类型只要有 String() string 方法,我们就说它实现了Stringer接口。

如果格式化输出某种类型的值,只要它实现了String()方法,那么会调用String()方法进行处理。

实例:

package main

import (
	"bytes"
	"fmt"
	"strconv"
)

type Person struct {
	Name string
	Age  int
	Sex  int
}

func (person *Person) String() string {
	buffer := bytes.NewBufferString("This is ")
	buffer.WriteString(person.Name + ", ")
	if person.Sex == 0 {
		buffer.WriteString("He ")
	} else {
		buffer.WriteString("She ")
	}
	buffer.WriteString("is ")
	buffer.WriteString(strconv.Itoa(person.Age))
	buffer.WriteString(" years old.")
	return buffer.String()
}

func main() {
	p1 := &Person{"polaris", 28, 0}
	// This is polaris, He is 28 years old.
	fmt.Println(p1)
	p2 := &Person{"marry", 38, 1}
	// This is tom, She is 38 years old.
	fmt.Println(p2)
}

可见,Stringer接口和Java中的ToString方法类似。

1.10.4 GoStringer 接口

GoStringer 接口定义如下:

GoStringer由自定义类型实现,用于实现该类型的自定义格式化过程。

当格式化器需要输出该类型的 Go 语法字符串(%#v)时就会调用其 GoString方法。

type GoStringer interface {
    GoString() string
}

实例:

package main

import (
	"fmt"
	"strconv"
)

type Person struct {
	Name string
	Age  int
	Sex  int
}

func (this *Person) GoString() string {
	return "&Person{Name is " + this.Name + ", Age is " + strconv.Itoa(this.Age) + ", Sex is " + strconv.Itoa(this.Sex) + "}"
}

func main() {
	p := &Person{"polaris", 28, 0}
	// &Person{Name is polaris, Age is 28, Sex is 0}
	fmt.Printf("%#v", p)
}

一般的,我们不需要实现该接口。

1.10.5 格式化综合实例

实例:

package main

import (
	"fmt"
	"strconv"
	"strings"
)

type Ustr string

func (us Ustr) String() string {
	return strings.ToUpper(string(us))
}

func (us Ustr) GoString() string {
	return `"` + strings.ToUpper(string(us)) + `"`
}

// c会输出格式化字符,如m,M,v,s,v,d
func (u Ustr) Format(f fmt.State, c rune) {
	write := func(s string) {
		f.Write([]byte(s))
	}
	switch c {
	case 'm', 'M':
		write("旗标:[")
		for s := "+- 0#"; len(s) > 0; s = s[1:] {
			// 通过Flag方法获取占位符中的旗标[+- 0#]是否被设置
			if f.Flag(int(s[0])) {
				write(s[:1])
			}
		}
		write("]")
		if v, ok := f.Width(); ok {
			write(" | 宽度:" + strconv.FormatInt(int64(v), 10))
		}
		if v, ok := f.Precision(); ok {
			write(" | 精度:" + strconv.FormatInt(int64(v), 10))
		}
	case 's', 'v': // 如果使用 Format 函数,则必须自己处理所有格式,包括 %#v
		if c == 'v' && f.Flag('#') {
			write(u.GoString())
		} else {
			write(u.String())
		}
	default: // 如果使用 Format 函数,则必须自己处理默认输出
		write("无效格式:" + string(c))
	}
}

func main() {
	u := Ustr("Hello World!")
	// "-" 标记和 "0" 标记不能同时存在
	// 旗标:[+- #] | 宽度:8 | 精度:5
	fmt.Printf("%-+ 0#8.5m\n", u)
	// 旗标:[+ 0#] | 宽度:8 | 精度:5
	fmt.Printf("%+ 0#8.5M\n", u)
	// 会调用String()
	// HELLO WORLD!
	fmt.Println(u)
	// HELLO WORLD!
	fmt.Printf("%s\n", u)
	// "HELLO WORLD!"
	fmt.Printf("%#v\n", u)
	// 无效格式:d
	fmt.Printf("%d\n", u)
}

1.10.6 Scanning

Scan 从标准输入中读取数据,并将数据用空白分割并解析后存入 a 提供的变量中(换行符会被当作空白处理),

变量必须以指针传入。

当读到 EOF 或所有变量都填写完毕则停止扫描。

返回成功解析的参数数量。

func Scan(a ...interface{}) (n int, err error)

ScanlnScan 类似,只不过遇到换行符就停止扫描。

func Scanln(a ...interface{}) (n int, err error)

Scanf 从标准输入中读取数据,并根据格式字符串 format 对数据进行解析,将解析结果存入参数 a 所提供的变

量中,变量必须以指针传入。

输入端的换行符必须和 format 中的换行符相对应(如果格式字符串中有换行符,则输入端必须输入相应的换行

符)。

占位符 %c 总是匹配下一个字符,包括空白,比如空格符、制表符、换行符。

返回成功解析的参数数量。

func Scanf(format string, a ...interface{}) (n int, err error)

实例:

package main

import "fmt"

// 对于Scan而言,回车视为空白
func main() {
	a, b, c := "", 0, false
	fmt.Scan(&a, &b, &c)
	fmt.Println(a, b, c)
	// 在终端执行后,输入 abc 1 回车 true 回车
	// 结果 abc 1 true
}
package main

import "fmt"

// 对于Scanln而言,回车结束扫描
func main() {
	a, b, c := "", 0, false
	fmt.Scanln(&a, &b, &c)
	fmt.Println(a, b, c)
	// 在终端执行后,输入 abc 1 true 回车
	// 结果 abc 1 true
}
package main

import "fmt"

// 格式字符串可以指定宽度
func main() {
	a, b, c := "", 0, false
	fmt.Scanf("%4s%d%t", &a, &b, &c)
	fmt.Println(a, b, c)
	// 在终端执行后,输入 1234567true 回车
	// 结果 1234 567 true
}

以下三个函数功能同上面三个函数,只不过从 r 中读取数据。

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)

以下三个函数功能同上面三个函数,只不过从 str 中读取数据。

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)
  • ScanScanfScanlnos.Stdin中读取;

  • FscanFscanfFscanln从指定的io.Reader中读取;

  • SscanSscanfSscanln从实参字符串中读取;

  • ScanlnFscanlnSscanln在换行符处停止扫描,且需要条目紧随换行符之后;

  • ScanfFscanfSscanf需要输入换行符来匹配格式中的换行符;

  • ScanFscanSscan将换行符视为空格;

ScanfFscanfSscanf根据格式字符串解析实参,类似于Printf,实例:

package main

import "fmt"

func main() {

	// Sscan、Sscanf和Sscanln从实参字符串中读取
	var (
		name1 string
		name2 string
		name3 string
		name4 string
		name5 string
		name6 string
		age1  int
		age2  int
		age3  int
		age4  int
		age5  int
		age6  int
	)
	// 可以将"polaris 28"中的空格换成"\n"试试
	n1, _ := fmt.Sscan("polaris 28", &name1, &age1)
	n2, _ := fmt.Sscan("polaris\n28", &name2, &age2)
	// 2 polaris 28
	fmt.Println(n1, name1, age1)
	// 2 polaris 28
	fmt.Println(n2, name2, age2)

	// 可以将"polaris 28"中的空格换成"\n"试试
	n3, _ := fmt.Sscanf("polaris 28", "%s%d", &name3, &age3)
	n4, _ := fmt.Sscanf("polaris\n28", "%s%d", &name4, &age4)
	// 2 polaris 28
	fmt.Println(n3, name3, age3)
	// 1 polaris 0
	fmt.Println(n4, name4, age4)

	// 可以将"polaris 28"中的空格换成"\n"试试
	n5, _ := fmt.Sscanln("polaris 28", &name5, &age5)
	n6, _ := fmt.Sscanln("polaris\n28", &name6, &age6)
	// 2 polaris 28
	fmt.Println(n5, name5, age5)
	// 1 polaris 0
	fmt.Println(n6, name6, age6)
}

实例:

package main

import "fmt"

func main() {
	for i := 0; i < 2; i++ {
		var name string
		fmt.Print("Input Name:")
		n, err := fmt.Scanf("%s", &name)
		fmt.Println(n, err, name)
	}
}
# 输入第一个元素zsx1然后按回车输入第二个元素,会出现如下现象
Input Name:1  zsx1
Input Name:0 unexpected newline

同样的代码在Linux下正常,这可能是go在Windows下的一个bug。

目前的解决方法是:换用Scanln或者改为Scanf("%s\n", &name)或者使用Scan

正常的结果为:

# 输入zsx1按回车然后输入zsx2
Input Name:1  zsx1
Input Name:1  zsx2

1.10.7 Scanner 和 ScanState 接口

Scanner 由自定义类型实现,用于实现该类型的自定义扫描过程。

当扫描器需要解析该类型的数据时,会调用其 Scan 方法。

type Scanner interface {
    // state 用于获取占位符中的宽度信息,也用于从扫描器中读取数据进行解析。
    // verb 是占位符中的动词
    Scan(state ScanState, verb rune) error
}

由扫描器(Scan之类的函数)实现,用于给自定义扫描过程提供数据和信息。

type ScanState interface {
    // ReadRune 从扫描器中读取一个字符,如果用在 Scanln 类的扫描器中,
    // 则该方法会在读到第一个换行符之后或读到指定宽度之后返回 EOF。
    // 返回“读取的字符”和“字符编码所占用的字节数”
    ReadRune() (r rune, size int, err error)
    // UnreadRune 撤消最后一次的 ReadRune 操作,
    // 使下次的 ReadRune 操作得到与前一次 ReadRune 相同的结果。
    UnreadRune() error
    // SkipSpace 为 Scan 方法提供跳过开头空白的能力。
    // 根据扫描器的不同(Scan 或 Scanln)决定是否跳过换行符。
    SkipSpace()
    // Token 用于从扫描器中读取符合要求的字符串,
    // Token 从扫描器中读取连续的符合 f(c) 的字符 c,准备解析。
    // 如果 f 为 nil,则使用 !unicode.IsSpace(c) 代替 f(c)。
    // skipSpace:是否跳过开头的连续空白。返回读取到的数据。
    // 注意:token 指向共享的数据,下次的 Token 操作可能会覆盖本次的结果。
    Token(skipSpace bool, f func(rune) bool) (token []byte, err error)
    // Width 返回占位符中的宽度值以及宽度值是否被设置
    Width() (wid int, ok bool)
    // 因为上面实现了 ReadRune 方法,所以 Read 方法永远不应该被调用。
    // 一个好的 ScanState 应该让 Read 直接返回相应的错误信息。
    Read(buf []byte) (n int, err error)
}

基本上,我们不会去自己实现这两个接口,只需要使用上文中相应的Scan函数就可以了。

任何实现了Scan方法的对象都实现了Scanner接口,Scan方法会从输入读取数据并将处理结果存入接收端,接收

端必须是有效的指针。Scan方法会被任何Scan、Scanf、Scanln等函数调用,只要对应的参数实现了该方法。

Scan方法接收的第一个参数为ScanState接口类型。

ScanState是一个交给用户定制的Scanner接口的参数的接口。Scanner接口可能会进行一次一个字符的扫描或者

要求ScanState去探测下一个空白分隔的token。

实例:

package main

import "fmt"

type Ustr string

func (u *Ustr) Scan(state fmt.ScanState, verb rune) (err error) {
	var s []byte
	switch verb {
	case 'S':
		s, err = state.Token(true, func(c rune) bool {
			return 'A' <= c && c <= 'Z'
		})
		if err != nil {
			return
		}
	case 's', 'v':
		s, err = state.Token(true, func(c rune) bool {
			return 'a' <= c && c <= 'z'
		})
		if err != nil {
			return
		}
	default:
		return fmt.Errorf("无效格式:%c", verb)
	}
	*u = Ustr(s)
	return nil
}

func main() {
	var a, b, c, d, e Ustr
	n, err := fmt.Scanf("%3S%S%3s%2v%x", &a, &b, &c, &d, &e)
	fmt.Println(a, b, c, d, e)
	fmt.Println(n, err)
	// 在终端执行后,输入 ABCDEFGabcdefg 回车
	// 结果:
	// ABC DEFG abc de
	// 4 无效格式:x
}

1.11 结构体格式化输出

在软件系统中定位问题时日志不可或缺,但是当一个系统功能繁多,需要打印的日志也多如牛毛,此时为了提高我

们浏览日志的效率,便于阅读的输出格式必不可少。

打印结构体是打印日志时最长见的操作,但是当结构体内容较多都在一行时,不易于阅读。在 Go 中结构体可以方

便的转为 JSON,因此我们可以借助 JSON 完成对 struct 的格式化输出。

1.11.1 输出结构体字段(%+v)

package main

import (
	"fmt"
)

// Student 学生信息
type Student struct {
	Name string
	Addr HomeInfo
	M    map[string]string
}

// HomeInfo 家庭住址
type HomeInfo struct {
	Province     string
	City         string
	County       string
	Street       string
	DetailedAddr string
}

var student = Student{
	Name: "dablelv",
	Addr: HomeInfo{
		Province:     "Guangdong",
		City:         "Shenzhen",
		County:       "Baoan",
		Street:       "Xixiang",
		DetailedAddr: "Shengtianqi",
	},
	M: map[string]string{
		"hobby": "pingpopng",
	},
}

func main() {
    // student={Name:dablelv Addr:{Province:Guangdong City:Shenzhen County:Baoan Street:Xixiang DetailedAddr:Shengtianqi} M:map[hobby:pingpopng]}
	fmt.Printf("student=%+v\n", student)
}

这种输出方式虽然可以将结构体的字段名称打印出来,但是当结构体字段多,嵌套层次深时,扎堆输出在一

行时不便于阅读。

1.11.2 输出格式化 JSON 串

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
)

// Student 学生信息
type Student struct {
	Name   string
	Addr   HomeInfo
	M      map[string]string
}

// HomeInfo 家庭住址
type HomeInfo struct {
	Province     string
	City         string
	County       string
	Street       string
	DetailedAddr string
}

var student = Student{
	Name: "dablelv",
	Addr: HomeInfo{
		Province:     "Guangdong",
		City:         "Shenzhen",
		County:       "Baoan",
		Street:       "Xixiang",
		DetailedAddr: "Shengtianqi",
	},
	M: map[string]string{
		"hobby": "pingpopng",
	},
}

func main() {
	bs, _ := json.Marshal(student)
	var out bytes.Buffer
	json.Indent(&out, bs, "", "\t")
	/*
	student={
        "Name": "dablelv",
        "Addr": {
                "Province": "Guangdong",
                "City": "Shenzhen",
                "County": "Baoan",
                "Street": "Xixiang",
                "DetailedAddr": "Shengtianqi"
        },
        "M": {
                "hobby": "pingpopng"
        }
	}
	*/
	fmt.Printf("student=%v\n", out.String())
}

将struct 转化为 json串后再格式化输出,大大增加了可读性。

1.11.3 使用 go-huge-util

package main

import (
	"fmt"
	huge "github.com/dablelv/go-huge-util"
)

// Student 学生信息
type Student struct {
	Name   string
	Addr   HomeInfo
	M      map[string]string
}

// HomeInfo 家庭住址
type HomeInfo struct {
	Province     string
	City         string
	County       string
	Street       string
	DetailedAddr string
}

var student = Student{
	Name: "dablelv",
	Addr: HomeInfo{
		Province:     "Guangdong",
		City:         "Shenzhen",
		County:       "Baoan",
		Street:       "Xixiang",
		DetailedAddr: "Shengtianqi",
	},
	M: map[string]string{
		"hobby": "pingpopng",
	},
}

func main() {
	s, _ := huge.ToIndentJSON(&student)
    /*
    student={
        "Name": "dablelv",
        "Addr": {
                "Province": "Guangdong",
                "City": "Shenzhen",
                "County": "Baoan",
                "Street": "Xixiang",
                "DetailedAddr": "Shengtianqi"
        },
        "M": {
                "hobby": "pingpopng"
        }
	}
    */
	fmt.Printf("student=%v\n", s)
}

你可能感兴趣的:(golang,golang,java,服务器)