第 1 章
1.1Golang的学习方向
Go语言,我们可以简单的写成Golang.
Golang开山篇
1.2Golang的应用领域
1.2.1区块链的应用开发
1.2.2后台的服务应用
1.2.3云计算/云服务后台应用
1.3学习方法的介绍
1.4讲课的方式的说明
- 努力做到通俗易懂
- 注重Go语言体系,同时也兼顾技术细节
3)在实际工作中,如何快速的掌握一个技术的分享,同时也是我们授课的思路(怎么讲解或者学习一个技术)。(很多学员反馈非常受用)
第 2 章
2.1什么是程序
程序:就是完成某个功能的指令的集合。
Golang的概述
画一个图理解:
2.2Go语言的诞生小故事
2.2.1Go语言的核心开发团队-三个大牛
2.2.2Google创造Golang的原因
2.2.3Golang的发展历程
- 2007年,谷歌工程师RobPike,KenThompson和RobertGriesemer开始设计一门全新的语言,这是
Go语言的最初原型。 -
-
2009年11月10日,Google将Go语言以开放源代码的方式向全球发布。2015年8月19日,Go1.5版发布,本次更新中移除了”最后残余的C代码”
-
-
2017年2月17日,Go语言Go1.8版发布。
-
2017年8月24日,Go语言Go1.9版发布。1.9.2版本
-
2018年2月16日,Go语言Go1.10版发布。
2.3Golang的语言的特点简介:
- Go语言保证了既能到达静态编译语言的安全和性能,又达到了动态语言开发维护的高效率,使用一个表达式来形容Go语言:Go=C+Python,说明Go语言既有C静态语言程序的运行速度,又能达到Python动态语言的快速开发。
- 从C语言中继承了很多理念,包括表达式语法,控制结构,基础数据类型,调用参数传值,指针等等,也保留了和C语言一样的编译执行方式及弱化的指针
举一个案例(体验):
//go语言的指针的使用特点(体验)
functestPtr(num*int){
*num=20
} - 引入包的概念,用于组织程序结构,Go语言的一个文件都要归属于一个包,而不能单独存在。
- 垃圾回收机制,内存自动回收,不需开发人员管理
4)天然并发(重要特点)
(1)从语言层面支持并发,实现简单
(2)goroutine,轻量级线程,可实现大并发处理,高效利用多核。
(3)基于CPS并发模型(CommunicatingSequentialProcesses)实现 - 吸收了管道通信机制,形成Go语言特有的管道channel通过管道channel,可以实现不同的goroute之间的相互通信。
- 函数可以返回多个值。举例:
//写一个函数,实现同时返回和,差
//go函数支持返回多个值
funcgetSumAndSub(n1int,n2int)(int,int){
sum:=n1+n2//go语句后面不要带分号.
sub:=n1-n2
returnsum,sub
} - 新的创新:比如切片slice、延时执行defer
2.4Golang的开发工具的介绍
2.4.1工具介绍
2.4.2工具选择:
- 如何选择开发工具
我们先选择用visualstudiocode或者vim文本编辑器本,到大家对Go语言有一定了解后,我们再使用Eclipse等IDE开发工具。 - 这是为什么呢?
- 更深刻的理解Go语言技术,培养代码感。->写代码的感觉。
- 有利于公司面试。->给你纸,写程序
2.4.3VSCode的安装和使用
- 先到下载地址去选择适合自己系统的VSCode安装软件
- 演示如何在windows下安装vscode并使用
步骤1:把vscode安装文件准备好
步骤2:双击安装文件,就可以一步一步安装,我个人的习惯是安装到d:/programs目录.当看到如下界面时,就表示安装成功!
步骤3:简单的使用一下vscode
在d盘创建了一个文件夹gocode.
3)演示如何在Linux(ubuntu/centos) 下安装vscode并使用
这里,我们介绍一下我的linux的环境:
步骤1:先下载linux版本的vscode安装软件.
步骤2:因为我这里使用的是虚拟机的ubuntu,因此我们需要先将vscode安装软件传输到ubuntu下,使用的xftp5软件上传。
步骤3:如果你是在ubuntu下做go开发,我们建议将vscode安装到/opt目录…
步骤4:将安装软件拷贝到/opt
步骤5:cd/opt【切换到/opt】
步骤6:将安装文件解决即可
步骤7:现在进入解压后的目录,即可运行我们的vscode
- 演示如何在Mac下安装vscode并使用
如果你使用的就是mac系统,也可以在该系统下进行go开发.
步骤1:下载mac版本的vscode安装软件
步骤2:把vscode安装软件,传输到mac系统
细节:在,默认情况下mac没有启动ssh服务,所以需要我们启动一下,才能远程传输文件.mac本身安装了ssh服务,默认情况下不会开机自启1.启动sshd服务:sudolaunchctlload-w/System/Library/LaunchDaemons/ssh.plist2.停止sshd服务:sudolaunchctlunload-w/System/Library/LaunchDaemons/ssh.plist3查看是否启动:sudolaunchctllist|grepssh
如果看到下面的输出表示成功启动了:
--------------
-0com.openssh.sshd
步骤3:将安装软件解压后即可使用.
进入到这个解压后的文件夹(图形界面),双击即可
步骤4:编写解简单测试.
在用户所在的目录,创建了gocode,然后将test.go写到这个文件夹下…
2.4.4小结
我们会讲解在windows,linux,mac如何安装vscode开发工具,并且还会讲解如何在三个系统下安装go的sdk和如何开发go程序。
但是为了学习方便,我们前期选择Windows下开发go。到我们开始讲项目和将区块链时,就会使用linux系统。
在实际开发中,也可以在windows开发好程序,然后部署到linux下。
2.5Windows下搭建Go开发环境-安装和配置SDK
2.5.1介绍了SDK
1)SDK的全称(SoftwareDevelopmentKit软件开发工具包)
2) SDK是提供给开发人员使用的,其中包含了对应开发语言的工具包
2.5.2下载SDK工具包
- Go语言的官网为:golang.org,因为各种原因,可能无法访问。
- SDK下载地址:Golang中国
- 如何选择对应的sdk版本
2.5.3windows下安装sdk
- Windows下SDK的各个版本说明:
Windows下:根据自己系统是32位还是64位进行下载:
32位系统:go1.9.2.windows-386.zip
64位系统:go1.9.2.windows-amd64.zip
- 请注意:安装路径不要有中文或者特殊符号如空格等
- SDK安装目录建议:windows一般我安装在d:/programs
- 安装时,基本上是傻瓜式安装,解压就可以使用
- 安装看老师的演示:
- 解压后,我们会看到d:/go目录,这个是sdk
如何测试我们的go的sdk安装成功。
2.5.4windows下配置Golang环境变量:
- 为什么需要配置环境变量
- 配置环境变量介绍
根据windows系统在查找可执行程序的原理,可以将Go所在路径定义到环境变量中,让系统帮我们去找运行执行的程序,这样在任何目录下都可以执行go指令。 - 在Go开发中,需要配置哪些环境变量
- 看老师如何配置
步骤1:先打开环境变量配置的界面
步骤2:配置我们的环境变量
对上图的一个说明:
- Path这个环境变量不需要在创建,因为系统本身就有,你后面增加即可
- 增加Go的bin:;%GOROOT%\bin
对上图的一个说明 - GOPATH:就是你以后go项目存放的路径,即工作目录
- GOPATH:是一个新建的环境变量
- 测试一下我们的环境变量是否配置ok
注意:配置环境变量后,需要重新打开一次dos的终端,这样环境变量才会生效。
2.6Linux下搭建Go开发环境-安装和配置SDK
2.6.1Linux下安装SDK:
- Linux下SDK的各个版本说明:
Linux下:根据系统是32位还是64位进行下载:
32位系统:go1.9.2.linux-386.tar.gz
64位系统:go1.9.2.linux-amd64.tar.gz
如何确认你的linux是多少位:
2) 请注意:安装路径不要有中文或者特殊符号如空格等
3) SDK安装目录建议:linux放在/opt目录下
4) 安装时,解压即可,我们使用的是tar.gz
5) 看老师演示
步骤1:将go1.9.2.linux-amd64.tar.gz传输到ubuntu
步骤2:将go1.9.2.linux-amd64.tar.gz拷贝到/opt下
步骤3:cd/opt
步骤4:tar-zxvfgo1.9.2.linux-amd64.tar.gz[解压后,就可以看到一个go目录]
步骤5:cdgo/bin
步骤6:./goversion
2.6.2Linux下配置Golang环境变量
步骤1:使用root的权限来编辑vim/etc/profile文件
步骤2:如果需要生效的话,需要注销一下(重新登录),再使用
2.7Mac下搭建Go开发环境-安装和配置SDK
2.7.1mac下安装Go的sdk
- Mac下SDK的各个版本说明:MacOS下:只有64位的软件安装包
MacOS系统的安装包:go1.9.2.darwin-amd64.tar.gz - 请注意:安装路径不要有中文或者特殊符号如空格等
- SDK安装目录建议:Mac一般放在用户目录下go_dev/go下
- 安装时,解压即可
- 看老师的演示步骤
步骤1:先将我们的安装文件go1.9.2.darwin-amd64.tar.gz上传到mac
步骤2:先在用户目录下,创建一个目录go_dev,将我们上传的文件移动到go_dev目录
步骤3:解压tar-zxvfgo1.9.2.darwin-amd64.tar.gz
步骤4:解压后,我们会得到一个目录go,进入到go/bin就是可以使用
这里还是有一个问题,就是如果我们不做bin目录下,就使用不了go程序。因此我们仍然需要配置go的环境变量。
2.7.2Mac下配置Golang环境变量:
步骤1:使用root用户,修改/etc/profile增加环境变量的配置
步骤2:配置完后,需要重新注销用户,配置才会生效.
2.8Go语言快速开发入门
2.8.1需求
要求开发一个hello.go程序,可以输出"hello,world”
2.8.2开发的步骤
- 开发这个程序/项目时,go的目录结构怎么处理.
- 代码如下:
对上图的说明
(1)go文件的后缀是.go
(2)packagemain
表示该hello.go文件所在的包是main,在go中,每个文件都必须归属于一个包。
(3)import“fmt”
表示:引入一个包,包名fmt,引入该包后,就可以使用fmt包的函数,比如:fmt.Println
(4)funcmain(){}
func是一个关键字,表示一个函数。
main是函数名,是一个主函数,即我们程序的入口。
(5)fmt.Println(“hello”)
表示调用fmt包的函数Println输出“hello,world” - 通过gobuild命令对该go文件进行编译,生成.exe文件.
- 运行hello.exe文件即可
- 注意:通过gorun命令可以直接运行hello.go程序[类似执行一个脚本文件的形式]
2.8.3linux下如何开发Go程序
说明:linux下开发go和windows开发基本是一样的。只是在运行可执行的程序时,是以./文件名方式
演示:在linux下开发Go程序。
编译和运行hello.go
也可以直接使用gorunhello.go方式运行
2.8.4Mac下如何开发Go程序
- 说明:在mac下开发go程序和windows基本一样。
- 演示一下:如何在mac下开发一个hello.go程序
- 源代码的编写:hello.go
- 编译再运行,直接gorun来运行
- 直接gorun来运行
2.8.5go语言的快速入门的课堂练习
2.8.6Golang执行流程分析
- 如果是对源码编译后,再执行,Go的执行流程如下图
- 如果我们是对源码直接执行gorun源码,Go的执行流程如下图
- 两种执行流程的方式区别
- 如果我们先编译生成了可执行文件,那么我们可以将该可执行文件拷贝到没有go开发环境的机器上,仍然可以运行
- 如果我们是直接gorungo源代码,那么如果要在另外一个机器上这么运行,也需要go开发环境,否则无法执行。
- 在编译时,编译器会将程序运行依赖的库文件包含在可执行文件中,所以,可执行文件变大了很多。
2.8.7编译和运行说明
- 有了go源文件,通过编译器将其编译成机器可以识别的二进制码文件。
- 在该源文件目录下,通过gobuild对hello.go文件进行编译。可以指定生成的可执行文件名,在windows下必须是.exe后缀。
3)如果程序没有错误,没有任何提示,会在当前目录下会出现一个可执行文件(windows下是.exeLinux下是一个可执行文件) ,该文件是二进制码文件,也是可以执行的程序。 - 如果程序有错误,编译时,会在错误的那行报错。有助于程序员调试错误.
- 运行有两种形式
2.8.8Go程序开发的注意事项
- Go源文件以"go"为扩展名。
2)Go应用程序的执行入口是main()函数。这个是和其它编程语言(比如java/c) - Go语言严格区分大小写。
4)Go方法由一条条语句构成,每个语句后不需要分号(Go语言会在每行后自动加分号) ,这也体
现出Golang的简洁性。 - Go编译器是一行行进行编译的,因此我们一行就写一条语句,不能把多条语句写在同一个,否则报错
- go语言定义的变量或者import的包如果没有使用到,代码不能编译通过。
- 大括号都是成对出现的,缺一不可。
2.9Go语言的转义字符(escapechar)说明:常用的转义字符有如下:
- \t:表示一个制表符,通常使用它可以排版。
- \n:换行符
- \:一个\
- ":一个"
5)\r:一个回车fmt.Println("天龙八部雪山飞狐\r张飞") ; - 案例截图
- 课堂练习
5min
作业评讲:
2.10Golang开发常见问题和解决方法
2.10.1文件名或者路径错误
2.10.2小结和提示
学习编程最容易犯的错是语法错误。Go要求你必须按照语法规则编写代码。如果你的程序违反了语法规则,例如:忘记了大括号、引号,或者拼错了单词,Go编译器都会报语法错误,要求:尝试着去看懂编译器会报告的错误信息。
2.11注释(comment)
2.11.1介绍注释
用于注解说明解释程序的文字就是注释,注释提高了代码的阅读性;
注释是一个程序员必须要具有的良好编程习惯。将自己的思想通过注释先整理出来,再用代码去体现。
2.11.2在Golang中注释有两种形式
- 行注释
- 基本语法
//注释内容 - 举例
2)块注释(多行注释) - 基本语法
/*
注释内容
*/ - 举例说明
- 使用细节
- 对于行注释和块注释,被注释的文字,不会被Go编译器执行。
- 块注释里面不允许有块注释嵌套[注意一下]
2.12规范的代码风格
2.12.1正确的注释和注释风格:
- Go官方推荐使用行注释来注释整个方法和语句。
- 带看Go源码
2.12.2正确的缩进和空白
- 使用一次tab操作,实现缩进,默认整体向右边移动,时候用shift+tab整体向左移看老师的演示:
- 或者使用gofmt来进行格式化[演示]
- 运算符两边习惯性各加一个空格。比如:2+4*5。
- Go语言的代码风格.
packagemain
import"fmt"
funcmain(){
fmt.Println("hello,world!")
}
上面的写法是正确的.
packagemain
import"fmt"
funcmain()
{
fmt.Println("hello,world!")
}
上面的写法不是正确,Go语言不允许这样编写。【Go语言不允许这样写,是错误的!】
Go设计者思想:一个问题尽量只有一个解决方法 - 一行最长不超过80个字符,超过的请使用换行展示,尽量保持格式优雅
- 举例说明
2.13Golang官方编程指南
- 说明:Golang官方网站
- 点击上图的tour->选择简体中文就可以进入中文版的Go编程指南。
- Golang官方标准库API文档,可以查看Golang所有包下的函数和使用
- 解释术语:API
api:applicationprograminterface:应用程序编程接口。
就是我们Go的各个包的各个函数。
2.14Golang标准库API文档
1)API(ApplicationProgrammingInterface,应用程序编程接口) 是Golang提供的基本编程接口。
2) Go语言提供了大量的标准库,因此google公司也为这些标准库提供了相应的API文档,用于告诉开发者如何使用这些标准库,以及标准库包含的方法。
3) Golang中文网在线标准库文档:
4) Golang的包和源文件和函数的关系简图
5) 有一个离线版的Golang_Manual_By_AstaXie_20120522.chm
2.15Dos的常用指令(了解)
2.15.1dos的基本介绍
Dos:DiskOperatingSystem磁盘操作系统,简单说一下windows的目录结构2.15.2dos的基本操作原理
2.15.3目录操作指令
- 查看当前目录是什么
- 切换到其他盘下:盘符号F盘
案例演示:
- 切换到当前盘的其他目录下(使用相对路径和绝对路径演示)
案例演示:
- 切换到上一级:
案例演示:
- 切换到根目录:
案例演示:
- 新建目录md(makedirectory)
新建一个目录:
新建多个目录:
- 删除目录
删除空目录
删除目录以及下面的子目录和文件,不带询问
删除目录以及下面的子目录和文件,带询问
2.15.4文件的操作
- 新建或追加内容到文件
案例演示:
- 复制或移动文件
复制
移动
- 删除文件
删除指定文件
删除所有文件
2.15.5其它指令
- 清屏
cls[苍老师] - 退出dos
exit
2.15.6综合案例
2.16课后练习题的评讲
- 独立编写Hello,Golang!程序[评讲]
2)将个人的基本信息(姓名、性别、籍贯、住址) 打印到终端上输出。各条信息分别占一行
- 在终端打印出如下图所示的效果
2.17本章的知识回顾
- Go语言的SDK是什么?
SDK就是软件开发工具包。我们做Go开发,首先需要先安装并配置好sdk. - Golang环境变量配置及其作用。
GOROOT:指定gosdk安装目录。
Path:指令sdk\bin目录:go.exegodoc.exegofmt.exe
GOPATH:就是golang工作目录:我们的所有项目的源码都这个目录下。 - Golang程序的编写、编译、运行步骤是什么?能否一步执行?
编写:就是写源码
编译:gobuild源码=》生成一个二进制的可执行文件
运行:1.对可执行文件运行xx.exe./可执行文件2.gorun源码 - Golang程序编写的规则。
- go文件的后缀.go
- go程序区分大小写
- go的语句后,不需要带分号
- go定义的变量,或者import包,必须使用,如果没有使用就会报错
- go中,不要把多条语句放在同一行。否则报错
6)go中的大括号成对出现,而且风格funcmain() {
//语句
}
- 简述:在配置环境、编译、运行各个步骤中常见的错误
对初学者而言,最容易错的地方拼写错误。比如文件名,路径错误。拼写错误
第 3 章Golang变量
3.1为什么需要变量
3.1.1一个程序就是一个世界
3.1.2变量是程序的基本组成单位
不论是使用哪种高级程序语言编写程序,变量都是其程序的基本组成单位,比如一个示意图:
比如上图的sum,sub都是变量。
3.2变量的介绍
3.2.1变量的概念
变量相当于内存中一个数据存储空间的表示,你可以把变量看做是一个房间的门牌号,通过门牌号我们可以找到房间,同样的道理,通过变量名可以访问到变量(值)。
3.2.2变量的使用步骤
1)声明变量(也叫:定义变量)
2) 非变量赋值
3) 使用变量
3.3变量快速入门案例看一个案例:
输出:
3.4变量使用注意事项
- 变量表示内存中的一个存储区域
2)该区域有自己的名称(变量名)和类型(数据类型) 示意图:
- Golang变量使用的三种方式
(1)第一种:指定变量类型,声明后若不赋值,使用默认值
(2)第二种:根据值自行判定变量类型(类型推导)
(3)第三种:省略var,注意:=左侧的变量不应该是已经声明过的,否则会导致编译错误
- 多变量声明
在编程中,有时我们需要一次性声明多个变量,Golang也提供这样的语法
举例说明:
如何一次性声明多个全局变量【在go中函数外部定义变量就是全局变量】:
5)该区域的数据值可以在同一类型范围内不断变化(重点)
6)变量在同一个作用域(在一个函数或者在代码块) 内不能重名
- 变量=变量名+值+数据类型,这一点请大家注意,变量的三要素
- Golang的变量如果没有赋初值,编译器会使用默认值,比如int默认值0string默认值为空串,小数默认为0
3.5变量的声明,初始化和赋值
3.6程序中+号的使用
- 当左右两边都是数值型时,则做加法运算
- 当左右两边都是字符串,则做字符串拼接
3.7数据类型的基本介绍
3.8整数类型
3.8.1基本介绍
简单的说,就是用于存放整数值的,比如0,-1,2345等等。
3.8.2案例演示
3.8.3整数的各个类型
int的无符号的类型:
int的其它类型的说明:
3.8.4整型的使用细节
- Golang各整数类型分:有符号和无符号,intuint的大小和系统有关。
- Golang的整型默认声明为int型
3)如何在程序查看某个变量的字节大小和数据类型(使用较多)
- Golang程序中整型变量在使用时,遵守保小不保大的原则,即:在保证程序正确运行下,尽量使用占用空间小的数据类型。【如:年龄】
- bit:计算机中的最小存储单位。byte:计算机中基本存储单元。[二进制再详细说]1byte=8bit
3.9小数类型/浮点型
3.9.1基本介绍
小数类型就是用于存放小数的,比如1.20.23-1.911
3.9.2案例演示
3.9.3小数类型分类
对上图的说明:
- 关于浮点数在机器中存放形式的简单说明,浮点数=符号位+指数位+尾数位
说明:浮点数都是有符号的.
2)尾数部分可能丢失,造成精度损失。-123.0000901
说明:float64的精度比float32的要准确.
说明:如果我们要保存一个精度高的数,则应该选用float64
3) 浮点型的存储分为三部分:符号位+指数位+尾数位在存储过程中,精度会有丢失
3.9.4浮点型使用细节
1)Golang浮点类型有固定的范围和字段长度,不受具体OS(操作系统) 的影响。
2) Golang的浮点型默认声明为float64类型。
3) 浮点型常量有两种表示形式
十进制数形式:如:5.12.512(必须有小数点)
科学计数法形式:如:5.1234e2=5.12*10的2次方5.12E-2=5.12/10的2次方
4) 通常情况下,应该使用float64,因为它比float32更精确。[开发中,推荐使用float64]
3.10字符类型
3.10.1基本介绍
Golang中没有专门的字符类型,如果要存储单个字符(字母),一般使用byte来保存。
字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。也就是说对于传统的字符串是由字符组成的,而Go的字符串不同,它是由字节组成的。
3.10.2案例演示
对上面代码说明
- 如果我们保存的字符在ASCII表的,比如[0-1,a-z,A-Z…]直接可以保存到byte
- 如果我们保存的字符对应码值大于255,这时我们可以考虑使用int类型保存
3)如果我们需要安装字符的方式输出,这时我们需要格式化输出,即fmt.Printf(“%c”,c1) …
3.10.3字符类型使用细节
1)字符常量是用单引号('') 括起来的单个字符。例如:varc1byte='a'varc2int='中'varc3byte='9'
2) Go中允许使用转义字符'\’来将其后的字符转变为特殊字符型常量。例如:varc3char=‘\n’//'\n'表示换行符
3) Go语言的字符使用UTF-8编码,如果想查询字符对应的utf8码值
英文字母-1个字节汉字-3个字节
4) 在Go中,字符的本质是一个整数,直接输出时,是该字符对应的UTF-8编码的码值。
5) 可以直接给某个变量赋一个数字,然后按格式化输出时%c,会输出该数字对应的unicode字符
6) 字符类型是可以进行运算的,相当于一个整数,因为它都对应有Unicode码.
3.10.4字符类型本质探讨
1)字符型存储到计算机中,需要将字符对应的码值(整数) 找出来存储:字符—>对应码值---->二进制–>存储
读取:二进制---->码值---->字符–>读取
2)字符和码值的对应关系是通过字符编码表决定的(是规定好)
3) Go语言的编码都统一成了utf-8。非常的方便,很统一,再也没有编码乱码的困扰了
3.11布尔类型
3.11.1基本介绍
- 布尔类型也叫bool类型,bool类型数据只允许取值true和false
- bool类型占1个字节。
- bool类型适于逻辑运算,一般用于程序流程控制[注:这个后面会详细介绍]:
- if条件控制语句;
- for循环控制语句
3.11.2案例演示
3.12string类型
3.12.1基本介绍
字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。Go语言的字符串的字节使用UTF-8编码标识Unicode文本
3.12.2案例演示
3.12.3string使用注意事项和细节
- Go语言的字符串的字节使用UTF-8编码标识Unicode文本,这样Golang统一使用UTF-8编码,中文乱码问题不会再困扰程序员。
- 字符串一旦赋值了,字符串就不能修改了:在Go中字符串是不可变的。
- 字符串的两种表示形式
(1)双引号,会识别转义字符
(2)反引号,以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果【案例演示】
- 字符串拼接方式
- 当一行字符串太长时,需要使用到多行字符串,可以如下处理
3.13基本数据类型的默认值
3.13.1基本介绍
在go中,数据类型都有一个默认值,当程序员没有赋值时,就会保留默认值,在go中,默认值又叫零值。
3.13.2基本数据类型的默认值如下
案例:
3.14基本数据类型的相互转换
3.14.1基本介绍
Golang和java/c不同,Go在不同类型的变量之间赋值时需要显式转换。也就是说Golang中数据类型不能自动转换。
3.14.2基本语法
表达式T(v)将值v转换为类型T
T:就是数据类型,比如int32,int64,float32等等
v:就是需要转换的变量
3.14.3案例演示
3.14.4基本数据类型相互转换的注意事项
- Go中,数据类型的转换可以是从表示范围小–>表示范围大,也可以范围大—>范围小
2)被转换的是变量存储的数据(即值) ,变量本身的数据类型并没有变化!
- 在转换中,比如将int64转成int8【-128—127】,编译时不会报错,只是转换的结果是按溢出处理,和我们希望的结果不一样。因此在转换时,需要考虑范围.
3.14.5课堂练习
- 练习1
如何修改上面的代码,就可以正确.
- 练习2
3.15基本数据类型和string的转换
3.15.1基本介绍
在程序开发中,我们经常将基本数据类型转成string,或者将string转成基本数据类型。
3.15.2基本类型转string类型
- 方式1:fmt.Sprintf("%参数",表达式)【个人习惯这个,灵活】
函数的介绍:
参数需要和表达式的数据类型相匹配
fmt.Sprintf()…会返回转换后的字符串 - 案例演示
- 方式2:使用strconv包的函数
- 案例说明
3.15.3string类型转基本数据类型
- 使用时strconv包的函数
- 案例演示
- 说明一下
3.15.4string转基本数据类型的注意事项
在将String类型转成基本数据类型时,要确保String类型能够转成有效的数据,比如我们可以把"123",转成一个整数,但是不能把"hello"转成一个整数,如果这样做,Golang直接将其转成0,其它类型也是一样的道理.float=>0bool=>false
案例说明:
3.16指针
3.16.1基本介绍
- 基本数据类型,变量存的就是值,也叫值类型
- 获取变量的地址,用&,比如:varnumint,获取num的地址:&num分析一下基本数据类型在内存的布局.
- 指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值
比如:varptr*int=&num
举例说明:指针在内存的布局.
- 获取指针类型所指向的值,使用:,比如:varptrint,使用*ptr获取ptr指向的值
- 一个案例再说明
3.16.2案例演示
- 写一个程序,获取一个int变量num的地址,并显示到终端
- 将num的地址赋给指针ptr,并通过ptr去修改num的值.
3.16.3指针的课堂练习
3.16.4指针的使用细节
- 值类型,都有对应的指针类型,形式为数据类型,比如int的对应的指针就是int,float32对应的指针类型就是*float32,依次类推。
- 值类型包括:基本数据类型int系列,float系列,bool,string、数组和结构体struct
3.17值类型和引用类型
3.17.1值类型和引用类型的说明
- 值类型:基本数据类型int系列,float系列,bool,string、数组和结构体struct
- 引用类型:指针、slice切片、map、管道chan、interface等都是引用类型
3.17.2值类型和引用类型的使用特点
- 值类型:变量直接存储值,内存通常在栈中分配示意图:
2)引用类型:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值) ,内存通常在堆
上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收
示意图:
- 内存的栈区和堆区示意图
3.18标识符的命名规范
3.18.1标识符概念
- Golang对各种变量、方法、函数等命名时使用的字符序列称为标识符
- 凡是自己可以起名字的地方都叫标识符
3.18.2标识符的命名规则
- 由26个英文字母大小写,0-9,_组成
- 数字不可以开头。varnumint//okvar3numint//error
- Golang中严格区分大小写。
varnumint
varNumint
说明:在golang中,num和Num是两个不同的变量 - 标识符不能包含空格。
5)下划线"_"本身在Go中是一个特殊的标识符,称为空标识符。可以代表任何其它的标识符,但是它对应的值会被忽略(比如:忽略某个返回值) 。所以仅能被作为占位符使用,不能作为标识符使用
6)不能以系统保留关键字作为标识符(一共有25个) ,比如break,if等等…
3.18.3标识符的案例
hello//ok
hello12//ok
1hello//error,不能以数字开头
h-b//error,不能使用-
xh//error,不能含有空格
h_4//ok
ab//ok
int//ok,我们要求大家不要这样使用
float32//ok,我们要求大家不要这样使用
//error
Abc//ok
3.18.4标识符命名注意事项
- 包名:保持package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,不要和标准库不要冲突fmt
- 变量名、函数名、常量名:采用驼峰法举例:
varstuNamestring=“tom”形式:xxxYyyyyZzzz…
vargoodPricefloat32=1234.5
3)如果变量名、函数名、常量名首字母大写,则可以被其他的包访问;如果首字母小写,则只能在本包中使用(注:可以简单的理解成,首字母大写是公开的,首字母小写是私有的) ,在golang没有
public,private等关键字。
案例演示:
3.19系统保留关键字
3.20系统的预定义标识符
第 4 章运算符
4.1运算符的基本介绍
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等
- 算术运算符
- 赋值运算符
- 比较运算符/关系运算符
- 逻辑运算符
- 位运算符
- 其它运算符
4.2算术运算符
算术运算符是对数值类型的变量进行运算的,比如:加减乘除。在Go程序中使用的非常多4.2.1算术运算符的一览表
4.2.2案例演示
- 案例演示算术运算符的使用。
+,-,*,/,%,++,–,重点讲解/、%
自增:++
自减:– - 演示/的使用的特点
- 演示%的使用特点
//演示%的使用
//看一个公式a%b=a-a/bb
fmt.Println("10%3=",10%3)//=1
fmt.Println("-10%3=",-10%3)//=-10-(-10)/33=-10-(-9)=-1
fmt.Println("10%-3=",10%-3)//=1
fmt.Println("-10%-3=",-10%-3)//=-1 - ++和–的使用
4.2.3算术运算符使用的注意事项
- 对于除号"/",它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。例如:x:=19/5,结果是3
- 当对一个数取模时,可以等价a%b=a-a/b*b,这样我们可以看到取模的一个本质运算。
- Golang的自增自减只能当做一个独立语言使用时,不能这样使用
- Golang的++和–只能写在变量的后面,不能写在变量的前面,即:只有a++a–没有++a
–a
5)Golang的设计者去掉c/java中的自增自减的容易混淆的写法,让Golang更加简洁,统一。(强制性的)
4.2.4课堂练习1
4.2.5课堂练习2
- 假如还有97天放假,问:xx个星期零xx天
2)定义一个变量保存华氏温度,华氏温度转换摄氏温度的公式为:5/9*(华氏温度-100) ,请求出华氏
温度对应的摄氏温度。
4.3关系运算符(比较运算符)
4.3.1基本介绍
- 关系运算符的结果都是bool型,也就是要么是true,要么是false
- 关系表达式经常用在if结构的条件中或循环结构的条件中
4.3.2关系运算符一览图
4.3.3案例演示
4.3.4关系运算符的细节说明
细节说明
- 关系运算符的结果都是bool型,也就是要么是true,要么是false。
- 关系运算符组成的表达式,我们称为关系表达式:a>b
- 比较运算符"=="不能误写成"="!!
4.4逻辑运算符
4.4.1基本介绍
用于连接多个条件(一般来讲就是关系表达式),最终的结果也是一个bool值4.4.2逻辑运算的说明
4.4.3案例演示
4.4.4注意事项和细节说明
- &&也叫短路与:如果第一个条件为false,则第二个条件不会判断,最终结果为false
- ||也叫短路或:如果第一个条件为true,则第二个条件不会判断,最终结果为true
- 案例演示
4.5赋值运算符
4.5.1基本的介绍
赋值运算符就是将某个运算后的值,赋给指定的变量。
4.5.2赋值运算符的分类
4.5.3赋值运算的案例演示
案例演示赋值运算符的基本使用。
- 赋值基本案例
- 有两个变量,a和b,要求将其进行交换,最终打印结果
- +=的使用案例
- 案例
4.5.4赋值运算符的特点
- 运算顺序从右往左
- 赋值运算符的左边只能是变量,右边可以是变量、表达式、常量值
- 复合赋值运算符等价于下面的效果
比如:a+=3等价于a=a+3
4.5.5面试题
有两个变量,a和b,要求将其进行交换,但是不允许使用中间变量,最终打印结果
4.6位运算符
4.7其它运算符说明
举例说明:
4.7.1课堂案例
案例1:求两个数的最大值
案例2:求三个数的最大值
4.8特别说明
举例说明,如果在golang中实现三元运算的效果。
4.9运算符的优先级
4.9.1运算符的优先级的一览表
4.9.2对上图的说明
- 运算符有不同的优先级,所谓优先级就是表达式运算中的运算顺序。如右表,上一行运算符总
优先于下一行。 - 只有单目运算符、赋值运算符是从右向左运算的。
- 梳理了一个大概的优先级
1:括号,++,–
2:单目运算
3:算术运算符
4:移位运算
5:关系运算符
6:位运算符
7:逻辑运算符
8:赋值运算符
9:逗号
4.10键盘输入语句
4.10.1介绍
在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取。InputDemo.go
4.10.2步骤:
- 导入fmt包
2)调用fmt包的fmt.Scanln()或者fmt.Scanf()
4.10.3案例演示:
要求:可以从控制台接收用户信息,【姓名,年龄,薪水,是否通过考试】。
1)使用fmt.Scanln() 获取
2)使用fmt.Scanf() 获取
4.11进制
对于整数,有四种表示方式:
- 二进制:0,1,满2进1。
在golang中,不能直接使用二进制来表示一个整数,它沿用了c的特点。 - 十进制:0-9,满10进1。
- 八进制:0-7,满8进1.以数字0开头表示。
- 十六进制:0-9及A-F,满16进1.以0x或0X开头表示。此处的A-F不区分大小写。
4.11.1进制的图示
4.11.2进制转换的介绍
4.11.3其它进制转十进制
4.11.4二进制如何转十进制
4.11.5八进制转换成十进制示例
4.11.616进制转成10进制
4.11.7其它进制转10进制的课堂练习
- 课堂练习:请将
二进制:110001100转成十进制
八进制:02456转成十进制
十六进制:0xA45转成十进制
4.11.8十进制如何转成其它进制
4.11.9十进制如何转二进制
4.11.10十进制转成八进制
4.11.11十进制转十六进制
4.11.12课堂练习课堂练习:请将
123转成二进制
678转成八进制
8912转成十六进制
4.11.13二进制转换成八进制、十六进制
4.11.14二进制转换成八进制
4.11.15二进制转成十六进制
- 课堂练习
课堂练习:请将
二进制:11100101转成八进制
二进制:1110010110转成十六进制
4.11.16八进制、十六进制转成二进制
4.11.17八进制转换成二进制
4.11.18十六进制转成二进制
4.12位运算
4.12.1位运算的思考题
1)请看下面的代码段,回答a,b,c,d结果是多少?funcmain() {
varaint=1>>2
varbint=-1>>2
varcint=1<<2
vardint=-1<<2
//a,b,c,d结果是多少
fmt.Println("a=",a)
fmt.Println("b=",b)
fmt.Println("c=",c)
fmt.Println("d=",d)
}
2) 请回答在Golang中,下面的表达式运算的结果是:
funcmain(){
fmt.Println(2&3)
fmt.Println(2|3)
fmt.Println(13&7)
fmt.Println(5|4)//?
fmt.Println(-3^3)//?
}
4.12.2二进制在运算中的说明
二进制是逢2进位的进位制,0、1是基本算符。
现代的电子计算机技术全部采用的是二进制,因为它只使用0、1两个数字符号,非常简单方便,
易于用电子方式实现。计算机内部处理的信息,都是采用二进制数来表示的。二进制(Binary)数用0和1两个数字及其组合来表示任何数。进位规则是“逢2进1”,数字1在不同的位上代表不同的值,按从右至左的次序,这个值以二倍递增。
在计算机的内部,运行各种运算时,都是以二进制的方式来运行。
4.12.3原码、反码、补码
4.12.4位运算符和移位运算符
- Golang中有3个位运算
分别是”按位与&、按位或|、按位异或^,它们的运算规则是: - 案例练习
比如:2&3=?2|3=?2^3=?
- Golang中有2个移位运算符:
>>、<<右移和左移,运算规则:
右移运算符>>:低位溢出,符号位不变,并用符号位补溢出的高位
左移运算符<<:符号位不变,低位补0 - 案例演示
a:=1>>2//00000001=>00000000=0
c:=1<<2//00000001==>00000100=>4
第 5 章程序流程控制
5.1程序流程控制介绍
在程序中,程序运行的流程控制决定程序是如何执行的,是我们必须掌握的,主要有三大流程控制语句。
- 顺序控制
- 分支控制
- 循环控制
5.2顺序控制
程序从上到下逐行地执行,中间没有任何判断和跳转。
一个案例说明,必须下面的代码中,没有判断,也没有跳转.因此程序按照默认的流程执行,即顺序控制。
5.2.1顺序控制的一个流程图
5.2.2顺序控制举例和注意事项
Golang中定义变量时采用合法的前向引用。如:
funcmain(){
varnum1int=10//声明了num1
varnum2int=num1+20//使用num1
fmt.Println(num2)
}
错误形式:
funcmain(){
varnum2int=num1+20//使用num1
varnum1int=10//声明num1(×)
fmt.Println(num2)
}
5.3分支控制
5.3.1分支控制的基本介绍
分支控制就是让程序有选择执行。有下面三种形式
- 单分支
- 双分支
- 多分支
5.3.2单分支控制
- 基本语法
- 应用案例
请大家看个案例[ifDemo.go]:
编写一个程序,可以输入人的年龄,如果该同志的年龄大于18岁,则输出"你年龄大于18,要对自己的行为负责!"需求—[分析]---->代码
代码:
输出的结果:
- 单分支的流程图
流程图可以用图形方式来更加清晰的描述程序执行的流程。
- 单分支的细节说明
5.3.3双分支控制
- 基本语法
- 应用案例
请大家看个案例[IfDemo2.go]:
编写一个程序,可以输入人的年龄,如果该同志的年龄大于18岁,则输出“你年龄大于18,要对
自己的行为负责!”。否则,输出”你的年龄不大这次放过你了.”
- 双分支的流程图的分析
对双分支的总结
1.从上图看条件表达式就是age>18
2.执行代码块1===>fmt.Println("你的年龄大于18") …
3.执行代码块2===>fmt.Println("你的年龄不大…") .
4.强调一下双分支只会执行其中的一个分支。
5.3.4单分支和双分支的案例
5) 编写程序,声明2个int32型变量并赋值。判断两数之和,如果大于等于50,打印“helloworld!
6)编写程序,声明2个float64型变量并赋值。判断第一个数大于10.0,且第2个数小于20.0,打印两数之和。
7) 【选作】定义两个变量int32,判断二者的和,是否能被3又能被5整除,打印提示信息
8)判断一个年份是否是闰年,闰年的条件是符合下面二者之一:(1)年份能被4整除,但不能被100整除;(2) 能被400整除
5.3.5多分支控制
- 基本语法
对上面基本语法的说明
- 多分支的判断流程如下:
(1)先判断条件表达式1是否成立,如果为真,就执行代码块1
(2)如果条件表达式1如果为假,就去判断条件表达式2是否成立,如果条件表达式2为真,
就执行代码块2
(3)依次类推.
(4)如果所有的条件表达式不成立,则执行else的语句块。 - else不是必须的。
- 多分支只能有一个执行入口。
- 看一个多分支的流程图(更加清晰)
- 多分支的快速入门案例
岳小鹏参加Golang考试,他和父亲岳不群达成承诺:
如果:
成绩为100分时,奖励一辆BMW;
成绩为(80,99]时,奖励一台iphone7plus;
当成绩为[60,80]时,奖励一个iPad;
其它时,什么奖励也没有。
请从键盘输入岳小鹏的期末成绩,并加以判断
代码如下:
对初学者而言,有一个使用陷阱.
- 多分支的课堂练习
案例3:
代码:
代码:
5.3.6嵌套分支
- 基本介绍
在一个分支结构中又完整的嵌套了另一个完整的分支结构,里面的分支的结构称为内层分支外面的分支结构称为外层分支。 - 基本语法
- 应用案例1
参加百米运动会,如果用时8秒以内进入决赛,否则提示淘汰。并且根据性别提示进入男子组或女
子组。【可以让学员先练习下】,输入成绩和性别。
代码:
- 应用案例2
出票系统:根据淡旺季的月份和年龄,打印票价[考虑学生先做]
4_10旺季:
成人(18-60):60
儿童(<18):半价
老人(>60):1/3
淡季:
成人:40
其他:20
代码:
5.4switch分支控制
5.4.1基本的介绍
- switch语句用于基于不同条件执行不同动作,每一个case分支都是唯一的,从上到下逐一测
试,直到匹配为止。 - 匹配项后面也不需要再加break
5.4.2基本语法
5.4.3switch的流程图
- 对上图的说明和总结
- switch的执行的流程是,先执行表达式,得到值,然后和case的表达式进行比较,如果相等,就匹配到,然后执行对应的case的语句块,然后退出switch控制。
- 如果switch的表达式的值没有和任何的case的表达式匹配成功,则执行default的语句块。执行
后退出switch的控制. - golang的case后的表达式可以有多个,使用逗号间隔.
- golang中的case语句块不需要写break,因为默认会有,即在默认情况下,当程序执行完case语句块后,就直接退出该switch控制结构。
5.4.4switch快速入门案例
- 案例:
请编写一个程序,该程序可以接收一个字符,比如:a,b,c,d,e,f,ga表示星期一,b表示星期二…根
据用户的输入显示相依的信息.要求使用switch语句完成 - 代码
5.4.5switch的使用的注意事项和细节
1)case/switch后是一个表达式(即:常量值、变量、一个有返回值的函数等都可以)
2) case后的各个表达式的值的数据类型,必须和switch的表达式数据类型一致
3) case后面可以带多个表达式,使用逗号间隔。比如case表达式1,表达式2…
4)case后面的表达式如果是常量值(字面量) ,则要求不能重复
5) case后面不需要带break,程序匹配到一个case后就会执行对应的代码块,然后退出switch,如果一个都匹配不到,则执行default
6) default语句不是必须的.
7) switch后也可以不带表达式,类似if–else分支来使用。【案例演示】
8) switch后也可以直接声明/定义一个变量,分号结束,不推荐。【案例演示】
9) switch穿透-fallthrough,如果在case语句块后增加fallthrough,则会继续执行下一个case,也叫switch穿透
10) TypeSwitch:switch语句还可以被用于type-switch来判断某个interface变量中实际指向的变量类型【还没有学interface,先体验一把】
5.4.6switch的课堂练习
1)使用switch把小写类型的char型转为大写(键盘输入) 。只转换a,b,c,d,e.其它的输出“other”。
2)对学生成绩大于60分的,输出“合格”。低于60分的,输出“不合格”。(注:输入的成绩不能大于100)
3) 根据用户指定月份,打印该月份所属的季节。3,4,5春季6,7,8夏季9,10,11秋季12,1,2冬季
5.4.7switch和if的比较
总结了什么情况下使用switch,什么情况下使用if
- 如果判断的具体数值不多,而且符合整数、浮点数、字符、字符串这几种类型。建议使用swtich语句,简洁高效。
- 其他情况:对区间判断和结果为bool类型的判断,使用if,if的使用范围更广。
5.5for循环控制
5.5.1基本介绍
听其名而知其意。就是让我们的一段代码循环的执行。
5.5.2一个实际的需求
- 请大家看个案例[forTest.go]:
编写一个程序,可以打印10句
"你好,尚硅谷!"。请大家想想怎么做? - 使用传统的方式实现
- for循环的快速入门
5.5.3for循环的基本语法
- 语法格式
for循环变量初始化;循环条件;循环变量迭代{
循环操作(语句)
} - 对上面的语法格式说明
- 对for循环来说,有四个要素:
- 循环变量初始化
- 循环条件
4)循环操作(语句) ,有人也叫循环体。 - 循环变量迭代
- for循环执行的顺序说明:
- 执行循环变量初始化,比如i:=1
- 执行循环条件,比如i<=10
3)如果循环条件为真,就执行循环操作:比如fmt.Println(“…”) - 执行循环变量迭代,比如i++
- 反复执行2,3,4步骤,直到循环条件为False,就退出for循环。
5.5.4for循环执行流程分析
- for循环的流程图
- 对照代码分析for循环的执行过程
5.5.5for循环的使用注意事项和细节讨论
- 循环条件是返回一个布尔值的表达式
- for循环的第二种使用方式
for循环判断条件{
//循环执行语句
}
将变量初始化和变量迭代写到其它位置
- 案例演示:
- for循环的第三种使用方式
for{
//循环执行语句
}
上面的写法等价for;;{}是一个无限循环,通常需要配合break语句使用
4)Golang提供for-range的方式,可以方便遍历字符串和数组(注:数组的遍历,我们放到讲数组的时候再讲解) ,案例说明如何遍历字符串。
- 字符串遍历方式1-传统方式
- 字符串遍历方式2-for-range
- 上面代码的细节讨论
如果我们的字符串含有中文,那么传统的遍历字符串方式,就是错误,会出现乱码。原因是传统的
对字符串的遍历是按照字节来遍历,而一个汉字在utf8编码是对应3个字节。
如何解决需要要将str转成[]rune切片.=>体验一把
对应for-range遍历方式而言,是按照字符方式遍历。因此如果有字符串有中文,也是ok
5.5.6for循环的课堂练习
- 打印1~100之间所有是9的倍数的整数的个数及总和
- 完成下面的表达式输出,6是可变的。
5.6while和do…while的实现
Go语言没有while和do…while语法,这一点需要同学们注意一下,如果我们需要使用类似其它语言(比如java/c的while和do…while),可以通过for循环来实现其使用效果。
5.6.1while循环的实现
- 说明上图
- for循环是一个无限循环
- break语句就是跳出for循环
- 使用上面的while实现完成输出10句”hello,wrold”
5.6.2do…while的实现
- 对上图的说明
- 上面的循环是先执行,在判断,因此至少执行一次。
- 当循环条件成立后,就会执行break,break就是跳出for循环,结束循环.
- 案例演示
使用上面的do…while实现完成输出10句”hello,ok”
5.7多重循环控制(重点,难点)
5.7.1基本介绍
- 将一个循环放在另一个循环体内,就形成了嵌套循环。在外边的for称为外层循环在里面的for
循环称为内层循环。【建议一般使用两层,最多不要超过3层】 - 实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的循环条件为false
时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的循环。 - 外层循环次数为m次,内层为n次,则内层循环体实际上需要执行m*n次
5.7.2应用案例
- 统计3个班成绩情况,每个班有5名同学,求出各个班的平均分和所有班级的平均分[学生的成绩从键盘输入]
编程时两大绝招
(1)先易后难,即将一个复杂的问题分解成简单的问题。
(2)先死后活
代码:
- 统计三个班及格人数,每个班有5名同学
对上面的代码进行了一点修改.
- 打印金字塔经典案例
使用for循环完成下面的案例请编写一个程序,可以接收一个整数,表示层数,打印出金字分析编程思路
-
- 走代码
- 走代码
- 打印出九九乘法表
代码:
5.8跳转控制语句-break
5.8.1看一个具体需求,引出break
随机生成1-100的一个数,直到生成了99这个数,看看你一共用了几次?
分析:编写一个无限循环的控制,然后不停的随机生成数,当生成了99时,就退出这个无限循环==》break提示使用
这里我们给大家说一下,如下随机生成1-100整数.
5.8.2break的快速入门案例
5.8.3基本介绍:
break语句用于终止某个语句块的执行,用于中断当前for循环或跳出switch语句。
5.8.4基本语法:
{……
break
……
}
5.8.5以for循环使用break为例,画出示意图
5.8.6break的注意事项和使用细节
- break语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块
- 看一个案例
- 对上面案例的说明
(1)break默认会跳出最近的for循环
(2)break后面可以指定标签,跳出标签对应的for循环
5.8.7课堂练习
- 100以内的数求和,求出当和第一次大于20的当前数
- 实现登录验证,有三次机会,如果用户名为”张无忌”,密码”888”提示登录成功,否则提示
还有几次机会.
5.9跳转控制语句-continue
5.9.1基本介绍:
continue语句用于结束本次循环,继续执行下一次循环。
continue语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环,这个和前面的break标签的使用的规则一样.
5.9.2基本语法:
{……
continue
……
}
5.9.3continue流程图
5.9.4案例分析continue的使用
5.9.5continu的课堂练习
- 练习1
- continue实现打印1——100之内的奇数[要求使用for循环+continue]
代码:
- 从键盘读入个数不确定的整数,并判断读入的正数和负数的个数,输入为0时结束程序
- 课后练习题(同学们课后自己完成):
某人有100,000元,每经过一次路口,需要交费,规则如下:
当现金>50000时,每次交5%
当现金<=50000时,每次交1000
编程计算该人可以经过多少次路口,使用forbreak方式完成
5.10跳转控制语句-goto
5.10.1goto基本介绍
- Go语言的goto语句可以无条件地转移到程序中指定的行。
- goto语句通常与条件语句配合使用。可用来实现条件转移,跳出循环体等功能。
- 在Go程序设计中一般不主张使用goto语句,以免造成程序流程的混乱,使理解和调试程序
都产生困难
5.10.2goto基本语法gotolabel
…
label:statement
5.10.3goto的流程图
5.10.4快速入门案例
5.11跳转控制语句-return
5.11.1介绍:
return使用在方法或者函数中,表示跳出所在的方法或函数,在讲解函数的时候,会详细的介绍。
说明
- 如果return是在普通的函数,则表示跳出该函数,即不再执行函数中return后面代码,也可以理解成终止函数。
- 如果return是在main函数,表示终止main函数,也就是说终止程序。
第 6 章函数、包和错误处理
6.1为什么需要函数
6.1.1请大家完成这样一个需求:
输入两个数,再输入一个运算符(+,-,*,/),得到结果.。
6.1.2使用传统的方法解决
- 走代码
- 分析一下上面代码问题
- 上面的写法是可以完成功能,但是代码冗余
- 同时不利于代码维护
- 函数可以解决这个问题
6.2函数的基本概念
为完成某一功能的程序指令(语句)的集合,称为函数。
在Go中,函数分为:自定义函数、系统函数(查看Go编程手册)
6.3函数的基本语法
6.4快速入门案例
使用函数解决前面的计算问题。
走代码:
6.5包的引出
- 在实际的开发中,我们往往需要在不同的文件中,去调用其它文件的定义的函数,比如main.go
中,去使用utils.go文件中的函数,如何实现?-》包 - 现在有两个程序员共同开发一个Go项目,程序员xiaoming希望定义函数Cal,程序员xiaoqiang也想定义函数也叫Cal。两个程序员为此还吵了起来,怎么办?-》包
6.6包的原理图
包的本质实际上就是创建不同的文件夹,来存放程序文件。
画图说明一下包的原理
6.7包的基本概念
说明:go的每一个文件都是属于一个包的,也就是说go是以包的形式来管理文件和项目目录结构的
6.8包的三大作用
区分相同名字的函数、变量等标识符
当程序文件很多时,可以很好的管理项目
控制函数、变量等访问范围,即作用域
6.9包的相关说明
- 打包基本语法
package包名 - 引入包的基本语法
import"包的路径"
6.10包使用的快速入门
包快速入门-Go相互调用函数,我们将funcCal定义到文件utils.go,将utils.go放到一个包中,当其它文件需要使用到utils.go的方法时,可以import该包,就可以使用了.【为演示:新建项目目录结构】
代码演示:
utils.go文件
main.go文件
6.11包使用的注意事项和细节讨论
- 在给一个文件打包时,该包对应一个文件夹,比如这里的utils文件夹对应的包名就是utils,文件的包名通常和文件所在的文件夹名一致,一般为小写字母。
- 当一个文件要使用其它包函数或变量时,需要先引入对应的包
- 引入方式1:import"包名"
- 引入方式2:
import(
"包名"
"包名"
) - package指令在文件第一行,然后是import指令。
- 在import包时,路径从$GOPATH的src下开始,不用带src,编译器会自动从src下开始引入
- 为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言的public,这样才能跨包访问。比如utils.go的
- 在访问其它包函数,变量时,其语法是包名.函数名,比如这里的main.go文件中
- 如果包名较长,Go支持给包取别名,注意细节:取别名后,原来的包名就不能使用了
说明:如果给包取了别名,则需要使用别名来访问该包的函数和变量。
6)在同一包下,不能有相同的函数名(也不能有相同的全局变量名) ,否则报重复定义 - 如果你要编译成一个可执行程序文件,就需要将这个包声明为main,即packagemain.这个就是一个语法规范,如果你是写一个库,包名可以自定义
6.12函数的调用机制
6.12.1通俗易懂的方式的理解
6.12.2函数-调用过程
介绍:为了让大家更好的理解函数调用过程,看两个案例,并画出示意图,这个很重要
- 传入一个数+1
对上图说明
(1)在调用一个函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其它的栈的空间区分开来
(2)在每个函数对应的栈中,数据空间是独立的,不会混淆
(3)当一个函数调用完毕(执行完毕)后,程序会销毁这个函数对应的栈空间。 - 计算两个数,并返回
6.12.3return语句
- 基本语法和说明
- 案例演示1
请编写要给函数,可以计算两个数的和和差,并返回结果。
- 案例演示2
一个细节说明:希望忽略某个返回值,则使用_符号表示占位忽略
6.13函数的递归调用
6.13.1基本介绍
一个函数在函数体内又调用了本身,我们称为递归调用
6.13.2递归调用快速入门
- 代码1
上面代码的分析图:
- 代码2
对上面代码分析的示意图:
6.13.3递归调用的总结
函数递归需要遵守的重要原则:
1)执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
2) 函数的局部变量是独立的,不会相互影响
3)递归必须向退出递归的条件逼近,否则就是无限递归,死龟了:)
4) 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当
函数执行完毕或者返回时,该函数本身也会被系统销毁
6.13.4递归课堂练习题
- 题1:斐波那契数
请使用递归的方式,求出斐波那契数1,1,2,3,5,8,13…
给你一个整数n,求出它的斐波那契数是多少?
思路:
- 当n1||n2,返回1
2)当n>=2,返回前面两个数的和f(n-1)+f(n-2) 代码:
- 题2:求函数值
已知f(1)=3;f(n)=2*f(n-1)+1;
请使用递归的思想编程,求出f(n)的值?
思路:
直接使用给出的表达式即可完成
代码:
- 练习题3
题3:猴子吃桃子问题
有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。当到第十天时,想再吃时(还没吃),发现只有1个桃子了。问题:最初共多少个桃子?
思路分析:
- 第10天只有一个桃子
2)第9天有几个桃子=(第10天桃子数量+1) *2
3)规律:第n天的桃子数据peach(n)=(peach(n+1)+1) *2代码:
6.14函数使用的注意事项和细节讨论
- 函数的形参列表可以是多个,返回值列表也可以是多个。
- 形参列表和返回值列表的数据类型可以是值类型和引用类型。
- 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其
它包文件使用,类似public,首字母小写,只能被本包文件使用,其它包文件不能使用,类似privat4)函数中的变量是局部的,函数外不生效【案例说明】
- 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。
6)如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型) ,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。
- Go函数不支持函数重载
- 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用
- 函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用
- 为了简化数据类型定义,Go支持自定义数据类型
基本语法:type自定义数据类型名数据类型//理解:相当于一个别名案例:typemyIntint//这时myInt就等价int来使用了.
案例:typemySumfunc(int,int)int//这时mySum就等价一个函数类型func(int,int)
int
举例说明自定义数据类型的使用:
11) 支持对函数返回值命名
12) 使用_标识符,忽略返回值
13) Go支持可变参数
(3)如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后。代码演示:
6.15函数的课堂练习
- 题1
- 题2
- 题3:请编写一个函数swap(n1int,n2int)可以交换n1和n2的值
6.16init函数
6.16.1基本介绍
每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用,也就是说init会在main函数前被调用。
6.16.2案例说明:
输出的结果是:
6.16.3inti函数的注意事项和细节
- 如果一个文件同时包含全局变量定义,init函数和main函数,则执行的流程全局变量定义->init函数->main函数
- init函数最主要的作用,就是完成一些初始化的工作,比如下面的案例
- 细节说明:面试题:案例如果main.go和utils.go都含有变量定义,init函数时,执行的流程又是怎么样的呢?
6.17匿名函数
6.17.1介绍
Go支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用。
6.17.2匿名函数使用方式1
在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。【案例演示】
6.17.3匿名函数使用方式2
将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数【案例演示】
6.17.4全局匿名函数
如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效。
6.18闭包
6.18.1介绍
基本介绍:闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)
6.18.2案例演示:
- 对上面代码的说明和总结
1)AddUpper是一个函数,返回的数据类型是fun(int) int
- 闭包的说明
返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成一
个整体,构成闭包。 - 大家可以这样理解:闭包是类,函数是操作,n是字段。函数和它使用到n构成闭包。
- 当我们反复的调用f函数时,因为n是初始化一次,因此每调用一次就进行累计。
5)我们要搞清楚闭包的关键,就是要分析出返回的函数它使用(引用) 到哪些变量,因为函数和它引用到的变量共同构成闭包。 - 对上面代码的一个修改,加深对闭包的理解
6.18.3闭包的最佳实践
- 请编写一个程序,具体要求如下
1)编写一个函数makeSuffix(suffixstring)可以接收一个文件后缀名(比如.jpg) ,并返回一个闭包
2)调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回文件名.jpg,如
果已经有.jpg后缀,则返回原文件名。
- 要求使用闭包的方式完成
- strings.HasSuffix,该函数可以判断某个字符串是否有指定的后缀。
代码:
- 上面代码的总结和说明:
1)返回的匿名函数和makeSuffix(suffixstring) 的suffix变量组合成一个闭包,因为返回的函数引用到suffix这个变量
- 我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入后缀名,比如.jpg,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。大家可以仔细的体会一把!
6.19函数的defer
6.19.1为什么需要defer
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制)。
6.19.2快速入门案例
执行后,输出的结果:
6.19.3defer的注意事项和细节
- 当go执行到一个defer时,不会立即执行defer后的语句,而是将defer后的语句压入到一个栈中[我为了讲课方便,暂时称该栈为defer栈],然后继续执行函数下一个语句。
2)当函数执行完毕后,在从defer栈中,依次从栈顶取出语句执行(注:遵守栈先入后出的机制) ,
所以同学们看到前面案例输出的顺序。 - 在defer将语句放入到栈时,也会将相关的值拷贝同时入栈。请看一段代码:
上面代码输出的结果如下:
6.19.4defer的最佳实践
defer最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源。看下模拟代码。。
说明
1)在golang编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源),可以执行deferfile.Close()deferconnect.Close()
2) 在defer后,可以继续使用创建资源.
3) 当函数完毕后,系统会依次从defer栈中,取出语句,关闭资源.
4) 这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心。
6.20函数参数传递方式
6.20.1基本介绍
我们在讲解函数注意事项和使用细节时,已经讲过值类型和引用类型了,这里我们再系统总结一
下,因为这是重难点,值类型参数默认就是值传递,而引用类型参数默认就是引用传递。
6.20.2两种传递方式
- 值传递
- 引用传递
其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低。
6.20.3值类型和引用类型
- 值类型:基本数据类型int系列,float系列,bool,string、数组和结构体struct
- 引用类型:指针、slice切片、map、管道chan、interface等都是引用类型
6.20.4值传递和引用传递使用特点
3) 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操
作变量。从效果上看类似引用。这个案例在前面详解函数使用注意事项的
6.21变量作用域
- 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
- 函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用
域在整个程序有效
- 如果变量是在一个代码块,比如for/if中,那么这个变量的的作用域就在该代码块
6.21.1变量作用域的课堂练习
输出的结果是:tomtomjacktom
6.22函数课堂练习(综合)
- 函数可以没有返回值案例,编写一个函数,从终端输入一个整数打印出对应的金子塔
分析思路:就是将原来写的打印金字塔的案例,使用函数的方式封装,在需要打印时,直接调用
即可。
2)编写一个函数,从终端输入一个整数(1—9) ,打印出对应的乘法表
分析思路:就是将原来写的调用九九乘法表的案例,使用函数的方式封装,在需要打印时,直接调用即可
代码:
3)编写函数,对给定的一个二维数组(3×3) 转置,这个题讲数组的时候再完成
6.23字符串常用的系统函数
说明:字符串在我们程序开发中,使用的是非常多的,常用的函数需要同学们掌握[带看手册或者官方编程指南]:
1)统计字符串的长度,按字节len(str)
2)字符串遍历,同时处理有中文的问题r:=[]rune(str)
3)字符串转整数:n,err:=strconv.Atoi("12")
4)整数转字符串str=strconv.Itoa(12345)
5)字符串转[]byte:varbytes=[]byte("hellogo")
6)[]byte转字符串:str=string([]byte{97,98,99})
7)10进制转2,8,16进制:str=strconv.FormatInt(123,2) //2->8,16
8)查找子串是否在指定的字符串中:strings.Contains("seafood","foo") //true
9)统计一个字符串有几个指定的子串:strings.Count("ceheese","e") //4
10)不区分大小写的字符串比较(==是区分字母大小写的):fmt.Println(strings.EqualFold("abc","Abc")) //true
11)返回子串在字符串第一次出现的index值,如果没有返回-1:strings.Index("NLT_abc","abc") //4
12)返回子串在字符串最后一次出现的index,如没有返回-1:strings.LastIndex("gogolang","go")
13)将指定的子串替换成另外一个子串:strings.Replace("gogohello","go","go语言",n) n可以指定你希望替换几个,如果n=-1表示全部替换
14)按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组:strings.Split("hello,wrold,ok",",")
15)将字符串的字母进行大小写的转换:strings.ToLower("Go")//gostrings.ToUpper("Go") //GO
16)将字符串左右两边的空格去掉:strings.TrimSpace("tnalonegopherntrn")
17)将字符串左右两边指定的字符去掉:strings.Trim("!hello!","!") //["hello"]//将左右两边!
和""去掉
18)将字符串左边指定的字符去掉:strings.TrimLeft("!hello!","!") //["hello"]//将左边!和""去掉
19)将字符串右边指定的字符去掉:strings.TrimRight("!hello!","!") //["hello"]//将右边!和""去掉
20)判断字符串是否以指定的字符串开头:strings.HasPrefix("ftp://192.168.10.1","ftp")//true
21)判断字符串是否以指定的字符串结束:strings.HasSuffix("NLT_abc.jpg","abc") //false
6.24时间和日期相关函数
6.24.1基本的介绍
说明:在编程中,程序员会经常使用到日期相关的函数,比如:统计某段代码执行花费的时间等
等。
- 时间和日期相关函数,需要导入time包
- time.Time类型,用于表示时间
- 如何获取到其它的日期信息
- 格式化日期时间
方式1:就是使用Printf或者SPrintf
方式二:使用time.Format()方法完成:
对上面代码的说明:
"2006/01/0215:04:05"这个字符串的各个数字是固定的,必须是这样写。
"2006/01/0215:04:05"这个字符串各个数字可以自由的组合,这样可以按程序需求来返回时间和日期 - 时间的常量const(
NanosecondMicrosecondMillisecondSecondMinute
Hour
)
Duration=1//纳秒
=1000Nanosecond//微秒
=1000Microsecond//毫秒
=1000Millisecond//秒
=60Second//分钟
=60Minute//小时
常量的作用:在程序中可用于获取指定时间单位的时间,比如想得到100毫秒
100time.Millisecond - 结合Sleep来使用一下时间常量
- time的Unix和UnixNano的方法
得到的结果是:
6.24.2时间和日期的课堂练习
编写一段代码来统计函数test03执行的时间
6.25内置函数
6.25.1说明:
Golang设计者为了编程方便,提供了一些函数,这些函数可以直接使用,我们称为Go的内置函数。文档:https://studygolang.com/pkgdoc->builtin
- len:用来求长度,比如string、array、slice、map、channel
- new:用来分配内存,主要用来分配值类型,比如int、float32,struct…返回的是指针举例说明new的使用:
上面代码对应的内存分析图:
- make:用来分配内存,主要用来分配引用类型,比如channel、map、slice。这个我们后面讲解。
6.26错误处理
6.26.1看一段代码,因此错误处理
对上面代码的总结
1)在默认情况下,当发生错误后(panic),程序就会退出(崩溃.)
2)如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。还可以在捕获到错误后,给管理员一个提示(邮件,短信。。。)
3) 这里引出我们要将的错误处理机制
6.26.2基本说明
- Go语言追求简洁优雅,所以,Go语言不支持传统的try…catch…finally这种处理。
- Go中引入的处理方式为:defer,panic,recover
- 这几个异常的使用场景可以这么简单描述:Go中可以抛出一个panic的异常,然后在defer中
通过recover捕获这个异常,然后正常处理
6.26.3使用defer+recover来处理错误
6.26.4错误处理的好处
进行错误处理后,程序不会轻易挂掉,如果加入预警代码,就可以让程序更加的健壮。看一个
案例演示:
6.26.5自定义错误
6.26.6自定义错误的介绍
Go程序中,也支持自定义错误,使用errors.New和panic内置函数。
1)errors.New("错误说明") ,会返回一个error类型的值,表示一个错误
2)panic内置函数,接收一个interface{}类型的值(也就是任何值了) 作为参数。可以接收error类型的变量,输出错误信息,并退出程序.
6.26.7案例说明
第 7 章数组与切片
7.1为什么需要数组
- 看一个问题
一个养鸡场有6只鸡,它们的体重分别是3kg,5kg,1kg,3.4kg,2kg,50kg。请问这六只鸡的总体重是多少?平均体重是多少?请你编一个程序。=》数组 - 使用传统的方法来解决
对上面代码的说明
- 使用传统的方法不利于数据的管理和维护.
- 传统的方法不够灵活,因此我们引出需要学习的新的数据类型=>数组.
7.2数组介绍
数组可以存放多个同一类型数据。数组也是一种数据类型,在Go中,数组是值类型。
7.3数组的快速入门
我们使用数组的方法来解决养鸡场的问题.
对上面代码的总结
- 使用数组来解决问题,程序的可维护性增加.
- 而且方法代码更加清晰,也容易扩展。
7.4数组定义和内存布局
- 数组的定义
var数组名[数组大小]数据类型
vara[5]int
赋初值a[0]=1a[1]=30… - 数组在内存布局(重要)
对上图的总结:
- 数组的地址可以通过数组名来获取&intArr
- 数组的第一个元素的地址,就是数组的首地址
- 数组的各个元素的地址间隔是依据数组的类型决定,比如int64->8int32->4…
7.5数组的使用
- 访问数组元素
数组名[下标]比如:你要使用a数组的第三个元素a[2] - 快速入门案例
从终端循环输入5个成绩,保存到float64数组,并输出.
- 四种初始化数组的方式
7.6数组的遍历
7.6.1方式1-常规遍历:
前面已经讲过了,不再赘述。
7.6.2方式2-for-range结构遍历
这是Go语言一种独有的结构,可以用来遍历访问数组的元素。
- for–range的基本语法
- for-range的案例
7.7数组使用的注意事项和细节
- 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化
- vararr[]int这时arr就是一个slice切片,切片后面专门讲解,不急哈.
- 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用。
4)数组创建后,如果没有赋值,有默认值(零值)
数值类型数组:默认值为0
字符串数组:默认值为""
bool数组:默认值为false
5)使用数组的步骤1.声明数组并开辟空间2给数组各个元素赋值(默认零值) 3使用数组 - 数组的下标是从0开始的
- 数组下标必须在指定范围内使用,否则报panic:数组越界,比如vararr[5]int则有效下标为0-4
- Go的数组属值类型,在默认情况下是值传递,因此会进行值拷贝。数组间不会相互影响
9)如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)
- 长度是数组类型的一部分,在传递函数参数时需要考虑数组的长度,看下面案例
7.8数组的应用案例
- 创建一个byte类型的26个元素的数组,分别放置'A'-'Z‘。使用for循环访问所有元素并打印出来。提示:字符数据运算'A'+1->'B'
- 请求出一个数组的最大值,并得到对应的下标。
- 请求出一个数组的和和平均值。for-range
- 要求:随机生成五个数,并将其反转打印,复杂应用.
7.9为什么需要切片
先看一个需求:我们需要一个数组用于保存学生的成绩,但是学生的个数是不确定的,请问怎么
办?解决方案:-》使用切片。
7.10切片的基本介绍
- 切片的英文是slice
- 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
3)切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度len(slice) 都一样。 - 切片的长度是可以变化的,因此切片是一个可以动态变化数组。
- 切片定义的基本语法:
var切片名[]类型
比如:vara[]int
7.11快速入门
演示一个切片的基本使用:
运行结果是:
7.12切片在内存中形式(重要)基本的介绍:
- 为了让大家更加深入的理解切片,我们画图分析一下切片在内存中是如何布局的,这个是一个非常重要的知识点:(以前面的案例来分析)
- 画出前面的切片内存布局
- 对上面的分析图总结
1.slice的确是一个引用类型
2.slice从底层来说,其实就是一个数据结构(struct结构体)
typeslicestruct{
ptr*[2]int
lenint
capint
}
7.13切片的使用
- 方式1
第一种方式:定义一个切片,然后让切片去引用一个已经创建好的数组,比如前面的案例就是这
样的。
- 方式2
第二种方式:通过make来创建切片.
基本语法:var切片名[]type=make([]type,len,[cap])
参数说明:type:就是数据类型len:大小cap:指定切片容量,可选,如果你分配了cap,则要求cap>=len.
案例演示:
对上面代码的小结:
- 通过make方式创建切片可以指定切片的大小和容量
- 如果没有给切片的各个元素赋值,那么就会使用默认值[int,float=>0string=>””bool=>false]
- 通过make方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice去访问各个元素.
- 方式3
第3种方式:定义一个切片,直接就指定具体数组,使用原理类似make的方式
案例演示:
- 方式1和方式2的区别(面试)
7.14切片的遍历
切片的遍历和数组一样,也有两种方式
- for循环常规方式遍历
- for-range结构遍历切片
7.15切片的使用的注意事项和细节讨论
- 切片初始化时varslice=arr[startIndex:endIndex]
说明:从arr数组下标为startIndex,取到下标为endIndex的元素(不含arr[endIndex])。
2)切片初始化时,仍然不能越界。范围在[0-len(arr) ]之间,但是可以动态增长.
varslice=arr[0:end]可以简写varslice=arr[:end]
varslice=arr[start:len(arr)]可以简写:varslice=arr[start:]
varslice=arr[0:len(arr)]可以简写:varslice=arr[:] - cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
- 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者make一个空间供切片来使用
- 切片可以继续切片[案例演示]
- 用append内置函数,可以对切片进行动态追加
对上面代码的小结
切片append操作的底层原理分析:
切片append操作的本质就是对数组扩容
go底层会创建一下新的数组newArr(安装扩容后大小)
将slice原来包含的元素拷贝到新的数组newArr
slice重新引用到newArr
注意newArr是在底层来维护的,程序员不可见. - 切片的拷贝操作
切片使用copy内置函数完成拷贝,举例说明
对上面代码的说明:
(1)copy(para1,para2)参数的数据类型是切片
(2)按照上面的代码来看,slice4和slice5的数据空间是独立,相互不影响,也就是说slice4[0]=999,slice5[0]仍然是1 - 关于拷贝的注意事项
说明:上面的代码没有问题,可以运行,最后输出的是[1] - 切片是引用类型,所以在传递时,遵守引用传递机制。看两段代码,并分析底层原理
7.16string和slice
- string底层是一个byte数组,因此string也可以进行切片处理案例演示:
- string和切片在内存的形式,以"abcd"画出内存示意图
- string是不可变的,也就说不能通过str[0]='z'方式来修改字符串
- 如果需要修改字符串,可以先将string->[]byte/或者[]rune->修改->重写转成string
7.17切片的课堂练习题
说明:编写一个函数fbn(nint),要求完成
- 可以接收一个nint
- 能够将斐波那契的数列放到切片中
- 提示,斐波那契的数列形式:
arr[0]=1;arr[1]=1;arr[2]=2;arr[3]=3;arr[4]=5;arr[5]=8
代码+思路:
第 8 章排序和查找
8.1排序的基本介绍
8.2冒泡排序的思路分析
8.3冒泡排序实现
8.4课后练习
要求同学们能够,不看老师的代码,可以默写冒泡排序法(笔试题)
8.5查找
- 介绍:
在Golang中,我们常用的查找有两种:
- 顺序查找
2)二分查找(该数组是有序)
- 案例演示:
- 有一个数列:白眉鹰王、金毛狮王、紫衫龙王、青翼蝠王
猜数游戏:从键盘中任意输入一个名称,判断数列中是否包含此名称【顺序查找】
代码:
- 请对一个有序数组进行二分查找{1,8,10,89,1000,1234},输入一个数看看该数组是否存在此数,并且求出下标,如果没有就提示"没有这个数"。【会使用到递归】
二分查找的思路分析:
二分查找的代码实现:
8.6二维数组的介绍
多维数组我们只介绍二维数组
8.7二维数组的应用场景
比如我们开发一个五子棋游戏,棋盘就是需要二维数组来表示。如图
8.8二维数组快速入门
快速入门案例:
- 请用二维数组输出如下图形
000000
001000
020300
000000 - 代码演示
8.9使用方式1:先声明/定义,再赋值
- 语法:var数组名[大小][大小]类型
- 比如:vararr[2][3]int,再赋值。
- 使用演示
- 二维数组在内存的存在形式(重点)
8.10使用方式2:直接初始化
- 声明:var数组名[大小][大小]类型=[大小][大小]类型{{初值…},{初值…}}
- 赋值(有默认值,比如int类型的就是0)
- 使用演示
- 说明:二维数组在声明/定义时也对应有四种写法[和一维数组类似]
var数组名[大小][大小]类型=[大小][大小]类型{{初值…},{初值…}}
var数组名[大小][大小]类型=[…][大小]类型{{初值…},{初值…}}
var数组名=[大小][大小]类型{{初值…},{初值…}}
var数组名=[…][大小]类型{{初值…},{初值…}}
8.11二维数组的遍历
- 双层for循环完成遍历
- for-range方式完成遍历
案例演示:
8.12二维数组的应用案例
- 要求如下:
定义二维数组,用于保存三个班,每个班五名同学成绩,
并求出每个班级平均分、以及所有班级平均分 - 代码
第 9 章map
9.1map的基本介绍
map是key-value数据结构,又称为字段或者关联数组。类似其它编程语言的集合,在编程中是经常使用到
9.2map的声明
9.2.1基本语法
varmap变量名map[keytype]valuetype
- key可以是什么类型
golang中的map,的key可以是很多种类型,比如bool,数字,string,指针,channel,还可以是只包含前面几个类型的接口,结构体,数组
通常key为int、string
注意:slice,map还有function不可以,因为这几个没法用==来判断 - valuetype可以是什么类型
valuetype的类型和key基本一样,这里我就不再赘述了
通常为:数字(整数,浮点数),string,map,struct
9.2.2map声明的举例
- map声明的举例:
varamap[string]string
varamap[string]int
varamap[int]string
varamap[string]map[string]string
注意:声明是不会分配内存的,初始化需要make,分配内存后才能赋值和使用。案例演示:
- 对上面代码的说明
- map在使用前一定要make
- map的key是不能重复,如果重复了,则以最后这个key-value为准
- map的value是可以相同的.
- map的key-value是无序
- make内置函数数目
9.3map的使用
- 方式1
- 方式2
- 方式3
- map使用的课堂案例
课堂练习:演示一个key-value的value是map的案例
比如:我们要存放3个学生信息,每个学生有name和sex信息
思路:map[string]map[string]string
代码:
9.4map的增删改查操作
- map增加和更新:
map["key"]=value//如果key还没有,就是增加,如果key存在就是修改。
- map删除:
说明:
delete(map,"key"),delete是一个内置函数,如果key存在,就删除该key-value,如果key不存在,不操作,但是也不会报错
案例演示:
- 细节说明
如果我们要删除map的所有key,没有一个专门的方法一次删除,可以遍历一下key,逐个删除或者map=make(…),make一个新的,让原来的成为垃圾,被gc回收
- map查找:
案例演示:
对上面代码的说明:
说明:如果heroes这个map中存在"no1",那么findRes就会返回true,否则返回false
9.5map遍历:
案例演示相对复杂的map遍历:该map的value又是一个map
说明:map的遍历使用for-range的结构遍历
- 案例演示:
- map的长度:
9.6map切片
9.6.1基本介绍
切片的数据类型如果是map,则我们称为sliceofmap,map切片,这样使用则map个数就可以动态变化了。
9.6.2案例演示
要求:使用一个map来记录monster的信息name和age,也就是说一个monster对应一个map,并且妖怪的个数可以动态的增加=>map切片
代码:
9.7map排序
9.7.1基本介绍
- golang中没有一个专门的方法针对map的key进行排序
- golang中的map默认是无序的,注意也不是按照添加的顺序存放的,你每次遍历,得到的输出
可能不一样.【案例演示1】 - golang中map的排序,是先将key进行排序,然后根据key值遍历输出即可
9.7.2案例演示
9.8map使用细节
- map是引用类型,遵守引用类型传递的机制,在一个函数接收map,修改后,会直接修改原来的map【案例演示】
2)map的容量达到后,再想map增加元素,会自动扩容,并不会发生panic,也就是说map能动态的增长键值对(key-value)
3)map的value也经常使用struct类型,更适合管理复杂的数据(比前面value是一个map更好) ,
比如value为Student结构体【案例演示,因为还没有学结构体,体验一下即可】
9.9map的课堂练习题
- 课堂练习:
- 使用map[string]map[string]sting的map类型
- key:表示用户名,是唯一的,不可以重复
3)如果某个用户名存在,就将其密码修改"888888",如果不存在就增加这个用户信息,(包括昵称nickname和密码pwd) 。
4)编写一个函数modifyUser(usersmap[string]map[string]sting,namestring) 完成上述功能
- 代码实现
第
10.1结构体
10.1.1看一个问题
10章面向对象编程(上)
10.1.2使用现有技术解决
- 单独的定义变量解决
代码演示:
- 使用数组解决
代码演示:
10.1.3现有技术解决的缺点分析
- 使用变量或者数组来解决养猫的问题,不利于数据的管理和维护。因为名字,年龄,颜色都是属于一只猫,但是这里是分开保存。
2)如果我们希望对一只猫的属性(名字、年龄,颜色)进行操作(绑定方法) ,也不好处理。 - 引出我们要讲解的技术-》结构体。
10.1.4一个程序就是一个世界,有很多对象(变量)
10.1.5Golang语言面向对象编程说明
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本身就是语言类型系统(typesystem)的一部分,通过接口(interface) 关联,耦合性低,也非常灵活。后面同学们会充分体会到这个特点。也就是说在Golang中面向接口编程是非常重要的特性。
10.1.6结构体与结构体变量(实例/对象)的关系示意图
- 对上图的说明
1)将一类事物的特性提取出来(比如猫类) ,形成一个新的数据类型,就是一个结构体。
2)通过这个结构体,我们可以创建多个变量(实例/对象)
- 事物可以猫类,也可以是Person,Fish或是某个工具类。。。
10.1.7快速入门-面向对象的方式(struct)解决养猫问题
- 代码演示
10.1.8结构体和结构体变量(实例)的区别和联系通过上面的案例和讲解我们可以看出:
- 结构体是自定义的数据类型,代表一类事物.
2)结构体变量(实例) 是具体的,实际的,代表一个具体变量
10.1.9结构体变量(实例)在内存的布局(重要!)
10.1.10如何声明结构体
- 基本语法
type结构体名称struct{
field1type
field2type
} - 举例:
typeStudentstruct{
Namestring//字段
Ageint//字段
Scorefloat32
}
10.1.11字段/属性
- 基本介绍
1)从概念或叫法上看:结构体字段=属性=field(即授课中,统一叫字段)
- 字段是结构体的一个组成部分,一般是基本数据类型、数组,也可是引用类型。比如我们前面定义猫结构体的Namestring就是属性
- 注意事项和细节说明
- 字段声明语法同变量,示例:字段名字段类型
- 字段的类型可以为:基本类型、数组或引用类型
3)在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值) ,规则同前面讲的一样:
布尔类型是false,数值是0,字符串是""。
数组类型的默认值和它的元素类型相关,比如score[3]int则为[0,0,0]
指针,slice,和map的零值都是nil,即还没有分配空间。
案例演示:
- 不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个,结构体是值类型。
案例:
画出上面代码的内存示意图:
10.1.12创建结构体变量和访问结构体字段
- 方式1-直接声明
案例演示:varpersonPerson
前面我们已经说了。 - 方式2-{}
案例演示:varpersonPerson=Person{}
- 方式3-&
案例:varperson*Person=new(Person)
- 方式4-{}
案例:varperson*Person=&Person{}
- 说明:
- 第3种和第4种方式返回的是结构体指针。
2)结构体指针访问字段的标准方式应该是:(*结构体指针).字段名,比如(*person) .Name="tom"
3)但go做了一个简化,也支持结构体指针.字段名,比如person.Name="tom"。更加符合程序员使用的习惯,go编译器底层对person.Name做了转化(*person) .Name。
10.1.13struct类型的内存分配机制
- 看一个思考题
输出的结果是:p2.Name=tomp1.Name=小明 - 基本说明
- 结构体在内存中示意图
- 看下面代码,并分析原因
输出的结果是:
上面代码对应的内存图的分析:
- 看下面代码,并分析原因
10.1.14结构体使用注意事项和细节
- 结构体的所有字段在内存中是连续的
对应的分析图:
2)结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)
3)结构体进行type重新定义(相当于取别名) ,Golang认为是新的数据类型,但是相互间可以强转
- struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化。
- 序列化的使用场景:
- 举例:
10.2方法
10.2.1基本介绍
在某些情况下,我们要需要声明(定义)方法。比如Person结构体:除了有一些字段外(年龄,姓
名…),Person结构体还有一些行为比如:可以说话、跑步…,通过学习,还可以做算术题。这时就要用方法才能完成。
Golang中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct。
10.2.2方法的声明和调用
typeAstruct{
Numint
}
func(aA)test(){
fmt.Println(a.Num)
}
- 对上面的语法的说明
1)func(aA)test() {}表示A结构体有一方法,方法名为test
2)(aA) 体现test方法是和A类型绑定的 - 举例说明
- 对上面的总结
- test方法和Person类型绑定
- test方法只能通过Person类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调
用
3)func(pPerson)test() {}…p表示哪个Person变量调用,这个p就是它的副本,这点和函数传参非常相似。 - p这个名字,有程序员指定,不是固定,比如修改成person也是可以
10.2.3方法快速入门
- 给Person结构体添加speak方法,输出xxx是一个好人
- 给Person结构体添加jisuan方法,可以计算从1+…+1000的结果,说明方法体内可以函数一样,进行各种运算
- 给Person结构体jisuan2方法,该方法可以接收一个数n,计算从1+…+n的结果
- 给Person结构体添加getSum方法,可以计算两个数的和,并返回结果
- 方法的调用
10.2.4方法的调用和传参机制原理:(重要!)
- 说明:
方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当做实参也传递给方法。下面我们举例说明。 - 案例1:
画出前面getSum方法的执行过程+说明
说明:
- 在通过一个变量去调用方法时,其调用机制和函数一样
- 不一样的地方时,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类
型,则进行值拷贝,如果变量是引用类型,则进行地质拷贝)
- 案例2
请编写一个程序,要求如下:
- 声明一个结构体Circle,字段为radius
- 声明一个方法area和Circle绑定,可以返回面积。
- 提示:画出area执行过程+说明
10.2.5方法的声明(定义)
func(receviertype)methodName(参数列表)(返回值列表){
方法体
return返回值
}
- 参数列表:表示方法输入
- receviertype:表示这个方法和type这个类型进行绑定,或者说该方法作用于type类型
- receivertype:type可以是结构体,也可以其它的自定义类型
4)receiver:就是type类型的一个变量(实例),比如:Person结构体的一个变量(实例) - 返回值列表:表示返回的值,可以多个
- 方法主体:表示为了实现某一功能代码块
- return语句不是必须的。
10.2.6方法的注意事项和细节
- 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
- 如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
3)Golang中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定) ,因此自定义类型,都可以有方法,而不仅仅是struct,比如int,float32等都可以有方法
- 方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。[讲解]
5)如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String() 进行输出
10.2.7方法的课堂练习题
1)编写结构体(MethodUtils) ,编程一个方法,方法不需要参数,在方法中打印一个108的矩形,在main方法中调用该方法。
2) 编写一个方法,提供m和n两个参数,方法中打印一个mn的矩形
3)编写一个方法算该矩形的面积(可以接收长len,和宽width) ,将其作为方法返回值。在main
方法中调用该方法,接收返回的面积值并打印。
4) 编写方法:判断一个数是奇数还是偶数
5) 根据行、列、字符打印对应行数和列数的字符,比如:行:3,列:2,字符*,则打印相应的效果
6)定义小小计算器结构体(Calcuator) ,实现加减乘除四个功能
实现形式1:分四个方法完成:
实现形式2:用一个方法搞定
10.2.8方法的课后练习题
强调:一定自己要做,否则学习效果不好!!
10.2.9方法和函数区别
- 调用方式不一样
函数的调用方式:函数名(实参列表)
方法的调用方式:变量.方法名(实参列表) - 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
3)对于方法(如struct的方法) ,接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以
总结: - 不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定.
2)如果是和值类型,比如(pPerson),则是值拷贝,如果和指针类型,比如是(p*Person) 则是地址拷贝。
10.3面向对象编程应用实例
10.3.1步骤
1)声明(定义) 结构体,确定结构体名
2) 编写结构体的字段
3) 编写结构体的方法
10.3.2学生案例:
- 编写一个Student结构体,包含name、gender、age、id、score字段,分别为string、string、int、
int、float64类型。 - 结构体中声明一个say方法,返回string类型,方法返回信息中包含所有字段值。
3)在main方法中,创建Student结构体实例(变量) ,并访问say方法,并将调用结果打印输出。 - 走代码
10.3.3小狗案例[学员课后练习]
- 编写一个Dog结构体,包含name、age、weight字段
- 结构体中声明一个say方法,返回string类型,方法返回信息中包含所有字段值。
3)在main方法中,创建Dog结构体实例(变量) ,并访问say方法,将调用结果打印输出。
10.3.4盒子案例
- 编程创建一个Box结构体,在其中声明三个字段表示一个立方体的长、宽和高,长宽高要从终端获取
- 声明一个方法获取立方体的体积。
- 创建一个Box结构体变量,打印给定尺寸的立方体的体积
- 走代码
10.3.5景区门票案例
- 一个景区根据游人的年龄收取不同价格的门票,比如年龄大于18,收费20元,其它情况门票免费.
- 请编写Visitor结构体,根据年龄段决定能够购买的门票价格并输出
- 代码:
10.4创建结构体变量时指定字段值
- 说明
Golang在创建结构体实例(变量)时,可以直接指定字段的值 - 方式1
- 方式2
10.5工厂模式
10.5.1说明
Golang的结构体没有构造函数,通常可以使用工厂模式来解决这个问题。
10.5.2看一个需求
一个结构体的声明是这样的:
packagemodel
typeStudentstruct{
Namestring…
}
因为这里的Student的首字母S是大写的,如果我们想在其它包创建Student的实例(比如main包),引入model包后,就可以直接创建Student结构体的变量(实例)。但是问题来了,如果首字母是小写的,比如是typestudentstruct{…}就不不行了,怎么办—>工厂模式来解决.
10.5.3工厂模式来解决问题
- 使用工厂模式实现跨包创建结构体实例(变量)的案例:
如果model包的结构体变量首字母大写,引入后,直接使用,没有问题
如果model包的结构体变量首字母小写,引入后,不能直接使用,可以工厂模式解决,看老师演示,代码:
student.go
main.go
10.5.4思考题
同学们思考一下,如果model包的student的结构体的字段Score改成score,我们还能正常访问
吗?又应该如何解决这个问题呢?[老师给出思路,学员自己完成]
- 解决方法如下:
第 11 章面向对象编程(下)
11.1VSCode的使用
11.1.1VSCode使用技巧和经验
- 设置字体
文件->首选项->设置
- 快捷键的使用
自定义快捷配置:文件->首选项->键盘快捷方式
- 介绍几个常用的快捷键
11.2面向对象编程思想-抽象
11.2.1抽象的介绍
我们在前面去定义一个结构体时候,实际上就是把一类事物的共有的属性(字段)和行为(方法)提取出来,形成一个物理模型(结构体)。这种研究问题的方法称为抽象。
11.2.2代码实现
- 对上面代码的要求
- 同学们自己可以独立完成
- 增加一个控制台的菜单,可以让用户动态的输入命令和选项
11.3面向对象编程三大特性-封装
11.3.1基本介绍
Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不一样,下面我们一一为同学们进行详细的讲解Golang的三大特性是如何实现的。
11.3.2封装介绍
封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作
11.3.3封装的理解和好处
- 隐藏实现细节
2)提可以对数据进行验证,保证安全合理(Age)
11.3.4如何体现封装
- 对结构体中的属性进行封装
- 通过方法,包实现封装
11.3.5封装的实现步骤
1)将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似private)
2) 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
3)提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判断并赋值func(var结构体类型名)SetXxx(参数列表)(返回值列表) {
//加入数据验证的业务逻辑
var.字段=参数
}
4)提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的值func(var结构体类型名)GetXxx() {
returnvar.age;
}
特别说明:在Golang开发中并没有特别强调封装,这点并不像Java.所以提醒学过java的朋友,不用总是用java的语法特性来看待Golang,Golang本身对面向对象的特性做了简化的.
11.3.6快速入门案例
- 看一个案例
请大家看一个程序(person.go),不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验证。设计:model包(person.go)main包(main.go调用Person结构体) - 代码实现
model/person.go
main/main.go
11.3.7课堂练习(学员先做)
- 要求
- 创建程序,在model包中定义Account结构体:在main函数中体会Golang的封装性。
2)Account结构体要求具有字段:账号(长度在6-10之间)、余额(必须>20) 、密码(必须是六 - 通过SetXxx的方法给Account的字段赋值。(同学们自己完成
- 在main函数中测试
- 代码实现
model/account.go
main/main.go - 说明:在老师的代码基础上增加如下功能:
通过SetXxx的方法给Account的字段赋值通过GetXxx方法获取字段的值。(同学们自己完成)在main函数中测试
11.4面向对象编程三大特性-继承
11.4.1看一个问题,引出继承的必要性
一个小问题,看个学生考试系统的程序extends01.go,提出代码复用的问题
- 走一下代码
- 对上面代码的小结
- Pupil和Graduate两个结构体的字段和方法几乎,但是我们却写了相同的代码,代码复用性不强
- 出现代码冗余,而且代码不利于维护,同时也不利于功能的扩展。
- 解决方法-通过继承方式来解决
11.4.2继承基本介绍和示意图
继承可以解决代码复用,让我们的编程更加靠近人类思维。
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如刚才的Student),在该结构体中定义这些相同的属性和方法。
其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个Student匿名结构体即可。[画出示意图]
也就是说:在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。
11.4.3嵌套匿名结构体的基本语法
typeGoodsstruct{
Namestring
Priceint
}
typeBookstruct{
Goods//这里就是嵌套匿名结构体Goods
Writerstring
}
11.4.4快速入门案例
- 案例
我们对extends01.go改进,使用嵌套匿名结构体的方式来实现继承特性,请大家注意体会这样编程
的好处
- 代码实现
11.4.5继承给编程带来的便利
- 代码的复用性提高了
- 代码的扩展性和维护性提高了
11.4.6继承的深入讨论
- 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,
都可以使用。【举例说明】
- 匿名结构体字段访问可以简化,如图
对上面的代码小结
(1)当我们直接通过b访问字段或方法时,其执行流程如下比如b.Name
(2)编译器会先看b对应的类型有没有Name,如果有,则直接调用B类型的Name字段
(3)如果没有就去看B中嵌入的匿名结构体A有没有声明Name字段,如果有就调用,如果没有
继续查找…如果都找不到就报错. - 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分【举例说明】
4)结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法) ,在访问时,就必须明确指定匿名结构体名字,否则编译报错。【举例说明】
- 如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字
6)嵌套匿名结构体后,也可以在创建结构体变量(实例) 时,直接指定各个匿名结构体字段的值
11.4.7课堂练习
结构体的匿名字段是基本数据类型,如何访问,下面代码输出什么
说明
- 如果一个结构体有int类型的匿名字段,就不能第二个。
- 如果需要有多个int的字段,则必须给int字段指定名字
11.4.8面向对象编程-多重继承
- 多重继承说明
如一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方
法,从而实现了多重继承。 - 案例演示
通过一个案例来说明多重继承使用
- 多重继承细节说明
- 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。【案例演示】
- 为了保证代码的简洁性,建议大家尽量不使用多重继承
11.5接口(interface)
11.5.1基本介绍
按顺序,我们应该讲解多态,但是在讲解多态前,我们需要讲解接口(interface),因为在Golang中多态特性主要是通过接口来体现的。
11.5.2为什么有接口
11.5.3接口快速入门
这样的设计需求在Golang编程中也是会大量存在的,我曾经说过,一个程序就是一个世界,在现实世界存在的情况,在程序中也会出现。我们用程序来模拟一下前面的应用场景。
- 代码实现
说明:上面的代码就是一个接口编程的快速入门案例。
11.5.4接口概念的再说明
interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。到某个自定义类型(比如结构体Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)。
11.5.5基本语法
- 小结说明:
- 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低偶合的思想。
- Golang中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang中没有implement这样的关键字
11.5.6接口使用的应用场景
11.5.7注意事项和细节
1)接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
2) 接口中所有的方法都没有方法体,即都是没有实现的方法。
3) 在Golang中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口。
4)一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量) 赋给接口类型
5) 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
6) 一个自定义类型可以实现多个接口
7) Golang接口中不能有任何变量
8)一个接口(比如A接口)可以继承多个别的接口(比如B,C接口) ,这时如果要实现A接口,也必须将B,C接口的方法也全部实现。
9)interface类型默认是一个指针(引用类型) ,如果没有对interface初始化就使用,那么会输出nil
10) 空接口interface{}没有任何方法,所以所有类型都实现了空接口,即我们可以把任何一个变量赋给空接口。
11.5.8课堂练习
11.5.9接口编程的最佳实践
- 实现对Hero结构体切片的排序:sort.Sort(dataInterface)
- 接口编程的课后练习
//1.声明Student结构体
typeStudentstruct{
Namestring
Ageint
Scorefloat64
}
//将Student的切片,安Score从大到小排序!!
11.5.10实现接口vs继承
- 大家听到现在,可能会对实现接口和继承比较迷茫了,这个问题,那么他们究竟有什么区别呢
代码说明:
- 对上面代码的小结
- 当A结构体继承了B结构体,那么A结构就自动的继承了B结构体的字段和方法,并且可以直接使用
- 当A结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我们可以认为:实现接口是对继承机制的补充.
- 实现接口可以看作是对继承的一种补充
- 接口和继承解决的解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。 - 接口比继承更加灵活PersonStudentBirdAbleLittleMonkey
接口比继承更加灵活,继承是满足is-a的关系,而接口只需满足like-a的关系。 - 接口在一定程度上实现代码解耦
11.6面向对象编程-多态
11.6.1基本介绍
变量(实例)具有多种形态。面向对象的第三大特征,在Go语言,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。
11.6.2快速入门
在前面的Usb接口案例,Usbusb,既可以接收手机变量,又可以接收相机变量,就体现了Usb接口多态特性。[点明]
11.6.3接口体现多态的两种形式
- 多态参数
在前面的Usb接口案例,Usbusb,即可以接收手机变量,又可以接收相机变量,就体现了Usb接口多态。 - 多态数组
演示一个案例:给Usb数组中,存放Phone结构体和Camera结构体变量
案例说明:
11.7类型断言
11.7.1由一个具体的需要,引出了类型断言.
11.7.2基本介绍
类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言,
具体的如下:
- 对上面代码的说明:
在进行类型断言时,如果类型不匹配,就会报panic,因此进行类型断言时,要确保原来的空接口指向的就是断言的类型. - 如何在进行断言时,带上检测机制,如果成功就ok,否则也不要报panic
11.7.3类型断言的最佳实践1
- 在前面的Usb接口案例做改进:
给Phone结构体增加一个特有的方法call(),当Usb接口接收的是Phone变量时,还需要调用call
方法,走代码:
11.7.4类型断言的最佳实践2
写一函数,循环判断传入参数的类型:
11.7.5类型断言的最佳实践3【学员自己完成】
在前面代码的基础上,增加判断Student类型和*Student类型
第 12 章项目1-家庭收支记账软件项目
12.1项目开发流程说明
12.2项目需求说明
- 模拟实现基于文本界面的《家庭记账软件》
- 该软件能够记录家庭的收入、支出,并能够打印收支明细表
12.3项目的界面
其它的界面,我们就直接参考项目效果图.txt
12.4项目代码实现
12.4.1实现基本功能(先使用面向过程,后面改成面向对象)
- 功能1:先完成可以显示主菜单,并且可以退出
思路分析:
更加给出的界面完成,主菜单的显示,当用户输入4时,就退出该程序
走代码:
- 功能2:完成可以显示明细和登记收入的功能
思路分析:
- 因为需要显示明细,我们定义一个变量detailsstring来记录
2)还需要定义变量来记录余额(balance)、每次收支的金额(money),每次收支的说明(note)
走代码:
- 功能3:完成了登记支出的功能
思路分析:
登记支出的功能和登录收入的功能类似,做些修改即可
走代码:
12.4.2项目代码实现改进
- 用户输入4退出时,给出提示"你确定要退出吗?y/n",必须输入正确的y/n,否则循环输入指
令,直到输入y或者n
- 当没有任何收支明细时,提示"当前没有收支明细…来一笔吧!"
- 在支出时,判断余额是否够,并给出相应的提示
- 将面向过程的代码修改成面向对象的方法,编写myFamilyAccount.go,并使用testMyFamilyAccount.go去完成测试
思路分析:
把记账软件的功能,封装到一个结构体中,然后调用该结构体的方法,来实现记账,显示明细。结
构体的名字FamilyAccount.
在通过在main方法中,创建一个结构体FamilyAccount实例,实现记账即可.
代码实现:
代码不需要重写,只需要重写组织一下.
familyaccount/main/main.go
familyaccount/utils/familyAccount.go
12.4.3对项目的扩展功能的练习
- 对上面的项目完成一个转账功能
- 在使用该软件前,有一个登录功能,只有输入正确的用户名和密码才能操作.
第 13 章项目2-客户信息关系系统
13.1项目需求分析
- 模拟实现基于文本界面的《客户信息管理软件》。
2)该软件能够实现对客户对象的插入、修改和删除(用切片实现) ,并能够打印客户明细表
13.2项目的界面设计主菜单界面
- 添加客户界面
- 修改客户界面
- 删除客户界面
- 客户列表界面
13.3客户关系管理系统的程序框架图
13.4项目功能实现-显示主菜单和完成退出软件功能
- 功能的说明
当用户运行程序时,可以看到主菜单,当输入5时,可以退出该软件. - 思路分析
编写customerView.go,另外可以把customer.go和customerService.go写上. - 代码实现
customerManage/model/customer.go
customerManage/service/customerService.go
customerManage/view/customerView.go
13.5项目功能实现-完成显示客户列表的功能
- 功能说明
- 思路分析
- 代码实现
customerManage/model/customer.go
customerManage/service/customerService.go[增加了两个方法]
customerManage/view/customerView.go
13.6项目功能实现-添加客户的功能
- 功能说明
- 思路分析
- 代码实现
customerManage/model/customer.go
customerManage/service/customerService.go
customerManage/service/customerView.go
13.7项目功能实现-完成删除客户的功能
- 功能说明
- 思路分析
- 代码实现
customerManage/model/customer.go[没有变化]
customerManage/service/customerService.go
customerManage/view/customerView.go
13.8项目功能实现-完善退出确认功能(课后作业)
- 功能说明:
要求用户在退出时提示"确认是否退出(Y/N):",用户必须输入y/n,否则循环提示。 - 思路分析:
需要编写customerView.go - 代码实现:
13.9客户关系管理系统-课后练习
第 14 章文件操作
14.1文件的基本介绍
- 文件的概念
文件,对我们并不陌生,文件是数据源(保存数据的地方)的一种,比如大家经常使用的word文档,txt文件,excel文件…都是文件。文件最主要的作用就是保存数据,它既可以保存一张图片,也可以保持视频,声音… - 输入流和输出流
- os.File封装所有文件相关操作,File是一个结构体
总结:后面我们操作文件,会经常使用到os.File结构体.
14.2打开文件和关闭文件
- 使用的函数和方法
- 案例演示
14.3读文件操作应用实例
1)读取文件的内容并显示在终端(带缓冲区的方式),使用os.Open,file.Close,bufio.NewReader() ,reader.ReadString函数和方法.
代码实现:
2)读取文件的内容并显示在终端(使用ioutil一次将整个文件读入到内存中),这种方式适用于文件不大的情况。相关方法和函数(ioutil.ReadFile)
代码演示:
14.4写文件操作应用实例
14.4.1基本介绍-os.OpenFile函数
14.4.2基本应用实例-方式一
- 创建一个新文件,写入内容5句"hello,Gardon"代码实现:
- 打开一个存在的文件中,将原来的内容覆盖成新的内容10句"你好,尚硅谷!"
- 打开一个存在的文件,在原来的内容追加内容'ABC!ENGLISH!'
代码实现: - 打开一个存在的文件,将原来的内容读出显示在终端,并且追加5句"hello,北京!"代码实现:
14.4.3基本应用实例-方式二
编程一个程序,将一个文件的内容,写入到另外一个文件。注:这两个文件已经存在了.说明:使用ioutil.ReadFile/ioutil.WriteFile完成写文件的任务.
代码实现:
14.4.4判断文件是否存在
14.5文件编程应用实例
14.5.1拷贝文件
说明:将一张图片/电影/mp3拷贝到另外一个文件e:/abc.jpgio包
funcCopy(dstWriter,srcReader)(writtenint64,errerror)
注意;Copy函数是io包提供的.
代码实现:
14.5.2统计英文、数字、空格和其他字符数量
说明:统计一个文件中含有的英文、数字、空格及其它字符数量
代码实现:
14.6命令行参数
14.6.1看一个需求
我们希望能够获取到命令行输入的各种参数,该如何处理?如图:=>命令行参数
14.6.2基本介绍
os.Args是一个string的切片,用来存储所有的命令行参数
14.6.3举例说明
请编写一段代码,可以获取命令行各个参数
代码实现:
14.6.4flag包用来解析命令行参数
说明:前面的方式是比较原生的方式,对解析参数不是特别的方便,特别是带有指定参数形式的命
令行。
比如:cmd>main.exe-fc:/aaa.txt-p200-uroot这样的形式命令行,go设计者给我们提供了flag
包,可以方便的解析命令行参数,而且参数顺序可以随意
请编写一段代码,可以获取命令行各个参数.
代码实现:
14.7json基本介绍概述
- 应用场景(示意图)
14.8json数据格式说明
14.9json数据在线解析
网站可以验证一个json格式的数据是否正确。尤其是在我们编写比较复杂的json格式数据时,很有用。
14.10json的序列化
- 介绍
json序列化是指,将有key-value结构的数据类型(比如结构体、map、切片)序列化成json字符串的操作。 - 应用案例
这里我们介绍一下结构体、map和切片的序列化,其它数据类型的序列化类似。 - 代码演示
- 注意事项
对于结构体的序列化,如果我们希望序列化后的key的名字,又我们自己重新制定,那么可以给struct指定一个tag标签.
序列化后:
{"monster_name":"牛魔王","monster_age":500,"Birthday":"2011-11-11","Sal":8000,"Skill":"牛魔拳"}
14.11json的反序列化
- 基本介绍
json反序列化是指,将json字符串反序列化成对应的数据类型(比如结构体、map、切片)的操作 - 应用案例
这里我们介绍一下将json字符串反序列化成结构体、map和切片
代码演示: - 对上面代码的小结说明
- 在反序列化一个json字符串时,要确保反序列化后的数据类型和原来序列化前的数据类型一致。
- 如果json字符串是通过程序获取到的,则不需要再对“转义处理。
第15
15.1先看一个需求
在我们工作中,我们会遇到这样的情况,
如:
章单元测试
就是去确认一个函数,或者一个模块的结果是否正确,
15.2传统的方法
15.2.1传统的方式来进行测试
在main函数中,调用addUpper函数,看看实际输出的结果是否和预期的结果一致,如果一致,则说明函数正确,否则函数有错误,然后修改错误
代码实现:
15.2.2传统方法的缺点分析
- 不方便,我们需要在main函数中去调用,这样就需要去修改main函数,如果现在项目正在运
行,就可能去停止项目。 - 不利于管理,因为当我们测试多个函数或者多个模块时,都需要写在main函数,不利于我们管理和清晰我们思路
- 引出单元测试。->testing测试框架可以很好解决问题。
15.3单元测试-基本介绍
Go语言中自带有一个轻量级的测试框架testing和自带的gotest命令来实现单元测试和性能测试,
testing框架和其他语言中的测试框架类似,可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例。通过单元测试,可以解决如下问题:
- 确保每个函数是可运行,并且运行结果是正确的
- 确保写出来的代码性能是好的,
3)单元测试能及时的发现程序设计或实现的逻辑错误,使问题及早暴露,便于问题的定位解决,而性能测试的重点在于发现程序设计上的一些问题,让程序能够在高并发的情况下还能保持稳定15.4单元测试-快速入门
使用Go的单元测试,对addUpper和sub函数进行测试。
特别说明:测试时,可能需要暂时退出360。(因为360可能会认为生成的测试用例程序是木马)演示如何进行单元测试:
单元测试的运行原理示意图:
15.4.1单元测试快速入门总结
- 测试用例文件名必须以_test.go结尾。比如cal_test.go,cal不是固定的。
- 测试用例函数必须以Test开头,一般来说就是Test+被测试的函数名,比如TestAddUpper
3)TestAddUpper(ttesing.T) 的形参类型必须是testing.T【看一下手册】 - 一个测试用例文件中,可以有多个测试用例函数,比如TestAddUpper、TestSub
- 运行测试用例指令
(1)cmd>gotest[如果运行正确,无日志,错误时,会输出日志]
(2)cmd>gotest-v[运行正确或是错误,都输出日志] - 当出现错误时,可以使用t.Fatalf来格式化输出错误信息,并退出程序
- t.Logf方法可以输出相应的日志
- 测试用例函数,并没有放在main函数中,也执行了,这就是测试用例的方便之处[原理图].
- PASS表示测试用例运行成功,FAIL表示测试用例运行失败
- 测试单个文件,一定要带上被测试的原文件
gotest-vcal_test.gocal.go - 测试单个方法
gotest-v-test.runTestAddUpper
15.5单元测试-综合案例
代码实现:
monster/monster.go
monster/monster_test.go
第 16 章goroutine和channel
16.1goroutine-看一个需求
- 需求:要求统计1-9000000000的数字中,哪些是素数?
- 分析思路:
- 传统的方法,就是使用一个循环,循环的判断各个数是不是素数。[很慢]
- 使用并发或者并行的方式,将统计素数的任务分配给多个goroutine去完成,这时就会使用到goroutine.【速度提高4倍】
16.2goroutine-基本介绍
16.2.1进程和线程介绍
16.2.2程序、进程和线程的关系示意图
16.2.3并发和并行
- 并发和并行
- 多线程程序在单核上运行,就是并发
- 多线程程序在多核上运行,就是并行
- 示意图:
- 小结
16.2.4Go协程和Go主线程
- Go主线程(有程序员直接称为线程/也可以理解成进程):一个Go线程上,可以起多个协程,你可以
这样理解,协程是轻量级的线程[编译器做优化]。 - Go协程的特点
- 有独立的栈空间
- 共享程序堆空间
- 调度由用户控制
- 协程是轻量级的线程
- 一个示意图
16.3goroutine-快速入门
16.3.1案例说明
- 请编写一个程序,完成如下功能:
1)在主线程(可以理解成进程) 中,开启一个goroutine,该协程每隔1秒输出"hello,world"
- 在主线程中也每隔一秒输出"hello,golang",输出10次后,退出程序
- 要求主线程和goroutine同时执行.
- 画出主线程和协程执行流程图
- 代码实现
输出的效果说明,main这个主线程和test协程同时执行.
- 主线程和协程执行流程图
16.3.2快速入门小结
- 主线程是一个物理线程,直接作用在cpu上的。是重量级的,非常耗费cpu资源。
- 协程从主线程开启的,是轻量级的线程,是逻辑态。对资源消耗相对小。
- Golang的协程机制是重要的特点,可以轻松的开启上万个协程。其它编程语言的并发机制是一
般基于线程的,开启过多的线程,资源耗费大,这里就突显Golang在并发上的优势了16.4goroutine的调度模型
16.4.1MPG模式基本介绍
16.4.2MPG模式运行的状态1
16.4.3MPG模式运行的状态2
16.5设置Golang运行的cpu数
- 介绍:为了充分了利用多cpu的优势,在Golang程序中,设置运行的cpu数目
16.6channel(管道)-看个需求
需求:现在要计算1-200的各个数的阶乘,并且把各个数的阶乘放入到map中。最后显示出来。要求使用goroutine完成
- 分析思路:
- 使用goroutine来完成,效率高,但是会出现并发/并行安全问题.
- 这里就提出了不同goroutine如何通信的问题
- 代码实现
1)使用goroutine来完成(看看使用gorotine并发完成会出现什么问题?然后我们会去解决)
- 在运行某个程序时,如何知道是否存在资源竞争问题。方法很简单,在编译该程序时,增加一个参数-race即可[示意图]
- 代码实现:
- 示意图:
16.6.1不同goroutine之间如何通讯
- 全局变量的互斥锁
- 使用管道channel来解决
16.6.2使用全局变量加锁同步改进程序
- 因为没有对全局变量m加锁,因此会出现资源争夺问题,代码会出现错误,提示concurrentmap
writes - 解决方案:加入互斥锁
- 我们的数的阶乘很大,结果会越界,可以将求阶乘改成sum+=uint64(i)
- 代码改进
16.6.3为什么需要channel
- 前面使用全局变量加锁同步来解决goroutine的通讯,但不完美
- 主线程在等待所有goroutine全部完成的时间很难确定,我们这里设置10秒,仅仅是估算。
- 如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine处于工作
状态,这时也会随主线程的退出而销毁 - 通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作。
- 上面种种分析都在呼唤一个新的通讯机制-channel
16.6.4channel的基本介绍
- channle本质就是一个数据结构-队列【示意图】
- 数据是先进先出【FIFO:firstinfirstout】
- 线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
- channel有类型的,一个string的channel只能存放string类型数据。
- 示意图:
16.6.5定义/声明channel
- var变量名chan数据类型
- 举例:
varintChanchanint(intChan用于存放int数据)
varmapChanchanmap[int]string(mapChan用于存放map[int]string类型)
varperChanchanPerson
varperChan2chan*Person
… - 说明
channel是引用类型
channel必须初始化才能写入数据,即make后才能使用
管道是有类型的,intChan只能写入整数int
16.6.6管道的初始化,写入数据到管道,从管道读取数据及基本的注意事项
16.6.7channel使用的注意事项
- channel中只能存放指定的数据类型
- channle的数据放满后,就不能再放入了
- 如果从channel取出数据后,可以继续放入
- 在没有使用协程的情况下,如果channel数据取完了,再取,就会报deadlock
16.6.8读写channel案例演示
16.7管道的课后练习题
16.8channel的遍历和关闭
16.8.1channel的关闭
使用内置函数close可以关闭channel,当channel关闭后,就不能再向channel写数据了,但是仍然可以从该channel读取数据
案例演示:
16.8.2channel的遍历
channel支持for–range的方式进行遍历,请注意两个细节
- 在遍历时,如果channel没有关闭,则回出现deadlock的错误
- 在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历。
16.8.3channel遍历和关闭的案例演示
看代码演示:
16.8.4应用实例1
- 思路分析:
- 代码的实现:
16.8.5应用实例2-阻塞
第424页
16.8.6应用实例3
- 需求:
要求统计1-200000的数字中,哪些是素数?这个问题在本章开篇就提出了,现在我们有goroutine和channel的知识后,就可以完成了[测试数据:80000] - 分析思路:
传统的方法,就是使用一个循环,循环的判断各个数是不是素数【ok】。
使用并发/并行的方式,将统计素数的任务分配给多个(4个)goroutine去完成,完成任务时间短。 - 画出分析思路
- 代码实现
结论:使用go协程后,执行的速度,比普通方法提高至少4倍
16.9channel使用细节和注意事项
- channel可以声明为只读,或者只写性质【案例演示】
- channel只读和只写的最佳实践案例
- 使用select可以解决从管道取数据的阻塞问题
- goroutine中使用recover,解决协程中出现panic,导致程序崩溃问题
代码实现:
第 17 章反射
17.1先看一个问题,反射的使用场景
17.2使用反射机制,编写函数的适配器,桥连接
17.3反射的基本介绍
17.3.1基本介绍
1)反射可以在运行时动态获取变量的各种信息,比如变量的类型(type),类别(kind)
2)如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)
3) 通过反射,可以修改变量的值,可以调用关联的方法。
4)使用反射,需要import(“reflect”)
5) 示意图
17.3.2反射的应用场景
17.3.3反射重要的函数和概念
3) 变量、interface{}和reflect.Value是可以相互转换的,这点在实际开发中,会经常使用到。画出示意图
17.4反射的快速入门
17.4.1快速入门说明
- 请编写一个案例,演示对(基本数据类型、interface{}、reflect.Value)进行反射的基本操作
代码演示,见下面的表格: - 请编写一个案例,演示对(结构体类型、interface{}、reflect.Value)进行反射的基本操作
代码演示:
17.5反射的注意事项和细节
- reflect.Value.Kind,获取变量的类别,返回的是一个常量
- Type和Kind的区别
Type是类型,Kind是类别,Type和Kind可能是相同的,也可能是不同的.
比如:varnumint=10num的Type是int,Kind也是int
比如:varstuStudentstu的Type是pkg1.Student,Kind是struct
- 通过反射的来修改变量,注意当使用SetXxx方法来设置需要通过对应的指针类型来完成,这样
才能改变传入的变量的值,同时需要使用到reflect.Value.Elem()方
6)reflect.Value.Elem() 应该如何理解?
17.6反射课堂练习
1)给你一个变量varvfloat64=1.2,请使用反射来得到它的reflect.Value,然后获取对应的Type,Kind和值,并将reflect.Value转换成interface{},再将interface{}转换成float64.[不说:]
- 看段代码,判断是否正确,为什么
packagemain
import(
"fmt"
"reflect"
)
funcmain(){
varstrstring="tom"//ok
fs:=reflect.ValueOf(str)//okfs->string
fs.SetString("jack")//error
fmt.Printf("%v\n",str)
}
修改如下:
17.7反射最佳实践
- 使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值
2)使用反射的方式来获取结构体的tag标签,遍历字段的值,修改字段值,调用结构体方法(要求:通过传递地址的方式完成,在前面案例上修改即可) - 定义了两个函数test1和test2,定义一个适配器函数用作统一处理接口【了解】
- 使用反射操作任意结构体类型:【了解】
- 使用反射创建并操作结构体
17.8课后作业
18.1看两个实际应用
- QQ,迅雷,百度网盘客户端.
第 18 章tcp编程
新浪网站,京东商城,淘宝…
18.2网络编程基本介绍
Golang的主要设计目标之一就是面向大规模后端服务程序,网络通信这块是服务端程序必不可少也是至关重要的一部分。
- 网络编程有两种:
- TCPsocket编程,是网络编程的主流。之所以叫Tcpsocket编程,是因为底层是基于Tcp/ip协议的.比如:QQ聊天[示意图]
- b/s结构的http编程,我们使用浏览器去访问服务器时,使用的就是http协议,而http底层依旧是用tcpsocket实现的。[示意图]比如:京东商城【这属于goweb开发范畴】
18.2.1网线,网卡,无线网卡
计算机间要相互通讯,必须要求网线,网卡,或者是无线网卡.
18.2.2协议(tcp/ip)
TCP/IP(TransmissionControlProtocol/InternetProtocol)的简写,中文译名为传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议是Internet最基本的协议、Internet国际互联网络的基础,简单地说,就是由网络层的IP协议和传输层的TCP协议组成的。
18.2.3OSI与Tcp/ip参考模型(推荐tcp/ip协议3卷)
18.2.4ip地址
概述:每个internet上的主机和路由器都有一个ip地址,它包括网络号和主机号,ip地址有ipv4(32位)或者ipv6(128位).可以通过ipconfig来查看
18.2.5端口(port)-介绍
我们这里所指的端口不是指物理意义上的端口,而是特指TCP/IP协议中的端口,是逻辑意义上的端口。
如果把IP地址比作一间房子,端口就是出入这间房子的门。真正的房子只有几个门,但是一个IP地址的端口可以有65536(即:256×256)个之多!端口是通过端口号来标记的,端口号只有整数,范围是从0到65535(256×256-1)
18.2.6端口(port)-分类
- 0号是保留端口.
- 1-1024是固定端口(程序员不要使用)
又叫有名端口,即被某些程序固定使用,一般程序员不使用.
22:SSH远程登录协议23:telnet使用21:ftp使用
25:smtp服务使用80:iis使用7:echo服务 - 1025-65535是动态端口
这些端口,程序员可以使用.
18.2.7端口(port)-使用注意
1)在计算机(尤其是做服务器) 要尽可能的少开端口
2) 一个端口只能被一个程序监听
3) 如果使用netstat–an可以查看本机有哪些端口在监听
4) 可以使用netstat–anb来查看监听端口的pid,在结合任务管理器关闭不安全的端口
18.3tcpsocket编程的客户端和服务器端
为了授课方法,我们将tcpsocket编程,简称socket编程.下图为Golangsocket编程中客户端和服
务器的网络分布
18.4tcpsocket编程的快速入门
18.4.1服务端的处理流程
- 监听端口8888
- 接收客户端的tcp链接,建立客户端和服务器端的链接.
3)创建goroutine,处理该链接的请求(通常客户端会通过链接发送请求包)
18.4.2客户端的处理流程
- 建立与服务端的链接
- 发送请求数据[终端],接收服务器端返回的结果数据
- 关闭链接
18.4.3简单的程序示意图
18.4.4代码的实现
- 程序框架图示意图
- 服务器端功能:
编写一个服务器端程序,在8888端口监听
可以和多个客户端创建链接
链接成功后,客户端可以发送数据,服务器端接受数据,并显示在终端上.
先使用telnet来测试,然后编写客户端程序来测试 - 服务端的代码:
- 客户端功能:
1.编写一个客户端端程序,能链接到服务器端的8888端口
2.客户端可以发送单行数据,然后就退出
3.能通过终端输入数据(输入一行发送一行) ,并发送给服务器端[]
4.在终端输入exit,表示退出程序.
5.代码:
对client.go做了改进:
18.5经典项目-海量用户即时通讯系统
18.5.1项目开发流程
需求分析–>设计阶段—>编码实现–>测试阶段–>实施
18.5.2需求分析
- 用户注册
- 用户登录
- 显示在线用户列表
4)群聊(广播) - 点对点聊天
- 离线留言
18.5.3界面设计
18.5.4项目开发前技术准备
项目要保存用户信息和消息数据,因此我们需要学习数据库(Redis或者Mysql),这里我们选择Redis,所以先给同学们讲解如何在Golang中使用Redis.
18.5.5实现功能-显示客户端登录菜单
功能:能够正确的显示客户端的菜单。
界面:
思路分析:这个非常简单,直接写.
代码实现:
client/main.go
client/login.go
18.5.6实现功能-完成用户登录
-
- 要求:先完成指定用户的验证,用户id=100,密码pwd=123456可以登录,其它用户不能登录这里需要先说明一个Message的组成(示意图),并发送一个Message的流程
- 要求:先完成指定用户的验证,用户id=100,密码pwd=123456可以登录,其它用户不能登录这里需要先说明一个Message的组成(示意图),并发送一个Message的流程
- 1.完成客户端可以发送消息长度,服务器端可以正常收到该长度值
分析思路
(1)先确定消息Message的格式和结构
(2)然后根据上图的分析完成代码
(3)示意图
代码实现:
server/main.go
common/message/message.go
client/main.go
和前面的代码一样,没有修改
client/login.go
- 2.完成客户端可以发送消息本身,服务器端可以正常接收到消息,并根据客户端发送的消息
(LoginMes),判断用户的合法性,并返回相应的LoginResMes
思路分析:
(1)让客户端发送消息本身
(2)服务器端接受到消息,然后反序列化成对应的消息结构体.
(3)服务器端根据反序列化成对应的消息,判断是否登录用户是合法,返回LoginResMes
(4)客户端解析返回的LoginResMes,显示对应界面
(5)这里我们需要做函数的封装代码实现:
client/login.go做了修改
server/main.go修改
将读取包的任务封装到了一个函数中.readPkg()
- 能够完成登录,并提示相应信息
server/main.go修改
client/utils.go新增
client/login.go增加代码
- 程序结构的改进,前面的程序虽然完成了功能,但是没有结构,系统的可读性、扩展性和维护性
都不好,因此需要对程序的结构进行改进。
- 先改进服务端,先画出程序的框架图[思路],再写代码.
- 步骤
[1].先把分析出来的文件,创建好,然后放到相应的文件夹[包]
[2]现在根据各个文件,完成的任务不同,将main.go的代码剥离到对应的文件中即可。[3]先修改了utils/utils.go
[4]修改了process2/userProcess.go
[5]修改了main/processor.go
[6]修改main/main.go
- 修改客户端,先画出程序的框架图[思路],再写代码
[1]步骤1-画出示意图
[2]先把各个文件放到对应的文件夹[包]
[3]将server/utils.go拷贝到client/utils/utils.go
[4]创建了server/process/userProcess.go
说明:该文件就是在原来的login.go做了一个改进,即封装到UserProcess结构体[5]创建了server/process/server.go
[6]server/main/main.go修改
- 在Redis手动添加测试用户,并画图+说明注意.(后面通过程序注册用户)
手动直接在redis增加一个用户信息:
- 如输入的用户名密码在Redis中存在则登录,否则退出系统,并给出相应的
提示信息:
1.用户不存在,你也可以重新注册,再登录
2.你密码不正确。。
代码实现:
[1]编写model/user.go
[2]编写model/error.go
[3]编写model/userDao.go
[4]main/redis.go
[5]main/main.go
[6]在process/userProcess.go使用到redis验证的功能
18.5.7实现功能-完成注册用户
- 完成注册功能,将用户信息录入到Redis中
- 思路分析,并完成代码
- 思路分析的示意图
- 实现功能-完成注册用户
[1]common/message/user.go
[2]common/message/message.go
[3]client/process/userProcess.go
[4]在client/main/main.go增加了代码
[5]在server/model/userDao.go增加方法
[6]在server/process/userProcess.go增加了方法,处理注册
[7]server/main/processor.go调用了
18.5.8实现功能-完成登录时能返回当前在线用户
- 用户登录后,可以得到当前在线用户列表思路分析、示意图、代码实现
思路分析:
代码实现:
[1]编写了server/process/userMgr.go
[2]server/process/userProcess.go
[3]common/message/message.go
[4]client/process/userProcess.go
- 当一个新的用户上线后,其它已经登录的用户也能获取最新在线用户列表,思路分析、示意图、代
码实现
[1]server/process/userProcess.go
[2]sever/proces/userProcess.go[的Login]
[3]common/mesage/message.go
[4]client/process/userMgr.go
[5]client/process/server.go
[6]client/process/server.go
18.5.9实现功能-完成登录用可以群聊
- 步骤1:步骤1:当一个用户上线后,可以将群聊消息发给服务器,服务器可以接收到
思路分析:
代码实现:
[1]common/message/messag.go
[2]client/model/curUser.go
[3]client/process/smsProcess.go增加了发送群聊消息
[4]测试
- 步骤2:服务器可以将接收到的消息,群发给所有在线用户(发送者除外)
思路分析:
代码实现:
[1]server/process/smsProcess.go
[2]server/main/processor.go
[3]client/process/smsMgr.go
[4]client/process/server.go
18.5.10聊天的项目的扩展功能要求
1.实现私聊.[点对点聊天]
2.如果一个登录用户离线,就把这个人从在线列表去掉【】
3.实现离线留言,在群聊时,如果某个用户没有在线,当登录后,可以接受离线的消息
4.发送一个文件.
第 19 章Redis的使用
19.1Redis基本介绍
19.1.1Redis的安装
19.1.2Redis操作的基本原理图
19.2Redis的安装和基本使用
19.2.1Redis的启动:
19.3Redis的操作指令一览
19.3.1Redis的基本使用:
说明:Redis安装好后,默认有16个数据库,初始默认使用0号库,编号是0…15
1.添加key-val[set]
2.查看当前redis的所有key[keys*]
3.获取key对应的值.[getkey]
4.切换redis数据库[selectindex]
5.如何查看当前数据库的key-val数量[dbsize]
6.清空当前数据库的key-val和清空所有数据库的key-val[flushdbflushall]
19.4Redis的Crud操作
19.4.1Redis的五大数据类型:
Redis的五大数据类型是:String(字符串)、Hash(哈希)、List(列表)、Set(集合)和zset(sortedset:有序集合)
19.4.2String(字符串)-介绍
string是redis最基本的类型,一个key对应一个value。
string类型是二进制安全的。除普通的字符串外,也可以存放图片等数据。
redis中字符串value最大是512M
- 举例,存放一个地址信息:
address北京天安门
说明:
key:address
value:北京天安门
- String(字符串)-CRUD
举例说明Redis的String字符串的CRUD操作.
set[如果存在就相当于修改,不存在就是添加]/get/del
19.4.3String(字符串)-使用细节和注意事项
- setex(setwithexpire)键秒值
- mset[同时设置一个或多个key-value对]
- mget[同时获取多个key-val]
19.4.4Hash(哈希,类似golang里的Map)-介绍
- 基本的介绍
Redishash是一个键值对集合。varuser1map[string]string
Redishash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 - 举例,存放一个User信息:(user1)
user1name"smith"age30job"golangcoder"
说明:
key:user1
name张三和age30就是两对field-value
19.4.5Hash(哈希,类似golang里的Map)-CRUD
举例说明Redis的Hash的CRUD的基本操作.
hset/hget/hgetall/hdel
演示添加user信息的案例(name,age)
19.4.6Hash-使用细节和注意事项
- 在给user设置name和age时,前面我们是一步一步设置,使用hmset和hmget可以一次性来设
置多个filed的值和返回多个field的值。 - hlen统计一个hash有几个元素.
- hexistskeyfield
查看哈希表key中,给定域field是否存在
19.4.7课堂练习
19.4.8List(列表)-介绍
列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列
表的头部(左边)或者尾部(右边)。
List本质是个链表,List的元素是有序的,元素的值可以重复.
举例,存放多个地址信息:
city北京天津上海
说明:
key:city
北京天津上海就是三个元素
- 入门的案例
19.4.9List(列表)-CRUD
举例说明Redis的List的CRUD操作。
lpush/rpush/lrange/lpop/rpop/del/
- 说明:
List画图帮助学员理解(可以把l想象成一根管道.)
herosList的演示
19.4.10List-使用细节和注意事项
19.4.11Set(集合)-介绍
- Redis的Set是string类型的无序集合。
- 底层是HashTable数据结构,Set也是存放很多字符串元素,字符串元素是无序
的,而且元素的值不能重复 - 举例,存放多个邮件列表信息:
emailsgg@sohu.comtom@sohu.com
说明:
key:email
tn@sohu.comtom@sohu.com就是二个元素
redis>saddemailxxxxx
19.4.12Set(集合)-CRUD
- 举例说明Redis的Set的CRUD操作.
sadd
smembers[取出所有值]
sismember[判断值是否是成员]
srem[删除指定值] - 演示添加多个电子邮件信息的案例
19.4.13Set课堂练习
- 举例,存放一个商品信息:
包括商品名、价格、生产日期。
完成对应的crud操作
19.5Golang操作Redis
19.5.1安装第三方开源Redis库
- 使用第三方开源的redis库:github.com/garyburd/redigo/redis
- 在使用Redis前,先安装第三方Redis库,在GOPATH路径下执行安装指令:D:\goproject>gogetgithub.com/garyburd/redigo/redis
- 安装成功后,可以看到如下包
- 特别说明:在安装Redis库前,确保已经安装并配置了Git,因为是从github下载安装Redis库的,
需要使用到Git。如果没有安装配置过Git,请参考:如何安装配置Git
19.5.2Set/Get接口
说明:通过Golang添加和获取key-value【比如name-tom~】
19.5.3操作Hash
说明:通过Golang对Redis操作Hash数据类型
对hash数据结构,field-val是一个一个放入和读取
代码:
对hash数据结构,field-val是批量放入和读取
19.5.4批量Set/Get数据
说明:通过Golang对Redis操作,一次操作可以Set/Get多个key-val数据
核心代码:
,err=c.Do("MSet","name","尚硅谷","address","北京昌平~")
r,err:=redis.Strings(c.Do("MGet","name","address"))
for,v:=ranger{
fmt.Println(v)
}
19.5.5给数据设置有效时间
说明:通过Golang对Redis操作,给key-value设置有效时间
核心代码:
//给name数据设置有效时间为10s
_,err=c.Do("expire","name",10)
19.5.6操作List
说明:通过Golang对Redis操作List数据类型
核心代码:
_,err=c.Do("lpush","heroList","no1:宋江",30,"no2:卢俊义",28)
r,err:=redis.String(c.Do("rpop","heroList"))
19.5.7Redis链接池
- 说明:通过Golang对Redis操作,还可以通过Redis链接池,流程如下:
- 事先初始化一定数量的链接,放入到链接池
- 当Go需要操作Redis时,直接从Redis链接池取出链接即可。
- 这样可以节省临时获取Redis链接的时间,从而提高效率.
- 示意图
- 链接池使用的案例
第 20 章数据结构
20.1数据结构(算法)的介绍数据结构的介绍
-
- 数据结构是一门研究算法的学科,只从有了编程语言也就有了数据结构.学好数据结构可以编写出更加漂亮,更加有效率的代码。
- 要学习好数据结构就要多多考虑如何将生活中遇到的问题,用程序去实现解决.
- 程序=数据结构+算法
20.2数据结构和算法的关系
- 算法是程序的灵魂,为什么有些网站能够在高并发,和海量吞吐情况下依然坚如磐石,大家可能会
说:网站使用了服务器群集技术、数据库读写分离和缓存技术(比如Redis等),那如果我再深入的问一句,这些优化技术又是怎样被那些天才的技术高手设计出来的呢? - 大家请思考一个问题,是什么让不同的人写出的代码从功能看是一样的,但从效率上却有天壤之别,
拿在公司工作的实际经历来说,我是做服务器的,环境是UNIX,功能是要支持上千万人同时在线,并保证数据传输的稳定,在服务器上线前,做内测,一切OK,可上线后,服务器就支撑不住了,公司的CTO对我的代码进行优化,再次上线,坚如磐石。那一瞬间,我认识到程序是有灵魂的,就是
算法。如果你不想永远都是代码工人,那就花时间来研究下算法吧! - 本章着重讲解算法的基石-数据结构。
20.3看几个实际编程中遇到的问题
20.4稀疏sparsearray数组
20.4.1先看一个实际的需求
- 编写的五子棋程序中,有存盘退出和续上盘的功能
- 分析按照原始的方式来的二维数组的问题
因为该二维数组的很多值是默认值0,因此记录了很多没有意义的数据
20.4.2基本介绍
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方法是:
- 记录数组一共有几行几列,有多少个不同的值
- 思想:把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
20.4.3稀疏数组举例说明
20.4.4应用实例
1)使用稀疏数组,来保留类似前面的二维数组(棋盘、地图等等)
2) 把稀疏数组存盘,并且可以从新恢复原来的二维数组数
3) 整体思路分析
4) 代码实现
- 对老师的稀疏数组的改进
- 将构建的稀疏数组,存盘chessmap.data
- 在恢复原始二维数组,要求从文件chessmap.data读取。
20.5队列(queue)
20.5.1队列的应用场景
20.5.2队列介绍
- 队列是一个有序列表,可以用数组或是链表来实现。
-
- 遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出示意图:(使用数组模拟队列示意图)
- 遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出示意图:(使用数组模拟队列示意图)
20.5.3数组模拟队列
- 先完成一个非环形的队列(数组来实现)
思路分析:
代码实现:
对上面代码的小结和说明:
- 上面代码实现了基本队列结构,但是没有有效的利用数组空间
- 请思考,如何使用数组实现一个环形的队列
20.5.4数组模拟环形队列
分析思路:
1)什么时候表示队列满(tail+1) %maxSize=hed
2) tail==head表示空
3) 初始化时,tail=0head=0
4)怎么统计该队列有多少个元素(tail+maxSize-head) %maxSize代码实现:
20.6链表
20.6.1链表介绍
- 链表是有序的列表,但是它在内存中是存储如下
20.6.2单链表的介绍
- 单链表的示意图:
- 说明:一般来说,为了比较好的对单链表进行增删改查的操作,我们都会给他设置一个头结点,头
结点的作用主要是用来标识链表头,本身这个结点不存放数据。
20.6.3单链表的应用实例
- 案例的说明:
使用带head头的单向链表实现–水浒英雄排行榜管理
完成对英雄人物的增删改查操作,注:删除和修改,查找可以考虑学员独立完成 - 第一种方法在添加英雄时,直接添加到链表的尾部
- 代码实现:
第580页 - 删除结点:
20.6.4双向链表的应用实例
- 示意图
- 代码实现
20.6.5单向环形链表的应用场景
20.6.6环形单向链表介绍
20.6.7环形的单向链表的案例
完成对单向环形链表的添加结点,删除结点和显示.
作业:
20.6.8环形单向链表的应用实例
- Josephu问题
Josephu问题为:设编号为1,2,…n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。 - 提示
用一个不带头结点的循环链表来处理Josephu问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。 - 示意图说明
- 走代码:
20.7排序
20.7.1排序的介绍
排序是将一组数据,依指定的顺序进行排列的过程,常见的排序:
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
20.7.2冒泡排序
20.7.3选择排序基本介绍
选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,经过和其他元素重整,再依原则交换位置后达到排序的目的。
20.7.4选择排序思想:
选择排序(selectsorting)也是一种简单的排序方法。它的基本思想是:第一次从R[0]R[n-1]中选取最小值,与R[0]交换,第二次从R[1]R[n-1]中选取最小值,与R[1]交换,第三次从R[2]R[n-1]中选取最小值,与R[2]交换,…,第i次从R[i-1]R[n-1]中选取最小值,与R[i-1]交换,…,第n-1次从R[n-2]~R[n-1]中选取最小值,与R[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。
20.7.5选择排序的示意图
20.7.6代码实现
20.7.7插入排序法介绍:
插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。
20.7.8插入排序法思想:
插入排序(InsertionSorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
20.7.9插入排序的示意图
20.7.10插入排序法应用实例
20.7.11插入排序的代码实现
20.7.12快速排序法介绍
快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这
两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列20.7.13快速排序法示意图
20.7.14快速排序法应用实例
20.7.15快速排序法的代码实现
第610页
20.7.16三种排序方法的速度的分析
20.8栈
20.8.1看一个实际需求
20.8.2栈的介绍
有些程序员也把栈称为堆栈,即栈和堆栈是同一个概念
1)栈的英文为(stack)
2)栈是一个先入后出(FILO-FirstInLastOut) 的有序列表。
3)栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom) 。
4) 根据堆栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相
反,最后放入的元素最先删除,最先放入的元素最后删除
20.8.3栈的入栈和出栈的示意图
20.8.4栈的应用场景
- 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
- 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
- 表达式的转换与求值。
- 二叉树的遍历。
5)图形的深度优先(depth一first) 搜索法。
20.8.5栈的案例
- 代码实现
20.8.6栈实现综合计算器
- 分析了实现的思路
- 代码实现
20.9递归
20.9.1递归的一个应用场景[迷宫问题]
20.9.2递归的概念
简单的说:第归就是函数/方法自己调用自己,每次调用时传入不同的变量.第归有助于编程者解决复杂的问题,同时可以让代码变得简洁。
20.9.3递归快速入门
我列举两个小案例,来帮助大家理解递归,递归在讲函数时已经讲过(当时讲的相对比较简单),这里在给大家回顾一下递归调用机制
- 打印问题
- 阶乘问题
- 快速入门的示意图
20.9.4递归用于解决什么样的问题
1)各种数学问题如:8皇后问题,汉诺塔,阶乘问题,迷宫问题,球和篮子的问题(google编程大赛)
2) 将用栈解决的问题–>第归代码比较简洁
20.9.5递归需要遵守的重要原则
1)执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
2) 函数的局部变量是独立的,不会相互影响,如果希望各个函数栈使用同一个数据,使用引用传递
3)递归必须向退出递归的条件逼近【程序员自己必须分析】,否则就是无限递归,死龟了:)
4) 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当函
数执行完毕或者返回时,该函数本身也会被系统销毁
20.9.6举一个比较综合的案例,迷宫问题
- 走代码:
- 课后思考题:
思考:如何求出最短路径?
20.10哈希表(散列)
20.10.1实际的需求google公司的一个上机题:
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,住址…),当输入该员工的id时,要求查找到该员工的所有信息.
要求:不使用数据库,尽量节省内存,速度越快越好=>哈希表(散列)
20.10.2哈希表的基本介绍
散列表(Hashtable,也叫哈希表),是根据关键码值(Keyvalue)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
20.10.3使用hashtable来实现一个雇员的管理系统[增删改查]
- 应用实例google公司的一个上机题:
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,住址…),当输入该员工的id时,要求查找到该员工的所有信息. - 要求:
1)不使用数据库,尽量节省内存,速度越快越好=>哈希表(散列)
- 添加时,保证按照雇员的id从低到高插入
- 思路分析
- 使用链表来实现哈希表,该链表不带表头
[即:链表的第一个结点就存放雇员信息] - 思路分析并画出示意图
3)代码实现[增删改查(显示所有员工,按id查询) ]