编辑
2023-03-14
🧰语言-go
00
请注意,本文编写于 828 天前,最后修改于 225 天前,其中某些信息可能已经过时。

目录

1. go语言简介
golang的应用
学习技术的方法
为什么要创造go语言?
go语言的特点
go是一门编译型的和静态的编程语言
2. go的特性
3. go的亮点
4. 安装go官方工具链
5. 运行一个go程序
6. 让项目支持GO模块特性来简化依赖管理
go语言的项目结构
7.程序源代码基本元素介绍
一个简单的go示例程序
关于代码断行
8. 关键字和标识符
关键字
标识符
9.基本类型和他们的字面量表示
基本内置类型
零值
基本类型的字面量表示形式
布尔值的字面量形式
整数类型值的字面量形式
浮点数类型值的字面量形式
虚部字面量形式
数值字面表示中使用下划线分段来增强可读性
rune值的字面量形式

1. go语言简介

golang的应用

Go语言在云计算、边缘计算、大数据、微服务、物联网、高并发领域应用得越来越广泛,越来越多的知名公司正在把Go作为开发新项目的首选语言,golanguage主要在如下场景中有着广泛的应用:

  • 服务器编程:可以替代前期C/C++,例如HTTP服务、文件系统、虚拟机处理、数据打包等场景,比较好的go开源项目入nsq/heka等
  • 分布式系统:可用于分布式计算、分布式存储、分布式读写等场景开发,例如分布式调度框架,分布式文件系统等,如:cbfs/skynet/doozer等
  • 网络编程:主要包括服务器编程,得益于其高并发能力,目前应用最为广泛,开源项目如:GIN、Beego、Buffalo、Echo、Iris、Revel等
  • 内存数据库/数据库代理:用于做内存数据库,或者内存缓存前一段时间google开发的groupcache,couchbase的部分组建
  • 云平台:基于go语言的云平台组件开发,例如:docker/k8s等,以及云原生服务的开发
  • 嵌入式应用开发:由于其占用资源较少、跨平台、高效率等优势,且其高校编译、高效执行、易于编程的特点,把go语言作为嵌入应用开发语言大厂越来越多

学习技术的方法

  1. 遇到一个需求(或者自己想要跳槽)
  2. 看使用传统的技术是否能够解决,若能解决就不用接下来的步骤,若否,继续往下走
  3. 学习新技术的或者知识点的原理和基本语法
  4. 实战快速入门案例,了解新技术、知识点的基本作用、不需要研究技术细节
  5. 讨论技术细节、怎么使用规范、使用陷阱、注意事项【此步骤是最耗费时间,是与大佬之间根本的区别】
  6. 解决需求

为什么要创造go语言?

Google在创造Go的原因:

  • 计算机硬件的发展特别快速,性能提高的很快。目前主流的编程语言发展明显落后与硬件的发展,不能够合理地利用多核CPU以提升软件性能。
  • 软件系统的复杂度越来越高,维护成本也越来越高。目前缺乏一个足够简洁高效的编程语言
  • 企业运行维护很多C/C++项目,这两个语言虽然运行速度很快,但是编译速度很慢,同时还存在内存泄露的问题需要解决。这使得我们程序员再解决业务的同时,还要分出精力去处理业务之外的事情。

go语言的特点

  1. 从C语言中继承了很多理念,包括表达式语法,控制结构,基础数据类型,调用参数传值,指针等等,也保留了和C语言一样的编译执行方式及弱化的指针。

  2. 引入包的概念,用于组织程序结构,GO的语言一个文件都要归属一个包,而不能单独存在。

  3. 垃圾回收机制,内存自动回收,不需要开发人员管理

  4. 天然并发(重要特点)

  从语言层面支持并发,实现简单

  goroutine,轻量级线程,可实现大并发处理,高效利用多核。

  基于GPS并发模型(Communicating Sequential Processes)实现

  1. 吸收了管道通信机制,形成GO语言特有的管道channel通过管道channel,可以实现不同的goroute之间的相互通信

  2. 函数可以返回多个值

  3. 新的创新:比如切片slice、延时执行defer等

go是一门编译型的和静态的编程语言

什么是静态语言?

类型原理优点缺点
静态语言静态语言是指在编译期间对数据类型进行检查的语言。这种语言在编写程序时需要声明所有变量的数据类型。典型的静态语言如C,C++、Java。1. 由于类型的强制声明,使得IDE有很强的代码感知能力,在实现复杂的业务逻辑、开发大型商业系统、以及那些生命周期很长的应用中,依托IDE对系统的开发很有保障;2. 由于静态语言相对比较封闭,使得第三方开发包对代码的侵害性可以降到最低。
动态语言动态语言是指在运行期间才去做数据类型检测的语言。在用动态语言编程的时候并不需要给变量指定数据类型,该语言会在第一次将数据赋值给变量的时候在内部将数。据类型记录下来。典型的动态语言如Python,Ruby。1. 思维不受束缚,可以任意发挥,把更多的精力放在产品本身上;2. 集中思考业务逻辑实现,思考过程即实现过程。

什么是编译型的语言?

类型原理优点缺点
编译型语言通过专门的编译器,将所有源代码一次性转换成特定平台(Windows、Linux 等)执行的机器码(以可执行文件的形式存在)。编译一次后,脱离了编译器也可以运行,并且运行效率高。可移植性差,不够灵活。
解释型语言由专门的解释器,根据需要将部分源代码临时转换成特定平台的机器码。跨平台性好,通过不同的解释器,将相同的源代码解释成不同平台下的机器码。一边执行一边转换,效率很低。

2. go的特性

  1. 内置并发编程支持

    • 使用协程(goroutine)作为基本的计算单元。轻松地创建协程
    • 使用通道(channel)来实现协程间的同步和通信
  2. 内置了映射(map)和切片(slice)类型

  3. 支持多态(polumorphism)

    类的多态特性,还要满足以下 2 个前提条件:

    • 继承:多态一定是发生在子类和父类之间;
    • 重写:子类重写了父类的方法。
python
class CLanguage: def say(self): print("调用的是 Clanguage 类的say方法") class CPython(CLanguage): def say(self): print("调用的是 CPython 类的say方法") class CLinux(CLanguage): def say(self): print("调用的是 CLinux 类的say方法") a = CLanguage() a.say() a = CPython() a.say() a = CLinux() a.say()

程序执行结果为:

调用的是 Clanguage 类的say方法 调用的是 CPython 类的say方法 调用的是 CLinux 类的say方法

可以看到,CPython 和 CLinux 都继承自 CLanguage 类,且各自都重写了父类的 say() 方法。从运行结果可以看出,同一变量 a 在执行同一个 say() 方法时,由于 a 实际表示不同的类实例对象,因此 a.say() 调用的并不是同一个类中的 say() 方法,这就是多态。

  1. 使用接口(interface)来实现装盒(value boxing)和反射(reflection)
  2. 支持指针
  3. 支持函数闭包(closure)
  4. 支持方法
  5. 支持延迟函数调用(defer) defer是Go语言提供的一种用于注册延迟调用的机制;让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行。

defer语句通常用于一些成对操作的场景:打开连接/关闭连接;加锁/释放锁;打开文件/关闭文件等。 9. 支持类型内嵌(type embedding) 结构体允许其成员字段在声明时没有字段名,而只有类型。这种形式的字段称为匿名字段或类型内嵌。

  1. 支持类型推断(type deduction or type inference) 类型推断是一些静态类型语言的一个特性.由编译器完成将类型分配给缺少任何类型注释的实体.编译器实际上只是代表程序员填充"了静态类型信息
  2. 内存安全
  3. 自动垃圾回收
  4. 良好的代码跨平台性
  5. 自定义泛型

3. go的亮点

  1. Go的语法很简洁并且和其它流行语言相似。 这使得具有一定编程经验的程序员很容易上手Go编程。 当然,对于没有编程经验的初学者,Go也比很多其它流行编程语言更容易上手一些。
  2. Go拥有一个比较齐全的标准库。这个标准库提供了很多常用的功能。
  3. Go拥有一个活跃和回应快速的社区。 社区贡献了大量高质量的第三方库包和应用。
  4. 使用go编写的程序常常编译的非常快。go程序生成的二进制可执行文件常常拥有以下优点:
    • 内存消耗少
    • 执行速度快
    • 启动快

4. 安装go官方工具链

链接

最简单的go程序,test.go

go
package main import "fmt" func main(){ fmt.Println("hello, world") }

在此程序中,单词package和func是两个关键字。两个main是两个标识符

5. 运行一个go程序

go run test.go

输出:

EricDeMac:第一天 eric$ go run test.go hello world

如果一个程序的main包中有若干Go源代码文件,我们也可以使用下面的命令运行此程序。

go run .

注意

  • go run子命令并不推荐在正式的大项目中使用。go run子命令只是一种方便的方式来运行简单的Go程序。 对于正式的项目,最好使用go build或者go install子命令构建可执行程序文件来运行Go程序。
  • 支持Go模块特性的Go项目的根目录下需要一个go.mod文件。此文件可以使用go mod init子命令来生成(见下)。
  • 名称以_和.开头的源代码文件将被Go官方工具链工具忽略掉。
  • go vet子命令可以用来检查可能的代码逻辑错误(即警告)。
  • 使用go fmt子命令来用同一种代码风格格式化Go代码。
  • go test子命令来运行单元和基准测试用例。
  • go doc子命令来(在终端中)查看Go代码库包的文档。

6. 让项目支持GO模块特性来简化依赖管理

  • go mod init example.com/myproject命令可以用来在当前目录中生成一个go.mod文件。 当前目录将被视为一个名为example.com/myproject的模块(即当前项目)的根目录。 此go.mod文件将被用来记录当前项目需要的依赖模块和版本信息。 我们可以手动编辑或者使用go子命令来修改此文件。
  • go mod tidy命令用来通过扫描当前项目中的所有代码来添加未被记录的依赖至go.mod文件或从go.mod文件中删除不再被使用的依赖。
  • go get命令用拉添加、升级、降级或者删除单个依赖。此命令不如go mod tidy命令常用。
  • go install example.com/program@latest来安装一个第三方Go程序的最新版本

go语言的项目结构

7.程序源代码基本元素介绍

一个简单的go示例程序

go
package main // 指定当前源文件所在的包名 import "math/rand" // 引入一个标准库包 const MaxRand = 16 // 声明一个具名整型常量 // 一个函数声明 /* StatRandomNumbers生成一些不大于MaxRand的非负 随机整数,并统计和返回小于和大于MaxRand/2的随机数 个数。输入参数numRands指定了要生成的随机数的总数。 */ func StatRandomNumbers(numRands int) (int, int) { // 声明了两个变量(类型都为int,初始值都为0) var a, b int // 一个for循环代码块 for i := 0; i < numRands; i++ { // 一个if-else条件控制代码块 if rand.Intn(MaxRand) < MaxRand/2 { a = a + 1 } else { b++ // 等价于:b = b + 1 } } return a, b // 此函数返回两个结果 } // main函数,或主函数,是一个程序的入口函数。 func main() { var num = 100 // 调用上面声明的StatRandomNumbers函数, // 并将结果赋给使用短声明语句声明的两个变量。 x, y := StatRandomNumbers(num) // 调用两个内置函数(print和println)。 print("Result: ", x, " + ", y, " = ", num, "? ") println(x+y == num) }

关于代码断行

像很多其它流行编程语言一样,Go也使用一对大括号{ and }来形成一个显式代码块。但是在Go代码中,编码样式风格有一些限制。 比如,很多左大括号{不能被放到下一行。 如果,上面的StatRandomNumbers被修改成如下所示,则上面的示例程序将编译不通过。

go
// 编译错误 func StatRandomNumbers(numRands int) (int, int) { // 编译错误:语法错误 var a, b int for i := 0; i < numRands; i++ { // 编译错误:语法错误 if rand.Intn(MaxRand) < MaxRand/2 { // 编译错误:语法错误 a = a + 1 } else { b++ } } return a, b }

8. 关键字和标识符

关键字

go中共有25个关键字

shell
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

这些关键字可以分为四组:

  • const、func、import、package、type和var用来声明各种代码元素。
  • chan、interface、map和struct用做 一些组合类型的字面表示中。
  • break、case、continue、default、 else、fallthrough、for、 goto、if、range、 return、select和switch用在流程控制语句中。 详见基本流程控制语法。
  • defer和go也可以看作是流程控制关键字, 但它们有一些特殊的作用。详见协程和延迟函数调用。

标识符

一个标识符是一个以Unicode字母或者_开头并且完全由Unicode字母和Unicode数字组成的单词。

  • Unicode字母是定义在Unicode标准8.0 中的Lu、Ll、Lt、Lm和Lo分类中的字符。
  • Unicode数字是定义在Unicode标准8.0中的Nd数字字符分类中的字符。 注意:关键字不能被用做标识符。

标识符_是一个特殊字符,它叫做空标识符。

以后,我们将知道所有的类型名、变量名、常量名、跳转标签、包名和包的引入名都必须是标识符。

一个由Unicode大写字母开头的标识符称为导出标识符。 这里导出可以被理解为公开(public)。 其它(即非Unicode大写字母开头的)标识符称为非导出标识符。 非导出可以被理解为私有(private)。 截至目前(Go 1.20),东方字符都被视为非导出字符。 非导出有时候也被称为未导出。

下面是一些合法的导出标识符:

Player_9 DoSomething VERSION Ĝo Π

下面是一些合法的未导出标识符:

_ _status memStat book π 一个类型 변수 エラー

下面这些不能被用做标识符:

// Unicode数字开头 123 3apples // 含有不符合要求的Unicode字符 a.b *ptr $name a@b.c // 这两个是关键字 type range

9.基本类型和他们的字面量表示

基本内置类型

Go支持如下内置基本类型:

  • 一种内置布尔类型:bool。
  • 11种内置整数类型:int8、uint8、int16、uint16、int32、uint32、int64、uint64、int、uint和uintptr。
  • 两种内置浮点数类型:float32和float64。
  • 两种内置复数类型:complex64和complex128。
  • 一种内置字符串类型:string。

Go中有两种内置类型别名(type alias):

  • byte是uint8的内置别名。 我们可以将byte和uint8看作是同一个类型。
  • rune是int32的内置别名。 我们可以将rune和int32看作是同一个类型。

以u开头的整数类型称为无符号整数类型。 无符号整数类型的值都是非负的。 一个数值类型名称中的数字表示每个这个类型的值将在内存中占有多少二进制位(以后简称位)。二进制位常称为比特(bit)。 比如,一个uint8的值将占有8位。 我们称uint8类型的值的尺寸是8位。 因此,最大的uint8值是255(28-1), 最大的int8值是127(27-1), 最小的int8值是-128(-27)。

下面是一个类型声明的例子。 在这些例子中,type是一个关键字。

go
// 一些类型定义声明 type status bool // status和bool是两个不同的类型 type MyString string // MyString和string是两个不同的类型 type Id uint64 // Id和uint64是两个不同的类型 type real float32 // real和float32是两个不同的类型 // 一些类型别名声明 type boolean = bool // boolean和bool表示同一个类型 type Text = string // Text和string表示同一个类型 type U8 = uint8 // U8、uint8和 byte表示同一个类型 type char = rune // char、rune和int32表示同一个类型

我们将上面定义的real类型和内置类型float32都称为float32类型 (注意这里的第二个float32是一个泛指,而第一个高亮的float32是一个特指)。 同样地,MyString和string都被称为字符串(string)类型,status和bool都被称为布尔(bool)类型。

零值

每种类型都有一个零值。一个类型的零值可以看作是此类型的默认值。

  • 一个布尔类型的零值表示真假中的假。
  • 数值类型的零值都是零(但是不同类型的零在内存中占用的空间可能不同)。
  • 一个字符串类型的零值是一个空字符串。

基本类型的字面量表示形式

布尔值的字面量形式

Go白皮书没有定义布尔类型值字面量形式。 我们可以将false和true这两个预声明的具名常量当作布尔类型的字面量形式。 但是,我们应该知道,从严格意义上说,它们不属于字面量。具名常量声明将在下一篇文章中介绍和详细解释。

布尔类型的零值可以使用预声明的false来表示。

整数类型值的字面量形式

go
0xF // 十六进制表示(必须使用0x或者0X开头) 0XF 017 // 八进制表示(必须使用0、0o或者0O开头) 0o17 0O17 0b1111 // 二进制表示(必须使用0b或者0B开头) 0B1111 15 // 十进制表示(必须不能用0开头)

下面的程序打印出两个true。

go
package main func main() { println(15 == 017) // true println(15 == 0xF) // true }

浮点数类型值的字面量形式

一个浮点数的完整十进制字面量形式可能包含一个十进制整数部分、一个小数点、一个十进制小数部分和一个以10为底数的整数指数部分。 整数指数部分由字母e或者E带一个十进制的整数字面量组成(xEn表示x乘以10n的意思,而xE-n表示x除以10n的意思)。 常常地,某些部分可以根据情况省略掉。一些例子:

go
1.23 01.23 // == 1.23 .23 1. // 一个e或者E随后的数值是指数值(底数为10)。 // 指数值必须为一个可以带符号的十进制整数字面量。 1.23e2 // == 123.0 123E2 // == 12300.0 123.E+2 // == 12300.0 1e-1 // == 0.1 .1e0 // == 0.1 0010e-2 // == 0.1 0e+5 // == 0.0

从Go 1.13开始,Go也支持另一种浮点数字面量形式:十六进制浮点数字面量。 在一个十六进制浮点数字面量中,

  • 一*个十六进制浮点数字面量必须以一个以2为底数的整数指数部分。 这样的一个整数指数部分由字母p或者P带一个十进制的整数字面量组成(yPn表示y乘以2n的意思,而yP-n表示y除以2n的意思)。
  • 和整数的十六进制字面量一样,一个十六进制浮点数字面量也必须使用0x或者0X开头。 和整数的十六进制字面量不同的是,一个十六进制浮点数字面量可以包括一个小数点和一个十六进制小数部分。

一些合法的浮点数的十六进制字面量例子:

go
0x1p-2 // == 1.0/4 = 0.25 0x2.p10 // == 2.0 * 1024 == 2048.0 0x1.Fp+0 // == 1+15.0/16 == 1.9375 0X.8p1 // == 8.0/16 * 2 == 1.0 0X1FFFP-16 // == 0.1249847412109375

虚部字面量形式

一个虚部值的字面量形式由一个浮点数字面量或者一个整数字面量和其后跟随的一个小写的字母i组成。 在Go 1.13之前,如果虚部中i前的部分为一个整数字面量,则其必须为并且总是被视为十进制形式。 一些例子:

go
1.23i 1.i .23i 123i 0123i // == 123i(兼容性使然。见下) 1.23E2i // == 123i 1e-1i 011i // == 11i(兼容性使然。见下) 00011i // == 11i(兼容性使然。见下) // 下面这几行从Go 1.13开始才能编译通过。 0o11i // == 9i 0x11i // == 17i 0b11i // == 3i 0X.8p-0i // == 0.5i

数值字面表示中使用下划线分段来增强可读性

从Go 1.13开始,下划线_可以出现在整数、浮点数和虚部数字面量中,以用做分段符以增强可读性。 但是要注意,在一个数值字面表示中,一个下划线_不能出现在此字面表示的首尾,并且其两侧的字符必须为(相应进制的)数字字符或者进制表示头。

一些合法和不合法使用下划线的例子:

go
// 合法的使用下划线的例子 6_9 // == 69 0_33_77_22 // == 0337722 0x_Bad_Face // == 0xBadFace 0X_1F_FFP-16 // == 0X1FFFP-16 0b1011_0111 + 0xA_B.Fp2i // 非法的使用下划线的例子 _69 // 下划线不能出现在首尾 69_ // 下划线不能出现在首尾 6__9 // 下划线不能相连 0_xBadFace // x不是一个合法的八进制数字 1_.5 // .不是一个合法的十进制数字 1._5 // .不是一个合法的十进制数字

rune值的字面量形式

go
'a' // 一个英文字符 'π' '众' // 一个中文字符 '\141' // 141是97的八进制表示 '\x61' // 61是97的十六进制表示 '\u0061' '\U00000061'
go
package main func main() { println('a' == 97) println('a' == '\141') println('a' == '\x61') println('a' == '\u0061') println('a' == '\U00000061') println(0x61 == '\x61') println('\u4f17' == '众') }

本文作者:Eric

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!