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 变量使用注意事项

  1. 变量表示内存中的一个存储区域
  2. 该区域有自己的名称 (变量名) 和类型 (数据类型)

示意图:

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 程序中 + 号使用

  1. 当左右两边都是数值型时,择做加法运算
  2. 当左右两边都是字符串,则做字符串拼接
//使用变量

2.1 整数类型

简单的来说,就是用于存放整数值的,比如 0,-1,23456 等等

序号类型和描述
1uint8 无符号 8 位整型 (0 到 255)
2unit16 无符号 16 位整型 (0 到 65534)
3uint32 无符号 32 位整型 (0 到 4294967295)
4uint64 无符号 64 位整型 (0 到 18446744073709551615)
5int8 有符号 8 位整型 (-128 到 127)
6int16 有符号 16 位整型 (-32768 到 32767)
7int32 有符号 32 位整型 (-2147483648 到 2147483647)
8int64 有符号 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

案例演示

}

小数类型分类

序号类型和描述
1float32 IEEE-754 32 位浮点型数
2float64 IEEE-754 64 位浮点型数
3complex64 32 位实数和虚数
4complex128 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 个),比如ifbreakfor

运算符是一种特殊的符号,用以表示数据的运算、复制和比较

运算符有以下分类

  • 算术运算符
  • 赋值运算符
  • 比较运算符 / 关系运算符
  • 逻辑运算符
  • 位运算符
  • 其它运算符

3.1 算术运算符

算术运算符是对数值类型的变量进行计算的,比如: 加减乘除

运算符运算范例结果
+正号+33
-负号-44
+5+510
-10-55
*3*412
/5/51
%取模 (取余)7% 52
++自增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 关系运算符

  1. 关系运算符的结果都是 bool 型,也就是要么是 true,要么是 false
  2. 关系表达式 通常在 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 的比较

  1. 如果判断的具体数值不多,而且符合整数、浮点数、字符、字符串这几种类型。建议使用 switch 语句,简洁高效
  2. 其他情况,对区间判断和结果为 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
说明
  1. 如果 return 是在普通的函数,则表示跳出该函数,即不在执行函数中的 return 后面代码,也可以理解成终止函数
  2. 如果 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,求出它的斐波那契数是多少?

思路分析

  1. 当 n == 1 && n ==2 ,返回 1
  2. 当 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. 第九天桃子 = (第十天桃子数量 + 1) 2
  3. 规律: 第 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)
  • 函数外部定义 / 声明的变量叫做全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效
}
  • 如果变量是在一个代码块,比如iffor中,那么这个变量的作用于就在该代码块中

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 中引入的处理方式为: deferpanicrecover
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.Newpanic内置函数

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 语言一种特有的结构,,可以用来遍历访问数组的元素

基本语法

语法说明:

  1. 第一个返回值 index 是数组的下标
  2. 第二个 value 是该下标位置的值
  3. 他们都是仅在 for 循环内部可见的局部变量
  4. 遍历数组元素的时候,如果不想使用下标 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'

思路分析

  1. 声明一个数组 var mychar [26]byte
  2. 使用 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(管道),还可以是只包含前面几个类型的结构体、接口、数组等。通常为intstring

注意: slice,map 还有 function 不可以,因为这几个没法用 == 来判断

value 可以是什么类型

valuetype 的类型和 key 基本上一样,通常为:数字(整数,浮点数)stringmapstrcuct

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 常见演示案例

  1. 使用 map[string]map[string]string 的 map 类型
    2.key: 表示某用户名, 是唯一的,不可以重复
  2. 如果某个用户粗奴在,就将其密码修改888888,如果不存在就增加这个用户信息 (包括昵称 nickname 和密码 pwd)
  3. 编写一个函数 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

总结:

  1. 第三种和第四种方式返回的是结构体指针
  2. 结构体指针访问字段的标准方式应该是: _(_结构体). 字段名 (*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 方法的调用和传参机制原理

方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,也当做实参传递给方法。

说明

  • 在通过一个变量去调用方法时,其调用机制和函数一样。
  • 不一样的地方是,调用方法该变量本身也会作为一个参数传递到方法中 (如果是值类型那么是值拷贝,如果是引用类型则是地址拷贝)

案例演示

  1. 声明一个结构体 Circle, 字段为 radius
  2. 声明一个方法 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,不能随便查看的人年龄,工资等隐私,并对输入的年龄进行合理的验证

10.8 面向对象三大特征 - 继承

10.9 接口 (INTERFACE)

10.10 面向对象 - 多态

10.11 类型断言

最后修改:2023 年 02 月 13 日
如果觉得我的文章对你有用,请随意赞赏