命令记不住

登陆

派生数据类型

16.派生数据类型

1.数组

var 数组名 [元素个数] 数据类型

var arr [10]int

var array [5] int // 全部为 0
var array [5] *int // 指针类型  

var array := [5] int {1,2,3,4,5} // 初始化
var array := [5] {1:1, 4:5} // 初始化 1, 5
var array := [...] {1,2,3,4,5} // 长度根据初始化确定
var array [5][2] int 二维数组
  • 数组特点

长度固定,不能修改
赋值和函数传递过程是值复制,涉及到内存 copy,性能低下

  • 数组在内存中的地址:
func main(){
  brr := [10]int{0,1,2,3,4,5,6,7,8,9}
  for i := 0; i < len(brr); i++ {
  	//& 取地址运算符
  	fmt.Println(&brr[i])
  }
}
C:\Users\vSphere\go\src\day02>go run 02.go
0xc000016230
0xc000016238
0xc000016240
0xc000016248
0xc000016250
0xc000016258
0xc000016260
0xc000016268
0xc000016270
0xc000016278
  • 数组的遍历
package main

import "fmt"

func main() {
	//数组定义和使用
	//var  数组名字 [元素的个数] 数据的类型
	//var  arr [10]int  默认值为0
	//使用数组名+下标进行数组初始化   下标是从0开始到数组最大元素-1
	//arr[0] = 123
	//数据下标越界  arr[-1] = 123

	//数组的初始化方式
	var arr [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
	brr := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

	fmt.Println(arr)
	fmt.Println(brr)
	/*
	      C:\Users\vSphere\go\src\day02>go run 02.go
	  	[1 2 3 4 5 6 7 8 9 0]
	  	[0 1 2 3 4 5 6 7 8 9]
	*/
	//len(数组名)  计算数组元素个数
	fmt.Println(len(arr))

	//遍历数组元素
	//方法1
	for i := 0; i < 10; i++ {
		fmt.Println(arr[i])
	}
	//方法2   i=index下标 v=value值
	for i, v := range arr {
		fmt.Printf("arr[%d]=%d\n", i, v)
	}

}

2.切片

切片的定义和使用

var 切片名 [ ]数据类型

  • slice声明方式
slice := make([]int, 5)
slice2 := make([]int, 0, 5)
  • slice数据结构
type slice struct {
	array unsafe.Pointer
	len   int //当前存储长度
	cap   int //可用长度
}

  • 计算方法

切片名[起始下标:结束下标:容量] 左闭右开 包含起始下标 不包含结束下标

s3 := s1[i:j]
len : j - i
cap : k - i , k为s1的长度  
  • 切片追加
slice = append(slice, 1, 2, 3)
slice = append(slice, slice2...)  

  • 示例
package main

import "fmt"

func main() {
	/*

		切片的定义和使用
		var  切片名字 []数据类型
		var  slice []int
		make([]数据类型,长度)
		var slice []int = make([]int,10)

		slice[0] = 123
		fmt.Println(slice)
		C:\Users\vSphere\go\src\day02>go run 03.go
		[123 0 0 0 0 0 0 0 0 0]

	*/

	var slice []int
	fmt.Println(len(slice)) //计算长度
	fmt.Println(cap(slice)) //计算容量

	//使用append对切片进行扩容
	slice = append(slice, 1, 2, 3, 4, 5)
	fmt.Println(len(slice)) //计算长度
	fmt.Println(cap(slice)) //计算容量
	/*
		C:\Users\vSphere\go\src\day02>go run 03.go
		0
		0
		5
		6
	*/

	//切片扩容为偶数的值    小于1024为上一次的2倍  大于1024为上一次的1/4
	fmt.Println(slice)
}
  • 切片的截取

切片的截取 是将新的切片指向源切片的内存地址 修改一个会影响另外一个

package main

import "fmt"

func main() {
	//切片的截取
	slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}

	//	切片名[起始下标:结束下标:容量]  左闭右开 包含起始下标  不包含结束下标

	s := slice[2:7]
	fmt.Println(s)
	s = slice[:7]
	fmt.Println(s)
	s = slice[2:]
	fmt.Println(s)
	s = slice[2:5:6] //实际容量=容量-起始下标
	fmt.Println(s)
	//实际容量=容量-起始下标
	fmt.Println(cap(s))
	/*
		C:\Users\vSphere\go\src\day02>go run 04.go
		[3 4 5 6 7]
		[1 2 3 4 5 6 7]
		[3 4 5 6 7 8 9 0]
		[3 4 5]
		4
	*/
}

查看占用空间

切片的截取可以确认是地址传递,会出现互相影响,但是append()之后会重新拷贝一份并不会互相影响;

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	//切片的截取
	slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	s := slice[2:5:6] //实际容量=容量-起始下标
	s[0] = 333
	//切片的截取 是将新的切片指向源切片的内存地址  修改一个会影响另外一个
	fmt.Println(s)
	fmt.Println(slice)

	fmt.Println(unsafe.Sizeof(slice))
	fmt.Println(unsafe.Sizeof(s))

	/*
		C:\Users\vSphere\go\src\day02>go run 05.go
		[333 4 5]
		[1 2 333 4 5 6 7 8 9 10]
		24
		24
	*/
}
  • unsafe.Sizeof()函数解释:

Sizeof接受任何类型的表达式x,并返回一个假设的变量的大小(以字节v好像被宣布通过var v = x。不包括任何内存大小可能引用的x。例如,如果x是一片,运算符返回的大小块描述符,而不是引用的内存块的大小。

查看占用空间

  • copy()在存储存储两个内容完全相同 但是不会相互影响
func main() {
	slice := []int{1, 2, 3, 4, 5}

	//s := slice
	//s[2] = 333
	//fmt.Println(s)
	//fmt.Println(slice)
	//在存储存储两个内容完全相同 但是不会相互影响
	s := make([]int, 5)
	copy(s, slice)
	s[2]=333
	fmt.Println(s)
	fmt.Println(slice)

}

C:\Users\vSphere\go\src\day02>go run 05.go
[1 2 333 4 5]
[1 2 3 4 5]

3.map

底层基于 Hash 实现,基于 Key-Value,无序的数据集合

dict := make(map[T_KEY]T_VALUE)

var 字典名字 map[键类型]值类型

key 的类型需要具备 == 和 != 操作
函数类型、字典类型和切片类型不能作为 key,不支持的操作类型会导致 panic

  • 检查值是否存在
package main

func main() {
	m := make(map[string]bool)

	_, h := m["hello"]
	if !h {

	}
	if _, h := m["hello"]; h {

	}
}

var m map[string]int

// nil 类型,添加和修改会导致 panic
nil: len/map[key]/delete(m, key)

// 可以正常工作
map 默认并发不安全,多个 goroutine 写同一个 map,引发竞态错误, go run –race 或者go build - race

map 对象即使删除了全部的 key,但不会缩容空间

  • map的定义和使用
package main

import "fmt"

func main() {
	//map的定义
	//var 字典名字 map[键类型]值类型
	var n map[int]string = make(map[int]string, 10)
	n[1001] = "x86"
	n[1002] = "cloud"
	fmt.Println(n)
	/*
	   C:\Users\vSphere\go\src\day02>go run 06.go
	   map[1001:x86 1002:cloud]
	*/

	//map是一个无序的集合
	m := map[int]string{1001: "x86", 1002: "cloud", 1003: "com", 1004: "cn"}
	fmt.Println(m)
	/*
	  C:\Users\vSphere\go\src\day02>go run 06.go
	  map[1001:x86 1002:cloud 1003:com 1004:cn]
	*/
	for k, v := range m {
		fmt.Println(k, v)
	}
	/*
	  C:\Users\vSphere\go\src\day02>go run 06.go
	  1004 cn
	  1001 x86
	  1002 cloud
	  1003 com
	*/

}
  • 判断map中是否存在value
func main() {
	//map中的key必须支持 ‘== ! =’运算操作符  不能是切片 map函数
	m := make(map[int]string)
	m[1001] = "x86"
	m[1002] = "cloud"

	//通过key判断value是否存在

	if v, ok := m[1001]; ok {
		fmt.Println(v)
	} else {
		fmt.Println("不存在key")
	}
}
  • map中进行删除键值操作delete()
func main() {
	//map中的key必须支持 ‘== ! =’运算操作符  不能是切片 map函数
	m := make(map[int]string)
	m[1001] = "x86"
	m[1002] = "cloud"
	//map原始长度以及键值
	fmt.Println(m)
	fmt.Println(len(m))
	//删除map中的数据
	delete(m, 1001)
	//删除后的长度以及键值
	fmt.Println(len(m))
	fmt.Println(m)
	fmt.Println(unsafe.Sizeof(m))
}

4.指针

  • 只要将数据存储在内存中都会为其分配内存地址。内存地址使用十六进数据表示
  • 内存为每一个字节分配一个32位或64位的编号(与32位或者64位处理器相关)
  • 可以使用运算符 & (取地址运算符)来获取数据的内存地址。
//定义变量
var i int = 10
//使用格式化打印变量的内存地址
//%p是一个占位符 输出一个十六进制地址格式
fmt.Printf("%p\n", &i)

如果想将获取的地址进行保存,应该怎样做呢?
可以通过指针变量来存储,所谓的指针变量:就是用来存储任何一个值的内存地址。

//定义指针变量
var 指针变量名 //默认初始值为nil 指向内存地址编号为0的空间
var 指针变量名 *数据类型 = &变量
package main

import "fmt"

func main() {
	var i int = 10
	//指针类型变量
	//指针变量也是变量  指针指向了变量的内存地址
	//对变量取地址  将结果赋值给指针变量
	var p *int = &i
	//打印指针变量p的值 同时也是i的地址
	fmt.Println(p)
}

指针变量,除了有正确指向,还可以通过new( )函数来指向。
具体的应用方式如下:

根据数据类型 创建内存空间 返回值为数据类型对应的指针
new(数据类型)

new( )函数的作用就是动态分配空间,不需要关心该空间的释放,Go语言会自动释放。

func main() {
	var p *int
	//创建一个int大小的内存空间 返回值为*int
	p = new(int)
	*p = 123
	//打印值
	fmt.Println(*p)
	//打印地址
	fmt.Println(p)
}
  • 通过指针间接修改变量值
package main

import "fmt"

func main() {
	a := 10
	//取出变量在内存中的值
	fmt.Println(&a)

	//指针的定义以及输出
	var p *int = &a
	fmt.Println(p)
	fmt.Println(*p)
	/*
		C:\Users\vSphere\go\src\day02>go run 07.go
		0xc00000a0d0
		0xc00000a0d0
		10
	*/

	//通过指针间接修改变量的值

	*p = 123
	fmt.Println(a)

	//不可以获取常量的地址
	const Max int = 100
	fmt.Println(&Max)
	/*
		C:\Users\vSphere\go\src\day02>go run 07.go
		# command-line-arguments
		.\07.go:28:14: cannot take the address of Max
	*/
}
  • 定义指针,默认值为nil指向内存地址编号为0的空间
  • 内存地址0-255为系统占用 不允许用户读写操作
package main

import "fmt"

func main(){
	//定义指针,默认值为nil,指向内存地址编号为0的空间
	//内存地址0-255为系统占用,不允许用户读写
	var p * int =nil
	var k  *int =nil  //*和int可以不留空格
	fmt.Println(p)
	fmt.Println(k)
	//给编号为0的内存地址空间进行存值
	*p = 123  //触发panic
	fmt.Println(*p)
	/*
	panic: runtime error: invalid memory address or nil pointer dereference
	[signal 0xc0000005 code=0x1 addr=0x0 pc=0x49df74]

	goroutine 1 [running]:
	main.main()
	        C:/Users/vSphere/go/src/day02/07.go:13 +0xd4
	exit status 2
	*/

	//开辟数据类型大小的空间  返回值为指针类型
	var k *int
	//new(数据类型)
	k = new(int)  //动态
	*k =123
	fmt.Println(*k)
}
C:\Users\vSphere\go\src\day02>go run 07.go
<nil>
123
推荐阅读