写给大忙人看的Go语言
Tips
写给大忙人看的Golang教程(一)
阅读本文之前,我认为你已经掌握其他语言基础并写出一个简单的项目。10年积累的网站设计、成都网站建设经验,可以快速应对客户对网站的新想法和需求。提供各种问题对应的解决方案。让选择我们的客户得到更好、更有力的网络服务。我虽然不认识你,你也不认识我。但先建设网站后付款的网站建设流程,更有黄石免费网站建设让你可以放心的选择与我们合作。
(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
(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()
(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()
本文题目:写给大忙人看的Go语言
文章地址:http://ybzwz.com/article/jjjodd.html