Tips
写给大忙人看的Golang教程(一)
阅读本文之前,我认为你已经掌握其他语言基础并写出一个简单的项目。
(1)Golang编程注意事项
- 源文件必须以
.go
为扩展名. - Go应用程序d额执行入口是
main()
方法. - Go代码严格区分大小写.
- Go代码不需要分号.
- Go代码不允许多条语句在同一行出现.
- Go语言重定义的变量和导入的包如果没有被使用不会编译通过.
- Go语言大括号是成对出现的.
(2)Golang中的常用转义字符
\t
制表符\n
换行符\\
一个斜杠\"
一个引号\r
一个回车
(3)注释方式
// 注释内容
行注释/* 注释内容 */
多行注释- 多行注释不可以嵌套多行注释
(4)Golang的代码规范
- 尽量使用行注释注释整个方法或语句
- 使用Tab缩进
- 使用
gofmt -w
格式化代码 - 运算符两侧加空格
-
Golang的代码风格:
// HelloWorld.go package main import "fmt" func main() { fmt.Println("Hello World") }
- 一行代码最好不要超过80个字符
(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
}
-
goto
语句可以无条件跳转,容易造成逻辑混乱,一般不主张使用goto
语句:label: for { goto label }
return
语句用户退出函数return // 或 return some
(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的每一个文件都必须属于一个包。
-
打包:
package packageName
-
导入包:
import "packageName" // 导入多个包 import ( "packageName" ... ) // 导入包时自动从GOPATH下面的src下面引入
-
包支持别名
package main import f "fmt" func main() { f.Println() }
(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)匿名函数
-
直接调用
func (paramsList)(returnList){ // something }()
-
赋值给一个变量
x := func (paramsList)(returnList){ // something } y := x(paramsList)
(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 关键字
-
defer是Go语言中的延时机制,用于处理关闭文件句柄等资源释放操作
package main import "fmt" func main() { SayHello() } func SayHello() { defer fmt.Println("Bye.") fmt.Println("Hi.") } // Hi. // Bye.
- 使用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映射
- 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{ }
- Map支持切片
(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()
有疑问加站长微信联系(非本文作者)