Golang 学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel 通道

Golang学习系列第二天之后,今天开始学习数据类型之高级类型: 派生类型。

学过java的人都知道,java其实就8种基本类型:byte、short、int、long、float、double、char、boolean,但它有引用数据类型:字符串、数组、集合、类、接口等。

而golang也有这样的划分,基本类型(Golang学习系列第二天已学过)和派生类型(不叫引用类型),派生类型有以下几种:数组类型、切片类型、Map类型、结构体类型(struct)、指针类型(Pointer)、函数类型、接口类型(interface)、Channel 类型。

1. 数组类型

数组是具有相同数据类型的元素序列。 数组在声明中定义了固定的长度,因此不能扩展超过该长度。 数组声明为

var variable_name [SIZE] variable_type

让我们以代码举例如下

package main

import "fmt"

func main() {
    var city [5]string
    city[0] = "北京"
    city[1] = "上海"
    city[2] = "广州"
    city[3] = "深圳"
    city[4] = "濮阳"
    fmt.Println(city[0], city[1], city[2], city[3], city[4])
    fmt.Println(city)
}

将变量city声明为5个字符串的数组,执行输出

不过也可以在声明数组时设置数组条目,看简洁版

package main

import "fmt"

func main() {
    var city = [5]string{ "北京", "上海","广州","深圳","濮阳"}
    fmt.Println(city[0], city[1], city[2], city[3], city[4])
    fmt.Println(city)

    //简写版
    othercity := [5]string{ "北京", "上海","广州", "深圳","濮阳"}
    fmt.Printf("%q", othercity)
}

输出结果

甚至,当您传递值时,可以使用省略号来使用隐式长度

package main

import "fmt"

func main() {
    var city = [5]string{ "北京", "上海","广州","深圳","濮阳"}
    fmt.Println(city[0], city[1], city[2], city[3], city[4])
    fmt.Println(city)

    //简写版
    othercity := [5]string{ "北京", "上海","广州", "深圳","濮阳"}
    fmt.Printf("%q\n", othercity)

     //隐士长度
    other_city := [...]string{ "北京", "上海","广州", "深圳","濮阳"}
    fmt.Printf("%q", other_city)
}

输出结果

以不同方式打印数组

注意我们使用带Printf的fmt包以及如何使用%q“动词”来打印每个引用的元素。

如果我们使用Println或%s动词,我们将得到不同的结果

package main

import "fmt"

func main() {  
     //不同方式打印数组
    other_city := [...]string{ "北京", "上海","广州", "深圳","濮阳"}
    fmt.Println("不同方式打印数组")
    fmt.Println(other_city)
    fmt.Printf("%s\n", other_city)
    fmt.Printf("%q", other_city)
}

执行后输出如图

多维数组

您还可以创建多维数组,示例代码:

package main

import "fmt"

func main() {
    var multi [2][3]int
    for i := 0; i < 2; i++ {
        for j := 0; j < 3; j++ {
            multi[i][j] = i + j
        }
    }
    fmt.Println("二维数组: ", multi)
}

输出结果

2. 切片Slices类型

切片包装数组可为数据序列提供更通用,更强大和更方便的接口。 除了具有明确维数的项(例如转换矩阵)外,Go中的大多数数组编程都是使用切片而不是简单数组完成的。

切片包含对基础数组的引用,如果您将一个切片分配给另一个,则两个切片都引用同一数组。 如果函数采用slice参数,则对slice的元素所做的更改将对调用者可见,这类似于将指针传递给基础数组。

切片指向值数组,并且还包含长度。 切片可以调整大小,因为它们只是另一个数据结构之上的包装。

例如, []T is a slice with elements of type T 表示[] T是具有T类型元素的切片。

举个小例子

package main

import "fmt"

func main() {
    var city = []string{ "北京", "上海","广州","深圳","濮阳"}
    fmt.Println(city[0], city[1], city[2], city[3], city[4])
    fmt.Println(city)
}

2.1 切分切片

可以对切片进行切片,以创建一个指向相同数组的新切片值。表达式为

slice[start:end]

表示计算从start到end-1(含)的元素的一部分。

注意:start和end是表示索引的整数。

下面举个小demo

package main

import "fmt"

func main() {
    var city = []string{ "北京", "上海","广州","深圳","濮阳","郑洲"}

    fmt.Println(city)
    fmt.Println(city[1:4])

    // missing low index implies 0
    fmt.Println(city[:3])
    // [2 3 5]

    // missing high index implies len(s)
    fmt.Println(city[4:])
}

输出结果

2.2 制造切片

除了通过立即传递值(切片文字)来创建切片之外,还可以使用make关键字创建切片。 您创建一个特定长度的空切片,然后填充每个条目。

package main

import "fmt"

func main() {

    fmt.Println("准备用make方式创建切片")
    cities := make([]string, 3)
    cities[0] = "郑洲"
    cities[1] = "濮阳"
    cities[2] = "安阳"
    fmt.Printf("%q", cities)
}

输出结果:

它通过分配清零的数组并返回引用该数组的切片来工作。

2.3 附加到切片

可以通过append方式附加值到切片中

package main

import "fmt"

func main() {
    //附加到切片
    other_cities := []string{}
    other_cities = append(other_cities, "濮阳")
    fmt.Println(other_cities)
}

看输出结果

也可以同时附加多个值到切片中,示例代码同时包括两个城市郑洲和濮阳

package main

import "fmt"

func main() {

    //附加多值到切片
    other_cities := []string{}

    other_cities = append(other_cities, "郑洲","濮阳")

    fmt.Println(other_cities)
}

输出结果

甚至您还可以使用省略号将切片附加到另一个切片

package main

import "fmt"

func main() {
    fmt.Println("准备用make方式创建切片")
    cities := make([]string, 3)
    cities[0] = "郑洲"
    cities[1] = "濮阳"
    cities[2] = "安阳"
    fmt.Printf("%q\n", cities)

    //附加切片到切片
    other_cities := []string{"南京"}

    other_cities = append(other_cities,  cities...)

    fmt.Println(other_cities)
}

输出结果

注意: 省略号是该语言的内置功能,这意味着该元素是一个集合。 我们无法将字符串类型([] string)类型的元素附加到字符串切片中,只能附加字符串。 但是,在切片后使用省略号(…),表示要附加切片的每个元素。 因为我们要从另一个片段追加字符串,所以编译器将接受操作,因为类型是匹配的。

您显然无法将[] int类型的切片附加到[] string类型的另一个切片中。

2.4 复制切片

切片也可以复制。 在这里,我们创建一个与other_cities长度相同的空切片copycities,并从other_cities复制到copycities中。

package main

import "fmt"

func main() {

    fmt.Println("准备用make方式创建切片")
    cities := make([]string, 3)
    cities[0] = "郑洲"
    cities[1] = "濮阳"
    cities[2] = "安阳"
    fmt.Printf("%q\n", cities)

    //附加多值到切片
    other_cities := []string{"南京"}

    other_cities = append(other_cities,  cities...)

    fmt.Println(other_cities)

    //拷贝切片
    fmt.Println("准备用copy方式创建切片")
    copycities := make([]string, len(other_cities))
    copy(copycities, other_cities)
    fmt.Println("copycities:", copycities)
}

输出结果

2.5 切片长度

您可以随时使用len检查切片的长度。

package main

import "fmt"

func main() {
    fmt.Println("准备用make方式创建切片")
    cities := make([]string, 3)
    cities[0] = "郑洲"
    cities[1] = "濮阳"
    cities[2] = "安阳"
    fmt.Printf("%q\n", cities)

    //附加多值到切片
    other_cities := []string{"南京"}

    other_cities = append(other_cities,  cities...)

    fmt.Println(other_cities)

    //打印切片长度
    fmt.Println(len(cities))
    countries := make([]string, 42)
    fmt.Println(len(countries))
}

输出结果

2.6 零片

切片的零值为nil。 无切片的长度和容量为0。

package main

import "fmt"

func main() {
    //零片
    var temp []int
    fmt.Println(temp, len(temp), cap(temp))
    if temp == nil {
        fmt.Println("nil!")
    }
}

输出结果

3. map类型

Map 是一种无序的键值对的集合,Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。

package main

import "fmt"

func main() {
    users := map[string]int{
        "admin":       0,
        "donguangming":       1,
        "student":           2,
    }

    fmt.Printf("%#v", users)
}

输出结果

当不使用上述map时,使用前必须使用make(不是新的)创建map。 nil映射为空,无法分配给它。

package main

import "fmt"

type User struct {
    name string
    age int
    city string
}

var user map[string]User

func main() {

    //通过make创建
    user = make(map[string]User)

    user["dgm"] = User{"董广明", 99, "南京"}
    fmt.Println(user["dgm"])
}

输出结果

3.1 操作map

3.1.1 在map中插入或更新元素

m[key] = elem

3.1.2 查询一个元素

elem = m[key]

3.1.3 删除一个元素

delete(m, key)

3.1.4 测试键是否存在并且有值

elem, ok = m[key]

如果键在m中,则确定为true。 如果不是,则ok为false,elem为map元素类型的零值。 同样,从map中读取时(如果没有按键)则结果是map元素类型的零值。

综上合起来代码如下

package main

import "fmt"

type User struct {
    name string
    age int
    city string
}

var user map[string]User

func main() {
    user = make(map[string]User)
    //赋值
    user["dgm"] = User{"董广明", 99, "南京"}
    fmt.Println("user:", user)
    //查询
    fmt.Println(user["dgm"])

    //删除
    delete(user, "dgm")
    fmt.Println("此时user:", user)

    //测试键
    _, u := user["dgm"]
    fmt.Println("u:", u)

    var users = map[string]User{
    "dmg": {"董广明", 99, "南京"},
    "dongguangming": {"董广明", 88, "南京"},
    }

    fmt.Println(users)
}

输出结果

3.2 map循环

如何在map上进行迭代?您可以使用循环范围来迭代map, 因为映射是无序集合,所以此循环的值可能会有所不同。

package main

import "fmt"

type User struct {
    name string
    age int
    city string
}

var user map[string]User

func main() {

    var users = map[string]User{
    "dmg": {"董广明", 99, "南京"},
    "dongguangming": {"董广明", 88, "南京"},
    }

    fmt.Println(users)

    /*使用键输出map键值 */
    for username := range users {
        fmt.Println(username, "用户=", users [username])
    }
}

输出结果

4. 指针类型(Pointer)

学过C(上学时第一门编程语言就是C)的人都知道, 指针是存放值内存地址的地方, 指针由*定义,根据数据类型定义指针,go也是这么玩的,声明格式如下

var var_name *var-type

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

&运算符可用于获取变量的地址。比如

var ap *int

a := 12
ap = &a

而指针指向的值可以使用*运算符进行访问,示例如下

package main

import "fmt"

func main() {
   var age int= 99   /* 声明实际变量 */
   var ip *int        /* 声明指针变量 */

   ip = &age  /* 指针变量的存储地址 */

   fmt.Printf("age 变量的地址是: %x\n", &age  )

   /* 指针变量的存储地址 */
   fmt.Printf("ip 变量储存的指针地址: %x\n", ip )

   /* 使用指针访问值 */
   fmt.Printf("*ip指针变量的值: %d\n", *ip )
}

执行以上代码,输出结果

4.1 空指针

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

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

一个指针变量通常缩写为 ptr。空指针判断方式:

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

综上示例代码如下

package main

import "fmt"

func main() {
   var age int =99 /* 声明实际变量 */
   var ptr *int        /* 声明指针变量 */
   var other_ptr *int        /* 声明指针变量 */

   ptr = &age  /* 指针变量的存储地址 */
   fmt.Printf("age 变量的地址是: %x\n", &age  )

   /* 使用指针访问值 */
   fmt.Printf("*ptr指针变量的值: %d\n", *ptr )

   if(ptr != nil)  {
      /* ptr 不是空指针 */  
      fmt.Printf("ptr指针变量储存的指针地址: %x\n", ptr )
   }  
   if(other_ptr == nil)  {
      /* other_ptr 是空指针 */
      fmt.Printf("other_ptr指针变量储存的指针地址: %x\n", other_ptr )
   }  
}

输出结果

4.2 指针数组

指针数组:简单点说它是一个数组,数组里面的每个元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。格式如下

var ptr [MAX]* type;

ptr 为type指针数组,因此每个元素都指向了一个值,只是它的值是指针。

package main

import "fmt"

const MAX int = 5

func main() {
  var city = [MAX]string{ "北京", "上海","广州","深圳","濮阳"}
   var i int
   var othercity [MAX]*string;

   for  i = 0; i < MAX; i++ {
      othercity[i] = &city[i] /* 字符串地址赋值给指针数组 */
   }

   for  i = 0; i < MAX; i++ {
      fmt.Printf("指针数组:索引:%d 值:%s 值的内存地址:%d\n", i, *othercity[i] , othercity[i] )
   }
}

输出结果

4.3 指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。

当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址。

指向指针的指针变量声明格式如下:

var ptr **type;

表示指向指针的指针变量为type。访问指向指针的指针变量值需要使用两个 * 号。

package main

import "fmt"

func main() {

   var age int
   var ptr *int
   var pptr **int

   age = 99

   /* 指针 ptr 地址 */
   ptr = &age
   /* 指向指针 ptr 地址 */
   pptr = &ptr

   /* 获取 pptr 的值 */
   fmt.Printf("变量 age = %d\n", age )
   fmt.Printf("指针变量 *ptr = %d,内存地址是:%d\n", *ptr, ptr )
   fmt.Printf("指向指针的指针变量 **pptr = %d,内存地址是:%d\n", *pptr, pptr)
}

输出结果

4.4 通过new函数创建指针

go支持通过new函数创建指针, new函数将类型作为参数,并返回指向新分配的作为参数传递的类型的零值的指针。

package main

import "fmt"

func main() {
   //通过new函数创建指针
    size := new(int)
    fmt.Printf("size的默认值= %d, 类型是: %T, 地址是: %v\n", *size, size, size)
    *size = 99
    fmt.Println("更改后新的值是:", *size)

}

输出结果

4.5 指针参数

可以将指针传递给函数

package main

import "fmt"

func changeValue(val *int) {  
    *val = 66
}

func main() {
   var age int
   var ptr *int
   var pptr **int

   age = 99

   /* 指针 ptr 地址 */
   ptr = &age
   /* 指向指针 ptr 地址 */
   pptr = &ptr

   /* 获取 pptr 的值 */
   fmt.Printf("变量 age = %d\n", age )
   fmt.Printf("指针变量 *ptr = %d,内存地址是:%d\n", *ptr, ptr )
   fmt.Printf("指向指针的指针变量 **pptr = %d,内存地址是:%d\n", *pptr, pptr)

   //函数调用
   changeValue(ptr)
   fmt.Printf("变量age更改后的值 = %d\n", age )

   //通过new函数创建指针
    size := new(int)
    fmt.Printf("size的默认值= %d, 类型是: %T, 地址是: %v\n", *size, size, size)
    *size = 99
    fmt.Println("更改后新的值是:", *size)
}

输出结果

特别注意这这两种传参的区别

func change(val int) {  
    val = 88
}

func changeValue(val *int) {  
    *val = 66
}

5. 函数类型

函数是基本的代码块,用于执行一个任务。

Go 语言最少有个 main() 函数。

你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。

函数声明告诉了编译器函数的名称,参数和返回类型。格式如下

func function_name( [parameter list] ) [return_types] {
   //函数体
}

函数定义解析:

  • func:函数由关键字 func 开始声明
  • function_name:函数名称,函数名和参数列表一起构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

5.1 无参无返回值函数

 package main

 import (
     "fmt"
 )

func hello(){
  fmt.Println("Hello World")
}

 func main() {
    fmt.Println("函数开始了")
    hello()
 }

5.2 有参无返回值函数

 package main

 import (
     "fmt"
 )

func hello(message string){
  fmt.Println(message)
}

 func main() {
    fmt.Println("函数开始了")
    hello("hello world")
 }

5.3 无参有返回值函数

 package main

 import (
     "fmt"
 )

func hello() string {
  return "Hello world"
}

 func main() {
    fmt.Println("函数开始了")
    greeting := hello()
    fmt.Println(greeting)
 }

5.4 有参有返回值函数

 package main

 import (
     "fmt"
 )

func hello(message string) string {
  return message
}

 func main() {
    fmt.Println("函数开始了")
    greeting := hello("hello world")
    fmt.Println(greeting)
 }

5.5 多个参数多返回值函数

package main

 import (
     "fmt"
 )

func hello(name string, age int) (string, int) {
  return name, age
}

 func main() {
    fmt.Println("函数开始了")
    name, age := hello("董广明", 99)

   fmt.Printf("name=%s, age = %d\n", name,age )
 }

5.6 在函数中预定义返回值的函数

 package main

 import (
     "fmt"
 )

func hello() (message string) {
  message = "hello world!"

  return
}

 func main() {
    fmt.Println("函数开始了")
    greeting := hello()

   fmt.Printf(greeting)
 }

message被定义为返回变量。 因此,定义的变量message将自动返回,而无需在最后的return语句中定义。

5.7 舍弃返回值的函数

 package main

 import (
     "fmt"
 )

func hello() (string, string) {
  return "hello world!", "hahaha"
}

 func main() {
    fmt.Println("函数开始了")
   greeting, _ := hello()

   fmt.Printf(greeting)
 }

5.8 指针传递函数

 package main

 import (
     "fmt"
 )

func change(name *string) {
  *name = "dongguangming"
}

 func main() {
    fmt.Println("函数开始了")
    name := "董广明"
    fmt.Println(name)

    change(&name)
    fmt.Printf(name)
 }

会改变原来的值,这很容易理解

5.9 可变参数函数

您可以使用Golang中的…运算符来传递数组,也可以使用相同的…运算符来接收参数。

 package main

 import (
     "fmt"
 )

func print(items ...string) {
  for _, v := range items {
    fmt.Println(v)
  }
}

 func main() {
    fmt.Println("函数开始了")

    print("董广明", "dongguangming", "dmg")

    list := []string{"Hello", "World"}
    print(list...) // An array argument
 }

实际输出结果

5.10 匿名函数

没有名字的函数被称为匿名函数

package main

 import (
     "fmt"
 )

 func main() {
    fmt.Println("函数开始了")

    func () {
      fmt.Println("我是一个匿名函数")
    } ()
 }

6. 结构体Struct

golang的世界里没有像java一样有class类的概念,但它有像C语言一样的结构体Struct。

结构体是不同字段的类型化集合,结构体用于将数据分组在一起。

例如,如果我们要对User类型的数据进行分组,则定义一个user的属性,其中可以包括姓名,年龄,性别,所在城市。 可以使用以下语法定义结构

package main

import "fmt"

type User struct {
     name   string
     age    int
     gender string
     city   string
 }

func main() {
   fmt.Println("结构体开始了")

   user := User{name: "董广明", age: 31, gender: "man", city: "南京"}

   fmt.Println(user)

   //简写
   u := User{"董广明", 31,  "man",  "南京"}

   fmt.Println(u)

    //通过指针访问
   u1 := &User{"董广明", 31,  "man",  "南京"}

   fmt.Println(u1.name)
}

输出结果

6.1 方法

方法是带有接收器的一种特殊函数。 接收者可以是值或指针。

package main

import "fmt"

type User struct {
     name   string
     age    int
     gender string
     city   string
 }

// 方法定义
 func (u *User) describe() {
     fmt.Printf("%v 今年 %v岁了\n", u.name, u.age)
 }
 //指针参数
 func (u *User) setAge(age int) {
     u.age = age
 }
 //值参数
 func (u User) setName(name string) {
     u.name = name
 }

func main() {
   fmt.Println("结构体开始了")

   user := User{name: "董广明", age: 31, gender: "man", city: "南京"}

   fmt.Println(user)

   user.describe()
   user.setAge(99)
   fmt.Println(user.age)
   user.setName("dongguangming")
   fmt.Println(user.name)
   user.describe()

}

输出结果

如图可以看到,现在可以使用点运算符user.describe调用该方法。 注意,接收者是一个指针。 使用指针,我们传递了对该值的引用,因此,如果对方法进行任何更改,它将反映在接收器user中。它也不会创建该对象的新副本,从而节省了内存。

请注意,在上面的示例中,age的值已更改,而name的值未更改,因为方法setName是接收者类型,而setAge是指针类型。

6.2 结构体域字段导出

如果字段名以大写字母开头,则定义该结构的包外部的代码将可对其进行读写。 如果该字段以小写字母开头,则只有该结构包中的代码才能读取和写入该字段。

package main

import "fmt"

type User struct {
    Name string
    Type string

    password string
}

func main() {
    u := User{
        Name: "董广明",
        Type: "1",

        password: "secret",
    }
    fmt.Println(u.Name, " 级别:", u.Type)
    fmt.Println("密码是:", u.password)
}

在同一包中,我们可以访问这些字段,如本示例所示, 由于main也在主程序包中,因此它可以引用u.password并检索存储在其中的值。 通常在结构体中具有未导出的字段,并通过导出的方法来访问它们。类比java里的访问权限private域,public访问方法。

以上程序输出结果

董广明  级别: 1
密码是: secret

6.3 创建匿名结构体

匿名仅创建新的结构变量,而不定义任何新的结构体类型。

package main

import "fmt"

func main() {

   emp := struct {
        firstName, lastName string
        age, salary         int
    }{
        firstName: "董",
        lastName:  "广明",
        age:       31,
        salary:    5000,
    }

    fmt.Println("员工", emp)
}

以上程序代码中,定义了一个匿名结构变量emp, 正如我们已经提到的,该结构称为匿名结构,因为它仅创建一个新的结构变量emp,而不定义任何新的结构类型。

输出结果

员工 {董 广明 31 5000}

6.4 嵌套结构体

结构可能包含一个字段,而该字段又是一个结构。 这些类型的结构称为嵌套结构。

比如我们经常网上购物时,填写订单接收地址(家里,公司,寄存点)

package main

import (  
    "fmt"
)

type Address struct {  
    city, state string
}
type Person struct {  
    name string
    age int
    address Address
}

func main() {  
    var p Person
    p.name = "董广明"
    p.age = 31
    p.address = Address {
        city: "南京某街道",
        state: "第一收货地址",
    }
    fmt.Println("名字:", p.name)
    fmt.Println("年龄:",p.age)
    fmt.Println("收货地址:",p.address.city)
    fmt.Println("是否第一:",p.address.state)
}

上面程序中的Person结构具有一个字段地址address,该字段地址又是一个结构体。

6.5 指向结构体的指针

您可以使用&运算符获取指向结构体的指针

package main

import "fmt"

type User struct {
     name   string
     age    int
     gender string
     city   string
 }

func main() {
   fmt.Println("结构体开始了")

   user := User{name: "董广明", age: 31, gender: "man", city: "南京"}

   fmt.Println(user)

    // Pointer to the user struct
    user_point := &user
    fmt.Println(user_point)

    // Accessing struct fields via pointer
    fmt.Println((*user_point).name)
    fmt.Println(user_point.name) // Same as above: No need to explicitly dereference the pointer

    user_point.age = 99
    fmt.Println(user_point)
}

以上示例所示,Go使您可以直接通过指针访问结构体的字段。

输出结果

​" class="reference-link">

6.6 结构体是值类型

结构体是值类型,当您将一个结构体变量分配给另一个时,将创建并分配该结构的新副本。 同样,当您将结构体传递给另一个函数时,该函数将获得其自己的结构副本。

package main

import "fmt"

type User struct {
     name   string
     age    int
     gender string
     city   string
 }

func main() {
   fmt.Println("结构体开始了")

   user := User{name: "董广明", age: 31, gender: "man", city: "南京"}
   user2 := user // A copy of the struct `user` is assigned to `user2`
   fmt.Println("user = ", user)
   fmt.Println("user2 = ", user2)

   //
   user2.name = "dongguangming"
   user2.age = 99
   user2.city ="上海"
   fmt.Println("\n更改user2:")
   fmt.Println("user = ", user)
   fmt.Println("user2 = ", user2)
}

输出结果

7. 接口Interface类型

Go编程提供了另一种称为接口的数据类型,它表示一组方法签名。

struct数据类型实现接口中定义的方法。

package main

import "fmt"
import "math"

type Shape interface {
   area() float64
}

type Rectangle struct{
   height float64
   width float64
}

type Circle struct{
   radius float64
}

func (r Rectangle) area() float64 {
    return r.height * r.width
}

func (c Circle) area() float64 {
    return math.Pi * math.Pow(c.radius, 2)
} 

func getArea(shape Shape) float64{

    return shape.area()

}

func main() {
   fmt.Println("接口开始了")
   rect := Rectangle{20, 50}
   circ := Circle{4}

   fmt.Println("长方形面积 =", getArea(rect))
   fmt.Println("圆的面积 =", getArea(circ))
}

输出结果

8. Channels类型

通道是一种类型化的管道,可以使用通道运算符<-发送和接收值。

channel<- value    // 发送value值到通道channel
value := <-channel  // 从通道查询,并把值赋给value

注意:数据按箭头方向流动

和其他数据类型类似,通道使用前必须先创建,其初始值是 nil。创建通道的语法格式如下:

var c1 chan [value type]
c1 = make([channel type] [value type], [capacity])

  • [value type] 定义的是 Channel 中所传输数据的类型。
  • [channel type] 定义的是 Channel 的类型,其类型有以下三种:
    • “chan” 可读可写——“chan int” 则表示可读写 int 数据的 channel
    • “chan<-“ 仅可写——“chan<- float64” 则表示仅可写64位 float 数据的 channel
    • “<-chan” 仅可读——“<-chan int” 则表示仅可读 int 数据的 channel
  • [capacity] 是一个可选参数,其定义的是 channel 中的缓存区 (buffer)。如果不填则默认该 channel 没有缓冲区 (unbuffered)。对于没有缓冲区的 channel,消息的发送和收取必须能同时完成,否则会造成阻塞并提示死锁错误。

比如我们想创建了一个读写 int 类型,buffer 长度 100 的 channel c1,则如下:

var c1 chan int
c1 = make(chan int, 100)

通过此通道,我们可以发送int类型的数据。 我们可以在此通道中发送和接收数据

package main

 import "fmt"

 func main() {
     ch := make(chan int)
     go func() { ch <- 31 }()
     age := <-ch
     fmt.Println(age)
 }

输出结果:

接收方通道等待,直到发送方将数据发送到通道。

8.1 单向通道

在有些情况下,我们希望通过通道接收数据但不发送数据, 为此我们还可以创建一个单向通道。 让我们看一个简单的例子:

package main

 import (
     "fmt"
 )

 func main() {
     ch := make(chan string)
     go sc(ch)
     fmt.Println(<-ch)
 }

 func sc(ch chan<- string) {
     ch <- "你好,董广明"
 }

在上面的示例代码中,sc是go协程,该例程只能将消息发送到通道,但不能接收消息。

执行代码输出结果

$go run main.go
你好,董广明

8.2 缓存通道(Buffered channel

在Golang中可以创建一个缓冲通道。 对于缓冲的通道,如果缓冲区已满,则将阻止发送到该通道的消息。 让我们看一个小例子

package main

 import "fmt"

 func main() {
     ch := make(chan string, 2)
     ch <- "hello"
     ch <- "world"

     ch <- "!!!" // 超了缓冲最大值要报错
     fmt.Println(<-ch)
 }

实际上超过缓冲最大值,要报错

那怎么办呢,还好有以下解决方法

package main

 import "fmt"

 func main() {
     ch := make(chan string, 2)
     ch <- "hello"
     ch <- "world"

     //创建匿名函数
     function := func(name string) { ch <- name }

     //
     go function("董广明") 
     go function("donguangming") 
     go function("dgm") 
     go function("dgmdgm") 
     go function("3dgm") 

     fmt.Println(<-ch)
     fmt.Println(<-ch)
     fmt.Println(<-ch)
     fmt.Println(<-ch)
     fmt.Println(<-ch)
     fmt.Println(<-ch)
     fmt.Println(<-ch)
 }

输出结果

golang测验结果评分

总结: golang就是综合性编程语言,幸好以前做过java、pyhon、JavaScript开发,上学时又学过C语言(第一编程语言),故学起golang很快,极少部分是golang本身特有的。

一句话概括:golang是几种语言的混合体,外加自己的特性。如果不考虑语言本身,你会觉得像是在写C,有时又像是写python,写函数时又像JavaScript中函数的变体。

参考:

  1. Golang Tutorial — from zero to hero milapneupane.com.np/2019/07/06/lea...

  2. Understanding Maps in Go www.digitalocean.com/community/tut...

  3. Golang Maps www.geeksforgeeks.org/golang-maps/

  4. Golang Tutorial – Learn Golang by Examples www.edureka.co/blog/golang-tutoria...

  5. The anatomy of Slices in Go medium.com/rungo/the-anatomy-of-sl...

  6. GoLang Tutorial - Structs and receiver methods - 2020 www.bogotobogo.com/GoLang/GoLang_S...

  7. Golang Cheatsheet: Functions ado.xyz/blog/golang-cheatsheet-fun...

  8. Ultimate Guide to Go Variadic Functions medium.com/m/global-identity?redir...

  9. Golang Methods Tutorial with Examples www.callicoder.com/golang-methods-...

  10. Go Best Practices: Should you use a method or a function? flaviocopes.com/golang-methods-or-...

  11. Methods that satisfy interfaces in golang suraj.io/post/golang-methods-inter...

  12. Pass by pointer vs pass by value in Go goinbigdata.com/golang-pass-by-poi...

  13. Go Data Structures: Interfaces research.swtch.com/interfaces

  14. How to Define and Implement a Go Interface code.tutsplus.com/tutorials/how-to...

  15. Methods and Interfaces in Go dev-pages.info/golang-interfaces/

  16. Go (Golang) - understanding the object oriented features with structs, methods, and interfaces unixsheikh.com/articles/go-underst...

  17. 理解 Golang 的 Channel 类型 studygolang.com/articles/25805

  18. Anatomy of Channels in Go - Concurrency in Go medium.com/rungo/anatomy-of-channe...

本作品采用《CC 协议》,转载必须注明作者和本文链接
人生,不应设限
dongguangming
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!