Golang
2022-04-14 07:41:38 70 举报
AI智能生成
Golang,又称Go语言,是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Golang语法与C相近,但功能上有:内存安全,GC(垃圾回收),结构形态及CSP-style并发计算。Golang的吉祥物是名为”Go”的小鸟,出自于韩愈《进学解》中的诗句:“贪多务得,细大不捐。” Go语言被设计成一门应用于搭载Web服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。对于高性能分布式系统领域而言,Go语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。
作者其他创作
大纲/内容
语法基础
数据类型
布尔类型(bool)
1. 布尔类型也叫 bool 类型,bool 类型数据只允许取值 ture 和 flase<br>
2. bool 类型占 1个字节<br>
3. bool 类型适用与逻辑运算,一般用于程序流程控制<br>
数字类型
int , float32, float64 , Go 语言支持整型和浮点数字,并且支持复数,其中位运算采用补码
数字类型
uint8
无符号 8 位整性 0 - 256
uint16
无符号 16 位整型 (0 到 65535) <br>
uint32
无符号 32 位整型 (0 到 4294967295) <br>
uint64
无符号 64 位整型 (0 到 18446744073709551615) <br>
int8
有符号 8 位整型 (-128 到 127) <br>
int16
有符号 16 位整型 (-32768 到 32767) <br>
int32
有符号 32 位整型 (-2147483648 到 2147483647) <br>
int64
有符号 64 位整型 (-9223372036854775808 到 9223372036854775807) <br>
浮点类型
float32
IEEE-754 32位浮点型数 <br>
float64
IEEE-754 64位浮点型数 <br>
complex64
32 位实数和虚数 <br>
complex128 <br>
64 位实数和虚数 <br>
字符串类型(string)
字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由<font color="#c41230">单个字节</font>连接起来的。<br>Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。 <font color="#c41230" style="">Go 的字符串不同,它是由字节组成的。</font><br>
注意事项(使用细节)
1. Golang 字符串的字节使用 UTF-8编码进行表示 Unicode 文本,这样golang 统一适用了 utf-8 变么,就能<font color="#c41230">避免了中文乱码的问题</font><br>
2. 字符串一旦定义就不能修改
错误案例:<br>var str1 string = "124"<br>str1[1] = "3"
3.字符串的两种形式
1) 双引号,会识别转义字符<br>
2)反引号,以字符串的原生形式输出,包括换行和特殊字符,可以实现<font color="#c41230">防止攻击,输出源代码</font>等效果<br>
4.字符串拼接的方式
1)通过 + 加号链接<br>
5. 字符串多行拼接的时候, <font color="#c41230">换行的时候 + 加号需要放到前行的尾部</font>
字符类型(char)<br>
1. 字符常量是用单引号('')括起来的单个字符,例如 var c1 char = '<font color="#c41230">a</font>', var c2 char = '中' var c3 char = '9'<br>
2. Go 中有女婿使用转义字符 '\' 来将后面的字符串转变为特殊字符类型常量。例如:var c3 = '\n' // '\n' 表示换行符<br>
3. Go 语言的字符串使用 UTF-8(<font color="#c41230">英文字母1个字节,汉字3个字节</font>)
4. 在 Go中, 字符的本质是一个整数, 直接输出的时,应该是字符对应的 UTF-8 码值。
5. 可以直接给某个变量赋一个数字,然后按照格式化输出时 %c, 会输出该字符串对饮的 unicode 字符<br>
6. 字符类型可以进行运算的,相当于一个函数,因为他们都有对应的 Unicode 码<br>
字符类型的本质
1. 字符型 存储到计算机中,需要讲字符对应的码值(整数)找出来<br>
存储:字符串 -> 对应码值--> 二进制 --> 存储<br>
读取:二进制 --> 码值 --> 字符 --> 读取<br>
2. 字符和码值的对应关系是通过字符编码表决定的(是规定好的)<br>
2.Go语言的编码都统一成了 utf-8 . 非常方便,很统一,不会有编码乱码的困扰<br>
其他数字类型 <br>
byte
类似 uint8
rune
类似 int32
uint
32 或 64 位
int
与 uint 一样
uintptr
无符号整形,用于存放指针
基本数据类型的默认值
整型 ==> 0<br>
浮点型 ==> 0<br>
字符串 ==> ""<br>
布尔类型 false<br>
复合类型
指针:pointer:存储的是变量的内存地址
声明和初始化
var 指针名 *变量类型
获取指针值
*指针名
指针不能运算
数组:array 值类型
声明和初始化
var 数组名 [长度] 类型
var 变量名 = [长度]类型{值, 值...}
数组名 := [...]类型{值, 值...}
数组名 := [长度]类型{下标:值...}
注意事项
长度不一样,类型就不一样
第一个元素的内存地址,是这个数组的内存地址
数组长度需要在编译期确定
切片:slice 引用类型
定义
var 切片名 []类型 = []类型{值, 值...}
基于数组创建
切片名 := 数组名[开始位置:结束位置]
直接创建
切片名 := make([]类型, [容量])
动态增加元素
新切片名 := append(旧切片名, 值)
新切片名 := append(旧切片名, 旧切片名...)
内容复制
copy(切片名1, 切片名2)
动态删除
切片名 = 切片名[start:end]
字典:map 引用类型
定义
var 字典名 map[类型]类型
初始化
查找元素:值, ok := 字典名["下标"]
删除元素:delete(切片名, 下标)
通道:chan 引用类型
结构体:struct 值类型
接口:interface
定义方法集,但不实现这些方法,不能包含属性
注意事项
接口名由方法名+(e)r或者以I开头
任何类型都可以实现接口
实现了接口的所有方法,就是这个接口的实例
类型转换
// byte转数字<br>s="12345" // s[0] 类型是byte<br>num:=int(s[0]-'0') // 1<br>str:=string(s[0]) // "1"<br>b:=byte(num+'0') // '1'<br>fmt.Printf("%d%s%c\n", num, str, b) // 111<br><br>// 字符串转数字<br>num,_:=strconv.Atoi()<br>str:=strconv.Itoa()
变量和常量
常量
定义
const 常量名 常量类型 = 值
const 常量名 = 值
注意事项
只能是布尔型、整型、浮点型、复数、字符串
值要在编译时就可以确定的
预定义常量
iota:初始值为0 每出现一次就自动增1 遇到const时会被重置为0
变量
变量声明和初始化
var 变量名 变量类型; 变量名 = 值
var 变量名 = 值
var(变量名1 变量类型; 变量名2 变量类型)
变量名 := 值
变量名1, 变量名2 = 变量名2, 变量名1
注意事项
1、使用":="的变量一定不能被声明过的,否则报错
2、":="只能在函数中使用
3、多个变量赋值,只要有一个是没被声明过的,就可以使用":="
4、局部变量声明之后,如果未被使用则会报错,全局变量则不会
init函数:包初始化之后自动执行,优先级比main函数高。每个源文件只能包含一个
变量的作用域
1. 函数内部声明 / 定义的变量叫做局部变量, 作用域仅局限于函数内部。<br>
2. 函数外部声明/ 定义的变量叫做全局变量, 作用于在整个包都有效,如果首字母为大写,则作用于在整个程序中都有效<br>
3. 如果变量是在一个代码块, 比如 for / if 中,那么这个变量的作用域在该代码块<br>
变量作用于分析<br>
函数
概述
函数是基本的代码块,用于执行一个任务。<br>
Go 语言最少有个 main() 函数。
你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。<br>
函数声明(函数签名)告诉了编译器函数的名称,返回类型,和参数。<br>
Go 语言标准库提供了多种可动用的内置的函数。例如,len() 函数可以接受不同类型参数并返回该类型的长度。<br>如果我们传入的是字符串则返回字符串的长度,如果传入的是数组,则返回数组中包含的元素个数。<br>
函数的命名遵循标识符规范,首字母不能是数字,手写字母答谢该函数可以被本包文件和其它包文件使用, 类似 public , 首字母小写, 只能被本包文件使用, 其它包文件不能使用, 类似 private
不支持重载
定义
函数定义如下:<br>fun function_name ([parameter list]) [return_types] {<br> 函数体<br>}<br>
简单函数调用
函数返回多个值
函数参数的传递方式
基本介绍<br>
我们在讲解函数注意事项和基使用细节时,已经讲过值类型和引用类类型了,这里我们在系统的总结一下。<br>因为这里是重难点, 值类型参数默认就是值传递。 而引用类型参数默认就是引用传递。<br>
两种传递方式
1. 值传递<br>
2. 引用传递
其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是, 值传递的是值的拷贝,<br>引用传递的地址的拷贝,一般来说,地址拷贝效率高,因为数量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低<br>
值类型和引用类型
1. 值类型,基本数据类型 int 系列, float 系列, bool, string, 数组和结构体 struct<br>
2. 引用类型, 指针, slice 切片、map、 管道 chan 、interface 等都是引用类型<br>
值传递和引用传递的使用特点
1. 值类型默认是值传递,变量直接存储值,内存通常在栈上分配<br>
2. 引用类型默认是引用传递,变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,<br>当没有任何变量引用这个地址时,该地址对应的数据空间就成为了一个垃圾,由 GC 来回收。<br>
值类型和引用类型<br>
值类型:基本数据类型 int 系列,bool , string , 数组和结构体 struct<br>
引用类型:指针,slice 切片, map , 管道 chan , interface 等都是引用类型<br>
使用特点
1. 值类型,变量直接存储值,通常在<font color="#c41230">栈中分配</font><br>
2. 引用类型,变量存储的是一个地址,这个地址空间才是真正存储数据(值),内存通常在<font color="#c41230">堆上分配</font>,<br>当没有任何变化引用这个地址时,该地址的数据空间就成为一个垃圾,由 GC 来回收<br>
make 和 new
new:作用于值类型,仅分配内存空间,返回指针
make:作用于引用类型,分配内存空间和初始化,返回初始值
标识符
1. 由 26 个英文字母大小写,0-9, _ 组成<br>
2. 数字不可以开头<br>
3. Golang 中严格区分大小写<br>
4. 标识符不能包含空格<br>
5. 下划线 “_”本身在 Go 中是一个特殊的标识符,成为空标识符。可以代表任何其他的标识符,但是它对应的值会被忽略(比如:忽略某个返回值)。<br><font color="#c41230">所以仅能够被作为占位符使用,不能作为标识符使用</font><br>
6. 不能以系统保留字作为标识符,比如 : brack , if 等<br>
7. 如果变量名、函数名、长两名首字母大写,则可以被其他的包访问;如果首字母小写,则只能在本包内使用<br>
函数和方法
包的使用
包的三大作用
1. 区分相同文件的函数,变量等标识符<br>
2. 当程序文件很多的时候,可以很好的管理项目<br>
3.控制函数。变量等访问范围,即作用域
包的相关说明
包的基本语法
package util<br>
引入包的基本语法<br>
import "包的路径"<br>
包的使用注意事项和细节说明
1)在给一个文件打包的时候,该包对应一个文件夹,比如这里的 utils 文件夹对应的包就是util, <br>通常和文件所在的文件名保持一致,一般为小写字母。<br>
2)当一个文件用使用其它包函数或变量时,需要先引入对应的包
a. 引入方式 1 : import "包名"
b. 引入方式 2: <br><br>import (<br> "包名"<br>)<br>
c. package 指令在文件第一行,然后是 import 指令
d. 在 import 包时, 路径从 $GOPATH 的 src 下开始, 不用带 src , 编译器会自动从 src 下开始引入
3)为了让其它包的文件,可以访问到本包的函数、变量,则该<font color="#c41230">函数名的首字母需要大写</font>,类似其它语言的 public , 这样才能跨包访问。
4)在访问其它包函数时,其语法是 包名.函数名
5)如果包名比较长,Go 支持给包取别名, 注意细节:<font color="#c41230">取别名后,原来的包名不能使用了</font>
6) 如果在<font color="#c41230">同一个包下不能有同名的函数,这样会导致编译报错</font>
7)如果你需要编译成可执行文件,就需要将这个包声明为 main, 即 pachage main 这就是一个语法规则, 如何你就是写一个库, 包名可以自定义
init 函数<br>
基本介绍
<font color="#c41230">每一个源文件都包含一个 init 函数</font>, 该函数会在main 函数执行之前被执行<br>
细节讨论<br>
1. 如果同一个文件中同时是包含全局变量定义, init 函数和main 函数, 则执行的流程是 变量定义 -> init 函数 -> main 函数<br>
2. init 函数的主要的作用, 就是完成一些初始化的工作,比如下面的案例<br>
案例代码<br>
3. 如果 main.go 和 utils.go 都包含变量定义, init 函数时,执行的流程是怎么样的<br>
示意图
函数中的 - defer<br>
为什么需要 defer<br>
在函数中, 程序员常需要创建资源(比如,数据库链接、文件句柄,锁等)为了在函数的执行完毕后,及时的释放资源,<br>Go 的设计者提供了 defer (延迟机制)。<br>
细节说明<br>
1. 当 go 执行到一个 defer 时, 不会立即执行 defer 的语句,而是讲 defer 语句压入到一个栈中, 然后继续执行函数的下一个语句<br>
2. 当函数执行完毕后, 在 defer 栈中,依次从栈顶取出语句执行(注,遵循栈先入后出的机制),所以看到上面的输入顺序<br>
3. 在 defer 讲语句放入到栈时,<font color="#c41230">也会将相关的值拷贝同时入栈。</font><br>
演示案例
defer 的最佳实践<br>
defer 最主要的价值是在, 当函数执行完毕后, 可以及时释放函数创建的资源<br>
1. 在 golang 编程中通常的做法是, 创建资源后, 比如(打开了文件,获取了数据库链接,或者锁资源)<br>可以执行 defer file.Close() , defer connect.Close()<br>
2. 在 defer 后, 可以继续使用创建资源<br>
3. 当函数完毕后, 系统会依次从 defer 栈中,取出语句,关闭资源<br>
4. 这种设计机制非常简介,程序员不用在等什么时机关闭资源而烦心<br>
字符串中常用的函数
字符串在我们程序开发中,使用非常的多,常用的函数需要掌握
1. 统计字符串长度, 按字节 len(str)<br>
2. 字符串遍历,同时处理有中文的问题, r := []rune(str)<br>
3. 字符串转整数: n, err := strconv.Atoi("12")<br>
4. 整数转换为字符串 str = strconv.Itoa(123456)<br>
5. 字符串转 []byte : var bytes = []byte("hello go")<br>
6. []byte 字符串: str = string([]byte{97, 98, 99})<br>
7. 10 进制转 2,8,16: str = strconv.FormartInt(123, 2)<br>
8. 查找字符串是否存在制定的子串: strings.Contains("abssdb", "ab") //true<br>
9. 统计一个字符串有几个指定的子串:strings.Count("ceheese", "e") //4
10. 不区分大小写的字符串比较(== 是区分字母大小写的):fmt.Println(string2.EqualFold("abc", "Abc")) //true<br>
11. 返回子串在字符串第一次出现的 index 值, 如果没有值返回 -1<br> strings.Index("NLT_abc", "abc" ) //4<br>
12. 返回子串在字符串最后出现的 index , 如果没有返回 -1: strings.LastIndex("go lang", "go") <br>
13. 将制定的字符串替换为另外一个子串:strings.Replace("go go hello", "go", "go 语言", n) n 可以制定你喜欢替换几个, 如果 n = -1 表示全部替换 <br>
14. 按照制定的某个字符,为分割标识,讲一个字符串拆分成字符串数组<br>strings.Split("Hello, Wrold!", ",")<br>
15. 将字符串的字母进行大小写的转换:strings.ToLower("GO") // go strings.ToUpper("Go") //GO<br>
16. 将字符串左右两边的空格去掉: strings.TrimSpace("tn a lone gopher ntrn ")<br>
17.将字符串左右两边的指定字符去掉:strings.Trim("! hello!", " !")//将左右两边 ! 和 "" 去掉 <br>
18. 将字符串左边指定的字符去掉: strings.TrimLeft("! hello !", " !") //将左边 ! 和 "" 去掉
19. 将字符串右边指定的字符去掉: strings.TrimRight("! hello !", " !") //将右边 ! 和 "" 去掉
20. 判断字符串是否以指定的字符串开头:strings.HasPrefix("ftp://192.168.1.1","ftp")<br> //true
21. 判断字符串是否以指定的只服从结束:strings.HasSuffix("a.jpg", "jpg") //true<br>
错误处理
错误处理总结 <br>
1. 在默认情况下,当发生错误后(panic), 程序就会退出(崩溃)<br>
2. 如果我们希望,发生错误后,可以捕获错误,并进行处理,保证程序可以继续执行。<br>还可以捕获到错误后,给管理员一个提示(邮件、短信。。。)<br>
基本说明
1. Go 语言最求简洁优雅, 所以 , Go 语言不支持传统的 try ... catch.. finally 这种处理,。<br>
2. Go 中引入的处理方式为:<font color="#c41230">defer , panic , recover</font><br>
3. 这几个异常的是采用场景可以这么简单描述:Go中可以抛出 一个 panic 的异常,然后在 defer 中通过 <font color="#c41230">recover 捕获异常</font>,然后正常处理<br>
自定义错误<br>
Go 程序中, 也支持自定义错误, 使用 errors.New , 和 painc 内置函数
1. errors.New("错误说明"), 会返回一个 error 类型的值 , 表示一个错误<br>
2. panic 内置函数, 接受一个 interface {} 类型的值(也就是任何值了)作为参数,<br>可以接受 error 类型的值, <font color="#c41230">输出错误信息, 并且退出程序。</font><br>
数组和切片
访问数组的元素<br>
数组的4种初始化的方式<br>
数组的遍历方式
1. 常规遍历<br>
2. for-range 结构遍历<br><br>for index, value : range array01 {<br> ....<br>}<br><br>
1. 第一个返回值是 index 的下标<br>
2. 第二个 value 是在该下标位置的值<br>
3. 他们都仅在 for 循环内部可见的局部变量<br>
4. 遍历数组元素的时候,如果不想使用下标 index,可以直接把下标为 _ 接收表示忽略<br>
5. index, value 的名称不是固定的,程序员可以自己定义,一般命名为index 和 value<br>
数组的使用细节<br>
1. 数组是多个<font color="#c41230">相同类型</font>的数据集合, 一个数组一旦声明/定义了,其长度是固定的,不能动态改变<br>
2. var arr []int 这时 arr 就是一个 slice 切片, 切片后面会提到<br>
3. 数组的元素可以是任意类型,包括值类型和引用类型, 但是不能混用<br>
4. 数组创建后如果没有赋值, 有默认值<br><br><br>数值类型数组 : 默认为0<br>字符串数组:默认为""<br>bool 数组:默认值为 false<br>
5. 使用步骤 1. 声明数组病开辟空间,2,给数组的各个元素赋值 3 使用数组<br>
6. 数组的下表是 0 开始的<br>
7. 数组的下标必须在指定的范围内使用,否者报 panic , 数组越界,比如: var arr [5]int 则有效下表为 0-4<br>
8. Go 的数组属于<font color="#c41230">值类型</font>,在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响
<font color="#c41230">9. 如果想在其他函数中,取修改原来的数组,可以使用引用传递(指针方式)</font><br>
10. <font color="#c41230">长度是数组类型的一部分</font>, 在传递参数的时候需要考虑数的长度<br>
切片<br>
切片的基本介绍<br>
切片是数组的一个引用, 因此切片是<font color="#f15a23">引用类型</font>,在进行传递时,遵循引用传递的机制<br>
切片的使用和<font color="#c41230">数组类似</font>,遍历切片、访问切片的元素和长度 len(slice) 都一样<br>
切片的长度是可以变化的, 因此切片可以<font color="#c41230">是一个动态变化的数组</font><br>
切片的基本定义语法:<br>var 变量名 [] 类型<br>举例:var a []int
切片使用的三种方式
1. 定义一个切片,然后让切片取引用一个已经创建好的数组,比如前面的案例就是这样的。<br>
2. 第二种方式: 通过 make 来创建切片,基本语法: var 切片名 []type = make([], len, cap) <br>参数说明: type 就是数据类型, len: 大小, cap: 制定切片容量,可选<br>
1. 通过 make 方式创建可以制定切片的大小和容量<br>
2. 如果没有给切片的各个元素赋值, 那么就会使用默认值 [ int, float = 0, string = "", bool= false ]<br>
3. 通过 make 方式创建的切片对应的数组是由 make 底层维护的,对外不可见,即只能通过 slice 去访问<br>
3. 第三种方式, 定义一个切片,直接制定具体数组 ,使用原理类似make 的方式<br>
方式 1 和方式 2 的区别
1. 直接的方式是引用数组, 这个数组是事先存在的, 程序员可见的。<br>
2. 方式二,通过 make 来创建切片, make 也会创建一个数组, 是由切片在底层进行维护, 程序员是看不见的。<br>
切片的遍历两种方式
1. 使用常规的 for 循环遍历<br>
2. 使用for ... range 来遍历切片
切片的细节说明<br>
1. 初始化时 var slice = arr[startIndex:endIndex]<br><font color="#c41230">说明</font>: 从arr 数组下标为 startIndex, 取到下表为 endIndex 的元素(不含 arr[endIndex])<br>
2. 切片初始化时, 不能越界, 范围在[0, len -1] 之间,但是可以动态增长<br>
1). var slice = arr[0:end] 可以简写 var slice = arr[:end]<br>
2). var slice = arr[start:len(arr)] 可以简写 var slice = arr[start:]<br>
3) var slice = arr[0:len(arr)] 可以简写 var slice = arr[:]<br>
3. cap 是一个内置函数, 用于统计切片的容量, 即最大可以存放多少个元素<br>
4. 切片定义后, 不能使用,因为本身是一个空的, 需要让其引用到一个数组或者 make 一个空间供切片使用<br>
5. 切片可以继续切片
演示案例
6. 使用 append 内置函数, 可以对切片进行动态追加<br>
append 操作的底层原理分析如下:
1. 切片 append 操作的本质是对数组拓容<br>
2. go 底层会创建一个新的数组 new Arr(按照拓容后的大小)<br>
3. 将 slice 原来包含的元素拷贝到新的数组 newArr<br>
4. slice 重新引用到 newArr<br>
5. 注意 newArr 是在底层来维护的, 程序员看不见<br>
7. 切片拷贝操作<br>
切片使用 copy 内置函数完成拷贝<br>
copy(para1,para2) : para1 和para2 都是<font color="#c41230">切片类型</font><br>
案例代码<br>
slice4, slice 5 的空间是相互独立的, 修改 slice4[0] = 5 那么 slice5 不会改变<br>
代码分析<br><br>var a []int = []int {1, 2, 3, 4,5 }<br>var slice = make([]int, 1) <br>fmt.Println(slice) <font color="#c41230">//[0]</font><br>copy(slice, a)<br>fmt.Println(slice) <font color="#c41230">//[1]</font><br>
8. 切片是引用类型, 所以在传递的时候, 准守引用传递机制。<br>
案例 1<br>
案例 2<br>
9. 切片只能和 nil 进行等值判断<br>
切片和切片比较会提示 <font color="#c41230">invalid operation: a[:] == b[:] (slice can only be compared to nil)go</font>
map<br>
map 介绍<br>
map 是 key - value 数据结构, 又称为字段或关联数组, 类似其他变成语言的集合<br>
map 的申明 <br>
var map 变量名 map[keytype][valuetype]<br>
key 可以是什么类型: golang 中 map 的 key 可以是很多种类型, 比如 bool, 数字, string , 指针, channel , 还可以是只包含前面几个类型的借口,结构体, 数组,<font color="#c41230"> 通常是 int, string</font> <br>
注意:slice , map,还有 function, 不可以因为这几个类型没有办法用 == 比较 <br>
一般 keytyp 的类型是:<font color="#c41230"> 整数、浮点数、string, map, struct</font><br>
创建的案例<br>
var a map[string]string<br>
var a map[string]int
var a map[int]string
var a map[string]string
var a map[string]map[string]string
注意:申明的时候不会进行内存分配的初始化需要 make , 分配内存后才能赋值和使用<br>
申明总结<br>
1. map 在使用之前需要 make 分配空间<br>
2. map 的 key 是不能重复的, 如果重复了将覆盖之前设置的值<br>
3. value 在不同的 key 下面是可以重复的 <br>
4. map 的 key-value 是无序的<br>
map 的使用<br>
三种使用方式<br>
1. 先申明后make<br>
2. 申明时候 make 分配空间<br>
3. 直接申明和赋值<br>
案例演示: 一个 key-value 的 value 是 map 的案例, 比如:我们需要存放是哪个学生信息,每个学生信息有 name 和 sex 信息 <br>
map 的 crud 操作<br>
1. map 删除, 使用 delete 函数<br>
示例代码<br>
删除细节<br>
1. 如果我们要删除 map 的所有 key, 没有一个专门的方法进行一次删除, 可以遍历 key 逐条删除<br>
2. 或者 map := make(...) make 一个新的, 让原来的成为垃圾被 gc 回收<br>
2. map 查找<br>
查找案例
//map 的查找<br> val, ok := cities["no01"]<br> if ok {<br> fmt.Println("有no01值是", val)<br> }
map 遍历<br>
案例演示相对复杂的 map 遍历, 该 map 的 value 又是一个 map <br>
说明: map 的遍历使用 for - range 的结构遍历<br>
map 的长度 <br>
len(map) 函数可以统计出 map 的函数<br>
例子: fmt.Printf("map len=%v\n", len(cities))<br>
map 切片<br>
基本介绍
切片数据类型如果是 map , 则我们称为 slice of map, map 切片,这样则 map 个数就可以动态变化了<br>
演示案例, 使用map 来 monster 的信息 name , age , 也就是说 monster 对应一个 map , <br>并且 monster 个数可以动态增加的 => map 切片<br>
测试代码案例<br>
append 函数使用<br>
map 排序
基本介绍
1. golang 中没有一个专门的方法针对 map 的key 进行排序
2. golang 中的 map 默认是无序的, 注意也不不是一个按照添加顺序的存放的 。每次便利,得到的输出结果是不一样的
3. golang 中的 map 排序, 先是对 key 进行排序,然后更具 key 值遍历输出即可
案例演示
map 的使用细节<br>
1. map 是一个引用类型, 准守应用类型的传递机制, 在一个函数接受 map, 修改后,会直接修改原来 map<br>
2. map 的容量达到后,如果在想忖度元素, 会自动拓容, 并不会发生 panic , 也就是说 map 能<font color="#c41230">动态增减键值对 (key-value)</font><br>
3. map 的 value 经常也是使用 struct 类型, 更适合管理复杂的数据, 比如 value 中是一个 Student 结构体<br>
案例代码1<br>
map 的练习<br>
1. 使用 map[string]map[string]string 的 map 类型<br>
2. key 表示用户名, 是唯一的不可以重复<br>
3. 如果某个用户名存在, 就将其密码修改为 "888888", 如果补存在就增加这个用户信息, (包括昵称 nickname, 和密码 pwd)<br>
4. 编写一个函数 modifyUser (users map[string]map[string]string, name string) 完成上述功能
面向对象编程<br>
golang 语言面向对象编程<br>
1. goalng 也支持面向对象 (OOP), 但是和传统的面向对象编程有却别, 并不是<b><font color="#381e11">纯粹的面向对象语言</font></b>, <br>所以我们说 <font color="#c41230">Golang 支持面向对象变成特征</font>是比较准确的。<br>
2. Golang 没有类 (class), Golang 语言的结构体(struct)和其他的语言类 (class)有同等的低位, <br>你可以理解 Golang 是基于 struct 来实现 oop 特征的。<br>
3. Golang 面向对象编程非常简介, 去掉了传统 OOP 语言的继承、方法重载、构造函数和析构函数、隐藏指针 this 等等<br>
4. Golang 仍然有面向对象的继承,封装和多台的特征,只是实现方式和其他的 OOP 语言不一样,比如继承:<br> golang 没有 extends 关键字, 继承是通过匿名字段来实现的 。<br>
5. Golang 面向对象 (OOP)很优雅, OOP 本身就是语言类型系统(type system) 的一部分, 通过接口(interface)关联,<font color="#c41230">耦合性低</font>,也非常灵活。后面充分会体现这个特征, 也就是说 Golang 中<font color="#c41230">面向接口编程</font>是非常重要的特征。<br>
结构体的定义
声明结构体<br>
type 标识符 struct {<br> field1 type<br> field2 type<br>}<br>
举例子:<br><br>type Cat struct {<br> Name string, <br> Age int32,<br>}<br>
字段/属性<br>
1. 从概念上或叫法上看, 结构体字段 = 属性 = field <br>
2. 字段是结构体的一个重要注册和那个部分, 一般是基本数据类型、数组、也可以是引用类型。<br>比如我们之前定义猫结构体的 Name string 就是属性<br>
注意细节说明
1. 字段语法同变量:示例: 字段名 字段类型<br>
2. 字段的类型可以分为:基本类型,数组,引用类型<br>
3. 在创建一个结构体变量后,如果没有给字段赋值,都是对应一个 <font color="#381e11">零值</font> (默认值), 规则同前面的一样:<br>布尔类型是 false , 数值是 0 ,字符串是 ""<br>数组类型的默认值和它的元素类型相关,比如 score[3]<font color="#c41230"> int</font> 则为 [0,0,0]<br><font color="#c41230">指针, slice , 和 map 的零值都是 nil 即还没有分配空间</font><br>
细节说明代码<br>
4. <font color="#c41230">不同结构体变量</font>的字段都是独立的,互不影响, 一个结构体字段的更改不影响另外一个。<b>(结构体是值类型)</b><br>
细节说明代码<br>
创建结构体变量和访问结构体字段
1. 方式 1- 直接声明<br>
var person Person<br>
2. 方式 2 - {}<br>
var person Person = Person{}<br>
3. 方式3 & <br>
var person *Person = new(Person)<br>
4. 方式 4 {}<br>
var person *Person = &Person{}
说明<br>
1. 第三种和第四种返回的是 <font color="#c41230">结构体指针</font><br>
2. 结构体指针访问字段的标准方式是: (*结构体指针). 字段名, 比如 (*persion).Name = "tom"<br>
3. 但 go 做了简化, 也支持结<font color="#c41230">构体指针.字段名</font>,比如 : person.Name = "tom" <font color="#f1753f">更加符合程序员的使用习惯</font>, <br>go 编译器底层堆 person.Name 做了转化 (*person).Name <br>
struct 类型的内存分配机制<br>
基本说明
变量总是在内存中的
实例代码<br>
var p1 Person<br> p1.Name = "小明"<br> p1.Age = 20<br><br> var p2 *Person = &p1<br> //不能这样写,编译报错,点的运算优先级比 * 的高<br> fmt.Println(*p2.Age)
结构体的注意事项和使用细节<br>
1. 结构体的所有字段的<font color="#c41230">内存地址是连续分布</font>的 。<br>
证明代码<br>
2. 结构体是用户单独定义的类型, 和其他类型进行转化的时候需要<font color="#c41230">完全相同</font>的字段名(名字、个数和类型)<br>
实践代码<br>
3. 结构体进行 type 重新定义(相当于取别名), Golang 认为是行的数据类型, <font color="#c41230">但是可以互相转换</font><br>
4. struct 的每个字段上,可以写一个 tag , 该tag, 可以通过反射机制获取,常见的使用场景是<font color="#c41230">序列化和反序列化</font>。<br>
序列化的场景<br>
1. JSON转换<br>
方法
基本介绍
在某些情况下,我们需要申明(定义)方法。比如 Person 结构体,除了一些字段外(年龄,姓名。。。。)Person 结构体还有一些行为,比如跑步,通过学习, 还可以做算数题。<font color="#c41230">这就要用到方法才能完成</font><br>
Golang 中的<font color="#c41230">方法是指作用在执行类型上</font>的(即, 和制定的数据类型绑定), 因此<font color="#c41230">自定义类型,都可以有方法</font>,不仅仅是 struct<br>
方法的申明和调用<br>
type A struct {<br> Num int<br>}<br><br>func (a A) test () {<br> fmt.Println(a.Num)<br>}<br>
举例说明<br>
1. test 方法是 Person 类型的板顶<br>
2. test 方法只能通过 Person 类型的变量来调用,而不能直接调用,也不能使用其他类型来调用<br>
3. func (p Person)test () {。。。} 表示那个 Person 变量调用, 这个 p 就是它的副本, 这点和函数传递非常相似<br>
4 。 p 这个变量名是程序员自己指定的, 而不是固定的, 比如修改为 person 也是可以的。<br>
方法的快速入门
1. 给 Person 结构体添加 speak 方法输出, xxx 是一个好人<br>
2. 给 Peson 结构体添加计算方法 , 可以计算 从 1+ ... 100 的结果<br>
3. 给 Peson 结构体 jisuan2 , 该方法可以接受一个数n, 计算 1 + .... n 的结果<br>
4. 给 Person 结构体添加 getSum 方法,可以计算两个数的和, 并且返回结果<br>
方法调用的传参机制和原理
说明:方法调用和传递参数机制和函数基本一样,不一样的地方是, 会讲调用方法的变量,也就是实参传递给方法<br>
1. 通过一个变量去调用方法的时候, 其调用机制和函数一样<br>
2. 不一样的地方, 变量调用方法时, 该变量本省也会作为一个参数传递到方法(如果变量是值类型的, 则进行值拷贝,如果变量是引用类型,则进行地址拷贝)<br>
方法的申明
func (recevier type) methodName (参数列表) (返回值列表) {<br> 方法体<br> return 返回值<br>}<br>
1. 参数列表,表示方法输入<br>
2. recevier type :表示这个方法和 type 的这个类型板顶, 或者说是该方法作用于 type 类型<br>
3.recevier type: type 可以是结构体,也可以是其他的自定义类型<br>
4. receiver:就是 type 类型的一个变量(实例),比如:Person 结构体的一个变量(实例)<br>
5.参数列表,表示方法输入
6.返回值列表, 表示返回值, 可以多个
7. 方法主体, 表示为了实现某个功能代码块<br>
8. return 语句不是必须的<br>
方法注意事项和细节讨论
1.结构体类型是值类型, 在方法调用中,遵循值类型的传递机制 是值拷贝传递方式
2.如果程序员希望在方法中, 修改结构体变量的值, 可以通过结构体指针的方式来处理
3.Golang 的方法<font color="#c41230">作用在指定的数据类型上</font>(即:和制定的数据类型板顶),因此自定义类型,都可以有方法,而不仅仅是 struct ,比如 int, float32 都可以<br>
4. 方法的访问, 范围控制规则和函数一样, 方法名首字母小写, 只能本包内访问,方法首字母大写, 可以在本包和其他包访问<br>
5. 如果一个变量实现了 String() 方法, 那么 fmt.Println 会默认调动这个便离开那个的 String() 进行输出<br>
代码实例<br>
方法和函数的却别
调用方式不一样 : 函数调用方式通过 函数名(实参列表),方法调用方式:变量.方法名(实参列表)<br>
对于与普通函数,接受者为值类型,不能将指针类型的数据直接传递, 反之亦然<br>
对于方法, 如struct 的方法, 接受者为值类型,可以通过指针变量调用方法, 反过来也可以<br>
创建结构体变量时指定字段的值
Golang 在创建结构体实例(变量)时, 可以直接指定字段的值<br>
方式一
var stu1 Student = Student {"tom", 10}<br>
方式二
var stu *Student = &Student{<br> Name : "tim",<br> Age : 20<br>}<br>
文件操作<br>
常用的文件操作的函数和方法<br>
1. 打开一个文件进行读操作<br>
os.Open(name string) (*File, err)<br>
2. 关闭一个文件 File.Close()<br>
3. 其他的函数和方法在案例中讲解<br>
对文件的简单操作<br>
文件读取实例<br>
1. 读取文件内容并在终端(带缓冲的方式),使用 os.Open , file.Close . bufio.NewReader(), reader.ReadString 函数和方法<br>
使用带缓冲区的方式来读取文件<br>
2. 读取文件的内容并显示在终端(使用 ioutil 一次将整个文件读取到内存中),<b><font color="#c41230">这种方式适合文件不大的情况</font></b>。<br>相关方法和函数(ioutil.ReadFile)<br>
实践代码<br>
写文件操作
基本介绍
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
说明: os.OpenFile 是一个更一般性的文件打开函数, 它会使用制定的选项(如:O_RDONLY 等)、<br>指定的模式(如0666 等)打开制定名称的文件。如果操作成功呢 返回的文件对象可用于 I/O , 如果出错错误底层类型是 *PathError<br>
第二个参数:文件打开模式(可以组合)<br>
第三个参数:权限控制 liunx<br>
r -> 4<br>
w -> 2<br>
x -> 1<br>
基本应用案例-方式一<br>
1. 创建一个新的文件, 写入内容 5 句 "hello, Gardon"<br>
实现代码<br>
2. 打开一个存在的文件中,讲原来的内容覆盖成新的内容 10 句 "你好, 张三"<br>
3. 打开一个存在的文件, 在原来的内容追加内容 "ABCI ENGLISHI"<br>
4. 打开一个存在的文件,将原进来的内容读取显示到终端商, 并且追加 5 句 "hello 北京"<br>
使用 os.OpenFile(), bufio.NewWriter(), * Writer 的方法 WriteString 完成上面的任务<br>
方式二<br>
编写一个程序讲文件容写到另外一个文件,注:这两个文件已经存在了, 说明:使用 ioutil.ReadFile/ioutil.WriteFIle 完成写文件的任务<br>
代码实现
判断文件或者文件夹是否存在<br>
golang <font color="#c41230">判断文件或文件夹是否存在</font>的方法使用 os.Stat() 函数返回值的错误进行判断:<br>
1. 如果返回错误为 nil, 说明文件或者文件夹存在<br>
2. 如果返回的错误类型使用 os.IsNotExist() 判断为 true , 说明文件或者文件夹不存在<br>
3. 如果返回的错误为其他类型, 则不确定是否存在<br>
实现代码:<br><br>func PathExists(path string) (bool, error) {<br> _, err := os.Stat(path)<br> if err != nil {<br> return true, nil<br> }<br> if os.IsNotExist(err) {<br> return false, nil<br> }<br> return false, err<br>}<br>
拷贝文件
将一张图片/电影/mp3 拷贝到另外一个文件 e:/abc.jpg io 包 func Copy (dst Writer, src Reader) (written int64, err error)<br>
实现代码<br>
统计英文, 数字,空格,和其他字符数量<br>
实现代码<br>
命令行参数
我们希望获取到命令行输入的各种参数, 命令行参数<br>
基本介绍
os.Args 是一个string 切片, 用来存储所有的命令行参数<br>
示例代码<br>
flag 包来解析命令行参数<br>
说明:前面的方式是比较原生的方式, 对解析参数不是特别的方便, 特别是带有指定参数形式的命令行<br>
比如:cmd> main.exe -f c:/aa.txt -p 200 -u root 这样的命令行, go 设计者给我们提供了 flag 包, <br>可以方便的解析命令行参数,<font color="#c41230">而且参数顺序可以随意</font><br>
代码示例<br>
调用 <br>go run main.go -u root -p zshhda -h 127.0.0.1 -port 100000<br>
JSON 数据格式<br>
JSON 的序列化<br>
化序列案例 <br>
json tag<br>
案例:<br><br>//定义一个结构体<br>type Monster struct {<br> Name string `json:"name"`<br> Age int `json:"age"`<br> Birthday string `json:"birthday"`<br> Sal float64 `json:"sal"`<br> SKill string `json:"skill"`<br>}
输出 json : <br> {"name":"XXX","age":500,"birthday":"2009-01-01","sal":1000,"skill":"AAA"}
JSON 反序列化<br>
介绍: json 反序列化是指, 将 <font color="#c41230">json 字符串反序列化成对应的数据类型</font>(比如结构体, map, 切片)的操作<br>
应用案例:我们这里介绍字符串反序列化, 成结构体, map 和 切片<br>
测试代码
小结说明
1. 在反序列化一个字符串的时候,确保反序列化的数据类型和反序列化前的数据类型一致<br>
2. 如果字符串通过程序获取到的, 则不需要对 "" 转义处理<br>
groutine(协程) 和 channcel(管道)<br>
Go 协程和 Go 主线程<br>
1. Go 主线程 (有人直接称为线程/也可以理解为进程);一个Go线程上, 可以开启多个协程,可以这样理解协程就是一个轻量级的线程<br>
2. Go 协程的特点<br>
1). 有独立的栈空间<br>
2) 共享线程堆空间<br>
3)调度由用户控制<br>
4)协程是轻量级的线程
channel (管道)<br>
不同 goroutine 之间如何通讯<br>
2. channel<br>
为什么需要 channel <br>
前面降到使用去全局变量加锁同步来解决 goroutine 的通讯, 但并不完美<br>
1. 主线程等待所有的 goroutine 全部完成的时间很难去而定,我们这里设置的是 10秒, 但是仅仅是估算<br>
2. 如果主线程休眠的时间长了, 会加长等待时间,如果等待时间断了,还有可能 goroutine 处于工作状态, 这时也会随着主线程的退出而消亡<br>
3. 通过全局变量来加锁实现通讯, 也并不利用多个协程对全局变量的读写操作<br>
4. 上面的分析, 都在为了引出一个新的通讯机制 channel<br>
channel 的基本介绍<br>
1. channel 的本质是一个数据结构 - 队列<br>
2. 数据是先进先出的 【FIFO】<br>
3. 线程安全, 多 goroutine 访问时, 不需要加锁, 就是说 channel 本身就是线程安全的 <br>
4. channel 是有类型的, 一个 string 的 channel 只能存放 string 数据类型<br>
示意图
channel 的基本使用<br>
channel 初始化<br>
使用make 进行初始化<br>
var intChan chan int<br>intChan = make(chan int, 10)<br>
向 channel 写入(存放)数据<br>
var intChan chan int<br><br>intChan =make(chan int , 10)<br>num := 999<br>intChan <- 10 <br>intChan <- num<br>
简单使用代码<br>
channel 的使用注意事项<br>
1. channel 中只能存放指定的数据类型<br>
2. channel 的数据放满后, 就不能再放了<br>
3. 如果 cahnnel 取出数据后,可以继续放了<br>
4. 没有使用协程的情况下,如果 channel 数据取完了, 再取出就会报 dead lock<br>
读写 channel 的案例<br>
1. 创建一个 intChan 最多可以窜访 3 个 int , 演示存 3 个数据到 intChan , 然后在取出这三个 int<br>
实现代码<br>
2. 创建一个 mapChan , 最多可以存放 10 个 map[string]string 的key-val , 演示数据读取<br>
3. 创建一个 catChan , 最多可以存放 10个 Cat 结构体变量,演示写入和读取的方法<br>
4. 创建一个 catChan , 最多可以存放 10个*Cat 结构体变量,演示写入和读取的方法
5.创建一个 allChan , 最多可存放 10 个任意类型变量, 演示写入和读取的方法<br>
6. 看看代码会输出什么?<br>
func main() {<br> var allChan chan interface {}<br> allChan = make(chan interface{}. 10 )<br> cat1 := Cat{"tom", 2}<br> cat1 := Cat{"jack..", 1}<br> allChan <- cat1<br> allChan <- cat2<br> allChan <- 10 <br> allChan <- "jack"<br> <br> cat11 := <- allChan<br> fmt.Println(cat11.Name)<br><br>}<br>
更正后的代码<br>
完成如下练习
1. 创建一个 Person 结构体 {Name, Age , Address}<br>
2. 使用 rand 方法随机创建10 个Person 实例, 并放入到 channel 中<br>
3. 遍历channel, 将各个 Person 实例在终端上显示<br>
channel 的遍历和关闭<br>
channel 的关闭<br>
使用内置函数 close 可以关闭 channel , 当关闭 channel 之后,就不能再向 channel 写数据了, <br>但是仍然可以从该 channel 中读取数据<br>
关闭演示代码<br>
channel 的遍历<br>
channel 支持 for - range 的方式进行遍历, 注意两个细节<br>
1. 在遍历时, <font color="#c41230">如果 channel 没有关闭, 则会出现 deadlock 的错误</font><br>
2. 在遍历时, <font color="#c41230">如果 channel 已经关闭</font>, 则会正常遍历数据, 遍历完成后,还会退出遍历<br>
管道遍历
//遍历管道<br> intChan2 := make(chan int, 100)<br> for i := 0; i <= 100; i++ {<br> intChan2 <- i * 2<br> }<br> //这里管道没有关闭出现死锁, 提示:fatal error: all goroutines are asleep - deadlock!<br> close(intChan2) //关闭管道<br> for v := range intChan2 {<br> fmt.Printf("v=%v\n", v)<br> }
channel 遍历和关闭的演示<br>
channel 遍历和关闭小结<br>
1. 前面的案例演示了对管道数据进行遍历, 就是等价于从管道中取出数据即: <- ch]<br>
2. 注意要 close 管道, 否则会出现 deadlock<br>
3. 在 for range 管道时, 当遍历到最后的时候,发现管道关闭了, 就结束从管道读取数据的遍历工作,正常退出<br>
4. 在 for range 管道时, 当遍历到最后的时候, 发现没有没关闭, 程序会认为可能有数据写入, 因此会等待,如果程序没有写入数据,则就会死锁<br>
应用案例
应用案例1 <br>
完成 goroutine 和 channel 协同工作的案例 , 具体要求<br>
1. 开启一个 writeData 协程 , 向管道 intChan 中写入 50 个整数<br>
2. 开启一个 readData 协程, 从管道 intChan 中读取 writeData 写入数据<br>
注意: writeData 和 readData 的操作都是同一个管道<br>
3. 主线程需要 writeData 和 readData 协程都完成工作才能退出<br>
案例代码<br>
案例图示
作业1(goroutine 练习)
1. 启动一个协程, 讲 1-2000 的数放入到一个channel 中, 比如 numChan <br>
2. 启动 8 个协程, 从 numChan 中取出 (比如 n)并且计算 1 + 2 + 。。 n 的和, 并且存放到 resChan <br>
3. 最后 8 个协同完成工作后 ,再遍历 resChan , 显示结果 如 res[i] = 1... res[10] = 55 .. <br>
4. 注意:考虑 resChan chan int 师傅合适?<br>
作业2(goroutine + channel 配合完成排序, 并写入文件)<br>
1. 开启一个协程 writeDataToFile , 随机生成 1000 个数据, 存放到文件中<br>
2. 当 writeDataToFile 完成 1000 个数据写到文件后, 让 sort 协程从文件中读取 1000 个文件<br>, 并且完成排序, 重写写入到另外一个文件<br>
3.考察点:协程和管道 + 文件的综合使用<br>
4. 功能拓展:开 10 个协程 writeDataToFile , 每个协程随机生成 1000 个数据, 存入到 10个文件中<br>
5. 当10 个文件都生成了, 让 10 个 sort 协程从10 个文件中读取 1000 个数据, 并且完成排序,重新写入 10 个文件中<br>
应用实例2 阻塞<br>
实验案例<br>
//创建两个管道<br> intChan := make(chan int, 10) // 10 -> 50 的化数据一下就放入了<br> exitChan := make(chan bool, 1)<br><br> go readData(intChan, exitChan)<br> go writeData(intChan)<br><br> for {<br> _, ok := <-exitChan<br> if !ok {<br> break<br> }<br> }
问题: 如果注释掉 go readData(intChan, exitChan) 会怎么样<br>
答:如果只是向管道中写入数据, <font color="#f15a23">而没有读取(如果, 编译器(运行)发现了一个管道只有写, 而没有读取, 则该管道, 会阻塞。<font color="#c41230">写管道和读管道的频率不一致, 无所谓</font>)</font>, 就会出现阻塞而导致 dead lock , 原因是 intChan 容量是 10 , 而代码writeData 会写入 50 个数据,因此会阻塞在 writeData ch <- i<br>
演示完整代码<br>
3. 应用案例
需求:要求统计 1-2000000 的数字中, 那些是素数?这个问题在开篇就提出了,我们现在有了 goroutine 和 channel 的知识后, 就可以完成了
分析思路
1. 传统的方法, 使用一个循环, 循环的判断各个数是不是素数<br>
2. 使用并发/并行的方式,<font color="#c41230"> 将统计素数的任务分配给(4个)goroutine 取完成</font>,完成任务时间段<br>
分析思路
实现代码
说明:使用 goroutine 完成后, 可以使用传统的方式来统计下看看这完成这个任务, 各自耗费的时间是多少, [用 map 保存 primeNum]<br>
普通方法和协程方法时间统计<br>
普通方法
协程方法
4. 练习题目<br>
1. 启动一个协程, 将 1-2000 的数放入到一个 channel 中,比如 numChan<br>
2. 启动 8 个协程, 从 numChan 取出数据(比如n), 并且计算 1 +++ .. n 的值, 并且存放到 resChan<br>
3. 最后8个协程完成工作后 ,再遍历 resChan, 显示结果 如 res[1] = 1, ,,,res[10] = 55<br>
4. 注意: 考虑 resChan chan int 是否合适<br>
channel 的注意事项和细节<br>
1. channel 可以声明为只读的 , 或者只写的性质<br>
2. channel 只读和只写的最佳时间案例<br>
3. 使用 select 可以解决从管道独立数据的阻塞问题<br>
select 的案例<br>
4. goroutine 中只用recover, 解决协程出现 panic , 导致程序崩溃问题<br>
示例代码<br>
说明: 如果我们启动了一个协程,但是这个协程出现了 panic , 我们没有捕获这个 panic , 进行处理, <br>这样即使这个协程发生的问题,但是线程任然不受影响, 可以继续执行。<br>
反射
反射能够解决什么问题(应用场景)
<font color="#c41230">1. 定义两个匿名函数</font><br><br>test1 := func(v1 int, v2 int) {<br> t.Log(v1, v2)<br>}<br><br>test2 := func(v1 int, v2 int, s tring) {<br> t.Log(v1, v2, s)<br>}<br>
<font color="#c41230">2. 定义一个适配器函数做统一的处理, 大致结构如下:<br></font>bridge := func (call interface{}. args.. interface{}) {<br>// 内容<br>}<br><br>//实现调用 test1 对应函数<br>bridge(test1, 1, 2)<br>//实现调用 test2 对应函数<br>bridge(test2, 1, 2, "test2")<br>
<font color="#c41230">3. 要求使用反射机智完成(note; 学习 reflect 后, 解决)</font>
反射得重要函数和概念
1. reflect.TypeOf(变量名), 获取变量的类型, 返回 reflect.Type 类型
2. reflect.ValueOf(变量名)。 获取变量的值, 返回 reflect.Value 类型 reflect.Value 是一个结构体类型。 通过 refect.Value , 可以获取到该函数的很多信息。
3. 变量, interface {} 和 reflect.Value 是一个相互转换的,这个点在实际开发中, 回经常使用到,画出示意图
反射的应用场景
反射常见应用场景有两种
1. 不知道接口调用那个函数, 根据传入的参数在运行时候确定调用的具体接口, 这种需要对函数或方法反射,例如这种桥接模式, 比如之前提到问题
func bridge(funcPtr interface{}, args ...interface{})
第一个函数 funcPrt 以接口的行是传入函数指针, 函数参数 args 以可变参数的形式传入, bridge 函数中可以用反射来动态执行 funcPtr 函数
快速入门案例
1. 编写一个案例, 演示对 (基本数据类型, inteface{}, reflect.Value) 进行反射的基本操作。
演示代码
2. 编写一个哪里,演示对(结构体类型, interface{}, reflect.Value) 进行反射的基本操作
演示代码
0 条评论
下一页