golang 的基础语法和其他语言有共通之处, 有其他语言的语法基础能让我们更快的熟悉 golang
golang 的基础语法和其他语言有共通之处, 有其他语言的语法基础能让我们更快的熟悉 golang
1.1 变量的介绍
变量概念
变量相当于内存中的一个数据存储空间,可以将变量看做是一个房间的门牌号,通过门牌号我们可以找到房间,同样的道理,通过变量名可以访问到变量 (值)
变量使用步骤
- 声明变量 (也叫定义变量)
- 非变量赋值
- 使用变量
1.2 变量快速入门案例
案例: 声明变量 i,类型为 int 类型,并给变量 i 赋值数据 10,最后打印量变量 i
package main
结果
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)第二种: 根据值自行判定变量类型 (类型推导)
``` import "fmt" ```
第三种: 省略 var,
:=
左侧的变量不应该是已经声明过的,否则编译报错
1) 多变量声明
在编写过程中,我们有时需要一次性声明多个变量,Golang 也提供这样的语法
案例如下:
func main() {
// 一次性声明多个变量并赋值
// 一次性声明多个变量,使用类型推导
// 定义变量/声明变量
2) 一次性声明全局变量 [在 GO 中函数外部定义变量就是去全局变量]
var i int
3) 该区域的数据值可以在同一类型范围内不断变化,但是不可以改变数据类型
//给 i赋值
4) 变量在同一个作用域 (一个函数或者代码块) 内不能重名
i = 10
变量 = 变量名 + 值 + 数据类型
Golang 的变量如果没有赋值,编译器会使用默认值,比如 int 默认值为 0,string 默认值为空船,小数默认值为 0
1.5 程序中 + 号使用
- 当左右两边都是数值型时,择做加法运算
- 当左右两边都是字符串,则做字符串拼接
//使用变量
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 使用
fmt.Println("i=",i)
整数使用的细节
- golang 各整数类型分: 有符号和无符号, int uint 的大小和系统有关
- golang 的整型默认声明为 int 型
golang 程序中整型变量在使用时,遵守保大不保小的原则,即: 在保证程序正确运行下,尽量使用占用空间小的数据类型
`var age byte = 30`
- bit: 计算机中的最小存储单位。1byte=8 bit
2.2 小数类型
小数类型就是用于存放有小数点的数字,比如11.1 , 3.14149
案例演示
}
小数类型分类
序号 | 类型和描述 |
---|---|
1 | float32 IEEE-754 32 位浮点型数 |
2 | float64 IEEE-754 64 位浮点型数 |
3 | complex64 32 位实数和虚数 |
4 | complex128 64 位实数和虚数 |
关于浮点数在机器中存放形式的简单说明,浮点数 = 符号位 + 指数为 + 尾数为
说明: 浮点数都是有符号的
- float64 的精度比 float32 的要准确
- 如果我们要存储一个精度高的数 (比如 3.1415926) 则应该选用 float64
小数类型使用细节
1) golang 浮点类型有固定的范围和长度,不受操作系统的影响
2) golang 的浮点型默认声明为 float64 类型
func main() {
2.3 字符类型
字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。也就是说对于传统的字符串是由字符组成,而 go 的字符串不同,它是由字节组成的。
案例演示
var i int
对于上面代码说明
1)如果我们保存在字符的 ASCII 表的,比如 [0-1,a-z,A-Z..] 可以直接保存在 byte
2) 如果我们保存的字符对应码值大于 255,这时我们考虑使用 int 类型保存
字符类型使用细节
- 字符常量是用单引号 (") 括起来的单个字符。
- go 中允许使用转义字符
\
来将其后的字符转变为特殊字符型常量 - go 语音的字符使用 UTF-8 (英文字符占用一个字节,汉字占用 3 个字节)
- 在 go 中,字符的本质是一个证书,直接输出时,是该字符对应的 UTF-8 编码的值
- 可以直接给某个变量赋一个数字,然后按照格式化输出时 %c,会输出该数字对应的 unicode 字符
- 字符类型是可以进行计算的,相当于一个证书,因为它都有对应的 Unicode 码
fmt.Println("i=",i)
字符类型本质探讨
[x] 字符型存储到计算机中,需要将字符对应的码值 (整数) 找出来
存储: 字符 ---> 对应码值 ---> 二进制 ---> 存储 读取: 二进制 ---> 码值 ---> 字符 ---> 读取
- [x] 字符和码值的对应关系是通过字符编码表决定的
- [x] go 语言的编码都统一成了 utf-8
2.4 布尔类型
基本介绍
1) 布尔类型也叫 bool 类型,bool 类型数据只允许取 true 和 false
2) bool 类型占用 1 个字节
3) bool 类型适用于逻辑运算
,一般用于程序流程控制
演示 golang 中 bool 类型使用
}
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
: 代表需要转换的变量
// 基本数据类型转换案例
func main() {
基本数据类型相互转换的注意事项
1) Go 中,数据类型的转换可以是从 表示范围小 --> 表示范围大,也可以范围大 --> 范围小
2) 被转换的是变量存储的数据 (即值),变量本身没有变化
3) 在转换中,比如讲 int64 转成 in8 [-128---127
],编译时不会报错,只是转换结果是按溢出处理,和我们希望的结果不一样。因此在转换时需要考虑范围
var num = 10.11
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 | %% | 百分号 |
布尔值:
``` fmt.Println("num=",num) ```
整数:
序号 | 参数 | 说明 |
---|---|---|
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("%参数",表达式)
}
fmt.Sprintf 案例演示
func main() {
在将 String 类型转换成基本数据类型时,要确保 string 类型能够转成有效的数据,例如可以将123
转成一个整数,但是不能把hello
转成一个证书,如果强制转换,golang 直接将其结果转成 0
2.9 指针
1) 基本数据类型,变量存的就是值,也叫值类型
2) 获取变量的地址用&
,例如:var num int
获取 num 地址:&num
3) 指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值
name := "abcdocker"
4) 获取指针类型所指向的值,使用: *
fmt.Println("name=", name)
案例演示
1) 写一个程序,获取一个 int 变量 num 的地址,并显示到终端
2) 将 num 的地址赋给指针 ptr,并通过 ptr 去修改 num 的值
}
指针使用细节
1) 值类型,都有对应的指针类型,形式为 * 数据类型 _,比如 int 的对应的指针就是 int,float32 对应的指针类型就是 \_float32,以此类推
2)值类型包括: 基本数据类型 int 系列,float 系列,bool,string、数组和结构体 struct
2.10 值类型和引用类型
- 值类型: 基本数据类型 int 系列,float 系列,bool,string、数组和结构体 struct
- 引用类型: 指针、slice 切片、map、管道 chan、interface 都是引用类型
值类型和引用类型的使用特点
1) 值类型: 变量直接存储值,内存通常在栈中分配
2) 引用类型: 变量存储的是一个地址,这个地址对应的空间才是真正存储数据 (值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就称为一个垃圾,由 GC 来回收
3) 内存占和堆区示意图
2.11 标示符的命名规则
1) 由 26 个英文字母大小写,0-9,_组成
2) 数据不可以开头。var 3num int
❌
3) Golang 中严格区分大小写
4) 标示符不能包含空格
5) 下划线_
本身在 Go 中是一个特殊的标示符,称为空标示符
。可以代表任何其他的标示符,但是它对应的值会被忽略。所以仅能被作为占位符使用,不能作为标示符
6) 不能以系统保留关键字作为标示符 (一共有 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" + "abcdocker" | "hello abcdocker" |
案例演示
// 演示 / 的使用特点
//演示Go如何一次性声明多个变量
// 演示 % 的使用特点
func main() {
// ++ -- 的使用
var n1,n2,n3 int
算术运算符使用注意事项
1) 对于除号/
,它的整数除和小数除是有区别的: 整数之间做除法时,只保留整数部分而舍弃小数部分。例如:x:=19/5
,结果是 3
2) 当对一个数取模时,可以等价a%b=a-a/b*b
3) Golang 中的自增和自建不可以以下使用方式 a = i++
❌ a=i--
❌ if i++ >0
❌
4) Golang 中 i++ 和 i-- 只能写在变量后面,不能写在变量前面 ++i
❌ --i
❌
3.1.1 算术运算符练习题
- 假如还有 97 天放假,问: xxx 个星期零 xx 天
fmt.Println("n1=",n1,"n2=",n2,"n3=",n3)
- 定义一个变量保存华氏温度,华氏温度转换摄氏温度的公式为: 5/9*(华氏摄氏度 - 100),请求出华氏温度对应的摄氏温度
}
3.2 关系运算符
- 关系运算符的结果都是 bool 型,也就是要么是 true,要么是 false
- 关系表达式 通常在 if 结构的条件中或循环结构的条件中
1) 关系运算符的结果都是 bool 型,也就是要么是 true,要么是 false
2) 关系运算符组成的表达式,我们称之为关系表达式 : a >b
3) 比较运算符 "==" 不能误写 "="
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 |
案例演示
// 演示逻辑运算符&&
使用
func main() {
// 演示逻辑运算符||
使用
var n1,name,n3=100,"tom",888
注意事项
(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; 是一个指针变量 |
案例演示
fmt.Println("n1=",n1,",n3)
3.7 运算符优先级
由高至低
3.8 键盘输入语句
在开发过程中,需要接受用户输入语句,可以使用Scanln
进行引用
案例演示
要求: 可以从前台接受用户信息, [姓名,年龄,薪水,是否通过考试]
1) 使用fmt.Scanln()
获取
}
2) 使用fmt.Scanf()
获取
对于整数,有四种表示方式。
1) 二进制: 0,1, 满 2 进 1
在 go 中,不能直接使用二进制来表示一个证书,它沿用了 c 的特点
2) 十进制: 0-9,满 10 进 1
3) 八进制: 0-7,满 8 进 1,以数字 0 开头表示
4) 十六进制: 0-9 及 A-F,满 16 进 1,以 0x 或者 0X 开头表示
此处 A-F 不区分大小写
4.1 进制转换
4.2 其他进制转十进制
4.3 二进制如何转十进制
4.4 位运算
在程序中,程序运行的流程控制决定程序是如何执行的,主要有三大流程控制语句
- [x] 顺序控制
- [x] 分支控制
- [x] 循环控制
5.1 顺序控制
程序从上到下逐行地执行,中间没有任何判断和跳转
结果如下:
Go 中定义变量时采用合法的前向引用
n1= 100 name= tom n3= 888
5.2 分支控制
分支控制就是让程序有选择执行,有三种形式
1) 单分支
2) 双分支
3) 多分支
单分支控制
func main() {
案例: 编写一个程序,可以输入年了,如果年龄大于 18,则输出 "你的年了大于 18"
n1,name,n3:=100,"tomcat~",888
单分支流程图
双分支控制
基本语法
fmt.Println("n1=",n1,",n3)
案例: 编写一个程序,可以输入年了,如果大于 18 岁则输出 "您已经大于 18 岁",否则输出 "未成年"
}
双分支只会执行其中一个分支
多分支控制
基本语法
对于基本语法的说明
(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
其他时没有奖励
请从键盘输入成绩,并加以判断
结果如下:
案例 2: 参加百米运动会,用时 8 秒内进入决赛,否则提示淘汰。根据性别提示男子组或女子组
n1= 100 name= tomcat~ n3= 888
案例,根据淡旺季的月份和年龄打印票价
4—10 旺季
成人 (18-60) :60
儿童 (<18) : 半价
老人 (>60): 1/3
淡季: 成人 40 其他 20
package main
5.3 SWITCH 分支控制
switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上到下逐一测试,直到匹配为止。
匹配项后面也不需要再加 break
- [x] switch 的执行流程是,先执行表达式,得到值,然后和 case 的表达式进行比较,如果相等,就匹配,然后执行对应 case 的语句块,最后退出 switch 控制
- [x] 如果 switch 的表达式的值没有和任何的 case 的表达式匹配成功,则执行 default 的语句块。
- [x] golang 的 case 后的表达式可以有多个,使用逗号间隔
- [x] golang 中的 case 语句块不需要写 break,因为默认会有,即在默认情况下,当程序执行完 case 语句后,就直接退出该 switch 控制结构
switch 案例: 请编写一个程序,该程序可以接受一个字符,比如: a,b,c,d,f a 表示星期一,b 表示星期二 ... 根据用户的呼入显示相依的信息,要求使用 switch 局域完成
import "fmt"
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)
案例: 根据用户指定月份,打印该月份所属的集结。 3,4,5 为春季,6,7,8 位夏季,9,10,11 秋季,12,1,2 位冬季
var n1 =100
switch 和 if 的比较
- 如果判断的具体数值不多,而且符合整数、浮点数、字符、字符串这几种类型。建议使用 switch 语句,简洁高效
- 其他情况,对区间判断和结果为 bool 类型的判断,使用 if,if 的适用范围更广。
5.4 FOR 循环控制
for 循环语法格式
var n2 =200
案例: for 循环打印 10 行
var name = "abcdocker"
语法格式说明
- 循环遍历初始值
- 循环条件
- 循环操作 (语句),有人也叫循环体
- 循环变量迭代
for 循环执行的顺序说明:
- 执行循环变量初始化,
i :=1
- 执行循环条件,
i <=10
- 如果循环条件为真,就执行循环操作 ,
fmt.Println("abcdocker")
- 执行循环变量迭代,
i++
可以解析为 (i = i +1) - 反复执行 2,3,4 步骤,执行循环为 Fakse,就退出循环
for 循环使用的注意事项
1)循环条件是返回一个布尔值的表达式
2)for 循环的第三种使用方式
案例演示
func main() {
for 循环的第三方使用方式
fmt.Println("n1=",n1,"n2=",n2,"name=",name)
上面的写法等价 for ;;{} 是一个无线循环,通常需要配置 break 语句使用
}
案例: 打印机 1 ~ 100 直接所有是 9 的倍数的整数的个数及总和
案例: 完成下面表达式的输出
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 循环来实现其使用效果。
n1= 100 n2= 200 name= abcdocker
案例演示 使用 while 实现输出 10 句 "Hello ,word"
package main
使用 do..while 完成输出 10 句 ok
do..while 说明
import "fmt"
5.6 多重循环控制
1) 将一个循环放在另一个循环体内,就形成了嵌套循环。在外面的 for 称为外层循环在里面的 for 循环称为内层循环。
2) 实际上,嵌套循环就是把内循环当成外循环的循环体。当只有内层循环的循环条件为 false 时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的循环
3) 外层循环次数为 m 次,内层为 n 此,则内循环体实际上需要执行 m*n 次
应用案例一
应用案例二 基于上面的脚本,统计每个班级的合格人数
func main() {
应用案例三 打印金字塔
使用 for 循环打印金字塔,并且可以接受一个整数表示层数,打印出金字塔 (空心金字塔)
//该区域的数据值可以在同一类型范围内不断变化
应用案例四 打印九九乘法表
//正确案例
5.7 跳转控制语句 - BREAK
break 语句用于终止某个语句块的执行,用于中断当前 for 循环或跳出 switch 语句
基本语法:
var i int = 10
break 快速入门案例
随机生成 1-100 的一个数,直到生成 99 这个数,查看一共生成了多少次
i = 30
label 标签使用
1.break 默认会跳出最近的 for 循环
2.break 后面可以指定标签,跳到标签对应的循环
案例
i = 40
练习题: 实现登陆验证,有三次机会,如果用户为 "张无忌", 密码 "888" 提示成功,否则提示剩余多少次机会
fmt.Println("i=",i)
5.8 跳转控制语句 - CONTINUE
continue 语句用于结束本次
循环,继续执行下一次循环。
continue 语句出现在多层嵌套的循环语句体重,可以通过标签指明要跳转的那一层循环
基本语法
案例演示
//错误案例: 不可以改变数据类型
案例演示: continue 实现打印 1-100 之内的奇数 [要求使用 for 循环 + continue]
//1.10属于浮点数,不可以使用int整数类型
从键盘输入个数不确定的证书,并判断读入的正数和负数的个数,输入为 0 时程序结束
i = 1.2 //错误
5.9 跳转控制语句 - GOTO
1)Go 语音的 goto 语句可以无条件地转移到程序中指定的行。
2)goto 语句通常与条件语句配合使用。可用来实现条件转移,跳出循环体等功能。
3)在 go 程序设计中一般不主张使用 goto 语句,以免造成程序流程的混乱
基本语法
案例演示
}
5.10 跳转控制语句 - RETURN
return 使用在方法或函数中,表示跳出所在的函数或方法
package main
说明
- 如果 return 是在普通的函数,则表示跳出该函数,即不在执行函数中的 return 后面代码,也可以理解成终止函数
- 如果 return 是在 main 函数,表示终止 main 函数,也就是说终止程序。
return 语句基本语法
1) 如果返回多个值时,在接受时希望忽略某个返回值,则使用_
符号表示占位忽略
2) 如果返回值只有一个,返回值类型列表
括号可以不写
_
案例演示: 忽略和的结果,包保留差的结果
import "fmt"
案例演示 请编写一个函数,可以计算两个数的和和差,并返回计算结果
案例演示
func main() {
为完成某一功能程序指令 (语句) 的集合,称为函数。
在 Golang 中,函数分为: 自定义函数
、系统函数
6.1 函数的基本概念及使用
基本语法
1)形参列表: 表示函数的输入
2)函数中的语句: 表示为了实现某一功能代码块
3)函数可以有返回值,也可以没有
案例演示
使用函数解决计算问题
var i int = 100
但是如果我们代码中有多个需要计算的,switch 就需要写入多行。这里就需要使用函数来解决
fmt.Println("i=", i)
6.2 包的引用
1) 在实际上开发中,需要在不同的文件调用函数,
2) 项目开发过程需,需要很多人参与,相关函数写在一起容易造成提及庞大,理解困难。所以有了包的概念
6.3 包的原理图
包的本质实际上就是创建不同的文件夹,来存放程序文件。
6.4 包的基本概念
在 go 语言中,go 的每一个文件都是属于一个包的,也就是说 go 是以包的形式来管理文件和项目目录的
6.5 包的三大作用
- 区分相同名字的函数、变量等标示符
- 当程序文件很多时,可以很好的管理项目
- 控制函数、变量等访问范围,即作用域
6.6 包的相关说明
打包基本语法
i := 20 ////变量名不允许重复
引入包的基本语法
var i bool //变量名不允许重复
6.7 包使用的快速入门
首先我们在 vscode 中创建一个单独的项目
我们将 func Cal 定义到utils.go
,将utils.go
放到一个包中,当其他文件需要使用 utils.go 方法时,可以 import 该包
utili.go 文件内容如下
}
main.go 文件内容如下
package main
输出结果如下:
6.8 包使用的注意事项
- 在给一个包打包时,该包对应一个文件夹,比如这里的 utils 文件夹对应的包名就是 utils。文件的包名
通常
和文件所在文件夹名一致 (一般为小写)
当一个文件要使用其它函数或变量时,需要先引入对应的包
1)引入方式 1: `import "包名"` 2)引入方式 2: ``` import "fmt" ```
package 指令在文件第一行,然后是 import 指令
在 import 包时,路径从
$GOPATH
的 src 下开始,不需要带 src,编译器会自动从 src 下开始引入为了其它包文件可以访问到本地的函数,则该函数名的首字母需要大写
![](https://image.abcdocker.com/abcdocker/2021/04/12/48116d63a8b15/48116d63a8b15.png)
同样我们如果要定义一个变量名,也是相同的使用方式
![](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAACbklEQVRoQ+2aMU4dMRCGZw6RC1CSSyQdLZJtKQ2REgoiRIpQkCYClCYpkgIESQFIpIlkW+IIcIC0gUNwiEFGz+hlmbG9b1nesvGW++zxfP7H4/H6IYzkwZFwQAUZmpJVkSeniFJKA8ASIi7MyfkrRPxjrT1JjZ8MLaXUDiJuzwngn2GJaNd7vyP5IoIYY94Q0fEQIKIPRGS8947zSQTRWh8CwLuBgZx479+2BTkHgBdDAgGAC+fcywoyIFWqInWN9BSONbTmFVp/AeA5o+rjKRJ2XwBYRsRXM4ZXgAg2LAPzOCDTJYQx5pSIVlrC3EI45y611osMTHuQUPUiYpiVooerg7TWRwDAlhSM0TuI+BsD0x4kGCuFSRVzSqkfiLiWmY17EALMbCAlMCmI6IwxZo+INgQYEYKBuW5da00PKikjhNNiiPGm01rrbwDwofGehQjjNcv1SZgddALhlJEgwgJFxDNr7acmjFLqCyJuTd6LEGFttpmkYC91Hrk3s1GZFERMmUT01Xv/sQljjPlMRMsxO6WULwnb2D8FEs4j680wScjO5f3vzrlNJszESWq2LYXJgTzjZm56MCHf3zVBxH1r7ftU1splxxKYHEgoUUpTo+grEf303rPH5hxENJqDKQEJtko2q9zGeeycWy3JhpKhWT8+NM/sufIhBwKI+Mta+7pkfxKMtd8Qtdbcx4dUQZcFCQ2I6DcAnLUpf6YMPxhIDDOuxC4C6djoQUE6+tKpewWZ1wlRkq0qUhXptKTlzv93aI3jWmE0Fz2TeujpX73F9TaKy9CeMk8vZusfBnqZ1g5GqyIdJq+XrqNR5AahKr9CCcxGSwAAAABJRU5ErkJggg==) ![](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAACbklEQVRoQ+2aMU4dMRCGZw6RC1CSSyQdLZJtKQ2REgoiRIpQkCYClCYpkgIESQFIpIlkW+IIcIC0gUNwiEFGz+hlmbG9b1nesvGW++zxfP7H4/H6IYzkwZFwQAUZmpJVkSeniFJKA8ASIi7MyfkrRPxjrT1JjZ8MLaXUDiJuzwngn2GJaNd7vyP5IoIYY94Q0fEQIKIPRGS8947zSQTRWh8CwLuBgZx479+2BTkHgBdDAgGAC+fcywoyIFWqInWN9BSONbTmFVp/AeA5o+rjKRJ2XwBYRsRXM4ZXgAg2LAPzOCDTJYQx5pSIVlrC3EI45y611osMTHuQUPUiYpiVooerg7TWRwDAlhSM0TuI+BsD0x4kGCuFSRVzSqkfiLiWmY17EALMbCAlMCmI6IwxZo+INgQYEYKBuW5da00PKikjhNNiiPGm01rrbwDwofGehQjjNcv1SZgddALhlJEgwgJFxDNr7acmjFLqCyJuTd6LEGFttpmkYC91Hrk3s1GZFERMmUT01Xv/sQljjPlMRMsxO6WULwnb2D8FEs4j680wScjO5f3vzrlNJszESWq2LYXJgTzjZm56MCHf3zVBxH1r7ftU1splxxKYHEgoUUpTo+grEf303rPH5hxENJqDKQEJtko2q9zGeeycWy3JhpKhWT8+NM/sufIhBwKI+Mta+7pkfxKMtd8Qtdbcx4dUQZcFCQ2I6DcAnLUpf6YMPxhIDDOuxC4C6djoQUE6+tKpewWZ1wlRkq0qUhXptKTlzv93aI3jWmE0Fz2TeujpX73F9TaKy9CeMk8vZusfBnqZ1g5GqyIdJq+XrqNR5AahKr9CCcxGSwAAAABJRU5ErkJggg==)
在访问其它包函数或变量时,其语法是
包名.函数名
![](https://image.abcdocker.com/abcdocker/2021/04/12/d40ead315ac55/d40ead315ac55.png)
如果包名较长,GO 支持给包取别名,注意: 取别名后,原来的包名就不能使用了
演示 ![](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAACbklEQVRoQ+2aMU4dMRCGZw6RC1CSSyQdLZJtKQ2REgoiRIpQkCYClCYpkgIESQFIpIlkW+IIcIC0gUNwiEFGz+hlmbG9b1nesvGW++zxfP7H4/H6IYzkwZFwQAUZmpJVkSeniFJKA8ASIi7MyfkrRPxjrT1JjZ8MLaXUDiJuzwngn2GJaNd7vyP5IoIYY94Q0fEQIKIPRGS8947zSQTRWh8CwLuBgZx479+2BTkHgBdDAgGAC+fcywoyIFWqInWN9BSONbTmFVp/AeA5o+rjKRJ2XwBYRsRXM4ZXgAg2LAPzOCDTJYQx5pSIVlrC3EI45y611osMTHuQUPUiYpiVooerg7TWRwDAlhSM0TuI+BsD0x4kGCuFSRVzSqkfiLiWmY17EALMbCAlMCmI6IwxZo+INgQYEYKBuW5da00PKikjhNNiiPGm01rrbwDwofGehQjjNcv1SZgddALhlJEgwgJFxDNr7acmjFLqCyJuTd6LEGFttpmkYC91Hrk3s1GZFERMmUT01Xv/sQljjPlMRMsxO6WULwnb2D8FEs4j680wScjO5f3vzrlNJszESWq2LYXJgTzjZm56MCHf3zVBxH1r7ftU1splxxKYHEgoUUpTo+grEf303rPH5hxENJqDKQEJtko2q9zGeeycWy3JhpKhWT8+NM/sufIhBwKI+Mta+7pkfxKMtd8Qtdbcx4dUQZcFCQ2I6DcAnLUpf6YMPxhIDDOuxC4C6djoQUE6+tKpewWZ1wlRkq0qUhXptKTlzv93aI3jWmE0Fz2TeujpX73F9TaKy9CeMk8vZusfBnqZ1g5GqyIdJq+XrqNR5AahKr9CCcxGSwAAAABJRU5ErkJggg==)
在同一个包下,不能有相同的函数名和全局变量名,否则报重复定义
如果我们要编译一个可执行程序文件, 就需要将包声明为 main,即 package main。
演示一个案例,模拟多个目录,多个包如何编译打包
编译时只需要编译 main 包即可
func main() {
打包 exe 文件
var i = 1
下面是在 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 函数的递归调用
一个函数在函数体内又调用了自己,我们称为递归调用
var a = 2
分析图
案例演示 2
var r = i + a //做加法运算
跟上面的步骤参数解释一样,当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,求出它的斐波那契数是多少?
思路分析
- 当 n == 1 && n ==2 ,返回 1
- 当 n >=2,返回前面两个数的和
f(n-1)+f(n-2)
fmt.Println("r=",r)
递归函数练习题 2
求函数的值
已知f(1)=3;f(n)= 2*f(n-1)+1;
请使用递归的编程思想,求出f(n)
的值
递归函数练习题 3
有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个。以后每天猴子都吃其中的一半,然后再多吃一个。当到第十天时,想再吃时 (还没吃),发现只有一个桃子,请问最初一共有多少个桃子
分析:
- 第十天只有一个桃子了
- 第九天桃子 = (第十天桃子数量 + 1) 2
- 规律: 第 n 天的桃子数量
peach(n) =(peach(n+1)+1) x 2
//string类型
6.11 函数注意事项
1) 函数的形参列表可以是多个,返回值列表也可以是多个
2) 形参列表和返回值列表的数据类型可以是值和引用类型
3) 函数的命名遵循标示符命名规范,首字母不能是数字,首字母大写该函数可以被本包或者其他包文件使用,首字母小写,只能被本包文件使用,其他包文件不能使用
4) 函数中的变量是局部的,函数外不生效
5) 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响原来的值。例如 main 函数中的 n1 和test
函数中的 n1 修改test
函数中的 n1 不会影响main
函数中的 n1
6) 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&
,函数内指针的方式操作变量。从效果上看类似引用
var str1 = "hello "
取值会直接修改 num 的变量,所以*n1 = *n1+10
可以理解为num == 20 +10
(*n1
是取值,而不是复制的关系)
7) GO 函数不支持传统的函数重载
8) 在 GO 中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用
var str2 = "abcdocker"
分析: 在函数中可以将函数给交一个变量,并且可以通过变量进行完成调用
9) 函数既然是一种数据类型,因此在 Go 中,函数可以作为形参,并且调用
var res = str1 + str2 //做拼接操作
10) 为了简化数据类型定义,go 支持自定义数据类型
基本语法: type 自定义数据类型名称 数据类型
// 相当于一个别名
例如type myint int
// 这时 myint 就等价 int 类型
例如type mysum func(int,int)int
这时 mysum 就等价一个函数类型func (int,int)int
举例子说明自定义数据类型的使用
fmt.Println("res=", res)
举例子说明自定义数据类型在函数内的使用
给函数取一个别名,将来我们函数作为形参就可以简化流程
}
11) go 语言中支持对返回值命名
package main
12) 使用_
占位符,来忽略返回值
13) go 支持可变参数
// 支持 0 到多个参数
// 支持 1 到多个参数
import "fmt"
说明: args 是 slice 切片,通过 args[index] 可以访问到各个值
// 如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后
案例演示,编写一个函数,可以求出 1 到多个 int 的和
6.12 函数练习
练习题 1,判断代码有无错误,输出什么?
func main() {
练习题 2,判断代码有无错误,输出什么?
练习题 3,请编写一个函数 swap(n1 int, n2 int) 可以交换 n1 和 n2 的值
var a int = 9000
6.13 函数参数的传递方式
值类型参数默认就是值传递,引用类型参数默认就是引用传递
两种传递方式
- 值传递
- 引用传递
其实,不管是值传递还是引用类型传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝。
地址拷贝效率高,因为数据量小,而值拷贝决定的拷贝的数据大小,数据越大,效率越低
值传递相关的数据类型
基本数据类型(int、float、bool、string)、数组、结构体
引用传递相关的数据类型
指针、slice 切片、map、管道 chan、interface
传递方式
值类型默认是值传递: 变量直接存储值,内存通常在栈中分配
引用类型默认是引用地址: 变量存储的是一个地址,这个地址对应的空间才正常存储数据 (值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就称为一个垃圾,由 GC 来回收
如果我们希望函数内的变量能修改函数外的变量,可以传入变量的地址 &,函数内以指针的方式操作变量。
fmt.Println("a=", a)
6.14 INIT 函数
基本介绍
每一个源文件都可以包含一个 int 函数,该函数会在 main 函数执行前,被 GO 运行框架调用,也就是说 int 会在 main 函数前被掉用。int 函数一般用于处理初始化前的工作
案例说明
var b uint = 1
如果一个文件同时包含了全局变量定义,int 函数和 main 函数,则执行的流程为: 全局变量定义
-->init函数
--> main函数
var c byte = 255
包引用 init 案例演示
init 初始化工作案例演示
main 包进行引用
fmt.Println("b=", b, "c=", c)
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 支持匿名函数,如果我们某个函数只是希望执行一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用
- 匿名函数使用方式一
在定义匿名函数时直接调用 (这种方式匿名函数只可以调用一次,因为没有将匿名函数给名字或者是交给一个变量。)
}
- 匿名函数使用方式二
将匿名函数给一个变量 (函数变量),再通过该变量来调用匿名函数
下面的匿名函数可以写在 main 函数中
func main() {
- 全局匿名函数
如果将匿名函数赋给一个全局变量,那么这个匿名函数就称为一个全局匿名函数
var price float32 = 11.19
6.16 闭包
闭包就是一个函数
和其相关的引用环境
(其他函数) 组合的一个整体
什么是闭包函数
"闭" 函数指的该函数是内嵌函数
"包" 函数指的该函数包含对外层函数作用域名字的引用 (不是对全局作用域)
fmt.Println("price=", price)
对上面的代码进行说明
(1) AddUpper 是一个函数,返回的数据类型是func(int) int
(2) 闭包: 返回的是一个匿名函数,但是这个匿名函数引用到函数外的n
,因此这个匿名函数就和n
形成一个整体。构成了闭包
(3) 可以理解为: 闭包是累,函数是操作,n 是字段。函数和它使用到的 n 构成闭包
(4) 当我们反复的调用 f 函数时,因为 n 是只初始化一次,因此每调用一次就进行累计
(5) 函数和它引用到的变量共同构成闭包
闭包实际上是返回的匿名函数,和用到的函数外的变量。它们共同构成一个闭包,而调用的时候它用到的变量不是每一次都会被初始化;每用到一次就会进行叠加一次
6.16.1 闭包实践
请编写一个程序,具体要求如下
- [x] 编写一个函数 makeSuffix(suffix string) 可以接收一个文件后缀名 (比如. jpg),并返回一个闭包
- [x] 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀 (比如. jpg) 则返回 文件名. jog,如果已经有. jpg 后缀,则返回原文件名
- [x] 要求使用闭包的方式完成
- [x] strings.HasSuffix,该函数可以判断某个字符串是否有指定的后缀
}
总结和说明
1) 返回的匿名函数和makeSuffix (suffix string)
的 suffix 变量组合成一个闭包,因为返回的函数引用到 suffix 这个变量
2) 如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入后缀名
。比如.jpg 而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用
3) 闭包的好处是传入一次后缀即可,因为函数调用一次后,函数是不会保留的,执行完就会销毁。闭包函数是会保留,每次会把 suffix 值保留下来
6.17 DEFER
DEFER 主要用于在函数执行完毕后,及时释放资源,Go 的设计者提供defer
延时机制
例如: 需要创建资源 (比如数据库连接、文件句柄、锁等)
func main() {
在defer
将语句放入到栈时,也会将相关的值拷贝,并且同时入栈。
var num1 = 1.1
defer 最主要的价值是在当函数执行完毕后,可以及时的释放函数创建的资源
实际使用defer案例
6.18 变量作用域
- 函数内部定义 / 声明的变量叫局部变量,作用域仅限于函数内部
fmt.Printf("num1的数据类型是 %T \n",num1)
- 函数外部定义 / 声明的变量叫做全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效
}
- 如果变量是在一个代码块,比如
if
、for
中,那么这个变量的作用于就在该代码块中
6.19 函数综合练习
使用函数打印金字塔
//输出结果
使用函数打印九九乘法表
num1的数据类型是 float64
使用函数对给定的一个二维数组 (3x3) 转置
func main() {
6.20 字符串常用系统函数
6.20.1 len
统计字符串、数组长度,按字节返回len(str)
。len 属于内建函数,不需要额外引用。直接使用即可
var n1 byte = 'a'
案例演示
var n2 byte = '0' //字符0
6.20.2 rune
字符串遍历,处理中文问题 r:= [rune(str)]
解决字符串遍历,中文乱码问题
//当我们直接输出byte值,就是输出了对应的字符串的码值
6.20.3 []byte
将字符串转换为 byte 切片
字符串转 []byte: var bytes = []byte("hello go")
byte 输出内容为 ascii 码
// 'a' ==>
103 111 对应的就是 go
[]byte 转字符串 str := string([]byte{104,101,108,108,111,32,103,111})
有些场景需要将 byte 类型的切片转换为字符串,按照字符串输出
fmt.Println("n1=", n1)
6.20.4 strconv.Atoi
fmt.Println("n2=", n2)
字符串转整数 n,err :=strconv.Atoi("12")
如果转不成功会产生一个 err,可以对 err 进行判断
6.20.5 strconv.Itoa
整数不存在无法转成字符串的情况,所以没有 error 的选项
整数转字符串 str = strcona.Itoa(123456)
//如果我们希望输出对应字符,需要使用格式化输出
6.20.6 strconv.FomatInt
fmt.Printf("n1=%c n2=%c \n",n1,n2)
10 进制转 2,8,16 进制: str := strconv.FormatInt(132,3)
,返回对应的字符串
}
6.20.7 strubgs.Contains
查找子串中,是否存在指定的字符: strubgs.Contains("seafood","foo")
如果有返回真,否则返回假
案例演示
//输出结果
6.20.8 strings.Count
统计一个字符串有几个指定的子串: strings.Count("ceheese","e")
n1= 97
案例演示
n2= 48
6.20.9 string.gs.EqualFold
不区分大小写的字符串比较 (== 是区分大小写)
n1=a n2=0
6.20.10 strings.Index
返回子串在字符串第一次
出现的 index 值,如果没有就返回-1
(负一)。index 值从 0 开始计算
func main() {
6.20.11 strings.LastIndex
返回子串在字符串最后一次出现的 Index,如果没有对应的数值,也返回-1
//字符类型是可以进行运算的,相当于一个证书,运算时是按照码值进行运算
6.20.12 strings.Replace
将制定的子串替换成另外一个子串
var h1 = 10 + 'a' //a的码值为97,相当于10+97
6.20.13 strings.Split
按照指定的某个字符,为分隔标示,将一个字符串拆分成字符串数组
fmt.Println("h1=",h1)
6.20.14 strings.ToLower
将字符串的字母进行大小写的转换
ToLower 小写
ToUpper 大写
}
6.20.15 strings.Space
将字符串左右两边的空格去掉
func main() {
6.20.16 strings.Trim
将字符串左右两边指定的字符去掉
6.20.17 strings.TrimLeft
将字符串左边指定的字符去掉
var b = false
6.20.18 strings.TrimRight
将字符右边指定的字符去掉
案例:↑
6.20.19 strings.HasPrefix
判断字符串是否以指定的字符串开头
fmt.Println("b=", b)
6.20.20 strings.HasSuffix
判断字符串是否以指定的字符串结束
案例:↑
6.21 时间和日期相关函数
时间与日期的函数尝尝用于订单下单时间,代码执行花费时间等
时间和日期相关函数需要导入time
包
6.21.1 time.Time
time.Time 类型,用于时间表示
获取当前时间
}
上面获取的时间不适合我们阅读,可以通过下面的方法进行处理
上面的方式虽然打印出时分秒、年月日,但是看的不太友好,我们需要进行使用格式化输出,输出成对应的格式
格式化输出第一种方式
//输出结果为false
格式化输出第二种方式
使用 time.Format() 方法完成
注意: 下面的数字不允许改变,格式可以修改
package main
6.21.2 时间常量
在程序中可用于获取指定单位的时间,比如 100 毫秒
结合 Sleep 来使用时间常量
例如: 每隔 1 秒打印一个数字,打印到 5 就退出
import "fmt"
每隔 0.1 秒打印一个数字,打印到 5 退出
获取当前 unix 时间戳和 unixnano 时间戳
可以获取随机的数字
- unix 时间戳 (获取秒数)
- unixnano 时间戳 (获取纳秒数)
func main() {
unix 和 unixnano 使用
6.21.3 统计函数执行时间
var i int32 = 100
6.22 内置函数
golang 设计者为了编程方便,提供了一些函数,这些函数可以直接使用
6.22.1 len
用来求长度,比如 string、array、alice、map、channel
//将i转变成float
6.22.2 new
用来分配内存,主要用来分配值类型,比如 int、float32、structu 返回的是指针。
var n1 float32 = float32(i)
内存分析图
new(int)
6.22.3 make
用来分配内存,主要用来分配引用类型,比如 chan、map、slice
6.23 错误处理
1) go 语言追求简洁优雅,所以 go 语言不支持传统的 try...catch...finally 这样处理
2) go 中引入的处理方式为: defer
、panic
、recover
3) go 中可以抛出一个 panic 的异常,然后在 defer 中通过reconver
捕获这个异常,进行正常处理
6.23.1 使用 defer+reconver 来处理 error 错误
案例: 下面的代码出现异常,10 无法除 0 所以提示下面的报错。 并且 main 函数打印操作没有输出
var n2 int8 = int8(i)
也可以使用下面的写法
var n3 int64 = int64(i)
6.23.2 自定义错误
go 程序中,也支持自定义错误,使用errors.New
和panic
内置函数
1) errors.New("错误说明"),会返回一个 error 类型的值,表示一个错误
2) panic 内置函数接收一个interface {}
类型的值作为参数,可以接受 error 类型的变量,输出错误信息,并退出程序
errors 包实现了创建错误值的函数。
当我们将文件名config.ini
进行修改,if 判断不是 config.ini 会输出下面的错误。并且会终端下面的执行
这里返回的类型都是 error 类型的!
数组可以存放多个同一类型数据。数组也是一个数据类型,在 go 中,数组是值类型
7.1 数组入门
案例: 一个养鸡场有 8 只鸡,请问这 8 只鸡的总体重是多少? 平均体重为多少?
fmt.Printf("i=%v n1=%v n2=%v n3=%v \n", i, n1, n2, n3)
7.2 数组定义和内存布局
数组的定义
var 数组名 [数组大小] 数据类型
}
赋初值
- 数组的地址可以通过数组名来获取
&intArr
- 数组的第一个元素地址,就是数组的首地址
- 数组的各个元素的地址间隔是依据数组的类型决定,例如 int64 为 8 个字节,int32 位 4 个字节,以此类推
7.3 数组使用
访问数组的元素
数组名 [下标] 比如: 使用 a 数组的第三个元素 a[2]
从终端循环输入 5 个成绩,保存到 float64 数组,并输出
//输出结果:
数组初始化方式
i=100 n1=100 n2=100 n3=100
7.4 数组的遍历
- 方式一: 传统 for 循环遍历
func main() {
- 方式二: for-range 遍历
for-range 遍历是 go 语言一种特有的结构,,可以用来遍历访问数组的元素
基本语法
语法说明:
- 第一个返回值 index 是数组的下标
- 第二个 value 是该下标位置的值
- 他们都是仅在 for 循环内部可见的局部变量
- 遍历数组元素的时候,如果不想使用下标 index,可以直接把下标 index 标为下划线
_
5.index 和 value 的名称不是固定的,即可以自定指定,一般命为 index 和 value
for-range 案例
for-range 遍历数组
var num1 int64 = 99999
i,v 的作用于只在 for 循环内有效,属于局部变量
7.5 数组细节
1) 数组是多个相同类型数据的组合,一个数组一旦声明 / 定义了,其固定长度不能动态变化
var num2 int8 = int8(num1)
2) 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用
3) 数组创建后,如果没有赋值,默认值如下
fmt.Println("num2=", num2)
4) 数组的使用步骤
- 声明数组并开辟空间
- 数组各个元素赋值(不赋值默认零值)
- 使用数组
5) 数组的下标是从 0 开始
6) 数组下标必须在指定范围内使用,否则报panic
错误: 数组越界
}
7) Go 的数组属于值类型
,在默认情况下是值传递。因此会进行值拷贝。数组间不会互相影响
在 go 中,数组的类型是数组长度的一部分 (可以理解 [4]int 和 [5]int 不是同一个类型)
8) 如果想在其它函数中修改原来的数组,可以使用引用传递(指针方式)
;效率高,速度快
代码如下:
9) 在 go 中长度是数组类型的一部分,在传递函数参数时需要考虑数组长度
❎ 案例一
//输出结果
❎ 案例二
num2= -97 //不符合预期
✅ 案例三
%t 单词true或false
7.6 数组案例
(1) 创建一个 byte 类型的 26 个元素的数组,分别放置A
-Z
。使用 for 循环访问所有元素并打印出来。提示: 字符数据运算'A'+1 ->'B'
思路分析
- 声明一个数组 var mychar [26]byte
- 使用 for 循环,利用字符可以进行运算的特点来赋值
'A' + 1
等于B
3.for 循环打印数组输出内容
func Sprintf(format string, a ... interface{}) string
(2) 请求出一个数组的最大值,并获得对应的下标
(3) 请求出一个数组的和和平均值。for-range
Sprintf根据format参数生成格式化的字符串并返回该字符串,参数需要和表达式的数据类型匹配
7.6.1 数组反转
随机生成 5 个数,并将其反转打印
package main
7.7 切片基础介绍
什么情况下使用切片?
当我们统计学生成绩的时候,但是学生的个数不确定,这时候就需要使用切片
切片基本介绍
1) 切片的英文是 slice
2) 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用类型的传递地址
3) 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度len(slice)
都一样
4) 切片的长度是可以变换的,因此切片是一个可以动态变化的数组。
5) 切片定义的基本语法
切片快速入门
import "fmt"
7.8 切片在内存中形式
切片在内存中如何布局
7.9 切片的使用
切片使用包含三种方式
● 第一种
第一种方式: 定义一个切片,然后让切片去引用一个已经创建好的数组
func main() {
● 第二种
第二种方式: 通过 make 来创建切片
基本语法如下
//定义数据类型
func make
为内置函数
var num1 int64 = 99999
当我们通过make
创建的数组,只可以使用 slice 下标访问,无法通过函数进行访问。对外不可见
针对于第一种和第二种使用方式的说明
- 通过 make 方式创建切片可以指定切片的大小和容量
- 如果没有给切片的各个元素赋值,那么就会使用默认值 [int,float=0 string = "" bool = false]
- 通过 make 方式创建的切片对应的数组是由 make 底层维护,对外不可见,即只能通过 slice 去访问各个元素。
● 第三种
定义一个切片,直接就指定具体数组,使用原理类似 make 的方式
var num2 int8 = int8(num1)
小结
1) 第一种使用方式是直接饮用数组,这个数组是事先存在的
2) 第二种使用方式是通过make
来创建切片,make 也会创建一个数组,是由切片在底层进行维护
7.10 切片的遍历
切片的遍历和数组一样,也有两种方式
- for 循环常规遍历
- for-range 结构遍历切片
- for 循环常规方式遍历演示
var b bool = true
- for-range 结构遍历切片演示
var mychar byte = 'a'
7.11 append 动态添加
append 内置函数,可以对切片进行动态添加
func append(slice []type, elems ...Type) []Type
内建函数 append 将元素追加到切片的末尾。若它有足够的容量,其目标就会重新切片以容纳新的元素。否则,就会分配一个新的基本数组。append 返回更新后的切片,因此必须存储追加后的结果
案例演示:
fmt.Println("num2=", num2)
切片 append 操作的底层原理分析:
1) 切片 append 操作的本质就是对数组库容
2) go 底层会创建一个新的数组 newArr (安装扩容后大小)
3) 将 slice 原来包含的元素拷贝到新的数组 newArr
4) slice 重新引用到 newArr
5) 注意 newArr 是在底层来维护的
7.12 切片拷贝操作
切片使用 copy 内置函数完成拷贝,案例如下
slice1 和 slice2 数据空间是独立的,相当于值拷贝
提示: copy(para1,para2): para1 和 para2 都是切片类型
当 slice 拷贝到数值 a 中,并且 a 的数只有一个,copy 拷贝并不会扩容,只会拷贝第一个元素
//定义空值
7.13 切片属于引用类型
切片属于引用类型,所以在传递时,遵守引用传递机制。
案例 1
var str string
案例 2
str = fmt.Sprintf("%d", num1)
7.14 STRING 和 SLICE
- string 底层是一个 byte 数组,因此 string 也可以进行切片处理
fmt.Printf("str type %T str=%q \n", str, str)
- string 和切片内存示意图
- string 是不可变的,也就是不能通过
str[0] = 'z'
方式来修改字符串
案例如下:
- 如果需要修改字符串,可以先将 string --> []byte(切片) / 或者 [] runne (切片) -> 修改 -> 重写转成 string
byte 类型是无法处理中文,英文是占用 1 个字节,中文是占用 3 个字节。 所以如果使用 byte 处理中文会出现乱码的情况
解决方法: 将 string 转成 []rune 即可,因为 []rune 是按字符处理,兼容汉字
7.15 切片 Demo
编写一个函数fbn(n int)
1) 可以接收一个 n int
2) 能够将斐波那契的的数列放到切片中
3) 提示, 斐波那契的数列形式 arr[0]=1;arr[1]=1;arr[2]=2;arr[3]=3;arr[4]=5;arr[5]=8
斐波那契的规律为前面两个数相加的和等于下一位数
代码如下
str = fmt.Sprintf("%f", num2)
8.1 排序基本介绍
排序是将一组数据,以指定的顺序进行排列的过程。
排序的分类:
内部排序: 指将需要处理的所有数据都加载到内存中进行排序。包括 (
交换式排序法
、选择式排序法
和插入式排序法
),适用于量小的场景交换式排序: 交换式排序属于内部排序法,是运用数据值比较后,依判断规则对数据位置进行交换,以达到排序的目的。交换式排序法又可以分为两种:**(1) 冒泡排序法 (Bubble sort)** (2) 快速冒泡排序法 (Quick sort)
外部排序: 数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括 (
合并排序法
和直接合并排序法
)
8.2 冒泡排序
交换式排序法 - 冒泡排序 (Bubble Sorting) 的基本思想是: 通过对待排序序列从后向前(从下标最大的元素开始),依次比较相邻元素的排序码,若发现逆序则交换,使排序码较小的元素逐渐从后部移向前部(从下标最大的单元移向下标较小的单元)
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行交换,就说明序列有序,因此要在排序过程中设置一个标志 flag 判断元素是否进行过交换。从而减少不必要的比较。
冒泡排序规则
- 一共会经过 arr.lengh-1 的轮次比较,每一轮将会确定一个数的位置
- 每一轮的比较次数再逐渐减少 [5,4,3,2,1]
- 当发现前面的一个数比后面的一个数大时候就进行交换。
Demo 演示
fmt.Printf("str type %T str=%q \n", str, str)
接下来我们可以设置第二轮排序,实际上只需要复制for
循环中的变量即可
最后拷贝 4 个 for 循环
str = fmt.Sprintf("%t", b)
✅ 正确冒泡排序如下
fmt.Printf("srr type %T str=%q \n", str, str)
冒泡面试可能需要手写,所以需要背下来。
8.3 查找
在 Golang 只能给,常用的查找有两种:
- 顺序查找
- 二分查找 (该数组是有序)
8.3.1 顺序查找
顺序查找可以通过下面的案例进行演示
数列: 白眉鹰王、金毛狮王、紫衫龙王、青翼蝠王,从键盘中任意一个名称,判断数列中是否包含此名称
第一种方式
第二种方式 推荐使用
str = fmt.Sprintf("%c", mychar)
8.3.2 二分查找
二分查找思路分析:
二分查找代码:
fmt.Printf("str type %T str=%q \n", str, str)
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所谓二维数组就是一维数组里面的元素又是数组
二维数组实际上就是一维数组的扩展而已
}
二维数组使用方式
第一种:
- 语法: var 数组名 大小 类型
- 比如:
var aa [2][3]int[][]
,在赋值
第二种:
- 声明; var 数组名 大小 类型 =大小类型{{初值}},{{初值..}}
- 赋值 (有默认值,比如 int 类型就是 0)
赋值
8.7 二维数组在内存中存在形式
8.8 二维数组的遍历
二维数组的遍历有两种,第一种是双for循环
完成,第二种是for-range方式
完成遍历
- 双层 for 循环案例
//输出结果如下
- for-range 方式案例
➜ 01 go run main.go
8.9 二维数组案例
要求: 定义二维数组,用于保存三个班,每个班五名同学成绩,要求出每个班级平均分、以及所有班级平均分
num2= -97
9.1 Map 基本介绍
map 是 key-value 数据结构,又称为字段或者关联数组。类似其它语言的集合
9.2 Map 声明
基本语法
str type string str="99999"
key 可以是什么类型
golang 中的 map 的 key 可以是很多种类型,例如 bool、数字、string、指针、channel(管道),还可以是只包含前面几个类型的结构体、接口、数组等。通常为int
、string
注意: slice,map 还有 function 不可以,因为这几个没法用 == 来判断
value 可以是什么类型
valuetype 的类型和 key 基本上一样,通常为:数字(整数,浮点数)
、string
、map
、strcuct
map 声明的举例
str type string str="%!f(int8=-97)"
注意: 声明是不会分配内存的,初始化需要 make,分配内存后才能赋值和使用
9.3 Map 使用
srr type string str="true"
9.3.1 Map 使用方式
map 使用方式有下面几种
方式一
str type string str="a"
方式二
✅ 推荐使用
var ptr *int = &num
方式三
package main
应用案例: 演示一个 key-value 的 value 是 map 的类型
例如: 存放 3 个学生信息,每个学生信息有 name 和 sex 信息
思路: map[string]map[string]string (第一个 map 为学生学号,后面的 map 为 name 和性别)
9.4 Map 增删改查
- 增、改
map["key"] = value
如果 key 还没有就增加,如果已存在就修改
- 删
map 删除使用delete
内置函数,如果 key 存在,就删除该 key-value,如果 key 不存在,不操作,但是也不会报错
import "fmt"
如果我们要删除 map 的所有 key,没有一个专门的方法一次性删除,可以遍历一下 key,然后进行逐个删除
或者通过 make 一个新的,让原来的称为垃圾,被 gc 回收
- 查
func main() {
9.5 Map 遍历
for range 遍历支持 map, 但是不能保证每次遍历次序是一样的。
通过 len 统计 map 长度
//基本数据类型在内存布局
9.6 Map 切片
切片的数据类似如果是 map,则我们称为 slice of map,map 切片,这样使用则 map 个数就可以动态变化
var i int = 10
需要使用 golang 内置函数 append 函数,进行动态增加,前面数组中已经介绍过了
fmt.Println("i的地址=", &i)
9.7 Map 排序
1.golang 中没有一个专门的方法针对 map 的 key 进行排序
2.golang 中 map 默认是无序的,也不是按照添加顺序存放,所以每次遍历可能不一样
3.golang 中 map 的排序是先将 key 进行排序,然后根据 key 值遍历输出即可
9.8 Map 注意事项
- map 是引用类型,遵守引用类型传递的机制,在一个函数接收 map,修改会,会直接修改原来的 map
//1. ptr是一个指针变量
map 动态扩容达到后,再想增加 map 元素,会自动扩容,map 可以动态的增长键值对
key-value
map 的 value 也经常使用 struct(结构体) 类型,更适合管理复杂的数据
9.9 map 常见演示案例
- 使用 map[string]map[string]string 的 map 类型
2.key: 表示某用户名, 是唯一的,不可以重复 - 如果某个用户粗奴在,就将其密码修改
888888
,如果不存在就增加这个用户信息 (包括昵称 nickname 和密码 pwd) - 编写一个函数 modifyUser(users map[string]map[string]string,name string) 完成上述功能
//2. ptr的类型 *int
- 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 结构体
1) 结构体是自定义的数据类型
2) 结构体变量 (实例) 是具体的,实际的,代表一个具体的变量
//3. ptr 本身的值&i
结构体变量在内存中的布局
当我们声明一个结构体时,它的内存变量已经生成。结构体是一个值类型
10.1.1 声明结构体
声明结构体基本语法
var ptr *int = &i
在结构体字段中,一般是基本数据类型、数组、引用类型
指针、slice 和 map 的默认值都是 nil,即还没有分配空间 (需要 make 后使用)
fmt.Printf("ptr=%v \n",ptr)
不同结构体变量的字段是独立的,互不影响,一个结构体变量字段的更改不会影响另外一个
结构体是值类型, 它们之间的拷贝是通过值拷贝,不会互相影响
fmt.Printf("ptr 的地址=%v \n", &ptr)
b := a
在内存中的变化
10.1.2 创建结构体变量的四种方式
- 方式一,直接声明
fmt.Printf("ptr 指向的值=%v \n", *ptr)
- 方式二,{}
}
- 方式三 (new)
var person *Person = new(Person)
因为 person 是一个指针,因此标准写法为(*person).Name = "abc"
,当然也可以采用简化的写法person.Name = "abc"
原因: go 设计者为了程序员方便使用,底层会对person.Name = "abc"
进行处理,会自动给 person 加上取值运算(*person).Name = "abc"
- 方式四 (-&)
1. 方式三和方式四返回的是结构体
_2. 结构体指针访问字段的标准方式应该是: (_结构体指针). 字段名 例如:(*person).Name = "docker"
3. 但 go 做了一个简化,也支持结构体指针. 字段名 _,比如person.Name = "i4t"
。go 编辑器底层对 person.Name 做了转化 (_person).Name
var person *Person = &Person{}
案例演示
//输出结果
同样,也可以在创建时直接赋值
➜ 01 go run main.go
总结:
- 第三种和第四种方式返回的是
结构体指针
- 结构体指针访问字段的标准方式应该是: _(_结构体). 字段名
(*person).Name = "tomcat"
3.go 做了一个简化,也支持结构体指针. 字段,比如person.Name = "tomcat"
。go 底层编辑器对 person.name 做了转化,所以可以直接使用
10.1.3 struct 类型的内存分配机制
变量总是存在内存中的
结构体变量在内存中存在方式见下图
i的地址= 0xc0000b4008
上述代码在内存中的布局图
10.1.4 结构体所有字段在内存中是连续分布的
连续分布的优点:
在取值的时候通过内存的地址进行加减取值,速度会比较快
10.1.5 结构体是用户单独定义类型,和其他类型进行转换时需要有完全相同的字段
根据标题的提示,也就是它的名称、个数和类型需要完全一致
ptr=0xc0000b4008
10.1.6 结构体进行 type 重新定义 (相当于取别名),golang 认为是新的数据类型,但是互相之间可以强转
ptr 的地址=0xc0000ae020
10.1.7 结构体序列化
struct 的每个字段上,可以写上一个 tag,该 tag 可以通过反射机制
获取,常见的场景就是序列化和反序列化
为什么需要序列化?
案例演示:
ptr 指向的值=10
10.2 方法
Golang 中的方法是作用在指定的数据类型上的。(和指定的数据类型绑定),因此自定义类型,都是可以有方法的,而不仅仅是 struct
10.2.1 方法的调用和声明
自定义方法语法说明
package main
对上面语法的说明
func (a Person){}
表示 Person 结构体有一方法,方法名为 test(a Person)
体现 test 方法是和 Person 绑定的
Demo 案例演示
总结说明
- test 方法和 Person 类型绑定
- test 方法只能通过 Person 类型的变量来调用,而不能直接调用,也不能使用其它类型的变量调用
func (a Person)test(){}
其中 a 表示哪个 Person 变量调用,这个 a 就表示谁的副本;这点和函数传参非常相似a
这个名字,是可以自己指定并且随意
10.2.2 方法的快速入门
1) 给 Person 结构体添加 speak 方法,输出 xxxx 运维开发。
import "fmt"
2) 给 Person 结构体添加 jisuan 方法,可以计算从 1+..+1000 的结果
3) 给 Person 结构体添加 jisuan2 方法,该方法可以接收一个数 n, 计算从 1+..+n 的结果
func main() {
4) 给 Person 结构体添加 getSum 方法,计算两个数的和,并返回结果
10.2.3 方法的调用和传参机制原理
方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,也当做实参传递给方法。
说明
- 在通过一个变量去调用方法时,其调用机制和函数一样。
- 不一样的地方是,调用方法该变量本身也会作为一个参数传递到方法中 (如果是值类型那么是值拷贝,如果是引用类型则是地址拷贝)
案例演示
- 声明一个结构体 Circle, 字段为 radius
- 声明一个方法 area 和 Circlee 绑定,可以返回面积
var num int = 10
10.2.4 方法的声明和定义
fmt.Printf("num address=%v \n", &num)
- 参数列表: 表示方法输入
- recervier type: 表示这个方法和 type 这个类型进行绑定,或者说该方法作用于 type 类型
- receiver type: type 可以是结构体,也可以其它的自定义类型
- receiver: 就是 type 类型的一个变量 (实例),比如: Person 结构体的变量 (实例)
- 返回值列表: 表示返回的值,可以多个
- 方法主体: 表示为了实现某一个功能代码块 (计算、写数据库等)
- return 语句不是必须的
10.2.5 方法注意事项
1. 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
2. 修改结构体变量,可以通过结构体指针的方式来处理
值引用的逻辑图
3.golang 中的方法作用在指定的数据类型上 golang的方法作用在指定的数据类型上
即: 和指定的数据类型绑定
。因此自定义类型,都可以有方法
,而不仅仅是 struct,比如 int、float64 等
4. 方法的访问范围控制规则
和函数一样,方法名首字母小写,只能在本地访问,方法首字母大写。可以在本包和其它包访问 (这里可以参考函数)
5. 如果一个类型实现了 String() 这个方法,那么fmt.Println
默认会调用这个变量的 String() 进行输出
比如我们输出结构体日志,就可以通过这种方法进行输出
//ptr变量
10.2.6 方法和函数的区别
调用方式不一样
函数的调用方式: 函数名 (实参列表) 方法的调用方式: 变量. 方法名 (实参列表)
对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
对于方法 (如 struct 的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样可以
10.2.7 方法练习题
第一题、
编写结构体 (MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个10*7
的矩形,在 main 方法中调用该方法
var ptr *int
第二题、
编写一个方法,提供 m 和 n 俩个参数,方法中打印一个m*n
的矩形
ptr = &num
第三题、
编写一个方法算出该矩形的面积 (可以接收长 len,和宽 width),将其作为方法返回值,在 main 方法中调用,接收返回的面积值并打印
*ptr = 10 //这里修改,会到num值的变化
第四题、
编写方法: 判断一个数是奇数还是偶数
fmt.Println("num =", num)
第五题、
根据行、列、字符打印对应的行数和列数的字符,比如: 行: 3,列: 2,字符*
, 则打印相应的结果
fmt.Println("ptr =", &ptr)
第六题、
定义小小计算器结构体Calcuator
,实现加减乘除四个功能
实现形式 1: 分四个方法完成:
实现形式 2: 用一个方法完成:
第一种方式: 通过 4 个方法完成
还有另外一种方式,将所有的加减乘除放在一个方法中
}
10.3 面向对象编程应用
1) 声明 (定义) 结构体,确定结构体名
2) 编写结构体的字段
3) 编写结构体的方法
学生案例
(1) 编写一个 Student 结构体,包含 name、gender、age、id、score 字段,分别为 string、string、int、int、float64
(2) 结构体中声明一个 say 方法,返回 string 类型,方法返回信息中包含所有字段
(3) 在 main 方法中,创建 Student 结构体实例 (变量),并访问 say 方法,并将结果调用输出
长方形案例
1) 创建一个 Box 结构体,在其中声明三个字段表示一个立方体的长、宽、高,长宽高要从终端获取
2) 声明一个方法获取立方体的体积
3) 创建一个 Box 结构体变量,打印给定尺寸的立方体的体积
//输出结果
景区门票案例
- 景区门票根据游人的年龄收取不同价格的门票,比如大于 18,收费 20 元。其它情况门票免费
- 请编写 Visitor 结构体,根据年龄段决定能够购买的门票价格并输出
➜ 01 go run main.go
10.4 创建结构体遍历时指定字段值
Golang 在创建结构体变量 (实例) 时,可以直接指定字段的值
创建结构体变量时指定字段值有以下方式
- 方式一
num address=0xc0000b4008
- 方式二
num = 10
- 方式三,返回结构体指针类型
ptr = 0xc0000ae020
10.5 工厂模式
在 Golang 的结构体没有构造函数
,通常可以使用工厂模式来解决这个问题。
需求: 为什么需要使用工厂模式
例如我们在module
包中,需要引用 Student 结构体,并且需要结构体名称首字母小写。那我们直接引用 module 包就不可以,这时候就需要使用工厂模式来解决问题
//演示/的使用特点
如果我们结构体字段首字母也是小写,可以通过下面的方式解决
10.6 面向对象编程思想 - 抽象
在结构体时,就是将一类事务所有的属性 (字段) 和行为 (方法) 提取出来,行程一个物理模型(结构体)。这种行为叫做抽象
案例演示
package main
输出结果
10.7 面向对象三大特征 - 封装
封装 (encapsulation) 就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作 (方法),才能对字段进行操作。
封装的理解和好处
- 隐藏实现细节
- 可以对数据进行验证,保证数据合理
如何体现封装
- 对结构体中的属性进行封装
- 通过方法,包 实现封装
封装的实现步骤
1) 将结构体、字段 (属性) 的首字母小写 (小写后不支持导出,其它包不可以直接调用)
2) 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
3) 提供一个首字母大写的 Set 方法 (类似其它语言的 public),用于对属性判断并赋值
import "fmt"
4) 提供一个首字母大写的 Get 方法 (类似其它语言的 public),用于获取属性的值
func main() {
特别说明: 在 Golang 开发中并没有特别强调封装,Golang 本身对面向对象的特性做了简化
封装案例: 编写一个 person.go,不能随便查看的人年龄,工资等隐私,并对输入的年龄进行合理的验证