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


    编程两大绝招

    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
    

    原码、反码、补码

    对于有符号的而言:

    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

    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的比较

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

    2. 对区间判断和结果为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次

    应用案例

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

    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 	",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

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

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

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

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

    变量作用域

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

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

    如果变量是在一个代码块,比如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))
    }
    

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

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

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

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

    4)函数中的变量是局部的,函数外不生效

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

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

    7)Go函数不支持函数重载

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

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

    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
    ",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)
    }
    
    1. 支持对函数返回值命名

    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函数

    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
    

    函数练习

    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 	",j,i,j * i)
          }
          fmt.Println()
       }
    }
    func main()  {
       //从终端输入一个整数表示要打印的乘法表对应的数
       var num int
       fmt.Println("请输入九九乘法表的对应数")
       fmt.Scanln(&num)
       printMulti(num)
    }
    
    1. 编写函数,对给定的一个二维数组(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号了,早清明。看天气预报说有雨,路上行人欲断魂,天上多了一颗星。有天我也会过去,不负此生是最坚强的倔强。路不止于此。

  • 相关阅读:
    Selenium 面试题总结
    springbatch 给自定义的processor传递JobParameters中设置的参数
    spring batch 相关的九张表的初始化脚本
    springbatch 读取csv文件时 文件中有字符串需要转换为Date类型的字段报错字符串不能转化为Date类型的解决方法
    spring+quartz定时任务配置---MethodInvokingJobDetailFactoryBean
    springmvc+mybatis+html 下将查询数据以excell形式上传到ftp(下)
    springmvc+mybatis+html 下将查询数据以excell形式上传到ftp(上)
    入门 ARM 汇编(二)—— 寻址方式
    系统进程 zygote(二)—— zygote.rc 脚本
    系统进程 zygote(一)—— 概述
  • 原文地址:https://www.cnblogs.com/zisefeizhu/p/12622745.html
Copyright © 2020-2023  润新知