Go语言——复合类型

2023-04-16 14:50:04 浏览数 (1)

1. 数组

a.【声明数组】

代码语言:javascript复制
var name [size] type
// 如果数组长度不确定,可以使用 ... 代替数组的长度,
// 编译器会根据元素个数自行推断数组的长度:
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

b.【初始化数组】

代码语言:javascript复制
name := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
// 1.	方式一:完整写法
	var arr [3]int = [3]int{1, 2, 3}
// 2.	方式二:短变量方式(常用)
	arr2 := [3]int{1, 2, 3}
// 3.	方式三:长度大于初始值个数.长度为4,只给前三个元素赋值,其余元素为默认值
	arr3 := [4]int{1, 2, 3}
// 4.	方式四:赋值时不写长度,数组长度根据元素个数确定(常用)
	arr4 := [...]int{1, 2, 3}
// 还可以通过指定下标来初始化元素
name := [...]float32{0:1000.0, 2:2.0, 3.4, 7.0, 50.0}

c.【数组作为参数传递】

代码语言:javascript复制
// 正常情况下 数组为值传递 即传递的是数组的值 在函数中对数组的操作 对于原数组无效。
func modify(array [5]int) {
	fmt.Println (array) // [1 2 3 4 5]
	array[0] = 10
	fmt.Println(array) // [10 2 3 4 5]
}
func main() {
	array := [5]int{1, 2, 3, 4, 5}
	modify(array)
	fmt.Println (array) // [1 2 3 4 5]
}

// 将数组结合指针作为参数传递 可以利用指针达到引用传递的效果
func modify(array *[5]int) {
	fmt.Println(*array) // [1 2 3 4 5]
	(*array)[0] = 10
	fmt.Println(*array) // [10 2 3 4 5]
}

func main() {
	array := [5]int{1, 2, 3, 4, 5}
	modify(&array)
	fmt.Println(array) // [10 2 3 4 5]
}

d.【冒泡排序】

代码语言:javascript复制
/*
	数组练习:冒泡排序
	注意:
		数组与切片的区别
*/
func main() {
	arr := [6]int{2,1,0,4,8,6}
	fmt.Println(BubbleSort(arr))
}

func BubbleSort(arr[6]int) [6]int {
	for i := 0; i < len(arr); i   {
		for i1 := 0; i1 < len(arr)-i-1; i1   {
			if arr[i1]<arr[i1 1] {
				continue
			}else {
				arr[i1],arr[i1 1] = arr[i1 1],arr[i1]
			}
		}
	}
	return arr
}

2. 二维数组

二维数组表示一个数组变量中每个元素又是一个一维数组变量,跟java一样 声明二维数组:

代码语言:javascript复制
var name [n] [m] 
// 使用和java一样  n为行 m为列

数组的声明与赋值:

代码语言:javascript复制
// 1.方式一:完整写法
var arr [3][3]int = [3][3]int{{1,2,3},{4,5,6},{7,8,9},}
// 2.方式二:短变量方式(常用) 注意 这里每行后都要有个逗号
arr := [3][3]int{
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9},
}

注意: golang中的二维数组和java不一样 java不可以直接打印二维数组 但是go可以。

3. 指针

所谓的指针就是一个地址 而*指针 就相当于操作指针指向的那段地址的内存。

a.【变量地址】

  1. 变量本质就是内存中一块数据的标记,把值存储到变量中实质是把值存储到内存中
  2. 每次对变量重新赋值就是在修改对应变量地址中的内容
  3. 重新创建一个非引用型变量(即使是把已有变量直接赋值给新变量)也会新开辟内存地址

b.【指针变量】

  1. 指针变量指向一个值的内存地址
  2. 使用& 变量 返回值就是一个指针类型
  3. 声明指针不会开辟内存地址,只是准备指向内存某个空间, 而声明变量会开辟内存地址,准备存放内容. 所以指针类型变量都是把一个变量的地址赋值给指针变量。
  4. 使用 * 指针 能够获取 和 操作 内存地址中的值 所以 * 指针 == 直接使用变量
  5. 应用指针可以实现多个地方操作同一个内存地址的值(在方法参数应用指针较多)

c.【指针的声明与赋值】

使用var变量名 *类型、 声明指针类型变量 【声明格式】 *var name type *号用于指定变量是作为一个指针。

代码语言:javascript复制
func main() {
    // 声明一个指针变量
	var a *int
	b:=1
    // 将指针指向变量b的内存地址
	a=&b
	*a = 2
	fmt.Println(b) // 2
}

使用new函数去创建指针, 使用new创建的指针是已经有指向的,所以可以直接通过 *指针 直接赋值; 而只声明的指针变量是不可以直接通过 *指针直接赋值的(野指针)。

new(type)会根据你给的type去开辟一个type大小空间 使指针指向这片空间,且不同于c语言 go语言的gc(垃圾回收)机制会自动释放这片空间。

【语法格式】 变量名 = new(type)

代码语言:javascript复制
// 不同于直接声明指针,使用new(type) 声明的指针有一个默认的
var ptr01 *int
fmt.Printf("%pn",ptr01) // 0x0
ptr02 := new(int)
fmt.Printf("%pn",ptr02) // 0xc0000ac080

d.【空指针与野指针】

指针的默认值为 即:空指针

指针变量指向一个未知的空间 即为:野指针

代码语言:javascript复制
// 空指针
var ptr01 *int
fmt.Println(ptr01) // nil
fmt.Printf("%pn",ptr01) // 0x0
// 野指针: 指针变量指向一个未知的空间 
*ptr01 = 1 // 会报错

e.【指针与数组 切片 map 结构体】

通过指针间接操作(数组 切片 map 结构体)的方法

代码语言:javascript复制
type Student struct {
	id int
}
func main() {
    // 与 数组 
    arr:=[3]int{1,2,3}
	s:=&arr
    // 方法一:(*变量名)[index]
	fmt.Println( (*s)[0] )
    // 方法二:变量名[index]
    fmt.Println( s[0] )
    
    // 与 结构体
	student := Student{1}
	i:=new(Student)
	i = &student
	i.id=2
	fmt.Println(student) // 2 
  
}

f.【指针作为参数传递】

指针作为参数传递为 引用传递

代码语言:javascript复制
func main() {
	a := new(int)
	*a = 1
	test(a)
	fmt.Println(*a)
}
func test(a *int) {
	*a = 2
}

g.【多级指针】

指针本身也是值,且这个值和 12 一样,是不可以寻址的,这也是为什么 &(&a) 不行,但是 先b = &a,再&b 却可以。

代码语言:javascript复制
func main() {
	s :=1
	p:= &s
	fmt.Println(a) // 变量的地址 0xc0000ac058
	fmt.Printf("%p",&p) // 一级指针的地址 0xc0000d8018
	pp:=&p
	fmt.Printf("%Tn",p) // *int 一级指针:指向变量的地址
	fmt.Printf("%Tn",pp) // **int 二级指针:指向一级指针的地址
}

通过二级指针 修改一级指针和变量的值

代码语言:javascript复制
func main() {
	s := 1
	p := &s
	s1 := 2
	pp := &p
	
	*pp = &s1   // 相当于将一级指针的指向 从s 改成了 s1
	fmt.Println(*p) // 2
    **pp = 100   // 直接修改变量的值
	fmt.Println(s1) // 100
}

4. 切片

代码语言:javascript复制
1.  切片的英文名称 slice
2.  切片:具有 可变长度 **相同类型**元素序列.
3.  由于长度是可变,可以解决数组长度在数据个数不确定情况下浪费内存的问题.
4.  切片和数组声明时语法最主要的区别就是长度
5.  切片只声明时为nil,没有开辟内存空间,不能直接操作切片,需要先初始化

注意:切片只能和nil进行判断是否相等

a.【切片是引用类型】

  1. 引用类型在变量之间赋值时传递的是地址.引用类型变量就是这个类型的指针.切片就是引用类型。
  2. 值类型在变量之间赋值时传递的是值的副本(就是复制了值 但是内存地址是重新开辟的)

b.【切片的声明与赋值】

代码语言:javascript复制
    var s1 []int //声明切片和声明array一样,只是少了长度,此为空(nil)切片
    s2 := []int{}

make 函数

  1. Go语言中可以使用make函数创建 slice、map、 channel、 interface
  2. 使用make函数定义 无内容,但不是nil 的切片,意味着切片已经申请了内存空间
  3. 语法格式: make(类型,初始长度[,初始容量])
  4. 初始容量可以省略,默认和长度相等
  5. 长度 表示切片中元素的实际个数, 容量 表示切片占用空间大小, 且切片成倍增加.当增加到1024后按照一定百分比增加。
  6. 可以使用len() cap() 查看长度和容量
  7. 注意 长度和容量的不同
代码语言:javascript复制
s := make([]int,1,3)
fmt.Printf("%p",&s) // 0xc000004078
fmt.Println(len(s))  // 1
fmt.Println(cap(s))  // 3
//  长度和容量的不同 
s := make([]int,0,3) // 此时有三个空位
s := make([]int,3,3) // 此时三个位置都是默认值 0

append 函数

  1. func append(slice []Type, elems …Type) []Type slice —> slice = append(slice, elem1, elem2) 或 slice = append(slice, anotherSlice…)
  2. 可以向切片中添加一一个或多个值,添加后 必须使用切片接收append()函数返回值
  3. 如果添加一次 添加多个值,且添加后的长度大于扩容一次的大小,容量和长度相等.等到下次添加内容时如果不超出扩容大小(就是原容量*2), 在现在的基础上进行翻倍。
  4. 如果向切片中添加的也是个切片 格式如下:(注意要加…) newSlice = append(oldSlice,array/slice [n:]…)
代码语言:javascript复制
s1 := make([]int, 0)
fmt.Println(s1)  // []
fmt.Println(len(s1),cap(s1))  	// 0 0
// 一次 添加多个值,且添加后的长度大于扩容一次的大小,会导致容量和长度相等.
s1 = append(s1,2,3,4)
fmt.Println(s1) // [2,3,4]
fmt.Println(len(s1),cap(s1)) 	//3 3
// 等到下次添加内容时如果不超出扩容大小(就是原容量*2), 在现在的基础上进行翻倍。
s1 = append(s1,5)
fmt.Println(s1)  //[2 3 4 5]
fmt.Println(len(s1),cap(s1)) 	//4 6
// 向切片中添加的也是个切片
a:=make([]string,0)
b:=make([]string,1,3)
b = append(b, "伤病","大傻逼")
a = append(b,b[1:]...)
fmt.Println(a) // [ 伤病 大傻逼 伤病 大傻逼]

通过数组产生切片

  1. 定义数组后,取出数组中一个片段,这个片段就是切片类型。
  2. 切片是指针,指向数组元素地址,修改切片的内容数组的内容会跟随变化。
  3. 当切片内容在增加时
  4. 如果增加后切片的长度没有超出数组,修改切片也是在修改数组(即和原数组指向同一个地址)
  5. 如果增加后切片的长度超出数组,会重新开辟一块空间放切片的内容
代码语言:javascript复制
slice := [] int {1,23,4,5}

// 1.如果增加后切片的长度没有超出数组,修改切片也是在修改数组(即和原数组指向同一个地址)
sb := [...]int{1,2,3,4,5}
s :=sb[0:]
s[0]=3
fmt.Println(sb) // [3 2 3 4 5]
// 2. 如果增加后切片的长度超出数组,会重新开辟一块空间放切片的内容
sb := [...]int{1,2,3,4,5}
s :=sb[0:]
fmt.Printf("%pn",&sb) //0xc00000c2a0
s = append(s,1,3,4)
fmt.Println(sb) // [1 2 3 4 5]
fmt.Println(s) // [1 2 3 4 5 1 3 4]
fmt.Printf("%pn",&s) // 0xc000004078

c.【切片元素的删除】

Go语言标准库中没有提供删除的函数

切片也可以取其中的一段形成子切片,利用这个特性可以实现删除效果(会导致原来的内容也随之改变 所以删除的话就不要使用内容了)

代码语言:javascript复制
slice := [5]int {1,2,3,4,5}
n:= 2
newSlice := slice[0:n]
newSlice = append(newSlice, slice[n 1:]...)
fmt.Println(newSlice) // [1 2 4 5]
newSlice[0]=8888
// 会导致原来的内容也随之改变 所以删除的话就不要使用内容了
fmt.Println(slice) // [8888 2 4 5 5]

d.【copy函数】

  1. 通过copy函数可以把一个切片内容复制到另一个切片中   func copy(dst, src []Type) int
  2. Go语言标准库源码定义如下 第一个参数是 目标切片 接收第二个参数内容 第二个参数是源切片,把内容拷贝到第一个参数中
  3. copy时严格按照角标进行
  4. 使用cope函数去实现删除功能(这个方法可以保证原切片内容不变)
代码语言:javascript复制
g:=[]int{1,2,3,4,5,6}
n := 2 //要删除元素的索引
newSlice := make([]int,n)
copy(newSlice,g[0:n])
newSlice = append(newSlice,g[n 1:]...)
fmt.Println(g) // [1 2 3 4 5 6]
fmt.Println(newSlice) // [1 2 4 5 6]

e.【排序】

代码语言:javascript复制
// 1. 正常冒泡排序
func main() {
	arr:=[]int{1,234,5,1,52,2,4}
	BubbleSort(arr)
}
func BubbleSort(arr[]int)  {
	for i := 0; i < len(arr); i   {
		for i1 := 0; i1 < len(arr)-i-1; i1   {
			if arr[i1]<arr[i1 1] {
				continue
			}else {
				arr[i1],arr[i1 1] = arr[i1 1],arr[i1]
			}
		}
	}
	fmt.Println(arr) // [1 1 2 4 5 52 234]
}

f.【切片作为参数传递】

代码语言:javascript复制
// 切片作为参数传递为引用传递,函数对切片的操作,同样也作用与原切片。
// 切片本身就是个引用变量 是一个指向某片内存的地址
func modify(array []int) {
	fmt.Println(array) // [1 2 3 4 5]
	array[0] = 10
	fmt.Println(array) // [10 2 3 4 5]
}

func main() {
	array := []int{1, 2, 3, 4, 5}
	modify(array)
	fmt.Println(array) // [10 2 3 4 5]
}

注意:使用append添加元素,容量足够,则在原基础之上添加数据,地址不会发生改变,容量如果不够,就会重新开辟一片空间。

代码语言:javascript复制
a:=[]int{1,3,4}
s:=a
s = append(s, 1,2,3,4,5,6)
fmt.Printf("%pn",a) // 0xc000012168
fmt.Println(a) // [1 3 4]
fmt.Printf("%pn",s) // 0xc0000160f0
fmt.Println(s) // [1 3 4 1 2 3 4 5 6]
//-------------------------------------------
b:=make([]int,0,3)
b = append(b, 1,2,3)
s:=b
s[0]=888
fmt.Printf("%pn",b) // 0xc000012168
fmt.Println(b) // [888 2 3]
fmt.Printf("%pn",s) // 0xc000012168
fmt.Println(s) // [888 2 3]

5. map集合

map以散列表方式存储键值对集合

map中每个元素都是键值对

声明格式: map [键的类型] 值的类型{key:value,key:value}

key是操作map的唯一标准可以通过key对map中元素进行增加/删除/修改/查看

key是唯一的,添加重 复的key会覆盖之前的元素.

map是 引用类型 (不是值类型,目前学到的引用类型就是切片和map),只声明时为空指针(nil)

map读写数据时并不是并发安全的,可以结合RWMutex保证并发安全(RWMutex在后面讲解)

代码语言:javascript复制
fmt.Println(map01) // map[名称:单冰 年龄:123]
map01["名称"]= "傻逼"
fmt.Println(map01) // map[名称:傻逼 年龄:123]

a.【实例化map的方式】

使用make函数 ap01:= make(map[int]int)

可以在声明map时直接给map赋初始值.注意初始值在一行和在多行写时的语法区别 := map[int]int{1:2,2:3}

map中元素键值对语法满足: key:value

key和value的类型必须和map[key]value类型严格对应

代码语言:javascript复制
map01 := make(map[int]string)
map02 := map[string]string{"年龄":"123","名称":"单冰"}

b.【操作map中的元素】

使用key判断,如果key不存在向map中新增数据,如果key存在会覆盖map中元素

Go语言标准库中提供了对map元素删除的函数,使用顶层delete()即可完成删除 如果key存在,执行删除元素 如果key不存在,map中内容不变,也不会有错误

代码语言:javascript复制
map01 := map[int]int{1:1,2:2}
delete(map01, 1)

查看map中key对应的value 如果有 返回对应value 如果没有 返回对应类型的默认值

value,ok := s[3] 返回俩值 一个是key对应value 一个是该key是否存在

代码语言:javascript复制
func main() {
    map01 := make(map[int]int)
    a,b:=map01[3]
    fmt.Println(a,b) // 0 false
}

c.【map作为参数传递】

同切片一样 都是引用传递,函数中对map的操作就是对原map的操作。

代码语言:javascript复制
func main() {
	map01 := map[int]int{1:1,2:2}
	modify(map01)
	fmt.Println(map01)

}
func modify(m map[int]int)  {
	m[3] = 3
	m[4] = 4
}

6. 结构体

有时我们需要将不同类型的数据组合成一个有机的整体。

如:一个学生有学号/姓名/性别/年龄/地址等属性。

显然单独定义以上变量比较繁琐,数据不便于管理。

代码语言:javascript复制
// 正常写法
var id int
var name string
var sex string
var age int 
var addr string
// 使用结构体表示法
type Student struct {
    id int
    name string
    sex string
    age int
    addr string
}

结构体是一种聚合的数据类型,它是由一系列具有相同类型或不同类型的数据构成的数据集合。

每个数据称为结构体的成员。

注意:结构体的定义是在主函数的外面的。

a.【结构体定义与初始化】

定义:

type 结构体名 struct {

​ (结构体成员列表) ​ 成员名 数据类型

}

定义结构体变量:

var 变量名 结构体名

代码语言:javascript复制
type Student struct {
    id int
    name string
    sex string
    age int
    addr string
    
    function func(int2 int) string
	slice []int
    arr [4]int
    map01 map[int]int
}

注意:

  1. 结构体名 大小写问题,见可见性部分。

初始化:

代码语言:javascript复制
//1. 顺序初始化(每个成员必须初始化 且顺序要一致)
var stu1 Student = Student{1,"xxx","男",1,"地球"}
//2. 自动推导 和指定成员赋值 未赋值的成员为默认值
stu1 := Student{1,"xxx","男",1,"地球"}
stu1 := Student{name: "xxx",id: 1}

fmt.Println(stu1.name) // xxx

b.【结构体变量的定义与赋值】

代码语言:javascript复制
var stu1 Student
stu1.id = 1
stu1.name = "xxx"

注意:

结构体名默认指向的就是第一个成员的地址

代码语言:javascript复制
var stu1 Student
stu1.id = 1
stu1.name = "xxx"
fmt.Printf("%pn",&stu1) // 0xc00001e080
fmt.Printf("%pn",&stu1.id) // 0xc00001e080
fmt.Printf("%pn",&stu1.name) // 0xc0000dc008

类似于java中的 类 student类 可以有成员变量 也可以有成员方法 。

c.【结构体数组 与 切片 与 map】

代码语言:javascript复制
type Student struct {
	id int
	age int
}

func main() {
	var stu01 Student
	var stu02 Student
	stu01.id=1
	stu02.id=2
	// 存放类型为结构体类型的数组、和 切片
	var stuArr = [2]Student{
		stu01,
		stu02,
	}
	fmt.Println(stuArr)
	var stuSli = []Student{
		stu01,
		stu02,
	}
	fmt.Println(stuSli)
	var stuMap  = map[int]Student{
		1:stu01,
		2:stu02,
	} 
	fmt.Println(stuMap)
}

d.【结构体作为参数传递】

结构体作为函数参数传递的时候为值传递

代码语言:javascript复制
type Student struct {
	id int
	age int
}
func main() {
	var stu01 Student = Student{1,19}
	test001(stu01)
	fmt.Println(stu01) // {1 19}

}
func test001(student Student)  {
	student.age=333
}

e.【可见性】

Go语言对关键字的增加非常吝啬,其中没有private、 protected、 public这样的关键字。

要使某个符号对其他包(package)可见(即可以访问),需要将该符号定义为以大写字母开头。

0 人点赞