GO学习笔记

这篇博客详细记录了Go语言的学习过程,包括包管理、常量、变量、指针、函数、数组、切片、map以及类型和方法等方面的基础知识点。重点讨论了Go中变量声明、指针操作、函数传递、数组和切片的区别以及map的使用注意事项。

开始学习GO相关知识,记录基础知识点。学习过程主要参考the-way-to-go。

<一>

1.GO中包的存在可以理解为类似命名空间管理的作用,因此在当前包中可以通过引入包的方式来使用该包中的变量和函数。被引用包中变量或函数若首字母大写则表示在其他包中可见(类比java类中public),若为小写开头,即时引用包也无法使用其中变量或函数。如:

import "fmt"
fmt.Print("hello")

应用fmt包使用包中函数Print,首字母为大写。

2.常量相关
(1)常量在定义时可以利用显示或隐式
显式:const Pi int = 4
隐式:const Pi = 4
可以根据上下文确定变量类型。但常量定义必须在编译时就能获得
如: const Pi = getNumber()是不合法的。因为在编译期间自定义函数是未知的,但内置函数可以。
如:const Pi = len(s)

(2)每遇到一次const则itoa就重置为0。

3.:=申明赋值操作符一般用在局部变量中,不能用于全局变量。

4.局部变量声明必须使用,全局变量声明后可不使用。变量交换可简单的使用a, b = b, a 省去外部交换函数。

5.空白变量_是一个只写变量,因此可以用作抛弃值 如_. b := 5, 7 表示5被抛弃。

<二>

1.利用逻辑运算符短路的特性,对有多个表达判断式可以将复杂的放在右边,避免一些复杂的计算。

2.GO中不允许不同类型变量混合使用,常量不做限制

func main() {
	var a int
	var b int32
	a = 15
	b = a + a	 // 编译错误
	b = b + 5    // 因为 5 是常量,所以可以通过编译
}

3.++/–只可用在语句中,不可用在表达式中。
如:a = b++ 错误写法。
java/c等可以使用foo(i++)或b[i] = a[i++],但Go中不允许。

4.使用指针需要注意无法获取字面量或常量地址,如:P := &1不合法。

5.Go中不支持指针运算,如:c = *p++不合法。

6.指针可以使用反向引用(间接引用),如:var p *int; t := 10; p = &t; print(*p)//10
但空指针无法使用反向引用。

<三>

1.函数类型:
(1)普通命名函数
(2)匿名函数或者lambda函数
(3)方法
Go中函数不支持重载。

2.函数中不能声明函数(不能嵌套),可通过匿名函数打破该限制。

3.GO中支持传递变长参数的变量,可理解为相同类型的slice,如:

func test(temp ...int){
	for _, v := range temp {
		fmt.Println(v)
	}
}

func main(){
	   test(3,4,5)
	}

此时test中相当于temp值为[]int{3,4 ,5}

若传递参数个数和类型不确定可以考虑使用空接口…interface{},如:

func test(temp ...interface{}){
	for _, v := range temp {
		fmt.Println(v)
	}
}

func main(){
	   test(3,"test",5)
	}

<四>

1.一个返回值为另一个函数的函数可以被称之为工厂函数,这在您需要创建一系列相似的函数的时候非常有用:书写一个工厂函数而不是针对每种情况都书写一个函数。如下:

func MakeAddSuffix(suffix string) func(string) string {
	return func(name string) string {
		if !strings.HasSuffix(name, suffix) {
			return name + suffix
		}
		return name
	}
}

生成函数:

addBmp := MakeAddSuffix(".bmp")
addJpeg := MakeAddSuffix(".jpeg")

使用函数:

addBmp("file") // returns: file.bmp
addJpeg("file") // returns: file.jpeg

2.可以使用闭包函数进行调试:

```go
where := func() {
	_, file, line, _ := runtime.Caller(1)
	log.Printf("%s:%d", file, line)
}
where()
// some code
where()
// some more code
where()//2020/05/07 10:24:08 F:/awesomeProject/test.go:27


在调试过程可以调用where()函数查看哪一行代码执行了

3.GO中数组和切片,数组是固定长度的相同元素的集合,而切片是不定长度的,个人理解为可以类比java的Arraylist和数组的关系。其中数组长度一般为常量,是定值,也可以用…参数可变的形式,但必须在申明时就进行初始化:

a := [...]string{"a", "b", "c"}
//正确
var arr [...]int
//这种写法是错误的

4.GO中数组是值类型而不是指针指向首元素地址。因此当一个数组赋值给另一个数组时需要再做一次数组内存值拷贝(不同于c)
var arr1 [5]int, var arr2 *[5]int,此时arr1 = *arr2
此时修改arr1[2] = 199,在赋值后由于已经有了不同值,因此再修改arr1时arr2的值不会改变。
所以在函数中数组作为参数传入时,如 func1(arr2),会产生一次数组拷贝,func1 方法不会修改原始的数组 arr2。
如果你想修改原数组,那么 arr2 必须通过&操作符以引用方式传过来,例如 func1(&arr2),下面是一个例子:

func main(){
	arr := [5]int{1,2,3,4,5}
	changeVal1(arr)
	fmt.Println("changeVal1:")
	for _, v := range arr{
		fmt.Println(v)
	}//输出1,2,3,4,5
	changeVal2(&arr)
	fmt.Println("changeVal2:")
	for _, v := range arr{
		fmt.Println(v)
	}//输出2,3,4,5,6
}

func changeVal1(arr [5]int){
	 for i := 0; i < len(arr); i++{
	 	arr[i] = arr[i] + 1
	 }
}

func changeVal2(arr *[5]int){
	for i := 0; i < len(*arr); i++{
		(*arr)[i] = (*arr)[i] + 1
	}
}

5.证明Go数组时值传递:

func main(){
	var arr *[2]int
	temp := [2]int{1,2}
	arr = &temp
	var arr2 [2]int
	arr2 = *arr
	arr2[0] = 3
	fmt.Println((*arr)[0])//1
	fmt.Println(arr2[0])//3由于是值传递,因此arr2的改变不影响arr
	
}

6.指针数组和数组指针的区别:
指针数组指的是数组中的每个元素都是指针类型,申明方式: var arr [5]*int,赋值方式:

var arr [2]*int
i, j := 1, 2
arr[0] = &i
arr[i] = &j

数组指针指的是指向数组的指针,申明方式: var arr *[2]int,赋值方式:

temp := [2]int{1, 2}
var arr *[2]int
arr = &temp

<五>

1.在函数中传递数组参数时为避免内存的浪费通常传递数组指针或数组的切片(数组切片更常用)

2.一般将切片作为函数的参数传递,切片本身是一种引用类型,因此不能再使用指针表示。

func sum(a []int){//切片作为参数
	for i := 0; i < len(a); i++ {
		a[i]++
	}
}

func main() {
	var arr = [5]int{0, 1, 2, 3, 4}
	sum(arr[:])
	fmt.Println(arr[0])//输出1,由于切片本身是引用类型,所以在函数中修改结果影响本身值。
}

3.new和make区别:
make一般用在切片,map,channel中,返回类型为T
new返回指针类型*T 如: var p *[]int = new([]int)

4.使用for-range过程中索引和值都是局部的,值是数组或切片值的拷贝,所以无法通过该值修改数组真正值。

for _, v := range temp {
    v++
}//不会修改数组或切片的值,需要通过下面的方法修改

for k, v := range temp {
		temp[k] = v + 1
	}

5.GO字符串时不可被修改的,因此需要先转换为切片修改后再转为字符串。

6.GO提供搜索函数func SearchInts(a []int, n int) int,但由于该标准库函数是通过二分查找实现的,因此需要先对数组进行排序使得数组有序后再调用。

7.使用append函数插入数据时可以插入多个数据,也可以使用切片进行插入,具体形式如下:

test := make([]int, 3)
test = append(test[:2], append([]int{2}, test[2:]...)...)

append的第二个参数为切片时需要加…表示接受多个参数

<六>

1.数组、切片、结构体不能作为map的key,指针和接口可以作为map的key。

2.map是引用类型,因此可以使用make方法进行内存分配。如果错误的使用 new() 分配了一个引用对象,你会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址。

3.map键值对数目是可以动态伸缩的,因此当超过容量继续添加时每次容量自动扩充1。当键值对数目较多时为了避免每次只扩充1效率较低,可以在声明map容量时给定大致大小,避免频繁扩充容量。

4.GO中map是无序的,因此若想排序可以将key取出放入到切片当中进行排序。

5.GO中的map为了效率考虑是非线程安全的。可以使用互斥锁sync.Mutex来进行访问。

6.在 sync 包中还有一个 RWMutex 锁:他能通过 RLock() 来允许同一时间多个线程对变量进行读操作,但是只能一个线程进行写操作。如果使用 Lock() 将和普通的 Mutex 作用相同。包中还有一个方便的 Once 类型变量的方法 once.Do(call),这个方法确保被调用函数只能被调用一次。

<七>

1.使用.的方式引用包后使用包中的方法可以不通过包名点的形式引用import . “./pack1”。

2.当使用import _ xxx时表示只引用该包的init函数,初始化全局变量。

3.GO中没有继承,使用组合的方式来模拟继承。

4.类型和作用在它上面定义的方法必须在同一个包里定义,这就是为什么不能在 int、float 或类似这些的类型上定义方法。因此无法使用int等基本类型作为方法的接收者,同样无法使用List.list 作为方法的接收者(在List包中)。可以通过别名的方式来实现此目标。

5.方法指在某种类型接收者之上的特殊函数,接收者类型·必须和方法在同一个包中,接收者可以使用类型指针,但不能是指针类型。

6.方法不允许重载,但可以对相同方法名的方法指定不同的接收者类型。

<八>

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值