Go36-4,5,6-变量

上篇

Go语言中的程序实体包括变量、常量、函数、结构体和接口。Go语言是静态类型的编程语言,所以我们在声明变量或常量的时候都需要指定它们类型,或者给予足够的信息以使Go语言能够推导出它们的类型。

声明变量

声明变量的方式:

var name string
var name = "Adam"
name := "Bob"  // 短变量声明

第三种短变量声明只能在函数体内部使用。

知识点

类型推断
后两种在声明的同时还进行了赋值,没有显示的指定类型,而是利用了Go的类型推断。

代码重构
我们通常把“不改变某个程序与外界的任何交互方式和规则,而只改变其内部实现”的代码修改方式,叫做对该程序的重构。

代码块
在Go语言中,代码块一般就是一个由花括号括起来的区域,里面可以包含表达式和语句。
Go语言本身以及我们编写的代码共同形成了一个非常大的代码块,也叫全域代码块。

空代码块

func main() {}

变量重声明

变量重声明,对已经声明过的变量再次声明:

  1. 由于变量的类型在其初始化时就已经确定了,所以对它再次声明时赋予的类型必须与其原本的类型相同,否则会产生编译错误。
  2. 变量的重声明只能发生在某一个代码块中。如果与当前的变量重名的是外层代码块中的变量,那么就是另外一种含义了。
  3. 变量的重声明只有在使用短变量声明时才会发生,否则也无法通过编译。如果要声明新变量,就使用var关键字声明,用新的变量名。
  4. 被“声明并赋值”的变量必须有多个,并且其中至少有一个是新的变量。这时才可以说对其中的旧变量进行了重声明。

重声明只在短变量声明中出现,并且是多个变量的声明中出现。给新的变量赋值,给旧的变量赋新值。
变量重声明,允许我们再使用短变量声明时不用理会被赋值的多个变量中是否有包含旧变量。好处是写代码时的便利。

package main

import (
    "os"
    "io"
    "fmt"
)

func main() {
    n1, err := io.WriteString(os.Stdout, "Test1")
    if err != nil {
        fmt.Printf("ERROR: %v\n", err)
    }
    fmt.Println("写入字节(byte)数:", n1)

    n2, err := io.WriteString(os.Stdout, "测试2")  // 对err进行了重声明
    if err != nil {
        fmt.Printf("ERROR: %v\n", err)
    }
    fmt.Println("写入字节(byte)数:", n2)

    n2, err = io.WriteString(os.Stdout, "测试三")  // 这里都是旧变量,没有新变量,所以用的是赋值=
    if err != nil {
        fmt.Printf("ERROR: %v\n", err)
    }
    fmt.Println("写入字节(byte)数:", n2)
}

小结

使用关键字var和短变量声明,都可以实现对变量的“声明并赋值”。
前者可以被用在任何地方,而后者只能被用在函数或者其他更小的代码块中。
前者无法对已有的变量进行声明,就是无法处理新旧变量混在一起的情况。可以使用后者的变量重声明实现。
共同点是,都是基于“类型推断”。

中篇

一个程序实体的作用域总是会被限制在某个代码块中。而这个作用域最大的用处,就是对程序实体的访问权限的控制。对“高内聚,低耦合”这种程序设计思想的实践恰恰可以从这里开始。

变量作用域

变量重名的示例:

package main

import "fmt"

var block = "package"

func main() {
    block := "function"
    {
        block := "inner"
        fmt.Printf("block here is %s\n", block)
    }
    fmt.Printf("block here is %s\n", block)
}

/* 执行结果
PS H:\Go\src\Go36\article05\example01> go run main.go
block here is inner
block here is function
PS H:\Go\src\Go36\article05\example01>
*/

上面的代码中有4个代码块:

  • 全域代码块
  • main包代表的代码块,var block = "package"
  • main函数代表的代码块,block := "function"
  • main函数中用大括号包起来的代码块,block := "inner"

在后3个代码块中都声明了一个block的变量,赋值为不同的字符串。
声明重名的变量是无法编译通过的,但是这是对同一代码块内部而言的。上面的例子中是在不同的代码块中进行的声明。

引用变量时的查找过程
首先,会在当前代码块中查找变量。不包含任何的子代码块。
其次,如果当前代码块没有什么此变量名,一层一层往上层的代码块查找。
最后,如果都找不到,则编译器会报错。

下篇

不同代码块的变量可以重名,并且类型也可以不同。必要时,在使用之前,要先对变量的类型进行检查。

示例

下面代码中的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 是不同的潜在类型,不能做类型转换。
另外,即使是相同的潜在类型,也不能进行判等或比较,变量之间不能赋值。

原文地址:http://blog.51cto.com/steed/2338528

时间: 2024-10-09 13:51:15

Go36-4,5,6-变量的相关文章

前端面试合集

VUE 1.什么是 vue 生命周期 2.vue生命周期的作用是什么 3.第一次页面加载会触发哪几个钩子 4.简述每个周期具体适合哪些场景 5.created和mounted的区别 6.vue获取数据在哪个周期函数 7.请详细说下你对vue生命周期的理解? vue路由面试题 mvvm 框架是什么?vue-router 是什么?它有哪些组件 active-class 是哪个组件的属性?怎么定义 vue-router 的动态路由? 怎么获取传过来的值vue-router 有哪几种导航钩子?6.$ro

Linux下修改环境变量PATH

1.什么是环境变量(PATH) 在Linux中,在执行命令时,系统会按照PATH的设置,去每个PATH定义的路径下搜索执行文件,先搜索到的文件先执行. 我们知道查阅文件属性的指令ls 完整文件名为:/bin/ls(这是绝对路径), 那你会不会觉得很奇怪:"为什么我可以在任何地方执行/bin/ls这个指令呢? " 为什么我在任何目录下输入 ls 就一定可以显示出一些讯息而不会说找不到该 /bin/ls 指令呢? 这是因为环境变量 PATH 的帮助所致呀! 当我们在执行一个指令癿时候,举例

Tomcat启动分析(我们为什么要配置CATALINA_HOME环境变量)

原文:http://www.cnblogs.com/heshan664754022/archive/2013/03/27/2984357.html Tomcat启动分析(我们为什么要配置CATALINA_HOME环境变量) 用文本编辑工具打开用于启动Tomcat的批处理文件startup.bat,仔细阅读.在这个文件中,首先判断CATALINA_HOME环境变量是否为空,如果为空,就将当前目录设为CATALINA_HOME的值.接着判断当前目录下是否存在bin\catalina.bat,如果文件

JavaScript的进阶之路(二)函数简介,变量、作用域和内存问题

<h3>ECMAScript中函数不存在函数签名的概念,没有重载</h3><h3>无需指定返回值,可以在任何时候返回任何值.未指定返回值的函数,返回的是一个特殊的undefined值</h3> <script type="text/javascript"> function sayHi(){ console.log("Hi"); }; sayHi(); function sayName(name,age){

Linux下修改.bash_profile 文件改变PATH变量的值

Linux中含有两个重要的文件 /etc/profile和$HOME/.bash_profile 每当系统登陆时都要读取这两个文件,用来初始化系统所用到的变量,其中/etc/profile是超级用户所用,$HOME/.bash_profile是每个用户自己独立的,我们可以修改该文件来设置一些变量. 命令用法如下 $ cd (进入用户登陆目录) $ls –al .bash_profile(.bash_profile为隐藏文件,因此要用ls –a命令查找) $vi .bash_profile(用vi

linux安装maven及配置环境变量 配图

Maven 3.5.0 maven安装和环境变量的配置 1 下载 maven 链接:http://pan.baidu.com/s/1qXXjXfe 密码:r92r 2 解压安装包 tar zvxf apache-maven-3.5.0-bin.tar.gz 3  配置maven环境变量 vi  /etc/profile   编辑系统配置文件 #set Maven environmentexport MAVEN_HOME=/usr/local/software/dir-maven/apache-m

20.1 Shell脚本介绍;20.2 Shell脚本结构和执行;20.3 date命令用法;20.4 Shell脚本中的变量

20.1 Shell脚本介绍 1. shell是一种脚本语言 aming_linux blog.lishiming.net 2. 可以使用逻辑判断.循环等语法 3. 可以自定义函数 4. shell是系统命令的集合 5. shell脚本可以实现自动化运维,能大大增加我们的运维效率 20.2 Shell脚本结构和执行 1. 开头(首行)需要加: #!/bin/bash 2. 以#开头的行作为解释说明: 3. 脚本的名字以.sh结尾,用于区分这是一个shell脚本 4. 执行.sh脚本方法有两种:

[转]表变量和临时表的比较

本文转自;http://www.cnblogs.com/CareySon/archive/2012/06/11/TableVariableAndTempTable.html 关于表变量是什么(和表变量不是什么),以及和临时表的比较让很多人非常困惑.虽然网上已经有了很多关于它们的文章,但我并没有发现一篇比较全面的.在本篇文章中,我们将探索表变量和临时表是什么(以及不是什么),然后我们通过使用临时表和表变量对其解密. 表变量 表变量在SQL Server 2000中首次被引入,那么,什么是表变量呢?

Java的成员变量初始化

对于方法里面的成员变量,Java要求程序员强制提供一个初始化的值.比如下面这个方法就会出错: public class Breakyizhan{ public void Z(){ int z; z++; } public static void main(String[] args) { Breakyizhan B = new Breakyizhan(); B.Z(); } } /* (www.breakyizhan.com) 输出结果是: 编译会出错,方法变量没有初始化 */ 而对于类的数据,

关于makefile中变量的多次赋值以及override指令

1 基本原则如下 1.1 原则1 变量的普通赋值是有先后顺序的,后面的赋值会覆盖掉前面的赋值. 1.2 原则2 使用的时候,用的是其前面最后的赋值,就算其后面有使用了override指令的赋值也不会影响这条原则. 1.3 原则3 当使用了override指令定义赋值了变量后,其后对该变量的所有的赋值都是无效的.但是override之前的所有的赋值都是有效的.使用的时候是往前最近原则. 2 override变量.命令行参数和普通变量之间的屏蔽关系 override变量会屏蔽命令行参数,除非用+=: