参照https://www.bilibili.com/video/BV1gf4y1r79E做的笔记
包定义
package main
main函数所在的包, 定义为main包
方法定义
func
关键字, 花括号左边和func
要在同一行
func main () {
}
有返回值, 参数值的
func function(a int, b string) (int,string){
}
函数名大写 可供外部包调用,小写则只能在本包内调用
声明变量
var a int
var a int =100
var a = 100
var a, b = 100, "abc"
//以上可以声明全局变量
a:=100
常量定义 枚举定义
将var
改成const
const(
BEIJING = iota * 10 //每行iota依次累加1, 第一行iota默认值0
SHANGHAI //省略后, 会重复上一行
NANJING
)
导包与路径
递归导包
导包会执行包的func init(){}
函数
导包时, go从goroot开始搜索路径, 也就是go的安装目录, 以后会用模块化管理, 进行go库的管理
导包时可指定别名, 此时即使包在后续没有被调用, 也可以编译成功
import lib "mylib/lib"
别名为.
时, 表示直接导入当前包
defer
defer
修饰的语句会在当前代码块最后运行–不论其写在代码块中的哪个地方
例如打开文件后, 紧跟defer
关闭文件, 很方便
多个defer
满足栈的调用顺序, 即先进后出
defer
后的语句甚至会在return
后的语句运行之后, 再运行
循环
for i:=0; i<10; i++ {}
for index, value := myArray {}
数组
var a [10] int
b:=[10]int{1,2,3,4}
数组作为函数参数时, 必须长度也一致, 但这里全是数据拷贝
只希望有一个副本, 则需要使用指针/动态数组
动态数组(切片)
array:=[]{1,2,3,4}
此时用函数传参, 传的是同一个对象引用
需要为动态数组开辟空间, 使用make
slice := make([]int, 3)//:=可以让编译器推测出slice是一个切片, 不用事先var定义
除了指定大小, 还可以指定容量
slice := make([]int, 3, 6)
cap(slice) == 6
存在尾指针:
使用append
追加元素:
slice = append(slice, 1)
若append
超过容量, 则会新开辟之前cap
空间一倍的新空间追加在尾指针后
slice
支持和python
一致的切片截取功能, 其中截取使用的是对原切片的引用
sliceSub = slice[0:2] //从0到下标2的前一个元素截取出来
copy(sliceCopy, slice) //copy函数深拷贝
map
中括号内是key
的数据类型, 外面是value
的数据类型,
虽然也会动态增加空间, 但需要在使用前先开辟依次空间
myMap := map[string]string
myMap = make(map[string]string, 10)
//当然也可以直接填充
myMap := map[string]string{
"1":"a",
"2":"b"
}
delete(myMap,"1")
在函数形参中以引用导入
type
基本同C中的type of
type myInt int
定义一个结构体
type Book struct{
title string
auth string
}
func main(){
var book Book
book.title = "cake"
}
直接在函数形参列表中传入的是对象的副本
func function(Book book){
}
通过添加*
符号, 可以传入指针, 当然调用时也需要指定是传入地址
func function(Book *book){
}
func main(){
function(&book)
}
面向对象->将结构体转化为类
绑定类方法
func (this Book) PrintBookInfo(){
fmt.Println("%v", this)
}
func (this Book) SetBookTitle(newName string){
this.title = newName
}
注意this
是调用的对象的一个拷贝
比较好的是换成this*
可以将this*
替换别名t*
类的构造(结构体的成员变量的初始化)
book:=Book{title:"cake", auth:"cakecn"}
函数指针
book:=Book{title:"cake", auth:"cakecn"}
tempfunciton:=book.PrintBookInfo
tempfunction()
//等同于
book.PrintBookInfo()
访问权限
同包内函数一样, 类名首字母大写, 说明其他包可访问, 小写则为private
成员变量, 方法也一样
封装主要在包
的层面
继承
type NiceBook struct{
Book
readersNum int
}
func (t* NiceBook) AddReader(){
t.readersNum++
}
子类的构造, 需要写出父类才能定义父类的成员
func main(){
niceBook:=NiceBook{Book{"cake","cakecn"}, 0}
}
接口/抽象类 interface
type Animal interface{
Sleep()
GetColor() string
GetType() string
}
对这个接口的实现, 无需像刚刚继承一个类一样, 在类定义里写出来
只需要实现同签名的函数, 那么这个接口指针就可以指向这个实现的类
type Cat struct{
name string
color string
}
func (t* Cat) Sleep(){
}
func (t* Cat) GetColor()string{
}
func (t* Cat) GetType()string{
}
func main(){
c:=Cat("Ketty", "white")
var animalPtr Animal
animalPtr = &c
animalPtr.Sleep()
}
interface{} 与 数据类型断言
interface{}
可以作为一种通用万能指针, 类似于C中的void*
, 可以用来引用任意的数据对象
所有的数据类型都实现interface{}
类型
断言就是将interface{}类型转化为实际的类型,
若interface{}指针指向的数据实际就是断言的类型,
那么ok为true
, value
就是实际类型的数据
value, ok := a.(string)//a是一个interface{}
在函数形参传递interface{}
时, 内部可以对实参类型进行断言校验
func interfaceFunction(t interface{}){
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}
}
变量内部的pair 和 反射
变量构造的时候有一个type
和value
的指针对,
其中type有两种可能, 一是指向基本数据类型的static type
, int, string之类的
二是指向interface{}所指向的具体数据类型, 运行时类型concrete type
而通过反射机制, 我们可以得到变量的concrete type
或者是value
中的值
package main
import (
"fmt"
"reflect"
)
func main() {
s := "hello"
fmt.Println("type of s : ", reflect.TypeOf(s))
fmt.Println("value of s : ", reflect.ValueOf(s))
}
两个指针能否互相断言, 其核心在于两者的type
指针是否一致
结构体标签
可以给结构体的成员变量添加标签, 类似一种可以在代码运行中做判断的高级注释
如下通过在结构体的成员变量后面添加"``"飘号对, 对其添加标签
type Book struct{
Name string `info:"name of a book" doc:"should be read-only access"`
Price int `info:"price of a book"`
}
通过反射机制可以获取到变量的标签
type Book struct {
Name string `info:"name of a book" doc:"should be read-only access"`
Price int `info:"price of a book"`
}
func main() {
var virtualbook interface{}
virtualbook = Book{"CPP入土", 10}
tags := reflect.TypeOf(virtualbook)
for i := 0; i < tags.NumField(); i++ {
info := tags.Field(i).Tag.Get("info")
fmt.Println("info: ", info)
}
}
标签在json中运用
使用encoding/json
库, 对结构体进行序列化时, 在标签中添加json:"name"
等语法, 就可以实现在json序列自定义键值对的键
jsonStr, err := json.Marshal(book)
book1 := Book{}
err = json.Unmarshal(jsonStr, &book1)
协程
语言支持 https://www.bilibili.com/video/BV1gf4y1r79E?p=26
Go语言开启一个协程只用一个go
关键字即可:
func newTask(routineName string) {
i := 0
for {
i++
fmt.Printf("%s : i = %d\n", routineName, i)
time.Sleep(1 * time.Second)
}
}
func main() {
go newTask("goRoutine")
time.Sleep(time.Second / 2)
newTask("mainRoutine")
}
如果用runtime.Goexit()
会直接结束当前的整个协程. 与return
类似defer
修饰的语句依然会最后按顺序运行一次
协程之间通信 channel
使用make
创建channel
make(chan Type, capacity = 0)//指定Type数据类型, 和capacity为缓冲大小
channel <- value //写入管道
<- channel //弹出
x := <- channel //弹出并读取
x, ok := <- channel
channel
具有同步功能, 读取的协程, 在管道还打开着的情况下, 会阻塞住直到能从管道中读取到数据
同样, 写入的协程也会阻塞住直到成功写入(除非缓冲区不为空)
关闭管道, 及时关闭管道可以防止持续等待导致死锁
close(channel)
关闭管道, 依然可以从中读取数据
一种简写, 可以方便的实现一个阻塞接收器:
for data := range c {
}
select 监听多个channel(读写均可)
select {
case x <- chan1:
case chan2 <-1:
default
}
使用gomodules管理模块
几个比较重要的环境变量
GO111MODULE
是否开启gomodules, 推荐开启
GOPROXY
设置下载模块的代理网站
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
其中direct
表示如果代理网站找不到就去github源找
GOPRIVATE
如果从一个私有仓库拉取
go env -w GOPRIVATE="*.example.com, github.com/cakecn/privateRepository"
使用GoModules初始化项目
go env -w GO111MODULE=on
- 在想要的文件夹下
go mod init myTestModules/test1
创建go.mod文件, 说明这个项目名称 - 手动下载三方模块(默认最新)
go get github.com/C/D
三方模块会放在$GOPATH/src下