详解Go 语言中的比较操作符

发布时间:2019-06-08  栏目:计算机教程  评论:0 Comments

这篇文章专注于 6 个操作符,==,!=,<,<=,> 和
>=。我们将深入探讨它们的语法和用法的细微差别。对很多人来说,这听起来不像是吸引人的事,或者他们可能已经从其他编程语言获得了糟糕的经验。然而,在
Go 中它们定义的很好并简洁。下面讨论的主题,如可比性将出现在其他场合,如
maps。为了使用上述操作符,至少有一个操作数需要可赋值给第二个操作数:


package main
import "fmt"
type T struct {
  name string
}
func main() {
  s := struct{ name string }{"foo"}
  t := T{"foo"}
  fmt.Println(s == t) // true
}

title: 《Go语言圣经》读书笔记1
date: 2017-10-19 09:04
tags: go
categories: go

这条规则显著缩小了可选范围:


var a int = 1
var b rune = '1'
fmt.Println(a == b)

链接

类似的代码在 Javascript 或 Python 中可以运行。但在 Go
中它是非法的,并且在编译时会被检测到。

相关链接

src/github.com/mlowicki/lab/lab.go:8: invalid operation: a == b (mismatched types int and rune)

前言

可赋值不是唯一要求。这是相等和顺序操作符的规则……

Go与C++的比较

技术选型

  • Python+Redis 脚本语言开发迅速、数据结构丰富、也有多线程等模式
    无法处理复杂数据结构、需要做C扩展或者使用很多复杂的实现方式,常驻内存后端程序还是不够稳定,无法处理非常高性能的场合,也不方便优化
  • C++ +Redis
    高性能、极强的定制性、可以实现任何内存和数据结构、可以达到任何功能和性能的要求和强大的底层操控能力
    开发维护成本高、出现问题几率较大、没有专职C++开发人员,需要构建很多基础库、包括异步网络框架,采用多线程还是异步IO、配置文件解析、内存管理、日志等基础库的开发
  • Go+Redis
    学习曲线短、高性能、高并发,支持强大的的类C的内存和各种数据结构操作可以满足一般场景需求

=:=

  • C++中没有:=这个符号
  • Go语言中:=用来声明一个变量的同时给这个变量赋值
    并且只能在函数体内使用
  • 主要是为了省略类型声明,系统会自己选择相应的类型识别定义的变量

New和make操作符

  • C++中new操作符用于给一个对象 分配内存但是没有清零
  • go语言中调用new(T)被求值时
    所做的是为T类型的新值分配并且清零一块内存空间,然后将内存空间的地址作为结果返回
    所以不用担心乱码问题 可以直接拿来使用
  • make用于内建类型(map、slice
    和channel)的内存分配。new用于各种类型的内存分配。
  • make只能创建slice、map和channel,并且返回一个有初始值(非零)的T类型(引用),而不是*T。

面向对象

  • go语言和c++在面向对象方面完全没有可比性,尤其是没有继承、泛型、虚函数、函数重载、构造函数、析构函数等。

相等操作符

入门

Go语言编译过程没有警告信息,争议特性之一

在表达式x +
y中,可在+后换行,不能在+前换行(译注:以+结尾的话不会被插入分号分隔符,但是以x结尾的话则会被分号分隔符,从而导致编译错误)

根据代码需要, 自动地添加或删除import声明
go get golang.org/x/tools/cmd/goimports

操作数需要使用 == 或 != 操作符进行比较。哪些方法,哪些值可以被比较?Go
规范定义的非常明确:

命令行参数

package main

import (
    "fmt"
    "os"
    "strings"
)

func main() {
    var s, sep string
    for _, args := range os.Args {
        s += sep + args
        sep = " "
    }
    fmt.Println(s)

    fmt.Println(strings.Join(os.Args, " "))
}

boolean 值可比较(如果俩个值都是真或假,那么比较结果被认为 true)
整数和浮点数比较:

查找重复的行

%d          十进制整数
%x, %o, %b  十六进制,八进制,二进制整数。
%f, %g, %e  浮点数: 3.141593 3.141592653589793 3.141593e+00
%t          布尔:true或false
%c          字符(rune) (Unicode码点)
%s          字符串
%q          带双引号的字符串"abc"或带单引号的字符'c'
%v          变量的自然形式(natural format)
%T          变量的类型
%%          字面上的百分号标志(无操作数)

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    counts := make(map[string]int)
    files := os.Args[1:]
    if len(files) == 0 {
        countLines(os.Stdin, counts)
    } else {
        for _, arg := range files {
            f, err := os.Open(arg)
            if err != nil {
                fmt.Fprintf(os.Stderr, "dup2: %v\n", err)
                continue
            }
            countLines(f, counts)
            f.Close()
        }
    }
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}

func countLines(f *os.File, counts map[string]int) {
    input := bufio.NewScanner(f)
    for input.Scan() {
        counts[input.Text()]++
    }
    // NOTE: ignoring potential errors from input.Err()
}
var a int = 1
var b int = 2
var c float32 = 3.3
var d float32 = 4.4
fmt.Println(a == b) // false
fmt.Println(c == d) // false

GIF动画

package main

import (
    "fmt"
    "image"
    "image/color"
    "image/gif"
    "io"
    "math"
    "math/rand"
    "os"
    "time"
)

//var palette = []color.Color{color.White, color.Black}
var palette = []color.Color{color.RGBA{0x00, 0xff, 0x00, 0xff}, color.RGBA{0xff, 0x00, 0x00, 0xff}}

const (
    backgroudIndex = 0 // first color in palette
    foregroudIndex = 1 // next color in palette
)

func main() {
    // The sequence of images is deterministic unless we seed
    // the pseudo-random number generator using the current time.
    // Thanks to Randall McPherson for pointing out the omission.
    rand.Seed(time.Now().UTC().UnixNano())
    fout, err := os.Create("out.gif")
    defer fout.Close()
    if err != nil {
        fmt.Println("out.gif", err)
        return
    }
    //lissajous(os.Stdout)
    lissajous(fout)
}

func lissajous(out io.Writer) {
    const (
        cycles  = 5     // number of complete x oscillator revolutions
        res     = 0.001 // angular resolution
        size    = 100   // image canvas covers [-size..+size]
        nframes = 64    // number of animation frames
        delay   = 8     // delay between frames in 10ms units
    )

    freq := rand.Float64() * 3.0 // relative frequency of y oscillator
    anim := gif.GIF{LoopCount: nframes}
    phase := 0.0 // phase difference
    for i := 0; i < nframes; i++ {
        rect := image.Rect(0, 0, 2*size+1, 2*size+1)
        img := image.NewPaletted(rect, palette)
        for t := 0.0; t < cycles*2*math.Pi; t += res {
            x := math.Sin(t)
            y := math.Sin(t*freq + phase)
            img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
                foregroudIndex)
        }
        phase += 0.1
        anim.Delay = append(anim.Delay, delay)
        anim.Image = append(anim.Image, img)
    }
    gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors
}

当编译时 a == d 会抛出异常( int 和 float32 类型不匹配)因为它不可能用
int 和 float 比较。

并发获取多个URL

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "os"
    "strings"
    "time"
)

func main() {
    start := time.Now()
    ch := make(chan string)
    for _, url := range os.Args[1:] {
        go fetch(url, ch) // start a goroutine
    }
    for range os.Args[1:] {
        fmt.Println(<-ch) // receive from channel ch
    }
    fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}

func fetch(url string, ch chan<- string) {
    start := time.Now()
    if ok := strings.HasPrefix(url, "http"); !ok {
        url = "http://" + url
    }
    resp, err := http.Get(url)
    if err != nil {
        ch <- fmt.Sprint(err) // send to channel ch
        return
    }

    nbytes, err := io.Copy(ioutil.Discard, resp.Body)
    resp.Body.Close() // don't leak resources
    if err != nil {
        ch <- fmt.Sprintf("while reading %s: %v", url, err)
        return
    }
    secs := time.Since(start).Seconds()
    ch <- fmt.Sprintf("%.2fs  %7d  %s", secs, nbytes, url)
}

复数相等,如果他们的是实数和虚数部分都相等:

Web服务

package main

import (
    "fmt"
    "image"
    "image/color"
    "image/gif"
    "io"
    "log"
    "math"
    "math/rand"
    "net/http"
    "strconv"
    "sync"
)

var mu sync.Mutex
var count int

func main() {
    http.HandleFunc("/", handler)
    http.HandleFunc("/gif", handlerGif)
    http.HandleFunc("/count", counter)
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

// handler echoes the Path component of the requested URL.
func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto)
    for k, v := range r.Header {
        fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
    }
    fmt.Fprintf(w, "Host = %q\n", r.Host)
    fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr)
    if err := r.ParseForm(); err != nil {
        log.Print(err)
    }
    for k, v := range r.Form {
        fmt.Fprintf(w, "Form[%q] = %q\n", k, v)
    }
}

// counter echoes the number of calls so far.
func counter(w http.ResponseWriter, r *http.Request) {
    mu.Lock()
    fmt.Fprintf(w, "Count %d\n", count)
    mu.Unlock()
}

func handlerGif(w http.ResponseWriter, r *http.Request) {
    if err := r.ParseForm(); err != nil {
        log.Print(err)
    }
    cycles, _ := strconv.ParseFloat(r.Form.Get("cycles"), 10)
    log.Println(cycles)
    lissajous(w, cycles)
}

//var palette = []color.Color{color.White, color.Black}
var palette = []color.Color{color.RGBA{0x00, 0xff, 0x00, 0xff}, color.RGBA{0xff, 0x00, 0x00, 0xff}}

const (
    backgroudIndex = 0 // first color in palette
    foregroudIndex = 1 // next color in palette
)

func lissajous(out io.Writer, cycles float64) {
    const (
        //cycles  = 5     // number of complete x oscillator revolutions
        res     = 0.001 // angular resolution
        size    = 100   // image canvas covers [-size..+size]
        nframes = 64    // number of animation frames
        delay   = 8     // delay between frames in 10ms units
    )

    freq := rand.Float64() * 3.0 // relative frequency of y oscillator
    anim := gif.GIF{LoopCount: nframes}
    phase := 0.0 // phase difference
    for i := 0; i < nframes; i++ {
        rect := image.Rect(0, 0, 2*size+1, 2*size+1)
        img := image.NewPaletted(rect, palette)
        for t := 0.0; t < cycles*2*math.Pi; t += res {
            x := math.Sin(t)
            y := math.Sin(t*freq + phase)
            img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
                foregroudIndex)
        }
        phase += 0.1
        anim.Delay = append(anim.Delay, delay)
        anim.Image = append(anim.Image, img)
    }
    gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors
}
var a complex64 = 1 + 1i
var b complex64 = 1 + 2i
var c complex64 = 1 + 2i
fmt.Println(a == b) // false
fmt.Println(b == c) // true

Switch

tag switch(tagless switch) —-
Go语言里的switch还可以不带操作对象(译注:switch不带操作对象时默认用true值代替,然后将每个case的表达式和true值进行比较)

字符串类型值可比较

程序结构

指针类型值相等,如果他们都是 nil 或都指向相同的变量:

命名

关键字

break      default       func     interface   select
case       defer         go       map         struct
chan       else          goto     package     switch
const      fallthrough   if       range       type
continue   for           import   return      var

预先定义的名字并不是关键字,你可以在定义中重新使用它们

内建常量: true false iota nil

内建类型: int int8 int16 int32 int64
          uint uint8 uint16 uint32 uint64 uintptr
          float32 float64 complex128 complex64
          bool byte rune string error

内建函数: make len cap new append copy close delete
          complex real imag
          panic recover

在习惯上,Go语言程序员推荐使用 驼峰式 命名

type T struct {
  name string
}
func main() {
  t1 := T{"foo"}
  t2 := T{"bar"}
  p1 := &t1
  p2 := &t1
  p3 := &t2
  fmt.Println(p1 == p2)  // true
  fmt.Println(p2 == p3)  // false
  fmt.Println(p3 == nil) // false
}

声明

Go语言主要有四种类型的声明语句:var、const、type和func,分别对应变量、常量、类型和函数实体对象的声明。

不同的 zero-size
变量可能具有相同的内存地址,因此我们不假设任何指向这些变量的指针相等。

变量

Go语言中不存在未初始化的变量。

在包级别声明的变量会在main入口函数执行前完成初始化(§2.6.2),局部变量将在声明语句被执行到的时候完成初始化。

变量的生命周期

  • 包一级声明的变量 和整个程序的运行周期是一致的
  • 局部变量的生命周期则是动态的:每次从创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。
    • 函数的参数变量和返回值变量都是局部变量。它们在函数每次被调用的时候创建。

垃圾回收

  • 实现思路是,从每个包级的变量和每个当前运行函数的每一个局部变量开始,通过指针或引用的访问路径遍历,是否可以找到该变量。如果不存在这样的访问路径,那么说明该变量是不可达的,也就是说它是否存在并不会影响程序后续的计算结果。
  • 因为一个变量的有效周期只取决于是否可达,因此一个循环迭代内部的局部变量的生命周期可能超出其局部作用域。同时,局部变量可能在函数返回之后依然存在。
  • 编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定的。

    var global *int
    
    func f() {
        var x int
        x = 1
        global = &x
    }
    
    func g() {
        y := new(int)
        *y = 1
    }
    
  • f函数里的x变量必须在堆上分配,因为它在函数退出后依然可以通过包一级的global变量找到,虽然它是在函数内部定义的;用Go语言的术语说,这个x局部变量从函数f中逃逸了。相反,当g函数返回时,变量*y将是不可达的,也就是说可以马上被回收的。因此,*y并没有从函数g中逃逸,编译器可以选择在栈上分配*y的存储空间(译注:也可以选择在堆上分配,然后由Go语言的GC回收这个变量的内存空间),虽然这里用的是new方式。

a1 := [0]int{}
a2 := [0]int{}
p1 := &a1
p2 := &a2
fmt.Println(p1 == p2) // might be true or false. Don't rely on it!

赋值

自增和自减是语句,而不是表达式,因此x = i++之类的表达式是错误的)

元组赋值

x, y = y, x

通道类型值相等,如果他们确实一样(被相同的内置 make 方法创建)或值都是
nil:

类型

type Celsius float64    // 摄氏温度
type Fahrenheit float64 // 华氏温度
  • Celsius和Fahrenheit分别对应不同的温度单位。它们虽然有着相同的底层类型float64,但是它们是不同的数据类型,因此它们不可以被相互比较或混在一个表达式运算。
  • 因此需要一个类似Celsius(t)或Fahrenheit(t)形式的显式转型操作才能将float64转为对应的类型。
ch1 := make(chan int)
ch2 := make(chan int)
fmt.Println(ch1 == ch2) // false

包和文件

因为汉字不区分大小写,因此汉字开头的名字是没有导出的

Import

  • 按照惯例,一个包的名字和包的导入路径的最后一个字段相同,例如gopl.io/ch2/tempconv包的名字一般是tempconv。
  • import别名

    import (
        sort "wuxu.bit/example/alg/sort"
        "fmt"
    )
    

golang在引入包的时候还有一些tricks:

  • import . "fmt" 这样在调用fmt包的导出方法时可以省略fmt
  • import _ "fmt"
    这样引入该包但是不引入该包的导出函数,而是为了使用该导入操作的副作用:
    调用包里面的init函数

包的初始化

  • 包的初始化首先是解决包级变量的依赖顺序,然后按照包级变量声明出现的顺序依次初始化
  • 初始化工作是自下而上进行的,main包最后被初始化。以这种方式,可以确保在main函数执行之前,所有依赖的包都已经完成初始化工作了。(包括init函数)

接口类型是可比较。与通道和指针类型值比较一样,如果是 nil 或
动态类型和动态值是相同的:

作用域

if,switch条件部分为一个隐式词法域,然后是每个分支的词法域。

type I interface {
  m()
}
type J interface {
  m()
}
type T struct {
  name string
}
func (T) m() {}
type U struct {
  name string
}
func (U) m() {}
func main() {
  var i1, i2, i3, i4 I
  var j1 J
  i1 = T{"foo"}
  i2 = T{"foo"}
  i3 = T{"bar"}
  i4 = U{"foo"}
  fmt.Println(i1 == i2) // true
  fmt.Println(i1 == i3) // false
  fmt.Println(i1 == i4) // false
  fmt.Println(i1 == j1) // false
}

基础数据类型

Go语言将数据类型分为四类:基础类型、复合类型、引用类型和接口类型。

本章介绍基础类型,包括:数字、字符串和布尔型。

比较接口类型的方法集不能相交。

整型

等价类型

  • Unicode字符rune类型是和int32等价的类型,通常用于表示一个Unicode码点。
  • byte也是uint8类型的等价类型,byte类型一般用于强调数值是一个原始的数据而不是一个小的整数。

二元运算符,它们按照优先级递减的顺序排列如下,二元运算符有五种优先级。在同一个优先级,使用左优先结合规则,但是使用括号可以明确优先顺序,使用括号也可以用于提升优先级

*      /      %      <<       >>     &       &^
+      -      |      ^
==     !=     <      <=       >      >=
&&
||

位操作符

&      位运算 AND
|      位运算 OR
^      位运算 XOR
&^     位清空 (AND NOT)
<<     左移
>>     右移

位操作运算符^作为二元运算符时是按位异或(XOR),当用作一元运算符时表示按位取反;

位操作代码

package main

import (
    "fmt"
)

func main() {
    var x uint8 = 1<<1 | 1<<5
    var y uint8 = 1<<1 | 1<<2

    fmt.Printf("%08b\n", x) // "00100010", the set {1, 5}
    fmt.Printf("%08b\n", y) // "00000110", the set {1, 2}

    fmt.Printf("%08b\n", x&y)  // "00000010", the intersection {1}
    fmt.Printf("%08b\n", x|y)  // "00100110", the union {1, 2, 5}
    fmt.Printf("%08b\n", x^y)  // "00100100", the symmetric difference {2, 5}
    fmt.Printf("%08b\n", x&^y) // "00100000", the difference {5}

    for i := uint(0); i < 8; i++ {
        if x&(1<<i) != 0 { // membership test
            fmt.Println(i) // "1", "5"
        }
    }

    fmt.Printf("%08b\n", x<<1) // "01000100", the set {2, 6}
    fmt.Printf("%08b\n", x>>1) // "00010001", the set {0, 4}
}

对于一个float,可以利用int(f)这样的语法进行转化,但是会丢弃小数部分

打印八进制、十进制、十六进制

o := 0666
fmt.Printf("%d %[1]o %#[1]o\n", o) // "438 666 0666"
x := int64(0xdeadbeef)
fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
// Output:
// 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF
  • %之后的[1]副词告诉Printf函数再次使用第一个操作数。
  • %后的#副词告诉Printf在用%o、%x或%X输出时生成0、0x或0X前缀。

接口类型 I 的 i 和 非接口类型 T 的 t 可比较,如果 T 实现了 I 则 T
类型的值是可比较的。如果 I 的 动态类型和 T 是相同的,并且 i 的动态值和 t
也是相同的,那么值是相等的:

浮点数

一个float32类型的浮点数可以提供大约6个十进制数的精度,而float64则可以提供约15个十进制数的精度;

type I interface {
  m()
}
type T struct{}
func (T) m() {}
type S struct{}
func (S) m() {}
func main() {
  t := T{}
  s := S{}
  var i I
  i = T{}
  fmt.Println(t == i) // true
  fmt.Println(s == i) // false
}
结构类型可比较,所以字段都需要比较。所有非空白字段相等则他们等。
a := struct {
  name string
  _ int32
}{name: "foo"}
b := struct {
  name string
  _ int32
}{name: "foo"}
fmt.Println(a == b) // true

复数

Go语言提供了两种精度的复数类型:complex64和complex128,分别对应float32和float64两种浮点数精度。内置的complex函数用于构建复数,内建的real和imag函数分别返回复数的实部和虚部:

var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y)                 // "(-5+10i)"
fmt.Println(real(x*y))           // "-5"
fmt.Println(imag(x*y))           // "10"

Go 中 数组是同质的 ——
只有同一类型(数组元素类型)的值可以被存储其中。对于数组值比较,它们的元素类型需要可比较。如果对应的元素相同,数组就相等。

布尔型

func btoi(b bool) int {
    if b {
        return 1
    }
    return 0
}
func itob(i int) bool { return i != 0 }

就是这样。上面列表很长但并不充满惊奇。尝试了解它在 JavaScript
是如何工作的……

字符串

内置的len函数可以返回一个字符串中的字节数目(不是rune字符数目)

字符串可以用==和<进行比较;比较通过逐个字节比较完成的,因此比较的结果是字符串自然编码的顺序。

字符串的值是不可变的:

原生的字符串面值\…`,用\包含的字符串中没有转义操作

Unicode和UTF-8

  • Unicode每个符号都分配一个唯一的Unicode码点,Unicode码点对应Go语言中的rune整数类型(译注:rune是int32等价类型)。
  • UTF8编码使用1到4个字节来表示每个Unicode码点,ASCII部分字符只使用1个字节,常用字符部分使用2或3个字节表示。

0xxxxxxx                             runes 0-127    (ASCII)
110xxxxx 10xxxxxx                    128-2047       (values <128 unused)
1110xxxx 10xxxxxx 10xxxxxx           2048-65535     (values <2048 unused)
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx  65536-0x10ffff (other values unused)

统计utf8的字符串中rune的个数,使用utf8.RuneCountInString(s)

转换代码

// "program" in Japanese katakana
s := "プログラム"
fmt.Printf("% x\n", s) // "e3 83 97 e3 83 ad e3 82 b0 e3 83 a9 e3 83 a0"
r := []rune(s)
fmt.Printf("%x\n", r)  // "[30d7 30ed 30b0 30e9 30e0]"
fmt.Println(string(r)) // "プログラム"
fmt.Println(string(65))     // "A", not "65"
fmt.Println(string(0x4eac)) // "京"
fmt.Println(string(1234567)) // "?"

在第一个Printf中的% x参数用于在每个十六进制数字前插入一个空格。

bytes、strings、strconv和unicode

  • bytes.Buffer提供构建字符串。
  • strconv包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换。
  • unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,它们用于给字符分类。每个函数有一个单一的rune类型的参数,然后返回一个布尔值。

有三种类型不能比较 —— maps, slices 和 functions。Go
编译器不允许这样做,并且编译比较 maps 的程序会引起一个错误 map can only
be compared to nil. 。展示的错误告诉我们至少可以用 maps,slices 或
functions 和 nil 比较。

常量

  • 常量表达式的值在编译期计算,而不是在运行期。每种常量的潜在类型都是基础类型:boolean、string或数字。
  • 常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都是返回常量结果:len、cap、real、imag、complex和unsafe.Sizeof

iota 常量生成器

const (
    FlagUp Flags = 1 << iota // is up
    FlagBroadcast            // supports broadcast access capability
    FlagLoopback             // is a loopback interface
    FlagPointToPoint         // belongs to a point-to-point link
    FlagMulticast            // supports multicast access capability
)
const (
    _ = 1 << (10 * iota)
    KiB // 1024
    MiB // 1048576
    GiB // 1073741824
    TiB // 1099511627776             (exceeds 1 << 32)
    PiB // 1125899906842624
    EiB // 1152921504606846976
    ZiB // 1180591620717411303424    (exceeds 1 << 64)
    YiB // 1208925819614629174706176
)

目前为止,我们知道接口值是可比较的,但 maps
是不可以的。如果接口值的动态类型是相同的,但是不能比较(如
maps),它会引起一个运行时错误:

复合数据类型

数组、slice、map和结构体

  • 数组和结构体是聚合类型;它们的值由许多元素或成员字段的值组成。
  • 数组是由同构的元素组成——每个数组元素都是完全相同的类型——结构体则是由异构的元素组成的。
  • 数组和结构体都是有固定内存大小的数据结构。
  • slice和map则是动态的数据结构,它们将根据需要动态增长。
type T struct {
  meta map[string]string
}
func (T) m() {}
func main() {
  var i1 I = T{}
  var i2 I = T{}
  fmt.Println(i1 == i2)
}
panic: runtime error: comparing uncomparable type main.T
goroutine 1 [running]:
panic(0x8f060, 0x4201a2030)
  /usr/local/go/src/runtime/panic.go:500 +0x1a1
main.main()
  ...

数组

长度固定

初始化

var a [3]int
var b [3]int = [3]int{1, 2, 3}
var c [3]int = [3]int{1, 2}
d := [...]int{1, 2, 3} //数组的长度是根据初始化值的个数来计算
e := [...]int{99: -1} //定义了一个含有100个元素的数组r,最后一个元素被初始化为-1,其它元素都是用0初始化。

数组比较

  • 相同类型(相同长度)的才能比较
  • 不同类型比较会编译错误
  • 数组中的元素完全一样,才是相等

a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // "true false false"
d := [3]int{1, 2}
fmt.Println(a == d) // compile error: cannot compare [2]int == [3]int

函数传参

  • 函数参数变量接收的是一个复制的副本
  • 传递大的数组类型将是低效的
  • Go语言对待数组的方式和其它很多编程语言不同,其它编程语言可能会隐式地将数组作为引用或指针对象传入被调用的函数。
  • 改进:传递数组指针

func zero(ptr *[32]byte) {
}

sha256创建及比较

package main

import (
    "crypto/sha256"
    "fmt"
)

var pc [256]byte

func init() {
    for i := range pc {
        pc[i] = pc[i/2] + byte(i&1)
    }
}

func main() {
    c1 := sha256.Sum256([]byte("李强"))
    c2 := sha256.Sum256([]byte("关凤瑜"))
    fmt.Printf("%x\n%x\n%t\n%T\n", c1, c2, c1 == c2, c1)
    fmt.Printf("Diff: %d\n", diffBitsSha256(c1, c2))
    // Output:
    // 2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881
    // 4b68ab3847feda7d6c62c1fbcbeebfa35eab7351ed5e78f4ddadea5df64b8015
    // false
    // [32]uint8
    // Diff: 127
}

func diffBitsSha256(c1, c2 [32]uint8) int {
    res := 0
    for i := 0; i < 32; i++ {
        res += int(pc[c1[i]^c2[i]])
    }
    return res
}

sha256,sha384,sha512输出

package main

import (
    "crypto/sha256"
    "crypto/sha512"
    "flag"
    "fmt"
    "os"
)

func main() {
    var sha int
    flag.IntVar(&sha, "sha", 256, "SHA")
    var str string
    str = os.Args[len(os.Args)-1]
    fmt.Println(str)
    flag.Parse()
    switch sha {
    case 256:
        OutputSHA256(str)
    case 384:
        OutputSHA384(str)
    case 512:
        OutputSHA512(str)
    }
}

func OutputSHA256(str string) {
    fmt.Printf("%x\n", sha256.Sum256([]byte(str)))
}
func OutputSHA384(str string) {
    fmt.Printf("%x\n", sha512.Sum384([]byte(str)))
}
func OutputSHA512(str string) {
    fmt.Printf("%x\n", sha512.Sum512([]byte(str)))
}

顺序操作符

Slice

语法

  • 一个slice类型一般写作[]T,slice的语法和数组很像,只是没有固定长度而已。
  • 初始化时,同样可以使用顺序指定序列或者通过索引指定。
  • 不能用==!=比较2个slice中的数据是否相同。
  • 使用高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,我们必须自己展开每个元素进行比较
  • make

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

底层数据结构

  • slice是轻量级的数据结构,slice的底层引用了一个数组对象。
  • slice由三个部分构成:指针、长度和容量。
  • 长度对应slice中元素的数目;长度不能超过容量
  • 容量一般是从slice的开始位置到底层数据的结尾位置
  • 内置的len和cap函数分别返回slice的长度和容量。
  • 切片操作超出cap(s)的上限将导致一个panic异常,但是超出len(s)则是意味着扩展了slice,因为新slice的长度会变大

底层实现

  • 创建slice时,会隐式地创建一个合适大小的数组,然后slice的指针指向底层的数组。
  • 多个slice之间可以共享底层的数据,并且引用的数组部分区间可能重叠。

零值

  • 一个零值的slice等于nil。一个nil值的slice并没有底层数组。一个nil值的slice的长度和容量都是0
  • 非nil值的slice的长度和容量也是0的,例如[]int{}或make([]int,
    3)[3:]

Append

var x []int
x = append(x, 1)
x = append(x, 2, 3)
x = append(x, 4, 5, 6)
x = append(x, x...) // append the slice x
fmt.Println(x)      // "[1 2 3 4 5 6 1 2 3 4 5 6]"
  • 内存分配策略类似于C++的Vector

重写reverse函数,使用数组指针代替slice。

package main

import "fmt"

// reverse reverses a slice of ints in place.
func reverseSlice(s []int) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
}

func reversePoint(p *[6]int) {
    for i, j := 0, len(*p)-1; i < j; i, j = i+1, j-1 {
        (*p)[i], (*p)[j] = (*p)[j], (*p)[i]
    }
}

func main() {
    a := [...]int{0, 1, 2, 3, 4, 5}
    reverseSlice(a[:])
    fmt.Println(a) // "[5 4 3 2 1 0]"
    reversePoint(&a)
    fmt.Println(a) // "[5 4 3 2 1 0]"
}

编写一个rotate函数,通过一次循环完成旋转。

package main

import "fmt"

// reverse reverses a slice of ints in place.
func gcd(a, b int) int {
    for b > 0 {
        a, b = b, a%b
    }
    return a
}

// n > 0   <-------
// n < 0   ------->
func rotate(s []int, n int) {
    length := len(s)
    n = (n + length) % length
    gcd := gcd(length, n)
    loop := length / gcd

    for i := 0; i < gcd; i++ {
        temp := s[i]
        j := 0
        for ; j < loop-1; j++ {
            s[(i+j*n)%length] = s[(i+j*n+n)%length]
        }
        s[(i+j*n)%length] = temp
    }
}

func main() {
    a := [...]int{0, 1, 2, 3, 4, 5}
    rotate(a[:], 2)
    fmt.Println(a) // "[5 4 3 2 1 0]"
}

写一个函数在原地完成消除[]string中相邻重复的字符串的操作。

package main

import "fmt"

func removeAdjRepeat(s []string) []string {
    for i := 0; i < len(s)-1; i++ {
        if s[i] == s[i+1] {
            copy(s[i:], s[i+1:])
            s = s[:len(s)-1]
            i--
        }
    }
    return s
}

func main() {
    s := []string{"1", "1", "2"}
    s = removeAdjRepeat(s)
    fmt.Println(s)
}

这些操作符只能应用在三种类型:整数,浮点数和字符串类型。这没有什么特别的或
Go 特有的。值得注意的是字符串是按字典顺序排列的。byte-wise
一次一个字节并没有 Collation 算法。

Map

底层数据结构

  • map是一个哈希表的引用
  • map中所有的key都有相同的类型,所有的value也有着相同的类型
  • map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作:禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效。

遍历

  • for k, v := range map
  • 迭代顺序是不确定的

按顺序遍历key/value对方法

import "sort"

keys := make([]string, 0, len(map))
for key := range map {
    keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
    fmt.Printf("%s\t%d\n", key, map[key])
}

查找

if age, ok := ages["bob"]; !ok { /* ... */ }

判断2个map是否相等

func equal(x, y map[string]int) bool {
    if len(x) != len(y) {
        return false
    }
    for k, xv := range x {
        if yv, ok := y[k]; !ok || yv != xv {
            return false
        }
    }
    return true
}
fmt.Println("aaa" < "b") // true
fmt.Println("ł" > "z")  // true

结构体

语法

  • 结构体成员名字是以大写字母开头的,那么该成员就是导出的;
  • 一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身。但是S类型的结构体可以包含*S指针类型的成员,这可以让我们创建递归的数据结构,比如链表和树结构等。

比较

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

结构体嵌入和匿名成员

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

type Point struct {
    X, Y int
}

type Circle struct {
    Point
    Radius int
}

type Wheel struct {
    Circle
    Spokes int
}

var w Wheel
w.X = 8            // equivalent to w.Circle.Point.X = 8
w.Y = 8            // equivalent to w.Circle.Point.Y = 8
w.Radius = 5       // equivalent to w.Circle.Radius = 5
w.Spokes = 20

w = Wheel{Circle{Point{8, 8}, 5}, 20}

w = Wheel{
    Circle: Circle{
        Point:  Point{X: 8, Y: 8},
        Radius: 5,
    },
    Spokes: 20, // NOTE: trailing comma necessary here (and at Radius)
}
  • 这样就可以直接访问叶子属性而不需要给出完整的路径。同时完整的访问方式同样支持w.Circle.Point.X
  • 匿名成员也有一个隐式的名字,因此不能同时包含两个类型相同的匿名成员,这会导致名字冲突。

结果

JSON

marshaling:结构体slice转为JSON

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

type Movie struct {
    Title  string
    Year   int  `json:"released"`
    Color  bool `json:"color,omitempty"`
    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"}},
    // ...
}

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

    data, err = json.MarshalIndent(movies, "", "    ")
    if err != nil {
        log.Fatalf("JSON marshaling failed: %s", err)
    }
    fmt.Printf("%s\n", data)
}
  • 成员Tag一般用原生字符串面值的形式书写
  • json开头键名对应的值用于控制encoding/json包的编码和解码的行为,并且encoding/…下面其它的包也遵循这个约定
  • 成员Tag中json对应值的第一部分用于指定JSON对象的名字
  • omitempty选项,表示当Go语言结构体成员为空或零值时不生成该JSON对象(这里false为零值)

任何比较操作符的结果都是无类型布尔常量(true 或
false)。因为它没有类型,所以可以分配了给任何布尔变量:

文本和HTML模板

const templ = `{{.TotalCount}} issues:
{{range .Items}}----------------------------------------
Number: {{.Number}}
User:   {{.User.Login}}
Title:  {{.Title | printf "%.64s"}}
Age:    {{.CreatedAt | daysAgo}} days
{{end}}`
  • 一个模板是一个字符串或一个文件,里面包含了一个或多个由双花括号包含的{{action}}对象。
  • 每一个action,都有一个当前值的概念,对应点操作符,写作“.”。
  • 模板中{{range
    .Items}}和{{end}}对应一个循环action,因此它们直接的内容可能会被展开多次,循环每次迭代的当前值对应当前的Items元素的值。
  • 一个action中,|操作符表示将前一个表达式的结果作为后一个函数的输入,类似于UNIX中管道的概念。
  • 对于Age部分,第二个动作是一个叫daysAgo的函数
var t T = true
t = 3.3 < 5
fmt.Println(t)

函数

这段代码输出 true。另一个,尝试分配 bool 类型的值:

函数声明

Go语言没有默认参数值

var t T = true
var b bool = true
t = b
fmt.Println(t)

递归

Go语言使用可变栈,栈的大小按需增加(初始时很小)。这使得我们使用递归时不必考虑溢出和安全问题。

产生一个错误,不能使用 b (bool类型)分配给 T 类型。

多返回值

如果一个函数所有的返回值都有显式的变量名,那么该函数的return语句可以省略操作数。这称之为bare
return。

func HourMinSec(t time.Time) (hour, minute, second int)

留下评论

网站地图xml地图