GO语言基础(上)

一、绪论

1 Go语言介绍

Go 即Golang,是Google公司2009年11月正式对外公开的一门编程语言。

Go是静态强类型语言,是区别于解析型语言的编译型语言(静态:类型固定 强类型:不同类型不允许直接运算)。

解析型语言——源代码是先翻译为中间代码,然后由解析器对代码进行解释执行。

编译型语言——源代码编译生成机器语言,然后由机器直接执行机器码即可执行。

2 Go语言特性

  • 跨平台的编译型语言

  • 交叉编译(在win平台可编译出mac平台的可执行文件)

  • 语法接近C语言

  • 管道(channel),切片(slice),并发(routine)

  • 有垃圾回收的机制

  • 支持面向对象和面向过程的编程模式

3 go适合做什么

  • 服务端开发
  • 分布式系统,微服务
  • 网络编程
  • 区块链开发
  • 内存KV数据库,例如boltDB、levelDB
  • 云平台

4 下载和安装

开发环境:

IDE:

  • GoLand

  • vscode

5 配置GOPATH和GOROOT

  • GOPATH:代码存放路径,该目录下有三个文件夹(如果没有,要手动创建), windows和mac默认在用户名下的go文件夹(GOPATH=“/Users/用户名/go”),所有代码必须写在这个路径下的src文件夹下,否则无法编译,可以手动修改。
-src——源码(包含第三方的和自己项目的)
-bin——编译生成的可执行程序
-pkg——编译时生成的对象文件
  • GOROOT:go开发工具包的安装路径,默认:C:\go

  • 将GOROOT下的bin路径加入环境变量(默认已处理),这样任意位置敲 go 都能找到该命令

6 命令介绍

直接在终端中输入 go help 即可显示所有的 go 命令以及相应命令功能简介,主要有下面这些:

  • build: 编译包和依赖
  • clean: 移除对象文件(go clean :删除编译的可执行文件)
  • doc: 显示包或者符号的文档
  • env: 打印go的环境信息
  • bug: 启动错误报告
  • fix: 运行go tool fix
  • fmt: 运行gofmt进行格式化(go fmt :自动将代码格式)
  • generate: 从processing source生成go文件
  • get: 下载并安装包和依赖(go get github.com/astaxie/beego:下载beego框架)
  • install: 编译并安装包和依赖(go install 项目名:会将go编译,并放到bin路径下)
  • list: 列出包
  • run: 编译并运行go程序
  • test: 运行测试
  • tool: 运行go提供的工具
  • version: 显示go的版本
  • vet: 运行go tool vet

build 和 run 命令

就像其他静态类型语言一样,要执行 go 程序,需要先编译,然后在执行产生的可执行文件。go build 命令就是用来编译 go程序生成可执行文件的。但并不是所有的 go 程序都可以编译生成可执行文件的, 要生成可执行文件,go程序需要满足两个条件:

  • 该go程序需要属于main包
  • 在main包中必须还得包含main函数

也就是说go程序的入口就是 main.main, 即main包下的main函数, 例子(test.go):

编译hello.go,然后运行可执行程序:

$ go build test.go   # 将会生成可执行文件 test
$ ./test # 运行可执行文件
Hello, World!

上面就是 go build 的基本用法,另外如果使用 go build 编译的不是一个可执行程序,而是一个包,那么将不会生成可执行文件。

go run 命令可以将上面两步并为一步执行(不会产生中间文件)。

$ go run test.go
Hello, World!

上面两个命令都是在开发中非常常用的。

此外 go clean 命令,可以用于将清除产生的可执行程序:

$ go clean    # 不加参数,可以删除当前目录下的所有可执行文件
$ go clean hello.go # 会删除对应的可执行文件

6.2 get 命令

这个命令同样也是很常用的,我们可以使用它来下载并安装第三方包, 使用方式:

go get src

从指定源上面下载或者更新指定的代码和依赖,并对他们进行编译和安装,例如我们想使用 beego 来开发web应用,我们首先就需要获取 beego:

go get github.com/astaxie/beego

这条命令将会自动下载安装 beego 以及它的依赖,然后我们就可以使用下面的方式使用:

package main

import "github.com/astaxie/beego" # 这里需要使用 src 下的完整路径

func main() {
beego.Run()
}

7 第一个go程序

package main // 声明包名是main,每一个go文件都必须属于某一个包

import "fmt" // 导入内置包

func main() { // func函数关键字,定义main函数 类似c语言,编译型语言需要有入口函数: main包下的main函数
fmt.Println("hello,world!") // 打印函数
}

bulid命令编译,再执行exe(或者使用run命令,编译并执行:)

go build 1.go # 或者 go run 1.go

8 命名规范

Go语言中的函数名、变量名、常量名、类型名、语句标号和包名等所有的命名,都遵循一个简单的命名规则:

1 一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线

2 大写字母和小写字母是不同的:Name和name是两个不同的变量

3 关键字和保留字都不建议用作变量名

Go语言中关键字有25个;关键字不能用于自定义名字,只能在特定语法结构中使用。

break      default       func     interface   select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

go语言中有37个保留字,主要对应内建的常量、类型和函数

内建常量: true false iota nil

内建类型: int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error

内建函数: make len cap new append copy close delete
complex real imag
panic recover

注意:

  1. 这些保留字并不是关键字,可以在定义中重新使用它们。在一些特殊的场景中重新定义它们也是有意义的,但是也要注意避免过度而引起语义混乱
  2. 如果一个名字是在函数内部定义,那么它就只在函数内部有效。如果是在函数外部定义,那么将在当前包的所有文件中都可以访问。名字的开头字母的大小写决定了名字在包外的可见性。如果一个名字是大写字母开头的(必须是在函数外部定义的包级名字;包级函数名本身也是包级名字),那么它将是导出的,也就是说可以被外部的包访问,例如fmt包的Printf函数就是导出的,可以在fmt包外部访问。包本身的名字一般总是用小写字母
  3. 名字的长度没有逻辑限制,但是Go语言的风格是尽量使用短小的名字,对于局部变量尤其是这样;你会经常看到i之类的短名字,而不是冗长的theLoopIndex命名。通常来说,如果一个名字的作用域比较大,生命周期也比较长,那么用长的名字将会更有意义
  4. 在习惯上,Go语言程序员推荐使用 驼峰式 命名,当名字由几个单词组成时优先使用大小写分隔,而不是优先用下划线分隔。因此,在标准库有QuoteRuneToASCIIparseRequestLine这样的函数命名,但是一般不会用quote_rune_to_ASCIIparse_request_line这样的命名。而像ASCII和HTML这样的缩略词则避免使用大小写混合的写法,它们可能被称为htmlEscapeHTMLEscapeescapeHTML,但不会是escapeHtml
  5. go文件的名字,建议用下划线的方式命名(参见go源码)

二、变量和常量

1 定义单个变量

func main(){
// 定义变量的第一种方法:全定义 var name type/var关键字 变量名 变量类型
// var name type = initialvalue 该语法可以声明并初始化
var age int = 10 // 变量如果声明就必须在后面使用,否则会报错
fmt.Println(age)

// 定义变量的第二种方法:类型推导 var name = initialvalue var关键字 变量名 = 值
// Go 能够自动推断具有初始值的变量的类型
var a = 50
fmt.Println(a)
fmt.Printf("%T", a) // %T 打印变量a的类型

// 定义变量的第三种方法:简略声明 var和类型都不写
// 简短声明要求 := 操作符左边的所有变量都有初始值
b:=20
fmt.Println(b)
}

2 定义多个变量

Go 能够通过一条语句声明多个变量。

// 定义多个变量var name1, name2 type = initialvalue1, initialvalue
var s1,s2 int = 10,20
fmt.Println(s1,s2)

在有些情况下,我们可能会想要在一个语句中声明不同类型的变量。其语法如下:

var (  
name1 = initialvalue1,
name2 = initialvalue2
)

使用上述语法,下面的程序声明不同类型的变量。

package main

import "fmt"

func main() {
var (
name = "naveen"
age = 29
height int
)
fmt.Println("my name is", name, ", age is", age, "and height is", height)
}

3 注意

简短声明的语法要求 := 操作符的左边至少有一个变量是尚未声明的。考虑下面的程序:

package main

import "fmt"

func main() {
a, b := 20, 30 // 声明变量a和b
fmt.Println("a is", a, "b is", b)
b, c := 40, 50 // b已经声明,但c尚未声明
fmt.Println("b is", b, "c is", c)
b, c = 80, 90 // 给已经声明的变量b和c赋新值
fmt.Println("changed b is", b, "c is", c)
}

在上面程序中的第 8 行,由于 b 已经被声明,而 c 尚未声明,因此运行成功

但是如果我们运行下面的程序

package main

import "fmt"

func main() {
a, b := 20, 30 // 声明a和b
fmt.Println("a is", a, "b is", b)
a, b := 40, 50 // 错误,没有尚未声明的变量
}

变量也可以在运行时进行赋值。考虑下面的程序:

package main

import (
"fmt"
"math"
)

func main() {
a, b := 145.8, 543.8
c := math.Min(a, b)
fmt.Println("minimum value is ", c)
}

在上面的程序中,c 的值是运行过程中计算得到的,即 a 和 b 的最小值

4 常量

常量是一个简单值的标识符,在程序运行时,不会被修改的量。

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

常量的定义格式:

const identifier [type] = value

你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

  • 显式类型定义: const b string = "abc"
  • 隐式类型定义: const b = "abc"

多个相同类型的声明可以简写为:

const c_name1, c_name2 = value1, value2

顾名思义,常量不能再重新赋值为其他的值。因此下面的程序将不能正常工作,它将出现一个编译错误: cannot assign to a.

package main

func main() {
const a = 55 // 允许
a = 89 // 不允许重新赋值
}

5 iota

iota,特殊常量,可以认为是一个可以被编译器修改的常量。

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

iota 可以被用作枚举值:

const (
a = iota
b = iota
c = iota
)

第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:

const (
a = iota
b
c
)

实例:

package main

import "fmt"

func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}

以上实例运行结果为:

0 1 2 ha ha 100 100 7 8

三、运算符

1 算数运算符

下表列出了所有Go语言的算术运算符。假定 A 值为 10,B 值为 20。

2 关系运算符

下表列出了所有Go语言的关系运算符。假定 A 值为 10,B 值为 20。

image-20240703194751887

3 逻辑运算符

下表列出了所有Go语言的逻辑运算符。假定 A 值为 True,B 值为 False。

image-20240703194807425

4 位运算符

位运算符对整数在内存中的二进制位进行操作。

下表列出了位运算符 &, |, 和 ^ 的计算:

image-20240703194824780

Go 语言支持的位运算符如下表所示。假定 A 为60,B 为13:

image-20240703194839398

5 赋值运算符

下表列出了所有Go语言的赋值运算符。

image-20240703194859220

6 其它运算符

下表列出了Go语言的其他运算符。

image-20240703194916098

7 运算符优先级

有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:

image-20240703194934377

当然,你可以通过使用括号来临时提升某个表达式的整体运算优先级。

四、数据类型

下面是 Go 支持的基本类型:

  • bool 布尔型

  • 数字类型

    • int8, int16, int32, int64, int 整型

    • uint8, uint16, uint32, uint64, uint 无符号整型

    • float32, float64 浮点型

    • complex64, complex128 复数

    • byte 类似 uint8

    • rune 类似 int32

  • string 字符串类型

1 bool

bool 类型表示一个布尔值,值为 true 或者 false

2 有符号整型

int8:表示 8 位有符号整型 大小:8 位 范围:-128~127

int16:表示 16 位有符号整型 大小:16 位 范围:-32768~32767

int32:表示 32 位有符号整型 大小:32 位 范围:-2147483648~2147483647

int64:表示 64 位有符号整型 大小:64 位 范围:-9223372036854775808~9223372036854775807

int:根据不同的底层平台(Underlying Platform),表示 32 或 64 位整型。除非对整型的大小有特定的需求,否则你通常应该使用 int 表示整型。 大小:在 32 位系统下是 32 位,而在 64 位系统下是 64 位。 范围:在 32 位系统下是 -2147483648~2147483647,而在 64 位系统是 -9223372036854775808~9223372036854775807。

3 无符号整型

uint8:表示 8 位无符号整型 大小:8 位 范围:0~255

uint16:表示 16 位无符号整型 大小:16 位 范围:0~65535

uint32:表示 32 位无符号整型 大小:32 位 范围:0~4294967295

uint64:表示 64 位无符号整型 大小:64 位 范围:0~18446744073709551615

uint:根据不同的底层平台,表示 32 或 64 位无符号整型。 大小:在 32 位系统下是 32 位,而在 64 位系统下是 64 位。 范围:在 32 位系统下是 0~4294967295,而在 64 位系统是 0~18446744073709551615。

4 浮点型

float32:32 位浮点数 float64:64 位浮点数

5 复数类型

complex64:实部和虚部都是 float32 类型的的复数。 complex128:实部和虚部都是 float64 类型的的复数。

内建函数 complex用于创建一个包含实部和虚部的复数。complex 函数的定义如下:

func complex(r, i FloatType) ComplexType

该函数的参数分别是实部和虚部,并返回一个复数类型。实部和虚部应该是相同类型,也就是 float32 或 float64。如果实部和虚部都是 float32 类型,则函数会返回一个 complex64 类型的复数。如果实部和虚部都是 float64 类型,则函数会返回一个 complex128 类型的复数。

还可以使用简短语法来创建复数:

c := 6 + 7i

6 string 类型

在 Golang 中,字符串是字节的集合。如果你现在还不理解这个定义,也没有关系。我们可以暂且认为一个字符串就是由很多字符组成的。我们后面会在一个教程中深入学习字符串。 下面编写一个使用字符串的程序。

package main

import (
"fmt"
)

func main() {
first := "Naveen"
last := "Ramanathan"
name := first +" "+ last
fmt.Println("My name is",name)
}

上面程序中,first 赋值为字符串 “Naveen”,last 赋值为字符串 “Ramanathan”。+ 操作符可以用于拼接字符串。我们拼接了 first、空格

和 last,并将其赋值给 name。上述程序将打印输出 My name is Naveen Ramanathan

7 类型转换

Go 有着非常严格的强类型特征。Go 没有自动类型提升或类型转换。

package main

import (
"fmt"
)

func main() {
i := 55 //int
j := 67.8 //float64
sum := i + j //不允许 int + float64
fmt.Println(sum)
}

上面的代码在 C 语言中是完全合法的,然而在 Go 中,却是行不通的。i 的类型是 int ,而 j 的类型是 float64 ,我们正试图把两个不同类型的数相加,Go 不允许这样的操作。如果运行程序,你会得到 main.go:10: invalid operation: i + j (mismatched types int and float64)

要修复这个错误,i 和 j 应该是相同的类型。在这里,我们把 j 转换为 int 类型。把 v 转换为 T 类型的语法是 T(v)。

package main

import (
"fmt"
)

func main() {
i := 55 //int
j := 67.8 //float64
sum := i + int(j) //j is converted to int 舍弃小数点后的数字
fmt.Println(sum)
}

现在,当你运行上面的程序时,会看见输出 122

赋值的情况也是如此。把一个变量赋值给另一个不同类型的变量,需要显式的类型转换。下面程序说明了这一点。

package main

import (
"fmt"
)

func main() {
i := 10
var j float64 = float64(i) // 若没有显式转换,该语句会报错
fmt.Println("j", j)
}

在第 9 行,i 转换为 float64 类型,接下来赋值给 j。如果不进行类型转换,当你试图把 i 赋值给 j 时,编译器会抛出错误。

8 数据类型的默认值

如果只定义不赋值,则:

  • 数字类型是0
  • 字符串类型是空字符串
  • 布尔类型是false
func main() {
var a int
var b string
var c bool
fmt.Println(a,b,c)
}