[Go]入门

文章介绍了Go语言的基本概念,包括文件结构、命令行参数的获取、变量声明方式以及for循环的使用。此外,还详细讲解了Go的效率问题,如使用strings.Join代替循环拼接字符串,以及如何查找重复的行。最后,文章探讨了goroutine和channel,展示如何在并发环境中通信。
摘要由CSDN通过智能技术生成

引言:该系列笔记,是本人自学Go过程中整理的笔记,供大家参考。

Go文件结构

Go语言原生支持Unicode,它可以处理全世界任何语言的文本。
fmt包,就含有格式化输出、接收输入的函数。可以对写好的源码文件的格式进行校对
main函数是整个程序执行时的入口
包:由单个目录下的一个或者是多个.go源码文件组成,一般目录的命名就定义了包的作用。
源码文件的结构
开头必是以一条package声明语句开始,表示该源码文件属于那个包
接下来是一系列导入(import)的包,如果在编译运行的时候如果缺少使用的包或者导多了没用到,都会导致编译失败。
有多个包的情况下可以用列表的形式规范导入的包:

  import (
   "fmt"
   "os"
  )

导入的顺序并不重要,在用fmt格式化时,会按照首字母进行排序
随后就是组成程序的函数、变量、常量、类型的声明语句(定义的关键字为 func var const type)

go和python一样,不用在每行结束的时候都加上分号,这点比C语言好。这是因为在编译的时候,编译器会自动给每行后面的换行符替换成分号。但是!
要注意一些特殊情况,比如说在定义函数的时候:

 func func_name () 
 {

 }

这样就是不行的,左花括号必须和func关键字在同一行,还有:

 x + y 

这个 + 号必须要和左边的元素在同一行,不然就是有语法错误,不通过编译,比如说:

  x + 
   y

这样是可以的。

命令行参数

go语言编写的程序获取外部参数的方式,就只有通过os.Args来获取。
os.Args是一个字符串(string)的切片(slice)
切片是go里面的一个概念
访问单个元素可以通过os.Args[i],获取子数组可以通过os.Args[m:n],和python一样也是采用左闭右开的方式,也就是说os.Args[m:n]获取的数据有[m: n-1]个
os.Args[0]是命令本身的名字,除开这个其他的元素就是传递给程序的参数。
os.Args[1: len(os.Args)],是获取到所有传递的参数,可以简写为 os.Args[1:]

和python不一样的是,注释python以#开头单行注释,Go以//开头单行注释
注意看! 这段代码的实现了仿echo命令的功能:

package main

import (
        "fmt"
        "os"
)

func main() {
        var s, sep string
        for i := 1; i < len(os.Args); i++ {
                s += sep + os.Args[i]
                sep = " "
        }
        fmt.Println(s)
}

其中main函数里定义了两个s、sep变量,他们的类型是string。变量会在声明时直接初始化。如果变量没有显示初始化,就会被隐式初始化并且其值会等于该类型的"零值"
如果是数值就是0,如果是字符串就是空字符串""
这里的s += sep + os.Args[i],等价于 s = s + sep + os.Args[i]
:= 是短声明变量的一部分,根据他们的初始值为他们赋予适当类型的语句,说白了就是靠猜。

变量声明方式

声明变量的好几种方式:

  sep := ""
  var sep string
  var sep = ""
  var sep string = ""

实践中常用第一种和第二种。第一种最简洁但只能用在函数内部,不能用于包变量。第二种适合初始值比较重要就显式指定变量,不然就隐式指定变量。
i++是语句,对应的还有i–,他们是语句,j = i++是非法的。而且++和–都是放在变量的后面,放前面是非法的(++i)。

for循环体

Go语言只有for一种循环语句,for循环有多种形式

for initialization; condition; post {
 ...
}
// initialization、condition、post都可以省略
for {
 ...
} // 代表了无限循环

initialization如果存在,必须是一条简单语句,即短变量声明、自增语句、赋值语句或函数调用。
condition是一个Boolean类型的值,其值在每次循环开始之前进行计算。如果为true就执行循环体语句,如果为false就结束循环。
post语句在每次循环结束后执行,之后再对condition求值。
for循环的另一种方式,在某数据类型的区间(range)上遍历,对上面的仿echo程序代码做变更:

  package main

  import (
          "fmt"
          "os"
  )

  var s, sep = "", ""

  func main() {
          for _, arg := range os.Args[1:] {
                  s += sep + arg
                  sep = " "
          }
          fmt.Println(s)
  }

这个例子中并不需要元素值的索引,但是range要求要处理元素,就必须处理索引。这里会想到索引赋值给一个临时变量不用就是了,但是Go语言不允许无用的局部变量,会导致编译错误。
解决办法是用空标识符,即下划线"_",这样就能满足程序需要变量名但不需要索引的时候处理。
这里用 := 隐式的索引os.Args[1:],比显式的索引os.Args[1:],更容易写对。

效率问题

这是用for循环去累加后面的字符串,数据量大的话,这种方式代价就很高昂。我们可以用strings包里的Join函数去处理,耗时上也比for方式短:

 package main

 import (
  "fmt"
  "os"
  "strings"
 )
 func main () {
  fmt.Println(strings.Join(os.Args[1:], " "))
 }

查找重复的行

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    counts := make(map[string]int)
    input := bufio.NewScanner(os.Stdin)
    for input.Scan() {
        counts[input.Text()]++
    }
    // NOTE: ignoring potential errors from input.Err()
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}

make是内置函数,这里用make函数创建了空map,map是存储了key/value的集合,类似python中的字典,但它是无序的,也就是说每次遍历map集合,它输出的值顺序都不一样。
map的方括号中指定了key的类型,后面的int则是指定了值的类型。
bufio包是处理输入输出的包。NewScanner方法是读取输入并将其拆成行或者单词,是处理行形式的输入最简单的方法。
for input.Scan() {…}是每次调用input.Scan()都会读入下一行,并移除行末的换行符。
input.Text()是获取input.Scan()得到的值,这里的counts[input.Text()]++,意思是每次都会把读到的行在集合中创建一个元素,key值为Text()内容,其值为int++。
map中不含某个键时,在首次读到新行counts[line]的值将会被计算为其类型的零值,int即为0。
fmt.Printf(“%d\t%s\n”, n, line)
这里的Printf和Println有区别,Printf(Print format)是格式化输出的方法,Println会以自然状态的方式输出结果。

Go动词

%d和%s在Go里面叫做动词(verb),常用的动词有以下这些:

  %d          十进制整数
  %x, %o, %b  十六进制,八进制,二进制整数。
  %f, %g, %e  浮点数: 3.141593 3.141592653589793 3.141593e+00
  %t          布尔:truefalse
  %c          字符(rune(Unicode码点)
  %s          字符串
  %q          带双引号的字符串"abc"或带单引号的字符'c'
  %v          变量的自然形式(natural format)
  %T          变量的类型
  %b          二进制类型
  %%          字面上的百分号标志(无操作数)

Printf不会换行,Println相当于%v的效果,在输出结构后面会加上一个换行符

使用一次的函数,可以直接写进main函数里,如果是多次调用,可以单独拎出来定义。
函数的定义在Go里面没有顺序之分,不像python里面如果是定义了一个函数,调用这个函数的指令,必须在定义这个函数之后。

resp.Body.Close关闭resp的Body流,防止资源泄漏。

goroutine和channel

注意看下面这段代码:

package main

import (
    "fmt"
    "io"
   // "io/ioutil"
    "net/http"
    "os"
    "time"
)

func main() {
    start := time.Now()
    ch := make(chan string)
    for _, url := range os.Args[1:] {
        go fetch(url, ch) // start a goroutine
    }
    for range os.Args[1:] {
        fmt.Println(<-ch) // receive from channel ch
    }
    fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}

func fetch(url string, ch chan<- string) {
    start := time.Now()
    resp, err := http.Get(url)
    if err != nil {
        ch <- fmt.Sprint(err) // send to channel ch
        return
    }
    nbytes, err := io.Copy(ioutil.Discard, resp.Body)
    resp.Body.Close() // don't leak resources
    if err != nil {
        ch <- fmt.Sprintf("while reading %s: %v", url, err)
        return
    }
    secs := time.Since(start).Seconds()
    ch <- fmt.Sprintf("%.2fs  %7d  %s", secs, nbytes, url)
}

channel是一个在goroutine之间传递参数的这么一个变量
使用指令go function 会创建一个goroutine,并在这个goroutine中异步执行function的功能
创建channel变量的方式,在main中函数创建:
ch := make(chan string),这个string是指定这个channel所属的变量类型
main函数本身也会创建一个goroutine异步运行

当一个goroutine尝试在一个channel里写或者接收参数的时候,这个goroutine会阻塞调用处,直到另一个goroutine从这个channel里接收或者写入值,
这样两个goroutine才会继续channel操作之后的逻辑
传递的方式 ch <- expression,<- ch

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值