Go语言基础入门(2)
更新日期:
接着上一篇博文的内容,继续瞎侃一下Go语言基础入门。
Map key-value形式
创建map的形式:
①
var m map[int] string
m=map[int] string{}
②
var m map[int] string
m=make(map[int]string)
③
var m map[int]string=make(map[int]string)
④
m:=make(map[int]string)
存取操作:
m[1]=”OK” 存
a:=m[1] 取
delete(m,1) 删
多级map时每级map都需要初始化
for k,v:=range map{
...
}
注意:k,v只是拷贝,对其的操作不会影响map
function 函数
Go语言中函数不支持嵌套,重载和默认参数。值类型拷贝的是值,引用类型拷贝的是地址。在Go语言中,所有的函数也是值类型,可以作为参数传递。
匿名函数:函数可以像普通变量一样被传递或使用。
// 匿名函数 1
f := func(i, j int) (result int) { // f 为函数地址
result = i+j
return
}
fmt.Fprintf(os.Stdout, "f = %v f(1,3) = %v\n", f, f(1, 3))
/ 匿名函数 2
x, y := func(i, j int) (m, n int) { // x y 为函数返回值
return j, i
}(1, 9) // 直接创建匿名函数并执行
fmt.Fprintf(os.Stdout, "x = %d y = %d\n", x, y)
闭包:闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)。
闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数+引用环境)。
闭包函数出现的条件:
1.被嵌套的函数引用到非本函数的外部变量,而且这外部变量不是“全局变量”
2.嵌套的函数被独立了出来(被父函数返回或赋值 变成了独立的个体),
而被引用的变量所在的父函数已结束
func closure(x int)func(int) int{
return func(y int) int{
return x*y
}
}
另一例子:
package main
import "fmt"
func ExFunc(n int) func() {
return func() {
n++ //这里对外部变量加1
fmt.Println(n)
}
}
func main() {
myFunc := ExFunc(10)
myFunc() //这里输出11
myAnotherFunc := ExFunc(20)
myAnotherFunc() //这里输出21
myFunc()
myAnotherFunc()
//再一次调用myFunc()函数,结果是12,由此得出以下两点
//1.内函数对外函数 的变量的修改,是对变量的引用
//2.变量被引用后,它所在的函数结束,这变量也不会马上被烧毁
}
defer
类似于析构函数,按照调用顺序相反顺序执行,常用于资源清理。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。
defer 还有一个重要的特性,就是即便函数抛出了异常,也会被执行的。 这样就不会因程序出现了错误,而导致资源不会释放了。
经典例子:
func CopyFile(dst, src string) (w int64, err error) {
srcFile, err := os.Open(src)
if err != nil {
return
}
defer srcFile.Close() //每次申请资源时,请习惯立即申请一个defer 关闭资源,这样就不会忘记释放资源了
dstFile, err := os.Create(dst)
if err != nil {
return
}
defer dstFile.Close()
return io.Copy(dstFile, srcFile)
}
Go语言中没有异常机制,但有panic/recover模式来处理错误, panic用于抛出异常, recover用于捕获异常.panic可以在任何地方引发, recover只能在defer语句中使用, 直接调用recover是无效的。
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
fmt.Println("Calling g.")
g()
fmt.Println("Returned normally from g.")
}
func g() {
panic("ERROR")
}
struct 结构(Go没有class)
定义:
type
type person struct{
Name string
Age int
}
赋值:
a:=&person{} a:&person{
a.Name="zhengsan" 或 Name:"zhangsan",
a.Age=12 Age:19,
}
结构支持指向自身的指针类型成员,允许直接通过指针来读写结构成员,仅支持==与!=,不支持< 或 >
支持匿名结构
a:&struct{
Name string
Age int
}{
Name : "zhangsan",
Age : 19,
}
支持结构嵌套
type person struct{
Name string
Age int
Contact struct{
Phone,city string
}
}
初始化:
a:=person{
Name:"zhangsan",
Age:19,
}
a.Contact.Phone="123233"
a.Contact.city="shanghai"
匿名字段:初始化只能按顺序赋值!!
type person struct{
string
int
}
a:=person{"haha",19}//正确
b:=person{12,"hah"}//错误
下面是个例子:
type human struct{
Sex int
}
type student struct{
human
Name string
Age int
}
a:=student{
Name:"joe",
Age:19,
human:human{Sex:0,}
}
a.Sex=100
a.human.Sex=100//两种方式都可以
方法(Method)
Go虽然没有class,但依旧有method,可以访问结构中的私有字段。
type A struct{
Name string
}
func (a *A) print(){//方法绑定
a.Name="AA"
}
a:=A{}
a.print()
(*A).print(&a)
字段访问权限为包内,即在同一个包中字段都是可以访问的。
接口(interface)
接口是一个或多个方法签名的集合
只要某个类型拥有该接口的所有签名方法,即实现该接口,无需显示声明。
接口只有方法声明,没有实现,没有字段。
接口的定义和使用
type I interface{
Get() int
Put(int)
}//定义了一个接口,它包含两个函数Get和Put
实现这个接口:结构S就是实现了接口I
type S struct {val int}
func (this *S) Get int {
return this.val
}
func (this *S)Put(v int) {
this.val = v
}
空接口
对于空接口interface{} 其实和泛型的概念很像。任何类型都实现了空接口。空接口可以作为任何数据接口的容器。
很多语言都是这样的逻辑:
function g(obj){
if (obj is I) {
return (I)obj.Get()
}
}
Go中是这样实现的:
func g(any interface{}) int {
return any.(I).Get()
}
并发(Goroutine)
当被问到为什么用Go语言,一定不得不提的是Go语言的并发程序编写。Go中并发程序依靠的是两个:goroutine和channel。
并发:主要由切换时间片来实现同时运行
并行:直接利用多核实现多线程的运行
一个goroutine并不相当于一个线程,goroutine的出现正是为了替代原来的线程概念成为最小的调度单位。一旦运行goroutine时,先去当先线程查找,如果线程阻塞了,则被分配到空闲的线程,如果没有空闲的线程,那么就会新建一个线程。注意的是,当goroutine执行完毕后,线程不会回收推出,而是成为了空闲的线程。Goroutine奉行通信来共享内存,而不是共享内存来通信。
channel的意思用白话可以这么理解:主线程告诉大家你开goroutine可以,但是我在我的主线程开了一个管道,你做完了你要做的事情之后,往管道里面塞个东西告诉我你已经完成了。
下面是一个例子:
c:=make(chan bool)
go func(){
fmt.Println("GOGOGO")
c<-true//往channel里输入数据
}()
<-c//从channel中输出数据
1.channel只能使用make来进行创建
基本格式是 c := make(chan int),int是说明这个管道能传输什么类型的数据
2 往channel中插入数据的操作 c <- 1
3 从channel中输出数据 <- c
channel分为两种:一种是有buffer的,一种是没有buffer的,默认是没有buffer的。
ci := make(chan int) //无buffer
cj := make(chan int, 0) //无buffer
cs := make(chan int, 100) //有buffer
有缓冲的channel,因此要注意“放”先于“取”
无缓冲的channel,因此要注意“取”先于“放”
同样要先输出hello world,使用有缓冲的channel和无缓冲的channel分别是这样的:
有缓冲的channel:
var a string
var c = make(chan int, 10)
func f() {
a = "hello, world"
c <- 0
}
func main() {
go f()
<-c
print(a)
}
这里有个缓冲,因此放入数据的操作c<- 0先于取数据操作 <-c
无缓冲的channel:
var a string
var c = make(chan int)
func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}
由于c是无缓冲的channel,因此必须保证取操作<-c 先于放操作c<- 0
使用同步包实现多个goroutine:
wg.sync.waitGroup{}
wg.Add(10)
...
wg.wait()
...
wg.Done()//执行一个消除一个
记住,无缓冲的channel永远不会存储数据,只负责数据流通。
从无缓冲信道取出数据,必须要有数据流进来,否则当前线程阻塞,无缓冲信道是一批数据一个一个流进流出。数据流入无缓冲信道,如果没有goroutine来拿走,那么当前线程也会阻塞。而有缓冲信道是一个一个存储数据,然后一起流出一批数据。