从大学就听说了Go,但一直没有时间去了解这种高效简洁的语言,现在需要学习,就记录一下学习过程,以便日后查找。
在之后还会继续了解Docker(基于Go进行开发),所以先记录一下Go的学习,先挖一个坑,到时候来填链接。
Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。
对于高性能分布式系统领域而言,Go 语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。
Go 语言环境安装 | 菜鸟教程 (runoob.com)
package main //package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
import "fmt" //告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。
func main() { //func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
/* 这是我的第一个简单的程序 */
fmt.Println("Hello, World!")
}
注意:1.当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
2.需要注意的是 { 不能单独放在一行,所以以下代码在运行时会产生错误:
package main
import "fmt"
func main()
{ // 错误,{ 不能在单独的行上
fmt.Println("Hello, World!")
}
/*
结果:
command-line-arguments
./main.go:5:6: missing function body
./main.go:6:1: syntax error: unexpected semicolon or newline before {
*/
3.文件名与包名没有直接关系,不一定要将文件名与包名定成同一个;文件夹名与包名没有直接关系,并非需要一致;同一个文件夹下的文件只能有一个包名,否则编译报错。
1.和Python一样不需要以分号结尾,自动分行
2.标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(A~Z和a~z)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字
3.Go 语言的字符串连接可以通过 + 实现:
package main
import "fmt"
func main() {
fmt.Println("Google" + "Runoob")
}
//GoogleRunoob
4.Go 语言中使用 fmt.Sprintf 格式化字符串并赋值给新串:
package main
import (
"fmt"
)
func main() {
// %d 表示整型数字,%s 表示字符串
var stockcode=123
var enddate="2020-12-31"
var url="Code = %d & endDate = %s"
var target_url=fmt.Sprintf(url,stockcode,enddate)
fmt.Println(target_url)
}
// Code = 123 & endDate = 2020-12-31
5.Go 程序的一般结构: basic_structure.go
// 当前程序的包名 package main // 导入其他包 import . "fmt" // 常量定义 const PI = 3.14 // 全局变量的声明和赋值 var name = "gopher" // 一般类型声明 type newType int // 结构的声明 type gopher struct{} // 接口的声明 type golang interface{} // 由main函数作为程序入口点启动 func main() { 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() { fmt.Println(math.Exp2(10)) // 1024 }使用
. 调用: package 别名: // 为fmt起别名为fmt2 import fmt2 "fmt"省略调用(不建议使用):
// 调用的时候只需要Println(),而不需要fmt.Println() import . "fmt"前面加个点表示省略调用,那么调用该模块里面的函数,可以不用写模块名称了:
import . "fmt" func main (){ Println("hello,world") }通过 const 关键字来进行常量的定义。
通过在函数体外部使用 var 关键字来进行全局变量的声明和赋值。
通过 type 关键字来进行结构(struct)和接口(interface)的声明。
通过 func 关键字来进行函数的声明。
可见性规则
Go语言中,使用大小写来决定该常量、变量、类型、接口、结构或函数是否可以被外部包所调用。
函数名首字母小写即为 private :
func getId() {}函数名首字母大写即为 public :
func Printf() {}
6.Golang fmt 包
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)以下三个函数功能同上面三个函数,只不过将转换结果写入到 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)以下三个函数功能同上面三个函数,只不过将转换结果以字符串形式返回。
func Sprint(a ...interface{}) string func Sprintln(a ...interface{}) string func Sprintf(format string, a ...interface{}) string以下函数功能同 Sprintf() 函数,只不过结果字符串被包装成了 error 类型。
func Errorf(format string, a ...interface{}) error实例:
func main() { fmt.Print("a", "b", 1, 2, 3, "c", "d", "\n") fmt.Println("a", "b", 1, 2, 3, "c", "d") fmt.Printf("ab %d %d %d cd\n", 1, 2, 3) // ab1 2 3cd // a b 1 2 3 c d // ab 1 2 3 cd if err := percent(30, 70, 90, 160); err != nil { fmt.Println(err) } // 30% // 70% // 90% // 数值 160 超出范围(100) } func percent(i ...int) error { for _, n := range i { if n > 100 { return fmt.Errorf("数值 %d 超出范围(100)", n) } fmt.Print(n, "%\n") } return nil }Formatter 由自定义类型实现,用于实现该类型的自定义格式化过程。
当格式化器需要格式化该类型的变量时,会调用其 Format 方法。
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 }Stringer 由自定义类型实现,用于实现该类型的自定义格式化过程。
当格式化器需要输出该类型的字符串格式时就会调用其 String 方法。
type Stringer interface { String() string }Stringer 由自定义类型实现,用于实现该类型的自定义格式化过程。
当格式化器需要输出该类型的 Go 语法字符串(%#v)时就会调用其 String 方法。
type GoStringer interface { GoString() string }实例:
type Ustr string func (us Ustr) String() string { return strings.ToUpper(string(us)) } func (us Ustr) GoString() string { return `"` + strings.ToUpper(string(us)) + `"` } 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:] { 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" 标记不能同时存在 fmt.Printf("%-+ 0#8.5m\n", u) // 旗标:[+- #] | 宽度:8 | 精度:5 fmt.Printf("%+ 0#8.5M\n", u) // 旗标:[+ 0#] | 宽度:8 | 精度:5 fmt.Println(u) // HELLO WORLD! fmt.Printf("%s\n", u) // HELLO WORLD! fmt.Printf("%#v\n", u) // "HELLO WORLD!" fmt.Printf("%d\n", u) // 无效格式:d }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)以下三个函数功能同上面三个函数,只不过从 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 而言,回车视为空白 func main() { a, b, c := "", 0, false fmt.Scan(&a, &b, &c) fmt.Println(a, b, c) // 在终端执行后,输入 abc 1 回车 true 回车 // 结果 abc 1 true } // 对于 Scanln 而言,回车结束扫描 func main() { a, b, c := "", 0, false fmt.Scanln(&a, &b, &c) fmt.Println(a, b, c) // 在终端执行后,输入 abc 1 true 回车 // 结果 abc 1 true } // 格式字符串可以指定宽度 func main() { a, b, c := "", 0, false fmt.Scanf("%4s%d%t", &a, &b, &c) fmt.Println(a, b, c) // 在终端执行后,输入 1234567true 回车 // 结果 1234 567 true }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) }实例:
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 }
7.Go 语言的包引入一般为: 项目名/包名
import "test/controllers"
方法的调用为: 包名.方法名()
controllers.Test()
1.布尔型
布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
2.数字类型
整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。
3.派生类型:
包括:(a) 指针类型(Pointer)(b) 数组类型(c) 结构化类型(struct)(d) Channel 类型(e) 函数类型(f) 切片类型(g) 接口类型(interface)(h) Map 类型
4.nil的理解:
在go语言中,nil可以代表下面这些类型的零值:
但是要注意的一点就是,nil是指申明了变量,但没有赋值:在Go语言中,如果你声明了一个变量但是没有对它进行赋值操作,那么这个变量就会有一个类型的默认零值。这是每种类型对应的零值。
//Example1:
func main() {
var a []int
if a==nil{
fmt.Println(123)
}
fmt.Println(a)
// a为nil
// []
}
//Example2:
func main() {
a :=[]int{}
if a==nil{
fmt.Println("a为nil")
}else {
fmt.Println("a不是nil")
}
fmt.Println(a)
//a不是nil
//[]
}
/*
区别两者之间的关系:
在第一个代码块中,a声明了变量但没有进行赋值,因此为nil
在第二个代码块中,a声明了变量并进行了初始化操作,只不过里面是空的,因此就行了赋值,不为nil
*/
(2条消息) Go语言中nil的理解_一只安慕嘻的博客-CSDN博客_go nil
6.error:
GO语言中err接口及defer延迟异常处理分析_Golang_脚本之家 (jb51.net)
Go 语言数据类型 | 菜鸟教程 (runoob.com)
Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。
声明变量的一般形式是使用 var 关键字:
//var identifier type
//var identifier1, identifier2 type
package main
import "fmt"
func main() {
var a string = "Runoob"
fmt.Println(a)
var b, c int = 1, 2
fmt.Println(b, c)
}
注意:
1.第一种,指定变量类型,如果没有初始化,则变量默认为零值
数值类型(包括complex64/128)为 0
布尔类型为 false
字符串为 ""(空字符串)
以下几种类型为 nil:
var a *int var a []int var a map[string] int var a chan int var a func(string) int var a error // error 是接口
2.第二种,根据值自行判定变量类型
package main
import "fmt"
func main() {
var d = true
fmt.Println(d)
}
3.第三种,如果变量已经使用 var 声明过了,再使用 := 声明变量,就产生编译错误
intVal := 1 相等于:
var intVal
int intVal =1
可以将 var f string = "Runoob" 简写为 f := "Runoob":
4.多变量声明
//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3
var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断
vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误
// 这种因式分解关键字的写法一般用于声明全局变量
var (
vname1 v_type1
vname2 v_type2
)
5.我们知道可以在变量的初始化时省略变量的类型而由系统自动推断,声明语句写上 var 关键字其实是显得有些多余了,因此我们可以将它们简写为 a := 50 或 b := false。
a 和 b 的类型(int 和 bool)将由编译器自动推断。
这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 := 可以高效地创建一个新的变量,称之为初始化声明。
如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 a:(局部声明必须使用)
6.多变量可以在同一行进行赋值
var a, b int var c string a, b, c = 5, 7, "abc" 或者 a, b, c := 5, 7, "abc"
7.并行赋值:
a, b, c := 5, 7, "abc"
右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 5, b 的值是 7,c 的值是 "abc"。
这被称为 并行 或 同时 赋值。
如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a,两个变量的类型必须是相同。
空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。
_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。
并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:val, err = Func1(var1)。
空白标识符在函数返回值时的使用:
package main
import "fmt"
func main() {
_,numb,strs := numbers() //只获取函数返回值的后两个
fmt.Println(numb,strs)
}
//一个可以返回多个值的函数
func numbers()(int,int,string){
a , b , c := 1 , 2 , "str"
return a,b,c
}
//2 str
1.显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"
多个相同类型的声明可以简写为:const c_name1, c_name2 = value1, value2
2.iota,特殊常量,可以认为是一个可以被编译器修改的常量
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
iota 可以被用作枚举值:
const ( a = iota b = iota c = iota )第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:
const ( a = iota b c )
iota用法
package main
import "fmt"
func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
//0 1 2 ha ha 100 100 7 8
1)普通占位符
Go 语言的%d,%p,%v等占位符的使用_后端码匠的博客-CSDN博客_go 百分号
1. 使用函数的时候,不知道参数个数可以使用小数点代替
func percent(i ...int) error {
for _, n := range i {
if n > 100 {
return fmt.Errorf("数值 %d 超出范围(100)", n)
}
fmt.Print(n, "%\n")
}
return nil
}
2.使用_可以将值丢掉
var a, b = 1, 2
_, b = 1, 2
3.理解error的用法
package main
import (
"errors"
"fmt"
)
func main() {
//接收错误信息和正确信息
result,err := test(5,0)
//加了判断,如果没有错误err=nil
if err!=nil{
fmt.Println(err)
fmt.Println(result)
}else {
fmt.Println(err)
fmt.Println(result)
}
}
//b为0时抛出异常
func test(a,b int) (result int, err error) { //返回错误信息
err = nil
if b==0{
err =errors.New("b不能为0")
}else {
result = a/b
}
return
}
/*
b不能为0
0
*/