Golang 学习笔记 (1)
变量
变量
是几乎所有编程语言中最基本的组成元素。从根本上说,变量相当于是对一块数据存储
空间的命名,程序可以通过定义一个变量来申请一块数据存储空间,之后可以通过引用变量名来
使用这块存储空间。
- 变量声明
var v1 int
var v2 string
var v3 [10]int // 数组
var v4 []int // 数组切片
var v5 struct {
f int
}
var (
v1 int
v2 string
)
- 变量初始化
var v1 int = 10 // 正确的使用方式1
var v2 = 10 // 正确的使用方式2,编译器可以自动推导出v2的类型
v3 := 10 // 正确的使用方式3,编译器可以自动推导出v3的类型
- 变量赋值
在Go语法中,变量初始化和变量赋值是两个不同的概念。下面为声明一个变量之后的赋值
过程:
var v10 int
v10 = 123
Go语言中提供了C/C++程序员期盼多年的多重赋值功能,比如下面这个交换i和j变量的语句:
i, j = j, i
- 匿名变量
golang结合使用多重返回和匿名变量来避免传统因为该函数返回多个值而不得不定义一堆没用的变量的丑陋写法
假 设GetName()函数的定义如下,它返回3个值,分别为firstName、lastName和nickName:
func GetName() (firstName, lastName, nickName string) {
return "May", "Chan", "Chibi Maruko"
}
若只想获得nickName,则函数调用语句可以用如下方式编写:
_, _, nickName := GetName()
常量
指编译期间就已知且不可改变的值。常量可以是数值类型(包括整型、浮点型和复数类型)、布尔类型、字符串类型等。
- 常量定义
const Pi float64 = 3.14159265358979323846
const zero = 0.0 // 无类型浮点常量
const (
size int64 = 1024
eof = -1 // 无类型整型常量
)
const u, v float32 = 0, 3 // u = 0.0, v = 3.0,常量的多重赋值
const a, b, c = 3, 4, "foo"
// a = 3, b = 4, c = "foo", 无类型整型和字符串常量
const mask = 1 << 3
- 预定义常量
Go语言预定义了这些常量:true、false和iota。
iota比较特殊,可以被认为是一个可被编译器修改的常量,在每一个const关键字出现时被
重置为0,然后在下一个const出现之前,每出现一次iota,其所代表的数字会自动增1。
从以下的例子可以基本理解iota的用法:
const ( // iota被重设为0
c0 = iota // c0 == 0
c1 = iota // c1 == 1
c2 = iota // c2 == 2
)
- 枚举
枚举指一系列相关的常量,比如下面关于一个星期中每天的定义。Go语言并不支持众多其他语言明确支持的enum关键字。
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
numberOfDays // 这个常量没有导出
)
类型
Go语言内置以下这些基础类型:
- 布尔类型:bool。
- 整型:int8、byte、int16、int、uint、uintptr等。
- 浮点类型:float32、float64。
- 复数类型:complex64、complex128。
- 字符串:string。
- 字符类型:rune。
- 错误类型:error。
此外,Go语言也支持以下这些复合类型:
- 指针(pointer)
- 数组(array)
- 切片(slice)
- 字典(map)
- 通道(chan)
- 结构体(struct)
- 接口(interface)
字符串
在Go语言中,字符串也是一种基本类型。
var str string // 声明一个字符串变量
- 字符串操作
x + y 字符串连接 "Hello" + "123" // 结果为Hello123
len(s) 字符串长度 len("Hello") // 结果为5
s[i] 取字符 "Hello" [1] // 结果为'e'
- 字符串遍历
str := "Hello,世界"
for i, ch := range str {
fmt.Println(i, ch)//ch的类型为rune
}
数组
指一系列同一类型数据的集合。数组中包含的每个数据被称为数组元素(element),一个数组包含的元素个数被称为数组的长度。
- 元素访问
for i := 0; i < len(array); i++ {
fmt.Println("Element", i, "of array is", array[i])
}
Go语言还提供了一个关键字range,用于便捷地遍历容器中的元素。当然,数组也是range
的支持范围。上面的遍历过程可以简化为如下的写法:
for i, v := range array {
fmt.Println("Array element[", i, "]=", v)
}
数组切片
数组的不足,长度一旦定义之后无法再次修改;Go语言提供了数组切片(slice)解决这个问题。
切片是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以 slice 是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型)。给定项的 slice 索引可能比相关数组的相同元素的索引小。和数组不同的是,slice 的长度可以在运行时修改,最小为 0 最大为相关数组的长度:slice 是一个 长度可变的数组。
- 优点
因为 slice 是引用,所以它们不需要使用额外的内存并且比使用数组更有效率,所以在 Go 代码中 slice 比数组更常用。
- 声明 slice
var identifier []type 不需要说明长度
一个 slice 在未初始化之前默认为 nil,长度为 0。
- slice 初始化:
var slice1 []type = arr1[start:end]
这表示 slice1 是由数组 arr1 从 start 索引到 end-1 索引之间的元素构成的子集(切分数组,start:end 被称为 slice 表达式)。所以 slice1[0] 就等于 arr1[start]。这可以在 arr1 被填充前就定义好。
- 例子
arr1[2:] 和 arr1[2:len(arr1)] 相同,都包含了数组从第二个到最后的所有元素。
arr1[:3] 和 arr1[0:3] 相同,包含了从第一个到第三个元素(不包括第三个)。
如果你想去掉 slice1 的最后一个元素,只要 slice1 = slice1[:len(slice1)-1]。
一个由数字 1、2、3 组成的切片可以这么生成:s := [3]int{1,2,3} 甚至更简单的 s := []int{1,2,3}。
- 用 make() 创建一个 slice
当相关数组还没有定义时,我们可以使用 make() 方法来创建一个 slice 同时创建好相关数组:
var slice1 []type = make([]type, len)
slice1 := make([]type, len) //简写
- 例子
package main
import "fmt"
func main() {
var slice1 []int = make([]int, 10)
// load the array/slice:
for i := 0; i < len(slice1); i++ {
slice1[i] = 5 * i
}
// print the slice:
for i := 0; i < len(slice1); i++ {
fmt.Printf("Slice at %d is %d\n", i, slice1[i])
}
fmt.Printf("\nThe length of slice1 is %d\n", len(slice1))
fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))
}
- new() 和 make() 的区别
new 方法分配内存,make 方法初始化;
- new(T) 为每个新的类型T分配一片内存,初始化为 0 并且返回类型为*T的内存地址:这种方法返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体(参见第 10 章);它相当于 &T{}。
- make(T) 返回一个类型为 T 的初始值,它只适用于3种内建的引用类型:slice, map 和 channel
map
在C++/Java中,map一般都以库的方式提供,比如在Java中是Hashmap<>。而在Go中,使用map不需要引入任何库,
map是一堆键值对的未排序集合。
- map声明
var map1 map[keytype]valuetype
var map1 map[string] PersonInfo
其中,myMap是声明的map变量名,string是键的类型,PersonInfo则是其中所存放的值类型。
- 创建map
maps 是引用类型的:内存用 make 方法来分配。
myMap = make(map[string] PersonInfo)
也可以选择是否在创建时指定该map的初始存储能力,下面的例子创建了一个初始存储能力
为100的map:
myMap = make(map[string] PersonInfo, 100)
- 元素赋值
赋值过程非常简单明了,就是将键和值用下面的方式对应起来即可:
myMap["1234"] = PersonInfo{"1", "Jack", "Room 101,..."}
- 元素删除
Go语言提供了一个内置函数delete(),用于删除容器内的元素。下面我们简单介绍一下如何用delete()函数删除map内的元素:
delete(myMap, "1234")
- 元素查找
value, ok := myMap["1234"]
if ok { // 找到了
// 处理找到的value
}
判断是否成功找到特定的键,不需要检查取到的值是否为nil,只需查看第二个返回值ok,
这让表意清晰很多。配合:=操作符,让你的代码没有多余成分,看起来非常清晰易懂。
流程控制
- 条件语句
if a < 5 {
return 0
} else {
return 1
}
关于条件语句,需要注意以下几点:
- 条件语句不需要使用括号将条件包含起来();
- 无论语句体内有几条语句,花括号{}都是必须存在的;
- 左花括号{必须与if或者else处于同一行;
- 在if之后,条件语句之前,可以添加变量初始化语句,使用;间隔;
- 在有返回值的函数中,不允许将“最终的”return语句包含在if...else...结构中,
否则会编译失败:
function ends without a return statement。
失败的原因在于,Go编译器无法找到终止该函数的return语句。编译失败的案例如下:
func example(x int) int {
if x == 0 {
return 5
} else {
return x
}
- 选择语句
switch i {
case 0:
fmt.Printf("0")
case 1:
fmt.Printf("1")
case 2:
fallthrough
case 3:
fmt.Printf("3")
case 4, 5, 6:
fmt.Printf("4, 5, 6")
default:
fmt.Printf("Default")
}
在使用switch结构时,我们需要注意以下几点:
- 左花括号{必须与switch处于同一行;
- 条件表达式不限制为常量或者整数;
- 单个case中,可以出现多个结果选项;
- 与C语言等规则相反,Go语言不需要用break来明确退出一个case;
- 只有在case中明确添加fallthrough关键字,才会继续执行紧跟的下一个case;
- 可以不设定switch之后的条件表达式,在此种情况下,整个switch结构与多个if...else...的逻辑作用等同。
- 循环语句
与多数语言不同的是,Go语言中的循环语句只支持for关键字,而不支持while和do-while
结构。关键字for的基本使用方法与C和C++中非常接近:
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
可以看到比较大的一个不同在于for后面的条件表达式不需要用圆括号()包含起来。Go语言
还进一步考虑到无限循环的场景,让开发者不用写无聊的for (;;) {} 和 do {} while(1);,
而直接简化为如下的写法:
sum := 0
for {
sum++
if sum > 100 {
break
}
}
- 跳转语句
goto语句的语义非常简单,就是跳转到本函数内的某个标签,如:
func myfunc() {
i := 0
HERE:
fmt.Println(i)
i++
if i < 10 {
goto HERE
}
}