GO语言学习part02

Go语言基础语法

标识符

25个关键字+37个保留字

变量

Go语言中的变量必须先声明再使用

var s1 string:声明一个保存字符串类型的s1变量

  1. var name string
  2. var age int
  3. var isOK bool

批量声明:

  1. var(
  2. name string
  3. age int
  4. isOK bool
  5. )

Go语言中 ,变量声明了必须使用,否则无法通过编译

可以在声明的同时进行初始化

var name string = "aa"

或者一次初始化多个变量

var name,age = "bb",20

类型推导

  1. var name = "cc"
  2. var age = 18

简短变量声明

只能在函数内部使用

  1. s3 := "ddd"
  2. m := 10
  3. n := 20

匿名变量

匿名变量使用一个_表示

  1. func foo(int,string){
  2. return 10,"aa"
  3. }
  4. func main(){
  5. x,_ = foo()
  6. _,y = foo()
  7. fmt.Println("x=",x)
  8. fmt.Println("y=",y)
  9. }

注意事项:

  1. 函数外的每个语句都必须以关键字开始
  2. :=不能用在函数外
  3. _多用于占位,表示忽略值

常量

const关键字

  1. const pi = 3.1415
  2. const e = 2.7128
  1. //批量声明常量
  2. const(
  3. pi = 3.1415
  4. e = 2.7128
  5. )
//常量声明时如果省略初始值,则默认和上一行相同
const(
    pi = 3.1415
    n
    m
)

iota

iota是go语言的常量计数器,只能在常量表达式中使用

iota在const关键字出现的时候被重置为0,const中每新增一行常量声明将使iota计数一次

const(
    n1 = 1    //0
    n2        //1
    n3        //2
    n4        //3
)
//定义数量级
const(
    _ = iota
    KB = 1 << (10*iota)
    MB = 1 << (10*iota)
    GB = 1 << (10*iota)
    TB = 1 << (10*iota)
    PB = 1 << (10*iota)
)

基本数据类型

整型

类型 描述
uint8 无符号8位整型(0~255)
uint16 无符号16位整型(0~65535)
uint32 无符号32位整型(0~2^32-1)
uint64 无符号64位整型(0~2^64-1)
int8 有符号8位整型
int16 有符号16位整型
int32 有符号32位整型
int64 有符号64位整型

特殊整型

类型 描述
uint 32位操作系统上就是uint32,64位操作系统上就是uint64
int 32位操作系统上就是int32,64位操作系统上就是int64
uintptr 无符号整型,用于存放一个指针

八进制&十六进制

var b int = 077 //八进制,0开头
var c int = 0xff //十六进制,0x开头

格式化输出:%b%o%x分别表示二进制、八进制、十六进制

布尔值

字符串

Go语言中字符串用双引号包裹的,字符用单引号包裹

多行字符串

//多行字符串
s2 := `
    xxx
    yyy
    zzz
`

字符串常用操作:

方法 介绍
len(str) 求长度
+或fmt.Sprintf 拼接字符串
strings.Split 分割
strings.contains 判断是否包含
strings.HasPrefix,string.HasSuffix 前缀/后缀判断
strings.Index(),string.LastIndex 子串出现的位置
strings.Join(a[]string,sep string) join操作

流程控制

if条件判断

for循环

//基本格式
for 初始语句;条件表达式;技术语句{
    循环体语句
}
//无限循环
for{

}

for循环可以通过breakgotoreturnpanic语句强制退出循环

for range

Go语言中可以使用for range遍历数组、切片、字符串、map 及通道(channel)。 通过for range遍历的返回值有以下规律:

  1. 数组、切片、字符串返回索引和值。
  2. map返回键和值。
  3. 通道(channel)只返回通道内的值。

switch和goto

switch基本使用和其他语言类似,和C不同的是执行完一个case就结束了

一个分支可以有多个值,多个case值中间使用英文逗号分隔。

func testSwitch3() {
    switch n := 7; n {
    case 1, 3, 5, 7, 9:
        fmt.Println("奇数")
    case 2, 4, 6, 8:
        fmt.Println("偶数")
    default:
        fmt.Println(n)
    }
}

分支还可以使用表达式,这时候switch语句后面不需要再跟判断变量。例如:

func switchDemo4() {
    age := 30
    switch {
    case age < 25:
        fmt.Println("好好学习吧")
    case age > 25 && age < 35:
        fmt.Println("好好工作吧")
    case age > 60:
        fmt.Println("好好享受吧")
    default:
        fmt.Println("活着真好")
    }
}

fallthrough语法可以执行满足条件的case的下一个case,是为了兼容C语言中的case设计的。

goto可以快速跳转,不建议在代码中使用

continue和break

运算符

复合数据类型

Array数组

数组定义:

var 数组变量名 [元素数量]T

数组初始化:

func main() {
    var testArray [3]int                        //数组会初始化为int类型的零值
    var numArray = [3]int{1, 2}                 //使用指定的初始值完成初始化
    var cityArray = [3]string{"北京", "上海", "深圳"} //使用指定的初始值完成初始化
    fmt.Println(testArray)                      //[0 0 0]
    fmt.Println(numArray)                       //[1 2 0]
    fmt.Println(cityArray)                      //[北京 上海 深圳]
}
//按照上面的方法每次都要确保提供的初始值和数组长度一致,一般情况下我们可以让编译器根据初始值的个数自行推断数组的长度
func main() {
    var testArray [3]int
    var numArray = [...]int{1, 2}
    var cityArray = [...]string{"北京", "上海", "深圳"}
    fmt.Println(testArray)                          //[0 0 0]
    fmt.Println(numArray)                           //[1 2]
    fmt.Printf("type of numArray:%T\n", numArray)   //type of numArray:[2]int
    fmt.Println(cityArray)                          //[北京 上海 深圳]
    fmt.Printf("type of cityArray:%T\n", cityArray) //type of cityArray:[3]string
}
//我们还可以使用指定索引值的方式来初始化数组
func main() {
    a := [...]int{1: 1, 3: 5}
    fmt.Println(a)                  // [0 1 0 5]
    fmt.Printf("type of a:%T\n", a) //type of a:[4]int
}

切片(slice)

切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。

切片是一个引用类型,它的内部结构包含地址长度容量。切片一般用于快速地操作一块数据集合。

声明切片类型的基本语法如下:

var name []T
func main() {
    // 声明切片类型
    var a []string              //声明一个字符串切片
    var b = []int{}             //声明一个整型切片并初始化
    var c = []bool{false, true} //声明一个布尔切片并初始化
    var d = []bool{false, true} //声明一个布尔切片并初始化
}

切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。

由数组得到切片:

a1 := []int{1,3,5,7,9,11,13}
s3 := a1[0:4]     //[1 3 5 7],规则:左闭右开
s4 := a1[1:6]
s5 := a1[:4]    //相当于a1[0:4]
s6 := a1[3:]    //相当于a1[3:len(a1)]
s7 := a1[:]        //相当于a1[0:len(a1)]

切片的容量指底层数组从切片的第一个元素到最后一个的容量

func main() {
    a := [5]int{1, 2, 3, 4, 5}
    s := a[1:3]  // s := a[low:high]
    fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s))
}

输出结果为:s:[2 3] len(s):2 cap(s):4

注意:

对于数组或字符串,如果0 <= low <= high <= len(a),则索引合法,否则就会索引越界(out of range)。对切片再执行切片表达式时(切片再切片),high的上限边界是切片的容量cap(a),而不是长度。

make函数创建切片

make([]T,size,cap)

其中:

  • T:切片的元素类型
  • size:切片中元素的数量
  • cap:切片的容量

切片的本质

切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。

Go_part02 - 图1

切片的比较:切片之间不能比较,唯一合法的比较是和 nil比较。所以要判断一个切片是否是空的,要是用len(s) == 0来判断,不应该使用s == nil来判断。

切片的扩容

append方法

Go语言的内建函数append()可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。

func main(){
    var s []int
    s = append(s, 1)        // [1]
    s = append(s, 2, 3, 4)  // [1 2 3 4]
    s2 := []int{5, 6, 7}  
    s = append(s, s2...)    // [1 2 3 4 5 6 7]
}

切片复制

Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:

copy(destSlice, srcSlice []T)

其中:

  • srcSlice: 数据来源切片
  • destSlice: 目标切片

切片删除

Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:

func main() {
    // 从切片中删除元素
    a := []int{30, 31, 32, 33, 34, 35, 36, 37}
    // 要删除索引为2的元素
    a = append(a[:2], a[3:]...)
    fmt.Println(a) //[30 31 33 34 35 36 37]
}

指针

GO语言不存在指针操作,只需要记住两个符号

  1. & 取地址
  2. *根据地址取值

new和make

new是一个内置的函数,它的函数签名如下:

func new(Type) *Type

其中,

  • Type表示类型,new函数只接受一个参数,这个参数是一个类型
  • *Type表示类型指针,new函数返回一个指向该类型内存地址的指针。

new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。

make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。make函数的函数签名如下:

func make(t Type, size ...IntegerType) Type

make和new的区别

  1. make和new都是用来申请内存的
  2. new很少用,一般用来给基本数据类型申请内存,返回对应类型的指针
  3. make用来给slice、map、chan申请内存,返回引用类型(本身)

map

  1. Go语言中 map的定义语法如下:
map[KeyType]ValueType

其中,

  • KeyType:表示键的类型。
  • ValueType:表示键对应的值的类型。

map类型的变量默认初始值为nil,需要使用make()函数来分配内存。语法为:

make(map[KeyType]ValueType, [cap])

其中cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容量。

  1. Go语言中有个判断map中键是否存在的特殊写法,格式如下:
value, ok := map[key]
  1. Go语言中使用for range遍历map。

使用delete()内建函数从map中删除一组键值对,delete()函数的格式如下:

delete(map, key)

函数

Go语言中定义函数使用func关键字,具体格式如下:

func 函数名(参数)(返回值){
    函数体
}

其中:

  • 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
  • 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
  • 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
  • 函数体:实现指定功能的代码块。

参数

  1. 类型简写
    函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:
func intSum(x, y int) int {
    return x + y
}
  1. 可变参数

可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加...来标识。

注意:可变参数通常要作为函数的最后一个参数。

举个例子:

func intSum2(x ...int) int {
    fmt.Println(x) //x是一个切片
    sum := 0
    for _, v := range x {
        sum = sum + v
    }
    return sum
}

返回值

Go语言中通过return关键字向外输出返回值。

  1. Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。
func calc(x, y int) (int, int) {
    sum := x + y
    sub := x - y
    return sum, sub
}
  1. 函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。
func calc(x, y int) (sum, sub int) {
    sum = x + y
    sub = x - y
    return //返回值名称可省略
}

函数类型与变量

  1. 定义函数类型

我们可以使用type关键字来定义一个函数类型,具体格式如下:

type calculation func(int, int) int

上面语句定义了一个calculation类型,它是一种函数类型,这种函数接收两个int类型的参数并且返回一个int类型的返回值。

我们可以声明函数类型的变量并且为该变量赋值:

func main() {
    var c calculation               // 声明一个calculation类型的变量c
    c = add                         // 把add赋值给c
    fmt.Printf("type of c:%T\n", c) // type of c:main.calculation
    fmt.Println(c(1, 2))            // 像调用add一样调用c

    f := add                        // 将函数add赋值给变量f1
    fmt.Printf("type of f:%T\n", f) // type of f:func(int, int) int
    fmt.Println(f(10, 20))          // 像调用add一样调用f
}

闭包

如以下代码段所示代码,

闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境。 首先我们来看一个例子:

func adder() func(int) int {
    var x int
    return func(y int) int {
        x += y
        return x
    }
}
func main() {
    var f = adder()
    fmt.Println(f(10)) //10
    fmt.Println(f(20)) //30
    fmt.Println(f(30)) //60

    f1 := adder()
    fmt.Println(f1(40)) //40
    fmt.Println(f1(50)) //90
}

变量f是一个函数并且它引用了其外部作用域中的x变量,此时f就是一个闭包。 在f的生命周期内,变量x也一直有效。