一、数据类型
类型 | 说明 |
---|---|
布尔类型 | 布尔类型的值只可以是常量true 或者 false |
数字类型 | 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码 |
字符串类型 | 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本 |
派生类型: | 指针类型(Pointer)、数组类型、结构化类型(struct)、Channel类型、函数类型、切片类型、接口类型(interface)、Map类型 |
数字类型
类型 | 说明 |
---|---|
unit8 | 无符号 8 位整型 (0 到 255) |
uint16 | 无符号 16 位整型 (0 到 65535) |
uint32 | 无符号 32 位整型 (0 到 4294967295) |
uint64 | 无符号 64 位整型 (0 到 18446744073709551615) |
int8 | 有符号 8 位整型 (-128 到 127) |
int16 | 有符号 16 位整型 (-32768 到 32767) |
int32 | 有符号 32 位整型 (-2147483648 到 2147483647) |
int64 | 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807) |
浮点类型
类型 | 说明 |
---|---|
float32 | IEEE-754 32位浮点型数 |
float64 | IEEE-754 64位浮点型数 |
float32 | 32 位实数和虚数 |
float32 | 64 位实数和虚数 |
其他数字类型
类型 | 说明 |
---|---|
byte | 类似 uint8 |
rune | 类似 int32 |
uint | 32 或 64 位 |
int | 与 uint 一样大小 |
uintptr | 无符号整型,用于存放一个指针 |
二、变量
Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字
声明变量的三种方式
使用 var 关键字声明,指定变量名称和变量类型
var v_name v_type
v_name = value
# 例:
var name string
name = "test"
# 一下几种类型不进行赋值,有自己默认值
package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
# 输出结果:
0 0 false ""
根据值自行判断变量类型
var v_name = value
var x = "test" // 此时x 数据类型为string
省略关键字 var ,使用 := 赋值,变量类型自动判断
v_name := v_name
x := 1 // 此时x 数据类型为 int
# 测试不指定类型自动判断
package main
import (
"fmt"
"reflect"
)
func main() {
x := false
x1 := "aaa"
x2 := 1
x3 := 3.14
fmt.Println("x type:", reflect.TypeOf(x))
fmt.Println("x1 type:", reflect.TypeOf(x1))
fmt.Println("x2 type:", reflect.TypeOf(x2))
fmt.Println("x3 type:", reflect.TypeOf(x3))
}
# 输出结果为:
x type: bool
x1 type: string
x2 type: int
x3 type: float64
说明:声明变量时,变量名称不可重复(局部变量,即方法内部使用的变量),全局变量可以只声明不适用,局部变量声明之后必须要使用,否则编译报错(declared and not used)
多变量声明
//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3
var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断
vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误
// 这种因式分解关键字的写法一般用于声明全局变量
var (
vname1 v_type1
vname2 v_type2
)
两个变量交换值
# 两个变量的数据类型必须一直才能使用
a, b = b, a
空白标识符()(用于抛弃值,比如一个函数/方法返回多个值,使用 "" 抛弃不需要的值)
package main
import "fmt"
func main() {
_,numb,strs := numbers() //只获取函数返回值的后两个
fmt.Println(numb,strs)
}
//一个可以返回多个值的函数
func numbers()(int,int,string){
a , b , c := 1 , 2 , "str"
return a,b,c
}
# 输出结果:
2 str
值类型和引用类型
值类型
所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值。当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝。可以通过 &i 来获取变量 i 的内存地址,值类型的变量的值存储在栈中。
内存地址会根据机器的不同而有所不同,相同的程序在不同的机器上执行后也会有不同的内存地址。因为每台机器可能有不同的存储器布局,并且位置分配也可能不同。
引用类型
更复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。
一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。这个内存地址为称之为指针,这个指针实际上也被存在另外的某一个字中。同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。
如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。
三、常量
常量是一个简单值的标识符,在程序运行时,不会被修改的量,常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型
定义格式
const name [type] = value // 数据类型可以省略,编译器会自动判断
# 例:
const a string = "test"
const b = 1
# 多个常量声明
const a, b, c = v1, v2, v3
# 常量用作枚举
const (
unknown = 0
female = 1
male = 2
)
常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过
package main
import "unsafe"
const (
a = "abc"
b = len(a)
c = unsafe.Sizeof(a)
)
func main(){
println(a, b, c)
}
# 运行结果:
abc 3 16
iota
iota,特殊常量,可以认为是一个可以被编译器修改的常量。iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
# iota 用法示例:const同时声明多个常量时,如果省略了值则表示和上面一行的值相同
# 左移:比如说 1<<2 ,把十进制 1 转成二进制,结果为 1,左移两位为 100,转成十进制为 4 (即 1*(2^2))
package main
import "fmt"
func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1 (e没有进行赋值使用上一个常量的值)
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
# 运行结果:
0 1 2 ha ha 100 100 7 8
# 左移: x<<n == x(2^n)
package main
import "fmt"
const (
i=1<<iota // i = 1*(2^0)
j=3<<iota // j = 3*(2^1)
k // k = 3*(2^2)
l // l = 3*(2^3)
)
func main() {
fmt.Println("i=",i)
fmt.Println("j=",j)
fmt.Println("k=",k)
fmt.Println("l=",l)
}
# 运行结果:
i= 1
j= 6
k= 12
l= 24
四、运算符
运算符用于在程序运行时执行数学或逻辑运算
golang 内置的运算符:
- 算数运算符
- 关系运算符
- 逻辑运算符
- 位运算符
- 赋值运算符
- 其他运算符
定义 a := 10,b := 20
算数运算符
运算符 | 描述 | 实例 |
---|---|---|
+ | 相加 | a + b = 30 |
- | 相减 | a - b = -10 |
* | 相乘 | a * b = 200 |
/ | 相除 | 例1: a / b = 0 例2: b / a = 2 如果结果为有余数,取整数部分 |
% | 取余数 | 例1: b % a = 0 例2: a % b = 10 |
++ | 自增 | a++ 结果:a为11 |
– | 自减 | a-- 结果:a为9 |
自增、自减不能用来赋值,不能使用 a = a++ 和 b = b–
关系运算符
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个值是否相等,如果相等返回 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 |
逻辑运算符
定义 x := true, y := false
运算符 | 描述 | 实例 |
---|---|---|
&& | 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False | x && y 结果为 false |
|| | 逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False | x // y 结果为 true |
! | 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True | !(x && y) 结果为 true |
位运算符
定义 z1 : 60, z2 = 13
A = 0011 1100
B = 0000 1101
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001
运算符 | 描述 | 实例 |
---|---|---|
& | 按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与 | (z1 & z2) 结果为 12, 二进制为 0000 1100 |
| | 按位或运算符"|"是双目运算符。 其功能是参与运算的两数各对应的二进位相或 | (z1 | z2) 结果为 61, 二进制为 0011 1101 |
^ | 按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1 | (A ^ B) 结果为 49, 二进制为 0011 0001 |
<< | 左移运算符"<<“是双目运算符。左移n位就是乘以2的n次方。 其功能把”<<“左边的运算数的各二进位全部左移若干位,由”<<"右边的数指定移动的位数,高位丢弃,低位补0 | z1 << 2 结果为 240 ,二进制为 1111 0000 |
>> | 右移运算符">>“是双目运算符。右移n位就是除以2的n次方。 其功能是把”>>“左边的运算数的各二进位全部右移若干位,”>>"右边的数指定移动的位数 | z1 >> 2 结果为 15 ,二进制为 0000 1111 |
位运算符对整数在内存中的二进制位进行操作。
下表列出了位运算符 &, |, 和 ^ 的计算:
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
赋值运算符
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,将一个表达式的值赋给一个左值 | 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 |
其他运算符
运算符 | 描述 | 实例 |
---|---|---|
& | 返回变量存储地址 | &a 将给出变量的实际地址。 |
* | 指针变量 | *a 是一个指针变量 |
指针变量 * 和地址值 & 的区别:指针变量保存的是一个地址值,会分配独立的内存来存储一个整型数字。当变量前面有 * 标识时,才等同于 & 的用法,否则会直接输出一个整型数字
func main() {
var a int = 4
var ptr *int
ptr = &a
println("a的值为", a); // 4
println("*ptr为", *ptr); // 4
println("ptr为", ptr); // 824633794744
}
运算符优先级
下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低
优先级 | 运算符 |
---|---|
5 | * / % << >> & &^ |
4 | + - |
3 | == != < <= > >= |
2 | && |
1 | || |
五、条件语句
Go 没有三目运算符,所以不支持 ?: 形式的条件判断
语句 | 描述 |
---|---|
if | 由一个布尔表达式后紧跟一个或多个语句组成 |
if …else | if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行 |
if嵌套 | 可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句 |
switch | 用于基于不同条件执行不同动作 |
select | 类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行 |
if
if 布尔表达式 {
// 当布尔表达式为true时,执行一些业务逻辑
}
# 例:
i := 8
if i > 5 {
fmt.Printf("i 大于 5")
}
if … else
if 布尔表达式 {
// 布尔表达式为true时,执行一些逻辑
} else {
// 布尔表达式不为true时,执行一些逻辑
}
# 例:
s:= "success"
if s == "success" {
fmt.Printf("运行成功")
} else {
fmt.Printf("运行失败")
}
if 嵌套
if 布尔表达式1 {
// 布尔表达式1为true时,执行一些逻辑
if 布尔表达式2 {
// 布尔表达式1、2为true时,执行一些逻辑
}
}
# 例:
i := 10
if i > 5 {
fmt.Println("i 大于 5")
if i < 20 {
fmt.Println("i 小于 20")
}
}
switch
switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止
switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加 break。
switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough
switch s {
case c1 :
// 当s 为c1时,执行一些逻辑
case c2 :
// 当s 为c2时,执行一些逻辑
default :
// 当s 不满足c1 和c2 时执行default中逻辑
}
# 例:
// 1:男;2:女;
sex := 1
switch sex {
case 1 :
fmt.Println("性别:男")
case 2 :
fmt.Println("性别:女")
default :
fmt.Println("性别保密")
}
# Type Switch
# switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型
# 格式:
switch x.(type){
case type:
statement(s);
case type:
statement(s);
/* 你可以定义任意个数的case */
default: /* 可选 */
statement(s);
}
# 例:
package main
import "fmt"
func main() {
var x interface{}
switch i := x.(type) {
case nil:
fmt.Printf(" x 的类型 :%T",i)
case int:
fmt.Printf("x 是 int 型")
case float64:
fmt.Printf("x 是 float64 型")
case func(int) float64:
fmt.Printf("x 是 func(int) 型")
case bool, string:
fmt.Printf("x 是 bool 或 string 型" )
default:
fmt.Printf("未知型")
}
}
# 运行结果:
x 的类型 :<nil>
# fallthrough
# 使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true
# 例:
package main
import "fmt"
func main() {
switch {
case false:
fmt.Println("1、case 条件语句为 false")
fallthrough
case true:
fmt.Println("2、case 条件语句为 true")
fallthrough
case false:
fmt.Println("3、case 条件语句为 false")
fallthrough
case true:
fmt.Println("4、case 条件语句为 true")
case false:
fmt.Println("5、case 条件语句为 false")
fallthrough
default:
fmt.Println("6、默认 case")
}
}
# 运行结果:
2、case 条件语句为 true
3、case 条件语句为 false
4、case 条件语句为 true
select
select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。
select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。
# 语法如下:
select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/* 你可以定义任意数量的 case */
default : /* 可选 */
statement(s);
}
- 每个 case 都必须是一个通信
- 所有 channel 表达式都会被求值
- 所有被发送的表达式都会被求值
- 如果任意某个通信可以进行,它就执行,其他被忽略。
- 如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。否则:
如果有 default 子句,则执行该语句。
如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求 值。
六、循环语句
当执行某些重复性逻辑,可以使用循环
循环类型 | 描述 |
---|---|
for循环 | 重复执行语句块 |
循环嵌套 | 在 for 循环中嵌套一个或多个 for 循环 |
for 循环
三种形式:
for init; condition; increment {
// 执行重复性逻辑
}
init:初始化一个变量
condition:执行循环体的条件,条件满足时才进入循环体
increment:对变量的值进行增加或者减少
# 例:
package man
import "fmt"
func main() {
for i := 0; i < 10; i++ {
fmt.Println("hello", i)
}
}
# init 和 increment可以省略,也可以这样写
i := 0
for ; i < 10; {
i += 1
fmt.Println("hello", i)
}
for condition {
// 当condition 条件满足,执行循环体
}
# 例:
package man
import "fmt"
func main {
i := 0
for i < 10 {
i++
fmt.Println("hello", i)
}
}
# 不指定条件,无限循环
for {
//
}
# 例:
package man
import "fmt"
func main {
i := 0
for {
i++
fmt.Println("hello", i)
}
}
for-each range 循环
这种格式的循环可以对字符串、数组、切片等进行迭代
for index, value := range 遍历的字符串/数组/切片 {
// index为遍历对象的下标;value为下表对应的值
}
# 例:
package man
import "fmt"
func main {
names:= []string{"小明", "小白"}
for index, name := range names {
fmt.Println("下标为", index, " 的姓名为:", name)
}
}
七、函数
函数概念和定义
函数是基本的代码块,用于执行一个任务。
Go 语言最少有个 main() 函数。
你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。
函数声明告诉了编译器函数的名称,返回类型,和参数。
func function_name ([parameter_list]) [return_types] {
// 函数体
// func:函数由 func 开始声明
// function_name 函数名
// parameter_list 函数参数,0-n个
// return_types 函数返回数据类型,0-n个
}
# 例:求和函数
func sum(n1 int, n2 int) int {
return n1 + n2
}
# 例:返回多个参数
func swap(s1 string, s2 string) (string,string) {
return s2,s1
}
参数传递
- 值传递:值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数
- 引用传递:引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数
- 默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
函数闭包
Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数可以直接使用函数内的变量,不必申明。
package main
import "fmt"
// getSequence 返回类型为函数,内部函数的作用为变量加1,并且可以直接使用局部变量 i
func getSequence() func() int {
i:=0
return func() int {
i+=1
return i
}
}
func main(){
/* nextNumber 为一个函数,函数 i 为 0 */
nextNumber := getSequence()
/* 调用 nextNumber 函数,i 变量自增 1 并返回 */
fmt.Println(nextNumber())
fmt.Println(nextNumber())
fmt.Println(nextNumber())
/* 创建新的函数 nextNumber1,并查看结果 */
nextNumber1 := getSequence()
fmt.Println(nextNumber1())
fmt.Println(nextNumber1())
}
# 运行结果:
1
2
3
1
2