1. 数组
a.【声明数组】
var name [size] type
// 如果数组长度不确定,可以使用 ... 代替数组的长度,
// 编译器会根据元素个数自行推断数组的长度:
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
b.【初始化数组】
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.【数组作为参数传递】
// 正常情况下 数组为值传递 即传递的是数组的值 在函数中对数组的操作 对于原数组无效。
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.【冒泡排序】
/*
数组练习:冒泡排序
注意:
数组与切片的区别
*/
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一样
声明二维数组:
var name [n] [m]
// 使用和java一样 n为行 m为列
数组的声明与赋值:
// 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.【变量地址】
- 变量本质就是内存中一块数据的标记,把值存储到变量中实质是把值存储到内存中
- 每次对变量重新赋值就是在修改对应变量地址中的内容
- 重新创建一个非引用型变量(即使是把已有变量直接赋值给新变量)也会新开辟内存地址
b.【指针变量】
- 指针变量指向一个值的内存地址
- 使用&+变量 返回值就是一个指针类型
- 声明指针不会开辟内存地址,只是准备指向内存某个空间,
而声明变量会开辟内存地址,准备存放内容.
所以指针类型变量都是把一个变量的地址赋值给指针变量。 - 使用 *+指针 能够获取 和 操作 内存地址中的值 所以 *+指针 == 直接使用变量。
- 应用指针可以实现多个地方操作同一个内存地址的值(在方法参数应用指针较多)
c.【指针的声明与赋值】
-
使用var变量名 *类型、 声明指针类型变量
【声明格式】
*var name type
*号用于指定变量是作为一个指针。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)// 不同于直接声明指针,使用new(type) 声明的指针有一个默认的 var ptr01 *int fmt.Printf("%p\n",ptr01) // 0x0 ptr02 := new(int) fmt.Printf("%p\n",ptr02) // 0xc0000ac080
d.【空指针与野指针】
指针的默认值为 即:空指针
指针变量指向一个未知的空间 即为:野指针
// 空指针
var ptr01 *int
fmt.Println(ptr01) // nil
fmt.Printf("%p\n",ptr01) // 0x0
// 野指针: 指针变量指向一个未知的空间
*ptr01 = 1 // 会报错
e.【指针与数组 切片 map 结构体】
通过指针间接操作(数组 切片 map 结构体)的方法
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.【指针作为参数传递】
指针作为参数传递为 引用传递
func main() {
a := new(int)
*a = 1
test(a)
fmt.Println(*a)
}
func test(a *int) {
*a = 2
}
g.【多级指针】
-
指针本身也是值,且这个值和
1
,2
一样,是不可以寻址的,这也是为什么&(&a)
不行,但是 先b = &a
,再&b
却可以。func main() { s :=1 p:= &s fmt.Println(a) // 变量的地址 0xc0000ac058 fmt.Printf("%p",&p) // 一级指针的地址 0xc0000d8018 pp:=&p fmt.Printf("%T\n",p) // *int 一级指针:指向变量的地址 fmt.Printf("%T\n",pp) // **int 二级指针:指向一级指针的地址 }
-
通过二级指针 修改一级指针和变量的值
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. 切片
1. 切片的英文名称 slice
2. 切片:具有 可变长度 **相同类型**元素序列.
3. 由于长度是可变,可以解决数组长度在数据个数不确定情况下浪费内存的问题.
4. 切片和数组声明时语法最主要的区别就是长度
5. 切片只声明时为nil,没有开辟内存空间,不能直接操作切片,需要先初始化
注意:切片只能和nil进行判断是否相等
a.【切片是引用类型】
- 引用类型在变量之间赋值时传递的是地址.引用类型变量就是这个类型的指针.切片就是引用类型。
- 值类型在变量之间赋值时传递的是值的副本(就是复制了值 但是内存地址是重新开辟的)
b.【切片的声明与赋值】
var s1 []int //声明切片和声明array一样,只是少了长度,此为空(nil)切片
s2 := []int{}
-
make 函数
- Go语言中可以使用make函数创建 slice、map、 channel、 interface
- 使用make函数定义 无内容,但不是nil 的切片,意味着切片已经申请了内存空间
- 语法格式:
make(类型,初始长度[,初始容量]) - 初始容量可以省略,默认和长度相等
- 长度 表示切片中元素的实际个数, 容量 表示切片占用空间大小, 且切片成倍增加.当增加到1024后按照一定百分比增加。
- 可以使用len() cap() 查看长度和容量
- 注意 长度和容量的不同
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 函数
-
func append(slice []Type, elems …Type) []Type
slice —>
slice = append(slice, elem1, elem2) 或 slice = append(slice, anotherSlice…) -
可以向切片中添加一一个或多个值,添加后 必须使用切片接收append()函数返回值
-
如果添加一次 添加多个值,且添加后的长度大于扩容一次的大小,容量和长度相等.等到下次添加内容时如果不超出扩容大小(就是原容量*2), 在现在的基础上进行翻倍。
-
如果向切片中添加的也是个切片 格式如下:(注意要加…)
newSlice = append(oldSlice,array/slice [n:]…)
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) // [ 伤病 大傻逼 伤病 大傻逼]
-
-
通过数组产生切片
- 定义数组后,取出数组中一个片段,这个片段就是切片类型。
- 切片是指针,指向数组元素地址,修改切片的内容数组的内容会跟随变化。
- 当切片内容在增加时
- 如果增加后切片的长度没有超出数组,修改切片也是在修改数组(即和原数组指向同一个地址)
- 如果增加后切片的长度超出数组,会重新开辟一块空间放切片的内容
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("%p\n",&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("%p\n",&s) // 0xc000004078
c.【切片元素的删除】
-
Go语言标准库中没有提供删除的函数
-
切片也可以取其中的一段形成子切片,利用这个特性可以实现删除效果(会导致原来的内容也随之改变 所以删除的话就不要使用内容了)
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函数】
- 通过copy函数可以把一个切片内容复制到另一个切片中
func copy(dst, src []Type) int - Go语言标准库源码定义如下
第一个参数是 目标切片 接收第二个参数内容
第二个参数是源切片,把内容拷贝到第一个参数中 - copy时严格按照角标进行
- 使用cope函数去实现删除功能(这个方法可以保证原切片内容不变)
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.【排序】
// 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.【切片作为参数传递】
// 切片作为参数传递为引用传递,函数对切片的操作,同样也作用与原切片。
// 切片本身就是个引用变量 是一个指向某片内存的地址
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添加元素,容量足够,则在原基础之上添加数据,地址不会发生改变,容量如果不够,就会重新开辟一片空间。
a:=[]int{1,3,4}
s:=a
s = append(s, 1,2,3,4,5,6)
fmt.Printf("%p\n",a) // 0xc000012168
fmt.Println(a) // [1 3 4]
fmt.Printf("%p\n",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("%p\n",b) // 0xc000012168
fmt.Println(b) // [888 2 3]
fmt.Printf("%p\n",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在后面讲解)
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类型严格对应
map01 := make(map[int]string) map02 := map[string]string{"年龄":"123","名称":"单冰"}
b.【操作map中的元素】
-
使用key判断,如果key不存在向map中新增数据,如果key存在会覆盖map中元素
-
Go语言标准库中提供了对map元素删除的函数,使用顶层delete()即可完成删除
如果key存在,执行删除元素
如果key不存在,map中内容不变,也不会有错误map01 := map[int]int{1:1,2:2} delete(map01, 1)
-
查看map中key对应的value 如果有 返回对应value 如果没有 返回对应类型的默认值
-
value,ok := s[3] 返回俩值 一个是key对应value 一个是该key是否存在
func main() { map01 := make(map[int]int) a,b:=map01[3] fmt.Println(a,b) // 0 false }
c.【map作为参数传递】
同切片一样 都是引用传递,函数中对map的操作就是对原map的操作。
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. 结构体
有时我们需要将不同类型的数据组合成一个有机的整体。
如:一个学生有学号/姓名/性别/年龄/地址等属性。
显然单独定义以上变量比较繁琐,数据不便于管理。
// 正常写法
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 变量名 结构体名
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. 顺序初始化(每个成员必须初始化 且顺序要一致)
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.【结构体变量的定义与赋值】
var stu1 Student
stu1.id = 1
stu1.name = "xxx"
注意:
-
结构体名默认指向的就是第一个成员的地址
var stu1 Student stu1.id = 1 stu1.name = "xxx" fmt.Printf("%p\n",&stu1) // 0xc00001e080 fmt.Printf("%p\n",&stu1.id) // 0xc00001e080 fmt.Printf("%p\n",&stu1.name) // 0xc0000dc008
-
类似于java中的 类 student类 可以有成员变量 也可以有成员方法 。
c.【结构体数组 与 切片 与 map】
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.【结构体作为参数传递】
结构体作为函数参数传递的时候为值传递
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)可见(即可以访问),需要将该符号定义为以大写字母开头。