go语言学习(第三章,表达式)(go 语言学习笔记)

3.1 保留字

Go语言仅25个保留关键字,关键字不能用作常量、变量、函数名、以及结构体等标识符

3.2 运算符

除位移运算符外,操作数据类型必须相同。如果其中一个是无显示类型声明的常量,那么它会自动转型

package main

import "fmt"

func main() {
	const v = 20
	var a byte = 10
	d := a + v
	fmt.Printf("%T , %v\n", d, d)
	const c float32 = 1.2
	e := v + c
	fmt.Printf("%T , %v\n", e, e)
}
结果:
uint8 , 30
float32 , 21.2

位移右操作符必须是无符号整数,或可以转换的无显示类型常量

package main

func main() {
	b := 23
	x := 1 << b		//invalid operation: 1 << b (shift count type int, must be unsigned integer)
	println(x)
}
自增

自增、自减不再是运算符。只能作为独立语句,不能用于表达式

package main

func main() {
	a := 1
	//++a 	//syntax error: unexpected ++, expecting }
	//if (a++) >1{		//syntax error: unexpected ++, expecting )

	//}
	p := &a
	*p++
	println(a)		//2
}

指针

不能将内存地址与指针混为一谈
内存地址是内存中每个字节单元的唯一编号,而指针会分配内存空间,相当于一个专门用来保存地址的整型变量

  • 取地址运算符“&”用于获取对象地址
  • 指针运算符“*”用于间接引用目标对象
  • 二级指针**T,如包含包名则写成*package.T
    并非所有对象都能进行取地址操作,但变量总是能正确返回(addressable)。指针运算符为左值时,我们可更新目标对象状态,而为右值时则是为了获取目标状态
package main

func main() {
	x := 10
	var p *int = &x 
	*p += 20
	println(x,&x,p,*p)
}
result:
30 0xc00002df80 0xc00002df80 30
package main

func main() {
	var m = make(map[string]int)
	m["a"] = 1
	println(&m["a"])	//cannot take the address of m["a"]

}

指针类型支持相等运算符,但不能做加减法运算和类型转换。如果两个指针指向同一地址,或都为nil,那么他们相等。

package main

func main() {
	x := 10
	p := &x
	//p ++	//	 invalid operation: p++ (non-numeric type *int)
	//var p2 *int = p + 1	// invalid operation: p + 1 (mismatched types *int and int)
	p2 := &x
	println(p == p2)	//true
}

可通过unsafe.Pointer 将指针转换为uintptr后进行加减法运算,但可能会造成非法访问。
Pointer 类似C语言中的void*万能指针,可用来转换指针类型。它能安全持有对象或对象成员,但uintptr不行,后者仅是一种特殊整型,并不能引用目标对象,无法阻止垃圾回收器回收对象内存。
指针没有专门指向成员的“->”运算符,统一使用“.”选择表达式

package main

func main() {
	x := struct{
		a int
	}{}
	x.a = 100
	p := &x
	p.a += 100
	println(p.a)	//200
}

零长度对象的地址是否相等和具体的实现版本有关,不过,肯定不等于nil

package main

func main() {
	var a, b struct{}
	println(&a, &b)		//0xc00002df88 0xc00002df88
	println(&a == &b, &a == nil)	//false false

}

即便长度是0,可该对象依然是“合法的存在”,拥有合法内存地址,这与nil语义完全不同。

3.3 初始化

对复合类型(数组、切片、字典、结构体)变量初始化时,有一些语法限制。

  • 初始化表达式必须包含类型标签。
  • 左花括号必须在类型尾部,不能另起一行
  • 多个成员初始值以逗号分开
  • 允许多行,但每行须以逗号或右花括号结束
package main

import "fmt"

func main() {
	type data struct {
		x int
		y string
	}
	var a data = data{1, "s"}
	b := data{
		1,
		"asdf",
	}
	c := []int{
		1,
		2,
		3,
	}
	d := []int{1, 2, 3,
		4,
		5,
	}
	fmt.Println(a, b, c, d)

}

3.4 流程控制

GO 精简了流控制语句,虽然某些时候不够便捷,但够用

package main

func main() {
	x := 3
	if x > 5 {
		println("a")
	} else if x <= 5 && x > 0 {
		println("b")
	} else {
		println("c")
	}
}

比较特别的是对初始化语句的支持,可定义块局部变量或执行初始化函数。

package main

func xint() {
	println("a")
}
func main() {
	x := 10
	if xint(); x == 0 {
		println("b")
	}
	if a, b := x+1, x+10; a < b {
		println(a)
	} else {
		println(b)
	}
}
结果
a
11

局部变量的有效范围包括整个if/else块
死代码是永远不会被执行的代码,可使用专门的工具或用代码覆盖率测试
尽量减少代码块嵌套,让逻辑出于正常水平

package main

import (
	"errors"
	"log"
)

func check(x int) error {
	if x <= 0 {
		return errors.New("x < 0")
	}
	return nil
}
func main() {
	x := 10
	if err := check(x); err == nil {
		x++
		println(x)
	} else {
		log.Fatalln(err)
	}
}

该例中,if块显然承担了两种逻辑:错误处理和后续正常操作。基于重构原则,我们应保持代码功能的单一性。

func main() {
	x := 10
	if err := check(x); err != nil {
		log.Fatalln(err)
	}
	x++
	println(x)
}

如此,if块仅完成条件检查和错误处理,相关正常逻辑保持在同一层次。当有人试图通过这段代码来获知逻辑流程时,完全可以忽略if块细节。同时,单一功能可提升代码可维护性,更利于拆分重构。
当然,如须在多个条件块中使用局部变量,那么只能保留原层次,或直接使用外部变量

package main

import (
	"log"
	"strconv"
)
func main() {
	s := "g"
	n, err := strconv.ParseInt(s, 10, 64)
	if err != nil {
		log.Fatalln(err)
	} else if n < 0 || n > 10 {
		log.Fatalln("invalid number")
	}
	println(n)
}

对于某些过于复杂的组合条件,建议将其重构为函数

package main

import (
	"log"
	"strconv"
)

func main() {
	s := "g"
	if n, err := strconv.ParseInt(s, 10, 64); err != nil || n < 0 || n > 10 || n%2 != 0 {
		log.Fatalln("invalid number")
	}
	println("ok")
}
package main

import (
	"errors"
	"log"
	"strconv"
)

func check(s string) error {
	n, err := strconv.ParseInt(s, 10, 64)
	if err != nil || n < 0 || n > 10 || n%2 != 0 {
		return errors.New("invalid num")
	}
	return nil
}
func main() {
	s := "g"
	if err := check(s); err != nil {
		log.Fatalln(err)
	}
	println("ok")

}

将流程和局部细节分离是很常见的做法,不同的变化因素被分隔在各自独立单元内,可避免修改时造成关联错误,减少“肥胖症”的函数数量。当然,代码单元测试也是主要原因之一。另一方面,该示例中的函数check仅被if模块调用,也可以将其作为局部函数,以避免扩大作用域,只是对测试的友好度会差一些

switch

与if语句类似,switch语句也用于选择执行,但具体使用的场景会有所不同

package main

func main() {
	a, b, c, x := 1, 2, 3, 2
	switch x {
	case a, b:
		println("a | b")
	case c:
		println("c")
	case 4:
		println("d")
	default:
		println("z")
	}

}

条件表达式支持非常量值,这要比C更加灵活。相比if表达式,switch值列表要更加直观
编译器对if、switch生成的机器指令可能完全相同,所谓谁性能更好须看具体情况,不能作为主观判断条件。
switch同样支持初始化语句,按从上到下,从左到右顺序匹配case执行,只有全部匹配失败时才会执行default块

package main

func main() {
	switch x := 5; x {
	default:		// 不会先执行default
		x += 100
		println(x)
	case 5:
		x += 50
		println(x)
	}
}
结果:55

考虑到default与else类似,建议将其放在switch末尾
相邻的空case不构成多条件匹配

package main

func main() {
	switch x := 10; x {
	case 10:		// 单条件,内容为空。隐式:“case 10: break”
	case 20:
		println(20)
	}
}

不能出现重复的case值:

package main

func main() {
	switch x := 10; x {
	case 10:
		println(10)
	case 20, 10:		//duplicate case 10 in switc
		println(20)
	}
}

无需显示执行break语句,case执行完毕后制动中断。如需贯通后续case(源码顺序),
须执行fallthrough,但不再匹配后续条件表达式

package main

func main() {
	switch x := 5; x {
	default:
		println(x)
	case 5:
		x += 10
		println(x)		//15
		fallthrough		// 继续执行下一case,但不再匹配表达式
	case 6:
		x += 20
		println(x)		//35
		//fallthrough		//cannot fallthrough final case in switch
	}
}

注意,fallthrough 必须放在case块结尾,可使用break语句阻止
某些时候,switch语句还被用于替换if语句。被省略的switch条件表达式默认值为true,
继而与case比较表达式结果匹配

package main

func main() {
	switch x := 5; {
	case x > 5:
		println("a")
	case x > 0 && x < 5:
		println("b")
	default:
		println("c")
	}
}

for

package main

func main() {
	for i:=0 ; i< 10 ; i++{

	}
	i:=1
	for i<10 {		// 类似 “while x <10 {}”
		i++
	}
	for {
		break 
	}
}

初始化语句仅被执行一次。表达式中如果有函数调用,须确认是否会重复执行。可能会被编译器优化掉,也可能是动态结果须每次确认

package main

func count() int {
	println("count,")
	return 3
}
func main() {
	for i, c := 0, count(); i < c; i++ {
		println("a", i)
	}
	c := 0
	for c < count() {		// 这里的函数会重复执行
		println("b", c)
		c++
	}
}

规避方式就是在初始化表达式中定义局部变量保存count结果
可用for…range完成数据迭代,支持字符串、数组、数组指针、切片、字典、通道类型,返回索引、键值数据

package main

import (
	"fmt"
)

func main() {
	s := "asdfsdf"
	for i, j := range s {
		fmt.Println(i, j)
	}
	a := [3]string{"a", "b", "c"}
	for i, j := range a {
		fmt.Println(i, j)
	}
	b := []string{"x", "x", "x", "y"}
	for i, j := range b {
		fmt.Println(i, j)
	}
	e, f, g := 1, 2, 3
	d := [3]*int{&e, &f, &g}
	for i, j := range d {
		fmt.Println(i, j)
	}
	var h = make(map[string]int)
	h["a"] = 1
	h["b"] = 2
	h["c"] = 3
	h["d"] = 4
	for i, j := range h {
		fmt.Println(i, j)
	}

}

允许返回单值,或用“_”忽略。

package main

import (
	"fmt"
)

func main() {
	data := [3]string{"a", "b", "c"}
	for i := range data { // 忽略值
		fmt.Println(i)
	}
	for _, j := range data { // 忽略索引
		fmt.Println(j)
	}
	for range data { //仅迭代,不返回。可用来执行清空channel等操作

	}

}

无论是普通for循环,还是range迭代,其定义的局部变量都会重复使用

package main

import "fmt"

func main() {
	var data = [3]string{"a", "b", "c"}
	for i, s := range data {
		fmt.Println(&i, &s)
	}
}
0xc00004c058 0xc0000401c0
0xc00004c058 0xc0000401c0
0xc00004c058 0xc0000401c0

如果range目标表达式是函数调用,也仅被执行一次。

package main

func data() []int {
	return []int{1, 2, 3, 4, 5}
}
func main() {
	for i, x := range data() {
		println(i, x)
	}
}

goto,continue,break

使用goto前,须先定义标签。标签区分大小写,且未使用的标签会引发编译错误。

func main() {
	for i := 0; i < 3; i++ {
		println(i)
		if i >= 1 {
			goto exit
		}
	}
exit:
	println("exit.")
}
0
1
exit.

不能跳转到其他函数,或内层代码块内

  • break: 用于switch、for、select语句,终止,整个语句块执行。
  • continue:仅用于for循环,终止后续逻辑,立即进入下一轮循环
func main() {
	for i := 0; i < 10; i++ {
		if i%2 == 0 {
			continue
		}
		if i > 5 {
			break
		}
		println(i)
	}
}

配合标签,break,continue可在多层嵌套中指定目标层级

func main() {
outer:
	for x := 0; x < 5; x++ {
		for y := 0; y < 10; y++ {
			if y > 2 {
				println()
				continue outer
			}
			if x > 2 {
				break outer
			}
			print(x, ":", y, " ")
		}
	}
}
结果
0:0 0:1 0:2
1:0 1:1 1:2
2:0 2:1 2:2
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值