Go语言基础学习

Go基础

1、包

1.1 概述

  • 每个Go程序都是由包构成的
  • 每个.go都要属于一个包
  • 程序从main包开始

1.2 导入包

  • 通过import关键字来导入包

  • 自定义的包从go.mod文件所在的那个目录算起

  • 导入多个包用()括起来

  • 导入包可以指定别名

    • import test "Go-basic/testpakage"
      
    • 后面再使用的时候就用test.*即可

  • 若在报名前用一个.,表示直接导入到该代码中,后面使用的时候直接使用变量名,无需使用包名.变量名

    • import . "Go-basic/testpakage"
      
1.2.2 匿名导入
  • 可以匿名导入一个包(用下划线来代表匿名导入),导入后代码中不能使用该包中方法和变量,一般用来执行该包的一些初始方法

    • import _ "Go-basic/testpakage"
      

1.3 已导出

  • 在一个包下的变量和方法来说,如果首字母大写,代表该变量和方法是已导出的,可以在别的包中使用
  • 小写的话,仅能在同一个包下使用,一个包可以包含多个.go文件

2、函数

2.1 函数定义格式

2.1.1 一般定义格式
  • 函数通过func关键字定义,一般格式为

    • func 函数名(参数列表) 返回值类型 {
          
      }
      
    • func add(x int, y int) int {
          return x + y;
      }
      
  • 注意,类型在变量名后面,相同的类型,类型可以只保留一个

    • func add(x, y int) int {
          return x + y;
      }
      
2.1.2 多值返回
  • 函数可以多值返回,返回多个值,一旦返回数量大于1时,需要用括号括起来,一个返回值时可以不用括号

    • func swap(x int, y int) (int, int) {
      	return y, x
      }
      
2.1.3 命名返回值
  • 函数可以直接在返回类型那里定义返回的变量名,在后面的函数体中可以直接使用,无需在创建

  • 此时一个返回值,也需要用括号括起来

    • func swap(x int, y int) (a int, b int) {
      	a = y
      	b = x
      	return
      }
      
      func add(x int, y int) (c int) {
      	c = x + y
      	return
      }
      

3、变量

3.1 概述

  • 通过var关键字来声明一个变量,基本格式为var 变量名 变量类型

  • var定义的变量可以出现在包中(函数外),也可以出现在函数中

    • import "fmt"
      
      // 在函数外定义
      var a int = 9
      
      func main() {
      	// 在函数内定义    
      	var b int = 10
      	fmt.Println(b)
      }
      

3.2 变量初始化

  • 变量可以直接初始化,如果没有直接初始化,会被赋予一个默认值

  • 如果直接初始化后,可以省略变量类型,go会从变量值中推断出变量类型

    • func main() {
          // 省略了int
      	var a = 100
          // 完整形式
      	var b int = 10
          // 未直接初始化,会有一个默认值,此时int不能省略
      	var c int
      	fmt.Println(a, b, c)
      }
      

3.3 短变量声明

  • 可以使用简洁赋值语句:=来赋值,无需关键字var,和变量类型

    • func main() {
          // 直接使用:=来赋值,会自动推断变量类型
          a := 100
      	fmt.Println(a, b, c)
      }
      
  • 注意:只能在函数内使用,不能在函数外使用,函数外必须使用关键字var

4、数据类型

4.1 基本数据类型

  • 整数类型:

    • int(常用)  int8  int16  int32  int64
      uint(常用) uint8 uint16 uint32 uint64 uintptr
      
      byte // uint8 的别名
      
      rune // int32 的别名,表示一个 Unicode 码点
      
    • 在未指定变量类型时,整数默认为int类型

    • 在未指定变量类型时,字符默认为int32类型

  • 布尔类型

    • bool
  • 字符串类型

    • string
  • 浮点数类型

    • float32 float64
    • 在未指定变量类型时,浮点数默认为float64类型
  • 复数类型

    • complex64 complex128
    • 在未指定变量类型时,复数默认为complex128类型
  • 指针类型

    • uintptr
  • 注意事项

    • int, uintuintptr 在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽

4.2 零值

  • 数值类型的零值为0
  • 布尔类型的零值为false
  • 字符串类型的零值为""(注意不是nil,是空字符串)

4.3 类型转化

  • T(v)可以将值v转换为T类型,需要显示转换,别名之间(int32<->rune, byte<->uint8)转换无需显示说明,但是自定义的别名就需要显示转换了

    • type i int
      
      func main() {
          // 默认为int类型
      	a := 1
          
      	var b int32 = 3
          // int32和rune可以直接转换
      	var c rune = b
          
      	//var d i = a,无法直接转换,自定义的别名必须强转
      	fmt.Println(a, b, c)
      }
      
      

5、常量

  • 常量是使用const关键字来声明的量

    • // float64可以省略,但是必须初始化
      const Pi float64 = 3.14
      
  • var用法类似,但是必须要初始化,不能使用:=

6、程序结构

6.1 for循环

  • Go中只有一种循环for循环

  • for循环中没有小括号,必须要有大括号

    • func main() {
          // 不能有小括号
      	for i := 0; i < 10; i++ {
      		fmt.Println(i)
      	}
      }
      
  • for循环的不同格式

    • func main() {
      	// 一般形式
      	for i := 0; i < 10; i++ {
      		fmt.Println(i)
      	}
      	
      	// 类似于while
      	var i int
      	for i != 10 {
      		fmt.Println(i)
      		i++
      	}
      	
      	// 无限循环
      	for {
      		fmt.Println("无限循环")
      	}
      }
      

6.2 if语句

  • if语句也没有小括号,但是必须有大括号

  • if语句格式

    • func main() {
      	var a int
          // 不用小括号
      	if a == 0 {
      		fmt.Println("a = 0")
      	} else if a == 1 {
      		fmt.Println("a = 1")
      	} else {
      		fmt.Println("a != 0 && a != 1")
      	}
      	
          // 条件前面可以有个简单的语句
      	if f := 0; a > 0 {
      		fmt.Println(f)
      	}
      }
      

6.3 switch语句

  • Go中switch语句无需break字段,它只会运行选中的case语句运行,如果想继续运行需要fallthrough关键字

    • func main() {
      	a := 1
      	switch a {
      	case 0:
      		fmt.Println(a)
      	case 1:
      		fmt.Println(a)
          	// 打印两遍1    
              fallthrough
      	case 2:
              // 这个语句也会运行
      		fmt.Println(a)
      	default:
      		fmt.Println(a)
      	}
      }
      
  • switch中也可以加一个简单语句,类似if语句

    • func main() {
      	a := 1
      	switch a = 2; a {
      	case 0:
      		fmt.Println(a)
      	case 1:
      		fmt.Println(a)
      		fallthrough
      	case 2:
      		fmt.Println(a)
      	default:
      		fmt.Println(a)
      	}
      }
      
  • 没有条件的switch语句,case中的条件哪个为true,执行哪个case的语句

    • func main() {
      	a := 1
      	switch {
      	case a == 0:
      		fmt.Println(a)
      	case a > 0:
      		fmt.Println(a)
      	default:
      		fmt.Println(a)
      	}
      }
      

6.4 defer语句

  • defer 语句会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但在外层函数返回后才执行

  • 如果有多个defer的函数,那么函数会被压进一个栈中,服从后进先出的原则

    • package main
      
      import "fmt"
      
      func main() {
      	fmt.Println("counting")
      
      	for i := 0; i < 10; i++ {
      		defer fmt.Println(i)
      	}
      
      	fmt.Println("done")
      }
      // 结果为:
      //counting
      //done
      //9
      //8
      //7
      //6
      //5
      //4
      //3
      //2
      //1
      //0
      

7、其他结构

7.1 Go指针

  • Go拥有指针,指针保存了值的内存地址

  • *T,表示指向T类型值的地址,零值为nil

  • var p *int
    
  • &操作符会生成一个指向其操作数的指针

  • i := 32
    p = &i
    
  • *操作符可以表示指针指向的值

  • fmt.Println(*p) // 通过指针 p 读取 i
    *p = 21         // 通过指针 p 设置 i
    
  • 也就是间接引用,和重定向

  • 注意:Go中没有指针运算,不能+,-

7.2 结构体struct

  • 一个结构体就是一组字段

    • type node struct {
      	value int
      	next *node
      }
      
  • 结构体字段通过.来访问

    • func main() {
      	node1 := node{1, nil}
      	fmt.Println(node1.value)
      }
      
  • 对于结构体指针,想要访问其中的字段,可以通过(*p).value来访问,但是也可以直接用p.value来访问,更简便

    • func main() {
      	node1 := node{1, nil}
      	p := &node1
          // 两者等效
      	fmt.Println(p.value)
      	fmt.Println((*p).value)
      	fmt.Println(node1.value)
      }
      
  • 结构体的创建

    • 通过直接列出字段的值来赋值

    • func main() {
      	var (
      		node1 = node{1, nil}
              // 指定特定字段赋值,其他字段为零值
      		node2 = node{value: 1}
      		node3 = node{next: nil}
      		node4 = node{}
              node5 = &node{2, nil}
      	)
      	fmt.Println(node1, node2, node3, node4, node5)
      }
      // 打印结果
      // {1 <nil>} {1 <nil>} {0 <nil>} {0 <nil>} &{2 <nil>}
      

7.3 数组

  • 类型[n]T表示有n个T类型值的数组,长度是数组类型的一部分,n不一样类型就不一样

  • 定义格式为

    • // 一般定义
      var arr [8]int
      
      arr = [8]int{1,2,3,4,5,6,7,8}
      
      // 简单定义
      arr1 := [8]int{1,2,3,4,5,6,7,8}
      
      // 不知道后面个数,注意这里是定义了一个数组,不是切片
      arr2 := [...]int{1,2,3,4,5,6,7,8}
      
      // 这是切片,不是数组
      arr3 := []int{1,2,3,4,5,6,7,8}
      
  • 数组的长度不可更改,是固定的

7.4 切片

7.4.1 切片概述
  • 数组的长度固定,但是切片的长度可变

  • 类型[]T,表示一个类型T的切片

  • // 这是切片,不是数组
    arr3 := []int{1,2,3,4,5,6,7,8}
    
  • 切片可以通过下标来截取,不包括high的位置,数组也可以截取,但是被截取的部分就变成了切片类型

  • a[low : high]
    
  • 注意

    • 切片并没有实际存储,它只是底层数组的一个引用
    • 底层仍然是一个数组在存储数据
    • 更改切片的内容实际是会修改底层数组的内容
    • 如果有其它切片也引用这个数组,那么其他切片内容也会改变
  • 切片的构造

    • 在创建一个切片时,首先会在底层创建一个数组,然后将它的引用给这个切片
  • 切片的默认行为

    • 切片的下界默认为0,上界为切片的长度
7.4.2 切片的长度与容量
  • 长度与容量是两个概念,可以通过len(s)和cap(s)来获取

  • 切片的长度为它实际包含的元素个数

  • 切片的容量为从切片引用的第一个元素(不是底层数组的第一个元素)到底层数组最后一个元素的个数

  • package main
    
    import "fmt"
    
    func main() {
    	s := []int{2, 3, 5, 7, 11, 13}
    	printSlice(s)
    
    	// 截取切片使其长度为 0
    	s = s[:0]
    	printSlice(s)
    
    	// 拓展其长度,能够往后扩展,只要不超过底层数组的范围,但是没办法向前扩展
    	s = s[:4]
    	printSlice(s)
    
    	// 舍弃前两个值
    	s = s[2:]
    	printSlice(s)
    }
    
    func printSlice(s []int) {
    	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
    }
    // 输出结果为
    // len=6 cap=6 [2 3 5 7 11 13]
    // len=0 cap=6 []
    // len=4 cap=6 [2 3 5 7]
    // len=2 cap=4 [5 7]
    
  • 切片的零值是nil,表示没有底层数组,且cap和len都为0的切片

7.4.3 用 make 创建切片
  • 切片用内置函数make来创建,返回一个创建好的参数

    • func make([]T, len, cap) []T
      
  • 使用方法

    • package main
      
      import "fmt"
      
      func main() {
          // 后两个参数分别代表长度和容量,没指定容量的时候,容量和长度相同
      	s := make([]int, 5, 10)
      	s1 := make([]int, 10)
      	printSlice(s)
      	printSlice(s1)
      }
      
      func printSlice(s []int) {
      	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
      }
      // 输出结果为
      // len=5 cap=10 [0 0 0 0 0]
      // len=10 cap=10 [0 0 0 0 0 0 0 0 0 0]
      
7.4.4 切皮的切片
  • 切片可以包含任何东西,甚至是切片的切片

  • s := make([][]string, 5)
    
    board := [][]string{
    		[]string{"_", "_", "_"},
    		[]string{"_", "_", "_"},
    		[]string{"_", "_", "_"},
    	}
    
7.4.5 向切片中追加元素
  • 通过apped可以向切片中追加元素
  • func append(s []T, vs ...T) []T
  • 向切片中追加切片,需要在后面加...来展开
  • s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
7.4.6 range
  • for 循环的 range 形式可遍历切片或映射。
  • 当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
  • 可以用_来忽略

7.5 map映射

  • 定义格式

    • // 这个只是定义,不能直接使用,需要make使用
      var m map[string]int
      
      // 这个可以直接使用,make函数同切片
      m1 := make(map[string]int)
      m1["a"] = 1
      fmt.Println(m1["a"])
      

7.6 函数

  • 函数也可以直接被定义

  • 也可以作为参数和返回值

  • package main
    
    import "fmt"
    
    func main() {
    	f := func(x, y int) int {
    		return x + y
    	}
    	fmt.Println(f(1, 2))
    }
    
    // 打印结果为3
    

7.7 方法

  • Go语言没有类,可以给结构体定义方法

    • type Vertex struct {
      	X, Y float64
      }
      
      func (v Vertex) Abs() float64 {
      	return math.Sqrt(v.X*v.X + v.Y*v.Y)
      }
      
  • 方法只是带有接收者参数的函数

  • 也可以为非结构体声明方法,但是只能为同一个包下的类型声明方法,主要就是type来定义的类型

  • 如果接收者是个指针类型,那么方法里面对结构体的修改会影响到调用的结构体,否则方法里面处理的是结构体的副本

  • 带指针参数的函数,必须传递指针类型的参数

  • 带指针类型接收者的方法,调用时可以直接v.method,但这并不意味着两者相同,只是编译器会自动转换

7.8 接口

  • 接口类型 是由一组方法签名定义的集合。接口类型的变量可以保存任何实现了这些方法的值。
  • 接口的实现无需显示说明,这样某个类型的方法实现了接口中的所有方法,就认为该类型实现了该接口
7.8.1 接口值
  • 接口也是个值,保存了具体底层类型的具体值,比如说某个类型实现了该接口,将该类型赋予接口时,接口保存的值,就是该类型的具体值
  • 接口可以用作函数参数和返回值
  • 接口值调用方法时会执行其底层类型的同名方法。
  • 对于底层值为nil的接口值,在调用方法的时候,也会被调用
  • 保存了具体值nil的接口自身并不为nil
  • nil 接口值既不保存值也不保存具体类型。
7.8.2 空接口
  • 空接口可以保存任何值

    • interface{}
      

7.9 类型断言与类型选择

7.9.1 类型断言
  • 类型断言 提供了访问接口值底层具体值的方式。

  • t := i.(T)
    
  • 该语句断言接口值 i 保存了具体类型 T,并将其底层类型为 T 的值赋予变量 t

  • i 并未保存 T 类型的值,该语句就会触发一个恐慌。

  • 为了 判断 一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。

  • t, ok := i.(T)
    
  • i 保存了一个 T,那么 t 将会是其底层值,而 oktrue

  • 否则,ok 将为 falset 将为 T 类型的零值,程序并不会产生恐慌。

7.9.2 类型选择
  • 类型选择 是一种按顺序从几个类型断言中选择分支的结构。

  • switch v := i.(type) {
    case T:
        // v 的类型为 T
    case S:
        // v 的类型为 S
    default:
        // 没有匹配,v 与 i 的类型相同
    }
    
  • 类型选择中的声明与类型断言 i.(T) 的语法相同,只是具体类型 T 被替换成了关键字 type

  • 一般用于判断接口值i的底层数据类型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值