有项目需要用到的go 先学习go
入门钥匙
简介:Go语言是谷歌2009发布的第二款开源编程语言,Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。
基本结构有:
- 命名
- 常量
- 变量
- 赋值
- 类型
- 包和文件
- 作用域
命名
规则的:一个一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。大写字母和小写字母是不同的
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
内建常量: true false iota nil
内建类型: int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64 bool byte rune string error
内建函数: make len cap new append copy close delete complex real imag panic recover
常量
使用const 关键字来定义常量
// "const" 关键字用来定义常量
const s string = "appropriate"
//常量必须有初始值
const n = 20
// 常量表达式可以执行任意精度数学计算
const d = 3e20 / n
变量
用var声明语句可以创建一个特定类型的变量,然后给变量附加一个名字,并且设置变量的初始值。
Go的基本类型有:
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名 代表一个Unicode码
float32 float64
complex64 complex128
var 变量名字 类型 = 表达式
零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在未初始化的变量。
在编程的过程中,也用简短声明变量。它以“名字 := 表达式”形式声明变量。
destination := 12
result := rand.Float64() * 3.0
var形式的声明语 句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。
赋值
赋值语句可以更新一个变量的值,最简单的赋值语句是将要被赋值的变量放在=的左边,新值的表达 式放在=的右边。
x = 1 // 命令变量的赋值
*p = true // 通过指针间接赋值
person.name = "keke" // 结构体字段赋值
count[n] = count[n] * scale // 数组、slice或map的元素赋值
v := 1
v++ // 等价方式 v = v + 1;v 变成 2
v-- // 等价方式 v = v - 1;v 变成 1
类型
变量或表达式的类型定义了对应存储值的属性特征,类型声明语句一般出现在包一级,因此如果新创建的类型名字的首字符大写,则在外部包也可以使用。
type 类型名字 底层类型
type Precision float64 #精确度
包和文件
包目的都是为了支持模块化、封装、单独编译和代码重用。在Go语言中,一个简单的规则 是:如果一个名字是大写字母开头的,那么该名字是导出的。
import导入包的用法:
import "github.com/tidwall/gjson" //通过包名gjson调用导出接口
import json "github.com/tidwall/gjson" //通过别名json调用gjson
import . "github.com/tidwall/gjson" //.符号表示,对包gjson的导出接口的调用直接省略包名
import _ "github.com/tidwall/gjson" //_ 仅仅会初始化gjson,如初始化全局变量,调用init函数
作用域
一个声明语句将程序中的实体和一个名字关联,比如一个函数或一个变量。声明语句的作用域是指源代 码中可以有效使用这个名字的范围。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译 时的属性。
基本数据类型
- 整型
- 浮点型
- 复数
- 布尔型
- 字符串
- 常量
- 整型运算
整型
Go语言同时提供了有符号和无符号类型的整数运算。
有符号整形数类型:
int8,长度:1字节, 取值范围:(-128 ~ 127)
int16,长度:2字节,取值范围:(-32768 ~ 32767)
int32,长度:4字节,取值范围:(-2,147,483,648 ~ 2,147,483,647)
int64.长度:8字节,取值范围:(-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807)
无符号整形数类型:
uint8,长度:1字节, 取值范围:(0 ~ 255)
uint16,长度:2字节,取值范围:(0 ~ 65535)
uint32,长度:4字节,取值范围:(0 ~ 4,294,967,295)
uint64.长度:8字节,取值范围:(0 ~ 18,446,744,073,709,551,615)
rune类型是 Unicode 字符类型,和 int32 类型等价,通常用于表示一个 Unicode 码点。rune 和 int32 可以互换使用。此外rune类型的值需要由单引号"’"包裹。
uintptr类型只有在底层编程是才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。
浮点型
浮点型(IEEE-754 标准):
float32:(+- 1e-45 -> +- 3.4 * 1e38)32位浮点类型
float64:(+- 5 1e-324 -> 107 1e308)64位浮点类型
在Golang中浮点数的相关部分只能由10进制表示法表示。
复数
complex64: 由两个float32类型的值分别表示复数的实数部分和虚数部分
complex128: 由两个float64类型的值表示复数的实数部分和虚数部分
复数类型的值一般由浮点数表示的实数部分、加号"+"、浮点数表示的虚数部分以及小写字母"i"组成,
var x complex128 = complex(1,2) //1+2i
对于一个复数 c = complex(x, y) ,可以通过Go语言内置函数 real(z) 获得该复数的实 部,也就是 x ,通过 imag© 获得该复数的虚部,也就是 y 。
布尔型
值是 true 或 false, 布尔可以做3种逻辑运算,&&(逻辑且),||(逻辑或),!(逻辑非),布尔类型的值不支持其他类型的转换.
字符串
字符串的表示法有两种,即:原生表示法和解释型表示法。原生表示法,需用用反引号"`“把字符序列包起来,如果用解释型表示法,则需要用双引号”""包裹字符序列。
var str1 string = "keke"
var str2 string = `keke`
这两种表示的区别是,前者表示的是所见即所得的(除了回车符)。后者所表示的值中转义符会起作用。字符串值是不可变的,如果我们创建了一个此类型的值,就不可能再对它本身做任何修改。
var str string // 声明一个字符串变量
str = "hai keke" // 字符串赋值
ch := str[0] // 取字符串的第一个字符
常量
常量表达式的值在编译期计算,而不是在运行期。每种常量的潜在类型都是基础类型:boolean、string或数字。
const x, y int = 1, 2 // 多常量初始化
const s = "Hello, KeKe!" // 类型推断
const ( // 常量组
a, b
c
= 10, 20
bool = false
)
func main() {
const m = "20"// 未使用用局部常量不会引发编译错误。
}
枚举:关键字 iota 定义常量组中从 0 开始按行行计数的自自增枚举值。iota可理解为const语句块中的行索引。使用iota能简化定义,在定义枚举时很有用。
const (
Sunday = iota // 0
Monday // 1,通常省略后续行行表达式。
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
在同一常量组中,可以提供多个 iota,它们各自增⻓。
const (
A, B = iota, iota << 10 // 0, 0 << 10
C, D // 1, 1 << 10
)
容量大小的单位的自增:
const (
B = 1 << (10*iota)
KB
MB
GB
TB
PB
)
整型运算
在整型运算中,算术运算、逻辑运算和比较运算,运算符优先级从上到下递减顺序排列:
* / % << >> & &^
+ - | ^
== != < <= > >=
&&
||
复合数据类型
- 数组
- Slice
- Map
- 结构体
- JSON
数组
数组和结构体都是有固定内存大小的数据结构,相比之下,slice和map则是动态的数据结构,它们将根据需要动态增长。
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。由于数组的长度是固定的,因而在使用的时候我们用的最多的是slice。数组的每个元素可以通过索引下标来访问,索引下标的范围是从0开始到数组长度减1的位置。内置的len函数将返回数组中元素的个数。
var m [3]int = [3]int{1, 2, 3}
var n [3]int = [3]int{1, 2}
fmt.Println(n[2]) // "0"
在数组字面值中,如果在数组的长度位置出现的是“…”省略号,则表示数组的长度是根据初始化值的 个数来计算
m := [...]int{1, 2, 3}
fmt.Printf("%T\n", m) // "[3]int"
数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。数组可以直接进行比较,当数组内的元素都一样的时候表示两个数组相等。
arr1 := [3]int{1, 2, 3}
arr2 := [3]int{1, 2, 3}
arr3 := [3]int{1, 2, 4}
fmt.Println(arr1 == arr2, arr1 == arr3) //true,false
数组可以作为函数的参数传入,但由于数组在作为参数的时候,其实是进行了拷贝,这样在函数内部改变数组的值,是不影响到外面的数组的值得。
func ArrIsArgs(arr [4]int) {
arr[0] = 120
}
m := [...]int{1, 2, 3, 4}
ArrIsArgs(m)
想要改变就只能使用指针,在函数内部改变的数组的值,也会改变外面的数组的值:
func ArrIsArgs(arr *[4]int) {
arr[0] = 20
}
m:= […]int{1, 2, 3, 4}
ArrIsArgs(&m)
通常这样的情况下都是用切片来解决,而不是用数组。
这里的* 和&的区别:
& 是取地址符号 , 即取得某个变量的地址 ,如: &a
* 是指针运算符 , 可以表示一个变量是指针类型 , 也可以表示一个指针变量所指向的存储单元 , 也就是这个地址所存储的值.
Slice
Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。
数组和slice关系非常密切,一个slice可以访问数组的部分或者全部数据,而且slice的底层本身就是对数组的引用。
一个Slice由三部分组成:指针,长度和容量。内置的len和cap函数可以分别返回slice的长度和容量。
slice的切片操作s[i:j]引用s的从第i个元素开始到第j-1个元素的子序列, 新的slice将只有j-i个元素。
func main(){
p:=[]int{1,2,3}
fmt.Println("p:",p)
m1 :=p[:2]
fmt.Println("m1:",m1)
m2 := m1[1:]
fmt.Println("m2:",m2)
m3 := m2[:2]
m4 := p[:2][1:][:2]
fmt.Println("m3",m3)
fmt.Println("m4",m4)
}
结果如下:
p: [1 2 3]
m1: [1 2]
m2: [2]
m3 [2 3]
m4 [2 3]
slice的底层本身就是对数组的引用,因而多个 slice 共享的是底层的同一个数组。
slice创建方式主要有两种:1.基于数组创建。 2.直接创建
基于数组创建:
arrVar := [4]int{1, 2, 3,4}
sliceVar := arrVar[1:3]
数组arrVar和sliceVar里面的地址其实是一样的,因而如果你改变sliceVar里面的变量,那么arrVar里面的变量也会随之改变。
直接创建
内建函数new分配了零值填充的元素类型的内存空间,并且返回其地址,一个指针类型的值。
var p *[]int = new([]int) //分配slice结构内存
var m []int = make([]int,100) //m指向一个新分配的有100个整数的数组
new 分配;make 初始化
new(T) 返回 *T 指向一个零值 T
make(T) 返回初始化后的 T
注意: make仅适用于 map,slice 和 channel,并且返回的不是指针。应当用 new 获得特定的指针。
置的make函数创建一个指定元素类型、长度和容量的slice。容量部分可以省略,在这种情况下,容量将等于长度。
make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]
make创建了一个匿名的数组变量,然后返回一个slice;只有通过返回的slice才能引用底层匿名的数组变量。在第一种语句中,slice是整个数组的view。在第二个语句中,slice只引用了底层数组的前len个元素,但是容量将包含整个的数组。额外的元素是留给未来的增长用的。
slice1 := make([]int,5)//创建一个元素个数5的slice,cap也是5
slice2 := make([]int,5,10)//创建一个元素个数5的slice,cap是10
slice3 := []int{1,2,3,4,5}//创建一个元素个数为5的slice,cap是5
var slice []int //创建一个空的slice,cap和len都是0
和数组不同的是,slice之间不能比较,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素。不过标准库提供了高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,我们必须自己展开每个元素进行比较:
func equal(x, y []string) bool {
if len(x) != len(y) {
return false
}
for i := range x {
if x[i] != y[i] {
return false
}
}
return true
}
安全的做法是直接禁止slice之间的比较操作。
slice唯一合法的比较操作是和nil比较,例如:
if slice == nil { /* ... */ }
一个零值的slice等于nil。一个nil值的slice并没有底层数组。一个nil值的slice的长度和容量都是0,但是也有非nil值的slice的长度和容量也是0的,例如[]int{}或make([]int, 3)[3:]。与任意类型的nil值一样,我们可以用[]int(nil)类型转换表达式来生成一个对应类型slice的nil值。
var s []int // len(s) == 0, s == nil
s = nil // len(s) == 0, s == nil
s = []int(nil) // len(s) == 0, s == nil
s = []int{} // len(s) == 0, s != nil
要测试一个slice是否是空的,使用len(s) == 0来判断,而不应该用s == nil来判断。
append函数用于向slice追加元素.虽然Slice是可以动态扩展的。但Slice的动态扩展是有代价的,也就是说如果在确定大小的前提下,最好是设置好slice的cap大小.
func main() {
var x, y []int
for i := 0; i < 10; i++ {
y = appendInt(x, i)
fmt.Printf("%d cap=%d\t%v\n", i, cap(y), y)
x = y
}
}
0 cap=1 [0]
1 cap=2 [0 1]
2 cap=4 [0 1 2]
3 cap=4 [0 1 2 3]
4 cap=8 [0 1 2 3 4]
5 cap=8 [0 1 2 3 4 5]
6 cap=8 [0 1 2 3 4 5 6]
7 cap=8 [0 1 2 3 4 5 6 7]
8 cap=16 [0 1 2 3 4 5 6 7 8]
9 cap=16 [0 1 2 3 4 5 6 7 8 9]
当slice的的容量等于len的时候,cap是翻倍了。append的底层原理就是当slice的容量满了的时候,重新建立一块内存,然后将原来的数据拷贝到新建的内存。所以说容量的扩充是存在内存的建立和复制的。该过程将会影响到系统的运行速度。从这个角度看,slice并不是一个纯粹的引用类型,它实际上是一个类似下面结构体的聚合类型:
type IntSlice struct {
ptr *int
len, cap int
}
内置的append函数则可以追加多个元素,甚至追加一个slice。
var x []int
x = append(x, 1)
x = append(x, 2, 3)
x = append(x, 4, 5, 6)
x = append(x, x...) // append the slice x
fmt.Println(x) // "[1 2 3 4 5 6 1 2 3 4 5 6]"
内置的copy函数可以方便地将一个slice复制另一个相同类型的slice。
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
copy函数的第一个参数是要复制的目标slice,第二个参数是源slice,copy函数将返回成功复制的元素的个数等于两个slice中较小的长度,所以覆盖不会超出目标slice的范围。
Slice使用技巧
Slice的使用方式有很多例如旋转slice、反转slice或在slice原有内存空间修改元素。
使用到slice的截取的特性,slice[0:1:1] ([起始index,终止index,cap终止index]),能够避免改变源数组的结构。
Map
储键值对的无序集合, 一个map就是一个映射Map的引用.
Map的声明
var m map[string] string
m是声明的变量名,string是对应的Key的类型,string是value的类型,因此这个是声明了一个key和value都是string的map.
创建
内置的make函数可以创建map
m := make(map[string]int)
也可以用map字面值的语法创建map,同时还可以指定一些最初的key/value:
m := map[string]int{
"keke": 001,
"jame": 002,
}
这个等价于:
m := make(map[string]int)
m["keke"] = 001
m["jame"] = 002
元素的删除
内置的delete函数可以删除元素
delete(ages, "jame") //可以删除m["jame"]
map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作,禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效。
遍历map中全部的key/value对的话,可以使用range风格的for循环实现,和之前的slice遍历语法类似。
for k, v := range m {
fmt.Printf("%s\t%d\n", k, v)
}
要按顺序遍历key/value对,我们必须显式地对key进行排序,可以使用sort包的Strings函数对字符串slice进行排序。
import "sort"
var names []string
for name := range ages {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
fmt.Printf("%s\t%d\n", name, ages[name])
}
map类型的零值是nil,也就是没有引用任何映射Map。
var ages map[string]int
fmt.Println(ages == nil) // "true"
fmt.Println(len(ages) == 0) // "true"
结构体
type Rectangle struct {
width float64
length float64
}
结构体特性
如果结构体成员名字是以大写字母开头的,那么该成员就是导出的;这是Go语言导出规则决定的。一个结构体可能同时包含导出和未导出的成员. 直白的讲就是首字母大写的结构体字段可以被导出,也就是说,在其他包中可以进行读写。结构体字段名以小写字母开头是当前包的私有的,函数定义也是类似的。
一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身(该限制同样适应于数组)。但是S类型的结构体可以包含*S指针类型的成员,这可以让我们创建递归的数据结构,比如链表和树结构等。
type tree struct {
value int
left, right *tree
}
结构体嵌入和匿名成员
type Point struct {
X, Y int
}
type Circle struct {
Center Point
Radius int
}
type Wheel struct {
Circle Circle
Spokes int
}
var w Wheel
w.Circle.Center.X = 8
w.Circle.Center.Y = 8
w.Circle.Radius = 5
w.Spokes = 20
匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。
结构体tag
在Golang中结构体和数据库表的映射关系的建立是通过struct Tag来实现的。
package main
import (
"fmt"
"reflect" // 这里引入reflect模块
)
type User struct {
Name string `json:"name"` //这引号里面的就是tag
Passwd int `json:"passwd"`
}
func main() {
user := &User{"keke", 123456}
s := reflect.TypeOf(user).Elem() //通过反射获取type定义
for i := 0; i < s.NumField(); i++ {
fmt.Println(s.Field(i).Tag.Get("json")) //将tag输出出来
}
}
name
passwd
结构体的成员Tag可以是任意的字符串面值,但是通常是一系列用空格分隔的key:"value"键值对序列;因为值中含义双引号字符,因此成员Tag一般用原生字符串面值的形式书写。
注意:这里的 & 和 * 的区别,& 运算符,用来获取指针地址,而 * 运算符是用来获取地址里存储的值。此外指针的值和指针的地址是不同的概念,指针的值: 指的是一个地址,是别的内存地址。指针的地址: 指的是存储指针内存块的地址。
要传递大的结构体的时候,用指针是一个明智的选。
Golang中string,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。若函数需改变slice的长度,则仍需要取地址传递指针。要访问指针 p 指向的结构体中某个元素 x,不需要显式地使用 * 运算,可以直接 p.x;
JSON
解析JSON
数据结构 --> 指定格式 = 序列化 或 编码(传输之前)
指定格式 --> 数据格式 = 反序列化 或 解码(传输之后)
json解析到结构体
Go中我们经常需要做数据结构的转换,Go中提供的处理json的标准包是 encoding/json,主要使用的是以下两个方法:
// 序列化 结构体=> json
func Marshal(v interface{}) ([]byte, error)
// 反序列化 json=>结构体
func Unmarshal(data []byte, v interface{}) error
序列化前后的数据结构有以下的对应关系:
bool for JSON booleans
float64 for JSON numbers
string for JSON strings
[]interface{} for JSON arrays
map[string]interface{} for JSON objects
nil for JSON null
json要转换成结构体就需要用到(Unmarshal)样的函数
func Unmarshal(data []byte, v interface{}) error
// demo
package main
import (
"encoding/json"
"fmt"
)
type Server struct {
ServerName string
ServerIP string
}
type Serverslice struct {
Servers []Server
}
func main() {
var s Serverslice
str := `{"servers":[{"serverName":"Shanghai","serverIP":"127.0.0.1"}, {"serverName":"Beijing","serverIP":"127.0.0.2"}]}`
json.Unmarshal([]byte(str), &s)
fmt.Println(s)
}
结构体转json
结构体转json就需要用到JSON包里面通过Marshal函数来处理:
func Marshal(v interface{}) ([]byte, error)
// demo
package main
import (
"encoding/json"
"fmt"
)
type Server struct {
ServerName string
ServerIP string
}
type Serverslice struct {
Servers []Server
}
func main() {
var s Serverslice
s.Servers = append(s.Servers, Server{ServerName: "Shanghai", ServerIP: "127.0.0.1"})
s.Servers = append(s.Servers, Server{ServerName: "Beijing", ServerIP: "127.0.0.2"})
b, err := json.Marshal(s)
if err != nil {
fmt.Println("json err:", err)
}
fmt.Println(string(b))
}
针对JSON的输出,我们在定义struct tag的时候需要注意的几点是:
-
字段的tag是"-",那么这个字段不会输出到JSON
-
tag中带有自定义名称,那么这个自定义名称会出现在JSON的字段名中,例如上面例子中serverName
-
tag中如果带有"omitempty"选项,那么如果该字段值为空,就不会输出到JSON串中
-
如果字段类型是bool, string, int,int64等,而tag中带有",string"选项,那么这个字段在输出到JSON的时候会把该字段对应的值转换成JSON字符串
type Server struct {
// ID 不会导出到JSON中
ID intjson:"-"
// ServerName2 的值会进行二次JSON编码 ServerName string `json:"serverName"` ServerName2 string `json:"serverName2,string"` // 如果 ServerIP 为空,则不输出到JSON串中 ServerIP string `json:"serverIP,omitempty"` } s := Server { ID: 1, ServerName: `Go "1.0" `, ServerName2: `Go "1.10" `, ServerIP: ``, } b, _ := json.Marshal(s) os.Stdout.Write(b)
Marshal函数只有在转换成功的时候才会返回数据,但是我们应该注意下:
- JSON对象只支持string作为key,所以要编码一个map,那么必须是map[string]T这种类型(T是Go语言中任意的类型)
- Channel, complex和function是不能被编码成JSON的
- 嵌套的数据是不能编码的,不然会让JSON编码进入死循环
- 指针在编码的时候会输出指针指向的内容,而空指针会输出null解析到interface
在Go中Interface{}可以用来存储任意数据类型的对象,这种数据结构正好用于存储解析的未知结构的json数据的结果。
JSON包中采用map[string]interface{}和[]interface{}结构来存储任意的JSON对象和数组。Go类型和JSON类型的对应关系如下:
bool 代表 JSON booleans,
loat64 代表 JSON numbers,
string 代表 JSON strings,
nil 代表 JSON null.
函数
- 函数参数与返回值
- 将函数作为参数传递
- 内置函数
- 递归函数
- 匿名函数
- defer延迟函数
- panic异常和
- recover捕获异常
函数参数与返回值
func function_name( [parameter list] ) [return_types] {
body(函数体)
}
// 这个函数计算两个int型输入数据的和,并返回int型的和
func plus(a int, b int) int {
// Go需要使用return语句显式地返回值
return a + b
}
fmt.Println(plus(3,4)) //7
将函数作为参数传递
package main
import (
"fmt"
)
func main() {
callback(1, Add)
}
func Add(a, b int) {
fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}
func callback(y int, f func(int, int)) {
f(y, 5) // this becomes Add(1, 5)
}
The sum of 1 and 5 is: 6
内置函数
close 用于关闭管道通信channel
len、cap len 用于返回某个类型的长度或数量(字符串、数组、切片、map 和管道);cap 是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
new、make new 和 make 均是用于分配内存:new 用于值类型和用户定义的类型,如自定义结构,内建函数new分配了零值填充的元素类型的内存空间,并且返回其地址,一个指针类型的值。make 用于内置引用类型(切片、map 和管道)创建一个指定元素类型、长度和容量的slice。容量部分可以省略,在这种情况下,容量将等于长度。它们的用法就像是函数,但是将类型作为参数:new(type)、make(type)。new(T) 分配类型 T 的零值并返回其地址,也就是指向类型 T 的指针)。它也可以被用于基本类型:v := new(int)。make(T) 返回类型 T 的初始化之后的值,因此它比 new 进行更多的工作,new() 是一个函数,不要忘记它的括号
copy、append copy函数用于复制,copy返回拷贝的长度,会自动取最短的长度进行拷贝(min(len(src), len(dst))),append函数用于向slice追加元素
panic、recover 两者均用于错误处理机制,使用panic抛出异常,抛出异常后将立即停止当前函数的执行并运行所有被defer的函数,然后将panic抛向上一层,直至程序carsh。recover的作用是捕获并返回panic提交的错误对象、调用panic抛出一个值、该值可以通过调用recover函数进行捕获。主要的区别是,即使当前goroutine处于panic状态,或当前goroutine中存在活动紧急情况,恢复调用仍可能无法检索这些活动紧急情况抛出的值。
print、println 底层打印函数
complex、real imag 用于创建和操作复数,imag返回complex的实部,real返回complex的虚部
delete 从map中删除key对应的value
递归函数
package main
import "fmt"
func main() {
result := 0
for i := 0; i <= 10; i++ {
result = processing(i)
fmt.Printf("processing(%d) is: %d\n", i, result)
}
}
func processing(n int) (res int) {
if n <= 1 {
res = 1
} else {
res = processing(n-1) + processing(n-2)
}
return
}
匿名函数
匿名函数由一个不带函数名的函数声明和函数体组成.通常不希望再次使用(即只使用一次的)的函数可以定义为匿名函数.
匿名函数结构:
func() {
//func body
}() //花括号后加()表示函数调用,此处声明时为指定参数列表,
如:
fun(a,b int) {
fmt.Println(a+b)
}(1,2)
定义在闭包中的函数可以”记忆”它被创建时候的环境。闭包函数=匿名函数+环境变量。
func f(i int) func() int {
return func() int {
i++
return i
}
}
c1 := f(0)
c2 := f(0)
c1() // reference to i, i = 0, return 1
c2() // reference to another i, i = 0, return 1
函数f返回了一个函数,返回的这个函数,返回的这个函数就是一个闭包。这个函数中本身是没有定义变量i的,而是引用了它所在的环境(函数f)中的变量i。
defer延迟函数
defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。
f,err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close()
如果有多个defer表达式,调用顺序类似于栈,越后面的defer表达式越先被调用。在处理其他资源时,也可以采用defer机制,比如对文件的操作:
package ioutil
func ReadFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
return ReadAll(f)
}
也可以处理互斥锁:
var mu sync.Mutex
var m = make(map[string]int)
func lookup(key string) int {
mu.Lock()
defer mu.Unlock()
return m[key]
}