Go复合数据类型�

数组和结构体都是聚合类型,它们的值是由内存中其他的值来组成的。
数组是同构类型的—每个数组中的元素都是一致的类型
结构体是异构类型的
数组和结构体都是具有固定内存大小的数据结构
切片和map则是动态的数据结构,他可以通过添加值而增长内存大小

Arrays 数组

数组是由固定长度的零值或特定类型的元素所组成的序列。
因为数组长度固定,所以很少在Go中直接使用
与数组相对的是切片Slice,他可以扩张和收缩,更加通用

数组的每个元素可以通过索引来访问,索引范围为0 ~ (len - 1)
注意:len(array) 返回的是数组元素的个数
默认,一个新的数组变量的元素会被初始化为元素类型的零值,我们可以使用数组字面量来初始化我们想要的值:

	var arr_int [2]int
	var arr_str [2]string

	var arr_str_value = [2]string{"1","2"} //字面量语法

	for index,no := range arr_int {
		fmt.Printf("index = %d and value = %d \n",index,no)
	}

	//输出
	index = 0 and value = 0 
	index = 1 and value = 0 
	
	for index,no := range arr_str {
		fmt.Printf("index = %d and value = %s \n",index,no)
	}
	//输出
	index = 0 and value =  
	index = 1 and value =  
	for index,no := range arr_str_value {
		fmt.Printf("index = %d and value = %s \n",index,no)
	}
	//输出
	index = 0 and value = 1 
	index = 1 and value = 2 

当初始化数组时候,初始长度可以使用[…]来表示,但是需要在{}放置初始化值,如下:

	arr_int_value := [...]int{1,2}
	for index,no := range arr_int_value {
		fmt.Printf("index = %d and value = %d \n",index,no)
	}

数组的大小也是类型的一部分。因此[3]int 与 [4]int是不同的类型,而数组的大小必须是一个常量表达式,因为数组的长度需要在编译时确定!

	arr01 := [3]int{1,2,3}
	arr01 = [4]int{1,2,3,4} //error

数组除了支持顺序的初始化,还支持索引初始化:

//未在初始化语句中初始化的索引,会被填充以零值(默认值)
arr_index := [...]string{1:"1",4:"4"}

type Currency int

const(
	RMB Currency = iota
	DOLLAR
)

money := [...]string{RMB:"¥",DOLLAR:"$"}

同类型的数组可以进行比较,只有当两个数组的所有元素都相等时,数组才相等。
crypto/sha256包中的函数Sum256函数,会将存储在任意字节切片中的消息,生成sha256密码散列或摘要。生成的摘要是256位的,因此它是[32]byte类型。如果两个摘要是相同的,那么其所对应的消息也是一样的,反之亦然。

	sum256_x := sha256.Sum256([]byte("x"))
	sum256_X := sha256.Sum256([]byte{'X'})
	fmt.Println(sum256_x)
	fmt.Println(sum256_X)
	fmt.Println(sum256_x == sum256_X)
[45 113 22 66 183 38 176 68 1 98 124 169 251 172 50 245 200 83 15 177 144 60 196 219 2 37 135 23 146 26 72 129]
[75 104 171 56 71 254 218 125 108 98 193 251 203 238 191 163 94 171 115 81 237 94 120 244 221 173 234 93 246 75 128 21]
false

当将数组传递给函数时候,实际是将拷贝传递给了函数,在函数内对数组的任何改变,并不会反馈到原始数组上。如果需要这种反馈机制,那么可以使用切片或者指针

Slice 切片

切片表示的变长的序列,序列内的元素的类型需一致。
序列的类型为[]T,其中T为元素类型,与数组区别的是没切片不需要有大小

切片是一个轻量级数据结构,他给予了对一个数组的全部或者一个子集的访问,而这个数组也被成为是切片的underlying array[底层数组]

切片有三个组件:

  1. point(指针):指针指向切片中第一个元素所对应的底层数组的元素的地址
  2. length(长度):即切片的长度
  3. capacity(容量):长度不能超过容量,容量则为切片起始位置到底层数组结束位置的元素数量
    内置的len()和cap()分别返回长度和容量

多个切片可以共享相同的底层数组,而且所引用的数组中的元素可以相互重叠:

	month := [...]int{0,1,2,3,4,5,6,7,8,9,10,11,12}
	summer := month[6:9] //切片
	//more := summer[:20] ---会报错,因为超过cap
	endlessSummer := summer[:5] //切片长度大于len小于cap,会自动扩展
	fmt.Println(summer)
	fmt.Println(endlessSummer)

字符串的切片操作与[]byte的切片是基本一致的,都写作x[m:n],且都返回一个原始序列的子序列(字符串本身就是字节序列),底层都是共享之前的底层数组。x[m:n]对于string,返回的是一个新的字符串,而对于[]byte则返回的是一个新的[]byte

	s := "hh哈哈"
	ss := s[2:5]
	b := []byte("xyz哈哈") 
	bb := b[2:5]
	fmt.Println(ss) //哈
	fmt.Println(bb) //[122 229 147]
	
	

因为切片包含指向底层数组的元素的指针,因此将切片传递给函数,可以允许函数修改底层的数组。换句话说,复制切片只不过是对底层的数组创建一个新的别名:

	nums := [10]int{1,2,3,4,5,6,7,8,9,10}
	reverse(nums[:])
	fmt.Println(nums)
	//[10 9 8 7 6 5 4 3 2 1]

切片的初始化与数组基本相似,只不过切片不需要指定长度。这样会在底层隐式的创建一个合适大小的数组,然后讲切片的指针指向该数组。

	init1 := []int{1,2,3}//顺序初始化[1 2 3]
	init2 := []int{0:0,2:2,3}//索引初始化+顺序初始化[0 0 2 3]

PS;切片无法比较,所以不能使用==或者!=来判断两个切片中的所有元素是否一致
Go函数库中提供了一个bytes.Equal函数来比较两个字节切片([]byte),而对于其他切片类型,则需要展开每一个元素进行比较:

func equals(s1,s2 []string) bool {
	if len(s1) != len(s2) {
		return false
	}
	for index,s := range s1 {
		if s != s2[index] {
			return false
		}
	}
	return true;
}

Slice不支持比较的原因:

  1. Slice的元素是间接引用的,它甚至可以引用自身
  2. 因为Sclice的元素是间接引用的,因此不同的时间,可能元素是不同的值,因为底层的数组是可以被修改的

Slice唯一合法的比较是与nil的比较,零值的Slice的类型就是nil,nil切片没有底层数组,且其length(长度)和capacity(容量)都是0,当然也有非nil的切片的length(长度)和capacity(容量)也是0,比如[]int{}或者make([]int, 3)[3:]

与任何可以有nil值的类型一样,特定类型的切片也可以有它的nil值,通过使用转换表达式,如:[]int(nil)

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

So:测试Slice是否为空,需要使用len(sclice) == 0,而不是slice == nil

make函数构造切片:(make会创建一个匿名的底层数组,以我们传递的参数为依据)

  1. make(type,length),例如:ints := make([]int, 2)
  2. make(type,length,capacity),例如;strings := make([]string, 2, 5)
    #####apend 添加
    Go内置了append函数,可以方便我们像切片中插入值
	makes := make([]int, 2, 2)
	fmt.Println(makes,"length = ",len(makes)," cap = ",cap(makes)) //[0,0] length =  2  cap =  2
	makes = append(makes,1)
	fmt.Println(makes,"length = ",len(makes)," cap = ",cap(makes)) // [0,0,1] length =  3  cap =  4
	makes = append(makes,2,3)
	fmt.Println(makes,"length = ",len(makes)," cap = ",cap(makes)) //[0,0,1,2,3] length =  5  cap =  8
Slice原处修改技术

模拟remove操作

func remove (ss []string,index int) []string {

	if index < len(ss) {
		copy(ss[index:],ss[index + 1 :])
		return ss[:len(ss)-1]
	} else {
		return ss
	}

}

Map

哈希是无序的K-V对的集合。
所有的key值均不同,但类型一致,通过key可以以常数时间复杂度检索出相对应的value(所有的value都是同一类型,由map的类型指定)
Go语言中,map是对哈希表的引用,map的类型为Map[K]V,K类型必须支持==比较操作。

	map1 := make(map[string]int)
	map1["piemon"] = 26
	map1["xxx"] = 99
	//等同于以下形式
	map2 := map[string]int{
		"piemon":26,
		"xxx":99,
	}
	//创建空的map
	empty := map[string]int{}

map通过内置的delete函数删除数据

	map2 := map[string]int{
		"piemon":26,
		"xxx":99,
	}
	delete(map2,"xxx")

当检索的key不存在于map中时,会返回value的零值(默认值)

	map2 := map[string]int{
		"piemon":26,
		"xxx":99,
	}
	map2["anokata"] += 1//map[piemon:26 anokata:1]
	

map支持+=、++

map的遍历:

  1. 仅遍历key
	//该map中存储的key为name,value为age
 	map2 := map[string]int{
		"piemon":26,
		"xxx":99,
	}
	for name := range map2 {
		fmt.Println(name)
	}
	//piemon
	//xxx
  
  1. 遍历键值
	for names,age := range map2 {
		fmt.Println(names,"=",age)
	}
  1. 遍历值
	for _,age := range map2 {
		fmt.Println(names,"=",age)
	}

Map的零值是nil,表示没有任何哈希表引用。
我们可以安全的在nil上操作查询、删除、len和range,它们的行为和空的map类似。
但是nil不可以操作插入,插入之前必须初始化map

	var null map[string]int
	fmt.Println(null == nil) //true
	fmt.Println(len(null)) // 0

因为可以对map操作不存在的key,当需要确认某个key是否存在时:

	var null map[string]int
	value,ok := null["piemon"]
	fmt.Println("ok = ",ok," value = ",value) // ok = false value = 
	if ok {
		//key存在
	}

与Slice相同,Map也不允许比较,但是可以与nil比较

Map的value可以是聚合类型,例如Slice/Map等


func addEgge(from,to string,complex map[string]map[string]bool) {

	view := complex[from]
	if view == nil {
		view = make(map[string]bool)
		complex[from] = view
	}
	view[to] = true
}

	complex := make(map[string]map[string]bool)

	addEgge("piemon","name",complex)
	fmt.Println(complex) //map[piemon:map[name:true]]

Structs 结构体

结构体是聚合类型,是由零或者多个任意类型的值组合而成的实体。
结构体中的每一个值成为它的属性(field)

下面是Employee和Address的结构体


type Address struct {

	Country string
}

type Employee struct {
	ID int
	Name string
	Address Address
	DoB time.Time
	Position string
	Salary int
	ManagerID int
}

对于结构体的属性,我们可以使用 【.】操作符来访问

	var piemon Employee
	piemon.Salary = 50000.00

亦可以操作属性的指针,但是结构体指针更加灵活:

	//基本数据类型的指针
	salary := &piemon.Salary
	*salary = 100000.00
	//结构体的指针
	address := &(piemon.Address) //结构体指针
	address.Country = "China"
	(×address).Country = "USA"

通常一行对应一个结构体属性,如果相邻的属性是相同类型的话,可以写在一行,与多常量/变量的声明类似。
需要注意的是结构体中的属性的定义顺序,与结构体类型密切相关
大写开头的属性则是导出的,这也是Go的访问控制机制。结构体内可以同时存在导出和非导出的属性

S结构体不可以包含S类型的属性(类似的限制也适用于数组。),但是可以声明*S类型的属性


type Tree struct {
	value int
	left,right *Tree//使用指针实现递归的数据结构
}

结构体的零值是由其所有属性的零值所组成的。

没有属性的结构体也被成为空结构体[empty struct],写作struct{},它的大小是0,而且不包含任何信息。
#####结构体字面量
结构体的值可以使用结构体字面量表示,字面量中可以指定所有的属性的值.Go字面量语法有两种形式:
语法一:顺序赋值


type Point struct {
	X,Y int
}
var point = Point{1,2}

语法二:指定赋值

p := Point{Y:20,X:10}

需要注意的是,Go语言中所有的参数都是值传递的,函数接收到的只不过是参数的拷贝,而不是原始参数的引用,所以,如果需要在方法中使得对于结构体的修改可以保持可见,那么我们在入参中需要传递指针,而不是结构体。

	//方式一
	p1 := &Point{1,2,""}
	//方式二
	p2 := new(Point)
	*p2 = Point{1,2,""}
	
	fmt.Println(p1,"  ",p2)
结构体比较

如果结构体的所有属性都是可比较的,那么结构体也可以比较
可比较的结构体可以作为Map数据结构的key

通过不同结构体的组合,我们可以实现复杂的结构体,但是对于内层属性的访问却变得冗长,例如:


	type Point struct {
		X,Y int
	}
	
	type Circle struct {
		Center Point
		Radius int
	}

	var pointer = Point{10,0}
	var circle = Circle{Center:pointer,Radius:2}
	//操作内部元素
	circle.Center.X = 20 //如果组合的比较复杂,那么语句会更复杂

Go提供了匿名属性【anonymous fields】,即所声明的属性只需要有类型即可,而不需要为属性指定属性名称。例如

	type Point struct {
		X,Y int
	}
	
	type Circle struct {
		Point //只需要声明属性的类型,而不需要为其指定属性名称
		Radius int
	}
	var pointer = Point{10,0}
	var circle = Circle{pointer,2}
	circle.X = 20 //更加简洁
	circle.Point.X = 30 //虽然没有了属性名称,但是我们依然可以访问匿名属性

匿名属性优点:

  1. 可以使用更短的路径访问到成员变量的属性,而不需要给出完整路径,同样也可以访问成员变量的方法
  2. 匿名属性可以通过类型名(即将属性类型名称作为了属性名称)来访问,其实匿名属性有一个隐含的名字
    匿名属性缺点:
  3. 无法应用到字面量语法上
  4. 因为匿名属性也有名称(即属性类型名称),所以不可以在一个结构体内有两个相同类型的匿名属性,会导致命名冲突

匿名类型不仅可以用于结构体类型的属性,而且可以应用于任何有名的类型,或者是任何有名的类型的指针。
使用匿名属性的一个原因是:这种简短的语法糖不仅可以应用于成员变量的属性,而且还可以应用于成员变量的方法集。

###JSON
JavaScript Object Notation(JSON)是针对发送和接收结构化信息的标准协议/格式
Go中负责JSON编码解码的包为;encoding/json

将Go数据结构转为JSON的的过程被称为marshaling,我们可以通过json.Marshal来实现:

	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"}},
	}

	for _,movie := range movies {
		data, err := json.Marshal(movie)
		if err != nil {
			log.Fatalf("JSON marshaling failed: %s", err)
		}
		fmt.Printf("%s\n", data)
	}
{"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"]}

	data, err := json.Marshal(movies)
		if err != nil {
			log.Fatalf("JSON marshaling failed: %s", err)
	}
[{"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"]}]

Marshal返回一个字节切片,该字节切片包含非常长的字符串,并且没有空白与缩进。
Go提供了一种美观的Marshal函数版本,即json.MarshalIndent:

	data, e := json.MarshalIndent(movies,"", " ")

PS:只有导出的属性才会被编码,即大写开头的属性。而未导出的属性不会被编码进JSON中。

属性标签[fiel d tags.],属性标签是一个在编译期间关联到该成员属性的元数据字符串

Year int `json:"released"`
Color bool `json:"color,omitempty"`

属性标签可以是任意的字符串字面量,一般格式为key:"value"对序列
因为标签内有双引号,所以一般使用原生字符串字面量来表示
实例中Year的key为“json”,表示它控制的是encoding/json包的行为,对于其他的encoding/…等其他包也遵循这种约定

实例中Year的value为“released”,用于指定Go的成员属性的替代名称。
实例中Color的value有两个,“omitempty”表示的是当成员属性的值为空或者零值时,则不将其转码进JSON

将JSON转为Go数据类型的过程称为unmarshaling,通过json.Unmarshal来实现

	//声明一个切片,该切片中元素是一个仅有Title属性的结构体
	var titles []struct{ Title string }
	//并将JSON解析为该结构体,因此除了Title之外的JSON数据都会被丢弃
	if err := json.Unmarshal(data, &titles); err != nil {
		log.Fatalf("JSON unmarshaling failed: %s", err)
	}
	fmt.Println(titles)//[{Casablanca} {Cool Hand Luke} {Bullitt}]
	

文本和HTML模板

text/template和html/template包分别提供了文本模板和HTML模板的功能

一个模板可以是一个字符串,也可以是一个文件,包含一个或者多个由双大扩报包围的部分:{{…}},我们称其为为actions。

模板中的字符串大部分是按照字面量打印,但是action会触发其他的行为。每个action都包含一个用模板语言书写的表达式:

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 // in Markdown format
}
type User struct {
	Login string
	HTMLURL string `json:"html_url"`
}

下面是字符串模板:

const templ = `{{.TotalCount}} issues:
	{{range .Items}}----------------------------------------
	Number: {{.Number}}
	User: {{.User.Login}}
	Title: {{.Title | printf "%.64s"}}
	Age: {{.CreatedAt | daysAgo}} days
	{{end}}`

该模板首先打印TotalCount属性的值,然后遍历Items数组,并分别打印Issue的Number、User.Login、Title以及CreatedAt。

在每一个Action中,都有一个当前值的概念,称作“点”,写作“.”
Action中的"|"类似于UNIX中的管道,表示将前面表达式的输出作为后面表达式的输入

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值