Go笔记-常量与变量

变量

变量声明

var v1 int
var v2 string
var v3 [10]int // 数组
var v4 []int   // 数组切片
var v5 struct {
  f int
}
var v6 *int
var v7 map[string]int
var v8 func(a int) int // 函数
var i int8 = 0 等同于 i := (int8)(0)
var i *int8 = nil 等同于 j := (*int8)(nil)
var min, max uint = 1, 5
var i, j, k int // 都是int,零值是0
var a, b, c = true, 2.3, "foo" // 自动推导类型
var f, err = os.Open(name) // 经过调用函数赋值

变量初始化

var v1 int = 10
var v2 = 10     // 编译器自动推导v2的类型
v3 := 10        // 编译器自动推导v2的类型

出如今 := 左侧的变量不该该是已经被声明过的,不然会致使编译错误,好比下面这个 写法:数组

var i int 
i := 2

会致使相似以下的编译错误:数据结构

no new variables on left side of :=

:= 不能在函数外使用,函数外的语法块必须以关键字开始ide

val := 4 // 错误
var val int32 = 4 // 正确

变量赋值

多重赋值功能,好比下面这个交换i和j变量的语句:函数

i, j = j, i

从新声明和从新赋值ui

a, err := f1.Close()
b, err := f2.Close()

err在两条语句中都出现了,第一次的err是声明,第二次的err只是从新赋值了,第二个语句中必须有至少一个变量是须要声明的,才能使用:=this


从新赋值与定义新同名变量的区别指针

s := "abs"
fmt.Println(&s)

s, y := "hello", 20 // 从新赋值,与前s在同一层次的代码块中,且有新的变量被定义
fmt.Println(&s, y)  // 一般函数多返回值 err 会被重复使用

{
	s, z := 1000, 30 // 定义新同名变量,不在同一层次代码块
	fmt.Println(&s, z)
}

输出:code

0xc08200c340
0xc08200c340 20
0xc08200c380 30

变量被从新赋值时不会改变地址ip

a := 100
b := "a"
fmt.Println(&a, &b)
a = 200
b = "b"
fmt.Println(&a, &b)

输出:内存

0xc08200c3a0 0xc08200c3b0
0xc08200c3a0 0xc08200c3b0

平行赋值

a[i], a[j] = a[j], a[i]

覆盖的问题: 在进行多个变量赋值时,编译器会先计算赋值语句右边全部的值,以后再进行赋值。

a[i] =1 , a[j] = 2
    a[i], a[j] = a[j], a[i]    <=>   a[i] ,a[j] = 2, 1

在赋值以前, 赋值语句右边的全部表达式将会先进行求值, 而后再统一更新左边变量的值.


来自[雨痕笔记]

多变量赋值时,先计算全部相关值,而后再从左到右依次赋值:

data, i := [3]int{0, 1, 2}, 0
i, data[i] = 2, 100 // (i=0) -> (i=2), (data[0] = 100)
fmt.Println(data)   // [100 1 2]

i被赋值成2以前,data[i]已经被计算成data[0]了。

再看一个右边是否也计算的例子:

data, i := [3]int{0, 2, 4}, 1
i, data[i] = data[i], 100
fmt.Println(i, data) // 2 [0 100 4]

也就是说在赋值以前,左右两边都计算过了。

匿名变量

假设GetName()函数的定义以下,它返回3个值,分别为firstName、lastName和nickName:

func GetName() (firstName, lastName, nickName string) { 
	return"May", "Chan", "Chibi Maruko" 
}

若只想得到nickName,则函数调用语句能够用以下方式编写:

_, _, nickName := GetName()

new()和make()

new:并不初始化内存,只是将其置零,并返回地址。如new(T),其返回一个指向新分配的 类型为T值为零的 指针。

t1 := new(T) // type *T
var t2 T     // type T

以后就能够直接使用t1,t2了。

但有时候须要一个初始化构造器:

t := new(T)
t.f1 = v1
t.f2 = v2

也可使用复合文字(composite literal):

t := T{v1, v2} // type T
t := &T{v1, v2} // type *T

复合文字的域按顺序排列,而且必须都存在。

经过field:value显式地为元素添加标号,则初始化能够按任何顺序出现,没有出现的则对应为零值

t := &T{f2:v2, f1:v1}

new(T)和&T{}是等价的,字段的值都是零值。

make:内建函数make(T, args)与new(T)的用途不同。它只用来建立slice,map和channel,而且返回一个初始化的(而不是置零),类型为T的值(而不是*T)。之因此有所不一样,是由于这三个类型的背后是象征着,对使用前必须初始化的数据结构的引用。例如,slice是一个三项描述符,包含一个指向数据(在数组中)的指针,长度,以及容量,在这些项被初始化以前,slice都是nil的。对于slice,map和channel,make初始化内部数据结构,并准备好可用的值. 如make([]int, 10, 100)表明分配一个有100个int的数组,而后建立一个长度为10,容量为100的slice结构,并指向数组前10个元素上。而new([]int)返回一个指向新分配的,被置零的slice结构体的指针,即指向nilslice值的指针。

这些例子阐释了new和make之间的差异。

var p *[]int = new([]int)       // allocates slice structure; *p == nil; rarely useful(不多使用)
var v  []int = make([]int, 100) // the slice v now refers to a new array of 100 ints

// Unnecessarily complex:(没必要要的组合)
var p *[]int = new([]int)
*p = make([]int, 100, 100)

// Idiomatic:(符合语言习惯的)
v := make([]int, 100)

记住make只用于map,slice和channel,而且不返回指针。要得到一个显式的指针,使用new进行分配,或者显式地使用一个变量的地址。


常量

字面常量

-12 
3.14159265358979323846 // 浮点类型的常量
3.2+12i // 复数类型的常量
true // 布尔类型的常量
"foo" // 字符串常量

Go语言的字面常量是无类型的,只要这个常量在相应类型的值域范围内,就能够做为该类型的常量,好比12,它能够赋值给int、uint、int3二、int6四、float3二、float6四、complex6四、complex128等类型的变量。

常量定义

存储在常量中的数据类型只能够是布尔型、数字型(整数型、浮点型和复数)和字符串型. 经过const关键字,你能够给字面常量指定一个友好的名字:

const Pi float64= 3.14159265358979323846 
const zero = 0.0 // 无类型浮点常量
const (
	size int64= 1024 
	eof = -1 // 无类型整型常量
) 
const u, v float32 = 0, 3 // u = 0.0, v = 3.0,常量的多重赋值
const a, b, c = 3, 4, "foo" 
// a = 3, b = 4, c = "foo", 无类型整型和字符串常量

Go的常量定义能够限定常量类型,但不是必需的。若是定义常量时没有指定类型,那么它与字面常量同样,是无类型常量。 一个没有指定类型的常量被使用时,会根据其使用环境而推断出它所须要具有的类型。换句话说,未定义类型的常量会在必要时刻根据上下文来得到相关类型。

var n int
f(n + 5) // 无类型的数字型常量 “5” 它的类型在这里变成了 int

常量定义的右值也能够是一个在编译期运算的常量表达式,好比

const mask = 1 << 3

因为常量的赋值是一个编译期行为,因此右值不能出现任何须要运行期才能得出结果的表达式,好比试图以以下方式定义常量就会致使编译错误:

const Home = os.GetEnv("HOME")

缘由很简单,os.GetEnv()只有在运行期才能知道返回结果,在编译期并不能肯定,因此没法做为常量定义的右值。

const (
	a = "abc"
	b = len(a)
	c = unsafe.Sizeof(b)
)
println(b, c) // 3 8

const x = "xxx" // 未使用的局部变量不会引起编译错误

在常量组中,如不提供类型和初始化值,那么视做与上一常量相同:

const (
	s = "abc"
	x
)
println(x) // abc

枚举的常量都为同一类型时, 可使用简单序列格式

const (
    a = iota    // a int32 = 0
    b            // b int32 = 1
    c            // c int32 = 2
)

枚举序列中的未指定类型的常量会跟随序列前面最后一次出现类型定义的类型

const (
    a byte = iota    // a uint8 = 0
    b                // b uint8 = 1
    c                // c uint8 = 2
    d rune = iota    // d int32 = 3
    e                // e int32 = 4
    f                // f int32 = 5
)

跳过一个iota

const (
		a byte = iota
		b
		_ // 跳过
		c
	)
	fmt.Println(a, b, c) // 0 1 3

在同一常量组中,能够提供多个iota,他们各自增加:

const (
	a, b = iota, iota << 10 // 0, 0<<10
	c, d                    // 1, 1<<10
)
println(a, b, c, d) // 0 0 1 1024

若是iota自增被打断,须显示恢复

const (
	a = iota // 0
	b        // 1
	c = "c"  // c
	d        // c,与上一行相同
	e = iota // 4, 显示恢复,注意计数包含了 c, d 两行
	f        // 5
)

注意e的值


自定义函数不能为常量赋值

const (
	a = iota
	//b = getNumber() 错误
	c = len([3]int{})
)

func getNumber() int {
	return 1
}

由于在编译期间自定义函数均属于未知,所以没法用于常量的赋值,但内置函数可使用,如:len() 。


数字型的常量是没有大小和符号的,而且可使用任何精度而不会致使溢出

const Ln2= 0.693147180559945309417232121458\
            176568075500134360255254120680009
const Log2E= 1/Ln2 // this is a precise reciprocal
const Billion = 1e9 // float constant
const hardEight = (1 << 100) >> 97

反斜杠 \ 能够在常量表达式中做为多行的链接符使用. 不过须要注意的是,当常量赋值给一个精度太小的数字型变量时,可能会由于没法正确表达常量所表明的数值而致使溢出,这会在编译期间就引起错误.


能够经过自定义类型来实现枚举类型限制:

type Color int
const (
	Black Color = iota
	Red
	Blue
)

func test(c Color) {}

func main() {
	c := Black
	test(c)

	//	x := 1
	//	test(x) // Error: cannot use x (type int) as type Color in argument to test

	test(1) // 常量会被编译器自动转换
}

预约义常量

Go语言预约义了这些常量:true、false和iota iota比较特殊,能够被认为是一个可被编译器修改的常量,在每个const关键字出现时被重置为0,而后在下一个const出现以前,每出现一次iota,其所表明的数字会自动增1。 从如下的例子能够基本理解iota的用法:

const(    // iota被重设为0
	c0 = iota  // c0 == 0 
	c1 = iota  // c1 == 1 
	c2 = iota  // c2 == 2 
) 
const( 
	a = 1 << iota   // a == 1 (iota在每一个const开头被重设为0) 
	b = 1 << iota   // b == 2 
	c = 1 << iota   // c == 4 
) 
const( 
	u = iota * 42  // u == 0 
	v float64 = iota * 42  // v == 42.0 
	w = iota * 42  // w == 84 
) 
constx = iota  // x == 0 (由于iota又被重设为0了) 
consty = iota  // y == 0 (同上)
const (
		e, f, g = iota, iota, iota //e=0,f=0,g=0 iota在同一行值相同
	)

若是两个const的赋值语句的表达式是同样的,那么能够省略后一个赋值表达式。所以,上面的前两个const语句可简写为:

const(      // iota被重设为0
	c0 = iota  // c0 == 0 
	c1       // c1 == 1 
	c2      // c2 == 2 
) 
const( 
	a = 1 << iota    // a == 1 (iota在每一个const开头被重设为0) 
	b       // b == 2 
	c       // c == 4 
)

枚举

Go语言并不支持众多其余语言明确支持的enum关键字。 下面是一个常规的枚举表示法,其中定义了一系列整型常量:

const( 
	Sunday = iota
	Monday 
	Tuesday 
	Wednesday 
	Thursday 
	Friday 
	Saturday 
	numberOfDays  // 这个常量没有导出
)

同Go语言的其余符号(symbol)同样,以大写字母开头的常量在包外可见。 以上例子中numberOfDays为包内私有,其余符号则可被其余包访问

相关文章
相关标签/搜索