我们一起学猫叫 一起喵喵喵喵喵~
[[TOC]]
常量 常量的定义格式:const identifier [type] = value
显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"
变量 使用 var
关键字:var identifier type
整数的类型
浮点型
通常情况下应该使用 float64, 因为它比 float32 更精确
字符
golang 中字符串是不可变的
1 2 s1 := "hello killy" s1[0 ] = "y"
字符串的表示
双引号
反引号: 以字符串的形式原生输出(python 中的 r'hello kitty'
)
基本数据类型默认值
基本数据类型的转换 Go 在不同的类型之间赋值时需要显示的转换. 即 Golang 中数据类型不能自动转换.
1 2 var i int32 = 100 var n1 float32 = float32 (i)
数据转换中若结果溢出, 编译时不会报错, 只是转换的结果会按照溢出处理.
1 2 3 var num1 int64 = 9999999 var num2 int8 = int8 (num1)
命名规范
保持 package 名字和目录一致.
变量名, 函数名, 常量名采用驼峰法命名
若首字母大写, 贼可以被其它的包访问, 若小写则只能在本包中使用.
指针 1 2 3 4 5 6 var intP *int var i int = 10 var ptr *int = &i
源码, 反码, 补码
控制结构 if-else 1 2 3 4 5 6 7 if condition1 { } else if condition2 { } else { }
常用例子 判断一个字符串是否为空:
if str == "" { ... }
if len(str) == 0 {...}
函数 Abs()
用于返回一个整型数字的绝对值:
1 2 3 4 5 6 func Abs (x int ) int { if x < 0 { return -x } return x }
测试多返回值函数的错误 参考教程
swich 1 2 3 4 5 6 7 8 switch var1 { case val1: ... case val2: ... default : ... }
for结构 1 for 初始化语句; 条件语句; 修饰语句 {}
Break 与 continue Break 与 continue
标签与 goto 标签与 goto
函数 传递变长参数 1 func myFunc (a, b, arg ...int ) {}
示例函数和调用:
1 2 func Greeting (prefix string , who ...string ) Greeting("hello:" , "Joe" , "Anna" , "Eileen" )
在 Greeting 函数中,变量 who
的值为 []string{"Joe", "Anna", "Eileen"}
函数作为参数 1 2 3 4 5 6 7 8 9 10 11 func main () { callback(1 , Add) }func Add (a, b int ) { fmt.Printf("The sum of %d and %d is: %d\n" , a, b, a+b) }func callback (y int , f func (int , int ) ) { f(y, 2 ) }
使用闭包调试 1 2 3 4 5 6 7 where := func () { _, file, line, _ := runtime.Caller(1 ) log.Printf("%s:%d" , file, line) } where() where()
通过内存缓存提升性能 牺牲空间换时间, 示例 fibonacci_memoization.go
数组和切片 声明 一个切片在未初始化之前默认为 nil,长度为 0。
切片的初始化格式是:var slice1 []type = arr1[start:end]
。
使用 make()
创建去切片 func make([]T, len, cap)
,其中 cap 是可选参数。
所以下面两种方法可以生成相同的切片:
1 2 make ([]int , 50 , 100 )new ([100 ]int )[0 :50 ]
new()
和 make()
的区别
new(T) 为每个新的类型 T 分配一片内存,初始化为 0 并且返回类型为 * T 的内存地址:这种方法 返回一个指向类型为 T,值为 0 的地址的指针 ,它适用于值类型如数组和结构体(参见第 10 章);它相当于 &T{}
。
make(T) 返回一个类型为 T 的初始值 ,它只适用于 3 种内建的引用类型:切片、map 和 channel(参见第 8 章,第 13 章)。
bytes
包For-range
结构这种构建方法可以应用于数组和切片
1 2 3 for ix, value := range slice1 { ... }
切片重组(reslice
) 改变切片长度的过程称之为切片重组 reslicing
,做法如下:slice1 = slice1[0:end]
,其中 end 是新的末尾索引(即长度)
参考示例
Map 声明、初始化和 make map 是引用类型
1 2 3 var map1 map [keytype]valuetypevar map1 map [string ]int
未初始化的 map 的值是 nil
var map1 = make(map[keytype]valuetype)
不要用 new
构造 Map
测试键值是否存在及删除元素 判断 key 是否存在
或者和 if 混合使用:
1 2 3 if _, ok := map1[key1]; ok { }
删除 key delete(map1, key1)
key 不存在, 不会产生错误.
For-range 1 2 3 for key, value := range map1 { ... }
map 类型的切片 想获取一个 map 类型的切片, 必须使用两次 make()
, 第一次分配切片, 第二次分配切片中每个 map 元素, 示例
排序 代码示例
键值对调 代码示例
包
GFW 牛逼 …
有些方法得研究研究
init 函数 init 是一类非常特殊的函数, 不能够被人为调用, 会在包初始化后自动执行, 优先级高于 main.
结构和方法 定义 1 2 3 4 5 type identifier struct { field1 type1 field2 type2 ... }
new() t := new(T)
,变量 t
是一个指向 T
的指针,此时结构体字段的值是它们所属类型的零值
工厂方法创建结构体实例 按惯例,工厂的名字以 new 或 New 开头.
1 2 3 4 type File struct { fd int name string }
这个结构体类型对应的工厂方法,它返回一个指向结构体实例的指针:
1 2 3 4 5 6 7 func NewFile (fd int , name string ) *File { if fd < 0 { return nil } return &File{fd, name} }
然后这样调用它:
1 f := NewFile(10 , "./test.txt" )
强制使用工厂方法 1 2 3 4 5 6 7 8 type matrix truct { ... }func NewMatrix (params) *matrix { m := new (matrix) return m }
在其他包里使用工厂方法:
1 2 3 4 5 package mainimport "matrix" ... wrong := new (matrix.matrix) right := matrix.NewMatrix(...)
带标签的结构体
略
匿名字段和内嵌结构体 结构体可以包含一个或多个 匿名(或内嵌)字段 ,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体
方法 定义方法的一般格式如下:
1 func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }
类型和作用在它上面定义的方法必须在同一个包里定义,这就是为什么不能在 int、float 或类似这些的类型上定义方法
函数和方法的区别 函数将变量作为参数:Function1(recv)
方法在变量上被调用:recv.Method1()
指针或值作为接收者 pointer_value
方法和未导出字段 提供 getter 和 setter 方法
person2.go
略
类型的 String() 方法和格式化描述符 如果类型定义了 String()
方法,它会被用在 fmt.Printf()
中生成默认的输出:等同于使用格式化描述符 %v
产生的输出。还有 fmt.Print()
和 fmt.Println()
也会自动使用 String()
方法。
接口 通过如下格式定义接口:
1 2 3 4 5 type Namer interface { Method1(param_list) return_type Method2(param_list) return_type ... }
上面的 Namer
是一个 接口类型 。
(按照约定,只包含一个方法的)接口的名字由方法名加 [e]r
后缀组成,例如 Printer
、Reader
、Writer
、Logger
、Converter
等等。还有一些不常用的方式(当后缀 er
不合适时),比如 Recoverable
,此时接口名以 able
结尾,或者以 I
开头(像 .NET
或 Java
中那样)
Go 语言中的接口都很简短,通常它们会包含 0 个、最多 3 个方法。
demo demo1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package mainimport "fmt" type Usb interface { Start() Stop() }type Phone struct { }func (p Phone) Start() { fmt.Println("手机开始工作" ) }func (p Phone) Stop() { fmt.Println("手机停止工作" ) }type Camera struct { }func (c Camera) Start() { fmt.Println("Camera 开始工作" ) }func (c Camera) Stop() { fmt.Println("Camera 停止工作" ) }type Cumputer struct { }func (c Cumputer) Working(usb Usb) { usb.Start() usb.Stop() }func main () { computer := new (Cumputer) phone := new (Phone) camera := new (Camera) computer.Working(phone) computer.Working(camera) }
1 2 3 4 手机开始工作 手机停止工作 Camera 开始工作 Camera 停止工作
demo2 使用接口对结构体排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package mainimport ( "fmt" "math/rand" "sort" )type Hero struct { Name string Age int }type HeroSlice []Herofunc (hs HeroSlice) Len() int { return len (hs) }func (hs HeroSlice) Less(i, j int ) bool { return hs[i].Age < hs[j].Age }func (hs HeroSlice) Swap(i, j int ) { hs[i], hs[j] = hs[j], hs[j] }func main () { intSlice := []int {0 , -1 , 10 , 7 , 90 } sort.Ints(intSlice) fmt.Println(intSlice) var heroes HeroSlice for i := 0 ; i < 10 ; i++ { hero := Hero{ Name: fmt.Sprintf("英雄_%d" , rand.Intn(100 )), Age: rand.Intn(100 ), } heroes = append (heroes, hero) } for _, v := range heroes { fmt.Println(v) } fmt.Println("------ 正在排序 ---------" ) sort.Sort(heroes) for _, v := range heroes { fmt.Println(v) } }
output
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [-1 0 7 10 90] {英雄_81 87} {英雄_47 59} {英雄_81 18} {英雄_25 40} {英雄_56 0} {英雄_94 11} {英雄_62 89} {英雄_28 74} {英雄_11 45} {英雄_37 6} ------ 正在排序 --------- {英雄_81 87} {英雄_81 87} {英雄_81 87} {英雄_81 87} {英雄_81 87} {英雄_81 87} {英雄_62 89} {英雄_62 89} {英雄_62 89} {英雄_62 89}
类型断言 使用以下形式来进行类型断言:
1 2 3 4 5 if v, ok := varI.(T); ok { Process(v) return }
如果转换合法,v
是 varI
转换到类型 T
的值,ok
会是 true
;否则 v
是类型 T
的零值,ok
是 false
,也没有运行时错误发生。
由于接口是一般类型, 不知道具体类型. 如果要转换成具体类型, 就需要使用类型断言.
1 2 3 4 5 6 7 8 9 10 func main () { var x interface {} b := 1.1 x = b b_type := reflect.TypeOf(b) fmt.Println(b_type) y := x.(float64 ) fmt.Printf("y 的类型是 %T, 值是%v" , y, y) }
输出:
1 2 3 4 map [1 :1 ][{} {} {}] float64 y 的类型是 float64, 值是1 .1
类型判断:type-switch 1 2 3 4 5 6 7 8 9 10 switch t := areaIntf.(type ) {case *Square: fmt.Printf("Type Square %T with value %v\n" , t, t)case *Circle: fmt.Printf("Type Circle %T with value %v\n" , t, t)case nil : fmt.Printf("nil value: nothing to check?\n" )default : fmt.Printf("Unexpected type %T\n" , t) }
测试值是否实现了某个接口 1 2 3 4 5 6 7 type Stringer interface { String() string }if sv, ok := v.(Stringer); ok { fmt.Printf("v implements String(): %s\n" , sv.String()) }
空接口 空接口或者最小接口 不包含任何方法,它对实现不做任何要求:
任何其他类型都实现了空接口(它不仅仅像 Java/C#
中 Object
引用类型),any
或 Any
是空接口一个很好的别名或缩写。
反射包 gorouttine Go 协程的特点:
- 有独立的栈空间
- 共享程序堆空间
- 调度由用户控制
demo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport ( "fmt" "strconv" "time" )func loop_print (c string ) { for i := 1 ; i <= 10 ; i++ { fmt.Println(c + strconv.Itoa(i)) time.Sleep(time.Second) } }func hello_world () { loop_print("hello world" ) }func main () { go hello_world() loop_print("hello golang" ) }
channel channel 本质是一个队列
线程安全, 多 goroutine 访问时, 不需要加锁.
channel 是有类型的.
声明 var <变量名> chan <数据类型>
ch := make(chan int)
channel 是引用类型, 必须初始化, 即 make 后才能使用.
在没有使用协程的情况下, 如果管道的数据被全部取出, 继续读取数据会死锁, 报 deadlock 错误
创建空接口类型的 channel, 可以存放任意类型.
1 allChan := make (chan interface {}, 10 )
使用 channel 进行 goroutine 通信demo
使用通信共享内存, 而不是共享内存来通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package mainimport ( "fmt" )func writeData (intchan chan int ) { for i := 0 ; i < 50 ; i++ { intchan <- i fmt.Printf("writeDate =%v\n" , i) } close (intchan) }func readData (intchan chan int , exitchan chan bool ) { for { v, ok := <-intchan if ok { fmt.Printf("readDate =%v\n" , v) } else { break } } exitchan <- true close (exitchan) }func main () { intchan := make (chan int , 50 ) exitchan := make (chan bool , 1 ) go writeData(intchan) go readData(intchan, exitchan) for true { _, ok := <-exitchan if !ok { break } } }
使用 select 读取管道数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package mainimport "fmt" func main () { intChan := make (chan int , 10 ) for i := 1 ; i <= 10 ; i++ { intChan <- i } strChan := make (chan string , 5 ) for i := 0 ; i < 5 ; i++ { strChan <- "hello" + fmt.Sprintf("%d" , i) } for { select { case v := <-intChan: fmt.Printf(" read data from intChan, %d\n" , v) case v := <-strChan: fmt.Printf("read data from strChan, %s\n" , v) default : fmt.Printf("read data failed, alsdkjfl;kasjdlkfjklajskldfj\n" ) return } } }
goroutine 中捕获错误 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package mainimport ( "fmt" "time" )func sayHello () { for i := 0 ; i < 10 ; i++ { time.Sleep(time.Second) fmt.Println("hello world" ) } }func test () { defer func () { err:=recover () if err !=nil { fmt.Println("test() panic, err:" , err) } }() var mymap map [int ]string mymap[0 ] = "golang" }func main () { go sayHello() go test() for i, _ := range "1234567890" { fmt.Println("main ok=" , i) time.Sleep(time.Second) } }
反射
反射可以在运行时动态的获取变量的各种信息
如果是结构体变量, 还可以获取到结构体本身的信息.
通过反射, 可以修改变量的值, 可以调用相关的方法
使用反射, 需要 import (“reflect”)
变量 interface() 和 reflect.Value 是可以相互转换的,
demo1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package mainimport ( "fmt" "reflect" )func reflectTest1 (b interface {}) { rType := reflect.TypeOf(b) fmt.Println("rType: " , rType) rValue := reflect.ValueOf(b) fmt.Printf("rValue: %v,\nrValueType: %T\n" , rValue, rValue) rValue1 := reflect.ValueOf(b).Int() fmt.Printf("rValue: %v,\nrValueType: %T\n" , rValue1, rValue1) iV := rValue.Interface() num2 := iV.(int ) fmt.Println("num2: " , num2) }func reflectTest2 (b interface {}) { rType := reflect.TypeOf(b) fmt.Println("rType: " , rType) rValue := reflect.ValueOf(b) fmt.Printf("rValue: %v,\nrValueType: %T\n" , rValue, rValue) iV := rValue.Interface() fmt.Printf("iv: %v, type: %T\n" , iV, iV) stu, ok:=iV.(*Student) if ok { fmt.Printf("stu.Name=%v \n" , stu.Name) } }type Student struct { Name string Age int }func main () { num := 10 reflectTest1(num) fmt.Println("\n" ) stu := new (Student) stu.Name = "tom" stu.Age = 10 reflectTest2(stu) }
demo2 通过反射修改值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" "reflect" )func reflectTest (b interface {}) { rVal:=reflect.ValueOf(b) rVal.Elem().SetInt(2 ) }func main () { num:=2 reflectTest(&num) fmt.Println("num: " ,num) }
demo3 通过反射遍历结构体, 获取方法和标签值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package mainimport ( "fmt" "github.com/bradfitz/iter" "reflect" )type Monster struct { Name string `json:"name"` Age int `json:"monster_age"` Score float64 Sex string }func (s Monster) Print() { fmt.Println("===== start =====" ) fmt.Println(s) fmt.Println("===== end =====" ) }func (s Monster) GetSum(n1, n2 int ) int { return n1 + n2 }func (s Monster) Set(name string , age int , score float64 , sex string ) { s.Name = name s.Age = age s.Score = score s.Sex = sex }func TestStruct (a interface {}) { typ := reflect.TypeOf(a) val := reflect.ValueOf(a) kd := val.Kind() if kd != reflect.Struct { fmt.Println("except struct" ) return } num := val.NumField() fmt.Printf("struct has %d fields \n" , num) for i, _ := range iter.N(num) { fmt.Printf("field %d: 值: %v\n " , i, val.Field(i)) tagVal := typ.Field(i).Tag.Get("json" ) if tagVal != "" { fmt.Printf("field %d: tag: %v\n" , i, tagVal) } } numOfMethod := val.NumMethod() fmt.Printf("struct has %d methods\n" , numOfMethod) val.Method(1 ).Call(nil ) var params []reflect.Value params = append (params, reflect.ValueOf(10 )) params = append (params, reflect.ValueOf(40 )) res := val.Method(0 ).Call(params) fmt.Println("red: " , res[0 ].Int()) }func main () { m := new (Monster) m.Name = "test" m.Age = 100 m.Score = 40.0 m.Sex = "nan" TestStruct(*m) }