编程两大绝招
1.先易后难,即将一个复杂的问题分解成简单的问题
2.先死后活
运算符
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等
运算符用于在程序运行时执行数学或逻辑运算
Go语言内置的运算符有:算术运算符、赋值运算符、逻辑运算符、关系运算符、位运算符、其他运算符
算术运算符
算术运算符是对数值类型的变量进行运算的,比如:加减乘除。在Go程序中使用的非常多
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) // =1
fmt.Println(“-10%-3=”,-10%-3) // =-1
Go的自增自减只能当作一个独立语言使用,Go的++ 和 -- 只能写在变量的后面,不能写在变量的前面,即:只有a++ a-- 没有 ++a --a
//1)假如还有97天放假,问:xx个星期零xx天
//2)定义一个变量保存华式温度,华式温度转换摄氏温度的公式为:5/9*(华式温度-100)
// 请求出华式温度对应的摄氏温度
func main() {
week := 97 / 7
days := 97 % 7
fmt.Printf("还有%d个星期零%d天
",week,days)
huashi := 134.2
sheshi := 5.0/9*(huashi-100)
fmt.Println("华式对应的摄氏",sheshi)
}
关系运算符(比较运算符)
关系运算符的结果都是bool型,也就是要么是true,要么是false
关系运算符组成的表达式,我们称为关系表达式:a > b
关系表达式,经常用在if结构的条件中或循环结构的条件中
运算符 | 描述 | 实例 A=10 B=20 |
---|---|---|
== | 检查两个值是否相等,如果相等返回 true 否则返回 false。 | (A == B) 为 false |
!= | 检查两个值是否不相等,如果不相等返回 true 否则返回 false。 | (A != B) 为 true |
> | 检查左边值是否大于右边值,如果是返回 true 否则返回 false。 | (A > B) 为 false |
< | 检查左边值是否小于右边值,如果是返回 true 否则返回 false。 | (A < B) 为 true |
>= | 检查左边值是否大于等于右边值,如果是返回 true 否则返回 false。 | (A >= B) 为 false |
<= | 检查左边值是否小于等于右边值,如果是返回 true 否则返回 false。 | (A <= B) 为 true |
逻辑运算符
用于连接多个条件(一般来讲就是关系表达式),最终的结果是一个bool值
func main() {
//演示逻辑运算符的使用 &&
//&&也叫短路与:如果第一个条件为false,则第二个条件不会判断,最终结果为false
var age int = 40
if age > 30 && age < 50 {
fmt.Println("ok1")
} else if age > 30 && age < 40 {
fmt.Println("ok2")
}
//演示逻辑运算符的使用 ||
//||也叫短路或:如果第一个条件为true,则第二个条件不会判断,最终结果为true
if age > 30 || age < 50 {
fmt.Println("ok3")
} else if age > 30 || age < 40 {
fmt.Println("ok4")
}
//演示逻辑运算符的使用 !
if age < 30 {
fmt.Println("ok5")
} else if !(age< 30) {
fmt.Println("ok6")
}
}
//ok1
//ok3
//ok6
进制
进制也就是进位计数制,是人为定义的带进位的计数方法(有不带进位的计数方法,比如原始的结绳计数法,唱票时常用的“正”字计数法,以及类似的tally mark计数)。 对于任何一种进制---X进制,就表示每一位置上的数运算时都是逢X进一位。 十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一,以此类推,x进制就是逢x进位。—— 百度百科
现代的电子计算机技术全部采用的是二进制,因为它只使用0、1两个数字符号,非常简单方便,易于用电子方式实现。计算机内部处理的信息,都是采用二进制数来表示的。二进制(Binary)数用0和1两个数字及其组合来表示任何数。进位规则是“逢2进1”,数字1在同的位上表示不同的值,按从右至左的次序,这个值以二倍递增。
在计算机的内部,运行各种运算时,都是以二进制的方式来运行的。
十进制 | 十六进制 | 八进制 | 二进制 |
---|---|---|---|
0 | 0 | 0 | 0 |
1 | 1 | 1 | 1 |
2 | 2 | 2 | 10 |
3 | 3 | 3 | 11 |
4 | 4 | 4 | 100 |
5 | 5 | 5 | 101 |
6 | 6 | 6 | 110 |
7 | 7 | 7 | 111 |
8 | 8 | 10 | 1000 |
9 | 9 | 11 | 1001 |
10 | A | 12 | 1010 |
11 | B | 13 | 1011 |
12 | C | 14 | 1100 |
13 | D | 15 | 1101 |
14 | E | 16 | 1110 |
15 | F | 17 | 1111 |
16 | 10 | 20 | 10000 |
17 | 11 | 21 | 10001 |
对于整数,常用的进制有二进制、八进制、十进制以及十六进制
1) 二进制: 0 - 1,满2进1
在golang中,不能直接使用二进制来表示一个整数,它沿用了C的特点。但是可以以二进制的形式输出它。
2) 八进制: 0 - 7,满8进1,计算机内以0开头表示
3) 十进制:0 - 9, 满10进1
4) 十六进制: 0 - 9及A - F,满16进1,计算机内以0x或者0X开头表示。此处的A-F不区分大小写,如0x21AF+1 = 0X21B0
进制转换规矩
其它进制转十进制
从最低位开始(右边的),将每个位上的数提取出来,乘以X进制的(位数-1)次方,然后求和
0x1011 = 1 * 16^3 + 0 * 16^2 + 1 * 16^1 + 1 * 16^0 = 4113
十进制转其它进制
将该数不断除以X,直到商为0为止,然后将每步得到的余数倒过来,就是对应的X进制
156 【156 / 8 = 19(余4) 19 / 8 = 2(余2)】 == 0234
二进制转其它进制
二进制转八进制
规则:将二进制数每三位一组(从低位开始组合),转成对应的八进制数即可。
11010101 => (11)(010)(101) => 0325
二进制转十六进制
规则:将二进制数每四位一组(从低位开始组合),转成对应的八进制数即可。
11010101 => (1101)(0101) =>0xD5
其它进制转二进制
八进制转二进制
将八进制的每1位,转成对应的一个三位的二进制数即可
0237 => (010)(011)(111) => 10011111
十六进制转二进制
将八进制的每1位,转成对应的一个四位的二进制数即可
0x237 => (0010)(0011)(0111) => 1000110111
原码、反码、补码
对于有符号的而言:
- 二进制的最高位是符号位:0表示整数,1表示负数
1 ==》[0000 0001] -1 ==》 [1000 0001]
-
正数的原码、反码、补码都一样
-
负数的反码 = 它的原码符号位不变,其它位取反(0 -> 1, 1 -> 0)
1 ==》原码[0000 0001] 反码[0000 0001] 补码[0000 0001]
-1 ==》 原码[1000 0001] 反码[1111 1110]补码[1111 1111]
-
负数的补码 = 它的反码 + 1
-
0的反码,补码都是0
-
在计算机运算的时候,都是以补码的方式来运算的
1 + 1 1 - 1 = 1 + (-1)
位运算符
Go 语言支持的位运算符如下表所示。假定 A 为60,B 为13:
运算符 | 描述 | 实例 A =60 B =13 |
---|---|---|
& | 按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。运算规则是:同时为1,结果为1,否则为0 | (A & B) 结果为 12, 二进制为 0000 1100 |
| | 按位或运算符"|"是双目运算符。 其功能是参与运算的两数各对应的二进位相或运算规则是:有一个为1,结果为1,否则为0 | (A | B) 结果为 61, 二进制为 0011 1101 |
^ | 按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或运算规则是:当二进位不同时,结果为1,否则为0 | (A ^ B) 结果为 49, 二进制为 0011 0001 |
<< | 左移运算符"<<"是双目运算符。左移n位就是乘以2的n次方。 其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。 | A << 2 结果为 240 ,二进制为 1111 0000 |
>> | 右移运算符">>"是双目运算符。右移n位就是除以2的n次方。 其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。低位溢出,符号位不变,并用符号位补溢出的高位 | A >> 2 结果为 15 ,二进制为 0000 1111 |
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
a := 1 >> 2 //0000 0001 ==> 0000 0000 ==> 0
c := 1 << 2 //0000 0001 ==> 0000 0100 ==> 4
假定 A = 60; B = 13; 其二进制数(求它们的补码,实际上是对补码进行运算)转换为:
A = 0011 1100
B = 0000 1101
-----------------
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001
计算机内部采用补码进行运算
A+B
A的原码为: 0011 1100
A的反码为: 0011 1100
A的补码为: 0011 1100
B的原码为: 0000 1101
B的反码为: 0000 1101
B的补码为: 0000 1101
A + B
0011 1100
0000 1101
--------------
0100 1001
0100 1001转换为10进制整数就是73
A-B = A + (-B)
A的原码为: 0011 1100
A的反码为: 0011 1100
A的补码为: 0011 1100
-B的原码为: 1000 1101
-B的反码为: 1111 0010
-B的补码为: 1111 0011
A - B =
0011 1100
1111 0011
--------------
0010 1111
0010 1111转换为10进制整数就是47
需要注意的是如果得到的结果是正数,则补码就是原码,但是如果得到的结果是负数,则需要将补码-1取反变成原码后再转换成整数
B - A
-A的原码为: 1011 1100
-A的反码为: 1100 0011
-A的补码为: 1100 0100
B的原码为: 0000 1101
B的反码为: 0000 1101
B的补码为: 0000 1101
B - A =
1100 0100
0000 1101
--------------
1101 0001
根据补码符号位可以看出结果为负数,所以需要先-1转换为反码1101 0000,然后符号位不变,取反为原码为1010 1111即为-47
赋值运算符
赋值运算符就是将某个运算后的值,赋给指定的变量
运算顺序从右往左
赋值运算符的左边只能是变量,右边可以是变量、表达式、常量值
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,将一个表达式的值赋给一个左值 | 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 |
<<= | 左移后赋值 | C <<= 2 等于 C = C << 2 |
>>= | 右移后赋值 | C >>= 2 等于 C = C >> 2 |
&= | 按位与后赋值 | C &= 2 等于 C = C & 2 |
^= | 按位异或后赋值 | C ^= 2 等于 C = C ^ 2 |
|= | 按位或后赋值 | C |= 2 等于 C = C | 2 |
其它运算
运算符 | 描述 | 实例 |
---|---|---|
& | 返回变量存储地址 | &a; 将给出变量的实际地址。 |
* | 指针变量。 | *a; 是一个指针变量 |
go语言明确不支持三元运算符。在go中实现三元运算的效果
func main() {
var (
n int
i int = 10
j int = 12
)
//传统的三元运算
//n = i > j ? i : j
if i > j {
n = i
} else {
n = j
}
fmt.Println("n = ",n)
}
运算符的优先级
分类 | 描述 | 关联性 |
---|---|---|
后缀 | () [] -> . ++ -- | 左到右 |
单目 | + - ! ~ (type) * & sizeof | *右到左* |
乘法 | * / % | 左到右 |
加法 | + - | 左到右 |
移位 | << >> | 左到右 |
关系 | < <= > >= | 左到右 |
相等 | == != | 左到右 |
按位AND | & | 左到右 |
按位XOR | ^ | 左到右 |
按位OR | | | 左到右 |
逻辑AND | && | 左到右 |
逻辑OR | || | 左到右 |
赋值运算符 | = += -= *= /= %= >>= <<= &= ^= |= | *右到左* |
逗号 | , | 左到右 |
键盘输入语句
在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取。InputDemo.go
func main() {
var (
name string
age byte
sal float32
isPass bool
)
//方式1:当程序执行到fmt.Scanln(&name),程序会停止在这里,等待用户输入,并回车
fmt.Println("请输入姓名:")
fmt.Scanln(&name)
fmt.Println("请输入年龄: ")
fmt.Scanln(&age)
fmt.Println("请输入薪水: ")
fmt.Scanln(&sal)
fmt.Println("请输入是否通过考试: ")
fmt.Scanln(&isPass)
fmt.Printf("名字是 %v 年龄是%v 薪水是 %v 是否通过考试%v
",name,age,sal,isPass)
//方式2:fmt.Scanf,可以按指定的格式输入
fmt.Println("请输入你的姓名,年龄,薪水,是否通过考试,使用空格隔开")
fmt.Scanf("%s %d %f %t",&name,&age,&sal,&isPass) //格式对应准确
fmt.Printf("名字是 %v 年龄是 %v 薪水是 %v 是否通过考试 %v
",name,age,sal,isPass)
}
程序流程控制
在程序中,程序运行的流程控制决定程序是如何执行的,是必须掌握的,主要有三大流程控制语句
1) 顺序控制
2) 分支控制
3) 循环控制
顺序控制
分支控制
分支控制就是让程序有选择执行。有下面三种形式
1) 单分支
2) 双分支
3) 多分支
单分支控制
func main() {
//当条件表达式为true时,就会执行{}的代码。{}是必须有的,就算只写一行代码
if age := 20;age > 18 { //可以直接定义一个变量
fmt.Println("你要对自己的行为负责")
}
}
双分支控制
func main() {
if age := 20;age < 18 { //当条件表达式成立,即执行代码块1,否则执行代码块2。{}也是必须有的
fmt.Println("你要对自己的行为负责")
} else { //else的位置需要注意
fmt.Println("你还小")
}
}
双分支只会执行其中的一个分支
多分支控制
func main() {
//分析思路
//1. score 分数变量int
//2. 选择多分支流程控制
//3. 成绩从键盘输入 fmt.Scanln
var score int
fmt.Println("请输入成绩:")
fmt.Scanln(&score)
//多分支判断
if score == 100 {
fmt.Println("奖励一辆BMW")
} else if score > 80 && score <= 99 {
fmt.Println("奖励一台P30pro")
} else if score >= 60 && score <= 80 {
fmt.Println("奖励一个iPad")
} else {
fmt.Println("什么都不会奖励")
}
}
多分支的判断流程如下:
先判断条件表达式1是否成立,如果为真,就执行代码块1
如果条件表达式1为假,就去判断条件表达式2是否成立,如果条件表达式2为真,就执行代码块2
依次类推
如果所有的条件表达式不成立,则执行else的语句块
else不是必须的
多分支只能有一个执行入口
嵌套分支
在一个分支结构中又完整的嵌套了另一个安整的分支结构,里面的分支的结构称为内层分支外面的分支结构称为外层分支。
func main() {
var second float64
fmt.Println("请输入秒数")
fmt.Scanln(&second)
if second <= 8 { //嵌套分支不宜过多,建议控制在3层内。
//进入决赛
var gender string
fmt.Println("请输入性别")
fmt.Scanln(&gender)
if gender == "男" {
fmt.Println("进入决赛的男子组")
} else {
fmt.Println("进入决赛的女子组")
}
} else {
fmt.Println("out ...")
}
}
switch 分支控制
switch的执行流程是,先执行表达式,得到值,然后和case的表达式进行比较,如果相等,就匹配到,然后执行对应的case语句块,然后退出switch控制
如果switch的表达式的值没有和任何的case的表达式匹配成功,则执行default的语句块。执行后退出switch的控制
Go的case后的表达式可以有多个,使用逗号间隔
Go中的case语句块不需要写break,因为默认会有,即在默认情况下,当程序执行完case语句块后,就直接退出该switch控制结构
func main() {
//思路分析
//1. 定义一个变量接收字符
//2. 使用switch完成
var key byte
fmt.Println("请输入一个字符a,b,c")
fmt.Scanf("%c",&key)
//这里不能使用fmt.Scanln
//当且仅当前面的输入类型不对,或者scan函数出错情况下,才会出现你的这种情况,所以,我们要捕获scan()系列方法的返回值,如果返回值不为nil,则不要再继续往下执行scan了,如果还继续scan,则会出现你的这种情况!
//至于为什么会出错!因为scan没有提供对byte类型的反射!也就是不能赋值给byte类型,可以赋值给string类型或者uint8类型,byte是uint8的类型的别名,也就是,比如,你要接收‘a’这个byte,但是你不能再控制台输入a,这样的话就是字符串了,你要输入97,也就是输入‘a’的ascii码值!‘+’号同理!
//switch后可以不带表达式,类似if -- else 分支来使用。
//switch后也可以直接声明/定义一个变量,分号结束,不推荐
switch test(key) { //case/switch后是一个表达式(即:常量值、变量、一个有返回值的函数都可以)
case 'a': //case后的各个表达式的值的数据类型,必须和switch的表达式数据类型一致
fmt.Println("周一,猴子穿新衣")
case 'b': //不需要大括号和break
fmt.Println("周二,猴子当小二")
fallthrough //默认只能穿透一层
case 'c': //case后面可以带多个表达式,使用逗号间隔。case后面的表达式如果是常量值(字面量),则要求不能重复
fmt.Println("周三,猴子爬雪山")
default: //default语句不是必须的
fmt.Println("输入有误")
}
}
//请输入一个字符a,b,c
//a
//周二,猴子当小二
//周三,猴子爬雪山
Type Switch:switch语句还可以被用于type-switch来判断某个interface变量中实际指向的变量类型
switch和if的比较
-
如果判断的具体数值不多,而且符号整数、浮点数、字符、字符串这几种类型。建议使用switch语句,简洁高效
-
对区间判断和结果为bool类型的判断,使用if,if的使用范围更广
for循环控制
让一段代码循环的执行
func main() {
var str string = "hello, world! 紫色飞猪"
//如果我们的字符串含有中文,那么传统遍历字符串的方式就是错误的,会出现乱码。原因是传统的对字符串的遍历是按照字节来遍历的,而一个汉字再utf8编码是对应3个字节的
//方法1:[]rune()
////需要将str转成[]rune切片
//str2 := []rune(str)
//for i := 0; i < len(str2) ; i++ {
//方法2: for range
//对应for-range 遍历方式而言,是按照字符方式遍历。因此如果字符串有中文,也是可以的
for _, val := range str { //Go提供for-range的方式,可以方便遍历字符串和数组
fmt.Printf("%c ", val)
}
}
//h e l l o , w o r l d ! 紫 色 飞 猪
while和do ... while的实现
Go语言没有while和do..while语法,这一点需要注意,如果我们需要使用类似其它语言(比如java/c 的while和do..while),可以通过for循环来实现其使用效果。
while循环的实现
循环变量初始化
for {
if 循环条件表达式 {
break //跳出for循环..
}
循环操作语句
循环变量迭代
}
do..while的实现
循环变量初始化
for {
循环操作(语句)
循环变量迭代
if循环条件表达式 {
break //跳出for 循环..
}
}
多重循环控制
一个循环放在另一个循环体内,就形成了嵌套循环。在外边的for循环称为外层循环在里面的for循环称为内层循环。【建议一般使用两层,最多不要超过3层】
实质上,嵌套循环就是把内层循环当作外层循环的循环体。当只有内层循环的循环条件为false时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的循环
外层循环次数为m次,内层为n次,则内层循环实际上需要执行m*n次
应用案例
- 统计3个班成绩情况,每个班有5名同学,求出各个班的平均分和所有班级的平均分[学生的成绩从键盘输入]
func main() {
var classNum int
fmt.Println("请输入有几个班:")
fmt.Scanln(&classNum)
//fmt.Scanf("%d",&classNum)
var stuNum int
fmt.Println("请输入每个班级有多少个人")
fmt.Scanln(&stuNum)
var totalSum float64 = 0.0
for j := 1; j <= classNum; j++ {
sum := 0.0
for i := 1; i <= stuNum; i++ {
var score float64
fmt.Printf("请输入第%d班 第%d个学生的成绩",j,i)
fmt.Scanln(&score)
//累计总分
sum += score
}
fmt.Printf("第%d个班级的平均分是%v
",j, sum / float64(stuNum))
//将各个班的总成绩累计到totalSum中
totalSum += sum
}
fmt.Printf("各个班级的总成绩%v 所有班级的平均成绩是%v ",totalSum,totalSum/float64(stuNum * classNum))
}
- 打印金字塔
使用for循环编写一个程序,可以接收一个整数,表示层数,打印出金字塔
func main() {
//编程思路
//1. 打印一个矩形
//
// 1. 打印一个矩形
// ***
// ***
// ***
//2. 打印半个金字塔
//2. 打印半个金字塔
//* 1 个 *
//** 2 个 *
//*** 3 个 *
//3. 打印整个金字塔
// * 1层1个* 规律:2* 层数 - 1 空格 2 规律 总层数-当前层数
// *** 2层3个* 规律:2*层数 - 1 空格1 规律 总层数-当前层数
// ***** 3层5个* 规矩:2*层数 -1 空格0 规律 总层数-当前层数
//4. 将层数做成一个变量,先死后活
//var totalLevel int
//5. 打印空心金字塔
// *
// * *
// *****
//分析:在给每行打印*号时,需要考虑是打印*还是打印空格
//分析的结果是,每层的第一个和最后一个是打印*,其它就应该是空,即输出空格
//分析到一个例外情况,最后层(底层)是全部打*
var totalLevel int
fmt.Println("请输入打印的层数:")
fmt.Scanf("%d", &totalLevel)
//i 表示层数
for i := 1; i <= totalLevel; i++ {
//在打印*前先打印空格
for k := 1; k <= totalLevel-i; k++ {
fmt.Print(" ")
}
//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() {
//打印出九九乘法表
//1. 正表
//var num int = 9
//for i := 1; i <= num ; i++ {
// for j := 1; j <= i; j++ {
// fmt.Printf("%v * %v = %v ",j,i,j * i)
// }
// fmt.Println()
//}
//2. 倒表
var num int = 9
for i := num; i >= 1 ; i-- {
for j := 1; j <= i; j++ {
fmt.Printf("%v * %v = %v ",j,i,j * i)
}
fmt.Println()
}
}
跳转控制语句-break
break语句用于终止某个语句块的执行,用于中断当前for循环或跳出switch语句。
func main(){
lable2:
//break语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块
for i := 0; i < 4 ; i++ {
for j := 0; j < 10;j++ {
if j == 2 {
//break //break 默认会跳出最近的for循环
//break lable1
break lable2
}
fmt.Println("j = ",j)
}
}
}
跳转控制语句-continue
continue语句用于结束本次循环,继续下一次循环
continue语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环,这个和前面的break标签的使用规则一样
func main(){
lable2:
//break语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块
for i := 0; i < 4 ; i++ {
for j := 0; j < 10;j++ {
if j == 2 {
//break //break 默认会跳出最近的for循环
//break lable1
continue lable2
}
fmt.Println("j = ",j)
}
}
}
//j = 0
//j = 1
//j = 0
//j = 1
//j = 0
//j = 1
//j = 0
//j = 1
跳转控制语句- goto
-
Go语言的goto语句可以无条件地转移到程序中指定的行
-
goto语句通常与条件语句配合使用。可用来实现条件转移,跳出循环体等功能
3)在Go程序设计中一般不主张使用goto语句,以免造成程序流程的混乱,使理解和调试程序都产生困难
func main() {
var n int = 30
//演示goto的使用
fmt.Println("ok1")
if n > 20 {
goto lable1
}
fmt.Println("ok2")
fmt.Println("ok3")
fmt.Println("ok4")
lable1:
fmt.Println("ok5")
fmt.Println("ok6")
}
跳转控制语句-reture
return使用在方法或者函数中,表示跳出所在的方法或函数
func main() {
for i := 1; i <= 10; i++ {
if i == 3 {
return
}
//如果return是在普通的函数,则表示跳出该函数,即不再执行函数中return后面的代码,也可以理解成终止函数
//如果return是在main函数,表示终止main函数,也就是说终止程序
fmt.Println("zisefeizhu",i)
}
fmt.Println("hello world!")
}
//zisefeizhu 1
//zisefeizhu 2
Go函数支持返回多个值,这一点是其它编程语法没有的
func 函数名 (形参列表) (返回值类型列表) {
语句...
return 返回值列表
}
1)如果返回多个值时,在接收时,希望忽略某个返回值,则使用 _符号表示占位忽略
2)如果返回值只有一个,(返回值类型列表) 可以不写()
package main
import "fmt"
//请编写要给函数,可以计算两个数的和 和 差,并返回结果
func getSumAndSub(n1 int, n2 int) (int, int) {
sum := n1 + n2
sub := n1 - n2
return sum, sub
}
func main() {
//调用getSumAndSub
//希望忽略某个返回值,则使用 _符号表示占位忽略
_, res2 := getSumAndSub(1,2)
fmt.Printf("res2 = %v",res2)
}
生成随机数
在Go语言中生成随机数需要使用Seed(value)函数来提供伪随机数生成种子,一般情况下都会使用当前时间的纳秒数字,如果不在生成随机数之前调用该函数,那么每次生成的随机数都是一样的。函数rand.Float32和rand.Float64返回介于[0.0, 1.0)之间的伪随机数,其中包括0.0但不包括1.0。函数rand.Intn(value)返回介于[0,value)之间的伪随机数。
import (
"fmt"
"math/rand"
"time"
)
func main() {
nanotime := int64(time.Now().Nanosecond())
rand.Seed(nanotime)
for i := 0; i < 10; i++ {
fmt.Printf("%2.2f ", 100 * rand.Float32())
}
}
随机生成1-100的一个数,直到生成了99这个数,看看一共用了几次?
分析:编写一个无限循环的控制,然后不停的随机生成数,当生成了99时,就退出这个无限循环 ==》 break提示使用
随机生成1 - 100整数
func main(){
//我们为了生成一个随机数,还需要read设置一个种子。
//time.Now().Unix:返回一个从1970:01:01的0时0分0秒到现在的秒数
//rand.Seed(time.Now().Unix())
//如何随机的生成1 - 100的整数
// n := rand.Intn(100) + 1 //[0 100)
//fmt.Println(n)
//随机生成1 - 100的一个数,直到生成了99这个数,看看一共用了几次
//编写一个无限循环的控制,然后不停的随机生成数,当生成了99时,就退出这个无限循环 ==》 break提示使用
//随机生成1 - 100整数
var count int = 0
for {
rand.Seed(time.Now().UnixNano()) //以当前系统时间作为种子参数,精确到纳秒
n := rand.Intn(100) + 1
fmt.Println("n = ", n)
count++
if (n == 99) {
break //表示跳出for循环
}
}
fmt.Println("生成 99 一共使用了",count)
}
函数、包和错误处理
为完成某一功能的程序指令(语句)的集合,称为函数
在Go中,函数分为:自定义函数、系统函数(查看Go编程手册)
函数的作用:减少代码冗余、利于代码维护
函数的基本语法
func 函数名 (形参列表) (返回值列表) {
执行语句...
return 返回值列表
}
形参列表:表示函数的输入
函数中的语句:表示为了实现某一功能代码块
函数可以有返回值,也可以没有
入门案例:输入两个数,再输入一个运算符(+,-,*,/),得到结果
func cal(n1,n2 float64, operator byte) float64 {
var res float64
switch operator {
case '+':
res = n1 + n2
case '-':
res = n1 - n2
case '*':
res = n1 * n2
case '/':
res = n1 / n2
default:
fmt.Println("操作符号错误...")
}
return res
}
func main() {
var n1 float64
var n2 float64
var operator byte
fmt.Println("请输入n1的值:")
fmt.Scanln(&n1)
fmt.Println("请输入n2的值:")
fmt.Scanln(&n2)
fmt.Println("请输入一个符号:")
fmt.Scanf("%c",&operator)
result := cal(n1,n2,operator)
fmt.Println("result = ", result)
}
函数参数传递方式
值类型参数默认就是值传递,而引用类型参数默认就是引用传递
其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低
值类型:基本数据类型 :int系列、float系列、bool、string、数组和结构体struct
引用类型:指针、slice切片、map、管道chan、interface等都是引用类型
值传递和引用传递使用特点
1)值类型默认是值传递:变量直接存储值,内存通常在栈中分配
-
引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收
-
如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。这个案例在前面详解函数使用注意事项时有写
变量作用域
函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效
如果变量是在一个代码块,比如for/if中,那么这个变量的作用域就在该代码块
包
在实际的开发中,我们往往需要在不同的文件中,去调用其它文件的函数,比如main.go中,去使用utils.go文件中的函数,如何实现? -》包
现在有两个程序员共同开发一个Go项目,程序员A希望定义函数Cal,程序员B也想定义函数也叫Cal。两个程序员为此吵了起来,怎么实现? -》包
包的本质实际上就是创建不同的文件夹,来存程序文件
Go的每一个文件都是属于一个包的,也就是说Go是以包的形式来管理文件和项目目录结构的
包的三大作用
1) 区分相同名字的函数、变量等标识符
2) 当程序文件很多时,可以很好的管理项目
3) 控制函数、变量等访问范围,即作用域
打包基本语法
package 包名
引入包的基本语法
import “包的路径”
快速入门案例
包快速入门-Go相互调用函数,我们将func Cal定义到文件utils.go,将utils.go放到一个包中,当其它文件需要使用到utils.go的方法时,可以import 该包,就可以使用了。
PS:需要提前设置好GOPATH
utils.go
package utils //在给一个文件打包时,该包对应一个文件夹,比如这里的utils文件夹对应的包名就是utils,文件的包名通常和文件所在的文件夹名一致,一般为小写字母
import "fmt"
//将计算的功能,放到一个函数中,然后在需要使用时,调用即可
//为了让其它包的文件使用Cal函数,需要将c大小类似其它语言的public
func Cal(n1 float64, n2 float64, operator byte) float64 {
//为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言的public,这样才能挎包访问
var res float64
switch operator {
case '+' :
res = n1 + n2
case '-' :
res = n1 - n2
case '*' :
res = n1 * n2
case '/' :
res = n1 / n2
default:
fmt.Println("操作符号错误...")
}
return res
}
main.go
package main //如果要编译成一个可执行程序文件,就需要将这个包声明为main,即package main 这个就是一个语法规范,如果是写一个库,包名可以自定义
import (
"2020-04-02/utils" // 导入包 (GOPATH提前设置好了) ////当一个文件要使用其它包函数或变量时,需要先引入对应的包
//在import包时,路径从 $GOPATH 的 src下开始,不用带src,编译器会自动从src下开始引入
"fmt"
)
func main() {
//分析思路...
var n1 float64 = 1.2
var n2 float64 = 2.3
var operator byte
fmt.Println("请输入一个符号")
fmt.Scanf("%c",&operator)
result := utils.Cal(n1, n2, operator) //调用函数,包名.函数名()
//在访问其它包函数,变量时,其语法是 包名.函数名
fmt.Println("result = ", result)
}
如果包名较长,Go支持给包取别名,注意细节:取别名后,原来的包名就不能使用了
函数的调用机制
函数的递归调用【重】
一个函数在函数体内又调用了本身,称为递归调用
函数递归调用需要遵守的重要原则
执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
函数的局部变量是独立的,不会相互影响
递归必须向退出递归的条件逼近,否则就是无限递归
当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁
题1:斐波那契数
请使用递归的方式,求出斐波那契数 1,1,2,3,5,8,13...
给你一个整数n,求出它的斐波那契数是多少?
思路:
1) 当n == 1 || n == 2,返回1
2) 当n >= 2,返回 前面两个数的和 f(n-1) + f(n-2)
//请使用递归的方式,求出斐波那契数 1,1,2,3,5,8,13...
//给你一个整数n,求出它的斐波那契数是多少?
func fbn(n int) int {
if (n == 1 || n == 2) {
return 1
} else {
return fbn(n - 1) + fbn(n - 2)
}
}
func main() {
var n int
fmt.Println("请输入n的值:")
fmt.Scanln(&n)
res := fbn(n)
fmt.Println("res = ",res)
}
题2:求函数值
已知f(1) = 3; f(n) = 2 * f(n-1) + 1;
请使用递归的思想编程,求出f(n)的值?
思路
直接使用给出的表达式即可完成
func f(n int) int {
if n == 1 {
return 3
} else {
return 2 * f(n - 1) + 1
}
}
func main() {
var n int
fmt.Println("请输入n的值:")
fmt.Scanln(&n)
fmt.Println("res = ",f(n))
}
题3:猴子吃桃子
有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。当到第十天时,想再吃时(还没吃),发现只有一个桃子了。问:最初共多少个桃子?
思路
1) 第10天只有一个桃子
2) 第9天有几个桃子 = (第10天桃子数量 + 1) * 2
3) 规矩:第n天的桃子数据 peach(n) = peach(n + 1) + 1) * 2
//猴子吃桃子
//有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。当到第十天时,想再吃时(还没吃),发现只有一个桃子了。问:最初共多少个桃子?
//思路
//1)第10天只有一个桃子
//2)第9天有几个桃子 = (第10天桃子数量 + 1) * 2
//3)规矩:第n天的桃子数据 peach(n) = peach(n + 1) + 1) * 2
func peach(n int) int {
if n > 10 || n < 1 {
fmt.Println("输入的天数不对")
return 0
} else if n == 10 {
return 1
} else {
return (peach(n + 1) + 1) * 2
}
}
func main() {
fmt.Println("第一天的桃子数量是 = ",peach(1))
}
函数使用的注意事项和细节
-
函数的形参列表可以是多个,返回值列表也可以是多个
-
形参列表和返回值列表的数据类型可以是值类型和引用类型
-
函数的命名遵循标识符命名规范,首字符不能是数字,首字母大写该函数可以被本包文件和其它包文件使用,类似public,首字母小写,只能被本包文件使用,其它包文件不能使用,类似privat
4)函数中的变量是局部的,函数外不生效
5)基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值
- 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。
7)Go函数不支持函数重载
- 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用
9)函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且可以被调用
- 为了简化数据类型定义,Go支持自定义数据类型
type 自定义数据类型名 数据类型 //相当于一个别名
type myInt int //这时 myInt 就等价int来使用了
type mySum func(int,int)int //这时mySum 就等价一个函数类型func(int,int)int
//这时 myFun 就是fun(int,int)int类型
//全局变量
type myFunType func(int,int) int
func getSum(n1 int, n2 int) int {
return n1 + n2
}
//函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用
func myFun(funvar myFunType, num1 int,num2 int) int {
return funvar(num1, num2)
}
func main() {
a := getSum
fmt.Printf("a的类型%T,getSum类型是%T
",a,getSum)
res := a(10,40) //等价 res := getSum(10,40)
fmt.Println("res = ",res)
b := myFun
fmt.Printf("b的类型%T
",b)
res2 := myFun(getSum, 50, 60)
fmt.Println("res2 = ",res2)
//给int 取了别名,在go中myInt和int虽然都是int类型,但是go认为myInt和int两个类型
type myInt int
var num1 myInt
var num2 int
num2 = int(num1) //注意:这里依然需要显示转换,Go认为myInt和int两个类型
fmt.Println("num1 = ",num1,"num2 = ",num2)
}
-
支持对函数返回值命名
-
使用 _ 标识符,忽略返回值
func cal(n1 int,n2 int)(sum int,sub int){
sum = n1 + n2
sub = n1 - n2
return
}
func main() {
res1, _ :=cal(10,20)
fmt.Printf("res1 = %d",res1)
}
- Go支持可变参数
//支持0到多个参数
func sum(args... int)sum int{
}
//支持1到多个参数
func sum(n1 int,args... int)sum int{
}
args是slice切片,通过args[index],可以访问到各个值
如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后
编写一个函数sum,可以求出1到多个int的和
//可变参数的使用
func sum(n1 int,args... int) int {
sum := n1
//遍历args
for i := 0; i < len(args); i++{
sum += args[i] //args[0]表示取出args切片的第一个元素值,其它依次类推
}
return sum
}
//测试一下可变参数的使用
func main() {
res := sum(10,0,-1,90,10,100)
fmt.Println("res = ",res)
}
init函数
每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用,也就是说init函数会在main函数前被调用
init函数的注意事项和细节
- 如果一个文件同时包含全局变量定义,init函数和main函数,则执行的流程:全局变量定义 --> init函数 --> main函数
2)init函数最主要的作用,就是完成一些初始化的工作,比如下面的案例
utils/utils.go
package utils
import "fmt"
var Age int
var Name string
//Age和Name全局变量,可以在main.go中使用
//但是需要初始化Age和Name
//init函数完成初始化工作
func init() {
fmt.Println("utils包的init()...")
Age = 100
Name = "zisefeizhu"
}
main/main.go
package main
import (
"2020-04-02/utils" // 导入包 (GOPATH提前设置好了)
"fmt"
)
var age = test()
//为了看到全局变量是先被初始化的,先写函数
func test() int {
fmt.Println("test()") //1
return 90
}
//init函数,通常可以在init函数中完成初始化工作
func init(){
fmt.Println("init()...") //2
}
func main() {
fmt.Println("main()... age=",age) //3
fmt.Println("Age = ",utils.Age,"Name = ",utils.Name)
}
//utils包的init()...
//test()
//init()...
//main()... age= 90
//Age = 100 Name = zisefeizhu
如果main.go和utils.go都含有变量定义,init函数时,执行的流程又是怎么样的呢?
匿名函数
Go支持匿名函数,匿名函数就是没有名字的函数,如果某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用
匿名函数使用方式1
在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。
func main() {
//使用匿名函数完成求两个数的和
res := func(n1 , n2 int) int {
return n1 + n2
}(10,20)
fmt.Println("res = ",res)
}
匿名函数使用方式2
将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
全局匿名函数
如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效
var (
//fun1就是一个全局匿名函数
Fun1 = func(n1 int, n2 int) int {
return n1 * n2
}
)
//全局匿名函数的使用
func main() {
res := Fun1(4,9)
fmt.Println("res = ",res)
}
闭包【重点】
闭包就是一个函数和与相关的引用环境组合的一个整体(实体)
//要搞清楚闭包的关键点就是要分析出返回的函数和它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包
//累加器
func AddUpper() (func (int) int) { //AddUpper是一个函数,返回的数据类型是fun(int)int
//可以这么理解:闭包是类,函数是操作,n是字段。函数和它使用到n构成闭包
var n int = 10 //5-9 闭包的说明
return func(x int) int { //返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成一个整体,构成闭包
n = n + x
return n
}
}
func main() {
//使用前面的代码
f := AddUpper()
fmt.Println(f(1)) // 11 //当反复的调用f函数时,因为n是初始化一次,因此每调用一次就进行累计
fmt.Println(f(2)) //13
fmt.Println(f(3)) //16
}
闭包的最佳实践
编写一个程序,具体要求如下:
1) 编写一个函数makeSuffix(suffix string) 可以接收一个文件后缀名(比如.jpg),并返回一个闭包
2) 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg),则返回 文件名.jpg,如果已经有.jpg后缀,则返回原文件名
3) 要求使用闭包的方式完成
4) strings.HasSuffix,该函数可以判断某个字符串是否有指定的后缀
函数的defer
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制)
当Go执行到一个defer时,不会立即执行defer后的语句,而是将defer后的语句压入到一个栈中【暂时称该栈为defer栈】,然后继续执行函数下一个语句
当函数执行完毕后,再从defer栈中,依次从栈顶取出语句执行(注:遵守栈 先入后出的机制),所以看到前面案例输出的顺序
在defer将语句放入到栈时,也会将相关的值拷贝同时入栈
defer的最佳实践
defer最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源。
在Go编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源),可以执行defer file.Close() defer connect.Close()
在defer后,可以继续使用创建资源
当函数完毕后,系统会依次从defer栈中,取出语句,关闭资源
这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心
时间和日期相关函数
在编程中,程序员会经常使用到日期相关的函数,比如:统计某段代码指向性花费的时间等等
时间和日期相关函数,需要导入time包
Time.Time 类型,用于表示时间
func main() {
//看看日期和时间相关函数和方法使用
//获取当前时间
now := time.Now()
fmt.Printf("now = %v now type = %T",now, now)
//输出:now = 2019-10-21 11:27:13.0130756 +0800 CST m=+0.005984201 now type = time.Time
}
如何获取到其它的日期信息
func main() {
//看看日期和时间相关函数和方法使用
//获取当前时间
now := time.Now()
fmt.Printf("now = %v now type = %T
",now, now)
//输出:now = 2019-10-21 11:27:13.0130756 +0800 CST m=+0.005984201 now type = time.Time
fmt.Printf("年 = %v
",now.Year())
fmt.Printf("月 = %v
",now.Month())
fmt.Printf("月 = %v
",int(now.Month()))
fmt.Printf("日 = %v
",now.Day())
fmt.Printf("时 = %v
",now.Hour())
fmt.Printf("分 = %v
",now.Minute())
fmt.Printf("秒 = %v
",now.Second())
//输出:年 = 2019
//月 = October
//月 = 10
//日 = 21
//时 = 11
//分 = 31
//秒 = 38
格式化日期时间
方式1:就是使用Printf或者Sprintf
func main() {
//看看日期和时间相关函数和方法使用
//获取当前时间
now := time.Now()
//格式化日期时间
fmt.Printf("当前年月日 %d-%d-%d %d:%d:%d
",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second())
dateStr := fmt.Sprintf("当前年月日 %d-%d-%d %d:%d:%d
",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second())
fmt.Printf("dateStr = %v
",dateStr)
//输出:当前年月日 2019-10-21 11:37:4
//dateStr = 当前年月日 2019-10-21 11:37:4
方式2:使用time.Format()方法完成
func main() {
//看看日期和时间相关函数和方法使用
//获取当前时间
now := time.Now()
fmt.Printf("now = %v now type = %T
",now, now)
//格式化日期时间的第二种方式
fmt.Printf(now.Format("2006-01-02 15:04:05"))
fmt.Println()
fmt.Printf(now.Format("2006-01-02"))
fmt.Println()
fmt.Printf(now.Format("15:04:05"))
fmt.Println()
//输出:2019-10-21 11:40:37
//2019-10-21
//11:40:37
}
对上面代码的说明
"2006-01-02 15:04:05" 这个字符串各个数字可以自由自合,这样可以按程序需求来返回时间和日期
时间的常量
const (
Nanosecond Duration = 1 //纳秒
Microsecond = 1000 * Nanosecond //微秒
Millisecond = 1000 * Microsecond //毫秒
Second = 1000 * Millisecond //秒
Minute = 60 * Second //分钟
Hour = 60 * Minute //小时
)
常量的作用:在程序中可用于获取指定时间单位的时间,比如想得到100毫秒
100 * time.Millisecond
结合Sleep来使用一下时间常量
func main() {
//每隔1秒打印一个数字,打印到100时就退出
i := 0
for {
i++
fmt.Println(i)
//休眠
//time.Sleep(time.Second)
time.Sleep(time.Millisecond * 100 )
if i == 100 {
break
}
}
}
time的Unix和UnixNano的方法
func main() {
//看看日期和时间相关函数和方法使用
//获取当前时间
now := time.Now()
//Unix和UnixNano的使用
fmt.Printf("unix时间戳 = %v unixnano时间戳 = %v
",now.Unix(),now.UnixNano())
//unix时间戳 = 1571641108 unixnano时间戳 = 1571641108537040600
时间和日期练习题
编写一段代码来统计 函数test 执行的时间
方法1:在开发的过程中,我们常常需要知道执行某一块代码需要消耗的时间,这有利于我们知道代码的执行效率一遍对其进行优化,我们一般就是在计算开始前设置一个起始时间,再在该块代码执行结束的地方设置一个结束时间,结束时间与开始时间的差值就是该快代码执行所消耗的时间。在Go语言中可以使用time包中的Now()和Sub()函数:
func main() {
start := time.Now()
test()
end := time.Now()
result := end.Sub(start)
fmt.Printf("该函数执行完成耗时: %s
", result)
}
func test() {
sum := 0
for i := 0; i < 100000000; i++ {
sum += i
}
}
方法2
func test() {
str := ""
for i := 0; i < 100000; i++ {
str += "hello" + strconv.Itoa(i)
}
}
func main() {
//在执行test前,先获取到当前的unix时间戳
start := time.Now().Unix()
test()
end := time.Now().Unix()
fmt.Printf("执行test()耗费时间为%v秒
",end - start)
}
内置函数
Go设计者为了编程方便,提供了一些函数,这些函数可以直接1使用,我们成为Go的内置函数。文档:https://studygolang.com/pkgdoc -> builtin
len:用来求长度,比如string、array、slice、map、channel
new:用来分配内存,主要用来分配值类型,比如int、float32、struct...返回的是指针
func main() {
num1 := 100
fmt.Printf("num1 的类型 %T , num1 的值=%v , num1 的地址%v
",num1, num1, &num1)
num2 := new(int) //* int
//num2的类型%T = > *int
//num2的值 = 地址 0x04204c098 这个地址是系统分配
//num2的地址%v = 地址 0xc04206a020 这个地址是系统分配
*num2 = 100
fmt.Printf("num2 的类型%T ,num2的值 = %v ,num2的地址%v
num2这个指针,指向的值=%v",num2,num2,&num2,*num2)
}
make:用来分配内存,主要用来分配引用类型,比如channel、map、slice 【放到后面再学习,毕竟需要管道、map、slice 的基础】
append -- 用来追加元素到数组、slice中,返回修改后的数组、slice
close -- 主要用来关闭channel
delete -- 从map中删除key对应的value
panic -- 停止常规的goroutine (panic和recover:用来做错误处理)
recover -- 允许程序定义goroutine的panic动作
imag -- 返回complex的实部 (complex、real imag:用于创建和操作复数)
real -- 返回complex的虚部
cap -- capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
copy -- 用于复制和连接slice,返回复制的数目
print、println -- 底层打印函数,在部署环境中建议使用 fmt 包
错误处理
在默认情况下,当发生错误后(panic),程序就会退出(崩溃)
如果希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。还可以在捕获到错误后,给管理员一个提示(邮件,短信 ...)
Go语言追求简洁优雅,所以,Go语言不支持传统的 try ... catch ... finally 这种处理
Go中引入的处理方式为:defer、panic、recover
这几个异常的使用场景可以这么简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理
使用defer+recover来处理错误
func test() {
//使用defer + recover来捕获处理异常
defer func() {
err := recover() //recover()内置函数,可以捕获到异常
if err != nil { //说明捕获到错误
fmt.Println("err = ",err)
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("res = ", res)
}
func main() {
//测试
test()
for {
fmt.Println("main()下面的代码...")
time.Sleep(time.Second)
}
}
//输出:err = runtime error: integer divide by zero
//main()下面的代码...
//main()下面的代码...
//...
错误处理的好处
进行错误处理后,程序不会轻易挂掉,如果加入预警代码,就可以让程序更加的健壮
func test() {
//使用defer + recover来捕获处理异常
defer func() {
err := recover() //recover()内置函数,可以捕获到异常
if err != nil { //说明捕获到错误
fmt.Println("err = ",err)
//这里就可以将错误信息发送给管理员...
fmt.Println("发送邮件给admin@zisefeizhu.com")
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("res = ", res)
}
func main() {
//测试
test()
for {
fmt.Println("main()下面的代码...")
time.Sleep(time.Second)
}
}
//输出:err = runtime error: integer divide by zero
//发送邮件给admin@zisefeizhu.com
//main()下面的代码...
//main()下面的代码...
//...
自定义错误
Go程序中,也支持自定义错误,使用errors.New 和 panic 内置函数
errors.New("错误说明"),会返回一个error类型的值,表示一个错误
panic内置函数,接收一个interface{}类型的值(也就是任何值)作为参数。可以接收error类型的变量,输出错误信息,并退出程序
import (
"errors"
"fmt"
)
//函数去读取以配置文件init.conf的信息
//如果文件名传入不正确,就返回一个自定义的错误
func readConf(name string) (err error) {
if name == "config.ini" {
//读取...
return nil
} else {
//返回一个自定义错误
return errors.New("读取文件错误...")
}
}
func test() {
err := readConf("config2.ini")
if err != nil {
//如果读取文件发送错误,就输出这个错误,并终止程序
panic(err)
}
fmt.Println("test()继续执行...")
}
func main() {
//测试自定义错误的使用
test()
fmt.Println("main()下面的代码...")
}
//输出: panic: 读取文件错误...
//
//goroutine 1 [running]:
//main.test()
// E:/gostudent/src/2020-04-02/main/main.go:24 +0x61
//main.main()
// E:/gostudent/src/2020-04-02/main/main.go:31 +0x29
函数练习
- 函数可以没有返回值,编写一个函数,从终端输入一个整数打印出对应的金字塔
思路分析:就是将原来写的打印金字塔的案例,使用函数的方式封装,在需要打印时,直接调用即可
//将打印金字塔的代码封装到函数中
func printPyramid(totoalLevel int) {
//i表示层数
for i := 1; i <= totoalLevel; i++ {
//在打印*前先打印空格
for k := 1; k <= totoalLevel-i; k++ {
fmt.Print(" ")
}
//j 表示每层打印多少*
for j := 1; j <= 2*i-1; j++ {
if j == 1 || j == 2*i-1 || i == totoalLevel {
fmt.Print("*")
} else {
fmt.Print(" ")
}
}
fmt.Println()
//for i := 1; i <= totoalLevel ; i++ {
// //在打印*前先打印空格
// for k := 1; k <= totoalLevel - i; k++ {
// fmt.Println(" ")
// }
// //j表示每层打印多少*
// for j := 1; j <= 2 * i -1 ; j++ {
// fmt.Println("*")
// }
// fmt.Println()
//}
}
}
func main(){
//调用printPyramid函数,就可以打印金字塔
//从终端输入一个整数打印出对应的金字塔
var n int
fmt.Println("请输入打印金字塔的层数")
fmt.Scanln(&n)
printPyramid(n)
}
- 编写一个函数,从终端输入一个整数(1-9),打印出对应的乘法表
思路分析:就是将原来写的调用九九乘法表的案例,使用函数的方式封装,在需要打印时,直接调用即可
//编写一个函数调用九九乘法表
func printMulti(num int) {
//打印出九九乘法表
//i 表示层数
for i := 1; i <= num; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%v * %v = %v ",j,i,j * i)
}
fmt.Println()
}
}
func main() {
//从终端输入一个整数表示要打印的乘法表对应的数
var num int
fmt.Println("请输入九九乘法表的对应数")
fmt.Scanln(&num)
printMulti(num)
}
-
编写函数,对给定的一个二维数组(3 × 3)转置,这个题讲数组的时候再完成
-
:
循环打印输入的月份的天数。【使用continue实现】
要有判断输入的月份是否错误的语句
类似打印:
请输入年: 2019
请输入月:-1
月份不足...
5):
编写一个函数;随机猜数游戏;
有十次机会,如果第一次就猜中,提示:“你真是天才”
如果第2 -- 3次猜中,提示“你很聪明,赶上我了”
如果第4 -- 9次猜中,提示“一般般”
如果最后一次猜中,提示“可算猜对啦”
一次都没猜对,提示“说你点啥好呢”
6):
编写一个函数:输出100以内的所有素数(素数就只能被1和本身整除的数),每行显示5个;并求和
7):
编写一个函数,判断是打鱼还是晒网
中国有句俗话叫“三天打鱼两天晒网”。如果从1990年1月1日起开始执行“三天打鱼两天晒网”,如果判断在以后某一天是“打鱼”还是“晒网”
8):
打印如下效果
----------小小计算器----------
1. 加法
2. 减法
3. 乘法
4. 除法
0. 退出
请选择:1
10 + 5 = 15
----------小小计算器----------
......
9):
输出小写的a-z 以及大写的Z-A
总结
这部分是学好Go语言的重中之重,基础才是王道,很是疲劳的一天。
明天就是4月3号了,早清明。看天气预报说有雨,路上行人欲断魂,天上多了一颗星。有天我也会过去,不负此生是最坚强的倔强。路不止于此。