便于有其它编程语言基础的编程者快速了解学习golang。
一、GO简介
1、Go语言的并发是基于 goroutine 的,goroutine 类似于线程,但并非线程。可以将 goroutine 理解为一种虚拟线程。Go语言运行时会参与调度 goroutine,并将 goroutine 合理地分配到每个 CPU 中,最大限度地使用 CPU 性能。
2、
一个Go语言项目的目录一般包含以下三个子目录:
src 目录:放置项目和库的源文件;
pkg 目录:放置编译后生成的包/库的归档文件;
bin 目录:放置编译后生成的可执行文件。
3、Go 源文件中第一段有效代码必须是package <包名> 的形式,如 package hello。
Go语言的包与文件夹是一一对应的,它具有以下几点特性:
一个目录下的同级文件属于同一个包。
包名可以与其目录名不同。
main 包是Go语言程序的入口包,一个Go语言程序必须有且仅有一个 main 包。如果一个程序没有 main 包,那么编译时将会出错,无法生成可执行文件。
4、Go语言函数的左大括号{必须和函数名称在同一行,否则会报错。
5、在运行Go语言程序之前,先要将其编译成二进制的可执行文件。
除了使用go build命令外,Go语言还为我们提供了go run命令,go run命令将编译和执行指令合二为一,会在编译之后立即执行Go语言程序,但是不会生成可执行文件。go run fileName
二、GO基本语法
1、
Go语言的基本类型有:
bool、string
int、int8、int16、int32、int64
uint、uint8、uint16、uint32、uint64、uintptr
byte // uint8 的别名
rune // int32 的别名 代表一个 Unicode 码
float32、float64
complex64、complex128
当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil 等。所有的内存在 Go 中都是经过初始化的。
声明变量的方法:
var hp int
var hp int = 100
简便hp := 100
2、
“_”本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。
3、
全局变量声明必须以 var 关键字开头,如果想要在外部包中使用全局变量的首字母必须大写。
4、
&&的优先级比||高(&& 对应逻辑乘法,|| 对应逻辑加法,乘法比加法优先级要高)
5、两个字符串 s1 和 s2 可以通过 s := s1 + s2 拼接在一起。
6、 T:看类型。s:字符串
fmt.Printf(“value type: %T\n”, value)
fmt.Printf(“value: %s\n”, value
7、*操作符的根本意义就是操作指针指向的变量。当操作在右值时,就是取指向变量的值,当操作在左值时,就是将值设置给指向的变量。
8、
str := new(string)
*str = “Go语言教程”
fmt.Println(*str)
new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值。
9、常量声明可以使用 iota 常量生成器初始化,不用每行都写。
const (
Sunday Weekday = iota
Monday
Tuesday
)
10、类型别名与类型定义
// 将NewInt定义为int类型
type NewInt int
// 将int取一个别名叫IntAlias
type IntAlias = int
func main() {
// 将a声明为NewInt类型
var a NewInt
// 查看a的类型名
fmt.Printf(“a type: %T\n”, a) a type: main.NewInt
// 将a2声明为IntAlias类型
var a2 IntAlias
// 查看a2的类型名
fmt.Printf(“a2 type: %T\n”, a2) a2 type: int
}
11、
Go语言中严格区分大小写。
变量、函数、常量名称的首字母也可以大写,如果首字母大写,则表示它可以被其它的包访问(类似于 Java 中的 public);如果首字母小写,则表示它只能在本包中使用 (类似于 Java 中 private)。
12、级数越大越优先
三、go语言容器
1、数组
声明:
var 数组变量名 [元素数量]Type
var a [3]int // 定义三个整数的数组
// 打印索引和元素
for i, v := range a {
fmt.Printf(“%d %d\n”, i, v)
}
// 仅打印元素
for _, v := range a {
fmt.Printf(“%d\n”, v)
}
可直接使用==比较两个类型相同的数组
2、切片
取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取;
var a = [3]int{1, 2, 3}
fmt.Println(a, a[1:2]) 只有2 不包括3
// 声明整型切片
var numList []int
或者使用make
make( []Type, size, cap )
b := make([]int, 2, 10)
a = append(a, 1) // 追加1个元素
copy(slice2, slice1) 复制元素
3、
sync.Map 有以下特性:
无须初始化,直接声明即可。
sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load 表示获取,Delete 表示删除。
使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。
4、列表
相当于数据结构中的链表
- 通过 container/list 包的 New() 函数初始化 list
变量名 := list.New() - 通过 var 关键字声明初始化 list
var 变量名 list.List
5、nil
在Go语言中,布尔类型的零值(初始值)为 false,数值类型的零值为 0,字符串类型的零值为空字符串"",而指针、切片、映射、通道、函数和接口的零值则是 nil。
nil 标识符是不能比较的。
四、流程空值
Go 语言的常用流程控制有 if 和 for,而 switch 和 goto 主要是为了简化代码、降低重复代码而生的结构,属于扩展类的流程控制。
1、if else
if ten > 10 {
fmt.Println(“>10”)
} else {
fmt.Println(“<=10”)
}
特殊写法:
if err := Connect(); err != nil {
fmt.Println(err)
return
}
2、for循环。go不支持 while 和 do-while 结构
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
无线循环
sum := 0
for {
sum++
if sum > 100 {
break
}
}
左花括号{必须与 for 处于同一行。
Go语言的 for 循环同样支持 continue 和 break 来控制循环,但是它提供了一个更高级的 break,可以选择中断哪一个循环。
3、for range
for key, val := range coll {
…
}
4、
var a = “hello”
switch a {
case “hello”:
fmt.Println(1)
case “world”:
fmt.Println(2)
default:
fmt.Println(0)
}
fallthrough——兼容C语言的 case 设计
5、goto
Go语言中 goto 语句通过标签进行代码间的无条件跳转,同时 goto 语句在快速跳出循环、避免重复退出上也有一定的帮助,使用 goto 语句能简化一些代码的实现过程。
五、函数声明
1、普通函数
func 函数名(形式参数列表)(返回值列表){
函数体
}
使用 return 语句返回时,值列表的顺序需要与函数声明的返回值类型一致
2、匿名函数
func(参数列表)(返回参数列表){
函数体
}
func(data int) {
fmt.Println(“hello”, data)
}(100)
注意(100),表示对匿名函数进行调用,传递参数为 100。
3、可变参数
func myfunc(args …int) {
for _, arg := range args {
fmt.Println(arg)
}
}
4、defer
先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。
5、
// 定义除数为0的错误
var errDivisionByZero = errors.New(“division by zero”)
6、recover
Go语言没有异常系统,其使用 panic 触发宕机类似于其他语言的抛出异常,recover 的宕机恢复机制就对应其他语言中的 try/catch 机制。
anic 和 recover 的组合有如下特性:
有 panic 没 recover,程序宕机。
有 panic 也有 recover,程序不会宕机,执行完对应的 defer 后,从宕机点退出当前函数后继续执行。
7、
func Since(t Time) Duration
Since() 函数返回从 t 到现在经过的时间,等价于time.Now().Sub(t)。
六、结构体
1、
Go语言中,还可以使用 new 关键字对类型(包括结构体、整型、浮点数、字符串等)进行实例化,结构体在实例化后会形成指针类型的结构体。
使用 new 的格式如下:
ins := new(T)
其中:
T 为类型,可以是结构体、整型、字符串等。
ins:T 类型被实例化后保存到 ins 变量中,ins 的类型为 *T,属于指针。
在Go语言中,对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作
2、结构体初始化
type People struct { 键值对初始化
name string
child *People
}
relation := &People{
name: “爷爷”,
child: &People{
name: “爸爸”,
child: &People{
name: “我”,
},
},
}
type Address struct { 顺序初始化
Province string
City string
ZipCode int
PhoneNumber string
}
addr := Address{
“四川”,
“成都”,
610000,
“0”,
}
// 实例化一个匿名结构体 匿名结构体
msg := &struct { // 定义部分
id int
data string
}{ // 值初始化部分
1024,
“hello”,
}
3、
Go语言中没有提供构造函数相关的特殊机制,可根据需求,将参数使用函数传递到结构体构造参数中即可完成构造函数的任务。
4、
- 内嵌的结构体可以直接访问其成员变量
- 内嵌结构体的字段名是它的类型名
package main
import “fmt”
type A struct {
ax, ay int
}
type B struct {
A
bx, by float32
}
func main() {
b := B{A{1, 2}, 3.0, 4.0}
fmt.Println(b.ax, b.ay, b.bx, b.by)
fmt.Println(b.A)
}
输出:
1 2 3 4
{1 2}
5、
Go语言标准库的 bufio 包中,实现了对数据 I/O 接口的缓冲功能。这些功能封装于接口 io.ReadWriter、io.Reader 和 io.Writer 中,并对应创建了 ReadWriter、Reader 或 Writer 对象,在提供缓冲的同时实现了一些文本基本 I/O 操作功能。
七、接口
1、
(1)方法中的名称、参数列表、返回参数列表都一致才算实现了接口,否则报错。
(2)接口中所有方法需要均被实现
2、
一个类型可以实现多个接口
多个类型可以实现相同的接口
3、类型断言
在Go语言中类型断言的语法格式如下:
value, ok := x.(T)
其中,x 表示一个接口的类型,T 表示一个具体的类型(也可为接口类型)。
该断言表达式会返回 x 的值(也就是 value)和一个布尔值(也就是 ok),可根据该布尔值判断 x 是否为 T 类型:
4、接口可以嵌套
5、error接口
创建一个 error 最简单的方法就是调用 errors.New 函数
func Sqrt(f float64) (float64, error) {
if f < 0 {
return -1, errors.New(“math: square root of negative number”)
}
return math.Sqrt(f), nil
}
6、web流程
八、包
1、封装的实现步骤:
将结构体、字段的首字母小写;
给结构体所在的包提供一个工厂模式的函数,首字母大写,类似一个构造函数;
提供一个首字母大写的 Set 方法(类似其它语言的 public),用于对属性判断并赋值;
提供一个首字母大写的 Get 方法(类似其它语言的 public),用于获取属性的值
2、
包要求在同一个目录下的所有文件的第一行添加如下代码,以标记该文件归属的包:
package 包名
包的特性如下:
一个目录下的同级文件归属一个包。
包名可以与其目录不同名。
包名为 main 的包为应用程序的入口包,编译源码没有 main 包时,将无法编译输出可执行的文件。
如果想在一个包里引用另外一个包里的标识符(如类型、变量、常量等)时,必须首先将被引用的标识符导出,将要导出的标识符的首字母大写就可以让引用者可以访问这些标识符了。
3、锁
互斥锁:一个互斥锁只能同时被一个 goroutine 锁定,其它 goroutine 将阻塞直到互斥锁被解锁
func (m *Mutex) Lock()
func (m *Mutex) Unlock()
读写锁有如下四个方法:
写操作的锁定和解锁分别是func (*RWMutex) Lock和func (*RWMutex) Unlock;
读操作的锁定和解锁分别是func (*RWMutex) Rlock和func (*RWMutex) RUnlock。
读写锁的区别在于:
当有一个 goroutine 获得写锁定,其它无论是读锁定还是写锁定都将阻塞直到写解锁;
当有一个 goroutine 获得读锁定,其它读锁定仍然可以继续;
当有一个或任意多个读锁定,写锁定将等待所有读锁定解锁之后才能够进行写锁定。
同时只能有一个 goroutine 能够获得写锁定;
同时可以有任意多个 gorouinte 获得读锁定;
同时只能存在写锁定或读锁定(读和写互斥)。
九、并发
1、
进程/线程
进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。
线程是进程的一个执行实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
一个进程可以创建和撤销多个线程,同一个进程中的多个线程之间可以并发执行。
并发/并行
多线程程序在单核心的 cpu 上运行,称为并发;多线程程序在多核心的 cpu 上运行,称为并行。
并发与并行并不相同,并发主要由切换时间片来实现“同时”运行,并行则是直接利用多核实现多线程的运行,Go程序可以设置使用核心数,以发挥多核计算机的能力。
协程/线程
协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。
线程:一个线程上可以跑多个协程,协程是轻量级的线程。
优雅的并发编程范式,完善的并发支持,出色的并发性能是Go语言区别于其他语言的一大特色。使用Go语言开发服务器程序时,就需要对它的并发机制有深入的了解。
2、goroutine
goroutine 是一种非常轻量级的实现,可在单个进程里执行成千上万的并发任务,它是Go语言并发设计的核心。
goroutine 其实就是线程,但是它比线程更小,十几个 goroutine 可能体现在底层就是五六个线程,而且Go语言内部也实现了 goroutine 之间的内存共享。
使用 go 关键字就可以创建 goroutine,将 go 声明放到一个需调用的函数之前,在相同地址空间调用运行这个函数,这样该函数执行时便会作为一个独立的并发线程,这种线程在Go语言中则被称为 goroutine。
3、通道:channel :先入先出
可以使用 channel 在两个或多个 goroutine 之间传递消息。
十、反射
1、
反射规则可以总结为如下三条:
反射可以将“接口类型变量”转换为“反射类型对象”;
反射可以将“反射类型对象”转换为“接口类型变量”;
如果要修改“反射类型对象”,其值必须是“可写的”。
2、
// 获取Zero常量的反射类型对象
typeOfA := reflect.TypeOf(Zero)
// 显示反射类型对象的名称和种类
fmt.Println(typeOfA.Name(), typeOfA.Kind())
可以通过 reflect.Elem() 方法获取这个指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作。
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(ins)
// 取类型的元素
typeOfCat = typeOfCat.Elem()
使用 reflect.ValueOf() 函数获得值的反射值对象(reflect.Value)。书写格式如下:
value := reflect.ValueOf(rawValue)
Field(i int) Value 根据索引,返回索引对应的结构体成员字段的反射值对象。
IsNil() bool 返回值是否为 nil。
IsValid() bool 判断值是否有效。