zoukankan      html  css  js  c++  java
  • 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", &age  )
    
       /* 指针变量的存储地址 */
       fmt.Printf("ip 变量储存的指针地址: %x
    ", ip )
    
       /* 使用指针访问值 */
       fmt.Printf("*ip指针变量的值: %d
    ", *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
    ", &age  )
    
       /* 使用指针访问值 */
       fmt.Printf("*ptr指针变量的值: %d
    ", *ptr )
       
       if(ptr != nil)  {
          /* ptr 不是空指针 */  
          fmt.Printf("ptr指针变量储存的指针地址: %x
    ", ptr )
       }  
       if(other_ptr == nil)  {
          /* other_ptr 是空指针 */
          fmt.Printf("other_ptr指针变量储存的指针地址: %x
    ", 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
    ", 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
    ", age )
       fmt.Printf("指针变量 *ptr = %d,内存地址是:%d
    ", *ptr, ptr )
       fmt.Printf("指向指针的指针变量 **pptr = %d,内存地址是:%d
    ", *pptr, pptr)
    }

    输出结果

    4.4  通过new函数创建指针

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

    package main
    
    import "fmt"
    
    func main() {
       //通过new函数创建指针
        size := new(int)
        fmt.Printf("size的默认值= %d, 类型是: %T, 地址是: %v
    ", *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
    ", age )
       fmt.Printf("指针变量 *ptr = %d,内存地址是:%d
    ", *ptr, ptr )
       fmt.Printf("指向指针的指针变量 **pptr = %d,内存地址是:%d
    ", *pptr, pptr)
       
       //函数调用
       changeValue(ptr)
       fmt.Printf("变量age更改后的值 = %d
    ", age )
    
       //通过new函数创建指针
        size := new(int)
        fmt.Printf("size的默认值= %d, 类型是: %T, 地址是: %v
    ", *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
    ", 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岁了
    ", 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使您可以直接通过指针访问结构体的字段。 

    输出结果

     

    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("
    更改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  https://milapneupane.com.np/2019/07/06/learning-golang-from-zero-to-hero/

    2. Understanding Maps in Go   https://www.digitalocean.com/community/tutorials/understanding-maps-in-go

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

    4. Golang Tutorial – Learn Golang by Examples https://www.edureka.co/blog/golang-tutorial/#map

    5. The anatomy of Slices in Go  https://medium.com/rungo/the-anatomy-of-slices-in-go-6450e3bb2b94

    6. GoLang Tutorial - Structs and receiver methods - 2020 https://www.bogotobogo.com/GoLang/GoLang_Structs.php

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

    8. Ultimate Guide to Go Variadic Functions  https://medium.com/m/global-identity?redirectUrl=https%3A%2F%2Fblog.learngoprogramming.com%2Fgolang-variadic-funcs-how-to-patterns-369408f19085

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

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

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

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

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

    14. How to Define and Implement a Go Interface https://code.tutsplus.com/tutorials/how-to-define-and-implement-a-go-interface--cms-28962

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

    16. Go (Golang) - understanding the object oriented features with structs, methods, and interfaces https://unixsheikh.com/articles/go-understanding-the-object-oriented-features-with-structs-methods-and-interfaces.html#interfaces-in-go

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

    18. Anatomy of Channels in Go - Concurrency in Go  https://medium.com/rungo/anatomy-of-channels-in-go-concurrency-in-go-1ec336086adb

  • 相关阅读:
    jquery 里面对数组去重操作-unique
    jquery序列化form表单
    [转载]说说JSON和JSONP,也许你会豁然开朗,含jQuery用例
    AMD和CMD的区别
    CSS中!important的使用
    HTML的map-area的使用
    CSS Sprite 精灵图
    UA 用户代理
    IE haslayout
    心情随笔
  • 原文地址:https://www.cnblogs.com/dongguangming/p/13311198.html
Copyright © 2011-2022 走看看