go基本语法
文章目录
数字类型
Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码(详情参见 二的补码 页面)。
Go 也有基于架构的类型,例如:int
、uint
和 uintptr
。
这些类型的长度都是根据运行程序所在的操作系统类型所决定的:
int
和uint
在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。uintptr
的长度被设定为足够存放一个指针即可。
Go 语言中没有 float 类型。(Go语言中只有 float32
和 float64
)没有 double 类型。
与操作系统架构无关的类型都有固定的大小,并在类型的名称中就可以看出来:
整数:
int8
(-128 -> 127)int16
(-32768 -> 32767)int32
(-2,147,483,648 -> 2,147,483,647)int64
(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
无符号整数:
uint8
(0 -> 255)uint16
(0 -> 65,535)uint32
(0 -> 4,294,967,295)uint64
(0 -> 18,446,744,073,709,551,615)
浮点型(IEEE-754 标准):
float32
(± 1e-45 -> ± 3.4 * 1e38)float64
(± 5 * 1e-324 -> 107 * 1e308)
int
型是计算最快的一种类型。
整型的零值为 0
,浮点型的零值为 0.0
。
float32
精确到小数点后 7 位,float64
精确到小数点后 15 位
你应该尽可能地使用 float64
,因为 math
包中所有有关数学运算的函数都会要求接收这个类型。
你可以通过增加前缀 0 来表示 8 进制数(如:077),增加前缀 0x 来表示 16 进制数(如:0xFF
),以及使用 e
来表示 10 的连乘(如: 1e3 = 1000,或者 6.022e23 = 6.022 x 1e23)。
你可以使用 a := uint64(0)
来同时完成类型转换和赋值操作,这样 a
的类型就是 uint64
。
Go 中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用
复数
两种类型:complex64;complex128
格式化说明符:%v
func main() {
var c1 complex64 = 5 + 10i
var c2 complex128 = 5 - 10i
var re float32 = 2
var im float32 = 3
var c = complex(re, im)
fmt.Println(c1)
fmt.Println(real(c1), imag(c2))
fmt.Printf("%v %v", c2, c)
}
输出
(5+10i)
5 -10
(5-10i) (2+3i)
格式化说明符
在格式化字符串里,%d
用于格式化整数(%x
和 %X
用于格式化 16 进制表示的数字),%g
用于格式化浮点型(%f
输出浮点数,%e
输出科学计数表示法),%0nd
用于规定输出长度为 n 的整数,其中开头的数字 0 是必须的。
%n.mg
用于表示数字 n 并精确到小数点后 m 位,除了使用 g 之外,还可以使用 e 或者 f,例如:使用格式化字符串 %5.2e
来输出 3.4 的结果为 3.40e+00
。
实例
package main
import "fmt"
func main() {
var n int16 = 34
var m int32
// compiler error: cannot use n (type int16) as type int32 in assignment
//m = n
m = int32(n)
fmt.Printf("32 bit int is: %d\n", m)
fmt.Printf("16 bit int is: %d\n", n)
}
输出
32 bit int is: 34
16 bit int is: 34
类型
package main
import (
"fmt"
"math"
)
func main() {
var a = "initial" //可以省略类型说明符
var b, c int = 1, 2
var d = true
var e float64
f := float32(e)
g := a + "foo"
fmt.Println(a, b, c, d, e, f)
fmt.Println(g)
const s string = "constant"
const h = 500000
const l = 3e20 / h
fmt.Println(s, h, l, math.Sin(h), math.Sin(l))
}
输出
initial 1 2 true 0 0
initialfoo
constant 500000 6e+14 0.17783120151825887 0.9538537219686818
变量(或常量)包含数据,这些数据可以有不同的数据类型,简称类型。使用 var
声明的变量的值会自动初始化为该类型的零值。类型定义了某个变量的值的集合与可对其进行操作的集合。
类型可以是基本类型,如:int
、float
、bool
、string
;结构化的(复合的),如:struct
、array
、切片 (slice)、map
、通道 (channel);只描述类型的行为的,如:interface
。
结构化的类型没有真正的值,它使用 nil
作为默认值
值得注意的是,Go 语言中不存在类型继承。
函数
可以是一个确定的类型
就是以函数作为返回类型。这种类型的声明要写在函数名和可选的参数列表之后,例如:
func FunctionName (a typea, b typeb) typeFunc
你可以在函数体中的某处返回使用类型为 typeFunc
的变量 var
:
return var
可拥有多种返回值
返回类型之间需要使用逗号分割,并使用小括号 ()
将它们括起来,如:
func FunctionName (a typea, b typeb) (t1 type1, t2 type2)
返回的形式:
return var1, var2
这种多返回值一般用于判断某个函数是否执行成功 (true/false) 或与其它返回值一同返回错误消息(详见之后的并行赋值)。
type关键字
使用 type
关键字可以定义你自己的类型,也可以定义一个已经存在的类型的别名,如:
type IZ int
这里并不是真正意义上的别名,因为使用这种方法定义之后的类型可以拥有更多的特性,且在类型转换时必须显式转换。
然后我们可以使用下面的方式声明变量:
var a IZ = 5
这里我们可以看到 int
是变量 a
的底层类型,这也使得它们之间存在相互转换的可能
如果你有多个类型需要定义,可以使用因式分解关键字的方式,例如:
type (
IZ int
FZ float64
STR string
)
每个值都必须在经过编译后属于某个类型(编译器必须能够推断出所有值的类型),因为 Go 语言是一种静态类型语言。
类型转换
go不存在隐式转换
只能在定义正确的情况下成功,例如从取值范围小到大;精度从小到大
具有相同底层类型的变量之间可以相互转换
a := 5.0
b := int(a)
常量
一个没有指定类型的常量被使用时,会根据其使用环境而推断出它所需要具备的类型。
换句话说,未定义类型的常量会在必要时刻根据上下文来获得相关类型。
const PI=3,1415
var n int
f(n + 5) // 无类型的数字型常量 “5” 它的类型在这里变成了 int
因为在编译期间自定义函数均属于未知,因此无法用于常量的赋值,但内置函数可以使用,如:len()
。
var c = "string"
const c = getnum() //未定义,所以是错误的
const c2 = len(s)
当常量赋值给一个精度过小的数字型变量时,可能会因为无法正确表达常量所代表的数值而导致溢出,这会在编译期间就引发错误。另外,常量也允许使用并行赋值的形式
const beef, two, c = "eat", 2, "veg"
const Monday, Tuesday, Wednesday, Thursday, Friday, Saturday = 1, 2, 3, 4, 5, 6
//常量可以进行枚举
const (
Monday, Tuesday, Wednesday = 1, 2, 3
Thursday, Friday, Saturday = 4, 5, 6
)
常量枚举
类似于c++当中的enum
关于iota:每遇到一个新的常量块或单个常量声明(每次遇到一个const关键字)iota都重置为0
const (
a=iota //0
b=iota //1
c=iota //2
d=5
e //5
)
// 赋值两个常量,iota 只会增长一次,而不会因为使用了两次就增长两次
const (
Apple, Banana = iota + 1, iota + 2 // Apple=1 Banana=2
Cherimoya, Durian // Cherimoya=2 Durian=3
Elderberry, Fig // Elderberry=3, Fig=4
)
// 使用 iota 结合 位运算 表示资源状态的使用案例
const (
Open = 1 << iota // 0001
Close // 0010
Pending // 0100
)
const (
_ = iota // 使用 _ 忽略不需要的 iota
KB = 1 << (10 * iota) // 1 << (10*1)
MB // 1 << (10*2)
GB // 1 << (10*3)
TB // 1 << (10*4)
PB // 1 << (10*5)
EB // 1 << (10*6)
ZB // 1 << (10*7)
YB // 1 << (10*8)
)
变量-var
变量的类型放在变量的名称后面,防止类型分不清
所有的内存在 Go 中都是经过初始化的,字符串指针为nil,其余的则为0
var a, b *int
var a int
var b bool
var str string
//全局变量
var (
a int
b bool
str string
)
不申明类型,go可以通过值来自行推断
var a = 15
var b = false
var str = "Go says hello to the world!"
在函数体内声明局部变量
这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 :=
可以高效地创建一个新的变量,称之为初始化声明。
a := 1
声明包级别的全局变量
var (
HOME = os.Getenv("HOME")
USER = os.Getenv("USER")
GOROOT = os.Getenv("GOROOT") //获取环境变量中的值
)
在 Go 语言中,指针属于引用类型,其它的引用类型还包括 slices,maps和 channel。被引用的变量会存储在堆中,以便进行垃圾回收,且比栈拥有更大的内存空间
一些省略写法
//并行赋值
//并行赋值也被用于当一个函数返回多个返回值时,比如这里的 `val` 和错误 `err` 是通过调用 `Func1` 函数同时得到:`val, err = Func1(var1)`。
a, b, c := 5, 7, "abc"
//交换值
a,b=b,a
抛弃值
_
实际上是一个只写变量,你不能得到它的值。这样做是因为你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值
_,b=5,7
易错
package main
import (
"fmt"
"./trans"
)
var twoPi = 2 * trans.Pi
func main() {
fmt.Printf("2*Pi = %g\n", twoPi) // 2*Pi = 6.283185307179586
}
输出
原因: ':='不用于全局变量的声明
G00
Bool值
可以使用 %t
来表示你要输出的值为布尔型。
var aVar = 10
aVar != 5 -> true
aVar != 10 -> false
位运算
条件:只能用于整数;且拥有等长位模式
%b
是用于表示位的格式化标识符
二元运算符
-
按位与
&
:有0则0 -
按位或
|
:有1则1 -
按位异或
^
:同为0,异为1 -
位清除
&^
:将指定位置上的值设置为0
。package main import "fmt" func main() { var x uint8 = 15 var y uint8 = 4 fmt.Printf("%08b\n", x &^ y); // 00001011 }
一元运算符
-
按位补足
^
:该运算符与异或运算符一同使用,即
m^x
,对于无符号x
使用 “全部位设置为 1” 的规则,对于有符号x
时使用m=-1
。例如:^10 = -01 ^ 10 = -11
-
位左移
<<
:- 用法:
bitP << n
。 bitP
的位向左移动n
位,右侧空白部分使用 0 填充;如果n
等于 2,则结果是 2 的相应倍数,即 2 的n
次方。例如:
- 用法:
-
位右移
>>
:- 用法:
bitP >> n
。 bitP
的位向右移动n
位,左侧空白部分使用 0 填充;如果n
等于 2,则结果是当前值除以 2 的 n 次方。
- 用法:
当希望把结果赋值给第一个操作数时,可以简写为 a <<= 2
或者 b ^= a & 0xffffffff
。
算术运算符
常见可用于整数和浮点数的二元运算符有 +
、-
、*
和 /
。
(相对于一般规则而言,Go 在进行字符串拼接时允许使用对运算符 +
的重载,但 Go 本身不允许开发者进行自定义的运算符重载)
/
对于整数运算而言,结果依旧为整数,例如:9 / 4 -> 2
。
取余运算符只能作用于整数:9 % 4 -> 1
。
整数除以 0 可能导致程序崩溃,将会导致运行时的恐慌状态(如果除以 0 的行为在编译时就能被捕捉到,则会引发编译错误);[第 13 章](file:///D:/self/资料/go/the-way-to-go/eBook/13.0.md) 将会详细讲解如何正确地处理此类情况。
浮点数除以 0.0
会返回一个无穷尽的结果,使用 +Inf
表示
你可以将语句 b = b + a
简写为 b += a
,同样的写法也可用于 -=
、*=
、/=
、%=
。
对于整数和浮点数,你可以使用一元运算符 ++
(递增)和 --
(递减)
同时,带有 ++
和 --
的只能作为语句,而非表达式,因此 n = i++
这种写法是无效的,其它像 f(i++)
或者 a[i]=b[i++]
这些可以用于 C、C++ 和 Java 中的写法在 Go 中也是不允许的。
在运算时 溢出 不会产生错误,Go 会简单地将超出位数抛弃
需要范围无限大的整数或者有理数(意味着只被限制于计算机内存),你可以使用标准库中的 big
包,该包提供了类似 big.Int
和 big.Rat
这样的类型
运算符优先级
优先级 运算符
7 ^ !
6 * / % << >> & &^
5 + - | ^
4 == != < <= >= >
3 <-
2 &&
1 ||
ASCII
var ch byte = 65
var ch byte = '\x41'
IF语句
判断条件不加括号,if之后必须加{},其余跟c类似
package main
import "fmt"
func main() {
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
}
for loop
go语言中没有while系列循环,只有for
package main
import "fmt"
func main() {
for {
fmt.Println("loop")
break
}
for j := 7; j < 9; j++ {
fmt.Println(j)
}
for n := 0; n < 5; n++ {
if n%2 == 0 {
fmt.Println(n)
continue
}
}
}
输出
loop
7
8
0
2
4
switch
变量可以不仅仅是数字,也可以是字符串
不需要加break
package main
import (
"fmt"
"time"
)
func main() {
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's befor noon")
default:
fmt.Println("It's afternoon")
}
}
输出
two
It's afternoon
切片
package main
import "fmt"
func main() {
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2])
fmt.Println("len:", len(s))
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s)
c := make([]string, len(s))
copy(c, s)
fmt.Println(c)
fmt.Println(s[2:5])
fmt.Println(s[:5])
fmt.Println(s[2:])
good := []string{"g", "o", "o", "d"}
fmt.Println(good)
}
输出
get: c
len: 3
[a b c d e f]
[a b c d e f]
[c d e]
[a b c d e]
[c d e f]
[g o o d]
map
随机顺序
package main
import "fmt"
func main() {
m := make(map[string]int) //类似于一个类型配另一个类型(可相同)
m["one"] = 1
m["two"] = 2
fmt.Println(m, len(m), m["one"], m["unknown"])
r, ok := m["unknown"]
fmt.Println(r, ok)
delete(m, "one")
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
}
输出
map[one:1 two:2] 2 1 0
0 false
map[one:1 two:2] map[one:1 two:2]
range
package main
import "fmt"
func main() {
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
sum += sum
if sum == 2 {
fmt.Println("index:", i, "num:", num)
}
}
fmt.Println(sum)
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v)
}
for k := range m {
fmt.Println("key", k)
}
}
输出
0
a A
b B
key b
key a
函数
package main
import "fmt"
func add(a int, b int) int { //数据类型后置
return a + b
}
func add2(a, b int) int {//类型相同可省略
return a + b
}
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
func main() {
res := add(1, 2)
fmt.Println(res)
v, ok := exists(map[string]string{"a": "A"}, "a")
fmt.Println(v, ok)
}
输出
3
A true
指针
对传入的参数作出修改
package main
import "fmt"
func add(a *int) {
*a += 2
}
func add2(b int) {
b += 2
}
func main() {
n := 5
add2(n)
fmt.Println(n)
add(&n)
fmt.Println(n)
}
输出
5
7
结构体
package main
import (
"fmt"
)
type user struct {
name string
password string
}
func checkpassword(u user, password string) bool {
return u.password == password
}
func check2(u *user, password string) bool {
return u.password == password
}
func main() {
a := user{name: "wang", password: "1024"}
b := user{"wang", "1024"}
c := user{name: "wang"}
c.password = "1024"
var d user
d.name = "wang"
d.password = "1024"
fmt.Println(a, b, c, d)
fmt.Println(checkpassword(a, "haha"))
fmt.Println(check2(&a, "haha"))
}
输出
{wang 1024} {wang 1024} {wang 1024} {wang 1024}
false
false
类成员函数
package main
import "fmt"
type user struct {
name string
password string
}
func (u *user) check2(password string) bool {
return u.password == password
}
func (u *user) reset(password string) { //从一个普通函数变成类成员函数
u.password = password
}
func main() {
a := user{name: "wang", password: "1024"}
a.reset("2048")
fmt.Println(a.check2("2048"))
}
输出
true