Go 语言基础语法与基本数据类型,整型、浮点型、字符型、字符串类型

go语言介绍和环境安装

1. go语言介绍

go语言是Google公司推出的一门开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。

从年龄上来看,go语言是比较年轻的,因为它是2007年末由Robert Griesemer、Rob Pike、Ken Thompson三位作者主持开发的,并于2009年11月正式开源。

到现如今,go编译器在各方面都已经做了充足的优化。下面说一下go的几个里程碑:

  • 2007.09 go语言设计草稿在白板上诞生;
  • 2008.01 Ken开发了go语言编译器负责把go代码转换成C代码;
  • 2009 go语言诞生;
  • 2016.01 go1.5版本中实现了自举, 不再依赖C编译器;
  • 2017.02 go1.8版本发布, 大幅度提升GC效率(在这之前, go的GC是一直被人诟病的);

那么go语言都有哪些特点呢?

  • 简洁、快速、安全;
  • 并行、有趣、开源;
  • 内存管理、类型安全、编译迅速;

go语言的方向

  • 网络编程领域;
  • 区块链开发领域;
  • 运维开发领域;
  • 高性能分布式领域;

2. go语言环境安装

首先是go语言环境的安装,我们直接下载go的编译器安装文件,然后进行安装即可。

可以进入:https://golang.google.cn/dl/ 这个网址下载对应系统的安装文件即可,我这里采用的是Windows系统。我这里采用的版本是1.13,由于安装比较简单,这里就不演示了,包括Linux系统安装go也是一样,随便找一篇博客就能找到安装教程。

这里我安装在了C盘下,编程语言的话我个人习惯安装在C盘。

Go 语言基础语法与基本数据类型,整型、浮点型、字符型、字符串类型

其中bin目录便是go编译器所在的目录,默认情况下安装之后会将C:\go\bin配置到环境变量中,我们在终端输入go version即可查看go编译器版本。

然后是go语言开发工具的安装,开发go语言程序怎么能没有一个好用的IDE呢。

go的IDE有很多,比如VSCode、Atom、LiteIDE等等,但是这里我着重推荐一个个人特别喜欢使用的IDE:Goland。这是Jetbrains公司推出的针对go语言的IDE,这家公司推出的IDE不用我多说吧,非常的好用。

至于安装方式,也可以网上搜索。当然你也可以使用其它的IDE,比如有人可能是重度VSCode用户,这一点看你自己。

go语言程序

1. 工作区

工作区是go语言中的一个对应特定工程的目录,其中包括src、pkg、bin三个目录。

  • src: 用于以代码包的形式组织并保存go源码文件 (比如.go .c .h .s等等);
  • pkg: 用于存放经由go install命令构建安装后的代码包(包含go库源码文件)的".a"归档文件;
  • bin: 与pkg目录类似, 在通过go install命令安装完成后, 保存由go命令源码文件生成的可执行文件

目录src用于包含所有的源代码,是go命令行工具一个强制的规则,而pkg和bin目录则无需手动创建,因为go命令行工具在构建过程中会自动创建这些目录。

这些东西不用刻意去记,我们后面用到的时候会说。

2. hello world

相信此时你已经安装好了go语言环境和相应的IDE,那么下面该做什么了呢?必然是打印一个"hello world"字符串啊,这是一种仪式感,学习任何一门语言都要经历的一步。

package main

import "fmt"

func main() {
    fmt.Println("hello world")
}

当我们在IDE中执行上面这个程序时,便会自动打印出"hello world"。

Go 语言基础语法与基本数据类型,整型、浮点型、字符型、字符串类型

3. 编译过程

我们在IDE中执行很方便,会自动将结果显示出来,那么如果不使用IDE该如何执行呢?

编译命令:go build 1.go,此时会在源文件所在目录生成一个具有相同名字的可执行文件,然后我们手动执行即可;如果想手动指定可执行文件的名字,那么可以通过go build -o 可执行文件 源文件的方式。

编译并运行:go run 1.go,编译之后会自动运行,注意:go run命令不会在当前目录留下可执行的文件。

举个例子:

Go 语言基础语法与基本数据类型,整型、浮点型、字符型、字符串类型

如果有多个源文件需要同时编译的话,那么将多个文件写在一起使用空格分开即可。

4. 常用命令行

1. go help

获取对应命令的帮助文档,可以获取到对应命令的作用以及对应参数,比如:go help build

2. go version

获取系统安装的go语言版本号

3. go build

编译项目,时期打包成为可执行程序,配合参数可以进行交叉编译。

标准格式:

go build [-o 可执行文件] [-i] [build flags] 源文件

  • 其中 -o 参数决定了编译后的文件名称,例如我们将1.go编译成2.exe就可以这么做:go build -o 2.exe 1.go,如果不指定-o参数,那么生成的可执行文件在Windows下默认叫1.exe。
  • -i 会安装作为目标的依赖关系的包(用于增量编译提速),一般很少使用。
  • 编译参数一般不会添加,以下列举几个,更详细的信息可以从go help build中获取。

Go 语言基础语法与基本数据类型,整型、浮点型、字符型、字符串类型

4. 交叉编译

首先go语言向下支持C语言,因此可以在go语言中直接编写C代码,我们未来会说。只是在编译时,必须有相应的C语言编译器。

另外我们可以在一个系统中编译出另一个系统中的可执行文件,比如在Windows系统中编译出Linux上的可执行文件,具体方式如下:

SET CGO_ENABLE=0
SET GOODS=linux
SET GOARCH=amd64
# 然后执行编译即可
  • CGO_ENABLE表示是否使用cgo编译, 0表示不使用、1表示使用, 使用cgo进行交叉编译时需要机器安装对应的cgo程序
  • GOODS表示系统表示, 如果生成Mac系统的可执行文件, 那么设置成darwin; Linux可执行文件设置成linux; Windows可执行文件则设置成windows
  • GOARCH表示可执行程序对应的操作系统架构, 包括386、amd64、arm等等
  • 最后直接按照正常的方式编译即可

5. go install

编译并安装项目,参数用法和build类似,这个命令用的比较少。

6. go doc

获取go函数帮助文档,比如:go doc strconv即可获取相关用法。

除此之外还可以使用网页形式查看帮助文档,go doc -http=localhost:6060,然后进入浏览器输入相关地址即可进行查看。但是这个功能被移除了,所以感觉说了些废话。

7. go env

查看当前系统内go相关的环境变量信息

8. go test

go语言自带的测试工具,会自动读取源码目录下以_test.go结尾的文件,生成并运行测试用的可执行文件。

原则:文件名必须是以_test.go结尾,这样在执行go test的时候才会执行到相应的代码;必须import testing这个包

5. go文件结构

说实话我们上面扔出来的东西有点多,但是不要紧先有一个印象即可,慢慢就熟悉了。

下面来看看go文件的一些基本组成。

// 首先是注释, go采用了C风格的注释, 通过两个//表示单行注释
/*
既然有单行注释, 那么就有多行注释
和C的多行注释是一样的
*/

/*
然后是package main, 这表示声明一个包, 表示该文件位于哪个包内; 如果是main包, 那么该文件是可以被编译并且执行的
但如果如果不是main包, 那么它只能被导入;此外, 如果不是main包的话, 那么包名一般和该文件所在的目录名保持一致

另外很重要的一点, go语言是将目录(包) 作为管理单元, 假设有一个包叫做abc, 目录abc下面有10个go文件,
那么这10个go文件的内部显然都要是package abc, 然后重点来了: 我们调用的时候直接通过包名去调用即可
假设目录abc下面有一个file.go文件, file.go文件内部定义了一个函数叫CreateFile, 那么我们直接通过abc.CreateFile调用即可

换言之, 里面的文件叫什么我们并不需要关心, 因为我们是通过包来调用的; 而且一个包内的文件可以直接使用其它文件中的函数或者变量;
所以这也表明了, 同一个包里面的这些文件里面的变量名不可以重复;
因此个人觉得这算是go的一个优点、也是一个缺点吧, 那么多文件变量名都不重复, 对于讨厌给变量起名字的人来说真是让人头疼的一件事

但是对于package main来说, 它是启动文件, 放在什么地方没有严格的要求, 我们可以在任何地方执行它;
关于package我们后面还会继续聊, 目前我们学习的时候写package main即可
*/
package main

// import是导入一个包
import (
    "fmt"
)

// func表示定义一个函数, 并且main包里面要有且仅有一个main函数
// 程序的执行会从这个函数开始, 并且这个函数无参数、无返回值
func main() {
    fmt.Println("hello world")
}

关键字

我相信学习go语言的,基本上都是有其它语言基础的,不管是转行学go、还是想多掌握一门语言。因此一些语言共有的东西,我们就不说那么详细了,用的时候自然会知道。

下面介绍一下go的关键字,go的关键字非常少,只有25个,我们来看看。

  • var, 用于变量声明
  • const, 用于常量声明
  • package, 用于包声明
  • import, 用于导入包
  • func, 用于定义函数
  • return, 定义函数返回值
  • interface, 声明接口
  • struct, 结构体
  • type, 定义类型
  • map, 定义映射
  • range, 用于遍历
  • go, 启动一个协程
  • select, 用于通道的选择
  • chan, 通道
  • if, 这个不用说
  • else, 这个也不用说
  • switch, 和case搭配, 用于分支选择
  • case, 和switch搭配, 用于分支选择
  • default, 如果switch case语句的所有分支都没有命中, 那么会执行default语句的内容; 另外default还可以用在select语句当中
  • fallthrough: C中的每一个case语句在执行完之后不会自动跳出, 需要手动写上break;在go中会自动跳出, 但如果不希望跳出, 可以加上fallthrough; 可能有人发现了这个和C完全是相反的, 但本质上是一样的, 但之所以这么做是因为可能go的作者认为case之后break的情况要更多一些吧, 事实上也确实如此
  • for, 无需多说
  • break, 用于跳出switch、select、for语句
  • continue, 无需多说
  • goto, 跳转, 和C中的goto类似
  • defer, 延迟调用

就是这25个关键字,贯穿了整个go语言。这些关键字无需刻意去记,当然里面绝大部分我相信所有人都认识,在学习的过程中就慢慢熟悉了。

基本数据类型

首先数据类型表示数据在内存中开辟的空间大小的别称。在go中数据类型可以分为:基本数据类型 和 派生数据类型,这里我们只介绍基本数据类型。至于派生数据类型,内容比较多,我们会一个一个说。

Go 语言基础语法与基本数据类型,整型、浮点型、字符型、字符串类型

1. go语言命名规则

  • 允许使用字母、数字、下划线;
  • 不允许使用关键字;
  • 不能以数字开头;
  • 区分大小写;
  • 最好不要和内置的函数、类型等等发生冲突,比如定义一个变量叫int显然是不合适的。
  • 最好见名知意;

而命名方式推荐驼峰命名法。

小驼峰命名法(lower camel case):第一个单词以小写字母开始,之后的单词首字母大写,比如:myName、ageIncrOne。

大驼峰命名法(upper camel case):每个单词的首字母大写,比如:MyName、AgeIncrOne。

这两种方式看似没啥区别,但是在go中是有很大不同的。因为我们说go语言中,变量首字母小写的话只能在包内使用,大写的话除了可以在包内使用、还可以在包外使用。

2. 基本数据类型

go语言基本数据类型分为5种:

  • 布尔类型
  • 整型类型
  • 浮点类型
  • 字符类型
  • 字符串类型

布尔型:

  • 布尔类型只允许取值true或false;
  • 布尔类型的值占一个字节;
  • 布尔类型适合逻辑运算,一般用于流程控制;
package main

import "fmt"

func main() {
    // go语言定义变量的格式如下: var 变量名 类型 = 值
    // 如果赋上了初始值, 那么值和类型必须要匹配
    var flag1 bool = true

    // 但是我们也可以省略类型, go编译器会自动根据赋的值推断出变量的类型
    // 所以此时赋给flag2的值也可以是其它类型的值, 但是flag1必须是布尔类型, 因为我们在声明变量的时候执行了它是布尔类型
    var flag2 = false

    // 如果没有赋初始值, 那么go编译器会自动给一个零值, 比如: 布尔类型的零值是false; 整型的零值是0; 字符串的零值是""等等
    // 这一点和C不一样, C的话只有全局变量才会给零值, 局部变量则是一个随机值; 但是go统统都会给零值
    // 另外如果没有赋初始值的情况下, 变量的类型就不可以省略了; 因为省略的话, go编译器就不知道要给变量赋哪个类型的零值了
    var flag3 bool
    fmt.Println(flag1, flag2, flag3)  // true false false
}

整型:

  • 整数类型分为两类,有符号和无符号两种类型。

有符号:int、int8、int16、int32、int64;

无符号:uint、uint8、uint16、uint32、uint64;

它们都表示整型,区别就在于能保存整型的数值范围不同;

有符号整型可以存储任意整数,无符号整型只能存储自然数;

至于int和uint,它们和系统有关。32位系统等价于int32和uint32、64位系统等价于int64和uint64。

  • 十进制的整数:使用0-9的数字并且不以0开头;
  • 二进制的整数:以 0b 或 0B 为前缀,数字由0或1组成;
  • 八进制的整数:以0 或 0o 或 0O 为前缀(0o 和0O 在1.13版本开始支持,之前的版本只能以0位前缀),数字由0-7组成;
  • 十六进制整数:以 0x 或 0X 为前缀,数字以0-9和a-f 或 0-9和A-F 组成。
package main

import "fmt"

func main() {
    // 变量声明还可以这么做, 每个变量单独写在一行
    var (
        a uint8 = 20
        b int16  // 可以不赋值, 但是要指定类型, 此时为0值
        c int32 = 888
        d = 255  // 可以不指定类型, 但是要赋值
    )
    // 另外go还可以通过海象运算符来进行赋值
    e := 999  // 等价于var e = 999

    // 但是对于整型而言, 我们还是最好规定变量是哪一种整型
    // 因为go对于类型检查是极其严苛的, int32 和 int64是不可以相互赋值的, 也就是不支持隐式转化, 必须要先经过显式转化
    // 甚至int 和 int64都不可以, 我们知道在64位机器上int等价于int64, 但是这两者也不可以相互赋值
    /*
    比如:
        var a int
        var b int64 = 123
    这个时候如果将b赋值给a, 是会报错的, 因为它们是不同的类型
    如果想赋值必须显式转化, b = int64(a)
    */

    // 另外还有一个关键的地方, go中的变量一旦声明、必须使用, 否则报错
    // 在C中编译器只是会给一个警告, 告诉你哪些变量你定义了但是却没有使用, 只是提示你一下, 在go中则是直接报错
    // 所以go语言是针对其它语言的痛点进行设计的, 为了避免在运行时犯错误, 而在编译层面做的更严格一些
    fmt.Println(a, b, c, d, e)  // 20 0 888 255 999
}

然后也可以输出其它进行数:

package main

import "fmt"

func main() {
    s := 755
    fmt.Printf("%b %o %x", s, s, s)  // 1011110011 1363 2f3
}

浮点型:

  • 浮点数由整数部分、小数点和小数部分组成,整数部分和小数部分可以隐藏其中一种,比如:32. 等价于 32.0,.32 等价于 0.32
  • 尾数部分可能丢失,造成精度损失
  • float64比float32类型的精度要准确
package main

import (
    "fmt"
)

func main() {
    // 一行赋多个值也是可行的, 但此时要求变量都是同一个类型
    // 等价于 a, b := 3.14, .2
    // 也可以写成 var a, b float32 = 3.14, .2
    var a, b = 3.14, .2
    /*
    注意: 以下写法都是非法的
        var a float32, b float32 = 3.14, .2
        var a float32, b float64 = 3.14, .2
        var a float32, b = 3.14, .2
        var a1 float32 = 3.14, b float32 = .2
        var a1 = 3.14, b = .2

    如果想每个变量指定单独的类型, 那么需要像之前说的那样, 以下是合法的
        var (
            a float32 = 3.14
            b float64 = .2
        )
    */
    fmt.Println(a, b)  // 3.14 0.2

    // 另外如果是纯数字, 那么不带小数点则会被认为是int、带小数点则会被认为是float64
    fmt.Println(8 / 3)  // 2
    // 上面得到的是整数, 因为go中的整数相除得到的还是整型, 那么如何才能得到浮点数呢?
    // 显然将其中一方转为浮点数即可
    fmt.Println(8 / 3.) // 2.6666666666666665

    // 值得一提的是如果是两个纯数字的话, 在进行运算的时候类型会自动像大的方向扩展, 所以 8 / 3.0 是会得到浮点数的
    // 此外 3. == 3 结果为true, 说明两个常量的值相同的
    // 但如果将值赋给了一个变量, 比如:
    /*
    假设:
        var m = 8
        var n = 3.
    这个时候, m会被推断成int, n会被推断成float64, 虽然 8 和 3.是可以相除的, 但是m和n相除的话则会报错
    因为两个变量不是同一个类型, 不同类型的变量不可以相互赋值, 也不可以相互运算, 也不能相互比较

    另外这样的话:
        var n = 3.
        8 / n是合法的, 因为8可以被当成是浮点数, 只不过小数点是0罢了, 因为 8 和 8.0 是等价的

    同理:
        var m = 8
        m / 3. 也是合法的,
    因为 3. 和 3 两个值是相等的, 并且其中一方是已经确定为int类型的变量, 所以结果也一定是int, 因此会将3.转成int类型、也就是3

    但是:
        var m = 8
        m / 3.14 就不合法了, 因为m是一个int, 那么它要求除数也必须是一个int, 3.是可以转成3的, 但是3.14不行, 所以此时会报错
        那么为啥 8 / 3.14 就可以呢? 因为这两者是纯数字, 运算时会向大的类型方向扩展; 但var m = 8, 这个m就已经确定是int了
        所以它要求除数3.14也能转成int, 但是我们知道3. 和 3的值是一样的、可以转化, 但是3.14不行
    */
}

字符型:

  • 不像C中的char类型,go中并没有专门的字符类型,如果要表示单个字符,可以使用整型来表示;
  • 字符只能被单引号包裹,不能用双引号,双引号包裹的是字符串;
  • 字符本质上是一个整数,可以直接运算;
package main

import (
    "fmt"
)

func main() {
    // 字符在底层是一个整数, 对应一个ASCII码
    // 对于英文字符, 我们使用uint8是最合适的, 并且uint8还有一个名字叫byte, 这俩是同一个类型
    var a int16 = \'a\'
    var b uint = \'a\'
    var c byte = \'a\'
    // 直接打印的话, 显示的是ascii码
    fmt.Println(a, b, c)  // 97 97 97
    // 打印字符的话, 可以使用%c
    // 注意: 使用的函数要换成fmt.Printf, 和C中的printf是类似的
    fmt.Printf("%c %c %c\n", a, b, c)  // a a a

    // 此外中文字符也是可以的
    var d = \'憨\'
    fmt.Println(d) // 25000
    fmt.Printf("%c\n", d) // 憨

    // go编译器比较智能, 在不指定类型的情况下: 如果是ascii字符, 那么自动使用uint8存储; 非ascii字符则自动使用int32存储, 因为非ascii字符byte类型表示不下
    // 所以uint8有另外一个名字叫byte, 同理int32也有另外一个名字叫rune
    var e rune = \'憨\'
    fmt.Println(e)  // 25000

    // 只不过编译会自动推断, 因此我们无需指定类型
    // 另外我们说字符本质上就是一串数字, 所以这两者是可以直接相加的
    fmt.Println(\'憨\' + \'a\', \'憨\' + 97, 25000 + \'a\', 25000 + 97)  // 25097 25097 25097 25097
    fmt.Println(\'憨\' - \'a\', 25000 - 97)  // 24903 24903
}

字符串类型:

  • 字符串就是一串固定长度的字符连接起来的字符序列,go语言中的字符串是由单个字节连接起来的;
  • go中的字符串使用的utf-8编码;
  • 字符串一旦赋值了,就不可以再修改了;
  • 字符串使用双引号或者反引号进行包裹,具体会举例说明;
package main

import "fmt"

func main() {
    var s = "hello, 古明地觉"
    fmt.Println(s)  // hello, 古明地觉
    // 使用utf-8编码, 一个汉字占3个字节, 而字符串的长度是按照底层字符数组的长度(即底层字符数组的元素个数)来计算的
    // 在go中可以通过len函数求出一个字符串的长度, 也就是底层字符数组的长度
    fmt.Println(len(s))  // 19
    // 因为一个汉字是占据3个字节

    var s1 = "古"
    //将字符串转成字节数组可以使用[]byte(string)
    fmt.Println([]byte(s1))  // [229 143 164]
    // 可以看到长度为3, 因此: 字符串的长度等于对应的底层字符数组的长度
    // 而go采用utf-8编码存储, 所以底层字符数组类型是byte, 因为一个英文字符占一个byte
    // 而超过一个byte的最大范围(255) 的话, 则使用3个byte来表示

    // 将字符数组转成字符串, 数组我们会在后面说
    fmt.Println(string([]byte{229, 143, 164}))  // 古

    // 问题来了, 如果我想通过索引获取某个字符串的指定字符, 要是包含中文的话, 显然无法准确定位
    // 因为有的字符在底层数组中用1个元素表示, 有的用3个元素表示, 显然此时无法通过索引准确定位
    // 而解决这个问题的办法就是让底层字符数组的一个元素表示一个字符, 怎么办呢? 很简单, 转成rune即可
    var s_rune = []rune(s)
    fmt.Println(len(s_rune))  // 11
    fmt.Println(s_rune)  // [104 101 108 108 111 44 32 21476 26126 22320 35273]
    /*
    我们看到此时底层数组变成了rune、也就是int32, 一个rune是4字节, 可以表示所有的字符
    因此无论中文字符、还是英文字符, 底层数组只用一个元素就可以表示了; 所以长度和我们想象的是一样的, 不是19而是11

    但是这样也带来了一个问题, 估计都能猜到, 那就是内存占用变高了
    比如: 字符串 "h憨", 虽然原来的底层数组使用4个元素, 但一个元素一个字节, 所以总共4字节
         但如果转成了rune, 那么不好意思, 虽然只有两个元素, 但是一个元素4字节, 因此总共8字节
    所以这种代价就是内存占用变高了, 但如果是支持非ascii字符串的话, 我们是有必要转成rune的
    */

    // 将rune数组转成字符串也是一样的方式, rune和int32同一种类型, 所以写成int32也是可以的
    // 但是注意: 不可以写uint8或byte, 因为它的最大范围是255
    fmt.Println(string([]rune{104, 101, 108, 108, 111, 44, 32, 21476, 26126, 22320, 35273}))  // hello, 古明地觉

    // 另外: string只能将 rune数组 和 byte数组 转成字符串, 其它像int16、int64、uint32等等是不行的
    // 同理 []byte(ascii)、[]rune(unicode)、[]uint8(ascii)、[]int32(unicode)是合法的, 但是像[]int16()、[]int64()之类的则不行
}

另外字符串如果太长了,可以使用加号进行连接;

package main

import "fmt"

func main() {
    // 加号一定要写在上面
    s := "hello" +
        " cruel" +
        " world"
    fmt.Println(s)  // hello cruel world
}

另外,还可以使用反引号表示原生字符串;

package main

import "fmt"

func main() {
    // 会自动识别转义字符
    s := "hello\tworld\""
    fmt.Println(s)  // hello    world"

    //里面的内容会原生输出
    s1 := `
    言いたいことがあるんだよ
    やっぱり祭はかわいいよ \t """"
    好き好き やっぱすき
`
    fmt.Println(s1)
    /*

    言いたいことがあるんだよ
    やっぱり祭はかわいいよ \t """"
    好き好き やっぱすき

    */
}

3. 数据类型转换

数据有不同的类型,不同类型的数据之间进行混合运算时必然要涉及到类型的转换问题;而两种不同的类型变量在进行运算时,不支持隐式转换,go语言强制要求你必须进行显式转换;自己做了哪些事情,go编译器会强制性让你知道。

而类型转换用于将一种类型的变量转成另一种类型的变量,当然前提是可以转化。具体方式如下:

  • 数据类型(变量)
  • 数据类型(表达式)
package main

import "fmt"

func main() {
    var a int
    var b int64 = 100
    // 这个时候将 b 直接给 a 是会报错的, 我们需要将b转化一下才可以
    a = int(b)
    fmt.Println(a)
}

另外,在go中有一点很不方便,我们举例说明:

package main

import "fmt"

func main() {
    /*
        var f float64 = 3.14
        那么int(f)会得到3吗? 在其它语言里面会的, 但是go里面会报错, 只有3.0可以转成3, 其余则不行
    */
    var a = 97
    // 显然此时得到的不是字符串97, 而是将对应的ascii码变成字符串
    fmt.Println(string(a))  // a
    fmt.Println(a == \'a\', string(a) == "a")  // true true
}

而解决这些问题的方法就是通过使用标准库,那么下面我们就来介绍一下。

与数值操作相关的math库

这里面定义了大量和数值相关的常量和函数,我们一起来看一下。

首先是常量,定义常量的语法和定义变量一模一样,只是把var换成了const。

const (
    MaxInt8   = 1<<7 - 1
    MinInt8   = -1 << 7
    MaxInt16  = 1<<15 - 1
    MinInt16  = -1 << 15
    MaxInt32  = 1<<31 - 1
    MinInt32  = -1 << 31
    MaxInt64  = 1<<63 - 1
    MinInt64  = -1 << 63
    MaxUint8  = 1<<8 - 1
    MaxUint16 = 1<<16 - 1
    MaxUint32 = 1<<32 - 1
    MaxUint64 = 1<<64 - 1
)

const (
    MaxFloat32             = 3.40282346638528859811704183484516925440e+38  // 2**127 * (2**24 - 1) / 2**23
    SmallestNonzeroFloat32 = 1.401298464324817070923729583289916131280e-45 // 1 / 2**(127 - 1 + 23)

    MaxFloat64             = 1.797693134862315708145274237317043567981e+308 // 2**1023 * (2**53 - 1) / 2**52
    SmallestNonzeroFloat64 = 4.940656458412465441765687928682213723651e-324 // 1 / 2**(1023 - 1 + 52)
)

const (
    E   = 2.71828182845904523536028747135266249775724709369995957496696763 // https://oeis.org/A001113
    Pi  = 3.14159265358979323846264338327950288419716939937510582097494459 // https://oeis.org/A000796
    Phi = 1.61803398874989484820458683436563811772030917980576286213544862 // https://oeis.org/A001622

    Sqrt2   = 1.41421356237309504880168872420969807856967187537694807317667974 // https://oeis.org/A002193
    SqrtE   = 1.64872127070012814684865078781416357165377610071014801157507931 // https://oeis.org/A019774
    SqrtPi  = 1.77245385090551602729816748334114518279754945612238712821380779 // https://oeis.org/A002161
    SqrtPhi = 1.27201964951406896425242246173749149171560804184009624861664038 // https://oeis.org/A139339

    Ln2    = 0.693147180559945309417232121458176568075500134360255254120680009 // https://oeis.org/A002162
    Log2E  = 1 / Ln2
    Ln10   = 2.30258509299404568401799145468436420760110148862877297603332790 // https://oeis.org/A002392
    Log10E = 1 / Ln10
)

常量的话,可以自己看一下,看看它们都有哪些。然后重点来看看函数,我们介绍几个常用的:

package main

import (
    "fmt"
    "math"
)

func main() {
    // 1. 求绝对值, 接收一个float64, 返回一个float64
    fmt.Println(math.Abs(-34))  // 34

    // 2. 求幂, 接收两个float64, 返回一个float64
    fmt.Println(math.Pow(3, 4))  // 81

    // 3. 求两个数的最小值, 接收两个float64, 返回一个float64
    fmt.Println(math.Min(3, 4))  // 3

    // 4. 求两个数的最小值, 接收两个float64, 返回一个float64
    fmt.Println(math.Max(3, 4))  // 4

    // 5. 求一个数的根, 接收一个float64, 返回一个float64
    fmt.Println(math.Sqrt(4))  // 2

    // 6. 向下取整, 接收一个float64, 返回一个float64
    fmt.Println(math.Floor(3.14))  // 3

    // 7. 向上取整, 接收一个float64, 返回一个float64
    fmt.Println(math.Ceil(3.14))  // 4
}

至于其它的数学函数:对数、三角函数之类的,可以自己去查看,都在里面。

与类型转化相关的strconv库

我们之前执行 string(97) 的时候,结果发现打印出来的是个a,因为golang的字符串底层实质上就是一个个byte组成的字节数组,如果使用string(num)这种方式,那么会将num这个数值对应字符转成字符串打印出来,而ASCII码中 97 和 a 是对应的的,所以打印了a

Itoa

如果想把int转成string应该是用Itoa, i就是integer,这个a是什么?其实这个a就是字符串,只不过这是从C语言中遗留下来的;因为C中没有字符串的概念,而是使用字符数组,所以这个a在C中是array。但是在go中就把a当中字符串即可:

package main

import (
    "fmt"
    "strconv"
)

func main() {
    num := 97
    fmt.Println(strconv.Itoa(num))  // 97
    fmt.Println(strconv.Itoa(num) == "97")  // true
}

Atoi

既然能把整型转成字符串,那么能不能把字符串转换为整型呢?显然是可以的,反过来即可,也就是Atoi

package main

import (
    "fmt"
    "strconv"
)

func main() {
    // 整型转字符串可以直接转,但是字符串转整型,则是有可能发生错误的,因为必须要求字符串的每一个字符必须是数字才可以转
    // 所以这里除了返回结果,还会返回一个error, 关于这里的语法后面会说
    // 总之go中异常捕获不是很方便, 所以很多时候, 如果可能发生错误, 那么都会返回一个error, 比如这里
    // 如果没有出错, 那么num就是转化后的值, err为nil; 如果出错了, 那么err就是异常信息, num就是nil
    str := "97"
    if num, err := strconv.Atoi(str); err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(num)  //97
    }


    str = "97xx"
    if num , err := strconv.Atoi(str); err != nil {
        fmt.Println(err)  //strconv.Atoi: parsing "97xx": invalid syntax
    } else {
        fmt.Println(num)
    }
}

Parse系列函数

Parse一类函数用于转换字符串为给定类型的值:ParseBoolParseFloatParseIntParseUint

1. ParseBool

将指定字符串转换为对应的bool类型,只接受1、0、t、f、T、F、true、false、True、False、TRUE、FALSE,否则返回错误;

package main

import (
    "fmt"
    "strconv"
)

func main() {
    //因为是字符串转回去,必然可能发生转化失败的情况,因此都会多返回一个error
    //而这里解析成功了,所以error是nil
    fmt.Println(strconv.ParseBool("1"))  // true <nil>
    fmt.Println(strconv.ParseBool("F")) // false <nil>
}

2. ParseInt

函数原型:func ParseInt(s string, base int, bitSize int) (i int64, err error)

  • s:转成int的字符串
  • base:指定进制(2到36),如果base为0,那么会从字符串的前置来判断,如0x表示16进制等等,如果前缀也没有那么默认是10进制
  • bistSize:整数类型,0、8、16、32、64 分别代表 int、int8、int16、int32、int64

返回的err是*NumErr类型的,如果语法有误,err.Error = ErrSyntax;如果结果超出类型范围err.Error = ErrRange

package main

import (
    "fmt"
    "strconv"
)

func main() {
    fmt.Println(strconv.ParseInt("0x16", 0, 0))  // 22 <nil>
    fmt.Println(strconv.ParseInt("16", 16, 0))  // 22 <nil>
    fmt.Println(strconv.ParseInt("16", 0, 0))  // 16 <nil>

    fmt.Println(strconv.ParseInt("016", 0, 0))  // 14 <nil>

    //进制为2,但是字符串出现了6,无法解析
    fmt.Println(strconv.ParseInt("16", 2, 0))  // 0 strconv.ParseInt: parsing "16": invalid syntax

    //只指定8位,显然存不下。因为给了一个能存储的最大的值
    fmt.Println(strconv.ParseInt("257", 0, 8))  // 127 strconv.ParseInt: parsing "257": value out of range

    //还可以指定正负号
    fmt.Println(strconv.ParseInt("-0x16", 0, 0))  // -22 <nil>
    fmt.Println(strconv.ParseInt("-016", 0, 0))  // -14 <nil>

}

3. ParseUint

ParseUint类似ParseInt,但不接受正负号,用于无符号整型。

4. ParseFloat

函数原型:func ParseFloat(s string, bitSize int) (f float64, err error),其中 bitSize为:32、64,表示对应精度的float

package main

import (
    "fmt"
    "strconv"
)

func main() {
    fmt.Println(strconv.ParseFloat("3.14", 64))  //3.14 <nil>
}

Format系列函数

Format系列函数就比较简单了,就是将指定数据类型格式化成字符串,Parse则是将字符串解析成指定数据类型,这两个是相反的。转成字符串的话,则不需要担心error了。

1. FormatBool

package main

import (
    "fmt"
    "strconv"
)

func main() {
    // 此时返回了一个值
    // 如果是Parse系列的话会返回两个值, 因为可能会出错, 所以多一个error, 因此需要两个变量来接收
    // 如果err不为nil, 证明出错了
    // 而Format系列则无需担心, 因为转成字符串是不会出错的, 所以只返回一个值, 接收的时候只需要一个变量即可
    fmt.Println(strconv.FormatBool(true))  //true
    fmt.Println(strconv.FormatBool(false) == "false")  //true
}

2. FormatInt

传入字符串和指定的进制。

package main

import (
    "fmt"
    "strconv"
)

func main() {
    //数值是22,但是它是16进制的,所以对应成10进制是16
    fmt.Println(strconv.FormatInt(22, 16))  //16
}

3. FormatUint

是FormatInt的无符号版本。

4. FormatFloat

函数原型:func FormatFloat(f float64, fmt byte, prec, bitSize int) string,作用是将浮点数表示为字符串并返回。

  • f:float64
  • fmt:fmt表示格式:\'f\'(-ddd.dddd)、\'b\'(-ddddp±ddd,指数为二进制)、\'e\'(-d.dddde±dd,十进制指数)、\'E\'(-d.ddddE±dd,十进制指数)、\'g\'(指数很大时用\'e\'格式,否则\'f\'格式)、\'G\'(指数很大时用\'E\'格式,否则\'f\'格式)。
  • prec:prec控制精度(排除指数部分),对\'f\'、\'e\'、\'E\',它表示小数点后的数字个数;对\'g\'、\'G\',它控制总的数字个数。如果prec 为-1,则代表使用最少数量的、但又必需的数字来表示f。
  • bitSize:f是哪一种float,32或者64
package main

import (
    "fmt"
    "strconv"
)

func main() {
    fmt.Println(strconv.FormatFloat(3.1415, \'f\', -1, 64))  //3.1415
    fmt.Println(strconv.FormatFloat(3.1415, \'e\', -1, 64))  //3.1415e+00
    fmt.Println(strconv.FormatFloat(3.1415, \'E\', -1, 64))  //3.1415E+00
    fmt.Println(strconv.FormatFloat(3.1415, \'g\', -1, 64))  //3.1415
}

小结

这次我们介绍了go语言的基本数据类型,下一次我们介绍go语言的派生数据类型。