「GO基础」变量


在这里插入图片描述
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
在这里插入图片描述

非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。💝💝💝 ✨✨ 欢迎订阅本专栏 ✨✨

💖The Start💖点点关注,收藏不迷路💖


1、概述

在Go语言中,声明变量的一般形式是使用var关键字,但与许多编程语言不同的是,Go选择将变量的类型放在变量的名称之后,而不是之前。这种设计选择主要是为了避免类似于C语言中含糊不清的声明形式。

例如,在C语言中,如果你想要声明两个指针变量,你可能会写:int* a, b;。然而,在这个例子中,只有a是指针,而b不是。如果你想要两个变量都是指针,你需要将它们分开书写。

相比之下,在Go中,你可以轻松地将它们都声明为指针类型,如下所示:var a, b *int。这种语法更清晰,减少了潜在的错误和混淆。你可以在Go语言的声明语法页面上找到更多关于这个话题的讨论。

其次,这种语法能够按照从左至右的顺序阅读,使得代码更加容易理解。

var a int
var b bool
var str string

var (
	a int
	b bool
	str string
)

在Go语言中,声明全局变量时通常使用因式分解关键字的写法。当一个变量被声明后,系统会自动为其赋予该类型的零值,例如int为0,float32(64)为0.0,boolfalsestring为空字符串,指针为nil。在Go中,所有内存都是经过初始化的。

变量的命名遵循骆驼命名法,即第一个单词小写,每个新单词的首字母大写,例如numShipsstartDate。但如果希望全局变量能被外部包使用,则需要将第一个单词的首字母也大写。

变量(包括常量、类型或函数)在程序中有一定的作用范围,称为作用域。如果一个变量在函数体外声明,则被认为是全局变量,可以在整个包甚至外部包(被导出后)使用,无论在哪个源文件中声明或调用。

在函数体内声明的变量称为局部变量,其作用域仅限于函数体内,参数和返回值变量也是局部变量。在if和for等控制结构中声明的变量的作用域仅限于相应的代码块内。通常情况下,局部变量的作用域可以通过代码块(用大括号括起来的部分)来判断。

虽然变量的标识符必须唯一,但可以在某个代码块的内层代码块中使用相同名称的变量,此时外部的同名变量会被暂时隐藏(结束内部代码块的执行后,隐藏的外部同名变量会重新出现,而内部同名变量会被释放),任何操作都只会影响内部代码块的局部变量。

变量可以在编译期间被赋值,赋值给变量使用运算符等号=,也可以在运行时对变量进行赋值操作。

a = 15
b = false

一般情况下,当变量a和变量b之间类型相同时,才能进行如 a = b 的赋值。

声明与赋值(初始化)语句也可以组合起来。

var identifier [type] = value
var a int = 15
var i = 5
var b bool = false
var str string = "Stormsha very NB!"

但是 Go 编译器的智商已经高到可以根据变量的值来自动推断其类型,这有点像 Ruby 和 Python 这类动态语言,只不过它们是在运行时进行推断,而 Go 是在编译时就已经完成推断过程。因此,你还可以使用下面的这些形式来声明及初始化变量:

var a = 15
var b = false
var str = "Stormsha very NB!"

var (
	a = 15
	b = false
	str = "Stormsha very NB!"
	numShips = 50
	city string
)

不过自动推断类型并不是任何时候都适用的,当你想要给变量的类型并不是自动推断出的某种类型时,你还是需要显式指定变量的类型,例如:

var n int64 = 2

然而,var a 这种语法是不正确的,因为编译器没有任何可以用于自动推断类型的依据。变量的类型也可以在运行时实现自动推断,例如:

var (
	GENDER = os.Getenv("GENDER")
	SOURCE = os.Getenv("SOURCE")
	GOROOT = os.Getenv("GOROOT")
)

这种写法主要用于声明包级别的全局变量,当你在函数体内声明局部变量时,应使用简短声明语法 :=,例如:

a := 1

下面这个例子展示了如何通过 runtime 包在运行时获取所在的操作系统类型,以及如何通过 os 包中的函数 os.Getenv() 来获取环境变量中的值,并保存到 string 类型的局部变量 path 中。

package main

import (
	"fmt"
   "runtime"
	"os"
)

func main() {
	var goos string = runtime.GOOS
	fmt.Printf("当前操作系统是: %s\n", goos)
	source := os.Getenv("SOURCE")
	fmt.Printf("资源位置是:%s\n", source)
}

如果你在 Windows 下运行这段代码,则会输出 The operating system is: windows 以及相应的环境变量的值;如果你在 Linux 下运行这段代码,则会输出 The operating system is: linux 以及相应的的环境变量的值。

2、值类型和引用类型

程序中所用到的内存在计算机中使用一堆箱子来表示(这也是人们在讲解它的时候的画法),这些箱子被称为“字”。根据不同的处理器以及操作系统类型,所有的字都具有 32 位(4 字节)或 64 位(8 字节)的相同长度;所有的字都使用相关的内存地址来进行表示(以十六进制数表示)。

所有像 intfloatboolstring 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值:

在这里插入图片描述

另外,像数组和结构 (struct)这些复合类型也是值类型。

当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝:

在这里插入图片描述

可以通过&i来获取变量i的内存地址,例如:0xf840000040(每次的地址可能不同)。值类型的变量的值存储在栈中。内存地址会因机器而异,即使相同的程序在不同的机器上执行也会有不同的内存地址,因为每台机器可能有不同的存储器布局和位置分配。

更复杂的数据通常需要使用多个字,这些数据一般使用引用类型保存。引用类型的变量r1存储的是r1的值所在的内存地址(数字),或者内存地址中第一个字所在的位置。

在这里插入图片描述

内存地址被称为指针,它实际上存储在另一个字中。引用类型的指针可以指向连续内存地址中的多个字(连续内存布局),这是效率最高的存储形式;也可以将这些字分散存储在内存中,每个字都指示下一个字所在的内存地址。

当使用赋值语句r2 = r1时,只有引用(地址)被复制。如果r1的值被改变,则所有引用该值的指针都会指向修改后的内容,在这个例子中,r2也会受到影响。

在Go语言中,指针属于引用类型,其他引用类型还包括slicesmapschannel。被引用的变量存储在堆中,以便进行垃圾回收,且比栈拥有更大的内存空间。

3、打印

函数Printf可以在fmt包外部使用,因为它以大写字母P开头,主要用于向控制台打印输出。通常使用的格式为:

func Printf(format string, list of variables to be printed)

格式化字符串为:"当前操作系统是: %s\n"

这个格式化字符串可以包含一个或多个格式化标识符,例如:%..,其中..可以被不同类型的标识符替换,如%s表示字符串标识符,%v表示使用类型的默认输出格式的标识符。这些标识符对应的值从格式化字符串后的逗号开始按顺序添加,如果参数超过1个,也需要用逗号分隔。这些占位符可以很好地控制格式化输出的文本。

函数fmt.SprintfPrintf的作用完全相同,但它将格式化后的字符串作为返回值返回给调用者,因此可以在程序中使用包含变量的字符串。

函数fmt.Printfmt.Println会自动使用格式化标识符%v对字符串进行格式化,两者都会在每个参数之间自动添加空格,后者还会在字符串末尾添加换行符。例如:

fmt.Print("年龄:", 18)

将输出:"年龄:18"

4、简洁形式,使用 := 赋值操作符

在变量初始化时,我们可以省略变量类型,让系统自动推断。在概述中的声明语句中,都是使用var关键字声明的变量。因此,我们可以将它们简写为a := 50b := false

ab的类型(intbool)将由编译器自动推断。这是使用变量的首选形式,但只能在函数体内使用,不能用于全局变量的声明和赋值。使用操作符:=可以高效地创建一个新的变量,这被称为初始化声明。

在相同的代码块中,我们不能对相同名称的变量再次使用初始化声明,例如:a := 20 是不被允许的,编译器会提示错误 "no new variables on left side of :="。但是,a = 20 是可以的,因为这是给相同的变量赋予一个新的值。

如果你在定义变量a之前使用它,会得到编译错误 "undefined: a"

如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 a:

func main() {
   var a string = "NB"
   fmt.Println("stormsha ")
}

尝试编译这段代码将得到错误 a declared and not used

此外,单纯地给 a 赋值也是不够的,这个值必须被使用,所以使用 fmt.Printf("stormsha ", a) 会移除错误。

但是全局变量是允许声明但不使用。

其他的简短形式为:

同一类型的多个变量可以声明在同一行,如:

var a, b, c int

多变量可以在同一行进行赋值,如:

a, b, c = 5, 7, "abc"

上面这行假设了变量 a,b 和 c 都已经被声明,否则的话应该这样使用:

a, b, c := 5, 7, "abc"

右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 5, b 的值是 7,c 的值是 “abc”。这被称为 并行 或 同时 赋值。如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a。

空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。

_实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。

并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:

val, err = Func1(var1)

5、init 函数

除了在全局声明中初始化,变量也可以在init()函数中初始化。init()函数是一种特殊的函数,不能被显式调用,而是在每个包完成初始化后自动执行,且执行优先级高于main()函数。

每个源文件可以包含多个init()函数,同一源文件中的init()函数按照从上到下的顺序执行。如果一个包有多个源文件包含init()函数,官方鼓励但不保证按照文件名的顺序调用。初始化总是以单线程的方式按包的依赖关系顺序执行。

init()函数的一个可能用途是在程序开始执行之前对数据进行检查或修复,以确保程序状态的正确性。

// trans/init.go
package trans

import "math"

var Pi float64

func init() {
   Pi = 4 * math.Atan(1) // init() function computes Pi
}

在它的 init() 函数中计算变量 Pi 的初始值。

如下示例中导入了包 trans(需要 init.go 目录为 ./trans/init.go )并且使用到了变量 Pi:

package main

import (
   "fmt"
   "./trans"
)

var twoPi = 2 * trans.Pi

func main() {
   fmt.Printf("2*Pi = %g\n", twoPi) // 2*Pi = 6.283185307179586
}

init() 函数也经常被用在当一个程序开始之前调用后台执行的 goroutine,如下面这个例子当中的 backend()

func init() {
   // setup preparations
   go backend()
}

6、练习

推断以下程序的输出,然后编译并执行它们,验证和你的推断是否一致。

练习1

package main

var a = "G"

func main() {
   n()
   m()
   n()
}

func n() { print(a) }

func m() {
   a := "O"
   print(a)
}

练习2:

package main

var a = "G"

func main() {
   n()
   m()
   n()
}

func n() {
   print(a)
}

func m() {
   a = "O"
   print(a)
}

练习3:

package main

var a string

func main() {
   a = "G"
   print(a)
   f1()
}

func f1() {
   a := "O"
   print(a)
   f2()
}

func f2() {
   print(a)
}

❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏、分享下吧,非常感谢!👍 👍 👍

🔥🔥🔥道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙

💖The End💖点点关注,收藏不迷路💖
  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

stormsha

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值