admin

Go语言学习笔记(1)
最近听朋友说go做后端开发和写并发都非常高效,决定开始学习一下go语言,附上一个比较好的中文教程如下。Github...
扫描右侧二维码阅读全文
14
2019/03

Go语言学习笔记(1)

最近听朋友说go做后端开发和写并发都非常高效,决定开始学习一下go语言,附上一个比较好的中文教程如下。
Github: the way to go

go语言简介

  go在2009年由谷歌开发出来,是一门很年轻的C family语言。开发者在设计这门语言时考虑了多种语言的特点,如Python, C, java, Scalar等。go在编译速度,开发速度上达到一个较好的平衡。经过测试,go在web方面比python要快好几倍,编写程序时一般可以比python少占用2/3的内存,效率高一半(尽管代码是python的2倍)。在最理想的情况下可以和C++一样快,而且不用写出C++那般冗长的代码。
  当然,go也存在一些特性缺失,包括一些面向对象语言都支持的特性。如: 不支持函数重载与运算符重载,不支持静态变量与动态链接库等等...

Linux下配置go环境

以下步骤均无需在root权限下进行

  下载源代码并进行编译

# 从官网下载源代码
wget https://dl.google.com/go/go1.12.linux-amd64.tar.gz ~/
tar -xzvf go1.12.linux-amd64.tar.gz

cd ~/go/src
# 编译安装
./all.bash
# 如果不希望进行测试的话
./make.sh

  配置环境变量, 用 vim 打开~/.bashrc, 加入如下代码

# 配置根目录
export $GOROOT=$HOME/go
# 将可执行目录加入搜索路径
export $PATH=$PATH:$GOROOT/bin
export $GOPATH=$HOME/workspace/Go

  使配置文件生效

source ~/.bashrc

  此时在$HOME的任意目录下应当都可以运行go程序了, go version可以查看go的版本测试是否可用, go install用以安装相关库

Linux 下配置Go的开发环境

  原本作为一个坚定的vim拥护者,我自然是想用vim来写go, 通过BundleInstall安装两个vim插件,打开~/.vimrc

Bundle 'faith/vim-go'
" For possible bugs
Bundle 'vim-scripts/dbtext.vim'

  如果在安装上面两个插件后没有问题,自然是皆大欢喜,但是我在编辑go文件的时候SQLComplete插件报错,不知如何解决,所以考虑用atom写程序

# 安装 atom 的 go 插件
git clone https://github.com/joefitzgerald/go-plus

Hello World

  编写我们的第一个程序

package main

/* 由于go有一个自动在每行结束加分号的功能,花括号不要换行,否则报错 */
func main(){
    /* 也可以不以分号结尾 */
    println("Hello, world");
}

  保存后在命令行输入go run hello.go则可以看到成功打印出了输出

Go的格式化

  为统一代码风格,官方提高go fmt hello.go类似命令用以格式代码

Go与C的交互

  为方便C程序的引用与迁移,go大量支持C库的调用,下面以一个程序为例分析

/* Interaction with C library */
package main
/*
#include<stdio.h>

static void SayHello(const char *s) {
    puts(s);
}
*/
import "C"

/* 如果需要go run c.go */的话一定要有main入口
func main() {
    /* 调用我们上面定义过的 C函数 */
    C.SayHello(C.CString("Hello,World!\n"))
}

  下面我们再看一个不含main函数的程序例子

/* print.go: Interaction with C library */
package print
//#include<stdlib.h>
//#include<stdio.h>
import "C"
import "unsafe"

func Print(s string){
    cs := C.CString(s)
    /* go的内存管理机制不会自己释放通过C开辟的内存 */
    defer C.free(unsafe.Pointer(cs))
    C.fputs(cs, (*C.FILE)(C.stdout))
}

  注意不含main函数的go程序是无法通过go run print.go命令运行的,我们可以通过命令go build -o print.a print.go命令进行编译(规则与gcc类似)

Go中的包

  上面我们已经接触过包的概念了,在go中每个程序属于且只属于一个包,如main.go中我们写package main,print.go中我们写package print,包的名字用小写命名。此外在包中我们可以用import命令调用其它的包

import "os"
import "fmt"

/* 简短的表达(常用) */
import (
      "os"
      "fmt" 
  )

  注意如果一个包中的函数以大写字母开头,表示可以被其它包所见并调用,否则不可。

常量

  常量定义必须在编译时确定值

const s string = "abc"
/* 可以不显式指定类型, 自动推断 */
const s = "abc"

/* 并行赋值*/
const (
    Monday, Tuesday, Wednesday = 0,1,2
    Thursday, Friday, Saturday = 3,4,5
)
/* 常量也可用于枚举 */
const (
    Unknown = 0
    Male = 1
    Female = 2
)
/* 也可用 iota 来表达, 自动加1*/
const (
    Unknown = iota
    Male
    Female
)

变量

package main

import "os"

/* 声明包级的全局变量, 需要先用 var 关键字声明 */
/* 全局变量允许只声明不使用 */
var x int
/* 同时初始化 */
var b bool = false
var s string = "abc"
/* 自动推断类型 */
var str = "abc"
/* 必须显式指定 */
var n int64 = 2
/* 将a,c 都定义为指针变量 */
var a,c *int

/* 还可以这样写, 调用函数获取环境变量 */
var (
    HOME = os.Getenv("HOME")
    GOROOT = os.Getenv("GOROOT")
)

func main(){
    /* 局部变量,无需先声明*/
    /* 这里只是示例,实际中只赋值但是不适用会编译出错 */
    tmp := 1
    println(HOME, GOROOT)
}

打印

   fmt包中包含很多打印函数, 下面是调用的例子

package main

import "fmt"

func main(){
    /* 大写开头,可以外部使用 */
    fmt.Print("abc")
    /* 换行效果 */
    fmt.Println("abc")
    fmt.Printf("abc%s\n", "de")
    /* 只格式化,不输出 */
    s := fmt.Sprintf("abc%s\n", "de")
    fmt.Printf(s)
}
output:
abcabc
abcde
abcde

init函数

  init是特殊函数,每个源文件只能包含一个init函数,按依赖关系顺序执行,比main函数的优先级要高,可以完成全局变量的赋值以及其它的一些初始化工作

package trans

import "math"

var Pi float64

func init(){
    Pi = 4 * math.Atan(1)
}

Unicode 包

  Unicode实际上在内存中还是以int类型存放,我们在赋值时以\U或者\u开头,前者的后面为8位16进制数, 后者的后面为4位16进制数 

var ch int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'
fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer
fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character
fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes
fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point
import "unicode"

/* 判断 ch 是否为字母 */
unicode.IsLetter(ch)
/* 是否为数字 */
unicode.IsDigit(ch)
/* 是否为空格 */
unicode.IsSpace(ch)

字符串

  有关字符串操作通过strings包实现

/* 是否以 prefix 开头 */
strings.HasPrefix(s, prefix string) bool
/* 是否以 suffix 结尾 */
strings.HasSuffix(s, suffix string) bool
/* 是否包含 substr 子串 */
strings.Contains(s, substr string) bool
/* 查询出现次数 */
strings.Count(s, substr string) int
/* 查询索引 */
strings.Index(s, substr string) int
strings.LastIndex(s, substr string) int
/* 字符串替换 */
strings.Replace(s, old, new, n) string
/* 字符串重复, 把字符串 s 重复 cnt 次并返回 */
strings.Repeat(s, cnt int) string
/* 去除首尾的空白字符 */
strings.TrimSpace(s) string
/* 去除指定字符 */
strings.Trim(s, str string) string 
strings.TrimLeft(s, str string) string
strings.TrimRight(s, str string) string
/* 切割字符串,返回 slice */
string.Split(s, sep) [] string

  字符串与其它类型的转换通过包strconv实现

import "strconv"
/* 整数值到字符串的转换 */
strconv.Itoa(val int) string
/* 从字符串到整数值的转换: 多返回值 */
strconv.Atoi(s string) (val int, err error)

指针

  Go中也提供了指针机制,但是不允许进行指针运算(像C/C++)

package main

import "fmt"

/* 不能得到一个常量的地址 */
/*  
    var p *int
    const i = 5
    p = &i
    p = &10
*/

/* 不允许进行指针运算 */
/* *p++ */

func main(){
    a := 5
    fmt.Printf("The location of a is %p\n", &a)
    p := &a
    fmt.Printf("The value at location p is: %d\n", *p)

}

控制结构

  if-else语句注意else需要与if的右花括号在同一行, Go中的if条件不加括号, 且注意即使只有一行语句,也要加花括号

if x {
    // do something
} else {
    // do some other ting
}

  for循环语句的条件也是不带括号的

for i,j :=  0, 10; i < j; i++ {
    println('...")
}

  goto语句只能跳转到正序的LABEL, 且中间不能有变量的声明

数组

    /* Go中可以用 new(不同于C++/C, where 数组是指向首元素的指针), Go中数组是一种值类型 */
    var arr1  = new([5]int)
    var arr2 [5]int
    arr2 = *arr1
    arr2[3] = 100
    /* 这样作一个内存拷贝带来的必然情况是 在传递数组参数时不会改变原数组 */
    /* 因此如果在被调用的函数中希望改变原数组,则传递引用 */

    /* 常量数组: 一种初始化方式 */
    s := [3]int{1,2,3}
    /* 或者: s:= [...]int{1,2,3} */
    /* 或者: s:= [...]string{"3":"John", "4": "Mike"} 只有第三个和第四个被赋值*/

切片

  当因为数组过大我们不想把整个数组传递给函数时,一种常见的做法就是传递切片给函数。切片是可变长的数组,是引用,因为不用占用额外的内存,所以效率更高更常用

arr := [3]int{1,2,3}
slice := arr[:]
/* 另一种方式得到slice */
slice = &arr
/* 输出切片的长度和最大容量 */
println(len(slice), cap(slice))

  当相关数组还没有创建的时候,我们可以用make函数创建切片并创建相关数组

/* 则 len(slice) = cap(slice) = 10 */
slice := make([]int, 10)
/* len = 10, cap = 20 */
slice := make([]int, 10, 20)

/* 下面两种表达是效果相同的 */
make([]int, 50, 100)
new([100]int)[0:50]
Last modification:March 16th, 2019 at 04:08 pm
If you think my article is useful to you, please feel free to appreciate

Leave a Comment