Go语言中的关键字
Go 语言语法简明,所有关键字如下:
- 包:
import
package
- 程序实体声明和定义:
chan
const
func
interface
map
struct
type
var
- 程序流程控制:
go
select
break
case
continue
default
defer
else
fallthrough
for
goto
if
range
return
switch
- 内置的预声明常量,类型,函数
- 常量:
true
false
iota
nil
- 类型:
int
int8
int16
int32
int64
uint
uint8
uint16
uint32
uint64
uintptr
float32
float64
complex128
complex64
bool
byte
rune
string
error
- 函数:
make
len
cap
new
append
copy
close
delete
complex
real
imag
panic
recover
- 常量:
变量声明
-
var声明创建一个具体类型的变量,然后将其初始化
var name type = expression eg : var s string = "" var s = "hello" // 省略类型 var s string // 省略表达式
支持类型推导,所以类型和表达式可以省略一个,但是不能都省略:
如果类型省略,它的类型将由初始化表达式决定
-
如果表达式省略,其初始值对应于类型的零值。
- 对于数字是
0
- 对于布尔值式
false
- 对于字符串是
""
- 对于接口和引用类型(slice,指针,map,通道,函数)是
nil
- 对于一个数组和结构体,零值是其所有成员的零值
零值机制保障所有的变量是良好定义,所以Go里面不存在未初始化变量。
- 对于数字是
-
短变量
在函数中,可以采用短变量声明方式,这种方式既包含声明又包含赋值。name := expression eg: s := ""
name的类型由
expression
的类型决定,所以局部变量的声明和初始化主要使用短变量
短变量模式也不总是重新定义变量,也可能是退化赋值的操作:f, err := os.Open("/dev/random) ... buf := make([]byte,, 1024) n, err := f.Read(buf) // err 退化赋值,n新定义
退化赋值的前提条件是:最少有1个新变量被定义,且必须是同一作用域:
x := 100 fmt.Println(x) x := 200 // 没有新变量被定义 fmt.Println(x)
-
多变量赋值
多变量赋值时,首先计算出所有的右值,然后再完成赋值操作x, y := 1, 2 x, y := y+3, x+2
变量的生命周期
- 包级别变量的生命周期时整个程序的执行时间
作用域
- 语法块
- 全局块
- 文件级别
- 包级别
包初始化函数
除了 main
这个特殊的函数外,Go 语言还有一个特殊的函数——init
,通过它可以实现包级别的一些初始化操作。
init
函数没有返回值,也没有参数,它先于 main
函数执行,
package main
import "fmt"
func init() {
fmt.Println("init func")
}
func main() {
fmt.Println("main func")
}
// 输出:
init
main
一个包中可以有多个 init
函数,但是它们的执行顺序并不确定,所以如果你定义了多个 init
函数的话,要确保它们是相互独立的,所以这就要求这些 init
函数所做的事不要有顺序上的依赖。
符号优先级
Go的二元操作符分五大优先级,同级别的运算符满足左结合律。
*
,/
,%
,<<
,>>
,&
,&^
+
,-
,|
,^
==
,!=
,<
,<=
,>
,>=
&&
||
if 语句
if condition1 {
// ...
} else if condition2 {
// ...
} else {
// ...
}
Go语言里面对if/else
格式对齐要求很严格,如果需要if/else
组合,则需要在if
语句结束的大括号后面就跟上else
,else if
同理。
if 语句还增加了对初始化语句的支持,可定义局部变量或执行初始化函数
func main() {
s := "9"
if err := check(s); err != nil {
log.Fatalln(err)
}
...
}
func check(s string) error {
n, err := strconv.ParseInt(s, 10, 64)
if err != nil || n < 0 {
return errors.New("invalid number")
}
return nil
}
go语言没有三元表达式
Go语言不支持?:
,其原因是:
语言的设计者看到这个操作经常被用来创建难以理解的复杂表达式(多层嵌套)。在替代方案上,if-else
形式虽然较长,但无疑是更清晰的,一门语言只需要一个条件控制流结构,所以就没有提供三元表达式。
-
简单造个三元运算符
func IFTHEN(expr bool, a, b interface{}) interface{} { if expr { return a } return b }
缺点是返回了万能类型 interface{},每次使用都需要断言,不方便性能还差。
-
利用go 1.18 之后的泛型
func IFTHEN[T any](expr bool, a, b T) T { if expr { return a } return b }
这个版本看起来解决了上述的问题,但如果
var p *Person genderDesc := IFTHEN(p == nil, "未知", Any(p.gender == 1, "男", "女")) // panic
以上示例将无可避免地发生 panic ,原因是当
p == nil
成立时,p.gender
将发生 panic 。相信肯定会有人觉得奇怪,明明在访问 gender 字段前已经做了判空操作,怎么还会 panic?实际上,这就是函数怎么也无法代替三目运算符语言特性的地方:作为语言特性的三目运算符可以做到惰性计算,而函数做不到。函数 IFTHEN 有3个参数,函数在压栈前必须对它的实参先进行计算并获得相应的值,也就是说,
p == nil
和p.gender == 1
都会被求值。
for 语句
for 语句时Go里面地唯一循环语句。
for init; condition; post {
//...
}
eg:
for i := 1; i < 5; i++ {
fmt.Println(i);
}
initialization 和 post省略就是传统的while
循环。
for condition {
//...
}
三个部分都可以省略,那就是无限循环。
for {
break
}
Go 语言范围语句(range)
Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、字符串、通道(channel) 、集合(map)的元素。
-
在数组和切片中它返回元素的索引和索引对应的值:
nums := []int{1, 2, 3} for i, num := range nums { fmt.Println("index: value", i, num) } // 输出: // index: value 0 1 // index: value 1 2 // index: value 2 3
或者直接只有索引:
nums := []int{1, 2, 3} for i := range nums { fmt.Println("index: value", i, nums[i]) } // 输出: // index: value 0 1 // index: value 1 2 // index: value 2 3
-
遍历string
按照rune的方式遍历,rune可以理解为是Unicode字符// 返回索引和Unicode字符 for index, c := range s { fmt.Printf("%d: [%c]\t", index, c) } // 输出: 0: [中] 3: [国]
这意味着:index 可能是不连续的。
-
遍历map返回 key,value
for name, age : range ages { fmt.Printf("%s\t%d\n", name, age) }
-
作用于channel
c := make(chan string, 2) c <- "hello" c <- "world" time.AfterFunc(time.Microsecond, func() { close(c) } for e := range c { fmt.Printf("element: %s\n", e) }
range 会阻塞等待 channel 中的数据,直到 channel 被关闭。
同时,如果 range 作用域值为nil
的 channel 时,则会永久阻塞。 -
rang 遍历是复制元素
type Foo struct { Num int } func main() { fooSlice := []Foo{{1}, {2}} fmt.Println(fooSlice) // 输出:[{1} {2}] for _, foo := range fooSlice { foo.Num = 100 } fmt.Println(fooSlice) // 输出: [{1} {2}] }
range 遍历是将每一个元素复制给临时变量,所以需要修改变量时,需要使用索引遍历方式
for i := range fooSlice { fooSlice[i].Num = 100 // 更改生效 }
-
range 语句和 for 迭代的效率
switch 语句
go 语言的switch-case 和其他语言不同,默认是加了 break 语句。
switch i {
case 0:
fmt.Println(0)
case 1:
fmt.Println(1)
case 2:
fmt.Println(2)
default:
}
// 相当于 C/C++ 中
switch i {
case 0:
fmt.Println(0)
break
case 1:
fmt.Println(1)
break
case 2:
fmt.Println(2)
break
default:
}
- fallthrough
如果真的想执行完一个 case 接着执行下一个 case,只要使用fallthrough
关键字就可以了:switch i { case 0: fmt.Println(0) fallthrough case 1: fmt.Println(1) case 2: fmt.Println(2) default: }
goto 语句
continue 语句
break语句
++
、--
操作符
go语言中的++
、--
操作符都是后置操作符,必须跟在操作数后面,并且它们没有返回值。
x := i++ // 错误,无返回值。
unsafe.Sizeof unsafe.Alignof unsafe.Offsetof
var x struct {
a bool
b int16
c []int
}
- unsafe.Sizeof
返回在内存中占用字节的长度unsafe.Sizeof(x)
- unsafe.Alignof
返回参数类型要求的对齐方式unsafe.Alignof(x)
- unsafe.Offsetof
结算成员相对于结构体的偏移量unsafe.Offsetof(x.b)
字符串格式化常用动词
动词 | 功能 |
---|---|
%v | 按值的本来值输出 |
%+v | 在 %v 基础上,对结构体字段名和值进行展开 |
%#v | 输出 Go 语言语法格式的值 |
%T | 输出 Go 语言语法格式的类型和值 |
%% | 输出 % 本体 |
%b | 整型以二进制方式显示 |
%o | 整型以八进制方式显示 |
%d | 整型以十进制方式显示 |
%x | 整型以十六进制方式显示 |
%X | 整型以十六进制、字母大写方式显示 |
%U | Unicode 字符 |
%f | 浮点数 |
%p | 指针,十六进制方式显示 |