Go语言初探

作者: 技术学习 | 来源:发表于2016-08-31 11:46 被阅读831次

    官方网站:https://golang.org/
    标准库文档:https://golang.org/pkg/
    在线编码学习:https://play.golang.org/
    PS:如若访问不了,请自行FQ。

    简介
    安装
    Hello World 实例
    编译
    基础语法
      行分隔符
      注释
      标识符
      关键字
    数据类型
      指针类型(Pointer)
      数组(Array)
      结构化类型(struct)
      接口类型(interface)
      Map 类型
      Any类型
      类型转换
      类型增加方法
    变量作用域
    值类型和引用类型
    常量
    iota
    运算符
    运算符优先级
    条件与循环语句
    函数
      返回多个值
      参数
      匿名函数与闭包
    范围(Range)
    递归
    查询
      接口查询
      类型查询
    错误处理
    面向对象
      成员的可访问性
      封装
      继承
      多态
    goroutine和channel
    同步
    消息传递
    多个channel
    参考
    

    简介

    (大家好,我是go吉祥物,地鼠君)
      Go语言是谷歌推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发Go,是因为过去10多年间软件开发的难度令人沮丧。
      Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人(真是豪门出身),并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。
      Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。
      Go的目标是希望提升现有编程语言对程序库等依赖性(dependency)的管理,这些软件元素会被应用程序反复调用。由于存在并行编程模式,因此这一语言也被设计用来解决多处理器的任务。
      如果第一次接触Go语言,更像是对c语言的修补和延伸。
    ** 特性 **
    • 编译型语言,执行效率接近c/c++,可以编译成机器码,不依赖其他库
    • 自动垃圾回收
    • 更丰富的内置类型和自动类型推导(类似c++11的auto)
    • 函数可以返回多个值
    • 拥有更好的错误处理
    • 匿名函数和闭包
    • 支持类型和接口
    • 并发编程
    • 支持反射机制
    • 语言交互性强
    • 工程依赖自动推导
    • 打包和部署容易

    安装

    $ sudo apt-get install golang 
    $ sudo apt-get install gccgo 
    $ go version 
    go version go1.2.1 linux/amd64
    

    Hello World 实例

    package main
    
    import"fmt"
    
    func main(){/* 这是我的第一个简单的程序 */
        fmt.Println("Hello, World!")
    }
    
    //执行
    $ go run hello.go
    Hello,World!
    

    编译

    Go是一个编译型的语言。目前有两种编译器,其中”Gccgo”采用GCC作为编译后端。另外还有 根据处理器架构命名的编译器:针对64位x86结构为”6g”,针对32位x86结构的为”8g”等等。 这些go专用的编译器编译很快,但是产生的目标代码效率比gccgo稍差一点。

    基础语法

    行分隔符

    在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。 如果你打算将多个语句写在同一行,它们则必须使用 ;人为区分,但在实际开发中我们并不鼓励这种做法。

    注释

    注释不会被编译,每一个包应该有相关注释。 单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾。如:

    // 单行注释
    /*
    Author by Tonny
    我是多行注释
    */
    

    标识符

    标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(AZ和az)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。

    关键字

    Go 代码中会使用到的 25 个关键字或保留字,Go 语言还有 36 个预定义标识符。

    break default func interface select case defer go 
    map struct chan else goto package switch const 
    fallthrough if range type continue for import return var 
    ---
    append bool byte cap close complex complex64 complex128 uint16 copy 
    false float32 float64 imag int int8 int16 uint32 int32 int64 iota len make 
    new nil panic uint64 print println real recover string true uint uint8 uintptr 
    

    程序一般由关键字、常量、变量、运算符、类型和函数组成。 程序中可能会使用到这些分隔符:括号 (),中括号 [] 和大括号 {}。 程序中可能会使用到这些标点符号:.、,、;、: 和 …。

    数据类型

    在 Go 编程语言中,数据类型用于声明函数和变量。 数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。
    ***布尔型 ***
    布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。

    ***数字类型 ***
    整型 int 和浮点型 float,Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码。
    uint8
    无符号 8 位整型 (0 到 255)
    uint16
    无符号 16 位整型 (0 到 65535)
    uint32
    无符号 32 位整型 (0 到 4294967295)
    uint64
    无符号 64 位整型 (0 到 18446744073709551615)
    int8
    有符号 8 位整型 (-128 到 127)
    int16
    有符号 16 位整型 (-32768 到 32767)
    int32
    有符号 32 位整型 (-2147483648 到 2147483647)
    int64
    有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)
    float32
    IEEE-754 32位浮点型数
    float64
    IEEE-754 64位浮点型数
    complex64
    32 位实数和虚数
    complex128
    64 位实数和虚数
    byte
    类似 uint8
    rune
    类似 int32
    uint
    32 或 64 位
    int
    与 uint 一样大小
    uintptr
    无符号整型,用于存放一个指针

    字符串类型
      字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。Go语言的字符串的字节使用UTF-8编码标识Unicode文本。

    指针类型(Pointer)

    Go 语言中指针是很容易学习的,Go 语言中使用指针可以更简单的执行一些任务。我们都知道,变量是一种使用方便的占位符,用于引用计算机内存地址。 Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。
      一个指针变量可以指向任何一个值的内存地址它指向那个值的内存地址。 类似于变量和常量,在使用指针前你需要声明指针。 在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。

    package main
    
    import"fmt"
    
    func main(){
        var a int = 20/* 声明实际变量 */
        var ip *int/* 声明指针变量 */
        ip = &a /* 指针变量的存储地址 */
        fmt.Printf("a 变量的地址是: %x\n",&a )
        /* 指针变量的存储地址 */
        fmt.Printf("ip 变量的存储地址: %x\n", ip )
        /* 使用指针访问值 */
        fmt.Printf("*ip 变量的值: %d\n",*ip )
    }
    

    当一个指针被定义后没有分配到任何变量时,它的值为 nil。 nil 指针也称为空指针。 nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。 一个指针变量通常缩写为 ptr。

    if(ptr !=nil)/* ptr 不是空指针 */
    if(ptr ==nil)/* ptr 是空指针 */
    

    如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。 当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址:


    import"fmt"
    
    func main(){
        var a int
        var ptr *int
        var pptr **int
        a =3000
        /* 指针 ptr 地址 */
        ptr =&a
    
        /* 指向指针 ptr 地址 */
        pptr =&ptr
    
        /* 获取 pptr 的值 */
        fmt.Printf("变量 a = %d\n", a )
        fmt.Printf("指针变量 *ptr = %d\n",*ptr )
        fmt.Printf("指向指针的指针变量 **pptr = %d\n",**pptr)
    }
    

    数组(Array)

    package main
    
    import "fmt"
    
    func main() {
        var n [10]int /* n 是一个长度为 10 的数组 */
        var i, j int
        /* 为数组 n 初始化元素 */
        for i = 0; i < 10; i++ {
            n[i] = i + 100 /* 设置元素为 i + 100 */
        }
        /* 输出每个数组元素的值 */
        for j = 0; j < 10; j++ {
            fmt.Printf("Element[%d] = %d\n", j, n[j])
        }
    }
    

    结构化类型(struct)

    Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。 Go语言的结构体(struct)和其它语言的类(class)有同等的地位。但Go语言放弃了包括继承在内的大量OOP特性,只保留了组合(compose)这个最基础的特性。 所有的Go语言的类型(指针类型除外)都是可以有自己的方法。在这个背景下,Go语言的结构体(struct)它只是很普通的复合类型。

    package main
    
    import"fmt"
    
    type Booksstruct{
        title string
        author string
        subject string
        book_id int
    }
    
    func main(){
        var Book1 Books/* 声明 Book1 为 Books 类型 */
        var Book2 Books/* 声明 Book2 为 Books 类型 */
        /* book 1 描述 */
        Book1.title ="Go 语言"
        Book1.author ="https://tour.go-zh.org"
        Book1.subject ="Go 语言教程"
        Book1.book_id =6495407
        
        /* book 2 描述 */
        Book2.title ="Python 教程"
        Book2.author ="https://tour.python-zh.org"
        Book2.subject ="Python 语言教程"
        Book2.book_id =6495700
        /* 打印 Book1 信息 */
        printBook(Book1)
        /* 打印 Book2 信息 */
        printBook(Book2)
    }
    
    func printBook( book Books){
        fmt.Printf("Book title : %s\n", book.title);
        fmt.Printf("Book author : %s\n", book.author);
        fmt.Printf("Book subject : %s\n", book.subject);
        fmt.Printf("Book book_id : %d\n", book.book_id);
    }
    

    构造函数?不需要。在Go语言中你只需要定义一个普通的函数,只是通常以NewXXX来命名,表示“构造函数”:

    func NewRect(x, y, width, height float64) *Rect { 
        return &Rect{x, y, width, height} 
    }
    

    这一切非常自然,没有任何突兀之处。

    切片(Slice)

    Go语言切片是对数组的抽象。
      Go数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比:

    • 切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
    • 切片是可索引的,并且可以由 len()方法获取长度。
    • 切片提供了计算容量的方法 cap()可以测量切片最长可以达到多少。
    • 一个切片在未初始化之前默认为nil,长度为0
    • 可以通过设置下限及上限来设置截取切片[lower-bound:upper-bound]
    package main
      
    import "fmt"
      
    func main() {
        /* 创建切片 */
        numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
        printSlice(numbers)
        /* 打印原始切片 */
        fmt.Println("numbers ==", numbers)
        /* 打印子切片从索引1(包含) 到索引4(不包含)*/
        fmt.Println("numbers[1:4] ==", numbers[1:4])
        /* 默认下限为 0*/
        fmt.Println("numbers[:3] ==", numbers[:3])
        /* 默认上限为 len(s)*/
        fmt.Println("numbers[4:] ==", numbers[4:])
        numbers1 := make([]int, 0, 5)
        printSlice(numbers1)
        /* 打印子切片从索引 0(包含) 到索引 2(不包含) */
        number2 := numbers[:2]
        printSlice(number2)
        /* 打印子切片从索引 2(包含) 到索引 5(不包含) */
        number3 := numbers[2:5]
        printSlice(number3)
    }
      
    func printSlice(x []int) {
        fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
    }
    //输出结果
    len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]
    numbers == [0 1 2 3 4 5 6 7 8]
    numbers[1:4] == [1 2 3]
    numbers[:3] == [0 1 2]
    numbers[4:] == [4 5 6 7 8]
    len=0 cap=5 slice=[]
    len=2 cap=9 slice=[0 1]
    len=3 cap=7 slice=[2 3 4]
    
    package main
    
    import "fmt"
    
    func main() {
        var numbers []int
        printSlice(numbers)
        /* 允许追加空切片 */
        numbers = append(numbers, 0)
        printSlice(numbers)
        /* 向切片添加一个元素 */
        numbers = append(numbers, 1)
        printSlice(numbers)
        /* 同时添加多个元素 */
        numbers = append(numbers, 2, 3, 4)
        printSlice(numbers)
        /* 创建切片 numbers1 是之前切片的两倍容量*/
        numbers1 := make([]int, len(numbers), (cap(numbers))*2)
        /* 拷贝 numbers 的内容到 numbers1 */
        copy(numbers1, numbers)
        printSlice(numbers1)
    }
    
    func printSlice(x []int) {
        fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
    }
    

    接口类型(interface)

    Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

    package main
    
    import"fmt"
    
    type Phoneinterface{
        call()
    }
    
    type NokiaPhonestruct{
    }
    
    func (nokiaPhone NokiaPhone) call(){
        fmt.Println("I am Nokia, I can call you!")
    }
    
    type IPhonestruct{
    }
    
    func (iPhone IPhone) call(){
        fmt.Println("I am iPhone, I can call you!")
    }
    
    func main(){
        var phone Phone
        phone =new(NokiaPhone)
        phone.call()
        phone =new(IPhone)
        phone.call()
    }
    

    Map 类型

    Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。 Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。

    package main
    
    import "fmt"
    
    func main() {
        /* 创建 map */
        countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New Delhi"}
        fmt.Println("原始 map")
        /* 打印 map */
        for country := range countryCapitalMap {
            fmt.Println("Capital of", country, "is", countryCapitalMap[country])
        }
        /* 删除元素 */
        delete(countryCapitalMap, "France")
        fmt.Println("Entry for France is deleted")
        fmt.Println("删除元素后 map")
        /* 打印 map */
        for country := range countryCapitalMap {
            fmt.Println("Capital of", country, "is", countryCapitalMap[country])
        }
    }
    

    Any类型

    由于Go语言中任何对象实例都满足空接口interface{},故此interface{}看起来像是可以指向任何对象的Any类型。如下:

    var v1 interface{}=1// 将int类型赋值给interface{}
    var v2 interface{}="abc"// 将string类型赋值给interface{}
    var v3 interface{}=&v2 // 将*interface{}类型赋值给interface{}
    var v4 interface{}=struct{ X int}{1}
    var v5 interface{}=&struct{ X int}{1}
    

    当一个函数可以接受任意的对象实例时,我们会将其声明为interface{}。最典型的例子是标准库fmt中PrintXXX系列的函数。例如:

    func Printf(fmt string, args ...interface{})
    func Println(args ...interface{})
    

    类型转换

    类型转换用于将一种数据类型的变量转换为另外一种类型的变量。Go 语言类型转换基本格式如下:

    type_name(expression)

    type_name 为类型,expression 为表达式。

    package main
    
    import "fmt"
    
    func main() {
        var sum int = 17
        var count int = 5
        var mean float32
        mean = float32(sum) / float32(count)
        fmt.Printf("mean 的值为: %f\n", mean)
    }
    

    类型增加方法

    在Go语言中,你可以给任意类型(包括内置类型,但指针类型除外)增加方法,例如:

    type Integer int
    
    func (a Integer) Less(b Integer) bool {
        return a < b
    }
    

    在Go语言中没有隐藏的this指针。这句话的含义是:
    第一,方法施加的目标(也就是“对象”)显式传递,没有被隐藏起来。
    第二,方法施加的目标(也就是“对象”)不需要非得是指针,也不用非得叫this。
      在这个例子中,我们定义了一个新类型Integer,它和int没有本质不同,只是它为内置的int类型增加了个新方法:Less。如此,你就可以让整型看起来像个类那样用:

    func main() { 
        var a Integer = 1 
        if a.Less(2) { 
            fmt.Println(a, “Less 2”) 
        } 
    }
    

    变量

    Go语言变量名由字母、数字、下划线组成,其中首个字母不能为数字。
    声明变量的一般形式是使用var关键字:

    var identifier type
    //指定变量类型,声明后若不赋值,使用默认值。
    var v_name v_type
    v_name = value
    //根据值自行判定变量类型。
    var v_name = value
    v_name := value
    // 省略var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误。
    var a int = 10
    var b = 10
    c : = 10
    //类型相同多个变量, 非全局变量
    var x, y int
    var ( // 这种因式分解关键字的写法一般用于声明全局变量
        a int
        b bool
    )
    var c, d int = 1, 2
    var e, f = 123, "hello"
    //这种不带声明格式的只能在函数体中出现
    //g, h := 123, "hello"
    func main(){
        g, h := 123, "hello"
        println(x, y, a, b, c, d, e, f, g, h)
    }
    

    变量作用域

    作用域为已声明标识符所表示的常量、类型、变量、函数或包在源代码中的作用范围。 Go 语言中变量可以在三个地方声明:

    • 函数内定义的变量称为局部变量
    • 函数外定义的变量称为全局变量
    • 函数定义中的变量称为形式参数

    值类型和引用类型

    多数Go语言中的类型,包括:
      基本类型。如byte、int、bool、float32、float64、string等等。
      复合类型。如数组(array)、结构体(struct)、指针(pointer)等。都基于值语义。Go语言中类型的值语义表现得非常彻底。我们这么说是因为数组(array)。如果你学习过C语言,你会知道C语言中的数组(array)比较特别。通过函数传递一个数组的时候基于引用语义,但是在结构体中定义数组变量的时候是值语义(表现在结构体赋值的时候,该数组会被完整地拷贝一份新的副本)。
    有4种引用类型:slice,map,channel,interface。
      值类型的变量的值存储在栈(stack)中。 当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝 可以通过 &i 来获取变量 i 的内存地址,例如:0xf840000040(每次的地址都可能不一样)。 内存地址会根据机器的不同而有所不同,甚至相同的程序在不同的机器上执行后也会有不同的内存地址。因为每台机器可能有不同的存储器布局,并且位置分配也可能不同。 更复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。
      一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置 这个内存地址为称之为指针,这个指针实际上也被存在另外的某一个字中。 同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。 当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。 如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。

    常量

    常量是一个简单值的标识符,在程序运行时,不会被修改的量。 常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。 常量的定义格式:

    package main
    
    import "fmt"
    
    func main() {
        const LENGTH int = 10
        const WIDTH int = 5
        var area int
        const a, b, c = 1, false, "str" //多重赋值
        area = LENGTH * WIDTH
        fmt.Printf("面积为 : %d", area)
        println()
        println(a, b, c)
    }
    
    //常量还可以用作枚举
    const (
        Unknown = 0
        Female  = 1
        Male    = 2
    )
    
    //常量可以用len(), cap(), unsafe.Sizeof()常量计算表达式的值。
    const (
        a = "abc"
        b = len(a)
        c = unsafe.Sizeof(a)
    )
    

    iota

    iota,特殊常量,可以认为是一个可以被编译器修改的常量。 在每一个const关键字出现时,被重置为0,然后再下一个const出现之前,每出现一次iota,其所代表的数字会自动增加1。

    package main
    
    import "fmt"
    
    func main() {
        const (
            a = iota //0
            b        //1
            c        //2
            d = "ha" //独立值,iota += 1
            e        //"ha" iota += 1
            f = 100  //iota +=1
            g        //100 iota +=1
            h = iota //7,恢复计数
            i        //8
        )
        fmt.Println(a, b, c, d, e, f, g, h, i)//#0 1 2 ha ha 100 100 7 8
    }
    

    运算符

    运算符用于在程序运行时执行数学或逻辑运算。 Go 语言内置的运算符有:(所有语言通用,略过)

    • 算术运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 赋值运算符
    • 其他运算符
        其中 & 返回变量存储地址 &a; 将给出变量的实际地址。
    • 指针变量。 *a; 是一个指针变量

    运算符优先级

    有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:
    ^ !
    * / % << >> & &^
    + - | ^
    == != < <= >= >
    <-
    &&
    ||

    条件与循环语句

    package main
    
    import "fmt"
    
    func main() {
        var b int = 15
        var a int
        numbers := [6]int{1, 2, 3, 5}
        /* for 循环 */
        for a := 0; a < 10; a++ {
            fmt.Printf("a 的值为: %d\n", a)
            /* 使用 break 语句跳出循环 */
            break
            /* 跳过此次循环 */
            continue
        }
        for a < b {
            a++
            fmt.Printf("a 的值为: %d\n", a)
        }
        for i, x := range numbers {
            fmt.Printf("第 %d 位 x 的值 = %d\n", i, x)
        }
        /* 循环 */
    LOOP:
        for a < 20 {
            if a == 15 {
                /* 跳过迭代 */
                a = a + 1
                goto LOOP
            }
            fmt.Printf("a的值为 : %d\n", a)
            a++
        }
        for true {
            fmt.Printf("这是无限循环。\n")
        }
    }
    

    函数

    Go 语言最少有个 main() 函数。 你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。 函数声明告诉了编译器函数的名称,返回类型,和参数。 Go 语言标准库提供了多种可动用的内置的函数。例如,len() 函数可以接受不同类型参数并返回该类型的长度。如果我们传入的是字符串则返回字符串的长度,如果传入的是数字,则返回数组中包含的函数个数。

    package main
    
    import "fmt"
    
    func main() {
        /* 定义局部变量 */
        var a int = 100
        var b int = 200
        var ret int
        /* 调用函数并返回最大值 */
        ret = max(a, b)
        fmt.Printf("最大值是 : %d\n", ret)
    }
    
    /* 函数返回两个数的最大值 */
    func max(num1, num2 int) int {
        /* 定义局部变量 */
        var result int
        if num1 > num2 {
            result = num1
        } else {
            result = num2
        }
        return result
    }
    

    返回多个值

    package main
    
    import "fmt"
    
    func swap(x, y string) (string, string) {
        return y, x
    }
    func main() {
        a, b := swap("Mahesh", "Kumar")
        fmt.Println(a, b)
    }
    

    参数

    函数如果使用参数,该变量可称为函数的形参。 形参就像定义在函数体内的局部变量。 调用函数,可以通过两种方式来传递参数:
    值传递:值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
    引用传递:引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
      默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。

    匿名函数与闭包

    Go 语言支持匿名函数,可作为闭包(闭包就是能够读取其他函数内部变量的函数)。匿名函数是一个”内联”语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

    package main
    
    import "fmt"
    
    func getSequence() func() int {
        i := 0
        return func() int {
            i += 1
            return i
        }
    }
    func main() {
        /* nextNumber 为一个函数,函数 i 为 0 */
        nextNumber := getSequence()
        /* 调用 nextNumber 函数,i 变量自增 1 并返回 */
        fmt.Println(nextNumber())
        fmt.Println(nextNumber())
        fmt.Println(nextNumber())
        /* 创建新的函数 nextNumber1,并查看结果 */
        nextNumber1 := getSequence()
        fmt.Println(nextNumber1())
        fmt.Println(nextNumber1())
    }
    

    范围(Range)

    Go 语言中 range 关键字用于for循环中迭代数组(array)、切片(slice)、链表(channel)或集合(map)的元素。在数组和切片中它返回元素的索引值,在集合中返回 key-value 对的 key 值。

    package main
    
    import "fmt"
    
    func main() {
        //这是我们使用range去求一个slice的和。使用数组跟这个很类似
        nums := []int{2, 3, 4}
        sum := 0
        for _, num := range nums {
            sum += num
        }
        fmt.Println("sum:", sum)
        //在数组上使用range将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。
        for i, num := range nums {
            if num == 3 {
                fmt.Println("index:", i)
            }
        }
        //range也可以用在map的键值对上。
        kvs := map[string]string{"a": "apple", "b": "banana"}
        for k, v := range kvs {
            fmt.Printf("%s -> %s\n", k, v)
        }
        //range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
        for i, c := range "go" {
            fmt.Println(i, c)
        }
    }
    /*
    sum: 9
    index: 1
    a -> apple
    b -> banana
    0 103
    1 111
    */
    

    递归

    递归,就是在运行的过程中调用自己。 Go 语言支持递归。但我们在使用递归时,开发者需要设置退出条件,否则递归将陷入无限循环中。 递归函数对于解决数学上的问题是非常有用的,就像计算阶乘,生成斐波那契数列等。

    package main
    
    import "fmt"
    
    func Factorial(x int) (result int) {
        if x == 0 {
            result = 1
        } else {
            result = x * Factorial(x-1)
        }
        return
    }
    func main() {
        var i int = 15
        fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(i))
    }
    

    查询

    接口查询

    var file1 Writer=...
    if file5, ok := file1.(two.IStream); ok {
        ...
    }
    

    这个if语句的含义是:file1接口指向的对象实例是否实现了two.IStream接口呢?如果实现了,则… 接口查询是否成功,要在运行期才能够确定。它不像接口赋值,编译器只需要通过静态类型检查即可判断赋值是否可行。 让语言内置接口查询,这是一件非常了不起的事情。

    类型查询

    在Go语言中,你还可以更加直接了当地询问接口指向的对象实例的类型。 就像现实生活中物种多得数不清一样,语言中的类型也多的数不清。所以类型查询并不经常被使用。它更多看起来是个补充,需要配合接口查询使用。

    type Stringerinterface{
        String()string
    }
    func Println(args ...interface{}){
        for _, arg := range args {
        switch v := v1.(type){
            case int:// 现在v的类型是int
            case string:// 现在v的类型是string
            default:
                if v, ok := arg.(Stringer); ok {// 现在v的类型是Stringer
                    val := v.String()
                    ...
                }else{
                    ...
                }
        }
    }
    

    利用反射(reflect)也可以进行类型查询,详细可参阅reflect.TypeOf方法相关文档。

    错误处理

    Go 语言通过内置的错误接口提供了非常简单的错误处理机制。 error类型是一个接口类型,这是它的定义:

    type error interface{
        Error()string
    }
    

    我们可以在编码中通过实现 error 接口类型来生成错误信息。 函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:

    package main
    
    import "fmt"
    
    // 定义一个 DivideError 结构
    type DivideError struct {
        dividee int
        divider int
    }
    
    // 实现 `error` 接口
    func (de *DivideError) Error() string {
        strFormat := `
                    Cannot proceed, the divider is zero.
                    dividee: %d
                    divider: 0
                    `
        return fmt.Sprintf(strFormat, de.dividee)
    }
    
    // 定义 `int` 类型除法运算的函数
    func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
        if varDivider == 0 {
            dData := DivideError{
                dividee: varDividee,
                divider: varDivider,
            }
            errorMsg = dData.Error()
            return
        } else {
            return varDividee / varDivider, ""
        }
    }
    
    func Sqrt(f float64) (float64, error) {
        if f < 0 {
            return 0, errors.New("math: square root of negative number")
        }
        // 实现
    }
    
    func main() {
        // 正常情况
        if result, errorMsg := Divide(100, 10); errorMsg == "" {
            fmt.Println("100/10 = ", result)
        }
        // 当被除数为零的时候会返回错误信息
        if _, errorMsg := Divide(100, 0); errorMsg != "" {
            fmt.Println("errorMsg is: ", errorMsg)
        }
    }
    

    面向对象

    Go 语言的面向对象编程(OOP)非常简洁而优雅。说它简洁,是因为它没有了OOP中很多概念,比如:类,继承、虚函数、构造函数和析构函数、隐藏的this指针等等。说它优雅,是**它的面向对象(OOP)是语言类型系统(type system)中的天然的一部分。整个类型系统通过接口(interface)串联,浑然一体。

    成员的可访问性

    Go语言对关键字的增加非常吝啬。在Go语言中没有private、protected、public这样的关键字。要想某个符号可被其他包(package)访问,需要将该符号定义为大写字母开头。

    type Rectstruct{
        X, Y float64
        Width,Height float64
    }
    func (r *Rect) area() float64 {
        return r.Width* r.Height
    }
    

    成员方法遵循同样的规则,Rect的area方法只能在该类型所在的包(package)内使用。 需要强调的一点是,Go语言中符号的可访问性是包(package)一级的,而不是类一级的。尽管area是Rect的内部方法,但是在同一个包中的其他类型可以访问到它。这样的可访问性控制很粗旷,很特别,但是非常实用。如果Go语言符号的可访问性是类一级的,少不了还要加上friend这样的关键字,以表示两个类是朋友关系,可以访问其中的私有成员。

    封装

    package main
    
    import "fmt"
    
    type data struct {
        val int
    }
    
    //只有在你需要修改对象的时候,才必须用指针。它不是Go语言的约束,而是一种自然约束。
    //究其原因,是因为Go和C语言一样,类型都是基于值传递。要想修改变量的值,只能传递指针。
    func (p_data *data) set(num int) {
        p_data.val = num
    }
    func (p_data *data) show() {
        fmt.Println(p_data.val)
    }
    func main() {
        p_data := &data{4}
        p_data.set(5)
        p_data.show()
    }
    

    继承

    package main
    
    import "fmt"
    
    type parent struct {
        val int
    }
    type child struct {
        parent
        num int
    }
    
    func main() {
        var c child
        c = child{parent{1}, 2}
        fmt.Println(c.num)
        fmt.Println(c.val)
    }
    

    多态

    package main
    
    import "fmt"
    
    type act interface {
        write()
    }
    type xiaoming struct {
    }
    type xiaofang struct {
    }
    
    func (xm *xiaoming) write() {
        fmt.Println("xiaoming write")
    }
    func (xf *xiaofang) write() {
        fmt.Println("xiaofang write")
    }
    func main() {
        var w act
        xm := xiaoming{}
        xf := xiaofang{}
        w = &xm
        w.write()
        w = &xf
        w.write()
    }
    

    goroutine和channel

    “网络,并发”是Go语言的两大feature。Go语言号称“互联网的C语言”,与使用传统的C语言相比,写一个Server所使用的代码更少,也更简单。写一个Server除了网络,另外就是并发,相对python等其它语言,Go对并发支持使得它有更好的性能。 Goroutine和channel是Go在“并发”方面两个核心feature。 Channel是goroutine之间进行通信的一种方式,它与Unix中的管道类似。

    ChannelType=("chan"|"chan""<-"|"<-""chan")ElementType.
    var ch chan int
    var ch1 chan<-int//ch1只能写
    var ch2 <-chan int//ch2只能读
    

    channel是类型相关的,也就是一个channel只能传递一种类型。例如,上面的ch只能传递int。 创建channel时可以提供一个可选的整型参数,用于设置该channel的缓冲区大小。该值缺省为0,用来构建默认的“无缓冲channel”,也称为“同步channel”。 Channel作为goroutine间的一种通信机制,与操作系统的其它通信机制类似,一般有两个目的:同步,或者传递消息。

    同步

    c := make(chan int)// Allocate a channel.
    // Start the sort in a goroutine; when it completes, signal on the channel.
    go func(){
    list.Sort()
    c <-1// Send a signal; value does not matter.
    }()
    doSomethingForAWhile()
    <-c // Wait for sort to finish; discard sent value.
    

    上面的示例中,在子goroutine中进行排序操作,主goroutine可以做一些别的事情,然后等待子goroutine完成排序。 接收方会一直阻塞直到有数据到来。如果channel是无缓冲的,发送方会一直阻塞直到接收方将数据取出。如果channel带有缓冲区,发送方会一直阻塞直到数据被拷贝到缓冲区;如果缓冲区已满,则发送方只能在接收方取走数据后才能从阻塞状态恢复。

    消息传递

    经典的生产者-消费者模型

    package main
    
    func Producer(queue chan<- int) {
        for i := 0; i < 10; i++ {
            queue <- i
        }
    }
    func Consumer(queue <-chan int) {
        for i := 0; i < 10; i++ {
            v := <-queue
            fmt.Println("receive:", v)
        }
    }
    func main() {
        queue := make(chan int, 1)
        go Producer(queue)
        go Consumer(queue)
        time.Sleep(1e9) //让Producer与Consumer完成
    }
    

    多个channel

    在实际编程中,经常会遇到在一个goroutine中处理多个channel的情况。我们不可能阻塞在两个channel,这时就该select场了。与C语言中的select可以监控多个fd一样,go语言中select可以等待多个channel。

    c1 := make(chan string)
    c2 := make(chan string)
    go func(){
        time.Sleep(time.Second*1)
        c1 <-"one"
    }()
    go func(){
        time.Sleep(time.Second*2)
        c2 <-"two"
    }()
    for i :=0; i <2; i++{
        select{
        case msg1 :=<-c1:
            fmt.Println("received", msg1)
        case msg2 :=<-c2:
            fmt.Println("received", msg2)
        }
    }
    

    参考

    相关文章

      网友评论

      • 00d192e4c101:文中结构化类型(struct)中的代码
        type Booksstruct{
        struct前应该有空格

        同样的问题在

        接口类型(interface)中
        type Phoneinterface{
        type NokiaPhonestruct{
        type IPhonestruct{

        类型查询
        type Stringerinterface{

        成员的可访问性
        type Rectstruct{
      • ea0562fe909b:感觉就像是一个java的变种,不过又想是js的风格,google就是牛逼
      • fd3313cb5de2:不错。

      本文标题:Go语言初探

      本文链接:https://www.haomeiwen.com/subject/mmpqettx.html