Go学习笔记

Go语言从Hello,world开始学习
1.从Hello,world开始
  package main


  import "fmt"


  func main() {
          /* 简单的程序 万能的hello world */
          fmt.Println("Hello Go")
  }
  • package关键字声明了是当前 go 文件属于哪一个包,入口文件都必须声明为main包,入口函数是main函数,在自定义包和函数时命名应当尽量避免与之重复。

  • import是导入关键字,后面跟着的是被导入的包名。

  • func是函数声明关键字,用于声明一个函数。

  • fmt.Println("Hello 世界!")是一个语句,调用了fmt包下的Println函数进行输出。

 2.import与init函数

 golang里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。

import _ "fmt"    //匿名导入包,无法使用包中的方法,但是会执行包中的init()方法

go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个package中的init函数都是可选的,但package main就必须包含一个main函数。

包的导入可以看作是一个递归的过程。

3.变量声明

变量主要通过以下两种方式声明

var name string = "Bond" 
/*
使用var关键字初始化变量,变量的类型在变量名后声明
变量初始化后会有一个默认初始值,例如:"",0等
也可以在初始化变量时就直接给变量赋值
*/

age := 11
/*
可以省略var,使用:=来声明变量
将会根据值自行判断变量类型
注意 :=左侧的变量不能是已声明过的
*/

在 go 语言中,有一个规则,那就是所有在函数中的变量都必须要被使用,否则编译时会报错。

我们可以用下划线表示不需要某一个变量

比如os.Open函数有两个返回值,我们只想要第一个,不想要第二个,可以按照下面这样写

file, _ := os.Open("readme.txt")
4.条件和循环

条件控制:if elseswitch

Go中的if else语句与其他语言用法基本相同。

Go的switch语句不同之处在于:当前的case执行结束后,不会继续执行下一个分支,所以不必在case语句的最后添加break,当然如果想要继续执行下一个分支,可以添加fallthrough关键字来声明。另外Go的case条件更宽泛,不仅局限于单个的数值或字符。还可以在表达式之前编写一些简单语句,例如声明新变量。

func main() {
  switch num := f(); { // 等价于 switch num := f(); true {
  case num >= 0 && num <= 1:
    num++
  case num > 1:
    num--
    fallthrough
  case num < 0:
    num += num
  }
}

func f() int {
  return 1
}

循环控制:for

在 Go 中,有且仅有一种循环语句:for,Go 抛弃了while语句,for语句可以被当作while来使用。

语句格式如下

for init statement; expression; post statement {
  execute statement
}
//可以同时初始化多个变量,然后将其递增
for i, j := 1, 2; i < 100 && j < 1000; i, j = i+1, j+1 {
  fmt.Println(i, j)
}
 5.数组、切片与Map

在Go语言中,数组长度在定义后就不可更改,在声明时长度可以为一个常量或者一个常量表达式(常量表达式是指在编译期即可计算结果的表达式)。数组的长度是该数组类型的一个内置常量,可以用Go语言的内置函数len()来获取。

需要特别注意的是,在Go语言中数组是一个值类型(value type)。所有的值类型变量在赋值和作为参数传递时都将产生一次复制动作。如果将数组作为函数的参数类型,则在函数调用时该 参数将发生数据复制。因此,在函数体中无法修改传入的数组的内容,因为函数内操作的只是所传入数组的一个副本。

/*数组的初始化*/
num := [3]int{1,2,3}
bytes := [2][2]byte

切片slice是动态数组,底层共享数组内存,一个切片在未初始化之前默认为 nil,长度为 0

基于数组创建:数组切片可以只使用数组的一部分元素或者整个数组来创建,甚至可以创建一个比所基于的数组还要大的数组切片。

array := [10]int{1,2,3,4,5,6,7,8,9,10}

slice1 := array[:5]  //基于array前5个元素创建切片
slice2 := array[5:]  //基于从第5个元素开始的所有元素创建数组切片
slice3 := array[:]  //基于array的所有元素创建数组切片
slice4 := array[:20] //不足的位置会初始化为0补齐

直接创建:使用make()函数来创建切片,本质上仍是基于数组的,事实上还会有一个匿名数组被创建出来。

var slice1 []type = make([]type, len)

//也可以简写为
slice1 := make([]type, len)
append() 向切片追加新元素和 copy() 函数拷贝切片
func append(slice []Type, elems ...Type) []Type

copy(dest, src)

map和slice类似,只不过是数据结构不同

    //第一种声明
    var test1 map[string]string
    //在使用map前,需要先make,make的作用就是给map分配数据空间
    test1 = make(map[string]string, 10) 
    test1["one"] = "php"
    test1["two"] = "golang"
    test1["three"] = "java"
    fmt.Println(test1) //map[two:golang three:java one:php]


    //第二种声明
    test2 := make(map[string]string)
    test2["one"] = "php"
    test2["two"] = "golang"
    test2["three"] = "java"
    fmt.Println(test2) //map[one:php two:golang three:java]

    //第三种声明
    test3 := map[string]string{
        "one" : "php",
        "two" : "golang",
        "three" : "java",}
   
6.指针

与C++不同的是Go 中是不支持指针运算的。因为垃圾回收功能的支持, 开发者无需担心所指向的对象失效的问题,因此Go语言中不需要delete关键字,也不需要free() 方法来明确释放内存。

7.函数

Go的函数使用关键字func声明,且可以有多个返回值,但是不支持函数重载。

func 函数名([参数列表]) [返回值] {
  函数体
}

func Sum(a int, b int) (int,int) {
   return b,a+b
}

函数参数的传递分为值传递和引用传递

可以使用关键字defer在函数中声明某条语句,该语句不会立即执行,而是在压入栈中,在函数结束后依次出栈执行。

8.面向对象特征

结构体可以存储一组不同类型的数据,是一种复合类型。Go 抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go 并非是一个传统 OOP 的语言,但是 Go 依旧有着 OOP 的影子,通过结构体和方法也可以模拟出一个类。

package main

import "fmt"

//定义一个结构体
type T struct {
    name string
}

//为结构体类型定义方法
//方法的接收者是值类型,无法修改接收者的值
func (t T) method1() {
    t.name = "new name1"
}

//方法的接收者是指针类型,可以修改接收者的值
func (t *T) method2() {
    t.name = "new name2"
}

/*所谓方法的接收者可以理解为this指针
只是在C++中this指针是隐藏起来的
而Go将其显示地表示出来了*/
9.接口

在 Go 语言中,接口是一种抽象类型,用于定义一组方法签名而不提供方法的实现。接口的核心理念是描述行为,而具体的行为实现由实现接口的类型提供。

type Person interface {
  Say(string) string
  Walk(int)
}


type Number int

func (n Number) Say(s string) string {
  return "bibibibibi"
}

func (n Number) Walk(i int) {
  fmt.Println("can not walk")
}

这是一个Person接口,有两个对外暴露的方法WalkSay,在接口里,函数的参数名变得不再重要,当然如果想加上参数名和返回值名也是允许的。

与其他语言不同的是,Go语言的接口是非侵入式的,不必显式地声明继承,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口,整个过程非常自然。

在Go语言中,只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就是等同的,可以相互赋值。接口赋值并不要求两个接口必须等价。如果接口A的方法列表是接口B的方法列表的子集, 那么接口B可以赋值给接口A。

10.Go特性:goroutine

Go 在语言级别支持协程,叫goroutine。Go 语言标准库提供的所有系统调用操作(包括所有同步IO操作),都会出让CPU给其他goroutine。这让轻量级线程的切换管理不依赖于系统的线程和进程,也不需要依赖于CPU的核心数量。

只需在函数调⽤语句前添加 go 关键字,就可创建并发执⾏单元。开发⼈员无需了解任何执行细节,调度器会自动将其安排到合适的系统线程上执行。

package main
 
import (
    "fmt"
    "time"
)
 
func newTask() {
    i := 0
    for {
        i++
        fmt.Printf("new goroutine: i = %d\n", i)
        time.Sleep(1*time.Second) //延时1s
    }
}
 
func main() {
    //创建一个 goroutine,启动另外一个任务
    go newTask()
    i := 0
    //main goroutine 循环打印
    for {
        i++
        fmt.Printf("main goroutine: i = %d\n", i)
        time.Sleep(1 * time.Second) //延时1s
    }
}
 11.Go特性:channel

channel是Go语言中的一个核心类型,可以把它看成管道。并发核心单元通过它就可以发送或者接收数据进行通讯。

//Type指定这个channel所能传递的元素类型。    
    make(chan Type)  //等价于make(chan Type, 0)
    make(chan Type, capacity)

    channel <- value      //发送value到channel
    <-channel             //接收并将其丢弃
    x := <-channel        //从channel中接收数据,并赋值给x
    x, ok := <-channel    //功能同上,同时检查通道是否已关闭或者是否为空
/*从channel接收数据时,<-和channel间不能有空格*/

默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得goroutine同步变的更加的简单,而不需要显式的lock。

无缓冲的channel

无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何数据值的通道。

这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。否则,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。

这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。

有缓冲的channel

有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个数据值的通道。

这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也不同。

只有通道中没有要接收的值时,接收动作才会阻塞。

只有通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。

这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同:无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换;有缓冲的通道没有这种保证。

select作用

Go里面提供了一个关键字select,通过select可以监听channel上的数据流动,用于处理异步IO问题

select的用法与switch语言非常类似,由select开始一个新的选择块,每个选择条件由case语句来描述。

与switch语句相比,select有比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作。

有时候我们希望能够借助channel发送或接收数据,并避免因为发送或者接收导致的阻塞,尤其是当channel没有准备好写或者读时。select语句就可以实现这样的功能。

    select {
    case <- chan1:
        // 如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
        // 如果成功向chan2写入数据,则进行该case处理语句
    default:
        // 如果上面都没有成功,则进入default处理流程
    }
/*可以看出,select不像switch,后面并不带判断条件,而是直接去查看case语句。每个
case语句都必须是一个面向channel的操作。*/

如果其中的任意一语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。

如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有两种可能的情况:

l 如果给出了default语句,那么就会执行default语句,同时程序的执行会从select语句后的语句中恢复。

l 如果没有default语句,那么select语句将被阻塞,直到至少有一个通信可以进行下去。


最近在转Go语言,这篇文章是一个小小的总结。希望对Go语言感兴趣的人可以通过这篇文章对Go有一个初步的了解。

入门指南 | Golang 中文学习文档

8小时转职Golang工程师

本文的内容和图片主要来自以上两个文档,侵删。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值