这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记
1.golang的优点
- 天生支持高并发,适合电商平台等网页后端的开发
- 功能上有内存安全、GC(垃圾回收)、结构形态以及CSP-style并发计算
- 内存runtime,支持垃圾回收
- 可以直接编译为机器码,而不依赖其他库
丰富的标准库- 可以跨平台编译
2.go语言结构
以下用hello_world例程来解释
//程序的第一部分,这一行代码定义了包名,必须在源文件中非注释的第一行指明这个文件属于哪个包
//package main表示一个可独立执行的程序,每个Go应用都包含一个名为main的包
package main
//告诉go编译器,将来这个go应用会引用到哪个包,fmt包实现了格式化I/O的各种API
import "fmt"
//是程序开始执行的函数,main函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数
//注意,如果有init函数,那么该应用会先执行init函数
func main() {
fmt.Print("Hello world!")
}
//需要注意的是 { 不能单独放在一行
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的protected)。
3.go基本语法
3.1 关于标识符命名
第一个字符必须是字母或下划线而不能是数字。
3.2 字符串命名
Go的字符串可以通过+来连接
3.3 go语言空格
//Go 语言中变量的声明必须使用空格隔开
var age int
3.4 格式化字符串
package main
import (
"fmt"
)
func main() {
var stockCode = 123
fmt.Println(fmt.Sprintf("%d", stockCode))
}
4. golang语言变量类型
4.1 数字类型
整形int和浮点型float32和float64,Go语言支持整形和浮点型数字,并且支持复数,其中位的运算采用补码
4.2字符串类型
字符串就是一串固定的字符连接起来的字符序列,Go的字符串是由单个字节连接起来的,Go语言的字符串的字节使用UTF-8编码标识UNICODE文本
4.3派生类型
(a)指针类型(pointer)
(b)数组类型
(c)结构化类型(struct)
(d)Channel类型
(e)函数类型
(f)切片类型
(g)接口类型(interface)
(h)map类型
5.变量声明类型
5.1 第一种,指定变量类型,如果没有初始化,则变量默认为零值
其中,零值所对应的值为: 数值类型(包括complex64/128)为 0 布尔类型为 false 字符串为 ""(空字符串) 以下几种类型为 nil:
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口
第二种,根据赋值类型自行判断变量类型
var a = 123//自动判断为整形123
第三种,使用 := 赋值符对已经赋值的变量再次赋值则编译会报错
s := "string"
fmt.Print(fmt.Sprintf("%v", s))
s := 123//编译报错
第四种,多变量声明
//类型相同多个变量, 非全局变量
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
)
关于值类型和引用类型
(1)所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值:
(2)当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝:
(3)与C++类似的,golang也支持通过&运算符来计算变量的内存地址,值类型的变量存储在堆中
6.语言常量
常量的定义格式
const arrLength = 155//隐式
const arrLength int = 155//显式
可以用函数/方法来计算常量的值,如
const (
arrLength = len("abc")
brrLength = unsafe.Sizeof("abs")
)
要注意的是用来计算常量的值必须是在标准库中已经存在的了
iota
iota被认为是一个可以被编译器修改的常量,iota中每新增一行常量将使iota计数一次, 可理解为const语句块中的行索引
const (
a = iota
b = iota
c = iota
)
fmt.Println(a, b, c)
const (
d = iota
e
f
)
fmt.Println(d, e, f)
输出: 0 1 2 0 1 2
7.函数定义
7.1函数定义的基本形式
func function_name([parameterlist])[return types]{
//do something
}
7.2函数传参的类型
值传递:值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,也不会影响到上一层函数中的变量
引用传递:引用传递是指在调用函数时将实际参数的地址传递到函数中去,那么在函数中对参数所进行的修改,将会影响到实际的参数
默认情况下,go语言所使用的是值传递,也就是在调用过程中不会影响到实际参数
7.3函数用法介绍
函数作为另一个函数的实参(lambda):函数定义后可以作为另一个函数的实参传入
闭包:闭包是匿名函数,可以在动态编程中使用
方法:方法就是一个包含了接受者的函数
8.数组
var variable_name [SIZE] variable_type//定义的基本格式
var balance[10] float32//定义的例子
balance1 := [5]float32{1000.0, 1.0, 2.0, 3.0, 4.0} //使用赋值运算符来进行快速初始化
var balance2 = [5]float32{1.0, 2.0, 3.0, 4.0, 5.0} //使用var运算符来进行初始化
fmt.Println(balance1)
fmt.Println(balance2)
//如果数组的长度不确定,[]内可以改成...
//如果只是需要初始化指定索引,写法为
balance3 := [...]float32{1: 3.0, 2: 5.0}
fmt.Println("balance3", balance3)
9.切片(重要)
9.1 切片简介
切片:切片是对数组的抽象,go的数组长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片,(“动态数组”),与数组相比,切片的长度不是固定的,可以追加元素,在追加时可能使切片的容量增大
9.2 切片的定义
var identifier []float32 = make([]float32, 5,6)
//其中[]float32是创建的切片类型,5是创建的切片长度,6是创建的切片容量
9.3 切片的初始化
s := [] int{1,2,3}
[]表示s是一个切片类型的变量,{1,2,3}初始化值为{1,2,3},其中容量 = 长度 = 3
s := arr[:]
arr := [5]float64{1.0, 2.0, 3.0}
s1 := arr[:] //切片s1代表着对arr的引用
s2 := s1[0:len(s1)]//从startIndex访问到endIndex-1
9.4 append与copy函数
如果想要增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来
s1 := make([]float32, 5)
s1 = append(s1, 1, 2, 3, 4, 5)
fmt.Println(s1)
s2 := make([]float32, 2*len(s1))
copy(s2, s1)
s2 = append(s2, 0, 1, 0, 1)
fmt.Println(s2)
首先我们需要知道,为什么需要容量这个东西,容量首先是有关于切片间的数据共享的,在容量允许的情况下,切片中的元素在底层中都是存在连续的内存空间的,如果底层数组没有足够的容量,这时候append会再创建一个新的底层数组。
10.范围Range
range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素
for key, value := range oldMap {
newMap[key] = value
}//遍历map
for key,_ := range nums{
fmt.print(key)
}//遍历普通数组,只取key
for i,v := range nums{
fmt.print("i:",i,"v:"v)
}
11.接口
定义接口的方法
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
package main
import (
"fmt"
)
type Phone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
phone.call()
phone = new(IPhone)
phone.call()
}
12.go并发
12.1 goroutine
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。 goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。 goroutine 语法格式:
package main
//例如
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
12.2 channel
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据
// 并把值赋给 v
创建通道的实例
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
12.3 通道缓冲区
ch := make(chan int, 100)
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
12.4 Go 遍历通道与关闭通道
v, ok := <-ch
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
// 会结束,从而在接收第 11 个数据的时候就阻塞了。
for i := range c {
fmt.Println(i)
}
}
13.错误处理
package main
import (
"errors"
"fmt"
)
type user struct { //定义结构体
userName string
password string
}
//定义结构体方法
func (u user) getUserName() string {
return u.userName
}
//定义结构体方法,带指针,能够直接修改到内存
func (u *user) setUserPassword(password string) bool {
u.password = password
return u.password == password
}
func findUser(users []user, userName string) (u *user, err error) {
for _, u := range users {
if u.userName == userName {
return &u, nil
}
}
return nil, errors.New("404")
}
func main() {
var users []user
name := "Tom"
findUser(users, name)
_, err := findUser(users, name)
if err != nil {
fmt.Println("not found user", name)
fmt.Println(err)
return
} else {
fmt.Println(name)
}
}
14. JSON操作
将结构体序列化
package main
import (
"encoding/json"
"fmt"
)
type user struct { //定义结构体,并且使得其能够被JSON序列化
//变量名首字母要大写
UserName string
PassWord string
Age int `json:"age"`
Hobby []string
}
func main() {
a := user{UserName: "Tom", PassWord: "123456", Age: 18, Hobby: []string{"basketball", "music"}}
buf, err := json.Marshal(a) //对a变量进行序列化
if err != nil {
panic(err)
} else {
//fmt.Println(buf),直接打印会打印出16进制的编码
fmt.Println(string(buf))
}
buf, err = json.MarshalIndent(a, "", " \t") //多做了一些格式化的处理
if err != nil {
panic(err)
} else {
fmt.Println(string(buf))
}
var b user
err = json.Unmarshal(buf, &b) //将解析之后的byte数组注入回原数组类型中
if err != nil {
panic(err)
} else {
fmt.Println(b)
}
}