1.1. 第 1 节:GO 语言介绍

未匹配的标注
本文档最新版为 2023,旧版本可能放弃维护,推荐阅读最新版!

1:GO语言介绍

1.1 GO语言是什么

“Go是一种开源的程序设计语言,它意在使得人们能够方便地构建简单、可靠、高效率的软件”(来自go官网golang.org)

我们程序员在开发程序,开发软件时都会选择一门编程语言,那么我们应该怎样进行选择呢?
可能有同学会说,我们要选择一门简单的,容易学习的,而且开发效率高的,能够在很短的时间内开发完成一个软件,这样老板会非常的满意,能够升职加薪,Python语言或者Ruby语言就非常适合这种快速开发。
但是问题是,用这种语言开发的软件,当用户量多了,运行的速度会非常慢,给人的感觉就是非常卡,大家想一下这种软件还有人愿意使用吗?
那可能又有同学说了,我们要学习运行速度快的编程语言,例如C或者是C++,但是这类编程语言学习难度是非常大的。
那么有没有一种编程语言,学习非常简单,开发速度非常快,开发出的软件电脑运行速度非常快呢?有,就是我们今天开始学习的GO语言

GO 语言借鉴了Python等其它编程语言简单,易学,生产效率高,同时GO语言专门针对多处理器(多核CPU,在这里可以给学生看一下windows下的多核CPU,)系统的程序进行了优化(让每核CPU都能够执行GO语言开发的程序),这样使用GO语言开发的程序运行的速度可以媲美C或C++开发程序的速度。

1.2GO语言优势

1:运行速度快,简单易学

2:薪水待遇高

3:适合区块链开发
4:跨平台
5:丰富的标准库(指令),Go目前已经内置了大量的库,特别是网络库非常强大(开发飞秋软件)
6:Go里面也可以直接包含C代码,利用现有的丰富的C库
7:语言层面支持并发,这个就是Go最大的特色,天生的支持并发。所谓并发,所有正在运行的程序轮流使用CPU,每个程序允许占用CPU的时间非常短(比如10毫秒),这样用户根本感觉不出来CPU是在轮流为多个程序服务,就好象所有的程序都在不间断地运行一样。并发好处就是可以运行多个程序,并不卡顿。

1.3 GO语言能干什么

1:网络编程,这一块目前应用最广,例如网站开发。
2:服务器编程,例如:处理日志,处理文件。(在网站开发的过程中都会涉及到)
3:区块链开发

2:环境安装

前面已经对GO语言做了一个简单的介绍,相信大家也已经基本有所了解了,那么我相信大家迫不及待的想学习GO语言了,但是我们在具体学习GO语言前,需要先安装GO的开发环境,只有把GO的开发环境安装好了,才能有GO语言的指令来开发网站,飞秋等等软件。也就是所谓的环境安装,就是将GO语言的指令都安装在你的电脑上,那么你在开发软件的时候就可以直接用了。GO环境的安装分为windows系统的安装和linux系统的安装,我们先学习windows下的安装,到项目可以再学习linux系统下Go环境的安装。

Windows下GO语言的环境安装步骤如下:
Go安装包下载网址:golang.google.cn/dl/。
有zip压缩版和msi安装版两个按本下载。(这里使用msi安装版,比较方便)。
运行msi安装文件,千万不要在安装路径中出现中文,一路Next。(关于安装路径在这里可以给学生重点强调,安装的位置以及不能出现中文名称)

title
点击下载
title
开始安装
title

title

如果你是其他系统请参考
各个系统下Go 语言环境安装

3:第一个GO程序

1:选择—>新建 文件

Go 语言源文件的扩展是 .go

注意:程序名称,和程序保存位置不能输入中文
2:新建完成后,拖入编辑器开始输入代码。

下载安装ide编辑器Gogland,自行下载破解即可

title

第一行:每个Go源代码文件的开头都是一个package声明,表示该Go代码所属的包。

GO语言中是通过包来对代码进行管理的。那么什么是包呢?我通过一个例子来解释一下包的概念。例如:有一家公司叫“京东“”,该公司有一个项目叫“京东网上商城”,该项目下面有管理“客户信息”的代码,另外一家公司叫“淘宝”,该公司也有一个项目叫“淘宝网上商城”,该项目下面也有管理的“客户信息”的代码,我开发的“客户管理软件”,既要用到“京东”的客户信息管理代码,也要用到“淘宝”的客户信息管理代码,为了能够很好的区分,我们在使用时必须写清楚,客户的信息管理代码到底是来自哪个商城,所以我可以这样写“import京东”,表示所使用的“客户信息代码”是来自“京东”的,那么“京东”就是一个标识,也就是包。所以说,包就是一个标识,标志着着你写的GO代码来自哪.这样代码管理非常清晰,使用也非常方便。
那么后面随着我们学习的深入,学习的代码也越来越多,我们给大家去讲解怎样自己创建包,来管理代码。创建包用到的就是package.
但是,不管你创建多少个包,要想让程序运行,必须包含一个main包。如果缺少该包,程序是无法运行的。

第三行代码:import表示导入,“fmt”是包名.

不管是使用我们自己定义的包还是GO自带都是通过import导入的,“fmt”是GO自带的包,该包 包含了怎样将信息打印在电脑屏幕上。注意包名要用“”表示。

第五行代码:表示程序的入口,程序就是从该位置开始执行的,就像是教室的门口一样,注意该行代码是固定的写法,func 与main之间必须有空格,同时main后面必须带小括号,后面的一对大括号表示要完成的具体工作,也就是完成具体任务的代码必须写在一对大括号内。
func表示后面跟着的main是一个函数,函数必须有一对小括号。什么是函数呢?现在大家可以先理解与我们在数学中学到的函数是一样的,都是实现某个具体的功能,例如我们学习的三角函数等。Main函数实现的就是入口功能。再次强调一下,GO语言中的函数前面必须加上func这个单词,而且函数名称后面必须跟一对小括号。

第六行代码:就是使用了fmt包(注意具体使用的时候不需要加上双引号)中的Print函数,将要“hello go” 打印在屏幕上。
这行代码要注意的是,fmt包与Print函数之间是用.(点连接的),该点是在半角输入法状态下输入,同时Print函数第一个字母P是大写的,不能写成小写,也就是说GO语言是区分大小写的,有很多同学在这里容易出错。将要输出的信息写在Print函数的括号内(注意括号也是在半角状态下输入的),并且用双引号包裹起来(双引号也是半角状态下输入的)

3:代码运行
我们第一个GO程序已经写完了,那么我们写的程序到底能否运行呢?是否有错误呢?下面我们执行我们的程序,程序的执行分为两步:
(1)编译
编译的操作步骤:点击 “编译” 菜单,选择 “Build” (或者直接按快捷键Ctrl+B).编译的目的是检查我们写的代码是否出错,如果没有出错,那么会提示“成功”,并且在我们程序所在的目录生成一个.exe文件,该文件就是可执行文件。如果出错那么会显示错误信息,并且不会生成.exe文件
(2)执行
编译操作完成后,我们就可以执行我们的代码了,选择“编译”菜单中的”Run”,执行。执行的就是编译所生成的.exe文件。这时,有同学可能就会问,如果我们不执行“Build”,直接执行”Run”,可以吗?不可以,因为不执行”Build”就无法生成”.exe”文件,那么也就无法执行。最后,我们为了方便高效可以选择“Run”,这个选项,将编译与执行放在一起操作。

如果通过命令行,则如下:

go build hello.go
./hello.go

4:其它注意事项
现在我们已经将我们写的第一个GO语言程序,执行成功了,但是还有以下几点内容需要在给大家强调一下:
(1)强制左花括号{的放置位置,如果把左花括号{另起一行放置,这样做的结果是Go编译器报告编译错误。
(2)每行代码占一行。
(3)如果我们在我们已经成功的程序中,在加入一行输入,应该怎么加呢?直接输入fmt.Print(“aaaaa”),然后直接执”Run”,发现并没有新的内容输出,原因是什么?应该怎么做?
(4)关于换行输出的问题,前面的输出都没有换行,那么怎样进行换行输出呢?

5:总结
对第一个GO程序进行总结。

“Go是一种开源的程序设计语言,它意在使得人们能够方便地构建简单、可靠、高效率的软件”(来自go官网golang.org)

我们程序员在开发程序,开发软件时都会选择一门编程语言,那么我们应该怎样进行选择呢?
可能有同学会说,我们要选择一门简单的,容易学习的,而且开发效率高的,能够在很短的时间内开发完成一个软件,这样老板会非常的满意,能够升职加薪,Python语言或者Ruby语言就非常适合这种快速开发。
但是问题是,用这种语言开发的软件,当用户量多了,运行的速度会非常慢,给人的感觉就是非常卡,大家想一下这种软件还有人愿意使用吗?
那可能又有同学说了,我们要学习运行速度快的编程语言,例如C或者是C++,但是这类编程语言学习难度是非常大的。
那么有没有一种编程语言,学习非常简单,开发速度非常快,开发出的软件电脑运行速度非常快呢?有,就是我们今天开始学习的GO语言

GO 语言借鉴了Python等其它编程语言简单,易学,生产效率高,同时GO语言专门针对多处理器(多核CPU,在这里可以给学生看一下windows下的多核CPU,)系统的程序进行了优化(让每核CPU都能够执行GO语言开发的程序),这样使用GO语言开发的程序运行的速度可以媲美C或C++开发程序的速度。

2. 注释

1:注释作用

标注说明
增强程序的可读性

在前面我们已经写过第一个GO程序了,该程序的功能很简单,代码量不是很多,也就是几行代码。但是大家看下面这段代码,代码量比我们写的第一个程序代码量多了很多,代码多了以后,阅读起来非常不容易。

我们通过用自己熟悉的语言(例如,汉语),在程序中对某些代码进行标注说明,这就是注释的作用,能够大大增强程序的可读性。以后,在公司中做开发,其他程序员会经常阅读我们写的代码,当然,我们也会经常阅读别的程序员写的代码,如果代码中加了注释,那么阅读起来就非常容易了。

##2:注释分类
(1)单行注释
所谓单行注释,就是每次给一行代码加注释,你可以在任何地方使用以 // 开头的单行注释。
title
(2)多行注释
若果代码量比多,每行加注释,比较麻烦,可以使用多行注释。
title
(3)重点强调
以后写程序要多加注释,这是我们程序猿的专业和职业道德,不加注释就是流氓。

3:变量

1:什么叫变量

可变的量, 计算机用来存储数据的.

在前面的讲解案例中,我们是让计算机输出数据到屏幕上,那么有同学可能就要问了,怎样让计算机保存我们通过键盘输入的数据呢?这就要用到变量了。
所谓的变量简单的理解就是计算机用来存储数据的。我们可以理解变量就像我们去超市买商品时用到的购物车,我们先将商品从货架上拿下来,放到购物车中,结账时在从购物车中取出商品。计算机通过变量来保存数据实际上将数据存储到计算机的内存中,这里我们可以画一个图给大家理解一下。

计算机将内存划分成不同的区域,数据就是存储在这些区域中,那么怎样从这些区域中将数据取出来呢?计算机将每块区域都加上了一串数字,作为编号。通过该编号就可以将数据取出来了,但是问题是,这一串数字对我们程序员来说是非常难记忆的,
为了解决这个问题,我们可以通过变量的方式来表示存储的数据,如下图:
title
我们给每个存储区域加上了Number1,Number2,Name等符号,这样通过这些符号来存储数据,然后通过这些符号取出数据就非常容易,方便了。这些符号就是变量。

2:变量类型

我们现在理解了变量可以用来存储数据,但是我们要存储的数据类型是各种各样的,例如:整数,小数,文本等等。所以我们必须在定义变量时就要告诉计算机,定义的变量存储是什么类型的数据。那么在内存中就可以根据不同的类型来开辟不同的存储空间。

关于GO语言中的有哪些类型,我们后面会具体讲解。下面我们先看一下怎样声明变量?

3. 变量声明

所谓声明变量就是创建一个变量,并且指定该变量存储什么类型的数据。
Go语言引入了关键字var,而类型信息放在变量名之后,示例如下:
title

注意:这里的输出是先将变量a的值取出来,然后在打印在屏幕上。所以不能给a这个变量加上引号。

4:变量初始化

我们可以在定义变量时,就给变量赋值,这种方式就是变量的初始化。示例如下:
title
注意:在这里我们将”=”符号,读作“赋值号”,不能读作“等号”。
问题:以下的程序输出的结果是多少?
var c int =20
fmt.Println(“c”)
同时再次强调一下在这里输出变量c的值,所以不能给变量c加上双引号。

5:变量赋值

除了在定义变量时,完成初始化以外,我们也可以在变量定义完后,再给变量赋值,也就是先声明后赋值,示例如下:
title
注意:上面的输出语句也可以只使用一个Println函数,具体如下:fmt.Println(a,b),中间用逗号进行分割
问题1:以下程序的输出结果是多少?
title
问题2:以下程序的输出结果是多少?
title
结论:变量可以重复赋值,一旦给一个变量赋了新值,那么变量中的老值就不复存在了


1-5的代码: 第二个代码文件

package main

import (
    "fmt"
    "math"
)

func main0201() {
    //变量的定义和使用
    //var 变量名 数据类型 = 值
    //int 表示整型数据
    var sun int = 50

    //变量在程序运行过程中 值可以发生改变
    //表达式
    sun = sun + 25

    fmt.Println(sun)
}
func main0202() {

    //变量的声明 如果没有赋值 默认值为0
    var sun int

    //为变量赋值
    sun = 50

    fmt.Println(sun)
}

func main0203() {
    //float64 浮点型数据
    var value float64 = 2

    //var sum int = value * value * value * value * value * value * value * value * value * value

    //可以使用系统提供的包 计算数据的n次方
    //需要导入math包 pow函数
    var sum float64 = math.Pow(value, 10)

    fmt.Println(sum)
}


6:自动推导类型
在给变量赋值时,我们感觉非常麻烦,有没有更简单的给变量赋值的方式,我们可以使用自动推导类型,具体示例如下:
title

所谓自动推导类型,就是不用通过var 声明变量,不用指定类型,直接在变量名后面跟”:”号,同时完成赋值。那么GO会根据所赋的值自动推导出变量的类型。如果给num变量赋值为小数,那么该变量的类型为小数类型(浮点)。
通过比较,我们发现这种方式比前面的赋值方式要简单方便。这种方式,也是我们以后开发过程中最常用的方式。


第三个代码文件:


package main

import "fmt"

func main0301() {
    //var a int = 10
    //var b int = 20
    //不同的数据类型在内存中开辟的空间不同
    //自动推导类型
    a := 10      //int     整型
    b := 123.456 //float64 浮点型
    c := "够浪"  //string 字符串类型

    fmt.Println(a + 10)
    fmt.Println(b + 3.14)
    fmt.Println(c)
    //不同的数据类型不能计算
    //fmt.Println(a + b)//err
}

//交换两个变量的值
func main() {
    a := 10
    b := 20

    //使用第三变量进行交换
    //先定义后使用
    //var c int
    //c = a

    //c := a
    //a = b
    //b = c

    //通过运算进行交换
    a = a + b
    b = a - b
    a = a - b

    fmt.Println(a)
    fmt.Println(b)
}

##7:多重赋值匿名变量

(1)多重赋值
在上面的讲解中,我们给变量num赋值,采用了自动推导的方式,如果想一次使用自动推导的方式,给多个变量赋值,应该怎样实现呢?具体如下:

title
但是这种方式写起来非常的复杂,可以用如下的方式进行简化:
title

将1的值赋值给a,将10的值赋值给b,将30的值赋值给c.

(2)匿名变量
_匿名变量,丢弃数据不进行处理, _匿名变量配合函数返回值使用才有价值,目前大家只需要知道其语法,后面学到函数时,我们在强调匿名变量的使用场景。
现在我们先看一下匿名变量的语法:
title


//04代码文件
package main

import "fmt"

func main0401() {
    //多重赋值
    //变量个数和值的个数要一一对应
    //a,b,c := 10,20,30
    a, b, c, d := 10, 3.14, "bokedu", "程序员"

    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)

}
func main0402() {
    var a int = 10
    var b int = 20

    //在一个作用域范围内变量名不能重复
    //var a float64 = 3.14

    //如果在多重赋值时有新定义的变量  可以使用自动推导类型
    a, b, c, d := 110, 120, "你好", "朋友"
    //a, b := 110, 120

    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)
}

func main() {
    //var a int = 10
    //var b int = 20

    //_表示匿名变量 不接收数据
    _, _, c, d := 120, 110, "你好", "朋友"
    //a, b := 110, 120

    //fmt.Println(_)
    fmt.Println(c)
    fmt.Println(d)
}

8:数据置换

到目前为止,我们已经学习了什么是变量,变量的定义,以及变量的赋值。那么下面大家思考如下问题:
有两个变量a和b, a的值为10,b的值为20,交换两个变量的值?
(有两个杯子,都盛满了水,怎样交换两个杯子中的水)
(1)使用传统方式实现
title
(2)使用多重赋值的方式实现
title
多重赋值的方式交换两个变量的值,比通过第三个变量来进行变量交换更简单,代码也少。


05代码

package main

import "fmt"

func main() {
    a, b := 10, 20

    //可以通过多重赋值进行交换
    a, b = b, a


    fmt.Println(a)
    fmt.Println(b)
}


9:输出格式

输出:将数据信息打印在电脑屏幕上
title

在我们GO语言中进行输出,用到我们前面所讲解的两个函数:Print()和Println()
这个两个函数的区别就是Print()函数不换行,Println()换行输出。
关于输出这里有两个问题,需要给大家强调清楚。
(1)我们前面的程序中,已经多次用到输出,不管是采用Print(),还是Println(),但是问题是,每次输出的数据结构不清晰,比较混乱。
例如如下程序:

title

该程序输出的结果是 1,10,20 .如果我们现在让另外一个程序员查看该结果,该程序员很难分清楚,
1是来自哪个变量,2是来自哪个变量,3来自哪个变量,除非该程序员阅读代码。但是,大家想一下,如果该程序的代码量非常大,那么该程序员阅读代码是否要花费很长的时间呢?所以,建议采用如下输出:

title

双引号内的内容会原样输出。这样结构比较清晰,注意与变量名之间用逗号分隔。

(3)除了使用Println()函数换行输出以外,还有另外一个函数Printf()也可以实现换行输出。示例如下:
title
运行以后,发现确实换行了。这种输出方式,就是格式化输出,%d,表示输出的是一个整数,第一个%d会被变量a的值替换,第二个%d会被变量b替换,其它类型的输出用什么去表示后面会给大家讲解。 “\n” 表示换行。
有同学可能就问了,这种换行输出方式比较麻烦,但是如果一次性有结构的输出多个变量的值,Println()输出就会比较麻烦,并且结构也会感觉比较混乱,还是以上面的代码为例,如下:

title
下面我们在给大家演示函数Printf()的输出:
title
通过对比发现Printf()函数一次性换行输出多个变量值,结构清晰。


//06
package main

import “fmt”

func main() {

//fmt.Println("hello world")
//fmt.Print("性感荷官在线发牌")
//fmt.Print("澳门在线赌场上线了")
//format
a := 10
b := 3.14559
//%d是一个占位符 表示输出一个整型数据

//%f是一个占位符 表示输出一个浮点型数据
//%f默认保留六位小数  因为浮点型数据不是精准的数据 六位是有效的
//%.2f保留小数位数为两位  会对第三位小数进行四舍五入
//\n表示一个转义字符 换行
fmt.Printf("%d %.2f\n", a, b)

c:="你瞅啥"
//%s是一个占位符 表示输出一个字符串类型
fmt.Printf("%s",c)

}


10:接收输入

前面我们所写的所有的程序,都是直接给变量赋值,但是很多情况下,我们希望用户通过键盘输入一个数值,存储到某个变量中,然后将该变量的值取出来,进行操作。我们日常生活中也经常用到输入的场景:
title
咱们在银行ATM机器前取钱时,肯定需要输入密码,对不?
那么怎样才能让程序知道咱们刚刚输入的是什么呢??
大家应该知道了,如果要完成ATM机取钱这件事情,需要先从键盘中输入一个数据,然后用一个变量来保存,是不是很好理解啊!
那么我们GO语言怎样接收用户的键盘输入呢?如下:
title

在GO中我们用到了“fmt”这个包中的Scanf()函数来接收用户键盘输入的数据。当程序执行到Scanf()函数后,会停止往下执行,等待用户的输入,输入完成后程序继续往下执行。在这里重点要注意的是Scanf()函数的书写格式,首先也要用“%d”,来表示输入的是一个整数,输入完整数后存储到变量age中,注意这里age变量前面一定要加上“&”符号,表示获取内存单元的地址(前面我们说的内存存储区域的编号),然后才能够存储。
还有另外一种获取用户输入数据的方式,如下:
title
通过Scan函数接收用户输入,这时可以省略掉%d,这种写法更简单。

##11.变量命名规范
在我们前面的编程中,我们定义变量时都是随意进行命名,但是在GO语言中,对变量的命名有一些规范性的要求,下面我们看一下具体的要求。
(1)变量命名规范要求
名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。大写字母和小写字母是不同的:heapSort和Heapsort是两个不同的名字。
除了上面提到的规范要求以外,GO语言自己特有的,具有一定含义的一些字符,也不能作为变量名称。例如,前面我们接触到的func, fmt,print等等,这些都是GO自带的,具有特殊含义的字符,我们称为关键字。

break       default        func         interface        select
case        defer         go           map              struct
chan        else           goto           package          switch
const        fallthrough    if             range            type
continue    for             import        return            var

以上关键字都不能作为变量的名称。
此外,还有大约30多个预定义的名字,比如int和true等

true false iota nil

        int int8 int16 int32 int64
        uint uint8 uint16 uint32 uint64 uintptr
        float32 float64 complex128 complex64
        bool byte rune string error

        make len cap new append copy close delete
        complexrealimag
        panic recover

(2)见名知意
起一个有意义的名字,尽量做到看一眼就知道是什么意思(提高代码可 读性) 比如: 名字 就定义为 name , 定义学生 用 student

(4)驼峰命名法
title
小驼峰式命名法(lower camel case): 第一个单词以小写字母开始;第二个单词的首字母大写,例如:myName、aDog
大驼峰式命名法(upper camel case): 每一个单字的首字母都采用大写字母,例如:FirstName、LastName
不过在程序员中还有一种命名法比较流行,就是用下划线“_”来连接所有的单词,比如send_buf


// 08
package main

func main() {
    //标识符命名规则
    /*
    1、允许使用字母 数字 下划线
    2、不允许使用go系统关键
    3、不允许使用数字开头
    4、区分大小写
    5、见名知义
     */

     //大驼峰命名
    //RoleLv
    //小驼峰命名
    //roleLv
    //linux命名
    //role_lv


}



4:基础数据类型

类型 名称 长度 零值 说明
bool 布尔类型 1 false 其值不为真即为假,不可以用数字代表true或false
byte 字节型 1 0 uint8别名
int, uint 整型 4或8 0 有符号32位或无符号64位
int8 整型 1 0 -128 ~ 127,
uint8 整型 1 0 0 ~ 255
int16 整型 2 0 -32768 ~ 32767,
uint16 整型 2 0 0 ~ 65535
int32 整型 4 0 -2147483648 到 2147483647
uint32 整型 4 0 0 到 4294967295(42亿)
int64 整型 8 0 -9223372036854775808到 92233720368547758070
uint64 整型 8 0 到 18446744073709551615(1844京)
float32 浮点型 4 0.0 小数位精确到7位
float64 浮点型 8 0.0 小数位精确到15位
string 字符串 “” utf-8字符串

下面我们看一下几个非常重要的类型,其它类型随着课程的深入,后面会给大家讲解到

1:布尔类型

在计算机中,我们用什么数据类型来表示一句话的对错,例如:在计算机中描述张三(20岁)比李四(18)小,这句话的结果?
布尔类型的变量取值要么是真(true),要么是假(false),用bool关键字来进行定义。示例如下:


package main
// 09
import "fmt"

func main() {
    //布尔类型  值为true 或者为 false
    //var a bool //默认值为false
    ////bool类型一般用于条件判断
    //a = true
    //fmt.Println(a)

    //自动推到类型创建bool类型变量
    a := false //bool

    //a=true
    //fmt.Println(a)
    //%T是一个占位符 表示输出一个变量对应的数据类型
    fmt.Printf("%T\n", a)
    b := 10 //int
    fmt.Printf("%T\n", b)
    c := 3.14 //float64
    fmt.Printf("%T\n", c)
    d := "你个锤子"//string
    fmt.Printf("%T", d)
}


布尔类型主要用于后面讲解的条件判断,关于条件判断,我们在这里先举一个生活中的案例,大家可以理解一下:

title

2:浮点型

package main

import "fmt"

func main0101() {

    //浮点型数据 分为 单精度浮点型 float32(小数位数为7位) 双精度浮点型 float64(小数位数为15位)
    //float64 比 float32 更精准
    //var a float64=1223.456
    ////保留六位小数数据会更精准
    //fmt.Printf("%f\n",a)
    //var a float32 =3.14
    //
    //fmt.Printf("%.2f",a)

    //通过自动推到类型创建的浮点型变量 默认类型为float64
    a := 123.456
    fmt.Printf("%T\n", a)
}

//买黄瓜 3.2一斤 买3斤

func main() {
    price := 3.2
    var weight float64

    fmt.Scan(&weight)

    sum := price * weight

    //fmt.Println(sum)
    fmt.Printf("%.2f",sum)
}

3:字符类型

字符基本输入与输出

所谓字符类型是用单引号括起来的单个字符,关于字符的定义使用如下:

func main() {
    // 声明变量
    var ch byte // 声明字符类型
    ch byte ='a' // 单引号字符
    fmt.Printf("%c",ch)  //  Printf中的,%c: 表示为输出类型为字符
}

输入一个字符,如下所示:

func main() {
    // 声明变量
    var ch byte // 声明字符类型
    fmt.Scantf("%c",&ch)
    fmt.Printf("%c",ch)
}

字符第二种输入与输出方式。

func main() {

    var ch byte
    ch = 97
    fmt.Printf("ch=%c", ch)
}

这里定义了,ch是一个字符类型,赋值却是一个整数97,打印的结果是小写字符’a’

原因是:计算机不能直接存储字符类型,只能转成数字存储,但是为什么小写字符‘a’,对应的整数是97呢?因为,计算机是根据’ASCII’码来存储。(这里只是简单介绍ASCII)

字符类型
注意强调:

字符类型

转义字符

字符除了我们常见的字符’a’,字符’b’等等以外,还有一些特殊的字符,例如我们讲解的’\n’, 它的作用是什么?换行,不会在屏幕上输出具体的内容。这种字符我们称为转义字符(具有特殊含义),那么除了’\n’字符以外,还有哪些是我们以后编程中经常用到的转义字符呢?

": 表示一个英文半角的双引号

func main() {

    fmt.Printf("波波  \"老师\"  开新课拉! ")

}

\t:表示一个tab键的空格(可以用来对输出的内容进行排版)

func main() {
    //转义字符 \n 换行
    fmt.Printf("波波  \t老师\t开新课拉!\n")
    fmt.Printf("波波  \t老师\t开新课拉!")
}

\:表示一个(主要用在表示磁盘目录上,后面我们讲解文件操作时,经验要根据磁盘目录获取文件然后进行读取,写入等操作)

func main() {
    fmt.Printf("F:\\我的备课\\go语言\\基础\\go资料")
}

以上就是我们以后编程中经常用到的转义字符,需要大家记住。

演示代码:

package main

import "fmt"

func main0201() {
    //byte字符类型 同时也是uint8的别名
    var a byte = 'a'

    //所有的字符都对应ASCII中的整型数据
    //'0'对应的48  'A'对应的65 'a' 对应的97
    //fmt.Println(a)

    //%c是一个占位符 表示打印输出一个字符
    fmt.Printf("%c\n", a)
    fmt.Printf("%c\n", 97)

    fmt.Printf("%T\n", a)
    var b byte = '0' //字符0  对应的ASCII值为为48

    fmt.Printf("%c\n", 48)
    fmt.Printf("%c\n", b)
}
func main0202() {

    var a byte = 'a'
    //将小写字母转成大写字母输出
    fmt.Printf("%c", a-32)
}

func main() {
    //转义字符 \n 换行

    //var a byte = '\n'
    //\0  对应的ASCII 值为0 用于字符串的结束标志
    //\t 对应的ASCII 值为9 水平制表符 一次跳八个空格
    var a byte ='\t'
    //fmt.Println(a)
    fmt.Printf("%c",a)


}

4:字符串类型

字符串是不可变的字节序列,它可以包含任意数据,包含 0 值字节。Go 语言总是以 UTF-8 编码处理字节序列。
用单引号括起来的单个字符是字符类型,用双引号括起来的字符是字符串类型。“a”是字符还是字符串呢?

下面是关于字符串的定义使用:

声明 

func main() {
    var a string 
    a = "你好"
    fmt.Println(a)
    fmt.Printf("%s",a) // 输出字符串格式
}

OR

func main() {
    a:="你好"
    fmt.Println(a)
    fmt.Printf("%s",a)
}

字符与字符串区别

func main() {
    var ch byte
    var str string

    // 字符
    // 1. 单引号
    // 2. 字符 往往都只有一个字符,转义字符除外,'\n'
    ch = 'a'
    fmt.Println("ch = ", ch)

    // 字符串
    // 1. 双引号
    // 2. 字符串都有一个或者多个字符组成
    // 3. 字符串都是一i内藏了一个结束符 '\0'
    str = "a"
    fmt.Println("str = ", str)

    str = "hello"
    // 测试字符串由多少字符组成
    //str = "hello"
    //在go语言中一个汉字占3个字符  为了和linux进行统一处理
    str = "波波"
    var count int
    count = len(a)
    fmt.Println(count)

    // 只想操作字符串的某个字符,从0开始操作
    fmt.Printf(“str[0] = %c, str[1] = %c\n”, str[0], str[1])


}

获取长度

func main() {
    // 字符与字符串区别
    //len 函数  用来计算字符串中字符个数 不包含\0  返回值为int类型
    //a := "hello"
    //在go语言中一个汉字占3个字符  为了和linux进行统一处理
    a := "波波"
    var count int
    count = len(a)
    fmt.Println(count)

}

访问

first := str[0] // 类似数组的下标
fmt.Println(str[len(str)]) // 下标不能越界 否则会宕机

对于非 ASCII 的字符串的 UTF-8 编码则需要两个以上字节,所以就不能使用下标这样的方式

连接

newStr := "good" + str

使用 + 则能连接两个字符串

截取

short := s[0:5] // 遵循左闭右开的原则 注意下标越界

演示:

package main

import "fmt"

func main0301() {
    //var a string = "你好"
    ////fmt.Println(a)
    //fmt.Printf("%s",a)
    //a:="你好"
    //fmt.Println(a)
    //fmt.Printf("%s",a)
    //双引号引起来的称为字符串
    ch := 'a'
    str := "a" //'a''\0'字符串结束标志

    fmt.Printf("%c\n", ch)
    //%s打印字符串打印到\0之前的内容
    fmt.Printf("%s\n", str)

}

func main0302() {
    // 字符与字符串区别
    //len 函数  用来计算字符串中字符个数 不包含\0  返回值为int类型
    //a := "hello"
    //在go语言中一个汉字占3个字符  为了和linux进行统一处理
    a := "bokedu"
    var count int
    count = len(a)
    fmt.Println(count)

}
func main() {
    str1 := "澳门在线赌场上线了"
    str2 := "性感荷官在线发牌"

    //字符串连接  +
    str3 := str1 + str2

    fmt.Println(str3)

}

##5:字符与字符串区别

上面的代码中已经实现

##6:fmt包的格式化输出输入

我们前面在输出与输入字符串类型,字符类型,整型等都指定了对应的格式化,那么go语言中还有哪些格式化呢?

格式 含义
%% 一个%字面量
%b 一个二进制整数值(基数为2),或者是一个(高级的)用科学计数法表示的指数为2的浮点数
%c 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符
%d 一个十进制数值(基数为10)
%e 以科学记数法e表示的浮点数或者复数值
%E 以科学记数法E表示的浮点数或者复数值
%f 以标准记数法表示的浮点数或者复数值
%g 以%e或者%f表示的浮点数或者复数,任何一个都以最为紧凑的方式输出
%G 以%E或者%f表示的浮点数或者复数,任何一个都以最为紧凑的方式输出
%o 一个以八进制表示的数字(基数为8)
%p 以十六进制(基数为16)表示的一个值的地址,前缀为0x,字母使用小写的a-f表示
%q 使用Go语法以及必须时使用转义,以双引号括起来的字符串或者字节切片[]byte,或者是以单引号括起来的数字
%s 字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\0‘结尾,这个’\0’即空字符)
%t 以true或者false输出的布尔值
%T 使用Go语法输出的值的类型
%U 一个用Unicode表示法表示的整型码点,默认值为4个数字字符
%v 使用默认格式输出的内置或者自定义类型的值,或者是使用其类型的String()方式输出的自定义值,如果该方法存在的话
%x 以十六进制表示的整型值(基数为十六),数字a-f使用小写表示
%X 以十六进制表示的整型值(基数为十六),数字A-F使用小写表示
%% 一个%字面量
%b 一个二进制整数值(基数为2),或者是一个(高级的)用科学计数法表示的指数为2的浮点数
%c 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符
%d 一个十进制数值(基数为10)
%e 以科学记数法e表示的浮点数或者复数值
%E 以科学记数法E表示的浮点数或者复数值
%f 以标准记数法表示的浮点数或者复数值
%g 以%e或者%f表示的浮点数或者复数,任何一个都以最为紧凑的方式输出
%G 以%E或者%f表示的浮点数或者复数,任何一个都以最为紧凑的方式输出
%o 一个以八进制表示的数字(基数为8)
%p 以十六进制(基数为16)表示的一个值的地址,前缀为0x,字母使用小写的a-f表示
%q 使用Go语法以及必须时使用转义,以双引号括起来的字符串或者字节切片[]byte,或者是以单引号括起来的数字
%s 字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\0‘结尾,这个’\0’即空字符)
%t 以true或者false输出的布尔值
%T 使用Go语法输出的值的类型
%U 一个用Unicode表示法表示的整型码点,默认值为4个数字字符
%v 使用默认格式输出的内置或者自定义类型的值,或者是使用其类型的String()方式输出的自定义值,如果该方法存在的话
%x 以十六进制表示的整型值(基数为十六),数字a-f使用小写表示
%X 以十六进制表示的整型值(基数为十六),数字A-F使用小写表示
package main

import "fmt"

func main0401() {

    //fmt.Printf("35%%")

    //十进制整型数据
    a := 123

    //%b 占位符 打印一个数据的二进制格式
    fmt.Printf("%b\n", a)
    //%o 占位符 打印一个数据的二进制格式
    fmt.Printf("%o\n", a)
    //%x %X 占位符 十六进制
    //10-15用a-f表示 %x  a-f  %X A-F
    fmt.Printf("%x\n", a)
    fmt.Printf("%X\n", a)
    //十进制
    fmt.Println(a)
    fmt.Printf("%d\n", a)

}

func main0402() {
    //十进制数据
    var a int = 10
    //八进制数据  八进制数据是以0开头  最大值为7
    var b int = 010
    //十六进制数据  十六进制数据是以0x开头
    //10-15用a-f表示 %x  a-f  %X A-F
    var c int = 0xABC

    //二进制  不能在go语言中直接表示

    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)

}

func main() {
    //''引起来的智能存储一个字符
    ch := 'a' //'\n'
    str := "瓜娃子"
    fmt.Printf("%c\n", ch)
    fmt.Printf("%s\n", str)
    fmt.Println(ch)

    //%p 占位符 打印是一个变量对应的内存地址 是以无符号十六进制整型表示
    fmt.Printf("%p\n", &ch)
    fmt.Printf("%p\n", &str)

    a := false

    //%t占位符 打印bool类型的值
    fmt.Printf("%t\n",a)
    fmt.Println(a)
}

#5:常量

##1:常量定义使用
我们都知道,变量是用来存储数据,并且变量里面存储的值是可以在程序运行时随时发生变化的,但是,常量里面的值确定好后,后面是不允许修改的。下面看一下关于常量的定义使用:

常量的命名规范与变量命名规范一致。

问题:什么场景下会用到常量呢?
在程序开发中,我们用常量存储一直不会发生变化的数据,例如:π,身份证号码等。像这类的数据,在整个程序中运行中都是不允许发生改变的。

package main

import "fmt"

func main0501() {
    //var a int = 10
    //常量的定义  一般定义常量使用大写字母
    const MAX int = 10
    //常量可以参与程序的计算
    //b:=a+10
    //常量不允许左值赋值
    //a=a+10//err
    //a=100//err
    fmt.Println(MAX)

}
func main0502() {
    const MAX = "你瞅啥"

    //fmt.Println(MAX)
    //fmt.Printf("%T\n",MAX )
    //go语言常量的地址 不允许访问
    //fmt.Printf("%p",&MAX)//err
}

func main0503() {
    //计算圆的面积和周长
    //面积 PI*r*r  math.Pow(r,2)
    //周长 2*PI*r

    //PI 3.14
    //常量必须定义
    const PI float64 = 3.14
    var r float64

    fmt.Scan(&r)
    //面积
    s := PI * r * r
    //周长
    l := 2 * PI * r
    //fmt.Println(PI)
    fmt.Printf("面积:%.2f\n", s)
    fmt.Printf("周长:%.2f\n", l)

}

func main() {
    //数据区 -》常量区
    //字面常量
    fmt.Println("hello world")
    fmt.Println(3.14)
    a := 10
    fmt.Println(a + 20)

}

##2:字面常量

所谓字面常量(literal),是指程序中硬编码的常量,如:

func main() {
    //数据区 -》常量区
    //字面常量
    fmt.Println("hello world")
    fmt.Println(3.14)
    a := 10
    fmt.Println(a + 20)

}

3:iota枚举

常量声明可以使用iota常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。
注意:在一个const声明语句中,在第一个声明的常量所在的行,iota将会被置为0,然后在每一个有常量声明的行加一。
具体使用方式如下:

package main

import "fmt"

func main() {

    //iota枚举
    //iota枚举格式如果写在一行中值相等 如果换行值在上一行加一
    //const (
    //    a    = iota
    //    b, c = iota, iota
    //)

    //只需要对第一个进行iota赋值 后面会依次增长
    //const (
    //    a = iota
    //    b
    //    c
    //    d
    //)
    //在定义iota枚举时可以自定义赋值
    const (
        a = iota
        b = 10
        c = 20
        d
        e
        f = iota
        g
    )
    //a=100//err
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)
    fmt.Println(e)
    fmt.Println(f)
    fmt.Println(g)
}

#6:运算符
在前面的课程中,我们已经学习了变量,常量,那么接下来我们学习一下运算符。GO语言中常见的运算符有算术运算符,赋值运算符,关系运算符,逻辑运算符等。

##1:算术运算符
| 运算符 | 术语 |
| :—:| :—: |
|+ |加|
|- |减|
|* |乘|
|/ |除|
|% |取模(取余)|
|++| 后自增,没有前自增|
|–| 后自减,没有前自减|

package main

import "fmt"

func main0701() {
    a := 10
    b := 5

    fmt.Println(a + b) //30
    fmt.Println(a - b) //-10
    fmt.Println(a * b) //200
    //两个整数相除等到的结果也是整型
    //在除法计算时 除数不能为0
    fmt.Println(a / b) //???0

    //通过命令编译代码
    //go build 源文件1.go 源文件2.go
    //编译并运行程序
    //go run 源文件1.go 源文件2.go
    //异常
    /*
    1、编辑时异常
    2、编译时异常
    3、运行时异常
     */
}
func main0702() {
    a := 10
    b := 2
    //取余运算符除数不能为0
    //取余运算符不能对浮点型使用
    c := a % b
    fmt.Println(c)
}

func main() {
    //自增自减运算符
    //可以对浮点型进行自增自减运算 但是不能对常量进行自增自减运算
    a := 10
    //const a =10
    //a = a + 1
    //a++//自增 在变量本身加一

    //a--//自减

    //自增自减不能出现在表达式中
    //a = a++ + a--
    //二义性 在不同操作系统中运算方式不同 结果可能会产生偏差
    //a = a++ * a-- - a--//err
    //b:=a--//err

    fmt.Println(a)
    //fmt.Println(b)
}

##2:类型转换:

package main

import "fmt"

func main0801() {

    a, b, c := 0, 0, 0

    fmt.Scan(&a, &b, &c)

    sum := a + b + c
    fmt.Println(sum)
    //类型转换 数据类型(变量)  数据类型(表达式)
    //fmt.Println(float64(sum / 3))
    fmt.Printf("%.2f", float64(sum)/3)
}

func main0802() {
    //var a float32 = 12.34
    //var b float64 = 22.22

    //int8 -> int16 ->int32 ->int64
    //float32 ->float64

    //int64 -> float64
    //在类型转换时建议低类型转成高类型  保证数据精度
    //建议整型转成浮点型
    //高类型转成低类型 可能会丢失精度
    //fmt.Printf("%.2f", float64(a)+b)

    //fmt.Println(float64(a) + b)

    //数据溢出 符号发生变化
    //var a int = 1234
    //fmt.Println(int8(a))
    var a float64 = 3.999

    //将浮点型转成整型 保留数据整数部分 丢弃小数部分
    b := int(a) //3
    fmt.Println(b)
}

//练习题
func main() {
    //107653几天几小时几分几秒

    s := 107653

    fmt.Println("天:", s/60/60/24%365)
    fmt.Println("时:", s/60/60%24)
    fmt.Println("分:", s/60%60)
    fmt.Println("秒:", s%60)

    //1*24*60*60 + 5*60*60 + 54*60 + 13 =107653
}

##3:赋值运算符

package main

import "fmt"

func main() {
    //a := 10
    //b := 20
    //c := a + b
    //
    ////c += 20 //c=c+20
    ////c -= 20
    ////c *= 20
    ////c /= 20 //30
    //c = 20
    //c %= 3 //c = c % 3

    var c int = 10
    //将表达式右侧进行结果计算在进行赋值运算符
    c %= (2 + 3)

    //c = c % 5 //ok
    //c = c % 2 + 3//err

    fmt.Println(c)
}

##4:关系(比较)运算符
关系运算符我们又称为比较运算符,听到比较这个词,大家想到的就是比较大小,例如:
大象的重量(1500)>老鼠的重量(1)
兔子的寿命(3)>乌龟的寿命(1000)
我的年龄(20)==你的年龄(20)
提问:比大比小的结果是什么?
结果是对或者是错,在GO语言中,怎样表示对于错呢?
关系运算的结果是布尔类型的。
bool值只有两个
真:true
假:false

package main

import "fmt"

func main1001() {

    //比较运算符返回值bool类型
    a := 10
    b := 10

    //大于 >  小于 <
    //fmt.Println(a < b)
    //大于等于 >= 小于等于 <=
    //fmt.Println(a < b)
    //相等于 ==  不等于 !=
    fmt.Println(a != b)
}

func main() {
    //a := 10
    //b := 20
    //
    ////比较运算符返回值类型为bool类型
    //c := a + 20 > b
    //
    //fmt.Printf("%T\n", c)
    //fmt.Println(c)
    a := 'a'
    b := 'A'

    fmt.Println(a > b)
}

##5:逻辑运算符
1)常见的逻辑运算符如下:
title

有逻辑运算符连接的表达式叫做逻辑表达式
通过以上表格我们发现:逻辑表达式的结果同样也是bool类型
逻辑运算符两边放的一般都是关系表达式或者bool类型的值。
例如:
5>3 &&true
3>5||false

package main

import "fmt"

func main1101() {

    a := 10
    b := 20

    //c := a > b //flase

    //逻辑非 !  非真为假 非假为真
    fmt.Println(!(a > b))
    //单目运算符
    // ! ++ -- &(取地址运算符)
    //双目运算符

}

func main1102() {

    a := 10
    b := 20

    //逻辑与  &&  同真为真 其余为假

    c := a < b && false
    fmt.Println(c)

}

func main1103() {
    a := 10
    b := 20
    //逻辑或  ||  同假为假 其余为真
    fmt.Println(a < b || a > b)
}

func main() {
    a := 10
    b := 20

    //逻辑与高于逻辑或
    fmt.Println(a > b && b > a || a > 0)
}

##6:其他运算符

运算符 术语 示例 说明
& 取地址运算符 &a 变量a的地址
* 取值运算符 *a 指针变量a所指向内存的值

传址:传内存块

传值:传变量值,两个内存块

关于指针的应用,我们后面会重点给大家讲解,目前大家只需要掌握 &

代码看下面

##7:运算符优先级

在Go语言中,一元运算符(一些只需要一个操作数的运算符称为一元运算符(或单目运算符)。)拥有最高的优先级,二元运算符的运算方向均是从左至右。
下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:

title

上图中部分运算符现在没有讲解到,后期用到时再给大家具体讲解,目前大家需要掌握的是算术,赋值,关系,逻辑,取地址运算符。

package main

import "fmt"

func main1201() {

    a := 10

    //& 取地址运算符
    fmt.Println(&a)

    //* 取值运算符 p  指针变量
    p := &a

    //通过指针间接修改变量的值
    *p = 123
    fmt.Println(*p)
    fmt.Println(a)


    //()括号运算符  最高级
    //[]数组切片下标
    //. 结构体.成员 包.函数 对象.方法

    // 位运算  & | ^ ~ 位移运算 >>  <<

    // 通信 >-

}

func main(){

    //()括号运算符  最高级
    //[]数组切片下标
    //. 结构体.成员 包.函数 对象.方法

    //单目运算符
    // !(逻辑非) ++(自增) --(自减) &(取地址) *(取值)

    //双目运算符
    //*(乘) /(除) %(取余)
    //+(加) -(减)
    // >(大于) <(小于) >=(大于等于) <=(小于等于) ==(相等于) !=(不等于)
    //&& (逻辑与)
    //|| (逻辑或)
    //= += -= *= /= %=(赋值运算符)


}

#7:流程控制
前面我们写的程序都是从第一行开始执行,一直执行到末尾,一行一行的顺序执行下来,这种执行结构叫顺序执行结构。
GO语言除了有顺序结构,还有选择结构,循环结构。

  • 顺序结构:程序按顺序执行,不发生跳转。
  • 选择结构:依据是否满足条件,有选择的执行相应功能。
  • 循环结构:依据条件是否满足,循环多次执行某段代码。

下面先讲解选择结构:

##1: 01if条件语句

package main

import (
    "fmt"
)

func main0101() {

    //if 表达式
    //{
    //代码体
    //}

    var score int

    fmt.Scan(&score)

    //if score > 700 {
    //    fmt.Println("我要上清华")
    //}

    //if score > 700 {
    //    fmt.Println("我要上清华")
    //} else {
    //    fmt.Println("我要上斯坦福")
    //}

    if score > 700 {
        fmt.Println("我要上清华")
    } else if score > 680 {
        fmt.Println("我要上北大")
    } else if score > 650 {
        fmt.Println("我要上人大")
    } else {
        fmt.Println("我要上斯坦福")
    }

    var m int

}

2:if语句嵌套

package main

import "fmt"

func main0201() {
    var score int

    fmt.Scan(&score)

    if score > 700 {
        fmt.Println("我要上清华")
        if score > 720 {
            fmt.Println("我要学挖掘机")
        } else if score > 710 {
            fmt.Println("我要学美容美发")
        } else {
            fmt.Println("我要学习汽修")
        }

    } else if score > 680 {
        fmt.Println("我要上北大")

        if score > 690 {
            fmt.Println("我要学盗墓")
        } else {
            fmt.Println("我要学习go语言")
        }
    }

}

//三只小猪称体重 通过键盘输入三只小猪体重 找到最重的
func main() {

    var a, b, c int
    //10 8 12
    fmt.Scan(&a, &b, &c)

    if a > b {
        //a重
        if a > c {
            fmt.Println("a最重")
        } else {
            fmt.Println("c最重")
        }
    } else {
        //b重
        if b > c {
            fmt.Println("b最重")
        } else {
            fmt.Println("c最重")
        }
    }
}
//根据分数 >=90 A >=80 B >=70 C >=60 D 不及格 E

##3:switch分支语句

package main

import "fmt"

func main0301() {

    //switch 变量(表达式) {
    //case 值1:
    //    代码体
    //  fallthrough
    //case 值2:
    //    代码体
    //default:
    //    代码体
    //}

    //根据分数 >=90 A >=80 B >=70 C >=60 D 不及格 E

    var score int
    fmt.Scan(&score)
    switch score / 10 {
    case 10:
        //fmt.Println("A")
        fallthrough
    case 9:
        fmt.Println("A")
    case 8:
        fmt.Println("B")
    case 7:
        fmt.Println("C")
    case 6:
        fmt.Println("D")
    default:
        fmt.Println("E")
    }
}
func main0302() {

    var score int
    fmt.Scan(&score)

    switch {
    case score >= 90:
        fmt.Println("A")
    case score >= 80:
        fmt.Println("B")
    case score >= 70:
        fmt.Println("C")
    case score >= 60:
        fmt.Println("D")
    default:
        fmt.Println("E")

    }
}
func main0303() {
    var score int
    fmt.Scan(&score)
    switch score > 60 {
    case true:
        fmt.Println("及格")
    case false:
        fmt.Println("不及格")
    }
}

func main() {
    //根据输入的年份月份 计算这个月有多少天

    var y int
    var m int
    fmt.Scan(&y, &m)

    //在switch语句中可以把相同的值放在一个case中
    switch m {
    case 1, 3, 5, 7, 8, 10, 12:
        fmt.Println(31)
    case 4, 6, 9, 11:
        fmt.Println(30)
    //fallthrough 在case中向下执行下一个case
    //case 1:
    //    fallthrough
    //case 3:
    //    fallthrough
    //case 5:
    //    fallthrough
    //case 7:
    //    fallthrough
    //case 8:
    //    fallthrough
    //case 10:
    //    fallthrough
    //case 12:
    //    fmt.Println(31)
    //
    //case 4:
    //    fallthrough
    //case 6:
    //    fallthrough
    //case 9:
    //    fallthrough
    //case 11:
    //    fmt.Println(30)
    case 2:
        //判断是否是闰年  能被4整除 但是 不能被100整除  或 能被400整除
        if y%4 == 0 && y%100 != 0 || y%400 == 0 {
            fmt.Println(29)
        } else {
            fmt.Println(28)
        }

    default:
        fmt.Println("月份输入错误")
    }
}

//case 3:
//    fmt.Println(31)
//case 4:
//    fmt.Println(30)
//case 5:
//    fmt.Println(31)
//case 6:
//    fmt.Println(30)
//case 7:
//    fmt.Println(31)
//case 8:
//    fmt.Println(31)
//case 9:
//    fmt.Println(30)
//case 10:
//    fmt.Println(31)
//case 11:
//    fmt.Println(30)
//case 12:
//    fmt.Println(31)

##4: if和switch比较

package main

func main() {


    //优点
    //if 可以进行区间判断 嵌套使用
    //switch 执行效率高 可以将多个满足相同条件的值放在一起

    //缺点
    //if 执行效率低
    //switch 不建议嵌套使用
}

##5: 循环语句

package main

import "fmt"

func main() {

    //for i := 0; i < 5; i++ {
    //    fmt.Println("6666")
    //}

    //for i := 1; i <= 10; i++ {
    //    fmt.Println(i)
    //}

    //计算1-100和
    //sum := 0
    //for i := 1; i <= 100; i++ {
    //    sum += i
    //}
    //
    //fmt.Println(sum)

    //计算1-100偶数的和
    //在for语句中嵌套if条件判断
    //sum := 0
    //for i := 1; i <= 100; i++ {
    //    if i%2 == 0 {
    //        sum+=i
    //    }
    //}
    //
    //fmt.Println(sum)
    //计算1-100偶数的和
    sum := 0
    for i := 0; i <= 100; i += 2 {
        sum += i
    }

    fmt.Println(sum)

}

##6: 循环练习

package main

import "fmt"

func main0601() {

    //敲7 7的倍数 个位为7 十位为7 需要敲桌子  1-100

    for i := 1; i <= 100; i++ {
        if i%7 == 0 || i%10 == 7 || i/10 == 7 {
            fmt.Println("敲桌子")
        } else {
            fmt.Println(i)
        }
    }

}

func main0602() {
    //水仙花数  一个三位数 各个位数的立方和等于这个数本身
    for i := 100; i <= 999; i++ {
        //百位
        a := i / 100
        //十位
        b := i / 10 % 10 //b:=i%100/10
        //个位
        c := i % 10

        if a*a*a + b*b*b + c*c*c == i {
            fmt.Println(i)
        }
    }

}

##7: 07循环嵌套

package main

import (
    "fmt"
    "time"
)

func main0701() {
    //代码对齐 ctrl+alt+l
    count := 0
    //外层执行一次 内层执行一周
    for i := 0; i < 5; i++ {
        //fmt.Println(i)
        for j := 0; j < 5; j++ {
            count++
            fmt.Println(i, j)
        }
    }

    fmt.Println(count)
}

//电子时钟
func main0702() {
    //时
    for i := 0; i < 24; i++ {
        //分
        for j := 0; j < 60; j++ {
            //秒
            for k := 0; k < 60; k++ {
                //程序暂停
                time.Sleep(time.Millisecond * 950)
                //fmt.Println(i, j, k)
                fmt.Printf("%d 时 %d 分 %d 秒\n", i, j, k)
            }
        }
    }
}

//func main() {
//    fmt.Println(time.Now())
//    fmt.Println(time.Now().Second())
//    fmt.Println(time.Now().Day())
//    fmt.Println(time.Now().Month())
//    //这一面已经过了240天了
//    fmt.Println(time.Now().YearDay())
//}

func main() {
    //九九乘法口诀
    /*
    1*1=1
    1*2=2 2*2=4
    1*3=3 2*3=6 3*3=9
     */
    // 外层控制行
    for i := 1; i <= 9; i++ {
        //内层控制列
        for j := 1; j <= i; j++ {
            //如果代码满足条件就跳出
            //if j > i {
            //    //跳出语句  可以跳出本层循环
            //    break
            //}
            fmt.Printf("%d*%d=%d\t", j, i, i*j)
        }
        fmt.Println()
    }

}

//打印等腰三角形
//     *
//    ***
//   *****
//  *******
// *********
//***********

##8. 08百钱百鸡

package main

import "fmt"

func main0801() {
    /*
    中国古代数学家张丘建在他的《算经》中提出了一个著名的“百钱百鸡问题”:
    一只公鸡值五钱,一只母鸡值三钱,三只小鸡值一钱,
    现在要用百钱买百鸡,请问公鸡、母鸡、小鸡各多少只?
     */

    //cock hen chicken
    count := 0
    for cock := 0; cock <= 20; cock++ {
        for hen := 0; hen <= 33; hen++ {
            for chicken := 0; chicken <= 100; chicken += 3 {
                count++
                //三种鸡个数为100 价格为100
                if cock+hen+chicken == 100 && 5*cock+3*hen+chicken/3 == 100 {
                    fmt.Printf("公鸡:%d  母鸡:%d  小鸡:%d\n", cock, hen, chicken)
                }

            }
        }

    }

    fmt.Println("执行次数", count)
}
func main() {

    count:=0
    for cock := 0; cock <= 20; cock++ {
        for hen := 0; hen <= 33; hen++ {
            count++
            //小鸡个数为100-公鸡-母鸡
            chicken := 100 - cock - hen
            if chicken%3==0 && 5*cock+3*hen+chicken/3 == 100 {
                fmt.Printf("公鸡:%d  母鸡:%d  小鸡:%d\n", cock, hen, chicken)

            }
        }
    }

    fmt.Println(count)
}
//72114  24308 714

##9. 09跳出语句

package main

import "fmt"

func main0901() {

    var i int = 0
    //死循环   循环没有出口
    for {

        //在有些程序循环中 不知道程序执行次数  只有条件满足时程序停止
        if i >= 5 {
            //跳出语句跳出当前循环
            break
        }

        fmt.Println(i)

        i++
    }

}
func main0902() {

    sum := 0
    for i := 0; i <= 100; i++ {
        if i%2 == 1 {
            //结束本次循环 继续下次循环
            //如果在程序中入到continue后剩余代码不会执行 会回到循环的位置
            continue
        }

        sum += i

    }

    fmt.Println(sum)
}
func main() {

    //fmt.Println("hello world1")
    //fmt.Println("hello world2")
    ////如果在代码中入到goto 会跳到所定义的标志位
    ////可以在一个循环中跳到另外一个循环中  可以在一个函数中跳到另外一个函数中
    //goto FLAG
    //fmt.Println("hello world3")
    //fmt.Println("hello world4")
    //FLAG:
    //fmt.Println("hello world5")
    //fmt.Println("hello world6")

    //死循环
FLAG:
    fmt.Println("hello world1")
    goto FLAG
    fmt.Println("hello world2")
}

练习题答案:


package main

import "fmt"

//打印等腰三角形
//     *        5 6-i-1        1 i*2+1
//    ***        4 6-i-1        3 i*2+1
//   *****        3 6-i-1        5 i*2+1
//  *******        2 6-i-1        7 i*2+1
// *********    1 6-i-1        9 i*2+1
//***********    0 6-i-1        11 i*2+1
func main() {

    //行数
    l := 6

    //整体执行次数
    for i := 0; i < l; i++ {
        //控制空格个数
        for j := 0; j < l-i-1; j++ {
            fmt.Print(" ")
        }
        //控制星星个数
        for k := 0; k < i*2+1; k++ {
            fmt.Print("*")
        }
        fmt.Println()
    }

}

#8:函数

##1:函数定义和使用

package main

import "fmt"

//func 函数名(参数列表)(返回值列表){
//代码体
//}
//函数定义 只能定义一次
//在整个项目中函数名是唯一的 不能重名
func add(s1 int, s2 int) {
    sum := s1 + s2
    fmt.Println(sum)
}

func main() {
    //fmt.Println("你好")
    //v := len("你好")
    //fmt.Println(v)
    a := 10
    b := 20

    //函数调用 函数可以多次调用
    //在函数调用时参数为实际参数(实参)有具体的值 用来给形式参数(形参)传递数据
    add(a, b)
    add(1, 2)
}

2:普通参数列表

所谓的普通参数列表指的是,我们给函数传递的参数的个数都是确定好。基本语法如下:

代码在上面

##3:不定参数列表
根据前面的讲解,我们都知道了,在定义函数的时候根据需求指定参数的个数和类型,但是有时候如果无法确定参数的个数呢?
举例说明:上一小节我们写过一个求两个整数之和的函数,但是在实际的开发中,也会经常遇到这样的情况,就是项目经理(对整个项目的进度进行把控,对程序员进行管理的人员,称为项目经理),要求你写一个函数,实现整数的和。在这个要求中,项目经理并没有说清楚到底是有几个整数,那么我们应该怎样确定该函数的参数呢?就用接下来给大家讲解的“不定参数列表”来解决这个问题

package main

import "fmt"

//...不定参 在函数调用时可以传递不定量(0-n)的参数
//不定参使用数据格式为切片
func sum(arr ...int) {
    //arr是一个数据的集合
    //fmt.Println(arr)

    //计算字符串个数
    //len("你好")
    //计算数据集合的个数
    //count := len(arr)
    //fmt.Println(count)

    //通过arr[下标]可以找到具体数据的值
    //下标是从0开始的  到len(arr)-1为最大值下标
    //fmt.Println(arr[0])
    //fmt.Println(arr[1])
    //fmt.Println(arr[2])
    //fmt.Println(arr[3])

    sum := 0
    //通过for循环遍历集合中的数据
    //for i := 0; i < len(arr); i++ {
    //    //fmt.Println(arr[i])
    //    sum += arr[i]
    //}
    //通过for循环遍历集合中的数据
    //i -> index 下标 v -> value 值
    //_匿名变量  如果数据的值不需要接收  可以通过匿名变量来接收数据
    for _, v := range arr {
        //fmt.Println(i, v)
        sum += v
    }
    fmt.Println(sum)
}
func main() {
    //不定参函数调用
    sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    sum(12, 21, 23, 32)

    //fmt.Println 就是一个不定参的格式
    fmt.Println("你好", 123, "再见")
}

##4: 函数嵌套调用

函数也可以像我们在前面学习if选择结构,for循环结构一样进行嵌套使用。所谓函数的嵌套使用,其实就是在一个函数中调用另外的函数。

package main

import "fmt"

func test1(a int, b int) {

    fmt.Println(a + b)
}

//函数参数传递时如果有多个参数 中间用逗号分隔
//在函数调用时 使用的是定义中形参的值
func test(n1, n2 int) {
    test1(n1, n2)

}

func main0401() {
    a := 10
    b := 20
    test(a, b)
}

func test3(arr ...int) {

    fmt.Println(arr)
}

//如果不定参函数调用时传递的参数为多个 不定参要写在其他参数后面
func test2(arr ...int) {
    //不能将不定参的名称传递给另外一个不定参
    //test3(arr[0], arr[1], arr[2], arr[3])
    //传递指定个数的数据
    test3(arr[0:4]...)
}

//不定参函数的嵌套调用
func main() {
    test2(1, 2, 3, 4)
}

##5: 函数的返回值

package main

import "fmt"

//func 函数名(函数参数列表)(函数返回值类型)
func test4(a int, b int) (sum int) {
    sum = a + b
    //sum := a + b
    //fmt.Println("hello world")
    //return 表示函数的结束  如果函数有返回值return 可以将返回值返回
    return
    //fmt.Println("你瞅啥")
    //fmt.Println("瞅你咋地")
}
func main0501() {
    a := 10
    b := 20
    //var sum int = 0
    sum := test4(a, b)
    fmt.Println(sum)

}

//多个返回值
func test5(a int, b int) (sum int, sub int) {

    sum = a + b
    sub = a - b
    return
}
func main() {

    a := 10
    b := 20

    //函数有多个返回值 要一一对应接收数据
    sum, sub := test5(a, b)

    fmt.Println(sum, sub)
}

##6: 函数类型

package main

import "fmt"

func demo1(a int, b int) {
    fmt.Println(a + b)
}

//func (int,int)int函数类型
func demo2(s1 int, s2 int) {
    fmt.Println(s1 + s2)
}

//定义函数类型 为已存在的数据类型起别名
type FUNCDEMO func(int, int)

func main() {
    //demo1(10, 20)
    //函数的名字表示一个地址 函数在代码区的地址
    fmt.Println(demo1)

    //f是func (int,int)函数类型定义的变量
    //var f func(int, int)
    var f FUNCDEMO
    //函数类型定义的变量结束函数的地址
    f = demo1
    fmt.Println(f)
    //通过f调用函数
    f(10, 20)

    f = demo2
    f(1, 2)

    fmt.Printf("%T", f)
}

##7: 07函数的作用域

package main

import "fmt"

//在函数外部定义的变量  成为全局变量
//作用域是在项目中整个文件去使用
//定义的全局变量名不能和其他文件中的全局变量重名
//全局变量名可以和局部变量名重名
//全局变量储存在内存的数据区
//如果全局变量定义时有值 存储在初始化数据区 没有值存储在为初始化数据区
var a int = 10

func main0701() {
    //变量先定义后使用 在函数内部变量名是唯一的
    //在函数内部定义的变量 成为局部变量
    //局部变量的作用域在函数内部

    //a := 10
    //fmt.Println(a)
    //var i int = 10
    //for i = 0; i < 5; i++ {
    //    fmt.Println(i)
    //}
    //fmt.Println(i)

    //在go语言中会采用就进原则 如果在函数内部定义局部变量 和全局变量名重名 会使用局部变量
    a := 123

    //修改全局变量的值
    //如果全局变量的值修改 会影响其他位置使用全局变量的值
    a = 110
    fmt.Println(a)

    demo3()
}

func demo3() {
    fmt.Println(a)
}

func main() {

    //打印代码区的地址   代码区
    fmt.Println(demo3)
    //打印全局变量的地址  数据区
    fmt.Println(&a)
    a := 10
    //打印局部变量的地址  栈区
    fmt.Println(&a)
}

##8: 08匿名函数

package main

import "fmt"

type FuncType func(int, int)

func main0801() {

    a := 10
    b := 20

    //在函数内部定义一个匿名函数
    //f 函数类型变量 接收函数

    var f FuncType

    f = func(a int, b int) {
        fmt.Println(a + b)
    }

    f(a, b)
    //fmt.Println(f)

}
func main0802() {
    //a := 10
    //b := 20
    // int类型
    //v := func(a int, b int) int {
    //    return a + b
    //}(a, b)

    //如果定义匿名函数后在{}后面如果有()表示函数调用
    f := func(a int, b int) int {
        return a + b
    }

    v := f(10, 20)
    fmt.Printf("%T\n", f)
    fmt.Println(v)
}
func main0803() {
    a := 10
    b := 20

    f := func() int {
        return a + b
    }

    //v := f(a, b)

    a = 100
    b = 200

    v := f()

    fmt.Println(v)
}

##9: 09递归函数

package main

import "fmt"

//递归函数  函数调用函数本身
//死递归
func demo4(n int) {

    //在函数中遇到return 表示函数的结束
    if n == 0 {
        return
    }

    demo4(n - 1)
    fmt.Println(n)
}

func main0901() {

    demo4(10)

}

//计算一个数的阶乘
var s int = 1

func demo5(n int) {
    if n == 1 {
        return
    }
    s *= n//5*4*3*2
    demo5(n - 1)


}

func main() {

    a := 5
    demo5(a)

    fmt.Println(s)

}

#9: 工程管理

##1 工作区介绍

通过前面函数的学习,我们能够体会到函数的优势,就是可以将不同的功能放在不同的函数中实现,主函数(main( ))可以直接调用。这样结构非常的清晰,也非常方面代码的管理。如果我们把所有的代码都写在main( )函数中,会出现什么样的情况呢?
代码混乱,非常不容易管理。但是现在我们面临了另外一个问题就是:我们所有自己定义的函数都写在了一个文件中,
如果我们做的项目代码量越来越多,那么该文件会变的非常臃肿,代码也会变得非常难管理。所以,我们在开发中,除了要定义函数,同时还要将代码放在不同的文件中。例如:我们定义了一个UserInfo.go文件,里面包含了用户的添加函数,修改函数,删除函数等操作。
这就涉及到项目的工程管理也就是怎样对项目中的文件进行管理。
为了更好的管理项目中的文件,要求将文件都要放在相应的文件夹中。GO语言规定如下的文件夹如下:
 (1)src目录:用于以代码包的形式组织并保存Go源码文件。(比如:.go .c .h .s等)
 (2)pkg目录:用于存放经由go install命令构建安装后的代码包(包含Go库源码文件)的“.a”归档文件。
 (3)bin目录:与pkg目录类似,在通过go install命令完成安装后,保存由Go命令源码文件生成的可执行文件。

以上目录称为工作区,工作区其实就是一个对应于特定工程的目录。
目录src用于包含所有的源代码,是Go命令行工具一个强制的规则,而pkg和bin则无需手动创建,如果必要Go命令行工具在构建过程中会自动创建这些目录

#10:复合类型

##1:数组

###01数组定义和使用

package main

import "fmt"

func main0101() {

    //    数组定义
    // 数组是一系列相同数据类型在内存中有序存储的数据集合
    // var 数组名 [元素个数]数据类型
    //定义了10个整型变量的数组元素
    var arr [10]int

    //通过下标找到具体元素  数组下标是从0开始的  到数组元素个数-1位数值最大下标
    //数组名[下标]  指向具体某一个元素
    arr[0] = 123
    arr[1] = 110
    arr[2] = 234

    arr[5] = 567
    //打印数组
    fmt.Println(arr)

    fmt.Println(arr[0])
    fmt.Println(arr[1])
    fmt.Println(arr[2])

}

func main0102() {
    //在定义数组时  依次为数组元素赋值
    //var arr [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    //在定义数组是 可以为部分元素赋值
    //var arr [10]int = [10]int{1, 2, 3, 4, 5}
    //在定义数组时可以指定下标赋值
    //var arr [10]int = [10]int{1: 10, 4: 20, 6: 30}

    //定义时写...可以根据元素个数赋值
    //var arr [4]int = [...]int{1, 2, 3, 4}
    arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    //len(数组名)//计算数组元素个数
    //fmt.Println(len(arr))

    //遍历数组元素值
    //for i:=0;i<len(arr);i++{
    //    fmt.Println(arr[i])
    //}

    //可以使用range 数组名变量
    //i -》index 下标 v-》value 值
    for _, v := range arr {
        fmt.Println(v)
    }
    //fmt.Println(arr)
}

func main() {
    //数组常见问题

    //i是一个变量  数组元素个数定义一个是常量 或是常量表达式
    //i:=10
    //const i =10
    var arr [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    //var arr1 [10]int

    //arr[0]=123

    //数组下标越界 最大值下标:len(arr)-1
    //arr[10]=321//err
    //arr[-1]=321//err

    //arr是数组名  表示的是一个数组
    //arr=123err

    //两个数组如果类型和元素个数相同可以赋值
    //arr1=arr

    fmt.Println(arr)
    //fmt.Println(arr1)

    //打印数组数据类型
    fmt.Printf("%T\n", arr)
    //fmt.Printf("%T\n",arr1)

    //打印数组地址
    //数组名表示整个数组 数组名对应的地址就是数组第一个元素的地址
    fmt.Printf("数组地址:%p\n", &arr)
    //fmt.Printf("数组第一个元素地址:%p\n",&arr[0])
    //fmt.Printf("数组第二个元素地址:%p\n",&arr[1])
    //fmt.Printf("数组第三个元素地址:%p\n",&arr[2])

    //打印数组所有元素地址
    for i := 0; i < len(arr); i++ {
        fmt.Printf("%p\n", &arr[i])
    }

}

###02十只小猪称体重

package main

import "fmt"

func main() {

    var arr [10]int

    //通过键盘为数组元素赋值
    for i := 0; i < len(arr); i++ {
        fmt.Scan(&arr[i])
    }

    //通过循环遍历数组
    max := arr[0]
    for i := 1; i < len(arr); i++ {
        if arr[i] > max {
            max = arr[i]
        }
    }

    fmt.Println(max)

}

###03数组逆置
将一个字符串数组的元素的顺序进行反转。{“我”,“是”,”好人”} {“好人”,”是”,”我”}。第i个和第length-i-1个进行交换。

package main

import "fmt"

func main() {

    var arr [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    //数组逆置
    i := 0            //最小值下标
    j := len(arr) - 1 //最大值下标


    //for 表达式1;表达式2;表达式3{}
    //for 返回值:=range 集合{}
    //for 条件 {}
    for i < j {
        //if (i >= j) {
        //    //跳出循环
        //    break
        //}

        //交换数据
        arr[i], arr[j] = arr[j], arr[i]
        //改变下标
        i++
        j--

    }

    fmt.Println(arr)

}

###04冒泡排序

package main

import "fmt"

func main() {
    var arr [10]int = [10]int{9, 1, 5, 6, 8, 2, 10, 7, 4, 3}

    //外层执行一次内层执行一周
    //外层控制行
    for i := 0; i < len(arr)-1; i++ {
        //内层控制列
        for j := 0; j < len(arr)-1-i; j++ {

            //满足条件进行交换  大于号 升序  小于号 降序
            if arr[j] < arr[j+1] {
                //交换数据
                arr[j], arr[j+1] = arr[j+1], arr[j]
            }

        }
    }

    fmt.Println(arr)

}

###05 随机数做一个数组去重

第一: 随机数使用

package main

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

func main() {

    //1、导入头文件  math/rand  time
    //2、随机数种子
    //3、创建随机数

    //创建随机数种子 进行数据混淆
    rand.Seed(time.Now().UnixNano())

    //伪随机数 使用的1970.1.1.0.0.0
    //fmt.Println(rand.Intn(10))//取模10   得到0-9
    for i := 0; i < 10; i++ {
        fmt.Println(rand.Intn(10))
    }
}

第二:数组去重

package main

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

func main0601() {

    rand.Seed(time.Now().UnixNano())
    var arr [10]int

    for i := 0; i < len(arr); i++ {
        arr[i] = rand.Intn(100)
    }

    fmt.Println(arr)

    for i := 0; i < len(arr)-1; i++ {
        for j := 0; j < len(arr)-1-i; j++ {
            if arr[j] > arr[j+1] {
                arr[j], arr[j+1] = arr[j+1], arr[j]
            }

        }
    }
    fmt.Println(arr)
}

func main0602() {
    //猜数字 1-100

    rand.Seed(time.Now().UnixNano())

    num := rand.Intn(100) + 1

    value := 0
    for {
        fmt.Println("请输入一个数字:")
        fmt.Scan(&value)

        if num > value {
            fmt.Println("您输入的数字太小了")
        } else if num < value {
            fmt.Println("您输入的数组太大了")
        } else {
            fmt.Println("您猜对了")
            break
        }
    }

}

//数组去重
func main() {
    //随机双色球彩票
    //红色 1-33  选择6个 不能重复 蓝球 1-16 选择1个 可以和红球重复

    rand.Seed(time.Now().UnixNano())
    var red [6]int

    for i := 0; i < len(red); i++ {
        v := rand.Intn(33) + 1

        for j := 0; j < i; j++ {
            //数据重复
            if v == red[j] {
                //重新随机
                v = rand.Intn(33) + 1
                //将j赋值为-1在循环执行到上面是进行++操作后值为0  也就相当于重新比对
                j = -1
            }
        }
        //将没有重复的数字添加到数组中
        red[i] = v

    }
    fmt.Println("红球:", red, "蓝球:", rand.Intn(16)+1)
}

###06二维数组定义和使用

package main

import "fmt"

func main0701() {

    //var arr [10]int 一维数组

    var arr [2][3]int

    arr[0][1] = 123
    arr[1][2] = 234

    //fmt.Println(arr)

    //len(二维数组名)一个二维数组有几行
    //fmt.Println(len(arr))
    //len(二维数组名[下标])一个二维数组有几列
    //fmt.Println(len(arr[0]))

    //外层控制行 内层控制列
    //for i := 0; i < len(arr); i++ {
    //    for j := 0; j < len(arr[0]); j++ {
    //        fmt.Println(arr[i][j])
    //    }
    //}

    for _, v := range arr {
        //fmt.Println(i,v)
        //fmt.Printf("%T\n",v)

        for _, data := range v {
            fmt.Println(data)
        }
    }

}
func main() {
    //二维数组定义方式
    //var arr [2][3]int =[2][3]int{{1,2,3},{2,3,4}}
    //var arr [2][3]int = [2][3]int{{1, 1}, {1}}
    // var arr[2][3]int=[2][3]int{1:{1:1}}
    arr:=[2][3]int{{1,2,3},{2,3}}
    fmt.Println(arr)
}

##2: 切片

###07切片定义和使用
切片:切片与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大,所以可以将切片理解成“动态数组”,但是,它不是数组。

package main

import "fmt"

func main0801() {

    //数组定义
    //var 数据名 [元素个数]数据类型

    //切片定义
    //var 切片名 []数据类型

    var slice []int = []int{1, 2, 3, 4, 5}
    slice[0] = 123
    slice[2] = 234
    //在使用切片是不能超过切片的范围  下标越界
    //slice[5] = 456 //err

    //append 切片添加数据

    slice = append(slice, 456)
    slice = append(slice, 7, 8, 9)

    fmt.Println(slice)
    //len(切片名)计算切片的长度
    for i := 0; i < len(slice); i++ {
        fmt.Println(slice[i])
    }

    //for i,v:=range slice{
    //    fmt.Println(i,v)
    //}
}
func main() {
    //切片赋值
    //var slice []int
    //slice[0]=123//err切片下标越界

    //slice = append(slice, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    //在定义切片时可以指定长度  make(切片类型,长度)
    //var slice []int=make([]int,10)
    //
    //slice[0]=123
    //slice[1]=234
    //slice = append(slice, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    //通过自动推到类型创建切片
    slice :=[]int{1,2,3}

    fmt.Println(slice)
    fmt.Println(len(slice))
}

###08切片的地址和扩容

package main

import "fmt"

func main0901() {

    //切片名本身就是一个地址
    //创建的空切片 指向内存地址编号为0的空间
    var slice []int

    fmt.Printf("%p\n", slice)

    //当使用append进行追加数据时 切片地址可能会发生改变
    slice = append(slice, 1, 2, 3)

    fmt.Printf("%p\n", slice)

    slice = append(slice, 4, 5, 6)

    fmt.Printf("%p\n", slice)

}

func main0902() {
    //var slice []int =make([]int,10)
    //make(数据类型,长度,容量)
    slice := make([]int, 10, 20)

    //在使用appned进行数据添加时  如果长度超出容量  容量会自动扩容
    //一般扩容方式为上一次 容量*2  如果超过1024字节 每次扩容上一次的1/4
    //容量扩容每次都是偶数
    //len(slice)  计算切片的长度
    //cap(slice)  计算切片的容量
    fmt.Println(cap(slice))
    fmt.Printf("%p\n", slice)
    fmt.Println(slice)

    slice = append(slice, 1, 2, 3, 4, 5)
    fmt.Println(cap(slice))
    fmt.Printf("%p\n", slice)
    fmt.Println(slice)

    slice = append(slice, 1, 2, 3, 4, 5, 6)
    fmt.Println(cap(slice))
    fmt.Printf("%p\n", slice)
    fmt.Println(slice)
}
func main() {
    slice := []int{1, 2, 3, 4, 5}

    fmt.Println("长度", len(slice))
    fmt.Println("容量", cap(slice))

    slice = append(slice, 1)
    fmt.Println("长度", len(slice))
    fmt.Println("容量", cap(slice))

    //使用len(slice)来打印数据 不能使用cap(slice)
    //for i := 0; i < cap(slice); i++ {
    //    fmt.Println(slice[i])
    //}
}

###9切片的截取


package main

import "fmt"

func main1001() {
    slice :=[]int{1,2,3,4,5,6,7,8,9,10}
    //切片名[起始位置:结束位置+1]  切片名[low:high]
    //s:=slice[3:7]
    //容量=max-low
    //切片名[起始位置:结束位置+1:容量]  切片名[low:high:max]
    //s:=slice[3:7:7]
    //切片名[起始位置:] 从起始位置打结束
    //s:=slice[2:]
    //切片名[:结束位置+1]
    //s:=slice[:5]
    //s:=slice[:]
    s:=slice
    fmt.Println(s)

    //容量要大于等于长度
    //fmt.Println(len(s))
    //fmt.Println(cap(s))
}

func main(){
    slice :=[]int{1,2,3,4,5,6,7,8,9,10}

    //截取后的切片还是原始切片中的一块内容 如果修改截取后的切片 影响原始切片的值
    //s:=slice[2:5]

    s:=slice[:]
    s[1]=123//3 123 5
    fmt.Println(s)

    fmt.Println(slice)

    fmt.Printf("%p\n",slice)
    fmt.Printf("%p\n",s)
}

###10 切片的拷贝


package main

import "fmt"

func main() {
    var slice []int = []int{1, 2, 3, 4, 5}

    s := make([]int, 5)
    //使用copy进行拷贝 在内存中存储两个独立的切片内容  如果任意一个发生修改不会影响另外一个
    copy(s, slice)

    fmt.Println(s)


    fmt.Printf("%p\n",slice)
    fmt.Printf("%p\n",s)
    //s[2]=123
    //fmt.Println(s)
    //fmt.Println(slice)
    //
    //
    //slice =append(slice,6,7,8)
    //fmt.Println(s)
    //fmt.Println(slice)
}

###11 切片的排序

package main

import "fmt"

func main() {
    var slice []int = []int{9, 1, 5, 6, 8, 3, 7, 2, 10, 4}

    for i := 0; i < len(slice)-1; i++ {
        for j := 0; j < len(slice)-1-i; j++ {
            if slice[j] > slice[j+1] {
                slice[j], slice[j+1] = slice[j+1], slice[j]
            }
        }
    }

    fmt.Println(slice)
}

#练习

###1.数组作为函数参数

package main

import "fmt"

func swap(a int, b int) {
    a, b = b, a
}

func main0101() {
    a := 10
    b := 20
    //值传递  形参不能改变实参的值  形参和实参是不同的地址单元
    swap(a, b)

    fmt.Println(a)
    fmt.Println(b)
}

//数组作为函数参数  返回值
func BubbleSort(arr [10]int)  [10]int {

    //fmt.Println(arr)
    //fmt.Println(len(arr))

    //arr[0]=123
    //arr[1]=234

    for i := 0; i < len(arr)-1; i++ {
        for j := 0; j < len(arr)-1-i; j++ {
            if arr[j] > arr[j+1] {
                arr[j], arr[j+1] = arr[j+1], arr[j]
            }
        }
    }

    return arr
    //fmt.Println(arr)

}
func main() {
    arr := [10]int{9, 1, 5, 6, 8, 4, 7, 10, 3, 2}
    //数组作为函数参数是值传递
    //形参和实参是不同的存储单元
    // 内存中有两份独立的数组存储不同的数据
    // 在函数调用结束形参单元销毁 不会影响主调函数中实参的值

    //如果想通过函数计算结果并传递给实参 需要使用数组作为函数的返回值
    arr = BubbleSort(arr)

    fmt.Println(arr)

}

###2.切片作为函数参数

package main

import "fmt"

//切片名作为函数参数 返回值
func test(s []int) []int{
    //fmt.Println(s)
    //fmt.Println(len(s))
    //s[0]=123
    //s[5]=666
    //fmt.Printf("%p\n", s)

    //for i := 0; i < len(s)-1; i++ {
    //    for j := 0; j < len(s)-1-i; j++ {
    //        if s[j] > s[j+1] {
    //            s[j], s[j+1] = s[j+1], s[j]
    //        }
    //    }
    //}

    //如果是函数中使用append进行数据添加时 形参的地址改变就不会在指向实参的地址

    s = append(s, 11, 12, 13, 14, 15)
    fmt.Printf("%p\n",s)
    fmt.Println(s)
    return s
}
func main() {
    //切片名本身就是一个地址
    slice := []int{9, 1, 5, 6, 8, 4, 7, 10, 3, 2}
    fmt.Printf("%p\n", slice)
    //地址传递  形参可以改变实参
    slice=test(slice)

    fmt.Println(slice)
}

###3.字符串处理函数

package main

import (
    "strings"
    "fmt"
)

func main0301() {
    //查找一个字符串在另外一个字符串中是否出现
    str1:="hello world"
    str2:="llo"
    //Contains(被查找字符串,查找字符串)
    //一般用作于模糊查找
    b:=strings.Contains(str1,str2)
    if b{
        fmt.Println("找到了")
    }else{
        fmt.Println("未找到")
    }
}


func main0302(){
    //字符串切片
    slice:=[]string{"1234","4637","2907","2308"}
    //字符串连接
    str:=strings.Join(slice,",")
    fmt.Println(str)

}


func main0303(){

    str1:="hello worllod"

    str2:="llo"

    //查找一个字符串在另外一个字符串中第一次出现的位置 返回值为int类型是下标
    i:=strings.Index(str1,str2)

    fmt.Println(i)
}
func main0304(){
    str:="性感法师在线讲课"
    //将一个字符串重复n次
    str1:=strings.Repeat(str,3)
    fmt.Println(str1)
}

func main0305(){


    str:="性感法师在线性感讲课"
    //字符串替换  用作于屏蔽敏感词汇
    //如果替换次数小于0 表示全部替换
    str1:=strings.Replace(str,"性感","**",-1)
    fmt.Println(str1)

}

func main0306(){
    //str1:="1300-199-1433"//13001991433
    str1:="123456@qq.com"
    //将字符串按照标志位进行切割变成切片
    slice:=strings.Split(str1,"@")

    //str2:=strings.Join(slice,"")

    fmt.Println(slice[0])
    //fmt.Println(str2)
}

func main0307(){

    str:="=====a=u=ok========"
    //去掉字符串头尾内容
    str1:=strings.Trim(str,"=")

    fmt.Printf("%s",str1)
}

func main0308(){

    str:="    are you ok     "

    //去掉字符串中的空格转成切片  一般用于做统计单词个数
    slice:=strings.Fields(str)

    //fmt.Println(slice)
    //for _,v:=range slice{
    //    fmt.Printf("=%s=\n",v)
    //}
    fmt.Println(len(slice))
}



func main(){
    //查找
    //bool类型=strings.Contains(被查找字符串,查找字符串)
    //int类型=strings.Index(被查找字符串,查找字符串)

    //分割组合
    //string类型=strings.Join(字符串切片,标志)
    //[]string类型=strings.Split(切割字符串,标志)

    //重复和替换
    //string类型=strings.Repeat(字符串,次数)
    //string类型=strings.Replace(字符串,被替换字符串,替换字符串,次数)

    //去掉内容
    //string类型=strings.Trim(字符串,去掉字符串)
    //[]string类型=strings.Fields(字符串)
}

###4. 字符串类型转换

package main

import (
    "fmt"
    "strconv"
)

func main0401() {

    str:="hello world"

    //将字符串转成字符切片  强制类型转换
    slice:=[]byte(str)

    //fmt.Println(slice)


    slice[4]='a'
    for i:=0;i<len(slice);i++{
        fmt.Printf("%c",slice[i])
    }

}
func main0402(){
    //字符切片
    slice:=[]byte{'h','e','l','l','o',97}

    //fmt.Println(slice)

    fmt.Println(string(slice))
}
func main0403(){
    //将其他类型转成字符串  Format
    //b:=false
    //str:=strconv.FormatBool(b)
    //fmt.Println(str)


    //str:=strconv.FormatInt(123,10)//在计算机中进制可以表示2-36进制  2 8 10 16
    //fmt.Println(str)

    //str:=strconv.FormatFloat(3.14159,'f',4,64)
    //fmt.Println(str)


    str:=strconv.Itoa(123)
    fmt.Println(str)
}

func main0404(){
    //将字符串转成其他类型
    //b,err:=strconv.ParseBool("true")
    //if err != nil{
    //    fmt.Println("类型转化出错")
    //}else{
    //    fmt.Println(b)
    //}

    //value,_:=strconv.ParseInt("abc",16,64)
    //fmt.Println(value)

    //value,_:=strconv.ParseFloat("3.14159",64)
    //fmt.Println(value)


    //value,_:=strconv.Atoi("123")
    //
    //fmt.Println(value)
}

func main(){

    slice:=make([]byte,0,1024)
    //将其他类型转成字符串添加到字符切片里面

    slice=strconv.AppendBool(slice,false)
    slice=strconv.AppendInt(slice,123,2)

    slice=strconv.AppendFloat(slice,3.14159,'f',4,64)
    slice=strconv.AppendQuote(slice,"hello")

    fmt.Println(string(slice))
}

##3.Map

前面我们学习了GO语言中数组,切片类型,但是我们发现使用数组或者是切片存储的数据量如果比较大,那么通过下标来取出某个具体的数据的时候相对来说,比较麻烦。例如:

现在要取出切片中存储的“王五”,那么需要数一下对应的下标值是多少,这样相对来说就比较麻烦。有没有一种结构能够帮我们快速的取出数据呢?就是字典结构。
说道字典大家想到的就是:

在使用新华字典查询某个字,我们一般都是根据前面的部首或者是拼音来确定出要查询的该字在什么位置,然后打开对应的页码,查看该字的解释。
GO语言中的字典结构是有键和值构成的。
所谓的键,就类似于新华字典的部首或拼音,可以快速查询出对应的数据。

如下图所示:
title
通过该图,发现某个键(key)都对应的一个值(value),如果现在要查询某个值,直接根据键就可以查询出某个值。
在这里需要注意的就是 字典中的键是不允许重复的,就像身份证号一样。

###1字典结构定义

package main

import "fmt"

func main0701() {

    //arr:=[2]int{1,2}
    //map中的key类型必须支持== !=  一遍建议写基本类型
    //map中的数据是无序的
    m := make(map[string][3]int)

    m["小明"] = [3]int{100, 99, 100}
    m["小罗"] = [3]int{3, 4, 5}
    m["乾隆"] = [3]int{101, 101, 101}

    //fmt.Println(m)

    for _, v := range m {
        //fmt.Println(k,v)

        //fmt.Printf("%T\n",v)
        //fmt.Printf("姓名:%s  语文:%d 数学:%d 英语:%d\n", k, v[0], v[1], v[2])

        for i := 0; i < len(v); i++ {
            fmt.Println(v[i])
        }
    }
}
func main0702() {
    m := make(map[int]string)

    m[101] = "刘备"
    m[102] = "关羽"
    m[103] = "李逵"

    //在map中只能通过key找到值
    //fmt.Println(m["李逵"])//err
    //在map中如果没有提供key找到具体的值 打印value类型的默认值
    //fmt.Println(m[105])

    //在毛重可以判断key和值是否存在
    value, ok := m[101]

    fmt.Printf("%T\n",ok)

    if ok {
        fmt.Println(value)
    } else {
        fmt.Println("未找到数据")
    }
}

func main() {
    m := make(map[int]string)

    m[101] = "刘备"
    m[102] = "关羽"
    m[103] = "李逵"

    //删除map中的一个元素 根据key进行删除
    delete(m,103)
    delete(m,102)
    delete(m,101)
    //delete在进行数据删除时 如果key不存在 不会报错
    delete(m,101)

    fmt.Println(m)

}

###2map作为函数参数

package main

import "fmt"

//将map作为函数参数
func demo(m map[int]string) {

    m[4] = "刘先生"
    m[5] = "阿先生"
    fmt.Println(m)
    //fmt.Printf("%p\n",m)
    delete(m, 5)
    fmt.Println(m)

}
func main() {

    m := map[int]string{1: "雷锋", 2: "董存瑞", 3: "邱少云"}
    //地址传递  引用传递  形参和实参指向内存中相同的地址段  修改形参会影响实参的值
    demo(m)
    fmt.Println(m)
    //fmt.Printf("%p\n",m)

}

##4.结构体

###1. 结构体定义和使用

package main

import "fmt"

//结构体定义在函数外部
//定义函数类型
//type  functye func(int,int)int

//type 结构体名 struct {
//    结构体成员列表
//    成员名 数据类型
//    姓名 string
//    age int
//}

//结构体是全局的可以在项目所有文件使用
//结构体是一种数据类型
type student struct {
    id    int
    name  string
    age   int
    sex   string
    score int
    addr  string
}

func main0901() {

    //定义结构体变量 复合类型
    //var 变量名 结构体名
    //var stu student
    //
    ////为结构体成员赋值 包名.函数名 结构体.成员  对象.方法
    //stu.name = "张大帅"
    //stu.score = 99
    //stu.addr = "奉天皇姑"
    //stu.sex = "男"
    //stu.age = 58
    //stu.id = 1

    //定义结构体是为成员赋值
    //var stu student = student{1, "张宗昌", 49, "男", 5, "山东济南"}
    //自动推导类型和指定成员赋值
    stu := student{name: "孙殿英", score: 60, sex: "男", addr: "北平", id: 1, age: 42}
    fmt.Println(stu)
}
func main0902() {
    stu := student{101, "朱德", 60, "男", 101, "四川"}

    fmt.Printf("%p\n", &stu)
    fmt.Printf("%p\n", &stu.id)
    //结构体成员为string 需要和结构体最大的数据类型进行对齐
    fmt.Printf("%p\n", &stu.name)
    fmt.Printf("%p\n", &stu.age)
    fmt.Printf("%p\n", &stu.sex)
    fmt.Printf("%p\n", &stu.score)
    fmt.Printf("%p\n", &stu.addr)
}

func main() {
    stu := student{102, "聂先生", 60, "男", 101, "湖南"}

    //将结构体变量赋值
    stu1 := stu
    stu1.id = 103
    //
    //fmt.Println(stu1)
    //fmt.Println(stu)

    //两个结构体比较 是比较所有成员 如果成员相同 结果为真  支持== !=比较操作
    if stu1 == stu {
        fmt.Println("相同")
    } else {
        fmt.Println("不相同")
    }

}

###2. 结构体数组

package main

import "fmt"

type Student struct {
    id    int
    name  string
    age   int
    sex   string
    score int
    addr  string
}

//结构体数组作为函数参数
func Sort(arr []Student) {

    for i := 0; i < len(arr)-1; i++ {
        for j := 0; j < len(arr)-1-i; j++ {
            //比较结构体成员信息
            if arr[j].age > arr[j+1].age {
                //交换数组元素
                arr[j], arr[j+1] = arr[j+1], arr[j]
            }
        }
    }

    //fmt.Println(arr)
}
func main() {

    //结构体数组 |  切片
    var arr []Student = []Student{
        Student{101, "唐三藏", 32, "男", 100, "东土大唐"},
        Student{102, "孙悟空", 532, "男", 100, "花果山"},
        Student{103, "猪悟能", 600, "男", 100, "高老庄"},
        Student{104, "沙悟净", 180, "男", 100, "流沙河"},
        Student{105, "白龙马", 10, "男", 100, "西海"}}

    //fmt.Println(arr)

    //打印结构体信息
    //for i := 0; i < len(arr); i++ {
    //    fmt.Println(arr[i])
    //}
    //修改指定结构体成员的信息
    //arr[1].score = 60
    //arr[2].score = 10
    //fmt.Println(arr)

    //结构体切片添加数据
    arr = append(arr, Student{106, "如来大魔王", 9999, "男", 100, "西天"})

    //结构体数组作为函数参数是值传递
    //结构体切片作为函数参数是地址传递(引用传递)
    Sort(arr)
    fmt.Println(arr)
}

##练习

###01结构体作为map中的value

package main

import "fmt"

type student struct {
    name  string
    sex   string
    age   int
    score int
    addr  string
}

func main0101() {
    //将结构体作为map中的value
    m := make(map[int]student)

    //map中的数据不建议排序操作
    m[101] = student{"擎天柱", "男", 30, 100, "赛博坦星球"}
    m[102] = student{"大黄蜂", "男", 10, 59, "赛博坦星球"}

    //fmt.Println(m)

    //fmt.Println(m[101])

    delete(m, 101)
    fmt.Println(m)
}

func main0102() {
    //value类型是一个切片
    m := make(map[int][]student)

    m[101] = []student{student{"擎天柱", "男", 30, 100, "赛博坦星球"},
        student{"大黄蜂", "男", 10, 59, "赛博坦星球"}}

    m[101] = append(m[101], student{"伯爵", "男", 30, 88, "赛博坦星球"})

    m[102] = []student{student{"水兵月", "女", 18, 100, "月亮"}}
    m[102] = append(m[102], student{"月野兔", "女", 20, 99, "地球"})
    fmt.Println(m[102])
}

###02结构体作为函数参数

package main

import "fmt"

type Student struct {
    id    int
    name  string
    age   int
    sex   string
    score int
    addr  string
}

//结构体变量作为函数参数
func test(stu Student) {
    stu.name = "野猪佩奇"
    fmt.Println(stu)

}

func main0201() {
    stu := Student{101, "喜羊羊", 6, "男", 100, "羊村"}
    //值传递
    test(stu)

    fmt.Println(stu)
}
func test1(m map[int]student) {
    //指针不能直接.成员
    //m[102].name = "威震天"//err

    stu := m[102]
    stu.name = "威震天"
    m[102] = stu
    //fmt.Println(stu)
    //fmt.Printf("%T\n", stu)
    //fmt.Println(m[102])
}
func main() {

    //将结构体作为map中的value
    m := make(map[int]student)

    //map中的数据不建议排序操作
    m[101] = student{"擎天柱", "男", 30, 100, "赛博坦星球"}
    m[102] = student{"大黄蜂", "男", 10, 59, "赛博坦星球"}

    //将map作为函数参数
    test1(m)
    fmt.Println(m)

}

##5. 指针–不容易懂重点

01指针定义和使用–不容易懂重点

现在已经知道怎样获取变量在内存中的地址,但是如果想将获取的地址进行保存,应该怎样做呢?
可以通过指针变量来存储,所谓的指针变量:就是用来存储任何一个值的内存地址。
指针变量的定义如下:
title

指针变量p的定义是通过 这个符号来定义,指针变量p的类型为int, 表示存储的是一个整型变量的地址。
如果指针变量p 存储的是一个字符串类型变量的地址,那么指针变量p的类型为*string
p=&i :该行代码的意思是,将变量i的地址取出来,并且赋值给指针变量p. 也就是指针变量p指向了变量i的存储单元。
可以通过如下图来表示:

title

在以上图中,一定要注意:指针变量p存储的是变量i的地址。

package main

import "fmt"

func main0301() {

    var a int = 10
    //fmt.Printf("%p\n", &a)

    //    定义指针变量存储变量的地址
    var p *int = &a

    //fmt.Printf("%p\n", p)
    //通过指针间接修改变量的值
    //写操作
    *p = 123

    //fmt.Println(a)
    //读操作
    fmt.Println(*p)

}
func main0302() {

    //声明指针变量 默认值为0x0 (nil)
    //内存地址编号为0  0-255的空间为系统占用  不允许用户访问(读写)
    //空指针
    var p *int = nil

    fmt.Println(p)
    //new(数据类型) 开辟数据类型对应的内存空间  返回值为数据类型指针

    //gc  垃圾回收机制
    p = new(int)

    *p = 123
    fmt.Println(p)

    fmt.Println(*p)
    //fmt.Printf("%p\n", p)
    //fmt.Println(*p)
    //*p = 123
    //
    //fmt.Println(*p)
}
func main() {

    //野指针  指针变量指向了一个未知空间 会报错
    //var p *int = *int(0xc042058088)
    //指针变量必须有一个合理的指向
    //在程序中允许出现空指针 不允许出现野指针

    //fmt.Println(*p)
}

###2.指针变量作为函数参数

package main

import "fmt"

//func swap(a, b int) {
//    a, b = b, a
//}

//指针变量作为函数参数
func swap(a *int, b *int) {
    //fmt.Println(*a)
    //fmt.Println(*b)

    //&变量  取地址操作  引用运算符
    //*指针变量 取值操作  解引用运算符
    //temp := *a
    //
    //fmt.Printf("temp类型:%T\n", temp)
    //*a = *b
    //*b = temp
    *a, *b = *b, *a
}
func main() {

    //a := 10.9 //float64
    ////通过自动推到类型创建指针变量
    ////所有的指针类型都存储的是一个无符号十六进制整型数据
    //p := &a //*float64
    ////*int 类型
    //fmt.Printf("%T\n", p)
    //fmt.Println(p)
    a := 10 //int
    b := 20
    //值传递
    //swap(a, b)
    //地址传递 形参可以改变实参的值
    swap(&a, &b)

    fmt.Println(a, b)
}

###3. 数组指针

前面在讲解数组的时候,我们用数组作为函数参数,但是数组作为参数进行传递是值传递,如果想引用传递,可以使用数组指针。具体使用方式如下:

title
定义一个数组,作为函数Swap的实参进行传递,但是这里传递的是数组的地址,所以Swap的形参是数组指针。,
这时指针p,指向了数组a,对指针p的操作实际上是对数组a的操作,所以如果直接执行如下语句:fmt.Println(p),会输出数组a中的值。也可以通过p结合下标将对应的值取出来进行修改。最终在main函数中输出数组a,发现其元素也已经修改。
当然,我们也可以通过循环的方式来将数组指针中的数据打印出来:

package main

import "fmt"

func main0501() {

    arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    //定义指针 指向数组  数组指针
    //自动推导类型创建数组指针
    //p := &arr

    //指针和数组建立关系
    var p *[10]int
    p = &arr

    //fmt.Printf("%T\n", p)

    //通过指针间接操作数组

    //fmt.Println(len(p))
    for i := 0; i < len(p); i++ {
        fmt.Println(p[i])
    }
    //(*数组指针变量)[下标]=值
    //(*p)[1] = 222
    //数组指针可以直接操作数组元素
    //p[1] = 222//ok
    //fmt.Println(*p)
}

func main0502() {
    arr := [5]int{1, 2, 3, 4, 5}
    //指针变量和要存储的数据类型要相同

    //p1和p2在内存中指向相同的地址  但是p1和p2的类型不同 一个是指向整个数组   一个是指向数组的元素
    p1 := &arr
    //p2 := &arr[0]

    //fmt.Printf("%p\n", p1)
    //fmt.Printf("%p\n", p2)
    //
    //fmt.Printf("%T\n", p1)
    //fmt.Printf("%T\n", p2)

    for i := 0; i < len(p1); i++ {
        fmt.Println(p1[i])
    }

}

//将数组指针作为函数参数

//func test2(p *[5]int) {
//
//    //通过指针间接操作数组
//    p[2] = 123
//
//}
//将数组指针作为函数参数
func BubbleSort(arr *[10]int) {

    for i := 0; i < len(arr)-1; i++ {
        for j := 0; j < len(arr)-1-i; j++ {
            if arr[j] > arr[j+1] {
                arr[j], arr[j+1] = arr[j+1], arr[j]
            }
        }
    }
}
func main() {
    arr := [10]int{9, 1, 5, 6, 8, 4, 10, 7, 2, 3}
    //p := &arr
    //地址传递
    //test2(p)

    BubbleSort(&arr)
    fmt.Println(arr)
}

###4. 指针数组–不容易懂重点
上一小节,讲解到的是数组指针,也就是让一个指针指向数组 ,然后可以通过该指针来操作数组。还有一个概念叫指针数组,这两个概念很容混淆,指针数组指的是一个数组中存储的都是指针(也就是地址)。也就是一个存储了地址的数组。
下面通过一个案例,看一下指针数组的应用
title

package main

import "fmt"

func main0601() {

    //var arr [10]int
    //指针数组  数组元素是指针类型
    a := 10
    b := 20
    c := 30
    var arr [3]*int = [3]*int{&a, &b, &c}

    //fmt.Println(arr)
    //
    //fmt.Println(&a)
    //fmt.Println(&b)
    //fmt.Println(&c)

    //*arr[1] = 200

    //fmt.Println(b)

    for i := 0; i < len(arr); i++ {
        fmt.Println(*arr[i])
    }
}
func main() {
    a := [3]int{1, 2, 3}
    b := [3]int{4, 5, 6}
    c := [3]int{7, 8, 9}

    //p := &a

    //二维数组模型
    var arr [3]*[3]int = [3]*[3]int{&a, &b, &c}

    fmt.Println(arr)
    //fmt.Printf("%T\n", p)

    //for i := 0; i < len(arr); i++ {
    //    //fmt.Println(*arr[i])//[3]int类型
    //    for j := 0; j < len(*arr[i]); j++ {
    //        fmt.Printf("%d ", (*arr[i])[j])
    //    }
    //    fmt.Println()
    //}

    (*arr[1])[1] = 555

    fmt.Println(b)
}

###5数组指针与指针数组的辅助理解

####数组指针(也称行指针)
定义 int (*p)[n];
()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。

如要将二维数组赋给一指针,应这样赋值:

int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
p=a;        //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++;       //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]

所以数组指针也称指向一维数组的指针,亦称行指针。

####指针数组
定义 int p[n];
[]优先级高,先与p结合成为一个数组,再由int
说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 p=a; 这里p表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组:

int *p[3];
int a[3][4]; p++;

//该语句表示p数组指向下一个数组元素。注:此数组每一个元素都是一个指针

for(i=0;i<3;i++)
p[i]=a[i] 这里int *p[3]

表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
所以要分别赋值。

这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。
比如要表示数组中i行j列一个元素:

*(p[i]+j)*(*(p+i)+j)(*(p+i))[j]、p[i][j]

优先级:()>[]>*

###6.指针和切片

package main

import "fmt"

func main0701() {
    slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    //指针和切片建立关系
    p := &slice
    //fmt.Printf("%p\n", p)
    //fmt.Printf("%p\n", slice)

    //*[]int
    //fmt.Printf("%T\n", p)
    //因为指向切片的指针 加下标找不到具体元素
    //p[1] = 222//err

    //(*p)[1] = 222

    //fmt.Println(slice)

    //通过指针间接操作切片的元素
    for i := 0; i < len(*p); i++ {
        fmt.Println((*p)[i])
    }
}

//func test2(s []int) {
//
//    s = append(s, 1, 2, 3)
//    fmt.Println(s)
//}

//切片指针作为函数参数
func test3(s *[]int) {

    *s = append(*s, 1, 2, 3)
}
func main() {
    s := []int{1, 2, 3, 4, 5, 6}

    //test2(s)
    //地址传递
    test3(&s)
    fmt.Println(s)
}

###7.结构体指针变量
我们前面定义了指针指向了数组,解决了数组引用传递的问题。那么指针是否可以指向结构体,也能够解决结构体引用传递的问题呢?完全可以。
下面我们先来看一下,结构体指针变量的定义:
title
也可以使用自动推导类型
title

现在定义了一个结构体指针变量,那么可以通过该指针变量来操作结构体中的成员项。
title
前面在讲解结构时,用结构体作为函数的参数,默认的是值传递,那么通过结构体指针,可以实现结构体的引用传递。具体实现的方式如下:
title

package main

import "fmt"

type Person struct {
    id   int
    name string
    sex  string
    age  int
    addr string
}

func main0801() {

    var per Person = Person{101, "张三疯", "男", 180, "武当山"}

    //fmt.Printf("%p\n", &per)

    //定义指针接收结构体变量地址
    //p := &per
    var p *Person = &per
    //fmt.Printf("%T\n", p) //*Person 类型

    //通过指针间接修改结构体成员的值
    (*p).name = "张君宝"
    //指针可以直接操作结构体成员
    p.age = 140
    p.id = 1000

    fmt.Println(per)
}

//结构体指针作为函数参数
func test4(p *Person) {
    p.addr = "斯塔克公司"
}
func main0802() {
    //结构体指针作为函数参数
    var per Person = Person{101, "钢铁侠", "男", 180, "武当山"}

    //地址传递 形参可以改变实参的值
    test4(&per)

    fmt.Println(per)
}

func main0803() {
    arr := [3]Person{Person{101, "钢铁侠", "男", 180, "斯塔克公司"},
        Person{102, "绿巨人", "男", 38, "实验室"},
        Person{103, "黑寡妇", "女", 23, "前苏联"}}
    //指向结构体数组的指针
    p := &arr

    fmt.Printf("%p\n", p)

    //p[0].age = 40//ok
    (*p)[0].age = 40 //ok

    for i := 0; i < len(p); i++ {
        fmt.Println(p[i])
    }

}

func main() {
    //map类型变量
    m := make(map[int]*[3]Person)

    //fmt.Printf("%T\n", m)
    m[1] = new([3]Person) //返回值类型为*[3]Person
    m[1] = &[3]Person{Person{101, "钢铁侠", "男", 180, "斯塔克公司"},
        Person{102, "绿巨人", "男", 38, "实验室"},
        Person{103, "黑寡妇", "女", 23, "前苏联"}}

    m[2] = new([3]Person)
    m[2] = &[3]Person{Person{101, "索尔", "男", 180, "斯塔克公司"},
        Person{102, "美队", "男", 38, "实验室"},
        Person{103, "黑豹", "女", 23, "前苏联"}}

    for k, v := range m {
        fmt.Println(k, *v)
    }

    ////数组指针
    //var p *[3]int
    //
    ////创建内存空间存储[3]int
    //
    //p = new([3]int)
    //
    //p[0] = 123
    //p[1] = 222
    //p[2] = 666
    //fmt.Println(p)
}

###8. 多级指针

package main

import "fmt"

func main0901() {
    a := 10
    //b := 20
    //  一级指针 指向变量的地址
    p := &a

    //二级指针 指向一级指针的地址
    var pp **int = &p

    //通过二级指针连接修改一级指针的值
    //*pp = &b
    //通过二级指针间接修改变量的值
    **pp = 100
    //var ppp ***int
    //var pppp ****int
    //pp := &p
    //*int
    fmt.Printf("%T\n", p)
    fmt.Printf("%T\n", pp)
}
func main0902() {
    a := 10

    var p *int = &a
    var pp **int = &p
    //三级指针指向二级指针的地址
    var ppp ***int = &pp
    //引用运算符 不能连续使用 如果放在一起成为 逻辑与

    //三级指针
    //ppp=&pp
    //二级指针
    //*ppp=pp=&p
    //一级指针
    //**ppp=*pp=p=&a
    //变量的值
    //***ppp=**pp=*p=a
}

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~