《Golang学习日记》——Day1:初识Go


《Golang学习日记》Day1:初识Go

前言

  几天前,在经过一番抉择之后,博主我终于下定决心去掉了C++和Java,而最终选中Golang作为我吃饭的语言了。为什么我会选择Go这门比较新的语言呢?我想有以下三个方面的原因:

个人方面:

  我是一个喜欢新鲜事物的人,Go诞生至今12年,还是一门比较新的语言。Go发展的时间相比于C++,Java这类老牌语言少了很多年,目前Go的生态对比Java这类生态极其丰富的语言差距也是比较大的,而我正好是一个喜欢当新领域开拓者的人,我认为Go还有很多可以开拓的地方,以及很多可以对Go生态做出贡献的机会。

社会方面:

  目前不少互联网企业都在使用Go或者把旧业务语言转为Go,Go开发的岗位越来越多,在Jetbrain的”2019开发人员生态系统现状”的调查报告中有一个结论——“Go是最有前途的语言”,在随后2020,2021的调查报告中,Go也都是”开发者计划采用或迁移到的第一大语言”。可见现在学习Go语言是很有前景的。

语言方面:

  Go语言是为了解决当下编程语言对并发支持不友好、编译速度慢、编程复杂这三个问题而诞生的,所以Go语言对于目前的环境来说对比老牌语言是有语言特性上的优势的。对我个人来说Go语言最吸引我的一个特点是:Go语言相较于C++使用要更简单,而且语言的性能接近C++。虽然我在大一以来一直在使用C++,目前我大二同时也是大一计算机系C++课程的助教,但我仍然感觉C++是一门复杂的语言。我很早之前就知道了Go这门语言的存在,但是听名字总感觉它是个“奇葩”,再加上它是个新语言前景不确定所以一直没有了解它,直到有一天在网上浏览时看到了Go语言的特点,我又惊又喜,当时我就有一种感触是——我看到了未来,而Go语言就是未来。

写作目的:

  我认为写学习日记能有很多益处,写学习日记可以记录自己学习Go的历程,方便自己以后复习用,同时用自己的话术把学到的知识复述一遍其实也正好使用了费曼学习法来加深记忆,除此之外,虽然这个小破站平时基本没什么人会进来访问,我还是希望能让进来的有缘人学到东西,所以我会尽量把学习日记写得简单易懂。不知道你是否也是和我一样对Go抱有浓厚兴趣的人,如果是,那就让我们一起学习这门“未来的计算机语言”吧。🍭🍭

  (注:博主写本篇(第一篇)学习日记时学习Go语言使用的书籍为:《Go语言核心编程》,写作内容和写作思想可能会大部分受这本书影响。同时博主写学习日记会将读者对象当成没学过Go语言的对象,并尝试以教学的口吻和通俗易懂的说法向读者解释Go语言,以更好地使用费曼学习法)


一. Go词法单元

  现代高级语言的源程序内部的几个基本的概念:token、关键字、标识符、操作符、分隔符和字面量是通用的,不是某种编程语言特有的,《Go语言核心编程》的作者特意强调和介绍了这几个概念,他认为这种语言层面通用的概念非常重要,这些概念能够帮助程序员更好地掌握语言的语法结构。博主的观点也和作者是一致的,但是原书在这个部分上花的笔墨较多,我将会做些简化来讲述这几个概念。


1.token

1.1 token的概念

  token是构成源程序的基本不可再分割的单元。 编译器编译源程序的第一步就是将源程序分割为一个个独立的token,这个过程就是词法分析。Go语言的token可以分为关键字、标识符、操作符、分隔符和字面常量等。


1.2 token的分割方法

  Go语言通过token分隔符将各token分割出来。Go的token分隔符有两类:操作符和纯分隔符。
  操作符:一个操作符既是一个分隔符,自身也是一个token。

  /*在该语句中":="和"+"为操作符,他们即是分隔符,也是token,所以这个简单的语句
  被分割为5个token:"a"、":="、"b"、"+"、"c"*/
  a := b+c

  纯分隔符:其本身不具备任何语法含义,只作为其他token的分割功能,且本身不是token。

  /*纯分隔符包括空格、制表符、换行符和回车符
  多个相邻的空格或者制表符会被编译器看作一个分隔符处理
  比如该包声明语句package和main之间有多个空格,但Go编译器会将其作为一个分隔符处理,该语句被分离成两个token:package和main*/
  package    main

2.标识符

  2.1 标识符的概念

  编程语言的标识符用来标识变量、类型、常量等语法对象的符号名称,其在语法分析时作为一个token存在。总体上分为两类:语言设计者预留的标识符和编程者自定义标识符。


  2.2 标识符构成规则

  Go的标识符构成规则与大部分编程语言一样:首位字符必须是字母或下划线,后面可跟任意多个字符、数字或下划线,Unicode字符也可以作为标识符的构成。编程者自定义标识符时要避开Go语言预声明标识符,以免引发混乱。


  2.3 预声明标识符

  Go语言共有65个预声明标识符:25个关键字(Keywords)、20个内置数据类型标识符、15个内置函数、4个常量值标识符、1个空白标识符。这些各类标识符在博主以后用到时,再在《Golang学习日记》以后的篇章中进一步介绍。

  (标识符快速理解:具有语义且符合命名规则的语言内置或用户自定义的文字,不过代表赋值的值的文字不是标识符)


3.操作符和分隔符

  3.1 操作符和分隔符的概念

  操作符就是语言所使用的符号集合,包括运算符、显式的分隔符,以及其他语法辅助符号。操作符不但自身是一个token,具备语法含义,同时也是分割其他token的分隔符。还有一类分隔符本身没有什么含义,仅起到分割token的功能,其本身也不算一个token,称为纯分隔符。纯分隔符有4个:空格、制表符、回车和换行。Go语言共使用了47个操作符。


4.字面常量

  4.1 字面常量的概念

  编程语言源程序中表示固定值的符号叫作字面常量,简称字面量。Go的字面量可以出现在两个地方:一是用于常量和变量的初始化,二是用在表达式里或者作为函数调用实参。

  (注:对于未显式声明数据类型的变量,Go编译器会结合字面量的值自动进行类型推断,Go中的字面量只能表达基本类型的值,Go不支持用户定义的字面量)


  4.2 字面常量的分类

  字面量种类有:整形字面量,浮点型字面量,复数类型字面量,字符型字面量,字符串字面量。

  (字面量快速理解:赋值中的代表值的文字或函数调用实参)


5.Go源程序框架

  在了解所有类型的token和分隔符后,我们来看一看Go语言中的hello world程序,从token的构成角度分析源程序。

/*定义一个包,包名为main,main是可执行程序的包名,所有的Go源程序头部都必须有一个
包声明语句,Go通过包来管理命名空间*/
package main

/*import为引用一个外部包fmt,可以是标准包也可以是第三方或自定义的包,fmt是标准输入/输出包*/
import "fmt"

/*func关键字声明一个函数,函数名为main,main代表Go程序入口函数*/
func main() {
  /*调用fmt包里的Printf函数,函数实参是一个字符串字面量,在标准输出里打印"Hello world!",\n为转义符,表示换行*/
    fmt.Printf("Hello world!\n")
}

  源代码可以拆分为以下token:

  •   关键字(3个) package   import  func
  •   标识符(3个) main   fmt   Printf
  •   字面量(2个) "fmt"   "Hello world! \n"
  •   操作符(5个) ()   {}  .

    现在总结Go的源程序基本构成:

      (1)关键字引导程序的基本结构

      (2)内置类型标识符辅助声明变量和常量

      (3)字面量辅助变量和常量的初始化

      (4)分隔符帮助Go编译器识别各个token

      (5)操作符和变量、关键字一起构成丰富的语法单元

      通过对上面概念的学习,把Go源程序的每个token梳理了一遍,相信大家对Go源程序的基本结构都已经有所了解了,对于一个Go源程序框架就应该能很快浮现起来了:

    package xxx
    
    import{
      "xxx"
      "xxx"
    }
    
    func xxx(){
    
    }
    

      随着接下来对Go语言的深入学习,该框架会逐步完善,从程序整体框架到具体语法知识的学习模式,可以帮助学习者建立整体和局部的概念,避免学习过程中“只见树木,不见森林”。


    二. 变量和常量

      高级语言通过一个标识符来绑定一块特定的内存,后续对特定内存的操作都可以使用标识符来代替。这类绑定某个存储单元的标识符又可以分为两类,一类称为“变量”,而另一类称为“常量”。变量表示指向的内存可以被修改,而常量表示指向的内存则不能被修改。


    1. 变量

      变量:使用一个标识符来绑定一块内存地址,该内存地址中存放的数据类型由定义变量时指定的类型决定,该内存地址里面存放的内容可以修改。

    1.1 显式的完整声明

    var varName DataType = value
    /* 关键字var含义为声明一个变量
       varName为变量名
       DataType为数据类型
       value是变量的初始值,可以是字面量,表达式,或其他变量名。如果未指定初始值,也
       就是删去"= value",则变量初始化为类型的零值
       Go的变量声明后就会立即为其分配空间
    */
    var a int = 1
    var a int = 2*3
    var a int = b
    

    1.2 短类型声明

    varName := value
    /* :=声明只能出现在函数内
       使用:=时Go编译器会自动进行数据类型推断,之后赋值
       Go支持多个类型变量同时声明并赋值
    */
    a,b := 1, "金毛败犬"
    

    1.3 变量的五个属性

  • 变量名

      声明了该变量的自定义标识符,具体命名规则在“一.Go词法单元”有相关介绍。

  • 变量值

      变量实际指向的内存地址里存放的值,变量的值具体怎么解析由变量的类型决定。

  • 变量存储和生存周期

      Go语言提供自动内存管理,通常程序员不需要特别关注变量的生存期和存放位置。编译器使用的栈逃逸技术能够自动为变量分配空间:有可能在栈上,也有可能在堆上。

  • 类型信息

      类型决定了该变量存储的值怎么解析,以及支持哪些操作和运算,不同类型的变量支持操作和运算集是不一样的。

  • 可见性和作用域

      Go内部使用统一的命名空间对变量进行管理,每个变量都有一个唯一的名字,包名是这个名字的前缀。


    2.常量

      常量使用一个名称来绑定一块内存地址,该内存地址中存放的数据类型由定义常量时指定的类型决定,而且该内存地址里面存放的内容不可以改变。常量存储在程序的只读段里(.rodata section)

    const v int =2
    const a,b =2,3 //支持多组相同类型赋值,可以省略类型标识符,编译器会自动推导类型
    

      预声明标识符iota用在常量声明中,其初始值为0。一组多个常量同时声明时对值逐行增加,iota可以看作自增的枚举变量,专门用来初始化常量。

    package main
    
    //iota逐行增加
    const (
        a = iota // 0
        b  //1 没有显式赋值会隐式赋值为iota,iota = 1
        c = "Eriri" //c独立赋值,但iota依然会自增1,iota = 2
        d = 2 //d独立赋值,iota依然自增1,iota = 3
        e = iota // 4
        f //5
        g = 1 << iota // g == 64,iota = 6
    )
    
    //分开的const语句,iota从0开始
    const h = iota //0
    
    func main() {
            println(a, b, c, d, e, f, g, h)
    }
    //运行结果为:0 1 Eriri 2 4 5 64 0
    

    三. 基本数据类型

      Go是一种强类型的静态编译语言,类型是高级语言的基础,有了类型,高级语言才能对不同类型抽象出不同的运算,编程者才能在更高的抽象层次上操作数据,而不用关注具体存储和运算细节。

      这个单元博主将会以代码为主,文字说明为辅,让大家更快速更好地弄懂各基本数据类型。


    1.Go语言内置七类基本数据类型(20个具体子类型)

      布尔类型:bool

      整型:byte int int8 int16 int32 int64 uint8 uint16 uint32 uint64 uintptr

      复数:complex64 complex128

      浮点型:float32 float64

      字符:rune

      字符串:string

      错误类型:error


    2. 布尔类型

      布尔类型关键字为bool,布尔类型只有两个值:true 和 false,true和false是Go语言内置的两个预声明标识符。

      //三种赋值,第一种为完整的赋值语句,第二第三种均为自动类型推导
      var ok bool = false
      var ok = false
      ok := true 
    
      //布尔型数据和整形数据不能相互转换
      var b bool = 0 
      //报错!'0' (type untyped int) cannot be represented by the type bool
    

    3. 整型

      Go语言内置了12种整数类型,分别是byte、int、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64、uintptr。其中byte是uint8的别名,不同类型的整型必须进行强制类型转换。

    //各整型所占空间大小,单位为字节,测试在64位系统环境下
        var i1 byte
        var i2 uint8
        var i3 int8
        var i4 uint16
        var i5 int16
        var i6 int32
        var i7 uint32
        var i8 int64
        var i9 uint64
        var i10 uintptr
        var i11 int
        fmt.Println(unsafe.Sizeof(i1))  //1
        fmt.Println(unsafe.Sizeof(i2))  //1
        fmt.Println(unsafe.Sizeof(i3))  //1
        fmt.Println(unsafe.Sizeof(i4))  //2
        fmt.Println(unsafe.Sizeof(i5))  //2
        fmt.Println(unsafe.Sizeof(i6))  //4
        fmt.Println(unsafe.Sizeof(i7))  //4
        fmt.Println(unsafe.Sizeof(i8))  //8
        fmt.Println(unsafe.Sizeof(i9))  //8
        fmt.Println(unsafe.Sizeof(i10)) /*8,uintptr与系统有关,32位下是4字节,
                                        64位下是8字节*/
        fmt.Println(unsafe.Sizeof(i11)) /*8,int与系统有关,32位下是4字节,
                                        64位下是8字节*/
    
    //赋值操作
        var a int = 5
        var b int32 = a //错误,Go的设计理念是不允许隐式转换,不同类型的整型必须进行强制转换
        var c int32 = int32(a) // ok
    
    //整型支持算术运算和位运算,算术表达式和位操作表达式的结果还是整型
        var t1 int = (1+2)*3
        var t2 int = 1000>>2
        var t3 int = 3/2 // t3=1,舍去小数部分
    
    //byte是uint8的别名
    var a byte = 1
    var b uint8 = a //ok
    
    //但在64位(或32位)系统下,int与int64值范围相同,但int不等于int64,不能相互赋值
    var t1 int = 1
    var t2 int64 = t1 //Cannot use 't1' (type int) as the type int64
    

    4. 浮点型

      浮点型用于表示包含小数点的数据,Go语言内置两种浮点类型:float32(4字节)和float64(8字节)。

    //浮点数字面量被自动类型推断为float64类型
      a:=5.0
      fmt.Printf("%T\n", a) //%T为输出a的类型名,float64
    
    /*计算机很难进行浮点数的精确表示和存储,因此两个浮点数之间不应该使用==或!=
    进行比较操作,高精度科学计算应该使用math标准库*/
    

    5. 复数类型

      Go语言内置的复数类型有两种,分别是complex64和complex128,复数在计算机里使用两个浮点数表示,一个表示实部,一个表示虚部。complex64是由两个float32构成的,complex128是由两个float64构成的。复数的字面量表示和数学表示法相同。

    // complex32为8字节(2*float32),complex128为16字节(2*float64)
        var a complex64 = 10.0 + 3i
        var b complex128 = 15.0 + 2i
        c := 20.0 + 1i
        fmt.Println(unsafe.Sizeof(a)) //8
        fmt.Println(unsafe.Sizeof(b)) //16
        fmt.Println(unsafe.Sizeof(c)) //16,c自动类型推导为complex128
        fmt.Println(b) //(15+2i)
        fmt.Println(b + c) //(35+3i)
    

    6. 字符串(string)

      Go语言将字符串作为一种原生的基本数据类型,字符串初始化可以使用字符串字面量。

        var a string = "hello"
        var b = "world"
        c := "金毛败犬"
        fmt.Println(unsafe.Sizeof(a)) //16
        fmt.Println(unsafe.Sizeof(b)) //16
        fmt.Println(unsafe.Sizeof(c)) //与字符长度无关,字符串为16字节(64位)
        fmt.Println(b) //world
        fmt.Println(a + b) //helloworld
    

      字符串是常量,可以通过下标访问其字节单元,但是不能修改某个字节的值。

      a := "hello"
      b := a[0] //b的类型为uint8/byte
      a[1] = 'b' //错误,Cannot assign to a[1]
    

      字符串尾部不包含NULL字符,这一点和C/C++不同。

      字符串类型底层实现是一个二元的数据结构,一个是指针指向字节数组的首地址,另一个是长度。

    //string结构源码
    type stringStruct struct {
        str unsafe.Pointer //指向底层字节数组的指针
        len int //字节数组长度
    }
    /*64位下unsafe.Pointer和int均为8字节,所以string共有16字节,
    32位下unsafe.Pointer和int均为4字节,所以32位下string位8字节*/
    

      字符串的运算与遍历如下:

        a := "hello"
        b := "world"
        c := a + b //字符串a与b拼接
    
        fmt.Println(len(c)) //10,内置len函数获取字符串c的长度
    
        //循环遍历字节数组
        for i := 0; i < len(c); i++ {
            fmt.Print(c[i], " ")
        }
        fmt.Println()
        for i, v := range c {
            fmt.Print(i, v, " ") //i为当前字符下标,v为字符的值
        }
        fmt.Println()
    
        //如果想要显示字符形式,需要转换为string
        for i := 0; i < len(c); i++ {
            fmt.Print(string(c[i]), " ")
        }
        /*输出结果:
        10
        104 101 108 108 111 119 111 114 108 100 
        0 104 1 101 2 108 3 108 4 111 5 119 6 111 7 114 8 108 9 100 
        h e l l o w o r l d */
    

    7. rune类型

      Go内置两种字符类型:一种是byte的字节类类型,另一种是表示Unicode编码的字符rune。rune在Go内部是int32类型的别名,占用4个字节。Go语言的默认字符编码是UTF-8类型,如果需要特殊的编码转换,则使用Unicode/UTF-8标准包。

    //rune是int32的别名
      var a int32;
      var b rune = a;
    

      第一章暂时就先讲到基本数据类型,下一章是复合数据类型:指针、数组、切片、map、struct,以及控制结构。写如此长的学习日记确实挺花时间,不知道大家觉得第一篇学习日记写得怎么样,是不是文字太多余了,或者说明不清晰,可以在下方评论反馈,博主会定期查看每个人的评论的哦,要是能帮到你我可太高兴了呢🍭🍭


  • 文章作者: 金毛败犬
    版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 金毛败犬 !
    评论
      目录