Go简明教程
1 变量与内置数据类型
1.1字符串
//返回某个变量的类型
reflect.TypeOf().Kind()
//字符串是以byte数组形式保存的,类型是uint8,占1个byte打印时需要使用string进行类型转换,否则打印的是编码值。
//一个字一般占3个字节,存文字一般使用rune数组
//转换成 []rune 类型后,字符串中的每个字符,无论占多少个字节都用 int32 来表示,因而可以正确处理中文。
1.1数组与切片
切片使用数组作为底层结构。
切片包含三个组件:容量,长度和指向底层数组的指针,切片可以随时进行扩展。
1.2字典
map 类似于 java 的 HashMap,Python的字典(dict),是一种存储键值对(Key-Value)的数据解构
赋值不存在的就是添加,如果已经存在就是修改
1.3指针
在Go语言中,一般情况参数是按值传递的,函数内部将会拷贝一份参数的副本,对参数的修改并不会影响到外部变量的值。所以如果想要从内部改变外部变量,参数使用指针。
2 流程控制(if ,for,switch)
2.1 条件语句 if else
if 条件{
}elif 条件{
}else{
}
//还有一种就是if带上表达式
if 表达式;条件{
}
2.2 switch
//switch
//和其他语言不同的地方在于,Go 语言的 switch 不需要 break,匹配到某个 case,执行完该 case 定义的行为后,默认不会继续往下执行。如果需要继续往下执行,需要使用 fallthrough
switch 条件{
case 条件1:
case 条件2:
default:
}
自定义类型
//定义出来的为新类型和go原来几个类型都不一样
type 名称 类型
//判断类型的几个方法:1.下断言可以断出类型(要放在接口里面就是)
type 小王 int
const 变量名(a) 小王 = 1
var duanyan interface{}
duanyan=a
value,ok:=duanyan.(小王)可以断出类型
Go 语言中没有枚举(enum)的概念,一般可以用常量的方式来模拟枚举
在 Go 语言中,我们如果要实现枚举,则需要使用常量计数器 iota 结合 const 来实现。
我们给第一个常量赋值为 iota,在这组常量中,之后的常量就会为我们自动赋值递增。
枚举:
const(
SexMan = iota
SexWoman
SexUnknown
)
func main() {
fmt.Println(SexMan,SexWoman,SexUnknown)
}
// 执行结果
$ go run main.go
0 1 2
2.3 for 循环
对数组(arr)、切片(slice)、字典(map)
for key,value :=range Arr {
}
for key,value :=range slice{
}
fo key,vale :=range map{
}
3 函数(functions)
3.1参数与返回值
//函数返回值的两种形式
func add(num1 int, num2 int) int {
ans :=num1 + num2
return ans
}
func add(num1 int, num2 int) (ans int) {
ans = num1 + num2
return
}
3.2错误处理(error handling)
如果函数实现过程中,如果出现不能处理的错误,可以返回给调用者处理。
//打开文件失败,他就会返回err给我
_,err:=os.open("filename.txt")
//errorw.New 返回自定义的错误
errors.New("error: name is null")
错误往往是可以预知的,如果出现一些不可预知的错误,如数组越界等这种情况可能会导致程序非正常退出,在Go中称为panic。
捕获异常
Go 语言也提供了类似的机制 defer
和 recover
我们调用recover只会捕获到抛出的第一个异常
defer func() {
if err := recover(); err != nil {
fmt.Println("如果出问题", err)
}
}()
for i := 0; i < 2; i++ {
if i == 0 {
panic("got i")
}
if i == 1 {
panic("got l")
}
4 结构体、方法和接口
4.1 结构体(struct)和方法(methods)
//随便举个例子吧
func (stu *Student) hello(person string) string {
return fmt.Sprintf("hello %s, I am %s", person, stu.name)
}
//后面我们要调用这个方法的时候就可以直接实例一个对象、然后直接调用该方法
4.2 接口
一般而言,接口定义了一组方法的集合,接口不能被实例化,一个类型可以实现多个接口。
看看面向接口编程
package main
import "fmt"
type Person interface {
getName() string
getAge() int
}
type Student struct {
name string
age int
}
func (stu *Student) getName() string {
return stu.name
}
func (stu *Student) getAge() int {
return stu.age
}
type Worker struct {
name string
gender string
}
func (w *Worker) getName() string {
return w.name
}
//声明一个接口的结构体实例
func main() {
var p Person = &Student{
name: "Tom",
age: 18,
}
fmt.Println(p.getName(), p.getAge()) // Tom
}
//感觉有点像面向接口编程
说白了就是声明一个接口类型的变量
然后给变量实例化一个想要调用这些方法的实例(结构体)
说白了就是装备给这个人穿,穿上就有各种超能力(方法)
Go 语言中,并不需要显式地声明实现了哪一个接口,只需要直接实现该接口对应的方法即可.
那么怎么判断接口里面的函数是否实现完整呢?
var _ Person = (*Student)(nil)
var _ Person = (*Worker)(nil
- 将空值 nil 转换为 *Student 类型,再转换为 Person 接口,如果转换失败,说明 Student 并没有实现 Person 接口的所有方法。
- Stduent没有实现Person接口的所有方法,那么只要该方法声明在接口里面,那么给接口附上该结构体的实例就会报错。
- Worker 同上
func main() {
var p Person = &Student{
name: "Tom",
age: 18,
}
stu := p.(*Student) // 接口转为实例
fmt.Println(stu.getAge())
}
4.3空接口
如果定义了一个没有任何方法的空接口,那么这个接口可以表示任意类型
5 并发编程(goroutine)
5.1 sync
Go 语言提供了 sync 和 channel 两种方式支持协程(goroutine)的并发。
- wg.Add(1):为 wg 添加一个计数,wg.Done(),减去一个计数。
- go download():启动新的协程并发执行 download 函数。
- wg.Wait():等待所有的协程执行结束
5.2 channel
var ch = make(chan string, 10) // 创建大小为 10 的缓冲信道
func download(url string) {
fmt.Println("start to download", url)
time.Sleep(time.Second)
ch <- url // 将 url 发送给信道
}
func main() {
for i := 0; i < 3; i++ {
go download("a.com/" + string(i+'0'))
}
for i := 0; i < 3; i++ {
msg := <-ch // 等待信道返回消息。
fmt.Println("finish", msg)
}
fmt.Println("Done!")
}
6 单元测试(unit test)
// calc_test.go
package main
import "testing"
func TestAdd(t *testing.T) {
if ans := add(1, 2); ans != 3 {
t.Error("add(1, 2) should be equal to 3")
}
}
//运行 go test,将自动运行当前 package 下的所有测试用例,如果需要查看详细的信息,可以添加-v参数。
$ go test -v
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok example 0.040s