zoukankan      html  css  js  c++  java
  • 第四章复合数据类型

    • 数组

    长度固定,类型特定

    数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定

    数组的初始化可以指定一个索引和对应值列表的方式初始化

    package main
    
    import "fmt"
    
    func main() {
        type Currency int
    
        const (
            USD Currency = iota
            EUR
            GBP
            RMB
        )
    
        symbol := [...]string{USD: "$", EUR: "*", GBP: "#", RMB: "&"}
    
        fmt.Println(RMB, symbol[RMB])
    }
    //在这种形式的数组字面值形式中,初始化索引的顺序是无关紧要的,而且没用到的索引可以省略
    //未指定初始值的元素将用零值初始化
    package main
    
    import "fmt"
    
    func main(){
        r := [...]int{99:-1}
        fmt.Print(r)
    }
    
    //最后一个元素为-1,其余为0

    如果一个数组的元素类型是可以相互比较的,那么数组类型也是可以相互比较的

    Printf函数的%x副词参数,它用于指定以十六进制的格式打印数组或slice全部的元素,%t副词参数是用于打印布尔型数据,%T副词参数是用于显示一个值对应的数据类型

    值传递?当调用一个函数的时候,函数的每个调用参数将会被赋值给函数内部的参数变量,所以函数参数变量接收的是一个复制的副本,并不是原始调用的变量。因为函数参数传递的机制导致传递大的数组类型将是低效的,并且对数组参数的任何的修改都是发生在复制的数组上,并不能直接修改调用时原始的数组变量。在这个方面,Go语言对待数组的方式和其它很多编程语言不同,其它编程语言可能会隐式地将数组作为引用或指针对象传入被调用的函数

    可以显式地传入一个数组指针,这样函数通过指针对数组的任何修改都可以直接反馈到调用者

    • Slice

    一个Slice由三个部分组成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,长度对应slice中元素的数目

    因为slice值包含指向第一个slice元素的指针,因此向函数传递slice将允许在函数内部修改底层数组的元素。换句话说,复制一个slice只是对底层的数组创建了一个新的slice别名

    和数组不同的是,slice之间不能比较,因此不能使用==操作符来判断两个slice是否含有全部相等元素

    标准库提供了高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,必须展开每个元素进行比较

    slice唯一合法的比较操作是和nil比较

    内置的make函数创建一个指定元素类型、长度和容量的slice,容量部分可以省略,在这种情况下,容量将等于长度

    make([]T, len)
    make([]T, len, cap) // same as make([]T, cap)[:len]
    

    在底层,make创建了一个匿名的数组变量,然后返回一个slice;只有通过返回的slice才能引用底层匿名的数组变量。在第一种语句中,slice是整个数组的view。在第二个语句中,slice只引用了底层数组的前len个元素,但是容量将包含整个的数组。额外的元素是留给未来的增长用的

    内置的copy函数可以方便地将一个slice复制另一个相同类型的slice。copy函数的第一个参数是要复制的目标slice,第二个参数是源slice,目标和源的位置顺序和dst = src赋值语句是一致的。两个slice可以共享同一个底层数组,甚至有重叠也没有问题。copy函数将返回成功复制的元素的个数,等于两个slice中较小的长度,所以不用担心覆盖会超出目标slice的范围

    通常并不知道append调用是否导致了内存的重新分配,因此也不能确认新的slice和原始的slice是否引用的是相同的底层数组空间。同样,不能确认在原先的slice上的操作是否会影响到新的slice。因此,通常是将append返回的结果直接赋值给输入的slice变量

    Slice内存技巧

    一个Slice可以用来模拟一个stack,最初给定的空slice对应一个空的stack,然后可以使用append函数将新的值压入stack

    stack的顶部位置对应slice的最后一个元素

    通过收缩stack可以弹出栈顶的元素 stack=stack[:len(stack)-1]

    要删除slice中间的某个元素并保存原有的元素顺序,可以通过内置的copy函数将后面的子slice向前依次移动一位完成

    如果删除元素后不用保持原来顺序,可以简单的用最后一个元素覆盖被删除的元素

    • Map

    在Go语言中,一个map就是一个哈希表的引用

    map[K]V,其中K对应的key必须是支持==比较运算符的数据类型,所以map可以通过测试key是否相等来判断是否已经存在

    //内置的make函数可以创建一个map
    ages := make(map[string]int)
    
    //也可以用map字面值的语法创建map,同时还可以指定一些最初的key/value
    ages := map[string]int{
        "alice": 31,
        "charlie": 34,
    }
    //相当于
    ages := make(map[string]int)
    ages["alice"] = 31
    ages["charlie"] = 34
    
    //创建空map的另一种方式
    map[string]int{}
    
    //访问
    ages["alice"] = 32
    
    //使用内置的delete函数可以删除元素
    delete(ages, "alice")
    
    //所有这些操作是安全的,即使这些元素不在map中也没有关系;如果一个查找失败将返回value类型对应的零值
    fmt.Println(ages["Bob"])//0
    
    // x += y 和 x++等简短赋值语法也可以用在map上
    ages["Bob"]++
    ages["Bob"]+=1
    
    //map中的元素并不是一个变量,因此不能对map的元素进行取址操作
    //禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效
    _ = &ages["Bob"] //compile error: cannot take address of map element
    
    //遍历map中全部的key/value对,可以使用range风格的for循环实现
    for name, age := range ages {
        fmt.Printf("%s	%d
    ", name, age)
    }
    
    //map的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序。在实践中,遍历的顺序是随机的,每一次遍历的顺序都不相同。这样可以强制要求程序不会依赖具体的哈希函数实现。如果要按顺序遍历key/value对,必须显式地对key进行排序,可以使用sort包的Strings函数对字符串slice进行排序
    
    import "sort"
    
    var names []string
    for name := range ages {
        names = append(names, name)
    }
    
    sort.Strings(names)
    for _, name := range names{
        fmt.Printf("%s	%d
    ", name, ages[name])
    }
    
    //map类型的零值是nil
    
    //map上的大部分操作,包括查找、删除、len和range循环都可以安全工作在nil值的map上,它的行为和一个空的map类似。但是向一个nil值的map存入元素将导致一个panic异常,在向map存数据前必须先创建map
    
    //如何判断一个对应的元素是否真的在map之中?
    age, ok := ages["Bob"]
    if !ok {/* "Bob" is not a key in this map; age == 0*/}
    
    if age, ok := ages["Bob"]; !ok{/**/}
    //这种场景下,map的下标语法将产生两个值;第二个是一个布尔值,用于报告元素是否真的存在。布尔变量一般命名为ok
    
    //和slice一样,map之间也不能进行相等比较,可以和nil进行比较。要判断两个map是否包含相同的key和value,必须通过一个循环实现
    //map或set的key需要式slice类型,但是slice是不可比较的,map和set的key又要求key是能够比较的,怎么处理?
    //定义一个函数将slice转换为string类型的key,确保只有两个slice相等时,辅助函数才相等,然后创建一个key为strng类型的map
    
    var m = make(map[string]int)
    
    //fmt.Sprintf函数将字符串列表转换为一个字符串
    //%q参数忠实地记录每个字符串元素的信息
    func k(list []string) string {return fmt.Sprintf("%q", list)}
    
    func Add(list []string) {m[k(list)]++}
    
    func Count(list []string) int { return m[k(list)]}

    例子?

    • 结构体

    结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员

    结构体成员的输入顺序有重要的意义,成员的不同顺序就相当于定义了不同的结构体类型,如果结构体成员名字是以大写字母开头的,那么该成员就是导出的

    一个命名为S的结构体类型将不能再包含S类型的成员,一个聚合的值不能包含它自身,数组同样适用

    S类型的结构体可以包含*S指针类型的成员,可以据此创建递归的数据结构,比如链表和树

    结构体类型的零值是每个成员都是零值。通常会将零值作为最合理的默认值。对于bytes.Buffer类型,结构体初始值就是一个随时可用的空缓存

    结构体值也可以用结构体字面值表示,结构体字面值可以指定每个成员的值

    //写法一
    type Point struct{ X, Y int}
    p := Point{1, 2}
    
    //写法二:以成员名字和相应的值来初始化,可以包含部分或全部的成员
    anim := gif.GIF{LoopCount: nframes}
    //在这种形式的结构体字面值写法中,如果成员被忽略的话将默认用零值

    结构体可以作为函数的参数和返回值

    如果考虑效率的话,较大的结构体通常会用指针的方式传入和返回

    如果要在函数内部修改结构体成员,必须用指针传入

    在Go语言中,所有的函数参数都是值拷贝传入的,函数参数将不再是函数调用时的原始变量

    //创建并初始化一个结构体变量,并返回结构体的地址
    pp := &Point{1, 2}
    
    //等价于
    pp := new(Point)
    *pp = Point{1, 2}

    结构体比较

    如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的

    可比较的结构体类型可以用于map的key类型

    结构体嵌套和匿名成员

    Go的结构体嵌套机制可以让我们将一个命名结构体当作另一个结构体类型的匿名成员使用,这样就可以通过简单的点运算符x.f来访问匿名成员链中嵌套的x.d.e.f成员

    Go语言有一个特性让我们只声明一个成员对应的数据类型而不指定成员的名字,这类成员就叫匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针

    //Circle和Wheel各自都有一个匿名成员
    //Point类型被嵌入到了Circle结构体,Circle类型被嵌入到了Wheel结构体
    
    type Circle struct {
        Point
        Radius int
    }
    
    type Wheel struct {
        Circle
        Spokes int
    }
    
    //得益于匿名嵌入的特性,可以直接访问叶子属性而不需要给出完整的路径
    var w Wheel
    w.X = 8  //相当于w.Circle.Point.X = 8
    w.Y = 8
    w.Radius = 5
    w.Spokes = 20

    //结构体成员Circle和Point是有名字的,就是对应类型的名字,只是这些名字在点号访问变量时是可选的,当访问最终需要的变量的时候可以省略中间所有的匿名成员

    使用匿名类型时,结构体字面值必须遵循形状类型声明时的结构,结构体字面量并没有快捷方式来初始化结构体

    w = Wheel{8, 8, 5, 20}//编译错误,未知成员变量
    w = Wheel{X: 8, Y: 8, Radius: 5, Spokes: 20}

    w = Wheel{Circle{Point{8, 8}, 5}, 20} w = Wheel{ Circle: Circle{ Point:Point{X:8,Y:8}, Radius:5, }, Spokes:20, //这里的逗号和Radius后的逗号都是必须的 } fmt.Printf("%#v ", w) //输出:Wheel{Circle:Circle{Point:Point{X:8, Y:8}, Radius:5}, Spokes:20} w.X = 42 fmt.Printf("%#V ", w) //输出:Wheel{Circle:Circle{Point:Point{X:42, Y:8}, Radius:5}, Spokes:20}

    匿名成员并不要求是结构体类型;其实任何命名的类型都可以作为结构体的匿名成员。但是为什么要嵌入一个没有任何子成员类型的匿名成员类型呢

    答案是匿名类型的方法集

    因为匿名成员拥有隐式的名字,所以不能在一个结构体里面定义两个相同类型的匿名成员,否则会引起冲突。由于匿名成员的名字是由它们的类型决定的,因此它们的可导出性也是由它们的类型决定的

    • JSON

    JavaScript对象表示法(JSON)是一种用于发送和接收结构化信息的标准协议,Go通过标准库encoding/json对json格式的编码和解码提供了很好的支持

    JSON是对JavaScript中各种类型的值-字符串、数字、布尔值和对象-的Unicode本文编码。可以用有效可读的方式表示基础数据类型、数组、slice、结构体和map等聚合数据类型

    基本的JSON类型有数字(以十进制或者科学计数法表示)、布尔值(true或false)、字符串,字符串是以双引号包含的Unicode代码点的序列,使用反斜杠作为转义字符,通过和Go类似的方式访问成员

    基础类型可以通过JSON的数组和对象类型进行递归组合。一个JSON数组是一个有序的值序列,写在一个方括号中并以逗号分隔;一个JSON数组可以用于编码Go语言的数组和slice。一个JSON对象是一个字符串到值的映射,写成一系列的name:value对形式,用花括号包含并以逗号分隔;JSON的对象类型可以用于编码Go语言的map类型和结构体

    package main
    
    type Movie struct {
        Title string
        //Year和Color成员后面的字符串面值是结构体成员Tag
    //一个结构体成员Tag是在编译阶段关联到该成员的元信息字符串
    Year int `json:"released"` Color bool `json:"color, omitempty"`
    //omitempty表示如果这个成员的值是零值或者为空,则不输出这个成员到JSON中 Actors []
    string } var movies = []Movie{ {Title: "Casablanca", Year: 1942, Color: false, Actors: []string{"Humphrey Bogart", "Ingrid Bergman"}}, {Title: "Cool Hand Luke", Year: 1967, Color: true, Actors: []string{"Paul Newman"}}, {Title: "Bullitt", Year: 1968, Color: true, Actors: []string{"Steve McQueen", "Jacqueline Bisset"}}, }

    将一个Go语言中类似movies的结构体slice转为JSON的过程叫编组(marshal),编组通过调用json.Marshal函数完成

    data, err := json.Marshal(movies)
    if err != nil {
        log.Fatalf("JSON marshaling failed: %s", err)
    }
    fmt.Printf("%s
    ", data)

    //Marshal生成了一个字节slice,其中包含一个不带有任何多余空白字符的很长的字符串

    [{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingrid Bergman"]},{"Title":"Cool Hand Luke","released":1967,"color":true,"Actors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"Actors":["Steve McQueen","Jacqueline Bisset"]}

    
    
    //为了方便阅读,json.MarshalIndent函数将产生整齐缩进的输出,该函数有两个额外的字符串参数用于表示每一行输出的前缀和每一个层级的缩进
    data, err := json.MarshalIndent(movies, "", " ")
    if err != nil {
    log.Fatalf("JSON marshaling failed: %s", err)
    }
    fmt.Printf("%s ", data)

    //Marshal使用Go结构体成员的名称作为JSON对象里面字段的名称(通过反射的方式)。只有可导出的成员可以转换为JSON字段,这就是为什么将Go结构体里面的所有成员都定义为首字母大写

    成员标签定义是结构体成员在编译期间关联的一些元信息。标签值的第一部分指定了Go结构体成员对应JSON中字段的名字。Color的标签还有一个额外的选项,omitempty,它表示如果这个成员的值是零值或者为空,则不输出这个成员到JSON中

    marshal的逆操作将JSON字符串解码为Go数据结构,这个过程叫unmarshal,由json.unmarshal实现

    //通过合理定义Go的数据结构,可以选择将哪部分JSON数据解码到结构体对象中,哪些数据可以丢弃
    
    //当函数unmarshal调用完成后,将填充结构体slice中Title的值,JSON中其它的字段就丢弃了
    
    var titles []struct{Title string}
    if err := json.unmarshal(data, &titles); err != nil {
        log.Fatalf("JSON unmarshaling failed: %s", err)
    }
    fmt.Println(titles)
    //"[{Casablanca} {Cool Hand Luke} {Bullitt}]"
    //很多Web服务都提供JSON接口,通过发送HTTP请求来获取想要得到的JSON信息
    
    
    package github
    
    import "time"
    //定义需要的类型和常量
    const IssuesURL = "https://api.github.com/search/issues"
    
    type IssuesSearchResult struct {
        TotalCount  int  `json:"total_count"`
        Items    []*Issue
    }
    
    type Issue struct {
        Number      int
        HTMLURL    string `json: "html_url"`
        Title           string
        State          string
        User          *User
        CreatedAt  time.Time `json:"created_at"`
        Body         string  //Markdown格式
    }
    
    type User struct {
        Login          string
        HTMLURL    string `json:"html_url"`
    }
    
    package github
    
    //函数SearchIssues发送HTTP请求并将回复解析为JSON。由于用户的查询请求参数中可能存在一些字符,这些字符在URL中是特殊字符,比如?或者&,因此使用url.QueryEscape函数来确保它们拥有正确的含义
    
    import (
        "encoding/json"
        "fmt"
        "net/http"
        "net/url"
        "strings"
    )
    
    //SearchIssues函数查询GitHub的issue跟踪接口
    func SearchIssues(terms []string) (*IssuesSearchResult, error) {
        q := url.QueryEscape(strings.Join(terms, " "))
        resp, err := http.Get(IssueURL + "?q=" + q)
        if err != nil {
            return nil, err
        }
        
        if resp.StatusCode != http.StatusOK {
            resp.Body.Close()
            return nil, fmt.Errorf("search query failed: %s", resp.Status)
        }
    
        var result IssuesSearchResult
        if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
            resp.Body.Close()
            return nil, err
        }
        resp.Body.Close()
        return &result, nil
    }
    • 文本和HTML模板

    有的情况下格式化要复杂的多,并且要求格式和代码彻底分离,可通过text/template和html/template包里面的方法来实现,这两个包提供了一种机制,可以将程序变量的值代入到文本或者HTML模板中

    text/template和html/template等模板包提供了一个将变量值填充到一个文本或HTML格式的模板的机制

    模板是一个字符串或者文件,它包含一个或者多个两边用双大括号包围的单元 {{...}},这称为操作。大多数的字符串是直接输出的,但是操作可以引发其它的行为。每个操作在模板语言里都对应一个表达式,提供简单但强大的功能:输出值、选择结构体成员、调用函数和方法、描述控制逻辑、实例化其他的模板等 

  • 相关阅读:
    Attribute在.NET编程中的应用
    Attribute应用权限验证
    Enterprise Library: Configuration Application Block
    App_Themes
    C# DNN 地址
    c++函数参数类型引用、指针、值
    四川新华社 关于IT诗人代腾飞 新书《赢道:成功创业者的28条戒律》新闻报道
    30岁之前创业想成功必看
    大师指点—为人处世秘诀之快乐成功之道!
    四川经济日报 关于代腾飞 新书《赢道:成功创业者的28条戒律》新闻报道
  • 原文地址:https://www.cnblogs.com/liushoudong/p/13029318.html
Copyright © 2011-2022 走看看