变量定义
使用var关键字
- 带类型
- 可以一次定义多个变量, 如果变量类型相同,只需要写最后一个的变量类型
- 在函数中可以使用 := 短声明, 函数体外不可使用
基本类型
go语言的基本类型如下
1 | bool |
变量初始化时的值为 ‘零值’
- 数值类型为
0, - 布尔类型为
false, - 字符串为
""(空字符串)。
类型转换
使用 type(value)的方式转换
1 | var i int = 42 |
当然, 也有更加简洁的方法:
1 | i := 42 |
不同于js, 在GO中, 不允许隐式转换。会编译不通过。
虽然没有隐式转换,但是有类型推导
我们定义一个变量,但是不指定其类型时, 变量进行右查询, 根据右查询的结果决定变量类型.
1 | var i int |
但当右边包含的是变量可能是int, float或者complex128的时候,取决于精度
1 | i := 42 // int |
常量
常量使用const关键字.但是不可以使用简洁语法(:=)
一个没有显示指定类型的常量由上下文环境决定类型.
for循环
1 | sum := 0 |
和js不一样, 不需要有括号, 但是{}, 花括号是必须的.
我们可以把前置语句和后置语句置空.做一个while循环
1 | sum := 1 |
条件语句 if
没有括号,但是花括号是必须的.
1 | if x < 0 { |
在if语句中,可以在条件语句之前执行一段代码:
1 | if v := math.Pow(x, n); v < lim { |
if-else 在if中的便捷代码里的变量,我们可以在else语句延用,但是,出了条件语句(if, else语句中), 那就脱离了范围,不可以使用了.换句话说,变量作用域在if, else语句体内
swich
和平常的swich没有什么区别,就是条件语句特性就是可以在判定条件前加一个简洁语句.
1 | switch os := runtime.GOOS; os { |
没有条件的swich可以用来做if-else的多分支语句。
1 | switch { |
defer语句 延迟执行
defer 语句会延迟函数的执行直到上层函数返回。
延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。
defer执行顺序,所有的defer都会被压入一个栈, 执行的时候会按照先进后执行的顺序.
指针
go中存在指针, 指针存的是变量的内存地址.
定义的方式是* type, type是值的类型, 也就是这个地址内存的是什么类型的值。初始值是nil
1 | var p *int |
& 符号会生成一个指向其作用对象的指针。也就是生成一个引用值, 新建一个区域, 存入指向对象的地址。
1 | i := 42 |
*符号表示指针指向的底层的值。我觉得指针操作带来的便利是, 性能, 我们并不需要每个值都copy一份.就像js里的值传递和地址传递.通过指针, 我们可以很大程度的优化我们的代码执行效率.
这也就是间接引用和直接引用的一个区别.间接引用通过指针和 * 来直接读取到被引用的值.
go中么有指针运算(指针运算是什么?)
结构体
怎么说,很像js的对象, 但是又有点不像
定义方式
1 | type structName struct { |
结构体访问方式
使用.运算符访问
1 | package main |
结构体指针
结构体可以通过指针来访问, 这意味着, 我们可以拥有类似原型链的能力了
这里要说的是, 当我们print(p)的时候, 得出的并不是内存的真实地址, 而是 &{1, 2} 的字符串, 但是此时, p和v是有相同的功能的, 由于p是引用,在P上的操作会直接反应到v上.这像不像js的地址传递.
1 | package main |
结构体文法
- 结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。这里的概念类似于js的结构赋值, 此时有顺序的概念
1
2
3
4
5type Vertex struct {
X, Y int
}
var v1 = Vertex{1, 2} // x:1 y:2 - 使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)
1
2
3
4
5
6type Vertex struct {
X, Y int
}
var v2 = Vertex{x: 2, y: 1} // x:1 y:2
var v3 = Vertex{y: 2, x: 1} // x:1 y:2 - 特殊的前缀 & 返回一个指向结构体的指针。使用结构体的指针和使用结构体本身没多大区别, 这一点要牢记.
1
2
3
4
5type Vertex struct {
X, Y int
}
var v4 = &Vertex{1, 2}
数组
定义方式
1 | var arrayName [length]Type // 意思是 1. 我要定义一个名字为arrayName的数组 2.它的长度为length 3.里面的元素是Type类型的 |
注意,在go中, 数组的长度是不可变的.但是go提供了一种机制解决这个问题.
slice
定义一个slice, 这个类似于js中的temp数组.临时数组.
1 | []Type // 类型为Type的slice |
我们可以访问的slice的长度信息通过len(p)
slice切片处理
表达式
1 | s[low:high] // 对数组s, 切片从low到high-1 |
slice数组产生方式二 make()
1 |
|
slice的初始值 nil
一个slice的初始值为nil, 特征是长度和容量都为0
1 | var z []int |
往数组中添加元素 append
go内建函数提供往数组尾部插入元素的方法
1 | func append(s []T, vs ...T) []T |
参数解读
s为类型为T的数组
其余类型为T的数组元素都会添加到slice数组中
输出是, 类型为T的数组S加上参数T的类型归集为一个slice
如果数组s太小, 那么go会为其分配一个更大的数组.返回的slice会指向这个新数组
Range
for循环的range格式可以遍历slice或者进行map循环
1 | package main |
如果想用range循环又不需要index, 或者value, 或者两样都不需要, 那么你可以用_符号忽略, 并且仍然使用循环体.
Map键值对
map使用前必须使用make创建
没有使用make的map值为nil的map是空的,并且不能赋值
使用make并没有赋值的时候,会按类型初始化.
1 | // 定义一个键值对 |
Map语法:类似于结构体, 但是key值必须有
1 | package main |
如果定义value值的类型和顶层的类型一致, 那么可以省略value值的类型名称(Vertex)
1 | var m = map[string]Vertex{ |
操作map
- 增加key:value
1
m[key] = element
- 获取元素
1
element = m[key]
- 删除元素
1
delete(m, key)
- 检测某个元素在不在同时, 如果我们从m中获取不存在的key那么我们会得到m的零值
1
elem, ok = m[key] // 如果key在m中, 那么ok为true, 否则ok为false, 并且elem为map元素m的零值
在go中,函数也是值, 这一点和js很像了, 意味着我可以使用函数定义式和函数表达式.
同样,在go中,函数是可以形成闭包的.
闭包是一个函数值,它来自函数体的外部的变量引用。 函数可以对这个引用值进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。
1 | package main |
方法: 给非结构体声名方法
1 | package main |
以上,定义了一个类型, 这个类型继承于float64, 然后呢, 我给这个类型增加了方法Abs, 此时, 该方法和平常结构体的方法调用方式一致.
指针方法与结构体方法的区别
在我理解, 定义一个结构体方法, 该方法内, 我去修改结构体的变量, 此时, 并不会反应到结构体挂载的变量上.但是我用了指针方法, 那么此时, 相当于我可以取到当前实例,的结构体本身, 继而对结构的变量进行修改.
1 | package main |
指针函数
入参指针的函数, 入参必须为指针.
而在指针结构体上定义的函数, 调用者可为结构体的指针类型, 也可以为结构体本身.
1 | package main |
同样的道理, 如果你定义了一个函数, 这个函数的入参规定为值类型, 而非指针类型,那么传入指针会导致程序在编译的时候抛出异常。
而在指针结构体上定义的函数, 调用者可为结构体的指针类型, 也可以为结构体本身.
1 | package main |
说了那么多指针的优点, 就是想告诉你, 多用指针类结构体定义的函数.
- 在指针结构体上定义的函数, 可以修改实例的值(通过指针)。
- 避免在每次调用该方法的时候 ,复制使用到的值(优化性能)
原则: 所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用。
1 | package main |
接口类: 是由一组方法签名定义的集合。
怎么解释捏, 就是, 这是一个代理, 这个代理上, 有被代理对象的所有方法.我们可以通过这个代理对象去调用被代理对象的方法.
1 | package main |
接口与隐式实现
通过上面的代码示例我们可以看出来, 在go中,接口直接通过interface类去实现.
假设我定义了一个接口F有方法Z
我定义了一个结构体W, 在这个结构体实现了Z
那么此时, 我声明一个接口类F, 然后, 给它赋W的实例.
此时, F就可以直接调用方法Z.形成了一个代理.
但是, F不可以访问Z上的自定义属性, 只能访问到Z与F共同定义的方法.也就是接口传递, 不传递属性.
1 | package main |
接口类型
接口值包含两个东西1. 被代理对象 2. 被代理的类型.
接口本身也是值, 可以像普通的类型进行传递.
从下面的实例代码我们可以看出, GO的接口类型.跟基本类型一样,可以进行重复赋值, 可以转移.
1 | package main |