本地启动 GO文档:
$ godoc -http=:8080
然后浏览器访问:http://localhost:8080
一、构建并运行Go程序
$ go run main.go // 直接运行,不进行编译
$ go build ./main/main.go // 编译并安装自身包和依赖包
$ go install //安装自身包和依赖包
二、格式化源代码
1、
gofmt –w program.go
会格式化该源文件的代码然后将格式化后的代码覆盖原始内容
(如果不加参数 -w
则只会打印格式化后的结果而不重写文件
);2、
gofmt -w *.go
会格式化并重写所有 Go 源文件;3、
gofmt map1
会格式化并重写map1 目录及其子目录下
的所有 Go 源文件。4、gofmt 也可以通过在参数 -r 后面加入用双引号括起来的替换规则实现代码的简单重构,规则的格式:<原始内容> -> <替换内容>。
实例:
gofmt -r '(a) -> a' –w *.go
上面的代码会将源文件中没有意义的括号去掉。
gofmt -r 'a[n:len(a)] -> a[n:]' –w *.go
上面的代码会将源文件中多余的 len(a) 去掉。( 译者注:了解切片(slice)之后就明白这为什么是多余的了 )
gofmt –r 'A.Func1(a,b) -> A.Func2(b,a)' –w *.go
上面的代码会将源文件中符合条件的函数的参数调换位置。
三、关键字
1、下面列举了 Go 代码中会使用到的 25 个关键字或保留字:
break | default | func | interface | select |
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for |
2、36 个预定义标识符:
int | uint | byte | len | uintptr | close | |||
int8 | uint8 | new | cap | real | ||||
int16 | uint16 | complex | bool | nil | append | imag | ||
int32 | float32 | uint32 | complex64 | true | string | make | panic | |
int64 | float64 | uint64 | complex128 | false | iota | copy | recover | println |
四、常量、变量
1、常量:使用关键字 const
定义,用于存储不会改变的数据
存储在常量中的数据类型只可以是
布尔型
、数字型(整数型、浮点型和复数)
和字符串型
。
常量的定义格式:const identifier [type] = value
在 Go 语言中,你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。
显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"
2、iota
const (
a = iota
b
c
)
# a=0 b=1 c=2
iota 默认值是0,每当在新的一行使用时,值会自动加1;
简单地讲,每遇到一次 const 关键字,iota 就重置为 0
3、变量:使用 var
关键字
var identifier type
变量初始值:
int = 0;float = 0.0;bool = false;string = '';指针 = nil;
4、声明和赋值可同时进行
var identifier [type] = value
var a int = 15
var i = 5
var b bool = false
var str string = "Go says hello to the world!"
1)、go语言可以在编译期根据变量的值来自动判断类型。所以可以这样赋值:
var a = 15
var b = false
var str = "Go says hello to the world!"
或: 分解因式式声明:
var (
a = 15
b = false
str = "Go says hello to the world!"
numShips = 50
city string
)
这种因式分解关键字的写法一般用于声明包级别的全局变量。
2)、对于特殊的类型还是需要手动指定
var n int64 = 2;
# 如果不显示指定,go不会指定 类型为:int64
3)、变量类型可以在运行时自动判断
go 会在运行时,根据os.Getenv()返回值的类型动态指定 各变量的类型
var (
HOME = os.Getenv("HOME")
USER = os.Getenv("USER")
GOROOT = os.Getenv("GOROOT")
)
5、值类型和引用类型
1)、值类型
- 1、int、float、bool、string、数组、结构 都属于基本类型
- 2、值类型的变量存在栈中。
整数:
int8(-128 -> 127)
int16(-32768 -> 32767)
int32(-2,147,483,648 -> 2,147,483,647)
int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
无符号整数:
uint8(0 -> 255)
uint16(0 -> 65,535)
uint32(0 -> 4,294,967,295)
uint64(0 -> 18,446,744,073,709,551,615)
浮点型(IEEE-754 标准):
float32(+- 1e-45 -> +- 3.4 * 1e38)--> 精确到小数点后 7 位
float64(+- 5 * 1e-324 -> 107 * 1e308)--> 精确到小数点后 15 位
2)、引用类型
- 1、指针、slices、maps、channel 都属于引用类型
- 2、引用类型的变量存在堆中,便于垃圾回收。
6、简短形式 := 赋值操作符
在代码块内部,可以使用
:=
方式声明变量
func main() {
a := "abc"
fmt.Println("hello, world--", a) # hello, world--abc
}
注意:
1、该方式不可声明全局变量
2、该方式声明的变量必须被使用
3、不可在该方式声明的变量之前使用该变量。
并行赋值
# _ 空白标识符,表示付给标识符的值不需要使用
_, a = 5, 6 # 这里 值 5 就被废弃了
# 并行赋值还可用于 承接:多返回值的函数
val, err = Func1(prams) # 变量val, err 分别承接Func1 返回val和err。
7、init 函数
- 每个源文件都只能包含一个 init 函数。
- 每个包初始化完成以后自动执行。
- 初始化总是以单线程执行,并且按照包的依赖关系顺序执行
- 不可人为调用
8、格式化输出
const a string = "abc";
fmt.Printf("this is a value : %s", a)
输出格式符:
%s:输出一个字符串(string)
%c: 用于表示字符(char)
%U : 输出格式为 U+hhhh 的字符串
%t:输出一个bool;
%v:按照其自身类型输出;
%d: 格式化整数;
%x 、%X :输出16进制数字
%g:格式化浮点型
%f:输出浮点数
%e:输出科学计数表示法
%0d:用于规定输出定长的整数
%n.mg:用于n位的占位
表示数字并精确到小数点后 m 位(四舍五入)
,除了使用 g(到整数)
之外,还可以使用 e(科学计数法
) 或者 f(以普通浮点数方式展示
),例如:使用格式化字符串 %5.2e 来输出 3.4 的结果为 3.40e+00
%p:格式化输出指针
%T:输出变量的原始类型
# 关于 %n.mg 中 n 的作用:
fmt.Printf("c")
fmt.Printf("%10.2e\n", 12.355678)
fmt.Printf("%10.2f\n", 12.345678)
fmt.Printf("%10.2g\n", 12.645678)
//c
// 1.24e+01
// 12.35
// 13
9、随机数
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
for i := 0; i < 10; i++ {
a := rand.Int()
fmt.Printf("%d / ", a)
}
for i := 0; i < 5; i++ {
r := rand.Intn(8)
fmt.Printf("%d / ", r)
}
fmt.Println()
timens := int64(time.Now().Nanosecond())
rand.Seed(timens)
for i := 0; i < 10; i++ {
fmt.Printf("%2.2f / ", 100*rand.Float32())
}
}
函数 rand.Float32
和 rand.Float64
返回介于 [0.0, 1.0)
之间的伪随机数,其中包括 0.0 但不包括 1.0。
函数rand.Intn
返回介于[0, n)
之间的伪随机数。
你可以使用 Seed(value)
函数来提供伪随机数的生成种子,一般情况下都会使用当前时间的纳秒级
数字
10、运算符优先级
优先级 运算符
7 ^ !
6 * / % << >> & &^
5 + - | ^
4 == != < <= >= >
3 <-
2 &&
1 ||
11、类型别名
package main
import "fmt"
type TZ int
func main() {
var a, b TZ = 3, 4
c := a + b
fmt.Printf("c has the value: %d", c) // 输出:c has the value: 7
}
类型别名得到的新类型没有原始类型的附带方法,但可以自定义新方法。
12、字符串
- 解释字符串:
该类字符串使用双引号括起来,其中的相关的转义字符将被替换,这些转义字符包括:
\n:换行符
\r:回车符
\t:tab 键
\u 或 \U:Unicode 字符
\\:反斜杠自身
- 非解释字符串:
该类字符串使用反引号括起来,支持换行,例如:
`This is a raw string \n` 中的 \n 会被原样输出。
unicode
包 包含了一些针对测试字符的非常有用的函数
- 判断是否为字母:unicode.IsLetter(ch)
- 判断是否为数字:unicode.IsDigit(ch)
- 判断是否为空白符号:unicode.IsSpace(ch)
13、指针
- 声明时:
*
:表示声明一个 指针变量声明一个指针变量:如果不赋值,默认为
nil
- 取值时:
&
:用于取 变量的内存地址;
*
:用于取指针所指的值(反引用)。
它俩刚好是相反的。
// 声明一个指针变量:如果不赋值,默认为`nil`
var stringP *string
fmt.Printf("stringP:%p\n", stringP) // 0x0
// 取值时:
fmt.Printf("stringP's value is :%s\n", *stringP) // 会报空指针错误
s := 'i'm a string'
stringP = &s // 将 string s 的指针赋给 stringP,stringP此时拿着s的内存地址
var ss = *stringP // 将 指针变量stringP 指向的值赋给 ss
你不能得到一个
文字
或常量
的地址,例如:
const i = 5
ptr := &i //error: cannot take the address of i
ptr2 := &10 //error: cannot take the address of 10
N、可见性原则及导入自定义包
标识符声明时用
大写字母
开头,则外包
可见。
标识符声明时用小写字母
开头,仅包内
可见。
项目目录结构如下图:
注意:go语言以
包
为基本单元,包
实际也就是一个文件夹
如图:test01.go
和 version.go
实际上都属于pkg1
这个包。
代码如下:
# pkg1/test01.go
package pkg1
import (
"fmt"
)
/**
注释: 函数首字母大写,表示它将被导出,即:它在其他包中可见。
*/
func Test01() {
fmt.Printf("这是Test01.go")
}
# pkg1/version.go
import (
"fmt"
"runtime"
)
func Version() {
fmt.Printf("%s\n", runtime.Version())
}
#hello_world.go
package main
import (
"fmt"
"./versions" // 引入versions 这个包。
// V "./versions" // 也可以给versions这个包起一个别名:V
)
func main() {
fmt.Print("hello world\n")
versions.Version() // 调用versions这个包中的versions.go中导出的方法。
versions.Test01() // 调用versions这个包中的test01.go中导出的方法。
// V.Version() // 使用包的别名来调用
// V.Test01() // 使用包的别名来调用
}
# bash
$ go build ./main/hello_world.go
五、控制结构
条件结构和分支结构:
- if-else 结构
- switch 结构
- select 结构,用于 channel 的选择(第 14.4 节)
迭代或循环结构:
- for (range) 结构
slice := []int{0, 1, 2, 3}
myMap := make(map[int]*int)
for index, value := range slice {
myMap[index] = &value
}
for-rang 拿到的value 是值的拷贝!这个一定要注意
一些如 break
和 continue
这样的关键字可以用于中途改变循环的状态。
此外,你还可以使用 return
来结束某个函数的执行,或使用 goto
和标签
来调整程序的执行位置。
1、if-else 结构
if-else结构,可以直接定义变量并赋初始值。
该变量只能在该 if-else结构中可见
if val := 10; val > max {
// do something
} else {
// do something
}
2、switch 结构
var num1 int = 100
#TYPE 1 (指定判断值)
switch num1 {
case 98, 99:
fmt.Println("It's equal to 98")
case 100:
fmt.Println("It's equal to 100")
default:
fmt.Println("It's not equal to 98 or 100")
}
#TYPE 2 (不跟任何判断值)
switch {
case condition1:
...
case condition2:
...
default:
...
}
六、for 结构
1、基础for循环
条件初始化;条件判断;条件变更
for i := 0; i < 5; i++ {
fmt.Printf("This is the %d iteration\n", i)
}
2、只有判断条件的for循环
for 结构中只有判断条件,条件初始化在for结构之外进行;条件变更在for结构内进行。
var i int = 5
for i >= 0 {
i = i - 1
fmt.Printf("The variable i is now: %d\n", i)
}
3、无限循环
没有任何判断条件,通过在for循环中进行条件判断并结合
bradk
或return
来结束循环。
注意:break
只会终止当前循环;return
会终止当前函数。
ii := 1
for {
if ii++; ii < 10 {
fmt.Println(ii)
} else {
return
}
}
4、基于goto实现的循环
标签的名称是大小写敏感的,为了提升可读性,一般建议使用全部
大写字母
# 案例一、
i := 0
START:
fmt.Printf("The counter is at %d\n", i)
i++
if i < 15 {
goto START
}
# 案例二、
LABEL1:
for i := 0; i <= 5; i++ {
for j := 0; j <= 5; j++ {
if j == 4 {
continue LABEL1
}
fmt.Printf("i is: %d, and j is: %d\n", i, j)
}
}
}
5、for-range循环
它可以迭代任何一个集合(包括数组和 map)
pos
:循环的下标;rune
:每个下标的值。
注意:rune对应的是每个索引的值的拷贝,所以修改rune时,原值是不会改变的;
但是:如果rune是一个指针的话,则原值是可以改变的(#这个不知道怎么验证#)
str := "Go is a beautiful language!"
for pos, rune := range str {
fmt.Printf("Character on position %d is: %c \n", pos, char)
}
七、函数
1、函数类型
Go 里面有三种类型的函数:
- 普通的带有名字的函数
- 匿名函数或者lambda函数
- 方法
2、函数不允许重载
func foo (入参1 类型1, 入参2 类型2...)(返回值1 类型2,返回值2 类型2...) { ... }
如果各入参类型相同,则可只在最后一个入参后面加类型;返回值亦然。
func Test02(p1, p2 int) (r1 int, r2 string) {
fmt.Printf("接收到了入参:%d和%d", p1, p2);
return (p1 + p2), "Test02的第二个返回值,哈哈" // 这里是返回值,返回了两个。
}
3、值传递和引用传递
Go 默认使用
按值传递
来传递参数,也就是传递参数的副本
- 值传递:调用函数时,传递的实参是值的副本,所以函数内改变实参的值,对应的变量值不会改变。
- 引用传递:调用函数时,传递的是值的引用(指针),改变指针值的时候,对应的变量值会发生改变。
# 值传递
fun(arg1)
# 引用传递
func Multiply(a, b int, reply *int) {
*reply = a * b
}
func main() {
n := 0
reply := &n
Multiply(10, 5, reply) // 传递 变量 n 的指针。
fmt.Println("Multiply:", *reply) // Multiply: 50
}
4、返回值类型
- 未命名返回值
return 后面需要跟上需要返回的值
func getX2AndX3(input int) (int, int) {
return 2 * input, 3 * input
}
- 命名返回值(尽量使用该种返回值方式)
一个空的 return即可 ----即使只有一个命名返回值,也需要使用 () 括起来
func getX2AndX3_2(input int) (x2 int, x3 int) {
x2 = 2 * input
x3 = 3 * input
// return x2, x3
return
}
5、空白符_
空白符用来匹配一些不需要的值,然后丢弃掉
package main
import "fmt"
func main() {
var i1 int
var f1 float32
i1, _, f1 = ThreeValues()
fmt.Printf("The int: %d, the float: %f \n", i1, f1)
}
func ThreeValues() (int, int, float32) {
return 5, 6, 7.5
}
6、变长参数的函数
参数个数不确定,以:func fun(prarm ...int) {...} 的形式来定义。
传递 slice 类型的变量时可以这样传递:fun(slice...)
。
函数内部接收到的参数param
的类型是一个slice | 切片
package main
import "fmt"
func main() {
x := min(1, 3, 2, 0)
fmt.Printf("The minimum is: %d\n", x)
slice := []int{7,9,3,5,1}
x = min(slice...)
fmt.Printf("The minimum in the slice is: %d", x)
}
func min(s ...int) int {
if len(s)==0 {
return 0
}
min := s[0]
for _, v := range s {
if v < min {
min = v
}
}
return min
}
7、defer 和追踪
defer 允许我们推迟函数 在
return之后
或主函数执行完毕之后
再执行。
有点类似于其他语言的finally
。
注意:
1、再循环中使用defer 的时候,遵循栈的后进先出规则
。及最后定义的defer
最先执行。
2、defer后面的函数值和参数会被求值但是实际函数调用却要等到最后
****** 一定要注意:变量的引用问题!!,如下例******
// 例1:
func func2() {
i := 0
defer ff(i)
defer func() {
fmt.Println("打印--3:", i)
}()
defer fmt.Println("打印--2:", i)
i++
fmt.Println("打印--1:", i)
}
func ff(i int) {
fmt.Println("打印--4:", i)
}
func2()
// 打印--1: 1
// 打印--2: 0
// 打印--3: 1
// 打印--4: 0
// 例2:
func defer06(funcName string) func(){
start := time.Now()
fmt.Printf("function %s enter\n",funcName)
return func(){
fmt.Printf("function %s exit (elapsed %s)\n",funcName,time.Since(start))
}
}
func defer07(funcName string){
defer defer06(funcName)()
time.Sleep(2 * time.Second)
}
func main(){
defer07("func01")
defer07("func02")
}
输出:
// function func01 enter
// function func01 exit (elapsed 2.000297349s)
// function func02 enter
// function func02 exit (elapsed 2.001124425s)
上面例2中,我们可以看到,defer 会先将函数
defer06(funcName)
执行,
然后等待time.Sleep
正常执行完成后再执行defer06
的返回值一个匿名函数
。
详见:https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/06.4.md
8、内置函数
名 称 | 说明 | |
---|---|---|
close | 用于管道通信 | |
len、cap len | 用于返回某个类型的长度或数量(字符串、数组、切片、map 和管道);cap 是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map) | |
new、make | new 和 make 均是用于分配内存:new 用于值类型和用户定义的类型,如自定义结构,make 用于内置引用类型(切片、map 和管道)。它们的用法就像是函数,但是将类型作为参数:new(type)、make(type)。new(T) 分配类型 T 的零值并返回其地址,也就是指向类型 T 的指针(详见第 10.1 节)。它也可以被用于基本类型:v := new(int)。make(T) 返回类型 T 的初始化之后的值,因此它比 new 进行更多的工作(详见第 7.2.3/4 节、第 8.1.1 节和第 14.2.1 节)new() 是一个函数,不要忘记它的括号 | |
copy、append | 用于复制和连接切片 | |
panic、recover | 两者均用于错误处理机制 | |
print、println | 底层打印函数(详见第 4.2 节),在部署环境中建议使用 fmt 包 | |
complex、real imag | 用于创建和操作复数(详见第 4.5.2.2 节) |