Go基础语法
  j3Rgi9wHu4Az 2023年11月02日 39 0
Go

目录可以在每个标题旁点击跳转

Go语言历史

作为程序员,我们每天会用到大量的编程语言,打开界面会碰到很多logo,在正式学习Go语言之前,我们先来了解一下Go语言的Logo。也就是它,下面这个动物,gopher [ˈɡoʊfər] ,囊地鼠,是北美的一种地鼠。也有人说这是土拨鼠,大家自行理解吧。

img

故事

Rob Pike是Go的发明者之一,贝尔实验室UNIX小组成员之一,UTF-8的设计人。他最喜欢做似乎就是:

  • 发明操作系统
  • 发明编辑器
  • 发明语言

img

Go语言这萌萌的吉祥物是由Rob Pike的妻子 Renee French绘制的,golang吉祥物的设计者Renee French是一位知名插画师,她的画风是这样的:

img

就是她设计出了Golang吉祥物,可爱的 Gordon [ˈgɔrdən]

比心心!

img

谷歌工程师的20%时间

谷歌的“20%时间”工作方式,允许工程师拿出20%的时间来研究自己喜欢的项目。语音服务Google Now、谷歌新闻Google News、谷歌地图Google Map上的交通信息等,全都是20%时间的产物。

Go语言最开始也是20%时间的产物。

为什么需要一个新的语言

最近十年来,C/C++在计算领域没有很好得到发展,并没有新的系统编程语言出现。对开发程度和系统效率在很多情况下不能兼得。要么执行效率高,但低效的开发和编译,如C++;要么执行低效,但拥有有效的编译,如.NET、Java;所以需要一种拥有较高效的执行速度、编译速度和开发速度的编程语言,Go就横空出世了。

传统的语言比如c++,大家花费太多时间来学习如何使用这门语言,而不是如何更好的表达写作者的思想,同时编译花费的时间实在太长,对于编写-编译-运行这个链条来说周期太长。动态语言如Python,由于没有强类型的约束,很多问题需要在运行时发现,这种低级错误更应该交给编译器来发现。

人力成本越来越高

机器越来越便宜

机器的性能越来越厉害

在开发效率和运行速度上达到平衡

go出现之前,无论汇编语言、还是动态脚本语言,在执行效率和开发效率上都不能兼备。

这个世界上总有一帮人在想着做这种事情,别人的用的不舒服,就自己开发了一个!

Go语言的创始人

Go的三个作者分别是:Rob Pike(罗伯.派克),Ken Thompson(肯.汤普森)和Robert Griesemer(罗伯特.格利茨默) 。

Rob Pike:曾是贝尔实验室(Bell Labs)的Unix团队,和Plan 9操作系统计划的成员。他与Thompson共事多年,并共创出广泛使用的UTF-8 字元编码。

Ken Thompson:主要是B语言、C语言的作者、Unix之父。1983年图灵奖(Turing Award)和1998年美国国家技术奖(National Medal of Technology)得主。他与Dennis Ritchie是Unix的原创者。Thompson也发明了后来衍生出C语言的B程序语言。

Robert Griesemer:在开发Go之前是Google V8、Chubby和HotSpot JVM的主要贡献者。

img

此外还有Plan 9开发者Russ Cox、和曾改善目前广泛使用之开原码编译器GCC的Ian Taylor。

Go语言的发展

故事一:名字的来源

img

这是一封由 Rob Pike 在 2007 年 9 月 25 号,星期二,下午 3:12 回复给 Robert Griesemer、Ken Thompson 的有关编程语言讨论主题的邮件。

邮件正文大意为:

在开车回家的路上我得到了些灵感,给这门编程语言取名为“go”,它很简短,易书写。工具类可以命名为:goc、 gol、goa。交互式的调试工具也可以直接命名为“go”。语言文件后缀名为 .go 等等

这就是 Go 语言名字的来源。

自此之后 Robert、Rob 和 Ken 三个人开始在 Google 内部进行了研发,一直到了 2009 年,Go 正式开源了。

Go 项目团队将 2009 年 11 月 10 日,即该语言正式对外开源的日子作为其官方生日。

源代码最初托管在 http://code.google.com 上,之后几年才逐步的迁移到 GitHub 上。

故事二:新伙伴的加入

img

这是一封由 Ian Lance Taylor 在 2008 年 6月 7 日(星期六)的晚上 7:06 写给 Robert Griesemer、Rob Pike、 Ken Thompson 的关于 Go gcc 编译器前端的邮件。

邮件正文大意如下:

我的同事向我推荐了这个网站 http://…/go_lang.html 。这似乎是一门很有趣的编程语言。我为它写了一个 gcc 编译器前端。虽然这个工具仍缺少很多的功能,但它确实可以编译网站上展示的那个素数筛选程序了。

Ian Lance Taylor 的加入以及第二个编译器 (gcc go) 的实现 在带来震惊的同时,也伴随着喜悦。

这对 Go 项目来说不仅仅是鼓励,更是一种对可行性的证明。语言的第二次实现对制定语言规范和确定标准库的过程至关重要,同时也有助于保证其高可移植性,这也是 Go 语言承诺的一部分。

自此之后 Ian Lance Taylor 成为了设计和实现 Go 语言及其工具的核心人物。

故事三:http.HandlerFunc、I/O 库

Russ Cox 在2008年带着他的语言设计天赋和编程技巧加入了刚成立不久的 Go 团队。Russ 发现 Go 方法的通用性意味着函数也能拥有自己的方法,这直接促成了 http.HandlerFunc 的实现,这是一个让 Go 一下子变得无限可能的特性。

Russ 还提出了更多的泛化性的想法,比如 io.Reader 和 io.Writer 接口,奠定了所有 I/O 库的整体结构。

故事四:cryptographic

安全专家 Adam Langley 帮助 Go 走向 Google 外面的世界。

Adam 为 Go 团队做了许多不为外人知晓的工作,包括创建最初的 http://golang.org 网站以及 build dashboard。

不过他最大的贡献当属创建了 cryptographic 库。

起先,在我们中的部分人看来,这个库无论在规模还是复杂度上都不成气候。但是就是这个库在后期成为了很多重要的网络和安全软件的基础,并且成为了 Go 语言开发历史的关键组成部分。

许多网络基础设施公司,比如 Cloudflare,均重度依赖 Adam 在 Go 项目上的工作,互联网也因它变得更好。我记得当初 beego 设计的时候,session 模块设计的时候也得到了 Adam 的很多建议,因此,就 Go 而言,我们由衷地感谢 Adam。

时间线小结

  • 2007年9月,Rob Pike在Google分布式编译平台上进行C++编译,在漫长的等待过程中,他和Robert Griesemer探讨了程序设计语言的一些关键性问题,他们认为,简化编程语言相比于在臃肿的语言上不断增加新特性,会是更大的进步。随后他们在编译结束之前说服了身边的Ken Thompson,觉得有必要为此做一些事情。几天后,他们发起了一个叫Golang的项目,将它作为自由时间的实验项目。
  • 2008年5月 Google发现了GO语言的巨大潜力,得到了Google的全力支持,这些人开始全职投入GO语言的设计和开发。
  • 2009年11月 GO语言第一个版本发布。2012年3月 第一个正式版本Go1.0发布。
  • 2015年8月 go1.5发布,这个版本被认为是历史性的。完全移除C语言部分,使用GO编译GO,少量代码使用汇编实现。另外,他们请来了内存管理方面的权威专家Rick Hudson,对GC进行了重新设计,支持并发GC,解决了一直以来广为诟病的GC时延(STW)问题。并且在此后的版本中,又对GC做了更进一步的优化。到go1.8时,相同业务场景下的GC时延已经可以从go1.1的数秒,控制在1ms以内。GC问题的解决,可以说GO语言在服务端开发方面,几乎抹平了所有的弱点。

在GO语言的版本迭代过程中,语言特性基本上没有太大的变化,基本上维持在GO1.1的基准上,并且官方承诺,新版本对老版本下开发的代码完全兼容。事实上,GO开发团队在新增语言特性上显得非常谨慎,而在稳定性、编译速度、执行效率以及GC性能等方面进行了持续不断的优化。

故事五:Docker、Kubernetes。

一家叫做 Docker 的公司。就是使用 Go 进行项目开发,并促进了计算机领域的容器行业,进而出现了像 Kubernetes 这样的项目。现在,我们完全可以说 Go 是容器语言,这是另一个完全出乎意料的结果。

除了大名鼎鼎的Docker,完全用GO实现。业界最为火爆的容器编排管理系统kubernetes完全用GO实现。之后的Docker Swarm,完全用GO实现。

除此之外,还有各种有名的项目,如etcd/consul/flannel,七牛云存储等等 均使用GO实现。有人说,GO语言之所以出名,是赶上了云时代。但为什么不能换种说法?也是GO语言促使了云的发展。

除了云项目外,还有像今日头条、UBER这样的公司,他们也使用GO语言对自己的业务进行了彻底的重构。

展望

Go语言是谷歌2009年发布的第二款开源编程语言(系统开发语言),它是基于编译、垃圾收集和并发的编程语言。

Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美 C / C++代码的速度,而且更加安全、支持并行进程。

作为出现在21世纪的语言,其近C的执行性能和近解析型语言的开发效率,以及近乎于完美的编译速度,已经风靡全球。

特别是在云项目中,大部分都使用了Golang来开发。不得不说,Golang早已深入人心。而对于一个没有历史负担的新项目,Golang或许就是个不二的选择。

很多人将Go语言称为21世纪的C语言,因为Go不仅拥有C的简洁和性能。而且还很好的提供了21世纪互联网环境下服务端开发的各种实用特性。

被称为Go语言之父的罗勃·派克(Rob Pike)就曾说过,你是否同意Go语言,取决于你是认可少就是多,还是少就是少(Less is more or less is less)。Go语言的整个设计哲学就是:将简单、实用体现得淋漓尽致。

如今 Go 已经是云计算编程语言,GO语言背靠Google这棵大树,又不乏牛人坐镇,是名副其实的“牛二代”。想象一下:一个只有十几年发展经历的编程语言,已经成为了如此巨大而且正在不断发展的行业的主导者,这种成功是每个人都无法想象的。

使用Go语言的项目

下面列举的是原生使用Go语言进行开发的部分项目。

Docker

Docker 是一种操作系统层面的虚拟化技术,可以在操作系统和应用程序之间进行隔离,也可以称之为容器。Docker 可以在一台物理服务器上快速运行一个或多个实例。例如,启动一个 CentOS 操作系统,并在其内部命令行执行指令后结束,整个过程就像自己在操作系统一样高效。

项目链接:https://github.com/docker/docker

Go语言

Go语言自己的早期源码使用C语言和汇编语言写成。从 Go 1.5 版本后,完全使用Go语言自身进行编写。Go语言的源码对了解Go语言的底层调度有极大的参考意义,建议希望对Go语言有深入了解的读者读一读。

项目链接:https://github.com/golang/go

Kubernetes

Google 公司开发的构建于 Docker 之上的容器调度服务,用户可以通过 Kubernetes 集群进行云端容器集群管理。系统会自动选取合适的工作节点来执行具体的容器集群调度处理工作。其核心概念是 Container Pod(容器仓)。

项目链接:https://github.com/kubernetes/kubernetes

etcd

一款分布式、可靠的 KV 存储系统,可以快速进行云配置。由 CoreOS 开发并维护键值存储系统,它使用Go语言编写,并通过 Raft 一致性算法处理日志复制以保证强一致性。

项目链接:https://github.com/coreos/etcd

beego

beego 是一个类似 Python 的 Tornado 框架,采用了 RESTFul 的设计思路,使用Go语言编写的一个极轻量级、高可伸缩性和高性能的 Web 应用框架。

项目链接:https://github.com/astaxie/beego

martini

一款快速构建模块化的 Web 应用的Go语言框架。

项目链接:https://github.com/go-martini/martini

codis

国产的优秀分布式 Redis 解决方案。可以将 codis 理解成为 Web 服务领域的 Nginx,它实现了对 Redis 的反向代理和负载均衡。

项目链接:https://github.com/CodisLabs/codis

delve

Go语言强大的调试器,被很多集成环境和编辑器整合。

项目链接:https://github.com/derekparker/delve

哪些大公司在用

Go语言是谷歌在 2009 年发布的一款编程语言,自面世以来它以高效的开发效率和完美的运行速度迅速风靡全球,被誉为“21 世纪的C语言”。

现在越来越多的公司开始使用Go语言开发自己的服务,同时也诞生了很多使用Go语言开发的服务和应用,比如 Docker、k8s 等,下面我们来看一下,有哪些大公司在使用Go语言。

Google

作为创造了Go语言的 google 公司,当然会力挺Go语言了。Google 有很多基于 Go 开发的开源项目,比如 kubernets,docker,大家可以参考《哪些项目使用Go语言开发》一节了解更多的Go语言开源项目。

Facebook

Facebook 也在使用Go语言,为此他们还专门在 Github 上建立了一个开源组织 facebookgo。大家可以通过 https://github.com/facebookgo 访问查看 facebook 开源的项目,其中最具代表性的就是著名平滑重启工具 grace。

腾讯

腾讯在 15 年就已经做了 Docker 万台规模的实践。因为腾讯主要的开发语言是 C/C++ ,所以在使用Go语言方面会方便很多,也有很多优势,不过日积月累的 C/C++ 代码很难改造,也不敢动,所以主要在新业务上尝试使用 Go。

百度

百度主要在运维方面使用到了Go语言,比如百度运维的一个 BFE 项目,主要负责前端流量的接入,其次就是百度消息通讯系统的服务器端也使用到了Go语言。

七牛云

七牛云算是国内第一家选Go语言做服务端的公司。早在 2011 年,当Go语言的语法还没完全稳定下来的情况下,七牛云就已经选择将 Go 作为存储服务端的主体语言。

京东

京东云消息推送系统、云存储,以及京东商城的列表页等都是使用Go语言开发的。

小米

小米对Go语言的支持,在于运维监控系统的开源,它的官方网址是 http://open-falcon.org/ 此外,小米互娱、小米商城、小米视频、小米生态链等团队都在使用Go语言。

360

360 对Go语言的使用也不少,比如开源的日志搜索系统 Poseidon,大家可以通过.

https://github.com/Qihoo360/poseidon 查看,还有 360 的推送团队也在使用Go语言。

除了上面提到的,还有很多公司开始尝试使用Go语言,比如美团、滴滴、新浪等。

Go语言的强项在于它适合用来开发网络并发方面的服务,比如消息推送、监控、容器等,所以在高并发的项目上大多数公司会优先选择 Golang 作为开发语言。

Go语言代码清爽

Go语言语法类似于C语言,因此熟悉C语言及其派生语言([C++]、[C#]、Objective-C 等)的人都会迅速熟悉这门语言。

C语言的有些语法会让代码可读性降低甚至发生歧义。Go语言在C语言的基础上取其精华,弃其糟粕,将C语言中较为容易发生错误的写法进行调整,做出相应的编译提示。

去掉循环冗余括号

Go语言在众多大师的丰富实战经验的基础上诞生,去除了C语言语法中一些冗余、烦琐的部分。下面的代码是C语言的数值循环:

// C语言的for数值循环for(int a =0;a<10;a++){// 循环代码}

在Go语言中,这样的循环变为:

for a :=0;a<10;a++{// 循环代码}

for 两边的括号被去掉,int 声明被简化为:=,直接通过编译器右值推导获得 a 的变量类型并声明。

去掉表达式冗余括号

同样的简化也可以在判断语句中体现出来,以下是C语言的判断语句:

if(表达式){// 表达式成立}

在Go语言中,无须添加表达式括号,代码如下:

if表达式{// 表达式成立}

强制的代码风格

Go语言中,左括号必须紧接着语句不换行。其他样式的括号将被视为代码编译错误。这个特性刚开始会使开发者有一些不习惯,但随着对Go语言的不断熟悉,开发者就会发现风格统一让大家在阅读代码时把注意力集中到了解决问题上,而不是代码风格上。

同时Go语言也提供了一套格式化工具。一些Go语言的开发环境或者编辑器在保存时,都会使用格式化工具对代码进行格式化,让代码提交时已经是统一格式的代码。

不再纠结于 i++ 和 ++i

C语言非常经典的考试题为:

int a, b;a = i++;b =++i;

这种题目对于初学者简直摸不着头脑。为什么一个简单的自增表达式需要有两种写法?

在Go语言中,自增操作符不再是一个操作符,而是一个语句。因此,在Go语言中自增只有一种写法:

i++

如果写成前置自增++i,或者赋值后自增a=i++都将导致编译错误。

Go语言基础语法

注释

给别人看的,机器并不会执行这行语句

1.单行注释

// 我是单行注释

2.多行注释

/*
我是多行注释
我是多行注释
我是多行注释
我是多行注释
*/

// 这是一个main函数,这个是go语言启动的入口
func main() {
   //fmt.Println :打印一句话,然后执行完毕后,进行换行
   fmt.Println("Hello,Word")
}

变量

字面理解为变量就是会变化的量

package main

import "fmt"

func main() {
   var name string = "DuPeng"
   fmt.Println(name)
}

注意:如果在点击这里执行会出错

image-20221108122850855

出错提示:项目里有多个main方法

image-20221108122911210

正确执行方式:鼠标单击右键执行,一定要在main方法外! 一定要在main方法外 !一定要在main方法外,重要事情说三遍

image-20230110154522376

也可以这样执行,也不会出错

image-20230106143929761

变量的定义

var name type

name

name 为变量,它指向的是地址,而这个地址指向内存空间,而内存空间的数据是可以更换的

image-20221108124245537

var

声明变量的关键字,固定的写法,记住即可

type

代表变量的类型

定义多个变量

package main

import "fmt"
/*
1、定义 姓名name、年龄age、addr地址
2、打印输出
*/
func main() {
	//1、定义 姓名name、年龄age、addr地址
	var (
		name string
		age  int
		addr string
	)
	//2、打印输出
	fmt.Println(name, age, addr) //string默认为空,int默认为0
}

变量的初始化

标准格式

var 变量名 类型 =值(表达式)

也可以先定义,再赋值

package main

import "fmt"

/*
1、定义 姓名name、年龄age、addr地址
2、“=” 赋值符号 将等号右边的值赋值给等号左边
3、打印输出
*/
func main() {
	//1、定义 姓名name、年龄age、addr地址
	var (
		name string
		age  int
		addr string
	)
	//2、“=” 赋值符号 将等号右边的值赋值给等号左边
	name = "kuangshenshuo"
	age = 18
	addr = "西安"
	//3、打印输出
	fmt.Println(name, age, addr)

}

短变量声明并初始化

package main

import "fmt"

/*
1、使用 :=  自动推导 赋值给属性
2、打印输出name和age类型---printf
printf和print差一个f 表示类型
*/
func main() {
	//1、使用 :=  自动推导 赋值给属性
	name := "kuangshenshuo"
	age := 18
	//2、打印输出name和age类型---printf
	fmt.Printf("%T,%T", name, age)//%T表示为输出类型,一个%T对应一个参数
}

打印输出声明类型

image-20230106145621235

内存地址

打印内存地址

package main

import "fmt"

/*
1、定义变量
2、打印输出内存地址
*/
func main() {
	//1、定义变量
	var num int
	num = 100
	//2、打印输出  值和内存地址
	fmt.Printf("mun的值为:%d,内存地址:%p", num, &num) //&取地址符号
}

不要忘记取地址符

image-20230106150159425

值发生变换而内存地址不会发生改变,但每次重启系统就会重新分配一个新的地址空间

image-20230106150331467

变量交换

/*
在其他语言中列如java、c语言 变量的交换为下列代码
a=100
b=200
temp=0

​ temp=a
​ a=b
​ b=temp

*/

package main

import "fmt"
/*
	1、定义两个变量
	2、将两个变量进行交换并打印输出
*/
func main() {
	//1、定义两个变量
	a := 100
	b := 200
	//2、将两个变量进行交换并打印输出
	fmt.Printf("交换前a:%d,b:%d\n", a, b)
	a, b = b, a
	fmt.Printf("交换后a:%d,b:%d", a, b)
}
/*
交换前a:100,b:200
交换后a:200,b:100
*/

匿名变量

特点是"_","_"本身就是一个特殊字符

被称为空白标识符,任何赋值给这个标识符的值都会被抛弃,这样可以加强代码的灵活性

当我们不想接收第二个值时可以废弃掉

package main

import "fmt"

/*
1、定义test方法,返回两个int类型的值
2、在main方法中定义两个变量接收,打印输出
3、将第二个变量用下划线废弃掉,打印输出
*/

// 1、定义test方法,返回两个int类型的值
func test() (int, int) {
   return 100, 200
}
func main() {

   //2、在main方法中定义两个变量接收,打印输出
   a, b := test()
   fmt.Println(a, b)

   //3、将第二个变量用下划线废弃掉,打印输出
   c, _ := test()
   fmt.Println(c)
}

image-20230106152550193

变量的作用域

一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域。

局部变量

在函数体声明的变量,只作用在函数体内

全局变量

在函数体外声明的变量为全局变量

全局变量必须以var关键字开头,如果要在外部包使用全局变量的首字母必须大写

package main

import "fmt"

// 定义全局变量
var name string = "kuansshenshuo"

func main() {
   fmt.Println(name) //kuansshenshuo
   
   //也可以通过方法的调用
   fmt.Println(name) //kuansshenshuo
   
   //定义局部变量
   name = "zhangsan"
   fmt.Println(name) //zhangsan

}
func test02() {
   fmt.Println(name)
}

image-20230106154025610

常量

是一个简单值得标识符,在程序运行时,不会被修改的量

数据类型:布尔型,数字型,(整数型、浮点型和复数型)和字符串型

const

当定义完常量后,常量不允许被修改,否则会出错

package main

func main()  {
   const  url string="www.baidu.com"
   url="www.kuangstudy.com"
}

image-20221109151316914

示例:

package main

import "fmt"

/*
1、用const关键字定义变量
2、打印输出
*/
func main() {
   //1、用const关键字定义变量
   const url string = "www.baidu.com"
   const url2 string = "www.kuangstudy.com"
   const a, b, c = 1, "feige", "false"
   //2、打印输出
   fmt.Println(url)
   fmt.Println(url2)
   fmt.Println(a, b, c)
}

image-20221109152158125

iota

特殊常量,可以认为是一个被编译器修改的常量

iota是go语言的常量计数器 从0开始 依次递增

func main() {
   //1、定义变量,将iota赋值给它们
   const (
      a = iota        //iota==0
      b = iota        //iota==1
      c = iota        //iota==2
   )
   //2、打印输出
   fmt.Println(a, b, c)
}

image-20221109152847871

package main

import "fmt"

/*
1、定义变量,将iota赋值给它们
2、打印输出
*/
func main() {
   //1、定义变量,将iota赋值给它们
   const (
      a = iota        //iota==0
      b               //iota==1
      c               //iota==2
      d = "kuangshen" //iota==3
      e = iota        //iota==4
      f = 100         //iota==5
      g               //iota==6
      h = iota      //iota==7
   )
   //2、打印输出
   fmt.Println(a, b, c, d, e, f, g, h)
}

image-20230106170855293

直至const出现之时将被重置

package main

import "fmt"

/*
1、定义变量,将iota赋值给它们
2、打印输出
3、再创建const的变量
*/
func main() {
	//1、定义变量,将iota赋值给它们
	const (
		a = iota        //iota==0
		b               //iota==1
		c               //iota==2
		d = "kuangshen" //iota==3
		e = iota        //iota==4
		f = 100         //iota==5
		g               //iota==6
		h = iota        //iota==7
	)
	//3、再创建const的变量,const出现之时将被重置
	const (
		j = iota
		k = iota
	)

	//2、打印输出
	fmt.Println(a, b, c, d, e, f, g, h, j, k)
}

image-20230106171429906

基本数据类型

在Go编程语言中,数据类型用于声明函数和变量。

数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。

编译器在进行编译的时候,就要知道每个值的类型,这样编译器就知道要为这个值分配多少内存,并且知道这段分配的内存表示什么。

布尔型

布尔型的值只可以是常量true或者false

布尔类型的值默认为false

package main

import "fmt"

func main() {

   // var 变量名 数据类型
   var isFlang bool
   fmt.Println(isFlang)
}
//输出结果为:false

数字型

整形

整形int和浮点型float32、float64、Go语言支持整形和浮点型数字,并且支持负数,其中位 的运算采用补码

序号 类型和描述
1 uint8无符号8位整型(O到255)
2 uint16无符号16位整型(O到65535)
3 uint32无符号32位整型(O到4294967295)
4 uint64无符号64位整型(0到18446744073709551615)
5 int8有符号8位整型(-128到127)
6 int16有符号16位整型(-32768到32767)
7 int32有符号32位整型(-2147483648到2147483647)
8 int64有符号64位整型(-9223372036854775808到 9223372036854775807)
package main

import "fmt"

func main() {
   //定义一个整形
    //byte uint8
	//rune int32
	//int int64
   var age int = 18
   fmt.Printf("%T,%d\n", age, age)

   //定义一个浮点型
   var money float32 = 300.15
   //这里打印浮点数类型为%f
    //默认是6位小数打印
   fmt.Printf("%T,%f\n", money, money)
}
//输出结果:int,18
//float32,300.149994

浮点型

1、关于浮点数在机器中存放形式的简单说明,浮点数=符号位+指数位+尾数位

2、尾数数部分可能丢失,造成精度损失。-123.0000901

序号 类型和描述
1 float32 IEEE-754 32位浮点型数
2 float64 lEEE-75464位浮点型数
3 complex64 32位实数和虚数
4 complex128 64位实数和虚数

类型别名

序号 类型和描述
1 byte类似于uint8
2 rune类似于int32
3 uInt32或64位
4 int与uint一样大小
5 uintprt无符号整形,用于存放一个指针

字符和字符串

字符串就是一串固定长度的字符连接起来的字符序列。

Go的字符串是由单个字节连接起来的。

Go语言的字符串的字节使用UTF-8编码标识Unicode文本。

package main

import "fmt"

/*
1、定义、打印输出字符串和值
2、定义、打印输出字符和值
3、字符串拼接
4、转义字符 \
5、间隔符 \t tab
*/
func main() {
	//1、定义、打印字符串类型和值
	var str string
	str = "hello,xuexiangban"
	fmt.Println(str)

	//2、定义、打印输出字符和值
	v1 := 'A'
	v2 := 'B'
	v3 := '中'
	fmt.Printf("%T\t%T\t%T\n", v1, v2, v3) //便于查看\t间隔距离

	//3、字符串拼接
	fmt.Println("hello" + "学相伴\n")

	//4、转义字符 \
	fmt.Println("hello\"学相伴\"\n")

	//5、间隔符 \t tab
	fmt.Println("hello\"学相伴\t")
}
/*
hello,xuexiangban
int32   int32   int32
hello学相伴

hello"学相伴"

hello"学相伴
*/

数据类型转换

在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。

由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明

package main

import "fmt"

/*
1、定义一个int和一个float64的变量
2、将a转换为float64
3、整形是不能转换为bool
4、打印输出类型
*/
func main() {
	//1、定义一个int和一个float64的变量
	var a int = 1
	var b float64 = 3.14

	//2、将a转换为float64
	c := float64(a)

	//3、整形是不能转换为bool
	//d := bool(a)//cannot convert a (variable of type int) to type bool

	//4、打印输出类型
	fmt.Printf("%T\n", a)
	fmt.Printf("%T\n", b)
	fmt.Printf("%T\n", c)
}

/*
	输出结果为:
	int
	float64
	float64
	*/

类型转换只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(将int16转换为int32)。

当从一个取值范围较大的类型转换到取值范围较小的类型时(将int32转换为int16或将 float32转换为 int),会发生精度丢失(截断)的情况。

运算符

算术运算符

下表列出了所有Go语言的算术运算符。假定A值为10,B值为20。

运算符 描述 实例
+ 相加 A+B输出结果30
- 相减 A-B输出结果-10
* 相乘 A*B输出结果为200
/ 相除 B/A输出结果为2
% 求余 B%A输出结果为0
++ 自增 A++输出结果11
-- 自减 A--输出结果为9
package main

import "fmt"

/*
1、定义两个int变量a,b
2、使用+ - * / % ++ --进行运算,打印输出
*/
func main() {
	//1、定义两个int变量a,b
	var a int = 10
	var b int = 3
	//2、使用+ - * / % ++ --进行运算,打印输出
	fmt.Println(a + b)
	fmt.Println(a - b)
	fmt.Println(a * b)
	fmt.Println(a / b)
	fmt.Println(a % b)

	a++
	fmt.Println(a)//a++之后这里a==11
	a-- //a==11之后 再减一
	fmt.Println(a)//a==10
}

/*输出结果为:
13
7
30
3
1
11
10
*/

关系运算符

下表列出了所有Go语言的关系运算符。假定A值为10,B值为20。

运算符 描述 实例
== 检查两个值是否相等,如果相等返回True否则返回false A==B 为false
!= 检查两个值是否不相等,如果不相等返回True否则返回false A!=B为true
> 检查左边值是否大于右边值,如果是返回True否则返回false A>B 为false
< 检查左边值是否小于右边值,如果是返回True否则返回false A<B为True
>= 检查左边值是否大于等于右边值,如果是返回True否则返回false A>=B 为false
<= 检查左边值是否小于等于右边值,如果是返回True否则返回false A<=B 为true
package main

import "fmt"

/*
1、定义两个整形变量
2、通过关系运算符 ==、!=、>、<、>=、<= 比较 并打印输出
*/
func main() {
	//1、定义两个整形变量
	var a int = 10
	var b = 20
	//2、通过关系运算符 ==、!=、>、<、>=、<= 比较 并打印输出
	fmt.Println(a == b) //false
	fmt.Println(a != b) //true
	fmt.Println(a > b)  //false
	fmt.Println(a < b)  //true
	fmt.Println(a >= b) //false
	fmt.Println(a <= b) //true

}

逻辑运算符

下表列出了所有Go语言的逻辑运算符。假定A值为true,B值为false。

运算符 描述 实例
&& 逻辑AND运算符,如果两边的操作数都是True,则条件True,否则为False。 A&&B 为false
|| 逻辑OR运算符,如果两边的操作数有一个True,则条件True,否则为False。 A||B为true
逻辑NOT运算符,如果条件为True,则逻辑NOT条件False,否则为True。 !(A&&B )为true

逻辑与&&

package main

import "fmt"

/*
1、定义两个布尔型的变量 一个为true 一个为false
2、通过if语句判断 并打印输出
*/
func main() {
	//1、定义两个布尔型的变量 一个为true 一个为false
	var a bool = true
	var b bool = false
	
	//2、通过if语句判断 并打印输出
	if a && b { //当ab同时为真
		fmt.Println(a) //那么我们输出为真 因为a=true
	} else {
		fmt.Println(b) //否则输出为假  因为b=false
	}
}
//输出结果:false

逻辑或||

遇真则真,全假则假

package main

import "fmt"
/*
1、定义两个布尔型的变量 一个为true 一个为false
2、通过if语句判断 并打印输出
*/
func main() {
   var a bool = true
   var b bool = false

   if a || b { //当a或者b其中一个为真那么结果为真
       
      fmt.Println(a)//那么我们输出为 true
       
   }
}
//输出结果: true

逻辑非!

非也,也可以理解取反,表示否定的意思

package main

import "fmt"
/*
1、定义两个布尔型的变量 一个为true 一个为false
2、通过if语句判断 并打印输出
*/
func main() {
    //1、定义两个布尔型的变量 一个为true 一个为false
   var a bool = true
   var b bool = false
    
	//2、通过if语句判断 并打印输出
   if !b {//非b  那么!b=ture
      fmt.Println(a)//那么我们输出为 a,因为a=true
   }
}
//输出结果: true

位运算符(二进制)

运算符 描述 实例
& 按位与运算符"&"是双目运算符。都是1结果为1,否则是0 (A&B)结果为12,二进制为0000 1100
| 按位或运算符"|"是双目运算符。都是0结果为0,否则是1 (A |B)结果为61,二进制0011 1101
^ 按位异或运算符"A"是双目运算符。不同则为1,相同为0 (A^B)结果为49,二进制为0011 0001
&^ 位清空,a &^b,对于b上的每个数值,如果为0,则取a对应位上的数值,如果为1,则取0. (A&^B)结果为48,二进制为0011 0000
<< 左移运算符"<<"是双目运算符。左移n位就是乘以2的n次方。其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补O。 A<<2结果为240,二进制为1111 0000
>> 右移运算符">>"是双目运算符。右移n位就是除以2的n次方。其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。 A>>2结果为15,二进制为0000 1111
package main

import "fmt"

func main() {

	//	二进制 逢二进一

	//位运算:二进制
	//	60  0011 1100
	//	13  0000 1101
	//----------------进行&运算
	//  12  0000 1100 我和你 同时满足
	//----------------进行|运算
	//  61	0011 1101  我或你 一个满足即可
	//	49	0011 0001  不同为1 相同为0

	var a uint = 60
	var b uint = 13

	//与运算结果
	fmt.Printf("%d,二进制:%b\n", a&b, a&b)

	//或运算结果
	fmt.Printf("%d,二进制:%b\n", a|b, a|b)

	//非运算结果
	fmt.Printf("%d,二进制:%b\n", a^b, a^b)

	//a向左移动两位
	//	60  0011 1100
	// 移动之后 1111 0000
	fmt.Printf("左移动后后的十进制:%d,左移动后的二进制:%b\n", a<<2, a<<2)

	//a向右移动两位
	//	60  0011 1100
	// 移动之后 0000 1111
	fmt.Printf("右移动后后的十进制:%d,右移动后的二进制:%b\n", a>>2, a>>2)

	/*
	   	输出结果:
	      12,二进制:1100
	61,二进制:111101
	49,二进制:110001
	左移动后后的十进制:240,左移动后的二进制:11110000
	右移动后后的十进制:15,右移动后的二进制:1111

	*/
}

赋值运算符

下表列出了所有的Go语言的赋值运算符

运算符 描述 实例
= 简单的赋值运算符,将一个表达式的值赋给一个左值 C =A+B将A+B表达式结果赋值给C
+= 相加后再赋值 C +=A等于C=C+A
-= 相减后再赋值 C -=A等于C=C-A
*= 相乘后再赋值 C *=A等于C=C *A
/= 相除后再赋值 C/=A等于C=C/ A
%= 求余后再赋值 C%=A等于C=C%A
<<= 左移后赋值 C<<=2等于C=C<<2
>>= 右移后赋值 C>>=2等于C=c>>2
&= 按位与后赋值 C&=2等于C=C&2
^= 按位异或后赋值 C=2等于C=C2
!= 按位或后赋值 C|=2等于C=C|=2
package main

import "fmt"

func main() {
   var A int = 10
   var B int = 20
   var C int
   // 赋值运算符 =
   C = A + B
   fmt.Println(C) //结果为30

   //     相加后再赋值
   C += A //注意这里初始值为:C=30  C= C+A=30+10
   fmt.Println(C)
/*
输出结果:
   30
   40

*/
}

其他运算符

运算符 描述 实例
& 返回变量存储地址 &a;将给出变量的实际地址
* 指针变量 *a;是一个指针变量

拓展:键盘输入输出

package main

import "fmt"

/*
1、定义int、float32两个变量
2、利用键盘来录入这两个变量 scanln ,传参不要忘记取地址符&
3、打印输出
*/
func main() {
	//1、定义int、float32两个变量
	var a int
	var b float32

	//2、利用键盘来录入这两个变量 scanln ,传参不要忘记取地址符&
	fmt.Printf("请输入两个数:1、整数,并按空格后输入 2、浮点数\n")
	fmt.Scanln(&a, &b)

	fmt.Printf("a的值为:%d\t,b的值为:%f", a, b)

}

image-20230106180852737

编码规范

为什么需要代码规范

  • 代码规范不是强制的,也就是你不遵循代码规范写出来的代码运行也是完全没有问题的
  • 代码规范目的是方便团队形成一个统一的代码风格,提高代码的可读性,规范性和统一性。本规范将从命名规范,注释规范,代码风格和Go语言提供的常用的工具这几个方面做一个说明。
  • 规范并不是唯一的,也就是说理论上每个公司都可以制定自己的规范,不过一般来说整体上规范差异不会很大。

代码规范

命名规范

命名是代码规范中很重要的一部分,统一的命名规则有利于提高的代码的可读性,好的命名仅仅通过命名就可以获取到足够多的信息。

  • 当命名(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的public) ;
  • 命名如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的private )

包名:package

保持package的名字和目录名保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写。

package model
package main

文件名

尽量采取有意义的文件名,简短,有意义,应该为小写单词,使用下划线分隔各个单词(蛇形命名)。

流程控制

流程控制

程序的流程控制结构一共有三种:顺序结构,选择结构,循环结构

顺序结构:从上到下,逐行执行。默认的逻辑
选择结构:条件满足某些代码才会执行

  • if
  • switch
  • select ,后面channel再讲

循环结构:条件满足某些代码会被反复执行0-N次.

  • for

if语句

条件语句需要开发者通过指定一个或多个条件,并通过测试条件是否为true来决定是否执行指定语句,并在条件为false的情况在执行另外的语句

下图展示了程序条件语句的结构

image-20221112124207698

if条件语句

package main

import "fmt"

/*
1、定义一个整数
2、通过if语句进行比较
*/
func main() {
	//1、定义一个整数
	a := 15

	//2、通过if语句进行比较
	if a > 20 {
		fmt.Println("a>20")
	}

	if a > 10 {
		fmt.Println("a>10")
	}
}

if else 语句

package main

import "fmt"

/*
1、定义一个分数score
2、通过if else语句判断  >=90合格  90<=不合格
*/
func main() {
	//1、定义一个分数score
	var score int = 90

	//2、通过if else语句判断  >=90合格  90<=不合格
	if score >= 90 {
		fmt.Println("你的成绩合格")
	} else {
		fmt.Println("你的成绩不合格")
	}

}

if嵌套语句

package main

import "fmt"

/*
通过键盘输入两次密码进行校验
1、定义三个变量 x,y,password
2、x,y为键盘输入的值
3、通过if嵌套语句进行验证
*/
func main() {
	//1、定义三个变量 x,y,password
	var x, y, pwd int
	pwd = 20230107
	fmt.Println("请输入您的密码:")
    //2、x,y为键盘输入的值
	fmt.Scanln(&x)
	if pwd == x {
		fmt.Println("请再次输入您的密码确认")
		fmt.Scanln(&y)
		//3、通过if嵌套语句进行验证
		if pwd == y {
			fmt.Println("登录成功")
		} else {
			fmt.Println("密码验证失败")
		}
	} else {
		fmt.Println("密码验证失败")

	}
}

第一次密码错误:

image-20230107112537847

第一次密码正确,第二次密码错误:

image-20230107112506954

两次密码都输入正确的:

image-20230107112700333

switch语句

switch语句用于基于不同条件执行不同动作,每一个case分支都是唯一的,从上至下逐一测试,直到匹配为止。

switch语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加break。switch 默认情况下case最后自带 break语句

示例一:

package main

import "fmt"

/*
1、定义一个变量score 成绩为60
2、通过 Switch语句进行判断  90==A	80==B	70-50==C
3、当所有条件不满足执行default语句
*/
func main() {
	//1、定义一个变量score 成绩为66
	var score int = 60
	
	//2、通过 Switch语句进行判断  90==A	80==B	70-50==C
	switch score {
	case 90:
		fmt.Println("A")
	case 80:
		fmt.Println("B")
	case 70, 50, 60:
		fmt.Println("C")

	//3、当所有条件不满足执行default语句
	default:
		fmt.Println("D")
	}

}

这里也可以按照条件输入判断

示例二:

package main

import "fmt"

/*
1、定义一个成绩score 
2、通过键盘输入一个数
3、通过Switch条件判断
*/
func main() {
	//1、定义一个成绩score 
	var score int
	//2、通过键盘输入一个数
	fmt.Println("请输入一个数:")
	fmt.Scanln(&score)
	//3、通过Switch条件判断
	switch {
	case score >= 90:
		fmt.Println("您的成绩为A")
	case score >= 80 && score < 90:
		fmt.Println("您的成绩为B")
	case score >= 60 && score > 0:
		fmt.Println("您的成绩为C")
	case score < 60 && score > 0:
		fmt.Println("您的成绩不合格")
	default:
		fmt.Println("查询不到您的成绩") //因为成绩不能为负数
	}
}

fallthrough 贯穿;直通

switch默认情况下匹配成功后就不会执行其他case,如果我们需要执行后面的case,可以使用fallthrough穿透case使用fallthrough 会强制执行后面的case语句,fallthrough 不会判断下一条case的表达式结果是否为true。

当进行匹配选择机制后就不会执行后面的语句

image-20221112150042437

如果想要无视任何条件直接执行下一条的语句就可以使用fallthrough

package main

import "fmt"

func main() {
   var a bool = false
   switch a {
   case false:
      fmt.Println("1,case条件为false")
      fallthrough
   case true:
      fmt.Println("2,case条件为true")
   }
}

image-20221112150214925

暴力穿透很容易影响我们的程序,我们可以使用break进行终止

package main

import "fmt"

func main() {
   var a bool = false
   switch a {
   case false:
      fmt.Println("1,case条件为false")
      fallthrough
   case true:
      if a == false {
         break //终止穿透
      }
      fmt.Println("2,case条件为true")

   }
    //输出结果:1,case条件为false
}

for语句

在不少实际问题中有许多具有规律性的重复操作,因此在程序中就需要重复执行某些语句。

for循环是一个循环控制结构,可以执行指定次数的循环。

package main

import "fmt"

/*
for 条件的起始值;循环条件;控制变量自增或者自减
1、无限循环
2、计算1-10数字和
*/
func main() {
	//1、无限循环
	/*i := 1
	for {
		fmt.Println(i)
		i++
	}*/

	//2、计算1-10数字和
	sum := 0
	for i := 0; i <= 10; i++ {
		sum += i
	}
	fmt.Println(sum)
}

练习一

package main

import "fmt"

/*
练习:打印5×5方阵
* * * * * 
* * * * * 
* * * * * 
* * * * * 
* * * * * 
*/
func main() {
	for j := 0; j < 5; j++ { //打印列数
		for i := 0; i < 5; i++ { //打印行数
			fmt.Print("* ")
		}
		fmt.Println()
	}

}

练习二

package main

import "fmt"

/*
练习二:九九乘法表
*/
func main() {

	sum := 0
	for j := 0; j < 10; j++ {
		for i := 1; i < j+1; i++ {
			sum = i * j
			fmt.Printf("%d×%d=%d ", i, j, sum)

		}
		fmt.Println()
	}
}
/*
1×1=1 
1×2=2 2×2=4 
1×3=3 2×3=6 3×3=9 
1×4=4 2×4=8 3×4=12 4×4=16 
1×5=5 2×5=10 3×5=15 4×5=20 5×5=25 
1×6=6 2×6=12 3×6=18 4×6=24 5×6=30 6×6=36 
1×7=7 2×7=14 3×7=21 4×7=28 5×7=35 6×7=42 7×7=49 
1×8=8 2×8=16 3×8=24 4×8=32 5×8=40 6×8=48 7×8=56 8×8=64 
1×9=9 2×9=18 3×9=27 4×9=36 5×9=45 6×9=54 7×9=63 8×9=72 9×9=81 
*/

break 结束当前整个循环

package main

import "fmt"

/*
1、for循环10个数
2、当循环到第5个数时终止循环
*/
func main() {
	for i := 0; i < 10; i++ {
		if i == 4 { //从0开始循环
			break	//break结束当前循环
		}
        fmt.Println(i)
	}
}
/*
0
1
2
3
4
*/

continue 结束当次循环

package main

import "fmt"

/*
1、for循环10个数
2、当循环到第5个数时结束当次循环
*/
func main() {
	for i := 0; i < 10; i++ {

		if i == 4 { //从0开始循环
			continue //continue结束当次循环
		}
		fmt.Println(i)
	}
}
/*
0
1
2
3
5
6
7
8
9
*/

String

什么是String

Go中的字符串是一个字节的切片,可以通过将其内容封装在""中来创建字符串,Go中的字符串Unicode兼容的,并且是UTF-8编码,字符串是一些字节的集合

package main

import "fmt"

/*
1、输出字符串长度
2、获取指定的字节
3、遍历字符串
4、for range循环,遍历数组和切片
*/
func main() {
	str := "hello,xuexiangban"
	fmt.Println(str)

	//1、输出字符串长度
	fmt.Println("字符串长度为:", len(str))
	//2、获取指定的字节
	fmt.Printf("%c\n", str[0])

	//3、遍历字符串
	for i := 0; i < len(str); i++ {
		fmt.Printf("%c", str[i])
	}
	fmt.Println()

	//4、for range循环,遍历数组和切片
	for i,i2 := range str {
		fmt.Println(i)//i代表下标 heelo,xuexiangban从0-16
		fmt.Printf("%c", i2) //i2代表字符
	}
}
/*
hello,xuexiangban
字符串长度为: 17
h
hello,xuexiangban
0
h1
e2
l3
l4
o5
,6
x7
u8
e9
x10
i11
a12
n13
g14
b15
a16
n
*/

函数详解

什么是函数

  • 函数是基本的代码块,用于执行一个任务
  • Go语言最少有个main()函数
  • 你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务
  • 函数声明告诉了编译器函数的名称,返回类型和参数
package main

import "fmt"

/*
1、定义一个函数
2、在main函数中调用
*/

// 1、定义一个函数
func test01(a, b int) int {
	c := a + b
	fmt.Println(c)
	return c
}

//2、在main函数中调用
func main() {
	test01(1, 2)
}

函数的声明

Go语言函数定义格式如下:

  • 无参无返回值函数
  • 有一个参数的函数
  • 有两个参数的函数
  • 有一个返回值的函数
    • 有多个返回值的函数
package main

import "fmt"

/*
1、无参无返回值函数 printfinfo
2、有一个参数的函数 add1
3、有两个参数的函数,一个返回值的函数 add2
4、有多个返回值的函数 add3
5、main函数中调用
*/
func main() {
	//5、main函数中调用
	printfinfo()
	add1("kuangshen")
	add2(10, 20)
	add3("hello", "xuexiangban")
}

// 1、无参无返回值函数 printfinfo
func printfinfo() {
	fmt.Println("无参无返回值函数")
}

// 2、有一个参数的函数 add1
func add1(str string) {
	fmt.Printf("调用了add1方法:%s\n", str)
}

// 3、有两个参数的函数,一个返回值的函数 add2
func add2(b, c int) int {
	fmt.Println("调用了add2方法:", b+c)
	return b + c
}

// 4、有多个返回值的函数 add3
func add3(d, e string) (string, string) {
	fmt.Println(d, e)
	return d, e
}

package main

func main() {
   //实际参数:调用函数时,传给形参实际数据叫做实际参数-num1,num2
   max(1, 2)
}

// 形式参数:定义函数时,用来接收外部传入数据的参数-num1,num2
func max(num1, num2 int) {

}

可变参数

概念:一个函数的参数类型确定,但个数不确定,就可以使用可变参数

package main

import "fmt"

/*
1、定义一个求和函数getnum方法,可变参数num
2、遍历输出每一个传递的值
*/

func main() {
   getnum(1, 2, 3, 4, 5, 6)
}

// 1、定义一个求和函数getnum方法,可变参数num
func getnum(num ...int) {
   sum := 0

   //2、遍历输出每一个传递的值
   for _, i := range num {
      sum += num[i-1]
   }
   fmt.Println("求和函数sum=",sum)
}

注意事项:

  • 如果一个函数的参数时可变参数,同时还有其他的参数,可变参数要放在列表的最后
  • 一个函数的参数列表最多只能有一个可变参数

参数传递

按照数据的存储特点来分:

  • 值类型的数据:操作的是数据本身、int .string、bool、float64、array...
  • 引用类型的数据:操作的是数据的地址slice、map、 chan....

值传递

package main

import "fmt"

/*
1、定义一个数组
2、通过方法修改数组里面的数据
3、打印输出修改后值
*/
func main() {
	//1、定义一个数组
	arr := [4]int{1, 2, 3, 4}
	//3、打印输出修改后值
	updata(arr)
	fmt.Println("调用修改后的数据:", arr)
}
//2、通过方法修改数组里面的数据
func updata(arr [4]int) {
	fmt.Println("arr接受的数据:", arr)
	arr[0] = 100
	fmt.Println("arr修改后的数据:", arr)
}

/*
输出结果:
arr接受的数据: [1 2 3 4]
arr修改后的数据: [100 2 3 4]
调用修改后的数据: [1 2 3 4]
*/

引用传递

package main

import "fmt"

/*
=====引用传递=======
1、定义一个切片
2、通过方法修改切片里面的数据
3、打印输出修改后值
*/
func main() {
	arr := []int{1, 2, 3, 4}
	fmt.Println("调用修改前的数据", arr)
	updata2(arr)
	fmt.Println("调用修改后的数据", arr)
}
func updata2(arr []int) {
	fmt.Println("arr接受的数据:", arr)
	arr[0] = 100
	fmt.Println("arr修改后的数据:", arr)
}
/*
调用修改前的数据 [1 2 3 4]
arr接受的数据: [1 2 3 4]
arr修改后的数据: [100 2 3 4]
调用修改后的数据 [100 2 3 4]
*/

函数中变量的作用域

作用域:变量可以使用的范围

局部变量:函数内部定义的变量,叫做局部变量

全部变量:函数外部定义的变量,叫做全局变量

package main

import "fmt"

// 全局变量  整个类都可以使用
var num int = 30

func main() {
   // 局部变量 temp 只能在temp方法中使用
   temp := 100

   if b := 1; b < 10 {
      temp := 30
      fmt.Println("局部变量b:", b)
      fmt.Println("局部变量temp:", temp)
   }
   fmt.Println(temp)

   // 调用f1,f2方法
   f1()
   f2()
}
func f1() {
   fmt.Println(num)
}
func f2() {
   fmt.Println(num)
}

递归函数

定义:一个函数自己调用自己,就叫递归函数

注意:递归函数需要有一个出口,逐渐向出口靠近,没有出口就会形成死循环

求和 :1,2,3,4,5

getSum(5)

​ getSum(4)+5

​ getSum(3)+4

​ getSum(2)+3

​ getSum(1)+2

package main

import "fmt"

/*
1、创建一个求和函数 getsum
2、给递归函数一个出口
3、创建对象来接收函数
*/
func main() {
	//3、创建对象来接收函数
	sum := getSum(5)

	fmt.Println(sum)
}

//1、创建一个求和函数 getsum
func getSum(n int) int {

	//2、给递归函数一个出口
	if n == 1 {
		return 1
	}
	return getSum(n-1) + n
}

defer

语义:推迟、延迟

在go语言中,使用defer关键字来延迟一个函数或者方法的执行

package main

import "fmt"

func main() {

   f("1")
   f("2")
   defer f("3")
   f("4")
}
func f(s string) {
   fmt.Println(s)
}

defer函数或者方法:一个函数或方法的执行被延迟了

  • 你可以在函数中添加多个defer语句,当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回,特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题
  • 如果有很多调用defer,那么defer是采用后进先出〈栈)模式。
package main

import "fmt"

func main() {

   f("1")
   f("2")
   defer f("3")
   f("4")
   defer f("5")
   f("6")
   defer f("7")
   f("8")
}
func f(s string) {
   fmt.Println(s)
}
/*
输出结果:
1
2
4
6
8
7
5
3
*/

image-20221116164255326

高级:函数的数据类型

package main

import "fmt"

/*
fun本身就是一个数据类型
1、创建一个函数
2、在mian方法定义相同类型的函数
3、将定义后的函数赋值个另一个函数
*/
func main() {
	//2、在mian方法定义相同类型的函数
	var fg func(int, int)

	//3、将定义后的函数赋值个另一个函数
	fg = ff
	fg(1, 2)

}

//1、创建一个函数
func ff(a, b int) {
	fmt.Println(a, b)
}

函数在Go语言中是复合类型,可以看做是一种特殊的变量。

函数名()︰调用返回结果
函数名:指向函数体的内存地址,一种特殊类型的指针变量

高级:匿名函数

package main

import "fmt"

/*
匿名函数的创建需要在函数内;函数本身也是一个变量,也可以将函数赋值
1、创建一个f1函数
2、调用f1这个函数
3、将f1函数以变量形式输出
4、创建一个匿名函数并调用
5、创建匿名函数直接调用
6、带参匿名函数、带参且有返回值匿名函数
7、带参且有返回值匿名函数
*/

func main() {
	//2、调用f1这个函数;
	f1()
	//3、将f1函数以变量形式输出
	f2 := f1
	f2()
	//4、创建一个匿名函数并调用
	f3 := func() {
		fmt.Println("我是匿名函数f3")
	}
	f3()

	//5、创建匿名函数直接调用func(){ }()
	func() {
		fmt.Println("我是匿名函数f")
	}()

	//6、带参匿名函数
	func(a, b int) {
		fmt.Println("我是带参匿名函数f")
	}(1, 2)

	//	7、带参且有返回值匿名函数
	f5 := func(a, b int) int {
		return a + b
	}(1, 2)
	fmt.Println("带参且有返回值匿名函数:", f5)
}

// 1、创建一个f1函数;
func f1() {
	fmt.Println("我是f1函数")
}
/*
我是f1函数
我是f1函数
我是匿名函数f3
我是匿名函数f
我是带参匿名函数f
带参且有返回值匿名函数: 3
*/

Go语言是支持函数式编程:
1、将匿名函数作为另外一个函数的参数,回调函数
2、将匿名函数作为另外一个函数的返回值,可以形成闭包结构

高级:回调函数

高阶函数:根据go语言的数据类型的特点,可以将一个函数作为另外一个函数的参数。fun1(), fun2()
将fun1函数作为fun2这个函数的参数
fun2函数:就叫做高阶函数,接收了一个函数作为参数的函数

fun1函数:就叫做回调函数,作为另外—个函数的参数

package main

import "fmt"

/*
1、创建一个高阶函数oper,传如一个函数类型的参数且有返回值
2、创建其他方法的函数
3、调用高阶函数时,传入其他方法的函数
*/

func main() {
	r1 := add(1, 2)
	fmt.Println(r1)

	//3、调用高阶函数时,传入其他方法的函数
	r3 := oper(3, 4, add)
	fmt.Println(r3)
	r4 := oper(8, 4, sub)
	fmt.Println(r4)
}
//1、创建一个高阶函数oper,传如一个函数类型的参数且有返回值
func oper(a, b int, fun func(int, int) int) int {
	r2 := fun(a, b)
	return r2
}
//2、创建其他方法的函数
func add(a, b int) int {
	return a + b
}
//2、创建其他方法的函数
func sub(a, b int) int {
	return a - b
}

高级:闭包

一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量并且该外层函数的返回值就是这个内层函数。
这个内层函数和外层函数的局部变量,统称为闭包结构。
局部变量的生命周期就会发生改变,正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用。

package main

import "fmt"

func main() {
	//定义一个变量 接受返回值(函数)
	r1 := increment()
	fmt.Println(r1)

	v1 := r1()
	fmt.Println(v1)
	//不断调用自增+1
	v2 := r1()
	fmt.Println(v2)
	//不断调用自增+1
	fmt.Println(r1())
	fmt.Println(r1())
	fmt.Println(r1())
	fmt.Println(r1())
	fmt.Println("====================")
	//	==========================================================
	r2 := increment()
	v3 := r2()
	fmt.Println(v3)
	fmt.Println(r2())
	fmt.Println(r2())
	fmt.Println(r2())
	fmt.Println(r1())
	fmt.Println(r2())
}

// 返回一个函数
func increment() func() int {

	//局部变量
	i := 0
	//定义一个匿名函数 给变量自增并返回
	fun := func() int {
		//内层函数,没有执行
		i++
		return i
	}
	return fun
}
/*
输出结果:
0xd5ea00
1
2
3
4
5
6
====================
1
2
3
4
7
5

*/

数组详解

初识数组

什么是数组

相同类型的数据,例如arr[1,2,3,4,5]

GO语言提供了数组类型的数据结构

数组是具有相同唯一类型的一组已编号且长度固定数据项序列,这种类型可以是任意的原始类型

数组元素可以通过索引(位置)来读取(或者修改),索引从0开始,第一个元素为0,第二个索引为1,以此类推,数组的下标取值范围是从0开始,到长度减一

数组一旦定义后,大小就不能改变

image-20221214200504076

声明数组

GO语言数组声明需要指定元素类型以及元素个数,语言格式:

var 变量名[大小]变量类型

注:太久没敲了上来一个基本错误,主函数没有执行按钮;把package导入lesson04修改下为mian

image-20221214201941262

package main

import "fmt"

/*
1、定义个大小为4的整形数组 nums
2、将数组里的元素进行赋值操作
3、打印输出类型
4、打印数组的长度和容量
*/
func main() {
	//1、定义个大小为4的整形数组 nums
	var nums [4]int
	//2、将数组里的元素进行赋值操作
	nums[0] = 1
	nums[1] = 2
	nums[2] = 3

	//3、打印输出类型
	fmt.Printf("%T\n", nums)
	fmt.Println(nums[3])//数组元素未赋值将默认设置为0
	//4、打印数组的长度和容量
	fmt.Println("长度:", len(nums))
	fmt.Println("容量:", cap(nums))
}
/*
[4]int
0
长度: 4
容量: 4
*/

数组的定义:

  • 数组是相同类型有序集合
  • 数组描述的是相同类型的若干数据,按照一定的先后次序排列组合而成
  • 其中,每个数据操作一个数组元素,每个数组元素通过一个下标来访问它们

数组的基本特点:

1.其长度是确定的,数组一旦被创建,它的大小就是不可以改变的。

2.其元素必须是相同类型不允许出现混合类型

初始化数组

package main

import "fmt"

/*
1、常量的初始化定义数组
2、快速定义数组
3、定义不确定长度的数组
4、给指定下标进行赋值操作
*/
func main() {
	//1、常量的初始化定义数组
	var arrs1 = [5]int{1, 2, 3, 4, 5}
	fmt.Println(arrs1[0]) //打印数组里第一个元素 1
	//2、快速定义数组
	arrs2 := [5]int{6, 7, 8, 9, 10}
	fmt.Println(arrs2) //打印数组

	//3、定义不确定长度的数组
	arrs3 := [...]string{"hello", "xuexiangban", "kuangshenshuo"}
	fmt.Println(arrs3)
	fmt.Println(len(arrs3)) //长度
	fmt.Println(cap(arrs3)) //容量

	//4、给指定下标进行赋值操作
	arrs4 := [5]int{1: 100, 3: 200}//给下标为1赋值为100,下标为3赋值为200
	fmt.Println(arrs4)
}
/*
1
[6 7 8 9 10]
[hello xuexiangban kuangshenshuo]
3
3
[0 100 0 200 0]
*/

初始化数组中的{}中的元素个数,不能大于[]中的数字

遍历数组元素

package main

import "fmt"

/*
1、for循环遍历数组
2、forage循环遍历数组
*/
func main() {
	//1、for循环遍历数组
	arr1 := [5]int{1, 2, 3, 4, 5}
	for i := 0; i < len(arr1); i++ {
		fmt.Println(arr1[i])
	}
	//2、forage循环遍历数组
	for index, value := range arr1 { //index下标,value对应的值
		fmt.Println(index, value)
	}
}
/*
1
2
3
4
5
0 1
1 2
2 3
3 4
4 5
*/

数组是值类型

package main

import "fmt"

/*
1、值传递
2、同理 数组类型也是值传递类型
*/
func main() {
	//1、值传递
	num1 := 100
	num2 := num1
	fmt.Println(num1, num2)
	num2 = 200
	fmt.Println(num1, num2) //num1本身的值还是100

	//2、同理 数组类型也是值传递类型
	arr1 := [3]int{1, 2, 3}
	arr2 := [2]string{"hello", "xuexiangban"}
	fmt.Printf("%T\n%T\n", arr1, arr2)
	arr3 := arr1
	fmt.Println(arr1, arr3)
	arr3 = [3]int{4, 5, 6}
	fmt.Println(arr1, arr3) //arr1本身数组元素信息还是[1,2,3]

}

/*
100 100
100 200
[3]int
[2]string
[1 2 3] [1 2 3]
[1 2 3] [4 5 6]
*/

综合案例冒泡排序

package main

/*
练习:冒泡排序
*/
import "fmt"

func main() {
	// 定义数组
	arr := [5]int{5, 7, 2, 6, 4}
	sort(arr)
}

// 函数封装
func sort(arr [5]int) {
	// 冒泡排序
	for j := 1; j < len(arr); j++ { //j=1;因为len(arr)-0,arr[i+1]会越界
		for i := 0; i < len(arr)-j; i++ { //执行后优化代码,因为每次排序好的数还会进行与后面的数比较
			if arr[i] > arr[i+1] { //当前面一个数大于后面一个数时,它们就互换位置
				arr[i], arr[i+1] = arr[i+1], arr[i]
			}
			fmt.Println(arr)
		}
	}
}
/*
[5 7 2 6 4]
[5 2 7 6 4]
[5 2 6 7 4]
[5 2 6 4 7]
[2 5 6 4 7]
[2 5 6 4 7]
[2 5 4 6 7]
[2 5 4 6 7]
[2 4 5 6 7]
[2 4 5 6 7]
*/

多维数组

Go语言支持多维数组,以下为常用的多维数组声明方式:

var 变量名[SIZE1][SIZE2]...[SIZEN]变量类型

二维数组是最简单的多维数组,二维数组本质上是由一维数组组成。二维数组定义方式:

var 变量名 [x][y]变量类型

二维数组可以是一个表格,x为行,y为列,下图演示了一个二维数组a为三行四列

image-20221218155556330

package main

import "fmt"

func main() {
	//	定义一个二维数组
	arr := [3][4]int{
		{1, 2, 3, 4},
		{5, 6, 7, 8},
		{9, 10, 11, 12},
	}
	fmt.Println(arr)

	//forage循环输出
	for _, i := range arr {
		fmt.Println(i)
		fmt.Println("=========")
		for _, i2 := range i {
			fmt.Println(i2)
		}
	}
}

切片Slice

Go语言切片是对数组的抽象

Go数组的长度不可改变,在特定场景这样的集合就不太适用,Go提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的可以追加元素,在追加时可能使切片的容量增大

切片是一种方便、灵活且强大的包装器,切片本身没有任何数据,他们只是对现有数组的引用

切片与数组相比,不需要设定长度,在[]中不用设定值,相对来说比较自由

从概念上面来说slice像一个结构体,这个结构体包括三个元素:

  • 指针:指向数组中slice指定的开始位置
  • 长度:即slice的长度
  • 最大长度:也就是slice开始位置到数组最后位置的长度

定义切片

package main

import "fmt"

/*
1、定义一个切片,里面不添加数据,打印输出
2、定义切片向里面添加数据,打印输出类型
*/
func main() {
	//1、定义一个切片,里面不添加数据,打印输出
	s1 := []int{}
	fmt.Println(s1) //[]

	//2、定义切片向里面添加数据,打印输出类型
	s2 := []int{1, 2, 3}
	fmt.Println(s2)
	fmt.Printf("%T", s2)
}

make函数创建切片

使用make()函数来创建切片

make([]T,length,capactity)

切片是可索引的,并且可以由len()方法来获取长度

切片提供了计算容量的方法cap()可以测量切片最长可以到达多少

package main

import "fmt"

/*
1、通过make函数来创建切片
2、给切片里面的元素进行赋值
3、打印输出长度,容量
*/
func main() {
	//1、通过make函数来创建切片
	s1 := make([]int, 5, 10) //长度:5,容量为10
	//2、给切片里面的元素进行赋值
	s1[0] = 100
	//3、打印输出长度,容量
	fmt.Println(s1)
	fmt.Println("长度:", len(s1))
	fmt.Println("容量:", cap(s1))
}

切片扩容和遍历

package main

import "fmt"

/*
通过append追加数据,如果切片中的数据超过了规定的容量,那么它就会自动扩容
1、创建一个切片s1
2、s1通过append追加数据
3、创建一个s2,将s2中数据追加到s1
4、for循环遍历输出
5、range循环
*/
func main() {
	//1、创建一个切片s1
	s1 := make([]int, 2, 4)
	fmt.Println("追加数据前的长度和容量:", len(s1), cap(s1))
	//2、s1通过append追加数据
	s1 = append(s1, 1, 2, 3, 4) //追加数据,不是添加数据
	fmt.Println(s1)
	fmt.Println("追加数据后的长度和容量:", len(s1), cap(s1))

	//	3、创建一个s2,将s2中数据追加到s1
	s2 := make([]int, 3, 3)
	s2 = []int{1, 2, 3}
	s1 = append(s1, s2...) //将s2里面的数据追加到s1
	fmt.Println(s1)

	//4、for循环遍历输出
	for i := 0; i < len(s1); i++ {
		fmt.Println(s1[i])
	}
	fmt.Println("===============")
	//5、range循环
	for _, v := range s1 {
		fmt.Println(v)
	}
}

扩容的内存分析

package main

import "fmt"

/*
追加数据后,一旦超过容量,容量就会成倍扩容,地址会发生变化
1、创建一个切片,打印输出长度、容量、地址
2、向切片追加数据后,再次打印输出观察变化
3、重复2
4、重复2
*/
func main() {
	//1、创建一个切片,打印输出长度、容量、地址
	s1 := []int{1, 2, 3}
	fmt.Println(s1)
	fmt.Printf("长度:%d,容量:%d\t", len(s1), cap(s1))
	fmt.Printf("地址%p\n", s1)
	//2、向切片追加数据后,再次打印输出观察变化
	s1 = append(s1, 4, 5)
	fmt.Println(s1)
	fmt.Printf("长度:%d,容量:%d\t", len(s1), cap(s1))
	fmt.Printf("地址%p\n", s1)
	//3、重复2
	s1 = append(s1, 6, 7)
	fmt.Println(s1)
	fmt.Printf("长度:%d,容量:%d\t", len(s1), cap(s1))
	fmt.Printf("地址%p\n", s1)
	//3、重复2
	s1 = append(s1, 8, 9)
	fmt.Println(s1)
	fmt.Printf("长度:%d,容量:%d\t", len(s1), cap(s1))
	fmt.Printf("地址%p\n", s1)

}
/*
[1 2 3]
长度:3,容量:3   地址0xc0000aa078
[1 2 3 4 5]
长度:5,容量:6   地址0xc0000c8030
[1 2 3 4 5 6 7]
长度:7,容量:12  地址0xc000086060
[1 2 3 4 5 6 7 8 9]
长度:9,容量:12  地址0xc000086060
*/
  • 每一个切片引用了一个底层数组
  • 切片本身不存储任何数据,都是这个底层数组存储,所以修改切片也就是修改这个数组的数据
  • 当向切片中添加数据时,如果没有超过容量,直接添加,若果超过容量,自动扩容,成倍增加
  • 切片一旦扩容,就会重新指向一个新的底层数组

在已有数组上创建切片

从已有的数组上,直接创建切片,该切片的底层数组就是当前的数组,长度是从start到end切到的数据量,但是容量从start到数组的末尾

slice := arr[start:end]

package main

import "fmt"

func main() {
	//定义一个数组
	arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

	s1 := arr[:5]  //切片从1-5 [1 2 3 4 5]
	s2 := arr[4:8] //[5 6 7 8]
	s3 := arr[5:]  //[6 7 8 9 10]
	s4 := arr[:]   //[1 2 3 4 5 6 7 8 9 10]
	fmt.Println(s1)
	fmt.Println(s2)
	fmt.Println(s3)
	fmt.Println(s4)

	fmt.Printf("长度len:%d,容量cap:%d\n", len(s1), cap(s1))
	fmt.Printf("长度len:%d,容量cap:%d\n", len(s2), cap(s2))
	fmt.Printf("长度len:%d,容量cap:%d\n", len(s3), cap(s3))
	fmt.Printf("长度len:%d,容量cap:%d\n", len(s4), cap(s4))

	arr[0] = 100 //将数组元素值进行更换
	fmt.Println(arr)
	fmt.Println(s1)

	s1[1] = 100 
	fmt.Println(arr)
	fmt.Println(s1)

	fmt.Printf("%p\n", s1)
	fmt.Printf("%p\n", &arr)
}
/*
[1 2 3 4 5]
[5 6 7 8]
[6 7 8 9 10]
[1 2 3 4 5 6 7 8 9 10]
长度len:5,容量cap:10
长度len:4,容量cap:6
长度len:5,容量cap:5
长度len:10,容量cap:10
[100 2 3 4 5 6 7 8 9 10]
[100 2 3 4 5]
[100 100 3 4 5 6 7 8 9 10]
[100 100 3 4 5]
0xc0000ac0f0
0xc0000ac0f0
*/

切片是引用类型

package main

import "fmt"

/*
1、数组--值传递
2、切片--引用传递
*/
func main() {
	//1、数组--值传递
	arr1 := [5]int{1, 2, 3, 4, 5}
	arr2 := arr1
	arr2[0] = 100
	fmt.Println(arr1, arr2)
	fmt.Printf("%p\t%p\n", &arr1, &arr2) //说明数组的copy指向的是值

	//2、切片--引用传递
	s1 := []int{1, 2, 3, 4, 5}
	s2 := s1
	s2[0] = 100
	fmt.Println(s1, s2)          //切片2里元素发生变化,切片1也会跟随着变化
	fmt.Printf("%p\t%p", s1, s2) //说明切片的copy指向的是地址
}

深拷贝与浅拷贝

深拷贝:拷贝的是数据本身

  • 值类型的数据,默认都是深拷贝: array、int、float、string.bool、sturct
  • 浅拷贝:拷贝的是数据的地址,会导致多个变量指向同一块内存
  • 引用类型的数据,默认都是浅拷贝: slice、map
  • 因为切片是引用类型的数据,直接拷贝的是地址

实现切片的深拷贝

package main

import "fmt"

/*
1、通过for循环实现切片的深拷贝
2、通过copy操作实现深拷贝
*/
func main() {
	//1、通过for循环实现切片的深拷贝
	s1 := []int{1, 2, 3, 4}
	s2 := make([]int, 0, 0)
	for i := 0; i < len(s1); i++ {
		s2 = append(s2, s1[i])
	}
	fmt.Println(s1, s2)
	//2、通过copy操作实现深拷贝
	s3 := []int{5, 6, 7, 8}
	fmt.Println(s1, s3)
	copy(s3, s1) //(接收者,传递者) 将s1copy给s3
	fmt.Println(s1, s3)
}

集合Map

Map初始化

Map是一种无序的键值对的集合。Map最重要的一点是通过key来快速检索数据,key类似于索引,指向数据的值。Map是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map是无序的,我们无法决定它的返回顺序。Map也是引用类型。

package main

import "fmt"

/*
创建map的三种类型
1、只是定义一个map1
2、通过make函数创建
3、创建map3并赋值
*/
func main() {
	//1、只是定义一个map1
	var map1 map[int]string //nil 创建但未初始化

	//2、通过make函数创建
	var map2 = make(map[string]string) //通过make一旦创建,就初始化了 值为nil

	//3、创建map3并赋值
	var map3 = map[string]int{"GO": 100, "java": 90}
	fmt.Printf("map1类型:%T\t,map2:%T\n", map1, map2)
	fmt.Println(map1, map2, map3)
	fmt.Println(map1 == nil) //true
	fmt.Println(map2 == nil) //false

	//	添加数据
	map2["haha"] = "hehe"
	map2["haha2"] = "hehe2"
	map2["haha2"] = "hehe3"//map3是无序的,添加重复的key值,会覆盖前面的key
	map2["haha3"] = "hehe3"
	fmt.Println(map2)
}
/*
map1类型:map[int]string ,map2:map[string]string
map[] map[] map[GO:100 java:90]
true
false
map[haha:hehe haha2:hehe3 haha3:hehe3]
*/

Map的使用

//创建map
//存储键值对,给键值对赋值
//获取数据,根据key获取value
//可以通过ok-idiom来判断key value是否存在
//修改数据,存在就是修改数据,不存在就是添加数据
//删除数据 delete(map,key)
//map长度 len
package main

import "fmt"

/*
1、创建map
2、存储键值对,给键值对赋值
3、如果没有初始化 会报错
4、通过ok-idiom来判断key value是否存在,并获取map
5、修改数据
6、delete删除数据
*/
func main() {
	//1、创建map
	var map1 map[int]string //panic: assignment to entry in nil map

	//3、如果没有初始化 会报错
	map1 = make(map[int]string)

	//2、存储键值对,给键值对赋值
	map1[1] = "hello"
	map1[2] = "kuangshenshuo"
	map1[3] = "xuexiangban"
	fmt.Println(map1) //这里直接执行会报错

	//4、通过ok-idiom来判断key value是否存在,并获取map
	value, ok := map1[1]
	if ok {
		fmt.Println("map key存在,其值为:", value)
	} else {
		fmt.Println("map key不存在")
	}
	//	5、修改数据
	map1[1] = "haha"
	fmt.Println(map1)

	//6删除数据
	delete(map1, 1)
	fmt.Println(map1)
}

Map的遍历

package main

import "fmt"

/*
1、创建map并添加数据
2、通过range循环
*/
func main() {
	var map1 = map[string]int{"Go": 100, "JAVA": 90, "Python": 80}
	for s, i := range map1 {
		fmt.Println(s, i) //每次遍历,顺序都将会不一样,因为map集合是无序的

	}
	map2 := map1//map也是引用类型
	map2["Go"] = 0
	fmt.Println(map1, map2)
}

使用map过程中需要注意的几点:

  • map是无序的,每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取
  • map的长度是不固定的,也就是和slice一样,也是一种引用类型
  • 内置的len函数同样适用于map,返回map拥有的key的数量
  • map的key可以是所有的比较类型,如布尔型、整数型、浮点型、字符串......

map结合slice

综合案例实现:

package main

import "fmt"

func main() {
	/*
	   1、创建map来存储人的信息,name、age、sex、addr
	   2、每个map存一个人的信息
	   3、将这些map存入到slice(切片)中
	   4、打印遍历输出
	*/

	//  1、创建map来存储人的信息,name、age、sex、addr  2、每个map存一个人的信息
	map1 := map[string]string{"name": "kuangshenshuo", "age": "18", "sex": "男", "addr": "重庆"}
	map2 := map[string]string{"name": "feige", "age": "35", "sex": "男", "addr": "湖南"}
	fmt.Println(map1, map2)
	Userdata := make([]map[string]string, 0, 3)
	//3、将这些map存入到slice(切片)中
	Userdata = append(Userdata, map1)
	Userdata = append(Userdata, map2)
	//4、打印遍历输出
	for i, userdatum := range Userdata {
		fmt.Println(i, userdatum)
	}
}

指针和结构体

指针

GO语言中指针是很容易学习的,GO语言中使用指针可以更简单的执行一些任务。我们都知道,变量是一种使用方便的占位符,用手引用计算机内存地址。Go语言的取地址符是&,放到一个变量的使用就会返回相应变量的内存地址

指针的概念

指针是存储另一个变量内存地址的变量

变量是一种使用方便的占位符,用于引用计算机内存地址

一个指针变量指向了一个值的内存地址

a存地址, 0xc00001c0b8 值为10
b指针变量,指向内存地址	0xc00001c0b8
变量b持有a的地址,即b指向a
package main
import "fmt"
/*
1、定义一个整数a
2、将a内存地址赋值b
3、将b的值更改
*/
func main() {
	//1、定义一个整数a
	a := 10
	fmt.Println(a, &a)
	//2、将a内存地址赋值b
	b := &a
	fmt.Println(b, &b, *b)//*b:指向a的值
	//3、将b的值更改
	*b = 20
	fmt.Println(a, &a)
}
/*
10 0xc0000a6058
0xc0000a6058 0xc0000ca020 10
20 0xc0000a6058
*/

指针的使用

指针使用流程:

  • 定义指针变量
  • 为指针变量赋值
  • 访问指针变量中指向的地址的值

在指针类型的前面加上*号(前缀)来获取指针所指向的内容

package main

import "fmt"

/*
1、定义一个指针变量
2、为指针变量赋值,访问指针变量中指向的地址的值
3、再定义一个指针ptr指向指针p
*/
func main() {
	var a int = 10
	fmt.Printf("a的值为:%d\t,a的地址为%p\n", a, &a)

	//1、定义一个指针变量
	var p *int
	p = &a
	fmt.Printf("p指向a的地址:%p\t,p取a的值为:%d\n", p, *p)

	//2、为指针变量赋值,访问指针变量中指向的地址的值
	*p = 20
	fmt.Printf("a的值为:%d\t,a的地址为%p\n", a, &a)
	fmt.Printf("p指向a的地址:%p\t,p取a的值为:%d\n", p, *p)
	fmt.Println("p的地址为:", &p)

	//3、再定义一个指针ptr指向指针p
	var ptr **int //可以*int看成一个整体
	ptr = &p
	fmt.Printf("ptr存储的p的地址:%p\t,ptr指向p的值:%d\n", ptr, *ptr)
	fmt.Printf("ptr自己的地址:%p\t,ptr取p变量的值为:%d\n", &ptr, **ptr)
}

/*
a的值为:10      ,a的地址为0xc0000a6058
p指向a的地址:0xc0000a6058       ,p取a的值为:10
a的值为:20      ,a的地址为0xc0000a6058
p指向a的地址:0xc0000a6058       ,p取a的值为:20
p的地址为: 0xc0000ca020
ptr存储的p的地址:0xc0000ca020   ,ptr指向p的值:824634400856
ptr自己的地址:0xc0000ca028      ,ptr取p变量的值为:20
*/

指针与数组

数组指针:首先是一个指针,一个数组的地址

指针数组:它是一个数组,存储的数据类型是指针

package main

import "fmt"

/*
1、定义一个数组
2、定义一个指针,指向数组
3、将数组的第一、二个元素改变
4、指针数组
*/
func main() {
	//1、定义一个数组
	arr := [4]int{1, 2, 3, 4}
	fmt.Printf("arr的内存地址:%p\n", &arr)

	//2、定义一个指针,指向数组
	var p *[4]int
	p = &arr
	fmt.Printf("p-->arr的内存地址:%p\n", p)
	fmt.Printf("p-->arr的值:%d\n", *p)
	fmt.Printf("p自己的地址:%p\n", &p)
	//3、将数组的第一、二个元素改变
	(*p)[0] = 100
	//也可以简写
	p[1] = 200
	fmt.Printf("p-->arr的值:%d\n", *p)

	//4、指针数组
	a := 1
	b := 2
	c := 3
	d := 4
	arr2 := [4]*int{&a, &b, &c, &d}
	fmt.Println(arr2)
	fmt.Println(*arr2[0])
	*arr2[0] = 100
	fmt.Println(*arr2[0])
}

/*
arr的内存地址:0xc000012200
p-->arr的内存地址:0xc000012200
p-->arr的值:[1 2 3 4]
p自己的地址:0xc00000a030
p-->arr的值:[100 200 3 4]
[0xc00001c0a8 0xc00001c0d0 0xc00001c0d8 0xc00001c0e0]
1
100

*/

指针函数

一个函数,该函数的返回值是一个指针

package main

import "fmt"

/*
1、定义一个函数f1 返回值类型为指针
2、接收f1传递过来的指针
3、打印输出
*/
func main() {
	//2、接收f1传递过来的指针
	ptr := f1()
	//3、打印输出
	fmt.Printf("%p\n", ptr)
	fmt.Println(*ptr)

}

// 1、定义一个函数f1 返回值类型为指针
func f1() *[4]int {
	arr1 := [4]int{1, 2, 3, 4}
	return &arr1

}


指针作为参数

package main

import "fmt"

func main() {
   a := 10
   fmt.Println("调用f2函数前a:", a)
   f2(&a)
   fmt.Println("调用f2函数后a:", a)
}
// 指针作为一个变量
// 定义一个f2的函数,传递进来的参数为指针
func f2(ptr2 *int) {
   fmt.Println("ptr2:", ptr2)
   fmt.Println("*ptr2:", *ptr2)
   *ptr2 = 100
}
/*
调用f2函数前a: 10
ptr2: 0xc00001c0b8
*ptr2: 10
调用f2函数后a: 100
*/

基础下部分更新在首页

结构体

结构体是由一系列具有相同类型或不同类型的变量数据构成的数据集合我们一般用来封装一类事务的变量,结构体可以理解为一堆变量的集合,结构体表示一项记录,比如保存人的信息,每个人都有以下属性:

  • Name:名字
  • Age:年龄
  • Sex:性别

定义结构体

结构体定义需要使用type和struct语句

struct语句定义了一个新的数据类型,结构体中有一个或多个成员

type语句设定了结构体的名称

package main

import "fmt"

/*
1、创建方式一:定义一个结构体 User  字段 name、age、sex
2、创建一个user对象并赋值
3、创建方式二:快捷创建
4、创建方式三:创建的时候进行赋值操作
*/

// 1、创建方式一:定义一个结构体 User  字段 name、age、sex
type User struct {
	name string //姓名
	age  int    //年龄
	sex  string //性别
}

func main() {
	//2、创建一个user对象并赋值
	var user1 User
	fmt.Println(user1)
	user1.name = "kuangshenshuo"
	user1.age = 18
	user1.sex = "男"
	//可以单个提取
	fmt.Println(user1.name)
	
	fmt.Println(user1)
	
	//3、创建方式二:快捷创建
	user2 := User{}
	user2.name = "feige"
	user2.sex = "男"
	user2.age = 30
	fmt.Println(user2)

	//4、创建方式三:创建的时候进行赋值操作
	user3 := User{"fiege2", 18, "男"}
	fmt.Println(user3)
}

/*
{ 0 }
{kuangshenshuo 18 男}
kuangshenshuo
{feige 30 男}
{fiege2 18 男}
*/

结构体指针

结构体是值类型的

使用内置函数new()创建,new的所有Type都返回指针

user1:=new(Users)

package main

import "fmt"

/*
1、结构体是值类型
2、指针对象
3、new关键字创建也是引用类型
*/
type User2 struct {
	name string
	age  int
	sex  string
}

func main() {
	//1、结构体是值类型
	user := User2{"kuangshen", 18, "男"}
	user2 := user
	user2.name = "zhangsan"
	fmt.Println(user) //将传递后的对象重新赋值之后打印输出,结果未改变,由此得出结构体是值类型

	//2、指针对象
	var user3 *User2
	user3 = &user
	user3.name = "lisi"
	fmt.Println(user) //因为指针是引用类型

	//3、new关键字创建也是引用类型
	user4 := new(User2)
	user4 = &user
	user4.name = "wangwu"
	fmt.Println(user)

}

/*
{kuangshen 18 男}
{lisi 18 男}
{wangwu 18 男}
*/

匿名结构体

匿名结构体:没有名字的结构体

匿名字段:一个结构体的字段没有字段名

package main

import "fmt"


/*
1、定义一个Student结构体 打印输出
2、定义匿名结构体 打印输出
3、定义Teacher结构体,匿名字段 打印输出
*/

//1、定义一个Student结构体 打印输出
type Student struct {
	name string
	age  int
}

//3、定义Teacher结构体,匿名字段 打印输出
type Teacher struct {
	string
	int
}

func main() {
	s1 := Student{"kuangshen", 18}
	fmt.Println(s1)

	//2、定义匿名结构体 打印输出
	s2 := struct {
		name string
		age  int
	}{"秦疆", 18}
	fmt.Println(s2)

	//	匿名字段
	t1 := Teacher{"feige", 3}
	fmt.Println(t1)
}
/*
{kuangshen 18}
{秦疆 18}
{feige 3}
*/

结构体嵌套

一个结构体可能包含一个字段,这个字段又是一个结构体,这被称为结构体嵌套

package main

import "fmt"

/*
结构体嵌套
1、定义Address结构体
2、定义Person结构体,将创建Address的对象添加
3、调用Person结构体,打印输出
*/

//2、定义Person结构体,将创建Address的对象添加
type Person struct {
	name    string
	age     int
	address Address //结构体嵌套
}
//1、定义Address结构体
type Address struct {
	city, state string
}

func main() {
	//3、调用Person结构体,打印输出
	pesoon1 := Person{"kuangshen", 18, Address{"中国", "广州"}}
	fmt.Println(pesoon1)
}
//{kuangshen 18 {中国 广州}}

结构体导出

如果结构体名称首字母小写,则结构体不会被导出。这时,即使结构体成员字段名首字母大写也不会导出

如果结构体首字母大写,则结构体有可能导出,只会导出大写首字母的成员字段,那些小写首字母的成员字段不会被导出

如果存在嵌套结构体,即使嵌套在内层的结构体名称首字母小写,外部也能访问到其中首字母大写的成员字段

这里自己出现一个小插曲,导包问题!

  • 直接在main方法中使用,系统会自动帮忙导入对应包,而自己手动导入,只要不使用就会消失
package main

import (
   "awesomeProject/pojo"
   "fmt"
)

func main() {
   var user pojo.User
   user.Name = "kuangshen"
   user.Money = 100
   fmt.Println(user)
}
//{kuangshen 0 100}

面向对象编程

OOP思想

Go语言不是面向对象语言,只是让大家理解一些面向对象的思想,通过一些方法来模拟面向对象

语言的进化发展跟生物的进化发展是一回事,都是"物以类聚"

语句多了,我们将完成同样功能相近的语句,聚到一块,便于我们使用,于是,函数出现了

变量多了,我们将功能相近的变量组在一起,聚到一起归类,便于我们调用。于是,结构体出现了

再后来,方法多了,变量多了!结构体不够用!我们就将功能相近的变量和方法聚到了一起,于是类和对象就出现了

企业的发展也是"物以类聚"的过程,完成市场维护的人员聚到了一起形成了市场部。完成技术开发的人员聚到了一起形成了开发部

面向对象的思维模式

面向对象的思维模式是简单的线性思维,思考问题首先陷入第一步做什么,第二步做什么的细节中,这种思维模式适合处理简单的事情,比如:上厕所

如果面对复杂的事情,这种思维模式会陷入令人发疯的状态!比如:如何造火箭!

面向对象的思维模式

面向对象的思维模式说白了就是分类思维模式,首先思考解决问题,需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索

这样就可以形成很好的协作分工。比如:架构师分了10个类,然后将10个类交给了10个人分别进行详细设计和编码!

显然,面向对象适合处理复杂的问题,适合处理需要多人协作的问题!

如果一个问题需要多人协作一起解决,那么你一定要用面向对象的方式来思考!

对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但,仍然需要面向过程的思路去处理。
面向对象的三大特点:封装、继承、多态I

继承

image-20221228100447165

兔子和羊属于食草动物类,狮子和豹属于食肉动物类。

食草动物和食肉动物又是属于动物类。
所以继承需要符合的关系是: is-a,父类更通用,子类更具体。
虽然食草动物和食肉动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特性。
继承就是子类继承父类的特征和行为,使得子类具有父类的属性和方法,使得子类具有父类相同的行为。子类会具有父类的一般特性也会具有自身的特性。

package main

import "fmt"

/*
1、父类结构体Person
2、子类结构体Student、创建一个父类匿名结构体对象
3、创建父类的对象
4、创建子类的对象
*/


//1、父类结构体Person
type Person struct {
	name string
	age  int
}
//2、子类结构体Student、创建一个父类匿名结构体对象
type Student struct {
	Person //匿名结构体
	school string
}

func main() {
	//3、创建父类的对象
	person := Person{"kuangshen", 18}
	fmt.Println(person)

	//4、创建子类的对象
	student := Student{Person{"feige", 35}, "清华"}
	fmt.Println(student)
}
/*
{kuangshen 18}
{{feige 35} 清华}
*/

方法讲解

什么是方法

Go语言中同时拥有函数和方法

  • 方法:
    • 某个类别的行为功能。需要指定接收者调用
    • 一段独立的功能代码,可以直接调用
  • 函数
    • 一段独立功能的代码,可以直接调用
    • 命名不能冲突
package main

import "fmt"

/*
1、定义一个Dog、cat结构体
2、定义方法接收Dog、cat类型
3、调用方法输出
*/

// 1、定义一个Dog、cat结构体
type Dog struct {
	name string
	age  int
}
type Cat struct {
	name string
	age  int
}

// eat方法接收者为Dog类型 只有Dog类型的对象才能调用方法
func (dog Dog) eat() {
	fmt.Printf("%s...eat\n", dog.name)
}
func (dog Dog) sleep() {
	fmt.Printf("%s...sleep\n", dog.name)
}

// sleep方法接收者为cat类型 只有cat类型的对象才能调用方法
func (cat Cat) sleep() {
	fmt.Printf("%s...sleep\n", cat.name)
}
func main() {
	//2、定义方法接收Dog、cat类型
	dog := Dog{"二哈", 3}
	cat := Cat{"喵喵", 2}
	//3、调用方法输出
	dog.eat()
	dog.sleep()
	cat.sleep()
}

/*
二哈...eat
二哈...sleep
喵喵...sleep
*/

方法重写

子类可以重写父类的方法override

子类可以新增自己的属性和方法

子类可以直接访问父类的属性和方法

package main

import "fmt"
/*
1、定义Animal、Dog2、Cat2结构体;	Dog2和Cat2继承Animal
2、定义Animal的方法;重写Dog2、cat2方法
3、调用父类的方法和重写的方法
*/

//1、定义Animal、Dog2、Cat2结构体;	Dog2和Cat2继承Animal
type Animal struct {
	name string
	age  int
}

type Dog2 struct {
	Animal
	sex string
}
type Cat2 struct {
	Animal
	color string
}

//2、定义Animal的方法;重写Dog2、cat2方法
func (animal Animal) eat() {
	fmt.Println("eat...")
}
func (animal Animal) sleep() {
	fmt.Println("sleep...")
}
func (dog Dog2) eat() {
	fmt.Println("Dog...eat...")
}
func (cat Cat2) sleep() {
	fmt.Println("Dog...eat...")
}

func main() {
	a1 := Animal{"二哈", 3}
	a1.eat()
	a1.sleep()
	
	//3、调用父类的方法和重写的方法
	dog := Dog2{Animal{"旺财", 5}, "公"}
	dog.Animal.eat() 
	dog.eat()        
	
	cat:=Cat2{Animal{"喵喵",10},"红色"}
	cat.Animal.sleep()
	cat.sleep()
}

接口实现

Go语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现接口定义的全部方法就是实现了这个接口

接口只做定义,不做具体的方法实现,具体实现交给实现方法

// go语言不需要显示的接口
// 实现接口中的方法,就算实现了接口
// go语言中,接口和实现类的关系,是非侵入式的

package main

import "fmt"

/*
1、定义一个USB接口、OInput、oOutput方法
2、定义一个Mouse鼠标结构体
3、实现接口中的所有方法,并约束对象为Mouse
4、测试方法
5、调用测试方法
6、示例二
*/

// 1、定义一个USB接口、OInput、oOutput方法
type USB interface {
	OInput()
	oOutput()
}

// 2、定义一个Mouse鼠标结构体
type Mouse struct {
	name string
}

// 3、实现接口中的所有方法,并约束对象为Mouse
func (mouse Mouse) OInput() {
	fmt.Println(mouse.name, "鼠标输入")
}
func (mouse Mouse) oOutput() {
	fmt.Println(mouse.name, "鼠标输出")
}

//4、测试方法
func test(u USB) {
	u.OInput()
	u.oOutput()
}

//6、示例二
type KeyBored struct {
	name string
}

func (k KeyBored) OInput() () {
	fmt.Println(k.name, "鼠标输入")
}
func (k KeyBored) oOutput() () {
	fmt.Println(k.name, "鼠标输出")
}

//5、调用测试方法
func main() {
	m := Mouse{"罗技"}
	test(m)

	k := KeyBored{"雷蛇"}
	test(k)
}

/*
罗技 鼠标输入
罗技 鼠标输出
雷蛇 键盘输入
雷蛇 键盘输出
*/

多态

多态:一个事务拥有多种形态,是面向对象中很重要的一个特点

Go语言通过接口来模拟多态

package main

import "fmt"

/*
1、定义Animal2接口、eat、sleep方法
2、定义Dogs2结构体
3、实现接口的方法
4、测试方法,传参为Animal2类型的对象
*/

//1、定义Animal2接口、eat、sleep方法
type Animal2 interface {
	eat()
	sleep()
}
//2、定义Dogs2结构体
type Dogs2 struct {
	name string
}

//3、实现接口的方法
func (dogs2 Dogs2) eat() {
	fmt.Println(dogs2.name, "...eat")
}
func (dogs2 Dogs2) sleep() {
	fmt.Println(dogs2.name, "...sleep")
}

//4、测试方法,传参为Animal2类型的对象
func test02(animal2 Animal2) {
	animal2.eat()
	animal2.sleep()
}
func main() {
	dogs2 := Dogs2{"旺财"}
	dogs2.eat()
	dogs2.sleep()
	test02(dogs2)
}

/*
旺财 ...eat
旺财 ...sleep
旺财 ...eat
旺财 ...sleep
*/

空接口

不包含任何的地方,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的值

package main

import "fmt"

/*
1、定义 Dog3 、Cat3结构体
2、定义空接口A
3、main方法中传参空接口A的对象,打印输出
4、创建测试方法
5、调用测试方法
*/

// 1、定义 Dog3 、Cat3结构体
type Dog3 struct {
	name string
}
type Cat3 struct {
	name  string
	color string
}

// 2、定义空接口A
type A interface {
}

// 4、创建测试方法
func test03(a A) {
	fmt.Println(a)
}

func main() {
	//如果定义空接口,我们可以将任意类型对象赋值
	//3、main方法中传参空接口A的对象,打印输出
	var a1 A = Dog3{"大黄"}
	var a2 A = Cat3{"喵喵", "白色"}
	var a3 A = "haha"
	var a4 A = 100
	fmt.Println(a1)
	fmt.Println(a2)
	fmt.Println(a3)
	fmt.Println(a4)

	//5、调用测试方法
	test03(a1)
	test03(a2)
	test03(a3)
	test03(a4)

	//6、map 输入string类型 传参为interface{}任意类型
	map1 := make(map[string]interface{})
	map1["name"] = "旺财"
	map1["age"] = 17
	fmt.Println(map1)

	//7、切片
	s1 := make([]interface{}, 0, 10)
	s1 = append(s1, a1, a2, a3, a4) //将a1-a4追加到s1
	fmt.Println(s1)

}

/*
{大黄}
{喵喵 白色}
haha
100
{大黄}
{喵喵 白色}
haha
100
map[age:17 name:旺财]
[{大黄} {喵喵 白色} haha 100]
*/

接口嵌套

接口可以继承,还可以多继承

package main

import "fmt"

/*
1、定义两个接口 A2、B2分别创建testa、testb方法
2、接口C2继承A2和B2,创建testc方法
3、定义结构体Dogg22
4、实现testc方法需先实现testa、testb方法
5、创建A2、B2、C2的对象,然后调用它们方法
*/

// 1、定义两个接口 A2、B2分别创建testa、testb方法
type A2 interface {
	testa()
}
type B2 interface {
	testb()
}

// 2、接口C2继承A2和B2,创建testc方法
type C2 interface {
	A2
	B2
	testc()
}

// 3、定义结构体Dogs2
type Dogg2 struct {
}

// 4、实现testc方法需先实现testa、testb方法
func (d2 Dogg2) testa() {
	fmt.Println("testa")
}
func (d2 Dogg2) testb() {
	fmt.Println("testb")
}
func (d2 Dogg2) testc() {
	fmt.Println("testc")
}

func main() {
	//5、创建A2、B2、C2的对象,然后调用它们方法
	var dog2 Dogg2 = Dogg2{}

	var ia A2 = dog2 //A2和B2的接口,创建的对象ia和ib只能调用自己的方法 不能调用ic方法
	ia.testa()
	var ib B2 = dog2
	ib.testb()

	var ic C2 = dog2 //由于C2继承了A2和B2的接口,因此可以调用A2和B2的方法
	ic.testa()
	ic.testb()
	ic.testb()
}

/*
testa
testb
testa
testb
testb
*/

接口断言

检查接口类型变量的值是否实现了期望的接口,就是检查当前接口类型的值有没有实现指定的接口

把接口类型变量的值转换为其他类型或其他接口,go语言空interface{}可以保存任何类型的变量,当程序中需要使用变量的时候,需要把接口类型变量的值转换为具体类型,可以通过接口类型断言

其实很好理解,假如接口类型保存的值时数字,当前的值要参与数学运算,就必须转为int类型才可以参加运算,这就是利用接口断言来实现类型转换的例子

被断言的对象必须是接口类型,否则会报错

t:=i.(T)

t:=i.(T)

/*
断言成功,则t为T类型的接口值
断言失败则报错:panic
*/

代码:

package main

import "fmt"

func main() {
   assertString("aaa")
   assertString(1)
}
func assertString(i interface{}) {
   s := i.(string) //判断传入进来的参数是否为string
   fmt.Println(s)
}
/*
输出结果:
aaa
panic: interface conversion: interface {} is int, not string
*/

v,ok :=i.(T)

v,ok :=i(T)
/*
断言成功,则v为T类型的接口值,ok为true
断言失败,则v为空值,ok为false
*/

代码:

func assertInt(i interface{}) {
   s, ok := i.(int)
   if ok {
      fmt.Println("接口变量i是int类型")
      fmt.Println(s)
   } else {
      fmt.Println("接口变量i不是int类型")
   }
}
func main() {
	assertInt("aaa")
	assertInt(1)
}

type-switch

type switch
switch 接口变量.(type){
 	case 类型1:
 	//变量是类型1是的处理
 	case 类型2:
 	//变量是类型2是的处理
 	case nil:
 	//空接口进入此流程
 	default:
 	//变量不是所有case中列举的类型时的处理
}

代码:

func test08(i interface{}) {
	switch i.(type) { //默认为空
	case string:
		fmt.Println("string")
	case int:
		fmt.Println("int")
	case bool:
		fmt.Println("bool")
	case nil:
		fmt.Println("nil")
	}
}
func main() {
	test08(nil)
	test08(true)
	test08(123)
	test08("haha")
}

type别名

自定义类型

package main

import "fmt"

type Diyint int

代码:

package main

import "fmt"

//同过type关键字的定义,DiyInt就是一种新的类型,它具有int的特性
type DiyInt int
func main()  {
   var a DiyInt=10
   var b int=20
   c:=int(a)+b //需要进行转换才能参与运算
   fmt.Println(c)
}

类型别名

若想要计算使用,需要进行转换,所以为了省点麻烦,在函数里进行赋值定义

package main

import "fmt"

func main() {
	//定义int类型的别名为myInt
	//MyInt类型只会在代码中存在,编译完成并不会有myint类型
	type myint=int
	var d myint=40
	var e  int=50
	fmt.Println(d+e)
}

错误与异常

什么是错误

什么是错误

错误指的是可能出现问题的地方出现了问题。比如打开一个文件时失败,这种情况在人们的意料之中。
和错误类似的还有一个异常,指的是不应该出现问题的地方出现了问题。比如引用了空指针,这种情况在人们的意料之外。可见,错误是业务过程的一部分,而异常不是。

打开文件,存在错误情况

image-20230112160041874

image-20230112160137322

package main

import (
	"fmt"
	"os"
)

func main() {
	//打开一个文件,当前并没有aaa.txt所以会报错
	file, error := os.Open("aaa.txt")
	if error != nil {
		fmt.Println(error) //open aaa.txt: The system cannot find the file specified.
		return             //return 默认返回为nil
	} else {
		fmt.Println(file.Name())
	}
}

在实际工程项目中,我们希望通过程序的错误信息快速定位问题,但是又不喜欢错误处理代码写的冗余而又啰嗦。

Go语言没有提供像Java.c#语言中的try..catch异常处理方式,而是通过函数返回值逐层往上抛。

这种设计,鼓励工程师在代码中显式的检查错误,而非忽略错误

好处就是避兔漏掉本应处理的错误。但是带来一个弊端,让代码繁琐。

Go中的错误也是一种类型。错误用内置的error类型表示。就像其他类型的,如int, float64。错误值可以存储在变量中,从函数中返回,等等。

返回错误error

//创建一个自己的error错误信息,通过errors可以创建
1、errors.New("我是一个错误信息")

//也可以通过fmt创建
2、fmt.Errorf("我是一个错误信息%T", 500)
package main

import (
	"errors"
	"fmt"
)

/*
1、通过errors.New创建错误信息
2、定义setage函数
3、err接收errors.New返回的错误
4、还可以通过fmt创建
*/

func main() {
	//1、通过errors.New创建
	errorinfo := errors.New("我是一个错误信息")
	fmt.Printf("%T\n", errorinfo)

	//3、err接收errors.New返回的错误
	err := setage(-1)
	if err != nil {
		fmt.Println(err)
	}

	//4、还可以通过fmt创建
	errorinfo2 := fmt.Errorf("我是一个错误信息%T", 500)
	fmt.Println(errorinfo2)
}

// 2、定义setage函数
func setage(age int) error {
	if age < 0 {
		return errors.New("年龄输入不合法")
	} else {
		fmt.Println(age)
		return nil
	}
}

/*
*errors.errorString
年龄输入不合法
我是一个错误信息int
*/

错误类型

error类型是一个接口类型

type error interface{
	Error()string
}

通过断言,来判断具体的错误类型,然后进行对应的错误处理

package main

import (
	"fmt"
	"os"
)

/*
1、将Dome01的代码粘贴
2、通过断言来判断错误类型
*/
func main() {
	//1、将Dome01的代码粘贴
	file, error := os.Open("aaa.txt")//打开一个文件,当前并没有aaa.txt所以会报错
	if error != nil {
		fmt.Println(error) //open aaa.txt: The system cannot find the file specified.
	
		//2、通过断言来判断错误类型
		temp, ok := error.(*os.PathError)
		if ok {
			fmt.Println(temp.Op)
			fmt.Println(temp.Err)
			fmt.Println(temp.Path)
		}
		
		
		return //return 默认返回为nil
	} else {
		fmt.Println(file.Name())
	}
}
/*
open aaa.txt: The system cannot find the file specified.
open
The system cannot find the file specified.
aaa.txt
*/
package main

import (
   "fmt"
   "net"
)

func main() {

   addrs, err := net.LookupHost("www.baidu.com")
   fmt.Println(err)
   dnsError, ok := err.(*net.DNSError)
   if ok {
      if dnsError.Timeout() {
         fmt.Println("超时")
      } else if dnsError.Temporary() {
         fmt.Println("临时错误")
      } else {
         fmt.Println("其他错误")
      }
   }
   fmt.Println(addrs)
}
/*
<nil>
[14.215.177.38 14.215.177.39]
*/

自定义错误类型

package main

import "fmt"

/*
1、自定义一个错误结构体
2、定义一个方法,接收者为自定义结构体的对象,返回信息类型为string
3、定义测试方法
4、调用测试方法并接收错误信息,输出
*/

// 1、自定义一个错误结构体
type Mydiyerror struct {
	msg  string
	code int
}

// 2、定义一个方法,接收者为自定义结构体的对象,返回信息类型为string
func (e Mydiyerror) Error() string {
	return fmt.Sprint("错误信息", e.msg, "状态码", e.code)
}

// 3、定义测试方法
func test04(i int) (int, error) {
	if i != 0 {
		return i, &Mydiyerror{"非零数据", 500}
	}
	return i, nil
}
func main() {
	//4、调用测试方法并接收错误信息,输出
	i, err := test04(1)//传入0或者其他数测试
	if err != nil {
		myerr, ok := err.(*Mydiyerror)
		if ok {
			fmt.Println(myerr.msg)
			fmt.Println(myerr.code)
		}
	}
	fmt.Println(i)
}

panic和recover

go语言追求简洁,所以go语言中没有try...catch语句

因为go语言的作者认为将异常和控制语句混在一起,很容易让这个程序变得混乱,异常也很容易被滥用。

所以在go语言中,为了防止异常被滥用,我们常常使用函数的返回值来返回错误,而不是用异常来代替错误

如果在一些场景下确实需要处理异常,就可以使用panic和recover

panic用来抛出异常,recover用来恢复异常

panic

如果函数F中书写并触发了panic语句,会终止其后要执行的代码。在panic所在函数F内如果存在要执行的defer函数列表,则按照defer书写顺序的逆序执行

package main

import "fmt"
/*
1、创建测试方法,执行defer和正常语句 观察异常出现后执行情况
2、调用测试方法,在主方法中统一再添加defer和正常语句 观察异常出现后执行情况
*/

//1、创建测试方法,执行defer和正常语句 观察异常出现后执行情况
func test05(num int) {
	defer fmt.Println("test---1")
	defer fmt.Println("test---2")
	fmt.Println("test---3")
	if num == 1 {
		panic("出现异常")
	}
	fmt.Println("test---4")
	defer fmt.Println("test---5")
}

//2、调用测试方法,在主方法中统一再添加defer和正常语句 观察异常出现后执行情况
func main() {
	defer fmt.Println("main---1")
	defer fmt.Println("main---2")
	fmt.Println("main---3")
	test05(1)
	fmt.Println("main---4")
	defer fmt.Println("main---5")
}
/*
main---3
test---3
test---2
test---1
main---2
main---1
panic: 出现异常
*/

recover

recover的作用是捕获panic,从而恢复正常代码执行

recover必须配合defer使用

recover没有传入的参数,但是有返回值,返回值就是panic传递的值

package main

import "fmt"

/*
1、导入上一个Dome
2、创建一个匿名函数  使用recover覆盖,defer执行
*/


//1、导入上一个Dome
func test05(num int) {
	//2、创建一个匿名函数  使用recover覆盖,defer执行
	defer func() {
		msg := recover()
		if msg != nil {
			fmt.Println("程序恢复了---")
		}
	}()

	defer fmt.Println("test---1")
	defer fmt.Println("test---2")
	fmt.Println("test---3")
	if num == 1 {
		panic("出现异常")
	}
	fmt.Println("test---4")
	defer fmt.Println("test---5")
}


func main() {
	defer fmt.Println("main---1")
	defer fmt.Println("main---2")
	fmt.Println("main---3")
	test05(1)
	fmt.Println("main---4")
	defer fmt.Println("main---5")
}

/*
main---3
test---3
test---2
test---1
程序恢复了---
main---4
main---5
main---2
main---1
*/
  • 触发panic
  • 逆序执行test05函数中的panic前三条的语句
  • 执行到第三条defer时,recover恢复恐慌并输出信息
  • 返回外部函数,程序继续执行

由于recover恢复恐惧,所以程序就不会panic而退出执行,外部函数还能够正常执行下去

Go常用包

Go语言中的包

包的本质:创建不同的文件夹,来存放程序文件。

Go语言的源码复用建立在包 (package)基础之上。

main包

Go语言的入口main()函数所在的包必须是main包。

main包想要引用别的代码,则需要import 导入!

包名为main的包为应用程序的入口包,其他包不能使用。

package

Src目录是以代码包的形式组织并保存Go源码文件的

每个代码包都和src目录下的文件夹——对应,每个子目录都是一个代码包。

代码包包名和文件目录名,不要求一致。比如文件目录叫hello,但是代码包包名可以声明为"main",但是同一个目录下的源码文件第一行声明的所属包,必须一致!

同一个目录下的所有go文件的第一行添加包定义,以标记该文件归属的包,演示语法:

package 包名

关于包的使用:

  • 一个目录下的统计文件归属一个包,package的声明要一致

  • package声明的包和对应的目录名可以不一致,但习惯上还是写成一致的

  • 同包下的函数不要导入包,可以直接使用

  • main包, main()的数所在的包,其他的包不能使用·导入包的时候,路径要从src下抒写。src

  • 对于包外而言,在导入包的时候可以使用"包名.函数名"的方式使用包内函数。fmt

使用包成员之前先要导入包。导入包的关键字时import

package main
//相对路径导入包
import "./pojo/user"
//包的重命名不仅可以用于解决包名冲突,还可以解决包名过长、避免与变量或常量名称冲突等情况。
import		"crypto/rand"	//默认模式: rand.Function包实现了用于加解密的更安全的随机数生成器。
import 	R 	"crypto/rand"	//包的别名:通过别名访问该包下的函数等
import  .	"crypto/rand"	//简便模式:可以直接调用该包下的函数等
import 	_	 "crypto/rand"//匿名导入:仅让该包执行init初始化函数
func main() {

}

Go语言使用名称首字母大小写来判断一个对象(全局变量、全局常量、类型、结构字段、函数、方法)的访问权限,相对于包而言同样如此,包的成员名称首字母大小写决定了该成员的访问权限。首字母大写,可被包外访问,即为public(公开的);首字母小写,则仅包内成员可以访问,即为internal (内部的)

Init()函数

Go语言有一个特殊的函数init,先于main函数执行,实现包级别的—些初始化操作。
init函数会在main函数执行之前进行执行、init用在设置包、初始化交量或者其他要在程序运行前优先完成的引导工作.

即使包被导入多次,初始化只需要—次。

init函数通常被用来

  • 对变量进行初始化
  • 检查/修复程序的状态
  • 注册
  • 运行一次计算

每个源文件中可以包含多个init函数

init不能被引用

image-20230113155956808

package test2

import "fmt"

func init() {
   fmt.Println("test02-->b-->init")
}
package test

import "fmt"
import _ "reviewlesson/lesson10/test2"

func init() {
   fmt.Println("test-a-init")
}
package main

import (
	"fmt"
	_ "reviewlesson/lesson10/test" //匿名导入
)

func main() {
	fmt.Println("mian---")

}
func init() {
	fmt.Println("main-->init")
}

错误 package xxxxxx is not in GOROOT

image-20230113153245143

这里报错表示不再ROOT环境当中,我去设置里面查看这里也没用问题

image-20230113153352675

去网上搜了一圈,突然想到这是第二次复习创的文件,是直接创建的,并没有在goland软件里创建,于是就测试:

image-20230113154039133

image-20230113154125250

成功。。。。。。

image-20230113154213983

init执行顺序

为了使用导入的包,首先必须将其初始化。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。这通过Golang的运行时系统控制,如下图所示:

  • 初始化导入的包(递归导入)
  • 对包块中声明的变量进行计算和分配初始值
  • 执行包中的init函数

image-20221229153219027

包:strings

字符串常用操作

package main
import (
   "fmt"
   "strings"
)
/*
1、定义一个字符串
2、练习string包下的方法:
Contains是否包含指定的内容,返回布尔值
ContainsAny 是否包含指定的任意一个内容,有一个即可返回布尔值
Count 统计指定内容在字符串中出现的次数
HasPrefix 以xx开头
HasSuffix 以xx结尾
Index 寻找指定字符串第一次出现的位置,找到返回下标,找不到返回 -1
IndexAny 寻找指定字符串任意字符第一次出现的位置,找到返回下标,找不到返回-1
LastIndex 寻找指定字符串最后一次出现的位置,找到返回下标,找不到返回-1
Join 用指定符号进行字符串拼接
Split 按照指定符号分割字符
Repeat重复拼接自己
Replace 替换指定字符,n代表替换个数,-1替换全部l
ToUpper 字母转大写  ToLower 字母转小写
截取字符串 str[start:end]
*/
func main() {
   //1、定义一个字符串
   str := "xuexiangban,kuangshenshuo"

   //2、练习string包下的方法:
   // Contains是否包含指定的内容,返回布尔值
   fmt.Println(strings.Contains(str, "k"))  //在str中是否包含k这个字符
   fmt.Println(strings.Contains(str, "ka")) //精确匹配,ka

   // ContainsAny 是否包含指定的任意一个内容,有一个即可返回布尔值
   fmt.Println(strings.ContainsAny(str, "kz")) //有k或者z字符都为true

   // Count 统计指定内容在字符串中出现的次数
   fmt.Println(strings.Count(str, "z")) //0次
   fmt.Println(strings.Count(str, "k")) //1次
   fmt.Println(strings.Count(str, "x")) //2次

   // HasPrefix 以xx开头
   file := "2022.12.29.mp4"
   fmt.Println(strings.HasPrefix(file, "2022"))
   // HasSuffix 以xx结尾
   fmt.Println(strings.HasSuffix(file, ".mp4"))

   // Index 寻找指定字符串第一次出现的位置,找到返回下标,找不到返回 -1
   fmt.Println(strings.Index(str, "k")) //k在13  因为从0开始数
   fmt.Println(strings.Index(str, "z")) //找不到返回-1

   //IndexAny 寻找指定字符串任意字符第一次出现的位置,找到返回下标,找不到返回-1
   fmt.Println(strings.IndexAny(str, "x")) //第一个x 为0
   fmt.Println(strings.IndexAny(str, "n")) //第7个字母第一次出现n ,为6

   //LastIndex 寻找指定字符串最后一次出现的位置,找到返回下标,找不到返回-1
   fmt.Println(strings.LastIndex(str, "x")) //3
   fmt.Println(strings.LastIndex(str, "n")) //20

   // Join 用指定符号进行字符串拼接
   str2 := []string{"a", "b", "c", "d"}
   fmt.Println(strings.Join(str2, "-"))

   // Split 按照指定符号分割字符
 
   str3 := "a-b-c"
   str4 := strings.Split(str3, "-") //要用切片接收
   fmt.Println(str4)

   //Repeat重复拼接自己
   str5 := "a"
   fmt.Println(strings.Repeat(str5, 3)) //重复拼接3次

   // Replace 替换指定字符,n代表替换个数,-1替换全部l
   fmt.Println(strings.Replace(str, "x", "*", 1))  //替换str中x字符为*,替换个数为1
   fmt.Println(strings.Replace(str, "n", "*", -1)) //替换全部的n字符

   //ToUpper 字母转大写  ToLower 字母转小写
   fmt.Println(strings.ToUpper(str))
   fmt.Println(strings.ToLower(str))

   //截取字符串 str[start:end]
   fmt.Println(str[0:3]) //截取0-3个字符
}
/*
true
false
true
0
1
2
true
true
12
-1
0
6
3
20
a-b-c-d
[a b c]
aaa
*uexiangban,kuangshenshuo
xuexia*gba*,kua*gshe*shuo
XUEXIANGBAN,KUANGSHENSHUO
xuexiangban,kuangshenshuo
xue
*/

包:strconv

string convert

字符串和基本类型之间的转换

package main

import (
	"fmt"
	"strconv"
)

/*
1、定义一个字符串
2、使用strconv将string转换为bool
3、使用strconv将string转换为int
4、itoa 将int类型转换为string类型
5、atoi 将string类型转换为int类型
*/
func main() {
	//1、定义一个字符串
	s1 := "true" //这里的true是字符串不能参与if判断
	//2、使用strconv将string转换为bool
	b1, _ := strconv.ParseBool(s1) //传回的erro用_抛弃掉
	fmt.Printf("b1的类型:%T\n", b1)
	if b1 {
		fmt.Println(s1)
	}
	//3、使用strconv将string转换为int
	s2 := "100"
	i1, _ := strconv.ParseInt(s2, 10, 64)
	fmt.Printf("i1的类型:%T\n", i1)
	fmt.Println("i1:", i1)

	//4、itoa 将int类型转换为string类型
	i2 := strconv.Itoa(20)
	fmt.Printf("i2的类型:%T\n", i2)
	fmt.Println("i2:", i2)

	//5、atoi 将string类型转换为int类型
	s3, _ := strconv.Atoi("64")
	fmt.Printf("s3的类型:%T\n", s3)
	fmt.Println("s3:", s3)
}

/*
b1的类型:bool
true
i1的类型:int64
i1: 100
i2的类型:string
i2: 20
s3的类型:int
s3: 64
*/

时间与时间戳

time包

时间和日期是我们编程中经常会用到的,本文主要介绍了Go语言内置的time包的基本用法

time包提供了时间的显示和测量用的函数

获取当前时间

package main

import (
	"fmt"
	"time"
)

/*
1、获取当前时间t1:=time.Now
2、获取年月日t1.Year t1.Month t1.Day
*/
func main() {
	//1、获取当前时间time.Now
	t1 := time.Now()
	fmt.Println(t1)
	//2、获取年月日t1.Year t1.Month t1.Day
	year := t1.Year()
	month := t1.Month()
	day := t1.Day()
	fmt.Printf("%d-%02d-%02d\n", year, month, day)//%02d不足两位时,用0补齐
}

时间格式化

	//时间格式化
	now := time.Now()
	fmt.Println(now.Format("2006-01-02 15:04:05"))
	//显示样式可以自己定义
	fmt.Println(now.Format("2006/01/02 15:04:05"))
	fmt.Println(now.Format("2006-01-02 15:04"))

解析字符串格式的时间

	//时区
	loc, _ := time.LoadLocation("Asia/Shanghai")
	timeobj, _ := time.ParseInLocation("2006-01-02 15:04", "2023-01-13 12:00", loc) //时间格式:2006-01-02 15:04	 要传入的字符串:这里传入2023:01:01 12:00	要格式化的时区:loc
	fmt.Println(timeobj)

时间戳

时间戳是自1970年1月1日(08:00:00GMT)至当前时间的总毫秒数,它也被称为Unix时间戳

	//时间戳
	timestamp := time.Now().Unix()
	fmt.Println(timestamp)           //1673602368 毫秒数
	time2 := time.Unix(timestamp, 0) //将1673602368 毫秒数转换为当前时间2023-01-13 17:32:48
	fmt.Println(time2)

完整代码:

package main

import (
	"fmt"
	"time"
)

/*
1、获取当前时间t1:=time.Now
2、获取年月日t1.Year t1.Month t1.Day
*/
func main() {
	//1、获取当前时间time.Now
	t1 := time.Now()
	fmt.Println(t1)
	//2、获取年月日time.Year time.Month time.Day
	year := t1.Year()
	month := t1.Month()
	day := t1.Day()
	fmt.Printf("%d-%02d-%02d\n", year, month, day) //%02d不足两位时,用0补齐

	//时间格式化
	now := time.Now()
	fmt.Println(now.Format("2006-01-02 15:04:05"))
	//显示样式可以自己定义
	fmt.Println(now.Format("2006/01/02 15:04:05"))
	fmt.Println(now.Format("2006-01-02 15:04"))

	//时区
	loc, _ := time.LoadLocation("Asia/Shanghai")
	timeobj, _ := time.ParseInLocation("2006-01-02 15:04", "2023-01-13 12:00", loc) //时间格式:2006-01-02 15:04	 要传入的字符串:这里传入明年的2023:01:01 12:00:00  	要格式化的时区:loc
	fmt.Println(timeobj)

	//时间戳
	timestamp := time.Now().Unix()
	fmt.Println(timestamp)           //1673602368 毫秒数
	time2 := time.Unix(timestamp, 0) //将1673602368 毫秒数转换为当前时间2023-01-13 17:32:48
	fmt.Println(time2)
}

随机数生成

package main

import (
   "fmt"
   "math/rand"
   "time"
)

func main() {
   //rand.Seed(5)  //Seed;种子 ,种子不一样,生成的随机数也就不同
   //i1 := rand.Int() //如果设置的数字没有改变,那么产生的随机数也不会改变
   //fmt.Println(i1)
   //i2 := rand.Intn(20) //Intn [0-19)随机产生
   //fmt.Println(i2)
   
   rand.Seed(time.Now().Unix()) //时间是不停的变化的,所以,这里生成的种子也不停变换,也就能生成随机数
   for i := 0; i < 10; i++ {
      //20-29
      num := rand.Intn(10) + 20
      fmt.Println("num=", num)
   }
}

定时器与时间判断

时间间隔常量Duration

utime.Duration是time包定义的一个类型,它代表两个时间点之间经过的时间以纳秒为单位,可表示的最长时间段大约290年。
utime包中定义的时间间隔类型的常量如下:

const (
Nanosecond Duration = 1
Microsecond		= 1000 * Nanosecond
Millisecond		= 1000 * Microsecond
Second			= 1000 * Mi77isecond
Minute			= 60   * Second
Hour			= 60   * Minute
)

time.Duration表示1纳秒 time.second表示1秒

定时器

package main

import (
	"fmt"
	"time"
)

/*
1、每间隔一秒输出当前时间
2、输出10秒钟每一秒的时间
*/
func main() {
	//Tick 心脏 心跳
	//1、每间隔一秒输出当前时间
	/*
		tick := time.Tick(time.Second) //Second以秒为单位
		//需要手动暂停
		for t := range tick {
			fmt.Println(t)
		}
	*/

	//2、输出10秒钟每一秒的时间
	for i := 0; i < 10; i++ {
		fmt.Println(time.Now())
		time.Sleep(time.Second)
	}
}

Sub:求两个时间之间的差值

Equal:判断两个时间是否相同,这种方法还会比较地点和时区信息,因此不同时区标准的时间也可以正确比较。

Before:如果t代表的时间点在u之前,返回真;否则返回假。

After:如果t代表的时间点在u之后,返回真;否则返回假。

package main

import (
	"fmt"
	"time"
)

/*
1、输出当前时间和一小时后时间 Now
2、输出时间差 Sub
3、判断时间是否相同 Equal
*/
func main() {
	//1、输出当前时间和一小时后时间 Now
	now := time.Now()           //当前时间
	later := now.Add(time.Hour) //添加一小时
	fmt.Println(now)            //2023-01-13 18:23:27.5599426 +0800 CST m=+0.006475701
	fmt.Println(later)          //2023-01-13 19:23:27.5599426 +0800 CST m=+3600.006475701

	//2、输出时间差 Sub
	subtime := later.Sub(now) //一小时后的时间与现在时间的时间差
	fmt.Println(subtime)      //1h0m0s

	//3、判断时间是否相同 Equal
	fmt.Println(now.Equal(now))   //判断当前时间now是否相同
	fmt.Println(now.Equal(later)) //判断当前时间now与一小时后是否相同
}
/*
2023-01-13 18:28:57.9653867 +0800 CST m=+0.006707701
2023-01-13 19:28:57.9653867 +0800 CST m=+3600.006707701
1h0m0s
true
false
*/

Go I/O

i:input 读

o:output 写

01010101 流

获取文件信息

计算机中的文件是存储在外部介质(通常是磁盘)上的数据集合,文件分为文本文件和二进制文件。file类是在os包中的,封装了底层的文件描述符和相关信息,同时封装了Read和Write的实现。

Fileinfo接口定义了file信息相关的方法

package main

/*
1、在包下创建一个a.txt文件
2、查看文件的状态 stat
3、打印输出文件的信息
*/
import (
	"fmt"
	"os"
)

func main() {
	//2、查看文件的状态 stat
	fileinfo, error := os.Stat("D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\a.txt")//这里版本问题使用相对路径会出错,下面会提到,所以直接选择绝对路径
	if error != nil {
		fmt.Println(error)
		return
	}
	//3、打印输出文件的信息
	fmt.Println(fileinfo.Name())    //文件的名称
	fmt.Println(fileinfo.Size())    //文件大小、字节
	fmt.Println(fileinfo.Mode())    //文件读写属性
	fmt.Println(fileinfo.ModTime()) //文件的修改时间
	fmt.Println(fileinfo.Sys())     //反射获取文件更加详细的信息
}
/*
a.txt
5
-rw-rw-rw-
2023-01-13 21:02:09.5708867 +0800 CST
&{32 {975102818 31008591} {1055668931 31008591} {1055668931 31008591} 0 5}
*/

错误信息:CreateFile ./a.txt: The system cannot find the file specified.

image-20230113211636513

这是因为版本问题,但也可以执行,在Terminal下进入当前目录

输入命令 go run Dome01.go就可以执行了

image-20230113211309792

image-20230113211449703

科普:权限

linux下有两种文件权限表示方式,符号和八进制表示

符号表示:

-		---		---		---
type	owner	group	others
type:文件类型 - 文件 d 目录|连接符号
---,代表的文件读写可执行 字母 r w x,如果额米有那个权限就用 - 代替

八进制表示

r 004
w 003
x 001
- 000

比如 777 就是可读可写可执行

创建目录与文件

package main

import (
	"fmt"
	"os"
)

/*
1、创建文件目录 os.Mkdir
2、创建层级文件目录 os.MkdirAll
3、删除单个文件目录 os.Remove
4、删除层级文件目录 os.RemoveAll
*/
func main() {
	//1、创建文件目录 os.Mkdir
	//err := os.Mkdir("D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\test", os.ModePerm) //权限:ModePerm可读可写
	//if err != nil {
	//	fmt.Println(err)
	//	return
	//}
	//fmt.Println("文件创建成功")

	//2、创建层级文件目录 os.MkdirAll
	err2 := os.MkdirAll("D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\test2\\a\\b\\c", os.ModePerm)
	if err2 != nil {
		fmt.Println(err2)
		return
	}
	fmt.Println("层级文件创建成功")

	//3、删除单个文件目录 os.Remove
	err3 := os.Remove("D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\test")
	if err3 != nil {
		fmt.Println(err3)
		//return
	}
	fmt.Println("删除成功")

	//4、删除层级文件目录 os.RemoveAll
	err4 := os.RemoveAll("D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\test2")
	if err4 != nil {
		fmt.Println(err4)
		return
	}
	fmt.Println("层级目录删除成功")
}

  • 创建单个文件目录

未执行前:

image-20230114134045560

执行后:

image-20230114134128382

再次执行后:这里提示错误信息,文件已存在

image-20230114134204886

  • 层级目录创建

image-20230114134906431

创建文件和删除文件

package main

import (
	"os"
)

/*
1、创建文件 os.Create
2、删除文件 os.Remove
*/
func main() {
	//1、创建文件 os.Create
	/*
	f1, err := os.Create("lesson11/a.txt")
	if err != nil {
		fmt.Println(err)
	return
	}
	fmt.Println(f1.Name())
	*/
	
	//2、删除文件 os.Remove
	os.Remove("D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\a.txt")
}

  • 创建文件执行前:

image-20230114140726418

  • 创建文件执行后

image-20230114140749903

I/0读

打开文件

读取a.txt文件中的数据

package main

import (
	"fmt"
	"os"
)

/*
1、查看文件权限 os.Stat
2、打开文件,建立连接 os.open
3、读取b.txt信息 o1.Read 
4、将1和2联系---也可以通过对文件进行指定的连接 os.OpenFile
*/
func main() {
	//1、查看文件权限 os.Stat
	fileinfo, err := os.Stat("D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\b.txt")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(fileinfo.Mode()) //权限:Mode -rw-rw-rw-
	//2、打开文件,建立连接 os.open
	o1, err2 := os.Open("D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\b.txt")
	if err2 != nil {
		fmt.Println(err2)
	}
	fmt.Println(o1.Name())
	defer o1.Close() //延时关闭

	//3、读取b.txt信息 o1.Read
	bs := make([]byte, 1, 1024)
	n, err3 := o1.Read(bs) //传入需要一个切片
	if err3 != nil {
		fmt.Println(err3)
		return
	}
	fmt.Println(n)          //读取到个数 一次可以读取到1个信息
	fmt.Println(string(bs)) //将读取到的信息转换为字符串

	//4、将1和2联系---也可以通过对文件进行指定的连接 os.OpenFile
	o2, err4 := os.OpenFile("D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\b.txt", os.O_RDONLY|os.O_WRONLY, os.ModePerm) //os.O_RDONLY|os.O_WRONLY 可读可写
	if err4 != nil {
		fmt.Println(err4)
		return
	}
	fmt.Println(o2.Name())
	defer o2.Close()
}

I/O写

package main

import (
	"fmt"
	"os"
)

/*
1、通过 os.OpenFile 打开文件,建立连接 file
2、向文件里面写入信息 file.Write
3、字符串写入 file.WriteString
*/
func main() {
	//1、通过 os.OpenFile 打开文件,建立连接 file
	file, err := os.OpenFile("D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\b.txt", os.O_WRONLY|os.O_RDONLY, os.ModePerm)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(file.Name())
	defer file.Close()

	//2、向文件里面写入信息 file.Write
	bs := []byte{65, 66, 67} //将ABC写入
	n, _ := file.Write(bs) //为了更好演示,这里将错误统一抛弃掉
	fmt.Println(n)
	//3、字符串写入 file.WriteString
	n2, _ := file.WriteString("xuexiangban")//字符串输入
	fmt.Println(n2)

}

文件复制

package main

import (
	"fmt"
	"io"
	"os"
)

/*
一:自定义copy方法
1、定义copy方法
2、指定目标文件地址dastination 复制文件地址soure
3、输入文件 Open
4、输出文件 OpenFile
5、设置缓冲区 buf	读取Read和写出Write
6、调用自定义copy方法
二:系统copy方法 
7、定义copy2方法
8、输入文件 Open
9、输出文件 OpenFile
10、使用系统的copy方法
11、main函数里调用copy2
*/
func main() {
	//2、指定目标文件地址dastination 复制文件地址soure
	soure := "D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\1.jpg"
	destination := "D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\3.jpg"
	
	//6、调用自定义copy方法
	copy(destination, soure, 1024)
	
	//11、main函数里调用copy2
	copy2(destination, soure)
}

// 1、自定义copy方法
func copy(destination, soure string, bufsize int) {
	//3、输入文件 Open
	sourefile, err := os.Open(soure)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer sourefile.Close()

	//4、输出文件 OpenFile
	destinationfile, err := os.OpenFile(destination, os.O_WRONLY|os.O_CREATE, os.ModePerm) //如果没有文件则自动创建
	if err != nil {
		fmt.Println(err)
		return
	}
	defer destinationfile.Close()

	//5、设置缓冲区 buf	读取Read和写出Write
	buf := make([]byte, bufsize)
	for {
		//读取
		n, err := sourefile.Read(buf)
		if err == io.EOF || n == 0 {
			fmt.Println("复制完毕")
			break
		}
		//写出
		_, err = destinationfile.Write(buf[:n])
		if err != nil {
			fmt.Println("写入失败", err)
		}
	}
}

//二:系统copy方法

//7、定义copy2方法
func copy2(destination, soure string) {
	//8、输入文件 Open
	sourefile, err := os.Open(soure)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer sourefile.Close()

	//9、输出文件 OpenFile
	detinationfile, err := os.OpenFile(destination, os.O_WRONLY|os.O_CREATE, os.ModePerm)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer detinationfile.Close()

	//10、使用系统的copy方法
	witten, error := io.Copy(detinationfile, sourefile)
	if error != nil {
		fmt.Println(error)
	}
	fmt.Println("文件大小:", witten)
}

Seeker接口

Seeker是包装Seek方法的接口

type Seeker interface{
	Seek(offset int 64,whence int)(int64,error)
}

Seek(offset,whence),设置指针光标的位置,读写文件:

第一个参数:偏移量

第二个参数:如何设置

0:seekStart  表示相对于文件开头
1:seekCurrent	表示相对于当前光标所在位置来说的
2:seekend		表示相对于文件末尾
package main

import (
	"fmt"
	"io"
	"os"
)
/*
1、打开文件os.OpenFile 权限为:可读可写可执行
2、读取光标位置后的信息 Seek
*/
func main() {
	//1、打开文件os.OpenFile 权限为:可读可写可执行
	file, _ := os.OpenFile("D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\b.txt", os.O_RDWR, os.ModePerm)
	defer file.Close()
	
	//2、读取光标位置后的信息 Seek
	//从光标开头向后移3位开始读
	file.Seek(3, io.SeekStart)
	buf := []byte{0}
	file.Read(buf)
	fmt.Println(string(buf))

	//从光标的当前位置 向后移5位开始读
	file.Seek(5, io.SeekCurrent) 
	file.Read(buf)
	fmt.Println(string(buf))

	//从光标的当前位置,不再向后移
	file.Seek(0, io.SeekEnd) 
	file.WriteString("kaibai")//追加内容
}

断点续传

首先思考几个问题

Q1:如果要传的文件比较大,那么是否有方法可以缩短耗时

Q2:如果在文件传递过程中,程序因各种原因被迫中断了,那么下次重启时,文件是否还需要重头开始

Q3:传递文件的时候,支持暂停和恢复吗?即使这两个操作分布在程序进程被杀前后

当然这些都是可以通过Seek()方法如何实现

先说一下思路:想实现断点续传,主要就是记住上一次已经传递了多少数据,那么我们可以创建一个临时文件,记录已经传递的数据量,当恢复传递的时候,先从临时文件中读取上次已经传递的数据量,然后通过Seek()方法,设置到该读和改写的位置,再继续传递数据

package main

import (
	"fmt"
	"io"
	"os"
	"strconv"
)

/*
1、定义 源文件 目的地 临时文件的位置
2、与文件建立连接读取
3、读取临时文件的数据
4、读出和写入
5、模拟断电
*/
func main() {
	//1、定义 源文件 目的地 临时文件的位置
	srcfile := "D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\img\\4.jpg"     //传输源文件
	destfile := "D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\seek\\4_3.jpg" //传输目的地
	tempfile := "D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11\\tmp.txt"       //临时文件:记录传输的大小

	//2、与文件建立连接读取
	file1, _ := os.Open(srcfile)
	file2, _ := os.OpenFile(destfile, os.O_RDWR|os.O_CREATE, os.ModePerm)
	file3, _ := os.OpenFile(tempfile, os.O_RDWR|os.O_CREATE, os.ModePerm)

	defer file1.Close() //延时关闭
	defer file2.Close() //延时关闭

	//3、读取临时文件的数据
	file3.Seek(0, io.SeekStart) //光标开头 SeekStart
	buf := make([]byte, 1024, 1024)
	n, _ := file3.Read(buf)          //读取到的总个数n
	counstr := string(buf[:n])       //从0开始读取到n
	fmt.Println("counstr:", counstr) //第一次执行:创建tmp.txt  第二次执行:读取tmp.txt里面的信息

	//4、读出和写入
	count, _ := strconv.ParseInt(counstr, 10, 64) //获取到的大小类型为string需要转换一下
	bufData := make([]byte, 1024, 1024)
	total := int(count) //total 统计传输的数据

	for {
		randnum, err := file1.Read(bufData) //randnum 读取到的数据
		if err == io.EOF {
			fmt.Println("读取完毕")
			file3.Close()
			os.Remove(tempfile) //读取完毕后清空
			break
		}
		witenum, _ := file2.Write(bufData[:randnum]) //bufData[:randnum]从0到总共的数据
		total = total + witenum
		file3.Seek(0, io.SeekStart)
		file3.WriteString(strconv.Itoa(total))
	}
	//5、模拟断电
	//if total > 3000 {
	//	panic("断电------")
	//}
}

包:bufio

Go语言在IO操作中,还提供了一个bufIO的包,使用这个包可以大幅提高文件的读写效率

bufio包

bufio是通过缓冲来提高效率

io操作本身的效率并不低,低的是频繁的访问本地磁盘的文件,所以bufio就提供了缓冲区(分配一块内存),读和写都在缓冲区中,最后再读写文件,来降低访问本地磁盘的次数,从而提高效率

简单的来说就是,把文件读取进缓冲(内存)之后再读取的时候就可以避免文件系统的io从而提高速度。同理,在进行写操作时,先把文件写入缓冲(内存),然后由缓冲写入文件系统。看完以上解释有人可能会表示困惑,直接把内容->文件和内容->缓冲->文件相比,缓冲区好像没有起到作用,其实缓冲区的设计是为了存储多次的写入,最后一口气把缓冲区内容写入文件

image-20221231125945529

package main

import (
   "bufio"
   "fmt"
   "os"
)
/*
1、打开目录下的a.txt————os.Open
2、通过bufio.NewReader包装
3、读取键盘的输入 os.Stdin
*/
func main() {
    //1、打开目录下的a.txt————os.Open
   file1, _ := os.OpenFile("D:\\Environment\\GoWorks\\src\\awesomeProject\\lesson10\\a.txt", os.O_RDWR, os.ModePerm)
   defer file1.Close()

   //2、通过bufio.NewReader包装
   file2 := bufio.NewReader(file1)//NewReader:包装

   buf := make([]byte, 1024)
   n, _ := file2.Read(buf)

   fmt.Println((n))
   fmt.Println(string(buf[:n])) //从buf里面获取信息,通过string转换,从0:n输出信息

   //读取键盘的输入 os.Stdin
   /*
   inputreadr := bufio.NewReader(os.Stdin)
   str, _ := inputreadr.ReadString('\n') //当输入回车时,打印出结果
   fmt.Println("读取到键盘的信息为:", str)
   */

   //3、读取键盘的输入 os.Stdin
   writer := bufio.NewWriter(file1)
   s, _ := writer.WriteString("Hello")
   fmt.Println("s:", s)
   writer.Flush() //发现并没有写入到文件,是留在了缓冲区,所以我们需要使用flush刷新缓冲区
  
}

遍历文件夹

package main

import (
	"fmt"
	"os"
)

/*
1、定义函数listdir 递归遍历文件目录
2、读取当前目录 os.ReadDir
3、mian方法调用
*/
func main() {
	//3、mian方法调用
	listdir("D:\\Environment\\GoWorks\\src\\reviewlesson\\lesson11")
}
//1、定义函数listdir 递归遍历文件目录
func listdir(filepath string) {
	dir := filepath
	//2、读取当前目录 os.ReadDir
	fileinfos, _ := os.ReadDir(dir) //ReadDir读取当前目录
	for _, file := range fileinfos {
		filname := dir + "\\" + file.Name()
		fmt.Println(filname)

		if file.IsDir() { //如果是目录
			listdir(filname)
		}
	}
}

Go反射

什么是反射

反射可以再运行时动态的获取变量的信息,比如变量的类型、值等

如果是结构体,还可以获取到结构体本身的各种信息,比如结构体的字段和方法

通过反射,还可以修改变量的值、调用方法

使用反射,需要引入一个包: reflect,它定义了两个重要的类型Type和Value任意接口值在反射中都可以理解为由 reflect.Type和reflect.Value两部分组成,并且reflect包提供了reflect.TypeOf 和reflect.ValueOf两个函数来获取任意对象的Value和Type。

在使用反射时,需要首先理解类型(Type) 和种类(Kind)的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个品种的类型时,就会用到种类(Kind) .

静态类型&动态类型

在反射的概念中,编译时就知道变量的是静态类型,运行时才知道一个变量类型叫做动态类型

静态类型:就是变量声明时赋予的类型

var name string //string就是静态类型
var age int	//int就是静态类型

动态类型:在运行是可能改变,这主要依赖于它的赋值

var A interface // 静态类型为 interface{}
A=10//静态类型为interface{} 动态类型为int
A="string"//静态类型为interface{} 动态类型string

为什么要用反射

需要反射的2个常见场景:
1.有时你需要编写一个函数,但是并不知道传给你的数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
⒉.有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。
但是对于反射,还是有几点不太建议使用反射的理由:
1、与反射相关的代码,经常是难以阅读的。在软件工程中,代码可读性也是一个非常重要的指标。
2、Go语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接panic,可能会造成严重的后果。
3、反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。

反射获取变量的信息

/*
获取字段
	1、先获取type对象,reflect.type
	Numfild() 获取有几个字段
	filed(index) 得到字段的值
	2、通过Filed获取没一个Filed字段
	3、interface(),得到对象的value

*/
package main

import (
   "fmt"
   "reflect"
)

type User struct {
   Name string
   Age  int
   Sex  string
}

func (use User) Say(msg string) {
   fmt.Println("User说:", msg)
}
func (user User) PrintInfo() {
   fmt.Printf("姓名:%s,年龄:%d,性别:%s\n", user.Name)
}

func main() {
   user := User{"kuangshen", 18, "男"}
   reflectinfo(user)

}
func reflectinfo(inter interface{}) {
   //获取参数类型
   gettype := reflect.TypeOf(inter)
   fmt.Println("get type is:", gettype.Name())
   fmt.Println("get kind is:", gettype.Kind())

   // 获取值
   getvalue := reflect.ValueOf(inter)
   fmt.Println("get value is:", getvalue)

   /*
      获取字段
         1、先获取type对象,reflect.type
         Numfild() 获取有几个字段
         filed(index) 得到字段的值
         2、通过Filed获取没一个Filed字段
         3、interface(),得到对象的value

   */
   for i := 0; i < gettype.NumField(); i++ {
      filed := gettype.Field(i)//gettype.Field字段的个数
      value := getvalue.Field(i).Interface()
      fmt.Printf("字段名:%s,字段类型:%s,字段值%v\n", filed.Name, filed.Type, value)
   }

   for i := 0; i < gettype.NumMethod(); i++ {
      method := gettype.Method(i)
      fmt.Printf("方法名:%s,方法类型%v\n", method.Name, method.Type)
   }
}

反射设置变量的值

package main

import (
	"fmt"
	"reflect"
)

/*
1、定义结构体
2、创建对象
3、通过反射获取对象的指针
4、判断种类是否为指针  指针---引用类型
5、判断值是否可以修改
*/

// 1、定义结构体
type User2 struct {
	Name string
	Age  int
	Sex  string
}

func main() {
	//2、创建对象
	user2 := User2{"kuangshen", 18, "男"}
	fmt.Println(user2)
	//3、通过反射获取对象的指针
	value := reflect.ValueOf(&user2)
	//4、判断种类是否为指针  指针---引用类型
	if value.Kind() == reflect.Ptr {
		newvalue := value.Elem()
		//5、判断值是否可以修改
		if newvalue.CanSet() {
			newvalue.FieldByName("Name").SetString("秦疆")
			newvalue.FieldByName("Age").SetInt(27)
		}
	}
	fmt.Println(user2)
}

反射调用的方法

package main

import (
	"fmt"
	"reflect"
)

/*
1、利用反射 无参方法的调用
2、利用反射 有参方法的调用
*/
type Users3 struct {
	Name string
	Age  int
	Sex  string
}

func (user3 Users3) PrinInfo() {
	fmt.Printf("姓名:%s,年龄:%d,性别:%s\n", user3.Name, user3.Age, user3.Sex)
}
func (user3 Users3) Say3(msg string) {
	fmt.Println("User说:", msg)
}

func main() {
	user3 := Users3{"kuangshen", 18, "男"}
	fmt.Println(user3)

	//1、利用反射 无参方法的调用
	value := reflect.ValueOf(user3)
	value.MethodByName("PrinInfo").Call(nil)

	//2、利用反射 有参方法的调用
	agrs := make([]reflect.Value, 1)
	agrs[0] = reflect.ValueOf("反射来调用实现的")
	value.MethodByName("Say3").Call(agrs)
}

这里报错:panic: reflect: call of reflect.Value.Call on zero Value

image-20230119205238778

方法首字母大写!!!

image-20230119205324662

image-20230119205405256

Go泛型1.18+

什么是泛型

Go并不是一种静止、一成不变的编程语言。新的功能是在经过大量的讨论和试验后慢慢采用的

最初的Go1.0发布以来,Go语言习惯的模式已经发生了重大变化

1.7的context.1.11的modules、1.13erro嵌套等

Go的1.18版本包括了参数类型参数的实现,也就是俗称的泛型

泛型虽然很受期待,但实际上推荐的使用场景也并没有那么广泛

但是我们作为学习者,一定要了解学会,至少遇到了不懵逼

package main

import "fmt"
/*
1、创建泛型函数,其约束类型为string和int类型
2、forage循环遍历
3、打印输出
*/
func main() {
	str := []string{"xuexiangban", "kuangshenshuo"}
	is := []int{1, 2, 3}
	//3、打印输出
	printarr2(str)
	printarr2(is)
}
//1、创建泛型函数,其约束类型为string和int类型
func printarr2[T string | int](arr []T) {
	//2、forage循环遍历
	for _, i := range arr {
		fmt.Println(i)
	}
}

泛型类型

观察下面这个简单的例子

type s1 []int
var a s1 = []int{1,2,3}//正确
var b s1 = []float32{1.0,2.0,3.0}//X错误,因为IntS1ice的底层类型是[]int,浮点类型的切片无法赋值

这里定义了一个新的类型Intslice,它的底层类型是[]int,理所当然只有int类型的切片能赋值给IntSlice类型的变量。
接下来如果我们想要定义一个可以容纳float32或string等其他类型的切片的话该怎么办?很简单,给每种类型都定义个新类型:

type si []int
type s2 []f1oat32
type s3 []f1oat64

但是这样做的问题显而易见,它们结构都是一样的只是成员类型不同就需要重新定义这么多新类型。那么有没有一个办法能只定义一个类型就能代表上面这所有的类型呢?答案是可以的,这时候就需要用到泛型了:

type slice[T int|float32|float64][]T

不同于一般的类型定义,这里类型名称Slice后带了中括号,对各个部分做一个解说就是:

  • T就是上面介绍过的类型形参(Type parameter),在定义Slice类型的时候T代表的具体类型并不确定,类似一个占位符
  • int float32|float64这部分被称为类型约束(Type constraint),中间的│的意思是告诉编译器,类型形参T只可以接收 int l或float32或float64这三种类型的实参
  • 中括号里的Tintlfloat32/float64这一整串因为定义了所有的类型形参(在这个例子里只有一个类型形参T),所以我们称其为类型形参列表(type parameter list)
  • 这里新定义的类型名称叫Slice[T]
    这种类型定义的方式中带了类型形参,很明显和普通的类型定义非常不一样,所以我们将这种类型定义中带类型形参的类型,称之为泛型类型
package main

import "fmt"

/*
1、定义泛型结构体
2、传入类型切片
3、定义一个map泛型
*/
func main() {
   //1、定义泛型结构体
   type Slice[T string | int | float64] []T
   //2、传入类型切片
   var a Slice[int] = []int{1, 2, 3}
   fmt.Println(a)
   fmt.Printf("Type Name:%T", a)

   var b Slice[string]
   b = []string{"1, 2, 3"}
   fmt.Println(b)
   fmt.Printf("Type Name:%T", b)

   var c Slice[float64] = []float64{1, 2, 3}
   fmt.Println(c)
   fmt.Printf("Type Name:%T", c)
    
   //×错误 float32不再类型约束[T string | int | float64]没有floato32
   //var d Slice[float32] = []float32{1, 2, 3}
   //fmt.Println(d)
   //fmt.Printf("Type Name:%T", d)
   //×错误 slice[T]是泛型类型,不可直接使用必须实例化为具体的类型
   //var a Slice[T] = []int{1, 2, 3}

   //3、定义一个map泛型
   type Mymap[Key string | int, value float32 | float64] map[Key]value
   var m1 Mymap[string, float64] = map[string]float64{
      "Go":   9.0,
      "Java": 8.0,
   }
   fmt.Println(m1)
}
KEY和VALUE是类型形参
int|string 是KEY的类型约束, float32 | float64是VALUE的类型约束
KEY int|string, VALUE float32| float64整个一串文本因为定义了所有形参所以被称为类型形参列表Map[KEY,VALUE]是泛型类型,类型的名字就叫Map[KEY,VALUE]
var a MyMap[string,float64]= xx 中的string和float64是类型实参,用于分别替换KEY和VALUE,实例化出了具体的类型MyMap[string, float64]

泛型函数

这种带类型形参的函数被称为泛型函数

它和普通函数的点不同在于函数名之后带了类型形参。这里的类型形参的意义、写法和用法因为与泛型类型是一模一样的,就不再赘述了.和泛型类型一样,泛型函数也是不能直接调用的,要使用泛型函数的话必须传入类型实参之后才能调用。

Go的泛型(或者或类型形参)目前可使用在3个地方

1、泛型类型-类型定义中带类型形参的类型

2、泛型receiver-泛型类型的receiver

3、泛型函数–带类型形参的函数

package main

import "fmt"
/*
package main

/*
1、定义泛型切片
2、定义泛型函数 传参类型为泛型
3、泛型方法 约束int float64 string类型
4、打印输出
*/

// 1、定义泛型切片
type MySlice[T int | float64] []T

// 2、定义泛型函数 传参类型为泛型
func (s MySlice[T]) Sum() T {
   var sum T
   for _, v := range s {
      sum += v
      fmt.Println(s)
   }
   return sum
}
func main() {
    //4、打印输出
   var s MySlice[int] = []int{1, 2, 3, 4}
   fmt.Println(s.Sum())

   var f MySlice[float64] = []float64{1.0, 2.3, 3.5}
   fmt.Println(f.Sum())

   fmt.Println(add2(1, 2))
   fmt.Println(add2("1", "haha "))
}

//3、泛型方法 约束int float64 string类型
func add2[T int | float64 | string](a T, b T) T {
   return a + b
}

自定义泛型类型

如果类型太多了怎么办呢? 这时候我们就可以自定义泛型类型

package main

import "fmt"
/*
1、定义接口,约束类型为int
2、自定义约束
3、方法实现
*/
func main() {
	//3、方法实现
   fmt.Println(GetNum(1, 2))

}
//1、定义接口,约束类型为int
type Myint interface {
   int | int16 | int32 | int64
}

//2、自定义约束
func GetNum[T Myint](a, b T) T { 
   if a > b {
      return a
   } else {
      return b
   }
}

理解泛型三大要素:类型参数、类型集合、类型推断

Go HTTP编程

web开发基础知识

静态web与动态web

web:网页

静态web:

  • html、css
  • 提供给所有的人数据不会发生变化

动态web:

  • 淘宝、京东几乎所有的网站
  • 提供给所有人的数据始终会发生变化、每个人在不同的时间内、不同的地点看到的信息也不同

web应用程序

可以提供浏览器访问的程序

服务器---网络请求

url https://www.kuangstudy.com/ ---解析:ip地址 服务器

你们能访问到的任何一个页面和资源,都存在于这个世界的某一个角落的计算机上

什么是HTTP

HTTP(超文本传输协议)请求---响应

  • 文本:html、字符串......
  • 超文本:图片、视频、音乐、地图......
  • 端口:80

https: s==ssl,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性

  • 443

http请求响应

客户端 ---> 发送请求Request---> 服务端 https://www.baidu.com

  • 请求方式:get
  • 请求方式:Get,Post ,head、delete、put、tract
    • get:请求能够携带的参数比较少,大小有限制,会在浏览器的URL地址栏显示数据内容,不安全,但高效
    • post:请求能够携带的参数没有限制,大小没有限制,不会再浏览器的URL地址栏显示数据内容,安全,但不高效

响应:

  • 服务器---> 响应Request---> 客户端

  • 数据格式

    • 网页
    • json
    • 文本
    • 文件
    • ......
  • 状态码

    • 200

    • http.StatusOK= 200成功连接访问。
      http.StatusFound = 302页面跳转的状态码。
      http.StatusBadRequest = 400非法请求,服务端无法解析。

      http.StatusUnauthorized = 401权限受限,未通过。
      http.StatusForbidden = 403禁止访问。
      http.StatusNotFound = 404请求页面不存在。
      http.StatuslnternalServerError = 500服务器内部错误。|

web应用程序工作流程

Web服务器的工作原理可以简单地归纳为:

  • 客户机通过TCP/IP协议建立到武器的TCP连接
  • 客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档
  • 服务器向客户机发送HTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理"动态内容”,并将处理得到的数据返回给客户端
  • 客户机与服务器断开。由客户端解释HTML文档,在客户端屏幕上渲染图形结果

HTTP编程

Go语言内置的net/http包十分的优秀,提供了HTTP客户端和服务端的实现

image-20230105174405795

image-20230105174451201

package main

import (
	"fmt"
	"net/http"
)

/*
=======服务端=========
1、请求处理 HandleFunc
2、本地端口 ListenAndServe
3、创建hello方法 接收 ResponseWriter
4、打印信息,写入信息 Write Request
*/
func main() {
	//1、请求处理 HandleFunc
	http.HandleFunc("/hello", hello) //页面为 127.0.0.1:8080/hello,处理的方法为hello
	//2、本地端口 ListenAndServe
	http.ListenAndServe("127.0.0.1:8080", nil) //handler:nil 服务器选择默认

}

// 3、创建hello方法 接收 ResponseWriter
func hello(writer http.ResponseWriter, request *http.Request) {
	//4、打印信息,写入信息 Write Request
	fmt.Println(request.URL)        //地址
	fmt.Println(request.Method)     //请求的方法
	fmt.Println(request.RemoteAddr) //请求的地址
	fmt.Println("程序被访问到了")

	writer.Write([]byte("hello,xuexiangban"))
}

package main

import (
	"fmt"
	"io"
	"net/http"
)

/*
=========客户端========
1、获取请求 Get
2、创建一个切片用于读取信息
3、请求和响应存储的数据
*/
func main() {
	//1、获取请求 Get
	ressponse, _ := http.Get("http://127.0.0.1:8080/hello")
	defer ressponse.Body.Close()
	//2、创建一个切片用于读取信息
	buf := make([]byte, 1024, 1024)
	//3、请求和响应存储的数据
	for {
		n, err := ressponse.Body.Read(buf)
		if err != nil && err != io.EOF {
			fmt.Println(err)
			return
		} else {
			fmt.Println("读取到的信息", string(buf[:n]))
			break
		}

	}
}

带参数的请求

关于GET的参数需要使用GO语言内置的net/url这个标准库来处理

http.HandleFunc("/login", login)
func login(writer http.ResponseWriter, request *http.Request) {
   //获取用户传递过来的数据
   data := request.URL.Query()
   username := data.Get("username")
   password := data.Get("password")
   fmt.Println("username", "password", username, password)
   //响应信息给客户端
   writer.Write([]byte(`{"登录状态:ok"}`))
}
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
)

/*
1、定义请求的数据 urlstr
2、拼接参数valuses
3、发送请求
*/
func main() {
	//1、定义请求的数据 urlstr
	urlstr := "http://127.0.0.1:8080/login"

	//2、拼接参数valuses
	data := url.Values{}
	data.Set("username", "kuangshenshuo")
	data.Set("password", "123456")

	//3、发送请求
	rurl, _ := url.ParseRequestURI(urlstr) // 解析url:ParseRequestURI
	rurl.RawQuery = data.Encode()          //RawQuery数据绑定 ,Encode可以将编码转换为string
	resp, _ := http.Get(rurl.String())
	b, _ := io.ReadAll(resp.Body) // 读取方式选择io下的ReadAll
	fmt.Println(string(b))

}

处理前端表单数据

编写注册表单

package main

import (
   "fmt"
   "net/http"
)
/*
1、请求处理HandleFunc
2、端口ListenAndServe
3、响应信息给客户端
*/
func main() {
   //1、请求处理HandleFunc

   http.HandleFunc("/register", register)
   //2、端口ListenAndServe
   http.ListenAndServe("localhost:8080", nil) //端口号为localhost:8080,服务器为nil表示默认,用handler服务器
}
func register(writer http.ResponseWriter, request *http.Request) {
   request.ParseForm()
   fmt.Println(request.ParseForm())
   username := request.PostForm.Get("username")
   password := request.PostForm.Get("password")
   fmt.Println("username:", username, "password:", password)
   //3、响应信息给客户端
   writer.Write([]byte(`{"登录状态:ok"}`))
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册表单</title>
</head>
<body>
<form action="http://localhost:8080/register" method="post">
    <p>
  用户名:<input type="text" name="username">
    </p>
    <p>
    密码:<input type="password" name="password">
    </p>

    <input type="submit" value="提交">
</form>

</body>
</html>

响应页面

模板文件中使用{{和}}包裹和标识需要传入的数据。
传给模板这样的数据就可以通过点号(.)来访问,如果数据是复杂类型的数据,可以通过( (.FieldName })来访问它的字段。

除{{和}}包裹的内容外,其他内容均不做修改原样输出。
如果要传入多个参数,一般都使用map 或者struct类型。

func temp(writer http.ResponseWriter, request *http.Request) {
   temp, _ := template.ParseFiles("./client4.html") //解析页面

   data := make(map[string]string)
   data["Info"] = "hello,kuangshen"

   temp.Execute(writer, data)
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>板块渲染</title>
</head>
<body>
<h1>学习使我快乐</h1>
{{.Info}}
</body>
</html>
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  uGYzDadp0Cs7   2024年04月18日   78   0   0 Go
  hyrB1Ag4eVs8   2024年04月15日   69   0   0 Go
  dHUS172Lkv6A   2024年05月08日   174   0   0 Go
  YFCZjJLTjJgW   2024年05月04日   51   0   0 Go
  YFCZjJLTjJgW   2024年05月17日   59   0   0 Go
  uGYzDadp0Cs7   2024年04月16日   118   0   0 Go
  YFCZjJLTjJgW   2024年05月17日   59   0   0 Go
j3Rgi9wHu4Az
作者其他文章 更多

2023-11-02

2023-11-02