golang学习笔记

基础语法

  1. 包名:package = namespace,每个文件都必须有一个包名,每个目录下必须有个main包
  2. 引入:import = use
  3. 入口:func main() {},同一级目录只能有一个main()函数
  4. GOROOT go程序的执行文件位置,go安装好之后会自动生成该环境变量
  5. 配置 GOPATH 环境变量 会影响构建、执行,GOPATH可以配置多个目录,编译的时候会自动在所有的GOPATH下寻找需要的包
  6. GOPATH目录下,通常会创建3个目录,src(项目源码),pkg(第三方包),bin(go install产生的文件)
  7. golang会自动去在GOPATH和GOROOT下的src目录和pkg目录寻找import的包

构建、执行、格式化

go build main.go

go run main.go

go test xxx (xxx为package名)

gofmt -w xxx.go 不加-w则只是格式化输出,加了之后会格式化改写代码文件

定义变量

var x = 1,或var()集中定义多个变量

var a,b int = 3,4 如果定义了类型,那么一行的所有变量都需要是同一类型

a,b,c,d := 1,2,true,“abc” # 如果不定义变量类型,在一行里可以多重赋值不同类型的变量

x := 1 (仅支持在func里这样写,func外只能用var来定义)

a, b int := 3, 4 这样是错误的 冒号+等号 不可以指定类型

var d 纯粹的变量声明时可不能省略类型,那样会编译器会报错

匿名变量 _

i,j = j,i // go支持多重赋值

func GetName() (userName, nickName string) { 
    return "nonfu", "学院君"
}
_,nickname := getName() // 只返回nickname,前一个_(匿名变量)被废弃

定义常量

基本用法同var

如果两个 const 的赋值语句的表达式是一样的,那么还可以省略后一个赋值表达式

iota 是一个在编译期间可变的常量,每定义一次iota时,iota的值自增1(初始值为0)

const (
    Sunday = iota 
    Monday 
    Tuesday 
    Wednesday 
    Thursday 
    Friday 
    Saturday 
    numberOfDays
) 
// 对应赋值分别是0,1,2,3,4,5,6,7

单元测试

func_test.go

变量类型

在 Go 语言中,引用类型包括切片(slice)、字典(map)和管道(channel),其它都是值类型。

结构体是值类型,不是引用类型

浮点型

查看数据类型和占用字节大小

fmt.Printf("n1类型是 %T, 占用字节数 %s", n1, unsafe.Sizeof(n1))
  • 浮点数都是有符号位的
  • float64比float32精度高
  • 浮点型默认声明为float64类型,通常应该用float64
  • 十进制的浮点数可以声明成3.12和.12(表示0.12)
  • 科学计数法 num1 := 2.1234e2 num2 := 2.1234E2 num3 := 2.1234e-2

字符

byte直接用fmt输出时,会输出其对于的Unicode码值,需要用fmt.Printf(“%c”, c1)才能格式化输出字符串

var c1 byte = 'a'
var c2 = '-'
fmt.Println(c1, c2) // 97 45
fmt.Printf("%c %c", c1, c2) // a -

汉字的字符Unicode码超出ASCII码范围,应该用int,用法同上

var c2 string = "abc"
c2[0] = "c"  // 这样是不允许的,go不支持单独改一个字符串的单个值
c2 = "www" // 这样是重新赋值,是支持的
  • 对于单个字符,双引号的字符存储的是字符本身,单引号的字符存储的是该字符对应的Unicode码值
  • 字符串必须用双引号包裹
fmt.Println("D",'D') // 会输出 D 68 
// 'DEF'会直接报错invalid character literal (more than one character)

数组、切片

var a [8]byte // 长度为8的数组,每个元素为一个字节
var b [3][3]int // 二维数组(9宫格)
var c [3][3][3]float64 // 三维数组(立体的9宫格)
var d = [3]int{1, 2, 3}  // 声明时初始化
var e = new([3]string)   // 通过 new 初始化
a := [...]int{1, 2, 3}

这种情况下,Go 会在编译期自动计算出数组长度(3

  • 数组在初始化的时候,如果没有填满,则空位会通过对应的元素类型空值填充
  • 如果初始化的数组元素个数超过声明的数组长度,则无法编译
  • []int 和 […]int{x,x,x…x} 不是同一种类型,前者是一个切片类型,后者在编译期间可以推算出长度,即指定长度的数组
  • 数组切片底层引用了一个数组,由三个部分构成:指针、长度和容量,指针指向数组起始下标,长度对应切片中元素的个数,容量则是切片起始位置到底层数组结尾的位置,切片长度不能超过容量
nums := [...]int{0,1,2,3,4,5,6,7,8,9,10,11,12}
q3 := nums[6:9]
fmt.Println("q3 = ", q3)
fmt.Println(q3[2:4])

// 以上输出
q3 = [6 7 8] // 左边包含,右边不包含
[8 9] // q3[0] = nums[6],则q3[2:4] = nums[8:10]
  • 切片自动扩容,一般会将容量扩大到原来的2倍,当容量扩大至1024时,容量扩大的比例由1/8逐渐增长至4/3,经测试,分别是2,1.25,1.325,1.3584905660377358,1.3333333333333333
  • 切片是对一个数组的视图,是对原数组的地址引用,此时,执行append(slice1, xxx),若新增后的切片容量不超过原数组的容量,则会直接体现到原数组该位置的值发生变更;而若新增后的切片容量超过了原数组的容量,则新的切片不再是之前原数组的视图,也不会影响原数组的值,如下:
  arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
  
  s1 := arr[2:6] // [2,3,4,5]
  s2 := s1[3:4] // [5]
  s3 := append(s2, 10) // [5,10],此时s3对应的arr的最大索引<cap(arr),arr的相应索引位置的值会被替换,arr = [0,1,2,3,4,5,10,7]
s4 := append(s3, 11) // [5,10,11],此时s4对应的arr的最大索引=cap(arr),未超过,arr的相应索引位置的值会被替换,arr = [0,1,2,3,4,5,10,11]
  s5 := append(s4, 12) // [5,10,11,12],此时s5对应的arr的最大索引>cap(arr),已超过,arr的值不再变化,arr = [0,1,2,3,4,5,10,11],arr的类型是[8]int,不会发生变化
// 此时s5实际已经不再是arr的视图了,系统底层会自动分配一个更大的数组给其当做原数组
  • make创建切片必须要指定长度 make([]interface{}, len)

字典

字典是一个无序集合,不会按存储的顺序打印内容,而是按照键名的ASCII码值顺序排列的

定义字典类型的2种方式:

testMap := map[string]int{
   "one":1,
   "two":2,
   "three":3,
}
tm := make(map[string]int)
tm["a"] = 23
tm["c"] = 34

以上两种方式都支持像php的关联数组那样追加键值对,如:

testMap["four"] = 4
testMap["five"] = 5
testMap["zero"] = 0

tm["b"] = 3
tm["z"] = 35

下面3中声明方式有差异:

var invMap = make(map[int]string) // ok
invMap := map[int]string{} // ok
var invMap map[int]string // 编译期间会报panic,不可用

指针

如上,ptr 就是一个指针类型,表示指向存储 int 类型值的指针。我们可以通过 *ptr 获取指针指向内存地址存储的变量值(我们通常将这种引用称作「间接引用」),ptr 本身是一个内存地址值(通过 &a 可以获取变量 a 所在的内存地址)

package main

import "fmt"

func main() {

   a := 100
   var ptr *int

   ptr = &a

   fmt.Println(ptr) // 输出0xc000062080
   fmt.Println(*ptr) // 输出100
   
    *ptr = 20
    fmt.Println(*ptr) // 输出20
    fmt.Println(a) // 输出20
}

上面例子,由于*ptr是指向变量a的内存地址,*ptr 改变时,也就是变量a的值被改变了

type关键字

package main
type NewInt int // 定义了一种新的类型,fmt.Printf("%T")会输出main.NewInt,
type IntAlias = int // 给int类型起别名为IntAlias,fmt.Printf("%T")会输出int,IntAlias 类型只会在代码中存在,编译完成时,不会有 IntAlias 类型。

非本地类型不能定义方法

package main
import (
    "time"
)
// 定义time.Duration的别名为MyDuration
type MyDuration = time.Duration // 会编译出错

编译器提示:不能在一个非本地的类型 time.Duration 上定义新方法。非本地方法指的就是使用 time.Duration 的代码所在的包,也就是 main 包。因为 time.Duration 是在 time 包中定义的,在 main 包中使用。time.Duration 包与 main 包不在同一个包中,因此不能为不在一个包中的类型定义方法。

解决这个问题有下面两种方法:

  • type MyDuration = time.Duration修改为 type MyDuration time.Duration,也就是将 MyDuration 从别名改为类型。
  • 将 MyDuration 的别名定义放在 time 包中。

条件控制

  • if 之后,条件语句之前,可以添加变量初始化语句,使用 ; 间隔,比如可以这么写

    if score := 100; score >= 90 {
    
  • 在 Go 语言中使用 switch...case... 分支语句时,需要注意以下几点:

    • 和条件语句一样,左花括号 { 必须与 switch 处于同一行;
    • 单个 case 中,可以出现多个结果选项(通过逗号分隔);
    • 与其它语言不同,Go 语言不需要用 break 来明确退出一个 case
    • 只有在 case 中明确添加 fallthrough 关键字,才会继续执行紧跟的下一个 case
    • 可以不设定 switch 之后的条件表达式,在这种情况下,整个 switch 结构与多个 if...else... 的逻辑作用等同。

函数、闭包

Go里面定义的闭包,可以直接调用闭包外定义的变量,而不用传入(php需要显示的use)

var j int = 1

f := func() {
   var i int = 1
   fmt.Printf("i, j: %d, %d\n", i, j)
}

f() // 输出i, j: 1, 1
j += 2
f() // 输出i, j: 1, 3

同时,可以在闭包里重新声明一个外部已经声明过的变量,且不会影响外部定义的变量,如:

var j int = 1

f := func() {
   var i int = 1
   j := 5
   fmt.Printf("i, j: %d, %d\n", i, j)
}

f() // 输出i, j: 1, 5
j += 2
f() // 输出i, j: 1, 5
fmt.Println(j) // 输出3

如果在闭包里引入了一个闭包外部声明的变量,然后离开这个外部变量的作用域去使用该闭包,仍然可以使用该闭包引入的外部变量。如我们可以先声明一个外部函数的参数为函数类型,然后定义一个闭包并赋值给指定变量,再将这个变量传递到外部函数中:

import "fmt"

func main() {
    i := 10
    add := func (a, b int) {
        fmt.Printf("Variable i from main func: %d\n", i)
        fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
    }
    callback(1, add);
}

func callback(x int, f func(int, int)) {
    f(x, 2)
}

上述代码的打印结果是:

Variable i from main func: 10
The sum of 1 and 2 is: 3

通过这个示例,我们还验证了虽然 i 变量声明在 main 函数中,在调用 callback 外部函数时传入了匿名函数 add作为参数,add 函数在外部函数中执行,虽然作用域离开了 main 函数,但是还是可以访问到变量 i

面向对象

归属同一个包的代码具备以下特性:

  • 归属于同一个包的代码包声明语句要一致,即同一级目录的源文件必须属于同一个包;
  • 在同一个包下不同的不同文件中不能重复声明同一个变量、函数和类;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值