Golang的学习

329 阅读13分钟

printf输出格式

%d 数字

%s 字符串

%x (X) , %p 地址

%T 值类型

studygolang.com/articles/26…

变量赋值

// Var project main.go
package main

import (
	"fmt"
)

func main() {

	//声明并初始化一个变量
	var m int = 10
	fmt.Printf("定义的变量值为:%d \n", m)

	//声明初始化多个变量
	var i, j, k = 1, 2, 3
	fmt.Printf("定义的变量值为:%d、%d、%d \n", i, j, k)

	//声明时不指明类型,通过初始化值来推导
	var b = true //bool型
	if b == true {
		fmt.Printf("bool值为真.\n")
	}

	//一种简单的方式 等价于 var str string = "Hello"
	str := "Hello"
	fmt.Printf("定义的变量值为:%s \n", str)

	//Go的编译器对声明却未使用的变量在报错
	//使用保留字var声明变量 然后给变量赋值
	var number int
	number = 121
	fmt.Printf("定义的变量值为:%d \n", number)

	//变量定义的另一种形式 这种情况下变量的类型是由值推演出来的
	text := "hahaya"
	fmt.Printf("定义变量的值为:%s \n", text)

	//多个变量的声明(注意小括号的使用)
	var (
		no   int
		name string
	)

	no = 1
	name = "hahaya"
	fmt.Printf("学号:%d \t 姓名:%s \n", no, name)

	//多个变量声明、定义的另一种形式
	x, y := 2, "ToSmile"
	fmt.Printf("学号:%d \t 姓名:%s \n", x, y)

	//Go中有一个特殊的变量_ 任何赋给它的值将被丢弃
	_, Ret := 2, 3
	fmt.Printf("变量的值为:%d \n", Ret)
}

基本数据类型

bool 
string 
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr 
byte //alias for uint8
rune // alias for int32,represents a Unicode code point
float32 float64
complex64 complex128

类型转换

与其他主要编程语言的差异

1.Go 语言不允许隐式类型转换

2.别名和原有类型也不能进行隐式类型转换

3.不支持指针运算

4.string是值类型,其默认的初始化值为空字符串,而不是nil

类型的预定义值

1.math.MaxInt64

2.math.MaxFloat64

3.math.MaxUint32

位运算符

&^按位置清零

go语言中按位取反写法是^, 所以 a&^b 其实是 a&(^b) 利用运算符优先级省略掉括号的写法而已. 下面的测试方法可以自行验证一下. 其它语言中关键字不同写法可能是 a&(~b),

循环

for i := 0; i <= 9; i++ {
    //for loop
}

n := 0
for n <= 5 {
    n++;
    //while loop
}

for {
    //while(true) loop 
}

条件语句

1.condition必须为布尔值

2.支持变量赋值

if var declaration; condition{
    //code
}

//example
if a := 1 == 1; a{
    //****
}
if v,err := fun(); err == nil{
    //****
}


//switch
1.条件表达式不限制为常量或者整数;
2.单个case中,可以出现多个结果选项,使用逗号分隔;
3.与C语言等规则相反,Go 语言不需要用break来明确退出一个case4.可以不设定switch之后的条件表达式,在此种情况下,整个switch结构与多个i...else..的逻辑作用等同

//switch ONE
switch os := runtime.GOOS; os{
    case "darwin","xx":
        fmt.Println("OS X")
    case "linux","windows":
        fmt.Println("Linux")
    default:
        fmt.Println("else things")
}

//switch TWO
switch {
    case 0 <= num && num <= 3:
        //***
    case 4 <= num && num <= 6:
        //***
    case 7 <= num && num <= 9:
        //***
}

数组

数组的声明

var a [3]int //声明并初始化为默认零值
a[0] = 1

b := [3]int{1,2,3} //声明同时初始化
c := [...]int{1,2,3,4}
d := [2][2]int{{1,2},{3,4}} //多维数组初始化

数组的截取

a[ 开始索引(包含) , 结束索引(不包含) ]
//example
a := [...]int{1,2,3,4,5}
a[1:2] // 2
a[1:3] // 2,3
a[1:len(a)] // 2,3,4,5
a[1:] // 2,3,4,5
a[:3] // 1,2,3

切片slice

切片的声明与使用

var s []int
t.Log(len(s),cap(s))
s = append(s, 1)
t.Log(len(s), cap(s))

s1 := []int{1,2,3,4,5}
t.Log(len(s1), cap(s1))

s2 := make([]int,3,5)
t.Log(len(s2), cap(s2))
/*
[]type,len,cap其中1en个元素会被初始化为默认零值,未初始化元素不可以访问
*/

list := make([]*int, 3)

切片共享存储结构

func TestShareMemory(t *testing.T)  {
	year := []string{"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"}
	Q2 := year[3:6]
	t.Log(Q2, len(Q2), cap(Q2))
	summer := year[5:8]
	t.Log(summer, len(summer), cap(summer))
	summer[0] = "unknown"
	t.Log(Q2)
}

切片不能进行比较

func TestCompare(t *testing.T)  {
	a := []int{1,2,3,4}
	b := []int{1,2,3,4}
	if a == b {
		//ERROR!!
	}
}

invalid operation: a == b (slice can only be compared to nil)

Map

Map的声明

注意!不能使用cap(map)

m := map[string]int{"one": 1, "two": 2, "three": 3}
t.Log(m["two"])
t.Logf("len m = %d", len(m))
	
m1 := map[string]int{}
m1["one"] = 1
t.Logf("len m1 = %d",len(m1))
	
m2 := make(map[string]int,10)//不初始化len
t.Logf("len m2 = %d",len(m2))

Map的元素访问及遍历

访问map中不存在的key时,会返回value的初始值。

因此一般需要先判断key是否存在,采用以下代码来判断。

func TestAccessNotExistingKey(t *testing.T)  {
	m1 := map[int]int{}
	t.Log(m1[1])
	m1[2] = 0
	t.Log(m1[2])
	m1[3] = 0
	if v,ok := m1[3]; ok {
		t.Logf("key 3 is exist,value is %d",v)
	}else {
		t.Log("key 3 is not exist")
	}
}

func TestMap(t *testing.T)  {
	m := map[string]int{"one": 1, "two": 2, "three": 3}
	t.Log(m["two"])
	t.Logf("len m = %d", len(m))

	m1 := map[string]int{}
	m1["one"] = 1
	t.Logf("len m1 = %d",len(m1))

	m2 := make(map[string]int,10)//不初始化len
	t.Logf("len m2 = %d",len(m2))

	for k, v := range m {
		t.Log(k,v)
	}
}

将value值置为方法

func TestFactory(t *testing.T)  {
	m := map[int]func(op int)int{}
	m[1] = func(op int) int {return op}
	m[2] = func(op int) int {return op*op}
	m[3] = func(op int) int {return op*op*op}
	for i := 0; i < 3; i++ {
		t.Log(m[i+1](2))
	}
}

实现Set

Go的内置集合中没有Set实现,可以map[type]bool

1.元素的唯一性

2.基本操作

1)添加元素

2)判断元素是否存在

3)删除元素

4)元素个数
func TestSet(t *testing.T)  {
	set := map[int]bool{}
	set[1] = true
	n := 1
	if set[n] {
		t.Logf("%d is exist",n)
	}else {
		t.Logf("%d is not exist",n)
	}
	delete(set,1)
	if set[n] {
		t.Logf("%d is exist",n)
	}else {
		t.Logf("%d is not exist",n)
	}
}

字符串

与其他主要编程语言的差异

1.string是数据类型,不是引用或指针类型

2.string 是只读的byte slice,len函数可以它所包含的byte数

3.string的byte数组可以存放任何数据

strings包

//Split方法,Join方法
func TestStrfn(t *testing.T)  {
	s := "A,B,C"
	parts := strings.Split(s,",")
	for _, part := range parts {
		t.Log(part)
	}
	t.Log(strings.Join(parts,"-"))
}

strconv包

//数字与字符串之间的转换
func TestStrconv(t *testing.T)  {
	s := strconv.Itoa(10)
	t.Log("str" + s)
	if i,err := strconv.Atoi("10");err == nil {
		t.Log(10+i)
	}
}

函数

与其他主要编程语言的差异

1.可以有多个返回值

2.所有参数都是值传递:slice,map,channel会有传引用的错觉

3.函数可以作为变量的值

4.函数可以作为参数和返回值

将函数作为参数或返回值的写法

//如同装饰模式,以下代码实现了计算运行某一特定函数所需的时间
func TestFun(t *testing.T) {
	res := timeSpent(slowFun)
	t.Log(res(10))
}

func slowFun(op int) int {
	time.Sleep(time.Second*2)
	return op
}

func timeSpent(inner func(op int) int) func(op int) int{
	return func(n int) int {
		start := time.Now()
		ret := inner(n)

		fmt.Println("time spent:",time.Since(start).Seconds())
		return ret
	}
}

可变参数

func TestPar(t *testing.T) {
	t.Log(sum(1,2,3,4,5,6))
}
func sum(ops ... int) int {
	s := 0
	for _, op := range ops {
		s += op
	}
	return s
}

defer 延迟执行

panic中断程序。

func TestDefer(t *testing.T)  {
	defer func() {
		t.Log("defer part")
	}()
	t.Log("2")
	t.Log("3")
	panic("error")
}

行为的定义和实现

实例创建及初始化

type Employee struct {
	name string
	age int
	class int
}

func TestType(t *testing.T) {
	em := Employee{
		name:  "songzhiyan",
		age:   18,
		class: 7,
	}
	em1 := &Employee{
		name:  "122131",
		age:   0,
		class: 0,
	}
	em2 := new(Employee)
	em2.name = ""
	t.Log(em,em1,em2)

	t.Logf("em is %T",em)
	t.Logf("em2 is %T",em2)
}

行为的定义

type Employee struct {
	name string
	age int
	class int
}

func (e *Employee) String() string  {
	return fmt.Sprintf("\ne's name is %s  age is %d  class is %d\n",e.name,e.age,e.class)
}

func TestMethod(t *testing.T) {
	em := Employee{
		name:  "SongZhiYan",
		age:   18,
		class: 7,
	}
	t.Log(em.String())
}

接口的定义与实现

DUCK TYPE

特点

1.接口为非入侵性,实现不依赖于接口定义

2.所以接口的定义可以包含在接口使用者包内

type Programmer interface {
	Hello() string
}
type GoLearner struct {
	
}

func (p *GoLearner)Hello() string {
	return fmt.Sprint("hello world")
}

func TestInterface(t *testing.T) {
	var p Programmer
	p = new(GoLearner)
	t.Log(p.Hello())
}

接口变量

自定义类型

type IntConv func(op int) int

func console(inner IntConv) IntConv {
	return func(n int) int {
		start := time.Now()
		ret := inner(n)
		fmt.Println("time spent: ",time.Since(start).Seconds())
		return ret
	}
}

匿名嵌套

、、******

不一样的接口类型,一样的多态

需要注意的点

package interfaceTry

import (
	"fmt"
	"testing"
)

type Programmer interface {
	Hello() string
}
type GoLearner struct {}
type JavaLearner struct {}

func (p *GoLearner)Hello() string {
	return "fmt.Sprint(\"hello world\")"
}
func (p *JavaLearner)Hello() string {
	return "System.out.println(\"hello world\")"
}

func TestInterface(t *testing.T) {
	g := new(GoLearner)
	********************
	//注意!!!此处会出错!!!
	//因为此时方法console(p Programmer)的Programmer是一个interface,所以只能对应指针类型的实例!!
	//改成j := new(JavaLearner)或者j := &JavaLearner{}
	j := JavaLearner{}
	********************
	console(g)
	console(j)
}
func console(p Programmer) {
	fmt.Printf("%T %v\n",p,p.Hello())
}

空接口与断言

1.空接口可以表示任何类型

2.通过断言来将空接口转换为制定类型v,ok:=p.(int)//ok=true 时为转换成功

3.断言:

value, ok = element.(T)
//这里value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型。

//如果element里面确实存储了T类型的数值,那么ok返回true,否则返回false。

4.例子

func DoSomething(p interface{})  {
	if i,ok := p.(int);ok{
		fmt.Println("Integer : ",i)
	}
	if s,ok := p.(string);ok {
		fmt.Println("String : ",s)
	}
	switch p.(type) {
	case string:
		fmt.Println("string")
	case int:
		fmt.Println("int")
	case bool:
		fmt.Println("bool")
	}

}

断言学习注意 :studygolang.com/articles/11…

补充:golang 中的 type switch 类型判断

a可能是任意类型

a.(某个类型) 返回两个值 inst 和 ok ,ok代表是否是这个类型,Ok如果是 inst 就是转换后的 类型

a.(type) type是关键字 结合switch case使用

TypeA(a) 是强制转换

func (a interface{}){
    //第一种
    if inst,ok:=a.(TypeA);ok{
        inst.MethodA()
    }  
    
    //第二种
    switch inst:=a.(type){
        case TypeA:
            inst.MethodA()
        default:
            fmt.Println("unknow")
        }    
    }

Go接口最佳实践

1.倾向于使用小的接口定义,很多接口只包含一个方法

type A interface {
	Hello()
}
type B interface {
	World()
}

2.较大的接口定义,可以有多个小接口定义组合而成

type C interface {
	A
	B
}

3.只依赖于必要功能的最小接口

*****

Go的错误处理

1.没有异常机制

2.error类型实现了error接口

type error interface{
    Error() string
}

3.可以通过errors.new来快速创建错误实例

errors.New("n must be in the range [0,1]")

panic

  1. panic用于不可以恢复的错误
  2. panic退出前会执行defer指定的内容

panic vs os.exit

  1. os.exit退出时不会调用defer指定的函数
  2. os.exit退出时不输出当前调用栈信息

recover

defer func(){
    if err := recover(); err != nil{
        //恢复错误
        log.Error("recovered panic",err)
    }
}

package 包

  1. 基本复用模块单元(以首字母大写来表明可被包外代码访问
  2. 代码的package可以和所在的目录不一致
  3. 同一目录里的Go代码的package要保持一致

init方法

  1. 在main 被执行前,所有依赖的package的init 方法都会被执行
  2. 不同包的init函数按照包导入的依赖关系决定执行顺序
  3. 每个包可以有多个init函数
  4. 包的每个源文件也可以有多个init函数,这点比较特殊

获取远程包的实例

  1. 通过go get来获取远程依赖

    ·go get-u强制从网络更新远程依赖

  2. 注意代码在GitHub上的组织形式,以适应go get

    ·直接以代码路径开始,不要有src

package remote_package

import (
	cm "github.com/easierway/concurrent_map"
	"testing"
)

func TestRe(t *testing.T) {
	m := cm.CreateConcurrentMap(99)
	m.Set(cm.StrKey("Key"),10)
	t.Log(m.Get(cm.StrKey("Key")))
}

依赖管理

Go 未解决的依赖问题

  1. 同一环境下,不同项目使用同一包的不同版本
  2. 无法管理对包的特定版本的依赖

协程

陷阱

studygolang.com/articles/73…

Thread vs Groutine

  1. 创建时默认的stack的大小:
    • jdk5以后java thread stack默认为1M
    • Groutine的Stack初始化大小为2k
  2. 和KSE(Kernel Space Entity)的对应关系
    • Java Thread是1:1
    • Groutine是M:N

共享内存并发机制

mutex互斥锁

func TestShare(t *testing.T) {
	var mut sync.Mutex
	counter := 0
	for i := 0; i < 5000; i++ {
		go func() {
			mut.Lock()
			counter++
			defer func() {
				mut.Unlock()
			}()
		}()
	}
	time.Sleep(1 * time.Second)
	t.Logf("counter = %d",counter)
}

RWlock读写锁

waitgroup

func TestShare(t *testing.T) {
	var mut sync.Mutex
	var wg sync.WaitGroup
	counter := 0
	for i := 0; i < 5000; i++ {

		//新加一个需要等待的协程
		wg.Add(1)

		go func() {
			defer func() {
				mut.Unlock()
			}()
			mut.Lock()
			counter++

			//一个协程已经完成了
			wg.Done()

		}()
	}
	//阻塞直到所有协程都完成,才会向下运行
	wg.Wait()
	t.Logf("counter = %d",counter)
}

CSP ---- channel!!!

channel的关闭

  • 向关闭的channel发送数据,会导致panic
  • v,ok<-ch;ok为bool值,true表示正常接受,false表示通道关闭
  • 所有的channel接收者都会在channel关闭时,立刻从阻塞等待中返回且上述ok值为false。这个广播机制常被利用,进行向多个订阅者同时发送信号。如:退出信号。
  • channel关闭之后,仍然可以从channel中读取剩余的数据,直到数据全部读取完成。
  • 判断一个channel的方式有两种:
    • 一种方式:value, ok := <- ch,ok是false,就表示已经关闭。
    • 另一种方式for value := range ch {},如果channel被关闭,会跳出循环。,
package channel_try

import (
	"fmt"
	"testing"
)

func f1(ch chan<- int)  {
	for i := 0; i < 1000; i++ {
		ch<-i
	}
	close(ch)
}
func f2(ch1 <-chan int ,ch2 chan<- int)  {
	for true {
		tmp,ok := <-ch1
		if !ok {
			break
		}
		ch2<-tmp*tmp
	}
	close(ch2)
}
func TestName(t *testing.T) {
	ch1 := make(chan int, 1000)
	ch2 := make(chan int, 2000)
	go f1(ch1)
	go f2(ch1,ch2)
	count := 0
	for ret := range ch2 {
		count++
		fmt.Println(ret)
	}
	t.Log(count)
}

多路选择与超时

需要看看!!!

任务的取消

需要看看!!!

context与任务取消

Context

  • 根 Context:通过context.Background()创建
  • 子Context:context.WithCancel(parentContext)创建
  • ctx,cancel:=context.WithCancel(context.Background())
  • 当前Context被取消时,基于他的子context都会被取消
  • 接收取消通知<-ctx.Done()
package channel_cancel

import (
	"context"
	"fmt"
	"testing"
	"time"
)

func isCanceled(ctx context.Context) bool {
	select {
	case <-ctx.Done():
		return true
	default:
		return false
	}
}
func TestCancel(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	for i := 0; i < 5; i++ {
		go func(i int, ctx context.Context) {
			for  {
				if isCanceled(ctx) {
					break
				}
				time.Sleep(time.Millisecond * 5)
			}
			fmt.Println(i,"Canceled")
		}(i,ctx)
	}
	cancel()
	time.Sleep(time.Second)
}

典型并发任务

只运行一次(单例模式)

package singleton

import (
	"fmt"
	"sync"
	"testing"
)
var once sync.Once
var obj *SingletonObj

type SingletonObj struct {
	name string
}

func TestSingle(t *testing.T) {
	for i := 0; i < 10; i++ {
		go func() {
			obj = GetSingleton()
			fmt.Println("try it")
		}()
	}
}
func GetSingleton() *SingletonObj {
	once.Do(func() {
		fmt.Println("Just Do it!")
		obj = new(SingletonObj)
	})
	return obj
}

仅对第一个完成的任做回复

缺点:导致会有很多协程一直在阻塞,浪费资源 改进:使用带缓存区的channel,即:ch := make(chan string,10)

package severalTast

import (
	"fmt"
	"testing"
	"time"
)

func TestName(t *testing.T) {
	res := doIt()
	fmt.Println(res)
}
func Task(i int) string {
	time.Sleep(5 * time.Millisecond)
	return fmt.Sprintf("The task is from %d",i)
}
func doIt() string {
	ch := make(chan string)
	for i := 0; i < 10; i++ {
		go func(i int) {
			fmt.Println(i)
			str := Task(i)
			ch<-str
		}(i)
	}
	return <-ch
}

所有任务完成

package allTaskFinished

import (
	"fmt"
	"testing"
	"time"
)

func TestName(t *testing.T) {
	res := doIt()
	println(res)
}
func doIt() string {
	numOfTask := 10
	ch := make(chan string,numOfTask)
	for i := 0; i < numOfTask; i++ {
		go func(i int) {
			fmt.Println(i)
			str := Task(i)
			ch<-str
		}(i)
	}
	res := ""
	for i := 0; i < numOfTask; i++ {
		res += <-ch + "\n"
	}
	return res
}
func Task(i int) string {
	time.Sleep(5 * time.Millisecond)
	return fmt.Sprintf("The task is from %d",i)
}

对象池

  • 使用buffered channel实现对象池
package ObjPool

import (
	"github.com/pkg/errors"
	"time"
)

type Obj struct {

}

type ObjPool struct {
	buffChan chan *Obj
}
func NewPool(num int) *ObjPool {
	pool := ObjPool{}
	pool.buffChan = make(chan *Obj,num)
	for i := 0; i < num; i++ {
		pool.buffChan <- new(Obj)
	}
	return &pool
}
func (p *ObjPool)GetObj(timeout time.Duration) (*Obj,error) {
	select {
	case ret := <-p.buffChan:
		return ret, nil
	case <-time.After(timeout):
		return nil,errors.New("time out")
	}
}
func (p *ObjPool) ReleaseObj(obj *Obj) error {
	select {
	case p.buffChan <- obj:
		return nil
	default:
		return errors.New("overflow!")
	}
}

sync.pool对象缓存

sync.Pool 对象获取

  • 尝试从私有对象获取
  • 私有对象不存在,尝试从当前Processor的共享池获取
  • 如果当前Processor共享池也是空的,那么就尝试去其他Processor的共享池获取
  • 如果所有子池都是空的,最后就用用户指定的New函数产生一个新的对象返回
  • 私有对象是协程安全的 共享池是协程不安全

sync.Pool 对象的放回

  • 如果私有对象不存在则保存为私有对象
  • 如果私有对象存在,放入当前Processor子地的共享池中

sync.Pool对象的生命周期(不能当对象池用的原因)

  • GC会清除sync.pool 缓存的对象
  • 对象的缓存有效期为下一次GC之前

sync.Pool总结

  • 适合于通过复用,降低复杂对象的创建和GC代价
  • 协程安全,会有锁的开销
  • 生命周期受GC影响,不适合于做连接池等,需自己管理生命周期的资源的池化
  • 学习网址:www.cnblogs.com/sunsky303/p…