Golang入门(2):一天学完GO的基本语法
摘要
在配置好环境之后,要研究的就是这个语言的语法了。在这篇文章中,作者希望可以简单的介绍一下Golang的各种语法,并与C和Java作一些简单的对比以加深记忆。因为这篇文章只是入门Golang的第二篇文章,所以本文并不会对一些指令进行深挖,仅仅只是停留在“怎么用”的程度,至于“为什么是这样”,则涉及到了具体的应用场景和汇编指令,作者将会在以后的文章中进行介绍。
1 导包
总所周知,“Hello World”是程序员的一种仪式感。
而这一行“Hello World”,一定会涉及到输入输出相关的方法。所以,如何导入包,是我们需要研究的第一步。
在C语言中,我们使用include
,在Java中,我们使用了import
。在Golang中也一样,我们使用import
引入其他的包。在上一篇文章中,我们已经提到了对于导入的包,编译器会首先在GOROOT
中寻找,随后会在项目所对应的GOPATH
中寻找,最后才是在全局GOPATH
中寻找,如果都无法找到,编译器将会报错。
注意,在Golang中和Java有一点很大的区别,就是在Golang中,import导入的是目录,而不是包名。而且,Golang没有强制要求包名和目录名需要一致。
下面举一些例子来说明在Golang中包名和目录的关系,先来看看目录结构:
可以看出,我们在src
下面设置了两个文件夹,在第二个文件夹下面设置了两个go文件。
来看看这两个文件的代码,test1.go如下:
package pktest
func Func1() {
println("这是第一个函数")
}
test2.go如下:
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
这样的方法。
这是因为在Golang中,没有强制要求包名和目录名称一致。也就是说,在上面的例子中,我们引用路径中的文件夹名称是package2
,而在这个文件夹下面的两个文件,他们的包名,却被设置成了pktest
。而在Golang的引用中,我们需要填写的是源文件所在的相对路径。
也就是说,我们可以理解为,包名和路径其实是两个概念,文件名在Golang中不会被显式的引用,通常的引用格式是packageName.FunctionName
。
结论如下:
import
导入的是源文件的相对路径,而不是包名。- 在习惯上将包名和目录名保证一致,但这并不是强制规定(但不建议这么做,这样容易造成调用这个包的人,无法快速知道这个包的名称是什么)
- 在代码中引用包内的成员时,使用包名而不是目录名。
- 在一个文件夹内,只能存在一种包名,源文件的名称也没有其他的限制。
- 如果多个文件夹下有相同名字的package,它们其实是彼此无关的package。
以上部分内容摘自于这篇文章
2 声明
看完了导包方面的内容,我们再来看看如何声明一个变量。在声明变量这一部分,和C以及Java也有较大的区别。
2.1 变量的定义
我们先定义一些变量看看:
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是强类型的一种语言,所有的变量必须拥有类型,并且变量仅仅可以存储特定类型的数据。
2.2 匿名变量
标识符为_(下划线)的变量,是系统保留的匿名变量,在赋值后,会被立即释放,称之为匿名变量。其作用是变量占位符,对其变量赋值结构。通常会在批量赋值时使用。
例如,函数返回多个值,我们仅仅需要其中部分,则不需要的使用_来占位
func main() {
// 调用函数,仅仅需要第二个返回值,第一,三使用匿名变量占位
_, v, _ := getData()
fmt.Println(v)
}
// 返回两个值的函数
func getData() (int, int, int) {
// 返回3个值
return 2, 4, 8
}
如上述代码所示,如果我仅仅需要一个变量的值,就不需要去额外定义一些没有意义的变量名了,仅仅只是需要使用占位符这种“用后即焚”的匿名变量。
2.3 常量
在Golang的常量定义中,使用const
关键字,并且不能使用:=
标识符。
3 判断
我们在使用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还允许在判断条件之前执行一个简单的语句,并用一个分号;
隔开。
4 循环
在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 {
...
}
}
5 函数
5.1 函数的定义
在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
语句则默认返回所有已经定义的返回变量。
5.2defer
在Golang中,有一个关键字叫defer
。
defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
在这段代码中,本来的执行路径是从上往下,也就是先输出“world”,然后再输出“hello”。但是因为defer
这个关键字的存在,这行语句将在最后才执行,所以产生了先打印“hello”然后再打印“world”的效果。
注意,defer后面必须是函数调用语句,不能是其他语句,否则编译器会报错。
可以考虑到的场景是,文件的关闭,或数据库连接的释放等,这样打开和关闭的代码写在一起,既可以使得代码更加的整洁,也可以防止出现开发者在写了长长的业务代码后,忘记关闭的情况。
至于defer的底层实现,本文不进行详细的解释,简单来讲就是将defer语句后面的函数调用的地址压进一个栈中,在当前的函数执行完毕,CPU即将执行函数外的下一行代码之前,先把栈中的指令地址弹出给CPU执行,直到栈为空,才结束这个函数,继续执行后面的代码。
从上文刚刚的表述中也可以推断出,如果有多条refer语句,将会从下往上依次执行。
因为本文只是对各种指令简单的进行对比,所以对于refer的详细解释,将在以后的文章中详细说明。
6 指针
对于指针,如果是C或者C++开发者,一定很熟悉;而对于Java开发者,指针是对开发者透明的一个东西,一个对象会在堆中占据一定的内存空间,而在当前的栈桢中,有一个局部变量,他的值就是那个对象的首地址,这也是一个指针。
可以说,指针就是开发者访问内存的一种途径,只不过是由控制权交给了开发者还是虚拟机。
在Golang中,指针的用法和 C 是一样的。同样是用&
取地址,用*
取地址中的值。
但是,与 C 不同,Golang没有指针运算。
7 数组
在Golang中,数组的定义是这样的:
var a [10]int
这样做会将变量 a 声明为拥有 10 个整数的数组。
注意,在Golang中,数组的大小也同样和 C 语言一样不能改变。
7.1切片
数组的切片,顾名思义,就是将一个数组按需切出自己所需的部分。
每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。
切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
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增加元素。但是,如果此时底层数组容量不够,此时切片将会指向一个重新分配空间后进行拷贝的数组。
因此可以得出结论:
- 切片并不存储任何数据,它只是描述了底层数组中的一段。
- 更改切片的元素会修改其底层数组中对应的元素。
- 与它共享底层数组的切片都会观测到这些修改。
7.2 make
切片可以用内建函数 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中的语法区别,也一定会有不少的认知错误。如果在这篇文章中你看到了任何与你的认识有差距的地方,请一定指出作者的错误。如果本文有哪些地方是作者讲的不够明白的,或者是你不理解的,也同样欢迎留言,一起交流学习进步。
而且在本文中,很多地方没有进行深入挖掘,这些作者都有记录,并且打算在之后的文章中,也会从源码的角度出发,分析这些原因。在这篇文章中,就只是单纯的学会怎么用,就达到目的了。
那么在最后,再次感谢~
PS:如果有其他的问题,也可以在公众号找到作者。并且,所有文章第一时间会在公众号更新,欢迎来找作者玩~
Golang入门(2):一天学完GO的基本语法的更多相关文章
- Golang入门(3):一天学完GO的进阶语法
摘要 在上一篇文章中,我们聊了聊Golang中的一些基础的语法,如变量的定义.条件语句.循环语句等等.他们和其他语言很相似,我们只需要看一看它们之间的区别,就差不多可以掌握了,所以作者称它们为&quo ...
- 0基础如何更快速入门Linux系统?学完Linux有哪些就业方向?
Linux系统是使用Linux内核及开源自由软件组成的一套操作系统,是一种类UNIX系统,其内核在1991年10月5日由林纳斯·托瓦兹首次发布. 它的主要特性:Linux文件一切皆文件.完全开源免费. ...
- Golang入门(4):并发
摘要 并发程序指同时进行多个任务的程序,随着硬件的发展,并发程序变得越来越重要.Web服务器会一次处理成千上万的请求,这也是并发的必要性之一.Golang的并发控制比起Java来说,简单了不少.在Go ...
- Golang入门(1):安装与配置环境变量的意义
摘要 在几年前学习Java的时候,环境的配置就会劝退一部分的初学者.而对于Golang来说,也需要从环境的配置开始学起.这一篇文章将从如何安装Golang开始讲起,随后将会提到Golang中的环境变量 ...
- 3分钟学完Python,直接从入门到精通
作为帅气小编,我已经把python一些模块的甩在这儿了qwq,只要你拿到这些干货,包你玩转python,直接冲向"大佬"的段位,如果已经学了C或者C++或者说如果你需要你的一段关键 ...
- Java程序员的Golang入门指南(下)
Java程序员的Golang入门指南(下) 4.高级特性 上面介绍的只是Golang的基本语法和特性,尽管像控制语句的条件不用圆括号.函数多返回值.switch-case默认break.函数闭包.集合 ...
- 在w3cschool学完html,css,javascript,jquery以后,还是不会做前端怎么办?
w3cschool是一个非盈利性的在线技术学习网站,提供按W3C标准编写的基础教程.完整的看完w3cschool上面的手册,可以基本掌握编程语法.基础性的东西通常都会比较零散,因此,在学习一段时间后, ...
- Golang 入门 : channel(通道)
笔者在<Golang 入门 : 竞争条件>一文中介绍了 Golang 并发编程中需要面对的竞争条件.本文我们就介绍如何使用 Golang 提供的 channel(通道) 消除竞争条件. C ...
- 为什么学完C语言觉得好像没学一般?
不少同学从Hello world学到文件操作之后,回顾感觉会又不会? 学会了又感觉没学会?这种不踏实.模糊虚无的感觉? 原因在于编程不同于理论学科,你听懂和理解了理论就可以运用. 比如历史地理,看 ...
随机推荐
- vue基础 ref的作用
1. ref 获取dom元素,除了能获取dom元素也能获取组件dom, 组件通信: 在父组件中直接调用ref定义的组件的数据或者方法 <div id="app&qu ...
- ssh 公钥 下载选择的时候 下拉选择 ssh 然后 git clone
ssh 公钥 下载选择的时候 下拉选择 ssh 然后 git clone
- 【简说Python WEB】flask-mail电子邮件异步Asynchronous
系统环境:Ubuntu 18.04.1 LTS Python使用的是虚拟环境:virutalenv Python的版本:Python 3.6.9 flask-mail电子邮件异步Asynchronou ...
- docker安装mysql主从
docker安装mysql主从 启动主库: 1.docker run --name master -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:5 ...
- 《面试经典系列》- 从底层理解==和equals的区别
前言 在我们Java面试中,基础知识基本上比定会考核的点,而“==和equals的区别”则是面试官最喜欢.最经常问的问题. 但我们看了不少的文章.解释,总是一头雾水.一知半解的,往往很容忘记.今天,我 ...
- C# 基础知识系列- 3 集合数组
简单的介绍一下集合,通俗来讲就是用来保管多个数据的方案.比如说我们是一个公司的仓库管理,公司有一堆货物需要管理,有同类的,有不同类的,总而言之就是很多.很乱.我们对照集合的概念对仓库进行管理的话,那么 ...
- 毕业设计——基于ZigBee的智能窗户控制系统的设计与实现
题目:基于物联网的智能窗户控制系统的设计与实现 应用场景:突降大雨,家里没有关窗而进水:家中燃气泄漏,不能及时通风,威胁人身安全,存在火灾的隐患:家中窗户没关,让坏人有机可乘.长时间呆在人多.封闭的空 ...
- 加油站问题 Gas Station
2019-06-01 17:09:30 问题描述: 问题求解: 其实本题本质上是一个数学题. [定理] 对于一个循环数组,如果这个数组整体和 SUM >= 0,那么必然可以在数组中找到这么一个元 ...
- 【分布式锁】06-Zookeeper实现分布式锁:可重入锁源码分析
前言 前面已经讲解了Redis的客户端Redission是怎么实现分布式锁的,大多都深入到源码级别. 在分布式系统中,常见的分布式锁实现方案还有Zookeeper,接下来会深入研究Zookeeper是 ...
- SpringMVC常见面试题总结(超详细回答)
SpringMVC常见面试题总结(超详细回答) 1.什么是Spring MVC ?简单介绍下你对springMVC的理解? Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的 ...