Hello,World
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
Basic
Packages, variables,and function
Packages (main包作为程序入口点,代码中用路径的最后一个单词表示该包,圆括号组合多个导入,导出名首字母大写)
- Programs start running in package
main
. - By convention, the package name is the same as the last element of the import path. For instance, the
"math/rand"
package comprises files that begin with the statementpackage rand
.
package main //1 Program start point
import (
"fmt"
"math/rand"
)
func main() {
//2 using last element of import path
fmt.Println("My favorite number is", rand.Intn(10))
}
- 使用圆括号组合多个导入 (Good style 👍)
import (
"fmt"
"math"
)
- 也可以使用单条声明语句
import "fmt"
import "math"
Exported Name(导出名要首字母大写)
导出名必须大小,否则无法导出
package main
import (
"fmt"
"math"
)
func main() {
//错误,pi并不是合法的导出名,故无法访问
fmt.Println(math.pi) //改成math.Pi
}
只可以访问一个包的所有导出名, 未导出的名字是无法访问的。❌
Function (变量类型写在变量名后面,输入参数的简写规则,可以有多个输出参数,命名返回值)
- 函数可以有0到多个输入参数
- 参数类型在变量名的后面 👍 (为啥类型放在变量名后面)
func add(x int,y int) int
{
return x + y
}
- 当两个连续的输入变量,类型相同时,可以省略前面那个变量的类型
func add(x,y int) int
{
return x + y
}
- 可以有多个返回值
func swap(x, y string) (string, string) {
return y, x
}
- 命名返回值, 要使用空的return,要返回的变量写在函数头上
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
变量 (变量使用_作用域_初始化_类型推断,短变量声明,变量的圆括号声明,默认初始值,类型转换,类型推断,常量_初始化_圆括号声明_常量的类型推断)
变量的作用域可以的包级别的,也可以是函数级别的
package main
var c, python, java bool //包级别作用域
func main() {
var i int //函数级别作用域
}
- 变量初始化
var i, j int = 1, 2 //
func main() {
var c, python, java = true, false, "no!" //类型推断
}
- 短变量声明 只能在函数内使用
func main() {
k := 3//使用变量:=初始值,代替var 声明语句
c, python, java := true, false, "no!"
}
- 多个变量的因子声明 (这里的因子,表示是乘法中的圆括号的意思)
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
//该写法与import 的圆括号写法类似
- 默认初始值为0
- 类型转换 T(x) 不存在自动的隐式转换
var i int = 42
var f float64 = float64(i)
- 类型推断 ,根据精度推断类型
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
//根据右值的精度来推断类型,可能是int,float64,complex128
- 常量 常量也是有类型的,根据上下文来推断
const Pi = 3.14
//常量不能使用:= ,因为这是Var的短变量声明写法
//圆括号声明
const (
Big = 1 << 100
Small = Big >> 99
)
控制结构
循环For(略写规则,while,无限循环)
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
}
func main() {
sum := 1
for ; sum < 1000; {//省略初始化,和后置操作
sum += sum
}
fmt.Println(sum)
}
//Go版本的while循环, go 本身没有while,需要用for 来模拟
func main() {
sum := 1
for sum < 1000 {//省略初始化,后置操作和分号
sum += sum
}
fmt.Println(sum)
}
//无限循环
func main() {
for {
}
}
If(不需要括号,要有{},可以加短声明)
If 后面不需要接括号,但要有{}
//If的短声明
if v := math.Pow(x, n); v < lim {
return v
}
Switch
//Switch 会从上到下遍历分值,如果满足,就进入
switch i {
case 0:
case f(): //switch的case 分支可以不是常量,可以是变量,函数调用的返回值等等(这点不同于C/C++)
}
Switch 省略条件
switch { //省略条件相当于 switch true
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
### Defer(表达式立即计算,函数调用在该函数返回后,后入先出规则)
package main
import "fmt"
func main() {
defer fmt.Println("world") //这个语句中的表达式会立即计算,但函数调用会发生在函数返回后
fmt.Println("hello")
}
后入先出规则
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
指针,结构体,切片,字典
指针(定义,从变量获取&,解引用)
var p *int
i := 4
p = &i
*p = 21
结构体(字段的集合)
type Vertex struct {
X int
Y int
}
字段访问(用点)
v := Vertex{1, 2}
v.X
指向结构体的指针(访问字段时可以省略解引用)
v := Vertex{1, 2}
p := &v
p.X = 1e9 //(*p).X
字面量初始化
v1 = Vertex{1, 2} // has type Vertex
v2 = Vertex{X: 1} // Y:0 is implicit
v3 = Vertex{} // X:0 and Y:0
数组(大小固定)
var a [10]int
primes := [6]int{2, 3, 5, 7, 11, 13}
切片(不存储数据,修改会影响底层数组)
构建
从数组初始化
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]//1-3
字面量初始化(不写明大小是切片)
[3]bool{true, true, false}//写明大小是数组
[]bool{true, true, false}//不写明大小是切片
动态构建
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
索引默认值
a[0:10]//四种写法等价
a[:10]
a[0:]
a[:]
长度与容量
s := []int{2, 3, 5, 7, 11, 13}
len(s)//切片包含元素数
cap(s)//底层数组元素数
空切片
var s []int
s == nil //为True
多维切片
// Create a tic-tac-toe board.
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
// The players take turns.
board[0][0] = "X"
board[2][2] = "O"
board[1][2] = "X"
board[1][0] = "O"
board[0][2] = "X"
追加
append函数发现切片cap 不够会重新分配,返回新分配的切片
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
var s []int
printSlice(s)//len=0 cap=0 []
// append works on nil slices.
s = append(s, 0)
printSlice(s) //len=1 cap=1 [0]
// The slice grows as needed.
s = append(s, 1)
printSlice(s)//len=2 cap=2 [0 1]
// We can add more than one element at a time.
s = append(s, 2, 3, 4)//len=5 cap=6 [0 1 2 3 4]
切片的Range操作(依次返回–index,value;略写规则-下划线-不写value)
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
for i, _ := range pow //省略value
for _, value := range pow//省略index
for i:= range pow // 不写value
字典
空字典
//值为nil,没有key; 不能添加key
创建
type Vertex struct {
Lat, Long float64
}
var m map[string]Vertex //未赋值,为空字典
func main() {
m = make(map[string]Vertex)//make返回指定类型的字典,并初始化,此时可以使用了
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
使用字面量初始化字典(key值不可省略)
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": {//Vertex不是接口,没有实现关系,它就是顶级类型(Top-level type)
37.42202, -122.08408,
},
}
非顶级类型不可以忽略类型
type NoiseMaker interface { MakeNoise() string }
type Person struct {}
func (p Person) MakeNoise() string {
return "Hello!"
}
type Car struct {}
func (c Car) MakeNoise() string {
return "Vroom!"
}
// We must provide NoiseMaker instances here, since
// there is no implicit way to make a NoiseMaker...
noisemakers := map[string]NoiseMaker{
"alice": Person{}, //Person实现基于NoiseMaker,有实现关系
"honda": Car{},
}
操作字典 (增删改查)
m[key] = elem //insert or update
elem = m[key] //Query
delete(m,key)
//ok变量表示key是否存在
//不存在,elem为该类元素的初始值
elem, ok = m[key]//Query
函数类型
传递给变量
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))
闭包
内部函数可以访问,并维持外部函数的变量
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
方法和接口
方法
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {//v是接收器,类似其他语言的this;
//方法的接收器不能跨包引用
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
v := Vertex{3, 4}
fmt.Println(v.Abs())
方法与函数
func Abs(v Vertex) float64 { //将接收器改成第一个函数入参即可变成函数
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
为基本类型写方法
type MyFloat float64
func (f MyFloat) Abs() float64 {//为基本类型写方法
if f < 0 {
return float64(-f)
}
return float64(f)
}
接收器为指针,可以改变原变量的值
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {//将*去掉就不可以改变原变量的值
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)//v.Scale(5) as (&v).Scale(5) 接收器的自动取地址(indirection) 基于类型推断
fmt.Println(v.Abs())
}
//等价的函数写法
func Scale(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
Scale(&v, 10)//函数入参的指针传值与接收器为指针时不同
//如果接收器不是指针,却使用p.Func,会自动解引用,相当于(*p).Func 基于类型推断
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// p.Abs() is interpreted as (*p).Abs().
/* 接收器会自动进行取地址(指针接收器,传入值和指针都可以)和解引用的操作(值接收器,传入指针和值都可以)*/
接收器的选择:指针 or 值?
- 需要修改原始值吗?
- 需要避免值拷贝吗?
- 值接收器和指针接收器不可同时存在
接口
定义
一组方法
type Abser interface {
Abs() float64
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat implements Abser
//如果指的接收器是指针,a赋值时也得是指针
a = &v // a *Vertex implements Abser
}
定义与实现是分离的
定义与实现是分离的,而且实现可以跨package。
type I interface {
M()
}
type T struct {
S string
}
// This method means type T implements the interface I,
// but we don't need to explicitly declare that it does so.
func (t T) M() {
fmt.Println(t.S)
}
func main() {
var i I = T{"hello"}
i.M()
}
接口值
接口可以当做是(value,type)的元组,在接口上调用方法与在底层值上调用效果一样。
打印时,使用%v打印值,%T打印类型
package main
import (
"fmt"
"math"
)
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
fmt.Println(t.S)
}
type F float64
func (f F) M() {
fmt.Println(f)
}
func main() {
var i I
i = &T{"Hello"}
describe(i)
i.M()
i = F(math.Pi)
describe(i)
i.M()
}
func describe(i I) { //重点
fmt.Printf("(%v, %T)\n", i, i)
}
底层为nil的情况
接收器就是nil
package main
import "fmt"
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
if t == nil {
fmt.Println("<nil>")
return
}
fmt.Println(t.S)
}
func main() {
var i I
var t *T
i = t
describe(i)//(<nil>, *main.T)
i.M() //<nil>
i = &T{"hello"}
describe(i)//(&{hello}, *main.T)
i.M()//hello
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
接口本身为nil
调用接口的方法会发生runtime error,以为<value,type>中, type也是nil, go 不知道应该调用哪个方法。
package main
import "fmt"
type I interface {
M()
}
func main() {
var i I
describe(i)//(<nil>,<nil>)
i.M() //这里会发生Error
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
空接口
可以存放任意类型的值。 可以存放未知类型值。
fmt.Print有多个可以存放任意值的参数
package main
import "fmt"
func main() {
var i interface{}
describe(i)//(<nil>, <nil>)
i = 42
describe(i)//(42, int)
i = "hello"
describe(i)//(hello, string)
}
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
类型假定
package main
import "fmt"
func main() {
var i interface{} = "hello"
s := i.(string)
fmt.Println(s)
s, ok := i.(string)//第二个参数,表示假定是否正确
fmt.Println(s, ok)
f, ok := i.(float64)
fmt.Println(f, ok)
f = i.(float64) // panic , 如果忽略第二个参数,假定失败,就会panic
fmt.Println(f)
}
type switch
package main
import "fmt"
func do(i interface{}) {
switch v := i.(type) { //type是一个关键字
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default: //Default时, 这个类型和i的实际类型(type)一定相同 <value,type>
fmt.Printf("I don't know about type %T!\n", v)
}
}
func main() {
do(21)
do("hello")
do(true)
}
Reader 接口
接口定义
func (T) Read(b []byte) (n int, err error)//将输入参数的数组填满;
//第一个返回值为实际读取的字节个数
//如果读完,第二个返回值为EOF
package main
import (
"fmt"
"io"
"strings"
)
func main() {
r := strings.NewReader("Hello, Reader!")
b := make([]byte, 8)
for {
n, err := r.Read(b)
fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
fmt.Printf("b[:n] = %q\n", b[:n])
if err == io.EOF {
break
}
}
}
/*
n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""
*/
image 接口
package image
type Image interface {
ColorModel() color.Model //这是一个接口
Bounds() Rectangle //Rectangle ,是左上角和右下角的坐标
At(x, y int) color.Color//这是一个接口
}
package main
import (
"fmt"
"image"
)
func main() {
m := image.NewRGBA(image.Rect(0, 0, 100, 100))
fmt.Println(m.Bounds())//(0,0)-(100,100)
fmt.Println(m.At(0, 0).RGBA())//0 0 0 0
}
结构字符串化接口
(类似其他语言中,class中定义一个String()方法,打印为字符串时,会自动调用)
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z)
}
Error 接口
和String接口一样,在打印变量的值的时候自动调用。
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
func run() error {
return &MyError{
time.Now(),
"it didn't work",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}
泛型
函数参数泛型
func Index[T comparable](s []T, x T) int {//comparable 规定T,必须支持==和!=
for i, v := range s {
// v and x are type T, which has the comparable
// constraint, so we can use == here.
if v == x {
return i
}
}
return -1
}
类型定义中的泛型
type List[T any] struct {
next *List[T]
val T
}
并发
啥是Goroutine(轻量级线程,访问共享内存需要锁)
Goroutine 是一个轻量级的线程,它被Go RunTime 管理和调度。
go f(x,y,z)//开启一个goroutine,直接开始跑, 函数是在goroutine 中跑的。
goroutine 使用相同的地址空间,所以访问共享内存需要同步。 sync package 中提供了基元,但除了它以为还有其他的并发原语。
Channel
Channel 是有类型的中间人,通过它可以进行收发消息。
ch<-v //发消息给channel
v:=<-ch //接收channel的消息
Channel和slice,map一样,需要先创建才能使用
ch: = make(chan int)
默认情况下,发送和结束会阻塞直到另一端处理了消息。这使得goroutine可以在没加锁或者条件变量的情况下进行同步。
//两个goroutine 分别求和,并在最后加起来。
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c 阻塞,直到消息被接收
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c 阻塞,直到收到消息
fmt.Println(x, y, x+y)
}
带buffer的channel
ch:=make(chan int,100)
想带buffer的channel发消息不会阻塞,除非buffer 满了。
接受者仅在buffer为空时才阻塞。
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
ch <- 2 //buffer 满了,会引起阻塞
fmt.Println(<-ch)
fmt.Println(<-ch)
}
遍历buffer中的消息
发送者可以关闭channel,表示没有更多数据了。仅发送者可以关闭channel,接受者不可以关闭channel。
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c { //依次处理带缓存的buffer的消息
fmt.Println(i)
}
}
Select(处理先接收到的消息)
select 结构中,谁的消息处理了,就处理谁; 两个一起到,则随机选一个。
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c) //c 先收到10个消息
}
quit <- 0 //然后,quit 收到消息
}()
fibonacci(c, quit)
}
default 分支
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default://default 分支是非阻塞的
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
Sync
Channel可以用于goroutine直接的通信。
但是,如果不需要通信呢? 或许我仅仅是为了确保仅仅一个协程可以访问变量来避免冲突呢?
使用互斥锁。
Lock
//需要加锁的代码段
Unlock
Lock
//需要加锁的代码段
defer Unlock //使用defer 确保函数结束时释放锁
package main
import (
"fmt"
"sync"
"time"
)
// SafeCounter is safe to use concurrently.
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
c.v[key]++
c.mu.Unlock()
}
// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
defer c.mu.Unlock()
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}