Hello world
package main // 包名与文件名没有关系。main 函数所在包名必须是 main
import ( // 导包
"fmt"
)
// main 函数,程序入口
func main() { // 左大括号必须和函数名在同一行
fmt.Println("Hello, golang!") // 分号可加可不加,和 JS 一样,一般不加
fmt.Printf("Hello, %s!", "golang") // Printf 格式化输出
}
Go 程序的入口是main
函数,包含main
函数的包名必须是main
。
Go 中很多代码规范是在编译器层面限定死的,比如“函数左大括号必须和函数名在同一行”,否则编译都无法通过。
变量
四种声明方式
// 方式一:声明一个变量,不赋值,会有默认值
var a int
fmt.Println("a = ", a) // 字符串用双引号,单个字符用单引号
fmt.Printf("type of a: %T\n", a) // 打印数据类型用 %T
// 方式二:声明一个变量,初始化一个值
var b float64 = 10 // go 没有 double,float64 就相当于 double
fmt.Println("b = ", b)
fmt.Printf("type of b: %T\n", b)
// 方式三:初始化时省略数据类型,编译器会根据初始化值自动判断类型
var c = 100.0
fmt.Println("c = ", c)
fmt.Printf("type of c: %T\n", c)
// 方式四:省略 var 关键词和数据类型(最常用)
d := "go"
fmt.Println("d = ", d)
fmt.Printf("type of d: %T\n", d)
:::warning 注:声明全局变量时不能使用方式四。 :::
不能重复声明
var c = 100.0
//var c = 10.0 // 不可重复声明
d := "go"
//d := "golang" // 这种方式同样不能重复声明变量
声明多个变量
var e, f int = 1, 2
var g, h = 3, "4"
var (
i int = 5
j = "6"
)
fmt.Println(e, f, g, h, i, j)
注意
变量类型不可修改。
Go 中局部变量声明后必须要使用,否则编译会报错。全局变量只声明不使用不会报错,但 IDE 一般也会提醒。
匿名变量
_
在 Go 中表示匿名变量(和 Python 相同),但在 Python 中只是一个约定,而在 Go 中变成了语法。
Python 中匿名变量可以当普通变量使用,而在 Go 中不行,比如 fmt.Println(_)
会报错。
匿名变量的作用主要是接收不需要用的数据。比如下面的例子:
a, b := func() // 这个函数返回两个值
假如a
的值后面的程序会用到,而b
用不到。其他语言这样写无所谓,但 Go 不行,因为 Go 的变量定义后必须要使用,此时就可以使用匿名变量:
a, _ := func()
常量
定义常量
const a int = 10 // 定义常量
const ( // 定义多个常量,可以分别初始化
b = 1
c = 2
d = 3
)
const ( // 若没有指定初始化的值,会复用 *上一行* 的 *表达式*
e = 1
f // 1
g = 2
h // 2
)
定义枚举类型
枚举类型本质上就是个常量组,不过我们只关注常量的名字,而不关注其值。
const ( // 定义枚举类型
A = iota // 初始化为 iota,每行的 iota 自增 1,第一行的 iota 默认为 0
B
C
)
const (
D = iota * 10 // D = 0 * 10 = 0
E // E = 1 * 10 = 10
F // F = 2 * 10 = 20
)
const (
AA, AB = iota, iota * 2 // 0, 0
BA, BB // 1, 2
CA, CB // 2, 4
DA, DB = iota, iota * 3 // 公式变了,但 iota 仍然累加。所以:3, 9
)
const (
G = 2
H = iota // 1。iota 可以认为是行数(从 0 开始的)
)
:::warning
iota
只能在const ()
中使用。
:::
基本数据类型
布尔
var _ bool // 1 byte
整数
var _ int8 // 1 byte, -128 ~ 127
var _ int16
var _ int32
var _ int64
var _ int // 在 32 位操作系统上相当于 int32,在 64 位操作系统上相当于 int64
var _ uint8 // 1 byte, 0 ~ 257
var _ uint16
var _ uint32
var _ uint64
var _ uint // 在 32 位操作系统上相当于 uint32,在 64 位操作系统上相当于 uint64
var _ = 0 // 不指定类型,默认为 int
fmt.Println("int64 能表示的最大值 =", math.MaxInt64, "; 能表示的最小值 =", math.MinInt64)
浮点数
var _ float32
var _ float64
fmt.Println("float32 能表示的最大值 =", math.MaxFloat32)
var flt = 0.0 // 默认为 float64,暂不知道是否与操作系统位数有关
byte 和 rune
byte
其实是uint8
的别称。专门再定义一个byte
是为了表示强调,因为uint8
比较特殊,刚好等于一个字节的大小。
下面是源码中byte
的定义:
// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8
rune
是int32
的别称。常用来处理中文字符。
下面是源码中rune
的定义:
// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32
字符
Go 中字符和字符串有区别,单引号表示字符,双引号表示字符串。
var c = 'c'
fmt.Printf("%T\n", c) // 结果:int32。所以说 Go 中字符实际上是 int32 类型,没有专门的 char 类型
fmt.Println(c) // 结果:99。直接打印会打印出 ascii 码值
fmt.Printf("%c\n", c) // 结果:c。这样才能打印出字符形式
复数
TODO
类型转换
基本类型的隐式类型转换
:::info
静态类型:变量定义后无法改变类型。
强类型:不支持隐式类型转换。
:::
Go 语言是强类型、静态类型语言。
Python 是强类型、动态类型语言。
C/C++ 是弱类型、静态类型语言。
Go 语言不支持 变量间 的隐式类型转换:
var a int = 1
var b float64
b = a // 报错。不支持(C/C++ 中这种情况是支持的)
var c int16 = 1
var d int32
d = c // 报错。连两种整型之间的转换都不支持
但常量转变量的隐式类型转换是简单支持的:
var e int = 5.0 // 简单浮点型 隐式转换为 整型
var f int = 5.1 // 报错。这种是不支持的
var g float32 = 5 // 整型 隐式转换为 浮点型
var h bool = 1 // 报错。不支持(C/C++ 中是支持的)
const A int = 1
var i float32 = A // 当然这种常量转变量也是不支持的
基本类型的强制类型转换
Go 允许底层结构相似的两个类型强制转换。
跟 C/C++ 类似,支持浮点型和整型互转,支持字符转换为整型、浮点型(因为字符底层实际上是int32
),不支持字符串和其他类型之间的转换。
与 C/C++ 不同的是,不支持布尔型和整型、浮点型之间的转换。
低精度转高精度是安全的,高精度转低精度会丢失精度。例如int
->float64
、int64
->int32
会丢失精度,这个很好理解。
var a2 int32 = 1
var b2 int64 = int64(a2)
var c2 float64 = float64(b2)
var e2 string = "2"
var f2 int = int(e2) // 报错。不支持
var g2 bool = bool(1) // 报错。不支持
var h2 int = int(g2) // 报错。不支持
type myInt int // 自定义一种类型,myInt 相当于 int 的别名
var i2 myInt = 1
var j2 int = i2 // 报错。即使是别名,也不支持隐式类型转换
var j2 int = int(i2) // 但由于底层结构相同,所以支持强制类型转换
var k2 string = "hello"
var l2 []rune = []rune(k2) // 字符串和切片的底层结构是相似的,所以可以强制转换
字符串与基本类型之间的转换
运算符
算数运算符、关系运算符、逻辑运算符、位运算符、赋值运算符等都和 C/C++ 差不多,只是不支持
++a
、--a
的写法(但支持a++
、a--
)。a := 1
a++
a--
++a // 不支持
--a // 不支持
fmt.Println(a++) // 不能和别的语句嵌套。其实这样确实更符合逻辑,因为 a++ 实际上是一条语句而非一个表达式
&
取地址、*
取内容 也和 C/C++ 相同。运算符优先级也和 C/C++ 差不多。
:::warning 运算符这一方面细节很多很多,没必要全搞清楚,平时用到的时候再查或者自己试试就知道了。 :::
控制台输入输出
输出:fmt.Printf()
、fmt.Println()
指定格式的方式部分与 C 的printf
相似,具体看老师笔记。
输入:fmt.Scanf()
、fmt.Scanln()
var name string
var age int
_, _ = fmt.Scanf("%s, %d", &name, &age) // 跟 C 的 scanf 基本相同
_, _ = fmt.Scanln(&name, &age) // 相当于 fmt.Scanf("%s %d", &name, &age)
流程控制
if
语句
基本写法:
num := 10
if num < 0 {
// some code
} else if num == 0 {
// some code
} else {
// some code
}
简化写法:
if n := 10; n < 0 {
// some code
} else if n == 0 {
// some code
} else {
// some code
}
注意:这种写法中,n
的作用域仅仅是if
语句块,不能在if
语句块外使用。
for
循环
Go 中没有while
和do while
,只有for
。
但for
的用法很多,功能很强大。
用法一(与 C/C++ 相似):
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
用法二(相当于while
):
sum := 0
i := 0
for i < 10 {
sum += i
i++
}
用法三(相当于for(;;)
,即无限循环):
sum := 0
i = 0
for {
if i >= 10 {
break
}
sum += i
i++
}
用法四(for-range,专门用来遍历字符串、数组、切片、map 等):
for index, char := range "hello,世界" { // 遍历字符串
fmt.Printf("index = %d, char = %c\n", index, char)
}
:::info 注:使用这种方式遍历字符串时,是可以自动处理中文的。 :::
break
、continue
不用说了,都一样。
goto
语句
Go 中是有goto
语句的,用法和 C/C++ 一样。
goto
语句能不用则不用,因为会影响程序结构,导致代码可读性差,难以维护。
goto
语句常见的两个使用场景:
- 跳出多重循环。
- 集中处理错误。
switch
语句
有些情况下使用switch
代替if
可以增强代码可读性。
由于 Go 中的switch
做了扩展,所以在 Go 中使用switch
比在 C/C++ 中更频繁。
做接口类型判断时经常用到switch
。
这种用法和 C/C++ 相似:
sex := 0
switch sex {
case 0: fmt.Println('女') // 只有一条语句的话可以写在一行里
case 1: // 多条语句可以这样写
fmt.Println('男')
//fallthrough // Go 的 switch 中默认不用在每个 case 最后写 break。如果要用到 C/C++ 中不写 break 时的那种继续执行下一个 case 的效果,可以用 fallthrough 关键字
default: { // 可以加大括号
fmt.Println("未知")
}
}
这个写法与 C/C++ 不一样:
sex := 0
switch sex {
case 0, 1:
fmt.Println("已知")
default:
fmt.Println("未知")
}
这是 C/C++ 中没有的扩展写法:
score := 90
switch {
case score >= 90: // case 不再只能是一个值,而可以是一个逻辑表达式
fmt.Println('A')
case 80 <= score && score < 90:
fmt.Println('B')
case 70 <= score && score < 80:
fmt.Println('C')
case 60 <= score && score < 70:
fmt.Println('D')
default:
fmt.Println('E')
}