Go语言基础
package main import "fmt" func main() { fmt.Println("hello,world") }
变量和常量
变量
var name [类型] = [表达式]
var i,j,k int m,n := 0,1
常量
常量一般用作全局常量
常量的值在程序编译的时候就确定了,之后不可改变
const LENGTH = 100 const ( SYS = "Linux" TYPE = "PRO" )
常量生成器iota, iota可以通过枚举创建一系列相关的值,而且不需要明确定义类型
iota每次从0开始取值,逐次+1
const ( Zero int = iota One Two Three Four )
iota支持在表达式中使用
const( p2_0 = 1<<iota //1 p2_1 //2 p2_2 //4 )
指针
- 取地址
&x - 取值
*p
指针类型的默认初始值为nil
new 函数可以创建一个不需要名称的变量,直接赋值给一个指针
p := new(int) *p = 10
包和作用域
- 包是数据和函数的集合,go使用包实现程序的模块化
- 使用package定义一个包,一般约定使用小写字母对包命名
- 一个包意味着一个独立的命名空间
- go使用首字母大小写控制可见性,首字母大写在包外可见,首字母小写在包内私有
- go中标识符命名为驼峰式
- 导入包取别名:
import f "fmt"
- 一般在package命令之前书写本包的说明
- init函数,在包初始化的时候调用,允许初始化的时候自动执行一次
- main包中导入了其他包,则会按照导入main包的顺序进行初始化
每个包先初始化常量,再初始化变量,然后执行init函数,之后开始执行main包下的main函数 - 一个包内可以有多个init函数,初始化时按照出现的顺序执行
选择和循环
if
if condition1 { }else if condition2 { }else{ }
switch
switch x { case xx1: case xx2: default: }
每个分支不需要使用break
for
只有for循环
for i:=0;i<100;i++ { }
for{ }
arr := []int{1,2,3,4} for i,v := range arr{ }
垃圾回收
go的垃圾回收自动实现 --> 释放不会再使用的程序所占用的空间
著名的垃圾回收机制:引用计数, 分代收集,标记-清除
Go的垃圾回收机制:三色标记算法
基本数据类型
整型,浮点型,复数,布尔类型
bool byte --> uint8 rune int ,uint int8, uint8 int16 uint16 int32,uint32 int64,uint64 float32 float64 complex64 complex128 uintptr
rune是int32的别名,遍历字符串中每个字符,可以使用这个类型
内置的real() 和 imag() 用于获取复数的实部和虚部
运算符
优先级:从上到下递减
* / % << >> & &^(与非, AND NOT) + - | ^ == != < <= > >= && ||
- %仅用于整型间的运算
- / 存在整除现象
- 如果算术运算的结果过大,就会出现溢出的现象,无论有无符号,超出高位的bit位部分将被丢弃
如果原始的数值是有符号类型,数值的正负号可能会发生变化 - 两个相同类型的值可以用== 或者 != 比较
- x << n 和 x>> n, n必须为无符号数
数组
数组是具有一定长度且元素数据类型相同的序列
数组的长度是固定的
在go中,数组是值类型,并不是引用类型
定义数组
var a [3]int var b [3]int = [3]int{1,2,3} c := [...]int{1,2,3} d := [...]int{4,4:1,1:2}
go语言数组长度是数组的一部分
go数组是传递值的,使用数组作为一个函数的参数时,应该使用指针方式,不然会完全复制
字符串
字符串是特殊的数组,字符串是只读的。
字符串的长度和具体元素值都是不可变的 --> 固定长度且元素不可变的字节数组
注意点:字符串s1=s2赋值时,本质不会进行赋值,仅会传递字符串的地址和字节长度,因为字符串不可变 --> 没必要再复制一份
Go源码要求为UTF-8编码,所以一般将字符串按照UTF-8的码点来理解
字符串本质上是一个字节序列,for range不支持非UTF-8编码的遍历
len() 返回的是字符串字节数而非字符个数
对字符串的操作,Go提供几个基础包:
- strings: 搜索,比较,切分,字符串连接
- bytes: 底层字节操作, 可以使用[]byte() 转换类型
- strconv: 字符串与其他类型的转换
- unicode: 对字符串中的单个字符做判断, IsLetter, IsDigit, IsUpper等
字符串可以强制转换为[]byte 和 []rune类型处理
slice切片
slice是一个拥有相同类型元素的可变长序列,且slice的定义与数组的定义非常像,就是没有长度的数组。
slice的长度不可以超过slice的容量
s := []int{1,2,3,4,5} ss := make([]int,10) // 释放 ss = nil
slice传递的是地址,不会复制值
append
通过apped可以在原来的切片上插入元素,可以在开头,结尾,指定位置插入元素或者slice
var a = []int{1,2,3} a = append(a,33) a = append(a,[]int{222,333}...) // ...代表可变参数 // 可以使用append进行删除 a = append(a[:0],a[:3]...) // 只保留前三个元素
copy
复制
a1 := []int{1,1,1,1,1} b1 := []int{-1,-1,-1} copy(a1,b1) // 将b1复制到a1
copy的参数必须是slice,不能是数组
map
key-value形式的无序集合
键必须唯一,可以通过键来获取或者更新值
map[k]v
在同一个map, k和v的类型只能是同一种类型
m1 := make(map[string]int) m2 := map[string]int{ "k1":11, "k2":22, } // 删除k1元素 delete(m2,"k1")
- 删除一个map中没有的键时,不会报错
- map在元素赋值之前必须初始化,要么使用make函数,要么声明时就带着初始值
- 未初始化的map时nil值,对未初始化的map执行删除元素,len操作,range操作或者查找元素都不会报错,但是在未初始化之前执行元素赋值就会报错
struct
struct 是值传递
type Person struct{ // 字段 }
并发编程,channel之间的通信可以使用空结构体作为信号量
- struct成员的可见型是通过首字母大小写控制的
- 结构体指针必须初始化后才可以使用,仅仅声明结构体指针类型变量,其默认初始值为nil
// 初始化一个struct,返回地址引用 pp := new(Person) // 使用new
- make函数用于slice, map和chan 进行内存分配,返回的是类型本身
- new返回初始化的类型对应的指针 --> 结构体初始化中使用较多
struct的组合
使用struct的组合来实现代码复用的效果 --> 类似继承
一个struct可以含有其他struct,达到复用效果
- 不可以含自身
- struct内的成员可以是执行自己的指针
函数
具名函数和匿名函数
//具名函数 func 函数名(形参列表) 返回值{ } // 匿名函数 var 变量名 = func(形参列表) 返回值{ } // 通过变量调用函数 --> 函数指针
闭包
匿名函数可以赋值给一个变量,也可以直接写在一个函数内部
func double() func() int { var r int return func()int{ r++ return r*2 } }
类似实现了 类的封装特性
闭包在作用上类似于面向对象编程中类的实例,会把函数和所访问的变量打包到一起,不在关心这个变量原来的作用域
从垃圾回收机制来看,因为闭包函数对外部变量的操作才使其不能被释放回收,从而跨越了作用域的限制
- r的定义虽然在匿名函数的外面,但是匿名函数在编译时,把这个变量包装进自己的函数内,从而跨越了作用域的限制,可以让r一直存在,这个匿名函数就是闭包函数
- 闭包会将自己用到的变量都保存在内存中,导致变量无法被及时回收,并且可能通过闭包修改父函数使用的变量值,所以在使用时要注意性能和安全性
多返回值及变长参数
// 变长参数 func sum(a int,others...int) int{ for _,v := range others{ a + = v } return a } sum(1,2,3,4) argus := [] int {2,3,4} sum(1,argus...)
- 调用sum的两种方式:
- 传入所有参数
- 传入一个切片,注意传递参数时要带…
- 可变参数接收时,是一个切片
- go中函数的参数只能进行值传递,切片的长度和容量是通过值传递到函数内的,如果在函数内部修改了长度或者容量,函数外的切片是接收不到的,所以需要再返回一个切片。
基于这个原因,append函数才会每次都返回切片
defer关键字
用于释放资源,会在函数返回之前调用,即使函数崩溃也会在结束前调用。
一个函数内可以有多个defer,在调用的时候按照栈的方式进行。
f,err := os.Open(fileName) if err != nil { panic(err) } defer f.Close()
方法
Go没有继承,但是有方法,方法是Go语言面向对象的主要特征。
Go的方法不再属于一个类,而是关联到类型的
type Rectangle struct{ w,h float64 } func (r Rectangle) area() float64{ return r.w * r.h }
Go语言没有方法重载
要求自定义类型和对应的方法在同一个包中
如果自定义类型本身已经是指针类型,例如: type p*int, 则不允许为该类型定义方法
接口
通过interface关键字定义接口,凡是满足定义的都被认定是该接口的实现。
接口定义了需要被实现的一组函数方法的抽象集合,如果要实现某个接口必须实现该接口的所有方法
type ShapeDesc interface { Area() float64 Perimeter() float64 }
接口断言: 向下转型
Go语言的类型断言可以用x.(T) 表达,x是一个接口类型的具体值表达式,T是一个类型(断言类型)
接口类型的值包括动态类型和动态值,在程序执行时通过动态类型和动态值去调用具体的方法。
注意事项:
- 接口中只能声明方法,不可以有具体实现
- 接口中不可以声明变量,仅允许声明方法
- 实现一个接口,就必须实现接口内声明的所有方法
- 接口也可以嵌套组合
空接口
interface{}
可以使用interface{}定义接收任何类型
反射
reflect 是Go提供的动态获取对象类型及结构信息的方式
越是需要编写尽可能通用的代码时,越是需要使用反射
反射可以帮助处理未知类型,灵活
反射中有两个核心类型: reflect.Value 和 reflect.Type, 前者用于存储任意值,后者用于存储任意类型
反射的缺点:
- 反射的实现比较复杂,所以反射执行得比较慢,会影响程序得整体性能
- 反射得错误在编译时无法发现,到运行时才报错,而且都是panic类型,容易导致程序崩溃