Go学习记录(二)

本文详细介绍了Go语言中的Map操作,包括声明、初始化、容量设置、键值判断、遍历以及存储结构。同时,讨论了如何在并发环境中确保Map的安全性,如使用锁和sync包。此外,还涵盖了Go语言的包管理、正则表达式、自定义包、结构体和方法,以及方法重写和继承特性。通过对Map的深入理解,有助于提升Go编程的效率和安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

本文为Go学习过程中记录的笔记,参考文档如下:《Go入门指南》

Map

1. map是引用类型,可以使用如下声明:

var map1 map[keytype]valuetype
var map1 map[string]int
map1 := make(map[string]int)

key可以是任意可以用==或者!=操作符进行比较(这里的比较其实是指Hash()返回值的比较)的类型,因此切片、函数、map不能作为key。但是指针和接口类型可以。如果要用结构体作为key需要实现相应的Key()和Hash()方法。

map也可以将函数用来作为自己的值,这样可以用来做分支结构。

map的索引效率是很高的,比线性查找快很多,但如果是在已知目标值的索引位置的情况下,map的效率则远远低于数组和切片。

如果key1是map1的key,可以直接通过索引的方式来获取key1的值,即map1[key1],key1的值也可以通过这种方式来设置。索引对象如果没有设置相应的值,在获取时则为值类型的零值。

2. 使用new初始化和使用make初始化的区别:

使用new初始化一个map对象,实际上并没有完成初始化的工作,只是系统分配了一块内存存储map,但这块内存并没有初始化,而new返回了指向这块内存的一个指针,即指向nil的指针。而使用make初始化,则完成了内存分配以及对象初始化的工作,同时将对象的引用地址返回。

3. map容量:

map2 := make(map[string]int,100)

这里的初始化中,第二个参数为map的容量,即使不设置也不会影响,因为map是可以动态扩容的。具体的扩容算法需要进行相关的资料查询。

4. 判断key是否存在:

前面提到,map类型在取key的时候,即使key不存在,也会返回相应类型的零值,因此我们怎么判断这个零值时因为key不存在而产生的,还是key本身就是设置的零值。

val,isPresent := map[key]
//这里的isPresent如果存在key则为true,否则为false
//如果不想取值可以使用"_",同时搭配if使用
if _,ok:=map[key];ok{
    // ...
}

如果想要删除key,直接使用delete(map1,key1)即可,如果key不存在并不会产生错误。

5. for-range遍历map:

for key,value := range map1{
    ...
}
//只想获取value
for _,value := range map1{
    ...
}
//只想获取key
for key := range map1{
    ...
}

6. 存储map的切片:

如果我们想获取一个map类型的切片,我们必须使用两次make()函数,第一次分配切片,第二次则是分配切片中的map。

7. 对map的排序:

由于map中存储的元素,是根据Hash()来进行计算决定存储位置的,因此其内部是无序的,既不按照存放顺序,也不按照存放元素的大小进行排序。因此如果想要对map中的key或者value进行排序的话, 需要先将其导出到数组或者切片中,再使用sort包中的相关方法进行排序。

8. 将map中的键值对调:

并没有一个内置函数可以实现该功能,如果需要将map的键值对调,需要额外设置一个外部map,遍历原始map,然后一一设置其中的值即可。

这里需要注意的是,原始map中的value类型如果是切片,map或者函数的话,则无法实现键值对调。而且这种对调的方式可能会存在Hash冲突的问题,可以使用存储原始map中key类型的切片来解决Hash冲突。

1. 一些包的介绍:

unsafe:			一般程序中不会调用,可用在C/C++程序中

syscall:		调用外部操作系统命令的包

archive/tar:	压缩(解压缩)文件
/zip-compress:	压缩(解压缩)文件

fmt:			格式化输入输出的功能
io:				基本输入输出
bufio:			带缓冲的输入输出功能
path/filepath:	用来操作在当前系统中的目标文件名路径
flag:			提供对命令行参数的操作

strings:		对字符串的操作
strconv:		提供将字符串转换为基础类型的功能
unicode:		为unicode型字符串提供特殊的功能
regexp:			正则表达式
bytes:			提供对字符型分片的操作

math:			基本的数学函数
math/cmath:		对复数的操作
math/rand:		伪随机数生成
sort:			排序算法相关

list:			实现双链表以及对双链表的操作
ring:			实现循环链表以及对循环链表的操作

time:			日期和时间的基本操作
log:			日志相关操作

encoding/json:	读取并解码和写入并编码JSON数据
encoding/xml:	xml解析器
text/template:	模板类,生成HTML等模板

net:			提供网络数据的基本操作
html:			HTML5解析器
http:			提供HTTP服务

reflect:		反射相关

2. regexp包:

ok,_ := regexp.Match(pat,[]byte(searchIn))
//这里的pat是正则表达式,searchIn是string类型的变量
//需要将其转换为byte数组进行正则表达式匹配,或者使用另外一个方法
ok,_ := regexp.MatchString(pat,searchIn)

//通过Compile方法可以返回一个Regexp对象,可以通过该对象实现匹配、查找、替换等功能
re,_ := regexp.Compile(pat)
//将匹配到的部分替换为"##.#"
str1 := re.ReplaceAllString(searchIn,"##.#")
//将匹配到的部分交予函数处理然后替换,这里的f是一个函数
str2 := re.ReplaceAllStringFunc(searchIn,f)
//也可以使用MustCompile()方法,一样可以检验正则的有效性
//同时如果正则不合法则会panic

3. 锁和sync包:

Go语言的锁机制是通过sync.Mutex来实现的,sync.Mutex是一个互斥锁,它的作用是保证同一时间只有一个线程可以进入临界区。可以参考Java的Lock接口。

var lock sync.Mutex
lock.Lock()
...
lock.Unlock()

在sync保重还有一个RWMutex锁,即读写锁,提供了RLock()与Lock()方法,分别对应读锁的上锁和写锁的上锁。

var lock sync.RWMutex
lock.Lock()
lock.Unlock()
lock.RLock()
lock.RUnlock()

4. 精密计算和big包:

当int64和float64不符合计算要求的时候,可以使用big包中的big.Int类型和big.Rat类型,big.Int表示整数,big.Rat表示有理数,初始化分别如下:

big.NewInt(n)
//n为int64类型的整数,这里的大整数
//是指通过对int64类型进行计算得到更大的数
big.NewRat(N,D)
//N为int64类型的整数,作为分子
//D为int64类型的整数,作为分母

对大数类型的数据进行运算,不能使用原始的运算符,应该使用大数类型的相应方法,且返回结果也是大树类型,因此可以采用链式编程:

Add()
Mul()
Div()

5. 自定义包:

  • 自定义包名不能含有"_"下划线,且包名必须为小写单词;

  • 如果自定义包的安装目录与程序在同一目录下,我们可以通过以下方式引入:

    import "./pack1"
    
    import . "./pack1"
    //这种情况下相当于给包设置别名
    
    import _ "./pack1"
    //只导入其副作用,也就是说,只执行它的init函数并初始化其中的全局变量
    
  • 导入外部安装包:使用 “go install” 命令,完成命令后外部安装包会自动存放在$GOROOT目录下的相应文件夹中,然后就可以通过import使用了。

  • 包的初始化:八股文

6. godoc的使用:为文件生成网页版注释

godoc -http=6060 -goroot="."
//6060是生成文档的指访问端口
//"."是指当前目录,也可以使用绝对路径
//命令执行完之后就可以通过http协议到6060端口查看

7. 安装自定义包:

也是使用 “go install” 命令,通过这种方式将自定义包安装到pkg中,可以直接引用。

安装的过程:

在这里插入图片描述

结构体与方法

1. 结构体的定义:

type identifier struct{
    field1 type1
    field2 type2
    ...
}

初始化的方式:

var t *T = new(T)
//通过这种方式,得到的是指向目标对象的指针
//且结构体中的字段值为相应类型的零值
var t *T = &T{val1,val2,...}
//这种方式定义获取到的结果与上面一致
var t T = T{val1,val2,...}
//获取值对象并对其初始化

Go语言中,不管是对结构体指针还是对结构体值进行操作,都可以直接使用 “.” 选择器,这是不同于C的一个地方。同时,结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,不像Java中的引用类型,一个对象和它里面包含的对象可能会在不同的内存空间中。

2. 结构体转换:

使用了结构体别名,但只要结构体类型和它的别名类型都有相同的底层类型,它们可以实现正常转换。

3. 工厂方法建造实例:

可以定义一个外部可见的方法,将目标构造体的初始化过程在该方法中完成,并将其结构体指针作为返回值返回。对于小写名字(即外部不可见)的结构体,需要对其增加外部可见的工厂方法,才可以完成相应的声明并且使用,否则该结构体只在内部可用。

4. make关键字不可搭配结构体使用,make只可以创建切片,map和通道。

5. 带标签的结构体:

type TagType struct { // tags
    field1 bool   "An important answer"
    field2 string "The name of the thing"
    field3 int    "How much there are"
}
//这里filed后面的类型,后面的双引号中内容即为标签
//标签的内容可以通过反射得到,一般也只能通过反射得到

6. 结构体的匿名字段:

type innerS struct{
    in1 int
    in2 int
}

type outerS struct{
    b int
    c float32
    int
    innerS
}

结构体可以包含一个或多个匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即结构体可以包含内嵌结构体。

7. 字段命名冲突:

  • 如果命名的级别不一样,则外层的名字会覆盖内层的名字(子类会覆盖父类);
  • 如果相同的名字在同一级别出现了两次,如果这个字段又刚好被使用了,就会引发错误,但是不使用的时候就不会出现异常。

8. 方法:Go方法是作用在接收者上的一个函数,接收者是某种类型(除了接口)的变量。即,方法是某一种特殊类型的函数。

接收者不可以是一个指针类型,但是它可以是任何其他允许类型的指针?

一个类型加上它的方法等价于面向对象中的一个类。在Go语言中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件中,唯一的要求是:它们必须是同一个包的。类型T(或*T)上的所有方法的集合叫做类型T(或*T)的方法。同时,因为方法是函数,所以不允许方法重载,别名类型上不能拥有它原始类型已经定义过的方法。

定义方法的一般格式如下:

func (recv recv_type) methodName(parameter_list) (return_list){
    ...
}

类型在其他的,或是非本地的包里定义,在它上面定义方法都会引发错误。但是有一个间接的方式:可以先定义该类型的别名类型,然后再为别名类型定义方法,这种情况下方法只在别名类型上有效。

9. 函数和方法的区别:

函数将变量作为参数:Function1(recv)

方法在变量上被调用:recv.Method1()

10. 指针或值作为接收者:

鉴于性能的原因,recv最常见的是一个指向recv_type的指针(如果是实例的拷贝传输成本比较高?),特别是在receive类型是结构体时,就更是如此了。如果传递的是recv类型的指针,那么可以直接在方法体中改变方法调用实例的值,Go自动做了解引用工作。

指针方法和值方法都可以在指针或非指针上被调用,Go会完成对调用对象的处理:将指针转换为值以及将值转换为指针。

由于代码的可见性问题,假设我们定义了一个结构体类型Person,其中有字段firstName和lastName。在外部使用这个结构体的时候,firstName和lastName无法被直接使用,可以通过给Person添加 getter and setter 方法支持外部的访问。

11. 内嵌类型和方法继承:

当一个匿名类型被内嵌在结构体中时,匿名类型的可见方法也同样被携带至新结构体中,同时会被提至外部,直接调用新结构体实例的该方法即可。

 package main
 
 import (
     "fmt"
     "math"
 )
 
 type Point struct {
     x, y float64
 }
 
 func (p *Point) Abs() float64 {
     return math.Sqrt(p.x*p.x + p.y*p.y)
 }
 
 type NamedPoint struct {
     Point
     name string
 }
 
 func main() {
     n := &NamedPoint{Point{3, 4}, "Pythagoras"}
     fmt.Println(n.Abs()) // 打印5
 }

通过在新结构体中,编写一个与内嵌类型的方法同名的方法,可以实现覆写。

在Go中其实并没有继承的概念,但是我们可以通过使用内嵌的方式将内嵌类型的方法传递给新结构体,以此实现继承。

String()方法为格式化输出的默认方法,因此不要在String()方法里面调用涉及String()方法的方法,如:Println,这样会导致无限迭代。

13. 和其他面向对象语言比较:

在这里插入图片描述

14. SetFinalizer:

如果需要在一个对象obj被从内存移除前执行一些特殊操作,比如写到日志文件中,可以通过如下方式调用函数来实现:

 runtime.SetFinalizer(obj,func(obj *typeObj))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值