golang的学习

12 篇文章 0 订阅

非0基础的Golang的学习

Golang的优势

  • 简单的部署方式
    • 可直接编译成机器码
    • 不依赖其他库
    • 直接运行可部署
  • 静态类型的语言
    • 编译时能检查出隐藏的大多数问题
  • 语言层面的并发
    • 天生支持
    • 充分利用多核
  • 强大的标准库
    • runtime系统调度机制
    • 高效的GC垃圾回收
    • 丰富的标准库
  • 简单易学
    • 25个关键字
    • C语言简洁基因,内嵌C语法支持
    • 面向对象特征(继承、封装、多态)
    • 跨平台性
  • “大厂”领军
    • Google、fackbook
    • Tencent、Baidu(运维)、JD

main包

go run hello.go			

go build hello.go
./hello

定义变量

var a int		 	//方法一:声明变量,默认是0
var b int = 100		//方法二:声明变量,初始化一个值
var c = 100			//方法三:省去数据类型,根据值去自动匹配数据类型
d := 100			//方法四(常用):省去var关键字,自动匹配数据类型

var {
    v1 = 10
    v2 string = "abcd"
    v3 = true
}

Tips

方法四只能在函数体中声明使用(局部变量),全局变量不能声明和使用。

常量中使用iota

在const( )中添加一个关键字iota,每行的iota都加1,第一行的iota默认值是0。(强调每行

const a = 100

//多常量定义
const (
	b = 100
    c = 100
)
func main()  {
	const (
		a = iota	//iota = 0
		b			//iota = 1
		c
		d
	)
	fmt.Println("a =",a)	//0
	fmt.Println("b =",b)	//1
	fmt.Println("c =",c)	//2
	fmt.Println("d =",d)	//3

	const (
		e = 10 * iota	// iota = 0, e= 0
		f				// iota = 1, f = 10
		g
	)
	fmt.Println("e =",e)	//0
	fmt.Println("f =",f)	//10
	fmt.Println("g =",g)	//20

	const (
		h , i = iota +1 , iota +2	//iota = 0, h = iota+1=0+1=1, i=iota+2=0+2=2
		j , k						//iota = 1, j = iota+1=1+1=2,l=iota+2=1+2=3
	)
	fmt.Println("h =",h)	//1
	fmt.Println("i =",i)	//2
	fmt.Println("j =",j)	//2
	fmt.Println("k =",k)	//3

}

多返回值

package main

import "fmt"

//单返回值((返回int类型)
func f1(s string, i int) int  {
	fmt.Println("s =", s)
	fmt.Println("i =", i)
	//一个返回值
	return 0;
}

//多返回值(匿名)
func f2(s string, i int) (int, int)  {
	fmt.Println("s =", s)
	fmt.Println("i =", i)
	//一个返回值
	return 0, 1;
}

//多返回值(具名)
func f3(s string, i int) (r1 int, r2 int)  {
	//两个返回值
	r1 = 0
	r2 = 1
	return;
}

//多返回值(同类型,且具名)
func f4(s string, i int) (r1,r2 int)  {
	fmt.Println("r1 =", r1)
	fmt.Println("r2 =", r2)
	//两个返回值
	r1 = 0
	r2 = 1
	return;
}


func main() {
	s := "test"
	i := 100
	r1 := f1(s, i)
	fmt.Println("r1 =",r1)
	r2, r3 := f2(s, i)
	fmt.Println("r2 =",r2, "r3 =",r3)
	f3(s,i)
	f4(s,i)
}

Tips

具名多返回值实质上是局部变量(有初始值)

init函数

init函数在import导包中的调用流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2sEK761V-1635763248563)(C:\Users\wqk\AppData\Roaming\Typora\typora-user-images\image-20211012053532973.png)]

包中可以使用init函数来进行包的一些初始化工作

import导包

import (
	"5-init/lib1"  			//使用原包名导入,调用方法为lib1.Api()
	_ "5-init/lib2"			//匿名导入包,即需要调用该包中的init(),但是不使用其中的接口
	. "5-init/lib3"			//将当前包导入并且和本包合并,直接使用Api()调用其中接口。(不推荐,可能出现命名重复	)
	mylib "5-init/lib4"		//自定义导入的包名。使用mylib.Api()调用
)

Tips

Golang中,如果import导入了包但没有使用会报错,所以导入的包必须使用,或者使用_进行匿名导入(只调用包中的init函数,不使用其中其他函数)

指针

&a			//传递a变量的地址(实参)
*p			//存储a变量的地址(形参)	
*p = 10		//通过修改a变量的地址的值的方式来修改a的值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xuj7CAY8-1635763248566)(C:\Users\wqk\AppData\Roaming\Typora\typora-user-images\image-20211012062746855.png)]

defer语句

defer类似于C++的析构函数和Java的finalize()函数类似

多次调用defer语句的执行顺序是类似栈

package main

import "fmt"

func f1()  {
	fmt.Println("f1 called...")
}

func f2()  {
	fmt.Println("f2 called...")
}

func f3()  {
	fmt.Println("f3 called...")
}


func main() {
	defer f1()
	defer f2()
	defer f3()
	
	/*
	f3 called...
	f2 called...
	f1 called...
	 */
}

defer在return返回值返回后执行

package main

import "fmt"

func deferFunc() int {
	fmt.Println("defer func called...")
	return 0
}

func returnFunc() int {
	fmt.Println("return func called...")
	return 0
}

func test() int {
	defer deferFunc()
	return returnFunc()
}

func main() {
	test()
	
	/*
	return func called...
	defer func called...
	 */
}

数组及其遍历

数组的定义方式

var myArray1 [10]int			//数组定义方式一:固定长度的数组(没有赋值,但有默认值0)
myArray2 := [4]int{1,2,3,4}		//数组定义方式二:固定长度数组并赋值
myArray3 := []int{1,2,3,4}		//数组定义方式三:动态数组(没有固定长度)

获取数组长度

//len()返回数组长度
length := len(myArray1)
fmt.Println("myArray1 len =",length)

数组遍历

// 数组遍历方式一:fori遍历
for i := 0; i < length; i++ {
    fmt.Println(myArray1[i])
}

// 数组遍历方式二:for range,可以返回index(下标)和value(元素)
for index, value := range myArray1 {
    fmt.Println("index =",index, "value =",value)
}

// 数组遍历方式三:for range,匿名返回index或者value
for _, value := range myArray1 {
    fmt.Println("value =",value)
}
for index, _ := range myArray1 {
    fmt.Println("index =",index)
}

固定数组传参

//传递固定长度数组必须严格匹配数组类型以及数组长度的
func printArray(array [4]int)  {
	//值拷贝
	for i := 0; i < len(array); i++ {
		fmt.Println(array[i])
	}
}

func main() {
	myArray2 := [4]int{1,2,3,4}
	printArray(myArray2)		//只能传递长度为4的数组
}

动态数组传参

//传递动态数组不需要匹配数组长度
func printSlice(slice []int)  {
	//引用拷贝
	fmt.Println("printSlice called...")
	for i := 0; i < len(slice); i++ {
		fmt.Println(slice[i])
	}
}

func main() {
	myArray3 := []int{1,2,3,4}
	printSlice(myArray3)		//传递动态数组
}

动态数组slice

声明方式

	//声明一个切片且初始化,默认值是1,2,3
	slice1 := []int{1,2,3}
	fmt.Println("=====================")
	for index, value := range slice1 {
		fmt.Println("index =",index,"value =",value)
	}

	//声明一个切片但没有初始化且没有初始空间
	var slice2 []int
	//手动利用make来分配空间,元素有默认值
	slice2 = make([]int,5)
	fmt.Println("=====================")
	for index, value := range slice2 {
		fmt.Println("index =",index,"value =",value)
	}

	//声明一个切片同时给切片分配空间
	var slice3 []int = make([]int, 5)
	fmt.Println("=====================")
	for index, value := range slice3 {
		fmt.Println("index =",index,"value =",value)
	}

	//声明一个切片,同时分配空间,自动匹配类型
	slice4 := make([]int,5)
	fmt.Println("=====================")
	for index, value := range slice4 {
		fmt.Println("index =",index,"value =",value)
	}

slice追加、截取和拷贝

slice的len和cap的概念,类似于Java的ArrayList(动态数组)的size和capacity。len代表slice中是实际元素的个数,而cap代表这个slice中能够存储的元素大小,同时也支持动态扩容,在len达到cap时扩容,可以在初始化slice为其指定cap,否则默认的cap等于len

初始化slice时没有指定cap

	//初始化slice1,没有指定cap(默认cap与len相等)
	var slice1 []int = make([]int,3)
	fmt.Printf("len = %d, cap = %d, slice = %v\n",len(slice1),cap(slice1),slice1)
	/*
	output
	len = 3, cap = 3, slice = [0 0 0]
	 */

初始化slice时指定cap

    //初始化slice2,指定cap为5
    var slice2 []int = make([]int,3,5)
    fmt.Printf("len = %d, cap = %d, slice = %v\n",len(slice2),cap(slice2),slice2)
    /*
    output
    len = 3, cap = 5, slice = [0 0 0]
     */

向slice中添加元素(append)

	slice2 = append(slice2,5)

向元素已满的slice中添加元素(会自动扩容)

	//向已满(len=cap)的slice中添加元素,slice会自动扩容(按照一定规则)
	slice1 = append(slice1, 5)
	fmt.Printf("len = %d, cap = %d, slice = %v\n",len(slice1),cap(slice1),slice1)
	/*
	output
	len = 4, cap = 6, slice = [0 0 0 5] (可以看到cap变大了)
	 */

从slice中截取元素(:)

	slice3 := []int{0,1,2,3}
	//左闭右开[0,2)即截取0到1的元素
	fmt.Printf("slice = %v\n",slice3[0:2])		//slice = [0 1]
	//截取0到2的元素
	fmt.Printf("slice = %v\n",slice3[:2])		//slice = [0 1]
	//截取0到末尾的元素
	fmt.Printf("slice = %v\n",slice3[0:])		//slice = [0 1 2 3]
	//截取全部元素
	fmt.Printf("slice = %v\n",slice3[:])		//slice = [0 1 2 3]

截取后的切片指向同一元素(浅拷贝)

	slice := slice3[:3]
	slice[0] = 100
	fmt.Printf("slice = %v\n",slice)			//slice = [100 1 2 3]
	fmt.Printf("slice3 = %v\n",slice3)		//slice = [100 1 2 3]

TIps

截取后的切片slice,修改其slice[0],发现slice3[0]也被修改了,说明修改前的slice3和修改后的slice底层指向同一数组

复制copy数组(深拷贝)

	slice := make([]int,len(slice3))
	//copy slice3到slice中
	copy(slice,slice3)
	fmt.Printf("slice = %v\n",slice)			//slice = [0 1 2 3]

map

map的格式是 map[type of key]type of value,如map[string]string即key为string类型,value为string类型的map集合

声明

	//方式一:声明空map,在使用前分配空间后再赋值
	var map1 map[int]string
	map1 = make(map[int]string,10)
	//map1 = make(map[int]string)	//两种方式都可以
	map1[1] = "Java"
	map1[2] = "Golang"
	map1[3] = "JavaScript"
	fmt.Println(map1)

	//方式二:声明map的时候同时make分配空间,后再赋值
	var map2 = make(map[int]string)
	map2[1] = "Java"
	map2[2] = "Golang"
	map2[3] = "JavaScript"
	fmt.Println(map2)

	//方式三:声明map时直接赋值
	var map3 = map[int]string{
		1: "Java",
		2: "Golang",
		3: "JavaScript",
	}
	fmt.Println(map3)

TIps

map声明完后一定要使用make()开辟内存!一定要使用make()开辟内存!一定要使用make()开辟内存!

遍历

	myMap := map[string]string {
		"China": "Beijing",
		"USA": "DC",
		"England": "London",
	}
	//遍历key和value
	for key, value := range myMap {
		fmt.Println("key =",key," value =",value)
	}

删除

	//删除myMap的"USA"键值对
	delete(myMap, "USA")

修改

	//修改"England"的值为Paris
	myMap["England"] = "Paris"

引用传递

func printMap(myMap map[string]string)  {
	//myMap是引用传递
	for key, value := range myMap {
		fmt.Println(key,"->",value)
	}
}

struct结构体

与类类似,Go使用结构体来实现对象

定义结构体

//定义Student这种结构体,它包含Name、Age、Score等属性
type Student struct {
	Name string
	Age int
	Score int
}

结构体传参

type Student struct {
	Name string
	Age int
	Score int
}

func printStudent(student Student)  {
	//值传递(相当于student对象的副本,无法修改其内部属性值)
	fmt.Printf("%v\n",student)
}

func changeStudent(student *Student)  {
	//引用传递(可以修改其内部属性值)
	student.Age = 20
}

func main() {
	student := Student{
		Name: "wqk",
		Age: 18,
		Score: 50,
	}
	printStudent(student)
	changeStudent(&student)
	printStudent(student)
}

封装成类

和Java类似的,利用Struct封装了Student类,并且包装好了属性的Getter和Setter,需要注意的是,由于Setter需要修改实际对象的值,所以需要传递引用对象(指针)

type Student struct {
	name string
	Age int
	Score int
}

func (this *Student)SetName(newName string)  {
	this.Name = newName
}

func (this *Student) GetName() string {
	return this.Name
}

func (this *Student) SetAge(newAge int)  {
	this.Age = newAge
}

func (this *Student) GetAge() int {
	return this.Age
}

func (this *Student) SetScore(newScore int)  {
	this.Score = newScore
}

func (this *Student) GetScore() int {
	return this.Score
}

func (this Student) ToString()  {
	fmt.Printf("%v\n",this)
}

Tips

类名、属性名、方法名首字母大写表示对外(其他包)可以访问,否则只能在本包访问

继承

父类Vehicle的属性及其方法

//父类:交通工具
type Vehicle struct {
	name 	string	//名称
	price 	int		//价格
}
func (this *Vehicle) Run()  {
	fmt.Println(this.name," is running...")
}

子类Car继承父类Vehicle

//子类:汽车
type Car struct {
	Vehicle			//继承Vehicle类的属性及其方法
	height 	int		//高度(Car类独有属性)
}
func (this *Car) Walk()  {
	fmt.Println(this.name, " is walking on the road...")
}

子类Ship继承父类Vehicle,并且重写Run方法

//子类:船
type Ship struct {
	Vehicle			//继承Vehicle类的属性及其方法
    width 	int		//宽度(Ship类独有属性)
}
func (this *Ship) Run()  {
	fmt.Println(this.name," is running in the water...")
}
func (this *Ship) Swim()  {
	fmt.Println(this.name, " is swimming on the water...")
}

定义父类和子类

func main() {
	//父类vehicle
	vehicle := Vehicle{
		name:  "vehicle",
		price: 2000,
	}
	vehicle.Run()

	//子类car
	car := Car{
		Vehicle: Vehicle{
			name: "car",
			price: 3000,
		},
		height:  10,
	}
	car.Run()
	car.Walk()

	//子类ship
	ship := Ship{
		Vehicle: Vehicle{
			name: "ship",
			price: 5000,
		},
		width: 20,
	}
	ship.Run()
	ship.Swim()
    
   /*
	output
	vehicle  is running...
	car  is running...
	car  is walking on the road...
	ship  is running in the water...
	ship  is swimming on the water...
	 */
}

另外一种定义子类的方式

	//先声明空对象,再赋值
	var ship2 Ship
	ship2.name = "ship2"
	ship2.price = 500
	ship2.width = 50
	ship2.Run()
	ship2.Swim()

多态(interface)

在Go中,子类只要实现了接口(interface)中的所有方法(包括返回值类型),那么就认为这个子类是这个接口的实现

Animal接口

//接口:动物接口
type IAnimal interface {
	Sleep()				//睡觉
	GetKind() string	//获得动物的类型
}

Catl实现类

实现了Animal接口的所有方法

//实现:猫类
type Cat struct {
	kind string
}
//实现IAnimal的Sleep()
func (this *Cat) Sleep()  {
	fmt.Println(this.kind,"is sleeping...")
}
//实现IAnimal的GetKind()
func (this *Cat) GetKind() string {
	return this.kind
}

Dog实现类

//实现:狗类
type Dog struct {
	kind string
}
func (this *Dog) Sleep()  {
	fmt.Println(this.kind,"is sleeping...")
}
func (this *Dog) GetKind() string {
	return this.kind
}

接口的使用

func main() {
	dog := Dog{kind: "dog"}	//Dog类
	cat := Cat{kind: "cat"}	//Cat类

	var animal IAnimal		//接口指针

	animal = IAnimal(&dog)	//传入dog对象的地址
	animal.Sleep()
	fmt.Println("kind =",animal.GetKind())

	animal = IAnimal(&cat)	//传入cat对象的地址
	animal.Sleep()
	fmt.Println("kind =",animal.GetKind())

	/*
	output
	dog is sleeping...
	kind = dog
	cat is sleeping...
	kind = cat
	 */
}

Tips

多态,即一个类有多种不同的实现方式,通过不同的使用场景来决定使用哪种实现方式。

一定要有接口,一定要有实现类。

上述例子就是有Animal类,有两种不同的实现方式(Cat和Dog)。决定使用哪一种实现方式通过传递不同的对象的地址来决定,如传递Dog对象的地址,则采用Dog的实现方式,传递Cat对象的地址,则采用Cat的实现方式。

通用万能类型(interface{})

interface{}空接口,int、string、float32、float64、struct都实现了它,可以用它引用任意的数据类型(类似Java的Object类),使用如下:

//打印传入的数据的类型以及值
func printType(arg interface{})  {
	fmt.Printf("type = %T , value = %v\n",arg,arg)
}

func main() {
	student := Student{name: "wqk"}
	printType(student)			//传入Student类
	printType("test")			//传入string类
	printType(123)				//传入int
	printType(true)				//传入bool
	/*
	output
	type = main.Student , value = {wqk 0}
	type = string , value = test
	type = int , value = 123
	type = bool , value = true
	 */
}

类型断言

判断当前传入的数据的类型(只能适用于interface{}类型)

value, ok := arg.(string)			//判断arg是否是string类型,是则ok为true,否则为false

使用场景

通常在一些需要判断其数据类型然后进行相应的操作

func printType(arg interface{})  {
	fmt.Printf("type = %T , value = %v\n",arg,arg)
	value, ok := arg.(string)
	if ok {
		//如果arg是string类型
		fmt.Println("arg is string type, value =",value)
	} else {
		//如果arg不是string类型
		fmt.Println("arg is not string type")
	}
}

pair

Go的变量中存在如下结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gUt4OxMW-1635763248567)(C:\Users\wqk\AppData\Roaming\Typora\typora-user-images\image-20211013165621673.png)]

反射(reflect)

可以动态获取未知变量的数据类型(type)和值(value)

利用reflect库里的reflect.TypeOf()reflect.Valueof()就可以获取变量的数据类型和值

获取基本类型的变量的数据类型和值

import (
	"fmt"
	"reflect"
)

func reflectNum(arg interface{})  {
	typeof := reflect.TypeOf(arg)			//获得变量的数据类型
	valueof := reflect.ValueOf(arg)			//获得变量的值
	fmt.Println("type =",typeof,"value =",valueof)
}

func main() {
	var num float64 = 3.14159
	reflectNum(num)
}

获取复杂类型的变量的数据类型和值

Student类

//Student类
type Student struct {
	Name	 string
	Age		 int
	Score	 int
}
//方法:打印Student对象
func (this *Student) ToString()  {
	fmt.Println("Student ToString is call...")
	fmt.Printf("name = %s age = %d score = %d\n",this.Name,this.Age,this.Score)
}

获取Student类对象的属性和值的集合

//反射object对象
func reflectObject(object interface{})  {
	objectType := reflect.TypeOf(object)	//获得object的类型
	objectValue := reflect.ValueOf(object)	//获得object的值
	fmt.Println("object type =",objectType, "value =",objectValue)
	/*
	output
	object type = main.Student value = {wqk 18 98}
	变量的类型和值(集合)
	 */
}

通过获取到的变量的类型和变量的值(集合)来获取具体的属性名、属性的类型、属性的值

	//objectType.NumField()是变量的类的属性个数 Student有3个属性,分别是Name、Age、Score
	for i := 0; i < objectType.NumField(); i++ {	//迭代获取field
		field := objectType.Field(i)				//field对象
		fieldName := field.Name						//field的名字
		fieldType := field.Type						//field的类型(field也有基本类型组成)
		value := objectValue.Field(i).Interface()	//field对应的value
		fmt.Printf("field = %s(%s) value = %v\n",fieldName,fieldType,value)
	}
	/*
	output
	field = Name(string) value = wqk
	field = Age(int) value = 18
	field = Score(int) value = 98
	 */

Tips

反射实质就是获取变量的类,通过类获取到变量的属性、属性的类型、属性的值以及类的方法。

结构体标签(Tag)

在结构体上定义结构体标签

type Student struct {
	Name 	string	`content:"name" doc:"姓名"`
	Age 	int		`content:"age" doc:"年龄"`
}

通过反射获取到结构体标签

func reflectTags(object interface{})  {
	typeof := reflect.TypeOf(object)	//获取到object的类
	for i := 0; i < typeof.NumField(); i++ {	//迭代field
		tagContent := typeof.Field(i).Tag.Get("content")
		tagDoc := typeof.Field(i).Tag.Get("doc")
		fmt.Println("content =",tagContent,"doc =",tagDoc)
	}
}

func main() {
	s := Student{}
	reflectTags(s)
}

结构体与JSON结合使用

定义结构体,同时定义JSON的tag

type Student struct {
	Name 	string		`json:"name"`
	Age 	int			`json:"age"`
	Score	int			`json:"score"`
	Lessons	[]string	`json:"lessons"`
}

json:"name" 即是最后转为JSON对象的属性名叫name

结构体对象转为JSON

func main() {
	student := Student{
		Name:    "test",
		Age:     18,
		Score:   98,
		Lessons: []string{
			"Chinese",
			"Math",
			"P.E",
		},
	}

	//object->json
	jsonStudent, err := json.Marshal(student)
	if err != nil {
		//转化失败
		fmt.Println("json marshal err...",err)
		return
	}
	fmt.Printf("%s\n",jsonStudent)
	/*
	output
	{"name":"test","age":18,"score":98,"lessons":["Chinese","Math","P.E"]}
	(可以看到输出的JSON对象的属性是根据tag定义的来的)
	 */
}

将JSON对象转为结构体对象

func main() {
	student := Student{
		Name:    "test",
		Age:     18,
		Score:   98,
		Lessons: []string{
			"Chinese",
			"Math",
			"P.E",
		},
	}

	//object->json
	jsonStudent, err := json.Marshal(student)
	if err != nil {
		//转化失败
		fmt.Println("json marshal err...",err)
		return
	}
	fmt.Printf("%s\n",jsonStudent)
	/*
	output
	{"name":"test","age":18,"score":98,"lessons":["Chinese","Math","P.E"]}
	 */

	//json->object
	objectStudent := Student{}
	err = json.Unmarshal(jsonStudent,&objectStudent)
	if err != nil {
		//转化失败
		fmt.Println("json unmarshal err...",err)
		return
	}
	fmt.Printf("%v\n",objectStudent)
	/*
	output
	{test 18 98 [Chinese Math P.E]}
	 */
}

Tips

在很多JSON编解码ORM映射关系当中可能就会使用到结构体标签

goroutine

创建线程体

具名函数

//子routine
func newTask()  {
	i := 0
	for true {
		fmt.Println("i =",i)
		i++
	}
}

开启goroutine(go关键字

//主routine
func main() {
	go newTask()
	time.Sleep(1 * time.Second)
}

匿名函数

同样的,在匿名函数前添加go执行匿名函数的routine

//主routine
func main() {
	//匿名函数
	go func() {
		i := 0
		for true {
			fmt.Println("i =",i)
			i++
		}
	}()
	time.Sleep(1 * time.Second)
}

退出当前goroutine

利用runtime.Goexit()退出当前goroutine

//具名函数
func newTask()  {
	i := 0
	for true {
		fmt.Println("i =",i)
		i++
		if i == 50000 {
			//退出当前goroutine
			runtime.Goexit()
		}
	}
}

即,当i为50000的时候退出当前goroutine

goroutine之间通信(channel)

多个goroutine之间利用channel进行通信,如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ufXu8s06-1635763248569)(C:\Users\wqk\AppData\Roaming\Typora\typora-user-images\image-20211013214558212.png)]

创建管道

channel := make(chan type,1)		//创建数据类型为type的channel(缓冲区为0,也可以不指定)
channel := make(chan type)			//亦可以
//如:创建int类型的channel
c := make(chan int)

写入管道

channel <- value ....			//将value写入channel管道

从管道读

<- channel						//从管道读,但不做任何操作(丢弃)
x := <- channel					//从管道读,并且赋值给num
x, ok := <- channel				//从管道读,还可以检查管道是否关闭或者是否为空

func main() {
	defer fmt.Println("main end ...")
	c := make(chan int)
	//开启子线程
	go func() {
		defer fmt.Println("goroutine end ...")
		i := 0
		for true {
			i++
			fmt.Println("i =",i)
			if i == 50 {	//当i为50000的时候写入管道
				c <- i	//将i写入c管道
			}
		}
	}()
	num := <- c	//从c管道中读出数据赋值给num
	fmt.Println("num =",num)
}

Tips

关于channel读写数据时候的阻塞同步问题可以参看@刘丹冰AceId讲的视频,https://www.bilibili.com/video/BV1gf4y1r79E?p=28,可能会有所帮助

无缓冲channel

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xKRjIEZ3-1635763248570)(C:\Users\wqk\AppData\Roaming\Typora\typora-user-images\image-20211013220227422.png)]

有缓冲channel

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JXKHFw1u-1635763248570)(C:\Users\wqk\AppData\Roaming\Typora\typora-user-images\image-20211013220410731.png)]

关闭channel

close(c)				//利用close()关闭channel

data, ok := <- c		//可以根据ok是否为true判断channel是否关闭

TIps

  1. 关闭channel后,无法再向channel发送数据
  2. 关闭channel后,仍然可以从channel读取数据

利用range操作channel

利用for循环获取管道中的数据
func main() {
	defer fmt.Println("main end ...")
	c := make(chan int,5)
	//向c中写入5个数字,写完关闭管道
	go func() {
		defer fmt.Println("goroutine end ...")
		for i := 0; i < 5; i++ {
			c <- i
		}
		close(c)
	}()
	time.Sleep(1 * time.Second)
	for true {
		//如果管道打开或者管道中还有数据则依次读数据
		data, ok := <- c
		if ok {
			fmt.Println(data)
		} else {
			break
		}

	}
}

利用range获取管道中的数据

	for data := range c {
		//从c中不断迭代数据
		fmt.Println(data)
	}

利用select操作channel

select {
    case c1 <- x:
    	//如果c1可写,则执行c1接下来的操作
    case <- c2
    	//如果c2可读,则执行c2接下的操作
}

Gopath的弊端

  1. 无版本控制概念
  2. 无法同步一致第三方版本号
  3. 无法指定当前项目引用的第三方版本号

Go modules环境变量

Go Module是语义化版本管理的依赖项的包管理工具;它解决了GOPATH存在的缺陷,最重要的是,它是Go官方出品。类似node的npm,spring的maven,都是包管理工具

使用如下命令可以看到当前Go module的配置

cmd> go env

修改环境变量

cmd> go env -w 变量名=

GO111MODULE(是否开启go modules模式)

打开Go module,将GO111MODULE置为on,如果置为off则代表继续沿用之前Gopath的工作模式(不推荐)

cmd> go env -w GO111MODULE=on

GOPROXY(项目第三方依赖的下载地址)

包下载的镜像,默认是从https://proxy.golang.org,direct(go官方)下载,可能会比较慢,推荐使用国内镜像,如阿里云、七牛云

cmd> go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,https://goproxy.cn,direct

可以利用逗号隔开,以填写多个镜像地址(依次访问)。

direct标识符的意思是:如果需要下载的包在前面的镜像中都没有,则会尝试去包的源主机尝试下载,如果仍没有,则会报错。

GOSUMDB(校验下载的第三方依赖的完整性)

校验从GOPROXY下载的包进行完整性

GONOPROXY/GONOSUMDB/GOPRIVATE

配置私有库,便于直接从私有库下载(用于公司内部)

利用Go modules初始化项目

  1. 创建go.mod

    cmd> go mod init <project_name>
    
  2. 下载依赖

    下载go.mod中指定的依赖

    cmd> go mod download
    

Golang面试题

是否是质数

利用该数n对2~ n \sqrt{n} n 从小依次进行取模,如果取模结果为0则说明能被整除,说明不是质数,否则就是质数

分解质因数

判断该数是否是质数,如果不是质数则利用该数n对2~ n \sqrt{n} n 从小依次取模,如果取模结果为0,其中这个数就是其中一个质因数。利用该数除以该质因数后再进行上述操作(即从2~ n \sqrt{n} n 从小依次取模),直到该数为质数为止。

作用域

变量的作用域是在“花括号内”

高精度计算

math.big

进程、线程、协程

一个进程有多个线程,一个线程有多个协程

Goroutine什么时候会发生阻塞

在当前Goroutine进行IO操作时:比如数据库查询、网络请求,或者等待channel中数据,还有等待回调结果(发生系统级调用)。阻塞时Goroutine的状态会发生变化,调度器会先让该Goroutine在旁边等待,在这个Goroutine的阻塞结束之前,调度器会先让其他的Goroutine执行

new和make的区别

  • new

    初始化一个指向某类型的指针,并且分配空间

  • make

    为slice、map、chan初始化并且返回其引用

channel的特性

  • 从nil channel接收数据或者给nil channel发送数据都会造成永久阻塞
  • 给一个已经关闭的channel发送数据,会引起panic
  • 从一个已经关闭的channel接受数据,如果缓冲区已经空了,返回零值

有缓存和无缓存的channel的区别

无缓存channel是同步的,有缓存channel是异步的

select关键字

  • select主要用于处理异步的I/O问题(和switch语法类似)
  • select的每一个case必须是一个IO的操作

如何判断map中是否存在key

利用map的两个返回值,第一个map的值,第二个是map的key是否存在

init函数

一个package中可以有0个或者多个init()函数,程序在编译时,先执行导入包的init函数,再执行本包的init函数。

Mutex

  • 当一个goruntine获得Mutex后,其他goruntine只有等待,除非该goruntine释放Mutex
  • RWMutex在读锁被占用时,不能写,但是可以读
  • RWMutex在写锁被占用时,既不能读也不能写,只被该goruntine独享

Go vendor

go vendor会将引用的外部包放置到当前工程的vendor目录下,编译当前工程时则会优先从当前工程的vendor目录去寻找依赖包

垃圾回收

标记清除(mark and sweep),还有标记清除算法的变种,三色标记法

反射原理

利用reflect包,获取对象的type和value,接着获取对象的具体的属性、属性值、属性的类型、type的方法等

空struct{}的用途

通常表示一个占位以达到节约资源的目的,并没有实际作用,比如在map中,通常可以利用空struct{}来占位一个value,不使用map的value,但是却要使用map

如何比较两个结构体

可以使用==DeeplyEquals()来比较两个结构相同的类型并且包含相同字段值的结构
否是质数,如果不是质数则利用该数n对2~ n \sqrt{n} n 从小依次取模,如果取模结果为0,其中这个数就是其中一个质因数。利用该数除以该质因数后再进行上述操作(即从2~ n \sqrt{n} n 从小依次取模),直到该数为质数为止。

作用域

变量的作用域是在“花括号内”

高精度计算

math.big

进程、线程、协程

一个进程有多个线程,一个线程有多个协程

Goroutine什么时候会发生阻塞

在当前Goroutine进行IO操作时:比如数据库查询、网络请求,或者等待channel中数据,还有等待回调结果(发生系统级调用)。阻塞时Goroutine的状态会发生变化,调度器会先让该Goroutine在旁边等待,在这个Goroutine的阻塞结束之前,调度器会先让其他的Goroutine执行

new和make的区别

  • new

    初始化一个指向某类型的指针,并且分配空间

  • make

    为slice、map、chan初始化并且返回其引用

channel的特性

  • 从nil channel接收数据或者给nil channel发送数据都会造成永久阻塞
  • 给一个已经关闭的channel发送数据,会引起panic
  • 从一个已经关闭的channel接受数据,如果缓冲区已经空了,返回零值

有缓存和无缓存的channel的区别

无缓存channel是同步的,有缓存channel是异步的

select关键字

  • select主要用于处理异步的I/O问题(和switch语法类似)
  • select的每一个case必须是一个IO的操作

如何判断map中是否存在key

利用map的两个返回值,第一个map的值,第二个是map的key是否存在

init函数

一个package中可以有0个或者多个init()函数,程序在编译时,先执行导入包的init函数,再执行本包的init函数。

Mutex

  • 当一个goruntine获得Mutex后,其他goruntine只有等待,除非该goruntine释放Mutex
  • RWMutex在读锁被占用时,不能写,但是可以读
  • RWMutex在写锁被占用时,既不能读也不能写,只被该goruntine独享

Go vendor

go vendor会将引用的外部包放置到当前工程的vendor目录下,编译当前工程时则会优先从当前工程的vendor目录去寻找依赖包

垃圾回收

标记清除(mark and sweep),还有标记清除算法的变种,三色标记法

反射原理

利用reflect包,获取对象的type和value,接着获取对象的具体的属性、属性值、属性的类型、type的方法等

空struct{}的用途

通常表示一个占位以达到节约资源的目的,并没有实际作用,比如在map中,通常可以利用空struct{}来占位一个value,不使用map的value,但是却要使用map

如何比较两个结构体

可以使用==DeeplyEquals()来比较两个结构相同的类型并且包含相同字段值的结构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值