结构体和方法

Go 中可以在 结构体 (Struct) 中封装属性和操作, 概念类似 java 中的类. 定义方式如下:

type Student struct {
    Name string
    Age int
    Gender string
}

实现方式如下:

s1 := Student{Name:"Mike", Age:11, Gender:"boy"}
// 可以如此简写, 但必须注意参数顺序的一一对应.
s2 := Student{"Rose", 12, "girl"}
s3 := new (Student)
fmt.Println(s1.Name, s2.Gender, s3.Age) // 值为 Mike, girl, 0

匿名结构体:

s3 := struct {
    Name string
    Age int
    Gender string
}{"Robert", "boy", 13}

注意: 匿名结构体无法定义方法.

因为为结构体定义方法要在结构体的外部. 如下:

type Student struct {
    Name string
    Age int
    Gender string
}

// * 是指向 Student 结构体类型
func (s *Student) fmtName() {
    fmt.Println(s.Name)
} 

func main() {
    s3 := new (Student)
    s3.Name = "王二狗"
    s3.fmtName()
}

结构体继承

直接看代码:

// 定义结构体 Person
type Person struct {
    Country string
}

// 定义结构体 Student 并继承 Person
type Student struct {
    Name string
    Age int
    Gender string
    Person // 继承 Person 所有属性与方法
}

// * 是指向 Student 结构体类型
func (s *Student) fmtName() {
    fmt.Println(s.Name)
} 

// 打印国家
func (p *Person) fmtCountry() {
    fmt.Println(p.Country)
} 

func main() {
    s1 := new (Student)
    // 这是 Person 的属性与方法
    s1.Country = "中国"
    s1.fmtCountry()
    // 这是 Student 的属性与方法
    s1.Name = "王二狗"
    s1.fmtName()
}

接口

接口是用来保存共性方法的容器. 看如下代码定义:

// 定义接口
type Skill interface {
    Say() string
    Eat()
}

// 定义结构体 Person
type Person struct {
    // 属性
    Country string
}

// 定义结构体 Student 并继承 Person
type Student struct {
    Name string
    Age int
    Gender string
    Person // 继承 Person 所有属性与方法
}

// 实现接口方法
func (s Student) Say() string{
    fmt.Println("我叫" + s.Name + ",我来自" + s.Country)
    return "over"
}

func (s Student) Eat() {
    fmt.Println("我吃茶叶蛋")
}

func main() {
    s1 := new (Student)
    // 这是 Person 的属性与方法
    s1.Country = "中国"
    s1.fmtCountry()
    // 这是 Student 的属性与方法
    s1.Name = "王二狗"
    msg := s1.Say()
    fmt.Println(msg)
    s1.Eat()
}

指针

指针涉及到两个操作符 &* , & 代表 取址符 * 代表 声明符 / 取值符

取址符 指的是取得内存地址的意思. 例如:

var a int = 1
fmt.Println("变量a的内存地址是: " , &a) 

输出结果为:

// 变量a的内存地址是:  0xc00004a090

声明符 在使用指针时需要为指针值声明一个类型, 这个类型就是用 声明符 来定义的. 例如:

// 声明指针变量p 指针类型为int
var p *int 

取值符 使用指针变量获取指针值

// 获取指针值
var b = *p

例子演示 :

// 定义变量a并赋值
var a int = 1
// 声明指针变量p 指针类型为int
var p *int 
// 获取变量a的内存地址赋值给p
p = &a

fmt.Println("变量a的内存地址是: " , &a)
fmt.Println("指针变量p的存储地址是: " , p)
fmt.Println("指针变量p的值是: " , *p)

注意: 这里 p 的存储地址与内存地址是有区别的, 我们可以把 p 看做是一个容器, 存储了一个 a 的内存地址, 而 p 本身也是指向一个内存地址的, 所以如果使用 &p 这种写法的话就会得到 p 本身所指向的内存地址.而不是储存的 a 的地址.

声明指针数组

使用下面这种方式声明:

const len int = 3
var ptr [len]*int  

实例:

func main() {
    // 定义常量长度
    const len int = 3;
    // 定义指针数组
    var ptr [len]*int 
    // 定义整数数组
    arr := []int{1, 2, 3}

    var i int
    for i = 0; i < len; i ++{
        // 把数组值的内存地址储存到指针数组中
        ptr[i] = &arr[i]
    }

    // 遍历打印
    for  i = 0; i < len; i++ {
        fmt.Printf("a[%d] = %d\n", i, *ptr[i] )
    }
}

打印结果为:

a[0] = 1
a[1] = 2
a[2] = 3

指向指针的指针

顾名思义, 指针也可以储存另一个指针的地址. 定义方式如下:

var ptr **int;

实例:

func main() {
    // 定义变量a
    var a int = 1
    // 定义指针
    var ptr *int
    // 定义指向指针的指针
    var pptr **int

    // 定义指针 ptr 地址
    ptr = &a
    // 定义指针 pptr 地址
    pptr = &ptr

    fmt.Printf("指针变量 *ptr = %d\n", *ptr )
    fmt.Println("指针变量 *pptr = ", *pptr )
}    

打印结果如下:

指针变量 *ptr = 1
指针变量 *pptr =  0xc00004a068    

if、switch、for、select

各种判断选择语句

if

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    // 程序重启后,产生的随机数和上次一样。是因为程序使用了相同的种子, 使用rand.Seed(seed)来设置一个不同的种子值。
    // 利用当前时间的UNIX时间戳初始化rand包
    rand.Seed(time.Now().UnixNano()) 

    // 定义变量a,b 并用随机数赋值
    var a int = rand.Intn(100)
    var b int = rand.Intn(100)

    // 使用if判断两个数值
    if a > b {
        fmt.Printf("结果是 a (%d) > b (%d)", a, b)
    } else if b > a {
        fmt.Printf("结果是 b (%d) > a (%d)", b, a)
    }
}

也可以使用这种方式

func main() {
    // 定义变量 i
    var i int
    // 判断是否小于 100
    if i := 1; i < 100 {
        fmt.Printf("结果是 i (%d) < 100 \n", i)
    }

    // 打印 i 的值
    fmt.Printf("i = %d", i)
}

打印结果:

结果是 i (1) < 100
i = 0    

由此可得出结论, 在 if 中定义的 变量 i 是与外部定义的变量 i 是相互独立的.

switch

func main() {
    // 定义变量 s
    s := "golang"

    switch s {
        case "java" :
            fmt.Println("使用的语言是java")
        case "PHP" :
            fmt.Println("使用的语言是PHP")
        case "golang" :
            fmt.Println("使用的语言是golang")
        default :
            fmt.Println("不是匹配的语言")
    }
}

if 相同, 也可以这么定义:

func main() {
    // 定义变量 s
    arr := []string{"java", "golang", "PHP", "Python"}

    switch s := arr[3]; s {
        case "java" :
            fmt.Println("使用的语言是java")
        case "PHP" :
            fmt.Println("使用的语言是PHP")
        case "golang" :
            fmt.Println("使用的语言是golang")
        default :
            fmt.Printf("%v不是匹配的语言", s)
    }
}

打印的结果为

Python不是匹配的语言变量

类型switch 判断类型的语句.

func main() {
    // 定义变量 i
    v := 1

    // 使用类型 switch 判断
    switch i := interface{}(v).(type) {
        case int, int8, int16, int32, int64:
            fmt.Printf("变量 %d 的类型为 %T. \n", i, v)
        case uint, uint8, uint16, uint32, uint64:
            fmt.Printf("变量 %d 的类型为 %T. \n", i, v)
        default:
            fmt.Printf("%v不是匹配的类型", i)
    }
}

打印的结果为

变量 1 的类型为 int.

for

// 定义条件循环. 
for i := 0; i < 10; i++ {
    fmt.Print(i, " ")
}

// 定义死循环
for {
    fmt.Print("abc")
}

range

特殊的范围表达式, 用法如下:

func main() {
    // 定义数组
    arrs := []int{1, 2, 3 ,4}

    // 使用 range 表达式会返回一个 key, value 形式的返回值
    for k , v := range arrs {
        fmt.Printf("key = %d, value = %d \n", k, v)
    }
}

打印结果为 :

key = 0, value = 1 
key = 1, value = 2 
key = 2, value = 3 
key = 3, value = 4 

再举几个栗子 :

栗子1

// 如果只想获取值, 可使用空白符 _ 代表不接收 key
for _ , v := range arrs {
    fmt.Printf("value = %d \n", v)
}

value = 1 
value = 2 
value = 3 
value = 4 

栗子2

// 遍历字符串
for k, v := range "go语言"{
    fmt.Printf("key = %d, value = %c\n", k , v)
}

key = 0, value = g
key = 1, value = o
key = 2, value = 语
key = 5, value = 言

注意: key 为何不是连续的? 因为一个中文字符在经过UTF-8编码之后会表现为三个字节。如下图:

select

select 语句类似于 switch 语句,但是 select 会随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行.

select 语句的语法特点:

  • 每个 case 都必须是一个通信
  • 所有 channel 表达式都会被求值
  • 所有被发送的表达式都会被求值
  • 如果任意某个通信可以进行,它就执行;其他被忽略。
  • 如果有多个 case 都可以运行,select 会随机公平地选出一个执行。其他不会执行。

否则:

  1. 如果有 default 子句,则执行该语句。
  2. 如果没有 default 字句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。

栗子1

// 测试select是否随机执行case
for i := 0 ; i < 5 ; i++ {
    c1 := make(chan int , 1)
    c1 <- 1
    c2 := make(chan int , 1)
    c3 := make(chan int , 1)
    c3 <- 3
    var i1, i2 int
    select {
    case i1 = <- c1 :
        fmt.Printf("i1 = %d \n", i1)
    case c2 <- i2 :
        fmt.Printf("发送 i2 (%d) 到通道 c2\n", i2)
    case i3, ok := <- c3 :
        if ok{
            fmt.Printf("i3 = %d \n", i3)
        } else {
            fmt.Printf("c3 is closed")
        }
    default:
        fmt.Printf("---------当前没有通信--------\n\n")
    }
}

打印结果 :

发送 i2 (0) 到通道 c2
i1 = 1 
i3 = 3 
发送 i2 (0) 到通道 c2
发送 i2 (0) 到通道 c2

栗子2

// 测试select是否在匹配完所有合适的case后进入默认语句
for i := 0 ; i < 3 ; i++ {
    c1 := make(chan int , 1)
    c1 <- 1
    c2 := make(chan int , 1)
    c3 := make(chan int , 1)
    c3 <- 3
    var i1, i2 int
    for i := 0 ; i < 4 ; i++ {
        select {
            case i1 = <- c1 :
                fmt.Printf("i1 = %d \n", i1)
            case c2 <- i2 :
                fmt.Printf("发送 i2 (%d) 到通道 c2\n", i2)
            case i3, ok := <- c3 :
                if ok{
                    fmt.Printf("i3 = %d \n", i3)
                } else {
                    fmt.Printf("c3 is closed")
                }
            default:
                fmt.Printf("---------当前没有通信--------\n")
        }
    }
}

打印结果 :

发送 i2 (0) 到通道 c2
i1 = 1 
i3 = 3 
---------当前没有通信--------
i1 = 1 
i3 = 3 
发送 i2 (0) 到通道 c2
---------当前没有通信--------
发送 i2 (0) 到通道 c2
i3 = 3 
i1 = 1 
---------当前没有通信--------