go基础
安装
- 从官网下载对应系统的对应版本。
- 放到想要安装的位置,随后修改环境变量指向
<install-path>/bin。 - 添加用户环境变量
export PATH=$(go env GOPATH)/bin:$PATH。
代理
在下载包的时候需要设置代理,不然下载不下来
# 设置环境变量
export GO111MODULE=on
export GOPROXY=https://goproxy.cn入门
hello world
package "main"
import "fmt"
import "math/rand"
func main() {
fmt.Println("Hello world")
}
// 运行:go run main.go- 每个go程序都由包构成,程序从
main开始执行。上述程序通过导入路径fmt和math/rand来使用这两个包。按照约定,包名和导入路径的最后一个元素一致。例如,"math/rand"包中的源码均以package rand语句开始。 - 通常导出变量都是以大写字母开头,例如:
math.Pi。
数据类型
指针
Go语言有指针,指针保存了值的地址。
// 类型*T是指向T类型的指针,其零值为nil。
var p *int
// &操作符会生成一个指向其操作数的指针。
i := 42
p = &i
// *操作符表示指针指向的底层值
fmt.Println(*p)
*p = 21
// go没有指针运算符结构体(struct)
- 一个结构体就是一组字段。
- 结构体通过
.号访问字段。 - 结构体指针:对于指向一个结构体的指针
p,访问字段X的时候可以通过(*p).X的方式,也可以通过go语言隐式解引用的方式p.X就可以。 - 结构体字面量:使用
Name:的语法可以仅列出部分字段(字段名和顺序无关) &返回一个指向结构体的指针。这种方式不会导致悬空指针,原因在于Go语言有垃圾回收器(GC)。这里p指向的实例是在堆上分配的,虽然是在main函数的作用域内创建的变量,但是可以通过返回值的方式返回出去,所以必须在堆上分配。当没有变量指向这个实例的时候,实例会被置为不可达,在未来的某个时刻,GC会检测到这块内存没有被任何活跃的变量引用,而将其回收。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
data := Vertex{5, 6}
data.X = 10
fmt.Println(data, data.X)
var (
V1 = Vertex{1, 2}
V2 = Vertex{X: 10} // Y被隐式的赋予0
V3 = Vertex{} // X Y均被隐式的赋予0
p = &Vertex{1, 2} // 创建一个*Vertex类型的结构体指针。
)数组(array)
- 通常数组是固定长度的:
[n]T表示一个n个T类型的元素的数组,var a [10]int - 数组是值类型:赋值给另外一个变量的话是创建了一个副本,修改新的值不会影响原始的值。
package main
import "fmt"
func main() {
var a [2]string
a[0] = "hello "
a[1] = "world"
fmt.Println("打印数组:", a)
b := [5]int{1,2,3,4,5}
fmt.Println("打印短变量声明:", b)
c := b[2:3]
fmt.Println("slice的数值:", c)
}切片(slice)
- 相当于"指针"/“引用”
- 动态长度:
[]T{...}长度跟初始化用的数据数量相关,可以通过append(a, "a")的方式添加元素 - 数组的切片:
a[low: high]slice不做数据的拷贝,只是原始数组部分内存的"别名",修改某些位置内容的话,所有slice都会生效。- 这时如果对切片
append的话,分两种情况:1)如果容量没有超出数组的长度时,会直接修改原始数组中的值。2)如果容量超出数组长度的话,创建一个更大容量的存储,并且修改slice的指向,这时候修改slice中的数值不再会影响原始数组的值。
- 这时如果对切片
- 切片的容量(cap)和长度(len):切片的容量和长度取决于其初始指向,和底层存储容量,见下面例子。
- 切片的默认值:
nil - 通过
make创建零值slice:make([]int, 3, 5),其中3是len,5是cap。 - 切片的切片:类似于动态二位数组
range操作:for i, v := range data {...},i是index,v是数值的拷贝,如果v不是数值(比如slice),则只是v的拷贝,而不是v引用的内容的拷贝。
func main() {
// 这里a就是切片
a := []string{"a", "b", "c"}
// 可以向a后面添加元素
append(a, "d") // a成为["a", "b", "c", "d"]
// 切片的写法和python相似
var a [10]int
a[0:10]
a[:10]
a[0:]
a[:]
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// 打印:len=6, cap=6 [2 3 5 7 11 13]
s = s[:0]
printSlice(s)
// 打印:len=0, cap=6 []
s = s[:4]
printSlice(s)
// 打印:len=4, cap=6 [2 3 5 7]
s = s[2:]
printSlice(s)
// 打印:len=2, cap=4 [5 7]
// slice的slice
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
// range操作
pow := []int{1, 2, 4, 8, 16}
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
// 还可以这样写
// for i, _ := range pow
// for _, v := range pow
// for i := range pow
}
func printSlice(s []int) {
fmt.Printf("len=%d, cap=%d %v\n", len(s), cap(s), s)
}字典(map)
map[<key_type>]<value_type>:- 字典的默认值:
nil,没有key也不能添加key,其实相当于空指针。 make创建map:m := make(map[string]int)- 字典字面量:在代码中直接创建和初始化
map类型变量的语法,通过这种语法,可以不用先创建字典,然后再添加元素。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
var m map[string]Vertex
func main() {
if m == nil {
fmt.Println("m尚未初始化")
m = make(map[string]Vertex)
}
m["hhhh"] = Vertex{X: 1, Y: 2}
fmt.Println(m)
// 字典字面量
m2 := map[string]Vertex {
"test1": {1, 2}, // 只能在字面量的情况下省略值的类型
"test2": {3, 4}
}
fmt.Println(m2)
// 增加键值对/修改键值对
m2["test3"] = Vertex{5, 8}
fmt.Println(m2)
// 删除键值对
delete(m2, "test3")
fmt.Println(m2)
// 判断键是否存在
res, ok := m2["test3"]
// 如果键存在的话,ok是true,res是对应的值,否则ok是false,且res是对应类型的零值
}变量&常量
变量
- 没有明确初始化的变量声明会被赋予对应类型的 零值。
- 零值是:
- 数值类型为
0, - 布尔类型为
false, - 字符串为
""(空字符串)。
- 数值类型为
- 零值是:
// 声明变量,并且写类型
var a int = 10
// go编译器根据初值自动推断类型
var b = "asdfasfd"
// 短变量声明,仅在函数内使用,不能用在包级别的层级
c := 1234常量
- Go语言的类型转换只能通过显式的方式。
- 常量不能用
:=语法声明。 - 数值常量是高精度的值,一个未指定类型的常量由上下文决定其类型。
// 常量类型自动推断
const d = 10
// 常量块
const (
StatusOK = 200
StatusNotFound = 404
StatusError = 500
)
// 使用 iota 创建枚举,go中没有传统的enum,可以使用iota创建自增
const (
Sunday = iota // 从零开始,后续逐个加1
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
// 高级用法(定义数据大小)
const (
_ = iota
KB = 1 << (10 * itoa) // 1 << (10 * 1) = 1024
MB = 1 << (10 * itoa) // 1 << (10 * 2) = 1048576
GB = 1 << (10 * itoa) // 1 << (10 * 3) = 1073741824
TB = 1 << (10 * iota) // ...
)
// 数值常量 `值` 的属性
package main
import (
"fmt"
)
const (
Big = 1 << 100
Small = Big >> 99
)
func needInt(x int) int { return x * 10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
// 这里常量Small、Big的类型由上下文决定,只是一个高精度的值
fmt.Println(needInt(Small))
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}函数
- 函数也是值,可以作为其他函数的参数或者返回值。
- 可以是一个闭包(closure):闭包是编程中一个非常重要的概念,闭包是一个函数,它会记住自己被创建时的环境(捕获相关变量,延长这些变量的生命周期到闭包的生命周期结束,即便这些变量所在的作用域早已消失)。
// go语言是强类型语言,包括**参数**和**函数返回值**。
func add(x int, y int) int {
return x + y
}
// 当连续两个或多个函数的已命名形参类型相同时,除最后一个类型以外,其它都可以省略。
func add(x int, y int) {}
// 上式可以简化为
func add(x, y int) {}
// 函数可以返回多个值
func swap(x, y string) (string, string) {
return y, x
}运算符
- 有
i++,但是没有++i这种写法。
流程控制
for循环
- go只有一种循环结构
for循环,基本的for循环由三部分组成,他们用分号隔开:- 初始化语句:在第一次迭代前执行
- 条件表达式:在每次迭代前求值
- 后置语句:在每次迭代的结尾执行
- 初始化语句通常为一个短变量声明,并且生命周期仅在
for循环中。 - 初始化语句和后置语句都是可选的,这时候前后两个分号都可以省略(也就是
while语句在go中是for语句)。
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println("和为", sum)
// 'while'语句
for x < 10 {
...
}
// 无限循环
for {
...
}
}if语句
if语句和for一样,条件不需要加(),但是执行过程{}是必须的。if语句也可以有初始化语句(短变量声明),该短变量的声明周期也只在if/if...else之内。
package main
import (
"fmt"
"math"
)
func main() {
x := 10
if x < 20 {
...
} else {
}
if v := math.Sqrt(x); v < 22 {
}
}switch语句
go中switch语句和其他语言不太一样,只会运行选定的case,也就是默认是直接break,除非增加fallthrough。case语句后面无需是常量,且取值不限于整数。
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
fmt.Println("Go 运行的系统环境:")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("macOS.")
// fallthrough
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.\n", os)
}
// case后接func或公式计算
today := time.Now().Weekday()
switch time.Saturday {
case today + 0:
fmt.Println("今天")
case today + 1:
fmt.Println("明天")
case today + 2:
fmt.Println("后天")
default:
fmt.Println("很多天后")
}
t := time.Now()
// 无条件switch语句可以当作一长串的if-then-else
switch {
case t.Hours() < 12:
fmt.Println("早上好!")
case t.Hours() < 17:
fmt.Println("下午好!")
default:
fmt.Println("晚上好!")
}
}defer推迟
- defer语句会将函数推迟到外层函数返回之后执行。
- 推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
- defer栈:当有多个defer调用时,在函数退出时会按照先进后出的方式执行。
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i ++ {
defer fmt.Printf("%d.", i)
}
fmt.Println("done")
}方法(methods)
- Go语言没有
类,但是你可以在types上定义定义方法(methods)。 - 方法是一个有一个接收器参数的函数。
- 如果要就地修改原始值的话,需要使用指针接收器:
- 定义方式:
func (v *Vertex) Scale(s float64) float64 {...} - 对于指针接收器,可以使用值或者指针通过
v.Scale(1.0)的方式调用,go编译器自动将值转换成(&v).Scale(1.0)的形式,方便使用。
- 定义方式:
- 原则上同一个
type上的方法要使用同一类型的接收器,而不是混合使用。 interfaces(接口):interface定义了一系列方法的签名。
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
// 这里(v Vertex)就是接收器参数
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X * v.X + v.Y * v.Y)
}
// 方法还可以用于非结构体参数
type MyFloat float32
func (v MyFloat) Abs() float32 {
if v < 0 {
return float32(-v)
}
return float32(v)
}
func main() {
v := Vertex(3, 4)
fmt.Println(v.Abs())
}