Golang

基本用法

输出

参数可以是字符串,数字,数学表达式,传递若干参数用逗号隔开.

1
2
3
4
5
6
7
8
9
package main
//引入fmt包
import "fmt"

func main() {
//使用fmt中Print函数进行打印
fmt.Print("Tom")
//下面的Println函数有换行功能
fmt.Println("my name is", "Tom")

Printf

第一个参数必须是字符串,其中包含格式化动词,如%v,值由第二个参数决定.

1
2
3
4
5
6
func main() {
fmt.Printf("My %v is 15", "age")
}
//其中格式化动词%v可以指定宽度
%4v,就是向左填充4个空格
&-5v,就是向右填充5个空格

常量和变量

const

声明常量

var

声明变量

1
2
3
4
5
6
7
8
9
var distance = 100
var speed = 30
//or
var distance, speed = 100, 30
/or
var (
distance = 100
speed = 30
)

自增运算

go 中没有++count这种,但是有count++

随机数

使用 math 包中的 rand

1
2
3
4
5
6
7
8
9
10
package main
import (
"fmt"
"math/rand"
)
func main () {
//Intn(10)是取0-10不包括10的整数.
var num = rand.Intn(10) + 1
fmt.Println(num)
}

Boolean 类型

true 和 false,不会进行类型转换.

字符串

strings.Contains

返回 boolean 类型.

1
2
3
4
5
6
7
8
package main
import (
"string"
)
func main () {
var str = "aaa bbb ccc"
var exit = strings.Contains(str, "aaa")
}

条件判断

if

注意 if 后不适应()

switch

switch 语句中不需要写 break
switch 后也不需要跟传入参数

循环

for

for 语句后也不用(),后面的判断如果没有就是无限循环

作用域

变量的作用域在{}中.

package 作用域

在 main 函数之外声明必须使用 var 声明,不能使用短声明.且作用域存在于 package 中.

短声明

优势:可以在无法使用 var 的地方使用.比如 for 循环中.

1
2
3
4
5
6
var count = 10
count := 10
//for循环中
for count:=10; count > 0; count++ {
fmt.Println(count)
}

声明浮点型变量

1
2
3
days := 365.2425
var days = 365.2425
var days float64 = 365.2425

只要数字带有小数部分,类型就是float64.
如果使用整数声明浮点型,必须使用float64,否则就是整数类型.

单精度浮点类型

go 语言默认是float64,双精度浮点类型.占用 8 字节内存.
也可以使用float32,单精度浮点类型,占用 4 字节内存.
使用场景: 处理大量数据时,可以牺牲精度,节省内存.

显示浮点型

使用PrintPrintln打印时,默认显示所有小数.
如果想控制小数显示位数,可以使用Printf,结合%f格式化动词.

1
2
3
4
5
6
7
third := 1.0 / 3
fmt.Println(third)
fmt.Printf("%f",third) //表示显示浮点型
fmt.Printf("%.3f",third) //表示浮点型有3位数
fmt.Printf("%4.2f",third) //表示浮点型有2位数,整体包括小数点至少有4位数,如果不足,默认空格填充
//如果想使用0填充空格
fmt.Printf("%05.2f",third)

浮点型的精度问题

浮点型不适合做金融计算,如果非要使用,可以先乘后除.

1
2
3
4
5
third : = 1.0 / 3.0
fmt.Println(third + third + third) // 输出1
pigBank := 0.1
pigBank += 0.2
fmt.Pirntln(pigBank) // 0.300000000004

零值

Go 里面每个类型都有一个默认值,即零值.
当声明变量却不初始化时,该值即零值.

1
2
3
4
var price float64
fmt.Println(price)
//相当于
price := 0.0

整数类型

int

有符号类型

uint

无符号类型

uint8 表示颜色

可以用来表示 8 位的颜色(红绿蓝 0-255).
var red, green, blue uint8 = 0, 141, 213;
uint8 的取值范围正合适,而 int 则多出来几十亿不合适的数.

打印数据类型

在 Printf 中使用%T.

十六进制

在数值前加0x即表示十六进制.

打印十六进制

使用Printf%x.

1
2
3
fmt.Printf("%x %x %x", red, green, blue) // 0 8d d5
//指定宽度
fmt.Printf("color: #%02x%02x%02x;",red, green, blue) //color: #008dd5;

整数环绕

当超出超出整数的取值范围,就会发生整数环绕.
原理: 用二进制表示,比如 11111111,加 1 后变成 100000000,但是内存只有 8 位,所以值变为 0.

1
2
3
4
5
6
7
var red uint8 = 255
red++
fmt.Println(red) // 0,因为uint的范围时0-255

var number int8 = 128
number++
fmt.Println(number) //-127,因为int的范围时-127~128

打印二进制

格式化动词%b

1
2
3
4
var green uint8 = 3
fmt.Printf("%08b\n", green) // 00000011
green++
fmt.Printf("%08b\n", green) // 00000100

整数类型的最大值最小值

math 包里,为与架构无关的整数类型,定义了最大值,最小值常量.

1
2
math.MaxInt8
math.MinInt64

而 int 和 uint 可能时 32 位或者 64 位,无最大最小值.

避免时间环绕

unix 系统中,时间从 1970 年开始,到 2038 年会超过 20 亿,超过了 int32 的范围.
方法: 使用 int64 或者 uint64.

数值过大

浮点类型数值大但是精度不高,
整数类型精度高但是取值范围小.
采用方法可以
int64 < uint64 < float64 < 使用 big 包

big 包

对于较大的整数(超过 10 的 18 次方): big.Int
对于任意精度的浮点型: big.Float
对于分数: big.Rat

big.Int

使用了big.Int的等式其他部分也需要使用big.Int.
NewInt()函数可以把int64转化成big.Int类型.

常量和 big.Int 的值不能互换.

字符串

字符串字面值和原始字符串字面值

字符串字面值可以包含转义字符,如\n.
如果想显示\n,而不要换行,则可以使用```,即原始字符串字面值

1
2
fmt.Println("pece you\nwith") //打印有换行
fmt.Println(`pece you \n with`) // 打印出了\n,没有换行,或者直接在这里使用换行会原始换行

code points, runes, bytes

rune 是 int32 的别名,byte 是 int8 的别名.

自定义类型别名

1
2
type byte = uint8
type rune = int32

可以使用%c打印该字符.

字符

字符字面量用''包裹,如果没有指定字符类型,Go 会推断该类型为rune.
字符字面量也可以用byte表示.

字符解码

Go 的函数len可以返回字符的长度,但是有的字符是 16 位或 32 位,那么需要把字符解码成rune类型,再操作.
使用UTF-8包,他提供可以按rune计算字符串长度的方法.
RuneCountInString可以返回转换后的长度.
DecodeRuneInString函数会返回第一个字符,以及字符所占的字节数.
所以 Go 里的函数可以返回多个值.

1
2
3
4
question := '(*Φ皿Φ*)'
fmt.Println(utf8.RuneCountInString(question)
c, size := utf8.DecodeRuneInStirng(question)
fmt.Println("First rune: %c %v bytes", c, size)

遍历方法 range

range 返回两个值,一个 i 是索引,c 就是遍历的元素.

1
2
3
4
5
6
7
func main() {
question := '(*Φ皿Φ*)'
for i, c := range question {
fmt.Printf("%v %c\n", i ,c)
}
}
//如果不想使用索引,可以使用_替代

类型转换

不同类型不能在一起使用,需要进行类型转换.

数值之间转换

1
2
age := 64
marsAge := float64(age) //如果将age转换为浮点型需要进行包裹

浮点型转换为整型,小数点后边会被舍弃.
无符号和有符号整数之间也需要进行转换
不同大小的整数类型之间也需要进行转换.

转换时的数据环绕

因为转换时有可能会超过转换类型的最大值,所以可以使用 math 包中的最大值最小值进行判断.

1
2
3
4
var bh float64 = 32678
if bh < math.MinInt16 || bh >math.MxInt16 {
//handle
}

字符串转换

rune,byte转换成string,

1
2
3
4
var pi rune = 960
var alpha rune = 940

fmt.Println(string(pi), string(alpha)) //转化的结果时code point对应的结果

想把数值类型转换成字符串,那么它的值必须可以转换成code point.

strconv包的Itoa函数可以转换,或者使用Sprintf
strconv包的Atoi函数可以转换,且有两个参数,第二个返回错误信息,如果不为空,则有错误.

1
2
3
4
countdown, err := strconv.Atoi("10")
if err != nil {
//handle
}

函数

  • 函数按值传递
  • 同一个包中的函数调用彼此不需要加上包名.

函数声明

1
2
3
4
5
//rand包的Intl:
func Intn(n int) int
//关键字 函数名(参数 参数类型) 返回值类型
//使用方法
num := rand.Intn(10)

多个返回值

1
2
3
4
func Atio (s string) (i int, err error)
//关键字 函数名 参数名 参数类型 返回值名 返回值类型
//简化写法可以把返回值名省略,只保留返回值类型
func Atio (s string) (int error)

可变参数函数

Println可以接收多个参数.

1
func Println (a ...interface{}) (n int, err error)
  • ...表示函数的参数是可变的
  • 参数 a 的类型为interface{},是一个空接口.
  • ...和空接口组合就可以接收任意数量,类型的参数

一等函数(头等函数,高阶函数)

  • 可以将函数赋值给变量
  • 将函数作为参数传递给函数
  • 将函数作为函数的返回类型

闭包和匿名函数

  • 匿名函数在 go 中被称为函数字面值,函数字面值需要保留外部作用域的变量引用,所以函数字面值都是闭包的.
  • 闭包就是由于匿名函数封闭并包围作用域中 的变量而得名的.

方法

方法也属于函数,只不过函数是独立存在的,方法属于某一个类型,或者与类型有关联.

1
2
func (k kelvin)  celsius() celsius
//关键字 (变量名 类型名)=接收者 方法名 返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import (
"fmt"
"math/rand"
"time"
)
type kelvin float64

func messs(samples int, sensor func() kelvin) {
for i:=0; i < samples; i++ {
k := sensor()
fmt.Printf("%v K\n", k)
time.Sleep(time.Second)
}
}
func fakeSensor() kelvin {
return kelvin(rand.Intn(151) + 150)
}
func main() {
messs(3, fakeSensor)
}

方法添加行为

可以将方法中同包中声明的任何类型相关联,但不可以是int,float64等预声明的类型进行关联,但是可以通过声明一个新类型的方式曲线救国

1
2
3
func (k kelvin) celsius() celsius
//关键字 接收者 接收者类型 方法名 返回值类型
//表明celsius是类型kevin的一个方法
  • 上例中celsius方法虽然没有参数,但他前面却有一个类型参数的接收者
  • 每个方法可以有多个参数,但只能有一个接收者.
  • 在方法中,接收者的行为和其他函数一样

调用方法

变量.方法()

1
2
3
4
5
6
7
8
9
10
type kevin float64
type celsius float64

func (k kevin) celsius() celsius {
return celsius(k -273.15)
}
func main() {
var k kevin = 294.0
var c = k.celsius()
}

数组

长度len,声明数组时未被声明的元素的值为零值.

1
2
//声明长度为8,元素类型为string的数组
var arr [8]string

数组越界

Go 在检测到对越界元素的访问时会报错
在编译时未发现错误,那么运行时会出现panic

复合字面值初始化数组

可以使用...作为数组的长度,go 会推断数组长度

1
2
3
4
5
var plants := [...]string{
"Earth",
"Mars",
"Venus"
}

遍历数组

for 循环

1
2
3
4
5
6
7
8
9
plants := [...]string{
"Earth",
"Mars",
"Venus"
}
for i := 0;i < len(dwarfs); i++ {
dwarf := dwarfs[i]
fmt.Println(i, dwarf)
}

range 循环

类似 map

1
2
3
for i, dwarf := range dwarfs {
fmt.Println(i, dwarf)
}

数组的复制

无论是复制给新变量还是传递给函数,都会产生一个新数组的副本.
数组也是一种值,通过值传递来接收参数,所以作为函数的参数很低效.
数组的长度也是数组类型的一部分.
函数一般使用slice作为参数.