从Hello Golang开始
package main
import "fmt"
func main() {
fmt.Println("Hello, Golang!")
}
main
包是程序的入口包,在Go中,所有可执行文件都必须包含main
包import
导入所需要的包,将在后续介绍具体使用func
用于定义函数的关键字,main()
则为主函数,运行时从此处开始fmt.Println()
调用fmt
包中的Println()
函数打印Hello, Golang!
变量、常量的声明和初始化
若要声明变量,需要使用 var
关键字,<u>声明的类型放在后面</u>,如果你决定初始化某个变量,则不需要指定其类型,因为当你**<u>使用具体值初始化该变量时,Go 会推断出其类型。如果声明了变量但未使用,Go 会抛出错误</u>**
var name string
var (
firstName, lastName string
age int
)
var (
firstName = "Lowell"
lastName = "Doe"
age = 32
)
var (
firstName, lastName, age = "John", "Doe", 32 // 按顺序赋值
)
firstName, lastName := "John", "Doe" // 声明同时初始化,这种方式用得多
age := 32
在定义变量名称后,需要在此处加入一个冒号等于号 (:=
) 和相应的值。 <u>使用冒号等于号时,要声明的变量必须是新变量。 如果使用冒号等于号并已经声明该变量,将不会对程序进行编译</u>
声明常量的关键字为 const
,与变量一样,Go 可以通过分配给常量的值推断出类型。 在 Go 中,常量名称通常以混合大小写字母或全部大写字母书写。如果需要在一个块中声明多个常量,可以按如下所示执行:
const STATUS = 0
const (
StatusOK = 0
StatusConnectionReset = 1
StatusOtherError = 2
)
可以在不使用常量的情况下声明常量,并且不会收到错误消息。 <u>不能使用冒号等于号来声明常量</u>。 如果采用这种方式,Go 会发出警告
Go 为常量定义了一个有趣的概念,即 iota,本模块未对此概念作进一步解释。 但你可以查看 GitHub 上的 Go wiki 了解更多信息。 请记住,iota
是一个关键字;如果这些值存在先后顺序,Go 可使用此关键字简化常量定义。
基本数据类型
Go 是一种强类型语言。声明的每个变量都绑定到特定的数据类型,并且只接受与此类型匹配的值。
四类数据类型:
- 基本类型:数字、字符串和布尔值
- 聚合类型:数组和结构
- 引用类型:指针、切片、映射、函数和通道
- 接口类型:接口
整数数字
一般来说,定义整数类型的关键字是 int
。 但 Go 还提供了 int8
、int16
、int32
和 int64
类型,其大小分别为 8、16、32 或 64 位的整数
var integer8 int8 = 127
var integer16 int16 = 32767
var integer32 int32 = 2147483647
var integer64 int64 = 9223372036854775807
类型转换
在 Go 中,int
与 int32
不同,即使整数的自然大小为 32 位也是如此。 换句话说,需要强制转换时,你需要进行显式转换。 如果尝试在不同类型之间执行数学运算,将会出现错误。在Go中隐式类型转换不起作用,所所以我们的需要进行显示类型转换。如下:
var integer16 int16 = 127
var integer32 int32 = 32767
fmt.Println(integer16 + integer32)
//报错 invalid operation: integer16 + integer32 (mismatched types int16 and int32)
将代码改成如下所示便没问题
var integer16 int16 = 127
var integer32 int32 = 32767
fmt.Println(int32(integer16) + integer32)
另一种转换方法是使用 strconv 包。 例如,若要将 string
转换为 int
,可以使用以下代码,反之亦然
package main
import (
"fmt"
"strconv"
)
func main() {
i, _ := strconv.Atoi("-42")
s := strconv.Itoa(-42)
fmt.Println(i, s)
}
在前面的代码中,有一个下划线 (_
) 用作变量的名称。 在 Go 中,_
意味着我们不会使用该变量的值,而是要将其忽略。 否则,程序将不会进行编译,因为我们需要使用声明的所有变量。
浮点数字
Go 提供两种浮点数大小的数据类型:float32
和 float64
。
当需要使用十进制数时,浮点类型也很有用。 例如,你可以编写类似于以下代码的内容:
const e = 2.71828
const Avogadro = 6.02214129e23
const Planck = 6.62606957e-34
布尔型
使用关键字 bool
声明布尔类型。 在 Go 中,不能将布尔类型隐式转换为 0 或 1。 你必须显式执行此操作。
var flag bool = true
字符串
在 Go 中,关键字 string
表示字符串数据类型。 若要初始化字符串变量,你需要在双引号("
)中定义值。 单引号('
)用于单个字符
var firstName string = "John"
lastName := "Doe"
fmt.Println(firstName, lastName)
默认值
在 Go 中,如果你不对变量初始化,所有数据类型都有默认值。 以下为几种数据类型的默认值
int
类型的0
(及其所有子类型,如int64
)
float32
和float64
类型的+0.000000e+000
bool
类型的false
string
类型的空值
函数
下面为一个从键盘读取参数,两数相加的例子
func main() {
// 直接相加
number1, _ := strconv.Atoi(os.Args[1])
number2, _ := strconv.Atoi(os.Args[2])
fmt.Println("Sum = ", number1+number2)
//调用自定义函数
sum := sum(os.Args[1], os.Args[2])
fmt.Println("Sum = ", sum)
}
func sum(number1 string, number2 string) int {
int1, _ := strconv.Atoi(number1)
int2, _ := strconv.Atoi(number2)
return int1 + int2
}
func sum1(number1 string, number2 string) (result int) {
int1, _ := strconv.Atoi(number1)
int2, _ := strconv.Atoi(number2)
restult = int1 + int2
return
}
访问 Go 中的命令行参数,可以使用用于保存传递到程序的所有参数的 os 包 和 os.Args
变量来执行操作。
在Go 中,你还可以为函数的返回值设置名称,将其当作一个变量,如下:
func name(parameters) (results) {
body-content
}
上面创建函数的语法,在命名后,指定函数的参数列表,可以指定零个或多个参数
返回多个值
Go中可以函数返回多个值,可以采用类似于定义函数参数的方式来定义这些值,如下,将两个相加并且相乘返回
func main() {
sum, mul := SumMul(os.Args[1], os.Args[2])
fmt.Println("Sum = ", sum, "Mul = ", mul)
}
func SumMul(number1 string, number2 string) (sum int, mul int) {
int1, _ := strconv.Atoi(number1)
int2, _ := strconv.Atoi(number2)
sum = int1 + int2
mul = int1 * int2
return
}
如果不需要函数的某个返回值,可以通过将返回值分配给 _
变量来放弃该函数。 _
变量是 Go 忽略返回值的惯用方式。 它允许程序进行编译。 因此,如果只需要求和,则可以使用以下代码:
func main() {
sum, _ := AddMul(os.Args[1], os.Args[2])
fmt.Println("Sum:", sum)
}
更改函数参数值(指针)
将值传递给函数时,该函数中的每个更改都不会影响调用方。 Go 是**“按值传递”**编程语言。 每次向函数传递值时,Go 都会使用该值并创建本地副本(内存中的新变量)。 在函数中对该变量所做的更改都不会影响你向函数发送的更改。
func main() {
name := "Lowell"
update(name)
fmt.Println(name)
}
func update(name string) {
name = "Tom"
}
即使你在函数中将该名称更改为 Tom,输出仍为 Lowell。由于 update
函数中的更改仅会修改本地副本,因此输出不会发生变化。 Go 传递变量的值,而不是变量本身。
使用指针在update
函数中修改main
中name
值,向函数发送指针时,不是传递值,而是传递内存地址。 因此,对该变量所做的每个更改都会影响调用方。如下:
func main() {
name := "Lowell"
Update(&name) //函数发送的是地址,所以要引用&
fmt.Println(name)
}
func Update(name *string) {
*name = "Tom"
}
了解包
main 包
当你使用 main
包时,程序将生成独立的可执行文件。 但当程序不是 main
包的一部分时,Go 不会生成二进制文件。 它生成包存档文件(具有 .a
扩展名的文件)。
在 Go 中,包名称需遵循约定。 包使用其导入路径的最后一部分作为名称。 例如,Go 标准库包含名为 math/cmplx
的包,该包提供用于处理复数的有用代码。 此包的导入路径为 math/cmplx
,
Go中没有private public
等关键字,但是 须遵循以下两个简单规则:
- 如需将某些内容设为专用内容,请以小写字母开始。
- 如需将某些内容设为公共内容,请以大写字母开始。
创建calculator
包
package calculator
var logMessage = "[LOG]" // 专用内容,只能从包内调用logMessage变量。
var Version = "1.0" // 公共内容,可以从任何位置访问Version变量。
func internalSum(number int) int { // 专用内容,只能从包内调用 `internalSum` 函数。
return number - 1
}
func Sum(number1, number2 int) int { // 公共内容,可以从任何位置访问 `Sum` 函数。
return number1 + number2
}
创建模块
Go 模块通常包含可提供相关功能的包。 包的模块还指定了 Go 运行组合在一起的代码所需的上下文。 此上下文信息包括编写代码时所用的 Go 版本。
此外,模块还有助于其他开发人员引用代码的特定版本,并更轻松地处理依赖项。 另一个优点是,我们的程序源代码无需严格存在于 $GOPATH/src
目录中。 如果释放该限制,则可以更方便地在其他项目中同时使用不同包版本
因此,若要为 calculator
包创建模块,请在根目录 ($GOPATH/src/calculator
) 中运行以下命令:
go mod init path
控制流
if / else 语句
在 Go 中,你不需要在条件中使用括号。 else
子句可选。 但是,大括号是必需的,不能将语句和if
写在同一行。 Go 不支持三元 if
语句,因此每次都需要编写完整的 if
语句。
package main
import "fmt"
func main() {
if num := 12; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has only one digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
Go 支持复合条件语句,在if
分支中初始化num
,并且这个num
的作用域仅限于这个 if-else-if 语句块,随后按条件执行输出:has multiple digits
switch 语句
像 if
语句一样,switch
<u>条件不需要括号</u>。如下:
func main() {
i := 3
switch i {
case 1 :
fmt.Prntln("1st")
case 2 :
fmt.Println("2nd")
case 3 :
fmt.Println("3rd")
default :
fmt.Println("not match")
}
}
Go 会比较 switch
语句的每个用例,直到找到与条件匹配的项。 如果都未匹配,则程序的输出为 not match
。
使用多个表达式
有时,多个表达式仅与一个 case
语句匹配。 在 Go 中,<u>如果希望 case
语句包含多个表达式,请使用逗号 (,
) 来分隔表达式,避免代码重复。</u>
package main
import "fmt"
func location(city string) (string, string) {
var region string
var continent string
switch city {
case "Delhi", "Hyderabad", "Mumbai", "Chennai", "Kochi":
region, continent = "India", "Asia"
case "Lafayette", "Louisville", "Boulder":
region, continent = "Colorado", "USA"
case "Irvine", "Los Angeles", "San Diego":
region, continent = "California", "USA"
default:
region, continent = "Unknown", "Unknown"
}
return region, continent
}
func main() {
region, continent := location("Irvine")
fmt.Printf("Lowell works in %s, %s\n", region, continent)
}
调用函数
switch
还可以调用函数。以下代码调用 time.Now()
函数。 它提供的输出取决于当前工作日。
package main
import (
"fmt"
"time"
)
func main() {
switch time.Now().Weekday().String() {
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
fmt.Println("It's time to learn some Go.")
default:
fmt.Println("It's the weekend, time to rest!")
}
fmt.Println(time.Now().Weekday().String())
}
使逻辑进入到下一个 case
在C/C++,在每个 case
语句末尾写一个 break
关键字。 但在 Go 中,当逻辑进入某个 case 时,它会退出 switch
块,除非你显式停止它。 若要使逻辑进入到下一个紧邻的 case,请使用 fallthrough
关键字。
package main
import (
"fmt"
)
func main() {
switch num := 15; {
case num < 50:
fmt.Printf("%d is less than 50\n", num)
fallthrough
case num > 100:
fmt.Printf("%d is greater than 100\n", num)
fallthrough
case num < 200:
fmt.Printf("%d is less than 200", num)
}
}
输出:
15 is less than 50
15 is greater than 100
15 is less than 200
我们可以发现在第二个case输出中有错误,是因为我们在第一个case中使用了 fallthrough
关键字,在判断满足第一个case的条件进入语句块,它会直接进入下一个case中,而不验证条件,因此慎重使用。
for语句
与 if
语句和 switch
语句一样,for
循环表达式不需要括号。 但是,大括号是必需的。
func main() {
sum := 0
for i := 1; i <= 100; i++ {
sum += i
}
fmt.Println("sum of 1..100 is", sum)
}
数组
<u>数组必须在声明或初始化它们时定义大小</u>。 此外,它们一旦创建,就无法调整大小。鉴于这些原因,数组在 Go 程序中并不常用,但它们是切片和映射的基础。如下:创建一个长度为3,类型为 int
的数组a,并给a[1]
赋值为10。
package main
import "fmt"
func main() {
var a [3]int
a[1] = 10
fmt.Println(a[0])
fmt.Println(a[1])
fmt.Println(a[len(a)-1])
}
输出:
0
10
0
可以看给a[0]
赋值,但是打印了值,默认情况下,Go 会用默认数据类型初始化每个元素。 这样的话,int
的默认值为零。len
函数是 Go 中的内置函数,用于获取数组、切片或映射中的元素数。
你也可以声明的同时初始化,如下所示,最后一个位置包含一个空的字符串,因为它是字符串数据类型的默认值。
cities := [5]string{"New York", "Paris", "Berlin", "Madrid"}
切片
与数组一样,切片也是 Go 中的一种数据类型,它表示一系列类型相同的元素。 不过,<u>与数组更重要的区别是切片(slice)的大小是动态的,不是固定的。切片是数组或另一个切片之上的数据结构。</u> 我们将源数组或切片称为基础数组。 通过切片,可访问整个基础数组,也可仅访问部分元素。
切片有 3 个组件:
- 指向基础数组中第一个可访问元素的指针。 此元素不一定是数组的第一个元素
array[0]
。 - 切片的长度。 切片中的元素数目。
- 切片的容量。 切片开头与基础数组结束之间的元素数目。
创建切片
slice1 := []int {1, 2, 3, 4, 5} // 创建一个包含五个元素的整型切片
slice2 := make([]int, 5) // 创建一个长度为5的整型切片
由上可以看出,在初始化slice1时没有指定长度,而是由后面给出的初始值个数确定,而创建数组时长度就确定了
切片项
Go 支持切片运算符 s[i:p]
,其中:
s
表示数组。i
表示指向要添加到新切片的基础数组(或另一个切片)的第一个元素的指针。 变量i
对应于数组array[i]
中索引位置i
处的元素。 请记住,此元素不一定是基础数组的第一个元素array[0]
。p
表示创建新切片时要使用的基础数组中的元素数目,也表示元素位置。 变量p
对应于可用于新切片的基础数组中的最后一个元素。 可在位置array[i+1]
找到基础数组中位置p
处的元素。 请注意,此元素不一定是基础数组的最后一个元素array[len(array)-1]
。
假设你需要 4 个变量来表示一年的每个季度,并且你有一个包含 12 个元素的 months
切片。
package main
import "fmt"
func main() {
months := []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
quarter1 := months[0:3]
quarter2 := months[3:6]
quarter3 := months[6:9]
quarter4 := months[9:12]
fmt.Println(quarter1, len(quarter1), cap(quarter1))
fmt.Println(quarter2, len(quarter2), cap(quarter2))
fmt.Println(quarter3, len(quarter3), cap(quarter3))
fmt.Println(quarter4, len(quarter4), cap(quarter4))
}
输出:
[January February March] 3 12
[April May June] 3 9
[July August September] 3 6
[October November December] 3 3
使用 append
追加元素
slice := []int{1, 2, 3, 4}
slice = append(silce, 5)
映射(map)
Go 中的映射是一个哈希表,是**<u>键值对的集合</u>**。 映射中所有的键都必须具有相同的类型,它们的值也是如此。 不过,可对键和值使用不同的类型。
使用关键字 map
创建映射
studentsAge := map[string]int // 键类型为string,值类型为int
使用内置函数 make()
创建映射
studentsAge := make(map[string]int) // 键类型为string,值类型为int
映射是动态的。 创建项后,可添加、访问或删除这些项。
studentsAge["john"] = 32
fmt.Println(studentsAge["john"]) // 打印键值对中,键为"john"的值
studentsAge["Lowell"] = 20
delete(studentsAge, "Lowell") // 删除键为"Lowell"的键值对
映射中的循环
range
会首先生成项的键,然后再生成该项的值。
package main
import (
"fmt"
)
func main() {
studentsAge := make(map[string]int)
studentsAge["john"] = 32
studentsAge["bob"] = 31
for name, age := range studentsAge {
fmt.Printf("%s\t%d\n", name, age)
}
}
输出:
john 32
bob 31
结构体
Go 中的结构,它可包含零个或多个任意类型的字段,并将它们表示为单个实体。声明结构,需要使用 struct
关键字
type Employee struct {
ID int
Name string
Age int
}
定义了一个Employee
结构体,包含ID,Name,Age三个字段,随后我们可以做如下操作
employee := Employee{1, Lowell, 20} // 初始化一个结构体
var employee1
employee1.ID = 2
employee1.Name = "Mike"
employee1.Age = 19