欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

学习golang

程序员文章站 2022-06-20 14:22:11
...

参考文档:

http://www.runoob.com/go/go-variables.html


go 语言学习历程: http://blog.csdn.net/hittata/article/details/42387297

go语言实战笔记:http://www.flysnow.org/2017/03/04/go-in-action-go-package.html

切片、接口、并行和并发、通道、Context

go中的make内建函数

内建函数 make 用来为 slice,map 或 chan 类型分配内存和初始化一个对象(注意:只能用在这三种类型上)

go结构体使用

实例化结构体两种常用形式:
1:用 new(T) 实例化。
2:用 &T{} 实例化。(推荐)

func NewPoint(x int, y int) *Point {
    if x < 0 || y < 0 {
        return nil
    }
    return &Point{x, y}
}

func Test_demo2(t *testing.T) {
    p1 := NewPoint(-1, 2)
    fmt.Println(p1) //nil

    p2 := NewPoint(2, 2)
    fmt.Println(p2) //&{2 2}
}



debug 在Gogland编辑器中和idea中java的debug一样,F8下一步,F7跳入函数中

运算与 c ++ 一模一样


切片容量必须>=长度,我们是不能创建长度大于容量的切片的。

go语言实战笔记(五章)

package main

import (
	"fmt"
)

// 切片包含三个对象:①指向底层数组的指针,②切片的长度 len() -> 就是切片对象下面的index,③切片的容量 cap() 底层数组的长度
// 切片操作其实就是底层数组的操作,只要切片容量不变,那么切片指向的底层数组就不变。如果切片容量变了,说明切片指向的底层数组变了。
// 数组的长度是不会改变的,只不过切片的容量变了,代表着新增了一个长度为切片新容量的数组(容量一变,就新增一个底层数组,之前的数组等待回收)。
// 定义数组和定义切片的关键区分在于,有没有设的[]中的值

func main() {
	// 定义切片
	slice :=[]int {1,2}
	fmt.Println("slice对应的是切片")
	fmt.Println(slice)
	// 定义数组
	arr := [2]int{1,2}
	// 定义切片
	slice0 := arr[0:1]
	// 打印数组和切片
	fmt.Println("打印数组和切片")
	fmt.Println(arr)
	fmt.Println(slice0)
	// 切片的cap容量不改变,改变切片的len长度
	slice0 = append(slice0, 22)
	fmt.Println("切片的cap容量不改变,改变切片的len长度。解析:切片的容量不变,那么它指向的底层数组的地址就不变")
	fmt.Println(arr)
	fmt.Println(slice0)
	// 再定义一个切片
	slice1 := arr[:]
	// 切片的slice2的容量由2变成了3
	slice2 :=append(slice1,3)
	fmt.Println("切片的cap容量变量,切片的len长度也变量。解析:切片的容量变,那么它指向的底层数组的地址就变,不指向之前的那个底层数组了,重新指向一个新的底层数组")
	fmt.Println(arr)
	fmt.Println(slice1)
	fmt.Println(slice2)
	slice2[0]=111
	fmt.Println(arr)
	fmt.Println(slice1)
	fmt.Println(slice2)
}

运行结果;

学习golang


接口

包通过下面这个被编译器强制执行的规则来决定是否将自身的代码对象暴露给外部文件:

可见性规则

当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的 代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。

(大写字母可以使用任何 Unicode 编码的字符,比如希腊文,不仅仅是 ASCII 码中的大写字母)。

因此,在导入一个外部包后,能够且只能够访问该包中导出的对象。

假设在包 pack1 中我们有一个变量或函数叫做 Thing(以 T 开头,所以它能够被导出),那么在当前包中导入 pack1 包,Thing 就可以像面向对象语言那样使用点标记来调用:pack1.Thing(pack1 在这里是不可以省略的)。

因此包也可以作为命名空间使用,帮助避免命名冲突(名称冲突):两个包中的同名变量的区别在于他们的包名,例如 pack1.Thing 和 pack2.Thing

你可以通过使用包的别名来解决包名之间的名称冲突,或者说根据你的个人喜好对包名进行重新设置,如:import fm "fmt"。下面的代码展示了如何使用包的别名:

示例 4.2 alias.go

package main

import fm "fmt" // alias3

func main() {
   fm.Println("hello, world")
}

注意事项

如果你导入了一个包却没有使用它,则会在构建程序时引发错误,如 imported and not used: os,这正是遵循了 Go 的格言:“没有不必要的代码!“。

包的分级声明和初始化

你可以在使用 import 导入包之后定义或声明 0 个或多个常量(const)、变量(var)和类型(type),这些对象的作用域都是全局的(在本包范围内),所以可以被本包中所有的函数调用(如 gotemplate.go 源文件中的 c 和 v),然后声明一个或多个函数(func)。


包的导入代码:样例

学习golang

dog.go

package core

type Dog interface {
	Eat()
}

cat.go

package core

import "fmt"

type Cat interface {
	Eat()
	Gg()
}

func Cry() bool{
	fmt.Println("哭了")
	return true
}
test02.go

package main

import (
	"fmt"
	"./core" // "goTest/core" 这两种导入方式二选一
)


type animal struct {
	name string
}

func (a animal) Eat(){
	fmt.Println(a.name + " eat something")
}

func (a animal) Gg(){
	fmt.Println(a.name + " gg")
}

func main(){
	var a animal
	a.name = "sff"
	var c core.Cat= a
	c.Eat()
	c.Gg()
	core.Cry()

	var d core.Dog = a
	d.Eat()
}
test02运行结果:

学习golang


并发和并行

go语言实战笔记(十二、十三章)

package main
import (
	"fmt"
	"runtime"
	"sync"
)
var (
	count int32
	wg    sync.WaitGroup
	mutex sync.Mutex  // 类似于java的所对象
)
func main() {
	runtime.GOMAXPROCS(runtime.NumCPU()) // 设置可调用的核数
	wg.Add(2) // 这里要用wg对象,Add(2)要等两个wg.Done()执行到才能执行到wg.wait()
	go incCount() // 每一个go func() 都是创建一个goroutine来执行,goroutine是由单独的线程执行的。
	go incCount()
	wg.Wait()  // 这里要用wg对象,可以让程序堵塞,直到出现执行wg.Done().还有如果主线程执行完了,goroutine还没执行完程序会报错的
	fmt.Println(count)
}
func incCount() {
	defer wg.Done()
	for i := 0; i < 2; i++ {
		mutex.Lock()
		value := count
		runtime.Gosched() // 让当前goroutine暂停的意思,退回执行队列,让其他等待的goroutine运行 ,有点java的Thread.yield()的感觉
		value++
		count = value
		mutex.Unlock()
	}
}


Context

go语言实战笔记(二十章)

Context,称之为上下文非常贴切,它就是goroutine的上下文。它可以控制goroutine什么时候结束,也可以向goroutine总传值

context 传值

var key string="name"
func main() {
	ctx, cancel := context.WithCancel(context.Background())
	//附加值
	valueCtx:=context.WithValue(ctx,key,"【监控1】")
	go watch(valueCtx)
	time.Sleep(10 * time.Second)
	fmt.Println("可以了,通知监控停止")
	cancel()
	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
	time.Sleep(5 * time.Second)
}
func watch(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			//取出值
			fmt.Println(ctx.Value(key),"监控退出,停止了...")
			return
		default:
			//取出值
			fmt.Println(ctx.Value(key),"goroutine监控中...")
			time.Sleep(2 * time.Second)
		}
	}
}

context不传值

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	go watch(ctx,"【监控1】")
	go watch(ctx,"【监控2】")
	go watch(ctx,"【监控3】")
	time.Sleep(10 * time.Second)
	fmt.Println("可以了,通知监控停止")
	cancel()
	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
	time.Sleep(5 * time.Second)
}
func watch(ctx context.Context, name string) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println(name,"监控退出,停止了...")
			return
		default:
			fmt.Println(name,"goroutine监控中...")
			time.Sleep(2 * time.Second)
		}
	}
}




学习心得:

① 局部变量声明后必须要使用,不然会报错:a declared and not used

② 可以使用空白标识符 _ 来抛弃声明出来的变量值

空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。

_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。

并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:val, err = Func1(var1)。

③ 移位(位运算)

i=1<<0,j=3<<1(<<表示左移的意思),即:i=1,j=6

1为:0000 0001 左移0位:值不变

3为:0000 0011 左移1位:值为:0000 0110

④defer 是函数最后调用的,类似于java的finally,一个函数中可以有多个defer,他们串行执行

    并发和并行样例中有defer的调用





切片、接口、并行和并发、通道



func mapFunc(mapSon map[string] string){
	// mapSOn 是一个map对象
}

func chanFUnc(input  chan <- int, output <-chan int, io chan int) {
	// input 是单向输入通道
	// output 是单向输出通道
	// io 是双向输入输出通道
}

func arrayFunc(array [5]int, slice []int){
	// array 是数组
	// slice 是切片
}


相关标签: golang