一、Go基础
1.1、基本数据类型
| 序号 | 类型和描述 |
|---|---|
| 1 | 布尔型 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。 |
| 2 | 数字类型 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。 |
| 3 | 字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。 |
| 4 | 派生类型: 包括:(a) 指针类型(Pointer)(b) 数组类型(c) 结构化类型(struct)(d) Channel 类型(e) 函数类型(f) 切片类型(g) 接口类型(interface)(h) Map 类型 |
1.2、语言变量
Go 语言变量名由字母、数字、下划线组成,其中==首个字符==不能为数字。
声明如下:
var identifier type
一次声明多个变量:
var identifier1, identifier2 type
对于一个变量,如果声明的时候没有指定初始值,会以默认值的形式赋值。
其中:
- 数值类型:
0 - 布尔类型:
false - 字符串:
"" - 一下几种类型为
nil:var a *intvar a []intmap集合chan通道- 函数变量,
var a func(string) int - 接口,
var a error
当然,还可以使用:= 实现声明并赋值,注意,这个变量之前不能出现过,不然会报错。
==Tips== : 在Java、C++ 中,要做到交换两个基本数据类型的值,我们通常要定义一个swap函数,如下:
public void swap(int a, int b) {
int t = a;
a = b;
b = t;
}
实现交换,但是在Go 语言中,我们无需这么做,只需要下面一行代码即可:
a, b = b, a
解释: 首先会把a,b的值取出来,依次放在第二个,第一个位置,然后按顺序赋值。
值引用与类型引用
Go语言中,基本数据类型(除了map)之外都是值类型,更复杂的数据通常会需要使用==多个字==,这些数据一般==使用引用类型==保存。
常量
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
声明如下:
const identifier [type] = value
iota,特殊常量,可以认为是一个可以被编译器修改的常量。
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。可以用作枚举值。
const (
a = iota
b = iota
c = iota
)
第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:
const (
a = iota
b
c
)
进阶用法:
package main
import "fmt"
func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
1.3、语言运算符
1.3.1、算术运算符
| 运算符 | 描述 | 实例 |
|---|---|---|
| + | 相加 | A + B 输出结果 30 |
| - | 相减 | A - B 输出结果 -10 |
| * | 相乘 | A * B 输出结果 200 |
| / | 相除 | B / A 输出结果 2 |
| % | 求余 | B % A 输出结果 0 |
| ++ | 自增 | A++ 输出结果 11 |
| -- | 自减 | A-- 输出结果 9 |
1.3.2、关系运算符
| 运算符 | 描述 | 实例 |
|---|---|---|
| == | 检查两个值是否相等,如果相等返回 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 |
1.3.3、逻辑运算符
| 运算符 | 描述 | 实例 |
|---|---|---|
| && | 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。 | (A && B) 为 False |
| || | 逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。 | (A || B) 为 True |
| ! | 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 | !(A && B) 为 True |
1.3.4、位运算符
| 运算符 | 描述 | 实例 |
|---|---|---|
| & | 按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。 | (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 |
==Note==:Go语言中位运算是以二进制补码的方式计算的。
1.3.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 |
1.3.6、其他运算符
| 运算符 | 描述 | 实例 |
|---|---|---|
| & | 返回变量存储地址 | &a; 将给出变量的实际地址。 |
| * | 指针变量。 | *a; 是一个指针变量 |
1.3.7、运算符优先级
有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:
| 优先级 | 运算符 |
|---|---|
| 5 | * / % << >> & &^ |
| 4 | + - | ^ |
| 3 | == != < <= > >= |
| 2 | && |
| 1 | || |
1.3.8、笔记
指针变量 ***** 和地址值 & 的区别:
指针变量保存的是一个地址值,会分配独立的内存来存储这个整型数字,当变量前面有*标识的时候,才等同于&的用法,也就是取到实际的值,否则会输出地址值。
func main() {
var a int = 4
var ptr *int
ptr = &a
println("a的值为", a); // 4
println("*ptr为", *ptr); // 4
println("ptr为", ptr); // 824633794744
}
1.4、条件运算符
| 语句 | 描述 |
|---|---|
| if 语句 | if 语句 由一个布尔表达式后紧跟一个或多个语句组成。 |
| if...else 语句 | if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。 |
| if 嵌套语句 | 你可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句。 |
| switch 语句 | switch 语句用于基于不同条件执行不同动作。 |
| select 语句 | select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。 |
==Note==
1、if语句用作返回值的时候,要保证外至少有一个返回值。
如下面代码在某些编辑器里面可能会报编译错误
func gt3(n int) bool {
if n > 3 {
return true
} else {
return false
}
}
2、else 语句应该紧跟在 if 语句的 } 后面,不能换行
下面代码会报编译错误
func gt3(n int) bool {
if n > 3 {
return true
}
else {
return false
}
}
1.5、循环语句
Go 语言的 For 循环有 3 种形式,只有其中的一种使用分号。和 C 语言的 for 一样:
for init; condition; post { }
Go语言只有for循环,没有while关键字,下面效果等同于while循环
for condition { }
死循环:
for {}
1.5.1、循环控制语句
| 控制语句 | 描述 |
|---|---|
| break 语句 | 经常用于中断当前 for 循环或跳出 switch 语句 |
| continue 语句 | 跳过当前循环的剩余语句,然后继续进行下一轮循环。 |
| goto 语句 | 将控制转移到被标记的语句。 |
1.6、函数
函数定义格式如下:
func function_name( [parameter list] ) [return_types] {
函数体
}
1.6.1、函数参数
| 传递类型 | 描述 |
|---|---|
| 值传递 | 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。 |
| 引用传递 | 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。 |
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
1.6.2、函数用法
1、函数作为实参
Go 语言可以很灵活的创建函数,并作为另外一个函数的实参。下面用这个特性实现回调。
package main
import "fmt"
// 声明一个函数类型
type cb func(int) int
func testCallBack(x int, f cb) {
f(x)
}
func callBack(x int) int {
fmt.Printf("我是回调1, x:%v\n", x)
return x
}
func main() {
testCallBack(1, callBack)
testCallBack(2, func(x int) int {
fmt.Printf("我是回调2, x:%v\n", x)
return x
})
}
2、匿名函数(闭包)
Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。匿名函数是一种==没有函数名==的函数,通常用于在==函数内部==定义函数,或者==作为函数参数==进行传递。
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())
}
相信不少人疑惑这里 为什么会自增。因为闭包允许函数访问并修改其外部作用域中的变量,即使外部函数已经返回。在这个例子中,getSequence 函数返回的匿名函数==持有==对 i 的引用,因此每次调用这个匿名函数时,i 的值都会增加。
虽然返回值没有被显式地接收,但它被 fmt.Println 函数接收并打印出来。fmt.Println 函数会接收传入的参数并将其打印到标准输出。
nextNumber := getSequence()调用getSequence函数,该函数返回一个匿名函数。这个匿名函数持有对getSequence函数内部i变量的引用。fmt.Println(nextNumber())调用nextNumber函数(即返回的匿名函数),这会使i自增1并返回新的值,然后fmt.Println打印这个值。- 每次调用
nextNumber()都会执行相同的操作:i增加1,返回新的值,并打印出来。
因此,尽管返回值没有被显式地接收,它实际上被 fmt.Println 接收并用于打印。这就是为什么每次调用 nextNumber() 时,i 都会加1并打印出不同的值。
1.6.3、方法
Go 语言中同时有函数和方法。一个方法就是一个==包含了接受者的==函数,接受者可以是==命名类型==或者==结构体类型==的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。
格式如下:
func (variable_name variable_data_type) function_name() [return_type]{
/* 函数体*/
}
示例:
package main
import (
"fmt"
)
/* 定义结构体 */
type Circle struct {
radius float64
}
func main() {
var c1 Circle
c1.radius = 10.00
fmt.Println("圆的面积 = ", c1.getArea())
}
//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c.radius * c.radius
}
深度解释:
在Go语言中没有面向对象,但是Java、C++等语言中,实现类的方法的做法都是编译器==隐式==的给函数加一个 指针,而在Go里面,这个指针需要我们显示的指定出来,也就是上面的接受者。
例如,在C++中是这样的:
class Circle {
public:
float getArea() {
return 3.14 * radius * radius;
}
private:
float radius;
}
// 其中 getArea 经过编译器处理大致变为
float getArea(Circle *const c) {
...
}
而在Go语言中如下:
func (c Circle) getArea() float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c.radius * c.radius
}
1.7、数组
Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:
var arrayName [size]dataType
初始化:
var numbers [5]int // 全部是0,也就是int的默认值
var numbers = [5]int{1, 2, 3, 4, 5}
numbers := [5]int{1, 2, 3, 4, 5}
如果数组长度不确定,可以使用... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
// or
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
1.8、指针
Go 语言的取地址符是 ,放到一个变量前使用就会返回相应变量的内存地址。==指针==变量指向了一个==值的内存地址==。在使用指针前你需要声明指针。指针声明格式如下:
var var_name *var-type
在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。
package main
import "fmt"
func main() {
var a int= 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */
ip = &a /* 指针变量的存储地址 */
fmt.Printf("a 变量的地址是: %x\n", &a )
/* 指针变量的存储地址 */
fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip )
}
1、指针数组
数组的每一个元素值都是一个指针。
var ptr [MAX]*int;
示例:
package main
import "fmt"
const MAX int = 3
func main() {
a := []int{10,100,200}
var i int
var ptr [MAX]*int;
for i = 0; i < MAX; i++ {
ptr[i] = &a[i] /* 整数地址赋值给指针数组 */
}
for i = 0; i < MAX; i++ {
fmt.Printf("a[%d] = %d\n", i,*ptr[i] )
}
}
==Note==:使用Range循环遍历创建的时候要注意,不能直接将取出来的值赋值给指针数组。
const max = 3
func main() {
number := [max]int{5, 6, 7}
var ptrs [max]*int //指针数组
//将number数组的值的地址赋给ptrs
for i, x := range &number {
ptrs[i] = &x
}
for i, x := range ptrs {
fmt.Printf("指针数组:索引:%d 值:%d 值的内存地址:%d\n", i, *x, x)
}
}
// out:
//指针数组:索引:0 值:7 值的内存地址:824634204304
//指针数组:索引:1 值:7 值的内存地址:824634204304
//指针数组:索引:2 值:7 值的内存地址:824634204304
可以发现,内存地址都是一样的,为什么呢?
这个问题是range循环的实现逻辑引起的。跟for循环不一样的地方在于range循环中的x变量是临时变量。range循环只是将值拷贝到x变量中,这个x变量内存地址不会变,因此内存地址都是一样的。
1.9、结构体
Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。定义格式如下:
type struct_variable_type struct {
member definition
member definition
...
member definition
}
声明格式:
variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
访问结构体成员:
结构体.成员名
1.10、切片
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
定义切片:
var identifier []type
或者使用make函数:
var slice1 []type = make([]type, len)
// or
slice1 := make([]type, len)
同时还可以指定容量,capacity是可选参数:
make([]T, lengthm capacity)
初始化切片:
s :=[] int {1,2,3 }
// 从数组中获取
s := arr[startIndex:endIndex] // 类似python
函数
切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少.
切片截取
可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound]
比如上面的s, s[1:] 可以获取除了第一个之外的所有元素。
==Note==:切片截取操作也会改变容量。
例如:
package main
import "fmt"
unc main() {
numbers := []int{0,1,2,3,4,5,6,7,8}
printSlice(numbers)
fmt.Println("numbers ==",numbers)
numbers1 := numbers[1:4]
printSlice(numbers1)
fmt.Println("numbers[:3] ==", numbers[:3])
numbers2 :=numbers[4:]
printSlice(numbers2)
numbers3 := make([]int,0,5)
printSlice(numbers3)
numbers4 := numbers[:2]
printSlice(numbers4)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
// out:
// len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]
// numbers == [0 1 2 3 4 5 6 7 8]
// len=3 cap=8 slice=[1 2 3]
// numbers[:3] == [0 1 2]
// len=5 cap=5 slice=[4 5 6 7 8]
// len=0 cap=5 slice=[]
// len=2 cap=9 slice=[0 1]
这是因为切片是由三部分组成:
- 指向底层数组的指针
- len
- cap
假如底层数组的长度是, 那么其切片的计算公式如下:
- 长度:
- 容量:
1.11、Range函数
Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回==元素的索引==和==索引对应的值==,在集合中返回 对。
for key, value := range oldMap {
newMap[key] = value
}
for i, v := range arr {
// ...
}
for _, v := range arr {
// ...
}