“Go是一个开源的编程语言,它很容易用于构建简单、可靠和高效的软件。”(摘自Go语言官
方网站:http://golang.org )
“前言、起源,均摘录于GO语言圣经”
Go语言由来自Google公司
的Robert Griesemer
,Rob Pike和Ken Thompson
三位大牛于2007
年9月开始设计和实现,然后于2009年的11月对外正式发布(译注:关于Go语言的创世纪过
程请参考 http://talks.golang.org/2015/how-go-was-made.slide )。语言及其配套工具的设计
目标是具有表达力,高效的编译和执行效率,有效地编写高效和健壮的程序。
Go语言有着和C语言类似的语法外表,和C语言一样是专业程序员的必备工具,可以用最小的
代价获得最大的战果。 但是它不仅仅是一个更新的C语言。它还从其他语言借鉴了很多好的
想法,同时避免引入过度的复杂性。 Go语言中和并发编程相关的特性是全新的也是有效的,
同时对数据抽象和面向对象编程的支持也很灵活。 Go语言同时还集成了自动垃圾收集技术用
于更好地管理内存。
Go语言尤其适合编写网络服务相关基础设施,同时也适合开发一些工具软件和系统软件。 但
是Go语言确实是一个通用的编程语言,它也可以用在图形图像驱动编程、移动应用程序开发
和机器学习等诸多领域。目前Go语言已经成为受欢迎的作为无类型的脚本语言的替代者: 因
为Go编写的程序通常比脚本语言运行的更快也更安全,而且很少会发生意外的类型错误。
Go语言还是一个开源的项目,可以免费获编译器、库、配套工具的源代码。 Go语言的贡献
者来自一个活跃的全球社区。Go语言可以运行在类UNIX系统—— 比如
Linux
、FreeBSD
、OpenBSD
、Mac OSX
——和Plan9系统
和Microsoft Windows
操作系统之
上。 Go语言编写的程序无需修改就可以运行在上面这些环境。
编程语言的演化跟生物物种的演化类似,一个成功的编程语言的后代一般都会继承它们祖先
的优点;当然有时多种语言杂合也可能会产生令人惊讶的特性;还有一些激进的新特性可能
并没有先例。通过观察这些影响,我们可以学到为什么一门语言是这样子的,它已经适应了
怎样的环境。
下图展示了有哪些早期的编程语言对Go语言的设计产生了重要影响
Go语言有
时候被描述为“C类似语言”
,或者是“21世纪的C语言”
。Go从C语言继承了相似的表
达式语法、控制流结构、基础数据类型、调用参数传值、指针等很多思想,还有C语言一直所
看中的编译后机器码的运行效率以及和现有操作系统的无缝适配。
但是在Go语言的家族树中还有其它的祖先。其中一个有影响力的分支来自Niklaus Wirth
所设
计的Pascal
语言。然后Modula-2语言
激发了包的概念。然后Oberon语言
摒弃了模块接口文件
和模块实现文件之间的区别。第二代的Oberon-2语言直接影响了包的导入和声明的语法,还有Oberon语言的面向对象特性所提供的方法的声明语法等。
Go语言的另一支祖先,带来了Go语言区别其他语言的重要特性,灵感来自于贝尔实验室的
Tony Hoare
于1978年发表的鲜为外界所知的关于并发研究的基础文献 顺序通信进程 (
communicating sequential processes ,缩写为CSP。在CSP中,程序是一组中间没有共享状
态的平行运行的处理过程,它们之间使用管道进行通信和控制同步。不过Tony Hoare
的CSP
只是一个用于描述并发性基本概念的描述语言,并不是一个可以编写可执行程序的通用编程
语言。
接下来,Rob Pike
和其他人开始不断尝试将CSP引入实际的编程语言中。他们第一次尝试引
入CSP特性的编程语言叫Squeak(老鼠间交流的语言)
,是一个提供鼠标和键盘事件处理的
编程语言,它的管道是静态创建的。然后是改进版的Newsqueak语言
,提供了类似C语言语
句和表达式的语法和类似Pascal语言的推导语法。Newsqueak
是一个带垃圾回收的纯函数式
语言,它再次针对键盘、鼠标和窗口事件管理。但是在Newsqueak语言
中管道是动态创建
的,属于第一类值, 可以保存到变量中。
在Plan9操作系统中,这些优秀的想法被吸收到了一个叫Alef的编程语言中。Alef试图将
Newsqueak
语言改造为系统编程语言,但是因为缺少垃圾回收机制而导致并发编程很痛苦。
(译注:在Aelf之后还有一个叫Limbo的编程语言,Go语言从其中借鉴了很多特性。 具体请
参考Pike的讲稿:http://talks.golang.org/2012/concurrency.slide#9 )
Go语言的
其他的一些特性零散地来自于其他一些编程语言;比如iota语法
是从APL语言
借鉴,
词法作用域与嵌套函数来自于Scheme语言
(和其他很多语言)。当然,我们也可以从Go中
发现很多创新的设计。比如Go语言的切片为动态数组提供了有效的随机存取的性能,这可能
会让人联想到链表的底层的共享机制。还有Go语言新发明的defer
语句
所有的编程语言都反映了语言设计者对编程哲学的反思,通常包括之前的语言所暴露的一些
不足地方的改进。Go项目是在Google公司维护超级复杂的几个软件系统遇到的一些问题的反
思(但是这类问题绝不是Google公司所特有的)。
正如Rob Pike
所说,“软件的复杂性是乘法级相关的”,通过增加一个部分的复杂性来修复问题
通常将慢慢地增加其他部分的复杂性。通过增加功能、选项和配置是修复问题的最快的途
径,但是这很容易让人忘记简洁的内涵,即从长远来看,简洁依然是好软件的关键因素。
简洁的设计需要在工作开始的时候舍弃不必要的想法,并且在软件的生命周期内严格区别好
的改变和坏的改变。通过足够的努力,一个好的改变可以在不破坏原有完整概念的前提下保
持自适应,正如Fred Brooks
所说的“概念完整性”;而一个坏的改变则不能达到这个效果,它
们仅仅是通过肤浅的和简单的妥协来破坏原有设计的一致性。只有通过简洁的设计,才能让
一个系统保持稳定、安全和持续的进化。
Go项目
包括编程语言本身,附带了相关的工具和标准库,最后但并非代表不重要的是,关于
简洁编程哲学的宣言。就事后诸葛的角度来看,Go语言
的这些地方都做的还不错:拥有自动 垃圾回收
、一个包系统
、函数作为一等公民
、词法作用域
、系统调用接口
、只读的UTF8字符串
等。
但是Go语言本身只有很少的特性,也不太可能添加太多的特性。例如,它没有隐式的
数值转换,没有构造函数和析构函数,没有运算符重载,没有默认参数,也没有继承,没有
泛型,没有异常,没有宏,没有函数修饰,更没有线程局部存储。但是,语言本身是成熟和
稳定的,而且承诺保证向后兼容:用之前的Go语言编写程序可以用新版本的Go语言编译器和
标准库直接构建而不需要修改代码。
Go语言
有足够的类型系统以避免动态语言中那些粗心的类型错误,但是,Go语言的类型系统
相比传统的强类型语言又要简洁很多。虽然,有时候这会导致一个“无类型”的抽象类型概念,
但是Go语言程序员并不需要像C++
或Haskell
程序员那样纠结于具体类型的安全属性。在实践
中,Go语言
简洁的类型系统给程序员带来了更多的安全性和更好的运行时性能。
Go语言鼓励当代计算机系统设计的原则,特别是局部的重要性。它的内置数据类型和大多数
的准库数据结构都经过精心设计而避免显式的初始化或隐式的构造函数,因为很少的内存分
配和内存初始化代码被隐藏在库代码中了。
Go语言的聚合类型(结构体
和数组
)可以直接操
作它们的元素,只需要更少的存储空间、更少的内存写操作,而且指针操作比其他间接操作
的语言也更有效率。由于现代计算机是一个并行的机器,Go语言提供了基于CSP的并发特性
支持。Go语言的动态栈使得轻量级线程goroutine
的初始栈可以很小,因此,创建一个
goroutine
的代价很小,创建百万级的goroutine
完全是可行的。
Go语言的标准库(通常被称为语言自带的电池),提供了清晰的构建模块和公共接口
,包含 I/O操作
、文本处理
、图像
、密码学
、网络
和分布式应用程序
等,并支持许多标准化的文件格
式和编解码协议。库和工具使用了大量的约定来减少额外的配置和解释,从而最终简化程序的逻辑,而且,每个Go程序结构都是如此的相似,因此,Go程序也很容易学习。使用Go语
言自带工具构建Go语言项目只需要使用文件名和标识符名称, 一个偶尔的特殊注释来确定所有
的库、可执行文件、测试、基准测试、例子、以及特定于平台的变量、项目的文档等;Go语
言源代码本身就包含了构建规范。
本章介绍Go语言的基础组件。本章提供了足够的信息和示例程序,希望可以帮你尽快入门, 写
出有用的程序。本章和之后章节的示例程序都针对你可能遇到的现实案例。先了解几个Go程
序,涉及的主题从简单的文件处理、图像处理到互联网客户端和服务端并发。当然,第一章
不会解释细枝末节,但用这些程序来学习一门新语言还是很有效的。
学习一门新语言时,会有一种自然的倾向, 按照自己熟悉的语言的套路写新语言程序。学习Go
语言的过程中,请警惕这种想法,尽量别这么做。我们会演示怎么写好Go语言程序,所以,
请使用本书的代码作为你自己写程序时的指南。
我们以现已成为传统的“hello world
”案例来开始吧, 这个例子首次出现于1978年出版的C语言
圣经《The C Programming Language》
gopl.io/ch1/helloworld
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
Go是一门编译型语言,Go语言的工具链将源代码及其依赖转换成计算机的机器指令(译注:
静态编译)。Go语言提供的工具都通过一个单独的命令 go 调用, go 命令有一系列子命令。
最简单的一个子命令就是run
。这个命令编译一个或多个以.go结尾的源文件,链接库文件,并
运行最终生成的可执行文件。
$ go run helloworld.go
毫无意外,这个命令会输出:
Hello, 世界
Go语言原生支持Unicode
,它可以处理全世界任何语言的文本。
build
子命令:
$ go build helloworld.go
这个命令生成一个名为helloworld
的可执行的二进制文件(译注:Windows系统下生成的可执
行文件是helloworld.exe
,增加了.exe后缀名),之后你可以随时运行它(译注:在Windows
系统下在命令行直接输入helloworld.exe
命令运行),不需任何处理(译注:因为静态编译,
所以不用担心在系统库更新的时候冲突,幸福感满满)
$ ./helloworld
Hello, 世界
Go的标准库提供了100多个包,以支持常见功能,如输入、输出、排序以及文本处理。比
如 fmt
包,就含有格式化输出、接收输入的函数。 Println
是其中一个基础函数,可以打印
以空格间隔的一个或多个值,并在最后添加一个换行符,从而输出一整行。
main 包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在 main
里的 main
函
数 也很特殊,它是整个程序执行时的入口(译注:C系语言差不多都这样)。 main
函数所做
的事情就是程序做的。当然了, main 函数一般调用其它包里的函数完成很多工作, 比如,
fmt.Println
。
必须告诉编译器源文件需要哪些包,这就是跟随在 package 声明后面的 import 声明扮演的角
色。hello world
例子只用到了一个包,大多数程序需要导入多个包。
必须恰当导入需要的包,缺少了必要的包或者导入了不需要的包,程序都无法编译通过。这
项严格要求避免了程序开发过程中引入未使用的包(译注:Go语言编译过程没有警告信息,
争议特性之一)。
import 声明必须跟在文件的 package
声明之后。随后,则是组成程序的函数、变量、常量、
类型的声明语句(分别由关键字 func
, var
, const
, type
定义)。这些内容的声明顺序并
不重要(译注:最好还是定一下规范)。这个例子的程序已经尽可能短了,只声明了一个函
数, 其中只调用了一个其他函数。为了节省篇幅,有些时候, 示例程序会省
略 package
和 import
声明,但是,这些声明在源代码里有,并且必须得有才能编译。
一个函数的声明由 func 关键字、函数名、参数列表、返回值列表(这个例子里的 main
函数
参数列表和返回值都是空的)以及包含在大括号里的函数体组成
Go语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会
主动把特定符号后的换行符转换为分号, 因此换行符添加的位置会影响Go代码的正确解析(译
注:比如行末是标识符、整数、浮点数、虚数、字符或字符串文字、关键
字 break
、 continue
、 fallthrough
或 return
中的一个、运算符和分隔符 ++
、 - -
、 )
、 ]
或 }
中的一个)
。举个例子, 函数的左括号 { 必须和 func
函数声明在同一行上,
且位于末尾,不能独占一行,而在表达式x + y
中,可在 +
后换行,不能在 +
前换行(译
注:以+
结尾的话不会被插入分号分隔符,但是以x结尾的话则会被分号分隔符,从而导致编
译错误)。
Go语言在代码格式上采取了很强硬的态度。 gofmt
工具把代码格式化为标准格式(译注:这
个格式化工具没有任何可以调整代码格式的参数,Go语言就是这么任性),并且 go 工具中
的 fmt
子命令会对指定包, 否则默认为当前目录, 中所有.go源文件应用 gofmt
命令。本博客中
所有代码都被gofmt
过。你也应该养成格式化自己的代码的习惯。以法令方式规定标准的代码
格式可以避免无尽的无意义的琐碎争执(译注:也导致了Go语言的TIOBE排名较低,因为缺
少撕逼的话题)。更重要的是,这样可以做多种自动源码转换,如果放任Go语言代码格式,
这些转换就不大可能了。
很多文本编辑器都可以配置为保存文件时自动执行gofmt
,这样你的源代码总会被恰当地格
式化。还有个相关的工具, goimports
,可以根据代码需要, 自动地添加或删除 import
声
明。这个工具并没有包含在标准的分发包中,可以用下面的命令安装:
$ go get golang.org/x/tools/cmd/goimports
但是如果你用的是Goland
进行编译那就不用
大多数的程序都是处理输入,产生输出;这也正是“计算”的定义。但是, 程序如何获取要处理
的输入数据呢?一些程序生成自己的数据,但通常情况下,输入来自于程序外部:文件、网
络连接、其它程序的输出、敲键盘的用户、命令行参数或其它类似输入源。下面几个例子会
讨论其中几个输入源,首先是命令行参数。
os
包以跨平台的方式,提供了一些与操作系统交互的函数和变量。程序的命令行参数可从os
包的Args变量获取;os
包外部使用os.Args
访问该变量。
os.Args
变量是一个字符串(string
)的切片(slice
)(译注:slice
和Python语言中的切片类
似,是一个简版的动态数组),切片是Go语言的基础概念,稍后详细介绍。现在先把切片s当
作数组元素序列, 序列的长度动态变化, 用 s[i]
访问单个元素,用 s[m:n]
获取子序列(译注:
和python里的语法差不多)。序列的元素数目为len(s)。和大多数编程语言类似,区间索引时,
Go言里也采用左闭右开形式, 即,区间包括第一个索引元素,不包括最后一个, 因为这样可以
简化逻辑。(译注:比如a = [1, 2, 3, 4, 5]
, a[0:3] = [1, 2, 3]
,不包含最后一个元素)。比如
s[m:n]
这个切片,0 ≤ m ≤ n ≤ len(s)
,包含n-m
个元素。
os.Args
的第一个元素,os.Args[0]
, 是命令本身的名字;其它的元素则是程序启动时传给它的
参数。s[m:n]
形式的切片表达式,产生从第m个元素到第n-1个元素的切片,下个例子用到的
元素包含在os.Args[1:len(os.Args)]
切片中。如果省略切片表达式的m或n,会默认传入0或
len(s)
,因此前面的切片可以简写成os.Args[1:]
。
下面是Unix
里echo
命令的一份实现,echo把它的命令行参数打印成一行。程序导入了两个
包,用括号把它们括起来写成列表形式, 而没有分开写成独立的 import
声明。两种形式都合
法,列表形式习惯上用得多。包导入顺序并不重要;gofmt
工具格式化时按照字母顺序对包名
排序。(示例有多个版本时,我们会对示例编号, 这样可以明确当前正在讨论的是哪个。)
gopl.io/ch1/echo1
package main
import (
"fmt"
"os"
)
func main() {
var s, sep string
for i := 1; i < len(os.Args); i++ {
s += sep + os.Args[i]
sep = " "
}
fmt.Println(s)
}
注释语句以 //
开头。对于程序员来说,//
之后到行末之间所有的内容都是注释,被编译器忽
略。按照惯例,我们在每个包的包声明前添加注释;对于 main package
,注释包含一句或几
句话,从整体角度对程序做个描述。
var
声明定义了两个string
类型的变量s
和sep
。变量会在声明时直接初始化。如果变量没有显
式初始化,则被隐式地赋予其类型的零值(zero value
),数值类型是0,字符串类型是空字
符串""
。这个例子里,声明把s
和sep
隐式地初始化成空字符串。后面再来详细地讲解变量和
声明。
对数值类型,Go语言提供了常规的数值和逻辑运算符。而对string
类型, + 运算符连接字符
串(译注:和C++或者js是一样的)。所以表达式:
sep + os.Args[i]
表示连接字符串sep
和os.Args
。程序中使用的语句:
s += sep + os.Args[i]
是一条赋值语句, 将s
的旧值跟sep
与os.Args[i]
连接后赋值回s
,等价于:
s = s + sep + os.Args[i]
运算符 +=
是赋值运算符(assignment operator),每种数值运算符或逻辑运算符,
如 +
或 *
,都有对应的赋值运算符。
echo程序可以每循环一次输出一个参数,这个版本却是不断地把新文本追加到末尾来构造字
符串。字符串s开始为空,即值为" "
,每次循环会添加一些文本;第一次迭代之后,还会再插
入一个空格,因此循环结束时每个参数中间都有一个空格。这是一种二次加工(quadratic
process),当参数数量庞大时,开销很大,但是对于echo
,这种情形不大可能出现。本章会
介绍echo的若干改进版,后面会解决低效问题。
循环索引变量i
在for
循环的第一部分中定义。符号:=
是短变量声明(short variable
declaration)的一部分, 这是定义一个或多个变量并根据它们的初始值为这些变量赋予适当类
型的语句。后面有这方面更多说明。
自增语句 i++
给 i
加1;这和 i += 1
以及 i = i + 1
都是等价的。对应的还有 i--
给 i
减
1。它们是语句,而不像C系的其它语言那样是表达式。所以j = i++
非法,而且++
和--
都只
能放在变量名后面,因此 --i
也非法。
Go语言只有for
循环这一种循环语句。for
循环有多种形式,其中一种如下所示:
for initialization; condition; post {
// zero or more statements
}
for
循环三个部分不需括号包围。大括号强制要求, 左大括号必须和post
语句在同一行。
initialization
语句是可选的,在循环开始前执行。initalization
如果存在,必须是一条简单语句
(simple statement),即,短变量声明、自增语句、赋值语句或函数调用。 condition
是一
个布尔表达式(boolean expression),其值在每次循环迭代开始时计算。如果为 true
则执
行循环体语句。 post 语句在循环体执行结束后执行,之后再次对condition
求
值。 condition
值为 false
时,循环结束。
for
循环的这三个部分每个都可以省略,如果省略 initialization
和 post
,分号也可以省
略:
// a traditional "while" loop
for condition {
// ...
}
如果连 condition
也省略了,像下面这样:
// a traditional infinite loop
for {
// ...
}
这就变成一个无限循环,尽管如此,还可以用其他方式终止循环, 如一条 break
或 return
语
句。
for
循环的另一种形式, 在某种数据类型的区间(range
)上遍历,如字符串或切
片。 echo
的第二版本展示了这种形式:
gopl.io/ch1/echo2
package main
import (
"fmt"
"os"
)
func main() {
s, sep := "", ""
for _, arg := range os.Args[1:] {
s += sep + arg
sep = " "
}
fmt.Println(s)
}
每次循环迭代, range
产生一对值;索引以及在该索引处的元素值。这个例子不需要索引,
但 range
的语法要求, 要处理元素, 必须处理索引。一种思路是把索引赋值给一个临时变量,
如 temp
, 然后忽略它的值,但Go语言不允许使用无用的局部变量(local variables),因为这
会导致编译错误。
Go语言中这种情况的解决方法是用 空标识符 (blank identifier),即 _ (也就是下划线)。
空标识符可用于任何语法需要变量名但程序逻辑不需要的时候, 例如, 在循环里,丢弃不需要
的循环索引, 保留元素值。大多数的Go程序员都会像上面这样使用 range
和 _
写 echo
程
序,因为隐式地而非显式地索引os.Args
,容易写对。
echo
的这个版本使用一条短变量声明来声明并初始化 s
和 seps
,也可以将这两个变量分开
声明,声明一个变量有好几种方式,下面这些都等价:
s := ""
var s string
var s = ""
var s string = ""
用哪种不用哪种,为什么呢?第一种形式,是一条短变量声明,最简洁,但只能用在函数内
部,而不能用于包变量。第二种形式依赖于字符串的默认初始化零值机制,被初始化为""
。第
三种形式用得很少,除非同时声明多个变量。第四种形式显式地标明变量的类型,当变量类型与初值类型相同时,类型冗余,但如果两者类型不同,变量类型就必须了。实践中一般使
用前两种形式中的某个,初始值重要的话就显式地指定变量的类型,否则使用隐式初始化。
如前文所述,每次循环迭代字符串s的内容都会更新。+=
连接原字符串、空格和下个参数,
产生新字符串, 并把它赋值给 s
。 s
原来的内容已经不再使用,将在适当时机对它进行垃圾
回收。
如果连接涉及的数据量很大,这种方式代价高昂。一种简单且高效的解决方案是使
用 strings
包的 Join
函数:
gopl.io/ch1/echo3
package main
import (
"fmt"
"os"
"strings"
)
func main() {
fmt.Println(strings.Join(os.Args[1:], " "))
}
最后,如果不关心输出格式,只想看看输出值,或许只是为了调试,可以用 Println
为我们
格式化输出。
fmt.Println(os.Args[1:])
这条语句的输出结果跟 strings.Join
得到的结果很像,只是被放到了一对方括号里。切片都
会被打印成这种格式
对文件做拷贝、打印、搜索、排序、统计或类似事情的程序都有一个差不多的程序结构:一
个处理输入的循环,在每个元素上执行计算处理,在处理的同时或最后产生输出。我们会展
示一个名为 dup
的程序的三个版本;灵感来自于Unix的 uniq
命令,其寻找相邻的重复行。
该程序使用的结构和包是个参考范例,可以方便地修改。
dup
的第一个版本打印标准输入中多次出现的行,以重复次数开头。该程序将引入 if
语
句, map
数据类型以及 bufio
包。
gopl.io/ch1/dup1
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
counts := make(map[string]int)
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
counts[input.Text()]++
}
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
正如 for
循环一样, if
语句条件两边也不加括号,但是主体部分需要加。 if 语句
的 else
部分是可选的,在 if 的条件为 false
时执行。
map存储了键/值(key/value)的集合,对集合元素,提供常数时间的存、取或测试操作。键
可以是任意类型,只要其值能用 ==
运算符比较,最常见的例子是字符串;值则可以是任意类
型。这个例子中的键是字符串,值是整数。内置函数 make
创建空 map
,此外,它还有别的
作用。4.3节讨论 map
。
(译注:从功能和实现上说, Go 的 map
类似于 Java 语言中的 HashMap
,Python语言中
的 dict
, Lua 语言中的 table
,通常使用 hash
实现。遗憾的是,对于该词的翻译并不统一,数学界术语为 映射
,而计算机界众说纷纭莫衷一是。为了防止对读者造成误解,保留不
译。)
每次 dup
读取一行输入,该行被当做 map
,其对应的值递增。 counts[input.Text()]++
语句
等价下面两句:
line := input.Text()
counts[line] = counts[line] + 1
map
中不含某个键时不用担心,首次读到新行时,等号右边的表达式 counts[line]
的值将被
计算为其类型的零值,对于int
即0。
为了打印结果,我们使用了基于 range
的循环,并在 counts
这个 map
上迭代。跟之前类
似,每次迭代得到两个结果,键和其在 map
中对应的值。 map
的迭代顺序并不确定,从实践
来看,该顺序随机,每次运行都会变化。这种设计是有意为之的,因为能防止程序依赖特定
遍历顺序,而这是无法保证的。(译注:具体可以参见这里
http://stackoverflow.com/questions/11853396/google-go-lang-assignment-order)
继续来看 bufio
包,它使处理输入和输出方便又高效。 Scanner
类型是该包最有用的特性之
一,它读取输入并将其拆成行或单词;通常是处理行形式的输入最简单的方法。
程序使用短变量声明创建 bufio.Scanner
类型的变量 input
。
input := bufio.NewScanner(os.Stdin)
该变量从程序的标准输入中读取内容。每次调用 input.Scan()
,即读入下一行,并移除行末
的换行符;读取的内容可以调用 input.Text()
得到。 Scan
函数在读到一行时返回 true
,不
再有输入时返回 false
。
类似于C或其它语言里的 printf
函数, fmt.Printf
函数对一些表达式产生格式化输出。该函
数的首个参数是个格式字符串,指定后续参数被如何格式化。各个参数的格式取决于“转换字
符”(conversion character),形式为百分号后跟一个字母。举个例子, %d
表示以十进制形
式打印一个整型操作数,而 %s
则表示把字符串型操作数的值展开。
Printf
有一大堆这种转换,Go程序员称之为动词(verb)。下面的表格虽然远不是完整的规
范,但展示了可用的很多特性:
符号 | 详细 |
---|---|
%d | 十进制整数 |
%x, %o, %b | 十六进制,八进制,二进制整数。 |
%f, %g, %e | 浮点数: 3.141593 3.141592653589793 3.141593e+00 |
%t | 布尔:true或false |
%c | 字符(rune) (Unicode码点) |
%s | 字符串 |
%q | 带双引号的字符串"abc"或带单引号的字符’c’ |
%v | 变量的自然形式(natural format) |
%T | 变量的类型 |
%% | 字面上的百分号标志(无操作数) |
dup1
的格式字符串中还含有制表符 \t
和换行符 \n
。字符串字面上可能含有这些代表不可
见字符的转义字符(escap sequences)。默认情况下, Printf
不会换行。按照惯例,以字
母 f 结尾的格式化函数,如 log.Printf
和 fmt.Errorf
,都采用 fmt.Printf 的格式化准则。
而以 ln 结尾的格式化函数,则遵循 Println
的方式,以跟 %v
差不多的方式格式化参数,并
在最后添加一个换行符。(译注:后缀 f 指 fomart
, ln 指 line
。)
很多程序要么从标准输入中读取数据,如上面的例子所示,要么从一系列具名文件中读取数
据。 dup 程序的下个版本读取标准输入或是使用 os.Open
打开各个具名文件,并操作它们。
gopl.io/ch1/dup2
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
counts := make(map[string]int)
files := os.Args[1:]
if len(files) == 0 {
countLines(os.Stdin, counts)
} else {
for _, arg := range files {
f, err := os.Open(arg)
if err != nil {
fmt.Fprintf(os.Stderr, "dup2: %v\n", err)
continue
}
countLines(f, counts)
f.Close()
}
}
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
func countLines(f *os.File, counts map[string]int) {
input := bufio.NewScanner(f)
for input.Scan() {
counts[input.Text()]++
}
}
os.Open
函数返回两个值。第一个值是被打开的文件( *os.File
),其后被 Scanner
读取。
os.Open
返回的第二个值是内置error
类型的值。如果 err
等于内置值 nil
(译注:相当于
其它语言里的NULL
),那么文件被成功打开。读取文件,直到文件结束,然后调用 Close
关
闭该文件,并释放占用的所有资源。相反的话,如果 err
的值不是 nil
,说明打开文件时出
错了。这种情况下,错误值描述了所遇到的问题。我们的错误处理非常简单,只是使
用 Fprintf
与表示任意类型默认格式值的动词 %v
,向标准错误流打印一条信息,然
后 dup
继续处理下一个文件; continue
语句直接跳到 for
循环的下个迭代开始执行。
为了使示例代码保持合理的大小,本书开始的一些示例有意简化了错误处理,显而易见的
是,应该检查 os.Open
返回的错误值,然而,使用 input.Scan
读取文件过程中,不大可能出
现错误,因此我们忽略了错误处理。我们会在跳过错误检查的地方做说明。5.4节中深入介绍
错误处理。
注意 countLines
函数在其声明前被调用。函数和包级别的变量(package-level entities)可
以任意顺序声明,并不影响其被调用。(译注:最好还是遵循一定的规范)
map
是一个由 make
函数创建的数据结构的引用。 map
作为为参数传递给某函数时,该函数
接收这个引用的一份拷贝(copy
,或译为副本),被调用函数对 map 底层数据结构的任何修
改,调用者函数都可以通过持有的 map
引用看到。在我们的例子中, countLines
函数
向 counts
插入的值,也会被 main
函数看到。(译注:类似于C++
里的引用传递,实际上指
针是另一个指针了,但内部存的值指向同一块内存)
dup
的前两个版本以"流”模式读取输入,并根据需要拆分成多个行。理论上,这些程序可以处
理任意数量的输入数据。还有另一个方法,就是一口气把全部输入数据读到内存中,一次分
割为多行,然后处理它们。下面这个版本, dup3
,就是这么操作的。这个例子引入
了 ReadFile
函数(来自 io/ioutil
包),其读取指定文件的全部内容, strings.Split 函
数把字符串分割成子串的切片。( Split 的作用与前文提到的 strings.Join 相反。)
我们略微简化了 dup3
。首先,由于 ReadFile
函数需要文件名作为参数,因此只读指定文
件,不读标准输入。其次,由于行计数代码只在一处用到,故将其移回 main
函数。
gopl.io/ch1/dup3
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
)
func main() {
counts := make(map[string]int)
for _, filename := range os.Args[1:] {
data, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Fprintf(os.Stderr, "dup3: %v\n", err)
continue
}
for _, line := range strings.Split(string(data), "\n") {
counts[line]++
}
}
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
ReadFile
函数返回一个字节切片(byte``````slice
),必须把它转换为 string ,才能
用 strings.Split
分割。我们会在3.5.4节详细讲解字符串和字节切片。
实现上, bufio.Scanner
、 ioutil.ReadFile
和 ioutil.WriteFile
都使
用 *os.File
的 Read
和 Write
方法,但是,大多数程序员很少需要直接调用那些低级
(lower-level
)函数。高级(higher-level
)函数,像 bufio
和 io/ioutil
包中所提供的那
些,用起来要容易点。
下面的程序会演示Go语言标准库里的image
这个package
的用法,我们会用这个包来生成一系
列的bit-mapped图,然后将这些图片编码为一个GIF动画。我们生成的图形名字叫利萨如图形
(Lissajous figures),这种效果是在1960年代的老电影里出现的一种视觉特效。它们是协振子
在两个纬度上振动所产生的曲线,比如两个sin正弦波分别在x轴和y轴输入会产生的曲线。图
1.1是这样的一个例子:
译注:要看这个程序的结果,需要将标准输出重定向到一个GIF图像文件(使用 ./lissajous
> output.gif 命令)。下面是GIF图像动画效果:
这段代码里我们用了一些新的结构,包括const
声明,struct
结构体类型,复合声明。和我们举
的其它的例子不太一样,这一个例子包含了浮点数运算。这些概念我们只在这里简单地说明
一下,之后的章节会更详细地讲解。
gopl.io/ch1/lissajous
package main
import (
"image"
"image/color"
"image/gif"
"io"
"math"
"math/rand"
"os"
"time"
)
var palette = []color.Color{color.White, color.Black}
const (
whiteIndex = 0 // first color in palette
blackIndex = 1 // next color in palette
)
func main() {
// The sequence of images is deterministic unless we seed
// the pseudo-random number generator using the current time.
// Thanks to Randall McPherson for pointing out the omission.
rand.Seed(time.Now().UTC().UnixNano())
lissajous(os.Stdout)
}
func lissajous(out io.Writer) {
const (
cycles = 5 // number of complete x oscillator revolutions
res = 0.001 // angular resolution
size = 100 // image canvas covers [-size..+size]
nframes = 64 // number of animation frames
delay = 8 // delay between frames in 10ms units
)
freq := rand.Float64() * 3.0 // relative frequency of y oscillator
anim := gif.GIF{LoopCount: nframes}
phase := 0.0 // phase difference
for i := 0; i < nframes; i++ {
rect := image.Rect(0, 0, 2*size+1, 2*size+1)
img := image.NewPaletted(rect, palette)
for t := 0.0; t < cycles*2*math.Pi; t += res {
x := math.Sin(t)
y := math.Sin(t*freq + phase)
img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
blackIndex)
}
phase += 0.1
anim.Delay = append(anim.Delay, delay)
anim.Image = append(anim.Image, img)
}
gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors
}
当我们import
了一个包路径包含有多个单词的package
时,比如image/color
(image和color两
个单词),通常我们只需要用最后那个单词表示这个包就可以。所以当我们写color.White
时,这个变量指向的是image/color
包里的变量,同理gif.GIF是属于image/gif
包里的变量。
这个程序里的常量声明给出了一系列的常量值,常量是指在程序编译后运行时始终都不会变
化的值,比如圈数、帧数、延迟值。常量声明和变量声明一般都会出现在包级别,所以这些
常量在整个包中都是可以共享的,或者你也可以把常量声明定义在函数体内部,那么这种常
量就只能在函数体内用。目前常量声明的值必须是一个数字值、字符串或者一个固定的
boolean
值。
[]color.Color{...}和gif.GIF{...}
这两个表达式就是我们说的复合声明(后面有说明)。这
是实例化Go语言里的复合类型的一种写法。这里的前者生成的是一个slice
切片,后者生成的
是一个struct
结构体。
gif.GIF
是一个struct
类型(参考4.4节)。struct
是一组值或者叫字段的集合,不同的类型集合
在一个struct
可以让我们以一个统一的单元进行处理。anim
是一个gif.GIF类型的struct
变量。
这种写法会生成一个struct
变量,并且其内部变量LoopCount
字段会被设置为nframes
;而其
它的字段会被设置为各自类型默认的零值。struct
内部的变量可以以一个点(.)来进行访问,就
像在最后两个赋值语句中显式地更新了anim
这个struct
的Delay
和Image
字段。
lissajous
函数内部有两层嵌套的for
循环。外层循环会循环64次,每一次都会生成一个单独的
动画帧。它生成了一个包含两种颜色的201*201大小的图片,白色和黑色。所有像素点都会被
默认设置为其零值(也就是调色板palette里的第0个值),这里我们设置的是白色。每次外层
循环都会生成一张新图片,并将一些像素设置为黑色。其结果会append
到之前结果之后。这
里我们用到了append
(参考)内置函数,将结果append
到anim
中的帧列表末尾,并设置一
个默认的80ms的延迟值。循环结束后所有的延迟值被编码进了GIF图片中,并将结果写入到
输出流。out
这个变量是io.Writer
类型,这个类型支持把输出结果写到很多目标,很快我们就
可以看到例子。
内层循环设置两个偏振值。x轴偏振使用sin函数。y轴偏振也是正弦波,但其相对x轴的偏振是
一个0-3的随机值,初始偏振值是一个零值,随着动画的每一帧逐渐增加。循环会一直跑到x
轴完成五次完整的循环。每一步它都会调用SetColorIndex
来为(x, y)点来染黑色。
main
函数调用lissajous
函数,用它来向标准输出流打印信息,所以下面这个命令会像图1.1中
产生一个GIF动画。
$ go build gopl.io/ch1/lissajous
$ ./lissajous >out.gif
上面说了那么多,后面我们来步入正题
编程语言是用来控制计算机的一系列指令(Instruction),它有固定的格式和词汇(不同编程语言的格式和词汇不一样)。就像我们中国人之间沟通需要汉语,英国人沟通需要英语一样,人与计算机之间进行沟通需要一门语言作为介质,即编程语言。
编程语言的发展经历了机器语言(指令系统)=>汇编语言=>高级语言(C
、java
、Go
等)。
010010101001-》ADD
计算机在设计中规定了一组指令(二级制代码),这组指令的集和就是所谓的机器指令系统,用机器指令形式编写的程序称为机器语言。
但由于机器语言的千上万条指令难以记忆,并且维护性和移植性都很差,所以在机器语言的基础上,人们提出了采用字符和十进制数代替二进制代码,于是产生了将机器语言符号化的汇编语言。
虽然汇编语言相较于机器语言简单了很多,但是汇编语言是机器指令的符号化,与机器指令存在着直接的对应关系,无论是学习还是开发,难度依然很大。所以更加接近人类语言,也更容易理解和修改的高级语言就应运而生了,高级语言的一条语法往往可以代替几条、几十条甚至几百条汇编语言的指令。因此,高级语言易学易用,通用性强,应用广泛。
计算机是不能理解高级语言的,更不能直接执行高级语言,它只能直接理解机器语言,所以使用任何高级语言编写的程序若想被计算机运行,都必须将其转换成计算机语言,也就是机器码。而这种转换的方式分为编译和解释两种。由此高级语言也分为编译型语言和解释型语言。
使用专门的编译器,针对特定的平台,将高级语言源代码一次性的编译成可被该平台硬件执行的机器码,并包装成该平台所能识别的可执行性程序的格式。
编译型语言写的程序执行之前,需要一个专门的编译过程,把源代码编译成机器语言的文件,如exe格式的文件,以后要再运行时,直接使用编译结果即可,如直接运行exe
文件。因为只需编译一次,以后运行时不需要编译,所以编译型语言执行效率高。
1、一次性的编译成平台相关的机器语言文件,运行时脱离开发环境,运行效率高;
2、与特定平台相关,一般无法移植到其他平台;
使用专门的解释器对源程序逐行解释成特定平台的机器码并立即执行。是代码在执行时才被解释器一行行动态翻译和执行,而不是在执行之前就完成翻译。
1.解释型语言每次运行都需要将源代码解释称机器码并执行,执行效率低;
2.只要平台提供相应的解释器,就可以运行源代码,所以可以方便源程序移植;
– 官网:https://golang.google.cn/
– go中文网:https://studygolang.com/dl
或者联系博主
作者没用过mac
可以参考此文档,点击跳转
双击我们下载好的Go语言开发包即可启动安装程序,如下图所示,这是Go语言的用户许可协议,无需管它,直接勾选“I accept
…”然后点击“Next”即可。
安装完成后,在我们所设置的安装目录下将生成一些目录和文件,如下图所示:
在默认情况下,win系统下Go 将会被安装在目录 c:\go
下,但如果你在安装过程中修改安装目录,则需要手动修改所有的环境变量的值。
通过go env命令可以查看环境变量的所有情况。值得一提的是,GOROOT 表示 Go 开发包的安装目录。
国内Go语言库镜像:https://github.com/goproxy/goproxy.cn 在终端输入:go env -w GOPROXY=https://goproxy.cn,direct
对代理进行修改。
GOPROXY https://proxy.golang.org,direct
阿里云: export GOPROXY=https://mirrors.aliyun.com/goproxy/
七牛云: export GOPROXY= https://goproxy.cn
go env -w “GO111MODULE=off
” // 关闭go mod
GOPATH 是 Go语言中使用的一个环境变量,它使用绝对路径提供项目的工作目录(workspace)。
GOPATH下创建src
文件夹,即存放Go项目代码的位置
开发包安装完成后,我们还需要配置一下GOPATH
环境变量,之后才可以使用Go语言进行开发。GOPATH
是开发人员编写Go程序的工作空间路径,也就是存放Go代码的地方。
在桌面或者资源管理器右键“此电脑”(或者“我的电脑”)→“属性”→“高级系统设置”→“环境变量”,如下图所示。
在弹出的菜单里找到 GOPATH
对应的选项点击编辑之后就可以修改了,没有的话可以选择新建,并将变量名填写为
GOPATH,变量值设置为任意目录均可(尽量选择空目录),例如 F:\GoWork
。
GOPATH对应创建的文件夹中里面,手动创建如下3个目录
src 存储go的源代码(需要我们自己手动创建)
pkg 存储编译后生成的包文件 (自动生成)
bin 存储生成的可执行文件(自动生成)
GoLand
是Jetbrains公司推出专为Go开发人员构建的跨平台IDE,可以运行在Windows,Linux,macOS系统之上,
-联系博主拿激活码以及软件,也可以前往官网自行下载 官方下载
-环境配置请参考-----参考文档
快捷键 | 作用 |
---|---|
Ctrl + / | 单行注释 |
Ctrl + Shift + / | 多行注释 |
Ctrl + D | 复制当前光标所在行 |
Ctrl + X | 删除当前光标所在行 |
Ctrl + Alt + L | 格式化代码 |
Ctrl + Shift + | 方向键上或下 将光标所在的行进行上下移动(也可以使用 Alt+Shift+方向键上或下) |
Ctrl + Alt + left/right | 返回至上次浏览的位置 |
Ctrl + R | 替换 |
Ctrl + F | 查找文本 |
Ctrl + Shift + F | 全局查找 |
注释就是对代码的解释和说明,其目的是让人们能够更加轻松地了解代码。注释是开发人员一个非常重要的习惯,也是专业的一种表现。单行注释是最常见的注释形式,你可以在任何地方使用以 //
开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾。
注释在代码运行时不会被运行,
注释是写给人看的,不是写给机器看的
package main
import "fmt"
func main() {
// 这是注释不会被运行
// fmt.Println("不会运行")
fmt.Println("这是注释外,会运行")
}
在计算机编程中,我们用变量来保存并管理很多数据,并用变量名来区分、识别和处理这些数据。
变量就是把数据,内存放到一个容器里面
和C语言一样,Go语言也是通过var
关键字进行声明,不同的是变量名放在类型前,具体格式如下
var 变量名 变量类型
package main
import "fmt"
func main() {
var x int
var s string
var b bool
fmt.Println(x) // 0
fmt.Println(s) // ""
fmt.Println(b) // false
}
Go里面声明未赋值默认是零型
int的零型是 0
string—> “”
bool—> false
如果声明多个变量,可以进行简写
package main
import "fmt"
func main() {
var (
name string
age int
)
fmt.Println(name, age)
}
GO中有三种赋值方式
先声明再赋值
var name int
name=10
直接声明赋值
var age=10
声明赋值简介版
z:="hello zth"
var a = 100
var b = a // 变量之间的赋值是值拷贝
fmt.Println(a, b)
a = 200
fmt.Println(b)
var a, b = 10, 20
var c = a + b
fmt.Println(c)
var d = c + 100
fmt.Println(d)
匿名变量即没有命名的变量,在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。
匿名变量用一个下划线_
表示
a,b,c :=4,5,6
fmt.Println(a,b,c)
// 如果只想接受第个变量,可以对前两个变量匿名
_,_,x := 4,5,6
fmt.Println(x)
匿名变量不占用命名空间,不会分配内存
让代码非常清晰,基本上屏蔽掉了可能混淆代码阅读者视线的内容,从而大幅降低沟通的复杂度和代码维护的难度。
变量命名是需要遵循一定的语法规范的,否则编译器不会通过。
1、变量名称必须由数字、字母、下划线组成。
2、标识符开头不能是数字。
3、标识符不能是保留字和关键字。
4、建议使用驼峰式命名,当名字有几个单词组成的时优先使用大小写分隔
5、变量名尽量做到见名知意。
6、变量命名区分大小写
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 |
都会在后面一一提到
还有30多个预定义的名字,用于内建的常量、类型和函数
// 内建常量:
内建常量 | 内建类型 | 内建类型(2) | 内型 |
---|---|---|---|
false | int | float32 | make |
iota | int8 | float64 | len |
nil | int16 | complex128 | cap |
int32 | complex64 | new | |
int64 | bool | append | |
uint | byte | copy | |
uint8 | rune | close | |
uint16 | string | delete | |
uint32 | error | complex | |
uint64 | real | ||
uintptr | imag | ||
panic | |||
recover |
基本数据类型包含整型和浮点型,布尔类型以及字符串,这几种数据类型在几乎所有编程语言中都支持。
int8 : -127~127 \\2的8次方个数
uint8 : 0~255
等等
int 在32位系统上就用的int32
int 在64位系统上就用的int64
int8: 一个字节 [-127~128]
uint8 : 从0开始[0~255]
类型 | 范围 |
---|---|
int8 | -127到127 |
int16 | -32768到32767 |
int32 | -2147483648到2147483647 |
uint32 | 0到4294967295 |
int64 | -9223372036854775808到9223372036854775807 |
uint64 | 0到18446744073709551615 |
uint | 与平台相关,32位操作系统上就是uint32,64位操作系统上就是uint64 |
int | 与平台相关,32位操作系统上就是int32,64位操作系统上就是int64 |
float类型
分为float32和float64两种类型,这两种浮点型数据格式遵循 IEEE 754 标准。
单精度浮点数占用4个字节(32位)存储空间来存储一个浮点数。而双精度浮点数使用 8个字节(64位)存储空间来存储一个浮点数。
单精度浮点数最多有7位十进制有效数字,如果某个数的有效数字位数超过7位,当把它定义为单精度变量时,超出的部分会自动四舍五入。双精度浮点数可以表示十进制的15或16位有效数字,超出的部分也会自动四舍五入。
浮点类型默认声明为float64。
var f1 float32
f1 = 3.15487464
//reflect.TypeOf 打印数据类型
fmt.Println(f1, reflect.TypeOf(f1))
//float32: 单精度浮点型
var f2 float64 // 双精度浮点型
f2 = 3.1234567890123456789
fmt.Println(f2, reflect.TypeOf(f2))
var f3 = 3.1234567890123456789
fmt.Println(f3, reflect.TypeOf(f2)) // 默认64
布尔类型是最基本数据类型之一,只有两个值:true
和false
,分别代表逻辑判断中的真和假,主要应用在条件判断中
var b bool // 声明b是一个布尔类型
b = true
b = false // 该类型只有true和false两个值,分别代表真假两种状态
fmt.Println(b, reflect.TypeOf(b))
fmt.Println(1 == 1) // 比较运算符的结果是一个布尔值
// fmt.Println(1 == "1") // 报错,mismatched types不能比较
fmt.Println(3 > 1)
var name = "yuan"
var b2 = name == "rain"
//false ****
fmt.Println(b2)
字符串是最基本也是最常用的数据类型,是通过双引号将多个字符按串联起来的一种数据,用于展示文本单引号只能标识字符,注意
:
索引从零开始计数
go语言不支持负索引
var s = "hello zhang"
fmt.Println(s)
// (1)索引取值 slice[index]
a := s[2]
fmt.Println(string(a))
// (2)切片取值slice[start:end], 取出的元素数量为:结束位置 - 开始位置;
b1 := s[2:5] //
fmt.Println(b1)
b2 := s[0:] // 当缺省结束位置时,表示从开始位置到整个连续区域末尾;
fmt.Println(b2)
b3 := s[:8] // 当缺省开始位置时,表示从连续区域开头到结束位置;
fmt.Println(b3)
// (3)字符串拼接
var s1 = "hello"
var s2 = "zhang"
var s3 = s1 + s2 // 生成一个新的字符串
fmt.Println(s3)
方法 | 介绍 |
---|---|
len(str) | 求长度 |
strings.ToUpper,strings.ToLower | 生成一个新的全部大写的字符串,生成一个新的全部小写的字符串 |
strings.ReplaceAll | 生成一个新的原字符串被指定替换后的字符串 |
strings.Contains | 判断是否包含 |
strings.HasPrefix,strings.HasSuffix | 前缀/后缀判断 |
strings.Trim、 | 去除字符串两端匹配的内容 |
strings.Index(),strings.LastIndex() | 子串出现的位置 |
strings.Split | 分割,将字符串按指定的内容分割成数组 |
strings.Join(a[]string, sep string) | join操作,将数组按指定的内容拼接成字符串 |
转义符 | 含义 |
---|---|
\r | 回车符(返回行首) |
\n | 换行符(直接跳到下一行的同列位置) |
\t | 制表符 |
\’ | 单引号 |
\" | 双引号 |
\\ | 反斜杠 |
// 十进制转化
var n int = 10
//printf 传输数据进去
fmt.Printf("%d \n", n) //%d 表示十进制
fmt.Printf("%o \n", n) // 占位符%o表示八进制
fmt.Printf("%b \n", n) //占位符%b表示二进制
fmt.Printf("%x \n", n) //占位符%x表示十六进制
// 八进制转化
var b int = 020
fmt.Printf("%o \n", b) // 20
fmt.Printf("%d \n", b) // 16
fmt.Printf("%x \n", b) // 10
fmt.Printf("%b \n", b) // 10000
// 十六进制转化
var c = 0x12
fmt.Printf("%d \n", c) // 18
fmt.Printf("%o \n", c) // 22
fmt.Printf("%x \n", c) // 12
fmt.Printf("%b \n", c) // 10010
//(1)整型之间的转换 int8 int16
var x int8 = 10
var y int16 = 20
fmt.Println(x + int8(y))
// (2)字符串与整型之间的转换 strconv库
var agestr = "32"
//var name = 12
//字符串转整型
var age, _ = strconv.Atoi(agestr)
fmt.Println(age)
//fmt.Println("err: ", err) // 空对象
price := 100
//整形转字符
price_str := strconv.Itoa(price)
fmt.Println(price_str, reflect.TypeOf(price_str))
//strconv parse系列函数
//字符串转整型 base进制 bitSize是比特位 8---int8
ret, _ := strconv.ParseInt("28", 10, 8)
fmt.Println(ret, reflect.TypeOf(ret))
//字符串转换为浮点型
floats, _ := strconv.ParseFloat("3.1415926", 64)
fmt.Println(floats, reflect.TypeOf(floats))
//字符串转换为布尔值
b, _ := strconv.ParseBool("0")
b1, _ := strconv.ParseBool("-1")
b2, _ := strconv.ParseBool("true")
b3, _ := strconv.ParseBool("T")
fmt.Println(b, b1, b2, b3)
和python运算符一样
//计算一个数是为奇数还是偶数
x, y := 10, 20
fmt.Println(x%2 == 0, y)
//关系运算符 与python相同 == != >= <= 返回布尔值
fmt.Println(x >= y)
//逻辑运算符
//与或非运算
/*
与&&: 真真-真,真假-假,假假-假 ,
或||: 真或真-真,真或假为真,假或假为假
非运算 !:非真为假,非假为真 取反
*/
fmt.Println(true && false)
fmt.Println(true || false)
fmt.Println(!true || false)
//database:root 123
username := "zhang"
password := 123
fmt.Println(username == "root" && password == 123)
/*
赋值运算
和python一样
*/
var a = 12
a += 1
fmt.Println(a)
var b = 10
//自加一
b++
fmt.Println(b)
//优先级
var q, w, z = 1, 2, 3
fmt.Println(q, w, z)
var t = q + w
fmt.Println(t)
fmt.Print
有几个变种:
Println
: 输出到控制台并换行Printf
: 只可以打印出格式化的字符串,只可以直接输出字符串类型的变量(不可以输出别的类型)Sprintf
:格式化并返回一个字符串而不带任何输出
//输出函数
//print println
var name, age = "yuan", 32
fmt.Println("hello world")
fmt.Println(name)
fmt.Println(age)
fmt.Println("姓名:", name, "年龄", age)
//fmt.Print(name)
//fmt.Print(age)
var isMarried = false
fmt.Printf("姓名:%s,年龄:%d,婚否:%t\n", name, age, isMarried)
//sprintf:
s := fmt.Sprintf("姓名:%s,年龄:%d,婚否:%t", name, age, isMarried)
fmt.Println(s)
go语言fmt
包下有三个函数,可以在程序运行过程中从标准输入获取用户的输入:
1、
fmt.Scan
2、fmt.Scanf
3、fmt.Scanln
1、语法:
func Scan(a ...interface{}) (n int, err error)
var (
birth string
)
fmt.Println("输入生日格式如:1988-3-16")
fmt.Scan(&birth)
birthslice := strings.Split(birth, "-")
fmt.Printf("您的生日是%s年-%s月-%s日", birthslice[0], birthslice[1], birthslice[2])
2、语法:
func Scanf(format string, a ...interface{})(n int, err error)
//(2)fmt.scanf 按照指定的格式输入
var a, b int
fmt.Scanf("%d+%d", &a, &b)
fmt.Println(a + b)
3、语法、
func Scanln(a ...interface{}) (n int, err error)
Scanln
类似于Scan
,它遇到换行立即停止扫描。
本函数返回成功扫描的数据个数和遇到的任何错误。
Scanln
和Scan
的区别就是Scanln遇到换行立即结束输入,而Scan
则会将换行符作为一个空白符继续下一个输入
常量是⼀个简单值的标识符,在程序运⾏时,不会被修改的量。 在Python、Java编程规范中,常量⼀般都是全⼤写字母,但是在Golang中,⼤⼩写是具有⼀定特殊含义的,所以不⼀定所有常量都得全⼤写。
声明赋值方式与变量接近,通过const
实现
const 常量名[数据类型] = value
数据类型可以忽略不写,Golang编译器会⾃动推断出数据类型。 在使⽤时,要注意以下⼏点:
数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型
满⾜多重赋值
常量只定义不使⽤,编译不会报错
常量可以作为枚举,常量组
常量组中如不指定类型和初始化值,则与上⼀⾏⾮空常量右值相同
显⽰指定类型的时候,必须确保常量左右值类型⼀致,需要时可做显⽰类型转换。
// (1)声明常量
const pai = 3.1415926
const e float64 = 2.7182818
fmt.Println(pai * pai)
// (2)常量也可以和变量一样一组一起声明
// const monday, tuesday, wednesday = 1, 2, 3
// 更推荐下面这种方式
const (
monday = 1
tuesday = 2
wednesday = 3
thursday = 4
friday = 5
saturday = 6
sunday = 7
)
const (
female = 0
male = 1
)
// ⼀组常量中,如果某个常量没有初始值,默认和上⼀⾏⼀致
const (
a int = 1
b
c = 2
d
)
fmt.Println(a, b, c, d)
iota
是go语言的常量计数器,只能在常量的表达式中使用。 使用iota
时只需要记住以下两点
1.iota
在const
关键字出现时将被重置为0。
2.const
中每新增一行常量声明将使iota
计数一次(iota
可理解为const
语句块中的行索引)。
package main
import "fmt"
type Weekday int
type Flags uint
//常量生成器iota
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
const (
FlagUp Flags = 1 << iota
FlagBroadcast
)
// 无类型常量
const (
deadful = 0xdeadbeef //无类型整数
)
func main() {
fmt.Println()
}
部分内容出自于《GO语言圣经》