本文正在参加金石计划
变量
什么是变量
变量——变化的数据。举两个例子:
- 假如我们是学生,中午饭点我们饿了,在这种情况下我们第一反应就是去饭堂,可是假如饭堂没有饭,那我们就只能饿肚子,吃不了饭了。在我们去饭堂吃饭的这个例子中,饭就是变量。
- 假如我们需要从手机复制一段文字到电脑上,通常最常用的方法就是用电脑与手机登陆同一个微信,然后通过从手机复制到「文件传输助手」上,然后电脑也会接收到信息,此时我们就可以用电脑复制了。在这个过程中,我们利用「文件传输助手」帮助我们在中途存着需要被传输的信息,而它就是一个变量
在以上的例子中,我们能获取变量的两个信息:
- 变量是可以变化的;
- 变量的作用是以容器的身份作中介。
声明一个变量
数据在电脑里面都是以0 1的形式存在的,这种形式为称之为二进制。计算机的创始人们为了能够简单使用计算机而提出了操作系统,并产生了将数据放起来的思想。随着历史的,发展形成了内存空间的概念——每个数据在系统中以内存空间为单位地被存放起来。可是假如他们没有“独特的名字“,我们想去找一个东西是非常麻烦的,因此为一块内存空间**设置具有逻辑意义的“名字”**是非常重要的。
在编程语言中,变量其实是语言为我们创建的一块内存空间,并且我们被允许去给这些空间”命名”,同时指定这块变量空间放的是什么东西,这个步骤就叫变量声明。
声明,是指先告诉给编译器我们需要什么样的变量,接着编译器会去做准备工作,只有在完成这个步骤之后我们才有机会去使用这个变量。以下是一个最简单的变量:
var msg chan string
var是Go声明变量的关键字,msg是变量的自定义名字,chan string是变量类型。
命名法
-
驼峰命名法
- 小驼峰命名法:除第一个单词之外,其他单词首字母大写。如:变量
myStudentCount第一个单词是全部小写,后面的单词首字母大写。 - 大驼峰命名法:把小驼峰的第一个单词的首字母也大写了。
- 小驼峰命名法:除第一个单词之外,其他单词首字母大写。如:变量
-
大小写确定可见性
- 以大写字母开头,可以在包外部访问;
- 以小写字母开头,只能在包内部访问。
变量类型
-
基本数据类型/预声明类型
分类 具体类型 整型 int8、uint8int16、uint16int32、uint32int64、uint64浮点型 float32、float64布尔型 bool字符型 rune字符串 string复数 complex64、complex128 -
⚠️自定义类型:
type newType oldType -
未命名类型/类型字面量
类型名称 例子 数组(array) var numsA [5]int切片(slice) var numsB []int字典(map) var hashMap map[string]int通道(channel) var msg chan string结构体(struct) struct{}type Person struct {}接口(interface) interface {}type Worker interface {}函数(function) func (){}func MethodA() (){}func (s something) MethodA()(){}
怎么给它一个值
- 给字面常量:字面常量就是类型
1、“A”、Person{ name: "Tom" }这样的常量值。 - 给一样类型的变量值:声明方式相同的变量就是相同类型的变量。
- 给相似类型的变量值:Go能够隐式相互转换的变量,如
string与[]byte。
函数
什么是函数
当我们在写数学题的时候,题目总会给我们一些已知条件,然后让我们证明某某条件是否正确或者解出某个数据的最终值。此时我们可以把数学题理解为是一个函数,那些题目的已知条件就是参数列表,解题的过程就是我们对已知条件的进一步加工,也可以被称为函数的函数体,而我们所得的最终结果就是函数的返回值。
在变量的声明中,我们告诉编译器:“我要声明一个名字是blabla的变量。”那么编译器就会在内存中创建一块空间,里面放的是变量的值,并且为其设置一个名字。那么在方法的声明中,编译器也是先分配一块内存空间并为其设置一个名字,但是不同于变量的是,内存空间里放的是计算机能看得懂的代码,即二进制代码。
怎么声明一个函数
函数的结构包括函数名、参数列表、返回值类型和函数体等四部分。其中,函数名是函数的标识符,用于在程序中调用函数。参数列表是一组参数,它们传递给函数的值可以在函数内部使用。返回值类型指定了函数返回值的类型。函数体是包含在大括号内的一段代码——它执行的逻辑。
以下是两种函数的声明结构:
func 函数名(参数列表) (返回值类型) {
函数体
}
func (参数列表) (返回值类型){
函数体
}
在上面代码中我们声明了两种函数,而最大的区别就是有没有名字。根据有没有名字这个条件,函数被分成两种——有名函数与匿名函数。
怎么初始化一个函数变量
函数签名
函数签名也被称为函数的类型。函数可以被看作是一种类型,也就是说函数类型是由一组特定的参数类型和返回值类型所定义的。如下所示:
func add(x int, y int) int {
return x + y
}
func tranc(str string) (int, int){
return 0, len(str)
}
在上面例子中,我们定义了两个函数,分别是add和tranc。前者接收两个int类型的参数并返回一个int类型的值,则其类型就是func(int, int) int;后者接收一个string类型的参数并返回两个int类型的值,则其类型就是func (string) (int, int)。
声明一个函数变量
type CalFuncType func(int, int) int
在上面的例子中,FuncType 是一个函数类型,它接收两个 int 类型的参数并返回一个 int 类型的值。因此,我们可以使用这个函数类型来定义一个变量:
var f CalFuncType
f = add
此时,f 就成为了一个函数类型的变量,并将 add 函数赋值给了 f 变量。
函数有什么用
调用函数
// 计算x+y的值,并返回结果
func add(x int, y int) (int) {
return x + y
}
r1 := add(1, 1)
// 计算x-y,并返回结果
r2 := func (x int, y int) (int) {
return x - y
}(2, 1)
f := func (x int, y int) (int) {
return x * y
}
r3 := f(2, 4)
在以上例子中,我们分别声明了一个名为add的有名函数(普通函数)和一个匿名函数。
- 对于
add的调用,我们只需要直接用方法名( 参数列表 )的方式就能直接调用 - 对于匿名函数的调用
- 在声明后添加
(参数列表)就能直接调用,然后将返回值赋值给r2 - 将匿名函数作为变量赋值给
f,通过f( 参数列表 )直接调用
- 在声明后添加
⚠️Go的参数传递是值传递
下面看例子:
func ptrFunc (x int){
fmt.Printf("x:%p\n", &x) // x:0xc0000b2018
}
func main(){
var x int = 0
fmt.Printf("x:%p\n", &x) // x:0xc0000b2008
ptrFunc(x)
}
以上我们定义了一个ptrFunc的方法,其接受一个int类型的参数,并且打印参数的地址。接着我们定义了一个int类型变量x并且初值为0,首先打印一次它的地址,然后传入ptrFunc内部再打印一次地址。这时我们发现两个地址是不一样的,这是因为在Go中,所有参数传递都是值传递——我们将参数传入方法中时,本质上是复制一份到方法里。这样会导致两种情况:
- 由于参数数据结构复杂,导致产生额外的开销;
- 我们在其他函数中无法修改参数本身。
我们可以通过将指针作为参数而改善以上两种情况。
- 虽然传入指针的操作也是值的传递,可是指针的数据结构是不变的,并且其本身足够小,因此复制的开销不会改变;
- 指针的指向就是某个变量的实际内存空间的地址,因此其具有改变内存空间中内容的能力。
⚠️闭包
先来看一个例子:
func counter() func() int {
i := 0
return func() int {
i++
return i
}
}
func main() {
c := counter()
fmt.Println(c()) // 输出 1
fmt.Println(c()) // 输出 2
fmt.Println(c()) // 输出 3
}
在这个例子中,counter 函数返回了一个内部函数,这个内部函数引用了外部函数中的变量 i。每次调用返回的内部函数,都会使计数器加一,并返回当前的计数值。因为 i 被保存在返回的内部函数中,所以计数器的值在不同的函数调用之间保持不变。counter中的匿名函数与变量i之间所存在的这种关系叫做闭包。
被称为“闭包”,是因为匿名函数与变量i的这种引用关系将两者封闭了起来,形成了一个封闭的空间使得函数能够访问超过其作用域的变量。
假如我们将代码修改成这样:
func counter() func() int {
i := 0
return func() int {
i++
return i
}
}
func main() {
c1 := counter()
fmt.Println(c1()) // 输出 1
fmt.Println(c1()) // 输出 2
fmt.Println(c1()) // 输出 3
c2 := counter()
fmt.Println(c2()) // 输出 1
fmt.Println(c2()) // 输出 2
fmt.Println(c2()) // 输出 3
}
通过这个例子我们会发现,c1与c2是两个不同的闭包环境并且互不影响。