您现在的位置是:首页 >技术交流 >Go语言基础语法网站首页技术交流
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 |
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.
", 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
", p)
// {x:1 y:2}
fmt.Printf("%+v
", p)
// main.point{x:1, y:2}
fmt.Printf("%#v
", p)
// main.point
fmt.Printf("%T
", p)
// 100%
fmt.Printf("%d%%
", 100)
// true
fmt.Printf("%t
", true)
// 123
fmt.Printf("%d
", 123)
// 1110
fmt.Printf("%b
", 14)
// 7886928847432581p-49
fmt.Printf("%b
", 14.01)
// 16
fmt.Printf("%o
", 14)
// 14
fmt.Printf("%d
", 14)
// e
fmt.Printf("%x
", 14)
// E
fmt.Printf("%X
", 14)
// 616263
fmt.Printf("%x
", "abc")
// 616263
fmt.Printf("%X
", "abc")
// c
fmt.Printf("%c
", 'c')
// 'a'
fmt.Printf("%q
", 'a')
// "abc"
fmt.Printf("%q
", "abc")
// U+0061
fmt.Printf("%U
", 'a')
// 1.234000e+08
fmt.Printf("%e
", 123400000.0)
// 1.234000E+08
fmt.Printf("%E
", 123400000.0)
// 78.900000
fmt.Printf("%f
", 78.9)
// 78.9
fmt.Printf("%g
", 78.9)
// 78.9
fmt.Printf("%G
", 78.9)
// 0xc0000160b0
fmt.Printf("%p
", &p)
// "abc"
fmt.Printf("%s
", ""abc"")
// +的演示
fmt.Printf("%+d
", 10)
fmt.Printf("%+d
", -10)
// '我'
fmt.Printf("%q
", '我')
// "a我们b"
fmt.Printf("%q
", "a我们b")
// 'u6211'
fmt.Printf("%+q
", '我')
// "au6211u4eecb"
fmt.Printf("%+q
", "a我们b")
// -的演示
// 默认右对齐
// | 12| 345|
fmt.Printf("|%6d|%6d|
", 12, 345)
// | 1.20| 3.45|
fmt.Printf("|%6.2f|%6.2f|
", 1.2, 3.45)
// 加-表示左对齐
// |1.20 |3.45 |
fmt.Printf("|%-6.2f|%-6.2f|
", 1.2, 3.45)
// 默认右对齐
//| foo| b|
fmt.Printf("|%6s|%6s|
", "foo", "b")
// 加-表示左对齐
// |foo |b |
fmt.Printf("|%-6s|%-6s|
", "foo", "b")
// #的演示
// 016
fmt.Printf("%#o
", 14)
// 0xe
fmt.Printf("%#x
", 14)
// c0000160b0
fmt.Printf("%#p
", &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"
)
使用 <PackageName>.<FunctionName>
调用:
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", "
")
// 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
", 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", "
")
// 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
", 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", "
")
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
", 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
", 1, 2, 3)
// ab 1 2 3 cd
fmt.Print(error)
}
不考虑占位符的话,如果操作数是接口值,就会使用其内部的具体值,而非接口本身。 因此:
var i interface{} = 23
# 23
fmt.Printf("%v
", 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("
"))
}
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
", u)
// 旗标:[+ 0#] | 宽度:8 | 精度:5
fmt.Printf("%+ 0#8.5M
", u)
// 会调用String()
// HELLO WORLD!
fmt.Println(u)
// HELLO WORLD!
fmt.Printf("%s
", u)
// "HELLO WORLD!"
fmt.Printf("%#v
", u)
// 无效格式:d
fmt.Printf("%d
", u)
}
1.10.6 Scanning
Scan
从标准输入中读取数据,并将数据用空白分割并解析后存入 a 提供的变量中(换行符会被当作空白处理),
变量必须以指针传入。
当读到 EOF
或所有变量都填写完毕则停止扫描。
返回成功解析的参数数量。
func Scan(a ...interface{}) (n int, err error)
Scanln
和 Scan
类似,只不过遇到换行符
就停止扫描。
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)
-
Scan
、Scanf
和Scanln
从os.Stdin
中读取; -
Fscan
、Fscanf
和Fscanln
从指定的io.Reader
中读取; -
Sscan
、Sscanf
和Sscanln
从实参字符串中读取; -
Scanln
、Fscanln
和Sscanln
在换行符处停止扫描,且需要条目紧随换行符之后; -
Scanf
、Fscanf
和Sscanf
需要输入换行符来匹配格式中的换行符; -
Scan
、Fscan
和Sscan
将换行符视为空格;
Scanf
、Fscanf
和Sscanf
根据格式字符串解析实参,类似于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"中的空格换成"
"试试
n1, _ := fmt.Sscan("polaris 28", &name1, &age1)
n2, _ := fmt.Sscan("polaris
28", &name2, &age2)
// 2 polaris 28
fmt.Println(n1, name1, age1)
// 2 polaris 28
fmt.Println(n2, name2, age2)
// 可以将"polaris 28"中的空格换成"
"试试
n3, _ := fmt.Sscanf("polaris 28", "%s%d", &name3, &age3)
n4, _ := fmt.Sscanf("polaris
28", "%s%d", &name4, &age4)
// 2 polaris 28
fmt.Println(n3, name3, age3)
// 1 polaris 0
fmt.Println(n4, name4, age4)
// 可以将"polaris 28"中的空格换成"
"试试
n5, _ := fmt.Sscanln("polaris 28", &name5, &age5)
n6, _ := fmt.Sscanln("polaris
28", &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 <nil> zsx1
Input Name:0 unexpected newline
同样的代码在Linux
下正常,这可能是go在Windows下的一个bug。
目前的解决方法是:换用Scanln
或者改为Scanf("%s
", &name)
或者使用Scan
。
正常的结果为:
# 输入zsx1按回车然后输入zsx2
Input Name:1 <nil> zsx1
Input Name:1 <nil> 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
", 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, "", " ")
/*
student={
"Name": "dablelv",
"Addr": {
"Province": "Guangdong",
"City": "Shenzhen",
"County": "Baoan",
"Street": "Xixiang",
"DetailedAddr": "Shengtianqi"
},
"M": {
"hobby": "pingpopng"
}
}
*/
fmt.Printf("student=%v
", 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
", s)
}