[toc]
1.Go简介
## 1.1 简介
Go(又称Golang Go语言)是Google的Robert Griesemer,Rob Pike及Ken Thompson(C语言)开发的一种计算机编程语言。
==Golang,也叫Go语言,也简称Go. 3个名字,1个意思。==
Go语言是谷歌推出的一种编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发Go,是因为过去10多年建软件开发的难度令人沮丧。派克表示,和今天的C++或C一样,Go是一种系统语言。他解释道,“使用它可以进行快速开发,同时它还是一个真正的编译语言,我们之所以现在将其开源,原因是我们认为它已经非常有用和强大。”
2007年开发第一版。
罗伯特.格瑞史莫(Robert Griesemer)、罗伯派克(Rob Pike)、肯.汤普逊(Ken Thompson)于2007年9月开始设计Go,稍后lan Lance Taylor、Russ Cox加入项目。
==肯.汤普逊(Ken Thom==pson)==:unix设计者 C语言发明者 B语言发明者 飞行员 83年 图灵奖 美国工程院院士
罗伯派克(Rob Pike): utf-8格式 射箭(奥运会奖牌)
罗伯特.格瑞史莫(Robert Griesemer):
发展简史:
2007年 开始设计
2009年11月 Google将Go语言以开放源代码的方式向全球发布
2015年8月,Go1.5版本发布,本地更新中移除了“最后参与的c代码”
2017年2月,Go语言Go1.8版本
2017年8月,Go语言Go1.9版本
2018年2月,GO语言Go1.10版本
2018年8月,Go语言Go 1.11版本
2019年2月,Go语言Go1.12版本
2019年9月,Go语言Go1.13版本
2020年2月,Go语言GO1.14版本
2020年8月,Go语言Go1.15版本
....一直迭代
Go语言吉祥物:金花鼠
1.2 设计初衷
(1)计算机硬件技术更新频繁,性能提高很快。目前主流的编程语言发展明显落后于硬件,不能合理利用多核多CPU的优势提升软件性能。
(2)软件系统复杂度越来越高,维护成本越来越高,目前缺乏一个足够简洁高效的编程语言。
(3)企业运行维护很多的C/C++的项目,C/C++程序运行速度虽然很快,但是编译速度确实很慢,同时还存在内存泄漏的一系列的困扰需要解决。
Go语言是Google公司大佬开发的,主要起因于Google公司有大量的C程序项目,但是开发起来效率太低,维护成本高,于是就开发了Go语言来提高效率,而且性能只是差一点。
1.3 Golang vs Java
Golang侵占Java份额,主要是中间件编写。
架构师:
==中间件开发 ------ 选择 Golang==
==业务逻辑 ------- 选择Java==
问题来了,为什么中间件开发,要选择golang语言?
-----因为==golang天生支持高并发==。
Golang内置并发性允许同时处理多项任务。Python 也使用并发性,但并非内置,它通过线程实现并行化。 这意味着如果你打算处理大型数据集,Golang 似乎是更适合的选择。
## 1.4 应用领域
(1)中间件开发 ==docker== ==k8s==都是go开发的
(2)Go分布式/云计算软件工程师 cdn 云计算能力
(3)区块链工程师 go就是区块链技术的主流开发语言
1.5 用go语言的公司
(1)Google
(2)FaceBook
(3)腾讯
(4)百度
(5)京东
(6)小米
(7)360
美团、滴滴、新浪、阿里、七牛
Golang特别适合做网络并发的服务,这是他的强项。
2.Go下载和安装
2.1 开发工具
(1)==Visual Studio Code== 简称: ==vs Code==
(2)Sublime
(3)Vim
(4)Eclipse
(5)LiteIDE
(6==)IntelliJ IDEA==,需要安装Go插件
2.2 Go下载
SDK (Software Development kit软件开发工具包)
SDK是提供给开发人员使用的,其中包含了对应开发语言的工具包。
SDK下载: Go语言官网:http://golang.org
Goland中文社区:https://studygolang.com/dl (1.15.6版本)
下载之后的文件:
2.3 Go安装
(1)解压
解压后目录:
测试安装是否成功:
如果想要在任意的路径下执行某个命令,就需要将这个命令所在的目录配置到环境变量path中去,将命令“注册”到当前的计算机中。
配置go的环境变量:
2.4 Idea安装go插件
2.5 DOS命令
黑窗口 显得高大上
1.进入盘符: d:
2.显示详细信息: dir
3.改变当前目录: cd zhaoss
4.返回上一层: cd ..
5.清屏 csl
6.切换上下命令: 上下箭头
7.补全命令:Tab键
8.创建目录 md
9.删除目录 rd
3.基础语法
3.1 Go执行流程
3.1.1 Go基本目录结构
在VSCode下写代码:在VSCode中打开上面的目录:
3.1.2 第一个HelloWord
3.1.3 编译teset.go
对源文件test.go进行编译
==go build test.go==
3.1.4 执行test.ext
3.1.5 编译+执行
==go run test.go==
3.1.6 Golang执行流程
1.执行流程分析:
2.上述两种执行流程方式的区别
(1)在编译时,编译器将程序运行依赖的库文件包含在可执行文件中,所以,可执行文件变大了很多:
(2)如果我们先编译生成了可执行文件,那么我们可以将该可执行文件拷贝到没有go开发环境的机器上,仍然可以运行
(3)如果我们是直接go run 源代码,那么如果要在另外一个机器上这么运行,也需要go开发环境,否则无法执行
3.注意事项:
编译后的文件可以另外指定名字。
3.2 标识符
[1] 标识符:读音 biao zhi fu Java里面叫变量的名称
[2]什么是标识符?
变量,方法等,只要是起名字的地方,那个名字就是标识符,
var ==age== int = 19 var ==price== float64 = 9.8
[3] 标识符的定义规则:
1.三个可以(组成部分):数字,字母,下划线_
ps:字母含义比较广泛,使用汉字也是可以的,不建议使用,建议用字母:26个字母
2.四个注意:不可以以数字开头,严格区分大小写,不能包含空格,不可以使用Go中的保留关键字
3.见名知意:增加可读性
4.下划线"_"本身在Go中是一个特殊的标识符,称为空标识符。可以代表任意其他的标识符,但是它对应的值会被忽略(比如:忽略某个返回值)。所以仅能被作为占位符使用,不能单独作为标识符使用。
5.可以用如下形式,但是不建议:var int int = 32 (int,float32,float64等不算关键字,但是也尽量不要使用)
6.长度无限制,但是不建议太长 abddfdbbdgadfdasfasdgfdsagadsgagadsgadsg
7.起名规则:
(1)包名:尽量保持package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,
和标准库不要冲突
.为什么之前在定义源文件的时候,一般我们都用package main包?
main包是一个程序的入口包,所以你main函数它所在的包建议定义为main包,如果不定义为main包,那么就不能得到可执行文件
(2) 变量名、函数名、常量名:采用驼峰法
就是单词按照大小写分开
(3)如果变量名、函数名、常量名首字母大写,则可以被其他的包访问
如果首字母小写,则只能在本包中使用
注意:import导入语句通常放在文件开头包声明语句的下面。
导入的包名需要使用双引号包裹起来
包名是从$GOPATH/src/后开始计算的,使用/进行路径分隔。
util.go
test.go
3.3 语法注意事项
1.源文件以“go”为扩展名
2.程序的执行入口是main()函数
Java叫方法
Go叫函数
3.严格区分大小写
4.方法由一条条语句构成,每个语句后不需要分号(Go语言会在每行后自动加分号),这也体现了Golang的简洁性
结尾加上分号也不会报错
5.Go编译器是一行行进行编译的,因此我们一行就写一条语句,不能把多条语句写在同一个,否则报错
6.定义的变量或者import的包人工没有使用到,代码不能编译通过
定义了变量,没有被使用会报错,多余的东西会报错
7.大括号都是成对出现的,缺一不可
3.4 注释
1.注释的作用:
用于注解说明解释程序的文字就是注释,注释提高了代码的阅读性;
注释是一个程序员必须要具有的良好编程习惯。
将自己的思想通过注释先整理出来,再用代码去体现。
2.Golang中注释的类型
==(1) 行注释 //== vscode快捷键 ctrl+/ 再按一次取消
==(2) 块注释 /**/== vscode快捷键:shift+alt+a 再按一次取消
3.5 代码风格
1.注意缩进
向后缩进 Tab键
向前取消缩进:shift+Tab键
通过命令完成格式化操作:gofmt -w test.go 才会写入到源文件中
2.成对编程{} () "" ''
3.运算符两边加空白
4.注释:推荐行注释
5.以下代码是错误的:
原因:go的设计者想要开发者有统一的代码风格,一个问题尽量只有一个解决方案是最好的
6.行长约定
一行最长不超过80个字符,超过的请使用换行展示,尽量保持格式优雅
3.6 API
API(Application Programming Interface) 应用程序接口
可以理解为 产品说明书 接口说明书
Go语言提供了大量的标准库,因此Google公司也为这些标准库提供了相应的API文档,用于告诉开发者如何使用这些标准库,以及标准库包括的方法:
Golang中文网在线标准文档: https://studygolang.com/pkgdoc
对应源代码:
Println函数对应源代码:
3.7 关键字
1.关键字寄宿程序发明者规定的特殊含义的单词,又叫保留字。
go语言中一共25个关键字。
2.预定义标识符:一共36个预定义标识符,包含基础数据类型和系统内嵌函数
4.变量与基本数据类型
变量的数据类型:
4.1 整数类型
1.整数类型:就是用于存放整数值的,比如10,-45,,6712等待
2.有符号的整数类型:
运行结果:
3.符号的整数类型:
4.其他整数类型:
注意:Golang的整数类型,默认声明为int类型
5.这么多整数类型,使用的时候该如何选择?
Golang程序中整型变量在使用时,遵守保小不保大的原则,
即:在保证程序正确运行下,尽量使用占用空间小的数据类型
4.2 浮点类型
1.浮点类型介绍:
简单的说,就是用于存放小数,比如3.14,0.28,-7.19等待
2.浮点类型种类:
没有float类型
注意:底层存储空间和操作系统无关
浮点类型底层存储:符号位+指数位+尾数位,所以尾数指数存了一个大概,很可能会出现精度的损失。
3.代码:
float.go
运行结果:
4.3 字符类型
1.Golang中没有专门的字符类型,如果要存储单个字符(字母),一般使用==byte==来保存
2.Golang中字符使用UTF-8编码
3.ASCII码表
4.代码
运行结果:
6.转义字符
==\n== 换行
==\‘’== 双引号原样输出
\ \ 输出\
4.4 布尔类型
1.布尔类型也叫bool类型,==bool==类型数据只允许取值true和false
2.布尔类型占一个字节
3.布尔类型适用于逻辑云想,一般用于程序流程控制
4.代码:
bool.go
运行结果:
4.5 字符串类型
1.字符串就是一串固定长度的字符连接起来的字符序列 ==string==
2.字符串的使用:
string.go
运行结果:
4.6 基本数据类型默认值
==整数类型==默认值: ==0== ==float32==默认值: ==0== ==float64==默认值: ==0== ==布尔类型==默认值: ==false== ==string类型==默认值: ==空==
代码:
defaultValue.go
运行结果:
4.7 基本数据类型转换
1.Go在不同类型的变量之间赋值时需要显式转换,并且只有显式转换(强制转换)
2.语法:
表达式 T(v)将值v转换为类型T
T: 就是数据类型
v: 就是要转换的变量
3.代码:
运行结果:
4.8 基本类型转为string
1.基本数据类型和string的转换介绍
在程序开发中,我们经常需要将基本数据类型转为string类型,或者将string类型转换成基本数据类型
2.基本类型转string类型
方式一:fmt.Sprintf("%参数",表达式)
方式二:使用strconv包的函数
3.代码
方式一: ==推荐使用==
方式二:
==用的少 不好用==
4.9 string转为基本类型
1.string类型转为基本数据类型
方式:使用strconv包下面的函数
2.代码:
运行结果:
4.10 go变量
一个程序就是一个世界
不论是使用哪种高级程序语言编写程序,变量都是其程序的基本组成单位
变量相当于内存中一个数据存储空间的表示
变量的使用步骤:
1.声明
2.赋值
3.使用
运行结果:
变量的4种使用形式:
运行结果:
一次性声明多个声明(多变量声明)
全局变量:
定义在函数外的变量
运行结果:
4.11 指针
1.基本数据类型和内存
运行结果:
2.指针数据类型
总结:指针就是内存地址
运行结果:
(1)可以通过指针改变指向值
(2)指针变量接收的一定是地址值
(3)指针变量的地址不可以不 匹配
注意:*float32意味着这个指针指向的是float32类型的数据,但是&num对应的是int类型的,不可以。
(4)基本数据类型(又叫值类型),都有对应的指针类型,形式为*数据类型,
比如int的对应的指针就是int,float32对应的指针类型就是 float32,以此类推。
5.运算符
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等
go语言中,没有三元运算符
5.1 算术运算符
【1】算术运算符:+ - * / % ++ --
【2】介绍:算术运算符是对数值类型的变量进行运算的,比如,加减乘除
【3】代码展示
运行结果:
go语言里,++,--非常简单,只能单独使用,不能参与到运算中去
go语言里,++,--只能在变量后面,不能写在变量的前面:++a,--a 都是错误写法
5.2 赋值运算符
【1】赋值运算符: = += -= /= %= -=*=
【2】赋值运算符就是将某个运算后的值,赋给指定的变量
【3】代码展示
运算结果:
5.3 关系运算符
【1】关系运算符: == != < = > <= >=
关系运算符的结果都是bool型,也就是要么是true,要么是false
【2】关系表达式经常用在流程控制中
【3】代码展示:
运行结果:
5.4 逻辑运算符
【1】逻辑运算符: && (逻辑与) ||(逻辑或) !(逻辑非)
【2】用来进行逻辑运算的
【3】代码展示
(省略)
5.5 位运算符
(省略)
5.6 其他运算符
【1】其他运算符:
&: 返回变量的存储地址
*: 取指针变量对应的数值
【2】代码:
运行结果:
5.7 运算符的优先级别
Go语言有几十种运算符,被分成十几个级别,有的运算符优先级不同,
有的运算符优先级相同,请看下表。
一句话:==为了提高优先级,可以加()==
6.流程控制
【1】 流程控制的作用
流程控制语句是用来控制程序中各语句执行顺序的,可以把语句组合成能完成一定功能的小逻辑模块。
【2】控制语句的分类
控制语句分为三类:顺序、选择、循环
三种流程控制语句就能表示所有的事情,不信,你可以试着拆分你遇到的各种事情,这三种基本逻辑结构是相互支撑的,它们共同构成了算法的基本结构,物流怎样复杂的逻辑结构,都可以通过它们来表达。所有任何一种高级语言都具备上述两种结构。
【3】流程控制的流程:
6.1 if分支
1.if单分支
【1】基本语法
==if 条件表达式 {==
==逻辑代码==
==}==
当条件表达式为true时,就会执行代码
注意:条件表达式左右的()可以不写,也建议不写
if和表达式中间,一定要有空格
在Golang中,{}是必须有的,就算你只有一行代码。
【2】代码:
运行结果:
2.if多分支
【1】基本语法
==if 条件表达式 {==
==逻辑代码1==
==}else{==
==逻辑代码2==
==}==
当表达式成立,记执行逻辑代码1,否则执行逻辑代码2。{}也是必须的。
注意:下面的格式是错误的:
if 条件表达式 {
逻辑代码1
}
else{
逻辑代码2
}
PS:空格加上,美观规范
【2】代码:
运行结果:
3.if双分支
【1】基本语法:
==if 条件表达式1 {==
==逻辑代码1==
==}else if 条件表达式2 {==
==逻辑代码2==
==}==
==.......==
==else{==
==逻辑代码n==
==}==
【2】代码:
运行结果:
6.2 switch分支
【1】基本语法:
==switch 表达式{==
==case 值1,值2,......==
==语句块1==
==case 值3,值4,......==
==语句块2==
==......==
==default:==
==语句块==
==}==
【2】代码
运行结果:
【3】注意事项:
1.switch后是一个表达式(即:常量值,变量、一个有返回值的函等都可以)
2.case后面的表达式如果是常量值,则要求不能重复
3.case后面的各个值的数据类型,必须和switch的表达式数据类型一致
4.case后面可以带多个值,使用逗号分隔,比如case 值1,值2
5.case后面不需要带break,==Java中需要加====break==
6.default语句不是必须的,位置也是可以随意的
7.switch后也可以不带表达式,当做if分支来使用 -----不推荐
8.switch后也可以直接声明/定义一个变量,分号结束,不推荐
9.switch穿透,利用fallthrough关键字,如果在case语句块后增加fallthrough,
则会继续执行下一个case,也叫switch穿透 ----不推荐使用
6.3 for循环
【1】语法结构:
==for(初始表达式;布尔表达式;迭代因子){==
==循环体==
==}==
for循环语句是支持迭代的一种通用结构,是最有效、最灵活的循环结构。for循环在第一次反复之前要进行初始化,即执行初始化表达式;随后,对布尔表达式进行判定,如判定结果为true,则执行循环体,否则,终止循环,最后在每一次反复的时候,进行某种形式的“步进”,即执行迭代因子。
1.初始化部分是设置变量的初值
2.条件判断部分为任意布尔表达式
3.迭代因子控制循环变量的增减
for循环在执行条件判断后,先执行的循环体部分,再执行步进。
for循环结构的流程图如图所示:
==go语言没有while和do...while,只有for循环==
for的初始表达式不能用var定义变量的形式,要用 :=
for循环实际上就是让程序员写代码的效率高了,但底层该怎么执行还是怎么执行的,底层效率没有提高
只是程序员写代码简洁了。
【2】代码
运行结果:
【3】注意事项
1.格式灵活
2.死循环
3.for range循环 ---foreach
for range结构是Go语言特有的一种迭代结构,在许多情况下非常有用,for range可以遍历数组、切片、字符串、map及通道,for range语法上类似于其他语言的foreach语句,一般形式为:
==for key,value := range coll{==
==.....==
==}==
代码:
运行结果:
6.4 关键字
6.4.1.break
跳出循环体
【1】感受break在循环中的作用
运行结果:
总结:
1.switch分支中,每个case分支后都用break结束当前分支,但是在go语言中break可以省略不写
2.break可以结束正在执行的循环
【2】深入理解
break的作用结束离它最近的循环
6.4.2 continue
结束本次循环
【1】continue的作用
运行结果:
【2】深入理解
总结:continue的作用是结束离它近的那个循环
6.4.3 goto
【1】Golang的goto语句可以无条件地转移到程序中指定的行
【2】goto语句通常和条件语句配合使用,可以用来实现条件转移
【3】在Go程序设计中一般不建议使用goto语句,以免造成程序流程的混乱。
【4】代码展示
运行结果:
6.4.4 return
【1】return的作用
结束当前的函数(方法)
【2】代码
运行结果:
7.函数和包
在Java中叫方法
7.1 为什么要使用函数
提高代码的复用性,减少代码的冗余,代码的维护性也提高了
7.2 函数的定义
为完成某一个功能的程序指令(语句)的集合,称为函数
7.3 基本语法
==func 函数名(形参列表)(返回值列表){==
==执行语句==
==return 返回值列表==
==}==
7.4 函数的调用案例
1.函数:对特定功能进行提取,形成一个代码片段,这个代码片段就是我们所说的函数
2.函数的作用:提高代码的复用性
3.函数和函数是并列的关系,所以我们定义的函数不能写到main函数中
4.函数名:
遵循标识符命名规范:见名知意 驼峰命名
首字母不能是数字
首字母大写改函数可以被本包文件和其他包文件使用(类似public)
首字母小写只能被本包文件使用,其他包文件不能使用(类似private)
5.形参列表:
个数:可以是一个参数,可以是n个参数,也可以是0个参数
形式参数列表作用:接收外来的数据
实际参数:实际传入的数据
6.返回值类型列表:函数的返回值对应的类型应该写在这个列表中
可以返回0个数据
可以返回1个数据
可以返回多个数据
==如果返回值类型就只有一个的话,那么()是可以省略不写的==
返回多个结果代码:不建议使用
7.通过案例感受函数的内存分析:值传递
8.Golang中函数不支持重载
9.Golang中支持可变参数(如果你希望函数电邮可变数量的参数)
10.基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响原来的值。
==在Java中,数组是地址传递,但在Go语言中,是值传递==
运行结果:
11.以值传递方式的数据类型,如果希望在函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以
指针的方式操作变量。从效果来看,类似引用传递。
运行结果:
内存分析:
12.在Go中,函数也是一种数据类型,可以赋值给一个变量,则变量就是一个函数类型的变量了。通过该变量可以
对函数进行调用。
运行结果:
13.函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用(把函数本身当做一种数据类型)
运行结果:
14.为了简化数据类型定义,Go支持自定义数据类型,
基本语法:
type 自定义数据类型名 数据类型
可以理解为:相当于起了一个别名
例如:type myInt int --->这时,myInt就等价于int来使用了
例如:type mySum func(int,int) int --->这时mySum就等价一个函数类型func(int,int) in
15.支持对函数返回值命名
7.5 包
7.5.1 使用包的原因
(1) 我们不可能把所有的函数都放在同一个源文件中,可以分门别类的把函数放在不同的源文件中
(2) 解决同名问题:两个人都想定义一个同名的函数,在同一个文件中是不可以定义相同名字的函数的。
此时可以用包来区分
7.5.2 案例展示包
项目结构:
main.go
util.go
运行结果:
说明:
1.package进行包的声明,建议:包的声明这个包和所在文件夹同名
2.main包是程序的入口包,一般main函数会放在这个包下
3.打包语法
package 包名
4.引入包的语法:import "包的路径"
包名是从 $GOPATH/src/后开始计算的,使用/进行路径分隔
5.如果有多个包,建议一次性导入,格式如下:
6.在函数调用的时候前面要定位到所在的包
7.首字母大写,函数可以被其他包访问
8.一个目录下不能有重复的函数
==同一个包下,不能有重复的函数,同一个包下的不同类也不行==
9.包名和文件夹的名字,可以不一样
10.一个目录下的同级文件归属一个包
一个源文件用了别名的包,那同目录下的同级文件也要用这个别名的包
11.包到底是什么:
(1)在程序层面,所有使用相同package包名的源文件组成的代码模块
(2)在源文件夹层面就是一个文件夹
(3)包相当于是一个类,包含包目录下所有函数的类,访问下面函数通过 ==包名.函数名== 访问
12.可以被包取别名,去别名后,原来的包名就不能使用了
7.6 init函数
【1】init函数:初始化函数,可以用来进行一些初始化的操作
每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用
运行结果:
【2】全局变量定义:init函数,main函数的执行流程?
先执行init函数,再试下main函数
【3】多个源文件都有init函数的时候,如何执行?
main.go
util.go
运行结果:
运行顺序总结:
7.7 匿名函数
【1】Go支持匿名函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数
匿名函数,该函数没有函数名称
【2】匿名函数的使用方式:
(1)==在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次 ----最常用,用得多==
运行结果:
(2)将匿名函数赋给一个变量(该变量就是函数变量),再通过改变量来调用匿名函数
==这种情况下,可以直接定义为普通函数,可以反复调用==
运行结果:
(3)如何让一个匿名函数,可以在整个程序中有效呢?将匿名函数给一个全局变量就可以了
7.8 闭包
【1】闭包就是一个函数和与其相关的引用环境组合的一个整体
【2】案例展示
运行结果:
感受:匿名函数中引用的那个变量会一直保存在内存中,可以一直使用
【3】闭包的本质
闭包的本质依旧是一个匿名函数,只是这个函数引入外界的变量/参数
匿名函数 + 引用的变量/参数 = 闭包
【4】特点
(1) 返回的是一个匿名函数,但是这个匿名函数引用到函数外的变量/参数,因此这个匿名函数就和变量/参数形成了一个整体,构成闭包
(2) 闭包中使用的变量/参数会一直保持在内存中,所以会一直使用--->意味着闭包不可滥用
(3)不使用闭包的时候,想保留的值,不可以反复使用
(4)闭包应用场景:闭包可以保留上次引用的某个值,我们传入一次就可以反复使用了
第1次调用和第n次调用,形成一个for循环
7.9 defer
【1】defer关键字的作用:
在函数中,程序员经常需要创建资源,为了在函数执行完毕后,及时的释放资源,Go的设计者提供了defer关键字。
【2】案例展示:
运行结果:
【3】代码变动一下,再次看结果:
运行结果:
发现:遇到defer关键字,会将后面的语句压入栈中,也会将相关的值同时拷贝入栈中,不会随着函数后面的变化
而变化。
【4】defer应用场景:
比如你想关闭某个使用的资源,在使用的时候直接随手defer,因为defer有延迟执行机制(函数执行完毕再执行defer压入栈的语句),所以你用完随手写个关闭,比较省心,省事。
7.10 系统函数
【1】统计字符串的长度,按字节进行统计
len(str)
使用内置函数不用导包,直接用就行
运行结果:
【2】字符串遍历:
方式一: 方式一:利用键值循环:for-range
运行结果:
方式二:利用r:[]rang(str)
运行结果:
【3】字符串转整数
==strconv.Atoi()==
【4】整数转字符串
==strconv.Itoa()==
【5】查找子串是否在指定的字符串中
【6】统计一个字符串有几个指定的子串
==strings.Count("golangandjava","go")==
【7】不区分大小写的字符串比较
==strings.EqualFold("hello","HELLO")==
【8】返回子串在字符串第一次出现的索引值,如果没有返回-1
==strings.Index("golangandjavaga","ga")==
【9】字符串的替换
==strings.Replace("goandjavagogo","go","golang",-1)==
【10】按照指定的某个字符,为分隔标识符,将一个字符串拆分成字符串数组
==strings.Split("go-python-java","-")==
【11】将字符串的字母进行大小写的转换
==strings.ToLower("Go")==
==strings.ToUpper("go")==
【12】将字符串左右两边的空格去掉
strings.TrimSpace(" go and java")
运行结果:
go and java
【13】将字符串左边指定的字符串去掉
strings.Trim("~golang~","~")
运行结果:
【14】将字符串左边指定的字符去掉
strings.TrimLeft("~golang~","~")
【15】将字符串右边指定的字符去掉
strings.TrimRight("~golang~","~")
【16】判断字符串是否以指定的字符串开头
strings.HasPrefix("http://java.sun.com","http")
【17】判断字符串是否以指定的字符串结尾
strings.HasSuffix("demo.png","jpg")
7.11 日期和时间相关函数
【1】时间和日期的函数,需要导入time包,所以你获取当前,就要调用函数Now函数
【2】日期的格式化
(1)将日期以年月日时分秒按照格式输出为字符串
time.Now()
fmt.Sprint()
(2)按照指定格式:
now2.Format("2006/01/02 15/04/05")
7.12 内置函数
【1】什么是内置函数
Golang设计者为了编程方便,提供了一些函数,这些函数不用导包就可以直接使用,我们称为Go的内置函数
【2】内置函数的存放位置
在builtin包下,使用内置函数,直接用就行
【3】常用函数:
(1)len函数
统计字符串的长度,按字节进行统计
(2)new函数
分配内存,主要用来分配值类型(int系列,float系列,bool,string,数组,和结构图struct)
内置函数new分配内存,其第一实参为类型,而非值。其返回值为指向该类型的新分配的零值的指针
运行结果:
(3)make函数
分配内存,主要用来分配引用类型(指针,slice切片,map,管道chan,interface等)
7.13 错误处理
【1】展示错误
(略)
【2】错误的处理/捕获机制
go中追求代码的优雅,引入机制:==defer+recover机制处理错误==
内置函数:recover
优点:提高 了程序的健壮性
自定义错误
【1】自定义错误:需要调用errors包下的New函数:函数返回error类型
errors.New("除数不能为0~~~")
运行结果:
有一种情况:程序出现错误以后,后续代码就没有必要执行,想让程序中断,退出程序
借助:builtin包下内置函数:panic
代码:
运行结果:
8.数组和切片
8.1 练习引入
(略)
缺点:成绩变量定义个数太多,成绩管理费劲,维护费劲 ----->将这个成绩找个地方存起来---->数组
----> 存储相同类型的数据
8.2 数组解决练习
运行结果:
总结:数组定义格式: ==var 数组名 []数据类型==
比如: var scores [5]int
每个每个空间占用的字节数取决于数组类型
8.3 内存分析
数组优点:访问/查询/读取 速度快
8.4 数组的遍历
(1)普通的for循环
(2)键值循环 for range
for range结构是Go语言特有的一种迭代结构,在许多情况下都非常有用,for range可以遍历数组,切片,字符串,map及通道,for range语法上类似于其他语言的foreach语句,一般形式为:
==for key,val := range coll{==
==......==
==}==
注意:1.coll就是你要遍历的数组
2.每次遍历得到的索引用key接收,每次遍历得到的索引位置上的值用value接收
3.key、value的名字随便起名 k、v key、value
4.key、value属于在这个循环中的局部变量
5.你想忽略某个值,用_就可以了
代码:
运行结果:
8.5 数组的初始化方式
运行结果:
8.6 数组的注意事项
1.长度属于类型的一部分
2.Go中数组属性类型,在默认情况下是值传递,因此沪进行值拷贝。
运行结果:
内存分析:
3.如果想在其他函数中,去修改原来的数组,可以使用引用传递(指针方式)
运行结果:
内存分析:
8.7 二位数组
二维数组的定义,并且有默认初始值:
运行结果:
内存分析:
### 8.7.4 二维数组的初始化
代码:
运行结果:
8.7.5 二维数组的遍历
方式1:普通for循环
代码:
运行结果:
方式2:for range循环
代码:
运行结果:
8.8 切片
### 8.8.1 切片介绍
【1】切片(slice)是golang中一种特有的数据类型
【2】数组有特定的用处,但是却有一些呆板(数组长度固定不可变),所以在Go语言的代码里并不是特别常见。相对的切片却是随处可见的,切片是一种建立在数组类型之上的抽象,它构建在数组之上并且提供更强大的能力和便捷。
【3】切片(slice)是对数组一个连续片段的引用,所以切片是一个引用类型。这个片段可以是整个数组,或者是由起始索引标识的一些项的子项。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口。
8.8.2 代码
切片的语法
==var slice []int = intarr[1:3]==
运行结果:
8.8.3 切片内存分析
### 8.8.4 切片的定
1.方式1:定义1个切片,然后让切片去引用一个已经创建好的数组
==var slice []int = intarr[1:3]==
2.方式2:通过make内置函数来创建切片,基本语法:
==var 切片名 type = make([],len,[cap])==
运行结果:
注意:make底层创建一个数组,对外不可见,所以不可以直接操作这个数组,要通过slice去间接的访问各个元
素,不可以直接对数组进行操作
3.方式3:定义一个切片,直接就指定具体数组,使用原理类似make的方式
==slice3 := []int {1,4,7}== 这其实就是定义了一个数组
运行结果:
8.8.5 切片的遍历
1.方式1:普通for循环
运行结果:
2.方式2:for range
运行结果:
8.8.6 注意事项
1.切片定义后不可以直接使用,需要让其引用到一个数组,或者make一个空间供切片来使用
运行结果:
2.切片使用不能越界
3.简写方式:
(1) var slice = arr[0:end] ----> var slice := arr[:end]
(2) var slice = arr[start:len(arr)] ----->var slice = arr[start]
(3)var slice = arr[0:len(arr)] -----> var slice = arr[:]
4.切片可以继续切片
运行结果:
【5】切片可以动态增长
运行结果:
总结:
底层原理:
1.底层追加元素的时候,对数组进行扩容,老数组扩容为新数组
2.创建一个新数组,将老数组中的3,6,1复制到新数组中,在新数组中追加88,50
3.slice2底层数组的指向,指向的是新数组
4.在使用追加的时候,其实想要做的效果给sclice追加
slice = append(slice, 88, 50)
5.底层的新数组不能直接维护,需要通过切片间接维护操作
6.可以通过append函数将切片追加给切片
==slice = append(slice,slice12...)==
运行结果:
8.8.7 切片的拷贝
==copy(b,a)==
运行结果:
9.map
9.1 map介绍
映射(map),Go语言中内置的一种类型,它将键值对相关联,我们可以通过键key来获取对应的值value,
类似其他语言的集合
9.2 map基本语法
==var map变量名 map[keytype]valuetype==
PS: key、value的类型:bool、数字、string、指针、channel、
还可以是只包含前面几个类型的接口、结构体、数组
PS:==key通常为int、string类型,value通常为数字(整数、浮点数)、string、map、结构体==
PS:key: slice、map、function不可以
## 9.3 代码
map的特点:
(1)map集合在使用前一定要make
(2)map的key-value是无序的
(3)key是不可以重复的,如果重复,后一个value会替换前一个value
(4)value是可以重复的
(5)make函数的第二个参数size可以省略,默认就分配一个内存
运行结果:
9.4 map的创建方式
1.方式1: make
==var userMap map[int]string==
==userMap = make(map[int]string,10)==
2.方式2:
==b := make(map[int]string)== 省略大小
运行结果:
3.方式3:直接赋值
==c := map[int]string{20095452:"张三",20095387:"李四",20096784:"王五"}==
运行结果:
9.5 map的操作
9.5.1 增加和更新操作
map["key"] = value ---->如果key还没有,就是增加,如果key存在就是修改
运行结果:
### 9.5.2 删除操作
delete(map,"key"),delete是一个内置函数,
如果key存在,就删除该key-value,
如果key不存在,就不操作,但是也不会报错
运行结果:
9.5.3 清空操作
(1)如果我们要删除map的所有key,没有一个专门的方法一次删除,可以遍历一下key,逐个删除
(2)或者map = make(...),make一个新的,让原来的成为垃圾,被gc回收
9.5.4 查找操作
==value,flag := b[20095452]==
运行结果:
9.5.5 获取长度
==len(map)==
运行结果:
9.6 遍历:for-range
运行结果:
9.7 加深难度
map里面的value放的又是map
运行结果:
10.面向对象
10.1 面向对象的引入
【1】Golang语言面向对象编程说明:
1.Golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说==Golang支持面向对象编程特性是比较准确的==。
2.Golang没有类(class),Go语言的结构体(struct)和其他编程语言的类(class)有同等的地位,你可以理解Golang是基于struct来实现OOP特性的。
3.Golang面向对象编程非常简洁,去掉了传统的OOP语言的方法重载、构造函数和解析函数、隐藏的this指针等等。
4.Golang仍然有面向对象编程的继承、封装和多态的特性,只是实现的方式和其他OOP语言不一样,比如继承:Golang没有extends关键字,继承是通过匿名字段来实现的。
5.Golang面向对象(OOP)很优雅,OOP本身就是语言类型系统的一部分,通过接口(interface)关联,耦合性低,也非常灵活。后面同学们会充分体会到这个特点。也就是说在Golang中面向接口编程是非常重要的特性。
【2】结构体的引入
具体的对象:
一位老师:珊珊老师 姓名:赵珊珊 年龄:31岁 性别:女
可以使用变量来处理:
缺点:
(1)不利于数据的管理、维护
(2)老师的很多属性属于一个对象,用变量管理太分散,后面获取一个对象的其他属性太麻烦了
10.2 结构体
10.2.1 结构体的引入
案例:老师结构体
后续实践中按照自己的需求定义结构体即可
10.2.2 代码
运行结果:
10.2.3 内存分析
10.2.4 结构体实例创建方式
1.方式1:直接创建
==var t1 Teacher== //var age int 一样
2.方式2:
==var t2 Teacher = Teacher{"赵珊珊",31,"黑龙江大学"}==
运行结果:
3.方式3
==var t3 *Teacher = new(Teacher)==
运行结果:
4.方式4
==var t4 *Teacher = &Teacher{}==
运行结果:
### 10.2.5 结构体之间的转换
1.结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字、个数和类型)
运行结果:
2.结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转
==s1 = Student(s2)== 强转
运行结果:
10.3 方法
10.3.1 基本介绍
在某些情况下,我们需要声明(定义)方法。比如Person结构体:除了有一些字段外(年龄,姓名...),Person结构体还有一些行为比如:可以说话、跑步....通过学习,还可以做算术题。这时就要用方法才能完成。
Golang中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct.
10.3.2 方法的声明和调用
==func(a A) test(){==
==}==
调用: var t A
t.test()
说明:
1.func(a A) test(){} 表示A结构体有一个方法,方法名为test
2.(a A)体现test方法是和A类型绑定的
代码:
运行结果:
对上面的总结:
1.test方法和Person2类型绑定
2.test方法只能通过person类型的变量来调用,而不能直接调用,也不能使用其他类型变量来调用
3.func(p Person2) test{}.... p表示哪个Person调用,p就表示谁,这点和函数传参非常相似,这里是值传递,拷贝一份传过去,p有点像Java里面的this
4.p这个形参名,可以由程序员指定,不是固定
10.3.3 方法快速入门
1.给Person结构体添加speak方法,输出xxx是一个好人
2.给Person2结构体添加jisuan方法,可以计算1+...+1000的结果,说明方法体内可以函数一样,进行各种运算
3.给Person2结构体添加jisuan2方法,该方法可以接收一个数n,计算1+...+n的结果
4.给Person结构体添加getSum方法,可以计算两个数的和,并返回结果
调用:
代码:
运行结果:
10.3.4 方法的调用和传参机制原理
说明:方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,也做实参也传递给方法。下面我们举例说明:
案例1:画出前面getSum方法的执行过程+说明
说明:
1.在通过一个变量去调用方法时,其调用机制和函数一样
2.不一样的地方是,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量时值类型,则进行值拷贝,如果变量时引用类型,则进行地址拷贝)
案例2:请编写一个程序,要求如下:
(1)声明一个结构体Circle,字段为radius
(2)声明一个方法area和Circle绑定,可以返回面积
提示:画出area执行过程+ 说明
代码:
运行结果:
内存分析:
10.3.5 方法的声明(定义)
==func (recevier type) methodName(参数列表) (返回值列表){==
==方法体==
==return 返回值==
==}==
例:
==func (circle Circle) area() float64{== ==total := 3.14 * circle.radius * circle.radius== ==return total== ==}==
1.参数列表:表示方法输入
2.recevier type:表示这个方法和type这个类型绑定,或者说该方法作用于type类型
3.recevier type:type可以是结构体,也可以其他的自定义类型
4.recevier:就是type类型的一个变量(实例),比如:Person结构体的一个变量
5.返回值列表:表示返回的值,可以多个
6.方法主体:表示为了实现某一功能代码块
7.return 语句不是必须的,
如果没有返回值列表,就没有return,
如果有返回值列表,就有return
10.3.6 方法注意事项和细节讨论
(1)结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
(2)如果程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
运行结果:
内存分析:
(3)Golang中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是Struct,比如int,float32等都可以有方法
(4)方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其他包访问
(5)如果一个类型实现了string()这个方法,那么fmt.Println默认会调用这个变量的string()进行输出
10.3.7 课堂练习题
1.编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个10*8的矩形,在main方法中调用该方法。
代码:
运行结果:
2.编写一个方法,提供m和n两个参数,方法中打印一个m*n的矩形
代码:
运行结果:
3.编写一个方法算该矩形的面积(可以接收长len,宽width),将其作为方法返回值。在main方法中调用该方法,接收返回的面积并打印。
代码:
运行结果:
10.3.8 方法和函数的区别
1.调用方式不一样
函数的调用方式: ==函数名(实参列表)==
方法的调用方式: ==变量.方法名(实参列表)==
2.对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
3.对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样可以
总结:
(1)不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定
(2)如果是和值类型绑定,比如(p Person),则是值拷贝
如果是和指针类型绑定,比如(p *person),则是地址拷贝
10.4 面向对象编程应用实例
10.4.1 步骤
1.声明(定义)结构体,确定结构体名
2.编写结构体字段
3.编写结构体的方法
10.4.2 学生案例
1.编写一个Student结构体,包含name、gender、age、id、score字段,
分别为string,string,int,int,float64类型。
2.结构体中声明一个say方法,返回string类型,方法返回信息中包含所有字段值
3.在main方法中,创建Student结构体实例(变量),并访问say方法,并将调用结果打印输出。
代码:
运行结果:
10.5 工厂模式
### 10.5.1 结构体首字母小写
如果==model包中的结构体首字母小写==,引入后,不能直接使用,可以工厂模式解决。
代码:
结构体文件:
main文件:
运行结果:
10.5.2 字段首字母小写
如果字段首字母小写,则在其他包不可以直接访问,我们可以提供一个方法解决
结构体文件:
main文件:
运行结果:
10.6 封装
10.6.1 封装介绍
封装就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有被授权的操作(方法),才能对字段进行操作。
### 10.6.2 封装的理解和好处
(1)隐藏实现细节
(2)可以对数据进行验证,保证安全合理
### 10.6.3 如何体现封装
(1)对结构体中的属性进行封装 ----属性小写
(2)通过方法,包实现封装
10.6.4 封装的实现步骤
1.将结构体、字段(属性)的首字母小写(不能导出了,其他包不能使用,类似private)
2.给结构体所在的包提供一个工厂模式的函数,首字母大写。类似一个构造函数
3.提供一个首字母大写的SetXxx方法(类似于其他语言的public),用于对属性判断并赋值
4.提供一个首字母大写的GetXxx方法(类似于其他语言的public),用于获取属性的值
特别说明:在Golang开发中并没有特别强调封装,这点并不像Java,所以体现学过Java的朋友,不用总是用Java的语法特性来看待Golang,Golang本身对面向对象的特性做了简化。
10.6.5 快速入门案例
请大家写一个程序(person.go),不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验证。
设计:==model包(person.go)==
==main包(main.go 调用person结构体)==
代码实现:
person.go
main.go
运行结果:
10.7 继承
10.7.1 为什么需要继承
1.Pupil和Graduate两个结构体的字段和方法几乎相同,但是我们却写了相同的2份代码,代码复用性不强
2.出现代码冗余,而且代码不利于维护,同时也不利于功能的扩展
3.解决方法-通过继承方式来解决 代码的复用性
10.7.2 继承介绍
继承可以解决代码复用,让我们的编程更加靠近人类思维。
当多个结构体存在相同的属性和方法时,可以从这些结构体中抽象出结构体(比如从小学生和大学生中抽象出student),在该结构体中定义这些相同的属性和方法。
其他结构体不需要重新定义这些属性和方法,只需要嵌套一个student匿名结构体即可。
也就是说:在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,==从而实现了继承特性==。
10.7.3 基本语法
嵌套匿名结构体的基本语法
10.7.4 快速入门案例
案例:我们对extends.go改进,使用嵌套匿名结构体的方式来实现继承特性,请大家注意体会这样编程的好处。
代码:
运行结果:
10.7.5 继承的好处
1.代码的复用性提高了
2.代码的扩展性和维护性提高了
10.7.6 继承的深入讨论
1.结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用
2.匿名结构体字段访问可以简化
==pupil.student4.ShowInfo()==
可以简化为:
==pupil.ShowInfo()==
对上面的代码小结:
(1)当我们直接通过pupil访问字段或者方法时,期执行流程如下,比如pupil.Name
(2)编译器会先看pupil对应的类型有没有Name,如果有,则直接调用Pupil类型的Name字段
(3)如果没有就去看pupil中嵌入的匿名结构体Student有没有声明Name字段,如果有就调用,如果没有继续查找,如果都找不到就报错。
3.当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近原则访问,
如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。
4.结构体嵌入两个或多个匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。
5.如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字。
6.嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值。
运行结果:
10.7.7 课堂练习
结构体的匿名字段是基本数据类型,如何访问。下面代码输出什么?
说明:
1.如果一个结构体有int类型的匿名字段,就不能有第二个
2.如果需要有多个int的字段,则必须给int字段指定名字
10.7.8 多重继承
如果==一个struct嵌套了多个匿名结构体==,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,==从而实现了多重继承。==
运行结果:
多重继承细节说明:
1.如嵌入的匿名结构体有==相同的字段名或者方法名==,则在访问时,需要通过匿名结构体类型名来区分
fmt.Println(==tv.Goods.Name==)
2.为了保证代码的简洁性,建议大家==尽量不使用多重继承==。(在很多语言都把多重继承拿掉了,比如Java)
10.8 接口
==interface==
10.8.1 接口介绍
一个程序就是一个世界,在现实世界存在的情况,在程序中也会出现。我们用程序来模拟一下前面的应用场景。
按顺序,我们应该讲解多态,但是在讲解多态前,我们需要讲解接口(interface),因为在Golang中,多态特性主要是通过接口来体现的。
interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。到某个自定义类型(比如结构体Phone)要使用的时候,再根据具体情况把这些方法写出来。
10.8.2 为什么需要接口
10.8.3 接口快速入门
代码:
运行结果:
说明:上面的代码就是一个接口编程的快速入门案例。
10.8.4 接口语法
接口语法:
==type 接口名 interface {==
==method1(参数列表) 返回值列表==
==method2(参数列表) 返回值列表==
==}==
比如:
实现接口语法: ==必须实现所有方法==
==func(t 自定义类型1) method1(参数列表) 返回值列表{==
==//方法实现==
==}==
==func(t 自定义类型) method2(参数列表) 返回值列表{==
==//方法实现==
==}==
如:
小结说明:
1.接口里的方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低耦合的思想。
2.Golang中的接口,不需要显示的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现了这个接口。因此,==Golang中没有implement这样的关键字==。
10.8.5 接口应用场景
对于初学者来讲,理解接口的概念并不算太难,难的是不知道什么时候使用接口,下面我列举几个应用场景:
1.说现在美国要制造轰炸机,武装直升机,专家只需要把飞机需要的功能/规格定下来即可,然后让别的人具体实现即可。
2.说现在有一个项目经理,管理3个程序员,开发一个软件,为了控制和管理软件开发,项目经理可以定义一些借口,然后由程序员具体实现。
10.8.6 注意实现和细节
1.接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
==var a A = stu== //接口实现类 可以赋值给接口
2.接口中所有的方法都没有方法体,即都是没有实现的方法
3.在Golang中,一个自定义elixir需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口。
4.一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
5.只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
6.一个自定义类型可以实现多个接口
运行结果:
7.Golang接口中不能有任何变量
8.一个接口(比如A接口)可以继承多个别的接口(比如B、C接口),这时如果要实现A接口,也必须将B、C接口的方法也全部实现。
运行结果:
9.interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用,那么会输出nil
10.空接口interface{} 没有任何方法,所以==所有类型都实现了空接口,即我们可以把任何一个变量赋给空接口==。
10.8.7 课堂练习
以下代码,有没有错误,你能得出哪些结论?
答案:以上代码正确
结论:以上代码正确
10.8.8 接口最佳实践
实现对Hero结构体切片的排序:sort.Sort(data Interface)
代码:
运行结果:
10.8.9 接口和继承的比较
实现接口 VS 继承
大家听到现在,可能会对实现接口和继承比较迷茫了,这个问题,那么他们究竟有什么区别呢?
总结: ==实现接口 可以理解为 继承 的一种补充== 想有一个功能的扩展(实现接口)
==扩展功能,但又没有破坏原来的继承关系==
代码:
运行结果:
小结:
1.当A结构体继承了B结构体后,那么A结构体就自动的继承了B结构体的字段和方法,可以直接使用
2.当A结构体需要某个功能,同时又不希望去破坏继承关系,则可以去实现某个接口接口。因此我们可以认为,==实现接口是对继承机制的补充。==
接口的优点:
1.不破坏原来的继承关系
2.让公共的功能写到一起,不会每个地方都一份,导致相同的功能多份代码
接口和继承解决的问题不同:
。继承的价值主要在于:解决代码的复用性和可维护性
。接口的价值主要在于:设计,设计好各种规范(方法),让其他自定义类型去实现这些方法
接口比继承更加灵活
==继承是is -a 的关系,而接口只需满足 like -a 觉得关系==
10.9 多态
10.9.1 基本介绍
变量(实例)具有多种形态。面向对象的第三大特征,在Go语言,多态特征是通过接口来实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。
10.9.2 快速入门
在前面的Usb接口案例,Usb usb,既key接收手机变量,有可以接收相机变量,就体现了Usb接口多态.
代码:
运行结果:
usb接口变量就体现出多态的特点
10.9.3 接口体现多态特征
1.==多态参数==
在前面的Usb接口案例,Usb usb,即可以接收手机变量,又可以接收相机变量,就体现了Usb接口多态
2.==多态数组==
演示一个案例:给Usb数组中,存放Phone结构体和Camera结构体变量
代码:
运行结果:
==当数组元素是Phone类型时,调用Phone类型的start()和stop(),==
==当数组元素是Camera类型时,调用Camera类型的start()和stop().==
==利用接口,可以实现接口里面放不同的类型元素。==
11.Go并发
11.1 goroutine引入
需求:要求统计1-20000(如果900000万呢)的数字中,哪些是素数?
分析思路:
1.传统的方法,就是使用一个循环,循环的判断各个数是不是素数。 -----==很慢==
2.使用并发或者并行的方式,将统计素数的任务分配给多个goroutine去完成,这时就会使用到goroutine。
11.2 goroutine基本介绍
协程 goroutine
11.2.1 进程和线程说明
1.进程就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位
==打开迅雷,就启动 迅雷线程==
==下载 封神榜,就是一个线程,一个下载任务。==
2.线程是进程的一个执行实例,是程序执行的最小单位,它是比进程更小的能独立运行的基本单位
3.一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行。
4.一个程序至少有一个进程,一个进程至少有一个线程
11.2.2 示意图
11.2.3 并发和并行
1.并发:多线程程序在单核上运行
2.并行:多线程程序在多核上运行
并发:因为是在一个cpu上,比如有10个线程,每个线程执行10毫秒(进行轮询操作),从人的角度看,好像这10个线程都在运行,但是从微观上看,在某一个时间点看,其实只有一个线程在执行,这就是并发。
并行:因为是多个cpu上(比如有10个cpu),比如有10个线程,每个线程执行10毫秒(各自在不同的cpu上执行),从人的角度看,这10个线程都在运行,但是从微观上看,在某一个时间点看,也同时有10个线程在执行,这就是并行。
传统的编程语言,并发也是在一个cpu上执行的。
11.2.4 Go主线程和Go协程
1.==Go主线程(Java中的进程)==,一个Go主线程上,可以起多个协程,你可以这样理解,协程是轻量级的线程。
2.==Go协程(Java中的线程)==的特点:
。有独立的栈空间
。共享程序堆空间
。调度由用户控制
。协程是轻量级的线程
示意图:
11.3 goroutine快速入门
11.3.1 案例说明
请编写一个程序,完成如下功能:
1.在主线程(可以理解为进程)中,开启一个goroutine,该协程每隔1秒输出“hello world”
2.在主线程中也每隔1秒输出“hello,golang”,输出10次后,退出程序
3.要求主线程和goroutine同时执行
4.画出主线程和协程执行流程图
代码:
运行结果:
主线程和协程流程图:
11.3.2 快速入门小结
1.主线程是一个物理线程,直接作用在cpu上的。是重量级的,非常耗费cpu资源。
2.协程从主线程开启的,是轻量级的线程,是逻辑态。对资源消耗相对小。
3.Golang的协程机制是重要的特点,可以轻松的开启上万个协程。其他编程语言的并发机制是一般基于线程的,开启过多的线程,资源耗费大,这里就突显Golang在并发上的优势了。
Java中的线程老贵了,花不起那个钱,所以Go发明了goroutine.
11.4 goroutine调度模型
11.4.1 MPG模式基本介绍
- M:操作系统的主线程(是物理线程)
- P:协程执行需要的上下文
- G:协程
11.4.2 MPG模式运行的状态1
(1)当前程序由3个M,如果3个M都在一个cpu运行,就是并发,如果在不同的cpu运行就是并行
(2)M1,M2,M3正在执行一个G,M1的协程队列有3个,M2的协程队列有3个,M3协程队列有2个
(3)从上图可以看到:Go的协程是轻量级的线程,是逻辑态的,Go可以容易的起上万个协程
(4)其他程序C、Java的多线程,往往是内核态的,比较重量级,几千个线程可能耗光cpu
11.4.3 MPG模式运行的状态2
(1)分成两个部分来看
(2)原来的情况是M0主线程正在执行G0协程,另外有3个协程在队列等待
(3)如果G0协程阻塞,比如读取文件或者数据库等
(4)这时就会创建M1主线程(也可能是从已有的线程池中取出M1),并且将等待的3个协程挂到M1下开始执行,M0的主线程下的G0仍然执行文件IO的读写
(5)这样的MPG调度模式,可以既让G0执行,同时也不会让队列的其他协程一直阻塞,仍然可变并发/并行执行
(6)等到G0不阻塞了,M0会被放到空闲的主线程继续执行(从已有的线程池中取),同时G0又会被唤醒。
==炒海鲜(洗海鲜) 的等待期 炒个鱼香肉丝==
11.5 设置Golang运行的cpu数
介绍:为了充分利用多CPU的优势,在Golang程序中,设置运行的cpu数目。
运行结果:
(1)go1.8后,默认让程序运行在多个核上,可以不用设置了,默认认为你,用Go语言,就是为了多核
(2)go1.8前,还是要设置一下,可以更高效的利用cpu
11.6 channel管道
需求:现在需要计算1-200的各个数的阶乘,并且把各个数的阶乘放入到map中。最后显示出来。要求使用goroutine完成。
分析思路:
(1)使用goroutine来完成,效率高,但是会出现并发/并行安全问题
(2)这里就提出了不同的goroutine如何通信的问题
代码实现:
运行结果:
报错
示意图:
11.6.1 不同的goroutine之间如何通讯
(1)全局变量的互斥锁
(2)channel管道
使用全局变量加锁同步改进程序
。因为没有对全局变量map加锁,因此会出现资源争夺问题,代码会出现错误,提示concurrent map writes
。解决方案:加入互斥锁
。我们的数的阶乘很大,结果会越界,可以将求阶乘改成sum += unit64(i)
11.6.2 全局变量的互斥锁
锁 ---> 钥匙 有钥匙 才能访问
==别人在用的时候,会挂上锁,==
==别人用完了,会把锁拿掉,好比古代皇帝就寝。== ==办公室排队上厕所==
代码改进:全局变量的互斥锁
运行结果:
11.6.3 为什么需要channel
前面使用全局变量加锁同步来解决goroutine的通讯,但不完美
(1)主线程在等待所以goroutine全部完成的时间很难确定,我们这里设置10秒,进阶是估算
(2)如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine还处在工作状态,这时也会随主线程的退出而销毁
(3)通过全局变量加锁同步来实现通讯,也并不利于多个协程对全局变量的读写操作
(4)上面种种分析都在呼唤一个新的通讯机制-channel管道
11.6.4 channel的基本介绍
(1)channel本质就是一个数据结构--队列
(2)数据是先进先出,
(3)线程安全,多个goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
(4)channel有类型的,一个string的channel只能存放string类型数据
(5)示意图:
11.6.5 定义/声明channel
var 变量名 chan 数据类型
举例:
var intChan chan int (intChan用于存放int数据)
var mapChan chan map[int]string (mapChan用于存放map[int]string类型)
var perChan chan Person
var perChan chan *Person
说明:
(1)channel是引用类型
(2)channel必须初始化才能写入数据,即make后才能使用
(3)管道是有类型的,intChan只能写入整数int
11.6.6 管道的操作
运行结果:
11.6.7 channel使用的注意事项
1.channel中只能存放指定的数据类型
2.channel的数据放满后,就不能再放入了
3.如果从channel取出数据后,就可以继续放入
4.在没有使用协程的情况下,如果channel数据取完了,再取,就会报dead lock
11.6.8 案例演示
1.创建一个mapChan,最多可以存放10个map[string]string的key-val,演示写入和读取
2.管道类型为接口 可以存放任意类型的数
运行结果:
11.6.9 channel的关闭
使用内置函数close可以关闭channel,==当channel关闭后,就不能再向channel写数据了,但是仍然可以从该channel读取数据==。好比买火车票排队,到点了,把后面门关上。
代码:
运行结果:
11.6.10 channel的遍历
channel支持for-range的方式进行遍历,请注意两个细节
(1)在遍历时,如果channel没有关闭,则会出现deadlock的错误
(2)在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历
代码:
运行结果:
11.6.11 协程配合channel案例
应用实例:
请完成goroutine和channel协同工作的案例,具体要求:
(1)开启一个writeData协程,向管道intChan中写入50个整数
(2)开启一个readData协程,从管道intChan中读取writeData写入的数据
(3)注意:writeData和readData操作的是同一个管道
(4)主线程需要等待writeData和readData协程都完成工作才能退出
思路分析:
代码:
运行结果:
11.6.12 协程配合channel阻塞
func main() {
//创建2个管道 intChan := make(chan int,50) exitChan := make(chan bool,1)
go writeData(intChan) ==// go readData(intChan,exitChan)==
for{ flag := <- exitChan if(flag){ break } } }
问题:如果注销掉go readData(intChan,exitChan),程序会怎么样?
答:如果只是向管道写入数据,而没有读取,就会出现阻塞而dead lock,原因是intChan容量是10,而代码writedata会写入50个数据,因此会阻塞在writeData的 intChan <- i
==如果编译器(运行),发现一个管道,只有写,而没有读,则该管道会阻塞==
==写管道和读管道的速度不一致,无所谓。==
11.6.13 协程求素数
需求:要求统计1-8000的数字中,哪些是素数?这个问题在本章开篇就提出了,现在我们用goroutine和channnel的知识完成。
分析思路:
。传统的方法:就是使用一个for循环,循环的判断各个数是不是素数
。使用并发/并行的方式,将统计素数的任务分配给多个(4个)goroutine去完成,完成任务时间短。
1.画出分析思路:
2.代码实现:
运行结果:
运行结果:
结论:200000,使用协程需要9s,使用普通方法需要25s,
使用go协程后,执行的速度,比普通方法提高了至少4倍。
11.6.14 channel使用细节
1.channel可以声明为只读,或者只写性质
==ch <-chan int== 只读管道
==ch chan<- int== 只写管道
2.channel只读和只写的最佳实践案例
2.使用select可以解决从管道取数据的阻塞问题
3.goroutine中使用recover,解决协程中出现的panic,导致程序崩溃问题
说明:如果我们起一个协程,但是这个协程出现了panic,如果我们没有捕获这个panic,就会造成整个程序崩溃,这时我们可以在goroutine中使用revover来捕获panic进行处理,这样即使这个协程发生了问题,但是主线程仍然不受影响,可以继续执行。
运行结果: