在配置好环境以后,要研究的就是这个语言的语法了。在这篇文章中,做者但愿能够简单的介绍一下Golang的各类语法,并与C和Java做一些简单的对比以加深记忆。由于这篇文章只是入门Golang的第二篇文章,因此本文并不会对一些指令进行深挖,仅仅只是停留在“怎么用”的程度,至于“为何是这样”,则涉及到了具体的应用场景和汇编指令,做者将会在之后的文章中进行介绍。php
总所周知,“Hello World”是程序员的一种仪式感。html
而这一行“Hello World”,必定会涉及到输入输出相关的方法。因此,如何导入包,是咱们须要研究的第一步。程序员
在C语言中,咱们使用include
,在Java中,咱们使用了import
。在Golang中也同样,咱们使用import
引入其余的包。在上一篇文章中,咱们已经提到了对于导入的包,编译器会首先在GOROOT
中寻找,随后会在项目所对应的GOPATH
中寻找,最后才是在全局GOPATH
中寻找,若是都没法找到,编译器将会报错。数据库
注意,在Golang中和Java有一点很大的区别,就是在Golang中,import导入的是目录,而不是包名。并且,Golang没有强制要求包名和目录名须要一致。 数组
下面举一些例子来讲明在Golang中包名和目录的关系,先来看看目录结构:bash
src
下面设置了两个文件夹,在第二个文件夹下面设置了两个go文件。
package pktest
func Func1() {
println("这是第一个函数")
}
复制代码
test2.go以下:app
package pktest
func Func2() {
println("这是第二个函数")
}
复制代码
而后咱们再来看看testmain.go下面的内容:函数
package main
import "package1/package2"
func main() {
pktest.Func1()
}
复制代码
注意到了吗,咱们在调用Func1
这个函数的时候,使用的是pktest
,而不是咱们认为的package1/package2
中的package2
。学习
按照咱们在Java中的思想,咱们应该是使用package2.Func1
的调用方法或者说是使用test1.Func1
这样的方法。ui
这是由于在Golang中,没有强制要求包名和目录名称一致。也就是说,在上面的例子中,咱们引用路径中的文件夹名称是package2
,而在这个文件夹下面的两个文件,他们的包名,却被设置成了pktest
。而在Golang的引用中,咱们须要填写的是源文件所在的相对路径。
也就是说,咱们能够理解为,包名和路径实际上是两个概念,文件名在Golang中不会被显式的引用,一般的引用格式是packageName.FunctionName
。
结论以下:
import
导入的是源文件的相对路径,而不是包名。以上部份内容摘自于这篇文章
看完了导包方面的内容,咱们再来看看如何声明一个变量。在声明变量这一部分,和C以及Java也有较大的区别。
咱们先定义一些变量看看:
var a int
var b float32
var c, d float64
e, f := 9, 10
var g = "Ricardo"
复制代码
咱们能够看到,在Golang中定义一个变量,须要使用var
关键字,而与C或者Java不一样的是,咱们须要将这个变量的类型写在变量名的后面。不只如此,在Golang中,容许咱们一次性定义多个变量并同时赋值。
还有另外的一种作法,是使用:=
这个符号。使用了这个符号以后,开发者再也不须要写var
关键字,只须要定义变量名,并在后面进行赋值便可。而且,Golang编译器会根据后面的值的类型,自动推导出变量的类型。
在变量的定义过程当中,若是定义的时候就赋予了变量的初始值,是不须要再声明变量的类型的,如变量g
。
注意,Golang是强类型的一种语言,全部的变量必须拥有类型,而且变量仅仅能够存储特定类型的数据。
标识符为_(下划线)的变量,是系统保留的匿名变量,在赋值后,会被当即释放,称之为匿名变量。其做用是变量占位符,对其变量赋值结构。一般会在批量赋值时使用。
例如,函数返回多个值,咱们仅仅须要其中部分,则不须要的使用_来占位
func main() {
// 调用函数,仅仅须要第二个返回值,第一,三使用匿名变量占位
_, v, _ := getData()
fmt.Println(v)
}
// 返回两个值的函数
func getData() (int, int, int) {
// 返回3个值
return 2, 4, 8
}
复制代码
如上述代码所示,若是我仅仅须要一个变量的值,就不须要去额外定义一些没有意义的变量名了,仅仅只是须要使用占位符这种“用后即焚”的匿名变量。
在Golang的常量定义中,使用const
关键字,而且不能使用:=
标识符。
咱们在使用Java或者C的时候,写判断语句是这样的:
if(condition){
...
}
复制代码
在Golang中,惟一的不一样是不须要小括号,可是大括号仍是必须的。以下:
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
复制代码
除去不须要写小括号之外,Golang还容许在判断条件以前执行一个简单的语句,并用一个分号;
隔开。
在Golang中,只有一种循环,for循环。
和判断语句同样,在Golang中也是没有小括号的。
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
复制代码
此外,在循环条件中,初始化语句和后置语句是可选的,这个时候把分号去掉,for循环
就变成了while循环
。
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
复制代码
不只如此,若是省略循环条件,该循环就不会结束,所以无限循环能够写得很紧凑,这个时候,和while(true)
的效果是同样的。
func main() {
for {
...
}
}
复制代码
在Golang的函数定义中,全部的函数都以func
开头,而且Golang命名推荐使用驼峰命名法。
注意,在Golang的函数中,若是首字母是小写,则只能在包内使用;若是首字母是大写,则能够在包外被引入使用。能够理解为,使用小写的函数,是`private`的,使用大写的函数,是`public`的。
在Golang的函数定义中,同样能够不接受参数,或者接受多个参数。而在参数的定义过程当中,也是按照定义变量的格式,先定义变量名,再声明变量类型。对于函数的返回类型,也是按照这样的格式,先写函数名,再写返回类型:
func add(x int, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
复制代码
而且,对于相同类型的两个参数,参数类型能够只写一个,用法以下:
func add(x, y int) int {
return x + y
}
复制代码
在Golang中,对于函数的返回值,和C以及Java是不同的。
Golang中的函数能够返回任意多个返回值。
例以下面的小李子,
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
复制代码
其次,函数的返回值是能够被命名的:
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
复制代码
在这里,咱们能够理解为在函数的顶部预先定义了这些变量值,而空的return
语句则默认返回全部已经定义的返回变量。
在Golang中,有一个关键字叫defer
。
defer 语句会将函数推迟到外层函数返回以后执行。 推迟调用的函数其参数会当即求值,但直到外层函数返回前该函数都不会被调用。
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
复制代码
在这段代码中,原本的执行路径是从上往下,也就是先输出“world”,而后再输出“hello”。可是由于defer
这个关键字的存在,这行语句将在最后才执行,因此产生了先打印“hello”而后再打印“world”的效果。
注意,defer后面必须是函数调用语句,不能是其余语句,不然编译器会报错。
能够考虑到的场景是,文件的关闭,或数据库链接的释放等,这样打开和关闭的代码写在一块儿,既可使得代码更加的整洁,也能够防止出现开发者在写了长长的业务代码后,忘记关闭的状况。
至于defer的底层实现,本文不进行详细的解释,简单来说就是将defer语句后面的函数调用的地址压进一个栈中,在当前的函数执行完毕,CPU即将执行函数外的下一行代码以前,先把栈中的指令地址弹出给CPU执行,直到栈为空,才结束这个函数,继续执行后面的代码。
从上文刚刚的表述中也能够推断出,若是有多条refer语句,将会从下往上依次执行。
由于本文只是对各类指令简单的进行对比,因此对于refer的详细解释,将在之后的文章中详细说明。
对于指针,若是是C或者C++开发者,必定很熟悉;而对于Java开发者,指针是对开发者透明的一个东西,一个对象会在堆中占据必定的内存空间,而在当前的栈桢中,有一个局部变量,他的值就是那个对象的首地址,这也是一个指针。
能够说,指针就是开发者访问内存的一种途径,只不过是由控制权交给了开发者仍是虚拟机。
在Golang中,指针的用法和 C 是同样的。一样是用&
取地址,用*
取地址中的值。
可是,与 C 不一样,Golang没有指针运算。
在Golang中,数组的定义是这样的:
var a [10]int
复制代码
这样作会将变量 a 声明为拥有 10 个整数的数组。
注意,在Golang中,数组的大小也一样和 C 语言同样不能改变。
数组的切片,顾名思义,就是将一个数组按需切出本身所需的部分。
每一个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更经常使用。
切片经过两个下标来界定,即一个上界和一个下界,两者以冒号分隔:
a[low : high]
复制代码
它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
如下表达式建立了一个切片,它包含 a 中下标从 1 到 3 的元素:
a[1:4]
复制代码
举个例子:
func main() {
str := [4]string{
"aaa",
"bbb",
"ccc",
"ddd",
}
fmt.Println(str)
a := str[0:2]
b := str[1:3]
fmt.Println(a, b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(str)
}
复制代码
咱们定义了一个数组,里面含有"aaa","bbb","ccc","ddd"四个元素。而后咱们定义了两个切片,a
和b
,根据定义能够知道,a
为"aaa"和"bbb",b
为"bbb"和"ccc"。
这个时候,咱们把b[0]改为了"XXX",那么b
变成了"XXX"和"ccc",这是毋庸置疑的。可是与直觉相违背的是,这个时候的数组str
,也变成了"aaa","XXX","ccc","ddd"。
这是由于,Golang中的切片,不是拷贝,而是定义了新的指针,指向了原来数组所在的内存空间。因此,修改了切片数组的值,也就相应的修改了原数组的值了。
此外,切片能够用append增长元素。可是,若是此时底层数组容量不够,此时切片将会指向一个从新分配空间后进行拷贝的数组。
所以能够得出结论:
切片能够用内建函数 make 来建立,这也是你建立动态数组的方式。
在此以前须要解释两个定义,len(长度)和cap(容量)。
len是数组的长度,指的是这个数组在定义的时候,所约定的长度。
cap是数组的容量,指的是底层数组的长度,也能够说是原数组在内存中的长度。
在前文中所提到的切片,若是我定义了一个str[0,0]的切片,此时的长度为0,可是容量依旧仍是5。
复制代码
make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:
a := make([]int, 5) // len(a)=5
复制代码
要指定它的容量,需向 make 传入第三个参数:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
复制代码
也就是说,make函数能够自定义切片的大小。用Java的话来讲,他能够被重载。
有两种形式,若是只有两个参数,第一个参数是数组内元素的类型,第二个参数是数组的长度(此时长度和容量都为5)。
而若是有第三个参数,那么第三个参数能够指定数组的容量,便可以指定这个数组在内存中分配多大的空间。
首先,谢谢你能看到这里。
若是这篇文章对你能起到哪怕一点点的帮助,做者都会很开心!
其次要说明的是,做者也是刚开始接触Golang,写这篇文章的目的是起到一个笔记的效果,可以去比较一些C,Java,Golang中的语法区别,也必定会有很多的认知错误。若是在这篇文章中你看到了任何与你的认识有差距的地方,请必定指出做者的错误。若是本文有哪些地方是做者讲的不够明白的,或者是你不理解的,也一样欢迎留言,一块儿交流学习进步。
并且在本文中,不少地方没有进行深刻挖掘,这些做者都有记录,而且打算在以后的文章中,也会从源码的角度出发,分析这些缘由。在这篇文章中,就只是单纯的学会怎么用,就达到目的了。
那么在最后,再次感谢~