Go语言开发必备的开发手册: https://studygolang.com/pkgdoc
Go语言进阶学习书籍->Go语言圣经: https://www.k8stech.net/gopl/
Go语言是典型的面向对象编程语言,特性有继承(匿名字段、实名字段),封装(方法),多态(接口interface)。
面向对象是一种编程思想,本身与编程语言没有关系,只是一些语言很好的支持了面向对象,C语言、Go语言的结构体就是后来面向对象编程语言中的类,面向对象编程可实现高内聚,低耦合。
凌晨五点了,C++程序员在考虑内存泄漏的问题,Java程序员在考虑提高效率的问题,Python程序员在考虑怎么对齐代码,而Go程序员在考虑怎么样找error
查看源码 CTRL+B (或者CTRL+鼠标左键)
从源码返回 CTRL+E+回车
自动排版快捷键CTRL+ALT+L
复制一行CTRL+D
s1:=[]int{1,2,3,4}
s2:=make([]int,5,10) //长度只有5想添加元素要用函数
s[low:high:max] 从切片s的索引位置low到high处所获得的切片,len = high-low,cap = max-low
[low,high)为左闭右开区间,low和high就是具体的下标数字,截取从low下标到high-1下标的元素
append函数底层智能容量增长的,通常2倍(1024以下)容量重新分配底层数组,并复制原来的数据。故此,做扩容时地址可能发生变化,但不影响使用,超过1024通常时四分之一倍扩容。可参考C语言中realloc()学习。
切片不是指向底层数组的指针,而是重新定义的一种新数据结构。结构中包含指向底层数组的指针。
type slice struct{ //go语言源码包runtime/slice.go 可以查看源码
array unsafe.Pointer -->指向底层数组
len int
cap int
}
1.go指针的默认值都是nil,没有随机数,就没有野指针的概念。
2.操作符“&”是取变量地址,“*”是通过指针访问目标对象。
3.不支持指针数学运算,不支持"->"运算符,直接用"."访问成员。
1.new(T)创建一个T类型的匿名变量,分配一块内存空间,并清零,返回这块空间的地址。
2.new出来的对象可能在堆上也可能在栈上,根据实际情况内部实现决定的。
3.只需要使用就完事了,不用担心内存的生命周期或怎么删除,go语言的内存管理系统会帮我们解决。
Go语言的自动垃圾收集器对编写正确的代码是一个巨大的帮助,但也并不是说你完全不用考虑内存了。你虽然不需要显式地分配和释放内存,但是要编写高效的程序你依然需要了解变量的生命周期。例如,如果将指向短生命周期对象的指针保存到具有长生命周期的对象中,特别是保存到全局变量时,会阻止对短生命周期对象的垃圾回收(从而可能影响程序的性能)。
1.func Contains(s, subStr string) bool
功能:判断字符串s是否包含子串substr
2.func Join(a []string, sep string) string
功能:将一系列字符串连接为一个字符串,之间用sep来分隔。
3.func Trim(s string, cutset string) string
功能:将s前后端所有cutset字符串去除。返回处理后的新字符串
4.func Replace(s, old, new string, n int) string
功能:将s中的old子串替换为new,替换n处,n<0替换全部。返回替换后的新字符串
5.func Split(s, sep string) []string
功能:把s字符串按照sep分割,返回slice
6.func Fields(s string) []string
功能:去除s字符串的空格符,并且按照空格分割,返回slice
7.func HasSuffix(s, suffix string) bool
功能:判断s字符串是否有后缀子串suffix
8.func HasPrefix(s, prefix string) bool
功能:判断s字符串是否有前缀子串suffix
1.创建、打开文件
func Create(name string) (file *File, err Error) //创建文件对象可读写
func Open(name string) (file *File, err Error) //只读打开
func OpenFile(name string, flag int, perm uint32) (file *File, err Error) //万能操作
2.关闭文件函数
func (f *File) Close() error
3.写文件
func (file *File) WriteString(s string) (ret int, err Error)
func (file *File) Write(b []byte) (n int, err Error)
4.读文件(用到bufio包函数)
func NewReader(rd io.Reader) *Reader
func (b *Reader) ReadBytes(delim byte) ([]byte, error)
func (file *File) Read(b []byte) (n int, err Error)
5.删除文件
func Remove(name string) Error
1.定义接口(建议er结尾)
2.按接口定义语法,实现方法(创建类,绑定方法)
3.创建接口变量
4.使用类对象给接口变量赋值
5.使用接口变量调用方法。
1.定义接口
2.创建类,绑定方法,实现接口
3.创建函数,指定接口类型为参数
4.函数内,使用接口调用方法
手动产生panic时可调用:func panic(v interface{})
defer用来对指令(函数、表达式)进行延迟调用。延迟到当前指令对应的函数运行结束,栈帧释放前。
要拦截panic异常,recover无法单独工作,必须依赖defer
固定用法:
//函数其实位置,注册recover拦截监听。在函数调用结束时,返回监听结果。
defer func(){
fmt.Println(recover())
}()
panic异常。
1.通道中的数据只能单向流动。一端读端、另外必须写端
2.通道中的数据只能以此读取,不能重复读。先进先出
3.读端和写端在不同的goroutine之间
4.读端读,写端不在线,读端阻塞。写端写,读端不再先,写端阻塞。
xxx := make(chan int)
1.var chw chan<- int = xxx //定义写channel
2.var chr <-chan int = xxx //定义读channel
缓冲区的好处:1.解耦 2.处理并发 3.缓存
type Timer struct{ //time包下的结构体
C <-chan Time
//内含隐藏或非导出字段
}
eg:(单次定时三秒输出当前时间)
普通版:
timer:=time.NewTimer(time.Second*3)
fmt.Println(time.Now())
t:=<-timer.C
fmt.Println(t)
简便版:
fmt.Println(time.Now())
t:= <-time.After(time.Second*3)
fmt.Println(t)
1.<-time.After(time.Second*3) 【主要掌握这种】
2.time.Sleep(time.Second*3)
3.time.NewTimer(time.Second*3)返回一个timer后 <-timer.C取出数据
var glb = 1
func subGo(ch_data chan<- int,ch_stdout <-chan int){
for{
fmt.Println("子:",glb)
ch_data <- glb+1
<-ch_stdout
}
}
func main() {
ch_data := make(chan int)
ch_stdout := make(chan int)
go subGo(ch_data,ch_stdout)
for num := range ch_data{
fmt.Println("主:",num)
glb = num+1
ch_stdout <- 1
}
}
步骤:
创建 select , 启动监听 channel
for {
select {
case num := <-ch:
fmt.Println("num =", num)
case <-time.After(time.Second * 3): //1.创建 Timer, 2.读 C 成员
fmt.Println("子go程读到系统时间, 定时满 3 秒")
quit <- false
}
}
监听超时定时器:
case <-time.After(time.Second * 3)
当select监听的其他case分支满足时,time.After所在的case分支,会被重置成初始定时时长。
直到在select 监听期间,没有任何case满足监听条件。time.After 才能定时满。
1. 单个go程使用同一个channel自己读、自己写。
2. 多个go程使用 channel通信,go程创建之前,对channel读、写造成死锁。
3. 多个go程使用多个channel 通信,相互依赖造成死锁。
4. 多个go程使用 锁(读写锁、互斥锁)和 channel 通信。
作用:保护公共区,防止多个控制流共同访问共享资源时造成数据混乱。
使用注意事项:
1. 访问公共区之前,加锁。访问结束立即解锁。
2. 粒度越小越好
使用方法:
1. 创建读写锁 var rwmutex sync.RWMutex
2. 加读锁 rwmutex.RLock() 加写锁 rwmutex.Lock()
...... 访问公共区
3. 解读锁 rwmutex.RUnlock() 解写锁 rwmutex.Unlock()
- 建议:不要将锁 和 channel 混合 使用。 —— 条件变量。
num :=rand.Intn(10) //生成一个0-9之间的随机数。
1.生产者有个条件变量判断公共区是否为满,未满才能获取锁之后向公共区添加数据
如果公共区满了(len(ch) == cap(ch))那么就等待cond.Wait(),等待消费者signal()唤醒阻塞在条件变量上的对端(生产者) [注意等待需要放在循环中]
2.消费者有个条件变量判断公共区是否为空,未空才能获取锁之后从公共区获取数据
如果公共区为空(len(ch) == 0)那么就等待cond.Wait(),等待生产者signal()唤醒阻塞在条件变量上的对端(消费者) [注意等待需要放在循环中]
func (c *Cond) Wait()这个函数的作用:
1. 阻塞等待条件变量满足
2. 释放已经掌握的互斥锁。(解锁)。要求,在调用wait之前,先加锁。c.L.Lock()
。。。。 阻塞等待条件变量满足
3. 解除阻塞,重新加锁。
func (c *Cond) Signal()这个函数的作用:
1.唤醒阻塞在条件变量上的 go程。
func (c *Cond) Broadcast()这个函数的作用:
1.唤醒阻塞在条件变量上的 所有 go程。
条件变量实现生产者具体步骤,消费者同理可得:
1. 创建条件变量 var cond sync.Cond
2. 初始化条件变量的成员 L ,指定锁(互斥、读写) cond.L = new(sync.Mutex)
3. 加锁 cond.L.Lock()
4. 判断条件变量满足, 调用 wait
// 此处必须使用 for 循环判断 wait是否满足条件。for循环会二次判断条件,确保正确性,因为多go程同时生产的时候,用if判断条件(一次判断)可能会导致错误生产。如果用if,刚提示可以公共区未满,可以生产了,但其他go程提前生产,公共区变成了满状态,这就导致生产错误。
for len(ch) == cap(ch) {
cond.Wait() // 1 2 3
}
5. 产生数据,写入公共区
6. 唤醒阻塞在条件变量上的 对端。cond.Signal() //官方文档建议调用signal时保持锁状态,解锁放后面
7. 解锁 cond.L.UnLock()
主go程等待子go程结束运行。这个是正规方法,要记住!
注意waitGroup创建的对象,如果在实名go程传参的话,必须传&,匿名go程直接使用对象即可。
实现具体步骤:
1. 创建 waitGroup对象。 var wg sync.WaitGroup
2. 添加 主go程等待的子go程个数。 wg.Add(数量)
3. 在各个子go程结束时,调用 wg.Done()。 相当于将主go等待的数量-1。 defer wg.Done
如果是实名子go程的话, 传地址。
4. 在主go程中等待。 wg.wait()
实现流程:
1. 创建监听器 listener
2. 启动监听,接收客户端连接请求 listener.Accept() --- conn
for {
3. conn.read 客户端发送的数据
4. 使用、处理数据
5. 回发 数据给客户端 write
6. 关闭连接 Close
}
7. 客户端可以使用 nc (netcat)测试:
语法:nc 服务器IP地址 服务器port
使用方法:
1.Listen函数 —— 创建监听器
func Listen(net, laddr string) (Listener, error)
参数1:协议类型:tcp、udp (必须小写)
参数2:服务器端的 IP: port (192.168.11.01:8888)
返回值:成功创建的监听器
type Listener interface { // Addr返回该接口的网络地址 Addr() Addr // Accept等待并返回下一个连接到该接口的连接 Accept() (c Conn, err error) // Close关闭该接口,并使任何阻塞的Accept操作都会不再阻塞并返回错误。 Close() error }
2.Accept 方法 —— 阻塞等待客户端连接
func (listener *Listener) Accept() (c Conn, err error)
返回值:成功与客户端建立的 连接conn(socket)
type Conn interface { // Read从连接中读取数据 // Read方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真 Read(b []byte) (n int, err error) // Write从连接中写入数据 // Write方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真 Write(b []byte) (n int, err error) // Close方法关闭该连接 // 并会导致任何阻塞中的Read或Write方法不再阻塞并返回错误 Close() error // 返回本地网络地址 LocalAddr() Addr // 返回远端网络地址 RemoteAddr() Addr 。。。 }
实现流程:
1. 创建与服务器的连接 net.Dial() -- conn(socket)
for {
2. 发送数据给服务器 conn.write
3. 接收服务器回发的 数据 conn.read
4. 关闭连接。
}
使用的方法:
1.Dial方法
func Dial(network, address string) (Conn, error)
参数1:使用的协议:udp、tcp
参数2:服务器的 IP:port (127.0.0.1:8888)
返回:conn 用于与服务器进行数据通信的 socket
type Conn interface { // Read从连接中读取数据 // Read方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真 Read(b []byte) (n int, err error) // Write从连接中写入数据 // Write方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真 Write(b []byte) (n int, err error) // Close方法关闭该连接 // 并会导致任何阻塞中的Read或Write方法不再阻塞并返回错误 Close() error // 返回本地网络地址 LocalAddr() Addr // 返回远端网络地址 RemoteAddr() Addr }
服务器包含: 主go程、子go程们。
实现流程:
1. 主go程中,创建监听器 Listener
2. 使用 for 监听 listener —— Accept() —— conn
3. 当有客户端发送连接请求时,创建 子go程。 使用 conn 与客户端进行数据通信
4. 封装、实现函数:完成单个子go程与客户端的 read、write —— handleConnet(conn)
关闭当前go程 conn —— defer
for {
1. 读取客户端的数据 conn.read()
2. 获取客户端的 IP+port 。 conn.RemoteAddr().string()[获取对端地址]
3. 处理数据
4. 写回数据到客户端。
}
网络通信中,通过 read 返回值 为 0 判断对端关闭。
n, err := conn.read(buf)
if n == 0 {
// 检查到 conn 对端关闭。
}
if err != nil && err != io.EOF {
// 处理错误。
}
实现流程:
1.获取UDP地址结构 —— UDPAddr
func ResolveUDPAddr(net, addr string) (*UDPAddr, error)
参数1:“udp”
参数2:“127.0.0.1:8888” IP+port
返回值:UDP地址结构
type UDPAddr struct { IP IP Port int Zone string // IPv6范围寻址域 }
2.绑定地址结构体,得到通信套接字 —— udpConn
func ListenUDP(net string, laddr *UDPAddr) (*UDPConn, error)
3.接收客户端发送数据 —— ReadFromUDP() —— n,cltAddr, err
func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error)
4.写数据给客户端 —— WriteToUDP(数据,cltAddr) —— n, err
func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error)
实现流程:
创建用于与服务器通信的 套接字 conn
net.Dial("udp", 服务器的IP+prot) --- conn
后续代码实现,参照 TCP-CS-Client。
UDP服务器,默认支持客户端并发访问。
func Stat(name string) (FileInfo, error)
type FileInfo interface {
Name() string Size() int64
Mode() FileMode
ModTime() time.Time
IsDir() bool
Sys() interface{}
}
在main函数启动时,向整个程序传参。
语法: go run xxx.go argv1 argv2 argv3 argv4 。。。。
xxx.exe: 第 0 个参数。
argv1 :第 1 个参数。
argv2 :第 2个参数。
argv3 :第 3 个参数。
argv4 :第 4 个参数
使用: list := os.Args —— for index, str := range list { fmt.println() 可以遍历 list }
1.注册 回调函数:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
pattern:服务器提供给客户端访问 url(路径、文件)
handler 回调函数。—— 系统自动调用
func handler(w http.ResponseWriter, req *http.Request)
w: 回写给客户端的 数据(http协议)
req:读取到的客户端请求 信息
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(int)
}
type Request struct {
Method string // 浏览器请求方法 GET、POST…
URL *url.URL // 浏览器请求的访问路径
……
Header Header // 浏览器请求头部
Body io.ReadCloser // 浏览器请求包体
RemoteAddr string // 浏览器地址
……
ctx context.Context
}
2.启动服务
func ListenAndServe(addr string, handler Handler) error