Go
7 package 包
- 导入的报必须在代码中使用,否则编译报错。或者:
package main
import (
_ "geometry/rectangle" // 现在暂时没有使用到
)
func main() {
}
- package中首字母大写的函数才可以被调用。
- package包的init函数可以在一个或者多个文件中(按顺序)。
- 所有可执行的go程序必然有main包中的main函数(入口)。
11 array and slice
array是同一类型元素的集合。(?interface{})
数组的声明
package main
import (
"fmt"
)
func main(){
var a[3]int // 长度是3的int型的数组,默认值0
b := [3]int{1,2,3} // 同时给定元素 或者a[0] = 1 进行赋值
c := [...]{1,2,3,4,5} // 不指定长度,但是array不能调整长度 error:c[7]=8、
}
非引用型
package main
import (
"fmt"
)
func change(num[5]string){
num[2] = "haha"
}
func main(){
// *** go中数组是数值,不是引用,改变e,不会导致f变化。
e := [...]string{"USA", "China", "India", "Germany", "France"}
f := e
e[0] = "Singapore"
// f is [USA China India Germany France]
// e is [Singapore China India Germany France]
// 同理传入别的函数后被修改也不变
change(e)
// e 仍旧是[Singapore China India Germany France]
}
数组的迭代
- 利用for循环和len函数根据索引迭代数组。
- 利用range函数迭代,i, v也可以用_忽略。
for i, v := range a {
}
多维数组
package main
import (
"fmt"
)
func main() {
a := [3][2]string{
{"lion", "tiger"},
{"cat", "dog"},
{"pigeon", "peacock"}, // **this comma is necessary. The compiler will complain if you omit this comma. 很多时候代码紧接着,比如 else
}
}
*切片
slice是array的引用
slice的创建
package main
import (
"fmt"
)
func main() {
// 第一种创建方式
a := [5]int{76, 77, 78, 79, 80}
var b []int = a[1:4] // 这里的slice b其实是对数组a的引用。
fmt.Println(b)
// 第二种创建方式
c := []int{6, 7, 8} // 这种情况下 slice的length和capacity都是3
fmt.Println(c)
// 第三种创建方式
i := make([]int, 5, 5) // capacity 是可选默认等于length
fmt.Println(i)
}
slice的修改
来自相同array的slice对元素的修改,相应的按照顺序体现在array上。
length capacity
len(), cap(), 其中可以使用slice=slice[:capacity(slice)]
进行扩容,扩容后元素也进入slice中。
切片的空
判断切片为空用len
var s2 []int
s := []int{}
fmt.Println(s)
fmt.Println(len(s))
fmt.Println(cap(s))
fmt.Println(s==nil)
fmt.Println(s2==nil)
}
切片append
package main
import (
"fmt"
)
func main() {
cars := []string{"Ferrari", "Honda", "Ford"}
cars = append(cars, "Toyota") // cars的length是4,但是capacity是6。
// 这里append会创建新的数组,并且capacity会翻倍。(不一定)
names := []string // length 和 capacity 都是nil
names = append(names, "Tom", "James", "Gabe") // len 3 cap 4?
total := append(cars, names...) // ... 切片相加
}
切片的传递
package main
import (
"fmt"
)
func subtactOne(numbers []int) {
for i := range numbers {
numbers[i] -= 2
}
}
func main() {
nos := []int{8, 7, 6}
fmt.Println("slice before function call", nos)
subtactOne(nos)
fmt.Println("slice after function call", nos) // 这里符合python里面操作
}
多维切片
内存优化
如果一个slice的array特别大,但是用到的slice很小,可以用copy函数复制一下那个
slice,使得原来的array被回收。
可变传参的函数
func test(num int, nums ...int){}
其实nums就是新创建的切片。- nums可以不传参数,此时nums是一个nil的切片。
test(12, []int{1,2,3})
不可以直接传一个slice,因为test函数其实在做:
func test(num int, []int{nums}){}
显然这里只接受int类型不是slice。
类似python,用...来传参:test(12, num...)
- 一些理解:
package main
import (
"fmt"
)
func change(s ...string) {
s[0] = "Go"
s = append(s, "playground")
fmt.Println(s)
}
func main() {
welcome := []string{"hello", "world"}
change(welcome...)
fmt.Println(welcome)
// 结果:s:[Go world playground] welcome:[Go world]
// ** 这里的...没有解包而是把slice的地址传了过去(引用),所以welcome
// 这个slice被修改了。然后change函数中的append是复制新的数组信息,
// 所以影响不到welcome,也就是没有append之前的那个s。
}
*map
make(map[type of key]type of value)
get set
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000 // set 和 get类似
employee := "jamie"
fmt.Println("Salary of", employee, "is", personSalary[employee])
// 获取不存在的key不会报错,返回类型的零值
fmt.Println("Salary of joe is", personSalary["joe"])
// map返回值和存在状态 status bool
value, status := personSalary["mike"]
}
迭代map
for key, value := range names_map{ // 同理遍历没有顺序
}
delete
delete(personSalary, "Tom") // 没返回值, 同理key可以不存在
引用型
package main
import(
"fmt"
)
func main(){
before_maps := map[string]:boolean{"ccc": true, "bbb":false,}
after_maps := before_maps
after_maps["ccc"] = false
fmt.Println("before_maps", before_maps)
}
*字符串
字符串是字节切片
package main
import(
"fmt"
)
func main(){
name := "caoge"
for i:=0;i<len(name);i++{ // len是字符所占字节的长度,有些字符占俩字节
// Señor 比如ñ占俩,len(Señor)结果是6。
fmt.Printf("--->%v", name[i]) // 打印的是utf8编码中字符的位置(数字)
fmt.Printf("--->%c", name[i]) // %c 格式限定符用于打印字符串的字符
}
}
- 利用rune打印字符串
rune_str := []rune("caoge")
将字符串转化成了rune切片,此时len方法和print%c都变成了直观效果。 - for range循环打印字符串
package main
import (
"fmt"
)
func main(){
a := "Señor"
for index, rune := range s{ // rune 不是关键字range的功劳
fmt.Printf("index -->%v", index)
fmt.Printf("index -->%c", rune)
}
}
- 同理string方法将字符切片转回字符
func main() {
runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072}
str := string(runeSlice)
fmt.Println(str)
}
- 通py,字符串不能index后改变,rune可以。
指针
- 指针的零值是nil
var a *int
package main
func main(){
b := 123
a *int = &b // & 获取变量地址
fmt.Printf("a type is %T", a)
fmt.Println("b address is ", a)
}
- 指针的解引用。(根据地址找到value)
fmt.Println("value a is", *a)
- 通过指针来修改值
package main
import(
"fmt"
)
func main(){
b := 123
var a *int
a = &b
fmt.Println("b value is", b)
*a ++
fmt.Println("b value is", b)
}
- 函数传递数组使用切片,不用指针。
- 指针不支持运算。
结构体struct
- 直观印象class。
- struct的声明和使用
type Person struct {
name string
age int
}
// 匿名struct,没有具体的名字,只有实例person。
var person struct {
name string
age int
}
person := struct{
name string
age int
}{
name "Caoge"
age 18
}
// 同理不一定所有字段都需要赋值(默认为类型的零值),也可
以通过.进行赋值或者访问。
- 匿名字段:默认字段名称是类型名称
type Person struct {
string
int
}
- 结构体字段也可以是结构体(嵌套结构体)
- 提升字段:匿名字段是另个一个结构体的,可直接用.来访问其字段。
package main
import (
"fmt"
)
func main(){
type Job struct {
companyName string
jobAge int
}
type Person struct{
name string // 定义不用逗号
age int
Job
}
/*
p := Person{
name: "caoge",
age: 18,
job, // error 这里直接写JOB的字段就行,或者通过.设置
}
*/
var p Person
p.name = "caoge"
p.age = 18
p.jobAge = 2 // 直接设置,或者Job=job
fmt.Println("lets see", p.name, p.jobAge)
}
- 同理首字母大写的类和字段才能被import。(相对路径./ )
方法
- 类似类的方法,因此可以同名。
package main
import (
"fmt"
"math"
)
type Rectangle struct {
length int
width int
}
type Circle struct {
radius float64
}
func (r Rectangle) Area() int { // r 接收器 自定
return r.length * r.width
}
func (c Circle) Area() float64 {
return math.Pi * c.radius * c.radius
}
func main() {
r := Rectangle{
length: 10,
5,
}
fmt.Printf("Area of rectangle %d
", r.Area()) // 调用方式
c := Circle{
radius: 12,
}
fmt.Printf("Area of circle %f", c.Area())
}
- 指针接收器和值接收器的区别
package main
import (
"fmt"
)
type Person struct{
Name string
Age int
}
func (a Person) change_name() {
a.Name = "zz"
}
func (a *Person) change_age() {
a.Age = 19
}
func main() {
p := Person{
Name: "caoge",
Age: 18,
}
// 先用值接收器修改
p.change_name()
fmt.Println("Is name change:", p.Name) // struct被copy
p.change_age()
fmt.Println("Is Age change:", p.Age) // 指针接收器值被改变。
// (&P).change_age() go中指针和对象往往都能调用方法。
// Go语言把 p.change_age() 解释为 (*p).change_age()。
}
- 同理匿名字段是struct的,其方法可以被直接调用。
接口
- 接口定义了一个对象的行为(这里的定义只是定义的意思,具体这个行为如何执行每个类(对象)的行为不一样)。
- 接口类似一些方法的集合(但是只有一个方法的名字)。
- 一个类拥有这些方法就隐式地实现了这个接口。
- 作用:假如有俩类,A和B。要计算A和B上的某些属性的总和,比如A.s A.j 和B的B.i。A和B同样名字的方法α用来分别计算自己是和,在python中只要写一个函数β接收一个list,list里面放着有函数名字为α的类的实例,在β中调用这些实例的α再相加就好。但是go中将不同的struct进行统一处理的方式就是interface。
package main
import (
"fmt"
)
type SalaryCalculator interface {
CalculateSalary() int
}
type Permanent struct {
empId int
basicpay int
pf int
}
type Contract struct {
empId int
basicpay int
}
//salary of permanent employee is sum of basic pay and pf
func (p Permanent) CalculateSalary() int {
return p.basicpay + p.pf
}
//salary of contract employee is the basic pay alone
func (c Contract) CalculateSalary() int {
return c.basicpay
}
/*
total expense is calculated by iterating though the SalaryCalculator slice and summing
the salaries of the individual employees
*/
func totalExpense(s []SalaryCalculator) {
expense := 0
for _, v := range s {
expense = expense + v.CalculateSalary()
}
fmt.Printf("Total Expense Per Month $%d", expense)
}
func main() {
pemp1 := Permanent{1, 5000, 20}
pemp2 := Permanent{2, 6000, 30}
cemp1 := Contract{3, 3000}
employees := []SalaryCalculator{pemp1, pemp2, cemp1}
totalExpense(employees)
}
- 空接口:若一个函数的参数是一个interface,则可以利用interface接收任意类型。
package main
import (
"fmt"
)
type Test interface {
Tester()
}
type MyFloat float64
func (m MyFloat) Tester() {
fmt.Println(m)
}
func describe(t Test) {
fmt.Printf("Interface type %T value %v
", t, t)
}
func main() {
var t Test
f := MyFloat(89.7)
t = f
describe(t)
t.Tester()
}
- 断言
func assert(i interface{}) {
s := i.(int) // 不是int就报错 或者用 v, ok := i.(int) 其中ok是Bool
fmt.Println(s)
}
func main() {
var s interface{} = 56
assert(s)
}
- 断言不够用的话有switch
package main
import "fmt"
type Describer interface {
Describe()
}
type Person struct {
name string
age int
}
func (p Person) Describe() {
fmt.Printf("%s is %d years old", p.name, p.age)
}
func findType(i interface{}) {
switch v := i.(type) {
case Describer:
v.Describe()
default:
fmt.Printf("unknown type
")
}
}
func main() {
findType("Naveen")
p := Person{
name: "Naveen R",
age: 25,
}
findType(p) // 之前说一个类型满足接口就隐式实现该接口,所以这里是Describer
}
- 接口无法获取值的地址(复习:值或者指针都能调用类方法),因此当接口的某个方法接收的参数是指针时,要赋值指针。比如:t = &a
- 一个类型可以实现多个接口
- 一个接口可以嵌套多个接口
协程
- 关键词go
package main
import(
"time"
"fmt"
)
func hello(){
time.Sleep(4 * time.Second)
fmt.Printf("hello")
}
func main(){
fmt.Println("func main start")
go hello() // 没有阻塞主协程
time.Sleep(2 * time.Second)
fmt.Println("func main end")
}
channel
ch := make(chan int)
- channel是有类型的
- 零值是nil var ch chan int
- 读取和接收 a := <- ch ; ch <- a 读取也可以 <- ch 不设置变量接收
- 死锁 :没有协程去接收触发panic。
- 双向信道可转化只读只写信道,反之不行。
- v, ok := <- ch ok:是否关闭。也可以用 for range循环关闭自动结束。
缓冲信道:channel的阻塞
WaitGroup :主协程等待多协程
package main
import (
"fmt"
"sync"
)
var x = 0
func increment(wg *sync.WaitGroup) {
x = x + 1
wg.Done() // 计数-1
}
func main() {
var w sync.WaitGroup
for i := 0; i < 1000; i++ {
w.Add(1) // 计数 +1
go increment(&w)
}
w.Wait() // 阻塞主协程 计数为0后解除
fmt.Println("final value of x", x)
}
工作池:利用WaitGroup和channel分配任务给协程
select
package main
import "fmt"
func main() {
ch := make(chan string)
select {
case <-ch: // select阻塞等待第一个或者随机
default:
fmt.Println("default case executed")
}
}
Mutex
- 多个协程操作一个变量发生的竞争用mutex提供的lock锁锁定
- 利用容量为1的缓冲信道也可以实现(利用信道的阻塞)。
面向对象
go中的struct类似python中的对象,但是没有构造方法init。手动创建一个方法来生成默认的对象(结构)即可。不然拿到的对象的属性都是nil。
继承
同理结构体之间的关系可以用组合代替继承。(就是嵌套)
多肽
利用接口实现
defer
- 当函数要结束时,调用defer后面的函数
- defer取参数的值是在其定义的那行
- 多个defer执行顺序与声明顺序相反
错误处理
--错误是可预见性,异常不可预见并会导致程序终止(recover可以挽回)。error.New()
创建一个错误类型,go中很多error都要判断是否nil。python中os.open打开一个文件不存在就raise error,go中要先判断error存在性。
-- panic 做了什么:
当函数发生 panic 时,它会终止运行,在执行完所有的延迟函数后,程序控制返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic 信息,接着打印出堆栈跟踪,最后程序终止。
package main
import (
"fmt"
)
func fullName(firstName *string, lastName *string) {
defer fmt.Println("deferred call in fullName")
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s
", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}
func main() {
defer fmt.Println("deferred call in main")
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
deferred call in fullName
deferred call in main
panic: runtime error: last name cannot be nil
goroutine 1 [running]:
main.fullName(0x1042bf90, 0x0)
/tmp/sandbox060731990/main.go:13 +0x280
main.main()
/tmp/sandbox060731990/main.go:22 +0xc0
- recover() 可以接棒panic,但是必须在defer中。
- recover只能接棒自己协程中的panic
- recover后堆栈信息在runtime/debug 中的PrintStack函数中。
reflect
清晰优于聪明。而反射并不是一目了然的。
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
type employee struct {
name string
id int
address string
salary int
country string
}
func createQuery(q interface{}) {
if reflect.ValueOf(q).Kind() == reflect.Struct { // ValueOf 结构体的value
t := reflect.TypeOf(q).Name() // TypeOf : main.order Name:order
query := fmt.Sprintf("insert into %s values(", t)
v := reflect.ValueOf(q)
for i := 0; i < v.NumField(); i++ { // NumField: 字段数量
switch v.Field(i).Kind() { // Field: v.Field(0) 第0个字段
case reflect.Int:
if i == 0 {
query = fmt.Sprintf("%s%d", query, v.Field(i).Int())
} else {
query = fmt.Sprintf("%s, %d", query, v.Field(i).Int())
}
case reflect.String:
if i == 0 {
query = fmt.Sprintf("%s"%s"", query, v.Field(i).String())
} else {
query = fmt.Sprintf("%s, "%s"", query, v.Field(i).String()) // Int String 转化方法
}
default:
fmt.Println("Unsupported type")
return
}
}
query = fmt.Sprintf("%s)", query)
fmt.Println(query)
return
}
fmt.Println("unsupported type")
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
e := employee{
name: "Naveen",
id: 565,
address: "Coimbatore",
salary: 90000,
country: "India",
}
createQuery(e)
i := 90
createQuery(i)
}