GO基础语法和常用特性万字解析(一) | 青训营

641 阅读11分钟

第一部分 基本数据类型

了解基本数据类型,string类型不属于基本类型

数值型
整数类型int , int8 , int16 , int32 , int64 , uint , uint8 , uint16 , uint32 , uint64 , byte
浮点类型float32(单精度) , float64(双精度)(默认8位,64字节)
字符型没有专门的字符型,使用byte来保存单个字符
布尔型bool(一个字节)
字符串string

第二部分 关键字、保留字

breakdefaultfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar

第三部分 运算符优先级

分类描述关联性
后缀() [] -> . ++ --从左到右
单目+ - ! ~ (type) * & sizeof从右到左
乘法* / %从左到右
加法+ -从左到右
移位<< >>从左到右
关系< <= > >=从左到右
相等(关系)== !=从左到右
按位and&从左到右
按位xor从左到右
按位or|从左到右
逻辑and&&从左到右
逻辑or||从左到右
赋值运算符= += -= *= /= %= >>= <<= &= ^= |=从右到左
逗号,从左到右

第四部分 分支、循环、判断

  1. 支持在if中定义变量, } else { 不允许换行

     if age := 20; age > 18 {    //条件不需要加括号
         fmt.Println("成年了")
     }
    
  2. if条件内不允许出现if b = false这种赋值语句

  3. switch语句case后不需要break语句,默认case最后为break;

  4. case后的表达式可以为多个用 , 隔开,可以是常量、变量、一个有返回值的函数;

  5. case后的数据类型与switch后表达式的数据类型相同

     var num1 int32 = 10
     var num2 int32 = 20
     switch num1 {
         case num2, 5, 10:       //数据类型相同即可
             fmt.Println("Yes")
         default:                //允许没有default
             fmt.Println("NO")
     }
    
  6. case后的值如果是常量则不允许重复

  7.  var num int = 10            //当作if...else分支使用
     switch {
         case num == 10:
             fmt.Println("Yes")
         default:
             fmt.Println("NO")
     }
     ​
     var score int = 90
     switch {
         case score > 90:
             fmt.Println("优秀")
         case score > 75 && score <= 90:
             fmt.Println("良好")
         case score >= 60 && score < 75:
             fmt.Println("及格")
         default:
             fmt.Println("不及格")
     }
    
  8. switch穿透;默认穿透一层

     var  num int = 10
     switch {
         case num == 10:
             fmt.Println("Yes")
             fallthrough
         default:
             fmt.Println("or NO")
     }
    
  9. Type Switch:switch语句还可以被用于type-switch来判断某个interface变量中实际指向的变量类型

         var x interface{}
         var y = 10.0
         x = y
         switch i := x.(type) {
         case nil:
             fmt.Printf("x的类型:%T\n", i)
         case int:
             fmt.Println("x是 int 型")
         case float64:
             fmt.Println("x是 float64 型")
         case func(int) float64:
             fmt.Println("x是 func(int) 型")
         case bool, string:
             fmt.Println("x是 bool或者string 型")
         default:
             fmt.Println("未知型")
         }
    
  10. 循环结构标准写法与特有写法

     // 1
         str1 := "hello~world!"
         for i := 0; i < len(str1); i++ {        //无法识别中文
             fmt.Printf("val = %c\n", str1[i])
         }
     // 1改
         str1 := "hello~world!"
         t := []rune(str1)               //[]rune方法接收,可以识别中文
         for i := 0; i < len(t); i++ {
             fmt.Printf("val = %c\n", t[i])
         }
     // 2
         str2 := "hello~world"
         for index, val := range str2 {          //识别全部类型
             fmt.Printf("index = %d val = %c\n", index, val)
         }
    
  11. for-range是按照字符的方式遍历,可以接收输出中文

  12. go没有while和do...while语法

         var num int = 10
         for {
             fmt.Println("hello", num)   //循环体在前,判断条件在后,循环体最少执行一次
             num++
             if num > 10 {
                 break
             }
         }
    
  13. 生成随机数

     import (
         "fmt"
         "math/rand"
         "time"
     )
     ​
     func main() {
         rand.Seed(time.Now().Unix())
         num := rand.Intn(100) + 1       //rand.Intn(n)的范围[0,n)
         fmt.Println(num)                //rand.Intn(100) + 1的范围[1,100)
     }
    
  14. break跳出最近for循环;break lablen跳出标记n之后的循环

  15. continue跳出最近for循环的本次循环;continue lablen跳过标记n之后的这本次for循环

  16. goto lablen跳转到指定lablen: 的位置,尽量不使用

第五部分 函数

  1. 函数也是数据类型,可以被赋予;也可以作为形参被调用

  2. go支持自定义数据类型;支持对返回值命名

     type mySum func(num1 int,num2 int)(sum01 int, sum02 int) 
     //mySum等价于一个函数类型,func(int, int)(int, int)
    
     package main
     
     import "fmt"
     
     func main() {
         a, b := geySumAndSub(1, 2)
         fmt.Println(a)
         fmt.Println(b)
     }
     
     func geySumAndSub(n1 int, n2 int) (sum int, sub int) {
         sum = n1 + n2
         sub = n1 - n2
         return
     }
    
  3. _标识符,忽略返回值

         c, _ := getSumAndSub(1, 2)  // ↑ 8~9
         fmt.Println(c)
    
  4. 支持可变参数 (可变参数必须在末尾),args时slice切片,通过args[i]可以访问各个值

     //0~若干
     func sum(args... int) sum int{
     }
     //1~若干
     func sum(arg int, args... int) sum int{
     }
    
  5. 形参数据类型相同时,可用逗号隔开,末位加数据类型(num1,num2...numn float32)

  6. intit()函数,在main()函数执行前执行

     func init() {
     
     }
    
  7. 执行流程:全局变量=>init()=>main()

  8. 匿名函数01(范围:当前语句)

     res := func(n1 int, n2 int) int {   //只有func (int,int) int格式
         return n1 + n2
     }(10, 20)                           //直接输入形参
    

    匿名函数02 res可以复用(范围:每次调用)

     res := func(n1 int, n2 int) int {   //将函数赋值res
         return n1 + n2
     }
     res02 := res(10, 20)                //res不是函数名,而是函数类型
     fmt.Println(res02)
    

    匿名函数03 赋值给全局变量(范围:当前程序),首字母大写

     var MyFun = func(n1 int,n2 int) int {   //赋值给全局变量,myFunc就是全局匿名函数
         return n1+n2
     }
     
     func main() {
         res := MyFun(10, 20)
         fmt.Println(res)
     }
    
  9. 闭包

     func AddUpper() func(int) int {
         
         var num = 0                     //3~7,形成闭包,num相当于静态变量,只初始化一次
         return func(x int) int {
             num = num + x
             return num
         }
         
     }
     
     func main() {
     
         res := AddUpper()
         fmt.Println(res(1))
         fmt.Println(res(2))
         fmt.Println(res(3))
         
     }
    
  10. 闭包演示

     func makeSuffix(suffix string) func(string) string {
     
         return func(name string) string {           //3~8为闭包
             if !strings.HasSuffix(name, suffix) {
                 return name + suffix
             }
             return name
         }
         
     }
     func main() {
     
         f := makeSuffix(".jpg")
         fmt.Println(f("text01"))
         fmt.Println(f("text02.jpg"))
         
     }
    
  11. defer函数结束后释放资源,defer修饰的语句在当前函数结束后执行

  12. defer修饰的语句,执行时,进入独立的defer栈当前函数执行完毕后按照先入后出的方式出栈

     func sum(n1 int, n2 int) int {
         defer fmt.Println("第一个入栈")  // 4
         defer fmt.Println("第二个入栈")  // 3
         defer fmt.Println("第三个入栈")  // 2
         res := n1 + n2
         fmt.Println("sum函数调用结束")    // 1
         return res
     }
     func main() {
     
         res := sum(10, 20)
         fmt.Println(res)                    // 5
         
     }
    
  13. defer将语句入栈时,相关的值也会被拷贝入栈

     var num int = 10
     defer fmt.Println("num = ",num) //函数结束后输出,输出 num = 10
     num++   
    
  14. defer主要用法;解决关闭代码时机问题

     func text() {
         //1.打开 and 延迟关闭文件
         file = openfile(文件名)
         defer file.close()      //text()函数执行完毕后,释放函数创建的资源
         //其他代码
         
         //2.引用 and 延迟释放数据库资源
         connect= openDatabase()
         defer connect.close()
         //其他代码
     }
    

第六部分 简单错误处理

异常处理方式:defer, panic, recover

 //defer
 func test() {
     defer func() {          //捕获异常
         err := recover()
         if err != nil {     // if err := recover(); err != nil {
             fmt.Println("err = ", err)
         }
     }()
     num1 := 10
     num2 := 0
     res := num1 / num2
     fmt.Println(res)
 }

第七部分 数组

  1. 初始化数组方法

     var numArr [3]int = [3]int{1, 2, 3}
     var numArr  = [3]int{1, 2, 3}
     var numArr  = [...]int{1, 2, 3}
     var numArr  = [...]int{0:1, 1:2, 2:3}   //0、1、2为数组下标
     var strArr := [...]string{"1", "2", "3"}
    
  2. for-range,go独有遍历结构

     for index, value := range array {   //array 数组名
         // 不需要下标index,可用`_`代替
         //输出时,index代表下标
         //value,代表下标对应的值
     }
    
  3. 数组默认值传递

  4. arr [3]int 与arr [4]int 不是一种数据类型,arr []int是切片

  5.  var slice []int = []int {1, 2, 3}
     var slice = arr[stsrtIndex : endIndex]
     var slice = make([]byte, 5, 10) //创建空间,make(数据类型,大小,容量)
    
  6. 创建切片为值传递,修改slice[index]arr[startIndex + index]也会改变

  7. 切片slice是可以增长的

slice = append(slice,elems...) //将slice以及elems,并赋给slice

slice本身没有变化,=将slices与elems赋给了slice

  1. 切片拷贝,拷贝不会扩容

     var a []int = []int {1,2,3,4,5}
     var slice = make([]int,1)       //创建slice切片,初始化
     fmt.PrintIn(slice)          // [ 0 ]
     copy(slice,a)                   //将a拷贝到slice内
     fmt.Println(slice)          // [ 1 ]
    

第八部分 Map(键值对部分)

  1. 声明是不会分配内存的,初始化需要make,分配内存后才能赋值和使用

    map在使用前一定要make map的key是不能重复,如果重复了,则以最后这个key-value为准 map的value是可以相同的 map的key-value是无序 make内置函数数目,make(Type, size) size可以省略,默认值为1

         //声明,这时map=nil
         var cities map[string]string
         //make(map[string]string,l0)分配一个p空间
         cities = make(map[string]string, 10)
     
         //方式2
         //声明,就直接make
         var cities = make(map[string]string)
     
         //方式3
         //声明,直接赋值
         cities := map[string]string{
             "no04": "成都",
         }
         cities["no01"] = "北京"
    
  2. map删除:

    说明: delete(map,"key"),delete是一个内置函数,如果key存在,就删除该key-value 如果key不存在,不操作,但是也不会报错 如果我们要删除map的所有y,没有一个专门的方法一次删除,可以遍历一下key,逐个删除 或者map=make(...),make一个新的,让原来的成为垃圾,被gc回收

  3. Map查找

    key,value := cities["no02"] if value{ fmt.Printf("有no01 key值为%v\n",key) }else{ fmt.Printf("没有no01 key\n") }

  4. Map切片动态添加元素

         var monsters []map[string]string
         monsters = make([]map[string]string, 2) //准备放入两个妖怪
     
         if monsters[0] == nil {
             monsters[0] = make(map[string]string, 2)
             monsters[0]["name"] = "牛魔王"
             monsters[0]["age"] = "500"
         }
         
         if monsters[1] == nil {
             monsters[1] = make(map[string]string, 2)
             monsters[1]["name"] = "玉兔精"
             monsters[1]["age"] = "400"
         }
         fmt.Println(monsters)
     
         newMonster := map[string]string{
             "name": "新的妖怪~火云邪神",
             "age":  "200",
         }
         monsters = append(monsters, newMonster)
         fmt.Println(monsters)
    
  5. Map按照key值排序

         var keys []int
         for k := range map01 {
             keys = append(keys, k)
         }
     
         fmt.Println(keys)
         sort.Ints(keys)
         fmt.Println(keys)
     
         for _, k := range keys {
             fmt.Printf("map01 [%d] : %d\n", k, map01[k])
         }
    
  6. 变量名首字母大写表示公开,首字母小写表示私有

第九部分 结构体

创建结构体方法:

type Person struct {
	Name string
	Age  int
}

func main() {
	//方式1
	//在创建结构体变量时,就直接指定字段的值
	var p1 = Person{"name01", 19}
	p2 := Person{"name02", 20}
    
	//在创建结构体变量时,把字段名和字段值写在一起,就不依赖字段的定义顺
	var p3 = Person{
		Name: "name03",
		Age:  20,
	}
	p4 := Person{
		Age:  30,
		Name: "name04",
	}
    fmt.Println(p1, p2, p3, p4)
    
	// 方式2
	var p5 = &Person{"name05", 29}
	p6 := &Person{"name06", 39}
    
    //在创建结构体指针变量时,把字段名和字段值写在一起,不依赖字段的定义顺序
	var p7 = &Person{
		Name: "name07",
		Age:  49,
	}
	p8 := &Person{
		Age:  59,
		Name: "name08",
	}
    fmt.Println(*p5, *p6, *p7, *p8)
}

第十部分 继承、接口

继承

  1. 结构体可以使用嵌套匿名结构体所有的字段和方去,即:首字母大写或者小写的字段、方法,都可以使用。

  2. 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分

  3. 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法 (同时结构体本身没有同名的字段和方法) ,在访问时,就必须明确指定匿名结构体名字,否则编译报错

  4. 如果结构体中是一个有名结构体,则访问有名结构体的字段时,就必须带上有名结构体的名字

    若嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分

  5. 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

  6. 结构体的匿名字段是基本数据类型

    如果一个结构体有int类型的匿名字段,就不能第二个 如果需要有多个int的字段,则必须给int字段指定名字

  7. 若一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承

接口

  1. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量
  2. 接口中所有的方法都没有方法体,即都是没有实现的方法
  3. 在Golang中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口
  4. 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
  5. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
  6. 一个自定义类型可以实现多个接口
  7. Golang接口中不能有任何变量
  8. 一个接口(比如A接口)可以继承多个别的接口(比如B,C接口),这时如果要实现A接口,也必须将B,C接口的方法也全部实现
  9. interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用那么会输出nil
  10. 空接口interface{}没有任何方法,所以所有类型都实现了空接口,即我们可以把任何一个变量赋给空接口

学习小结

在学习go语言的过程中,能感受到代码的美感与细节,如数据类型中,go将string不再作为基本数据类型,而是使用更加简短的byte,针对常用数据类型,根据使用时的数据来选择占用空间更小的数据类型;分支循环中,穿透的使用,使得分支冗余量减少,for-range将遍历方式拆解切片,字符串,数组等更加方便

到目前为止的学习中,看到的go兼容简洁与高性能,学习到go的代码简化与其他语言的区别时,更加了解设计者对于每一个关键字,每一个函数的创造思路,观看源码,提升了自己对于编写代程时的各种思维方式,代码并非越复杂越好,优秀的代码应该是简洁高效,尽量减少不必要的实体与不需要的流程,时间复杂度与空间复杂度的取舍不仅要应对当前内容,更要思考面对及大数据量时,应该如何处理,如何避免出现不可知的错误