go语言之变量基础

标识符

规则

标识符用来表示函数、变量、常量、类型、语句标签和包的命名,命名标准如下:

  • 字母或下划线开头
  • 后面任意数量的字符、数字、下划线(区分大小写)

命名风格

go对于函数、变量、常量等有一些自己的命名风格,在语法上不是强制性的:

  • 尽量使用短名称
  • 单词组合时,采用"驼峰式"
  • 包名总是由小写字母组成

变量

声明

go中对变量的声明,有以下几种方式:

// 1. var  name  type = expression  同时指定类型和表达式
var name string = "Jim"

// 2. var name type   只指定类型
var name string 

// 3. var name = expression  只有表达式(go根据表达式的返回值类型自动推导类型)
var name = "Jim" 
var age = getAge() 

// 4. name := expression   短变量声明,无需var关键字,:=可以表达变量声明的意思。根据表达式返回值推导类型
name := "Jim"  // 推导name为string类型   
age := getAge() // 推导 age为int类型

go除了支持上面的单变量声明,还支持声明变量列表:

// 1. var name1, name2, name3  type
var v1, v2, v3 string  // 三个变量必须是相同类型的
var v1 int, v2 string  // 错误语法

// 2. var name1, name2, name3 = expressions
var name1, name2, name3 = 1, true, "hello"  // 必须每个变量都有初始值,否则编译错误

// 3. name1, name2 := expressions
v1, v2 := 1, "hello"  // 必须每个变量都有初始值

go语言中提供了多种变量声明的方式,主要目的还是为了提高程序员编程效率,因此我们应该合理的使用这些声明方式:

  • var关键字声明一般用于跟初始化表达式类型不一致局部变量、或者后面才赋值的变量、或包变量
  • 短变量,只能用于函数内部局部变量声明,另外可用于var关键字之外的场景

局部变量和全局变量

局部变量是声明在{}里面的变量,包括函数内、循环语句内等。作用域也只在{}内部,

全局变量函数外部的变量称为全局变量,作用域在包内的任意地方。对于全局变量不区分声明和引用位置的前后,可以在任意位置声明,也可以在任意位置引用。

指针

go中的指针概念与C++的指针不完全相同。指针的类型为* type&为变量取地址操作符,*为指针解引用操作符,但是指针不支持运算(指针的运算可以通过unsafe包和uintptr实现),如:

age := 18
var p * int
p = &age  // p 指向 age

// 指针不支持运算
p = p + 1 // 错误
p++  // 错误

new函数

new函数可以用来创建变量:new(T)用来创建一个未命名的T类型的变量,并初始化为零值,返回地址。

p := new(int) // new创建一个int变量,将地址返回,p是一个(*int)指针
fmt.Println(*p)  

与C++不同,new创建的变量和普通局部变量没有什么不同,并不能决定变量是在栈上还在堆上

func test() {
    var age = 18  // 普通变量,在栈帧上
    var *p = new(int)  // *p 也是栈帧上的一个普通变量
    fmt.Println(age, *p)    
}

简单来讲,变量一般存在与栈帧或者,在go里面这个是编译器自动决定的,程序员无法指定。go的设计原则认为,手动指定变量的存储位置容易引入bug,另外go是通过GC来管理堆内存的,可以自动释放内存。因此go为了简化内存管理,自动判断变量应该存储于栈帧还是堆,大概的原则为:

  • golang编译器会尽可能将变量分配在函数的栈帧。因为分配速度快,且函数调用结束直接释放。
  • 如果一个变量非常大,则将其分配在堆上。操作系统的栈是有限长度的。linux默认8MB。
  • 如果变量发生了逃逸,则将其分配在堆上。比如当函数返回一个局部变量的地址时,则此变量会被分配在堆上。go对变量逃逸的分析是机械的、不灵活的,这也会造成将非必要的一些变量分配到堆上,对于内存和性能要求较高的地方需要注意。

多重赋值和空标识符_

go允许几个变量同时被赋值

  var x, y int
x, y = 1, 3
x, y = y, x  // 交换x、y的变量
x, y = y, x+y  

go会保证在实际更新变量前,右边所有的表达式被推演。

可以将不需要的值赋给空标识符'_'

func getNameAge() (int, string) {
	age, name := 18, "Jim"
	return age, name
}

func main() {
	age, _ := getNameAge()   // 空标识符,起到占位的作用,否则编译错误
	fmt.Println(age)

	_, name := getNameAge() // 空标识符,起到占位的作用,否则编译错误
	fmt.Println(name)
}

type类型声明

go提供了type关键字,用于声明新的类型。有点类似于C++中的typedef,但又不完全相同:

type newType underlying-type

type PriceType float64  // 定义了新类型,基础类型为float64
type VolumeType float64 // 定义了新类型

func main() {
	var oilPrice PriceType = 7.8 // 使用新类型定义变量。可以表示为油价为 7.8 元/升
	var volume VolumeType = 10.4 // 表示加了10.4升 油

	var amount float64 = float64(oilPrice) * float64(volume) //计算总金额
	fmt.Println(amount)
}

type关键字在已有的具名或者匿名类型上,建立一个新的类型。新类型拥有底层类型一样的操作(算术运算、比较运算等)。在go中不同类型的数据是不能在一起运算的(无论新类型合适基础类型)

var x int = 2
var y float64 = 3.4

// z := x * y   /// 错误,不同类型不能在一起运算
z := float64(x) * y  // 将x转换为float64类型
fmt.Println(z)

不同类型间的数据进行运算,需要使用显示类型转换,每个类型都有一个对应的类型转换操作符:T(x),表示将x值/变量转换为T类型。

  • 对于底层类型相同的类型,类型转换不会改变的值的表达方式,如上例中的PriceType和VolumeType之间的转换。
  • 对于底层类型不同的类型,类型转换会改变值的表达方式,如浮点转换为整型会丢失小数部分。

作用域和生命周期

生命周期是一个运行时属性,表示变量在程序执行期间能被程序的其他部分所引用的起止时间。

作用域是一个编译时属性,表示声明在程序文本中出现的区域。作用域一般包括:

  • {}语法块如函数、循环体等
  • 词法块,没有被{}显示包含的声明代码,如if 、for、switch语句
  • 包,声明在同一个包的标识符(可以在不同文件)
  • 文件,当一个包被导入时,只能在该导入文件中有效,其他文件中无效(即使是同一个包的不同文件)

有些情况下当程序运行到标识符的作用域之外时,该标识符的声明周期也就结束了,但有时也不会结束(如函数中的局部变量发生了逃逸),当一个变量逃逸出它声明的区域时,我们无法再通过此变量名引用该变量,因为此时已经出了其作用域;但是该变量的生命周期还未结束,因为我们可以通过其他标识符引用此变量。

func getAge() {
    var age int = 18  // age的作用域仅在getAge()函数中,出了此函数,便无法通过age引用此变量。
    return &age 
}

func test() {
    p := getAge()   // 此时age变量的生命周期依然未结束,因为我们可以通过p来引用访问此变量
    fmt.Println(*p)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值