Go语言有一些让人影响深刻的核心特性核心特性,比如:以消息传递模式的并发、独特的_符号、defer 、函数和方法、值传递等等,可以查看这篇文章《Go语言-让我印象深刻的13个特性》。首先要记住一些核心特性的用法。
Goroutine就是这种协程特性的实现。Goroutine 是通过通信来共享内存,而不是共享内存来通信。通过共享内存来控制并发,会使编程变得更复杂,容易引入更多的问题。
Goroutine是由Go的运行时调度和管理。Go程序会智能地将 Goroutine 中的任务合理地分配给每个CPU,它在语言层面已经内置了调度和上下文切换的机制,不需要程序员去操作各种方法实现调度。
在Go语言中,当需要让某个任务并发执行时,只需要把这个任务包装成一个函数,开启一个Goroutine去执行就可以了。如下,只需要在调用函数时,在前面加上go关键字。
func hello_go() { fmt.Println("hello go!!!")}func main() { go hello_go() fmt.Println("main done!!!") time.Sleep(time.Second)}
在Go语言中接口interface是一种类型。Go语言的接口比较松散,只要是实现了接口定义的方法,就是实现了这个接口,无需使用implement等关键字去声明。
定义接口:
// 定义接口type Sayer interface { say()}// 定义结构体type dog struct {}type cat struct {}// 定义方法func (d dog) say() { fmt.Println("狗叫")}func (c cat) say() { fmt.Println("猫叫")}
空接口可以存储任意类型:
// 比如定义一个map类型的对象var obj = map[string]interface{}
使用类型断言判断空接口中的值:
// x:表示类型为interface{}的变量// T:表示断言x可能是的类型。x.(T)
func main() { var x interface{} x = 123 //v, ok := x.(int) v, ok := x.(string) if ok { fmt.Println(v) } else { fmt.Println("类型断言失败") }}
接口特性:
// 定义接口type Sayer interface { say()}// 定义结构体type dog struct {}type cat struct {}// 定义方法func (d dog) say() { fmt.Println("狗叫")}func (c cat) say() { fmt.Println("猫叫")}func main(t *testing.T) { var x Sayer // 声明一个接口类型的变量 c := cat{} // 实例化cat d := dog{} // 实例化dog x = c // cat赋值给接口类型 x.say() // 打印:猫叫 x = d // dog赋值给接口类型 x.say() // 打印:狗叫}
// 定义接口type Sayer interface { say()}type Mover interface { move()}// 定义结构体type dog struct {}// 定义方法func (d dog) say() { fmt.Println("狗叫")}func (d dog) move() { fmt.Println("狗移动")}func main(t *testing.T) { var x Sayer var y Mover var d = dog{} x = d y = d x.say() y.move()}
// 定义接口type Mover interface { move()}type Sayer interface { say()}// 定义结构体type dog struct {}// 定义方法func (d *dog) say() { fmt.Println("狗叫")}func (d dog) move() { fmt.Println("狗移动")}func TestProgram(t *testing.T) { var x Sayer var y Mover //var d = dog{} var d = &dog{} x = d // x不可以接收 dog类型,因为golang 不会 将值类型 转换 为指针类型 y = d // y可以接受 *dog类型,因为golang 会 将指针类型 转换 为值类型 x.say() y.move()}
_是特殊标识符,用来忽略结果。
buf := make([]byte, 1024)f, _ := os.Open("/Users/***/Desktop/text.txt")
func main() { a := 10 fmt.Printf("type of a: %T/n", a) b := &a // 取变量a的地址,将指针保存到b中 fmt.Printf("type of b: %T/n", b) c := *b // 取出 指针b 所指向的值 fmt.Printf("type of c: %T/n", c) fmt.Printf("value of c: %v/n", c)}
关键字 defer 用于注册延迟调用。这些调用直到 return 前才被执。可以用来做资源清理,常用来关闭资源。defer 是先进后出。
func main() { arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} for _, v := range arr { defer fmt.Println("循环:", v) } fmt.Println("主流程跑完") time.Sleep(time.Second * 3) // 等待3秒后,执行defer,输出时先输出10,最后输出1,因为是先进后出}
bool // 布尔int, int8, int16, int32, int64 // 整数uint, uint8, uint16, uint32, uint64 // 0 和正整数float32, float64 //浮点数string // 字符串complex64, complex128 // 数学里的复数array // 固定长度的数组struct // 结构体string // 字符串slice // 序列数组map // 映射chan // 管道interface // 接口 或 任意类型func // 函数
append // 追加元素到数组copy // 用于复制和连接slice,返回复制的数目len // 求长度,比如string、array、slice、map、channelcap // capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)delete // 从map中删除key对应的valuepanic // 抛出异常(panic和recover:用来做错误处理)recover // 接受异常make // 分配内存,返回Type本身(只能应用于slice, map, channel)new // 分配内存,主要用来分配值类型,比如int、struct。返回指向Type的指针close // 关闭channel
// 申明变量var name string// 申明常量const pi = 3.1415const e = 2.7182// 或const ( pi = 3.1415 e = 2.7182 )// 申明并且初始化n := 10
数组的长度固定:
var arr1 = [5]int{1, 2, 3, 4, 5}// 或arr2 := [...]struct { name string age int8}{ {"yangling", 1}, {"baily", 2},}
切片的长度不固定:
// 1.声明切片var s1 []ints2 := []int{}var s3 = make([]int, 0)// 向切片中添加元素s1 = append(s1, 2, 3, 4)// 从切片中按照索引获取切片s1[low:high]// 循环for index, element := range s1 { fmt.Println("索引:", index, ",元素:", element)}
scoreMap := make(map[string]int)scoreMap["张三"] = 90scoreMap["李四"] = 100userInfo := map[string]string{ "username": "baily", "password": "111111",}// 如果key存在ok 为true,v为对应的值;// 如果key不存在ok 为false,v为值类型的零值v, ok := scoreMap["李四"]if ok { fmt.Println(v)} else { fmt.Println("查无此人")}// 循环for k, v := range scoreMap { fmt.Println(k, v)}//将王五从map中删除delete(scoreMap, "王五")
不同的使用方式,可能返回指针,也可能返回值。
// 定义结构体type Student struct { name string age int}func main() { // 使用结构体 // 方式1,返回的是值 var stu1 Student stu1.name = "baily" stu1.age = 1 fmt.Println("baily1:", stu1) // 方式2,返回的是值 var stu2 = Student{ name: "baily", age: 1, } fmt.Println("baily2:", stu2) // 方式3,返回的是指针 stu3 := &Student{ name: "baily", age: 1, } fmt.Println("baily3指针:", stu3) fmt.Println("baily3值:", *stu3) // 方式4,返回的是指针 var stu4 = new(Student) stu4.name = "baily" stu4.age = 1 fmt.Println("baily4指针:", stu4) fmt.Println("baily4值:", *stu4)}
流程控制包括:if、switch、for、range、select、goto、continue、break。主要记下select,其他的跟别的语言类似。主要用于等待资源、阻塞等待等等。
select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。
func main() { var c1 = make(chan int) go func() { time.Sleep(time.Second * 10) c1 <- 1 }() // 此处会一直等到10S到期,通道里有值才会继续往下走。 // 如果增加了 time.After(time.Second * 3) ,则最多3秒则结束 // 如果这2个case都不行,会走default,也可以不设置default select { case i, ok := <-c1: if ok { fmt.Println("取值", i) } case <-time.After(time.Second * 3): fmt.Println("request time out") default: fmt.Println("无数据") }}
// 正常函数func test(x int, y int, s string) (int, string) { n := x + y return n, fmt.Sprintf(s, n)}// 匿名函数func main() { getSqrt := func(a float64) float64 { return math.Sqrt(a) } fmt.Println(getSqrt(4))}
在Go语言中,闭包是一种函数值,它引用了其函数体外部的变量。闭包允许函数访问并处理其外部范围内的变量,即使函数已经返回了,这些外部变量也会被保留在闭包内。
所以说,一个闭包由两部分组成:函数体 和 与其相关的引用外部变量的环境。
当一个函数被定义在另一个函数内部时,并且引用了外部函数的变量,就会创建一个闭包。这个闭包函数可以随时访问和修改外部函数中的变量,即使外部函数已经执行完毕。
func main() { // 外部函数定义并返回内部函数 add := adder() // 通过闭包调用内部函数,increment是闭包函数 fmt.Println(add(1)) // 输出:1 fmt.Println(add(2)) // 输出:3 fmt.Println(add(3)) // 输出:6}// 外部函数,返回一个闭包函数func adder() func(int) int { sum := 0 // 外部函数中的变量 // 闭包函数 return func(x int) int { sum += x // 闭包函数使用了外部函数中的变量 return sum }}
type error interface { //只要实现了Error()函数,返回值为string的都实现了err接口 Error() string}
使用 panic 抛出错误,然后在defer中通过recover捕获异常。
func main() { testPanic()}func testPanic() { defer func() { if err := recover(); err != nil { fmt.Println(err.(string)) } }() panic("抛出异常")}
// 隐式地返回2个值func getCircleArea(radius float32) (area float32, err error) { if radius < 0 { // 构建个异常对象 err = errors.New("半径不能为负") return } area = 3.14 * radius * radius return}func main() { area, err := getCircleArea(-5) if err != nil { fmt.Println(err) } else { fmt.Println(area) }}
可以使用匿名字段:
type Person struct { name string age int}type Student struct { Person id int addr string}func main() { s1 := Student{ Person{"baily", 20}, 1, "南京市雨花台区南京南站", } fmt.Println(s1)}
如果对象内部嵌套的对象有同名字段的情况,只取对象自己的字段:
type Person struct { name string age int}type Student struct { Person id int addr string name string}func main() { var s Student s.name = "baily" s.Person.name = "baily-parent" fmt.Println(s) // 打印出 baily }
一个方法就是一个包含了接受者的函数,接受者可以是 类型或者结构体 的值或者指针。
type Test struct{}// 多参数、多返回值func (t Test) method1(x, y int) (z int, err error) { return}// 多参数、多返回值func (t *Test) method2(x, y int) (z int, err error) { return}
当方法作用于值接收者时,Go语言会在代码运行时将接收者的值复制一份。在值接收者的方法中可以获取接收者的成员值,但修改操作只是针对复制出来的副本,无法修改接收者本身。
而指针接受者,在修改成员时,会修改接受者本身。
// SetAge 设置p的年龄// 使用指针接收者func (p *Person) SetAge(newAge int) { p.age = newAge}// SetAge2 设置p的年龄// 使用值接收者func (p Person) SetAge2(newAge int) { p.age = newAge}func main() { p := new(Person) p.age = 11 p.SetAge(22) // 对象p的age会被改变 fmt.Println(p.age) p.SetAge2(33) // 对象p的age不会被改变 fmt.Println(p.age)}
什么时候应该使用指针接受者?
TCP编程:
// 处理函数func process(conn net.Conn) { defer conn.Close() // 关闭连接 for { reader := bufio.NewReader(conn) var buf [128]byte n, err := reader.Read(buf[:]) // 读取数据 if err != nil { fmt.Println("读取客户端数据失败:", err) break } recvStr := string(buf[:n]) fmt.Println("收到client端发来的数据:", recvStr) conn.Write([]byte("回复客户端:" + recvStr)) // 发送数据 }}func main() { listen, err := net.Listen("tcp", "127.0.0.1:9587") if err != nil { fmt.Println("启动监听异常:", err) return } for { conn, err := listen.Accept() // 建立连接 if err != nil { fmt.Println("没有连接:", err) continue } go process(conn) // 启动一个goroutine处理连接 }}
var wg sync.WaitGroupfunc hello_wg(i int) { defer wg.Done() // goroutine结束就登记-1 fmt.Println("hello_wg!", i)}func main() { for i := 0; i < 10; i++ { wg.Add(1) // 启动一个goroutine就登记+1 go hello_wg(i) time.Sleep(time.Second) } wg.Wait() // 等待所有登记的goroutine都结束}
Go语言的并发模型是CSP(Communicating Sequential Processes),通过通信共享内存,而不是通过共享内存而实现通信。
func recv(c chan int) { ret := <-c fmt.Println("接收成功", ret)}func main() { c := make(chan int) go recv(c) // 启用goroutine从通道接收值 c <- 10 fmt.Println("发送成功") }
func main() { var c1 = make(chan int) go func() { time.Sleep(time.Second * 10) c1 <- 1 }() // 此处会一直等到10S到期,通道里有值才会继续往下走。 // 如果增加了 time.After(time.Second * 3) ,则最多3秒则结束 // 如果这2个case都不行,会走default,也可以不设置default select { case i, ok := <-c1: if ok { fmt.Println("取值", i) } case <-time.After(time.Second * 3): fmt.Println("request time out") default: fmt.Println("无数据") }}
多个go协程操作同一个资源时,会发生并发问题,需要加锁解决。有互斥锁,还有读写锁。
func add() { for i := 0; i < 5000; i++ { // 如果不加锁,此处会有并发问题 lock.Lock() // 加锁 x = x + 1 lock.Unlock() // 解锁 } wg.Done()}func main() { wg.Add(2) go add() go add() wg.Wait() fmt.Println(x) }
文件以_test.go结尾,方法以Test开头,方法入参t *testing.T。
func TestProgram(t *testing.T) { split := strings.Split("a,b,c", ",") defer func() { if err := recover(); err != nil { fmt.Println("异常:", err) } }() findElement(split, "a")}// 查找元素func findElement(split []string, target string) { flag := false for _, e := range split { if e == target { flag = true break } } if flag { fmt.Println("已经找到") } else { panic("没找到") }}
本文链接:http://www.28at.com/showinfo-26-55136-0.htmlGo语言的常用基础
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
下一篇: 通过Uri加载raw目录下的文件