Golang学习笔记
Golang学习笔记
前言
2022年寒假,为了学习tinyKV课程而学习Golang,就写一些东西来记录一下吧
大括号不能换行是真的难受!
package
- package中的函数必须首字母大写才能被其他package调用
- package可以有init函数,该函数在package被import的时候会调用
变量、数组、切片
语法
var a int = 10
var p *int = &a
a := 10
p := &a
a := [4]int {1, 2, 3, 4}
b := [...]int {2, 3, 4)
a := []int {1, 2, 3, 4)
a := [3][2]string {
{"11", "12"},
{"21", "22"},
{"31", "32"}, //The comma is necessary
}
传递参数
Go中将数组作为参数进行传递的时候执行的是值传递,这与C++不同,需要特别注意。切片可以看成是对原数组的引用,改变切片也会改变原数组。
数组赋值
数组之间可以直接赋值,但数组的类型和长度要一样,如:
a := [5]int {1, 2, 3, 4, 5}
b := [5]int {6, 7, 8, 9, 10}
a = b
数组遍历
可以用for range遍历数组和切片
a := [5]int {1, 2, 3, 4, 5}
for i, v := range a {
//do something
}
for _, v := range a {
//do something
}
可以用len(a)获取a数组的长度
for i := 0; i < len(a); i++ {
fmt.Printf("%d th element is %d\n", i, a[i])
}
创建切片
用slc := a[st: ed]创建数组a在[st, ed)区间的切片
a := [5]int {1, 2, 3, 4, 5}
slc := a[1:4] //从a[1]到a[4-1]的切片
用make创建切片
func make([]T,len,cap)[]T 通过传递类型,长度和容量来创建切片。容量是可选参数, 默认值为切片长度。make 函数创建一个数组,并返回引用该数组的切片。
sld := make([]int, 5, 5)
fmt.Println(sld)
追加切片元素
sld := append(sld, 123, 34) //向sld中追加两个元素
函数
语法
func name(ele1 int, ele2 string) (rtnval1 int, rtnval2 string){
rtnval1 = 1
rtnval2 = "asd"
return
//
}
func main(){
val1, val2 := name(2, "zxc")
}
可变参数函数
只有最后一个参数可以为可变参数。当传入一组数时,会转化为切片,切片名为该可变参数名。可以使用语法糖直接将切片传递给可变参数
func f(nums ...int){
for _, v := range nums{
fmt.Printf("%d ", v)
}
}
func main()
{
f(1, 2, 3, 4)
a := []int {1, 2, 3, 4}
f(a...)//直接传递切片,在函数中改变nums将同时改变a
}
map
语法
mp := make(map[string]int)
mp := map[string]int {
"caution": 509,
"joker": 717,
"oyster": 213,
}
map[T1]T2是一个类型
var mp map[string]int
mp = make(map[string]int)//必须使用make初始化
添加、获取元素
同C++
删除元素
delete(mp, "joker")
map的长度
可用len(mp)获取
map是引用类型
当 map 被赋值为一个新变量的时候,它们指向同一个内部数据结构。因此,改变其中一个变量,就会影响到另一变量。
mp1 := map[string]int {
"oyster": 1,
"caution": 2,
}
mp2 := mp1
mp2["oyster"] = 3
fmt.Println(mp1) //mp1也会改变
fmt.Println(mp2)
map作为参数进行传递的时候,也是如此。
rune
rune 是 Go 语言的内建类型,它也是 int32 的别称。在 Go 语言中,rune 表示一个代码点。代码点无论占用多少个字节,都可以用一个 rune 来表示。
func printString(s string){
r := []rune(s)
for i, v := range r {
fmt.Printf("%c", v)
}
}
结构体
语法
type E struct { //命名结构体
s1, s2 string
age int
}
func main(){
e1 := E {
s1 : "asd",
age : 3,
s2 : "zxc",
}
e2 := E{"asd", "zxc", 3}
}
func main(){
e3 := struct{ //匿名结构体
s1, s2 string
age int
}{
s1 : "asd",
age : 3,
s2 : "zxc",
}
}
指向结构体的指针
若p为指向结构体的指针,则可以用p.s1代替(*p).s1
匿名字段
省略变量名,用类型名代替变量名
type Person struct {
string
int
}
func main() {
var p1 Person
p1.string = "naveen"
p1.int = 50
fmt.Println(p1)
}
提升字段
若一个结构体中有匿名结构体,则可以像访问正常成员一样访问匿名结构体的成员
导出结构体、导出字段
首字母大写才能被其他包使用
方法
方法类似于类的成员函数
语法
func (e E) show(tms int) {
for i := 0; i < tms; i++ {
fmt.Println(e)
}
}
func main(){
e := E { "asd", "zxc", 3 }
e.show(3)
}
为了在一个类型上定义一个方法,方法的接收器类型定义和方法的定义应该在同一个包中。
指针接收器
使用指针接收器可以改变e的内容。当使用指针接收器时,e.show()和(&e).show()都是合法的
接口
语法
package main
import "fmt"
type VolFinder interface {
FindVol() []rune
}
type MyString string
func (s MyString) FindVol() (ans []rune) {
for _, v := range s {
if(v == 'a' || v == 'e' || v == 'i' || v == 'o' || v == 'u') {
ans = append(ans, v)
}
}
return
}
func main(){
s := MyString("OysterMaZhiHao")
it := VolFinder(s)
fmt.Println(MyString(it.FindVol()))
}
空接口 & 类型断言
由于空接口没有方法,因此所有类型都实现了空接口。
package main
import "fmt"
func show(it interface{}) {
fmt.Printf("Type = %T, value = %v\n", it, it)
v, ok := it.(int)
fmt.Println(v, " ", ok)
}
func main(){
a := interface{} ("asd")
b := interface{} (509)
show(a)
show(b)
}
类型选择
package main
import "fmt"
func show(it interface{}) {
switch it.(type){
case string:
fmt.Printf("I'm a string, %v\n", it)
case int:
fmt.Printf("i'm an int, %v\n", it)
default:
fmt.Printf("i'm an unknown type, %v\n", it)
}
}
type MyString string
func main(){
a := interface{} ("asd")
b := interface{} (509)
c := interface{} (MyString("zxc"))
show(a)
show(b)
show(c)
}
实现了接口的类型可以与接口进行比较(类型相等)
并发
Go协程
使用go来创建Go协程实现并发
package main
import "fmt"
import "time"
func a () {
for i := 0; i < 5; i++ {
time.Sleep(250 * time.Millisecond)
fmt.Printf("%d ", i)
}
}
func b () {
for i := 'a'; i <= 'e'; i++ {
time.Sleep(400 * time.Millisecond)
fmt.Printf("%c ", i)
}
}
func main(){
go a()
go b()
time.Sleep(3000 * time.Millisecond)
fmt.Println("Main")
}
信道
a := make(chan int)
data := <- a // 读取信道 a
a <- data // 写入信道 a
当把数据发送到信道时,程序控制会在发送数据的语句处发生阻塞,直到有其它 Go 协程从信道读取到数据,才会解除阻塞。与此类似,当读取信道的数据时,如果没有其它的协程把数据写入到这个信道,那么读取过程就会一直阻塞着。
信道的这种特性能够帮助 Go 协程之间进行高效的通信,不需要用到其他编程语言常见的显式锁或条件变量。
package main
import "fmt"
func a (done chan bool) {
for i := 0; i < 5; i++ {
fmt.Printf("%d ", i)
}
done <- true
}
func b (done chan bool) {
for i := 'a'; i <= 'e'; i++ {
fmt.Printf("%c ", i)
}
done <- true
}
func main(){
done := make(chan bool)
go a(done)
<- done
go b(done)
<- done
fmt.Println("Main")
}
使用for range遍历信道
package main
import "fmt"
func write(chnl chan <- int) {
for i := 0; i < 10; i++ {
chnl <- i
}
close(chnl)
}
func main() {
chnl := make(chan int)
go write(chnl)
for v := range chnl {
fmt.Printf("%v\n", v)
}
}
缓冲信道
只有在缓冲满的时候,才会发生阻塞,容量参数为0时,即为之前所讨论的会发生阻塞的信道。当写入信道后超过信道容量,但又没有读取信道,就会发生死锁。
package main
import "fmt"
import "time"
func fun(chnl chan int){
for i := 0; i < 5; i++ {
chnl <- i
fmt.Println("Write ", i)
}
close(chnl)
}
func main() {
chnl := make(chan int, 2)
go fun(chnl)
time.Sleep(2 * time.Second)
for v := range chnl {
fmt.Println("Read ", v)
time.Sleep(2 * time.Second)
}
}
WaitGroup
当执行WaitGroup.Wait()时,只有当group中所有协程都执行完毕,才会停止阻塞。
package main
import "fmt"
import "time"
import "sync"
func fun (id int, wg *sync.WaitGroup) {
fmt.Println("Start ", id)
time.Sleep(1 * time.Second)
fmt.Println("End ", id)
wg.Done()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go fun(i, &wg)
}
wg.Wait()
fmt.Println("All done")
}
工作池
Example : Job Allocating
package main
import(
"fmt"
"time"
"sync"
)
type Job struct {
id int
tsk int
}
type Result struct {
job Job
ans int
}
func allocate(num int, chnl chan Job) {
for i := 0; i < num; i++ {
chnl <- Job{i, i + 10}
}
close(chnl)
}
func worker(in chan Job, out chan Result, wg *sync.WaitGroup) {
for job := range in {
out <- Result{job, job.tsk * 10}
time.Sleep(1 * time.Second)
}
wg.Done()
}
func getResult(out chan Result, done chan bool) {
for result := range out {
fmt.Println("Task", result.job.id, "Finished, the ans is ", result.ans)
}
done <- true
}
func work(wrknum int, in chan Job, out chan Result, wg *sync.WaitGroup) {
for i := 0; i < wrknum; i++ {
wg.Add(1)
go worker(in, out, wg)
}
wg.Wait()
close(out)
}
func main(){
const jobNum = 30
const workerNum = 30
fmt.Println("You have", jobNum, "jobs and ", workerNum, "workers\nNow jobs starts")
startTime := time.Now()
jobs := make(chan Job)
results := make (chan Result)
done := make (chan bool)
var wg sync.WaitGroup
go allocate(jobNum, jobs)
go work(workerNum, jobs, results, &wg)
go getResult(results, done)
<- done
endTime := time.Now()
fmt.Println("All done, tot time : ", endTime.Sub(startTime).Seconds(), "seconds")
}
select
select用于选择一个chan读取数据,在执行case或default前会阻塞。当有多个case可以读取时,会随机读取一个case。
package main
import "time"
import "fmt"
func fun1 (chnl chan string) {
time.Sleep(1 * time.Second)
chnl <- "From func1"
}
func fun2 (chnl chan string) {
time.Sleep(2 * time.Second)
chnl <- "From func2"
}
func main() {
chnl1 := make(chan string)
chnl2 := make(chan string)
go fun1(chnl1)
go fun2(chnl2)
for i := 0; i < 5; i++ {
select{
case v1 := <- chnl1 :
fmt.Println(v1)
case v2 := <- chnl2 :
fmt.Println(v2)
default:
fmt.Println("No data received")
}
time.Sleep(1 * time.Second)
}
}
竞态条件
用Mutex处理竞态条件
在sync.Mutex.Lock()和sync.Mutex.Unlock()之间的代码只会被一个协程同时执行,其他协程在当前协程unlock之前都会阻塞
package main
import(
"fmt"
"sync"
)
var x int = 0
func incre(wg *sync.WaitGroup, mx *sync.Mutex) {
mx.Lock()
x++
mx.Unlock()
wg.Done()
}
func main() {
var wg sync.WaitGroup
var mx sync.Mutex
for i := 0; i < 10000; i++ {
wg.Add(1)
go incre(&wg, &mx)
}
wg.Wait()
fmt.Println("All done, x = ", x)
}
用信道处理竞态条件
package main
import(
"fmt"
"sync"
)
var x int = 0
func incre(wg *sync.WaitGroup, chnl chan bool) {
chnl <- true
x++
<- chnl
wg.Done()
}
func main() {
var wg sync.WaitGroup
chnl := make(chan bool, 1)
for i := 0; i < 10000; i++ {
wg.Add(1)
go incre(&wg, chnl)
}
wg.Wait()
fmt.Println("All done, x = ", x)
}
Go的面向对象
Go用struct代替class,用struct嵌套代替class的继承,用interface实现多态
defer
- 在函数返回之前,调用defer的函数。
- defer调用的函数的参数的值是defer时的值,而不是实际调用时的值。
- 当有多次defer时,按defer栈顺序调用(先进后出)
错误处理
error
error是一个interface,nil为无错误,实现了Error() string方法的都是error类型。在打印error类型时,会自动调用error.Error()获取错误信息并打印
获取更多错误信息
- 断言底层struct类型,用struct字段获取更多信息
- 断言底层struct类型,调用方法获取更多信息
自定义错误
- 使用errors.New(string)自定义错误
panic + recover
- panic时,会先调用defer函数,然后结束当前协程,在上一层中在调用defer函数,再结束这一层,直到程序退出。
- 在defer函数中执行recover可恢复到上一层,不结束程序。
- 可以在recover后用debug.PrintStack()打印栈堆
package main
import (
"fmt"
"runtime/debug"
)
func r() {
if r := recover(); r != nil {
fmt.Println("Recovered", r)
debug.PrintStack()
}
}
func a() {
defer r()
n := []int{5, 7, 4}
fmt.Println(n[3])
fmt.Println("normally returned from a")
}
func main() {
a()
fmt.Println("normally returned from main")
}
头等函数
支持头等函数(First Class Function)的编程语言,可以把函数赋值给变量,也可以把函数作为其它函数的参数或者返回值。Go 语言支持头等函数的机制。
匿名函数
package main
import (
"fmt"
)
func main() {
a := func() {
fmt.Println("hello world first class function")
}
a()
fmt.Printf("%T", a)
}
函数类型
package main
import "fmt"
type add func(a, b int) int
func main() {
var a add = func(a, b int) int {
return a + b
}
s := a(2, 3)
fmt.Println(s)
}
高阶函数
wiki 把高阶函数(Hiher-order Function)定义为:满足下列条件之一的函数:
- 接收一个或多个函数作为参数
- 返回值是一个函数
闭包
package main
import (
"fmt"
)
func appendStr() func(string) string {
t := "Hello"
c := func(b string) string {
t = t + " " + b
return t
}
return c
}
func main() {
a := appendStr()
b := appendStr()
fmt.Println(a("World"))
fmt.Println(b("Everyone"))
fmt.Println(a("Gopher"))
fmt.Println(b("!"))
}
reflect
- import "reflect"
- reflect.ValueOf()
- reflect.TypeOf()
- reflect.TypeOf().Kind()
- reflect.ValueOf().NumField()
- reflect.ValueOf().Fieldd()
- reflect.ValueOf().Int()
文件操作
读取文件
package main
import (
"fmt"
"io/ioutil"
)
func main() {
data, err := ioutil.ReadFile("test.txt")
if err != nil {
fmt.Println("File reading error", err)
return
}
fmt.Println("Contents of file:", string(data))
}
使用命令行标记来传递文件路径
package main
import (
"flag"
"fmt"
)
func main() {
fptr := flag.String("fpath", "test.txt", "file path to read from")
flag.Parse()
fmt.Println("value of fpath is", *fptr)
}
创建一个字符串标记,名称是 fpath,默认值是 test.txt,描述为 file path to read from。这个函数返回存储 flag 值的字符串变量的地址。在程序访问 flag 之前,必须先调用 flag.Parse()。
用wrkspacepath/bin/filehandling -fpath=/path-of-file/test.txt运行程序
写入文件
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Create("test.txt")
if err != nil {
fmt.Println(err)
return
}
l, err := f.WriteString("Hello World")
if err != nil {
fmt.Println(err)
f.Close()
return
}
fmt.Println(l, "bytes written successfully")
err = f.Close()
if err != nil {
fmt.Println(err)
return
}
}
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Create("/home/naveen/bytes")
if err != nil {
fmt.Println(err)
return
}
d2 := []byte{104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100}
n2, err := f.Write(d2)
if err != nil {
fmt.Println(err)
f.Close()
return
}
fmt.Println(n2, "bytes written successfully")
err = f.Close()
if err != nil {
fmt.Println(err)
return
}
}
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Create("lines")
if err != nil {
fmt.Println(err)
f.Close()
return
}
d := []string{"Welcome to the world of Go1.", "Go is a compiled language.",
"It is easy to learn Go."}
for _, v := range d {
fmt.Fprintln(f, v)
if err != nil {
fmt.Println(err)
return
}
}
err = f.Close()
if err != nil {
fmt.Println(err)
return
}
fmt.Println("file written successfully")
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战