这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记
1. golang的介绍
Go 语言保证了既能到达静态编译语言的安全和性能,又达到了动态语言开发维护的高效率,使用一个表达式来形容 Go 语言:Go = C + Python , 说明 Go 语言既有 C 静态语言程序的运行速度,又能达到 Python 动态语言的快速开发。
① 从 C 语言中继承了很多理念,包括表达式语法,控制结构,基础数据类型,调用参数传值,指针等等,也保留了和 C 语言一样的编译执行方式及弱化的指针
func testPtr(num *int) {
*num = 20
}
②垃圾回收机制,内存自动回收,不需开发人员管理 ③天然并发 (1) 从语言层面支持并发,实现简单 (2) goroutine,轻量级线程,可实现大并发处理,高效利用多核。 (3) 基于 CPS 并发模型(Communicating Sequential Processes )实现
④吸收了管道通信机制,形成 Go 语言特有的管道 channel 通过管道 channel , 可以实现不同的 goroutine之间的相互通信。
⑤go函数支持返回多个值。举例:
func getSumAndSub(n1 int, n2 int) (int, int ) {
sum := n1 + n2 //go 语句后面不要带分号.
sub := n1 - n2
return sum, sub
}
⑥其他:比如切片slice、延时执行defer
1.2 golang的前景
服务端开发
分布式系统,微服务
网络编程
区块链开发
内存KV数据库,例如boltDB、levelDB
云平台
1.3 使用go的公司的开源项目
-
Google
-
Facebook
-
360开源日志系统
3. 变量与常量
3.1 变量数据类型
值类型:
布尔型:bool
整型:int(32 or 64), int8, int16, int32, int64
uint(32 or 64), uint8(byte), uint16, uint32, uint64
浮点型:float32, float64
字符串:string
数组:array
结构体:struct
引用类型:
指针
slice:切片(变长数组)(常用)
map:映射
channel:管道
interface:接口
值类型:变量直接存储值,内存通常在栈中分配
引用类型:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆中分配
| 类型 | 长度(字节) | 默认值 | 说明 |
|---|---|---|---|
| bool | 1 | false | 无法参与数值运算,也无法与其他类型进行转换 |
| byte | 1 | 0 | uint8,代表ASCII字符集的一个字符 |
| rune | 4 | 0 | int32,代表Unicode字符集的一个字符 |
| int, uint | 4或8 | 0 | 32 或 64 位(取决于你电脑是32位还是64位) |
| int8, uint8 | 1 | 0 | -128 ~ 127, 0 ~ 255,byte是uint8 的别名 |
| int16, uint16 | 2 | 0 | -32768 ~ 32767, 0 ~ 65535,对应C语言的short |
| int32, uint32 | 4 | 0 | -21亿~ 21亿, 0 ~ 42亿,rune,对应C语言的int |
| int64, uint64 | 8 | 0 | 对应C语言的long |
| float32 | 4 | 0.0 | |
| float64 | 8 | 0.0 | 默认是float64 |
| complex64 | 8 | 复数,由实部+虚部组成 | |
| complex128 | 16 | ||
| uintptr | 4或8 | 以存储指针的 uint32 或 uint64 整数 | |
| array | 值类型 | ||
| struct | 值类型 | ||
| string | "" | UTF-8 字符串 | |
| slice | nil | 引用类型 | |
| map | nil | 引用类型 | |
| channel | nil | 引用类型 | |
| interface | nil | 接口 | |
| function | nil | 函数 |
3.2 变量的初始化
Go语言在声明变量的时候,每个变量均会被初始化成其类型的默认值,例如:
①整型和浮点型变量的默认值为0。
②字符串变量的默认值为空字符串。
③布尔型变量默认为false。
④切片、函数、指针变量的默认为nil。
3.3 变量的public和private
①变量在函数内部声明,则类似于private ②变量在函数外声明,是for当前包下所有.go文件可使用的全局值,类似于protect ③变量在函数外声明,且首字母大写,则是for所有包所有.go文件可使用的全局值,类似于public
另一说法:无论是变量、方法、函数、结构体等等,只要名字是大写字母开头,均是public,若名字是小写字母开头,均是private
3.4 值类型变量声明格式
正常声明
var 变量名 变量类型
var flag bool
var age int
var name string
var array1 [5]int
批量声明
var (
a string
b int
c bool
d float32
)
简便声明
var name = "franky"
name := "franky"
匿名变量声明
x, _ := func1()
_, y := func1()
3.5 内置函数*(下面的很多数据类型都会用到)*
Go 语言拥有一些不需要进行导入操作就可以使用的内置函数。它们有时可以针对不同的类型进行操作,例如:len、cap 和 append,或必须用于系统级的操作,例如:panic。因此,它们需要直接获得编译器的支持。
append -- 用来追加元素到数组、slice中,返回修改后的数组、slice
close -- 主要用来关闭channel
delete -- 从map中删除key对应的value
panic -- 停止常规的goroutine(panic和recover:用来做错误处理)
recover -- 允许程序定义goroutine的panic动作
real -- 返回complex的实部(complex、real imag:用于创建和操作复数)
imag -- 返回complex的虚部
make -- 用来分配内存,返回Type本身(只能应用于slice, map, channel)
new -- 用来分配内存,主要用来分配值类型,比如int、struct。返回指向Type的指针
cap -- capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
copy -- 用于复制和连接slice,返回复制的数目
len -- 来求长度,比如string、array、slice、map、channel ,返回长度
print、println -- 底层打印函数,在部署环境中建议使用 fmt 包
3.6 字符串string
string底层就是一个byte的数组
3.6.1 字符串的常用操作
| 方法 | 介绍 |
|---|---|
| len(str) | 求长度 |
| +或fmt.Sprintf | 拼接字符串 |
| strings.Split | 分割 |
| strings.Contains | 判断是否包含 |
| strings.HasPrefix, strings.HasSuffix | 前缀/后缀判断 |
| strings.Index(), strings.LastIndex() | 子串出现的位置 |
| strings.Join(a[]string, sep string) | join操作 |
3.6.2 字符串的修改
要修改字符串,需要先将其转换成[]rune或[]byte,完成后再转换为string。无论哪种转换,都会重新分配内存,并复制字节数组。
func changeString() {
s2 := "博客"
runeS2 := []rune(s2)//强制类型转换,含中文字符的字符串必须转成[]rune而不是[]byte,不含中文则无所谓但建议[]byte
runeS2[0] = '狗'
fmt.Println(string(runeS2))
}
3.6.3 字符串与基本类型的互相转换
与13.2 strconv包互为参考
①基本类型->字符串
//fmt.Sprintf("%参数类型", 表达式)
num1 := 1
str = fmt.Sprintf("%d", num1)
②字符串->基本类型
/*
使用strconv包的函数:
ParseBool(str string)(value bool, err error)
ParseBool(str string, bitSize int)(f float64, err error)//bitSize指32还是64位
ParseBool(str string, base int, bitSize int)(i int64, err error)//base指10进制还是2进制
*/
var b1 bool
var str1 = "true"
b, _ = strconv.ParseBool(str1)
3.7 数组array
①数组:是同一种数据类型的固定长度的序列。
②数组定义:var a [len]int,比如:var a [5]int 或 var a [...]int{1,2},数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。
③长度是数组类型的一部分,因此,var a[5] int和var a[10]int是不同的类型。
④数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1
遍历的两种方式:
for i := 0; i < len(a); i++ {
}
for index, value := range a {
}
⑤访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
⑥数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。
⑦支持 "=="、"!=" 操作符,因为内存总是被初始化过的。
⑧数组的截取与切片截取一样,详看3.9.2
3.8 结构体struct
Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。
3.8.1 自定义数据类型
type MyInt int
将MyInt定义为int类型,MyInt就是一种新的数据类型,它具有int的特性。
3.8.2 给类型起别名
type byte = uint8
type rune = int32
自定义数据类型和给类型起别名的区别:
type Int1 int//它的数据类型是main.Int1
type Int2 = int//它的数据类型是 int
3.8.3 struct的定义
type person struct {//属于自定义新的数据类型
name string
city string
age int8
}
3.8.4 struct的实例化(自定义类型的变量的定义)
实际上就是定义一个变量,它的数据类型是person
①定义值类型struct变量
var p1 person
p1.name = "franky"
②定义值类型struct变量
p1 := person{name:"franky", age:1}
③定义指针类型struct变量
var p1 *person = &person
p1.name = "franky"//指针类型变量访问成员也是用.就可以了,原理是go在底层帮我们转化了类型
④定义指针类型struct变量
p1 := &person{name:"franky", age:1}
3.8.5 匿名struct
以下只展示了最常见的用法,实际上其他用法跟一般的struct一样
var p1 struct{name string; age int}
p1.name = "franky"
3.8.6 方法
方法是指与数据类型绑定的函数,常见于struct中,在struct中,字段代表是struct的静态属性,方法则代表struct的动态属性
方法的定义格式:
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
3.8.6.1 方法的定义
①指针类型的接收者*Person
type Person struct {//属于自定义新的数据类型
name string
age int8
}
func (p *Person) SetAge1(newAge int8) {//表示方法与Person类型绑定,只能由Person类型的变量来调用该方法
p.age = newAge
}
②值类型的接收者Person
func (p Person) SetAge2(newAge int8) {
p.age = newAge
}
对于方法而言,假设现有var p1 Person,var p2 *Person,p1 p2都能调用指针类型或者值类型的方法,那怎么判断是值类型还是指针类型的调用呢?一切以方法定义时的接收者类型为准。举例:p1.SetAge1()属于指针类型调用
3.8.7 struct嵌套实现“继承”
struct里其中一个字段也是struct类型,就能实现继承
type Animal struct {
name string
}
func (a *Animal) move() {
fmt.Println(a.name)
}
type Dog struct {
age int8
animal Animal //通过嵌套匿名结构体实现继承
}
func (d *Dog) speak() {
fmt.Println(d.animal.name)
}
func main() {
d1 := &Dog{
age: 4,
animal: Animal{
name: "franky",
},
}
d1.speak()//输出franky
d1.animal.move()//输出franky
}
3.9 指针
区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针。
Go语言中的指针操作非常简单,只需要记住两个符号:&(取地址)和*(根据地址取值)。
num := 1
var ptr1 *int = &num
ptr2 := &num
fmt.println(*sptr2)
3.10 切片slice*(常用)*
①切片:切片是数组的一个引用,因此切片是引用类型。切片底层是结构体类型,值拷贝传递。
//这是切片这种数据类型的定义
type slice struct {
ptr *[2]int
len int
cap int
}
②切片的长度可以改变,因此,切片是一个变长数组。
③切片遍历方式和数组一样,可以用len()求长度。表示可用元素数量,读写操作不能超过该限制。
④cap可以求出slice最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中array是slice引用的数组。
⑤如果 slice == nil,那么 len、cap 结果都等于 0。
3.10.1 切片的定义
①先定义一个数组,然后定义一个切片去引用那个数组
arr := [...]int{1, 2, 3, 4, 5}//[...]代表数组的cap根据后面初始化来定,但是不能写成[],这是切片的定义
slice1 := arr[0:3:5]//声明切片的cap为5,将数组前三个元素赋值到切片中
②通过make来定义切片
var slice1 []int = make([]int, 3, 5)//声明切片的len为3,cap为5。如果只有一个参数,那参数就是len
③直接初始化切片,就不用make了
var slice1 []int = []int{1, 2, 3}//len和cap都是3
3.10.2 切片的截取
下图是标准:
截取切片示例:
s := []int{1,2,3,4,5}
s1 := s[2:4]//3,4
3.10.3 切片与底层数组的关系
如下图,我们在声明一个切片(如X)的时候,实际上go会帮我们声明一个底层数组,然后让这个切片指向这个数组,这就是为什么说*“切片是引用类型。切片底层是结构体类型,值拷贝传递”*,在下图我们又声明了一个切片Y,这个Y是引用X的,因此X和Y是指向同一个底层数组的,当修改X切片中某个元素的值是,会直接修改底层数组对应元素的值,导致Y切片中对应元素的值改变,如 x[1] = 100,则fmt.Println(y) == [100, 5]
3.10.4 使用内置函数对切片进行常用操作
增1:在切片的最后一个元素的后面追加元素
s1 := []int{1,2}
s2 := append(s1, 3, 4)//s2=[1,2,3,4]
s3 := append(s1, s2...)//s3=[1,2,1,2,3,4],这里必须要加... ...的意思是将切片打散再进行传递
增2:在切片的指定索引后插入元素
s1 := []int{1,2}
s2 := append(s1[:1], append([]int{444}, s1[1:]...)...)//s2=[1 444 2]
删:删除切片的指定索引元素
s1 := []int{1,2,3}
s2 := append(s1[:1], s1[2:]...)//[1 3]
切片拷贝,拷贝的长度=min{len(s1), len(s2)}
s1 := []int{1, 2, 3, 4, 5}
s2 := make([]int, 10)
if(1) copy(s2, s1)//s2=[1 2 3 4 5 0 0 0 0 0],拷贝的长度=5
else copy(s1, s2)//s1=[0 0 0 0 0], 拷贝的长度=5
改
s1 := []int{1,2}
s1[1] = 100//s1=[1,100]
遍历
for index, value := range slice {
}
3.10.5 对切片进行增的底层原理
已知①数组的长度一旦被定义就不能改变,②切片是引用了一个底层数组。当给切片增加元素但超出切片cap时候,那么golang是如何解决的?golang会在底层创建一个新数组(新数组的cap一般是旧数组的2倍),将旧数组的元素全部复制到新数组中,然后将旧数组回收。可以随时打印%p来留意是否为同一个切片
3.11 映射map
①map 是 key-value 数据结构
②map的格式是
map [KeyType]ValueType//[键的数据类型]值的数据类型
③map里面的键值对是无序的
④map 的容量达到后,再想往 map 中增加元素,会自动扩容,并不会发生 panic,也就是说 map 能动态的添加键值对(key-value)
⑤map 的 value 也经常使用 struct 类型, 更适合管理复杂的数据(比起value 是一个 map更好)
3.11.1 map的定义
①使用 make(map[KeyType]ValueType, [cap]),再往map中放数据
var a map[string]string
a = make(map[string]string, 10)//10是cap
a["key1"]="value1"
a["key2"]="value2"
②不使用make但是要初始化数据
a := map[string]string{
"key1":"value1",
"key2":"value2",
}
a["key3"] = "value3"
3.11.2 使用内置函数对map进行常用操作
增/改:往map中添加一个键值对
a := make(map[string]string)
a["key1"] = “value1” //如果key还没有,就是增加,如果key存在就是修改
删除:格式delete(map, key)
a := map[string]string{"key1":"value1"}
delete(a, "key1")//结果是a=[],当然如果要删除的key不存在也不会报错
遍历1:无序且随机
for k, v := range map {
}//可以只遍历k而不用加_
遍历2:按照指定顺序遍历map:①将map中的key全部存入切片;②对切片进行排序;③按照排序后的key来遍历map
func main() {
rand.Seed(time.Now().UnixNano()) //初始化随机数种子
var scoreMap = make(map[string]int, 200)
for i := 0; i < 100; i++ {
key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串
value := rand.Intn(100) //生成0~99的随机整数
scoreMap[key] = value
}
//取出map中的所有key存入切片keys
var keys = make([]string, 0, 200)
for key := range scoreMap {
keys = append(keys, key)
}
//对切片进行排序
sort.Strings(keys)
//按照排序后的key遍历map
for _, key := range keys {
fmt.Println(key, scoreMap[key])
}
}
判断map中某个key是否存在:
a := map[string]string{"key1":"value1"}
value, ok := a["key1"]///若key存在则ok为true,v为对应的值;若key不存在则ok为false,v为值类型的零值
3.12 管道channel
写在了后面并发编程中,详见9.5
3.13 接口interface
写在了后面面向对象编程中,详见8.1
3.14 常量声明格式
正常声明
const pi = 3.1415
批量声明
const (
pi = 3.1415
e = 2.7182
)
\