第二天
指针
- 一个指针变量指向了一个值的内存地址,类似于变量和常量。
- 指针声明格式如下:
var var_name *var_type
str := new(string)
//列
var ip *int
- 指针赋值
var a int= 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */
- 获取指针的值指针
/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip )
- Go 空指针
var ptr *int
var ptr *int;
fmt.Println(ptr)
fmt.Printf("ptr 的值为 : %x\n", ptr )
//输出: <nil>
//输出: ptr 的值为 : 0
- 指针数组
package main
import "fmt"
const MAX int = 3
func main() {
a := []int{10,100,200}
var i int
var ptr [MAX]*int;
for i = 0; i < MAX; i++ {
ptr[i] = &a[i] /* 整数地址赋值给指针数组 */
}
for i = 0; i < MAX; i++ {
fmt.Printf("a[%d] = %d\n", i,*ptr[i] )
}
}
- 指向指针的指针
package main
import "fmt"
func main(){
var a int = 5
//把ptr指针 指向5所在地址
var ptr *int = &a
//开辟一个新的指针,指向ptr指针指向的地方
var pts *int = ptr
//二级指针,指向一个地址,这个地址存储的是一级指针的地址
var pto **int = &ptr
//三级指针,指向一个地址,这个地址存储的是二级指针的地址,四级指针同上
var pt3 ***int = &pto
fmt.Println("a的地址:",&a,
"\n 值", a, "\n\n",
"ptr指针所在地址:",&ptr,
"\n ptr指向的地址:",ptr,
"\n ptr指针指向地址对应的值",*ptr,"\n\n",
"pts指针所在地址:",&pts,
"\n pts指向的地址:", pts,
"\n pts指针指向地址对应的值:",*pts,"\n\n",
"pto指针所在地址:",&pto,
"\n pto指向的指针(ptr)的存储地址:",pto,
"\n pto指向的指针(ptr)所指向的地址: " ,*pto,
"\n pto最终指向的地址对应的值(a)",**pto,"\n\n",
"pt3指针所在的地址:",&pt3,
"\n pt3指向的指针(pto)的地址:",pt3,//等于&*pt3,
"\n pt3指向的指针(pto)所指向的指针的(ptr)地址", *pt3, //等于&**pt3,
"\n pt3指向的指针(pto)所指向的指针(ptr)所指向的地址(a):",**pt3, //等于&***pt3,
"\n pt3最终指向的地址对应的值(a)", ***pt3)
}
//结果
/*
a的地址: 0xc0000140d8
值 5
ptr指针所在地址: 0xc00000e028
ptr指向的地址: 0xc0000140d8
ptr指针指向地址对应的值 5
pts指针所在地址: 0xc00000e030
pts指向的地址: 0xc0000140d8
pts指针指向地址对应的值: 5
pto指针所在地址: 0xc00000e038
pto指向的指针(ptr)的存储地址: 0xc00000e028
pto指向的指针(ptr)所指向的地址: 0xc0000140d8
pto最终指向的地址对应的值(a) 5
pt3指针所在的地址: 0xc00000e040
pt3指向的指针(pto)的地址: 0xc00000e038
pt3指向的指针(pto)所指向的指针的(ptr)地址 0xc00000e028
pt3指向的指针(pto)所指向的指针(ptr)所指向的地址(a): 0xc0000140d8
pt3最终指向的地址对应的值(a) 5
*/
- 总结
- 指针是指向值的地址
- 指向指针的指针是指向指针的地址
结构体
- 示例
package main
import "fmt"
type Book struct {
title string
author string
subject string
}
func main() {
fmt.Println(createBook())
//访问结构体成员
fmt.Println(createBook().title)
}
// 创建一个新的结构体
func createBook() Book{
// book := Book{"go", "ll", "编程语言"}
var book Book
//成员设置
book.title = "go"
// 忽略的字段为 0 或 空
return book
}
- 提示: 定义指向结构体的指针类似于其他指针变量
切片
-
Go 语言切片是对数组的抽象
-
切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大(可以理解为动态数组)
-
定义切片(使用make函数)
var slice1 []type = make([]type, len, capacity)//len为切片的初始长度。
var slice2 = [] int {1,2,3 } //slice2 后面不能跟类型[]中什么都不写
var slice3 = slice2[0:2] //将slice2中从下标s0到1下的元素截取出来创建为一个新的切片,不设置分别默认0和最后一个
- 切片函数
len(slice) //获取切片的长度
cap(slice) //获取切片的容量
slice = append(sliece, value) //向切片中添追加值,可以添加切片
copy(slice1,slice2) //拷贝 slice2 的内容到 slices1
语言范围(Range)
- Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
map集合
- 定义 Map
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
- 使用示例
package main
import "fmt"
func main() {
/*创建集合 */
// var countryCapitalMap map[string]string countryCapitalMap = make(map[string]string)
/* 创建map */
countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}
fmt.Println("原始地图")
/* 打印地图 */
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap [ country ])
}
/*删除元素*/
delete(countryCapitalMap, "France")
fmt.Println("法国条目被删除")
fmt.Println("删除元素后地图")
/*打印地图*/
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap [ country ])
}
}
-
map在只读的时候是线程安全的,在写的时候是线程不安全的
-
sync.Map(线程安全的map)
package main
import (
"fmt"
"sync"
)
func main() {
var scene sync.Map
// 将键值对保存到sync.Map
scene.Store("greece", 97)
scene.Store("london", 100)
scene.Store("egypt", 200)
// 从sync.Map中根据键取值
fmt.Println(scene.Load("london"))
// 根据键删除对应的键值对
scene.Delete("london")
// 遍历所有sync.Map中的键值对
scene.Range(func(k, v interface{}) bool {
fmt.Println("iterate:", k, v)
return true
})
}
- 列表(List)
var list_nam list.List//第一种定义方式
list_name := list.New()//第二种定义方式
-
列表与切片和 map 不同的是,列表并没有具体元素类型的限制,因此,列表的元素可以是任意类型,这既带来了便利,也引来一些问题,例如给列表中放入了一个 interface{} 类型的值,取出值后,如果要将 interface{} 转换为其他类型将会发生宕机
-
方法
//均返回操作节点句柄
list.InsertAfter(v interface {}, mark * Element) //在 mark 点之后插入元素,mark 点由其他插入函数提供
list.InsertBefore(v interface {}, mark * Element) //在 mark 点之前插入元素,mark 点由其他插入函数提供
list.PushBackList(other *List) //添加 other 列表元素到尾部
list.PushFrontList(other *List) //添加 other 列表元素到头部
list.Remove(element)
- 遍历(list是双链表,需要Front() 函数获取头元素,遍历时只要元素不为空就可以继续进行,每一次遍历都会调用元素的 Next() 函数)
l := list.New()
// 尾部添加
l.PushBack("canon")
// 头部添加
l.PushFront(67)
for i := l.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
接口
- Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
- 示例
package main
import "fmt"
type Phone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
phone.call()
phone = new(IPhone)
phone.call()
}
- 注意: 接口中的方法需要被全部实现
并发
- Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
- 同一个程序中的所有 goroutine 共享同一个地址空间
- 开启线程:
go func_name()
通道(channel)
- 通道声明:
ch := make(chan int)
- 发送值:
ch <- value
- 接受值:
value <- ch
- 注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
- 示例:
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int) //建立通道
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
通道缓冲区
- 通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
ch := make(chan int, 100)
-
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
-
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。