一、GO变量
1.1 变量的介绍
变量概念 变量相当于内存中的一个数据存储空间,可以将变量看做是一个房间的门牌号,通过门牌号我们可以找到房间,同样的道理,通过变量名可以访问到变量(值)
变量使用步骤
- 声明变量 (也叫定义变量)
- 非变量赋值
- 使用变量
1.2 变量快速入门案例
案例: 声明变量i,类型为int类型,并给变量i赋值数据10,最后打印量变量i
package main
import "fmt"
func main() {
// 定义变量/声明变量
var i int
//给 i赋值
i = 10
//使用变量
fmt.Println("i=",i)
}
结果
1.3 变量的声明初始化和赋值
声明变量
基本语法: var 变量名 数据类型
var a int
声明了一个变量,变量名是a
var num1 float32
声明了一个变量,表示一个单精度类型的小数,变量名是num1
初始化变量
在声明变量的时候就给值
var a int =10
初始化变量a并赋值10
如果使用类型推导,可以省略数据类型
var b =100
变量赋值
比如先声明了变量: var abc int
//默认为0
然后,在给值abc = 100
,这就是给变量赋值
1.4 变量使用注意事项
- 变量表示内存中的一个存储区域
- 该区域有自己的名称 (变量名)和类型(数据类型)
示意图:
Golang变量使用的三种方式
-
第一种: 指定变量类型,
声明后若不赋值,使用默认值
(默认值为0)func main() { var i int fmt.Println("i=",i) }
-
第二种: 根据值自行判定变量类型 (类型推导)
func main() { var num = 10.11 fmt.Println("num=",num) }
-
第三种: 省略var,
:=
左侧的变量不应该是已经声明过的,否则编译报错
func main() { name := "sundayhk" fmt.Println("name=", name) }
1) 多变量声明
在编写过程中,我们有时需要一次性声明多个变量,Golang也提供这样的语法
案例如下:
//演示Go如何一次性声明多个变量
func main() {
var n1,n2,n3 int
fmt.Println("n1=",n1,"n2=",n2,"n3=",n3)
}
//一次性声明多个变量并赋值
func main() {
var n1,name,n3=100,"tom",888
fmt.Println("n1=",n1,"name=",name,"n3=",n3)
}
结果如下:
n1= 100 name= tom n3= 888
//一次性声明多个变量,使用类型推导
func main() {
n1,name,n3:=100,"tomcat~",888
fmt.Println("n1=",n1,"name=",name,"n3=",n3)
}
结果如下:
n1= 100 name= tomcat~ n3= 888
2) 一次性声明全局变量 [在GO中函数外部定义变量就是去全局变量]
package main
import "fmt"
var n1 =100
var n2 =200
var name = "sundayhk"
func main() {
fmt.Println("n1=",n1,"n2=",n2,"name=",name)
}
结果如下:
n1= 100 n2= 200 name= sundayhk
3) 该区域的数据值可以在同一类型范围内不断变化,但是不可以改变数据类型
package main
import "fmt"
func main() {
//该区域的数据值可以在同一类型范围内不断变化
//正确案例
var i int = 10
i = 30
i = 40
fmt.Println("i=",i)
//错误案例: 不可以改变数据类型
//1.10属于浮点数,不可以使用int整数类型
i = 1.2 //错误
}
4) 变量在同一个作用域 (一个函数或者代码块)内不能重名
package main
import "fmt"
func main() {
var i int = 100
fmt.Println("i=", i)
i := 20 ////变量名不允许重复
var i bool //变量名不允许重复
}
变量=变量名+值+数据类型 Golang的变量如果没有赋值,编译器会使用默认值,比如int默认值为0,string默认值为空船,小数默认值为0
1.5 程序中 + 号使用
- 当左右两边都是数值型时,择做加法运算
- 当左右两边都是字符串,则做字符串拼接
package main
import "fmt"
func main() {
var i = 1
var a = 2
var r = i + a //做加法运算
fmt.Println("r=",r)
//string类型
var str1 = "hello "
var str2 = "sundayhk"
var res = str1 + str2 //做拼接操作
fmt.Println("res=", res)
}
二、数据类型基本介绍
2.1 整数类型
简单的来说,就是用于存放整数值的,比如0,-1,23456等等
序号 | 类型和描述 |
---|---|
1 | uint8 无符号8位整型(0到255) |
2 | unit16 无符号16位整型(0到65534) |
3 | uint32 无符号32位整型 (0到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) |
// int ,uint,rune,byte使用
package main
import "fmt"
func main() {
var a int = 9000
fmt.Println("a=", a)
var b uint = 1
var c byte = 255
fmt.Println("b=", b, "c=", c)
}
整数使用的细节
- golang各整数类型分: 有符号和无符号, int uint的大小和系统有关
- golang的整型默认声明为int 型
- golang程序中整型变量在使用时,遵守保大不保小的原则,即: 在保证程序正确运行下,尽量使用占用空间小的数据类型
var age byte = 30
- bit: 计算机中的最小存储单位。1byte=8 bit
2.2 小数类型
小数类型就是用于存放有小数点的数字,比如11.1 , 3.14149
案例演示
func main() {
var price float32 = 11.19
fmt.Println("price=", price)
}
小数类型分类
序号 | 类型和描述 |
---|---|
1 | float32 IEEE-754 32位浮点型数 |
2 | float64 IEEE-754 64位浮点型数 |
3 | complex64 32 位实数和虚数 |
4 | complex128 64 位实数和虚数 |
- 关于浮点数在机器中存放形式的简单说明,浮点数=符号位+指数为+尾数为 说明: 浮点数都是有符号的
- float64的精度比float32的要准确
- 如果我们要存储一个精度高的数(比如3.1415926)则应该选用float64
小数类型使用细节
- golang浮点类型有固定的范围和长度,不受操作系统的影响
- golang的浮点型默认声明为float64类型
func main() {
var num1 = 1.1
fmt.Printf("num1的数据类型是 %T \n",num1)
}
//输出结果
num1的数据类型是 float64
2.3 字符类型
字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。也就是说对于传统的字符串是由字符组成,而go的字符串不同,它是由字节组成的。
案例演示
func main() {
var n1 byte = 'a'
var n2 byte = '0' //字符0
//当我们直接输出byte值,就是输出了对应的字符串的码值
// 'a' ==>
fmt.Println("n1=", n1)
fmt.Println("n2=", n2)
//如果我们希望输出对应字符,需要使用格式化输出
fmt.Printf("n1=%c n2=%c \n",n1,n2)
}
//输出结果
n1= 97
n2= 48
n1=a n2=0
对于上面代码说明 1)如果我们保存在字符的ASCII表的,比如[0-1,a-z,A-Z..] 可以直接保存在byte 2) 如果我们保存的字符对应码值大于255,这时我们考虑使用int类型保存
字符类型使用细节
- 字符常量是用单引号(")括起来的单个字符。
- go中允许使用转义字符
\
来将其后的字符转变为特殊字符型常量 - go语音的字符使用UTF-8 (英文字符占用一个字节,汉字占用3个字节)
- 在go中,字符的本质是一个证书,直接输出时,是该字符对应的UTF-8编码的值
- 可以直接给某个变量赋一个数字,然后按照格式化输出时%c,会输出该数字对应的unicode字符
- 字符类型是可以进行计算的,相当于一个证书,因为它都有对应的Unicode码
func main() {
//字符类型是可以进行运算的,相当于一个证书,运算时是按照码值进行运算
var h1 = 10 + 'a' //a的码值为97,相当于10+97
fmt.Println("h1=",h1)
}
字符类型本质探讨
- 字符型存储到计算机中,需要将字符对应的码值(整数)找出来 存储: 字符—>对应码值—>二进制—>存储 读取: 二进制—>码值—>字符—>读取
- 字符和码值的对应关系是通过字符编码表决定的
- go语言的编码都统一成了utf-8
2.4 布尔类型
基本介绍
- 布尔类型也叫bool类型,bool类型数据只允许取true和false
- bool类型占用1个字节
- bool类型适用于
逻辑运算
,一般用于程序流程控制
演示golang中bool类型使用
func main() {
var b = false
fmt.Println("b=", b)
}
//输出结果为false
bool类型只能取true 或者false (默认为false)
2.5 基本数据类型默认值
在go中,数据类型都有一个默认值,当变量没有赋值时,就会保留默认值
数据类型 | 默认值 |
---|---|
整型 | 0 |
浮点型 | 0 |
字符串 | " " (空) |
布尔类型 | false |
2.6 基本数据类型相互转换
Golang和java/c不同,go在不同类型的变量之间赋值时需要显式转换。也就是golang中数据类型不能自动转换
基本语法
表达式T(v)将值v转换为类型T
T
: 代表数据类型,比如int32,int64,float32等等
v
:代表需要转换的变量
//基本数据类型转换案例
package main
import "fmt"
func main() {
var i int32 = 100
//将i转变成float
var n1 float32 = float32(i)
var n2 int8 = int8(i)
var n3 int64 = int64(i)
fmt.Printf("i=%v n1=%v n2=%v n3=%v \n", i, n1, n2, n3)
}
//输出结果:
i=100 n1=100 n2=100 n3=100
基本数据类型相互转换的注意事项
- Go中,数据类型的转换可以是从 表示范围小–>表示范围大,也可以范围大–>范围小
- 被转换的是变量存储的数据(即值),变量本身没有变化
- 在转换中,比如讲int64转成in8 [
-128---127
],编译时不会报错,只是转换结果是按溢出处理,和我们希望的结果不一样。因此在转换时需要考虑范围
func main() {
var num1 int64 = 99999
var num2 int8 = int8(num1)
fmt.Println("num2=", num2)
}
//输出结果
num2= -97 //不符合预期
2.7 package fmt
mt包实现了类似C语言printf和scanf的格式化I/O。格式化动作(‘verb’)源自C语言但更简单。
为了以后对格式化输出有更详细的了解,这里整理了一些fmt通用参数
- 通用:
序号 | 参数 | 说明 |
---|---|---|
1 | %v | 值的默认格式表示 |
2 | %+v | 类似%v,但输出结构体时会添加字段名 |
3 | %#v | 值的Go语法表示 |
4 | %T | 值的类型的Go语法表示 |
5 | %% | 百分号 |
-
布尔值:
%t 单词true或false
-
整数:
序号 | 参数 | 说明 |
---|---|---|
1 | %b | 表示为二进制 |
2 | %c | 该值对应的unicode码值 |
3 | %d | 表示为十进制 |
4 | %o | 表示为八进制 |
5 | %q | 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示 |
6 | %x | 表示为十六进制,使用a-f |
7 | %X | 表示为十六进制,使用A-F |
8 | %U | 表示为Unicode格式:U+1234,等价于"U+%04X" |
- 浮点数与复数:
序号 | 参数 | 说明 |
---|---|---|
1 | %b | 无小数部分、二进制指数的科学计数法,如-123456p-78; |
2 | %e | 科学计数法,如-1234.456e+78 |
3 | %E | 科学计数法,如-1234.456E+78 |
4 | %f | 有小数部分但无指数部分,如123.456 |
5 | %F | 等价于%f |
6 | %g | 根据实际情况采用%e或%f格式(以获得更简洁、准确的输出) |
7 | %G | 根据实际情况采用%E或%F格式(以获得更简洁、准确的输出) |
- 字符串和[]byte:
序号 | 参数 | 说明 |
---|---|---|
1 | %s | 直接输出字符串或者[]byte |
2 | %q | 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示 |
3 | %x | 每个字节用两字符十六进制数表示(使用a-f) |
4 | %X | 每个字节用两字符十六进制数表示(使用A-F) |
转载: https://studygolang.com/pkgdoc
2.8 基本数据类型和string转换
方式1: fmt.Sprintf("%参数",表达式)
func Sprintf(format string, a ... interface{}) string
Sprintf根据format参数生成格式化的字符串并返回该字符串,参数需要和表达式的数据类型匹配
fmt.Sprintf案例演示
package main
import "fmt"
func main() {
//定义数据类型
var num1 int64 = 99999
var num2 int8 = int8(num1)
var b bool = true
var mychar byte = 'a'
fmt.Println("num2=", num2)
//定义空值
var str string
str = fmt.Sprintf("%d", num1)
fmt.Printf("str type %T str=%q \n", str, str)
str = fmt.Sprintf("%f", num2)
fmt.Printf("str type %T str=%q \n", str, str)
str = fmt.Sprintf("%t", b)
fmt.Printf("srr type %T str=%q \n", str, str)
str = fmt.Sprintf("%c", mychar)
fmt.Printf("str type %T str=%q \n", str, str)
}
//输出结果如下
➜ 01 go run main.go
num2= -97
str type string str="99999"
str type string str="%!f(int8=-97)"
srr type string str="true"
str type string str="a"
在将String类型转换成基本数据类型时,要确保string类型能够转成有效的数据,例如可以将123
转成一个整数,但是不能把hello
转成一个证书,如果强制转换,golang直接将其结果转成0
2.9 指针
- 基本数据类型,变量存的就是值,也叫值类型
- 获取变量的地址用
&
,例如:var num int
获取num地址:&num
- 指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值
var ptr *int = &num
- 获取指针类型所指向的值,使用: *
package main
import "fmt
func main() {
//基本数据类型在内存布局
var i int = 10
fmt.Println("i的地址=", &i)
//1. ptr是一个指针变量
//2. ptr的类型 *int
//3. ptr 本身的值&i
var ptr *int = &i
fmt.Printf("ptr=%v \n",ptr)
fmt.Printf("ptr 的地址=%v \n", &ptr)
fmt.Printf("ptr 指向的值=%v \n", *ptr)
}
//输出结果
➜ 01 go run main.go
i的地址= 0xc0000b4008
ptr=0xc0000b4008
ptr 的地址=0xc0000ae020
ptr 指向的值=10
案例演示
- 写一个程序,获取一个int变量num的地址,并显示到终端
- 将num的地址赋给指针ptr,并通过ptr去修改num的值
package main
import "fmt"
func main() {
var num int = 10
fmt.Printf("num address=%v \n", &num)
//ptr变量
var ptr *int
ptr = &num
*ptr = 10 //这里修改,会到num值的变化
fmt.Println("num =", num)
fmt.Println("ptr =", &ptr)
}
//输出结果
➜ 01 go run main.go
num address=0xc0000b4008
num = 10
ptr = 0xc0000ae020
指针使用细节
- 值类型,都有对应的指针类型,形式为数据类型*,比如int的对应的指针就是 *int,float32对应的指针类型就是 \float32,以此类推2)值类型包括: 基本数据类型int系列,float系列,bool,string、数组和结构体struct*
2.10 值类型和引用类型
- 值类型: 基本数据类型int系列,float系列,bool,string、数组和结构体struct
- 引用类型: 指针、slice切片、map、管道chan、interface都是引用类型
值类型和引用类型的使用特点
- 值类型: 变量直接存储值,内存通常在栈中分配
- 引用类型: 变量存储的是一个地址,这个地址对应的空间才是真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就称为一个垃圾,由GC来回收
- 内存占和堆区示意图
2.11 标示符的命名规则
- 由26个英文字母大小写,0-9,_组成
- 数据不可以开头。
var 3num int
❌ - Golang中严格区分大小写
- 标示符不能包含空格
- 下划线
_
本身在Go中是一个特殊的标示符,称为空标示符
。可以代表任何其他的标示符,但是它对应的值会被忽略。所以仅能被作为占位符使用,不能作为标示符 - 不能以系统保留关键字作为标示符(一共有25个),比如
if
、break
、for
等
三、运算符
运算符是一种特殊的符号,用以表示数据的运算、复制和比较
运算符有以下分类
- 算术运算符
- 赋值运算符
- 比较运算符/关系运算符
- 逻辑运算符
- 位运算符
- 其它运算符
3.1 算术运算符
算术运算符是对数值类型的变量进行计算的,比如: 加减乘除
运算符 | 运算 | 范例 | 结果 |
---|---|---|---|
+ | 正号 | +3 | 3 |
- | 负号 | -4 | 4 |
+ | 加 | 5+5 | 10 |
- | 减 | 10-5 | 5 |
* | 乘 | 3*4 | 12 |
/ | 除 | 5/5 | 1 |
% | 取模(取余) | 7% 5 | 2 |
++ | 自增 | a=2 a++ | a=3 |
– | 自减 | a=2 a– | a=1 |
+ | 字符串相加 | “hello” + “sundayhk” | “hello sundayhk” |
案例演示 //演示/的使用特点
//演示/的使用特点
package main
import "fmt"
func main() {
//如果运算符都是整数,那么除后,去掉小数部分,保留整数部分
fmt.Println(10 / 4)
var n1 float32 = 10 / 4
fmt.Println(n1)
//如果希望保留小数部分,则需要有浮点数参与运算
var n2 float32 = 10.0 / 4
fmt.Println(n2)
}
//输出结果
➜ 01 go run main.go
2
2
2.5
//演示%的使用特点
package main
import "fmt"
func main() {
//%公式 a%b = a-a / b * b
fmt.Println("10%3=", 10%3) // = 1
fmt.Println("-10%3=", 10%3) //= -10 -(-10)/ 3*3 = -10-(-9) = -1
fmt.Println("10%-3=", 10%-3)
}
//输出结果
➜ 01 go run main.go
10%3= 1
-10%3= 1
10%-3= 1
// ++ – 的使用
package main
import "fmt"
func main() {
var i int = 10
i++ //等价 i = i+1
fmt.Println("i=", i)
i-- //等价i = i -1
fmt.Println("i=", i)
}
//输出结果
➜ 01 go run main.go
i= 11
i= 10
算术运算符使用注意事项
- 对于除号
/
,它的整数除和小数除是有区别的: 整数之间做除法时,只保留整数部分而舍弃小数部分。例如:x:=19/5
,结果是3 - 当对一个数取模时,可以等价
a%b=a-a/b*b
- Golang中的自增和自建不可以以下使用方式
a = i++
❌a=i--
❌if i++ >0
❌ - Golang中i++和i–只能写在变量后面,不能写在变量前面
++i
❌--i
❌
3.1.1 算术运算符练习题
- 假如还有97天放假,问: xxx个星期零xx天
package main
import "fmt
func main() {
var days int = 97
var week int = days / 7
var day int = days % 7
fmt.Printf("%d个星期零%d天\n", week, day)
}
//输出结果如下
➜ 01 go run main.go
13个星期零6天
- 定义一个变量保存华氏温度,华氏温度转换摄氏温度的公式为: 5/9*(华氏摄氏度-100),请求出华氏温度对应的摄氏温度
package main
import "fmt"
func main() {
var huashi float32 = 134.2
var sheshi float32 = 5.0 / 9 * (huashi - 100)
fmt.Printf("%v 对应的摄氏温度%v \n" , huashi,sheshi)
}
//输出结果
➜ 01 go run main.go
134.2 对应的摄氏温度19
3.2 关系运算符
- 关系运算符的结果都是bool型,也就是要么是true,要么是false
- 关系表达式 通常在 if结构的条件中或循环结构的条件中
- 关系运算符的结果都是bool型,也就是要么是true,要么是false
- 关系运算符组成的表达式,我们称之为关系表达式 : a >b
- 比较运算符 “==” 不能误写"="
3.3 逻辑运算符
用于连接多个条件(一般来讲就是关系表达式),最终的结果也是一个bool值
假定A值为True,B值为False
运算符 | 描述 | 实例 |
---|---|---|
&& | 逻辑与 运算符。如果两边的操作数都是True,则为True,否则为False |
(A && B)为False |
丨丨 | 逻辑或 运算符。如果两边的操作数有一个True,则为True,否则False |
(a 丨丨 b)为true |
! | 逻辑非 运算符。如果条件为True,则逻辑为False,否则为true |
!(a && b)为true |
案例演示
//演示逻辑运算符&&
使用
package main
import "fmt"
func main() {
var age int = 40
if age > 30 && age < 50 {
fmt.Println("OK")
}
}
//演示逻辑运算符||
使用
package main
import "fmt"
func main() {
var age int = 40
if age > 30 || age < 50 {
fmt.Println("OK")
}
}
注意事项 (1) && 也叫短路与: 如果第一个条件为false,则第二个条件不会判断,最终结果为false (2) || 也叫短路或: 如果第一个条件为true,则第二个条件不会判断,最终结果为true
3.4 赋值运算符
赋值运算符就是讲某个运算后的值,赋给指定的变量
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,将一个表达式的值赋给一个左值 | 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 |
3.5 位运算符
描述符 | 描述 |
---|---|
& | 按位与运算符"&“是双目运算符。其功能是参与运算符的两数各对应的二进制位相与 。运算规则是:同时为1,结果为1,否则为0 |
丨 | 按位或运算符"丨"是双目运算符。其功能是参与运算的两数各自对应的二进制相或。 运算规则是: 有一个为1,结果为1,否则为0 |
^ | 按位异或运算符”^“是双目运算符。其功能是参与运算的两数各对应的二进制位相异或。运算规则是: 当二进位不同时,结果为1,否则为0 |
« | 左移运算符”«" 是双目运算符。 其功能是把"«“左边的运算符的各二进制位全部左移若干位,高位丢弃,低位补0。左移n位就是乘以2的n次方 |
» | 右移运算符”»" 是双目运算符。 其功能是把"»" 运算的运算数的各二进制全部右移若干位,右移n位就是除以2的n次方 |
3.6 其他运算符说明
运算符 | 描述 | 实例 |
---|---|---|
& | 返回变量存储地址 | &a;将给出变量的实际地址 |
* | 指针变量 | *a;是一个指针变量 |
案例演示
package main
import "fmt"
func main() {
a := 100
fmt.Println("a的地址=", &a)
var ptr *int = &a
fmt.Println("ptr指向的值是=", *ptr)
}
//输出结果
➜ 01 go run main.go
a的地址= 0xc00001a090
ptr指向的值是= 100
3.7 运算符优先级
由高至低
3.8 键盘输入语句
在开发过程中,需要接受用户输入语句,可以使用Scanln
进行引用
案例演示 要求: 可以从前台接受用户信息, [姓名,年龄,薪水,是否通过考试]
1)使用fmt.Scanln()
获取
package main
import "fmt"
func main() {
var name string
var age byte
var sal float32
var isPass bool
fmt.Println("请输入姓名 ")
fmt.Scanln(&name)
fmt.Println("请输入年龄")
fmt.Scanln(&age)
fmt.Println("请输入薪水")
fmt.Scanln(&sal)
fmt.Println("请输入是否通过考试[true & false]")
fmt.Scanln(&isPass)
fmt.Printf("您的名字是:%v\n 您的年龄是:%v \n 您的薪水是:%v \n 是否通过考试:%v \n", name, age, sal, isPass)
}
//输入内容
请输入姓名
sundayhk
请输入年龄
20
请输入薪水
10000
请输入是否通过考试[true & false]
true
//输出内容
您的名字是:sundayhk
您的年龄是:20
您的薪水是:10000
是否通过考试:true
2)使用fmt.Scanf()
获取
package main
import "fmt"
func main() {
var name string
var age byte
var sal float32
var isPass bool
fmt.Println("请输入您的姓名,年龄,薪水,是否通过考试,请使用空格隔开")
fmt.Scanf("%s %d %f %t", &name, &age, &sal, &isPass)
fmt.Printf("名字是%v \n年龄是%v\n薪水是%v\n是否通过考试%v\n", name, age, sal, isPass)
}
//输入内容
➜ 01 go run main.go
请输入您的姓名,年龄,薪水,是否通过考试,请使用空格隔开
sundayhk 19 10000 false
//输出内容
名字是sundayhk
年龄是19
薪水是10000
是否通过考试false
四、进制
对于整数,有四种表示方式。
- 二进制: 0,1,满2进1 在go中,不能直接使用二进制来表示一个证书,它沿用了c的特点
- 十进制: 0-9,满10进1
- 八进制: 0-7,满8进1,以数字0开头表示
- 十六进制: 0-9及A-F,满16进1,以0x或者0X开头表示
此处A-F不区分大小写
4.1 进制转换
4.2 其他进制转十进制
4.3 二进制如何转十进制
4.4 位运算
五、程序流程控制
在程序中,程序运行的流程控制决定程序是如何执行的,主要有三大流程控制语句
- 顺序控制
- 分支控制
- 循环控制
5.1 顺序控制
程序从上到下逐行地执行,中间没有任何判断和跳转
//案例,顺序执行
package main
import "fmt"
func main() {
var name string
var age byte
var sal float32
var isPass bool
fmt.Println("请输入姓名 ")
fmt.Scanln(&name)
fmt.Println("请输入年龄")
fmt.Scanln(&age)
fmt.Println("请输入薪水")
fmt.Scanln(&sal)
fmt.Println("请输入是否通过考试[true & false]")
fmt.Scanln(&isPass)
fmt.Printf("您的名字是:%v\n 您的年龄是:%v \n 您的薪水是:%v \n 是否通过考试:%v \n", name, age, sal, isPass)
}
Go中定义变量时采用合法的前向引用
//正确操作✔️
package main
import "fmt"
func main() {
var num1 int = 10 //声明了num1
var num2 int = num1 + 20 //使用了num1
fmt.Println(num2)
}
//错误方式❎
package main
import "fmt"
func main() {
var num2 int = num1 + 20 //使用了num1
var num1 int = 10 //声明了num1
fmt.Println(num2)
}
5.2 分支控制
分支控制就是让程序有选择执行,有三种形式
- 单分支
- 双分支
- 多分支
单分支控制
//基本语法
if 条件表达式 {
执行代码块
}
//当条件表达式为true时,就会执行{}的代码。注意{}是必须要有
案例: 编写一个程序,可以输入年了,如果年龄大于18,则输出"你的年了大于18"
package main
import "fmt"
func main() {
var age byte
fmt.Println("请输入您的年龄")
fmt.Scanln(&age)
if age > 18 {
fmt.Println("你的年龄大于18")
}
}
//输出结果
➜ 01 go run main.go
请输入您的年龄
20
你的年龄大于18
单分支流程图
双分支控制
基本语法
if 条件表达式{
执行代码块1
}else {
执行代码块2
}
//说明: 当条件表达式成立,即执行代码块1,否则执行代码块2. {}必须要有的
案例: 编写一个程序,可以输入年了,如果大于18岁则输出"您已经大于18岁",否则输出"未成年"
package main
import "fmt"
func main() {
var age byte
fmt.Println("请输入您的年龄")
fmt.Scanln(&age)
if age > 18 {
fmt.Println("你的年龄大于18")
} else {
fmt.Println("未成年")
}
}
//输出结果
➜ 01 go run main.go
请输入您的年龄
12
未成年
➜ 01 go run main.go
请输入您的年龄
20
你的年龄大于18
双分支只会执行其中一个分支
多分支控制
基本语法
if 条件表达式1 {
执行代码块1
}else if 条件表达式2{
执行代码块2
}
...
else {
执行代码块n
}
对于基本语法的说明 (1) 多分支的判断流程如下
- 1.1 先判断条件表达式1是否成立,如果为真,就执行代码1
- 1.2 如果条件表达式1为假,就去判断条件表达式2是否成立,如果条件表达式2位真,就执行代码块2
- 1.3 以此类推
- 1.4如果所有的条件表达式不成立,则执行else的语句块
(2) else 不是必须的 (3) 多分支只能有一个执行入口
多分支案例
小明参考考试当成绩是以下结果是进行奖励 成绩为100分时,奖励BMW 成绩为(80,99)时,奖励一台iPhone X 成绩为(60,80时),奖励iPad 其他时没有奖励 请从键盘输入成绩,并加以判断
package main
import "fmt"
func main() {
var fenshu int
fmt.Println("请输入查询分数")
fmt.Scanln(&fenshu)
if fenshu >= 100 {
fmt.Println("100分,牛逼")
} else if fenshu >= 80 && fenshu <= 90 {
fmt.Println("该加油了")
} else if fenshu >= 60 && fenshu <= 80 {
fmt.Println("等着挨揍吧")
} else {
fmt.Println("重开吧")
}
}
//输出结果
➜ exec1 go run main.go
请输入查询分数
50
重开吧
案例2: 参加百米运动会,用时8秒内进入决赛,否则提示淘汰。根据性别提示男子组或女子组
package main
import "fmt"
func main() {
var miao float64
fmt.Println("请输入秒数")
fmt.Scanln(&miao)
var nannv string
fmt.Println("请输入性别")
fmt.Scanln(&nannv)
if miao < 8 {
if nannv == "nan" {
fmt.Println("男子组")
} else {
fmt.Println("女子组")
}
fmt.Println("恭喜入围,用时", miao, "秒")
} else {
fmt.Println("您已淘汰")
}
}
//输出结果
➜ exec3 go run main.go
请输入秒数
6
请输入性别
nan
男子组
恭喜入围,用时 6 秒
➜ exec3 go run main.go
请输入秒数
9
请输入性别
nv
您已淘汰
案例,根据淡旺季的月份和年龄打印票价 4—10 旺季 成人(18-60) :60 儿童(<18) :半价 老人(>60): 1/3 淡季: 成人40 其他20
package main
import "fmt"
func main() {
var age int
var month int
var price float64 = 60.0
fmt.Println("请输入月份")
fmt.Scanln(&month)
fmt.Println("请输入年龄")
fmt.Scanln(&age)
if month >= 4 && month <= 10 {
if age > 60 {
fmt.Printf("%v月 票价%v 年龄%v \n", month, price/3, age)
} else if age >= 18 {
fmt.Printf("%v月 票价%v 年龄%v \n", month, price, age)
} else {
fmt.Printf("%v月 票价%v 年龄%v \n", month, price/2, age)
}
} else {
if age >= 18 && age < 60 {
fmt.Println("淡季成人票价40")
} else {
fmt.Println("淡季儿童和老人 票价20")
}
}
}
//输出结果
➜ exec4 go run main.go
请输入月份
8
请输入年龄
20
8月 票价60 年龄20
➜ exec4 go run main.go
请输入月份
1
请输入年龄
60
淡季儿童和老人 票价20
5.3 SWITCH 分支控制
switch 语句用于基于不同条件执行不同动作,每一个case分支都是唯一的,从上到下逐一测试,直到匹配为止。
匹配项后面也不需要再加break
//基本语法
switch 表达式{
case 表达式1,表达式2,....:
语句块
case 表达式3,表达式4, ....:
语句块
default:
语句块
}
- switch的执行流程是,先执行表达式,得到值,然后和case的表达式进行比较,如果相等,就匹配,然后执行对应case的语句块,最后退出switch控制
- 如果switch的表达式的值没有和任何的case的表达式匹配成功,则执行default的语句块。
- golang的case后的表达式可以有多个,使用逗号间隔
- golang中的case语句块不需要写break,因为默认会有,即在默认情况下,当程序执行完case语句后,就直接退出该switch控制结构
switch 案例: 请编写一个程序,该程序可以接受一个字符,比如:a,b,c,d,f a表示星期一,b表示星期二 … 根据用户的呼入显示相依的信息,要求使用switch局域完成
package main
import "fmt"
func main() {
var zm string
fmt.Println("请输入a,b,c,d")
fmt.Scanln(&zm)
switch zm {
case "a":
fmt.Println("周一")
case "b":
fmt.Println("周二")
case "c":
fmt.Println("周三")
default:
fmt.Println("请重新输入")
}
}
//输出结果
➜ 01 go run main.go
请输入a,b,c,d
a
周一
➜ 01 go run main.go
请输入a,b,c,d
b
周二
switch使用细节
1)case/switch后是一个表达式(即: 常量值、变量、一个有返回的函数都可以)
2)case 后的各个表达式的值的数据类型,必须和switch的表达式数据类型一致
3)case后面可以带多个表达式,使用逗号间隔。比如case 表达式1,表达式2
4)case后面的表达式如果是常量值,则要求不能重复
5)case后面不需要带break,程序匹配到一个case后就会执行对应的代码块,然后退出switch,如果一个都匹配不到,则执行default
6)default语句不是必须的
7)switch后也可以不带表达式,类似if –else分支来使用 case age > 90:
8)switch后也可以直接声明/定义一个变量,分号结束switch age := 90;
9)switch穿透-fallthrough
,如果在case语句块后增加allthrough,则会继续执行下一个case
案例: 对学生成绩大于60分,输出合格。低于60分的,输出不合格 (输入成绩不能大于100)
//第一种方式
package main
import "fmt"
func main() {
var cj byte
fmt.Println("请输入分数")
fmt.Scanln(&cj)
switch {
case cj > 60 && cj < 101:
fmt.Println("成绩合格")
case cj < 60:
fmt.Println("成绩不合格")
default:
fmt.Println("请重新输入")
}
}
//输出结果
➜ 02 go run main.go
请输入分数
100
请重新输入
➜ 02 go run main.go
请输入分数
59
成绩不合格
➜ 02
案例: 根据用户指定月份,打印该月份所属的集结。 3,4,5为春季,6,7,8位夏季,9,10,11 秋季,12,1,2位冬季
package main
import "fmt"
func main() {
var ji byte
fmt.Println("请输入月份 [1..12]")
fmt.Scanln(&ji)
switch ji {
case 3, 4, 5:
fmt.Println("春季")
case 6, 7, 8:
fmt.Println("夏季")
case 9, 10, 11:
fmt.Println("秋季")
case 12, 1, 2:
fmt.Println("冬季")
default:
fmt.Println("请重新输入")
}
}
//输出结果
➜ 03 go run main.go
请输入月份 [1..12]
4
春季
switch和if的比较
- 如果判断的具体数值不多,而且符合整数、浮点数、字符、字符串这几种类型。建议使用switch语句,简洁高效
- 其他情况,对区间判断和结果为bool类型的判断,使用if,if的适用范围更广。
5.4 FOR 循环控制
for循环语法格式
for 循环变量初始化; 循环条件;循环变量迭代{
循环操作(语句)
}
案例: for 循环打印10行
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
fmt.Println("hellow")
}
}
//输出结果
➜ 01 go run main.go
hellow
hellow
... //省略
hellow
hellow
语法格式说明
- 循环遍历初始值
- 循环条件
- 循环操作(语句),有人也叫循环体
- 循环变量迭代
for循环执行的顺序说明:
- 执行循环变量初始化,
i :=1
- 执行循环条件,
i <=10
- 如果循环条件为真,就执行循环操作 ,
fmt.Println("sundayhk")
- 执行循环变量迭代,
i++
可以解析为(i = i +1) - 反复执行 2,3,4 步骤,执行循环为Fakse,就退出循环
for循环使用的注意事项
1)循环条件是返回一个布尔值的表达式 2)for循环的第三种使用方式
for 循环判断条件{
//循环执行语句
}
//将变量初始化和变量迭代写到其他位置
案例演示
package main
import "fmt"
func main() {
j := 1 //循环变量初始化
for j <= 10 {
fmt.Println("您好,sundayhk", j)
j++
}
}
//输出结果
➜ 01 go run main.go
您好,sundayhk 1
您好,sundayhk 2
您好,sundayhk 3
您好,sundayhk 4
您好,sundayhk 5
您好,sundayhk 6
您好,sundayhk 7
您好,sundayhk 8
您好,sundayhk 9
您好,sundayhk 10
for循环的第三方使用方式
for {
//循环执行语句
}
上面的写法等价for ;;{} 是一个无线循环,通常需要配置break语句使用
package main
import "fmt"
func main() {
k := 1 //循环变量初始化
for {
if k <= 10 {
fmt.Println("ok~", k)
} else {
break
}
k++
}
}
//输出结果
➜ 01 go run main.go
ok~ 1
ok~ 2
ok~ 3
ok~ 4
ok~ 5
ok~ 6
ok~ 7
ok~ 8
ok~ 9
ok~ 10
案例: 打印机1 ~ 100直接所有是9的倍数的整数的个数及总和
package main
import "fmt"
func main() {
// 思路分析
// 1. 使用for 循环对max进行遍历
// 2. 当一个数是9% ==0 就是9的倍数
// 3. 我们需要声明两个变量count和sum来保存个数和总和
var max uint64 = 100
var count uint64 = 0
var sum uint64 = 0
var i uint64 = 1
for ; i <= max; i++ {
if i%9 == 0 {
count++
sum += i
}
}
fmt.Printf("count=%v sum=%v \n", count, sum)
}
//输出结果
➜ 01 go run main.go
count=11 sum=594
案例: 完成下面表达式的输出 0 + 6 = 6 1 + 5 = 6 2 + 4 = 6 3 + 3 = 6 4 + 2 = 6 5 + 1 = 6 6 + 0 = 6
package main
import "fmt"
func main() {
var n int = 6
for i := 0; i <= n; i++ {
fmt.Printf("%v + %v = %v \n", i, n-i, n)
}
}
//输出结果
➜ 01 go run main.go
0 + 6 = 6
1 + 5 = 6
2 + 4 = 6
3 + 3 = 6
4 + 2 = 6
5 + 1 = 6
6 + 0 = 6
5.5 WHILE和DO..WHILE的实现
Go语言没有while和do..while语法,可以通过for循环来实现其使用效果。
//循环变量初始化
for {
if 循环条件表达式{
break //跳出for循环..
}
循环操作(语句)
循环变量迭代
}
案例演示 使用while实现输出10句 " Hello ,word"
package main
import "fmt"
func main() {
var i int = 1
for {
if i >10 { //循环条件
break //跳出for循环,结束for循环
}
fmt.Println("hello word",i)
i++ //循环变量迭代
}
}
使用do..while完成输出10句 ok
package main
import (
"fmt"
)
func main() {
var a int = 1
for {
fmt.Println("hello ok", a)
a++ //循环变量迭代
if a > 10 {
break //a >10之后就跳出for循环
}
}
}
do..while说明
循环变量初始化
for {
循环操作(语句)
循环变量迭代
if 循环条件表达式{
break //跳出for循环
}
}
5.6 多重循环控制
- 将一个循环放在另一个循环体内,就形成了嵌套循环。在外面的for称为外层循环在里面的for循环称为内层循环。
- 实际上,嵌套循环就是把内循环当成外循环的循环体。当只有内层循环的循环条件为false时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的循环
- 外层循环次数为m次,内层为n此,则内循环体实际上需要执行m*n次
应用案例一
# 统计3个班学习情况,每个班有5名同学,求出各个班的平均分和所有班级的平均分[学生成绩从键盘输入]
//第一步: 分析思路
// 1.先统计一个班的数据,学生有5名
// 2.要求从键盘输入
// 3.sum要计算出总分和平均分
package main
import "fmt"
func main() {
var sum float64 = 0.0 //定义总数
for i := 1; i <= 5; i++ {
var fen float64 = 0.0 //每个学生的分数
fmt.Printf("请输出 %v 学生的分数 \n", i)
fmt.Scanln(&fen)
sum += fen
}
fmt.Printf("全班的总分为 %v 平均分为%v \n", sum, sum/5)
}
//第一步已经完成一个班的总数和平均分
//输出结果如下
➜ 01 go run main.go
请输出 1 学生的分数
10
请输出 2 学生的分数
10
请输出 3 学生的分数
10
请输出 4 学生的分数
10
请输出 5 学生的分数
10
全班的总分为 50 平均分为10
// 第二步: 统计3个班的数据
// 1.统计3个班的数据,每个班有5名同学,求出每个班的平均分
// 2. j表示第几个班级
// 3.定义一个变量存放总成绩
package main
import "fmt"
func main() {
// totalSum 所有班总成绩
var totalSum float64 = 0.0
// j表示第几个班级
for j := 1; j <= 3; j++ {
var sum float64 = 0.0 //定义总数
for i := 1; i <= 5; i++ {
var fen float64 = 0.0 //每个学生的分数
fmt.Printf("请输入第%d班,第 %d 个学生的分数 \n", j, i)
fmt.Scanln(&fen)
// sum为总数,fen是每个学生的分数,加在一起就是这个班级的总分
sum += fen
}
// 计算所有班级的总成绩到totalSum
totalSum += sum
fmt.Printf("第%d 班级,平均分为%v \n", j, sum/5)
}
fmt.Printf("各个班的总成绩是%v 各个班级的平均分是%v \n", totalSum, totalSum/(5*3))
}
//输出结果如下
➜ 01 go run main.go
请输入第1班,第 1 个学生的分数
10
请输入第1班,第 2 个学生的分数
10
请输入第1班,第 3 个学生的分数
10
请输入第1班,第 4 个学生的分数
10
请输入第1班,第 5 个学生的分数
10
第1 班级,平均分为10
请输入第2班,第 1 个学生的分数
20
请输入第2班,第 2 个学生的分数
20
请输入第2班,第 3 个学生的分数
20
请输入第2班,第 4 个学生的分数
20
请输入第2班,第 5 个学生的分数
20
第2 班级,平均分为20
请输入第3班,第 1 个学生的分数
30
请输入第3班,第 2 个学生的分数
30
请输入第3班,第 3 个学生的分数
30
请输入第3班,第 4 个学生的分数
30
请输入第3班,第 5 个学生的分数
30
第3 班级,平均分为30
各个班的总成绩是300 各个班级的平均分是20
//3.优化
// 目前已经实现功能了,但是后期如果班级增加,几百个几千人修改起来就比较麻烦。接下来我们将代码优化
// 1) 定义2个变量,表示班级的个数,和学生的个数
package main
import "fmt"
func main() {
// totalSum 所有班总成绩
var totalSum float64 = 0.0
// 优化: 定义班级变量
var classNum int = 3
var stuNum int = 5
// j表示第几个班级
for j := 1; j <= classNum; j++ {
var sum float64 = 0.0 //定义总数
for i := 1; i <= stuNum; i++ {
var fen float64 = 0.0 //每个学生的分数
fmt.Printf("请输入第%d班,第 %d 个学生的分数 \n", j, i)
fmt.Scanln(&fen)
// sum为总数,fen是每个学生的分数,加在一起就是这个班级的总分
sum += fen
}
// 计算所有班级的总成绩到totalSum
totalSum += sum
fmt.Printf("第%d 班级,平均分为%v \n", j, sum/float64(stuNum))
}
fmt.Printf("各个班的总成绩是%v 各个班级的平均分是%v \n", totalSum, totalSum/float64(classNum*stuNum))
}
//输出结果
➜ 01 go run main.go
请输入第1班,第 1 个学生的分数
10
请输入第1班,第 2 个学生的分数
10
请输入第1班,第 3 个学生的分数
10
请输入第1班,第 4 个学生的分数
10
请输入第1班,第 5 个学生的分数
10
第1 班级,平均分为10
请输入第2班,第 1 个学生的分数
10
请输入第2班,第 2 个学生的分数
10
请输入第2班,第 3 个学生的分数
10
请输入第2班,第 4 个学生的分数
10
请输入第2班,第 5 个学生的分数
10
第2 班级,平均分为10
请输入第3班,第 1 个学生的分数
10
请输入第3班,第 2 个学生的分数
10
请输入第3班,第 3 个学生的分数
10
请输入第3班,第 4 个学生的分数
10
请输入第3班,第 5 个学生的分数
10
第3 班级,平均分为10
各个班的总成绩是150 各个班级的平均分是10
➜ 01
应用案例二 基于上面的脚本,统计每个班级的合格人数
// 首先,我们需要定义一个变量,用于保存及格人数
// 第二,在每个班级中的for循环添加if 判断,当大于60分标记为几个
package main
import "fmt"
func main() {
// totalSum 所有班总成绩
var totalSum float64 = 0.0
// 定义保存及格人数变量
var countOK int = 0
// 优化: 定义班级变量
var classNum int = 2
var stuNum int = 5
// j表示第几个班级
for j := 1; j <= classNum; j++ {
var sum float64 = 0.0 //定义总数
for i := 1; i <= stuNum; i++ {
var fen float64 = 0.0 //每个学生的分数
fmt.Printf("请输入第%d班,第 %d 个学生的分数 \n", j, i)
fmt.Scanln(&fen)
// if 判断学生是否及格
if fen >= 60 {
countOK++
}
// sum为总数,fen是每个学生的分数,加在一起就是这个班级的总分
sum += fen
}
// 计算所有班级的总成绩到totalSum
totalSum += sum
fmt.Printf("第%d 班级,平均分为%v \n", j, sum/float64(stuNum))
}
fmt.Printf("各个班的总成绩是%v 各个班级的平均分是%v \n", totalSum, totalSum/float64(classNum*stuNum))
//打印及格人数
fmt.Printf("及格人数为%v \n", countOK)
}
//输出结果
➜ 01 go run main.go
请输入第1班,第 1 个学生的分数
100
请输入第1班,第 2 个学生的分数
100
请输入第1班,第 3 个学生的分数
100
请输入第1班,第 4 个学生的分数
100
请输入第1班,第 5 个学生的分数
100
第1 班级,平均分为100
请输入第2班,第 1 个学生的分数
80
请输入第2班,第 2 个学生的分数
70
请输入第2班,第 3 个学生的分数
1
请输入第2班,第 4 个学生的分数
2
请输入第2班,第 5 个学生的分数
1
第2 班级,平均分为30.8
各个班的总成绩是654 各个班级的平均分是65.4
及格人数为7
➜ 01
应用案例三 打印金字塔 使用for循环打印金字塔,并且可以接受一个整数表示层数,打印出金字塔 (空心金字塔)
// 第一步,打印一个矩形
/*
***
***
***
*/
package main
import "fmt"
func main() {
// i 表示层数
for i := 1; i <= 3; i++ {
//j表示每层打印多少个
for j := 1; j <= 3; j++ {
fmt.Print("*")
}
fmt.Println()
}
}
//输出结果
➜ 02 go run main.go
***
***
***
// 第二步,打印半个金字塔
/*
*
**
***
*/
package main
import "fmt"
func main() {
// i 表示层数
for i := 1; i <= 3; i++ {
//j表示每层打印多少个
for j := 1; j <= i; j++ {
fmt.Print("*")
}
fmt.Println()
}
}
//输出结果
➜ 02 go run main.go
*
**
***
//第三步: 打印整个金字塔
//规律
* 1层1个* 规则: 2 * 层数 - 1
*** 2层3个* 规则: 2 * 层数 - 1
**** 3层5个* 规则: 2 * 层数 - 1
package main
import "fmt"
func main() {
// i 表示层数
for i := 1; i <= 3; i++ {
//j表示每层打印多少个
for j := 1; j <= 2 * i -1 ; j++ {
fmt.Print("*")
}
fmt.Println()
}
}
//输出结果
➜ 02 go run main.go
*
***
*****
//目前输出还是有问题,我们需要解决空格的问题
//调整空格
//规律
* 1层1个* 规则: 2 * 层数 - 1 空格规律 总层数-当前层数
*** 2层3个* 规则: 2 * 层数 - 1 空格规律 总层数-当前层数
**** 3层5个* 规则: 2 * 层数 - 1 空格规律 总层数-当前层数
package main
import "fmt"
func main() {
// i 表示层数
for i := 1; i <= 3; i++ {
//在打印*号前打印空格
//k<= 总层数-当前层数
for k := 1; k <= 3-i; k++ {
fmt.Printf(" ")
}
//j表示每层打印多少个
for j := 1; j <= 2*i-1; j++ {
fmt.Print("*")
}
fmt.Println()
}
}
//输出结果
➜ 02 go run main.go
*
***
*****
//第四步: 优化代码扩展性
//将3(层数)设置为一个变量,每次修改只需要修改这一个变量
package main
import "fmt"
func main() {
//设置层数变量
var totalLevel int = 5
// i 表示层数
for i := 1; i <= totalLevel; i++ {
//在打印*号前打印空格
//k<= 总层数-当前层数
for k := 1; k <= totalLevel-i; k++ {
fmt.Printf(" ")
}
//j表示每层打印多少个
for j := 1; j <= 2*i-1; j++ {
fmt.Print("*")
}
fmt.Println()
}
}
//输出结果
➜ 02 go run main.go
*
***
*****
*******
*********
// 第五步: 打印空心金字塔
//分析: 在我们给每行大银行*号时,需要考虑是打印*号还是打印空格
//我们分析的结果是,每层的一个和最后一个是打印*,其他就应该是空的,即输出空格
package main
import "fmt"
func main() {
//设置层数变量
var totalLevel int = 9
// i 表示层数
for i := 1; i <= totalLevel; i++ {
//在打印*号前打印空格
//k<= 总层数-当前层数
for k := 1; k <= totalLevel-i; k++ {
fmt.Printf(" ")
}
//j表示每层打印多少个
for j := 1; j <= 2*i-1; j++ {
//开头的第一个和最后一个打印*号,其他的打印空格
//最后一行都打*号
if j == 1 || j == 2*i-1 || i == totalLevel {
fmt.Print("*")
} else {
fmt.Print(" ")
}
}
fmt.Println()
}
}
//输出结果
➜ 02 go run main.go
*
* *
* *
* *
* *
* *
* *
* *
*****************
应用案例四 打印九九乘法表
package main
import "fmt"
func main() {
//打印九九乘法表
//表示行数
for i := 1; i <= 9; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%v * %v = %v \t", j, i, i*j)
// \t制表符
}
fmt.Println()
}
}
//输出结果
➜ 03 go run main.go
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
5.7 跳转控制语句-BREAK
break语句用于终止某个语句块的执行,用于中断当前for循环或跳出switch语句
基本语法:
{ ...
break
}
break快速入门案例
随机生成1-100的一个数,直到生成99这个数,查看一共生成了多少次
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
var conut int = 0
for {
//并且由于rand生成的数值是不变的,所以需要给rand设置一个总值,并且生成随机数
rand.Seed(time.Now().UnixNano())
n := rand.Intn(100) + 1 //默认rand生成的数为0-99,不包含100.所以我们需要加1 | 光是用这一行是不满足随机数的,所以需要上面的函数来同时使用
// fmt.Println(n)
conut++
if n == 99 {
break //跳出循环
}
}
fmt.Println("生成99一共循环了 ", conut)
}
//输出结果
➜ 05 go run main.go
生成99一共循环了 202
label标签使用
1.break默认会跳出最近的for循环 2.break后面可以指定标签,跳到标签对应的循环
案例
package main
import (
"fmt"
)
func main() {
//break 指定标签来使用
label: //定义标签,注意需要添加冒号
for i := 0; i < 4; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
break label //标签名称可以修改
}
fmt.Println("j=", j)
}
// 当j==2,break 直接跳转到外层,所以只会打印0和1,外层for循环便退出
}
}
//输出结果
➜ 06 go run main.go
j= 0
j= 1
练习题: 实现登陆验证,有三次机会,如果用户为"张无忌",密码"888"提示成功,否则提示剩余多少次机会
package main
import (
"fmt"
)
func main() {
var user string
var passwd string
var login = 3
fmt.Println("欢迎登陆sundayhk运维系统")
fmt.Println()
for i := 1; i <= 3; i++ {
fmt.Println("请输入登陆用户名")
fmt.Scanln(&user)
fmt.Println("请输入登陆密码")
fmt.Scanln(&passwd)
if user == "张无忌" && passwd == "888" {
fmt.Println("恭喜您登陆成功!")
break
} else {
login--
fmt.Printf("你还有%v次登陆机会 \n", login)
}
}
if login == 0 {
fmt.Println("机会用完,没有登陆成功")
}
}
//输出结果
➜ 07 go run main.go
欢迎登陆sundayhk运维系统
请输入登陆用户名
abc
请输入登陆密码
abc
你还有2次登陆机会
请输入登陆用户名
张无忌
请输入登陆密码
888
恭喜您登陆成功!
5.8 跳转控制语句-CONTINUE
continue语句用于结束本次
循环,继续执行下一次循环。
continue语句出现在多层嵌套的循环语句体重,可以通过标签指明要跳转的那一层循环
基本语法
{ ...
continue;
...
}
案例演示
package main
import (
"fmt"
)
func main() {
for i := 0; i < 3; i++ {
fmt.Println()
for j := 0; j < 3; j++ {
if j == 0 {
continue
}
fmt.Println("j=", j)
}
}
}
//输出结果
➜ 08 go run main.go
j= 1
j= 2
j= 1
j= 2
j= 1
//永远不会输出0
案例演示:continue 实现打印1-100之内的奇数[要求使用for循环+continue]
package main
import (
"fmt"
)
func main() {
//continue 实现打印1-100之内的奇数[要求使用for循环+continue]
var conut byte
for i := 1; i <= 100; i++ {
if i%2 == 0 {
continue
}
fmt.Println("奇数是", i)
conut++
}
fmt.Printf("奇数一共有%v 个\n", conut)
}
从键盘输入个数不确定的证书,并判断读入的正数和负数的个数,输入为0时程序结束
package main
import (
"fmt"
)
func main() {
//从键盘输入个数不确定的证书,并判断读入的正数和负数的个数,输入为0时程序结束
var positiveCount int
var negativeCount int
var num int
for {
fmt.Println("请输入一个整数")
fmt.Scanln(&num)
if num == 0 {
break //退出循环
}
if num > 0 {
positiveCount++
continue //结束本次循环,进行下次循环
}
negativeCount++
}
fmt.Printf("正数个数是%v 负数的个数是%v \n", positiveCount, negativeCount)
}
//输出结果
➜ 08 go run main.go
请输入一个整数
1
请输入一个整数
2
请输入一个整数
3
请输入一个整数
-1
请输入一个整数
-2
请输入一个整数
0
正数个数是3 负数的个数是2
5.9 跳转控制语句-GOTO
1)Go语音的goto语句可以无条件地转移到程序中指定的行。 2)goto语句通常与条件语句配合使用。可用来实现条件转移,跳出循环体等功能。 3)在go程序设计中一般不主张使用goto语句,以免造成程序流程的混乱
基本语法
goto label
...
label: statement
案例演示
package main
import (
"fmt"
)
func main() {
var n int = 30
//演示goto的使用,通常配合if使用
fmt.Println("ok1")
if n > 20 {
goto label1 //当n 大于20,就goto跳转到下面label1
}
fmt.Println("ok2") //此处为不执行
fmt.Println("ok3") //此处为不执行
fmt.Println("ok4") //此处为不执行
label1: //标签名,可变更
fmt.Println("ok5")
fmt.Println("ok6")
fmt.Println("ok7")
fmt.Println("ok8")
}
//输出结果
➜ 08 go run main.go
ok1
ok5
ok6
ok7
ok8
5.10 跳转控制语句-RETURN
return使用在方法或函数中,表示跳出所在的函数或方法
package main
import (
"fmt"
)
func main() {
for i := 1; i <= 10; i++ {
if i == 3 {
return
}
fmt.Println("sundayhk", i)
}
fmt.Println("hello word")
}
说明
- 如果return是在普通的函数,则表示跳出该函数,即不在执行函数中的return后面代码,也可以理解成终止函数
- 如果return是在main函数,表示终止main函数,也就是说终止程序。
return语句基本语法
func 函数名 (形参列表) (返回值类型列表) {
语句
return 返回值列表
}
- 如果返回多个值时,在接受时希望忽略某个返回值,则使用
_
符号表示占位忽略 - 如果返回值只有一个,
返回值类型列表
括号可以不写
_案例演示: 忽略和的结果,包保留差的结果
package main
import "fmt"
//定义函数
func getSumAndSub(n1 int, n2 int) (int, int) { //(int, int)表示返回2个结果
sum := n1 + n2
sub := n1 - n2
return sum, sub //返回sum和sub给调用者
}
func main() {
//调用变量
_, sub := getSumAndSub(1, 10)
fmt.Println("sub =", sub)
}
//运行结果
➜ exec4 go run main.go
sub = -9
案例演示 请编写一个函数,可以计算两个数的和和差,并返回计算结果
package main
import "fmt"
//定义函数
func getSumAndSub(n1 int, n2 int) (int, int) { //(int, int)表示返回2个结果
sum := n1 + n2
sub := n1 - n2
return sum, sub //返回sum和sub给调用者
}
func main() {
//调用变量
res1, res2 := getSumAndSub(10, 20) //res1 = sum = 10+20 && res2 = sub = 10-20
fmt.Println("rest1 =", res1)
fmt.Println("rest2 =", res2)
}
//输出结果
➜ exec4 go run main.go
rest1 = 30
rest2 = -10
案例演示
package main
import (
"fmt"
_ "go_code/package_demo/utils"
)
func sumNum(n1 int, n2 int) int {
sum := n1 + n2
fmt.Println("get sum =", sum)
//当函数中有return语句时,就是将结果返回给调用者
return sum
}
func main() {
sum := sumNum(10, 20)
fmt.Println("main sum =", sum) //输出结果为30
// fmt.Println("utils.go = ", utils.sundayhk) //utils为包名 sundayhk为变量名,变量需要大写
// var num1 float64 = 1.1
// var num2 float64 = 1.2
// var operator byte = '+'
// //引用utils包中的函数
// result := utils.Cal(num1, num2, operator) //utils代表包名,Cal代表函数名称,如果是小写cal无法包调用
// fmt.Println("result=", result)
}
//输出结果
➜ main go run main.go
get sum = 30
main sum = 30
六、函数
为完成某一功能程序指令(语句)的集合,称为函数。
在Golang中,函数分为: 自定义函数
、系统函数
6.1 函数的基本概念及使用
基本语法
func 函数名 (形参列表) (返回值列表){
执行语句..
return 返回值列表
}
1)形参列表: 表示函数的输入 2)函数中的语句: 表示为了实现某一功能代码块 3)函数可以有返回值,也可以没有
案例演示 使用函数解决计算问题
package main
import "fmt"
func main() {
//输入两个数,在输入一个运算符得到结果
var num1 float64 = 10.0
var num2 float64 = 2.1
var operator byte = '+'
var res float64
switch operator {
case '+':
res = num1 + num2
case '-':
res = num1 - num2
case '*':
res = num1 * num2
case '/':
res = num1 / num2
default:
fmt.Println("请重新输入操作符号")
}
fmt.Println("res= ", res)
}
//输出结果
➜ 08 go run main.go
res= 12.1
但是如果我们代码中有多个需要计算的,switch就需要写入多行。这里就需要使用函数来解决
package main
import "fmt"
func cal(num1 float64, num2 float64, operator byte) float64 {
//num1和num2类型为float64,并且返回的类型也是float64
var res float64
switch operator {
case '+':
res = num1 + num2
case '-':
res = num1 - num2
case '*':
res = num1 * num2
case '/':
res = num1 / num2
default:
fmt.Println("请重新输入操作符号")
}
return res //将res结果返回
}
func main() {
//引用函数
//1.首先定义一个变量, 用于接受返回值
result := cal(10.0, 2.0, '+')
//2.cal为函数名称,后面的10.0 == num1、2.0 ==num2,其中➕ == operator
fmt.Println("result=", result)
//3.除了直接输入数字的方式外,我们还可以把10.0和2.0以及'+'设置为变量
var n1 float64 = 1.1
var n2 float64 = 1.2
var operator byte = '+'
result1 := cal(n1, n2, operator)
fmt.Println("result1=", result1)
}
6.2 包的引用
- 在实际上开发中,需要在不同的文件调用函数,
- 项目开发过程需,需要很多人参与,相关函数写在一起容易造成提及庞大,理解困难。所以有了包的概念
6.3 包的原理图
包的本质实际上就是创建不同的文件夹,来存放程序文件。
oa
db/db.go
utils/utils.go
main/main.go
6.4 包的基本概念
在go语言中,go的每一个文件都是属于一个包的,也就是说go是以包的形式来管理文件和项目目录的
6.5 包的三大作用
- 区分相同名字的函数、变量等标示符
- 当程序文件很多时,可以很好的管理项目
- 控制函数、变量等访问范围,即作用域
6.6 包的相关说明
打包基本语法
package 包名
例如package main,这里就相当于我们打包了一个main包
引入包的基本语法
import "包的路径"
6.7 包使用的快速入门
首先我们在vscode中创建一个单独的项目
目录结构如下
src
go_code
package_demo
main
main.go
utils
utils.go
我们将func Cal定义到utils.go
,将utils.go
放到一个包中,当其他文件需要使用utils.go方法时,可以import该包
utili.go文件内容如下
package utils //打包指令,utils代表包名
import "fmt"
//将计算的功能,放到一个函数中,然后在使用调用
func Cal(num1 float64, num2 float64, operator byte) float64 {
//最后一个float64将结果输出为float64格式,前面的是定义变量类型
//为了让其他的包使用cal,需要将这里的Cal函数名称需要大写
//在go语言中将函数大写,表示该函数可导出
var res float64
switch operator {
case '+':
res = num1 + num2
case '-':
res = num1 - num2
case '*':
res = num1 * num2
case '/':
res = num1 / num2
default:
fmt.Println("操作符号错误...")
}
return res //将res结果返回
}
main.go文件内容如下
package main
import (
"fmt"
"go_code/package_demo/utils" //src后面开始写,src前面的路径不需要写
)
func main() {
var num1 float64 = 1.1
var num2 float64 = 1.2
var operator byte = '+'
//引用utils包中的函数
result := utils.Cal(num1, num2, operator) //utils代表包名,Cal代表函数名称,如果是小写cal无法包调用
fmt.Println("result=", result)
}
输出结果如下:
➜ package_demo ls
go.mod main utils
➜ package_demo go run main/main.go
result= 2.3
6.8 包使用的注意事项
- 在给一个包打包时,该包对应一个文件夹,比如这里的utils文件夹对应的包名就是utils。文件的包名
通常
和文件所在文件夹名一致 (一般为小写)
-
当一个文件要使用其它函数或变量时,需要先引入对应的包 1)引入方式1:
import "包名"
2)引入方式2:import ("包名")
-
package指令在文件第一行,然后是import指令
-
在import包时,路径从
$GOPATH
的src下开始,不需要带src,编译器会自动从src下开始引入 -
为了其它包文件可以访问到本地的函数,则该函数名的首字母需要大写
-
同样我们如果要定义一个变量名,也是相同的使用方式
-
-
在访问其它包函数或变量时,其语法是
包名.函数名
-
如果包名较长,GO支持给包取别名,注意: 取别名后,原来的包名就不能使用了 演示
-
在同一个包下,不能有相同的函数名和全局变量名,否则报重复定义
-
如果我们要编译一个可执行程序文件, 就需要将包声明为main,即package main。 演示一个案例,模拟多个目录,多个包如何编译打包
目录结构如下
package_demo
main
main.go
utils
utils.go
编译时只需要编译main包即可
➜ main go build main.go
➜ main ./main
utils.go = 100
result= 2.3
#编译时也可以指定打包名称和路径
➜ main go build -o sundayhk main.go
➜ main ls
sundayhk main.go
➜ main ./sundayhk
utils.go = 100
result= 2.3
打包exe文件
➜ main CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
➜ main ls
main.exe main.go
下面是在linux打包不同平台的包 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
6.9 函数的调用机制
在go中,最先使用的函数为main函数,当n1 :=10
定义变量后,main中会执行下一步,通过调用test
函数,将n1
传给test
函数。接下来就到test函数进行执行。 在test函数中n1= n1+1
基本是相当于10+1。所以在test函数中会输出11
数字
当test函数将结果输出后,返回到main函数中,继续执行下一条,test函数执行完毕后就会在内存中释放,也就是目前只保留了n1 := 10
,那么下面打印出来的就是10
编辑器会回收不使用的栈区,当test栈区执行完毕,编辑器会进行回收
6.10 函数的递归调用
一个函数在函数体内又调用了自己,我们称为递归调用
//案例演示
package main
import "fmt"
//定义函数
func test(n int) {
if n > 2 {
n--
test(n)
}
fmt.Println("n=", n)
}
func main() {
//调用变量
test(4)
}
//输出结果
➜ exec4 go run main.go
n= 2
n= 2
n= 3
分析图
案例演示2
package main
import "fmt"
//定义函数
func test(n int) {
if n > 2 {
n--
test(n)
} else {
fmt.Println("n=", n)
}
}
func main() {
//调用变量
test(4)
}
//输出结果
➜ exec4 go run main.go
n= 2
跟上面的步骤参数解释一样,当n >
2时,会再次创建test函数,直到n = 2 >2
,当n不大于2之后,if判断下的内容不执行,反而执行else打印语句。其他函数中的else语句不会执行,因为他们已经在入口处执行了n --
,并且在当时n是大于2
的,所以执行n --
在调用新的test函数。最后上面的语句只会输出一个2
函数递归需要遵守的重要原则
- 执行一个函数时,就创建一个新的受保护的独立空间 (新函数栈)
- 函数的局部变量是独立的,不会相互影响 [例如上面的test函数中的n变量,在多个函数中不受影响,互相独立]
- 递归必须向退出递归的条件逼近,否则就是无限递归 (例如上面案例的n–)
- 当一个函数执行完毕,或者遇到
return
,就会返回,遵守谁调用,就将结果返回给谁。同时当函数执行完毕或者返回时,该函数本身也会被销毁
递归函数练习题1
请使用递归的方式,求出斐波那契数1,1,2,3,5,7,13 给你一个整数n,求出它的斐波那契数是多少?
思路分析
1.当n == 1 && n ==2 ,返回1
2.当n >=2,返回前面两个数的和 f(n-1)+f(n-2)
package main
import "fmt"
func fbn(n int) int {
if n == 1 || n == 2 {
return 1
} else {
return fbn(n-1) + fbn(n-2)
}
}
func main() {
//定义接收函数
res := fbn(3)
//引用函数
fmt.Println("rest=", res) //2
fmt.Println("rest=", fbn(4)) //3
fmt.Println("rest=", fbn(5)) //5
fmt.Println("rest=", fbn(6)) //8
}
//输出结果
➜ exec4 go run main.go
rest= 2
rest= 3
rest= 5
rest= 8
递归函数练习题2
求函数的值
已知f(1)=3;f(n)= 2*f(n-1)+1;
请使用递归的编程思想,求出f(n)
的值
package main
import "fmt"
func peach(n int) int {
if n > 10 || n < 1 {
fmt.Println("输入的天数不对")
return 0 //返回0 表示没有得到正确数量
}
if n == 10 {
return 1
} else {
return (peach(n+1) + 1) * 2
}
}
func main() {
//使用
fmt.Println("第一天桃子的数量是", peach(1))
fmt.Println("第十天桃子的数量是", peach(10))
fmt.Println("第⑨天桃子的数量是", peach(9))
fmt.Println()
fmt.Println("测试输入非1-10的数量", peach(15))
}
//输出结果
➜ exec4 go run main.go
第一天桃子的数量是 1534
第十天桃子的数量是 1
第⑨天桃子的数量是 4
输入的天数不对
测试输入非1-10的数量 0
递归函数练习题3
有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个。以后每天猴子都吃其中的一半,然后再多吃一个。当到第十天时,想再吃时(还没吃),发现只有一个桃子,请问最初一共有多少个桃子
分析:
1.第十天只有一个桃子了
2.第九天桃子= (第十天桃子数量 + 1) 2
3.规律: 第n天的桃子数量 peach(n) =(peach(n+1)+1) x 2
func test(n1 *int) {
*n1 = *n1 + 10
fmt.Println("test() n1 ==", *n1)
}
func main() {
num := 20
test(&num)
fmt.Println("main() num ==", num)
//当时用了指针的方式,会修改num :=20这个值
}
//输出结果
➜ exec4 go run main.go
test() n1 == 30
main() num == 30
➜ exec4
6.11 函数注意事项
- 函数的形参列表可以是多个,返回值列表也可以是多个
- 形参列表和返回值列表的数据类型可以是值和引用类型
- 函数的命名遵循标示符命名规范,首字母不能是数字,首字母大写该函数可以被本包或者其他包文件使用,首字母小写,只能被本包文件使用,其他包文件不能使用
- 函数中的变量是局部的,函数外不生效
- 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响原来的值。例如main函数中的n1和
test
函数中的n1修改test
函数中的n1不会影响main
函数中的n1 - 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址
&
,函数内指针的方式操作变量。从效果上看类似引用
package main
import "fmt"
func getSum(n1 int, n2 int) int {
return n1 + n2
}
func main() {
a := getSum
//它们的值相同,因为a将代码变量指向了getSum函数 (代码空间)
fmt.Printf("a的类型为%T,getSum类型是%T\n", a, getSum)
res := a(10, 20) //等价 res := getSum(10,20)
fmt.Println("res=", res)
}
//输出结果
PS C:\Users\Administrator\Desktop\GOstudy\day2> go run .\main.go
a的类型为func(int, int) int,getSum类型是func(int, int) int
res= 30
取值会直接修改num的变量,所以*n1 = *n1+10
可以理解为num == 20 +10
(*n1
是取值,而不是复制的关系)
7) GO函数不支持传统的函数重载
- 在GO中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用
package main
import "fmt"
func getsum(n1 int, n2 int) int {
return n1 + n2
}
//将函数当成形参传给其他函数
func myfun(funvar func(int, int) int, num1 int, num2 int) int {
//myfun函数名
//funvar 变量名,将funvar变量变成函数类型
//func (int,int)int 形参类型,定义了2个int类型,并且返回了一个int类型
//num1 int,num2 int 传2个类型给getsum
//int 返回类型
return funvar(num1, num2)
//本函数也可以调用其他函数
//传入num1和num2
//上面说到可以把一个函数交给一个变量,同时也可以把一个变量交给一个形参
}
func main() {
res2 := myfun(getsum, 50, 60) //调用getsum函数,将myfun函数中的形参传给getsum函数进行调用
//res2 := 类型推导
//myfun调用函数
//getsum,50,60 通过myfun函数调用getsum,将50和60调用给num1和num2
fmt.Println("rest2=", res2)
}
//输出结果
➜ exec4 go run main.go
rest2= 110
分析: 在函数中可以将函数给交一个变量,并且可以通过变量进行完成调用
- 函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用
package mainimport "fmt"func getsum(n1 int, n2 int) int { return n1 + n2}//将函数当成形参传给其他函数func myfun(funvar func(int, int) int, num1 int, num2 int) int { //myfun函数名 //funvar 变量名,将funvar变量变成函数类型 //func (int,int)int 形参类型,定义了2个int类型,并且返回了一个int类型 //num1 int,num2 int 传2个类型给getsum //int 返回类型 return funvar(num1, num2) //本函数也可以调用其他函数 //传入num1和num2 //上面说到可以把一个函数交给一个变量,同时也可以把一个变量交给一个形参}func main() { res2 := myfun(getsum, 50, 60) //调用getsum函数,将myfun函数中的形参传给getsum函数进行调用 //res2 := 类型推导 //myfun调用函数 //getsum,50,60 通过myfun函数调用getsum,将50和60调用给num1和num2 fmt.Println("rest2=", res2)}//输出结果➜ exec4 go run main.gorest2= 110
- 为了简化数据类型定义,go支持自定义数据类型
基本语法: type 自定义数据类型名称 数据类型
//相当于一个别名
例如type myint int
//这时myint就等价int类型
例如type mysum func(int,int)int
这时mysum就等价一个函数类型func (int,int)int
举例子说明自定义数据类型的使用
package main
import "fmt"
func main() {
type myInt int
// 给int取了别名,在go语言中,myInt和int虽然都是int类型,但是go在语法中认为myInt和int是两个类型
var num1 myInt
num1 = 10
fmt.Println("num1=", num1)
//但是num1不等价于num2
var num2 int
//num2 = num1 ❌调用
num2 = int(num1) // ✅正确,虽然myInt和int都是int类型,但是go语法不识别,如果想引用需要强制将myInt类型转换为int类型
//重点! go语法认为myInt和int是不同的类型,所以需要强制转换
fmt.Println("num2=", num2)
}
//输出结果
➜ exec4 go run main.go
num1= 10
num2= 10
举例子说明自定义数据类型在函数内的使用
给函数取一个别名,将来我们函数作为形参就可以简化流程
package main
import "fmt"
//定义计算函数模板
func getSum(n1 int, n2 int) int {
return n1 + n2
}
//添加函数类型
type myFunType func(int, int) int
//添加新函数调用myFunType
func getFunType(abc myFunType, num1 int, num2 int) int {
//getFunType为新的函数名
//abc 为变量名
//myFunType 为调用函数的类型
// num1 返回值1,num2返回值2
return abc(num1, num2)
}
func main() {
//调用myFunType
res1 := getFunType(getSum, 10, 20)
fmt.Println("res1=", res1)
}
//输出结果
➜ exec4 go run main.go
res1= 30
- go语言中支持对返回值命名
package main
import "fmt"
func getSumAndSub(n1 int, n2 int) (sum int, sub int) {
sub = n1 - n2
sum = n1 + n2 //最先计算的是加法,是根据上面sum int写在前面进行定义,而并非此行
return
//这里sum和sub无需在定义变量,直接引用即可
}
func main() {
a, b := getSumAndSub(10, 10)
fmt.Printf("a=%v b=%v\n", a, b)
//sum和sub是按照函数的计算方式进行调用,最先进行计算的是加法,是在函数中定义的,并非是sum = n1 +2
}
//输出结果
➜ exec4 go run main.go
a=20 b=0
- 使用
_
占位符,来忽略返回值
- go支持可变参数
//支持0到多个参数
func sum(agrs ...int)sum int{}
//支持1到多个参数
func sum(n1 int,agrs ..int)sum int{}
说明: args是slice切片,通过args[index]可以访问到各个值 //如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后
案例演示,编写一个函数,可以求出 1到多个int的和
package main
import "fmt"
//案例演示,编写一个函数,可以求出 1到多个int的和
func sum(n1 int, args ...int) int {
//args的值可以变
sum := n1
for i := 0; i < len(args); i++ {
sum += args[i] //args[0]表示取出args切片的第一个元素值,其他依次类推
}
return sum
}
func main() {
res := sum(10, 0, -1)
fmt.Println("res=", res)
}
//输出结果
➜ exec4 go run main.go
res= 9
6.12 函数练习
练习题1,判断代码有无错误,输出什么?
package main
import "fmt"
func sum(n1, n2 float32) float32 {
fmt.Printf("n1 type = %T\n", n1)
return n1+n2
}
func main() {
fmt.Println("sum=", sum(1, 2))
}
//答案✅,无错误,输出以下内容
➜ exec4 go run main.go
n1 type = float32
sum= 3
练习题2,判断代码有无错误,输出什么?
package main
import "fmt"
type mySum func(int, int) int
func sum(n1 int, n2 int) int {
return n2 + n2
}
func sum2(n1, n2, n3 int) int {
return n1 + n2
}
//使用type自定义数据类型来简化定义
func myFunc(funVar mySum,num1 int,num2 int)int{
return funVar(num1,num2)
}
func main() {
a:=sum
b:=sum2
fmt.Println(myFunc(a,1,2)) //✅ 正确
fmt.Println(myFunc(b,1,2)) //❌ 由于sum2的类型是3个int,类型不匹配。因为不能将sum2(n1, n2, n3 int) int函数类型,赋给func(int, int) int类型
}
练习题3,请编写一个函数swap(n1 *int, n2 *int)可以交换n1和n2的值
package main
import "fmt"
func swap(n1 *int, n2 *int) { //*int代表指针变量
//定义t来接受临时变量
t := *n1 //t来临时接受n1指针变量
*n1 = *n2 //n1赋值为n2
*n2 = t //n2赋值为t,t指针变量已经为n1的数值
}
func main() {
a := 10 //定义变量a = 10
b := 20 //定义变量b = 20
fmt.Printf("默认结果: a=%v , b=%v \n", a, b) //默认结果
swap(&a, &b) //指针变量传入需要添加&符
fmt.Printf("替换后结果: a=%v , b=%v \n", a, b) //替换后输出结果
}
//输出结果
➜ exec4 go run main.go
默认结果: a=10 , b=20
替换后结果: a=20 , b=10
6.13 函数参数的传递方式
值类型参数默认就是值传递,引用类型参数默认就是引用传递
两种传递方式
- 值传递
- 引用传递
其实,不管是值传递还是引用类型传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝。
地址拷贝效率高,因为数据量小,而值拷贝决定的拷贝的数据大小,数据越大,效率越低
值传递相关的数据类型 基本数据类型(int、float、bool、string)、数组、结构体
引用传递相关的数据类型 指针、slice切片、map、管道chan、interface
传递方式
值类型默认是值传递: 变量直接存储值,内存通常在栈中分配
引用类型默认是引用地址: 变量存储的是一个地址,这个地址对应的空间才正常存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就称为一个垃圾,由GC来回收
如果我们希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。
func test(n1 *int) {
*n1 = *n1 + 10
fmt.Println("test() n1 ==", *n1)
}
func main() {
num := 20
test(&num)
fmt.Println("main() num ==", num)
//当时用了指针的方式,会修改num :=20这个值
}
//输出结果
➜ exec4 go run main.go
test() n1 == 30
main() num == 30
➜ exec4
取值会直接修改num的变量,所以*n1 = *n1+10 可以理解为num == 20 +10(*n1是取值,而不是复制的关系)
6.14 INIT函数
基本介绍 每一个源文件都可以包含一个int函数,该函数会在main函数执行前,被GO运行框架调用,也就是说int会在main函数前被掉用。int函数一般用于处理初始化前的工作
案例说明
package main
import "fmt"
func init() {
fmt.Println("int()...")
}
func main() {
fmt.Println("main()...")
}
//输出结果
➜ exec4 go run main.go
int()...
main()...
如果一个文件同时包含了全局变量定义,int函数和main函数,则执行的流程为: 全局变量定义–>init函数 –> main函数
package main
import "fmt"
var day = test() //全局变量,调用test函数
func test() int { //定义一个函数
fmt.Println("test()...") //执行顺序1
return 90
}
func init() {
fmt.Println("int()...") //执行顺序2
}
func main() {
fmt.Println("main()...day=", day) //执行顺序3
}
//输出结果
➜ exec4 go run main.go
test()...
int()...
main()...day= 90
包引用init案例演示
init初始化工作案例演示
main包进行引用
package main
import (
"fmt"
"go_code/project01/01/exec4/utils"
)
var day = test() //全局变量,调用test函数
func test() int { //定义一个函数
fmt.Println("test()...") //执行顺序2
return 90
}
func init() {
fmt.Println("int()...") //执行顺序3
}
func main() {
fmt.Println("main()...day=", day) //执行顺序4
fmt.Println("Age=", utils.Age, "Name=", utils.Name)//执行顺序1
}
//输出结果 [优先输出utils包中的init函数]
➜ exec4 go run main.go
utils包的init()...
test()...
int()...
main()...day= 90
Age= 100 Name= sundayhk
init优先级
- 首先先执行被引入文件的变量定义 [1]
- 其次执行被引入文件的init函数 [2]
- 后面开始执行main.go文件的变量定义 [3]
- 其次是main.go文件的init函数 [4]
- 最后执行main.go文件中的main主函数 [5]
例如main.go文件引入了utils.go,utils.go又引入了ok.go。那么执行顺序就是ok.go(变量定义–>init函数)然后执行utils.go,最后执行main.go (每个文件中的执行顺序依旧保持变量定义
、init函数
、main.go
)
6.15 匿名函数
所谓匿名函数,就是没有名字的函数。一般情况下,我们函数都是有名称的。 GO支持匿名函数,如果我们某个函数只是希望执行一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用
- 匿名函数使用方式一
在定义匿名函数时直接调用 (这种方式匿名函数只可以调用一次,因为没有将匿名函数给名字或者是交给一个变量。)
package main
import (
"fmt"
)
func main() {
res1 := func(n1 int, n2 int) int { //定义匿名函数,res1定义变量来接收匿名函数
return n1 + n2
}(10, 20) //调用匿名函数,输入值即可
//数字10赋值给n1
//数字20赋值给n2
//int返回值给res1
fmt.Println("res1=", res1)
}
//输出结果
➜ exec4 go run main.go
res1= 30
- 匿名函数使用方式二
将匿名函数给一个变量(函数变量),再通过该变量来调用匿名函数
下面的匿名函数可以写在main函数中
package main
import (
"fmt"
)
func main() {
//将匿名函数func(n1 int, n2 int) int赋值给a变量
//此时a的数据类型为函数类型,此时我们可以通过A完成调用
a := func(n1 int, n2 int) int {
return n1 - n2
}
fmt.Println("res2=", a(11, 10))
}
//输出结果
➜ exec4 go run main.go
res2= 1
- 全局匿名函数
如果将匿名函数赋给一个全局变量,那么这个匿名函数就称为一个全局匿名函数
package main
import (
"fmt"
)
var (
Func = func(n1 int, n2 int) int { //Func变量名需要大写
return n1 * n2
}
)
func main() {
//全局匿名函数调用
fmt.Println("Func=", Func(9, 9))
}
//输出结果
➜ exec4 go run main.go
Func= 81
6.16 闭包
闭包就是一个函数
和其相关的引用环境
(其他函数)组合的一个整体
什么是闭包函数
“闭"函数指的该函数是内嵌函数 “包"函数指的该函数包含对外层函数作用域名字的引用(不是对全局作用域)
package main
import (
"fmt"
)
//累加器
func AddUpper() func(int) int {
var n int = 0 //此处的变量不会归0,而是属于累加的
return func(x int) int {
n += x //n +=x 可以理解为n = n + x //并且每次执行完n变量不会归0,这里的结果会返回给上面的函数
return n
}
}
func main() {
//使用前面AddUpper
f := AddUpper()
fmt.Println(f(1)) //n + 1 = 0+1
fmt.Println(f(2)) //n + 2 = 1+2
fmt.Println(f(3)) //n + 3 = 3+3
}
//输出结果
➜ exec4 go run main.go
1
3
6
对上面的代码进行说明
(1) AddUpper是一个函数,返回的数据类型是func(int) int
(2) 闭包: 返回的是一个匿名函数,但是这个匿名函数引用到函数外的n
,因此这个匿名函数就和n
形成一个整体。构成了闭包
(3) 可以理解为: 闭包是累,函数是操作,n是字段。函数和它使用到的n构成闭包
(4) 当我们反复的调用f函数时,因为n是只初始化一次,因此每调用一次就进行累计
(5) 函数和它引用到的变量共同构成闭包
闭包实际上是返回的匿名函数,和用到的函数外的变量。它们共同构成一个闭包,而调用的时候它用到的变量不是每一次都会被初始化;每用到一次就会进行叠加一次
6.16.1 闭包实践
请编写一个程序,具体要求如下
- 编写一个函数 makeSuffix(suffix string) 可以接收一个文件后缀名(比如.jpg),并返回一个闭包
- 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg)则返回 文件名.jog,如果已经有.jpg后缀,则返回原文件名
- 要求使用闭包的方式完成
- strings.HasSuffix,该函数可以判断某个字符串是否有指定的后缀
package main
import (
"fmt"
"strings"
)
func makeSuffix(suffix string) func(string) string {
//定义匿名函数makeSuffix,接收变量suffix 类型为string
//func (string)接收string类型,并返回一个string类型
return func(name string) string { //接收name变量
if !strings.HasSuffix(name, suffix) { //如果name 没有指定的后缀名,则加上,否则返回原来的名字
return name + suffix
}
return name
}
}
func main() {
//第一步返回一个闭包
f := makeSuffix(".jpg")
//第二步调用
fmt.Println("文件名处理后=", f("abc"))
fmt.Println("文件名处理后=", f("abc.jpg"))
fmt.Println("文件名处理后=", f("sundayhk.mp4"))
}
//输出结果
➜ exec4 go run main.go
文件名处理后= abc.jpg
文件名处理后= abc.jpg
文件名处理后= sundayhk.mp4.jpg
总结和说明
- 返回的匿名函数和
makeSuffix (suffix string)
的suffix变量组合成一个闭包,因为返回的函数引用到suffix这个变量 - 如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入
后缀名
。比如**.jpg** 而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用 - 闭包的好处是传入一次后缀即可,因为函数调用一次后,函数是不会保留的,执行完就会销毁。闭包函数是会保留,每次会把suffix值保留下来
6.17 DEFER
DEFER主要用于在函数执行完毕后,及时释放资源,Go的设计者提供defer
延时机制
例如: 需要创建资源(比如数据库连接、文件句柄、锁等)
//deffer的作用是等函数执行完毕后,在按照先入后出的方式执行deffer语句
package main
import (
"fmt"
)
func sum(n1 int, n2 int) int {
//当函数执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈)
//当函数执行完毕后,再从defer栈,按照先入后出的方式出栈,执行
defer fmt.Println("ok1 n1=", n1) //第一次循环此处不执行,压入到defer 函数内第三个打印
defer fmt.Println("ok2 n2=", n2) //第一次循环此处不执行,压入到defer 函数内第二个打印
res := n1 + n2
fmt.Println("ok3 res=", res) //函数内第一个打印
return res //返回结果
//当return res执行完毕后,释放sum函数,接下来才开始执行defer栈中的语句,优先执行后面加入的语句,最先加入的到最后一个执行
}
func main() {
res := sum(10, 20)
fmt.Println("res= ", res) //函数第四个打印
}
//输出结果
➜ exec4 go run main.go
ok3 res= 30
ok2 n2= 20
ok1 n1= 10
res= 30
在defer
将语句放入到栈时,也会将相关的值拷贝,并且同时入栈。
package main
import (
"fmt"
)
func sum(n1 int, n2 int) int {
defer fmt.Println("ok1 n1=", n1) //函数内第三个打印
defer fmt.Println("ok2 n2=", n2) //函数内第二个打印
//增加n1++和n2++不会修改defer语句中的n1和n2
//在defer将语句放入到栈时,也会将相关的值拷贝,并且同时入栈。
n1++
n2++
res := n1 + n2
fmt.Println("ok3 res=", res) //函数内第一个打印
return res //返回结果
}
func main() {
res := sum(10, 20)
fmt.Println("res= ", res) //函数第四个打印
}
//输出结果
➜ exec4 go run main.go
ok3 res= 32
ok2 n2= 20
ok1 n1= 10
res= 32
defer最主要的价值是在当函数执行完毕后,可以及时的释放函数创建的资源
实际使用defer案例
go
6.18 变量作用域
- 函数内部定义/声明的变量叫局部变量,作用域仅限于函数内部
func test(){
//abc的作用域只在test函数内部
abc := 10
}
- 函数外部定义/声明的变量叫做全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效
package main
import (
"fmt"
)
//全局变量
var abc int = 60
var Name string = "sundayhk"
//全局变量不支持类型推导
func test() {
//abc的作用域只在test函数内部
abc := 10
Name := "tom~"
fmt.Println("abc=", abc)
fmt.Println("Name=", Name)
}
func main() {
fmt.Println("abc=", abc)
fmt.Println("Name=", Name)
}
//输出结果
➜ 01 go run main.go
abc= 60
Name= sundayhk
- 如果变量是在一个代码块,比如
if
、for
中,那么这个变量的作用于就在该代码块中
for i :=0; i<=10;i++{
fmt.Println("i=",i)
}
//如果希望i在函数外被使用,只需要定义一下
var i int
for i :=0; i<=10;i++{
fmt.Println("i=",i)
}
fmt.Println("i=",i)
6.19 函数综合练习
使用函数打印金字塔
package main
import "fmt"
//将打印金字塔的代码封装到函数中
func printPyramid(totalLevel int) { //设置层数变量
// i 表示层数
for i := 1; i <= totalLevel; i++ {
//在打印*号前打印空格
//k<= 总层数-当前层数
for k := 1; k <= totalLevel-i; k++ {
fmt.Printf(" ")
}
//j表示每层打印多少个
for j := 1; j <= 2*i-1; j++ {
//开头的第一个和最后一个打印*号,其他的打印空格
//最后一行都打*号
// if j == 1 || j == 2*i-1 || i == totalLevel {
fmt.Print("*") //注释其它代表打印实心金字塔
// } else {
// fmt.Print(" ")
// }
}
fmt.Println()
}
}
func main() {
var n int
fmt.Println("请输入打印金字塔的层数")
fmt.Scanln(&n)
//调用printPyramid函数,就可以打印金字塔
printPyramid(n)
}
//输出结果
➜ 01 go run main.go
请输入打印金字塔的层数
10
*
***
*****
*******
*********
***********
*************
***************
*****************
*******************
使用函数打印九九乘法表
package main
import "fmt"
func jj(num int) {
//打印九九乘法表
//表示行数
for i := 1; i <= num; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%v * %v = %v \t", j, i, i*j)
// \t制表符
}
fmt.Println()
}
}
func main() {
var num int
fmt.Println("请输入需要打印的乘法表")
fmt.Scanln(&num)
jj(num)
}
//输出结果
➜ 01 go run main.go
请输入需要打印的乘法表
7
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
使用函数对给定的一个二维数组(3x3)转置
6.20 字符串常用系统函数
6.20.1 len
统计字符串、数组长度,按字节返回len(str)
。len属于内建函数,不需要额外引用。直接使用即可
func len(v Type) int //内建函数len返回 v 的长度,这取决于具体类型:
数组:v中元素的数量
数组指针:*v中元素的数量(v为nil时panic)
切片、映射:v中元素的数量;若v为nil,len(v)即为零
字符串:v中字节的数量
通道:通道缓存中队列(未读取)元素的数量;若v为 nil,len(v)即为零
案例演示
func main() {
//golang中的编码同意为utf-8
//ascii的字符 (字母和数字)占一个字节,中文汉字占3个字节 (注意是字节不是字符)
str := "你好,sundayhk" //16个字节,中文6个,特殊符号1个,字母9个
fmt.Println("str len=", len(str)) //使用方法[len(需要引用的变量)]
}
//输出结果
➜ 01 go run main.go
str len= 16
6.20.2 rune
字符串遍历,处理中文问题 r:= [rune(str)]
//默认情况下直接打印中文会出现乱码的情况
func main() {
str := "你好,sundayhk"
//循环输出每一行内容
for i:= 0;i < len(str);i++{
fmt.Printf("字符=%c \n",str[i])
}
}
//输出结果
➜ 01 go run main.go
字符=ä
字符=½
字符=
字符=å
字符=¥
字符=½
字符=,
字符=a
字符=b
字符=c
字符=d
字符=o
字符=c
字符=k
字符=e
字符=r
解决字符串遍历,中文乱码问题
func main() {
str := "你好,sundayhk"
//将str转换成rune切片
r := []rune(str)
//循环输出每一行内容
for i := 0; i < len(r); i++ {
fmt.Printf("字符=%c \n", r[i])
}
//输出结果
➜ 01 go run main.go
字符=你
字符=好
字符=,
字符=a
字符=b
字符=c
字符=d
字符=o
字符=c
字符=k
字符=e
6.20.3 []byte
将字符串转换为byte切片
字符串转 []byte: var bytes = []byte("hello go")
byte输出内容为ascii码
func main() {
var bytes = []byte("hello go")
fmt.Printf("bytes=%v \n", bytes)
}
//输出结果
➜ 01 go run main.go
bytes=[104 101 108 108 111 32 103 111]
103 111对应的就是go
[]byte 转字符串 str := string([]byte{104,101,108,108,111,32,103,111})
有些场景需要将byte类型的切片转换为字符串,按照字符串输出
func main() {
str := string([]byte{104, 101, 108, 108, 111, 32, 103, 111})
fmt.Printf("str=%v \n", str)
}
//输出内容如下
➜ 01 go run main.go
str=hello go
6.20.4 strconv.Atoi
func Atoi(s string) (i int, err error)
字符串转整数 n,err :=strconv.Atoi("12")
如果转不成功会产生一个err,可以对err进行判断
package main
import (
"fmt"
"strconv" //需要引入包包
)
func main() {
//字符串转整数
n, err := strconv.Atoi("123")
if err != nil { //nil代表错误,相当于shell $?=0,此处代表如果err不等于nil代表错误
fmt.Println("转换错误", err)
} else {
fmt.Println("转换结果为", n) //转换成功后,n等于整数
}
}
//输出结果
➜ 01 go run main.go
转换结果为 123
//当我们输入一个错误的数值,无法转换为整数时,会提示下面的报错
➜ 01 go run main.go
转换错误 strconv.Atoi: parsing "abc": invalid syntax
6.20.5 strconv.Itoa
整数不存在无法转成字符串的情况,所以没有error的选项
整数转字符串 str = strcona.Itoa(123456)
package main
import (
"fmt"
"strconv"
)
func main() {
str := strconv.Itoa(12312)
fmt.Printf("str=数值为: %v str类型为:%T \n", str, str)
}
//输出结果
➜ 01 go run main.go
str=数值为: 12312 str类型为:string
6.20.6 strconv.FomatInt
func FormantInt (i int64, bease int ) string
//返回i的base进制的字符串传播。base必须在2到36之间,结果中会使用小写字母'a'到'z' 表示大于10的数字
10进制转2,8,16进制: str := strconv.FormatInt(132,3)
,返回对应的字符串
package main
import (
"fmt"
"strconv"
)
func main() {
str := strconv.FormatInt(123, 2) //将123转换为2进制
fmt.Printf("123对应的二进制是=%v \n", str)
str = strconv.FormatInt(123, 8) //将123转换为8进制
fmt.Printf("123对应的八进制是=%v \n", str)
str = strconv.FormatInt(123, 16) //将123转换为16进制
fmt.Printf("123对应的十六进制是=%v \n", str)
}
//输出结果
➜ 01 go run main.go
123对应的二进制是=1111011
123对应的八进制是=173
123对应的十六进制是=7b
6.20.7 strubgs.Contains
查找子串中,是否存在指定的字符: strubgs.Contains("seafood","foo")
如果有返回真,否则返回假
func Contains(s, substr string)
bool判断字符串s是否包含子串substr
案例演示
package main
import (
"fmt"
"strings" //需要引入包
)
func main() {
//true
a := strings.Contains("seafood", "foo") //seafood中存在foo,所以返回true
fmt.Printf("a=%v \n", a) //true
//false
b := strings.Contains("seafood", "abc") //seafood中不存在abc,所以返回false
fmt.Printf("b=%v \n", b) //false
}
//输出结果
➜ 01 go run main.go
a=true
b=false
6.20.8 strings.Count
统计一个字符串有几个指定的子串: strings.Count("ceheese","e")
func Count(s, sep string) int
返回字符串s中有几个不重复的sep子串
案例演示
package main
import (
"fmt"
"strings" //需要引入包
)
func main() {
num := strings.Count("sundayhk_aaa", "a") //查找sundayhk_aaa中有几个a
fmt.Printf("num=%v \n", num)
}
//输出结果
➜ 01 go run main.go
num=4
//当然如果字符中查找不到,就会返回0
6.20.9 string.gs.EqualFold
不区分大小写的字符串比较 (==是区分大小写)
package main
import (
"fmt"
"strings" //需要引入包
)
func main() {
//不区分大小写
a := strings.EqualFold("abc", "Abc")
fmt.Printf("结果: %v \n", a)
//区分大小写
fmt.Println("结果:", "abc" == "Abc")
}
//输出结果
➜ 01 go run main.go
结果: true
结果: false
6.20.10 strings.Index
返回子串在字符串第一次出现的index值,如果没有就返回-1
(负一)。index值从0 开始计算
package main
import (
"fmt"
"strings" //需要引入包
)
func main() {
//正确
index := strings.Index("sundayhk", "er")
fmt.Printf("index =%v \n", index)
//错误
index = strings.Index("sundayhk", "hello")
fmt.Printf("index-error =%v \n", index)
//当结果查询错误,找不到对应的会输出-1
}
//输出结果
➜ 01 go run main.go
index =7
index-error =-1
6.20.11 strings.LastIndex
返回子串在字符串最后一次出现的Index,如果没有对应的数值,也返回-1
package main
import (
"fmt"
"strings" //需要引入包
)
func main() {
//正确
index := strings.LastIndex("go golang", "go")
//go golang
//012345678
//最后一次出现在3,所以index是3
fmt.Printf("index =%v \n", index)
//错误
index = strings.LastIndex("go golang", "hello")
fmt.Printf("index-error =%v \n", index)
//当结果查询错误,找不到对应的会输出-1
}
//输出结果
➜ 01 go run main.go
index =3
index-error =-1
6.20.12 strings.Replace
将制定的子串替换成另外一个子串
package main
import (
"fmt"
"strings" //需要引入包
)
func main() {
str := strings.Replace("go go hello", "go", "北京", 1)
//第一个"go go hello"代表内容 (这里也可以是一个变量)
//go代表要替换的字符
//"北京"代表需要替换为的内容
//1,代表替换几个 -1代表所有
fmt.Printf("str =%v \n", str)
}
//输出结果
➜ 01 go run main.go
str =北京 go hello
//如果使用变量进行替换并不会影响原来的值,原来的变量不会发生变化
6.20.13 strings.Split
按照指定的某个字符,为分隔标示,将一个字符串拆分成字符串数组
package main
import (
"fmt"
"strings" //需要引入包
)
func main() {
strArr := strings.Split("hello,work,go", ",") //这里的逗号代表以逗号进行拆分,这里的hello,work,go也可以是一个变量。
//这里本身不会改变字符串,只是将其拆分,形成一个新的数组。 之前的不变,这里是属于一个值拷贝,并不会影响之前的变量以及数值
//此时strArr是一个数组,我们可以通过下面的方式获取到
for i := 0; i < len(strArr); i++ {
fmt.Printf("strArr_index=%v strArr=%v\n", i, strArr[i])
}
//输出正常结果
fmt.Printf("strArr=%v\n", strArr)
}
//输出结果
➜ 01 go run main.go
strArr_index=0 strArr=hello
strArr_index=1 strArr=work
strArr_index=2 strArr=go
strArr=[hello work go]
6.20.14 strings.ToLower
将字符串的字母进行大小写的转换
ToLower 小写 ToUpper 大写
package main
import (
"fmt"
"strings" //需要引入包
)
func main() {
str := "golang Hello"
str1 := strings.ToLower(str)
str2 := strings.ToUpper(str)
fmt.Printf("str全小写=%v\n", str1)
fmt.Printf("str全大写=%v\n", str2)
}
6.20.15 strings.Space
将字符串左右两边的空格去掉
package main
import (
"fmt"
"strings" //需要引入包
)
func main() {
str := strings.TrimSpace(" sundayhk ") //前面有空格,后面也有空格
//处理
fmt.Printf("str=%q\n", str)
}
//输出结果
➜ 01 go run main.go
str="sundayhk"
//-q参数会将字符串加双引号引起来
6.20.16 strings.Trim
将字符串左右两边指定的字符去掉
package main
import (
"fmt"
"strings" //需要引入包
)
func main() {
str := strings.Trim("! abcd!ocker !", " !") //将字符串左右两边的!号及空格去掉
//处理
fmt.Printf("str=%q\n", str)
}
//输出结果
➜ 01 go run main.go
str="abcd!ocker" //但是中间的!是不可以去除的
6.20.17 strings.TrimLeft
将字符串左边指定的字符去掉
package main
import (
"fmt"
"strings" //需要引入包
)
func main() {
str := strings.TrimLeft("! abcd!ocker !", " !") //将字符串左边的空格和叹号删除
//处理
fmt.Printf("str_Left=%q\n", str)
str = strings.TrimRight("! abcd!ocker !", " !") //将字符串右边的空格和叹号删除
//处理
fmt.Printf("str_Right=%q\n", str)
}
//输出结果
➜ 01 go run main.go
str_Left="abcd!ocker !"
str_Right="! abcd!ocker"
6.20.18 strings.TrimRight
将字符右边指定的字符去掉
案例:↑
6.20.19 strings.HasPrefix
判断字符串是否以指定的字符串开头
package main
import (
"fmt"
"strings" //需要引入包
)
func main() {
str := strings.HasPrefix("ftp://sundayhk.com","ftp") //如果是以ftp开头返回true,否则false
fmt.Printf("str HasPrefix=%v \n",str)
str = strings.HasSuffix("ftp://sundayhk.com","com") //如果是以com结尾返回true,否则false
fmt.Printf("str HasSuffix=%v\n",str)
}
//输出结果
➜ 01 go run main.go
str HasPrefix=true
str HasSuffix=true
6.20.20 strings.HasSuffix
判断字符串是否以指定的字符串结束
案例:↑
6.21 时间和日期相关函数
时间与日期的函数尝尝用于订单下单时间,代码执行花费时间等
时间和日期相关函数需要导入
time
包
6.21.1 time.Time
time.Time类型,用于时间表示
获取当前时间
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now() //获取当前时间
fmt.Printf("now当前时间为%v \nnow类型为:%T\n", now, now)
}
//输出结果
➜ 01 go run main.go
now当前时间为2021-04-27 14:34:49.401729 +0800 CST m=+0.000097106
now类型为:time.Time
上面获取的时间不适合我们阅读,可以通过下面的方法进行处理
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now() //获取当前时间
fmt.Printf("now当前时间为%v \nnow类型为:%T\n", now, now)
//通过now获取年月日、时分秒
fmt.Printf("年=%v\n", now.Year()) //now是上面的变量
fmt.Printf("月=%v\n", now.Month())
fmt.Printf("月=%v\n", int(now.Month())) //强制转换为数字
fmt.Printf("日=%v\n", now.Day())
fmt.Printf("时=%v\n", now.Hour())
fmt.Printf("分=%v\n", now.Minute())
fmt.Printf("秒=%v\n", now.Second())
}
//输出结果
➜ 01 go run main.go
now当前时间为2021-04-27 15:01:22.816391 +0800 CST m=+0.000088057
now类型为:time.Time
年=2021
月=April
月=4
日=27
时=15
分=1
秒=22
上面的方式虽然打印出时分秒、年月日,但是看的不太友好,我们需要进行使用格式化输出,输出成对应的格式
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now() //获取当前时间
fmt.Printf("%d年%d月%d日:%d:%d:%d\n", now.Year(), now.Month(), now.Day(),
now.Hour(), now.Minute(), now.Second())
}
//输出结果
➜ 01 go run main.go
2021年4月27日:15:10:28
//我们也可以将内容返回给一个变量,方便以后存入数据库中
func main() {
now := time.Now() //获取当前时间
dataStr := fmt.Sprintf("%d-%d-%d:%d:%d:%d\n", now.Year(), now.Month(), now.Day(),
now.Hour(), now.Minute(), now.Second())
fmt.Printf("data=%v", dataStr)
}
//输出结果
➜ 01 go run main.go
data=2021-4-27:15:22:45
使用time.Format()方法完成
注意: 下面的数字不允许改变,格式可以修改
package main
import (
"fmt"
"time"
)
func main() {
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"))
fmt.Println(now.Format("2006年01月02日"))
//输出时分秒
fmt.Println(now.Format("15:04:05"))
fmt.Println(now.Format("15时04分05秒"))
}
//输出结果
➜ 01 go run main.go
2021/04/27 16:57:11
2021-04-27 16:57:11
2021-04-27
2021年04月27日
16:57:11
16时57分11秒
6.21.2 时间常量
const ( Nanosecond Duration = 1 Microsecond = 1000 * Nanosecond Millisecond = 1000 * Microsecond Second = 1000 * Millisecond Minute = 60 * Second Hour = 60 * Minute)
在程序中可用于获取指定单位的时间,比如100毫秒
结合Sleep来使用时间常量 例如:每隔1秒打印一个数字,打印到5就退出
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
每隔0.1秒打印一个数字,打印到5退出
package main
import (
"fmt"
"time"
)
func main() {
i := 0
for {
i++
fmt.Println(i)
time.Sleep(time.Second)
if i == 5 {
break
}
}
}
//输出结果
➜ 01 go run main.go
1
2
3
4
5
获取当前unix时间戳和unixnano时间戳
可以获取随机的数字
- unix 时间戳 (获取秒数)
- unixnano时间戳 (获取纳秒数)
func (t time) Unix() int64
Unix将t表示为Unix时间,即从时间点January 1,1970 UTC到时间t所经过的时间(单位秒)
func (t time) UnixNano() int64
UnixNano将t表示为Unix时间,即从时间点January 1,1970 utc到时间点t所经过的时间(单位纳秒)。如果纳秒为单位的unix时间超过了int64能表示的范围,结果是未定义的。
注意这就意味着Time零值调用UnixNano方法的话,结果是未定义的
unix和unixnano使用
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Printf("unix时间戳=%v unixnano时间戳=%v \n",now.Unix(),now.UnixNano())
}
//输出结果
➜ 01 go run main.go
unix时间戳=1619581076 unixnano时间戳=1619581076595019000
➜ 01 go run main.go
unix时间戳=1619581077 unixnano时间戳=1619581077404640000
➜ 01 go run main.go
unix时间戳=1619581078 unixnano时间戳=1619581078288449000
➜ 01 go run main.go
unix时间戳=1619581081 unixnano时间戳=1619581081008677000
6.21.3 统计函数执行时间
package main
import (
"fmt"
"strconv" //引用转换函数
"time" //引入time包
)
func test() {
str := ""
for i := 0; i < 100000; i++ {
str += "hello" + strconv.Itoa(i)
}
}
func main() {
//在test函数执行前,先获取当前的unix时间戳
start := time.Now().Unix()
test() //执行test函数
//获取执行完毕后unix时间戳
end := time.Now().Unix()
fmt.Printf("执行test函数消费时间为%v秒\n", end-start) //结束时间减去开始时间
}
//输出结果
➜ 01 go run main.go
执行test函数消费时间为6秒
6.22 内置函数
golang设计者为了编程方便,提供了一些函数,这些函数可以直接使用
6.22.1 len
用来求长度,比如string、array、alice、map、channel
统计字符串、数组长度,按字节返回len(str)。len属于内建函数,不需要额外引用。直接使用即可
func len(v Type) int //内建函数len返回 v 的长度,这取决于具体类型:
数组:v中元素的数量
数组指针:*v中元素的数量(v为nil时panic)
切片、映射:v中元素的数量;若v为nil,len(v)即为零
字符串:v中字节的数量
通道:通道缓存中队列(未读取)元素的数量;若v为 nil,len(v)即为零
案例演示
func main() {
//golang中的编码同意为utf-8
//ascii的字符 (字母和数字)占一个字节,中文汉字占3个字节 (注意是字节不是字符)
str := "你好,sundayhk" //16个字节,中文6个,特殊符号1个,字母9个
fmt.Println("str len=", len(str)) //使用方法[len(需要引用的变量)]
}
//输出结果
➜ 01 go run main.go
str len= 16
6.22.2 new
用来分配内存,主要用来分配值类型,比如int、float32、structu返回的是指针。
package main
import (
"fmt"
_ "strconv" //引用转换函数
_ "time" //引入time包
)
func main() {
num2 := new(int) //此时num2为 *int类型
*num2 = 100
fmt.Printf("num2的类型%T \nnum2的值%v\nnum2地址%v\nnum2指针指向的值%v\n", num2, num2, &num2, *num2)
}
//输出结果
➜ 01 go run main.go
num2的类型*int
num2的值0xc00001a090 //这个地址是系统分配,每次不唯一。根据当前情况内存分配
num2地址0xc00000e028 //这个地址是系统分配,每次不唯一。根据当前情况内存分配
num2指针指向的值100 //默认是0
内存分析图
new(int)
6.22.3 make
用来分配内存,主要用来分配引用类型,比如chan、map、slice
6.23 错误处理
- go语言追求简洁优雅,所以go语言不支持传统的try…catch…finally这样处理
- go中引入的处理方式为:
defer
、panic
、recover
- go中可以抛出一个panic的异常,然后在defer中通过
reconver
捕获这个异常,进行正常处理
6.23.1 使用defer+reconver来处理error错误
案例: 下面的代码出现异常,10无法除0所以提示下面的报错。 并且main函数打印操作没有输出
package main
import (
"fmt"
)
func test() {
//使用defer配合匿名函数还有reconver来捕获和处理异常
defer func() { //定义匿名函数
err := recover() //reconver内置函数,可以捕获到异常
if err != nil { //nil不等于0,说明有异常
fmt.Println("err=", err) //输出异常
}
}() //使用匿名函数
n1 := 10
n2 := 0
res := n1 / n2
fmt.Printf("res=%v", res)
}
func main() {
//调用test函数
test()
fmt.Println("main函数下面的代码")
}
//输出结果
➜ 01 go run main.go
err= runtime error: integer divide by zero
main函数下面的代码
也可以使用下面的写法
func test() {
//使用defer配合匿名函数还有reconver来捕获和处理异常
defer func() { //定义匿名函数
if err := recover(); err != nil { //nil不等于0,说明有异常
fmt.Println("err=", err) //输出异常
}
}()
6.23.2 自定义错误
go程序中,也支持自定义错误,使用errors.New
和panic
内置函数
- errors.New(“错误说明”),会返回一个error类型的值,表示一个错误
- panic内置函数接收一个
interface {}
类型的值作为参数,可以接受error类型的变量,输出错误信息,并退出程序
errors包实现了创建错误值的函数。
package main
import (
"errors"
"fmt"
)
func intconfig(name string) (err error) { //返回值为err error
if name == "config.ini" {
//读取
return nil
} else {
//返回一个自定义错误
return errors.New("读取文件错误")
}
}
//定义调用intconfig函数
func readfile() {
err := intconfig("config.ini")
if err != nil {
panic(err) //如果读取文件发送错误,就输出这个错误,并终止程序
}
fmt.Println("readfile代码")
}
func main() {
//调用readfile函数
readfile()
fmt.Println("main代码")
}
//输出内容
➜ 01 go run main.go
readfile代码
main代码
当我们将文件名config.ini
进行修改,if判断不是config.ini会输出下面的错误。并且会终端下面的执行
这里返回的类型都是error类型的!
七、数组与切片
数组可以存放多个同一类型数据。数组也是一个数据类型,在go中,数组是值类型
7.1 数组入门
案例: 一个养鸡场有8只鸡,请问这8只鸡的总体重是多少? 平均体重为多少?
package main
import (
_ "errors"
"fmt"
)
func hensFunc() {
//定义一个数组
var hens [8]float64
//给数组的每个元素赋值,元素的下标是从0开始的
hens[0] = 3.0
hens[1] = 5.0
hens[2] = 1.0
hens[3] = 3.4
hens[4] = 2.0
hens[5] = 50.0
hens[6] = 150.0
hens[6] = 200.0 //新增一只鸡只需要给hens数组添加一个元素, 并且赋值即可
//遍历数组求出总体重
totalWeight := 0.0 //设置总体重变量
//使用for循环遍历hens数组
for i := 0; i < len(hens); i++ { //这里的len是求出数组的个数,也可以直接写死
totalWeight += hens[i] //每个数组的数相加
}
//此时的totalWeight已经是7只鸡的总数,接下来我们需要求出平均体重
avgWeight := fmt.Sprintf("%.2f", totalWeight/float64(len(hens))) //后面的len计算完是int类型,所以我们需要强制转换
//%.2f保留小数点2位,如果不使用这个变量,float64会打印出7位
fmt.Printf("一共有%v鸡\n", len(hens))
fmt.Printf("所有鸡总体重为%vkg 平均体重为%vkg\n", totalWeight, avgWeight)
}
func main() {
hensFunc()
}
//执行结果
➜ 02 go run main.go
一共有8鸡
所有鸡总体重为264.4kg 平均体重为33.05kg
7.2 数组定义和内存布局
数组的定义
var 数组名 [数组大小]数据类型
var a [5]int
赋初值
a[0]=1 a[1]=30 ...
- 数组的地址可以通过数组名来获取
&intArr
- 数组的第一个元素地址,就是数组的首地址
- 数组的各个元素的地址间隔是依据数组的类型决定,例如int64为8个字节,int32位4个字节,以此类推
7.3 数组使用
访问数组的元素
数组名[下标] 比如: 使用a数组的第三个元素 a[2]
从终端循环输入5个成绩,保存到float64数组,并输出
package main
import (
_ "errors"
"fmt"
)
func main() {
var source [5]float64
for i := 0; i < len(source); i++ {
fmt.Printf("请输入第%d个元素的值\n", i+1)
fmt.Scanln(&source[i])
}
//变量数组打印
for i := 0; i < len(source); i++ {
fmt.Printf("source [%d]=%v \n", i, source[i])
}
}
//输出结果
➜ 03 go run main.go
请输入第1个元素的值
10.1
请输入第2个元素的值
2
请输入第3个元素的值
13
请输入第4个元素的值
4
请输入第5个元素的值
6
source [0]=10.1
source [1]=2
source [2]=13
source [3]=4
source [4]=6
数组初始化方式
有四种初始化方式
package main
import (
_ "errors"
"fmt"
)
func main() {
var num1 [3]int = [3]int {1,2,3}
var num2 = [3]int {1,2,3}
var num3 = [...]int {1,2,3}
//可以指定元素值对应的下标
var num4 = [3]string{1:"abc",0:"aaa",2:"mmm"}
fmt.Println(num1)
fmt.Println(num2)
fmt.Println(num3)
fmt.Println(num4)
}
//输出结果
➜ 03 go run main.go
[1 2 3]
[1 2 3]
[1 2 3]
[aaa abc mmm]
7.4 数组的遍历
- 方式一: 传统for循环遍历
for i := 0; i < len(source); i++ {
fmt.Printf("source [%d]=%v \n", i, source[i])
}
- 方式二: for-range遍历
for-range遍历是go语言一种特有的结构,,可以用来遍历访问数组的元素
基本语法
for index, value :=range array01{ //:=是类型推导,定义+赋值
//array01遍历的数组名称
...
}
语法说明:
1.第一个返回值index是数组的下标
2.第二个value是该下标位置的值
3.他们都是仅在for循环内部可见的局部变量
4.遍历数组元素的时候,如果不想使用下标index,可以直接把下标index标为下划线_
5.index和value的名称不是固定的,即可以自定指定,一般命为index和value
for-range案例
for-range遍历数组
package main
import (
"fmt"
)
func main() {
var num = [3]string{"a", "b", "c"}
// fmt.Println(num)
for i, v := range num {
fmt.Printf("i=%v v=%v\n", i, v)
}
fmt.Println()
//如果我们只想要元素的值v,不想要下标可以通过[_]下划线来忽略
for _, v := range num {
fmt.Printf("i= v=%v\n", v)
}
}
//输出结果
➜ 04 go run main.go
i=0 v=a
i=1 v=b
i=2 v=c
i= v=a
i= v=b
i,v的作用于只在for循环内有效,属于局部变量
7.5 数组细节
- 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其固定长度不能动态变化
var arr01 [3]int
arr01[0] = 1
arr02[1] = 2
arr03[2] = 2.2 //需要是相同类型的组合,否则报错
//其长度也要固定,不能动态调整
arr04 [3] = 10 //上面只定义了3个,这里在添加会报错
- 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用
- 数组创建后,如果没有赋值,默认值如下
数值类型数组(整数、浮点数): 默认值是0
字符串数组: 默认值是""
bool数组: 默认值是false
- 数组的使用步骤
- 声明数组并开辟空间
- 数组各个元素赋值(不赋值默认零值)
- 使用数组
-
数组的下标是从0开始
-
数组下标必须在指定范围内使用,否则报
panic
错误:数组越界
例如: var arr [5]int 则有效下标为0-4
- Go的数组属于
值类型
,在默认情况下是值传递。因此会进行值拷贝。数组间不会互相影响
在go中,数组的类型是数组长度的一部分 (可以理解[4]int 和 [5]int不是同一个类型)
8) 如果想在其它函数中修改原来的数组,可以使用引用传递(指针方式);效率高,速度快
代码如下:
package main
import (
"fmt"
)
func test01(arr *[3]int) { //此时arr为数组指针
(*arr)[0] = 88 //(*arr)代表使用取值符,上面已经将arr设置为指针,我们需要使用取值符取出数组的0号索引
fmt.Printf("*arr地址= %p \n", &arr)
}
func main() {
arr := [3]int{11, 22, 33}
test01(&arr) //arr默认已经是指针类型,我们需要使用&符号使用
fmt.Println("main arr=", arr)
fmt.Printf("arr数组首地址= %p\n", &arr)
}
//输出结果
➜ 04 go run main.go
*arr地址= 0xc0000ae018
main arr= [88 22 33]
arr数组首地址= 0xc0000b6000
- 在go中长度是数组类型的一部分,在传递函数参数时需要考虑数组长度
❎ 案例一
func modify(arr []int){
arr[0] = 100
fmt.Println("modify的arr",arr)
}
func main(){
var arr = [...]int{1,2,3}
modify(arr)
}
//编译错误,因为不能把[3]int传给[]int
❎ 案例二
func modify(arr [4]int){
arr[0] = 100
fmt.Println("modify的arr",arr)
}
func main(){
var arr = [...]int{1,2,3}
modify(arr)
}
//编译错误,不能将[3]int传给][4]int,编译器判断这俩个不是同一种类型
✅ 案例三
func modify(arr [3]int){
arr[0] = 100
fmt.Println("modify的arr",arr)
}
func main(){
var arr = [...]int{1,2,3}
modify(arr)
}
7.6 数组案例
(1) 创建一个byte类型的26个元素的数组,分别放置A
-Z
。使用for循环访问所有元素并打印出来。提示: 字符数据运算'A'+1 ->'B'
思路分析
1.声明一个数组 var mychar [26]byte
2.使用for循环,利用字符可以进行运算的特点来赋值'A' + 1
等于B
3.for循环打印数组输出内容
package main
import (
"fmt"
)
func main() {
var numChar [26]byte
for i := 0; i < 26; i++ {
numChar[i] = 'A' + byte(i)
}
for i := 0; i < 26; i++ {
fmt.Printf("%c ", numChar[i])
}
}
//输出结果
➜ 04 go run main.go
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
//%c按照字符输出
(2) 请求出一个数组的最大值,并获得对应的下标
package main
import (
"fmt"
)
func main() {
//思路
//1.声明一个数组 var intArr[5] = [...]int {1,-1,9,90,11,9000}
//2.假定第一个元素就是最大值,下标是0
//3.从第二个元素开始循环比较,如果发现有更大的则替换
//定义数组并赋值
var intArr [6]int = [...]int{1, -1, 9, 9, 111, 9000}
//定义默认元素最大值为下标0
maxVal := intArr[0]
maxValIndex := 0
//执行for循环
for i := 1; i < len(intArr); i++ {
//从第二个元素开始比较,如果有更大的数,则进行替换
if maxVal < intArr[i] {
maxVal = intArr[i]
maxValIndex = i
}
}
fmt.Printf("maxVal=%v maxValIndex=%v \n", maxVal, maxValIndex)
}
//执行结果
➜ 04 go run main.go
maxVal=9000 maxValIndex=5
(3) 请求出一个数组的和和平均值。for-range
package main
import (
"fmt"
)
func main() {
//思路
//1.声明一个数组 var intArr[5]int = [...]int{1,-1,9,99,11}
//2.声明sum变量,用于求出和
//3.通过sum变量除 intArr[5]统计出平均数
var intArr [5]int = [...]int{1, -1, 9, 90, 12}
sum := 0 //sum设置一个变量
for _, v := range intArr { //因为我们不需要Index数,所以这里直接使用_过滤
sum += v //计算总和
}
//打印总和和平均数
fmt.Printf("intArr数组总和为%v intArr数组平均数为%v \n", sum, float64(sum)/float64(len(intArr)))
//这里sum和intArr数组为int类型,但是如果我们取平均数都是用可能无法获取到小数点,为了准确性。这里强制将int类型转换为float64,在进行相除
}
//执行结果
➜ 04 go run main.go
intArr数组总和为111 intArr数组平均数为22.2
7.6.1 数组反转
随机生成5个数,并将其反转打印
思路分析
1.随机生成5个随机数,使用rand.Intn函数 (使用time包中的纳秒生成随机数)
2.当我们得到5个随机数后,将随机数放入数组中
3.反转打印,交换的次数是数组的大小除2,倒数第一个和第一个元素交换,倒数第二个和第二个元素交换
package main
import (
"fmt"
"math/rand" //rand包实现了伪随机数生成器。
"time" //打印时间包包
)
func main() {
var numArr [5]int //定义数组
len := len(numArr) //计算数组长度,方便后续使用
rand.Seed(time.Now().UnixNano()) //使用纳秒生成随机数,如果不使用可能造成多次执行随机数不变
for i := 0; i < len; i++ {
numArr[i] = rand.Intn(100) //给每个数随机生成0~100内
}
fmt.Println("交换前: ", numArr)
//接下来我们需要反向打印,交换的次数为 len / 2
//倒数第一和第一个元素交换,倒数第二个和第二个元素交换
temp := 0 //做一个临时变量
for i := 0; i < len/2; i++ {
temp = numArr[len-1-i]
numArr[len-1-i] = numArr[i] //二次赋值
numArr[i] = temp
}
//打印交换后数组
fmt.Println("交换后: ", numArr)
}
//输出结果
➜ 04 go run main.go
交换前: [8 42 44 65 59]
交换后: [59 65 44 42 8]
7.7 切片基础介绍
什么情况下使用切片?
当我们统计学生成绩的时候,但是学生的个数不确定,这时候就需要使用切片
切片基本介绍
- 切片的英文是slice
- 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用类型的传递地址
- 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度
len(slice)
都一样 - 切片的长度是可以变换的,因此切片是一个可以动态变化的数组。
- 切片定义的基本语法
var 变量名 []类型
例如: var a []int
切片快速入门
package main
import (
"fmt"
)
func main() {
//声明一个数组
var ArrNum [5]int = [5]int{1, 2, 3, 4, 5}
//声明一个切片
slice := ArrNum[1:3]
//slice := ArrNum[1:3] 声明一个切片
//1.slice为切片名称
//2.ArrNum[1:3] 表示slice切片引用到ArrNum这个数组
//3.引用ArrNum数组起始下标为1,最后的下班为3(不包含3)
fmt.Println("ArrNum=", ArrNum)
fmt.Println("slice元素是=", slice)
fmt.Println("slice元素的个数=", len(slice))
fmt.Println("slice容量=", cap(slice)) //切片内容可以动态变化
//cap(内置函数) 容量: 目前切片可以存放最大个数的元素(最多元素的个数)切片的容量是可以动态变化的,容量一般是个数的2倍(不一定,有可能是1.5倍或者其它倍数)
}
//输出结果如下
➜ 04 go run main.go
ArrNum= [1 2 3 4 5]
slice元素是= [2 3]
slice元素的个数= 2
slice容量= 4
7.8 切片在内存中形式
切片在内存中如何布局
总结:
1.slice的确是一个引用类型
2.slice从底层来说,其实就是一个数据结构(struct结构体)
type slice struct{
ptr *[2]int
len int
cap int
}
7.9 切片的使用
切片使用包含三种方式
● 第一种
第一种方式: 定义一个切片,然后让切片去引用一个已经创建好的数组
参考上面案例
● 第二种
第二种方式: 通过make来创建切片
package main
import "fmt"
func main() {
var slice []int = make([]int, 4, 10)
//slice []int 定义切片数据类型
//make ([]type) make括号里面第一个为切片的类型
//4 = len切片大小
//10 = cap切片容量
fmt.Println("slice切片:", slice) //默认情况下,切片值均为0
fmt.Println("slice len=", len(slice), "\nslice cap=", cap(slice))
//重新赋值
slice[0] = 100 //slice[0]表示访问切片的第一个元素,这里的含义为将切片第一个元素进行赋值
slice[2] = 200 //slice[2]表示访问切片的第三个元素,并重新赋值
fmt.Println("slice切片:", slice)
}
//输出结果如下
➜ 05 go run main.go
slice切片: [0 0 0 0]
slice len= 4
slice cap= 10
slice切片: [100 0 200 0]
基本语法如下
var 切片名 []type = make([]type,len,[cap])
参数说明: type :就是数据类型
len: 大小
cap: 切片容量
func make
为内置函数
func make(Type,size IntegerType) Type
内置函数make分配并初始化一个类型为切片、映射、或通道的对象。其第一个实参为类型,而非值。make的返回类型与其参数相同,而非指向它的指针。其具体结果取决于具体的类型
切片: size指定了其长度。该切片的容量等于其长度。切片支持第二个整数实参可用来指定不同的容量;它必须不小于其长度,,因此make([]int,0,10)会分配一个长度为0,容量为10的切片。
映射: 初始分配的创建取决于size,但产生映射的长度为0。size可以省略,这种情况下就会分配一个小的起始大小
通道: 通道的缓存根据指定的缓存容量初始化。若size为零或被忽略,该通道则无缓存
当我们通过make 创建的数组,只可以使用slice下标访问,无法通过函数进行访问。对外不可见
针对于第一种和第二种使用方式的说明
- 通过make方式创建切片可以指定切片的大小和容量
- 如果没有给切片的各个元素赋值,那么就会使用默认值[int,float=0 string = "” bool = false]
- 通过make 方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice去访问各个元素。
● 第三种 定义一个切片,直接就指定具体数组,使用原理类似make的方式
package main
import "fmt"
func main() {
var slice []string = []string{"abc", "ukx", "sundayhk"}
fmt.Println("slice=", slice)
fmt.Println("slice len=", len(slice))
fmt.Println("slice cap=", cap(slice))
}
//输出结果
➜ 06 go run main.go
slice= [abc ukx sundayhk]
slice len= 3
slice cap= 3
小结
- 第一种使用方式是直接饮用数组,这个数组是事先存在的
- 第二种使用方式是通过
make
来创建切片,make也会创建一个数组,是由切片在底层进行维护
7.10 切片的遍历
切片的遍历和数组一样,也有两种方式
- for循环常规遍历
- for-range结构遍历切片
- for循环常规方式遍历演示
package main
import "fmt"
func main() {
var arr [5]int = [...]int{10, 20, 30, 40, 50} //定义数组
slice := arr[1:4] //定义切片
//for 循环遍历
for i := 0; i < len(slice); i++ {
fmt.Printf("slice[%v]=%v \n", i, slice[i])
}
}
//输出结果如下
➜ 06 go run main.go
slice[0]=20
slice[1]=30
slice[2]=40
- for-range结构遍历切片演示
package main
import "fmt"
func main() {
var arr [5]int = [...]int{10, 20, 30, 40, 50} //定义数组
slice := arr[1:4] //定义切片
//for range循环遍历
for i, v := range slice {
fmt.Printf("i=%v v=%v \n", i, v)
}
}
//输出结果
➜ 06 go run main.go
i=0 v=20
i=1 v=30
i=2 v=40
7.11 append动态添加
append内置函数,可以对切片进行动态添加
func append(slice []type, elems ...Type) []Type
内建函数append将元素追加到切片的末尾。若它有足够的容量,其目标就会重新切片以容纳新的元素。否则,就会分配一个新的基本数组。append返回更新后的切片,因此必须存储追加后的结果
案例演示:
package main
import "fmt"
func main() {
//使用append内置函数,对切片进行动态追加
var slice []int = []int{10, 20, 30}
//通过append直接给slice追加具体元素
slice = append(slice, 31, 32)
fmt.Println("slice=", slice)
}
//输出结果
➜ 06 go run main.go
slice= [10 20 30 31 32]
##############################################
package main
import "fmt"
func main() {
//使用append内置函数,对切片进行动态追加
var slice []int = []int{10, 20, 30}
//通过append直接给slice追加具体元素
slice = append(slice, 31, 32)
fmt.Println("slice=", slice)
//另外一种追加方式,通过将slice的切片追加给自己
slice = append(slice, slice...) //后面的slice也可以是别切片,但是后面的3个点位固定写法 (...前面的必须为切片,不能是数组)
fmt.Println("slic2=", slice)
}
//输出结果
➜ 06 go run main.go
slice= [10 20 30 31 32]
slic2= [10 20 30 31 32 10 20 30 31 32]
切片append操作的底层原理分析:
- 切片append操作的本质就是对数组库容
- go底层会创建一个新的数组newArr (安装扩容后大小)
- 将slice原来包含的元素拷贝到新的数组newArr
- slice重新引用到newArr
- 注意newArr是在底层来维护的
7.12 切片拷贝操作
切片使用copy内置函数完成拷贝,案例如下
slice1和slice2数据空间是独立的,相当于值拷贝
package main
import "fmt"
func main() {
var slice1 []int = []int{1, 2, 33}
var slice2 = make([]int, 10)
//使用copy函数进行拷贝切片
copy(slice2, slice1) //slice2为需要覆盖的切片,slice1为需要拷贝的切片
fmt.Println("slice1=", slice1, "slice2=", slice2)
}
//输出结果
➜ 06 go run main.go
slice1= [1 2 33] slice2= [1 2 33 0 0 0 0 0 0 0]
提示: copy(para1,para2): para1和para2都是切片类型
当slice拷贝到数值a中,并且a的数只有一个,copy拷贝并不会扩容,只会拷贝第一个元素
package main
import "fmt"
func main() {
var a []int = []int{1, 2, 3, 4, 5}
var slice = make([]int, 1)
fmt.Println(slice)
copy(slice, a)
fmt.Println(slice)
}
//输出结果
➜ 06 go run main.go
[0]
[1]
7.13 切片属于引用类型
切片属于引用类型,所以在传递时,遵守引用传递机制。
案例1
package main
import "fmt"
func main() {
var slice []int
var arr [5]int = [...]int{1, 2, 3, 4, 5} //创建了一个数组
slice = arr[:] //数组所有元素赋值给切片
var slice2 = slice //将slice切片赋值给slice2切片
slice[0] = 10
fmt.Println("slice2", slice2) //输出多少?
fmt.Println("slice", slice) //输出多少?
fmt.Println("arr", arr) //输出多少?
}
//输出结果
➜ 06 go run main.go
slice2 [10 2 3 4 5]
slice [10 2 3 4 5]
arr [10 2 3 4 5]
案例2
package main
import "fmt"
func test(slice []int) {
slice[0] = 100 //这里修改slice[0],会改变实参
}
func main() {
var slice = []int{1, 2, 3, 4}
fmt.Println("slice=", slice)
test(slice)
fmt.Println("slice", slice) //由于test函数修改的下标0元素,此时输出应该是[100,2,3,4]
}
//输出结果
➜ 06 go run main.go
slice= [1 2 3 4]
slice [100 2 3 4]
7.14 STRING和SLICE
- string底层是一个byte数组,因此string也可以进行切片处理
package main
import "fmt"
func main() {
//string底层是一个byte数组,因此string也可以进行切片处理
str := "hello@sundayhk"
//使用切片进行获取sundayhk
slice := str[6:]
fmt.Println("slice=", slice)
}
//输出结果
➜ 06 go run main.go
slice= sundayhk
- string和切片内存示意图
- string是不可变的,也就是不能通过
str[0] = 'z'
方式来修改字符串
案例如下:
- 如果需要修改字符串,可以先将string –> []byte(切片) / 或者 [] runne (切片) ->修改->重写转成string
byte类型是无法处理中文,英文是占用1个字节,中文是占用3个字节。 所以如果使用byte处理中文会出现乱码的情况
解决方法: 将string转成 []rune即可,因为[]rune是按字符处理,兼容汉字
package main
import "fmt"
func main() {
//string底层是一个byte数组,因此string也可以进行切片处理
str := "hello@sundayhk"
//使用切片进行获取sundayhk
slice := str[6:] //获取第六个到最后一个
fmt.Println("slice=", slice)
// 将hello@sundayhk修改为iello@sundayhk
//将str修改为byte类型的数组
arr1 := []rune(str)
//替换字符
arr1[0] = '你'
//将类型转换回string
str = string(arr1)
fmt.Println("str=", str)
}
//输出结果
➜ 06 go run main.go
slice= sundayhk
str= 你ello@sundayhk
7.15 切片Demo
编写一个函数fbn(n int)
- 可以接收一个n int
- 能够将斐波那契的的数列放到切片中
- 提示,斐波那契的数列形式
arr[0]=1;arr[1]=1;arr[2]=2;arr[3]=3;arr[4]=5;arr[5]=8
斐波那契的规律为前面两个数相加的和等于下一位数
代码如下
package main
import (
"fmt"
)
//为什么不使用数组,因为数组在声明的时候就需要把数量确定下来,但是切片make的时候进行分配空间。所以下面使用切片进行创建
/*
思路:
1.声明一个函数,让它返回一个切片 fbn(n int) ([uint64])
2.编写斐波那契 (n int)进行for循环来存放斐波那契数列 (0对应1,1对应的也是1)
*/
//编写函数切片
func fbn(n int) ([]uint64){ //返回一个切片
//声明一个切片,切片大写 n
fbnSlice := make([]uint64, n )
//第一个数和第二个数斐波那契数为1
fbnSlice[0] = 1
fbnSlice[1] = 1
//使用for循环来存放斐波那契的数列
for i := 2;i < n; i ++{
fbnSlice[i] = fbnSlice[i - 1] + fbnSlice[i -2]
//arr[0]=1;arr[1]=1;arr[2]=2;arr[3]=3;arr[4]=5;arr[5]=8
//斐波那契的规律为前面两个数相加的和等于下一位数,其中0和1数值都为1
}
//接下来我们将切片返回
return fbnSlice
}
func main(){
//测试
v := fbn(10) //给fbn函数传入一个10 (代表求出10个斐波那契数),并使用v变量名进行接收
fmt.Println("v=",v) //打印结果
}
//输出结果如下
PS C:\Users\Administrator\Desktop\GOstudy\day3\01> go run .\main.go
v= [1 1 2 3 5 8 13 21 34 55]
八、排序和查找
8.1 排序基本介绍
排序是将一组数据,以指定的顺序进行排列的过程。
排序的分类:
-
内部排序: 指将需要处理的所有数据都加载到内存中进行排序。包括(
交换式排序法
、选择式排序法
和插入式排序法
),适用于量小的场景交换式排序: 交换式排序属于内部排序法,是运用数据值比较后,依判断规则对数据位置进行交换,以达到排序的目的。交换式排序法又可以分为两种:(1) 冒泡排序法(Bubble sort) (2)快速冒泡排序法(Quick sort)
-
外部排序: 数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括(
合并排序法
和直接合并排序法
)
8.2 冒泡排序
交换式排序法-冒泡排序(Bubble Sorting)的基本思想是: 通过对待排序序列从后向前(从下标最大的元素开始),依次比较相邻元素的排序码,若发现逆序则交换,使排序码较小的元素逐渐从后部移向前部(从下标最大的单元移向下标较小的单元)
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。
冒泡排序规则
- 一共会经过arr.lengh-1的轮次比较,每一轮将会确定一个数的位置
- 每一轮的比较次数再逐渐减少 [5,4,3,2,1]
- 当发现前面的一个数比后面的一个数大时候就进行交换。
Demo演示
// 首先我们使用for循环一步步打印,先排序最大的数
package main
import (
"fmt"
)
//3. 创建函数,进行排序
func BubbleSort(arr *[5]int) { //4.传入指针类型数组,此时函数中的arr 等于main函数中的arr
//6.定义临时变量,接收数值
temp := 0
//5.按照大小进行排序,最大数放在最后
for j := 0; j < 4; j++ { //根据图片需要进行4次比较,所以我们写4
//(*arr)表示数组,[j]代表元素,第几位元素
if (*arr)[j] > (*arr)[j+1] { //6. (*arr)进行取值运算,如果前面的数大于后面的数就进行交换
//7.通过临时变量进行交换
temp = (*arr)[j]
(*arr)[j] = (*arr)[j+1]
(*arr)[j+1] = temp
}
}
fmt.Println("第一次排序arr=", (*arr))
}
func main() {
//1. 定义数组
arr := [5]int{11, 22, 44, 1, 8} //数组大小为5
//2. 将数组传递给一个函数,完成排序
BubbleSort(&arr)
}
//输出结果
➜ 07 go run main.go
第一次排序arr= [11 22 1 8 44]
接下来我们可以设置第二轮排序,实际上只需要复制for
循环中的变量即可
//第二轮排序
for j := 0; j < 3; j++ { //按照上图说明,每一次排序都会确认一位,所以我们这里减1
if (*arr)[j] > (*arr)[j+1] {
temp = (*arr)[j]
(*arr)[j] = (*arr)[j+1]
(*arr)[j+1] = temp
}
}
fmt.Println("第二次排序arr=", (*arr))
最后拷贝4个for循环
package main
import (
"fmt"
)
//3. 创建函数,进行排序
func BubbleSort(arr *[5]int) { //4.传入指针类型数组,此时函数中的arr 等于main函数中的arr
//6.定义临时变量,接收数值
temp := 0
//5.按照大小进行排序,最大数放在最后
for j := 0; j < 4; j++ { //根据图片需要进行4次比较,所以我们写4
//(*arr)表示数组,[j]代表元素,第几位元素
if (*arr)[j] > (*arr)[j+1] { //6. (*arr)进行取值运算,如果前面的数大于后面的数就进行交换
//7.通过临时变量进行交换
temp = (*arr)[j]
(*arr)[j] = (*arr)[j+1]
(*arr)[j+1] = temp
}
}
fmt.Println("第一次排序arr=", (*arr))
//第二轮排序
for j := 0; j < 3; j++ { //按照上图说明,每一次排序都会确认一位,所以我们这里减1
if (*arr)[j] > (*arr)[j+1] {
temp = (*arr)[j]
(*arr)[j] = (*arr)[j+1]
(*arr)[j+1] = temp
}
}
fmt.Println("第二次排序arr=", (*arr))
//第三轮排序
for j := 0; j < 2; j++ { //按照上图说明,每一次排序都会确认一位,所以我们这里减1-1
if (*arr)[j] > (*arr)[j+1] {
temp = (*arr)[j]
(*arr)[j] = (*arr)[j+1]
(*arr)[j+1] = temp
}
}
fmt.Println("第三次排序arr=", (*arr))
//第三轮排序
for j := 0; j < 1; j++ { //按照上图说明,每一次排序都会确认一位,所以我们这里减1-1-1
if (*arr)[j] > (*arr)[j+1] {
temp = (*arr)[j]
(*arr)[j] = (*arr)[j+1]
(*arr)[j+1] = temp
}
}
fmt.Println("第四次排序arr=", (*arr))
}
func main() {
//1. 定义数组
arr := [5]int{11, 22, 44, 1, 8} //数组大小为5
//2. 将数组传递给一个函数,完成排序
BubbleSort(&arr)
}
//输出结果
➜ 07 go run main.go
第一次排序arr= [11 22 1 8 44]
第二次排序arr= [11 1 8 22 44]
第三次排序arr= [1 8 11 22 44]
第四次排序arr= [1 8 11 22 44]
✅ 正确冒泡排序如下
package main
import (
"fmt"
)
//3. 创建函数,进行排序
func BubbleSort(arr *[5]int) { //4.传入指针类型数组,此时函数中的arr 等于main函数中的arr
//6.定义临时变量,接收数值
temp := 0
//8.外层嵌套for循环
for i := 0; i < len(*arr)-1; i++ {
//5.按照大小进行排序,最大数放在最后
for j := 0; j < len(*arr)-1-i; j++ { //根据图片需要进行4次比较,数组长度-1在-i
//(*arr)表示数组,[j]代表元素,第几位元素
if (*arr)[j] > (*arr)[j+1] { //6. (*arr)进行取值运算,如果前面的数大于后面的数就进行交换
//7.通过临时变量进行交换
temp = (*arr)[j]
(*arr)[j] = (*arr)[j+1]
(*arr)[j+1] = temp
}
}
fmt.Println("第",i ,"次排序arr=", (*arr))
}
}
func main() {
//1. 定义数组
arr := [5]int{11, 22, 44, 1, 8} //数组大小为5
//2. 将数组传递给一个函数,完成排序
BubbleSort(&arr)
}
//输出结果
➜ 07 go run main.go
第 0 次排序arr= [11 22 1 8 44]
第 1 次排序arr= [11 1 8 22 44]
第 2 次排序arr= [1 8 11 22 44]
第 3 次排序arr= [1 8 11 22 44]
冒泡面试可能需要手写,所以需要背下来。
8.3 查找
在Golang只能给,常用的查找有两种:
- 顺序查找
- 二分查找 (该数组是有序)
8.3.1 顺序查找
顺序查找可以通过下面的案例进行演示
数列: 白眉鹰王、金毛狮王、紫衫龙王、青翼蝠王,从键盘中任意一个名称,判断数列中是否包含此名称
第一种方式
package main
import (
"fmt"
)
func main() {
//定义数组
names := [4]string{"白眉鹰王", "金毛狮王", "紫衫龙王", "青翼蝠王"}
var inputName string
fmt.Println("请输入任意名称")
fmt.Scanln(&inputName)
//第一种for循环方式
for i := 0; i < len(names); i++ {
if inputName == names[i] {
fmt.Printf("匹配到%v,下标为%v\n", inputName, i)
break
} else if i == len(names) - 1 { //判断如果已经循环了4次还没匹配到就提示没有找到并退出
fmt.Println("没有匹配,请重新输入!")
}
}
}
//输出结果
✅ 输入正确结果
➜ 08 go run main.go
请输入任意名称
金毛狮王
匹配到金毛狮王,下标为1
❌ 输入错误结果
➜ 08 go run main.go
请输入任意名称
123
没有匹配,请重新输入!
第二种方式 推荐使用
package main
import (
"fmt"
)
func main() {
//定义数组
names := [4]string{"白眉鹰王", "金毛狮王", "紫衫龙王", "青翼蝠王"}
var inputName string
fmt.Println("请输入任意名称")
fmt.Scanln(&inputName)
//第二种for循环方式
input := -1
for i := 0; i < len(names); i++ {
if inputName == names[i] {
input = i
fmt.Printf("匹配到%v,下标为%v\n", inputName, i)
break
}
}
if input == -1 {
fmt.Println("没有匹配")
}
}
8.3.2 二分查找
二分查找思路分析:
二分查找代码:
package main
import (
"fmt"
)
//2.创建二分查询函数
func BinaryFind(arr *[6]int, leftIndex int, rightIndex int, findVal int) { //数组大小为6个,类型为int类型。需要定义leftIndex和rightIndex,确认从哪个下标开始,还需要定义findVal
//先找到中间的下标middle
middle := (leftIndex + rightIndex) / 2 //确定完下标后,接下来开始判断
// 3.判断leftIndex是否大于rightindex
if leftIndex > rightIndex {
fmt.Println("找不到")
return
}
// 4.判断
if (*arr)[middle] > findVal { // arr是指针,需要用括号加*号取值
//middle > findVal 说明我们要查找的书应该在 leftIndex --- middle -1中间
BinaryFind(arr, leftIndex, middle-1, findVal) //传入参数
} else if (*arr)[middle] < findVal {
//middle < findVal 说明我们要查找的书应该在 middle +1 --- rightIndex中间
BinaryFind(arr, middle+1, rightIndex, findVal) //传入参数
} else {
//大于小于都没有就是可能是等于
fmt.Printf("找到了,查到的数为%v 下标为%v\n", findVal,middle)
}
}
func main() {
//1.数组列表
arr := [6]int{1, 8, 10, 89, 1000, 2000} //需要有序排序
//5.调用函数
BinaryFind(&arr,0, len(arr) -1 , 10 ) //arr数组地址,0 left下标(代表最小),len(arr) -1 表示最大下标rightindex,需要- 1不然跑到2000后面了
//最后一个10代表我们需要查找的数
//这里我们需要传入地址,因为BinaryFind函数内部接收的是一个数组的指针
}
//输出结果
➜ 09 go run main.go
找到了,查到的数为10 下标为2
8.4 二维数组介绍
二维数组常见于五子棋游戏,游戏地图,棋盘等
8.5 二维数组快速入门
案例: 请使用二维数组输出如下图形
0 0 0 0 0 0 0 0 1 0 0 0 0 2 0 3 0 0 0 0 0 0 0 0
所谓二维数组就是一维数组里面的元素又是数组
二维数组实际上就是一维数组的扩展而已
package main
import (
"fmt"
)
func main() {
//首先定义/声明二维数组
var arr [4][6]int //可以理解4个数组,每个数组里面还有6个int,数组套数组
//接下来进行复制,否则默认打印都为0
arr[1][2] = 1
arr[2][1] = 2 //一维数组的第二个数组中的下标为1的数
arr[2][3] = 3
for i := 0; i < 4; i++ {
for j := 0; j < 6; j++ {
fmt.Print(arr[i][j], " ") //[i]打印最外层,[j]打印数组里面的数组
}
fmt.Println() //打印空行,否则都竖着打印了
}
}
//输出结果
➜ 10 go run main.go
0 0 0 0 0 0
0 0 1 0 0 0
0 2 0 3 0 0
0 0 0 0 0 0
二维数组使用方式
第一种:
- 语法: var 数组名[大小][大小]类型
- 比如:
var aa [2][3]int[][]
,在赋值
第二种:
- 声明; var数组名[大小][大小]类型=[大小][大小]类型{{初值}},{{初值..}}
- 赋值 (有默认值,比如int 类型就是0)
赋值
package main
import "fmt"
func main() {
var arr [2][3]int = [2][3]int{{1, 2,3}, {4, 2,5}} //一共有2个数组,每个数组里面有3个元素
fmt.Println("arr=", arr)
}
//输出结果
➜ 11 go run main.go
arr= [[1 2 3] [4 2 5]]
8.7 二维数组在内存中存在形式
8.8 二维数组的遍历
二维数组的遍历有两种,第一种是双for循环
完成,第二种是for-range方式
完成遍历
- 双层for循环案例
package main
import "fmt"
func main() {
arr := [2][3]int{{1, 2, 3}, {4, 2, 5}} //一共有2个数组,每个数组里面有3个元素
//首先打印第一个数组最外层的
for i := 0; i < len(arr); i++ { //这里的for循环实际上就是执行arr[2],2个数字
for j := 0; j < len(arr[i]); j++ { //这里的for循环实际上就是打印每个数组里面的元素
fmt.Printf("%v", arr[i][j]) //打印时添加可以增加\t增加美观
}
//每执行一个数组就进行换行
fmt.Println()
}
}
//执行结果
➜ 11 go run main.go
123
425
- for-range方式案例
package main
import "fmt"
func main() {
arr := [2][3]int{{1, 2, 3}, {4, 2, 5}} //一共有2个数组,每个数组里面有3个元素
for i, v := range arr { //最外层数组,i=有2个数组,v=是一维数组的值,不是某个元素
for j, v2 := range v { //j表示第几个元素,v2表示值
fmt.Printf("arr[%v][%v]=%v \t", i, j, v2)
}
fmt.Println() //换行
}
}
//执行结果
➜ 11 go run main.go
arr[0][0]=1 arr[0][1]=2 arr[0][2]=3
arr[1][0]=4 arr[1][1]=2 arr[1][2]=5
8.9 二维数组案例
要求: 定义二维数组,用于保存三个班,每个班五名同学成绩,要求出每个班级平均分、以及所有班级平均分
package main
import (
"fmt"
)
/*
**要求:** 定义二维数组,用于保存三个班,每个班五名同学成绩,要求出每个班级平均分、以及所有班级平均分
*/
func main() {
//1.定义一个二维数组
var scores [3][5]float64
//2.for循环遍历数组
for i := 0; i < len(scores); i++ {
for j := 0; j < len(scores[i]); j++ {
fmt.Printf("请输入第%d个班级的第%d个学生成绩\n", i+1, j+1)
fmt.Scanln(&scores[i][j])
}
}
//3.遍历输出成绩后的二维数组,统计平均分
totalSum := 0.0 //用于累计所有班级平均分
for i := 0; i < len(scores); i++ {
sum := 0.0 //定义每个班级的总分
for j := 0; j < len(scores[i]); j++ {
sum += scores[i][j]
}
totalSum += sum
fmt.Printf("第%d班级的总分为%v 平均分为%v\n", i+1, sum, sum/float64(len(scores[i])))
}
fmt.Printf("所有班级的总分为%v 所有班级的平均分为%v \n", totalSum, totalSum/15)
fmt.Println(scores)
}
九、MAP
9.1 Map基本介绍
map是key-value数据结构,又称为字段或者关联数组。类似其它语言的集合
9.2 Map声明
基本语法
var map变量名 map[keytype]valuetype
#后面的map为关键字,必须要填写
#keytype key的类型
#valuetype valuet类型
key可以是什么类型
golang中的map的key可以是很多种类型,例如bool、数字、string、指针、channel(管道),还可以是只包含前面几个类型的结构体、接口、数组等。通常为int
、string
注意: slice,map还有function不可以,因为这几个没法用==来判断
value可以是什么类型
valuetype的类型和key基本上一样,通常为:数字(整数,浮点数)
、string
、map
、strcuct
map 声明的举例
var a map[string]string //key为string,值为string
var a map[string]int
var a map[int]string
var a map[string]map[string]string //key为string,值为另外一个map
注意: 声明是不会分配内存的,初始化需要make,分配内存后才能赋值和使用
9.3 Map使用
package main
import (
"fmt"
)
func main() {
var a map[string]string //声明map
//因为map默认不分配数据空间(内存),所以我们需要make创建一个数据空间
a = make(map[string]string, 10) //make的第一个参数要写map的数据类型,10代表分配10对key-value
a["n1"] = "abc" //赋值
a["n3"] = "ukx"
a["n2"] = "sundayhk" //如果key的值相同,map会进行覆盖
fmt.Println(a)
//key不可以重复,值可以重复
//map无法保证每次都是有序
}
9.3.1 Map使用方式
map使用方式有下面几种
方式一
//声明
var abc map[string]string
//make
make(map[string]string,10)
abc = make(map[string]string,10)
方式二
✅ 推荐使用
//声明就直接make
abc := make(map[string]string)
abc["no1"] = "北京"
abc["no2"] = "上海"
abc["no3"] = "深圳"
fmt.Println(abc)
方式三
//声明,直接赋值
var abc map[string]string = map[string]string{
"no1" : "北京",
"no2" : "上海",
}
或者~
abc := map[string]string{
"no1" : "北京",
"no2" : "上海",
}
应用案例: 演示一个key-value的value是map的类型
例如: 存放3个学生信息,每个学生信息有name和sex信息
思路: map[string]map[string]string (第一个map为学生学号,后面的map为name和性别)
qwe := make(map[string]map[string]string)
//后面的值是一个map类型,所以还需要make一下
qwe["n1"] = make(map[string]string, 2)
qwe["n1"]["name"] = "丛宇鸿"
qwe["n1"]["sex"] = "男"
qwe["n2"] = make(map[string]string, 2)
qwe["n2"]["name"] = "abc"
qwe["n2"]["sex"] = "女"
//如果我们只想打印一个信息,之后需要输入第一个key即可
fmt.Println(qwe["n2"]) //取出第二个学生的完全信息
fmt.Println(qwe["n2"]["sex"]) //只要第二个学生的性别
fmt.Println(qwe)
9.4 Map增删改查
- 增、改
map[“key”] = value 如果key还没有就增加,如果已存在就修改
- 删
map删除使用delete
内置函数,如果key存在,就删除该key-value,如果key不存在,不操作,但是也不会报错
abc["no1"] = "成都"
delete(abc,"no1")
fmt.Println(abc)
如果我们要删除map的所有key,没有一个专门的方法一次性删除,可以遍历一下key,然后进行逐个删除
或者通过make一个新的,让原来的称为垃圾,被gc回收
func main() {
abc := map[string]string{
"no1": "北京",
"no2": "上海",
}
abc = make(map[string]string)
fmt.Println(abc)
}
- 查
v, key := abc["no1"]
if key {
fmt.Println("no1存在,值为", v)
} else {
fmt.Println("no1不存在")
}
9.5 Map遍历
for range遍历支持map,但是不能保证每次遍历次序是一样的。
package main
import (
"fmt"
)
func main() {
qwe := make(map[string]map[string]string)
//后面的值是一个map类型,所以还需要make一下
qwe["n1"] = make(map[string]string, 2)
qwe["n1"]["name"] = "丛宇鸿"
qwe["n1"]["sex"] = "男"
qwe["n2"] = make(map[string]string, 2)
qwe["n2"]["name"] = "abc"
qwe["n2"]["sex"] = "女"
//使用for-range遍历比较复杂的map
for k, v := range qwe {
//这里的k=n1,第一个map的key
//这里的v=map,是一个map,所以需要在加一层for range遍历
fmt.Println("k=", k)
for k1, v1 := range v { //因为外层的v是一个for循环,我们直接循环v这个值即可
fmt.Printf("\tk1=%v v=%v\n", k1, v1) //这里的k1代表的是map里面的key,v1就是key对应值
//\t为制表符,有层级关系
}
fmt.Println()
}
}
//输出结果
➜ 14 go run main.go
k= n1
k1=name v=丛宇鸿
k1=sex v=男
k= n2
k1=name v=abc
k1=sex v=女
通过len统计map长度
fmt.Printf("qwe函数一共有 %v 对\n",len(qwe))
9.6 Map切片
切片的数据类似如果是map,则我们称为slice of map,map切片,这样使用则map个数就可以动态变化
// 使用一个map来记录monster的信息name和age,也就是说一个monster对应一个map,并且monster的个数可以动态变化
需要使用golang内置函数append函数,进行动态增加,前面数组中已经介绍过了
package main
import (
"fmt"
)
func main() {
//声明一个map切片,并进行make
monsters := make([]map[string]string, 2)
if monsters[0] == nil {
monsters[0] = make(map[string]string, 2) //在使用的过程中,需要make
monsters[0]["name"] = "牛博网"
monsters[0]["age"] = "200"
}
// fmt.Println(monsters)
//使用append进行动态扩容切片
newmonsters := map[string]string{
"name": "牛老师",
"age": "200",
}
monsters = append(monsters, newmonsters) //将newmonsters追加到monsters,并且重新赋值
fmt.Println(monsters)
}
//输出结果
➜ 16 go run main.go
[map[age:200 name:牛博网] map[] map[age:200 name:牛老师]]
9.7 Map排序
1.golang中没有一个专门的方法针对map的key进行排序 2.golang中map默认是无序的,也不是按照添加顺序存放,所以每次遍历可能不一样 3.golang中map的排序是先将key进行排序,然后根据key值遍历输出即可
package main
import (
"fmt"
"sort" //引用排序
)
func main() {
num := make(map[int]int)
num[1] = 10424240
num[2] = 10
num[3] = 1044
num[4] = 1041
num[55] = 10414
num[22221] = 104
num[2222] = 10441
// fmt.Println(num)
//1.先将map的key放入切片中
//2.对切片进行排序
//3.遍历切片,然后按照key来输出map的值
var keys []int
//循环num[1--22221]
for k, _ := range num {
keys = append(keys, k) //将k 追加到keys切片中
}
//使用sort进行排序
sort.Ints(keys) //需要排序的切片
fmt.Println(keys) //将num1...num222221进行输出
fmt.Println()
//遍历切片,输出值
for _, k := range keys { //这里的_是下标,我们不需要下标,所以忽略
fmt.Printf("num[%v] = %v\n", k, num[k]) //这里的k就是我们num[1...2222]的值,num[k] 就是num[1] 对应的结果
}
}
//输出结果
➜ 17 go run main.go
[1 2 3 4 55 2222 22221]
num[1] = 10424240
num[2] = 10
num[3] = 1044
num[4] = 1041
num[55] = 10414
num[2222] = 10441
num[22221] = 104
9.8 Map 注意事项
- map是引用类型,遵守引用类型传递的机制,在一个函数接收map,修改会,会直接修改原来的map
package main
import (
"fmt"
)
func test(map1 map[int]int){
map1[10] = 100
}
func main(){
map1 := make(map[int]int)
map1[1] = 1
map1[2] = 14
map1[9] = 20
map1[10] = 15
test(map1)
fmt.Println(map1)
}
- map动态扩容达到后,再想增加map元素,会自动扩容,map可以动态的增长键值对key-value
- map的value也经常使用struct(结构体)类型,更适合管理复杂的数据
9.9 map常见演示案例
1.使用map[string]map[string]string的map类型
2.key: 表示某用户名,是唯一的,不可以重复
3.如果某个用户粗奴在,就将其密码修改888888
,如果不存在就增加这个用户信息(包括昵称nickname和密码pwd)
4.编写一个函数modifyUser(users map[string]map[string]string,name string)完成上述功能
package main
import (
"fmt"
)
//定义函数
func modifyUser(users map[string]map[string]string, name string) {
//map的key是一个string类型,value是一个map类型(里面有2对),定义了一个name,也是string类型
//modifyUser函数接收users和name参数
//简单就是传一个用户名,判断用户名中是否有这个密码
//判断users中有没有对应的名字
if users[name] != nil { //如果users[name]有这个用户了,就是不等于
//有这个用户
users[name]["passwd"] = "888888"
} else { //没有这个用户就增加用户名和密码
//首先make一个map
users[name] = make(map[string]string, 2) //需要有2对,昵称和密码
users[name]["passwd"] = "888888"
users[name]["nickname"] = "新增用户昵称" + name
}
}
func main() {
//定制users map
//后面的值也是map,所以我们在使用前还需要在make一下
users := make(map[string]map[string]string, 10)
//这里我们定义一个存在的用户,即存在用户只会修改密码
users["sundayhk"] = make(map[string]string) //因为value是map,所以这里直接写密码是要make的
users["sundayhk"]["passwd"] = "111111"
users["sundayhk"]["nikename"] = "运维啊"
//传入users和name到modifyUser函数中
modifyUser(users, "docker")
modifyUser(users, "test")
modifyUser(users, "sundayhk")
//打印结果
fmt.Println(users)
}
十、面向对象
- golang也支持面向对象编程
OOP
,和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以说golang支持面向对象编程特性是比较准确的 - golang没有类(class),go语言的结构体(struct)和其它编程语言的类(class)有同等的地位,可以理解golang是基于struct来实现OOP特性的
- golang面向对象编程非常简,去掉了传统OOP语言的继承、方法重载、构造函数和析构函数、隐藏的this指针等等
- golang有面向对象的继承、封装和多态的特性,只是实现的方式和其它OOP语言不一样,比如继承: golang没有extends关键字,继承是通过匿名字段来实现
- golang面向对象(OOP)很优雅,OOP本身就是语言类型系统(type system)的一部分,通过接口关联,耦合性低
10.1 结构体
- 结构体是自定义的数据类型
- 结构体变量(实例)是具体的,实际的,代表一个具体的变量
package main
import (
"fmt"
)
type Cat struct { //定义一个名称为cat的struct
Name string //名称
Age int //年龄
Eat string
}
func main() {
// e := Cat
var e Cat
e.Name = "abc" //复制,如果struct不赋值,默认都是空或者0
e.Age = 4
e.Eat = "<・)))><<"
fmt.Println(e)
fmt.Printf("名称:%v 年龄:%v 喜欢:%v \n", e.Name, e.Age, e.Eat)
}
//输出结果
➜ 01 go run main.go
{abc 4 <・)))><<}
名称:abc 年龄:4 喜欢:<・)))><<
结构体变量在内存中的布局
当我们声明一个结构体时,它的内存变量已经生成。结构体是一个值类型
10.1.1 声明结构体
声明结构体基本语法
type 结构体名称(标示符) struct{
field1 type //字段名称及字段类型
field2 type
}
如果我们定义的结构体名称及字段名称首字母大写,表示可以在其他包中使用
在结构体字段中,一般是基本数据类型、数组、引用类型
指针、slice和map的默认值都是nil,即还没有分配空间 (需要make后使用)
//声明struct
type Person struct{
ptr *int //指针类型
slice []int //切片类型
map1 map[string]string //map类型
}
**** 下面为引用 *****
//使用slice,需要make
p1.slice = make([]int, 10)
p1.slice[0] = 100
//使用map,也需要make
p1.map1 = make(map[string]string)
p1.map["key1"] = "docker"
fmt.Println(p1)
不同结构体变量的字段是独立的,互不影响,一个结构体变量字段的更改不会影响另外一个
结构体是值类型,它们之间的拷贝是通过值拷贝,不会互相影响
package main
import (
"fmt"
)
type test struct {
Name string
Age int
}
func main() {
var a test
a.Name = "sundayhk"
a.Age = 15
fmt.Printf("默认a=%v\n", a)
//我们将a通过值拷贝到b
b := a
b.Name = "docker"
b.Age = 20
fmt.Printf("b=%v\n", b)
fmt.Printf("a=%v\n", a)
}
//输出结果
➜ 02 go run main.go
默认a={sundayhk 15}
b={docker 20}
a={sundayhk 15}
b := a
在内存中的变化
10.1.2 创建结构体变量的四种方式
- 方式一,直接声明
var presson Person
变量名称 结构体
- 方式二,{}
案例 var person Person = Preson{}
package main
import (
"fmt"
)
type Fname struct {
Name string
Age int
Like string
}
func main() {
v := Fname{}
v.Name = "abc"
v.Age = 18
v.Like = "eat"
fmt.Println(v)
//还可以直接在v :=直接引用
v1 := Fname{"sundayhk", 20, "computer"}
fmt.Println(v1)
}
//输出结果
➜ 03 go run main.go
{abc 18 eat}
{sundayhk 20 computer}
- 方式三 (new)
var person *Person = new(Person)
因为person是一个指针,因此标准写法为(*person).Name = "abc"
,当然也可以采用简化的写法person.Name = "abc"
原因: go设计者为了程序员方便使用,底层会对person.Name = "abc"
进行处理,会自动给person 加上取值运算(*person).Name = "abc"
package main
import (
"fmt"
)
type Fname struct {
Name string
Age int
Like string
}
func main() {
v := Fname{}
v.Name = "abc"
v.Age = 18
v.Like = "eat"
fmt.Println(v)
var person *Fname = new(Fname) //通过创建指针来修改变量
person.Name = "sundayhk"
person.Age = 19
person.Like = "play"
fmt.Println(*person)
}
//输出结果
➜ 03 go run main.go
{abc 18 eat}
{sundayhk 19 play}
- 方式四 (-&)
1. 方式三和方式四返回的是结构体 **2. 结构体指针访问字段的标准方式应该是: (*结构体指针).字段名 **例如:(*person).Name = “docker”3. 但go做了一个简化,也支持结构体指针.字段名,比如person.Name = “sundayhk”。go编辑器底层对person.Name做了转化(*person).Name
var person *Person = &Person{}
案例演示
package main
import (
"fmt"
)
type Fname struct {
Name string
Age int
Like string
}
func main() {
v := Fname{}
v.Name = "abc"
v.Age = 18
v.Like = "eat"
fmt.Println(v)
var person *Fname = &Fname{} //struct结构体名称为Fname
(*person).Name = "go"
(*person).Age = 20
//同样也可以使用简化的命令
person.Name = "Time"
person.Age = 21
person.Like = "playgame"
fmt.Println(*person) //打印person变量
}
//输出结果
➜ 03 go run main.go
{abc 18 eat}
{Time 21 playgame}
同样,也可以在创建时直接赋值
var person *Fname = &Fname{"abc",18}
总结:
1.第三种和第四种方式返回的是结构体指针
2.结构体指针访问字段的标准方式应该是: *(*结构体).字段名 (*person).Name = "tomcat"
3.go做了一个简化,也支持结构体指针.字段,比如person.Name = "tomcat"
。go底层编辑器对person.name做了转化,所以可以直接使用
10.1.3 struct类型的内存分配机制
变量总是存在内存中的 结构体变量在内存中存在方式见下图
package main
import (
"fmt"
)
type lei struct {
Name string
Age int
}
func main() {
var p1 lei
p1.Age = 10
p1.Name = "abc"
var p2 *lei = &p1 //将p1地址赋给p2
//输出P2结果
fmt.Println((*p2).Age)
fmt.Println(p2.Age)
//修改p2内容
p2.Name = "tom"
//打印输出结果
fmt.Printf("p2.Name=%v p1.Name=%v \n", p2.Name, p1.Name)
fmt.Printf("p2.Name=%v p1.Name=%v \n", (*p2).Name, p1.Name) //这里只是表示(*p2).Name和p2.Name作用相同
}
//输出结果
➜ 04 go run main.go
10
10
p2.Name=tom p1.Name=tom
p2.Name=tom p1.Name=tom
上述代码在内存中的布局图
10.1.4 结构体所有字段在内存中是连续分布的
连续分布的优点: 在取值的时候通过内存的地址进行加减取值,速度会比较快
10.1.5 结构体是用户单独定义类型,和其他类型进行转换时需要有完全相同的字段
根据标题的提示,也就是它的名称、个数和类型需要完全一致
package main
import "fmt"
type A struct {
Num int
}
type B struct {
Num float64
}
func main() {
//定义a 和b
var a A
var b B
fmt.Println(a, b) //此时我们直接打印是没有问题的
//但是目前a和b的结构体数据类型不相同,我们不可以把a赋给b
a = A(b) //此时编辑器提示类型不相同(强制转换也不行)
//不只是类型不相同不可以转换,包括名字,数据类型,个数等都相同,才可以进行转换
}
10.1.6 结构体进行type重新定义(相当于取别名),golang认为是新的数据类型,但是互相之间可以强转
package main
import (
"fmt"
)
type Student struct {
Name string
Age int
}
type Stu struct {
Name string
Age int
}
func main() {
var stu1 Student
var stu2 Stu
// stu2 = stu1 //错误
stu2 = Stu(stu1)
fmt.Println(stu1, stu2)
}
//输出结果
➜ 07 go run main.go
{ 0} { 0}
10.1.7 结构体序列化
struct的每个字段上,可以写上一个tag,该tag可以通过反射机制
获取,常见的场景就是序列化和反序列化
为什么需要序列化?
案例演示:
package main
import (
"encoding/json"
"fmt"
//引用json反射
)
type Student struct {
Name string `json:"name"` //因为是要通过别的包引入Student这个结构体,所以下面的变量必须要大写
Age int `json:"age"` //通过后面`json:" "`实现给结构体打tag标签
Like string `json:"like"`
}
func main() {
//接下来给结构体进行赋值
Stu := Student{"sundayhk", 20, "english"}
//接下来对Stu进行处理
jsonStr, err := json.Marshal(Stu) //调用json.Marshal方法来进行处理
//因为json.Marshal会返回2个值,一个是byte类型和err错误,我们这里加一个判断
if err != nil {
fmt.Println("json错误", err)
}
fmt.Println("jsonStr", string(jsonStr)) //通过json.Marshal方法返回的是byte类型,所以我们需要通过string类型转换一下
}
//输出结果
➜ 07 go run main.go
jsonStr {"name":"sundayhk","age":20,"like":"english"}
温馨提示: 如果我们在struct结构体中后面不添加tag标签,那么使用的时候会提示大写,但是我们又不能取消大写。如果取消了encoding包就调用不到了
➜ 07 go run main.go
jsonStr {"Name":"sundayhk","Age":20,"Like":"english"} //不添加tag输出结果
10.2 方法
Golang中的方法是作用在指定的数据类型上的。(和指定的数据类型绑定),因此自定义类型,都是可以有方法的,而不仅仅是struct
10.2.1 方法的调用和声明
自定义方法语法说明
type Person struct {
Name string
}
func (a Person) test() {
fmt.Println("test()", a.Name)
}
对上面语法的说明
func (a Person){}
表示Person结构体有一方法,方法名为test(a Person)
体现test方法是和Person绑定的
Demo案例演示
package main
import (
"fmt"
)
type Person struct {
Name string
}
//给Person类型,绑定一个方法
func (a Person) test() { //a=变量名 Person=结构体 test方法的名字
fmt.Println("test()", a.Name) //输出名字
}
func main() {
//定义一个Person实例
var a Person
a.Name = "sundayhk"
a.test() //调用方法
}
//A结构体有一个方法test
总结说明
- test方法和Person类型绑定
- test方法只能通过Person类型的变量来调用,而不能直接调用,也不能使用其它类型的变量调用
func (a Person)test(){}
其中a表示哪个Person变量调用,这个a就表示谁的副本;这点和函数传参非常相似a
这个名字,是可以自己指定并且随意
10.2.2 方法的快速入门
- 给Person结构体添加speak方法,输出 xxxx 运维开发。
package main
import (
"fmt"
)
type Speak struct {
Name string
}
func (input Speak) speak() {
fmt.Println(input.Name, "运维开发")
}
func main() {
var a Speak
fmt.Println("请输入名称")
fmt.Scanln(&a.Name)
a.speak()
}
//输出结果
➜ 02 go run main.go
请输入名称
sundayhk
sundayhk 运维开发
- 给Person结构体添加jisuan方法,可以计算从1+..+1000的结果
package main
import (
"fmt"
)
type Speak struct {
Name string
}
func (input Speak) jisuan() {
res := 0
for i := 1; i <= 1000; i++ {
res += i
}
fmt.Println(input.Name, "res等于", res)
}
func main() {
var a Speak
a.jisuan()
}
- 给Person结构体添加jisuan2方法,该方法可以接收一个数n,计算从1+..+n的结果
package main
import (
"fmt"
)
type Speak struct {
Name string
Num int
}
func (input Speak) jisuan2(n int) { //返回n参数
res := 0
for i := 1; i <= n; i++ {
res += i
}
fmt.Println(input.Name, "res等于", res)
}
func main() {
var a Speak
a.Name = "sundayhk"
a.jisuan2(2) //传入n
}
//输出结果
➜ 02 go run main.go
sundayhk res等于 3
- 给Person结构体添加getSum方法,计算两个数的和,并返回结果
package main
import (
"fmt"
)
type Speak struct {
Name string
Num int
}
func (input Speak) getSum(n1 int, n2 int) int { //多个返回值需要(int)
return n1 + n2 //返回n1+n2的结果
}
func main() {
var a Speak
res := a.getSum(10, 20)
fmt.Println(res)
}
//输出结果
➜ 02 go run main.go
30
10.2.3 方法的调用和传参机制原理
方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,也当做实参传递给方法。
说明
- 在通过一个变量去调用方法时,其调用机制和函数一样。
- 不一样的地方是,调用方法该变量本身也会作为一个参数传递到方法中 (如果是值类型那么是值拷贝,如果是引用类型则是地址拷贝)
案例演示
1.声明一个结构体Circle,字段为radius 2.声明一个方法area和Circlee绑定,可以返回面积
package main
import (
"fmt"
)
type Circle struct {
radius float64
}
func (c Circle) area() float64 {
return 3.14 * c.radius * c.radius
}
func main() {
var c Circle
c.radius = 4
res := c.area()
fmt.Println(res)
}
//输出结果
➜ 03 go run main.go
50.24
10.2.4 方法的声明和定义
func (recevier type) methoName (参数列表) (返回值列表) {
方法体
return 返回值
}
- 参数列表: 表示方法输入
- recervier type: 表示这个方法和type这个类型进行绑定,或者说该方法作用于type类型
- receiver type: type可以是结构体,也可以其它的自定义类型
- receiver: 就是type类型的一个变量(实例),比如: Person结构体的变量(实例)
- 返回值列表: 表示返回的值,可以多个
- 方法主体: 表示为了实现某一个功能代码块(计算、写数据库等)
- return语句不是必须的
10.2.5 方法注意事项
1.结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
2.修改结构体变量,可以通过结构体指针的方式来处理
值引用的逻辑图
3.golang中的方法作用在指定的数据类型上
golang的方法作用在指定的数据类型上
即:和指定的数据类型绑定
。因此自定义类型,都可以有方法
,而不仅仅是struct,比如int、float64等
package main
import (
"fmt"
)
type interger int
func (in interger) inp() {
fmt.Println("i=", in)
}
func main() {
var in interger
in = 10
in.inp()
}'
//输出结果
➜ 04 go run main.go
i= 10
4.方法的访问范围控制规则 和函数一样,方法名首字母小写,只能在本地访问,方法首字母大写。可以在本包和其它包访问 (这里可以参考函数)
5.如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出
比如我们输出结构体日志,就可以通过这种方法进行输出
package main
import (
"fmt"
)
type Abc struct {
Name string
Age int
Like string
}
func (a *Abc) String() string { //指针绑定,绑定到Abc struct
//给*Abc实现方法String()
str := fmt.Sprintf("Nmae=[%v] Age=[%v] Like=[%v]", a.Name, a.Age, a.Like)
//将结果返回
return str
}
func main() {
//定义变量
stu := Abc{
Name: "tomcat",
Age: 18,
Like: "eat 老冰棍",
}
fmt.Println(&stu) //这里需要传入地址,如果不添加&,会将结果输出。就不会调用我们的String方法,所以我们需要使用&获取地址
//如果你实现了 *Abc 类型的String方法,就会自动调用
fmt.Println(stu)
//如果没有实现会直接输出结果
}
//输出结果
➜ 07 go run main.go
Nmae=[tomcat] Age=[18] Like=[eat 老冰棍]
{tomcat 18 eat 老冰棍}
10.2.6 方法和函数的区别
- 调用方式不一样 函数的调用方式: 函数名(实参列表) 方法的调用方式: 变量.方法名(实参列表)
- 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
- 对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样可以
10.2.7 方法练习题
第一题、
编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个10*7
的矩形,在main方法中调用该方法
package main
import (
"fmt"
)
//定义字段,也可以没有字段
type MethodUtils struct {
}
//编写方法
func (Me MethodUtils) pri() {
for i := 1; i <= 10; i++ { //打印10行,相当于最外层的for循环
for j := 1; j <= 8; j++ {
fmt.Print("*")
}
fmt.Println()
}
}
func main() {
//在main函数中调用
var m MethodUtils
m.pri() //调用
}
//输出结果
➜ 08 go run main.go
********
********
********
********
********
********
********
********
********
********
第二题、
编写一个方法,提供m和n俩个参数,方法中打印一个m*n
的矩形
package main
import (
"fmt"
)
type Mn struct {
}
func (mn Mn) printmn(m int, n int) {
for i := 1; i <= m; i++ { //打印10行,相当于最外层的for循环
for j := 1; j <= n; j++ {
fmt.Print("*")
}
fmt.Println()
}
}
func main() {
var M Mn
//在方法中传参
M.printmn(2, 4)
}
//输出结果
➜ 09 go run main.go
****
****
第三题、
编写一个方法算出该矩形的面积(可以接收长len,和宽width),将其作为方法返回值,在main方法中调用,接收返回的面积值并打印
package main
import (
"fmt"
)
type MethodUtils struct {
}
func (m MethodUtils) sum(len float64, width float64) float64 { //sum后面为传入数据,最后一个float64为返回,可以写多个
return len * width
}
func main() {
var m MethodUtils
areaRes := m.sum(2.5, 8.7)
fmt.Println("结果", areaRes)
}
//输出结果
➜ 10 go run main.go
结果 21.75
第四题、
编写方法: 判断一个数是奇数还是偶数
package main
import (
"fmt"
)
type MethodUtils struct {
}
func (M *MethodUtils) Jnum(num int) {
if num%2 == 0 {
fmt.Printf("数字%v 为偶数\n", num)
} else {
fmt.Printf("数字%v 为奇数\n", num)
}
}
func main() {
var M MethodUtils
M.Jnum(103)
}
//输出结果
➜ 11 go run main.go
数字103 为奇数
第五题、
根据行、列、字符打印对应的行数和列数的字符,比如: 行:3,列:2,字符*
,则打印相应的结果
package main
import (
"fmt"
)
type Num struct {
}
func (N *Num) Sum(n int, m int, key string) {
for i := 1; i <= n; i++ {
for j := 1; j <= m; j++ {
fmt.Print(key)
}
fmt.Println()
}
}
func main() {
var num Num
num.Sum(7, 7, "=") //输入需要打印的行和列以及打印出的符号
}
//输出结果
➜ 12 go run main.go
=======
=======
=======
=======
=======
=======
=======
第六题、
定义小小计算器结构体Calcuator
,实现加减乘除四个功能
实现形式1: 分四个方法完成:
实现形式2: 用一个方法完成:
第一种方式: 通过4个方法完成
package main
import (
"fmt"
)
type Calcuator struct {
Num1 float64
Num2 float64
}
func (calcuator *Calcuator) getsum() float64 { //加号
return calcuator.Num1 + calcuator.Num2
}
func (calcuator *Calcuator) getsub() float64 { //减号
return calcuator.Num1 - calcuator.Num2
}
func main() {
var calcuator Calcuator
calcuator.Num1 = 1.1
calcuator.Num2 = 1.2
//Sprintf 保留小数点2位
fmt.Println(calcuator.getsum())
fmt.Println(fmt.Sprintf("%.2f", calcuator.getsub()))
}
//输出结果
➜ 13 go run main.go
2.3
-0.10
还有另外一种方式,将所有的加减乘除放在一个方法中
package main
import (
"fmt"
)
type Calcuator struct {
Num1 float64
Num2 float64
}
func (calcuator *Calcuator) getsum(Operator byte) float64 {
res := 0.0
switch Operator {
case '+':
res = calcuator.Num1 + calcuator.Num2
case '-':
res = calcuator.Num1 - calcuator.Num2
case '*':
res = calcuator.Num1 * calcuator.Num2
case '/':
res = calcuator.Num1 / calcuator.Num2
default:
fmt.Println("请输入[+ - * /]")
}
return res
}
func main() {
var calcuator Calcuator
calcuator.Num1 = 1.2
calcuator.Num2 = 1.1
fmt.Println(calcuator.getsum('+'))
fmt.Println(calcuator.getsum('-'))
fmt.Println(calcuator.getsum('*'))
fmt.Println(calcuator.getsum('/'))
}
//输出结果
➜ 14 go run main.go
2.3
0.09999999999999987
1.32
1.0909090909090908
10.3 面向对象编程应用
- 声明(定义)结构体,确定结构体名
- 编写结构体的字段
- 编写结构体的方法
学生案例 (1) 编写一个Student结构体,包含name、gender、age、id、score字段,分别为string、string、int、int、float64 (2) 结构体中声明一个say方法,返回string类型,方法返回信息中包含所有字段 (3) 在main方法中,创建Student结构体实例(变量),并访问say方法,并将结果调用输出
package main
import (
"fmt"
)
type Student struct {
name string
gender string
age int
id int
score float64
}
func (student *Student) say() string {
info := fmt.Sprintf("名称[%v] 性别[%v] 年龄[%v] ID[%v] 成绩[%v]", student.name, student.gender, student.age, student.id, student.score)
//Sprintf根据format参数生成格式化的字符串并返回该字符串,参数需要和表达式的数据类型匹配
return info
}
func main() {
var student = Student{ //创建一个变量来接受Student结构体值
name: "abc",
gender: "男",
age: 20,
id: 1,
score: 88.2,
}
say := student.say()
fmt.Println(say)
}
//输出结果
➜ 02 go run main.go
名称[abc] 性别[男] 年龄[20] ID[1] 成绩[88.2]
长方形案例
- 创建一个Box结构体,在其中声明三个字段表示一个立方体的长、宽、高,长宽高要从终端获取
- 声明一个方法获取立方体的体积
- 创建一个Box结构体变量,打印给定尺寸的立方体的体积
package main
import (
"fmt"
)
type Box struct {
len float64 //长
wide float64 //宽
high float64 //高
}
func (box *Box) get() float64 {
return box.len * box.wide * box.high
}
func main() {
var box = Box{
len: 1.1,
wide: 2.0,
high: 3.0,
}
res := box.get()
fmt.Printf("体积为:%.2f \n", res)
}
//输出结果
➜ 04 go run main.go
体积为:6.60
景区门票案例
- 景区门票根据游人的年龄收取不同价格的门票,比如大于18,收费20元。其它情况门票免费
- 请编写Visitor结构体,根据年龄段决定能够购买的门票价格并输出
package main
import (
"fmt"
)
type Visitor struct {
Name string
Age int
}
func (mon *Visitor) sum() {
if mon.Age > 18 {
fmt.Printf("\n您的名称: %v\n 您的年龄: %v\n 门票售价: 20", mon.Name, mon.Age)
fmt.Println()
} else {
fmt.Printf("\n 您的名称: %v\n 您的年龄: %v\n 门票价格: 免费", mon.Name, mon.Age)
fmt.Println()
}
}
func main() {
var mon Visitor
for {
fmt.Println("请输入您的姓名")
fmt.Scanln(&mon.Name)
if mon.Name == "n" {
fmt.Println("退出成功!")
fmt.Println()
break
}
fmt.Println("请输入您的年龄")
fmt.Scanln(&mon.Age)
mon.sum()
}
}
//输出结果
➜ 05 go run main.go
请输入您的姓名
999
请输入您的年龄
8
您的名称: 999
您的年龄: 8
门票价格: 免费
请输入您的姓名
n
退出成功!
10.4 创建结构体遍历时指定字段值
Golang在创建结构体变量(实例)时,可以直接指定字段的值
创建结构体变量时指定字段值有以下方式
- 方式一
var stu Student = student{"abc",10}
//另外一种
stu := Student{"abc",10}
//更简介的方式
var stu Student = Student{
Name: "abc",
Age: 10,
}
//或
stu := Student{
Name: "abc",
Age: 20,
}
- 方式二
var stu *Student = &student{"abc",10}
var stu *Student = &student{
Name: "abc",
Age: 20,
}
- 方式三,返回结构体指针类型
package main
import (
"fmt"
)
type Sum struct { //声明结构体
Name string
Age int
}
func main() {
//使用结构体
var Stu = &Sum{"abc", 20}
fmt.Println(Stu)
//第二种方式,类型推导
Stu1 := &Sum{"dddd", 20}
fmt.Println(Stu1)
//第三种方式,也可以在创建变量时将结构体的字段名和字段值写在一起
Stu2 := &Sum{
Name: "ttt",
Age: 20,
}
fmt.Println(Stu2)
//默认输出会带一个&符,可以通过取值符号来进行过滤
fmt.Println(*Stu2)
}
//输出结果
➜ 06 go run main.go
&{abc 20}
&{dddd 20}
&{ttt 20}
{ttt 20}
10.5 工厂模式
在Golang的结构体没有构造函数,通常可以使用工厂模式来解决这个问题。
需求: 为什么需要使用工厂模式
例如我们在module
包中,需要引用Student结构体,并且需要结构体名称首字母小写。那我们直接引用module包就不可以,这时候就需要使用工厂模式来解决问题
//main包
package main
import (
"fmt"
"go_code/factory/model"
)
func main() {
var student = model.NewStudent("小明", 20) //此时我们调用返回的也是指针类型
fmt.Println(student)
//如果我们不想要指针类型的&符,可以通过*来获取
fmt.Println(*student)
}
//model包
package model
//定义一个结构体
type student struct {
Name string
Age int
}
//创建一个方法,我们调用方法来使用结构体
func NewStudent(n string,a int) *student{ //传入2个参数,并且返回一个结构体
return &student{
Name: n,
Age : a,
}
}
//输出结果
➜ main go run main.go
&{小明 20}
{小明 20}
//当我们需要取结构体的某个字段,也可以通过下面的写法
fmt.Println("name=", student.Name)
如果我们结构体字段首字母也是小写,可以通过下面的方式解决
//main包
package main
import (
"fmt"
"go_code/factory/model"
)
func main() {
var student = model.NewStudent("小明", 20) //此时我们调用返回的也是指针类型
fmt.Println(student)
//如果我们不想要指针类型的&符,可以通过*来获取
fmt.Println(*student)
//解决结构体字段小写报错
fmt.Println("age=", student.AgeSum()) //这里还是指针类型,通过指针去调用一个方法,来获取age字段值
}
//model
package model
//定义一个结构体
type student struct {
Name string
age int
}
//创建一个方法,我们调用方法来使用结构体
func NewStudent(n string, a int) *student { //传入2个参数,并且返回一个结构体
return &student{
Name: n,
age: a,
}
}
//添加解决结构体小写问题
func (age *student) AgeSum() int { //返回一个int类型
return age.age
}
//输出结果
➜ main go run main.go
&{小明 20}
{小明 20}
age= 20
10.6 面向对象编程思想-抽象
在结构体时,就是将一类事务所有的属性(字段)和行为(方法)提取出来,行程一个物理模型(结构体)。这种行为叫做抽象
案例演示
package main
import (
"fmt"
)
//定义结构体
type Account struct {
UserName string
UserPass int
UserBalance float64
Pwd int
Money float64
}
//方法
//1.存款
func (account *Account) query() {
if account.UserBalance < 0 {
fmt.Println("余额输入错误,请从新输出")
}
account.UserBalance += account.Money
fmt.Printf("存款成功,存款%v元\n", account.Money)
}
//2.取款
func (account *Account) qukuan() {
if account.UserBalance < 0 || account.Money > account.UserBalance {
fmt.Printf("取款输入错误,已退出\n")
return
}
account.UserBalance -= account.Money
fmt.Printf("取款成功,取款%v元\n", account.Money)
}
//3.查询
func (account *Account) chaxun(user string) {
fmt.Printf("用户名:%v 余额剩余: %v\n", account.UserName, account.UserBalance)
}
func main() {
var account Account
var xx int
//默认密码
account.Pwd = 123456
for {
fmt.Println("请输入用户名")
fmt.Scanln(&account.UserName)
fmt.Println("请输入密码")
fmt.Scanln(&account.UserPass)
if account.UserPass != account.Pwd {
fmt.Printf("密码输入错误,请检查密码\n")
return
} else {
fmt.Printf("登陆成功! 用户名:%v \n", account.UserName)
}
for {
fmt.Println("请输入选项 [1]查询 [2]存款 [3]取款 [0]退出")
fmt.Scanln(&xx)
switch xx {
case 1:
account.chaxun(account.UserName)
case 2:
fmt.Printf("请输入存款金额")
fmt.Scanln(&account.Money)
account.query()
case 3:
fmt.Printf("请输入取款金额")
fmt.Scanln(&account.Money)
account.qukuan()
case 0:
fmt.Printf("账号%v 退出成功!\n", account.UserName)
return
default:
fmt.Println("请从新输入,输入[1] [2] [3] [0]")
}
}
}
}
输出结果
10.7 面向对象三大特征-封装
封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作。
封装的理解和好处
- 隐藏实现细节
- 可以对数据进行验证,保证数据合理
如何体现封装
- 对结构体中的属性进行封装
- 通过方法,包 实现封装
封装的实现步骤
- 将结构体、字段(属性)的首字母小写(小写后不支持导出,其它包不可以直接调用)
- 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
- 提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判断并赋值
func (var 结构体类型名) Set xx (参数列表) (返回值列表){
//加入数据验证的业务逻辑
var.字段 = 参数
}
- 提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的值
func (var 结构体类型名) Get xxx(){
return var.age;
}
特别说明: 在Golang开发中并没有特别强调封装,Golang本身对面向对象的特性做了简化
封装案例: 编写一个person.go,不能随便查看的人年龄,工资等隐私,并对输入的年龄进行合理的验证