Go语言基础知识一

Go语言基础篇

go的包管理工具

![image][go_package]

  • Gopath < go 1.5. 2009.11.10 随go语言诞生,通过统一包存放的路径实现包管理,不支持依赖包的版本控制。
    Gopath模式和Gopath路径的区别:Gopath模式是指我们通过Gopath来管理我们的包,Gopath路径指的是GOPATH这个环境变量的路径。 GoRoot目录是Go语言的安装路径,GoPath跟GoRoot不能是同一个目录。
    go get: 1.先将远程代码克隆到 G O P A T H / s r c 目录下 2. 执行 g o i n s t a l l 命令。可以指定 − d 参数仅下载不安装。 ∗ ∗ g o i n s t a l l : ∗ ∗ 包如果可生成可执行的二进制文件,则存储在 GOPATH/src目录下 2.执行go install命令。可以指定-d参数仅下载不安装。 **go install:** 包如果可生成可执行的二进制文件,则存储在 GOPATH/src目录下2.执行goinstall命令。可以指定d参数仅下载不安装。goinstall:包如果可生成可执行的二进制文件,则存储在GOPATH/bin 普通包,会将编译生成.a结尾的文件放到 G O P A T H / p k g 下作为编译缓存加快后续的编译速度。如何判断一个包能否生成可执行文件呢?在 g o 语言中 m a i n 包中存在 m a i n 函数的情况下,才能生成可执行文件。 g o i n s t a l l 是建立在 G O P A T H 上的无法单独使用 g o i n s t a l l 生成的可执行文件的名称与包名一致 g o i n s t a l l 输出目录是不支持通过命令指定的。 g o 1.15 版本以后如果没有本地包, g o i n s t a l l 也会从远程下载依赖包。 ∗ ∗ g o b u i l d : ∗ ∗ 默认会在当前目录下编译生成可执行文件,可通过参数指定安装目录。不会将可执行文件复制到 GOPATH/pkg下 作为编译缓存加快后续的编译速度。 如何判断一个包能否生成可执行文件呢? 在go语言中 main包中存在main函数的情况下,才能生成可执行文件。 go install是建立在GOPATH上的 无法单独使用 go install生成的可执行文件的名称与包名一致 go install输出目录是不支持通过命令指定的。 go1.15版本以后 如果没有本地包,go install也会从远程下载依赖包。 **go build:** 默认会在当前目录下编译生成可执行文件,可通过参数指定安装目录。不会将可执行文件复制到 GOPATH/pkg下作为编译缓存加快后续的编译速度。如何判断一个包能否生成可执行文件呢?在go语言中main包中存在main函数的情况下,才能生成可执行文件。goinstall是建立在GOPATH上的无法单独使用goinstall生成的可执行文件的名称与包名一致goinstall输出目录是不支持通过命令指定的。go1.15版本以后如果没有本地包,goinstall也会从远程下载依赖包。gobuild:默认会在当前目录下编译生成可执行文件,可通过参数指定安装目录。不会将可执行文件复制到GOPATH/bin目录下。
    go run: 编译并运行go文件,一般在开发过程中测试使用。 go run不依赖GOPATH。 只能编译可执行的go文件 即main包中存在main函数的go文件。
  • GOPATH目录: src存放源代码文件 pkg存放编译后的文件 bin存放编译后的可执行文件
  • GoVendor >= go 1.5
  • Go Modules >= Go1.11 2018年8月 go1.11版本发布。Go1.13版本后 Go Modules默认开启
  • Go Moudles具体用法 1.环境准备 Go版本>1.11 2.设置go代理 3.初始化工程:go mod init {go Modules的名称}
  • go mod 常用命令 go mod init:初始化go module工程 2. go mod tidy:解决工程中包的依赖关系 3.go mod download:下载依赖包到本地缓存里。
go.mod文件

![image][go_mod]

![image][go_fake_version]

go.sum文件
  • 详细罗列了当前项目直接或间接依赖的所有模块的版本

![image][go_sum_1]

go.sum是怎么做包校验的?
  • 若本地缓存有依赖包,计算包的hash并与go.sum记录对比
  • 依赖包版本中任何一个文件 (包括go.mod)的改变都会改变hash
  • 哈希值是由算法SHA-256计算出来的
  • 校验目的是保证项目所依赖的那些模块版本不会被篡改
  • 公网可下载的包会去Go校验数据库获取模块的校验和(sum.golang.org/sum.golang.google.cn)
不会对依赖包做哈希校验的情况
  • GOPRIVATE匹配到的包
  • 打包到vendor目录中的包
  • GOSUMDB设置为off

同一个模块版本的数据只会缓存一份,所有其他模块共享使用

清除包缓存:go clean -modcache

为什么go.sum中版本数量会比go.mod多呢?
  • go.mod只在依赖包不含go.mod文件时才会记录间接依赖包版本
  • go.sum则是要记录构建用到的所有依赖包版本
关闭依赖包校验
  • go env -w GOSUMDB=off
  • 或者将环境变量添加到/etc/profile || export GOSUMDB=off(unix环境)
依赖包的存储
  • GOPATH模式下,依赖包存储在$GOPATH/src 只存放当前依赖版本的文件 包含.git目录
  • GOMODULE模式下,依赖包存储在$GOPATH/pkg/mod,存放各个版本的依赖包 只包含包文件 不包含.git目录

如何使用内部包?

internal文件夹内的包
  • go 1.4版本开始 可以使用internal文件限制包的导入权限
  • internal文件夹内的包只能被其父目录下的包或子包导入
go语言中函数和变量的可见性
  • 小写字母开头的函数、变量、结构体只能在本包内访问
  • 大写字母开头的函数、变量、结构体可以在其他包访问
公司内部开发的私有包
  • 通过本地包的方式导入

![image][go_local_pkg]

  • 通过私有仓库的方式导入

​ GOPRIVATE 在go1.13版本后开始支持,可以使用域名 也可以使用通配符。

​ GONOPROXY 也不通过GOPROXY拉取。一般和GONOSUMDB搭配。GONOSUMDB=$GONOPROXY匹配到的包都不去校验。

​ 通过GOPRIVATE拉取的包,自动地不去校验。

  • 工作区模式

init()函数是什么时候执行的?

init()函数的作用是什么?

  • 程序执行前包的初始化
  • 同一个go文件中 可以定义多个同名的init()函数,这一点和普通函数不一样。

init()函数的执行顺序

  • 在同一个Go文件中的多个init方法,按照代码顺序依次执行
  • 同一个package中,按照文件名的顺序执行
  • 不同package且不相互依赖,按照import顺序执行
  • 不同package且相互依赖,最后被依赖的最先被执行

init()函数的注意事项

  • 一个包被引用多次,这个包的init函数只会执行一次
  • 所有init函数都在同一个goroutine内执行
  • 注意包的循环导入问题

go文件的初始化顺序

  • 引入的包
  • 当前包中的常量 变量
  • 当前包的init
  • main函数

go语言如何获取项目的跟目录

  • os.Getwd() 打印的是当前命令的执行路径
  • os.Args[0]
  • os.Executable
  • runtime.Caller

![image][go_root_path]

  • 环境变量:1.通过GOPATH或者自定义项目根路径的环境变量 2.利用系统自带的环境变量和路径

Go输出时%v %+v %#v有什么区别?

go语言中各种类型的输出

  • go语言中一个中文占3个字符

![image][go_print]

![image][go_print_float]

![image][go_print_string]

![image][go_print_pointer]

fmt的几个print函数的区别

![image][go_fmt_diff]

%v %+v %#v

  • %v只输出结构体的所有的值,不会输出结构体的字段名
  • %+v不仅会输出结构体的值,还会输出结构体的字段名
  • %#v还会输出结构体的名字以及字段中指针类型的名称

go语言处理输入

![image][go_input_1]

![image][go_input_2]

Go语言中new和make有什么区别?

  • make不仅分配内存,还会初始化。new只会分配零值填充的值。

  • make只适用于slice,map,channel的数据,new没有限制。

  • make返回元素类型(T),new返回类型的指针(*T)。

    new和make函数在源码中的定义

    func make(t Type, size...IntegerType) Type
    func new(Type) *Type
    

    总结

    • new可以为任何类型的值开辟内存并返回此值的指针。
    • new申请的值均为零值,对创建映射和切片没有意义。
    • 实际工作中通常使用字面量来创建数组而很少使用new。

切片和数组的区别

数组和切片的相同点

  • 数组和切片都要求全部元素的类型都必须相同
  • 数组和切片的所有元素都是紧挨着存放在一块连续的内存中

slice和map的区别

  • map中的所有元素所在内存连续但不一定紧邻
  • 访问元素的时间复杂度都是O(1),但map更慢

map的优势

  • map的key值类型可以是任何可比较的类型
  • 对于大多数元素为零值的情况,map可节省大量内存

数组和切片的零值不同

  • 数组的零值是每个元素类型的零值
  • 切片的零值为nil
  • 指针类型的数组和切片直接用类型声明后是nil,不能直接使用

![image][go_zero_1]

![image][go_zero_2]

数组和切片的存储形式

![image][go_array_slice_memory]

切片的赋值过程

image

数组和切片的字面量表示方式

	//切片的字面量有下面几种表示方式:
	s1 := []string{"a", "b", "c"}
	fmt.Println("s1:", s1)
	s2 := []string{0: "a", 1: "b", 2: "c"}
	fmt.Println("s2:", s2)
	s3 := []string{2: "c", 1: "b", 0: "a"}
	fmt.Println("s3:", s3)
	s4 := []string{2: "c", 0: "a", "b"}
	fmt.Println("s4:", s4[1])

    //1.通过make申请
	var s6 = make([]int, 100)

	//2.初始化第100个元素
	var s7 = []int{99: 0}
	//
	3.先创建一个容量为100的数组取其地址后再截取全部元素作为切片
	var s8 = (&[100]int{})[:]
	//
	4.通过new创建一个容量为100的数组,再取全部元素
	var s9 = new([100]int)[:]
	 100 100 100 100
	println(len(s6), len(s7), len(s8), len(s9)) //100 100 100 100
	//数组的字面量有下面几种表示方式:
	a1 := [4]string{"a", "b", "c", "d"}
	fmt.Println("a1:", a1)
	//
	a2 := [4]string{0: "a", 1: "b", 2: "c", 3: "d"}
	fmt.Println("a2:", a2)
	//
	a3 := [4]string{1: "b", "a", "c"}
	fmt.Println("a3:", a3)
	fmt.Println("a3:","a3[0]:", a3[0], "a3[2]:",a3[2]," a3[3]:", a3[3])

	//如果不指定下标,将会从前一个下标开始递增
	a4 := [4]string{1: "b", "a", "c", "d"} //编译错误

	//这里元素c的下标也是1,而数组和切片的下标是不能重复的,即元素键值不能重复,但元素值可以重复
	a5 := [4]string{1: "b", 0: "a", "c", "d"}//编译错误

	a6 := [...]string{"a", "b", "c", "d"}
	a6[4] = "4"
	
	fmt.Println("a6:", a6)
	a7 := [...]string{3: "d", 1: "b", "a"}
	
	fmt.Println("a7:", a7, a7[2])
	//编译错误
	a8 := [...]string{3: "d", 1: "b", "a", "c"}//编译错误

	//大于等于0的常量整型可以作为数组和切片的下标,但变量不行
	const x uint = 1
	var y uint = 1
	a8 := []string{0: "a", x: "b", 2: "c"}
	fmt.Println("a8:", a8)
	a9 := []string{0: "a", y: "b", 2: "c"} 	//编译错误

数组的比较

  • 不同长度的数组不可比较
  • 数组不能跟nil比较,切片可以
  • 切片之间不能比较,但相同长度的数组可以

go语言中双引号和反引号有什么区别

go语言数据类型

![image][go_zero_1]

![image][go_zero_2]

go语言中的单引号

  • Go语言中没有专门的字符类型,单个字符一般使用byte来声明
  • 单引号用来定义一个byte或者rune
  • byte是uint8类型的别名,而rune是int32类型的别名

双引号和反引号的区别

  • 双引号的字符串支持转义,而反引号的字符串不支持转义

双引号定义字符串的两种表示方式

  • 字符串字面量
  • rune字面量
	//a,b,c的ASCII码值的十进制分别是97,98,99,对应的8进制为141,142,143
	//对于字符串abc我们可以通过
	s3 := "\141\142\143"
	fmt.Println("s3:", s3, reflect.TypeOf(s3))

	//a,b,c的ASCII码值的十进制分别是97,98,99,对应的16进制为61,62,63
	s4 := "\x61\x62\x63"
	fmt.Println("s4:", s4, reflect.TypeOf(s4))

	//unicode也是通过16进制的码值表示的
	s5 := "\u0061\u0062\u0063"
	fmt.Println("s5:", s5, reflect.TypeOf(s5))
	s6 := "\U00000061\U00000062\U00000063"
	fmt.Println("s6:", s6, reflect.TypeOf(s6))
  • 字符串字面量不支持单引号转义
  • 双引号字符串中不能包含单引号和双引号八进制ASCII码值
  • 双引号字符串中允许单引号和双引号的16进制ASCII码值

字符串相关考点

字符串的转义

字面量的表示

格式化输出

字符串的截取替换等操作

正则匹配

字符串与数值之间的转换

零拷贝复制

strings.TrimRight 和 strings.TrimSuffix有什么区别?

func main() {
	var s = "gogo123go面试专题goabcgogo"
	p := fmt.Println
	p("1:", strings.TrimPrefix(s, "go")) //go123go面试专题goabcgogo
	p("2:", strings.TrimSuffix(s, "go")) // gogo123go面试专题goabcgo
	p("3:", strings.TrimLeft(s, "go"))   // 123go面试专题goabcgogo
	p("4:", strings.TrimRight(s, "go"))  // gogo123go面试专题goabc
	p("5:", strings.Trim(s, "go"))       // 123go面试专题goabc
	p("6:", strings.TrimFunc(s, func(r rune) bool {
		return r < 128 // trim all ascii chars
	})) // 面试专题
}
  • trim = TrimLeft + TrimRight
  • Trim, TrimLeft, TrimRight是找到不满足条件的字符串为止
  • TrimPrefix 和 TrimSuffix是只要找到一个满足条件的就终止

数值类型运算后值溢出会发生什么?

值溢出问题

  • 无符号整型溢出时,值为0
  • 有符号整型的溢出,值出现反转
  • 数值类型转换时溢出
  • 整型转无符号整型时,要考虑符号丢失的问题。
  • 移位操作时要考虑位数是否会溢出。
  • 浮点型溢出后为最大值
  • 浮点数不能被移位

精度问题

  • 在指定范围内,将n位十进制数(按照科学计数法表示)与二进制数互转,如果数据不发生损失,就意味着在此范围内有n位精度。
  • 浮点数不满足结合律

iota计数器

  • const中每新增一行常量声明将使iota计数一次。
  • iota在const关键字出现时将被重置为0
  • 当且仅当iota出现在const声明中的第一行时,iota常量值才会从0开始。
const (
	_  = iota             //将0号计数占位,后面从1开始
	KB = 1 << (10 * iota) // <<移位操作,速度比乘除法快
	MB = 1 << (10 * iota)
	GB = 1 << (10 * iota)
	TB = 1 << (10 * iota)
	PB = 1 << (10 * iota)
)

Go语言中每个值在内存中只分布在一个内存块上的类型有哪些?

内存块

  • 内存块就是一段在运行时刻承载着若干值部的连续内存片段
  • 一个内存块可承载的值部可能不止一个

内存块什么时候开辟?

image

  • 拼接非常量字符串时
  • 字符串转字节切片或者字节切片转字符串的时候
  • 将一个整数转换为字符串
  • 调用内置append函数触发切片扩容时
  • 向map中添加元素,且map底层内部的哈希表需要改变容量时

内存块在哪里开辟?

  • 栈,本质上是一个预申请的内存段由协程维护,初始化最小2KB。
  • 协程栈尺寸有最大限制,64位系统512MB,32位系统128MB。
  • 开辟在栈上的内存块只能在此协程内部使用,与其他协程隔离。
  • 内存块也可以开辟在堆上,堆是个虚拟概念,每个程序只有一个堆。

内存块会开辟在堆上?还是会开辟在栈上呢?

  • 内存块会开辟的位置是由编译器来决定的,如果编译器不能确定某块内存只会被一个协程访问,那编译器就会将这个内存块开辟在堆上。编译器采取一种保守且安全的策略。

开辟在栈上的内存块与开辟在堆上的内存块相比有哪些优势呢?

  • 从栈上开辟内存块速度会更快。
  • 栈上开辟的内存块不需要被垃圾回收。
  • 栈上的内存块的访问对CPU缓存更加友好。

内存逃逸

  • 如果一个局部变量的某些值部被开辟到了堆上,我们就认为这个局部变量发生了内存逃逸。
  • 逃逸到堆上的值部至少会被一个开辟在栈上的值部所引用
  • 包级别的变量都被开辟到了堆上,并被一个全局内存区的隐式指针所引用。

Go值的直接值部和间接值部

  • 分布在不同内存块上的值就有直接值部和间接值部
  • 被直接值部引用的值就是间接值部
  • 直接值部是每个值只分布在一个内存块的类型,包括布尔、数值、指针、结构体和数组类型。
  • 间接值部是被一个或者多个直接值部引用的值,会分布在不同的内存块上
  • 包括切片类型,映射类型,通道类型,函数类型,接口类型,字符串类型。

Go语言中哪些类型可以被内嵌?

结构体的类型内嵌

  • 内嵌,其实就是字段只包含类型不包含字段名。
  • 一个指针类型*T只有在T为一个类型名并且T既不表示一个指针类型,也不表示一个接口类型的时候才能被用作内嵌字段。
	type s1 struct {
		// 可以被内嵌的类型
		m
		*m
		Int
		*Int
		IPerson
		Person
		*Person
		PersonPtr
		int
		*int

		//不能被内嵌的类型:
		IntPtr         //具名指针类型
		*IntPtr        //基类型IntPtr为指针类型
		AliasIntPtr    //基类型AliasIntPtr为指针类型
		*PersonPtr     //基类型PersonPtr为指针类型
		*IPerson       //基类型IPerson为接口类型
		[]int          //无名非指针类型
		map[string]int //无名非指针类型
		func()         //无名非指针类型
	}

内嵌的意义

  • 类似于面向对象编程中的继承。

[go_datatype]:

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值