有关心情 - Go·语法
type: channel
link: https://blog.youguanxinqing.xyz/index.php/category/go_grammer/
description: Go的语法相关文章。
items: 快速入门Go（6）
items: 快速入门Go（5）
items: 快速入门Go（4）
items: 快速入门Go（3）
items: 快速入门Go（2）
items: 快速入门Go（1）

快速入门Go（6）
type: item
date: 2019-03-02T13:22:00+08:00
description: 前言这是我学习Go语法的笔记。由于有C和Python的基础，上手Go很快。笔记很粗糙，好在自己够用。此篇包括了Go相关的：并发编程，信号通道，定时器等。涉及关键字go select。goroutineGo天生优秀，利用关键字go可以很容易的实现并发编程。如一个并发示例：func print_a() { for { fmt.Println("a") time.Sleep(time.Second) // 睡1s } } func print_b() { for { fmt.Println("b") time.Sleep(time.Second) // // 睡1s } } func main() { go print_a() // 打印 a go print_b() // 打印 b fmt.Println("等待线程执行") for { // 死循环 } }运行上述代码，可看到程序交替打印ab。如果不出意外，程序会先打印“等待线程执行”，然后打印ab。所需注意两点：以go方式调用的函数都是异步执行的。也就是说，不会等到被调函数返回结果才往下执行；主协程退出，它的子协程跟着退出。因此上述demo中设置死循环，是为避免程序退出，导致两个协程跟着退出。runtime以下简单介绍runtime包的使用，其中包括了Gosched，Goexit和GOMAXPROCS。GoschedGosched表示让出时间片，可以让本应该执行的协程稍后执行。func main() { go func() { fmt.Println("this is a") }() fmt.Println("this is b") } // 输出： this is b上述代码中匿名函数的功能是打印语句“this is a”，以协程的方式执行。然而，在子协程开始之前，主协程已经往下执行并执行结束，所以demo的运行结果是：只打印了“this is b”。马上来试试Gosched的效果：func main() { go func() { fmt.Println("this is a") }() runtime.Gosched() fmt.Println("this is b") } // 输出： this is a this is b现在demo的执行结果是先打印a然后打印b。这是因为程序遇到runtime.Gosched()后会主动让出执行权，此时子协程也排队等着了，一看到“有空位”就立马钻进去执行，才有了我们看到的结果。GoexitGoexit能够终止一个正在执行的协程，其作用效果跟return类似。有一点需要额外注意：Goexit不能在主函数中使用，否则程序panic。子协程退出时，依然能够执行defer修饰的语句。func test() { defer fmt.Println("three") runtime.Goexit() // 等同return fmt.Println("four") // 这句话不会被执行 } func main() { fmt.Println("one") go test() fmt.Println("two") for{ } } // 输出： one two threeGOMAXPROCSGo语言默认单核运行，通过runtime.GOMAXPROCS()设置参与运行的CPU个数。runtime.NumCPU()用来获取计算机CPU数。查阅网上部分资料，发现有人说设置最大CPU数最好不要超过实际CPU数，但也有人说在一定范围内超过效果最佳。这里存疑。func main() { ... runtime.GOMAXPROCS(2) // 设置最大2核处理，函数返回计算机实际最大核数 ... }channelgoroutine运行在相同的地址空间上，因此访问内存必须做好同步。goroutine通过通信共享内存，而不是共享内存来通信。在函数传递中，channel引用传递。不同角度可对channel做不同分类：第一种可以是：有缓存channel和无缓存channel；第二种可以是：单向channel和双向channel。无缓存与有缓存创建channel：make(chan Type) // 无缓存，等价于make(chan Type, 0) make(chan Type, capacity) // 有缓存当capacity=0时，channel无缓冲阻塞读写；当capacity>0时，channel有缓冲非阻塞，直到写满capacity个元素才阻塞写入。channel通过操作符<-接收、发送数据。如ch<-表示发出数据到ch中，<-ch表示从ch中接收数据。常用操作如下：channel<- value // 发送value到channel <-channe…

快速入门Go（5）
type: item
date: 2019-02-07T01:48:34+08:00
description: 前言这是我学习Go语法的笔记。由于有C和Python的基础，上手Go很快。笔记很粗糙，好在自己够用。此篇包括了Go相关的：错误处理，字符串处理，正则，JSON，文本操作。异常处理error官方errors包源码如下：// Package errors implements functions to manipulate errors. package errors // New returns an error that formats as the given text. func New(text string) error { return &errorString{text} } // errorString is a trivial implementation of error. type errorString struct { s string } func (e *errorString) Error() string { return e.s }即New()函数是返回一个errorString类型的变量，这种类型的变量拥有Error()方法，用法如下：err := errors.New("this is a err") fmt.Println(err.Error()) // 输出： this is a err另一种error来自于fmt包：err := fmt.Errorf("%s", "this is error in fmt") fmt.Println(err) // 输出： this is error in fmt一个完整的errors的使用：func myDiv(a, b int) (result int, err error) { if b == 0 { err = errors.New("除数不能为0") } else { result = a / b } return } func main() { result, err := myDiv(10, 0) if err != nil { fmt.Println(err.Error()) } else { fmt.Println("结果为：", result) } }panic致命错误panic()recover必须放在defer后面使用。恢复程序。func testb(x int) { defer func() { if err := recover(); err != nil { // 只在出现panic错误的起作用 fmt.Println(err) } }() var a[10] int a[x] = 111 // 当x>=10之后，属于越界访问 } func main() { testb(100) } // 输出： runtime error: index out of range当没有错误时，recover()函数返回nil，如果有错误，则返回错误信息。字符串处理字符串操作（strings）Contains// 前者（str）是否包含后者（substr） fmt.Println(strings.Contains("hello world", "hello")) // true fmt.Println(strings.Contains("hello world", "abc")) // falseJoin// 组合，类似Python中的 "".join() s := []string{"abc", "zty", "guan", "suanle"} fmt.Println(strings.Join(s, ",")) // abc,zty,guan,suanleIndex// 子字符串在字符串中出现的位置，第一个字符的索引，不存在返回-1 fmt.Println(strings.Index("helloworld", "wor")) // 5 fmt.Println(strings.Index("helloworld", "go")) // -1Repeat// 重复次数 buf := strings.Repeat("zty", 3) fmt.Println(buf) // ztyztyztyReplace// 替换 fmt.Println(strings.Replace("abcbe", "b", "!", 2)) // a!c!eSplit// 按指定sep拆分，返回切片 str := "zty-zty-zty" result := strings.Split(str, "-") fmt.Println(result) // [zty zty zty]Trim// 去掉两头 words := "fre--fre--fre" fmt.Pri…

快速入门Go（4）
type: item
date: 2019-01-14T00:18:00+08:00
description: 前言这是我学习Go语法的笔记。由于有C和Python的基础，上手Go很快。笔记很粗糙，好在自己够用。此篇包括了Go相关的：匿名组合，方法，接口等。涉及关键字interface。匿名组合Go语言没有封装、继承、多态的概念，但可以通过别的方式实现这些特性：封装：通过方法实现。继承：通过匿名字段实现。多态：通过接口实现。匿名字段理解Go语言中匿名字段不妨类比其他的语言的“类和继承”的概念，这里我用Python举例。当我们需要避免一些重复工作时——比方现在我们需要两种人，一种学生，一种上班族，代码可以如下：# 学生 class Student(object): name = "" age = 0 gender = 1 # 1表示男生 school = "" # 上班族 class OfficeWorker(object): name = "" age = 0 gender = 1 company = ""很好，代码写完了……欸卧槽？！怎么重复了name、age、gender？事实上，但凡一个人，都有应该有名字年纪和性别。所以“学生”与“上班族”之间存在相同的成员变量不足为奇，而程序员为了“偷懒”，一拍脑袋想出了继承这一概念，于是：class Person(object): name = "" age = 0 gender = 1 class Student(Person): school = "" class OfficeWorker(Person): company = ""我们称Student和OfficeWorker是Person的派生类，它们继承自Person。现在Go语言的程序员也想偷懒，因此有了匿名字段，上述内容用Go实现如下：type Person struct { name string age int gender byte } type Student struct { Person // 结构体匿名字段 school string } type OfficeWorker struct { Person company string }此时结构体Person就作为结构体Student的匿名字段。很显然，由于Person充当的是一个数据类型（声明一个Person类型的结构体语法：var p Person），所以对于结构体Student来说，数据类型为Person的变量名并不存在，那么，就“匿名”了！且Student包含Person中的所有字段。结构体匿名字段也就是说，这个匿名变量，其类型为结构体。因此，前面说的Person，在Student中就起到了辅助匿名变量的任务。初始化赋值：对于初始化一个Student类型的变量，其方法可以如下几种：var stu = Student{Person{"ty", 22, 'm'}, "XHUnivercity" } // 顺序初始化 var stu = Student{Person{age:22}, "XHUnivercity"} // 对于Person来说是指定初始化，对整个Student来说，是顺序初始化 var stu = Student{Person:Person{age:22}} // 整个Student为指定初始化 stu := Student{Person{"ty", 22, 'm'}, "XHUnivercity" } // 简短声明当匿名字段的数据类型为指针时，需要加上取地址符（&）：type Student struct { *Person // 结构体匿名字段 school string } var stu = Student{&Person{"ty", 22, 'm'}, "XHUnivercity" } // &Person或者用new()提前分配空间，再用点操作访问成员变量：var stu Student stu.Person = new(Person) stu.name = ... ...非初始化赋值：非初始化赋值有三种方法：type Person struct { name string age int gender byte } type Student struct { Person school string } func main() { var stu1, stu2, stu3 Student // 第一种方法 stu1.name = "ty" stu1.age = 22 stu1.gender = 'm' stu1.school = "XHUnivercity" fmt.Printf("%+v\n", stu1) // 第二种方法 stu2.Person.name = "ty" stu2.Person.age = 22 stu2.Per…

快速入门Go（3）
type: item
date: 2019-01-06T14:11:00+08:00
description: 前言这是我学习Go语法的笔记。由于有C和Python的基础，上手Go很快。笔记很粗糙，好在自己够用。此篇包括了Go相关的：指针，数组，切片，字典，结构体等。涉及关键字struct。指针Go语言中的指针与C稍异。如C中，声明一个int类型的指针语句：int* p ，所以p的类型就为int*。而Go语言中，*在前面：func main() { num := 512 var p *int // Go语言中‘*’在前面 p = &num fmt.Printf("address of num is %p", p) }由于Go语言在声明变量的时候会自动初始化值，对于指针，会指向nil，而不是NULL（Go中没有NULL）：func main() { var p *int fmt.Printf("address of pointer is %p, and its content is %v", p, p) } // 输出： address of num is 0x0, and its content is <nil>Go语言不允许操作没有合法指向的内存：func main() { var p *int *p = 512 // 对p指针指向的内存赋值 fmt.Printf("address of pointer is %p, and its content is %v", p, p) } // 抛异常： panic: runtime error: invalid memory address or nil pointer dereference [signal 0xc0000005 code=0x1 addr=0x0 pc=0x490056]newnew()函数是用来分配内存的内建函数，用法为new(T)（T是类型），会将分配的内存置为0值，同时返回该内存的地址：func main() { p := new(int) // 返回*int指针 fmt.Printf("type of pointer is %T\n", p) fmt.Printf("content of memory is %v\n", *p) } // 输出： type of pointer is *int content of memory is 0此时你可以正确操作指针p指向的内存，比如正确的赋值：*p = 512 // 不可以赋值字符串从C语言角度理解这个过程，可以近似为：int main() { int *p = (int*)malloc(sizeof(int)); // 申请内存 memset(p, 0, sizeof(int)); // 内存置0操作 printf("address of memory is %p, content of memory is %d", *p, *p); free(p); // 释放操作 return 0; }重点在于，Go语言有完善的GC机制，不需要手动释放内存；而C中，每一个malloc后，都应该有对应的free操作。数组Go中的数组声明与C也不同：// Go var array [3]int // 声明一维数组 var array [2][3]int // 声明二维数组 var array = [3]int{1, 2, 3} // 对一维数组赋值 array := [3]int{1, 2, 3} // 简短声明一维数组 var array = [2][3] // 对二维数组赋值 array := [2][3]int{{1, 2, 3}, {3, 2, 1}} // 简短声明一维数组 var arr = [...]int{1, 2, 3, 4} // 允许使用`...`自动计算长度 arr := [...]int{1, 2, 3, 4} // C int array[3]; // 声明一维数组 int array[2][3]; // 声明二维数组 int array[3] = {1, 2, 3}; int array[2][3] = {{1, 2, 3}, {3, 2, 1}};对Go中的数组，有以下几点需要注意：数组声明时不可以用变量表示元素个数，如：// 错误操作 n := 10 array := [n]int数组可以指定元素初始化（未指定元素默认0值），如：func main() { array := [5]int{2: 512, 4: 1024} // 冒号前边的数表示索引值 for i := range array { fmt.Printf("%d ", array[i]) } }0 512 0 1024数组支持==或!=运算，前提是数组类型要一样，即[x][y]type（eg：2int） 一致：func main() {…

快速入门Go（2）
type: item
date: 2019-01-03T00:21:00+08:00
description: 前言这是我学习Go语法的笔记。由于有C和Python的基础，上手Go很快。笔记很粗糙，好在自己够用。此篇包括了Go相关的：函数，闭包，类型命名，项目管理等。涉及关键字type defer。函数无参无返回值函数func myfun() { fmt.Println("this is a func") }被调函数放在调用函数的前后都允许，不需要像C那样放在后面需要前置声明。有参无返回值函数func myfun(str string) { fmt.Println(str) } // 形参同类型时 func myfun(str1, str2 string) { // ... } // 形参不同类型时 func myfun(num int, str string) { // ... }需要在参数列表中指明参数的类型。与C语言的区别是：形参名在前，类型在后。不定长参数函数func myfun(str ...string) { // ... }在函数类型前加美式省略号（...）可实现不定长参数传参。func myfun(str ...string) { for _, s := range str { fmt.Printf("%s\n", s) } } func main() { myfun("hello", "guan", "ying") } // 输出 hello guan ying需要注意的是：当参数列表中有多个参数时，不定长参数必须在固定参数的后面。func myfun(num int, str ...string) {/* ... */} // 对 func myfun(str ...string, num int) {/* ... */} // 错当我们需要把一个函数中的不定长参数传给另一个函数时，也需要用到...：func herfun(num ...int) { for _, n := range num { fmt.Println(n) } } func myfun(num ...int) { herfun(num...) // 如果只需要传最后两个参数，可以 // herfun(num[1:]...) } func main() { myfun(1, 2., 3) }有返回值函数一般来说，让人习惯的用法是：func myfun() int {/* ... */} // 表示return了一个int类型 func myfun(str string) (byte, byte, byte) {/* ... */} // 表示return了三个byte类型此时返回的格式与Python格式相同。如第一个函数可以return 512；第二个函数可以return 'z', 't', 'y'。像下面这样：func myfun(str string) (byte, byte, byte) { return str[0], str[1], str[2] } func main() { str := "zty" z, t, y := myfun(str) fmt.Printf("%c, %c, %c", z, t, y) }但是，这并不是官方推荐的返回方式。官方更建议一种酷炫、也更一目了然的方法——返回列表中写明返回的变量的名字：func myfun(str string) (z byte, t byte, y byte) { z, t, y = str[0], str[1], str[2] return }看仔细！在myfun()函数中，我们并没有声明变量z、t、y，但它们确实、已然可用。而return的时候，return就够了。自然与参数列表一样，当返回值类型相同时我们可以简化代码：func myfun(str string) (z , t, y byte) {/* ... */}type变量类型关键字type可以为数据类型重命名，让类型有了语义。func main() { type alpha byte // 类型alpha的底层数据类型是byte var z alpha = 'z' fmt.Printf("%c 的类型是 %T", z, z) } // 输出 z 的类型是 main.alpha如果我们用alpha来声明一个变量，我们很显然可以知道这个变量企图存放一个字母。这里需要说明一下，Python中获取一个变量的类型通过type()函数；而Go语言是通过格式占位符%T来实现。通过type命名的数据类型默认有一个方法，可以显式地转换变量的类型：func main() { type positive string type negative string var pWords positive = "beautiful" var nWor…

快速入门Go（1）
type: item
date: 2018-12-31T18:18:00+08:00
description: 前言这是我学习Go语法的笔记。由于有C和Python的基础，上手Go很快。笔记很粗糙，好在自己够用。此篇包括了Go相关的：变量声明，分支语句（if,switch），循环语句(for)。涉及关键字var const fallthrough goto range 等。简单示例// go语言以包作为管理单位 package main // 声明包 import "fmt" // 导入包 func main() { // 左括号必须与函数名同行 fmt.Println("hello world") }注意事项：每个文件必须声明包；程序必须有一个main包。声明变量声明变量的四种方式：func main() { var a int a = 512 var b int = 513 // 此时类型可以省略，就像声明c一样 var c = 514 d := 515 // 简短声明，让编译器为d自动推导类型 fmt.Println("a =", a) // 打印语句，类似Python中的print fmt.Println("b =", b) fmt.Println("c =", c) fmt.Println("d =", d) } // 输出： a = 512 b = 513 c = 514 d = 515同时多个同类型变量的声明：// 第一种 var a int var b int // 第二种 var a, b int如果多个变量类型不同，可以：var ( a int b int c float64 ) // 或者赋初值 var ( a int = 512 b int = 1024 c float64 = 3.14 )Go语言中，每个声明的变量如果未被初始化，编译器默认为其设置0值。每个类型都有其对应的0值，但不一定是0。func main() { var num int // int类型 var str string // 字符串类型 var p *int // int指针类型 fmt.Println("a =", num) // 输出0 fmt.Println("str =", str) // 输出空字符串 fmt.Println("p =", p) // 输出nil } // 输出： a = 0 str = p = <nil>nil相当于Python中的NULL。声明常量关键字const承担常量声明的职责，与var声明变量的用法相同。const num = 512 // 或者 const num int = 512注意以下几点：常量必须在声明的时候赋值；常量没有简短声明方式（:=）；常量一旦声明，就不可以赋值，否则报错：cannot assign to num 。iota枚举iota为每个常量自动赋值（从0开始），每次赋值后自动累加1。func main() { const ( a = iota b = iota c = iota ) fmt.Printf("a = %d b = %d c = %d", a, b, c) } // 输出 a = 0 b = 1 c = 2多个同一行的变量值相同：func main() { const ( a = iota b, c, d = iota, iota, iota e = iota ) fmt.Printf("a = %d b = %d c = %d d = %d e = %d", a, b, c, d, e) }也可以简化代码，只开头写一个iota。此时不允许一行多个变量：const ( a = iota b c )iota遇到const会被重置为0func main() { const ( a = iota b ) const c = iota fmt.Printf("a = %d b = %d c = %d", a, b, c) } // 输出： a = 0 b = 1 c = 0基础类型布尔类型Go语言中布尔值的false和true全小写，而Python是首字母大写：func main() { var flag bool // 声明 fmt.Println("flag =", flag) var mark = true // 声明的同时初始化 fmt.Println("mark =", mark) // 或者 // mark := true } // 输出 flag = false mark = true字符型Go语言中，用单引号表示一个字符 （byte)，同时字符类型以数值的方式存储：func main() { var c byte = 'z' fmt.Println("c =", c) } // 输出 c = 122如果希望字节正常输出，可以用fmt.Printf：fmt.Pri…