Tips
写给大忙人看的Golang教程(一)
阅读本文之前,我认为你已经掌握其他语言基础并写出一个简单的项目。
(1)Golang编程注意事项
- 源文件必须以
.go为扩展名.
- Go应用程序d额执行入口是
main()方法.
- Go代码严格区分大小写.
- Go代码不需要分号.
- Go代码不允许多条语句在同一行出现.
- Go语言重定义的变量和导入的包如果没有被使用不会编译通过.
- Go语言大括号是成对出现的.
(2)Golang中的常用转义字符
\t 制表符
\n 换行符
\\ 一个斜杠
\" 一个引号
\r 一个回车
(3)注释方式
// 注释内容行注释
/* 注释内容 */多行注释
- 多行注释不可以嵌套多行注释
(4)Golang的代码规范
(5)官方编程指南
- Go的官方网站 https://golang.org
- Go的标准库英文官方文档 https://golang.org/pkgdoc
- Go的标准库中文官方文档 https://studygolang.com/pkgdoc
(6)变量
- 使用
var关键字定义变量:var 变量名称 数据类型
- 使用类型推导:
var 变量名称 = 值
- 省略
var关键字:变量名称 := 值, 变量名称不应该是已经定义过的
变量名称 := 值等同于var 变量名称 数据类型 = 值
- 多变量声明:
var 变量名称, 变量名称... 数据类型
- 多变量赋值:
var 变量名称, 变量名称, ... = 值, 值, ...
- 省略
var关键字多变量声明和赋值: 变量名称, 变量名称, ... := 值, 值, ...
- 声明全局变量:
var (
变量名称 = 值
...
)
(7)Golang支持的数据类型

-
使用unsafe.Sizeof()查看变量占用的空间
package main
import (
"fmt"
"unsafe"
)
func main() {
var x int = 10
fmt.Println("The x size: ", unsafe.Sizeof(x))
// The x size: 8
}
-
float32表示单精度,float64表示双精度
-
字符常量使用单引号
-
Go中的字符编码使用UTF-8类型
-
bool类型只能取true或false
-
在Go中字符串是不可变的
-
Go支持使用反引号输出原生字符串
- 字符串拼接使用加号时最后一个加号需要保留在行尾
(8)基本数据类型转换
- 数值类型互相转换:
目标数据类型(变量)。
- 数值与字符串互转:
- 数字转字符串:使用
fmt.Sprintf()字符串格式化函数。
- 数字转字符串:使用
strconv.FormatBool()、strconv.FormatInt()、strconv.FormatUint()、strconv.FormatFloat()格式化函数。
- 字符串转数字:使用
strconv.ParseBool()、strconv.ParseInt()、strconv.ParseUint()、strconv.ParseFloat()格式化函数。
(9)指针数据类型
package main
import "fmt"
func main() {
var i = 10
var ptr *int = &i
fmt.Println("变量i的内存地址是: ", ptr)
// 变量i的内存地址是: 0xc00004a080
fmt.Println("变量i的存储内容是: ", *ptr)
// 变量i的存储内容是: 10
}
(10) 值类型与引用类型


- 值类型通常存放到栈区。
- 引用类型通常存放在堆区,栈中有堆中的引用。
(11)Golang中的标识符
- Go中使用
_表示空标识符
- 严格区分大小写
- 包名尽量与目录保持一致
- 推荐使用驼峰命名法
- 变量名称、函数名称、常量名称首字母大写表示可以被其他包访问,否则表示私有的
(12)运算符
- Golang中只有
x++和x--,没有++x和--x
- 自增自减运算值独立的语句,不允许类似的:
x := a++
(13)控制台输入输出
fmt.Sacnf():使用指定的格式获取文本
fmt.Sacnln():以换行为结束的文本
(14) 原码、反码、补码
- 计算机中0表示正数,1表示负数
- 计算机中都是用补码进行运算的,因为CPU只会加法运算
- 正数的原、反、补都是相同的
- 负数的反码是原码符号位不变其他位取反
- 负数的补码是反码加1
- 0的原、反、补相同
(15)流程控制
(15.1)顺序控制
略
(15.2) 分支控制
-
if语句:
if x>12 {
}
// Golang支持直接在condition中定义变量
if x:=12; x>12 {
}
-
if-else语句:
if x:=12; x>20 {
}else {
}
-
if-else if-else语句:
if x:=12; x>100 {
}else if x>50 {
}else if x>10 {
}else {
}
-
switch-case-default语句:
// 每个分支不需要break语句
// switch也支持在condition中直接定义变量
// case支持多个表达式
// 取消break使用fallthrough语句————switch穿透
switch y:=10;y {
case 5:
// something
case 10:
// something
fallthrough
case 20, 25, 30:
// something
default:
// something
}
(15.3)循环控制
-
for循环
for i:=1;i<10;i++ {
}
// Golang也提供了for-each或for-range类似的循环
str := "Hello Golang."
for index, value:=range str {
// index表示索引
// value表示值
}
-
while循环
for {
if condition {
break
}
// something
}
-
do-while循环
for {
// something
if condition {
break
}
}
(16) 随机数
// 设置随机数的种子为当前的系统时间
rand.Seed(time.Now().Unix())
// 生成0-99范围的随机数
randomNumber := rand.Intn(100)
(17)break、continue、goto、return语句
break语句在多层嵌套中可以通过标签指明要终止到哪一层语句块:
label:
for {
break label
}
continue语句在多层嵌套中可以通过标签指明要跳出到到哪一层语句块:
label:
for {
continue label
}
(18)函数
-
函数基本语法:
func functionName (paramsList) (returnList) {}
-
Golang不支持函数重载
-
Golang函数本身也是一种数据类型,可以赋值给变量,那么该变量也是函数类型
-
Golang函数可以作为实参传入另一个函数
-
Golang支持自定义数据类型,使用:type 自定义数据类型名 数据类型
type myfunc func(int)(int, int)
-
支持使用_忽略返回值
-
支持可变参数
package main
import "fmt"
func main() {
ret := sum(1, 2, 3)
fmt.Println(ret) //6
}
// 可变参数
func sum(args...int) int {
sum := 0
for i:=0; i<len(args); i++ {
sum += args[i]
}
return sum
}
(19)包
包的本质就是一个目录,Go的每一个文件都必须属于一个包。
(20)init函数
- 在Go中每一个源文件都可以有一个
init函数,它优先于main函数执行,被Go框架调用。
func init() {}
- 先执行引入的包中的
init函数再执行main包中的init函数
// util.HelloWorld.go
package utils
import "fmt"
func init() {
fmt.Println("Util.HelloWorld() init")
}
func HelloWorld()(){
fmt.Println("Hello World")
}
// main.test.go
package main
import (
"StudyGo/utils"
"fmt"
)
func init() {
fmt.Println("Main.main() init")
}
func main() {
utils.HelloWorld()
}
// Util.HelloWorld() init
// Main.main() init
// Hello World
(21)匿名函数
(22)闭包
-
闭包就是函数与其相关的引用环境构成的实体
package main
import (
"fmt"
"strings"
)
func main() {
fileName := "file"
fileSuffix := ".mp3"
ms := makeSuffix(fileSuffix)
ret := ms(fileName)
fmt.Println(ret)
}
func makeSuffix(suffix string) func(string) string {
return func (s string) string {
if strings.HasSuffix(s, suffix) {
return s
}else {
return s + suffix
}
}
}
(23)defer 关键字
(24) 字符串函数
len():计算字符串长度
strconv.Atoi(s string) (i int, err error):将字符串转换为整数
strconv.Itoa(i int) string:将整数转换为字符串
strconv.FormatInt(i int64, base int) string:将十进制转换为其他进制
strings.Contains(s string, sub string) bool:判断字符串是否包含子字符串
strings.Count(s string, sub string) int:统计字符串中有几个子字符串
strings.EqualFold(s_0 string, s_1 string) bool:忽略大小写比较字符串是否相等
strings.Index(s string, sub string) int:返回子字符串在字符串中的第一个位置
strings.LastIndex(s string, sub string):返回子字符串在字符串中的最后一个位置
string.Replace(s string, oldSub string, newSub string, n int) string:将指定的子字符串替换为其他字符串,n代表替换个数,-1表示全部,返回新字符串
string.ToLower(s string):将字符串转换为小写
string.ToUpper(s string):将字符串转换为大写
string.Split(s string, sep string) array:将字符串按照sep分隔
string.TrimSpace(s string) string:删除字符串两侧的空格
string.Trim(s string, sub string) string:将字符串两侧的sub去掉
string.TrimLeft(s string, sub string) string:将字符串左边的sub删除
string.TrimRight(s string, sub string) string:将字符串右边的sub删除
string.HasPrefix(s string, sub string) bool:判断s是否以sub开头
string.HasSuffix(s string, sub string) bool:判断s是否以sub结尾
(25)时间日期函数
time.Time:表示时间类型
time.Now() struct:获取当前本地时间
time.Now().Year():返回年
time.Now().Month():返回月,使用int(time.Now().Month())取得数字
time.Now().Day():返回日
time.Now().Hour():返回时
time.Now().Minute():返回分
time.Now().Second():返回秒
time.Now().Format(s string):格式化时间数据,2006-01-02 15:04:05表示格式化的格式字符串其中的值不能改变
time.Sleep(d Duration):休眠函数
time.Hour:一小时
time.Minute:一分钟
time.Second:一秒
time.Millisecond:一毫秒
time.Microsecond:一微秒
time.Nanosecon:一纳秒
time.Now().Unix() int64:返回Unix秒时间戳
time.Now().UnixNano() int64:返回Unix纳秒时间戳
(26)内置函数
len():求长度
new():分配内存,主要用来分配值类型
make():分配内存,主要用来分配引用类型
(27)错误处理
-
在Go中捕获异常的机制是使用defer关键字修饰匿名函数,导致匿名函数最后执行,在匿名函数中调用recover()函数,通过返回值是否为nill来判断是否发生异常信息。
package main
import "fmt"
func main() {
ret := ComputeNumber(1, 0)
fmt.Println(ret)
}
func ComputeNumber(n_0 int, n_1 int) int {
defer func() {
if e := recover(); e != nil{
fmt.Println(e)
}
}()
result := n_0 / n_1
return result
}
-
自定义错误
使用errors.New(Type) *Type创建一个error类型,panic()接收一个空接口类型,输出错误信息并结束运行。
package main
import "errors"
func main() {
err := readConfigureFile("config.json")
if err !=nil {
panic(err) // panic: Read config.ini error.
}
}
func readConfigureFile(path string)(err error) {
if path != "config.ini" {
return errors.New("Read config.ini error.")
} else {
return nil
}
}
(28)数组
在Go中数据是值类型,使用以下方式创建数组。
var 数组名称 [元素个数]数据类型 = [元素个数]数据类型{元素}
var 数组名称 = [元素个数]数据类型{元素}
var 数组名称 = [...]数据类型{元素个数}
var 数组名称 = [...]数据类型{索引:值}
- 数组支持类型推导
- 数组支持
for-each/for-range遍历
(29)slice 切片
数组的长度是固定的,切片 的长度不是固定的。
-
var 切片名称 []数据类型
-
切片名称[索引:索引]
-
切片的结构:[起始数据元素指针, 长度, 容量]
-
通过make创建切片:var 切片名称 []数据类型 = make([]数据类型, 长度, 容量)
-
切片支持普通遍历和for-range方式遍历
-
使用append()函数追加元素到切片末尾,容量不够时自动扩容
-
使用copy()函数拷贝数组
string类型底层是个byte数组,也可以进行切片处理。string是不可变的,如果要修改字符串,需要先将字符串转换为切片修改完成后再转换成为字符串。
str := "Hello World."
arr := []byte(str)
arr[11] = '!'
str = string(arr)
fmt.Println(str)
(28)Map映射
var Map名称 map[KeyType]ValueType
-
使用make(map[KeyType]ValueType)分配空间
-
delete(m map[Type]Type, key Type):通过Key删除元素,如果元素不存在也不会报错
-
清空Map一种是遍历删除,一种是make重新分配空间,使得原来的Map成为垃圾让GC回收
-
查找使用value, ok = mapName[Key],如果ok为true,表示元素存在
- Map支持
for-range遍历
for key, value := range mapName{
}
(28)OOP
-
Go中的OOP是通过struct来实现的
type 类名 struct {
属性名 数据类型
...
}
-
创建结构体变量
var 变量名称 结构体类型
var 变量名称 结构体类型 = 结构体类型{}
变量名称 := 结构体类型{}
// 下面两种写法等价:
var 变量名称 *结构体名称 = new(结构体名称)
var 变量名称 *结构体名称 = &结构体名称
// 在操作属性、方法的时候Go进行了优化,下面两种写法是等价的:
(*变量名称).属性 = 值
变量名称.属性 = 值
-
每一个字段可以加上一个tag,该tag可以通过反射机制获取,常见的场景就是序列化与反序列化
属性名称 数据类型 `json:Tag名称`
-
Go中的类没有构造函数,通常通过工厂模式来实现
package model
// 如果Name和Age改为name和age,需要为person绑定Getter和Setter方法
type person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func NewPerson(n string, a int)(*person){
return &person{
Name : n,
Age : a,
}
}
package main
import "fmt"
import "StudyGo/model"
func main() {
var tom = model.NewPerson("Tom", 20)
fmt.Println((*tom).Name)
fmt.Println((*tom).Age)
}
-
在Go中在一个结构体中嵌套另一个匿名结构体就认为实现了继承
type Ani struct {
name string
age int
}
type Cat struct {
Ani
say string
}
- Go中的结构体可以访问嵌套结构体中的所有字段和方法
- 结构体的字段和属性采用就近原则
- 如果一个结构体继承自两个结构体,这两个结构体都有同名字段但是该子结构体没有,访问时需要指明父结构体
- struct支持匿名字段,但是数据类型不能重复,使用
结构体变量.数据类型 = 值来访问
-
接口
type 接口名称 interface {
方法名称(参数列表)(返回值列表)
}
- 接口不允许包含任何变量
- Go中的接口不需要显式实现,只要一个变量含有接口的所有方法,那么就实现了这个接口
-
类型断言
- 当一个类型转换为了接口类型在转换为该类型时需要使用类型断言判断是否可以转换为该类型
var number float32
var x interface{}
x = t
t = x.(float32) // 判断一下是否可以转换成为float32类型
(29)方法
func (recv type) funcName (paramsList)(returnList) {
// something
}
recv 表示这个方法与type类进行绑定,方法内通过recv操作type类中的字段
type是个结构体类型
recv是个结构体类型变量
通常为了执行效率一般不会直接传入结构体类型作为接收器,而是结构体类型指针:
func (dog *Dog) function()(){ // 绑定的是地址,操作时也要使用地址
// something
}
// 调用时
var d Dog
(&d).function()
但是编译器做出了相关的优化:
var d Dog
d.function()
(30)文件
File.Open(name string) (file *File, err error):打开文件
File.Close() error:关闭文件
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("main/file.txt")
if err != nil {
fmt.Println(err)
}
err = file.Close()
if err != nil {
fmt.Println(err)
}
}
-
使用bufio.NewReader(file *File) *reader创建读取器对象,调用bufio.ReadString(end string) (s string, err error)读取文件,以end结尾。
-
使用ioutil.ReadFile(path string) ([]byte, error):返回nil,一次性读取全部文件,不需要手动打开和关闭。
-
使用os.OpenFile(name string, flag int, perm FileMode)(file *File, err error)
name:文件完整路径
flag:打开模式
perm:权限(类Unix生效)
// 打开模式:可以使用"|"进行组合
const (
O_RDONLY int = syscall.O_RDONLY // 只读打开
O_WRONLY int = syscall.O_WRONLY // 只写打开
O_RDWR int = syscall.O_RDWR // 读写打开
O_APPEND int = syscall.O_APPEND // 追加打开
O_CREATE int = syscall.O_CREAT // 如果不存在就创建
O_EXCL int = syscall.O_EXCL // 创建打开,文件必须不存在
O_SYNC int = syscall.O_SYNC // 打开文件用于同步IO
O_TRUNC int = syscall.O_TRUNC // 如果可能,打开文件是清空文件
)
// 权限:
r,w,x
-
使用bufio.NewWriter(file *File) *writer来创建写入器,使用bufio.Flush()将缓存同步到文件,使用bufio.WriterString(str string)来写入文件。
- 使用
io.Copy(dst Writer, src Reader)(written int64, err error)来实现文件拷贝
(31)命令行参数处理
os.Args []string保管了所有命令行参数,第一个参数是程序名称。
flag包可以实现更加高级的命令行参数处理:
var username string
// 绑定参数
flag.StringVar(&username, "u", "root", "Username")
// -- 保存参数字符串的地址
// -- 参数名称
// -- 默认值
// -- 参数释义
// 解析参数
flag.Parse()
(32)Json文件处理
结构体、切片、Map等都可以解析为Json字符串,使用encoding/json.Marshal(i interface{},)([]byte, error)来实现各种类型到Json数据;使用encoding/json.Unmarshal(Json字符串, 实例对象的引用)反序列化。
(33)单元测试
Go语言自带轻量级的测试框架和go test -v命令来实现单元测试和性能测试。Go的测试指令会自动识别以TestXxx命名的函数:
import "testing"
func TestXxx(t *testing.T){
t.Fatalf(string) // 停止测试并记录日志
}
(34)goroutine
Go主线程可以理解为线程也可以理解为进程,一个Go线程可以包含多个协程(微程),Go程具备以下几点特质:
-
有独立的栈空间
-
共享程序堆空间
-
调度由用户控制
- 轻量级的线程
主线程是一个重量级物理线程,直接作用在CPU上,非常消耗资源,协程从主线程开启,是逻辑态的轻量级线程,相对资源消耗少。在Go中可以轻松开启成千上万个协程,其他语言的并发机制一般是线程实现,这就是Go的优势。使用go关键字修饰一个函数等即可开启一个Go程。Go可以充分发挥多核多CPU的优势,使用runtime.NumCpu()可以获取当前机器的CPU个数,使用runtime.GOMAXPROCS(n int)设置可用的CPU数量。在Go1.8之前需要手动设置,Go1.8以后默认使用多CPU。
(35)管道
不同的Go协程如何实现通信,下面给出两种方法:
在Go中,sync包提供了基本的同步单元,大部分适用于低水平的程序线程,高水平的同步一般使用管道解决。
-
使用全局变量加锁
使用sync.Lock 声明一个全局变量:
var lock sync.Mutex
使用lock.Lock()加锁,使用lock.Unlock()解锁。
-
使用管道
管道的本质就是队列,使用var 管道名称 chan 数据类型,channel必须是引用类型,管道使用make声明空间以后才可以使用,管道是有数据类型区分的,如果要存储任意数据类型需要声明为interface{}类型。
管道使用<-运算符存取数据:
var MyChannel chan int
MyChannel = make(chan int, 8)
MyChannel <- 8 // 存入数据
number := <- MyChannel // 取出数据
close(MyChannel) // 关闭管道,但是可以读取数据
管道容量用完以后不能再存储数据;在没有协程使用的情况下,如果管道的数据用完就会产生dead lock错误。
默认情况下管道是双向的,可读可写,声明只读/写管道:
var chan_0 = chan<- int // 只读
var chan_1 = <-chan int // 只写
在使用管道读取数据的时候没有关闭可能会发生阻塞,如果没有数据会发生死锁现象,因此可以使用select关键字来解决。
for {
select {
case v <- chan_0 :
// something
default:
// something
}
}
如果有一个协程出现panic,将会导致整个程序崩溃,因此需要异常处理机制来维护。
(36)反射
通过reflect.TypeOf()获取变量类型,返回reflect.Type类型
通过reflect.ValueOf()获取变量的值,返回reflect.Value类型
(37)网络编程
listen, err := net.Listen("tcp", "0.0.0.0:8888")
if err != nil{
return
}
defer listen.Close()
for {
connect, err := listen.Accept()
if err == nil {
go func (connect net,Conn)(){
defer connect.Close()
buffer := make([]byte, 1024)
num, err := connect.Read(buffer)
if err != nil {
return
}
}()
}
}
connect, err := Dial("tcp", "127.0.0.1:8888")
if err != nil {
return
}
num, err := connect.Write([]byte("Hello"))
connect.Close()