IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码

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

IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码

编程两大绝招

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

2.先死后活

运算符

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

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

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

算术运算符

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

72_1.png

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值
72_2.png

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

72_3.png

进制转换规矩

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

72_5.png

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) 循环控制

顺序控制

72_6.png

分支控制

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

1) 单分支

2) 双分支

3) 多分支

单分支控制
72_7.png

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

双分支控制
72_8.png

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

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

多分支控制
72_9.png

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

72_10.png

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变量中实际指向的变量类型
72_11.png

switch和if的比较

1、 如果判断的具体数值不多,而且符号整数、浮点数、字符、字符串这几种类型。建议使用switch语句,简洁高效
2、 对区间判断和结果为bool类型的判断,使用if,if的使用范围更广

for循环控制

让一段代码循环的执行
72_12.png
72_13.png

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   !       紫   色   飞   猪

72_14.png
72_15.png
72_16.png

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

72_17.png

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语句。
72_18.png

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标签的使用规则一样
72_19.png

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语句,以免造成程序流程的混乱,使理解和调试程序都产生困难
72_20.png

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

72_21.png

随机生成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)值类型默认是值传递:变量直接存储值,内存通常在栈中分配
72_22.png

1、 引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收
72_23.png
2、 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。这个案例在前面详解函数使用注意事项时有写
72_24.png

变量作用域

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

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

如果变量是在一个代码块,比如for/if中,那么这个变量的作用域就在该代码块
72_25.png

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

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

包的本质实际上就是创建不同的文件夹,来存程序文件
72_26.png

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

包的三大作用

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

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

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

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

快速入门案例

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

PS:需要提前设置好GOPATH
72_27.png

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)
}

72_28.png

如果包名较长,Go支持给包取别名,注意细节:取别名后,原来的包名就不能使用了
72_29.png

函数的调用机制

72_30.png
72_31.png

函数的递归调用【重】

一个函数在函数体内又调用了本身,称为递归调用
72_32.png
72_33.png

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

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

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

1、 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。
72_35.png

7)Go函数不支持函数重载
72_36.png

1、 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用
72_37.png

9)函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且可以被调用
72_38.png

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、 支持对函数返回值命名
72_39.png
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函数
72_40.png

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函数时,执行的流程又是怎么样的呢?
72_41.png

匿名函数

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

匿名函数使用方式1

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

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

匿名函数使用方式2

将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
72_42.png

全局匿名函数

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

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
}

72_43.png

闭包的最佳实践

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

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

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

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

4) strings.HasSuffix,该函数可以判断某个字符串是否有指定的后缀
72_44.png

函数的defer

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

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

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

在defer将语句放入到栈时,也会将相关的值拷贝同时入栈
72_45.png

defer的最佳实践

defer最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源。
72_46.png

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

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

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

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

时间和日期相关函数

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

时间和日期相关函数,需要导入time包
72_47.png

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的方法
72_48.png

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)
}

72_49.png

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)转置,这个题讲数组的时候再完成
72_50.png
2、 :

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

文章永久链接:https://tech.souyunku.com/?p=38622


Warning: A non-numeric value encountered in /data/wangzhan/tech.souyunku.com.wp/wp-content/themes/dux/functions-theme.php on line 1154
赞(68) 打赏



未经允许不得转载:搜云库技术团队 » go语言系列-从运算符到函数

IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码
IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码

评论 抢沙发

大前端WP主题 更专业 更方便

联系我们联系我们

觉得文章有用就打赏一下文章作者

微信扫一扫打赏

微信扫一扫打赏


Fatal error: Uncaught Exception: Cache directory not writable. Comet Cache needs this directory please: `/data/wangzhan/tech.souyunku.com.wp/wp-content/cache/comet-cache/cache/https/tech-souyunku-com/index.q`. Set permissions to `755` or higher; `777` might be needed in some cases. in /data/wangzhan/tech.souyunku.com.wp/wp-content/plugins/comet-cache/src/includes/traits/Ac/ObUtils.php:367 Stack trace: #0 [internal function]: WebSharks\CometCache\Classes\AdvancedCache->outputBufferCallbackHandler() #1 /data/wangzhan/tech.souyunku.com.wp/wp-includes/functions.php(5109): ob_end_flush() #2 /data/wangzhan/tech.souyunku.com.wp/wp-includes/class-wp-hook.php(303): wp_ob_end_flush_all() #3 /data/wangzhan/tech.souyunku.com.wp/wp-includes/class-wp-hook.php(327): WP_Hook->apply_filters() #4 /data/wangzhan/tech.souyunku.com.wp/wp-includes/plugin.php(470): WP_Hook->do_action() #5 /data/wangzhan/tech.souyunku.com.wp/wp-includes/load.php(1097): do_action() #6 [internal function]: shutdown_action_hook() #7 {main} thrown in /data/wangzhan/tech.souyunku.com.wp/wp-content/plugins/comet-cache/src/includes/traits/Ac/ObUtils.php on line 367