zoukankan      html  css  js  c++  java
  • 补充 2:Golang 一些特性

    Go语言的这些地方都做的还不错:

    • 拥有自动垃圾回收: 不用手动释放内存
    • 一个包系统:

                 Go 语言的源码复用建立在包(package)基础之上。包通过 package, import, GOPATH 操作完成。

            Go 语言的入口 main() 函数所在的包(package)叫 main,main 包想要引用别的代码,需要import导入.

           包需要满足:

    1.  一个目录下的同级文件归属一个包。
    2.  包名可以与其目录不同名
    3. 包名为 main 的包为应用程序的入口包,其他包不能使用
    4.    包中,通过标识符首字母是否大写,来确定是否可以被导出。首字母大写才可以被导出,视为 public 公共的资源。
    5.    要引用其他包,可以使用 import 关键字,可以单个导入或者批量导入,语法演示:

      // 单个导入
      import "package"
      // 批量导入
      import (
        "package1"
        "package2"
        )
    6.    导入时,可以为包定义别名:
    import (
      p1 "package1"
      p2 "package2"
      )
    // 使用时
    p1.Method()

      7.  import导入时,会从GO的安装目录(也就是GOROOT环境变量设置的目录)和GOPATH环境变量设置的目录中,检索 src/package 来导入包。如果不存在,则导入失败。

         GOROOT,就是GO内置的包所在的位置。

              GOPATH,就是我们自己定义的包的位置。

      8. 可以在源码中,定义 init() 函数。此函数会在包被导入时执行,例如如果是在 main 中导入包,包中存在 init(),那么 init() 中的代码会在 main() 函数执行前执行,用于初始化包所需要的特定资料。

      golang的包管理有很大的问题,这个后续有章节单独介绍。

    • 函数作为一等公民
        支持头等函数(First Class Function)的编程语言,可以把函数赋值给变量,也可以把函数作为其它函数的参数或者返回值。Go 语言支持头等函数的机制
    package main
    
    import (  
        "fmt"
    )
    
    func main() {  
        a := func() {
            fmt.Println("hello world first class function")
        }
        a()
        fmt.Printf("%T", a)
    }

      在上面的程序中,我们将一个函数赋值给了变量 a这是把函数赋值给变量的语法。你如果观察得仔细的话,会发现赋值给 a 的函数没有名称。由于没有名称,这类函数称为匿名函数(Anonymous Function)

    输出:

    hello world first class function
    func()

      调用一个匿名函数,也可以不赋值。如下是上面使用的简化版本:

    package main
    
    import (  
        "fmt"
    )
    
    func main() {  
        func() {
            fmt.Println("hello world first class function")
        }()
    }

        而且还可以给匿名函数传参数呢:

    // mainfile
    package main
    
    import (
        "fmt"
    )
    
    func main() {
        func(n string) {
            fmt.Println("Welcome", n)
        }("Gophers")
    }

    输出:

    Welcome Gophers

     正如我们定义自己的结构体类型一样,我们可以定义自己的函数类型

    // mainfile
    package main
    
    import (
        "fmt"
    )
    type add func(a int, b int) int
    func main() {
        var a add = func(a int, b int) int {
            return a + b
        }
        s := a(5, 6)
        fmt.Println("Sum", s)
    }

    高阶函数:

      满足下列条件之一的函数

    1. 接收一个或多个函数作为参数
    package main
    
    import (  
        "fmt"
    )
    
    func simple(a func(a, b int) int) {  
        fmt.Println(a(60, 7))
    }
    
    func main() {  
        f := func(a, b int) int {
            return a + b
        }
        simple(f)
    }

              2.  函数返回函数

    func simple() func(a, b int) int {  
        f := func(a, b int) int {
            return a + b
        }
        return f
    }
    
    func main() {  
        s := simple()
        fmt.Println(s(60, 7))
    }

     下面通过一个例子来看看头灯函数的实际用途:

    首先定义一个 student 类型:

    type student struct {  
        firstName string
        lastName string
        grade string
        country string
    }

    下一步是编写一个 filter 函数。该函数接收一个 students 切片和一个函数作为参数,这个函数会计算一个学生是否满足筛选条件:

    func filter(s []student, f func(student) bool) []student {  
        var r []student
        for _, v := range s {
            if f(v) == true {
                r = append(r, v)
            }
        }
        return r
    }

    filter 的第二个参数是一个函数。这个函数接收 student 参数,返回一个 bool 值。这个函数计算了某一学生是否满足筛选条件。我们在第 3 行遍历了 student 切片,将每个学生作为参数传递给了函数 f。如果该函数返回 true,就表示该学生通过了筛选条件,接着将该学生添加到了结果切片 r 中。

    package main
    
    import (  
        "fmt"
    )
    
    type student struct {  
        firstName string
        lastName  string
        grade     string
        country   string
    }
    
    func filter(s []student, f func(student) bool) []student {  
        var r []student
        for _, v := range s {
            if f(v) == true {
                r = append(r, v)
            }
        }
        return r
    }
    
    func main() {  
        s1 := student{
            firstName: "Naveen",
            lastName:  "Ramanathan",
            grade:     "A",
            country:   "India",
        }
        s2 := student{
            firstName: "Samuel",
            lastName:  "Johnson",
            grade:     "B",
            country:   "USA",
        }
        s := []student{s1, s2}
        f := filter(s, func(s student) bool {
            if s.grade == "B" {
                return true
            }
            return false
        })
        fmt.Println(f)
    }

     在 main 函数中,我们首先创建了两个学生 s1 和 s2,并将他们添加到了切片 s。现在假设我们想要查询所有成绩为 B 的学生。为了实现这样的功能,我们传递了一个检查学生成绩是否为 B 的函数,如果是,该函数会返回 true。我们把这个函数作为参数传递给了 filter 函数(第 38 行)。上述程序会输出

     假设我们想要查找所有来自印度的学生。通过修改传递给 filter 的函数参数,就很容易地实现了。

    c := filter(s, func(s student) bool {  
        if s.country == "India" {
            return true
        }
        return false
    })
    fmt.Println(c)
    • 词法作用域
    • 系统调用接口: 系统调用是程序向操作系统内核请求服务的过程,通常包含硬件相关的服务(例如访问硬盘),创建新进程等。系统调用提供了一个进程和操作系统之间的接口。
    • 只读的UTF8字符串: golang的字符串是只读的unicode字节序列,Go语言使用UTF-8格式编码Unicode字符,每个字符对应一个rune类型。一旦字符串变量赋值之后,内部的字符就不能修改。

    但是Go语言本身只有很少的特性,也不太可能添加太多的特性:

    • 它没有隐式的数值转换,
    • 没有构造函数和析构函数,
    • 没有运算符重载,
    • 没有默认参数,
    • 也没有继承: 

      golang语言中没有继承,但是可以依靠组合来模拟继承和多态。

      但是,这样模拟出来的继承是有局限的,也就是说:在需要多态的时候,需要小心。

    package main  
    
    type ST struct{  
    }  
    
    func (s *ST)Show(){  
        println("ST")  
    }  
    
    func (s *ST)Show2(){  
        println("ST:Show2()")  
    }  
    
    type ST2 struct{  
        ST             // 注意,这里是ST 而不是 st ST !!!!!!!!
        I int  
    }  
    
    func (s *ST2)Show(){  
        println("ST2")  
    }  
    
    func main() {  
        s := ST2{I:5}  
        s.Show()    // ST2自己有show( )方法;
        s.Show2()   // ST2自己没有show2方法
        println(s.I)  
    }  

    输出:

    ST2
    ST:Show2()
    5
    • 没有泛型: 但是Golang也有解决方案:

    看一下冒泡排序:

    package main
     
    import (
        "fmt"
    )
     
    func bubbleSort(array []int) {
        for i := 0; i < len(array); i++ {
            for j := 0; j < len(array)-i-1; j++ {
                if array[j] > array[j+1] {
                    array[j], array[j+1] = array[j+1], array[j]
                }
            }
        }
    }
     
    func main() {
        a1 := []int{3, 2, 6, 10, 7, 4, 6, 5}
        bubbleSort(a1)
        fmt.Println(a1)
    }

    针对上面的排序问题,我们可以分析一下排序的步骤:

      1. 查看切片长度,以用来遍历元素(Len);
      2. 比较切片中的两个元素(Less);
      3. 根据比较的结果决定是否交换元素位置(Swap)。
        到这里,或许你已经明白了,我们可以把上面的函数分解为一个支持任意类型的接口,任何其他类型的数据只要实现了这个接口,就可以用这个接口中的函数来排序了。

           定义接口:

    type Sortable interface{
        Len() int
        Less(int, int) bool
        Swap(int, int)
    }

     下面看一下实现:

    package main
     
    import (
        "fmt"
    )
    
    // 定义接口 type Sortable
    interface { Len() int Less(int, int) bool Swap(int, int) }
    // 对接口编程,实现冒泡排序 func bubbleSort(array Sortable) {
    for i := 0; i < array.Len(); i++ { for j := 0; j < array.Len()-i-1; j++ { if array.Less(j+1, j) { array.Swap(j, j+1) } } } } //实现接口的整型切片 type IntArr []int
    // 实现接口 func (array IntArr) Len()
    int { return len(array) } // 实现接口 func (array IntArr) Less(i int, j int) bool { return array[i] < array[j] } // 实现接口 func (array IntArr) Swap(i int, j int) { array[i], array[j] = array[j], array[i] } //实现接口的字符串,按照长度排序 type StringArr []string
    // 实现接口 func (array StringArr) Len()
    int { return len(array) } // 实现接口 func (array StringArr) Less(i int, j int) bool { return len(array[i]) < len(array[j]) } // 实现接口 func (array StringArr) Swap(i int, j int) { array[i], array[j] = array[j], array[i] } //测试 func main() { intArray1 := IntArr{3, 4, 2, 6, 10, 1} bubbleSort(intArray1) fmt.Println(intArray1) stringArray1 := StringArr{"hello", "i", "am", "go", "lang"} bubbleSort(stringArray1) fmt.Println(stringArray1) }
    • 没有异常,

      在Golang错误处理中,变量名称err遍布各处。不论Golang项目有多大和多重要,普遍的格式化错误结构如下:

      f, err := os.Open(filename)

        if err != nil {

        // Handle the error here

      }

      根据Golang的约定,每个可能导致错误的函数都将error其作为最后一个返回值,码农有责任在每一步都正确处理它,所以golang代码中随处都是"if err != nil"语句。用条件处理每一个错误令人反感,而且非常不好看。

      golang 中的错误处理的哲学和 C 语言一样,函数通过返回错误类型(error)或者 bool 类型(不需要区分多种错误状态时)表明函数的执行结果,调用检查返回的错误类型值是否是 nil 来判断调用结果。

      "这设计有问题,为啥处处要检查错误",估计每一个刚入Golang的人都会有这样的共识。

      在Go以外的语言中(c++, java,python),你需要将所有内容包装在相同的内容中try...catch.

      Golang中,将他严格规定下来:可能失败的每个函数都应该返回一个error类型作为最后一个值,并且随后对其处理。

         golang 中内置的错误类型 error 是一个接口类型,自定义的错误类型也必须实现为 error 接口,这样调用总是可以通过 Error() 获取到具体的错误信息而不用关心错误的具体类型。标准库的 fmt.Errorf 和 errors.New 可以方便的创建 error 类型的变量。

    type error interface {
        Error() string
    }

      golang 的多返回值语法糖(可以返回多个值),错误值一般作为返回值列表的最后一个,其他返回值是成功执行时需要返回的信息。为了避免错误处理时过深的代码缩进。

    if err != nil {
        // error handling
    } else {
        // normal code
    }

           预定义错误:

    func doStuff() error {
        if someCondition {
            return errors.New("no space left on the device")      // errors.NEW -  非常方便的生成新的error
        } else {
            return errors.New("permission denied")
        }
    }

      但是上面的方法不好,因为对返回error进行检查时,需要根据字符串比较才能知道错误的类型,下面是一种优雅的方式:

    var ErrNoSpaceLeft = errors.New("no space left on the device")
    var ErrPermissionDenied = errors.New("permission denied")
    
    func doStuff() error {
        if someCondition {
            return ErrNoSpaceLeft
        } else {
            return ErrPermissionDenied 
        }
    }

    根据下面的方式进行判断:优雅很多

    if err == ErrNoSpaceLeft {
        // handle this particular error
    }

    自定义错误:

      HTTP 表示客户端的错误状态码有几十个。如果为每种状态码都预定义相应的错误值,代码会变得很繁琐:

    var ErrBadRequest = errors.New("status code 400: bad request")
    var ErrUnauthorized = errors.New("status code 401: unauthorized")
    // ...

      这种场景下最佳的最法是自定义一种错误类型,并且至少实现 Error() 方法(满足 error 定义):

    type HTTPError struct {
        Code        int
        Description string
    }
    
    func (h *HTTPError) Error() string {
        return fmt.Sprintf("status code %d: %s", h.Code, h.Description)
    }

    判断的的代码如下:

    func request() error {
        return &HTTPError{404, "not found"}
    }
    
    func main() {
        err := request()
    
        if err != nil {
            // an error occured
            if err.(*HTTPError).Code == 404 {
                // handle a "not found" error
            } else {
                // handle a different error
            }
        }
    
    }

    panic 、recover、 defer  :   会有专门的章节讲解这个专题

    • 没有宏,   没有define(define有副作用:  没有类型检查, 还有富作用)
    • 没有函数修饰,  大写表示public, 小写表示private
    • 更没有线程局部存储
  • 相关阅读:
    mysql在ubuntu中的操作笔记(详)
    Ubuntu16.04上安装MySQL(详细过程)
    Python全栈开发day7
    Python全栈开发day6
    Python全栈开发day5
    Python内置函数总结
    Python全栈开发day4
    Python 集合方法总结
    Python全栈开发day3
    Web前端之CSS_day3-4
  • 原文地址:https://www.cnblogs.com/liufei1983/p/10660105.html
Copyright © 2011-2022 走看看