Golang学习笔记

以下的学习笔记来源于李文周博客,感谢作者的分享

变量 var

声明方式:
var 变量名 变量类型
如果未声明,可以直接使用 a := 123 来实现,自动推导出变量类型
匿名变量:x,_ := aaa() 一般用于需要忽略这个值

常量 const

常量是恒定不变的值,一般定义一些永远不能修改的值
声明方式:
const aaa = 1

常量计数器 iota

在iota出现时会被重置为0

const (
		n1 = iota //0
		n2        //1
		_        //2
		n4        //3
	)

数据类型

整型

无符号 :unit8,uint16,uint32,uint64
有符号: int8, int16, int32,int64

后面的数字用来表示有几位2进制。比如unit8,表示8位2进制的数,换算10进制就是255,那长它的最大长度便是0~255之间,其它同理。

特殊整型

uintptr: 用来存放一个指针

浮点型

float32: 一般表示到小数点后面6位精度
float64: 一般表示到小数点后面的15位精度

通常可以使用float64,但是这样占用内存空间也高

布尔值

true: 表示为真
false: 表示为假

布尔类型的变量默认的初始值为false

字符串

str: var a str

在golang的fmt包里面有两个方法,printf,println…两者的区别是:printf 会自动 格式化内部的变量,而println并不会

运算符

算术运算符

+:加
-:减
*:乘
/: 除
%: 求余

关系运算符

==: 相等
!=:

逻辑运算符

&&: and运算,两边都为true,则为true
||: OR运算,两边有一个true,则为true
!: 取反

流程控制

if条件判断

if 表达式1 {
    分支1
} else if 表达式2 {
    分支2
} else{
    分支3
}

for循环

for 初始语句;条件表达式;结束语句{
    循环体语句
}

也可以直接死循环

for {
    循环体语句
}

循环键值: for range(键值循环)

switch case

finger := 3
switch finger {
case 1:
	fmt.Println("大拇指")
case 2:
	fmt.Println("食指")
case 3:
	fmt.Println("中指")
case 4:
	fmt.Println("无名指")
case 5:
	fmt.Println("小拇指")
default:
	fmt.Println("无效的输入!")
}

goto 跳转到指定标签

goto语句通过标签进行代码间的无条件跳转

break 跳出循环

continue 跳出本次循环

数组 array

数组是同一种数据类型元素的集合,可以修改成员内容 ,但是不可以修改长度

// 定义一个长度为3元素类型为int的数组a
var a [3]int

初始化数组的三个方法

1、直接初始化列表
func main() {
	var testArray [3]int                        //数组会初始化为int类型的零值
	var numArray = [3]int{1, 2}                 //使用指定的初始值完成初始化
	var cityArray = [3]string{"北京", "上海", "深圳"} //使用指定的初始值完成初始化
	fmt.Println(testArray)                      //[0 0 0]
	fmt.Println(numArray)                       //[1 2 0]
	fmt.Println(cityArray)                      //[北京 上海 深圳]
}

2、根据内容长度自行推断数组长度
func main() {
	var testArray [3]int
	var numArray = [...]int{1, 2}
	var cityArray = [...]string{"北京", "上海", "深圳"}
	fmt.Println(testArray)                          //[0 0 0]
	fmt.Println(numArray)                           //[1 2]
	fmt.Printf("type of numArray:%T\n", numArray)   //type of numArray:[2]int
	fmt.Println(cityArray)                          //[北京 上海 深圳]
	fmt.Printf("type of cityArray:%T\n", cityArray) //type of cityArray:[3]string
}

3、指定索引来初始化数组
func main() {
	a := [...]int{1: 1, 3: 5}
	fmt.Println(a)                  // [0 1 0 5]
	fmt.Printf("type of a:%T\n", a) //type of a:[4]int
}

定义二维数组

func main() {
	a := [3][2]string{
		{"北京", "上海"},
		{"广州", "深圳"},
		{"成都", "重庆"},
	}
	fmt.Println(a) //[[北京 上海] [广州 深圳] [成都 重庆]]
	fmt.Println(a[2][1]) //支持索引取值:重庆
}

切片 slice

数组的长度是固定并且长度属于类型的一部分,这样会有很大的限制,所以便有了切片 。
切片是一个拥有相同元素可变长度的序列,基于数组做了一层封装,支持自动扩容。

声明方式

var a []int
a := []int{12}
a := make([]int, 2, 5)
三种声明方式,第一二两种是申请一个空的切片,容量也是空,需要使用append添加元素进来 ,动态扩容容量。

make可以直接定义切片的容量和初始化时添加的元素数量

可以使用内置的方法len()查看切片的长度,cap()查看切片的容量

append

func main(){
	var s []int
	s = append(s, 1)        // [1]
	s = append(s, 2, 3, 4)  // [1 2 3 4]
	s2 := []int{5, 6, 7}  
	s = append(s, s2...)    // [1 2 3 4 5 6 7]
}
可以一次添加一个或者多个, ...表示添加另一个切片的元素

map

map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。
map,slice,channel,func,指针都是引用类型,需要make(new)初始化分配内存空间才能使用
基本数据类型,array,结构体,接口都是值类型,可以直接使用,在声明时内存空间会直接会配好


make只能用于map,slice,channel的初始化,返回的还是这三个引用类型的本身
new用于类型的内存分配,返回的是类型的指针

定义

map[KeyType]ValueType

Map初始值为nil,需要使用make分配内存空间
make(map[KeyType]ValueType, [cap])

函数

Go语言中支持函数、匿名函数和闭包

定义

func 函数名(参数)(返回值){
    函数体
}

函数中的可变参数

func intSum2(x ...int) int {
	fmt.Println(x) //x是一个切片
	sum := 0
	for _, v := range x {
		sum = sum + v
	}
	return sum
}
可变参数实际是一个切片

返回值

如果返回值为多个,需要用括号包起来

函数类型

这里没懂,后面查资料

高阶函数

函数可以做为参数传入函数,也可以做为返回值返回函数

func add(x, y int) int {
	return x + y
}
func calc(x, y int, op func(int, int) int) int {
	return op(x, y)
}
func main() {
	ret2 := calc(10, 20, add)
	fmt.Println(ret2) //30
}




func do(s string) (func(int, int) int, error) {
	switch s {
	case "+":
		return add, nil
	case "-":
		return sub, nil
	default:
		err := errors.New("无法识别的操作符")
		return nil, err
	}
}

匿名函数

func main() {
	// 将匿名函数保存到变量
	add := func(x, y int) {
		fmt.Println(x + y)
	}
	add(10, 20) // 通过变量调用匿名函数

	//自执行函数:匿名函数定义完加()直接执行
	func(x, y int) {
		fmt.Println(x + y)
	}(10, 20)
}

闭包

这里没懂,后面查资料

defer语句

defer语句会将其后面跟随的语句进行延迟处理,由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。

func main() {
	fmt.Println("start")
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
	fmt.Println("end")
}

start
end
3
2
1

指针

变量都是写入内存中,指针指的便 是这个变量在内存中的地址。

&:获取一个变量的指针。即内存地址
*:把指针传递给其它函数或者结构体,要获取里面的内容 ,则使用星号就能得到指针类型的内容

在go中,当一个变量当做一个参数传递的时候,实际上中是一种按值拷贝,它自动创建一个副本,把副本传递给函数或者方法,所以当函数或者方法对这个数据有变更的话,那么本身的数据是不会变的。只是修改了副本内的数据而已。
当变量被使用指针方式传递时,一个新的指针副本被创建,它指向了同一个内存地址空间。可以直接对指针内的值做修改,这样作用域外面的变量也可以被修改

结构体

自定义类型

//将MyInt定义为int类型
type MyInt int

//类型别名
type TypeAlias = Type

自定义类型的应用场景 ,一般是可以把func也可以定义为变量,可以做为值或者其它函数的参数传递。

结构体定义

type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    …
}

//结构体实例化
var a := 类型名

匿名结构体

//不需要使用type定义结构体
 var user struct{Name string; Age int}
    user.Name = "小王子"
    user.Age = 18
    fmt.Printf("%#v\n", user)

创建指针类型的结构体

var p2 = new(person)
fmt.Printf("%T\n", p2)     //*main.person
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}

结构体初始化

//不进行初始化都是0值 
type person struct {
	name string
	city string
	age  int8
}

func main() {
	var p4 person
	fmt.Printf("p4=%#v\n", p4) //p4=main.person{name:"", city:"", age:0}
}

//初始化方法
p5 := person{
	name: "小王子",
	city: "北京",
	age:  18,
}
fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"小王子", city:"北京", age:18}

构造函数

struct是值类型,返回数据如果很复杂,在进行值拷贝的时候性能开销会比较 大,所以结构体返回指针类型

func newPerson(name, city string, age int8) *person {
	return &person{
		name: name,
		city: city,
		age:  age,
	}
}

//调用
p9 := newPerson("张三", "沙河", 90)
fmt.Printf("%#v\n", p9) //&main.person{name:"张三", city:"沙河", age:90}

方法和接收

Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}

结构体继承

结构体标签tag

Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出

type Student struct {
	ID     int    `json:"id"` //通过指定tag实现json序列化该字段时的key
	Gender string //json序列化是默认使用字段名作为key
	name   string //私有不能被json包访问
}

包管理

自己建的方法如果想被外部调用,需要在方法名中,首字母大写,来实现对外暴露

使用go mod方式

mkdir Gone
cd Gone
//初始化go mod
go mod init Gone

//在代码中设置使用外部包名。执行go mod tidy检查依赖,不需要的删除,需要的下载或者保留
go mod tidy

go PATH 与go mod的区别

在go1.14版本之前,使用go path,会默认把依赖放入到一个全局路径$PATH/src中来做管理,这样多个项目的依赖放在一起难免会有冲突。
在go mod中会把自己要使用的依赖放到到当前项目的vendor目录中来使用,不存在版本冲突的问题

接口

接口定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。

定义接口

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2}

接口类型名:Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,
有关闭操作的接口叫closer等。接口名最好要能突出该接口的类型含义。

实现接口的条件

接口就是规定了实现了方法的列表

为什么要使用接口

当我们有多个struct时,不需要关心最终的结果 ,只需要提供同一个方法就可以了

空接口

空接口是指没有定义任何方法的接口类型。因此任何类型都可以视为实现了空接口。也正是因为空接口类型的这个特性,空接口类型的变量可以存储任意类型的值。

空接口作为函数的参数

func show(a interface{}) {
	fmt.Printf("type:%T value:%v\n", a, a)
}

空接口作为Map的值

使用空接口实现可以保存任意值的字典。

// 空接口作为map值
	var studentInfo = make(map[string]interface{})
	studentInfo["name"] = "沙河娜扎"
	studentInfo["age"] = 18
	studentInfo["married"] = false
	fmt.Println(studentInfo)

反射

变量的内在机制

  • 类型信息: 预先定义好的元信息
  • 值信息: 程序运行过程中可动态变化的

什么是反射

反射是指在程序运行期间对程序本身进行访问和修改的能力。在程序编译时,变量被转化为内存地址,变量名不会被写入到程序执行的部分。程序在运行时,无法获得自身信息。
支持反射的语言可以在程序编译期间将变量的反射信息,比如字段名称,类型信息,结构体信息整合到可执行文件当中,并给程序提供接口访问反射信息,这样就可以在程序运行期间获得类型的反射信息,并且有能力修改它们。
go程序在运行期间使用reflect包访问程序的反射信息

reflect包

在Go语言的反射机制中,任何接口值都由(一个具体类型)和(具体类型的值组成),反射的功能由内置的reflect包提供。并且提供了reflect.TypeOf和reflect.ValueOf两个函数来获取任意对象的type和value

typeOf

使用reflect.TypeOf()函数可以获得任意值的类型对象(reflect.Type)
package main

import (
	"fmt"
	"reflect"
)

func reflectType(x interface{}) {
	v := reflect.TypeOf(x)
	fmt.Printf("type:%v\n", v)
}
func main() {
	var a float32 = 3.14
	reflectType(a) // type:float32
	var b int64 = 100
	reflectType(b) // type:int64
}

type name和type kind

在反射中关于类型还分为两种,类型(type)和种类(kind)。在golang中我们可以使用type构建许多种自定义类型,而种类(Kind)是指底层的类型,在反射中,需要区分指针,结构体等大品种的类型时,就会用的种类(kind)

valueOf

reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值的值信息。reflect.Value与原始值之间可以互相转换。

通过反射设置变量的值

想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。而反射中使用专有的Elem()方法来获取指针对应的值。

结构体反射

任意值通过reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的NumField()和Field()方法获得结构体成员的详细信息。

并发

go 关键字

Go语言中使用 goroutine 非常简单,只需要在函数或方法调用前加上go关键字就可以创建一个 goroutine ,从而让该函数或方法在新创建的 goroutine 中执行。

channel

如果说 goroutine 是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个 goroutine 发送特定值到另一个 goroutine 的通信机制。

channel操作

通道共有发送(send)、接收(receive)和关闭(close)三种操作。而发送和接收操作都使用<-符号。

并发安全和锁

读写互斥锁

你可能感兴趣的:(golang)