07月17日, 2018

goweb-032-golang-复合数据类型

流程控制

类比 clang

if

  • 条件表达式没有括号
  • 左大括号必须和条件语句或else在同一行
package main
func main() {
	a,b:=1,2
	// 没有小括号
	if a>b{
		println(b)
	}else{
		print(a)
	}
}

  • 支持一个初始化表达式(可以是并行方式)
  • 支持单行模式

    package main
    func main() {
    	if a,b:=1,2;a>b{
    		println(b)
    	}else{
    		print(a)
    	}
    }
    
  • 初始化语句中的变量为block级别,同时隐藏外部同名变量

    package main
    func main() {
    	var a =true
    	if a,b:=1,2;a>b{
    		println(b)
    	}else{
    		print(a)
    	}
    	println(a)
    }
    

for

  • Go只有for一个循环语句关键字,但支持3种形式
  • 初始化和步进表达式可以是多个值
  • 条件语句每次循环都会被重新检查,因此不建议在条件语句中使用函数,尽量提前计算好条件并以变量或常量代替
  • 左大括号必须和条件语句在同一行

    package main
    func main() {
    	for{
    		//死循环
    	}
    }
    
    package main
    func main() {
    	flag := 1
    	//while
    	for flag < 5 {
    		flag++
    		println(flag)
    	}
    }
    
    package main
    func main() {
    	//index:=1
    	// for ;index < 5;index++ {
    	// 	println(index)
    	// }
    
    	for idx:=0;idx<5;idx++{
    		println(idx)
    	}
    }
    

switch

  • 可以使用任何类型或表达式作为条件语句
  • 不需要写break,一旦条件符合自动终止
  • 如希望继续执行下一个case,需使用fallthrough语句
  • 支持一个初始化表达式(可以是并行方式),右侧需跟分号
  • 左大括号必须和条件语句在同一行

    package main
    func main() {
    	swh := 1
    	switch swh {
    	case 0:
    		println(0)
    	case 1:
    		{
    			println(1)
    			println("OK")
    		}
    	default:
    		println("default")
    	}
    }
    
    package main
    func main() {
    	switch swh:=1;{
    	case swh > 0:
    		println(0)
    		fallthrough
    	case swh == 1:
    		{
    			println("OK")
    		} 
    	default:
    		println("default")
    	}
    }
    

goto, break, continue

  • 三个语法都可以配合标签使用
  • 标签名区分大小写,若不使用会造成编译错误
  • breakcontinue配合标签可用于多层循环的跳出标签同级
  • goto调整执行位置,与其它2个语句配合标签的结果并不相同

    package main
    func main() {
    	FLG:
    	for{
    		for i:=0;i<10;i++{
    			if i>2{
    				break FLG
    			}else{
    				println(i)
    			}
    		}
    	}
    }
    
    package main
    func main() {
    FLG:
    	for i := 0; i < 10; i++ {
    		for {
    			println(i)
    			continue FLG
    		}
    	}
    }
    

    将上面中的continue替换成goto,程序运行的结果还一样吗❓

数组

  • 数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。
  • 数组的每个元素可以通过索引下标来访问,索引下标的范围是从0开始到数组长度减1的位置。
  • 内置的len函数将返回数组中元素的个数。
  • 值类型 数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。

    var a [3]int           
    fmt.Println(a[0])        
    fmt.Println(a[len(a)-1]) 
    
    for i, v := range a {
    	fmt.Printf("%d %d\n", i, v)
    }
    
    // Print the elements only.
    for _, v := range a {
    	fmt.Printf("%d\n", v)
    }
    

数组的每个元素都被初始化为元素类型对应的零值,也可以使用数组字面值语法用一组值来初始化数组。

var q [3]int = [3]int{1, 2, 3}
var r [3]int = [3]int{1, 2}
fmt.Println(r[2]) // "0"

在数组字面值中,如果在数组的长度位置出现的是“…”省略号,则表示数组的长度是根据初始化值的个数来计算。

q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"

数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。

q := [3]int{1, 2, 3}
q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int

可以指定一个索引和对应值列表的方式初始化,初始化索引的顺序是无关紧要的。

func main() {
	arr := [...]int{2: 10}
	fmt.Println(arr)//[0 0 10]
}
package main
import "fmt"
func main() {
	var arr1 [5]int = [5]int{}
	arr1[1] = 99
	var arr2 = [4]int{}
	// paintln 只能输出简单类型
	//println(arr1)
	fmt.Println(arr1)
	// 不同的类型不能比较
	//invalid operation: arr1 == arr2 (mismatched types [5]int and [4]int)
	// if arr1 == arr2 {
	// 	fmt.Println("arr1==arr2")
	// }
	//指向数组的指针
	var arr3 = new([3]int)
	fmt.Println(&arr2, arr3)
	// 由编译器推断数组大小
	arr4 := [...]int{1, 2, 3, 4, 5, 6, 10: 9}
	fmt.Println(arr4, len(arr4))
	// 值类型 copy
	arr5 := arr4
	fmt.Printf("%p,%p\n", &arr4[1], &arr5[1])
	//arr6:=[2][3]int{}
        //多维数组
	arr6 := [2][3]int{
		{1, 2, 3},
		{4, 5, 6},
	}
	fmt.Println(arr6)
}

range

  • 完整使用方式 for k,v:=range arr{ /* do something*/}
  • 索引方式 for item:=range { /* do something*/}
  • 值方式 for _,v:=range arr{/* do something*/}

    package main
    import "fmt"
    func main() {
    var arr = [10]int{2, 3, 4, 5, 6, 7, 8, 9}
    	for k,v:=range arr{
    		fmt.Println(k,v)
    		//i:=0
    		//fmt.Printf("i:%p\n",&i)
    		fmt.Printf("%p,%p\n",&k,&v)
    	}
    	println("oth")
    	for item:=range arr{
    		fmt.Println(item)
    	}
    	for _,v:=range arr{
    		fmt.Println(v)
    	}
    }
    

Slice

  • Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。
  • 一个slice类型一般写作[]T,其中T代表slice中元素的类型。
  • 变长数组啦

    type slice struct{
      len  int
      cap  int
      data point
    }
    

数组和slice之间有着紧密的联系。
一个slice由三个部分构成:指针、长度和容量。
指针指向第一个slice元素对应的底层数组元素的地址,slice的第一个元素不一定就是数组的第一个元素。
长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。
lencap函数分别返回slice的长度和容量。

alt

多个slice之间可以共享底层的数据,并且引用的数组部分区间可能重叠。
字符串的切片操作和[]byte字节类型切片的切片操作是类似的。都写作x[m:n],并且都是返回一个原始字节系列的子序列,底层都是共享之前的底层数组,因此这种操作都是常量时间复杂度。

Array_ori := [...]byte{'a', 'b', 'c', 'd', 'e', 'f', 'h', 'i', 'j', 'k'}
Slice_a := Array_ori[2:5]
Slice_b:= Array_ori[3:5]
fmt.Println(Array_ori, len(Array_ori))
fmt.Println(Slice_a, len(Slice_a), cap(Slice_a))
fmt.Println(Slice_b, len(Slice_b), cap(Slice_b))

alt

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

if summer == nil { /* ... */ }

一个零值的slice等于nil。一个nil值的slice并没有底层数组。一个nil值的slice的长度和容量都是0。 但也有非nil值的slice的长度和容量也是0的。

var s []int    // len(s) == 0, s == nil
s = nil        // len(s) == 0, s == nil
s = []int(nil) // len(s) == 0, s == nil
s = []int{}    // len(s) == 0, s != nil

内置的make函数创建一个指定元素类型、长度和容量的slice。容量部分可以省略(容量将等于长度)。

make([]T, len)
make([]T, len, cap) 

append函数

内置的append函数用于向slice追加元素

var arr []rune
for _, r := range "Hello,世界" {
	arr = append(arr, r)
}
fmt.Println(arr) //[72 101 108 108 111 44 19990 30028]
//%q 单引号围绕的字符字面值
fmt.Printf("%q\n", arr) // ['H' 'e' 'l' 'l' 'o' ',' '世' '界']

append函数则可以追加多个元素,甚至追加一个slice。 面向过程

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]"

Map

  • 无序的key/value对的集合,其中所有的key都是不同的,然后通过给定的key可以在常数时间复杂度内检索、更新或删除对应的value
  • 一个map就是一个哈希表的引用,map类型可以写为map[K]V,其中KV分别对应keyvalue的数据类型,keyvalue可以是不同的数据类型。
  • K对应的key必须是支持==比较运算符的数据类型,V对应的value数据类型则没有限制。

内置的make函数可以创建一个map:

ages := make(map[string]int) 

也可以用map字面值的语法创建map,同时还可以指定一些最初的key/value

ages := map[string]int{
	"zxy":   24,
	"zxysilent": 24,
}

等价于

ages := make(map[string]int)
ages["zxy"] =24
ages["zxysilent"] = 24

创建空的map的表达式是map[string]int{}

Map中的元素通过key对应的下标语法访问。

ages["zxy"] = 32
fmt.Println(ages["zxy"]) // "32"

内置的delete函数可以删除元素。

delete(ages, "zxy") 

⚠️所有这些操作是安全的,即使这些元素不在map中也没有关系;如果一个查找失败将返回value类型对应的零值。

ages["xxx"] = ages["xxx"] + 1

x += yx++等简短赋值语法也可以用在map上。

ages["xxx"] += 1

更简单的写法

ages["xxx"]++

不能对map的元素进行取址操作⁉️。

遍历 map

ages := make(map[string]int)
ages["zxysilent"] = 24
ages["zxy"] = 24
for k, v := range ages {
	fmt.Println(k, v)
}
fmt.Println("------------")
for k := range ages {
	fmt.Println(k)
}
fmt.Println("------------")
for _, v := range ages {
	fmt.Println(v)
}

遍历的顺序是随机的

map上的大部分操作,包括查找、删除、len和range循环都可以安全工作在nil值的map上,它们的行为和一个空的map类似。但向一个nil值的map存入元素将导致一个panic异常:

ages["carol"] = 21 // panic: assignment to entry in nil map

⛔️在向map存数据前必须先创建map。

通过key作为索引下标来访问map将产生一个value。如果key在map中是存在的,那么将得到与key对应的value;如果key不存在,那么将得到value对应类型的零值,有时候可能需要知道对应的元素是否真的是在map之中。

age, ok := ages["zxy"]
if !ok { /* todo */ }

变成一行

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

map的下标语法将产生两个值;第二个是一个布尔值,用于报告元素是否真的存在。

结构体

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

    // Person 结构体 描述人
    type Person struct {
    	Name     string    //姓名
    	Age      int       //年龄
    	Addr     string    //地址
    	Birthday time.Time //生日
    }
    
    var zxy Person
    

声明了一个叫Person命名的结构体类型,并且声明了一个Person类型的变量zxyzxy结构体变量的成员可以通过点操作符访问。

zxy.Name = "曾祥银"
fmt.Println(zxy.Name)

对成员取地址,然后通过指针访问。

age := &zxy.Age
*age = 24
fmt.Println(zxy.Age)

指向结构体的指针也可用点操作符。

alias := &zxy
alias.Age = 24
fmt.Println(zxy.Age)
(*alias).Age = 24
fmt.Println(zxy.Age)

一个命名为S的结构体类型将不能再包含S类型的成员但可以包含*S指针类型的成员。

type tree struct {
	data interface{}
	left  *tree
	right *tree
}

结构体字面值

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

两种形式的结构体字面值语法: 1. 以结构体成员定义的顺序为每个结构体成员指定一个字面值。 2. 以成员名字和相应的值来初始化,可以包含部分或全部的成员,顺序并不重要❗️。

type Point struct {
	X int
	Y int
}

func main() {
	p := Point{1, 2}
	p1 := Point{
		Y: 10,
		X: 3,
	}
}

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

func Scale(p Point, f int) Point {
	return Point{p.X * f, p.Y * f}
}
fmt.Println(Scale(Point{1, 2}, 5)) // "{5 10}"

较大的结构体通常会用指针的方式传入和返回, 要在函数内部修改结构体成员的话,用指针传入是必须的⛵️(值传递)。

创建并初始化一个结构体变量,并返回结构体的地址。

p := &Point{1, 2}

等价于

p := new(Point)
*p = Point{1, 2}
  • 结构体类型的零值是每个成员都是零值。
  • 结构体没有任何成员的话就是空结构体,写作struct{}。

结构体高级操作

//Point 一个点
type Point struct {
	X int
	Y int
}

//Circle 圆
type Circle struct {
	Point      //圆心//嵌套结构体匿名成员
	Radius int //半径
}

func main() {
	c := Circle{
		Point:  Point{0, 0},
		Radius: 10,
	}
	c1 := Circle{Point{0, 0}, 10}
	c1.X
	c1.Point.X
}

JSON

  • JavaScript对象表示法(JSON)是一种用于发送和接收结构化信息的标准协议。 还有 XMLProtocol Buffers等。

  • 标准库支持 encoding/json ✴️

序列化/编码 Marshal
> Go语言的数据结构数据转换为json字符串

//Point 一个点
type Point struct {
	X int
	Y int
}

//Circle 圆
type Circle struct {
	Point      //圆心
	Radius int //半径
}

func main() {
	c := Circle{
		Point:  Point{1, 2},
		Radius: 10,
	}
	//序列化
	buf, _ := json.Marshal(c)
	fmt.Println(buf)         //[123 34 88 34 58 49 44 34 89 34 58 50 44 34 82 97 100 105 117 115 34 58 49 48 125]
	fmt.Println(string(buf)) //{"X":1,"Y":2,"Radius":10}
}

在编码时,默认使用Go语言结构体的成员名字作为JSON的对象。 一个结构体成员标签Tag可以修改映射关系

//Circle 圆
type Circle struct {
	Point      //圆心
	Radius int `json:"半径"` //半径
}

c := Circle{
	Point:  Point{1, 2},
	Radius: 10,
}
//序列化
buf, _ := json.Marshal(c)
fmt.Println(string(buf)) //{"X":1,"Y":2,"半径":10}

反序列化/解码 Unmarshal
> json字符串转换为Go语言的数据结构

s := `{"X":1,"Y":2,"半径":10}`
	c := Circle{}
	json.Unmarshal([]byte(s), &c)
	fmt.Println(c)//{{1 2} 10}

文本和HTML模板

  • Go语言提供了对模板的支持(数据驱动模板)在”text/template”和”html/template”两个包下。
  • 使用方式类似,html/template主要针对htmltext/template 主要针对文本。

    package main
    
    import (
    	"html/template"
    	"os"
    )
    
    // Person 结构体 描述人
    type Person struct {
    	Name string   //姓名
    	Age  int      //年龄
    	Addr string   //地址
    	Arr  []string //oth
    }
    
    const str = `
    	<html>
    	<head>
    		<title>template</title>
    	</head>
    	<body>
    		<h2>{{.Name}}</h2>
    		<ul>
    			{{range $k,$v := .Arr}}
    				<li>{{$v}}</li>
    			{{end}}
    		</ul>
    	</body>
    	</html>
    `
    
    func main() {
    	z := Person{
    		Name: "曾祥银",
    		Age:  24,
    		Arr:  []string{"A", "B", "C", "D"},
    	}
    	t, _ := template.New("模板名称").Parse(str) //.ParseFiles 读取文件
    	t.Execute(os.Stdout, z)                 //os.Stdout 标准输出流 相当于 fmt.Ptintln
    }
    

alt

官方文档 https://golang.google.cn/pkg/html/template/

本文链接:/posts/goweb-032/

-- EOF --