golang 基础笔记

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)
}

原文地址:https://www.cnblogs.com/khal-Cgg/p/14586212.html