Golang 基础知识
1 基础概念
以 // 开头的为单行注释,多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾
// hello.go 测试文件
package main
import "fmt"
/* 主函数入口 */
func main() {
fmt.Println("Hello, www.soloman.vip")
}
执行以上go示例代码,进入代码所在目录执行命令:
# 直接执行go代码
go run hello.go
# 编译生成二进制文件
go build hello.go
1.1 数据类型
| 数据类型 | 相关描述 |
|---|---|
| 布尔型 | 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true |
| 数字类型 | 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码 |
| 字符串类型 | 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本 |
| 派生类型 | 包括:(a) 指针类型(Pointer)、(b) 数组类型、(c) 结构化类型(struct)、(d) Channel 类型、(e) 函数类型、(f) 切片类型、(g) 接口类型(interface)、(h) Map 类型 |
1.2 变量
变量名由字母、数字、下划线组成,其中首个字符不能为数字,声明变量常见方式:
// 声明单个变量
var identifier type
// 一次声明多个同类型变量
var identifier1, identifier2 type
// 先声明,再赋值
var v_name v_type
v_name = value
// 直接赋值,自动判断类型
var v_name = value
// 对于新变量(未声明过),同时声明赋值,只能被用在函数体内,而且不可以用于全局变量的声明与赋值
v_name := value
// 类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3
// 和 python 很像,不需要显示声明类型,自动推断
var vname1, vname2, vname3 = v1, v2, v3
// 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误
vname1, vname2, vname3 := v1, v2, v3
// 这种因式分解关键字的写法一般用于声明全局变量
var (
vname1 v_type1
vname2 v_type2
)
声明变量举例:
package main
var x, y int
var ( // 这种因式分解关键字的写法一般用于声明全局变量
a int
b bool
)
var c, d int = 1, 2
var e, f = 123, "hello"
func main(){
// 这种不带声明格式的只能被用在函数体内,而且不可以用于全局变量的声明与赋值。
g, h := 123, "hello"
println(x, y, a, b, c, d, e, f, g, h)
}
值类型
所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值,值类型变量的值存储在堆中
引用类型
复杂数据一般使用引用类型保存,一个引用类型的变量存储的是其值所在的内存地址(数字),或内存地址中第一个字节所在的位置。
注意事项
如果你声明了一个局部变量却没有在相同的代码块中使用它,会得到编译错误。空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。_ 实际上是一个只写变量,你不能获取它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。
变量类型
Go 语言中变量可以在三个地方声明:
- 函数内定义的变量称为局部变量,作用域只在函数体内,参数和返回值变量也是局部变量
- 函数外定义的变量称为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用
- 函数定义中的变量称为形式参数,作为函数的局部变量来使用
1.3 常量
常量中的数据类型只可以是布尔、数字(整数型、浮点型和复数)和字符串。
// 单个常量声明
const identifier [type] = value
// 多个常量声明赋值
const c_name1, c_name2 = value1, value2
常量声明举例
package main
import "fmt"
func main() {
const LENGTH int = 10
const WIDTH int = 5
var area int
const a, b, c = 1, false, "str" // 多重赋值
area = LENGTH * WIDTH
fmt.Printf("面积为 : %d", area)
println()
println(a, b, c)
}
常量表达式中,函数必须是内置函数,否则编译报错。常量还可以用作枚举:
const (
Unknown = 0
Female = 1
Male = 2
)
iota
iota 在 const 关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)
// a=0, b=1, c=2
const (
a = iota
b = iota
c = iota
)
// 等同于
const (
a = iota
b
c
)
iota 只是在同一个 const 常量组内递增,每当有新的 const 关键字时,iota 计数会重新开始。
package main
const (
i = iota
j = iota
k = iota
)
const xx = iota
const yy = iota
func main(){
println(i, j, k, xx, yy)
}
// 输出是 0 1 2 0 0
2 运算符
Go 语言内置的运算符有:
- 算术运算符
- 关系运算符
- 逻辑运算符
- 位运算符
- 赋值运算符
- 其他运算符
2.1 算术运算符
A=10,B=20
| 运算符 | 描述 | 实例 |
|---|---|---|
+ | 相加 | A + B 输出结果 30 |
- | 相减 | A - B 输出结果 -10 |
* | 相乘 | A * B 输出结果 200 |
/ | 相除 | B / A 输出结果 2 |
% | 求余 | B % A 输出结果 0 |
++ | 自增 | A++ 输出结果 11 |
-- | 自减 | A-- 输出结果 9 |
2.2 关系运算符
A=10,B=20
| 运算符 | 描述 | 实例 |
|---|---|---|
== | 检查两个值是否相等,如果相等返回 True 否则返回 False。 | (A == B) 为 False |
!= | 检查两个值是否不相等,如果不相等返回 True 否则返回 False。 | (A != B) 为 True |
> | 检查左边值是否大于右边值,如果是返回 True 否则返回 False。 | (A > B) 为 False |
< | 检查左边值是否小于右边值,如果是返回 True 否则返回 False。 | (A < B) 为 True |
>= | 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。 | (A >= B) 为 False |
<= | 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。 | (A <= B) 为 True |
2.3 逻辑运算符
A=true,B=false
| 运算符 | 描述 | 实例 |
|---|---|---|
&& | 逻辑 AND 运算符。如果两边的操作数都是 True,则条件 True,否则为 False。 | (A && B) 为 False |
|| | 逻辑 OR 运算符。如果两边的操作数有一个 True,则条件 True,否则为 False。 | (A || B) 为 True |
! | 逻辑 NOT 运算符。如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 | !(A && B) 为 True |
2.4 位运算符
A = 60 = 0011 1100,B = 13 = 0000 1101
| 运算符 | 描述 | 实例 |
|---|---|---|
& | 按位与运算符。其功能是参与运算的两数各对应的二进位相与。 | (A & B) 结果为 12, 二进制为 0000 1100 |
| | 按位或运算符。其功能是参与运算的两数各对应的二进位相或 | (A | B) 结果为 61, 二进制为 0011 1101 |
^ | 按位异或运算符。其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。 | (A ^ B) 结果为 49, 二进制为 0011 0001 |
<< | 左移运算符。左移n位就是乘以2的n次方。其功能把左边的运算数的各二进位全部左移若干位,由右边的数指定移动的位数,高位丢弃,低位补0。 | A << 2 结果为 240,二进制为 1111 0000 |
>> | 右移运算符。右移n位就是除以2的n次方。其功能是把左边的运算数的各二进位全部右移若干位,由右边的数指定移动的位数。 | A >> 2 结果为 15,二进制为 0000 1111 |
2.5 赋值运算符
| 运算符 | 描述 | 实例 |
|---|---|---|
= | 简单的赋值运算符,将一个表达式的值赋给一个左值 | C = A + B 将 A + B 表达式结果赋值给 C |
+= | 相加后再赋值 | C += A 等于 C = C + A |
-= | 相减后再赋值 | C -= A 等于 C = C - A |
*= | 相乘后再赋值 | C *= A 等于 C = C * A |
/= | 相除后再赋值 | C /= A 等于 C = C / A |
%= | 求余后再赋值 | C %= A 等于 C = C % A |
<<= | 左移后赋值 | C <<= 2 等于 C = C << 2 |
>>= | 右移后赋值 | C >>= 2 等于 C = C >> 2 |
&= | 按位与后赋值 | C &= 2 等于 C = C & 2 |
^= | 按位异或后赋值 | C ^= 2 等于 C = C ^ 2 |
|= | 按位或后赋值 | C |= 2 等于 C = C | 2 |
2.6 其他运算符
| 运算符 | 描述 | 实例 |
|---|---|---|
| & | 返回变量存储地址 | &a 将给出变量的实际地址 |
| * | 指针变量。 | *a 是一个指针变量 |
3 条件判断
3.1 if
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
}
if statement; condition {
/* 在布尔表达式为 true 时执行 */
}
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
} else {
/* 在布尔表达式为 false 时执行 */
}
// 条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内
if num := 10; num % 2 == 0 { // 判断数字是否为偶数
fmt.Println(num, "偶数")
} else {
fmt.Println(num, "奇数")
}
// if ... else if ... else ...
package main
import "fmt"
func main() {
var age int
fmt.Println("Please input your age:")
fmt.Scan(&age)
if age < 60 {
fmt.Println("Too young too naive.")
} else if age < 90 {
fmt.Println("Not naive not bold.")
} else {
fmt.Println("Too old too bold.")
}
}
3.2 switch
switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case。使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果。
switch var1 {
case val1:
...
case val2:
...
default:
...
}
package main
import "fmt"
func main() {
/* 定义局部变量 */
var grade string = "D"
var marks int = 90
switch marks {
case 90: grade = "A"
case 80: grade = "B"
case 50,60,70 : grade = "C"
default: grade = "D"
}
switch {
case grade == "A" :
fmt.Printf("优秀!\n" )
case grade == "B", grade == "C" :
fmt.Printf("良好\n" )
case grade == "D" :
fmt.Printf("及格\n" )
case grade == "F":
fmt.Printf("不及格\n" )
default:
fmt.Printf("差\n" );
}
fmt.Printf("你的等级是 %s\n", grade );
}
3.3 select
每个 case 必须是一个通信操作,要么是发送要么是接收。select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。
select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/* 你可以定义任意数量的 case */
default : /* 可选 */
statement(s);
}
4 循环
/*
常规for循环
init: 为赋值表达式,给控制变量赋初值;
condition: 关系表达式或逻辑表达式,循环控制条件;
increment: 一般为赋值表达式,给控制变量增量或减量。
*/
for init; condition; increment { }
// 类似while循环
for condition { }
// 死循环
for { }
// for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环
for key, value := range oldMap {
newMap[key] = value
}
// 示例
package main
import "fmt"
func main() {
var num int = 0
for i:=1; i<=10; i++ {
num += i
if i == 1 {
fmt.Printf("Sum of 1 is:%d\n", num)
} else {
fmt.Printf("Sum of 1-%d is:%d\n", i, num)
}
}
}
// 9 X 9乘法表
func nineNine() {
const BLANK = " "
for i:=1; i<=9; i++ {
for j:=1; j<=i; j++ {
fmt.Printf("%d x %d = %d%s", i, j, i*j, BLANK)
}
println("")
}
}
4.1 循环控制语句
| 控制语句 | 描述 |
|---|---|
| break | 经常用于中断当前 for 循环或跳出 switch 语句 |
| continue | 跳过当前循环的剩余语句,然后继续进行下一轮循环 |
| goto | 将控制转移到被标记的语句 |
5 函数
// 函数定义
func function_name( [parameter list] ) [return_types] {
函数体
}
// 值传递
func swap(x, y int) int {
var temp int
temp = x /* 保存 x 的值 */
x = y /* 将 y 值赋给 x */
y = temp /* 将 temp 值赋给 y*/
return temp;
}
// 引用传递
func swap(x *int, y *int) {
var temp int
temp = *x /* 保存 x 地址上的值 */
*x = *y /* 将 y 值赋给 x */
*y = temp /* 将 temp 值赋给 y */
}
5.1 函数参数传递
| 传递类型 | 描述 |
|---|---|
| 值传递 | 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。go函数默认即为值传递 |
| 引用传递 | 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。 |
6 数组
Go 数组可以存储同一类型的数据,声明数组需要指定元素类型及元素个数,如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度。初始化数组中 { } 中的元素个数不能大于 [ ] 中的数字。
// 一维数组
var arrayName [SIZE] variable_type
// m * n 二维数组
var arrayName [ m ][ n ] variable_type
// 多维数组
var arrayName [SIZE1][SIZE2]...[SIZEN] variable_type
// 示例
func arrayTest() {
var arr = [2][3] string {
{"www.", "soloman.vip", "/zh"},
{"www.", "sunnie.fun", "/kr"},
}
for i, row := range arr {
for j, val := range row {
fmt.Printf("arr[%d][%d]=%s\t", i, j, val)
}
println("")
}
}
6.1 区分数组与切片
Go 数组是值,其长度是其类型的一部分,作为函数参数时,是值传递,函数中的修改对外部无影响。[ ] 不指定长度参数时表示是切片类型,Go 切片包含对底层数组内容的引用,作为函数参数时,类似于指针传递,函数中的修改对外部参数有影响。
7 指针
一个指针变量指向了一个值的内存地址。Go 语言的取地址符是 &,放到一个变量前就会返回相应变量的内存地址。在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。
var var_name *var-type
// 声明指针数组
var ptr [MAX]*int;
// 指向指针的指针
var ptr **int;
// 示例
func pointerTest() {
var a int = 10
var ptr *int
var pptr **int
ptr = &a
pptr = &ptr
fmt.Printf("Value of variable a is %d\n", a)
fmt.Printf("Address of variable a is %x\n", ptr)
fmt.Printf("Address of pointer ptr is %x\n", pptr)
}
当一个指针被定义后没有分配到任何变量时,它的值为 nil。nil 指针也称为空指针。nil 在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。
8 结构体
结构体中属性的首字母大写相当于 public,首字母小写相当于 private。这个 public 和 private 是相对于包(go 文件首行的 package 后面跟的包名)来说的。
type struct_variable_type struct {
member definition
member definition
...
member definition
}
// 定义结构体指针
var struct_pointer *struct_variable_type
struct_pointer = &struct_variable
// 示例
func structTest() {
type Websites struct {
name string
domain string
category string
age int
}
var soloman = Websites{"Soloman", "soloman.vip", "personal site", 6}
var psite *Websites
psite = &soloman
fmt.Printf("Site %s is a %s, \nwhose domain is %s, \nwhich was built %d year ago.",
psite.name, psite.category, psite.domain, psite.age,
)
}
9 切片
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大(类似Python的List)。一个切片在未初始化时为 nil,长度为 0。
// 切片无需指定长度
var identifier []type
// 通过make()函数创建切片,这里 len 是数组的长度并且也是切片的初始长度
var slice_name []type = make([]type, len)
slice_name := make([]type, len)
// 指定容量
make([]type, length, capacity)
nums1 := []int {0, 1, 2, 3}
var num2 = make([]int, 3, 5)
var num3 []int
10 字典
Map 是一种无序的键值对的集合,底层使用 hash 表来实现。
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
countryCapital := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}
11 接口
接口把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
// 示例
package main
import (
"fmt"
)
type Phone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
phone.call()
phone = new(IPhone)
phone.call()
}
12 并发和通道
Go 语言支持并发,通过 go 关键字来开启 goroutine 即可。goroutine 是轻量级线程,goroutine 的调度由 Golang 运行时进行管理。同一个程序中的所有 goroutine 共享同一个地址空间。
go 函数名( 参数列表 )
go function(x, y, z)
// 示例
package main
import(
"fmt"
"time"
)
func main() {
go saying("Hello")
go saying("Welcome to www.soloman.vip")
saying("Nice to meet you")
}
func saying(sentence string) {
for i:=0; i<5; i++ {
time.Sleep(1000 * time.Millisecond)
fmt.Println(sentence)
}
}
12.1 通道
通道(channel)是用来传递数据的一个数据结构。通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
ch := make(chan int)
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据,并把值赋给 v
// 创建带缓冲区的通道
ch := make(chan int, 100)