编程两大绝招

1.先易后难,即将一个复杂的问题分解成简单的问题

2.先死后活

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

运算符

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

运算符用于在程序运行时执行数学或逻辑运算

Go语言内置的运算符有:算术运算符、赋值运算符、逻辑运算符、关系运算符、位运算符、其他运算符

算术运算符

算术运算符是对数值类型的变量进行运算的,比如:加减乘除。在Go程序中使用的非常多

go语言系列-从运算符到函数 go 第1张

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天\n",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值 go语言系列-从运算符到函数 go 第2张

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

go语言系列-从运算符到函数 go 第3张

进制转换规矩

其它进制转十进制
	从最低位开始(右边的),将每个位上的数提取出来,乘以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

原码、反码、补码

对于有符号的而言:

  1. 二进制的最高位是符号位:0表示整数,1表示负数

1 ==》[0000 0001] -1 ==》 [1000 0001]

  1. 正数的原码、反码、补码都一样

  2. 负数的反码 = 它的原码符号位不变,其它位取反(0 → 1, 1 → 0)

1 ==》原码[0000 0001] 反码[0000 0001] 补码[0000 0001]

-1 ==》 原码[1000 0001] 反码[1111 1110]补码[1111 1111]

  1. 负数的补码 = 它的反码 + 1

  2. 0的反码,补码都是0

  3. 在计算机运算的时候,都是以补码的方式来运算的

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 go语言系列-从运算符到函数 go 第4张

go语言系列-从运算符到函数 go 第5张

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 \t 年龄是%v \t 薪水是 %v \t 是否通过考试%v \n ",name,age,sal,isPass)

	//方式2:fmt.Scanf,可以按指定的格式输入
	fmt.Println("请输入你的姓名,年龄,薪水,是否通过考试,使用空格隔开")
	fmt.Scanf("%s %d %f %t",&name,&age,&sal,&isPass)   //格式对应准确
	fmt.Printf("名字是 %v \t 年龄是 %v \t 薪水是 %v \t 是否通过考试 %v \n ",name,age,sal,isPass)
}

程序流程控制

在程序中,程序运行的流程控制决定程序是如何执行的,是必须掌握的,主要有三大流程控制语句

​ 1) 顺序控制

​ 2) 分支控制

​ 3) 循环控制

顺序控制

go语言系列-从运算符到函数 go 第6张

分支控制

分支控制就是让程序有选择执行。有下面三种形式

​ 1) 单分支

​ 2) 双分支

​ 3) 多分支

单分支控制 go语言系列-从运算符到函数 go 第7张

func main()  {
	//当条件表达式为true时,就会执行{}的代码。{}是必须有的,就算只写一行代码
	if age := 20;age > 18 {  //可以直接定义一个变量
		fmt.Println("你要对自己的行为负责")
	}
}

双分支控制 go语言系列-从运算符到函数 go 第8张

func main()  {
	if age := 20;age < 18 {  //当条件表达式成立,即执行代码块1,否则执行代码块2。{}也是必须有的
		fmt.Println("你要对自己的行为负责")
	} else {    //else的位置需要注意
		fmt.Println("你还小")
	}
}

双分支只会执行其中的一个分支

多分支控制 go语言系列-从运算符到函数 go 第9张

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 分支控制

go语言系列-从运算符到函数 go 第10张

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变量中实际指向的变量类型 go语言系列-从运算符到函数 go 第11张

switch和if的比较

  1. 如果判断的具体数值不多,而且符号整数、浮点数、字符、字符串这几种类型。建议使用switch语句,简洁高效

  2. 对区间判断和结果为bool类型的判断,使用if,if的使用范围更广

for循环控制

让一段代码循环的执行 go语言系列-从运算符到函数 go 第12张 go语言系列-从运算符到函数 go 第13张

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 \t", val)
	}
}
//h 	e 	l 	l 	o 	, 	  	w 	o 	r 	l 	d 	! 	  	紫 	色 	飞 	猪

go语言系列-从运算符到函数 go 第14张 go语言系列-从运算符到函数 go 第15张 go语言系列-从运算符到函数 go 第16张

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次

应用案例

  1. 统计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 \n",j, sum / float64(stuNum))
		//将各个班的总成绩累计到totalSum中
		totalSum += sum
	}
	fmt.Printf("各个班级的总成绩%v 所有班级的平均成绩是%v ",totalSum,totalSum/float64(stuNum * classNum))
}
  1. 打印金字塔

使用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()
      }
}

go语言系列-从运算符到函数 go 第17张

  1. 打印九九乘法表
func main()  {
   //打印出九九乘法表
   //1. 正表
   //var num int = 9
   //for i := 1; i <= num ; i++ {
   // for j := 1; j <= i; j++ {
   //    fmt.Printf("%v * %v = %v \t",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 \t",j,i,j * i)
      }
      fmt.Println()
   }
}

跳转控制语句-break

break语句用于终止某个语句块的执行,用于中断当前for循环或跳出switch语句。 go语言系列-从运算符到函数 go 第18张

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标签的使用规则一样 go语言系列-从运算符到函数 go 第19张

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

  1. Go语言的goto语句可以无条件地转移到程序中指定的行

  2. goto语句通常与条件语句配合使用。可用来实现条件转移,跳出循环体等功能

3)在Go程序设计中一般不主张使用goto语句,以免造成程序流程的混乱,使理解和调试程序都产生困难 go语言系列-从运算符到函数 go 第20张

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\t", 100 * rand.Float32())
   }
}

go语言系列-从运算符到函数 go 第21张

随机生成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)值类型默认是值传递:变量直接存储值,内存通常在栈中分配 go语言系列-从运算符到函数 go 第22张

  1. 引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收 go语言系列-从运算符到函数 go 第23张

  2. 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。这个案例在前面详解函数使用注意事项时有写 go语言系列-从运算符到函数 go 第24张

变量作用域

函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部

函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效

如果变量是在一个代码块,比如for/if中,那么这个变量的作用域就在该代码块 go语言系列-从运算符到函数 go 第25张

在实际的开发中,我们往往需要在不同的文件中,去调用其它文件的函数,比如main.go中,去使用utils.go文件中的函数,如何实现? -》包

现在有两个程序员共同开发一个Go项目,程序员A希望定义函数Cal,程序员B也想定义函数也叫Cal。两个程序员为此吵了起来,怎么实现? -》包

包的本质实际上就是创建不同的文件夹,来存程序文件 go语言系列-从运算符到函数 go 第26张

Go的每一个文件都是属于一个包的,也就是说Go是以包的形式来管理文件和项目目录结构的

包的三大作用

​ 1) 区分相同名字的函数、变量等标识符

​ 2) 当程序文件很多时,可以很好的管理项目

​ 3) 控制函数、变量等访问范围,即作用域

打包基本语法
	package 包名
引入包的基本语法
	import  “包的路径”

快速入门案例

包快速入门-Go相互调用函数,我们将func Cal定义到文件utils.go,将utils.go放到一个包中,当其它文件需要使用到utils.go的方法时,可以import 该包,就可以使用了。

PS:需要提前设置好GOPATH go语言系列-从运算符到函数 go 第27张

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语言系列-从运算符到函数 go 第28张

如果包名较长,Go支持给包取别名,注意细节:取别名后,原来的包名就不能使用了 go语言系列-从运算符到函数 go 第29张

函数的调用机制

go语言系列-从运算符到函数 go 第30张 go语言系列-从运算符到函数 go 第31张

函数的递归调用【重】

一个函数在函数体内又调用了本身,称为递归调用 go语言系列-从运算符到函数 go 第32张 go语言系列-从运算符到函数 go 第33张

函数递归调用需要遵守的重要原则

执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
函数的局部变量是独立的,不会相互影响
递归必须向退出递归的条件逼近,否则就是无限递归
当一个函数执行完毕,或者遇到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))
}

函数使用的注意事项和细节

  1. 函数的形参列表可以是多个,返回值列表也可以是多个

  2. 形参列表和返回值列表的数据类型可以是值类型和引用类型

  3. 函数的命名遵循标识符命名规范,首字符不能是数字,首字母大写该函数可以被本包文件和其它包文件使用,类似public,首字母小写,只能被本包文件使用,其它包文件不能使用,类似privat

4)函数中的变量是局部的,函数外不生效 go语言系列-从运算符到函数 go 第34张

5)基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值

  1. 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。 go语言系列-从运算符到函数 go 第35张

7)Go函数不支持函数重载 go语言系列-从运算符到函数 go 第36张

  1. 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用 go语言系列-从运算符到函数 go 第37张

9)函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且可以被调用 go语言系列-从运算符到函数 go 第38张

  1. 为了简化数据类型定义,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\n",a,getSum)
   res := a(10,40) //等价 res := getSum(10,40)
   fmt.Println("res = ",res)
   b := myFun
   fmt.Printf("b的类型%T\n",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)
}
  1. 支持对函数返回值命名 go语言系列-从运算符到函数 go 第39张

  2. 使用 _ 标识符,忽略返回值

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)
}
  1. 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函数的注意事项和细节

  1. 如果一个文件同时包含全局变量定义,init函数和main函数,则执行的流程:全局变量定义 --> init函数 --> main函数 go语言系列-从运算符到函数 go 第40张

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语言系列-从运算符到函数 go 第41张

匿名函数

Go支持匿名函数,匿名函数就是没有名字的函数,如果某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用

匿名函数使用方式1

在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。

func main()  {
   //使用匿名函数完成求两个数的和
   res := func(n1 , n2 int) int {
      return n1 + n2
   }(10,20)
   fmt.Println("res = ",res)
}

匿名函数使用方式2

将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数 go语言系列-从运算符到函数 go 第42张

全局匿名函数

如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效

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
}

go语言系列-从运算符到函数 go 第43张

闭包的最佳实践

编写一个程序,具体要求如下:

​ 1) 编写一个函数makeSuffix(suffix string) 可以接收一个文件后缀名(比如.jpg),并返回一个闭包

​ 2) 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg),则返回 文件名.jpg,如果已经有.jpg后缀,则返回原文件名

​ 3) 要求使用闭包的方式完成

​ 4) strings.HasSuffix,该函数可以判断某个字符串是否有指定的后缀 go语言系列-从运算符到函数 go 第44张

函数的defer

在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制)

当Go执行到一个defer时,不会立即执行defer后的语句,而是将defer后的语句压入到一个栈中【暂时称该栈为defer栈】,然后继续执行函数下一个语句

当函数执行完毕后,再从defer栈中,依次从栈顶取出语句执行(注:遵守栈 先入后出的机制),所以看到前面案例输出的顺序

在defer将语句放入到栈时,也会将相关的值拷贝同时入栈 go语言系列-从运算符到函数 go 第45张

defer的最佳实践

defer最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源。 go语言系列-从运算符到函数 go 第46张

在Go编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源),可以执行defer file.Close() defer connect.Close()

在defer后,可以继续使用创建资源

当函数完毕后,系统会依次从defer栈中,取出语句,关闭资源

这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心

时间和日期相关函数

在编程中,程序员会经常使用到日期相关的函数,比如:统计某段代码指向性花费的时间等等

时间和日期相关函数,需要导入time包 go语言系列-从运算符到函数 go 第47张

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 \n",now, now)
   //输出:now = 2019-10-21 11:27:13.0130756 +0800 CST m=+0.005984201 now type = time.Time
   fmt.Printf("年 = %v \n",now.Year())
   fmt.Printf("月 = %v \n",now.Month())
   fmt.Printf("月 = %v \n",int(now.Month()))
   fmt.Printf("日 = %v \n",now.Day())
   fmt.Printf("时 = %v \n",now.Hour())
   fmt.Printf("分 = %v \n",now.Minute())
   fmt.Printf("秒 = %v \n",now.Second())
   //输出:年 = 2019
   //月 = October
   //月 = 10
   //日 = 21
   //时 = 11
   //分 = 31
   //秒 = 38

格式化日期时间

方式1:就是使用Printf或者Sprintf
func main()  {
   //看看日期和时间相关函数和方法使用
   //获取当前时间
   now := time.Now()
   //格式化日期时间
   fmt.Printf("当前年月日 %d-%d-%d %d:%d:%d \n",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second())
   dateStr := fmt.Sprintf("当前年月日 %d-%d-%d %d:%d:%d \n",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second())
   fmt.Printf("dateStr = %v\n",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 \n",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的方法 go语言系列-从运算符到函数 go 第48张

func main()  {
   //看看日期和时间相关函数和方法使用
   //获取当前时间
   now := time.Now()
   //Unix和UnixNano的使用
   fmt.Printf("unix时间戳 = %v unixnano时间戳 = %v \n",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\n", 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秒\n",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 \n",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\n num2这个指针,指向的值=%v",num2,num2,&num2,*num2)
}

go语言系列-从运算符到函数 go 第49张

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

函数练习

  1. 函数可以没有返回值,编写一个函数,从终端输入一个整数打印出对应的金字塔

​ 思路分析:就是将原来写的打印金字塔的案例,使用函数的方式封装,在需要打印时,直接调用即可

//将打印金字塔的代码封装到函数中
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. 编写一个函数,从终端输入一个整数(1-9),打印出对应的乘法表

​ 思路分析:就是将原来写的调用九九乘法表的案例,使用函数的方式封装,在需要打印时,直接调用即可

//编写一个函数调用九九乘法表
func printMulti(num int)  {
   //打印出九九乘法表
   //i 表示层数
   for i := 1; i <= num; i++ {
      for j := 1; j <= i; j++ {
         fmt.Printf("%v * %v = %v \t",j,i,j * i)
      }
      fmt.Println()
   }
}
func main()  {
   //从终端输入一个整数表示要打印的乘法表对应的数
   var num int
   fmt.Println("请输入九九乘法表的对应数")
   fmt.Scanln(&num)
   printMulti(num)
}
  1. 编写函数,对给定的一个二维数组(3 × 3)转置,这个题讲数组的时候再完成 go语言系列-从运算符到函数 go 第50张

​ 循环打印输入的月份的天数。【使用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号了,早清明。看天气预报说有雨,路上行人欲断魂,天上多了一颗星。有天我也会过去,不负此生是最坚强的倔强。路不止于此。

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄