Go 语言入门教程

Hello,World

package main

import "fmt"
func main() {
	fmt.Println("Hello, 世界")
}

Basic

Packages, variables,and function

Packages (main包作为程序入口点,代码中用路径的最后一个单词表示该包,圆括号组合多个导入,导出名首字母大写)

  1. Programs start running in package main.
  2. 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 statement package 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 (变量类型写在变量名后面,输入参数的简写规则,可以有多个输出参数,命名返回值)

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"))
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李来群

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值