Go语言基础(二)
流程相关
1、for
for在很多编程语言都是有的,golang的for循环与c有点不太一样,倒像是结合了c与python的特性。
for k,v:=range array{
}
for i:=0;i>10,i++{
//i只在此函数内生效
}
sum:=100
for sum<200{
//可以当作while来使用,
}
2、if
其实if语言没有什么过多想讲的,但是golang的if有一个特性需要注意一下
if i:=1;i>10{
//在条件表达式里面可以短变量赋值。所以一般可以直接将需要判断的函数放在表达式里面。
}
if x:=myFunc();x>10{
}
3、switch
switch主要用于判断条件简单的,明确的,切分支多的。switch在每个分支里面找到了是自带break的,如果没有找到则继续往下走。
sum:=2
swith sum{
case 1:
fmt.Println('found',sum)
case 2,3,4:
fmt.Println("found")
default:
fmt.Println("No found")
}
4、break、continue、goto
似乎每个编程语言都离开不开前两者,goto由于容易破坏语言的流程结构在c语言中被用的很谨慎,当然在go中也是如此,不到万不得已也别使用,不然很难维护,代码可读性也非常差。
for i:=1;i>10;i++{
if i==2{
break/continue
}
}
flag:
fmt.Println("goto here)
for i:=1;i<10;i--{
if i==2{
goto flag
}
}
函数相关
go函数是以关键字func
标明的。一般格式是func myFunc(a int,b string)(int,string){}
可返回多参数内容。例如:
func findMax(a,b int)int{ //返回一个参数内容
if a>b{
return a
}
return b
}
func findMaxAndMin(a int,b int)(max int,min int){
if a>b{ //有两个返回值
return a,b
}
return b,a
}
//一般多返回值我们会用在其中一个返回错误标记位
//返回值虽然命名了类型,也还要命名下参数名,这样利于代码可读性
//变参
func variArgs(varibel ...int){
//变参指的是参数的个数是无法确定的。我们编译程序的时候传入的参数就是一个slice多参数。还有就是在go基础一中数组初始化时的多元素个数也是用...来标示的。
}
1、传值与传引用
官方说法都采用指针,似乎指针听起来更理解,引用就是对别人的链接,相当于自身并不存有对方的值,而只是可以得到对方值的路径。之前在基础1中我就讲到slice和map都是底层数据类型的引用,相当于在底层结构上面裹了一层,高级数据结构就是这么来的,一层层裹,特性会越来越多,兼容性也越来越强。
现在我们要讲的是函数传参的时候,是传引用还是传自身的值,还是拷贝呢?
首先我么看一个例子:
//简写我略去了包声明和引入包
func changeValue(a int)int{
a:=5
return a
}
value:=6
changeValue(value)
---------
结果:value=6
这是因为我们传入的a是value的副本(copy),因此就算在函数内更改了a的值也影响不到value本身。如果要在函数内改变传入的值,那么就得用引用类型。
func changeValue(a *int)int{
*a:=*a+1
return *a
}
value:=6
changeValue(&value)
----------
结果:value=7
可以看到,通过引用我们改变了value值。可以这么理解,引用链接放到哪里都能找到原有值,但是传值只是传个副本,影响不到原有值本身。而且传引用还可以节省内存开销,但是也有弊端就是需要谨慎用。
2、defer
defer是延迟操作,类似于python的finally,就是说无论如何都是会执行此语句。只不过defer是在函数内推出时。它是一个先进后出的操作。
func readFile()bool{
file.open("file.txt")
defer file.close()
file... operation//各种操作
//但是一旦出现操作错误前,最终都会执行defer的语句。这种就能保证文件能够正常的关闭。
}
//可以试下以下程序,观察现象
func myPrint(){
for i:=0;i<5;i++{
defer fmt.Printf("%d",i)
}
}
---------
结果:
9
8
7
6
5
4
3
2
1
//这是因为,第一个defer是不输出值的,然后进入了第二个defer,到达最后一个的时候。开始准备退出for,这个时候就开始后进先出了
3、函数作为类型
type myFunc func(int)bool
可用于实现多态,例如:
type myFunc func(int)bool
func isOdd(value int)bool{
if value %2!=0{
return true
}
return false
}
func isEven(value int)bool{
if value%2==0{
return true
}
return false
}
//check函数可以把myFunc当作类型传入
func check(a int,f myFunc)bool{
return f(a)
}
main和init函数
1、main函数
main函数是一个程序的入口,不需要自己调用。package main包必须只能包含一个main函数入口。
2、init函数
go语言中init函数用于包(package)的初始化,该函数是go语言的一个重要特性,
有下面的特征:
-
init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等
-
每个包可以拥有多个init函数
-
包的每个源文件也可以拥有多个init函数
-
同一个包中多个init函数的执行顺序go语言没有明确的定义(说明)
-
不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序
-
init函数不能被其他函数调用,而是在main函数执行之前,自动被调用
-
当出现嵌套包的情况,会把所有根源的包都导入进来。例如:a1导入包b1,但b1包倒入c1,这样一级一级的把所有包都导入进来
但是使用init函数也有以下几个原则:
- 一个package或者是go文件可以包含多个init函数
- init函数是在main函数之前执行的
- init函数被自动调用,不能在其他函数中显示调用
package main
import (
"fmt"
)
func main(){
fmt.Println("do main")
}
func init() {
fmt.Println("do in init1")
}
func init() {
fmt.Println("do in init2")
}
-----------
结果:do in init1
do in init2
do main
可以看到上面代码执行的顺序,init是先于main执行的。一个包里面可以有多个init函数。
模块导入import
模块导入有两种方式,一种是import "fmt"
绝对路径,另外一种是相对路径。
1、点操作
import . "fmt"
这样导入后,在代码中fmt.Println可以写成Println。这是将导入符略去了,但是不建议大家这样,如果出现多个模块中相同的函数,那么会有冲突。
2、别名替换
可以给要导入的模块加上别名,这有点类似于python中as的做法。import f "fmt"
,所以可以缩写成f.Println()
3、单纯导入操作
有些时候你只是想用包里面的初始变量名,并不需要用到函数。这个时候可以用到我们之前在基础一中说到的_
下划线占位符,可以用下划线当作别名占位。import _ "fmt"
struct类型
struct类型和c/c++里面的struct结构体有点类似,但是定义和声明的方式还有些不同。我们可以定义申明struct如下:
type person struct{ //定义
name string
age int
}
//初始化方式1
var Person1 person
Person1.name="leezhu"
Person1.age=10
fmt.Printf("name is %s,age=%d",Person1.name,Person1.age)
//初始化方式2---顺序初始化
Person2:=person{"leezhu",10}
//初始化方式3---通过k:v方式初始化,任意顺序
Person3:=person{name:"leezhu",age:10}
//初始化方式3--初始化*Person4 指针类型
Person4:=new(person)
*Person4.name="leezhu"
*Person4.age=10
以上是struct类型的一般运用,但是struct也有高级应用,例如struct匿名结构。
type person struct{
name string
age int
human //匿名对象,就是包含另外一个结构体,嵌入字段,不写类型
}
type human struct{
eat string
talk string
age int
}
var Person5 person
person.human=human{eat:"noodle",talk:"chinese"}
Person6:=person{"leezhu",10,human{"noodle","chinese"}}
//访问字段,可以当作当前类型直接使用,这是因为匿名字段是实现了字段的继承。
fmt.Println("eat =",Person5.eat)
//有人注意到human 和student都有age,那当Person5.age是访问哪一个呢?Go里面很简单的解决了这个问题,最外层的优先访问,也就是访问student里面的age