本文首发于公众号:Hunter后端
这一篇笔记主要介绍 Golang 的基础内容,包括 Golang 的运行,变量声明以及 Golang 里的各种数据类型。
以下是本篇笔记目录:
- Golang 的运行
- 变量声明
- Golang 数据类型
- fmt 函数
1、Golang 的运行
在介绍后面的函数前,我们所有的代码都放在 main 函数里,如果有特殊需要引入的包,这里会提前说明。
下面我们创建一个 main.go 文件,内容如下:
go
// main.go
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
运行的方式,我们直接使用 go run main.go
,执行了之后在控制台看到 hello world
的输出说明我们已经运行成功了。
2、变量声明
Go 中使用变量前必须要先声明,声明变量可以使用 var
来修饰,也可以使用 :=
来声明。
如果使用 :=
则必须要在声明的时候赋值,而使用 var 的时候则不需要,会自动给定该变量类型的初始值。
使用 var 声明变量并赋值
go
var i int = 10
使用 var 声明变量不赋值
go
var i int
上面声明了 i
变量不赋值,因为声明了变量类型为 int
,则该变量初始化为 0。
使用 :=
声明变量
go
i := 10
通过这种方式声明的变量会根据变量值自动为其获取变量类型。
批量声明变量
可以批量声明多个变量:
go
var a, b int
批量声明多个变量的时候赋值:
go
var (
a = 100
b = "456"
)
或者这样声明:
go
a, b := 123, "456"
3、Golang 的数据类型
1. 数值类型
Golang 里数值类型有整型、浮点型等,这里介绍整型和浮点型
1) 整型
整数类型分为两种,有符号整数和无符号整数。
有符号整型就是是否包含负数,如果包含负数,则其范围是对应的无符号的一半。
有符号整数:int8, int16, int32, int64 和 int
有符号整数:uint8, uint16, uint32, uint64 和 uint
int8 的整数范围是 -2^7 ~ 2^7 -1
uint8 的整数范围是 0 - 2^8-1
同理,对于 int16,int32,int64 都是对应的负的 2 的 n-1 次方到 2 的 n-1 次方减一。
go
package main
import (
"fmt"
)
func main() {
var a int8 = -128
fmt.Println(a)
var b uint8 = 255
fmt.Println(b)
}
对于我们常用的 int,它的范围则取决于我们使用的系统,我们的系统是 32 位,则范围是 负的 2 的 31 次方到 2 的 31 次方减一。
2) 浮点型
Go 中的浮点型有两种,单精度 float32 和双精度 float64,分别是 32 位浮点数和 64 位浮点数。
比如我们这样定义:
go
var a float64 = 16.789
float32 可以精确到小数点后 6 位,float64 可以精确到小数点后 15 位。
可以精确到小数点后的位数是怎么计算的,比如 float32,使用 32位表示一个浮点数,其中,1位表示符号,8位表示指数,23位表示位数,而浮点数在计算机中以二进制形式存储,所以 23 位的位数从二进制转为十进制精度可以如下计算:
go
d = n * log(2)
float32 的尾数有 23 位,大概可以精确到十进制的小数点后位数位 23 * log(2) = 6.9
,大约在 6-7,所以可以提供 6-7位十进制数字的精度。
同理 float64 使用 64位表示一个浮点数,1位表示符号,11位表示指数,52位表示尾数,所以可以精确到的小数点位数是 15 位。
2. 布尔类型
布尔类型只有两个值,true
和 false
:
go
var a bool = true
fmt.Println(a)
3. 字符串
字符串使用双引号包起来,下面是其定义:
go
var a string = "abc"
fmt.Println("a: ", a)
var b = "abc"
fmt.Println("b: ", b)
c := "abc"
fmt.Println("c: ", c)
4. 字符
字符类型有两种,一种是 byte,一种是 rune,其实他们分别是 uint8 和 int32 的别名,分别用来表示 ASCII 字符和 Unicode 字符,使用单引号包起来。
1) byte
先来介绍 byte,前面说过,byte 实际上就是 uint8,所以虽然我们给其赋值 ASCII 字符,但是它的值本身还是一个整型:
go
var a byte = 'a'
fmt.Println("a: ", a)
// 输出结果是 97
如果我们想要其输出原本的字符内容,可以使用 fmt.Printf() 函数:
go
var byteContent byte = 'a'
fmt.Printf("byteContent 的内容是: %c\n", byteContent)
// byteContent 的内容是: a
2) rune
rune 本质上是 int32,所以它的使用和 byte 是一样的:
go
var runeContent rune = '中'
fmt.Println("runeContent 的值是: ", runeContent)
// runeContent 的值是: 20013
fmt.Printf("runeContent 的内容是: %c\n", runeContent)
// runeContent 的内容是: 中
5. 指针
指针用于存储变量的内存地址,我们可以通过指针访问这个变量的内存地址,也可以通过指针访问到这个变量的内存地址存储的值。
使用 &
符号来获取变量的内存地址,使用 *
获取指针指向的内存地址的值:
go
var a int = 10
var a_ptr *int = &a
fmt.Println("a 的内存地址是: ", &a)
fmt.Println("a_ptr 的值是: ", a_ptr)
fmt.Println("根据指针获取的值是: ", *a_ptr)
6. 数组
数组是具有固定长度的相同类型元素的序列。
这里有两个点需要注意,数组的长度是固定的,数组的元素是相同的,且在定义的时候就定好的。
go
var arr [3]int
arr[0] = 1
arr[1] = 2
arr[2] = 3
fmt.Println("arr: ", arr)
也可以在定义的时候直接对其赋值:
go
var arr [3]int = [3]int{1, 2, 3}
fmt.Println("arr: ", arr)
或者定义的时候不指定数量,自动获取:
go
var arr = [...]int{1, 2, 3}
fmt.Println("arr: ", arr)
还可以在定义的时候,指定索引位置的值:
go
var arr = [...]string{0: "Peter", 3: "Tome", 1: "Hunter"}
fmt.Println("arr: ", arr)
7. 切片
切片是对数组的一个连续片段的引用,它本身不存储数据,而是指向底层数组。
go
var arr = [...]int{1, 2, 3, 4, 5}
slice := arr[1:3]
fmt.Println("slice 内容为: ", slice)
有关于切片的更多的内容在后面笔记中会更详细的介绍。
8. map
map 是 Golang 里的映射:
go
m := make(map[string]int)
m["apple"] = 1
m["banana"] = 2
fmt.Println(m)
9. 结构体
结构体是将零个或多个任意类型的命名变量组合在一起的聚合数据类型,其定义和使用方式如下:
go
type Person struct {
Age int
Name string
}
func main() {
var person Person
person.Age = 25
person.Name = "Hunter"
fmt.Println("结构体 person: ", person)
}
10. 通道
goroutine 是 Go 协程,而通道(channel)是用于在不同的 goroutine 之间进行通信的工具。
创建一个通道的方式如下:
go
ch := make(chan int)
上面的操作表示我们创建了一个 ch
通道,可以往通道里传入 int 型数据。
使用 <-
向通道传入数据,或者接收数据:
go
ch <- x // 将 x 传入通道
x = <- ch // 接收并将值赋给 x
<-ch // 接收语句,将结果丢弃
4、fmt 函数
Golang 的 fmt 包可以用于格式化输入和输出。
1. 格式化输出函数
先介绍几个格式化输出函数:fmt.Print
、fmt.Println
、fmt.Printf
。
fmt.Print()
相当于是直接输出打印内容,而 fmt.Println()
相当于是在输出内容末尾加上换行符 \n
。
比如下面两个输出在格式上是等效的:
go
fmt.Print("123\n")
fmt.Println("123")
而 fmt.Printf()
则是对含有变量的输出内容进行格式化处理,常见的格式化内容如下:
占位符 | 输出格式 |
---|---|
%s | 字符串 |
%T | 变量类型 |
%d | 输出十进制 |
%b | 输出二进制 |
%f | 输出浮点数 |
%c | 输出变量的 unicode 值 |
%t | 输出布尔型 |
%v | 输出变量的值 |
以下是对应的示例:
go
var a int = 10
fmt.Printf("a 的整数值是 %d\n", a)
fmt.Printf("a 的二进制是 %b\n", a)
var b string = "this is a test"
fmt.Printf("b 的字符串输出是 %s\n", b)
fmt.Printf("b 的值的输出是 %v\n", b)
fmt.Printf("b 的类型是 %T\n", b)
var c float64 = 12.3456
fmt.Printf("c 浮点数输出是 %f\n", c)
fmt.Printf("c 保留两位小数输出是 %.2f\n", c)
var d bool = true
fmt.Printf("d 的布尔值是 %t\n", d)
var e rune = '中'
fmt.Printf("e 的 unicode 值是 %c\n", e)
fmt 还有一个 Sprintf 函数,作用是按照指定的格式将参数格式化后并返回一个字符串:
go
name := "Hunter"
age := 28
message := fmt.Sprintf("My name is %s and I'm %d years old.", name, age)
fmt.Println("message: ", message)
// message: My name is Hunter and I'm 28 years old.
2. 格式化输入函数
1) fmt.Scan
从标准输入读取数据,并将读取到的数据赋值给参数,参数之间以空格或者键入回车键进行分隔,直到给定的参数都被输入:
go
var num1, num2 int
fmt.Print("输出两个整数:")
fmt.Scan(&num1, &num2)
fmt.Printf("输出的两个值是 %d 和 %d\n", num1, num2)
在上面的操作中,如果我们只输入一个数字就按了回车键,等待输入的程序并不会停止,而是直到我们输入指定个数的参数。
2) fmt.Scanln
从标准输入读取一行数据,将读取到的数据赋值给参数,遇到换行符结束读取,这个操作同样会以空格将参数进行分隔:
go
var num1, num2 int
fmt.Print("输出两个整数:")
fmt.Scanln(&num1, &num2)
fmt.Printf("输出的两个值是 %d 和 %d\n", num1, num2)
在这个操作里,即便我们只输入了一个数字,就按下了回车键,等待输入的程序也会结束,没有输入值的变量会使用其默认值。
3) fmt.Scanf
按照指定的格式从标准输入读取数据,然后赋值给参数,相对于 fmt.Scan() 和 fmt.Scanln(),fmt.Scanf() 输入数据的分割方式更自由一点,比如如果我们想一行一个参数,可以如下操作:
go
var num1, num2 int
fmt.Print("输出两个整数:")
fmt.Scanf("%d\n%d", &num1, &num2)
fmt.Printf("输出的两个值是 %d 和 %d\n", num1, num2)
3. fmt.Sprint
这个函数的功能是按照指定的格式将参数格式化并返回一个字符串,也就是说我们使用这个函数对字符串进行格式化,下面是操作示例:
go
formatStr := "My name is %s and I'm %d years old"
message := fmt.Sprintf(formatStr, "Hunter", 28)
fmt.Println(message)
如果想获取更多相关文章,可扫码关注阅读: