Go语言中的变量、常量和代码块

目录

变量

包级变量:

局部变量:

怎样判断一个变量的类型?

常量

iota

别名类型

代码块


变量

变量的概念:一小块内存,用于存储数据,在程序运行过程中数值可以改变。Go语言中使用变量之前需要先进行变量声明。
变量的声明和赋值:

var a int = 10 //声明int类型的变量a,并赋值为10
var a = 10 //声明变量a,并赋值为10,此时Go编译器会自动推导出变量类型
var a = int32(13) //显式类型转换
var a int //a的初始值为int类型的零值:0
a := 10 //简短声明

//声明一堆变量(声明块)
var (
    a int = 128
    b int8 = 6
    s string = "hello"
    c rune = 'A'
    t bool = true
)

//一行声明多个变量
var a, b, c int = 5, 6, 7
a, b, c := 12, 'A', "hello" //可以是不同类型的

//声明块中的多变量
var (
    a, b, c int = 5, 6, 7
    c, d, e rune = 'C', 'D', 'E'
)

Go语言的两类变量:一种是包级变量(package varible),也就是在包级别可见的变量。如果是导出变量(大写字母开头),那么这个包级变量也可以被视为全局变量;另一类则是局部变量 (local varible),也就是 Go 函数或方法体内声明的变量,仅在函数或方法体内可见。 

包级变量:

包级变量只能使用带有 var 关键字的变量声明形式,不能使用简短声明形式;通常会将同一类的变量声明放在一个 var 变量声明块中,不同类的声明放在不同的var 声明块中;变量声明最好是就近原则,也就是尽可能在靠近第一次使用变量的位置声明这个变量。

/*包级变量声明*/
var b int32 = 17 // 显式指定类型
var b = int32(17) // 显式指定类型(从声明一致性的角度考虑,推荐这种写法)

//同一类的声明放到一个var中
var (
    name string
    intro string
)
var (
    age int
    score int
)

//尽可能在靠近第一次使用变量的位置声明这个变量
var num = 100
func getData(){
    fmt.Println(num + 1)
}

局部变量:

对于声明且显式初始化的局部变量,建议使用短变量声明形式;尽量在分支控制时使用短变量声明形式;

/*局部变量声明*/
//建议使用短变量声明
a := 20
f := 3.05
x := "hello, nihaoya!"

//对于不接受默认类型的变量,依然可以使用短变量声明形式,只是在":="右侧要做一个显式转型,以保持声明的一致性
a := int32(20)
f := float32(3.05)
x := []byte("hello, nihaoya!")

//尽量在分支控制时使用短变量声明形式
num = 10
if num > 5 {
    score := 60
    fmt.Println(num + score)
}

关于变量的更多示例代码:

//go01/variable1.go

package main

import "fmt"

func main() {
	//第一种:定义变量,然后进行赋值
	var num1 int
	num1 = 30
	fmt.Printf("num1的数值是:%d\n", num1)

	//写在一行
	var num2 int = 15
	fmt.Printf("num2的数值是:%d\n", num2)

	//第二种:类型推断
	var user = "abc"
	fmt.Printf("类型是:%T,数值是:%s\n", user, user)

	//第三种:简短声明
	sum := 100
	fmt.Println(sum)

	//多个变量同时定义
	var a, b, c int
	a = 1
	b = 2
	c = 3
	fmt.Println(a, b, c)

	var m, n int = 100, 200
	fmt.Println(m, n)

	var n1, f1, s1 = 100, 3.14, "Go"
	fmt.Println(n1, f1, s1)

	var (
		name = "张三"
		age  = 21
		sex  = "男"
	)
	fmt.Printf("姓名:%s,年龄:%d,性别:%s\n", name, age, sex)
}

Go语言中的变量、常量和代码块_第1张图片

总结:
1.变量必须先声明才能使用
2.变量的类型和赋值必须一致
3.同一个作用域内,变量名不能冲突
4.简短定义方式,左边的变量至少有一个是新的
5.简短定义方式,不能定义全局变量
6.变量的零值,就是默认值,参考下表: 

变量类型 默认值(零值)
所有整型类型 0
浮点类型 0.0
布尔类型 FALSE
字符串类型 ""
指针、接口、切片、channel、 map和函数类型 nil
//go01/variable2.go

package main

import "fmt"

var a = 1000 //全局变量
var b int = 2000

//c := 3000 //语法错误
func main() {
	var num int
	num = 100
	fmt.Printf("num的数值是:%d,地址是:%p\n", num, &num)

	num = 200
	fmt.Printf("num的数值是:%d,地址是:%p\n", num, &num)
	// fmt.Println(num2) //undefined: num2

	var name string
	//name = 100
	//fmt.Println(name) //cannot use 100 (type int) as type string in assignment
	name = "张三"
	fmt.Println(name)

	var name1 string = "李四"
	fmt.Println(name1)

	num, name, sex := 1000, "王五", "男"
	fmt.Println(num, name, sex)

	fmt.Println(a)

	//变量的默认值
	var m int
	fmt.Println(m) //整数 0
	var n float64
	fmt.Println(n) //0.0
	var s string
	fmt.Println(s) //""
	var s2 []int
	fmt.Println(s2)        //切片[]
	fmt.Println(s2 == nil) //true
}

Go语言中的变量、常量和代码块_第2张图片

【问】Go 语言中短变量声明的类型推断可以带来哪些好处?

【答】可以体现在代码重构上。不显式地指定变量的类型,使得它可以被赋予任何类型的值。也就是变量的类型可以在其初始化时,由其他程序动态地确定。Go 语言的类型推断可以明显提升程序的灵活性,使得代码重构变得更加容易,同时又不会给代码的维护带来额外负担,更不会损失程序的运行效率。参考如下代码:

func main() {
	var name1 = getTheFlag1()
	var name2 = getTheFlag2()
	flag.Parse()
	fmt.Printf("Hello, %v!\n", *name1) //Hello, everyone!
	fmt.Printf("Hello, %v!\n", *name2) //Hello, 100!
}

func getTheFlag1() *string {
	return flag.String("name", "everyone", "The greeting object.")
}

func getTheFlag2() *int {
	return flag.Int("num", 100, "The number of greeting object.")
}

怎样判断一个变量的类型?

使用“类型断言”表达式,语法形式是 x.(T)。其中的x代表要被判断类型的值。这个值当下的类型必须是接口类型的,不过具体是哪个接口类型其实是无所谓的。

在 Go 语言中,一对不包裹任何内容的花括号,除了可以代表空的代码块之外,还可以用于表示不包含任何内容的数据结构(或者说数据类型),
比如:struct{} 代表了不包含任何字段和方法的、空的结构体类型;空接口interface{}则代表了不包含任何方法定义的、空的接口类型;对于一些集合类的数据类型来说,{}还可以用来表示其值不包含任何元素,比如空的切片值[]string{},以及空的字典值map[int]string{}。

container := map[int]string{0: "zero", 1: "one", 2: "two"}
elem, err := getElement(container)
if err != nil {
	fmt.Printf("Error: %s\n", err)
	return
}
fmt.Printf("container type: %T \n", container) //container type: map[int]string

func getElement(containerI interface{}) (elem string, err error) {
	switch t := containerI.(type) {
	case []string:
		elem = t[1]
	case map[int]string:
		elem = t[1]
	default:
		err = fmt.Errorf("unsupported container type: %T", containerI)
		return
	}
	return
}

常量

Go 常量一旦声明并被初始化后,它的值在整个程序的生命周期内便保持不变。因此在并发设计时就不用考虑常量访问的同步,并且被创建并初始化后的常量还可以作为其他常量的初始表达式的一部分。使用const关键词声明常量,const 也支持单行声明多个常量,以及以代码块形式聚合常量声明的形式。

//go01/const.go

package main

import "fmt"

func main() {
	//1.定义常量
	const PATH string = "/usr/local/go"
	const PI = 3.14
	fmt.Println(PATH)
	fmt.Println(PI)

	//2.尝试修改常量的数值
	//PATH = "/usr/local/php" //cannot assign to PATH

	//3.定义一组常量
	const C1, C2, C3 = 100, 3.14, "haha"
	const (
		MALE   = 0
		FEMALE = 1
		UNKNOW = 3
	)
	//4.一组常量中,如果某个常量没有初始值,默认和上一行一致
	const (
		a int = 123
		b
		c string = "php"
		d
		e
	)
	fmt.Printf("a: %T,%d\n", a, a)
	fmt.Printf("b: %T,%d\n", b, b)
	fmt.Printf("c: %T,%s\n", c, c)
	fmt.Printf("d: %T,%s\n", d, d)
	fmt.Printf("e: %T,%s\n", e, e)

	//5. 枚举类型:使用常量组作为枚举类型。一组相关数值的数据
	const (
		SPRING = 0
		SUMMER = 1
		AUTUMN = 2
		WINTER = 3
	)
	fmt.Println(SPRING, SUMMER, AUTUMN, WINTER)
}

Go语言中的变量、常量和代码块_第3张图片

Go 语言对类型安全是有严格要求的:即便两个类型拥有着相同的底层类型,但它们仍然是不同的数据类型,不可以被相互比较或混在一个表达式中进行运算。

type myInt int
const x myInt = 20
const y int = x + 5 //报错  cannot use x + 5 (constant 25 of type myInt) as int value in constant declaration

var z int = 5
fmt.Println(z + x) //报错 invalid operation: z + x (mismatched types int and myInt)
fmt.Println(z + int(x)) //类型转换后正常输出:25

Go 的 const 语法提供了“隐式重复前一个非空表达式”的机制

const (
	rx, book = "hello", 123
	ab, test
	mn, data
)
fmt.Println(rx, book, ab, test, mn, data) //hello 123 hello 123 hello 123

iota

iota是 Go 语言的一个预定义标识符,它表示的是 const 声明块(包括单行声明)中,每个常量所处位置在块中的偏移值(从零开始),每个 const 代码块的 iota都是从0开始。

//iota
const (
	rx1, book1 = iota, iota + 5
	ab1, test1
	mn1, data1
)
fmt.Println(rx1, book1, ab1, test1, mn1, data1) //0 5 1 6 2 7

//如果想要从 iota = 1 开始正式定义枚举常量
const (
	_ = iota // 0
	a1 // 1
	_ //跳过了2
	b1 // 3
)
fmt.Println(a1, b1) //1 3

//也可以下面的写法
const (
	a2 = iota + 1 // 1
	_  //跳过了2
	b2 // 3
)
fmt.Println(a2, b2) //1 3

//按位运算
const (
	Readable = 1 << iota
	Writable
	Executable
	None
)
num := 7 //0111
fmt.Println(num&Readable, num&Writable, num&Executable, num&None)  // 1 2 4 0
fmt.Println(num&Readable == Readable, num&Writable == Writable, num&Executable == Executable, num&None == None) //true true true false

别名类型

上面示例代码中用到了 type myInt int,它和 type myInt = int 有啥区别

  • type myInt int,这里的myInt是一个新的类型,也可以叫做对类型的再定义。
  • type myInt = int,表示myInt是int类型的别名类型,别名类型与其源类型的区别恐怕只是在名称上,它们是完全相同的。别名类型主要是为了代码重构而存在的。Go内置的 byte 是 uint8的别名类型,而 rune 是 int32 的别名类型。更详细的信息可参见 Go 语言官方的文档:https://golang.org/design/18130-type-alias

代码块

Go 语言中的代码块是包裹在一对大括号内部的声明和语句序列;空代码块 是一对大括号内部没有任何声明或其他语句。每个源码文件都是一个代码块,每个函数也是一个代码块,每个if语句、for语句、switch语句和select语句都是一个代码块。甚至switch或select语句中的case子句也都是独立的代码块。Go 代码块支持嵌套。

Go语言中的变量、常量和代码块_第4张图片

使用短变量声明可以对同一个代码块中的变量进行重声明,也就是对已经声明过的变量再次声明,例如:

name := zhangsan
name = lisi

由于变量的类型在其初始化时就已经确定了,所以对它再次声明时赋予的类型必须与其原本的类型相同,否则会产生编译错误。变量的重声明只可能发生在某一个代码块中;变量的重声明只有在使用短变量声明时才会发生。

type T struct {
	name string
}

func (t T) blockDemo(x int) (err error) {
	// 代码块1
	m := 13
	fmt.Println(m)
	// 代码块1是包含m、t、x和err三个标识符的最内部代码块
	{ // 代码块2
		// "代码块2"是包含类型bar标识符的最内部的那个包含代码块
		type bar struct{} // 类型标识符bar的作用域始于此
		{                 // 代码块3
			// "代码块3"是包含变量a标识符的最内部的那个包含代码块
			a := 5 // a作用域开始于此
			{      // 代码块4
				fmt.Println(a)
			}
			// a作用域终止于此
		}
		// 类型标识符bar的作用域终止于此
	}
	// m、t、x和err的作用域终止于此
	return nil
}

参考资料:11|代码块与作用域:如何保证变量不会被遮蔽?-极客时间 

【问】如果一个变量与其外层代码块中的变量重名会出现什么状况?

【答】对于不同的代码块来说,其中的变量重名也可以通过编译,即使这些代码块有直接的嵌套关系也是如此。参考下面的代码:

package main

import "fmt"

var block = "package"

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

//输出
//The block is inner.
//The block is function.

源代码:https://gitee.com/rxbook/go-demo-2023/tree/master/basic/go01

你可能感兴趣的:(Golang,golang,开发语言,后端)