Go语言实践[回顾]教程07--通过时间判断时辰的示例【中】
继续修改上节的代码
经过上节的几次调整修改,代码越来越逻辑清晰易读,但依旧还有很大的修改空间 (咦!为什么不说优化,因为优化是针对特定情况进行的修改,不是所有的修改都一定是优化,要看具体针对的场景,所以用修改一词无争议)。目的是经过使用不同的编程方式方法,实践到更多的 Go 语言基础知识,掌握更多编程技巧,体验编程的魅力!
使用数组存储时辰和俗称的源代码
// test01 project main.go
package main
import (
"fmt"
"time"
)
func main() {
var ( // 批量声明的语法
now = time.Now().Hour()
name = [24]string{ // 声明数组变量并初始化
"子时", "子夜",
"丑时", "鸡鸣",
"寅时", "黎明",
"卯时", "破晓",
"辰时", "早晨",
"巳时", "上午",
"午时", "中午",
"未时", "日斜",
"申时", "日馎",
"酉时", "傍晚",
"戌时", "黄昏",
"亥时", "深夜", // 最后这个逗号如果省略,那就必须将右花括号移上拉
}
num int
)
switch now {
case 23, 0:
num = 0
case 1, 2:
num = 2
case 3, 4:
num = 4
case 5, 6:
num = 6
case 7, 8:
num = 8
case 9, 10:
num = 10
case 11, 12:
num = 12
case 13, 14:
num = 14
case 15, 16:
num = 16
case 17, 18:
num = 18
case 19, 20:
num = 20
case 21, 22:
num = 22
}
fmt.Println("现在是" + name[num] + ",俗称" + name[num+1])
}
上述代码编译运行,输出结果是正确的。
这次修改取消了全局变量的声明,改用局部变量批量声明的方式。取消了在 swith 语句的 case 中给两个变量直接赋值时辰名、俗称名的字符串,改为在 case 中确定索引值,使用数组预置所有时辰名和俗称名的方式。
第10~27行,是批量声明变量,var 后面的括号内,以每个变量占一行的形式,可以同时定义多个变量,不需要每个变量都要写个 var 了,变量之间也不需要逗号分割。不能整体写在一行上,右括号必须单独占一行(可以在最后一个变量的尾部,但是格式化工具也会修改成上面代码所示的格式)。
第12~25行,是声明定义了一个数组变量 name,[24] 表示是具有24个元素的数组,string表示数组元素是字符串类型的。左花括号必须与类型在同一行(这是Go语言的一个语法特点),数组元素和右花括号可以与左花括号在同一行,也可以分开多行。各元素之间用逗号分开,如果右花括号紧跟最后一个元素在同一行,那么最后一个元素后面不用逗号,如果右花括号单独占一行,最后一个元素后面就必需有逗号。
第31、33、35、37、39、41、43、45、47、49、51、53行,给变量 num 赋值的是该时间对应在数组 name 中的 时辰名 的索引号(第一个元素的索引号是 0),因为还有 俗称名,所以各值之前就隔了一个数。
第56行,将 fmt.Println() 函数的参数改成一个字符串拼接的表达式,name[num] 是表示获取数组 name 中的 num 号索引的元素,num 的值是在上面的 switch 语句中各 case 内赋值的,这样当前时间与数组的索引就有了对应关系。name[num+1] 也是表示获取数组 name 的元素,索引值是 num+1 后的结果。为什么要这样呢?我们看数组声明是24个元素,每两个元素对应一个时辰。这两个元素一个是时辰名,一个是俗称名,且俗称名刚好比时辰名索引号大1。而在 swith 语句中 num 被赋值的是 时辰名的位置,因此有了加一的逻辑。
可能有些同学会有个问题,明明是12个时辰, swith 语句也是12个分支,能不能让数组也是12个元素这样一一对应,是不是更容易理解呢?好,那我们就继续修改!
使用二维数组明确与12时辰对应关系的源代码
// test01 project main.go
package main
import (
"fmt"
"time"
)
func main() {
var ( // 批量声明的语法
now = time.Now().Hour()
name = [12][2]string{ // 声明二维数组变量并初始化
{"子时", "子夜"},
{"丑时", "鸡鸣"},
{"寅时", "黎明"},
{"卯时", "破晓"},
{"辰时", "早晨"},
{"巳时", "上午"},
{"午时", "中午"},
{"未时", "日斜"},
{"申时", "日馎"},
{"酉时", "傍晚"},
{"戌时", "黄昏"},
{"亥时", "深夜"},
}
num int
)
switch now {
case 23, 0:
num = 0
case 1, 2:
num = 1
case 3, 4:
num = 2
case 5, 6:
num = 3
case 7, 8:
num = 4
case 9, 10:
num = 5
case 11, 12:
num = 6
case 13, 14:
num = 7
case 15, 16:
num = 8
case 17, 18:
num = 9
case 19, 20:
num = 10
case 21, 22:
num = 11
}
fmt.Println("现在是" + name[num][0] + ",俗称" + name[num][1])
}
上述代码编译运行,输出结果是正确的。
这次修改将数组由一维数组改成了二维数组,将每个 case 对 num 变量跳跃隔1赋值改为顺序号赋值,这样就与数组定义的元素索引(顺序号)一一对应了。
第12~25行,修改为声明定义了一个二维数组变量 name。[12] 表示这个数组第一维有12个元素,[2] 表示每个元素还是个具有2个元素的数组(第二维),string 表示第二维里的元素是字符串类型。第一维的每个元素要用花括号包起来(因为元素还是数组),各元素之间依然需要逗号分开。第二维里面就与上面的一维数组一样了,其他注意事项与上面的一维数组相同。
第31、33、35、37、39、41、43、45、47、49、51、53行,修改为给变量 num 赋值的是数组 name 第一维的索引号,所以各值之间是连续的。这样实际值就是在 0~11 之间,刚好与 name 数组的第一维索引号一一对应了。
第56行,将 fmt.Println() 函数的参数的字符串拼接的表达式中的变量部分,分别改成 name[num][0] 和 name[num][1],name[num][0] 是表示获取数组 name 的 num 号元素中的0号元素,name[num][1] 是表示获取数组 name 的 num 号元素中的1号元素。这样就用同一个 num 值,同时获得了 时辰名 和 俗称名,而不需要加1运算了。
现在我们再仔细看代码,是不是感觉 swith 语句占用的较大篇幅,且作用就是赋值给 num 一个有规律的顺序号,可不可以将 swith 语句用简单点的语句替换掉呢?好的,我们继续实践修改!
去掉 swith 语句改用计算公式的源代码
// test01 project main.go
package main
import (
"fmt"
"time"
)
func main() {
var ( // 批量声明的语法
now = time.Now().Hour()
name = [12][2]string{ // 声明二维数组变量并初始化
{"子时", "子夜"},
{"丑时", "鸡鸣"},
{"寅时", "黎明"},
{"卯时", "破晓"},
{"辰时", "早晨"},
{"巳时", "上午"},
{"午时", "中午"},
{"未时", "日斜"},
{"申时", "日馎"},
{"酉时", "傍晚"},
{"戌时", "黄昏"},
{"亥时", "深夜"},
}
num int
)
if now%2 == 0 { // 是偶数
num = now / 2
} else if now == 23 { // 是奇数且等于23
num = 0
} else { // 是奇数且不等于23
num = (now + 1) / 2
}
fmt.Println("现在是" + name[num][0] + ",俗称" + name[num][1])
}
上述代码编译运行,输出结果是正确的。
若想去掉对每个时间的逐一判断,那就要找到时间与顺序号的对应规律。在前面的代码中,我们可以看到,除了23点,其他的时间若是偶数的,直接除以 2,得数就是顺序号;若是奇数的,先加 1 后再除以 2,得数也是顺序号。根据这个规律,我们就有了修改思路:
先判断当前时间 now 是否是偶数?
如果是偶数,就将当前时间 now 除以 2,结果赋值给代表顺序号的变量 num;
如果是奇数,再判断 now 是否等于 23 ?
如果等于 23,那就让顺序号变量 num 等于 0;
如果不等于 23,那就将当前时间 now 先加上1,然后再除以 2,最后将结果赋值给顺序号变量 num。
第29~35行,就是根据上面思路编写的代码。if now%2 == 0 表示 now变量的值除以2后的余数等于 0这个条件是否成立。如果成立说明没有余数,那就是偶数,否则就是奇数。% 是求余数的运算符。大部分编程语言中,乘除的运算都比较影响运行效率,所以判断是否是偶数还可以写成 if num&1 == 0 这样,& 是二进制位运算符 与运算,对于位运算,因为计算机底层指令都是二进制运算,所以普遍运算效率要高很多,不熟悉二进制位运算的可以先不用,但无需质疑。
本节小结
以下是对本节涉及的 Go 语言编程内容的归纳总结,方便记忆:
● Go 语言的常见运算符整理如下:
算术运算符
运算符 | 说明 |
---|---|
+ | a + b,两边都是数字则为相加,都是字符串则是拼接 |
- | a - b,两数相减 |
* | a * b,两数相乘 |
/ | a / b,两数相除,两数中有任何一个是浮点数,结果则为浮点数,否则结果只保留整数部分 |
% | a % b,求两数相除后的余数 |
++ | a++,自身值增加1 |
– | a–,自身值减少1 |
关系运算符
运算符 | 说明 |
---|---|
== | a == b,如果 a 等于 b 则返回 True,否则返回 False |
!= | a != b,如果 a 不等于 b 则返回 True,否则返回 False |
> | a > b,如果 a 大于 b 则返回 True,否则返回 False |
>= | a >= b,如果 a 大于或等于 b 则返回 True,否则返回 False |
< | a < b,如果 a 小于 b 则返回 True,否则返回 False |
<= | a <= b,如果 a 小于或等于 b 则返回 True,否则返回 False |
逻辑运算符
运算符 | 说明 |
---|---|
&& | a && b,如果 a 为 True 并且 B 为 True,则条件为 True,否则条件为 False |
|| | a || b,如果 a 为 True 或者 B 为 True,则条件为 True,否则条件为 False |
! | !a,如果 a 为 True,则条件反转为 False;如果 a 为 False,则条件反转为 True |
位运算符
运算符 | 说明(假设 a = 3 二进制表示 00000011,b = 5 二进制表示 00000101) |
---|---|
& | 按位与运算,两数二进制同位都为 1 结果该位为 1,否则为 0。a & b 结果为 1 (00000001) |
| | 按位或运算,两数二进制同位有一个为 1 结果该位就为 1,都是 0 为 0。a | b 结果为 7 (00000111) |
^ | 按位异或运算,两数二进制同位不一样则结果该位就为 1,否则为 0。a ^ b 结果为 6 (00000110) |
<< | 按位左移运算符,a<< 1 结果为 6 (00000110) ,b << 1 结果为 10 (00001010) |
>> | 按位右移运算符,a >> 1 结果为 1 (00000001) ,b >> 1 结果为 2 (00000010) |
赋值运算符
运算符 | 说明 |
---|---|
= | 简单赋值运算,将右侧的值赋给左侧 |
+= | 左侧与右侧相加后再赋给左侧。a += b, 等同于 a = a + b |
-= | 左侧与右侧相减后再赋给左侧。a -= b, 等同于 a = a - b |
*= | 左侧与右侧相乘后再赋给左侧。a *= b, 等同于 a = a * b |
/= | 左侧与右侧相除后再赋给左侧。a /= b, 等同于a = a / b |
%= | 左侧与右侧相除取余后再赋给左侧。a %= b, 等同于 a =a % b |
&= | 左侧与右侧按位相与后再赋给左侧。a &= b, 等同于 a = a & b |
|= | 左侧与右侧按位相或后再赋给左侧。a |= b, 等同于 a = a | b |
^= | 左侧与右侧按位异或后再赋给左侧。a ^= b, 等同于 a = a ^ b |
<<= | 左侧左移右侧值那么多位后再赋给左侧。a <<= 3, 等同于 a = a << 3 |
>>= | 左侧右移右侧值那么多位后再赋给左侧。a >>= 3, 等同于 a = a >> 3 |
指针类运算符
运算符 | 说明 |
---|---|
& | 返回变量的存储地址而不是变量的值。&a,这将得到变量 a 的实际地址 |
* | 定义指针变量。*a,表示 a 是一个指针变量 |
运算符的优先级(优先级数字越大级别越高)
优先级 | 分类 | 运算符(同类优先级为从左至右) |
---|---|---|
14 | 后缀运算 | ( )、[ ] |
13 | 单目运算 | -(负号)、+(正号)、–、++、& 、*(指针)、! |
12 | 乘除法/取余 | *(乘号)、/、% |
11 | 加减法 | +、- |
10 | 位移运算 | <<、>> |
9 | 关系运算 | <、<=、>、>= |
8 | 等于不等 | ==、!= |
7 | 按位与 | & |
6 | 按位异或 | ^ |
5 | 按位或 | | |
4 | 逻辑关系与 | && |
3 | 逻辑关系或 | || |
2 | 赋值运算 | |=、^=、&=、<=、>=、%=、/=、*=、-=、+=、= |
● 一维数组声明定义格式如下:
var a [12]string 这是定义了一个有12个字符串类型元素的数组变量 a,没有初始化赋值。
var a = [3]int{10, 26, 19} 这是定义了一个有3个int类型元素的数组,元素值分别是10、26、19。
var a = [5]int{10, 26, 19} 这是定义了一个有5个int类型元素的数组变量 a,元素值从前至后分别是10、26、19,余下两个元素默认为 0(字符串类型的则为空字符串)。
a := [3]int{10, 26, 19} 这是定义了一个有3个int类型元素的数组,元素值分别是10、26、19。这种语法仅适用于局部变量。
a[0] 表示获取数组a的第一个元素。
● 二维数组声明定义格式如下:
var b [3][2]int 这是定义了一个第一维有3个元素,第二维有2个int类型元素的二维数组变量 b,没有初始化赋值。
var b [3][2]int{{11, 15}, {21, 26}, {36, 38}} 这是定义了一个第一维有3个元素,第二维有2个int类型元素的二维数组变量 b,并初始化赋值了。其他特点与一维数组相同。
● 使用 var () 可以批量声明变量,每个变量占一行,不需要逗号分隔。
.
.
上一节:Go/Golang语言学习实践[回顾]教程06–通过时间判断时辰的示例【上】