zoukankan      html  css  js  c++  java
  • 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)
    }
    
    
  • 相关阅读:
    决战72hours
    学习中的十七条建议
    数学建模终结篇
    数学建模(7)建模开始
    ASP升级程序
    为blog挑选logo
    Mysql源代码分析系列(4): 主要调用流程(续)转载
    AS学习步骤
    什么是敏捷软件测试[转]
    Mysql源代码分析(6): Plugin架构介绍(续)转载
  • 原文地址:https://www.cnblogs.com/khal-Cgg/p/14586212.html
Copyright © 2011-2022 走看看