写给大忙人看的Golang教程(一)
阅读本文之前,我认为你已经掌握其他语言基础并写出一个简单的项目。
.go
为扩展名.main()
方法.\t
制表符\n
换行符\\
一个斜杠\"
一个引号\r
一个回车// 注释内容
行注释/* 注释内容 */
多行注释gofmt -w
格式化代码Golang的代码风格:
// HelloWorld.go
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
var
关键字定义变量:var 变量名称 数据类型
var 变量名称 = 值
var
关键字:变量名称 := 值
, 变量名称不应该是已经定义过的变量名称 := 值
等同于var 变量名称 数据类型 = 值
var 变量名称, 变量名称... 数据类型
var 变量名称, 变量名称, ... = 值, 值, ...
var
关键字多变量声明和赋值: 变量名称, 变量名称, ... := 值, 值, ...
var (
变量名称 = 值
...
)
使用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支持使用反引号输出原生字符串
目标数据类型(变量)
。fmt.Sprintf()
字符串格式化函数。strconv.FormatBool()
、strconv.FormatInt()
、strconv.FormatUint()
、strconv.FormatFloat()
格式化函数。strconv.ParseBool()
、strconv.ParseInt()
、strconv.ParseUint()
、strconv.ParseFloat()
格式化函数。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
}
_
表示空标识符x++
和x--
,没有++x
和--x
x := a++
fmt.Sacnf()
:使用指定的格式获取文本fmt.Sacnln()
:以换行为结束的文本略
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
}
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
}
}
// 设置随机数的种子为当前的系统时间
rand.Seed(time.Now().Unix())
// 生成0-99范围的随机数
randomNumber := rand.Intn(100)
break
语句在多层嵌套中可以通过标签指明要终止到哪一层语句块:label:
for {
break label
}
continue
语句在多层嵌套中可以通过标签指明要跳出到到哪一层语句块:label:
for {
continue label
}
goto
语句可以无条件跳转,容易造成逻辑混乱,一般不主张使用goto
语句:
label:
for {
goto label
}
return
语句用户退出函数 return
// 或
return some
函数基本语法:
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
包的本质就是一个目录,Go的每一个文件都必须属于一个包。
打包:
package packageName
导入包:
import "packageName"
// 导入多个包
import (
"packageName"
...
)
// 导入包时自动从GOPATH下面的src下面引入
包支持别名
package main
import f "fmt"
func main() {
f.Println()
}
init
函数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
直接调用
func (paramsList)(returnList){
// something
}()
赋值给一个变量
x := func (paramsList)(returnList){
// something
}
y := x(paramsList)
闭包就是函数与其相关的引用环境构成的实体
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
}
}
}
defer是Go语言中的延时机制,用于处理关闭文件句柄等资源释放操作
package main
import "fmt"
func main() {
SayHello()
}
func SayHello() {
defer fmt.Println("Bye.")
fmt.Println("Hi.")
}
// Hi.
// Bye.
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结尾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纳秒时间戳len()
:求长度new()
:分配内存,主要用来分配值类型make()
:分配内存,主要用来分配引用类型在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
}
}
在Go中数据是值类型,使用以下方式创建数组。
var 数组名称 [元素个数]数据类型 = [元素个数]数据类型{元素}
var 数组名称 = [元素个数]数据类型{元素}
var 数组名称 = [...]数据类型{元素个数}
var 数组名称 = [...]数据类型{索引:值}
for-each/for-range
遍历数组的长度是固定的,切片 的长度不是固定的。
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)
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
,表示元素存在
for-range
遍历for key, value := range mapName{
}
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
}
结构体变量.数据类型 = 值
来访问接口
type 接口名称 interface {
方法名称(参数列表)(返回值列表)
}
类型断言
var number float32
var x interface{}
x = t
t = x.(float32) // 判断一下是否可以转换成为float32类型
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()
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)
来实现文件拷贝os.Args []string
保管了所有命令行参数,第一个参数是程序名称。
flag
包可以实现更加高级的命令行参数处理:
var username string
// 绑定参数
flag.StringVar(&username, "u", "root", "Username")
// -- 保存参数字符串的地址
// -- 参数名称
// -- 默认值
// -- 参数释义
// 解析参数
flag.Parse()
结构体、切片、Map等都可以解析为Json字符串,使用encoding/json.Marshal(i interface{},)([]byte, error)
来实现各种类型到Json数据;使用encoding/json.Unmarshal(Json字符串, 实例对象的引用)
反序列化。
Go语言自带轻量级的测试框架和go test -v
命令来实现单元测试和性能测试。Go的测试指令会自动识别以TestXxx
命名的函数:
import "testing"
func TestXxx(t *testing.T){
t.Fatalf(string) // 停止测试并记录日志
}
Go主线程可以理解为线程也可以理解为进程,一个Go线程可以包含多个协程(微程),Go程具备以下几点特质:
有独立的栈空间
共享程序堆空间
调度由用户控制
主线程是一个重量级物理线程,直接作用在CPU上,非常消耗资源,协程从主线程开启,是逻辑态的轻量级线程,相对资源消耗少。在Go中可以轻松开启成千上万个协程,其他语言的并发机制一般是线程实现,这就是Go的优势。使用go
关键字修饰一个函数等即可开启一个Go程。Go可以充分发挥多核多CPU的优势,使用runtime.NumCpu()
可以获取当前机器的CPU个数,使用runtime.GOMAXPROCS(n int)
设置可用的CPU数量。在Go1.8之前需要手动设置,Go1.8以后默认使用多CPU。
不同的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,将会导致整个程序崩溃,因此需要异常处理机制来维护。
通过reflect.TypeOf()
获取变量类型,返回reflect.Type
类型
通过reflect.ValueOf()
获取变量的值,返回reflect.Value
类型
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()
转载于:https://blog.51cto.com/xvjunjie/2292893