go语言基础语法
![](http://39.101.72.240:8080/picture/幻紫.jpg)
go入门及简单应用
输出
1 | func main() { |
随机数
这里通过运行代码会发现生成的随机数总是一样的
1 | func main() { |
rand包实现的是伪随机数生成器,需要先对随机数种子进行初始化,为了保证每次运行的随机性,通常在对于种子的sourse初始化用时间戳
1 | rand.Seed(time.Now().UnixNano()) //随机数种子 |
rand.Intn()代表的是rand的取值范围
下面是一个随机数的猜数字游戏
1 | func main() { |
函数
函数可以没有参数或者接受多个参数,同时在go语言里面函数可以同时返回多个值
接受参数
1 | func add(x int, y int) int { |
注意返回类型以及变量类型后置的写法
有多个类型同时赋值的时候,可以这样写
1 | x int, y int -> x, y int |
返回参数
函数可以返回任意数量的值,可以这样定义
1 | func swap(x, y string) (string, string) { |
函数命名
- 函数名称命名
函数名称小写代表函数私有,大写代表函数公有,公有的时候,可以被别的函数跨包调用
- 函数返回值命名
1 | func split(sum int) (x, y int) { |
没有参数的return会直接返回,仅用于短的函数,长的函数会影响代码的可读性
变量
变量的初始化
在这个代码中展示了两种变量赋值的方法,就是创建和赋值,以及创建并赋值
注意go里面变量创建就必须要赋值并使用,否则会报错
1 | var x, y int |
类型转换
go里面类型转换需要显式转换
1 | var f float64 = math.Sqrt(float64(x*x + y*y)) |
常量
用const关键字
1 | const Pi = 3.14 |
循环
for循环
- 一般for结构
go里面只有for循环结构,下面是一个例子
1 | for i := 0; i < 10; i++ { |
go语言中没有条件括号,但是必须有花括号,其他的用法与cpp相同
- for的缺省结构
可以用这种写法替代while
1 | sum := 1 |
判断
if
Go 的 if 语句与 for 循环类似,表达式外无需小括号 ( ) ,而大括号 { } 则是必须的。
1 | if x < 0 { |
- if 的简短语句
通常可以在if的条件判断语句执行之前,执行一段简短的语句,该语句声明的变量作用域仅在 if 之内。
1 | if value, err = judge(key); err!=nil { |
- if-else语句
语法与c++, java类似
swich
go中的switch语句不限制参数的类型,因此也可以替代if,以及if else语句,
同时switch中不需要加break因为go中执行了相应的case之后会跳出switch
1 | func main() { |
注意 switch中代替if的写法,不带switch
1 | func main() { |
defer
defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
这是一个栈式调用,通常用于关闭流
1 | func main() { |
指针
Go 拥有指针。指针保存了值的内存地址。
go的指针定义与cpp非常相似
类型 *T 是指向 T 类型值的指针。其零值为 nil。
1 | var p *int |
& 操作符会生成一个指向其操作数的指针。
1 | i := 42 |
- 操作符表示指针指向的底层值。
1 | fmt.Println(*p) // 通过指针 p 读取 i |
这也就是通常所说的“间接引用”或“重定向”。
与 C 不同,Go 没有指针运算。
结构体
定义
结构体与java类比 有点类似于实体类与对象
下面是一个简单例子
1 | type Vertex struct { |
结构体指针
在这一段代码中,我之前的疑惑时p时结构体v的指针,
如果要访问v的内存,按照c++的写法时需要*p来访问,但是这么写会报错
查询资料后,发现,go语言中对于结构体指针的使用,允许隐式调用,也就是p.x等价于*p.x
原因是go中没有指针运算,一直写*p.x过于啰嗦
1 | type Vertex struct { |
结构体指针的实际应用
在函数中对于大的结构体进行调用的时候,直接用应用类型,会导致执行过程中会对结构体进行复制
非常消耗内存,此时可以用结构体指针进行传参,这样可以节省性能消耗
结构体实例化
1 | type Vertex struct { |
运行结果:
{1 2} &{1 2} {1 0} {0 0}
结构体方法
由于go中没有类,但是可以写结构体方法,未结构体加上方法
方法就是一类带特殊的 接收者 参数的函数。
1 | type Vertex struct { |
而以指针为接收者的方法被调用时,接收者既能为值又能为指针:
1 | var v Vertex |
- 选择值或指针作为接收者
使用指针接收者的原因有二:
首先,方法能够修改其接收者指向的值。
其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效。
在本例中,Scale 和 Abs 接收者的类型为 *Vertex,即便 Abs 并不需要修改其接收者。
通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用。
所以对于结构体方法的调用 我们通常先取地址 然后才调用函数
1 | type Vertex struct { |
数组
类型后置,其余一样
切片
定义
切片与数组相比,长度更加灵活,不受限制
切片并不存储任何数据,它只是描述了底层数组中的一段。
更改切片的元素会修改其底层数组中对应的元素。
与它共享底层数组的切片都会观测到这些修改,共享内存
1 | a := [4]int{1, 2, 3 ,4} |
左闭右开
切片写法
下面这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片:
1 | []bool{true, true, false} |
切片默认行为
在进行切片时,你可以利用它的默认行为来忽略上下界。
切片下界的默认值为 0,上界则是该切片的长度。
对于数组
1 | var a [10]int |
来说,以下切片是等价的:
1 | a[0:10] |
切片与make实现动态数组
1 | a := make([]int, 5) // len(a)=5 |
append() 向切片追加元素
使用append追加元素的时候,如果追加元素之后,超过了元素的长度,会将切片重新分配地址
由于我们常用空切片 ,因此在追加元素之后都会把切片赋值回去
1 | // 添加一个空切片 |
map
定义
1 | map[key]value |
创建
1 | type Vertex struct { |
这里的Vertex也可以省略
1 | type Vertex struct { |
修改map
在映射 m 中插入或修改元素:
1 | m[key] = elem |
获取元素:
1 | elem = m[key] |
删除元素:
1 | delete(m, key) |
通过双赋值检测某个键是否存在:
1 | elem, ok = m[key] |
若 key 在 m 中,ok 为 true ;否则,ok 为 false。
若 key 不在映射中,那么 elem 是该映射元素类型的零值。
同样的,当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。
注 :若 elem 或 ok 还未声明,你可以使用短变量声明:
1 | elem, ok := m[key] |
range遍历
- 遍历数组
返回两个参数,第一个参数是索引,第二个参数是value,如果不想要索引,可以用_替代
1 | var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} |
- 遍历map
1 | for key, value := range mapName { |
接口
接口类型 是由一组方法签名定义的集合。
1 | type Abser interface { |
接口的实现
go中分为显式实现与隐式实现,而隐式实现更为常见
首先定义接口
1 | type sleeper interface { |
然后定义两个结构体
1 | type dog struct { |
定义结构体实现的接口的方法
其实就是把名字省去,然后名字一样的方法
1 | func (d *dog) sleep() { |
接口同样是一种数据类型 类似于 java 的声明
通过对接口的赋值(实现了接口的结构体)
通过调用接口的方法 实现 Java 里面的上转型
1 | func main() { |
- 还可以调用接口切片,然后循环赋值,调用方法
1 | func main() { |
接口嵌套
首先定义接口
1 | type sleeper interface { |
然后实现方法
注意 结构体需要实现所有的方法才能够实现该接口 才能进行赋值
1 | type dog struct { |
最后用切片 遍历赋值接口 实现多态
1 | func main() { |
接口断言-检查接口被赋值的是哪个结构体
获取接口值得真正类型
1 | dog, ok = s.(dog) |
泛型
泛型是通过空接口实现的,所有的结构体都实现了空接口,因此可以通过对空接口进行赋值
空接口可以被任何的结构体赋值 所以这里就实现了泛型
1 | var T interface{} // 空接口 |
fmt包就使用了泛型