如果与当前变量重名的是外层代码中的变量,这意味着什么?
var s2 = string("out") func main() { var s string { s2 := string("internal") fmt.Println(s,s2) } fmt.Println(s2) } output: internal out 局部变量会隐藏外部变量使用关键字var和短变量声明,都可以实现对变量的“声明并赋值”。 前者可以被用在任何地方,而后者只能被用在函数或者其他更小的代码块中。 前者无法对已有的变量进行声明,就是无法处理新旧变量混在一起的情况。可以使用后者的变量重声明实现。 共同点是,都是基于“类型推断”。
package main import ( "flag" "fmt" ) func main() { var name string // [1] flag.StringVar(&name, "name", "everyone", "The greeting object.") // [2] // 方式1。 //var name = flag.String("name", "everyone", "The greeting object.") // 方式2。 //name := flag.String("name", "everyone", "The greeting object.") flag.Parse() fmt.Printf("Hello, %v!\n", name) // 适用于方式1和方式2。 //fmt.Printf("Hello, %v!\n", *name) } package main import ( "flag" "fmt" ) func main() { var name = getTheFlag() flag.Parse() fmt.Printf("Hello, %v!\n", *name) } func getTheFlag() *string { return flag.String("name", "everyone", "The greeting object.") } //上面函数的实现也可以是这样的。 //func getTheFlag() *int { // return flag.Int("num", 1, "The number of greeting object.") //}首先,会在当前代码块中查找变量。不包含任何的子代码块。 其次,如果当前代码块没有什么此变量名,一层一层往上层的代码块查找。 最后,如果都找不到,则编译器会报错。
不同代码块的变量可以重名,并且类型也可以不同。必要时,在使用之前,要先对变量的类型进行检查。
示例 下面代码中的container变量,虽然类型不同,但是都可以使用下标[0]、[1]、[2],获取到值:
package main import "fmt" var container = []string{"ZERO", "ONE", "TWO"} func main() { container := map[int]string{0: "zero", 1: "one", 2: "two"} fmt.Println(container[0], container[1], container[2]) }如果,要判断变量的类型,就要使用“类型断言”表达式。
类型断言 语法:x.(T)。 其中的x代表要被判断类型的那个值。T是要判断的类型。针对上面示例中的类型断言:
value, ok := interface{}(container).([]string) 上面是一条赋值语句,赋值符号的右边,就是一个类型断言表达式。 先把变量container的值转换为空接口的值interface{}(container)。然后再判断他的类型是否为后面.()中的类型。 有2个返回值,value和ok。ok是布尔类型,代码类型判断的结果:
如果是true,被判断的值自动转换为.()中的类型的值,并且赋值给value。 如果是false,value会赋值为nil,就是空。 不接收ok 这里ok也是可以没有的:
value := interface{}(container).([]string) 这样的话,如果类型不对,就是引发异常panic。
转为空接口的语法 在Go语言中,interface{}代表空接口。任何类型的值都可以很方便地被转换成空接口的值,语法:interface{}(x)。 一对不包裹任何东西的花括号,除了可以代表空的代码块之外,还可以用于表示不包含任何内容的数据结构(或者说数据类型)。
小括号中[]string是一个类型字面量。所谓类型字面量,就是用来表示数据类型本身的若干个字符。 比如:string是表示字符串类型的字面量,uint8是表示8位无符号整数类型的字面量。
优化示例代码 修改开始的示例,在打印前,先对变量的类型进行判断,只有map或切片类型才进行打印:
package main import "fmt" var container = []string{"ZERO", "ONE", "TWO"} func main() { container := map[int]string{0: "zero", 1: "one", 2: "two"} // 打印之前先要做判断,只有map或者切片类型才能通过 _, ok1 := interface{}(container).([]string) _, ok2 := interface{}(container).(map[int]string) if !(ok1 || ok2) { fmt.Printf("ERROR: 类型断言失败 %T\n", container) return } fmt.Println(container[0], container[1], container[2]) }另外还有一种switch语句的实现形式:
package main import "fmt" var container = []string{"ZERO", "ONE", "TWO"} func main() { container := map[int]string{0: "zero", 1: "one", 2: "two"} switch v := interface{}(container).(type) { case []string: fmt.Println("[]string:", v) case map[int]string: fmt.Println("map[int]string:", v) default: fmt.Printf("ERROR: 类型断言失败 %T\n", container) return } }类型转换的坑 类型转换表达式的语法:T(x)。 其中的x可以是一个变量,也可以是一个代表值的字面量(比如1.23和struct{}),还可以是一个表达式。如果是表达式,表达式的结果只能是一个值。 x被叫做源值,它的类型就是源类型。T代表的类型是目标类型。
对于整数类型值、整数常量之间的类型转换,原则上只要源值在目标类型的可表示范围内就是合法的。 上面说的只是语法上合法,但是转换后的结果可能是可坑。比如,如果源整数类型的可表示范围大,而目标类型的可表示范围小:
package main import "fmt" func main() { var srcInt = int16(-255) // 1111111100000001 dstInt := int8(srcInt) // 00000001,简单粗暴的截掉最前面的8位 fmt.Println(srcInt, dstInt) } /* 执行结果 PS H:\Go\src\Go36\article06\example04> go run main.go -255 1 PS H:\Go\src\Go36\article06\example04> */在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。补码就是原码的各位求反再加1。比如-255: 原码: 1000 0000 1111 1111 反码: 1111 1111 0000 0000 最高位是符号位,不反转。 补码: 1111 1111 0000 0001 类型转换的很简单粗暴,直接把最高的8位截掉,并不处理符号位,结果就是0000 0001,所以转换后的值就变成1了。
浮点类型转换 如果把浮点数转换为整数,则小数部分会被全部截掉:
package main import "fmt" func main() { var x = float64(1.9999999) y := int(x) fmt.Println(x, y) } /* 执行结果 PS H:\Go\src\Go36\article06\example05> go run main.go 1.9999999 1 PS H:\Go\src\Go36\article06\example05> */直接把一个整数值转换为一个string类型的值是可行的。但是,被转换的整数值应该是一个有效的Unicode码点,否则转换的结果将会是"�"。字符’�’的Unicode码点是U+FFFD。它是Unicode标准中定义的Replacement Character,专用于替换那些未知的、不被认可的以及无法展示的字符。无效的码点有很多,如果自己要搞一个测试,那么就用-1吧:
package main import "fmt" func main() { fmt.Println(string(-1)) // 一个无效的Unicode码点 fmt.Println(string(65)) // 字符A fmt.Println(string(24464)) // 中文 }一个值在从string类型转为[]byte类型时,其中UTF-8编码的字符串会被拆分成零散、独立的字节。这样只有ASCII码的那部分字符是一个字节代码一个字符的。而其他字符,比如中文(UTF-8里中文字符用3个字节表示),会被拆开成3个字节。而且由于UTF-8的长度是可变的,这样还要想办法判断那几个字节应该是一个字符。 可以转为[]rune类型,这样转换时,每个字符会被拆开成一个个的Unicode字符。
package main import "fmt" func main() { s := "你好" s1 := []byte(s) fmt.Println(s1) s2 := []rune(s) fmt.Println(s2) for _, v := range(s1) { fmt.Print(string(v)) // 乱码 } fmt.Println() for _, v := range(s2) { fmt.Print(string(v)) } fmt.Println() } /* 执行结果 PS H:\Go\src\Go36\article06\example07> go run main.go [228 189 160 229 165 189] [20320 22909] ä½ å¥½ 你好 PS H:\Go\src\Go36\article06\example07> */别名类型声明与类型再定义之间的区别,以及由此带来的它们的值在类型转换、判等、比较和赋值操作方面的不同。
别名类型 可以用关键字type声明自定义的各种类型。比如,可以声明别名类型:
type MyString = string 上面的声明语句表示,MyString是string类型的别名类型。别名类型与其源类型除了在名称上以外,都是完全相同的。别名类型主要是为了代码重构而存在的。 Go语言的基本类型中就存在两个别名类型。byte是uint8的别名类型,而rune是int32的别名类型。
潜在类型 另外一种声明:
type MyString2 string// 注意,这里没有等号 这种方式也可以被叫做对类型的再定义。这里MyString2是一个新的类型,和string是不同的类型。string可以被称为MyString2的潜在类型。 潜在类型相同的不同类型的值之间是可以进行类型转换的。因此,MyString2类型的值与string类型的值可以使用类型转换表达式进行互转。 但是,[]MyStrings 和 []string 是不同的潜在类型,不能做类型转换。 另外,即使是相同的潜在类型,也不能进行判等或比较,变量之间不能赋值。