以下的学习笔记来源于李文周博客,感谢作者的分享
声明方式:
var 变量名 变量类型
如果未声明,可以直接使用 a := 123 来实现,自动推导出变量类型
匿名变量:x,_ := aaa() 一般用于需要忽略这个值
常量是恒定不变的值,一般定义一些永远不能修改的值
声明方式:
const aaa = 1
在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 表达式1 {
分支1
} else if 表达式2 {
分支2
} else{
分支3
}
for 初始语句;条件表达式;结束语句{
循环体语句
}
也可以直接死循环
for {
循环体语句
}
循环键值: for range(键值循环)
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语句通过标签进行代码间的无条件跳转
数组是同一种数据类型元素的集合,可以修改成员内容 ,但是不可以修改长度
// 定义一个长度为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]) //支持索引取值:重庆
}
数组的长度是固定并且长度属于类型的一部分,这样会有很大的限制,所以便有了切片 。
切片是一个拥有相同元素可变长度的序列,基于数组做了一层封装,支持自动扩容。
var a []int
a := []int{1,2}
a := make([]int, 2, 5)
三种声明方式,第一二两种是申请一个空的切片,容量也是空,需要使用append添加元素进来 ,动态扩容容量。
make可以直接定义切片的容量和初始化时添加的元素数量
可以使用内置的方法len()查看切片的长度,cap()查看切片的容量
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是一种无序的基于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语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。
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是结构体的元信息,可以在运行的时候通过反射的机制读取出
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
在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值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "沙河娜扎"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)
反射是指在程序运行期间对程序本身进行访问和修改的能力。在程序编译时,变量被转化为内存地址,变量名不会被写入到程序执行的部分。程序在运行时,无法获得自身信息。
支持反射的语言可以在程序编译期间将变量的反射信息,比如字段名称,类型信息,结构体信息整合到可执行文件当中,并给程序提供接口访问反射信息,这样就可以在程序运行期间获得类型的反射信息,并且有能力修改它们。
go程序在运行期间使用reflect包访问程序的反射信息
在Go语言的反射机制中,任何接口值都由(一个具体类型)和(具体类型的值组成),反射的功能由内置的reflect包提供。并且提供了reflect.TypeOf和reflect.ValueOf两个函数来获取任意对象的type和value
使用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)和种类(kind)。在golang中我们可以使用type构建许多种自定义类型,而种类(Kind)是指底层的类型,在反射中,需要区分指针,结构体等大品种的类型时,就会用的种类(kind)
reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值的值信息。reflect.Value与原始值之间可以互相转换。
想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。而反射中使用专有的Elem()方法来获取指针对应的值。
任意值通过reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的NumField()和Field()方法获得结构体成员的详细信息。
Go语言中使用 goroutine 非常简单,只需要在函数或方法调用前加上go关键字就可以创建一个 goroutine ,从而让该函数或方法在新创建的 goroutine 中执行。
如果说 goroutine 是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个 goroutine 发送特定值到另一个 goroutine 的通信机制。
通道共有发送(send)、接收(receive)和关闭(close)三种操作。而发送和接收操作都使用<-符号。