go语言学习记录3

目录

go语言指针

地址:

定义指针:

使用指针:

空指针:

go语言结构体

定义结构体

访问结构体成员

结构体作为函数参数

结构体指针

go语言递归函数

阶乘:

斐波那契数列:

go语言类型转换

go语言接口

接口和类型的关系

go错误处理

go并发

通道channel

通道缓冲区

go遍历和关闭通道


学习资料来源:菜鸟教程

go语言指针

地址:

变量是一种使用方便的占位符,用于引用计算机内存地址。

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。

访问变量在内存中的地址:

package main

import "fmt"

func main() {
   var a int = 10  

   fmt.Printf("变量的地址: %x\n", &a  )
}
/*
代码输出结果为:

变量的地址: 20818a220
*/

定义指针:

一个指针变量指向了一个值的内存地址。

类似于变量和常量,在使用指针前你需要声明指针。

指针声明格式如下:

var var_name *var-type

(var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。)

有效的指针声明:

var ip *int        /* 指向整型*/
var fp *float32    /* 指向浮点型 */

使用指针:

指针使用流程:

  • 定义指针变量。
  • 为指针变量赋值。
  • 访问指针变量中指向地址的值。

(在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。)

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 )
}
/*
a 变量的地址是: 20818a220
ip 变量储存的指针地址: 20818a220
*ip 变量的值: 20
*/

空指针:

当一个指针被定义后没有分配到任何变量时,它的值为 nil。

nil 指针也称为空指针。

nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。

一个指针变量通常缩写为 ptr:

package main

import "fmt"

func main() {
   var  ptr *int

   fmt.Printf("ptr 的值为 : %x\n", ptr  )
}
//ptr 的值为 : 0

空指针判断:

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

go语言结构体

go语言在结构体中我们可以为不同项定义不同的数据类型。

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

结构体表示一项记录,比如保存图书馆的书籍记录,每本书有以下属性:

  • Title :标题
  • Author : 作者
  • Subject:学科
  • ID:书籍ID

定义结构体

结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员,type 语句设定了结构体的名称。

结构体的格式如下:

type struct_variable_type struct {
   member definition
   member definition
   ...
   member definition
}

一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:

variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

示例:

package main

import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}


func main() {

    // 创建一个新的结构体
    fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 0})

    // 也可以使用 key => value 格式
    fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 0})

    // 忽略的字段为 0 或 空
   fmt.Println(Books{title: "Go 语言", author: "www."})
}
/*
输出结果为:

{Go 语言 www. Go 语言教程 0}
{Go 语言 www. Go 语言教程 0}
{Go 语言 www.  0}
*/

访问结构体成员

如果要访问结构体成员,需要使用点号 . 操作符,格式为:

结构体.成员名"

示例:

package main

import "fmt"

type Books struct {
   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 = "www.0"
   Book1.subject = "Go 语言教程"
   Book1.book_id = 0

   /* book 2 描述 */
   Book2.title = "Python 教程"
   Book2.author = "www.0"
   Book2.subject = "Python 语言教程"
   Book2.book_id = 0

   /* 打印 Book1 信息 */
   fmt.Printf( "Book 1 title : %s\n", Book1.title)
   fmt.Printf( "Book 1 author : %s\n", Book1.author)
   fmt.Printf( "Book 1 subject : %s\n", Book1.subject)
   fmt.Printf( "Book 1 book_id : %d\n", Book1.book_id)

   /* 打印 Book2 信息 */
   fmt.Printf( "Book 2 title : %s\n", Book2.title)
   fmt.Printf( "Book 2 author : %s\n", Book2.author)
   fmt.Printf( "Book 2 subject : %s\n", Book2.subject)
   fmt.Printf( "Book 2 book_id : %d\n", Book2.book_id)
}
/*
Book 1 title : Go 语言
Book 1 author : www.
Book 1 subject : Go 语言教程
Book 1 book_id : 0
Book 2 title : Python 教程
Book 2 author : www.
Book 2 subject : Python 语言教程
Book 2 book_id : 0

结构体作为函数参数

将结构体类型作为参数传递给函数:

package main

import "fmt"

type Books struct {
   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 = "www."
   Book1.subject = "Go 语言教程"
   Book1.book_id = 0

   /* book 2 描述 */
   Book2.title = "Python 教程"
   Book2.author = "www."
   Book2.subject = "Python 语言教程"
   Book2.book_id = 0

   /* 打印 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)
}
/*
运行结果为:

Book title : Go 语言
Book author : www.
Book subject : Go 语言教程
Book book_id : 0
Book title : Python 教程
Book author : www.
Book subject : Python 语言教程
Book book_id : 0
*/

结构体指针

定义指向结构体的指针类似于其他指针变量,格式如下:

var struct_pointer *Books

定义指向结构体的指针类似于其他指针变量,格式如下:

var struct_pointer *Books

使用结构体指针访问结构体成员,使用 "." 操作符:

struct_pointer.title

示例:

package main

import "fmt"

type Books struct {
   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 = "www."
   Book1.subject = "Go 语言教程"
   Book1.book_id = 0

   /* book 2 描述 */
   Book2.title = "Python 教程"
   Book2.author = "www."
   Book2.subject = "Python 语言教程"
   Book2.book_id = 0

   /* 打印 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)
}
/*
以上实例执行运行结果为:

Book title : Go 语言
Book author : www.
Book subject : Go 语言教程
Book book_id : 0
Book title : Python 教程
Book author : www.
Book subject : Python 语言教程
Book book_id : 0
*/

go语言递归函数

递归(运行中调用自己)的语法格式如下:

func recursion() {
   recursion() /* 函数调用自身 */
}

func main() {
   recursion()
}

注意:

Go 语言支持递归。但我们在使用递归时,开发者需要设置退出条件,否则递归将陷入无限循环中。递归函数在数学上有计算阶乘、生成斐波那契数列等。

阶乘:

用示例来理解:

package main

import "fmt"

func Factorial(n uint64)(result uint64) {
    if (n > 0) {
        result = n * Factorial(n-1)
        return result
    }
    return 1
}

func main() {  
    var i int = 15
    fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(uint64(i)))
}
/*
以上实例执行输出结果为:

15 的阶乘是 1307674368000
*/

斐波那契数列:

示例理解:

package main

import "fmt"

func fibonacci(n int) int {
  if n < 2 {
   return n
  }
  return fibonacci(n-2) + fibonacci(n-1)
}

func main() {
    var i int
    for i = 0; i < 10; i++ {
       fmt.Printf("%d\t", fibonacci(i))
    }
}
/*
0    1    1    2    3    5    8    13    21    34
*/

go语言类型转换

类型转换用于将一种数据类型的变量转换为另外一种类型的变量。

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)
}
/*mean 的值为: 3.400000*/

go语言接口

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

接口特点:只有方法的声明,没有字段。

/* 定义接口 */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}


/* 定义结构体 */
type struct_name struct {
   /* variables */
}


/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* 方法实现 */
}

...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法实现*/
}

package main

import (
    "fmt"
)

type Phone interface {
    call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

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

}
/*
输出结果如下:

I am Nokia, I can call you!
I am iPhone, I can call you!
*/

在上面的例子中,我们定义了一个接口Phone,接口里面有一个方法call()。然后我们在main函数里面定义了一个Phone类型变量,并分别为之赋值为NokiaPhone和IPhone。然后调用call()方法。

接口和类型的关系

一个类型可实现多个接口(moile可以playmusic和playvideo)

多个类型可以实现同一个接口(猫和狗都是宠物,都可以eat)

go错误处理

error类型是一个接口类型,在编码中通过实现 error 接口类型来生成错误信息。

定义:

type error interface {
    Error() string
}

函数通常在最后的返回值中返回错误信息。使用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 main() {

    // 正常情况
    if result, errorMsg := Divide(100, 10); errorMsg == "" {
            fmt.Println("100/10 = ", result)
    }
    // 当除数为零的时候会返回错误信息
    if _, errorMsg := Divide(100, 0); errorMsg != "" {
            fmt.Println("errorMsg is: ", errorMsg)
    }

}
/*
输出结果为:

100/10 =  10
errorMsg is:  
    Cannot proceed, the divider is zero.
    dividee: 100
    divider: 0
*/

go并发

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。

goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

goroutine 语法格式:

go 函数名( 参数列表 )

Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。

通道channel

通道是用来传递数据的一个数据结构。

通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

ch <- v    // 把 v 发送到通道 ch
v := <-ch  // 从 ch 接收数据
           // 并把值赋给 v

声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:

ch := make(chan int)

注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。

示例:

通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:

package main

import "fmt"

func sum(s []int, c chan int) {
        sum := 0
        for _, v := range s {
                sum += v
        }
        c <- sum // 把 sum 发送到通道 c
}

func main() {
        s := []int{7, 2, 8, -9, 4, 0}

        c := make(chan int)
        go sum(s[:len(s)/2], c)
        go sum(s[len(s)/2:], c)
        x, y := <-c, <-c // 从通道 c 中接收

        fmt.Println(x, y, x+y)
}
/*
输出结果为:

-5 17 12
*/

通道缓冲区

通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:

ch := make(chan int, 100)

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。

注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。

package main

import "fmt"

func main() {
    // 这里我们定义了一个可以存储整数类型的带缓冲通道
        // 缓冲区大小为2
        ch := make(chan int, 2)

        // 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
        // 而不用立刻需要去同步读取数据
        ch <- 1
        ch <- 2

        // 获取这两个数据
        fmt.Println(<-ch)
        fmt.Println(<-ch)
}
/*
执行输出结果为:

1
2
*/

go遍历和关闭通道

Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:

v, ok := <-ch
package main

import (
        "fmt"
)

func fibonacci(n int, c chan int) {
        x, y := 0, 1
        for i := 0; i < n; i++ {
                c <- x
                x, y = y, x+y
        }
        close(c)
}

func main() {
        c := make(chan int, 10)
        go fibonacci(cap(c), c)
        // range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
        // 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
        // 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
        // 会结束,从而在接收第 11 个数据的时候就阻塞了。
        for i := range c {
                fmt.Println(i)
        }
}
/*
执行输出结果为:

0
1
1
2
3
5
8
13
21
34
*/

上一篇:GoLang设计模式10 - 中介者模式


下一篇:装饰模式(Decorator Pattern)