go基础
go开发语言环境搭建
windows平台
// 下载地址
https://golang.google.cn/dl/
// 查看版本号
go version
// 设置环境变量
go env -w GO111MODULE = 'on' // 1.11版本之后使用mod管理项目
go env -w GOPROXY='https://goproxy.cn' // 代理
go env -w GOROOT = 'c:/go' // go的安装路径
go env -w GOPATH = "" // go的项目安装路径,现在有了mod之后也不怎么用了
linux平台
// 配置环境变量~/.zshrc
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin
注意:
- go包仓库: https://go.dev
第一个程序
package main
import "fmt"
func main() {
fmt.Println("hello go")
}
go常用命令
go build **.go // 编译包和依赖, 在本地目录生成可执行文件
go clean // 移除编译后的对象文件
go doc ** // 显示包或者符号的文档。比如 go doc fmt
go env // 查看go环境信息
go env -w key=value // 设置go环境信息
go bug // 启动错误信息
go fix // 运行go tool fix
go fmt xx.go // 运行gofmt进行格式化
go get xx // 下载并安装包和依赖,可能需要先go mod init 项目名。比如:go get github.com/go-sql-driver/mysql
go install xx // 编译并安装包和依赖
go list // 列出包(.mod存在的目录下)
go run xx.go // 编译并运行go程序
go test // 运行测试
go tool // 运行go提供的工具
go version // 查看go版本
go vet // 运行go tool vet
vscode代码片段
pkgm // main包和main函数
ff // fmt.Printf("", var)
for // for i :=0; i< count; i++{}
forr // for _, v := range aa1{}
fmain // func main(){}
a.print! // fmt.Printf('a: %v\n', a)
函数名.var! // 调用并定义变量
tys // 结构体
tf // 测试函数定义
go项目管理工具
在1.11
版本之前使用gopath管理项目,在之后使用go mod
来管理项目,当然还有第三方的管理工具,例如:govendor
步骤:
- 创建项目
- 初始化项目 go mod init mypro
- 创建包 mkdir ./user
- 创建模块 touch ./user/user.go
- 项目调用
示例:
/*
// user.go
func Hello()string {
return "hello"
}
// main.go
import "mypro/user" // 到文件名
func main(){
s := user.Hello()
fmt.Println(s)
}
*/
go的标识符,关键字,命名规则
1. 标识符
标识符组成:
- 由数字、字母、下划线(_)组成。
- 只能以字母和下划线(_)开头。
- 标识符区分大小写。
示例:
package main
import "fmt"
func main() {
var name string // 正确的标识符
var age int
var email string
fmt.Printf("name: %v\n", name)
fmt.Printf("age: %v\n", age)
fmt.Printf("email: %v\n", email)
// var 1name string // 错误的标识符
// var &test int
// var !asd string
}
2. 关键字
go提供了25个关键字,如下图所示。
break | default | func | interface | select |
---|---|---|---|---|
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
go还提供了36个预定义标识符。 其中包含基本类型的名称和一些基本的内置函数。如下图
append | bool | byte | cap | close | complex | complex64 | complex128 | uint16 |
---|---|---|---|---|---|---|---|---|
copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 |
int32 | int64 | iota | len | make | new | nil | panic | uint64 |
println | real | recover | string | true | uint | uint8 | unitptr |
3. 命名规范
-
任何需要对外暴露的名字【函数名,变量名等】必须以大写字母开头,不需要对外暴露的则使用小写字母开头。
例如:当命名为GetUserName, 则这个对象可以被外部的包所使用(像public)。
命名如果以小写字母开头,则对包外是不可见的,但是整个包内是可见的且可用的(像private)。
// goods.go package goods import "fmt" func SayHello() { fmt.Println("goods....") } func sayPrivateHello() { fmt.Println("say private hello ") } // goods2.go package goods func SayGood2Hello(){ sayPrivateHello() fmt.Println("say goods2 hello") } // main.go package main import ( "test_package_pro2/goods" ) func main() { goods.SayHello() goods.sayPrivateHello() // error. protected }
-
包名称
保持package 名称和目录名称一致,且应该为小写,不要使用下划线和大小写混合。
例如: package dao
例如: package service
-
文件命名
应该为小写单词,使用下划线分隔各个单词。
例如: customer_dao.go
-
结构体命名
使用驼峰命名法
例如:
type CustomerOrder struct{
Name string
Address string
}
order := CustomerOrder
- 接口命名
同结构体,使用驼峰命名法
注意: 单个函数的接口,以“er”作为后缀。例如: Reader, Writer
type Reader interface {
Read(p[] byte)(n int, err error)
}
-
变量命名
使用驼峰命名法,首字母根据访问控制原则,使用大写或者小写。
特殊情况,还要遵循如下原则:
如果遇到特有名词时,且变量为私有,则首字母为小写。如变量类型为bool,则名称以has, is, can,allow开头等。
例如:
var isExist bool
var hasConfilct bool
var allowGitHook bool
-
常量命名
常量使用大写字母组成,多个单词使用下划线分词。
例如: const APP_URL = "https://www.baidu.com"
-
错误处理
错误处理的原则是不能丢弃任何有返回err的调用,即不要使用
_
丢弃, 要全部处理。尽量不使用panic,除非知道在作什么。
例如:
if err != nil {
// 错误处理
} else {
// 正常代码
}
if err != nil {
// 错误处理
return
}
//正常代码
-
单元测试
测试文件名命名规范
example_test.go
,测试用例必须以Test
开头。
变量
1. 变量的声明
go语言中变量需要先声明后使用,同一作用域内不支持重复声明,并且变量声明后必须使用。
格式:
var 变量名 类型
示例:
var name string
var age int
var ok bool
var _ int // ok
2. 变量的批量声明
使用 var
和 小括号
组合使用
示例:
var (
name string
age int
married bool
)
fmt.Printf("name: %v\n", name)
fmt.Printf("age: %v\n", age)
fmt.Printf("married: %v\n", married)
3. 变量的初始化
go语言在声明变量的时候,会自动对变量进行初始化操作,每个变量会初始化称为其类型的默认值(零值)。
例如:整形和浮点型的默认值为0
,字符串的默认值为""
, 布尔类型默认值为false
,切片、map,channel,函数、指针变量的默认值为nil
格式:
var 变量名 类型 = 表达式
示例:
// 初始化
var name string = "tom"
var age int = 12
var site string = "www.baidu.com"
4. 类型推导
可以根据初始值进行类型推导,从而忽略类型
package main
func main() {
// 类型推导, 此时类型可以省略
var name = "tom"
var age = 12
var site = "www.baidu.com"
}
5. 初始化多个变量
// 初始化多个变量
var name, site, age = "tom", "www.baiodu.com", 123 // 还可以不同类型
fmt.Printf("name: %v\n", name)
fmt.Printf("site: %v\n", site)
fmt.Printf("age: %v\n", age)
6. 短变量声明
在函数内部,可以使用:=
运算符进行声明和初始化。
package main
func main(){
name := "tom" // 只能在函数内部使用
age := 123
site := "www.baidu.com"
// _ := "abc" // error 不能用于短变量声明
// 注意: 符号左侧必须有一个变量名未声明过。 (声明中至少一个新的变量)
name := "zs"
gender := "男"
name, age := "ww", 18 // ok
name, gender := "ll", "女" // error
}
7. 匿名变量
使用_
表示变量名称
func getNameAndAge()(string, int){
return "tom", 11
}
func main() {
name, _ := getNameAndAge()
fmt.Println(name)
}
常量
常量的使用
使用const
关键字,在程序编译阶段确定下来的值,在程序运行阶段无法改变的值。在go中,常量可以是数值型(整形,浮点型,复数),布尔类型,字符串类型。 在定义的时候必须初始化, 【可以定义不使用】。
格式:
const 名称 [类型] = 初始化
示例:
const NAME string = "123"
fmt.Printf("NAME: %v\n", NAME)
const PI float32 = 3.14
fmt.Printf("PI: %v\n", PI)
const PI2 = 3.1414 // 可省略类型
fmt.Printf("PI2: %v\n", PI2)
const I, J = 1, 2 // 多重赋值
const A, B, C = 1, 2, "abc" // 不同类型
const ( // 定义多个常量
WIDTH = 100
HEIGHT = 200
)
fmt.Printf("WIDTH: %v\n", WIDTH)
fmt.Printf("HEIGHT: %v\n", HEIGHT)
iota
可被认为是一个可被编译器修改的常量,默认开始值为0
,每调用一次加1
,遇到const
关键字时被重置为0
。
示例:
func main() {
const (
A1 = iota
A2 = iota
A3 = iota
)
fmt.Printf("A1: %v\n", A1) // 0
fmt.Printf("A2: %v\n", A2) // 1
}
// 使用_ 跳过一个值
const (
A4 = iota
_
A5 = iota
)
fmt.Printf("A4: %v\n", A4) // 0
fmt.Printf("A5: %v\n", A5) // 2
// 中间插队
const (
A6 = iota
A7 = 100
A8 = iota
)
fmt.Printf("A6: %v\n", A6) // 0
fmt.Printf("A8: %v\n", A8) // 2
const (
AA1 = 100
BB1
CC1 // 100
)
fmt.Printf("BB1: %v\n", BB1) // 100
fmt.Printf("CC1: %v\n", CC1) // 100
const (
A11 = iota // 0
A22 = 100
A34 = iota // 2
)
数据类型
go语言中有以下几种数据类型:
序号 | 类型和描述 |
---|---|
1 | 布尔型 布尔值只能为true和false。比如:var b bool = true |
2 | 数字类型 整形有int和浮点型float32, float64,复数 |
3 | 字符串类型 使用UTF-8编码 |
4 | 派生类型 包括 指针类型(Pointer)、数组类型、结构体类型、 Channel类型、函数类型、切片类型、接口类型(interface)、Map类型 |
示例:
package main
import "fmt"
func foo() {
}
func main() {
var name string = "tom"
age := 20
b := true
fmt.Printf("%T\n", name) // 使用%T打印类型
fmt.Printf("%T\n", age) // int
fmt.Printf("%T\n", b) // bool
a := 100
c := &a
fmt.Printf("%T\n", c) // *int
arr := [2]int{1, 2}
fmt.Printf("%T\n", arr) // [2]int
arr2 := []int{1, 2, 3}
fmt.Printf("%T\n", arr2) // []int
// 函数类型
fmt.Printf("%T\n", foo) // func()
}
布尔类型
布尔类型有两个常量,true
和false
, 经常用在条件判断
语句和循环语句
。可以用于逻辑表达式中。
示例:
package main
import "fmt"
func main() {
var b1 bool = true
var b2 bool = false
var b3 = true
var b4 = false
b5 := true
b6 := false
fmt.Printf("b1: %v\n", b1)
fmt.Printf("b2: %v\n", b2)
fmt.Printf("b3: %v\n", b3)
fmt.Printf("b4: %v\n", b4)
fmt.Printf("b5: %v\n", b5)
fmt.Printf("b6: %v\n", b6)
}
用于条件判断中
// 用于判断里面
age := 16
ok := age >= 18
fmt.Printf("ok: %v\n", ok)
if ok {
fmt.Println("成年了")
} else {
fmt.Println("没有成年")
}
用于循环中
// 用与循环中
count := 10
for i := 0; i < count; i++ {
fmt.Printf("%d ", i)
}
用于逻辑表达式中
// 用于逻辑表达式中
age1 := 19
gender := "男"
if age1 > 18 && gender == "男" {
fmt.Println("你是成年男子")
}
注意:
-
在go中,不能用
0
或非0
表示条件i := 1 if i { // error 不能使用非bool作为条件 }
数字类型
整形
类型 | 描述 |
---|---|
uint8 | 无符号8位 |
uint16 | 无符号16位 |
uint32 | 无符号32位 |
uint64 | 无符号64位 |
int8 | 有符号8位 |
int16 | 有符号16位 |
int32 | 有符号32位 |
int64 | 有符号64位 |
浮点类型
类型 | 描述 |
---|---|
float32 | 32位浮点类型 |
float64 | 64位浮点类型 |
complex64 | 复数(32位实部吗,32位虚部) |
complex128 | 复数(64位实部,64位虚部) |
其他类型
类型 | 描述 |
---|---|
byte | 字节类型,类似uint8 |
rune | 同int32。 type rune = int32 |
uintptr | 无符号整形,存放一个指针 |
注意:
- go也有基于架构的类型。例如:
int
,uint
和uintptr
。 - 其中,
int
和uint
在32位操作系统上,使用32位(4个字节),在64位操作系统上,使用64个字节,uintptr
的长度设定为足够存放一个指针即可。 - go语言中,没有
float
类型,也没有double
类型。
示例:
package main
import (
"fmt"
"math"
"unsafe"
)
func main() {
var i8 int8
var i16 int16
var i32 int32
var i64 int64
var ui8 uint8
var ui16 uint16
var ui32 uint32
var ui64 uint64
var f32 float32
var f64 float64
var i int
var ui uint
fmt.Printf("%T %d %v~%v\n", i8, unsafe.Sizeof(i8), math.MinInt8, math.MaxInt8)
fmt.Printf("%T %d %v~%v\n", i16, unsafe.Sizeof(i16), math.MinInt16, math.MaxInt16)
fmt.Printf("%T %d %v~%v\n", i32, unsafe.Sizeof(i32), math.MinInt32, math.MaxInt32)
fmt.Printf("%T %d %v~%v\n", i64, unsafe.Sizeof(i64), math.MinInt64, math.MaxInt64)
fmt.Printf("%T %d %v~%v\n", ui8, unsafe.Sizeof(ui8), 0, math.MaxUint8)
fmt.Printf("%T %d %v~%v\n", ui16, unsafe.Sizeof(ui16), 0, math.MaxInt16)
fmt.Printf("%T %d %v~%v\n", ui32, unsafe.Sizeof(ui32), 0, math.MaxInt32)
fmt.Printf("%T %d %v~%v\n", ui64, unsafe.Sizeof(i8), 0, math.MaxInt64)
fmt.Printf("%T %d %v~%v\n", f32, unsafe.Sizeof(f32), -math.MaxFloat32, math.MaxFloat32)
fmt.Printf("%T %d %v~%v\n", f64, unsafe.Sizeof(f64), -math.MaxFloat64, math.MaxFloat64)
fmt.Printf("%T %d %v~%v\n", i, unsafe.Sizeof(i), math.MinInt, math.MaxInt)
fmt.Printf("%T %d %v~%v", ui, unsafe.Sizeof(ui), 0, uint(math.MaxUint)) // int类型溢出
}
数字的进制
// int类型进制定义和输出
var a int = 10
fmt.Printf("%d\n", a) // 10进制输出
fmt.Printf("%b\n", a) // 2进制格式输出
// 8进制
var b int = 077
fmt.Printf("%o\n", b) // 8进制格式输出
// 16进制
var c int = 0xff
fmt.Printf("%x\n", c) // 16进制格式输出
fmt.Printf("%X\n", c)
浮点型和复数
// 浮点型
fmt.Printf("%f\n", math.Pi)
fmt.Printf("%.2f\n", math.Pi)
// 复数
var d complex64
fmt.Println(d)
字符串类型
go语言的字符串是一个任意字节的常量序列。
格式:
var 名称 string // 字符串声明
1. 字符串定义声明且初始化
在go语言中,字符串字面量使用""
或者反引号`来创建。双引号支持转义,但不能用来引用多行。
反引号创建的字符串,可以由多行组成,但不支持转义,并且可以包含除反引号外所有其他字符。
示例:
package main
import "fmt"
func main() {
var name string = "hello\tworld\nasdasdsda" // 支持转义
fmt.Printf("%s", name)
// 不能转义\n
var html string = `
<html>
<body>hello world\n</body>
</html>
`
fmt.Printf("%v", html)
str := "hello world"
fmt.Printf("%v", str)
}
2. 字符串连接
// 字符串连接
// 方法1: 使用+号
var name1 = "zs"
var age = "20"
msg := name1 + age
fmt.Printf("msg: %v\n", msg)
// 方法2: 使用fmt.Sprintf()
s := fmt.Sprintf("%s,%s", name1, age)
fmt.Printf("s: %v\n", s)
// 方法3: 使用strings模块的join()方法
s2 := strings.Join([]string{name1, age}, ",")
fmt.Printf("s2: %v\n", s2)
// 方法4: 使用bytes.Buffer
var buffer bytes.Buffer
buffer.WriteString(name1)
buffer.WriteString(",")
buffer.WriteString(age)
fmt.Printf("string: %v\n", buffer.String())
3. 转义字符
符号 | 解释 |
---|---|
\t |
制表符 |
\n |
换行符 |
\r |
回车 |
\' |
单引号 |
\" |
双引号 |
\\ |
反斜杠 |
4. 字符串的切片操作
示例:
// 字符串切片操作
str := "helloAworld"
n := 3
m := 5
fmt.Println(str[0]) // h
fmt.Println(str[n]) // l
fmt.Println(str[m]) // A
fmt.Println(str[n:m]) // lo
fmt.Println(str[n:]) // loAworld
fmt.Println(str[:m]) // hello
// 注意:中文切片的问题
5. 字符串的常用函数
函数名 | 函数功能 |
---|---|
len(str) | 求字符串的长度。注意:utf-8编码一个英文字符占用1个长度,中文占3长度。例如:len("go语言编程") 为14 |
fmt.Sprintf("", var) | 字符串拼接 |
strings.Split(str, sep) [] string | 字符串的分割,返回字符串切片 |
strings.Contains(str, "") bool | 判断是否包含,返回bool类型 |
strings.HasPrefix(str, "") bool / strings.HasSuffix() bool | 前缀和后缀判断,返回bool类型 |
strings.Index(str, "") int / strings.LastIndex() int | 子串出现的第一次或最后一次的位置, |
strings.Join(a[]string, sep string) string | join操作 |
strings.ToLower(str) string | 字符串全部转为小写 |
strings.ToUpper(str) string | 字符串全部转为大写 |
strings.Count(str, substr) int | 统计substr出现的次数,如果substr为空,则为字符个数+1 |
strings.Compare(strA, strB) int | 比较两个字符串,返回int,如果A==B返回0,如果A>B返回1,否则返回-1。 |
strings.Repeat(str, count) string | 返回一个新的string, 对str重复count次 |
strings.Replace(s, old, new, count) string | 将s字符串的old替换为new,替换count次 |
strings.ReplaceAll(s, old, new) string | 字符串替换 |
strings.Trim(s, substr) | 去除s开始位置和最后位置的substr |
strings.TrimLeft(s, substr) | 去除s开始位置的substr |
strings.TrimRight(s, substr) | 去除s最后位置的substr |
strings.TrimSpace(s) | 去除s开始位置和最后位置所有的\t, \n \r等 |
示例:
// 字符串的常用函数
s1 := "fanzone"
fmt.Printf("len(s): %v\n", len(s1))
// 1. 字符串分割
s3 := strings.Split(s1, "n")
fmt.Printf("s3: %v, len(s3)=%d \n", s3, len(s3)) // [fa zo e]
s4 := "hello world"
fmt.Printf("s4 split: %v\n", strings.Split(s4, " ")) // [hello world]
// 2. 是否包含某个字符串
s5 := "Hello World"
fmt.Printf("s5 contains: %v\n", strings.Contains(s5, "hello")) // false
// 3. 大小写
fmt.Printf("strings.ToLower(s5): %v\n", strings.ToLower(s5))
fmt.Printf("strings.ToUpper(s5): %v\n", strings.ToUpper(s5))
// 4. 以前后缀结尾
fmt.Printf("strings.HasPrefix(s5, "hello"): %v\n", strings.HasPrefix(s5, "hello")) // false
fmt.Printf("strings.HasPrefix(s5, "Hello"): %v\n", strings.HasPrefix(s5, "Hello")) // true
fmt.Printf("strings.HasSuffix(s5, "world"): %v\n", strings.HasSuffix(s5, "world")) // false
// 5. 返回索引, 查找
fmt.Printf("strings.Index(s5, "ll"): %v\n", strings.Index(s5, "ll")) // 2
fmt.Printf("strings.LastIndex(s5, "l"): %v\n", strings.LastIndex(s5, "l")) // 9
// 6. Join连接
var names = []string{"zs", "ls", "ww", "zl"}
fmt.Printf("strings.Join(names, "-"): %v\n", strings.Join(names, "-")) // zs-ls-ww-zl
// 7. Count统计
fmt.Printf("strings.Count(s5, "l"): %v\n", strings.Count(s5, "l")) // 统计l出现的次数 3
// 8. Compare("a", "b") 字符串比较
fmt.Printf("strings.Compare(\"a\", \"b\"): %v\n", strings.Compare("a", "b"))
// 9. Repeat(str, count) 返回一个count次组成的str的新的字符串
fmt.Printf("strings.Repeat(\"abc\", 3): %v\n", strings.Repeat("abc", 3))
// 10.Replace(s, old, new, count) 字符串替换count次
fmt.Printf("strings.Replace("hello world", "l", "L", 2): %v\n", strings.Replace("hello world", "l", "L", 2))
fmt.Printf("strings.ReplaceAll(\"hello world\", \"l\", \"L\"): %v\n", strings.ReplaceAll("hello world", "l", "L"))
// 11. Trim() 去除字符串前后的substr
fmt.Printf("strings.Trim(\"!hello!\", \"!\"): %v\n", strings.Trim("!hel!lo!", "!"))
// 12.TrimLeft() 去除字符串左侧的substr
fmt.Printf("strings.TrimLeft(\"!hello!\", \"!\"): %v\n", strings.TrimLeft("!hello!", "!"))
fmt.Printf("strings.TrimRight(\"!hello!\", \"!\"): %v\n", strings.TrimRight("!hello!", "!"))
fmt.Printf("strings.TrimSpace(): %v\n", strings.TrimSpace("\t hello \n world\n\r\t"))
6. 格式化输出
普通占位符
符号 | 含义 |
---|---|
%v | 任何变量输出。若为结构体,输出结构体变量的值。 例如: |
%#v | 任何变量输出。字符串输出会带引号,结构体会返回变量的详细结构输出。 例如: |
%T | 返回变量类型 |
%% | 百分号占位符 |
布尔占位符
符号 | 含义 |
---|---|
%t | bool占位符 |
整数占位符
符号 | 含义 |
---|---|
%b | 二进制 |
%c | 字符输出 |
%d | 十进制输出 |
%o | 八进制输出 |
%x / %X | 十六进制输出 |
%U | unicode格式输出 |
浮点数和复数的组成(实部和虚部)
符号 | 含义 |
---|---|
%e | 科学计数法 |
%E | 科学计数法 |
%f | 有小数,无指数,浮点数 |
字符串与字节切片
符号 | 含义 |
---|---|
%s | 字符串 |
%c | 字符 |
%q | 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示 |
指针
符号 | 含义 |
---|---|
%p | 输出地址,不能输出变量,十六进制类型。 例如:var a int ; fmt.Printf("%p", a) // 输出的内容有误 |
示例:
package main
import "fmt"
type Website struct {
Name string
}
func main() {
var site = Website{Name: "baidu"}
// %v var 任何变量输出
fmt.Printf("%v\n", "abcdef") // abcedf
fmt.Printf("%#v\n", "abcdef") // "abcdef"
// %T 返回变量类型
fmt.Printf("%T\n", site) // main.Website
// %%百分号
fmt.Printf("%%\n") // %
// 布尔占位符
fmt.Printf("%t\n", false)
// 整数占位符
fmt.Printf("%b\n", 9) // 二进制
fmt.Printf("%o\n", 8) // 八进制
fmt.Printf("%x\n", 12) // 十六进制
fmt.Printf("%d\n", 10) // 十进制
fmt.Printf("%X\n", 10) // 十六进制
// 浮点数
fmt.Printf("%e\n", 0.01)
fmt.Printf("%E\n", 0.01)
fmt.Printf("%f\n", 0.01)
// 字符串和字节切片
fmt.Printf("%c\n", 'c')
fmt.Printf("%c\n", 97) // a
fmt.Printf("%s\n", "hello world")
fmt.Printf("%q\n", "hello\tdef") // "hello\tdef"
// 指针
a := 100
b := &a
fmt.Printf("%p\n", b) // 0xc000014138
fmt.Printf("%x\n", b) // c000014138
fmt.Printf("%d", *b) // 100
}
运算符
go中的运算符,包含:
- 算术运算符
- 关系运算符
- 逻辑运算符
- 位运算符
- 赋值运算符
1. 算术运算符
运算符 | 描述 |
---|---|
+ | 加法 |
- | 减法 |
* | 乘法 |
/ | 除法 |
% | 取余 |
注意: ++
和--
在go语言中是单独语句,并不是运算符。
示例:
a := 100
b := 11
// 算术运算符
x := a + b
fmt.Printf("x: %v\n", x)
x = a - b
fmt.Printf("x: %v\n", x)
x = a * b
fmt.Printf("x: %v\n", x)
x = a / b
fmt.Printf("x: %v\n", x)
x = a % b
fmt.Printf("x: %v\n", x)
c := 100
c-- // 自减
fmt.Printf("c: %v\n", c)
c++ // 自加
fmt.Printf("c: %v\n", c)
2. 关系运算符
运算符 | 描述 |
---|---|
== | 等于 |
> < | 大于 小于 |
<= >= | 小于等于 大于等于 |
!= | 不等于 |
示例:
// 关系运算符
r := a == b
fmt.Printf("%v\n", r)
r = a > b
fmt.Printf("%v\n", r)
r = a < b
fmt.Printf("%v\n", r)
r = a >= b
fmt.Printf("%v\n", r)
r = a <= b
fmt.Printf("%v\n", r)
r = a != b
fmt.Printf("%v\n", r)
3. 逻辑运算符
操作数类型需要为bool
运算符 | 描述 |
---|---|
&& | and运算符 |
|| | or运算符 |
! | 取反运算符 |
示例:
// 逻辑运算符
// r = a && b // error类型需要为bool
a1 := true
a2 := false
r = a1 && a2
fmt.Printf("%v\n", r)
a1 = false
a2 = true
r = a1 || a2
fmt.Printf("%v\n", r)
fmt.Printf("%v\n", !a1)
fmt.Printf("%v\n", !a2)
fmt.Printf("%v\n", 2 == 5 && 4 > 2) // false
4. 按位运算符
运算符 | 描述 |
---|---|
& | 按位与 |
| | 按位或 |
^ | 异或 |
<< | 左移 |
>> | 右移 |
示例:
// 位运算符
fmt.Println(2 & 3) // 0010 & 0011 => 0010
fmt.Println(2 | 3) // 0010 | 0011 => 0011
fmt.Println(2 ^ 3) // 0001
fmt.Println(4 << 1) // 0100 << 1 => 1000
fmt.Println(4 >> 1) // 0010
5. 赋值运算符
运算符 | 描述 |
---|---|
= | 赋值 |
+= | 相加后赋值 |
-= | 相减后赋值 |
*= | 相乘后赋值 |
/= | 相除后赋值 |
%= | 取余后赋值 |
<<= | 左移后赋值 |
>>= | 右移后赋值 |
示例:
// 赋值运算符
a3 := 100
a3 = 1000
fmt.Printf("a3: %v\n", a3)
a3 += 10
fmt.Printf("a3: %v\n", a3)
a3 -= 10
fmt.Printf("a3: %v\n", a3)
a3 *= 10
fmt.Printf("a3: %v\n", a3)
a3 /= 10
fmt.Printf("a3: %v\n", a3)
a3 %= 10
fmt.Printf("a3: %v\n", a3)
流程控制
1. 条件语句
if语句
格式:
if bool表达式 {
// 执行代码
}
注意:
- 在go中,布尔表达式不用使用括号。
示例:
package main
import "fmt"
func main() {
var age = 20
if age > 18 {
fmt.Println("你是成年人")
}
}
注意:
- 初始变量可以声明在布尔表达式里面,注意它的作用域
示例:
if age := 20; age > 18 { // 声明在布尔表达式里面
fmt.Println("你是成年人了.")
}
if age := user.age; age > 18 {
}
注意:
-
if
语句必须使用大括号{}
,即使只有一条语句。age := 20 if age > 18 fmt.Println("成年了") // error
-
左括号
{
必须与if
和else
同一行。 -
在
if
语句,条件语句之前,可以添加初始化语句,使用;
进行分隔。 -
不能使用
0
或非0
表示真假age := 10 if age { // error, 需要为bool类型 }
if-else语句
格式:
if 布尔表达式 {
// 为true执行
} else { // 必须前面有} 后面有 { 括号
}
示例:
// if else语句
a := 1
b := 2
if a > b {
fmt.Printf("a: %v\n", a)
} else {
fmt.Printf("b: %v\n", b)
}
// 判断是奇数还是偶数
var s int
fmt.Println("输入一个数字")
fmt.Scan(&s)
if s%2 == 0 {
fmt.Println("是偶数")
} else {
fmt.Println("是奇数")
}
fmt.Printf("s: %v\n", s)
if-else-if语句
格式
if 布尔表达式 {
} else if 布尔表达式 {
} else {
}
示例:
// if-else-if语句
score := 80
if score >= 90 {
fmt.Println("优秀")
} else if score >= 80 && score < 90 {
fmt.Println("良好")
} else if score >= 70 && score < 80 {
fmt.Println("还行")
} else if score >= 60 && score < 70 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
示例:
// Monday tuesday wendnesday thursday friday saturday sunday
var c string
fmt.Println("输入一个字符")
fmt.Scan(&c)
if c == "S" {
fmt.Println("请输入第二个字符")
fmt.Scan(&c)
if c == "A" {
fmt.Println("saturday")
} else {
fmt.Println("sunday")
}
} else if c == "F" {
fmt.Println("friday")
} else if c == "T" {
fmt.Println("请输入第二个字符")
fmt.Scan(&c)
if c == "U" {
fmt.Println("tuesday")
} else if c == "H" {
fmt.Println("thursday")
}
} else if c == "W" {
fmt.Println("wendnesday")
} else if c == "M" {
fmt.Println("Monday")
} else {
fmt.Println("有误!")
}
if嵌套语句
格式:
if 布尔表达式 {
if 布尔表达式 {
}
}
示例:
// if嵌套
a11 := 100
b11 := 200
c11 := 3
if a11 > b11 {
if a11 > c11 {
fmt.Println("a11最大")
}
} else {
if b11 > c11 {
fmt.Println("b11最大")
} else {
fmt.Println("c11最大")
}
}
switch语句
格式:
switch [var1] { // var1不写,switch默认为true,case可写成条件表达式
case val1:
...
case val2:
...
default:
...
}
示例:
// switch语句
grade := "B"
switch grade {
case "A":
fmt.Println("优秀")
case "B":
fmt.Println("良好")
case "C":
fmt.Println("一般")
default:
fmt.Println("及格")
}
注意:
switch
语句中不需要break
语句。一个case
执行结束,不会执行下一个case
。默认只执行一个case
switch的多条件匹配
switch
语句可以同时匹配多个条件,中间使用逗号分割,有一个匹配成功即可。
示例:
// switch多条件匹配
day := 7
switch day {
case 1, 2, 3, 4, 5:
fmt.Println("工作日")
case 7, 6:
fmt.Println("休息日")
default:
fmt.Println("其他日")
}
case可以是条件表达式
switch
的条件不写,默认为true
示例:
// case可以是条件表达式
score = 85
switch {
case score >= 90:
fmt.Println("有假期")
case score < 90 && score >= 80:
fmt.Println("好好学习吧")
default:
fmt.Println("玩命学习!")
}
switch{
case true: // ok
fmt.Println("ok")
}
fallthrough 可以执行满足条件的下一个
case
向下穿透
示例:
// fallthrough语句
a = 100
switch a {
case 100:
fmt.Println("100")
fallthrough
case 200:
fmt.Println("200")
case 300:
fmt.Println("300")
default:
fmt.Println("other")
}
// 100
// 200
// fallthrough语句
a = 100
switch a {
case 100:
fmt.Println("100")
fallthrough
case 200:
fmt.Println("200")
fallthrough
case 300:
break // 中止fallthrough
fmt.Println("300") // 不会执行, 如果a为300时候,也不会执行
default:
fmt.Println("other")
}
// 100
// 200
总结:
switch
支持多条件匹配(一个case多个值)- 如果想要执行多个
case
,需要使用fallthrough
关键字,也可以使用break
中止。 switch
分支可以使用条件表达式。例如:a > 10
fallthrough
不能在对最后一个case
使用。
2. 循环语句
go语言中,只有for
循环
for语句
格式:
for 初始语句;条件表达式;结束语句 {
循环体
}
注意: for
表达式也不需要加括号。
示例:
// 基本写法
for i := 1; i < 11; i++ {
fmt.Printf("i: %v\n", i)
}
// 初始条件可以省略
i := 1
for ; i <= 10; i++ {
fmt.Printf("i: %v\n", i)
}
// 初始条件和结束条件也可以省略
i = 1
for i <= 10 {
fmt.Printf("i: %v\n", i)
i++
}
// 永真循环
for {
fmt.Println("一直执行")
}
// 不能使用var
// for var i = 0; i< 10; i++{ // 错误写法
//}
for range语句
可以使用for range
遍历数组,切片,字符串,map和通道(channel),通过for range
遍历有以下特点:
- 数组、切片、字符串返回索引和值。
- map返回键和值
- 通道(channel)只返回通道内的值。
示例:
// for range语句
// 循环数组
var a = [5]int{1, 2, 3, 4, 5}
for i, v := range a {
fmt.Printf("index=%d, value:%d\n", i, v)
}
for _, v := range a { // 数组
fmt.Printf("v: %v\n", v)
}
s := []int{10, 20, 30, 40} // 切片
for i, v := range s {
fmt.Printf("index: %v value: %v\n", i, v)
}
m := make(map[string]string) // map
m["name"] = "fhz"
m["age"] = "20"
m["email"] = "fhz@gmail.com"
for k, v := range m {
fmt.Printf("%v:%v\n", k, v)
}
str := "hello" // string
for i, v := range str {
fmt.Printf("%v: %c\n", i, v)
}
break关键字
可以结束for
, switch
, select
的代码块。也可以在break
语句后加标签,用于跳出某个标签对应的代码块。
注意:
- 单独在
select
使用break
和不使用break
没有啥区别。 - 单独在
switch
语句中,没有fallthrough
语句,使用break
和不使用没区别 - 单独在
switch
语句中,并且有fallthrough
语句,使用break
可以中止fallthrough
后面的case。 - 带标签的
break
,可以跳出多层的for / select / switch
作用域。让break
更加灵活,不需要一层一层循环。没有带标签的break
只能跳出当前语句块。 - 标签要求定义在对应的
for
、swtich
、select
代码块上。
示例:
package main
import (
"fmt"
)
func main() {
for i := 0; i < 10; i++ {
fmt.Printf("i: %v\n", i)
if i >= 5 {
break
}
}
i := 2
switch i {
case 1:
fmt.Println("1")
case 2:
fmt.Println("2")
fallthrough
case 3:
fmt.Println("3")
break // 不会执行4
fmt.Println("4")
}
// break加标签
test()
}
func test() {
MY_LABEL:
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
fmt.Println(i, j)
if j == 5 {
break MY_LABEL
}
}
}
fmt.Println("end....") // break之后这里会执行。
}
/*
// test() 打印结果
0 0
0 1
0 2
0 3
0 4
0 5
end....
*/
continue关键字
在go语言中,continue
只能使用在for
循环中,中止本次循环。
示例:
package main
import "fmt"
func main() {
// 输出偶数
for i := 0; i < 10; i++ {
if i%2 == 1 {
continue // 跳出本层的本次循环
} else {
fmt.Printf("i: %v\n", i)
}
}
// 跳转到label
test()
}
func test() {
MY_LABEL:
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
continue MY_LABEL // 当j=2时候,contine 内层所有,从i的下一个值开始。
}
fmt.Printf("%d: %d\n", i, j)
}
}
}
/*
i: 0, j:0
i: 0, j:1
i: 0, j:2
i: 1, j:0
i: 1, j:1
i: 1, j:2
i: 2, j:0
i: 2, j:1
i: 2, j:2
...
*/
goto关键字
无条件跳转。
package main
import "fmt"
func test() {
a := 0
if a == 1 {
goto LABEL
} else {
fmt.Println("other")
}
LABEL:
fmt.Println("next...")
}
func main() {
test()
// 跳出多层循环
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if i >= 2 {
goto END
} else {
fmt.Println(i, j)
}
}
}
END:
fmt.Println("END...")
}
注意:
goto
语句的label,可以在goto之前,也可以之后。但break
和continue
的label只能在break
和contiue
之前。
数组
数组为相同类型的一组数据集合,一旦长度固定就不能修改,数据可以通过下标来访问。
数组的定义
声明格式:
var 变量名 [size] 类型 // 数组声明
// size 必须为常量
示例:
var a [3]int
var s [2]string
fmt.Printf("a: %v\n", a)
fmt.Printf("s: %v\n", s)
fmt.Printf("a: %T\n", a) // [3]int
数组的声明
对数组进行声明,为未初始化的数组,整形默认为0,布尔默认为false,字符串默认为空字符。
示例:
var a [3]int // 未初始化的int数组,长度为3
var s [2]string
fmt.Printf("a: %v\n", a) // [0 0 0]
fmt.Printf("s: %v\n", s) // []
fmt.Printf("a: %T\n", a) // [3]int
var a3 [3]bool
fmt.Printf("a3: %v\n", a3) // [false false false]
使用初始化列表进行初始化
// 使用初始化列表,初始化
var a1 = [4]int{1, 2, 3}
fmt.Printf("a1: %v\n", a1) // [1 2 3 0]
var a2 = [2]string{"hello", "world"}
fmt.Printf("a2: %v\n", a2)
var a4 = [2]bool{true, false}
fmt.Printf("a4: %v\n", a4) // true false
省略数组长度
数组的长度可以使用...
省略。长度根据初始化值的数量自动推断。
// 省略长度
var a5 = [...]int{10, 12, 13, 11}
fmt.Printf("a5: %v\n", a5)
fmt.Printf("len(a5): %v\n", len(a5))
var s1 = [...]string{"hello", "world"}
fmt.Printf("s1: %v\n", s1)
var b1 = [...]bool{true, false}
fmt.Printf("b1: %v\n", b1)
指定索引值的方式来初始化
可以通过指定索引的方式来初始化,未指定的位置默认为零值。
示例:
var arr = [...]int{0: 1, 2: 2, 10: 1} // 0号位置值为1,2位置值为2,10号位置值为1
fmt.Printf("arr: %v\n", arr) // [1 0 2 0 0 0 0 0 0 0 1]
var str1 = [...]string{1: "tom", 3: "world"}
fmt.Printf("str1: %v\n", str1)
var bo1 = [...]bool{1: true, 4: false}
fmt.Printf("bo1: %v\n", bo1) // [false true false false false]
// 两种方法混用
var arr3 = [...]int{1, 2, 3, 10:200} // [1 2 3 0 0 0 0 0 0 0 200]
数组的切片操作
// 按索引取值
fmt.Printf("arr[2]: %v\n", arr[2]) // 3
fmt.Printf("arr[2:4]: %v\n", arr[2:4]) // [3 4] 不包含索引为4
数组的修改
示例:
// 数组的修改
var arr2 = [...]int{1, 2, 3}
fmt.Printf("arr2: %v\n", arr2)
arr2[0] = 100
fmt.Printf("arr2: %v\n", arr2)
数组的遍历
// 数组的遍历
// 使用索引和下标
for i := 0; i < len(strArr); i++ {
fmt.Printf("%v\n", strArr[i])
}
// 使用for range访问
for i, v := range strArr {
fmt.Printf("%v: %v\n", i, v)
}
数组的地址
数组会在内存中开辟连续的内存空间,取数组的地址(&arr)就是数组第一个元素的地址。
对数组赋值给另一个变量,另一个变量是全新的变量,修改一个变量不影响第二个变量。
package main
import "fmt"
func main() {
var a [3]int
a[0] = 100
fmt.Println(a) // [100 0 0]
fmt.Printf("%p\n%p\n%p\n%p\n", a, &a, &a[0], &a[1]) // a 为变量,输出的%p有误
b := a // 重新定义了b
b[1] = 200
fmt.Printf("a: %v\n", a) // [100 0 0]
fmt.Printf("b: %v\n", b) // [100 200 0]
fmt.Printf("%p\n%p\n%p\n%p\n", b, &b, &b[0], &b[1])
}
`
%!p([3]int=[100 0 0]) // a
0xc00001a2b8 // &a
0xc00001a2b8 // &a[0]
0xc00001a2c0 // &a[1]
%!p([3]int=[100 0 0]) // b
0xc0000ba048 // 赋值,则重新定义了变量b,与a没有引用关系。
0xc0000ba048
0xc0000ba050
`
切片
数组是固定长度,可以容纳相同数据类型的元素集合。缺点:当长度固定时,若申请的长度太大,会浪费内存,太小又不够用。
切片(slice),可以理解为可变长数组,是一个拥有相同类型元素的可变长度序列,底层是使用数组来实现的,增加了自动扩容的功能。
切片的定义
声明格式:
var 变量名 [] 类型
例如: var names[]string // 切片的零值为 names == nil
切片是引用类型,切片变量保存的是底层数组的元素的地址。可以使用make
来创建切片并初始化:
var slice1 []类型 = make([]类型, 长度)
也可以简写:
slice1 := make([]类型, 长度)
make()函数
make([]Type, length, capacity)
// length是数组的长度,也是切片的初始长度。
// capacity 为底层数组的长度。
示例:
// 1. 切片声明
var s []int // nil切片 []
fmt.Printf("s: %p\n", s) // 0x00
s = append(s, 100)
fmt.Printf("s: %p\n", s) // 0xc000014150
// 2. 定义为空切片
var s = []int{} // []
fmt.Printf("a1: %p\n", a1) // 0x555f30
// 3. 声明并初始化,使用make函数创建的切片,长度为2, 元素都为0
var s2 = make([]int, 2) // [0 0]
fmt.Printf("s2: %v\n", s2)
注意:
var a []int // 切片声明,此时为nil切片, 地址为0x00
var a = []int{} // 切片声明并初始化,此时为空切片, 若有几个【空切片】,则地址指向相同的某个位置
切片的初始化
1. 直接初始化
s := []int{1, 2, 3}
fmt.Printf("s: %v\n", s)
s := []int{} // 空切片
s[0] = 100 // error, 切片的长度为0
s = append(s, 100) // ok
2. 使用数组初始化
// 使用数组初始化
arr := [...]int{1, 2, 3}
s1 := arr[:] // 必须加[:],短变量声明,创建的是切片,而不是数组。
s2 := arr // 创建的是数组。而不是切片了
fmt.Printf("arr: %v\n", arr)
fmt.Printf("s1: %v\n", s1) // [1 2 3]
// 下面这种方式也可以
var s1 = arr[:]
fmt.Printf("s1: %T\n", s1) // []int 切片类型
3. 使用数组部分(切片)进行初始化
可以通过切片表达式得到切片,切片表达式中的low和high表示一个索引范围[low, high)
// 切片操作
var sli1 = [...]int{1, 2, 3, 4, 5, 6} // 定义为切片也可以
fmt.Printf("sli1: %v\n", sli1)
var sli2 = sli1[0:3]
fmt.Printf("sli2: %v\n", sli2) // [1 2 3]
var sli3 = sli1[3:]
fmt.Printf("sli3: %v\n", sli3) // [4 5 6]
var sli4 = sli1[2:5]
fmt.Printf("sli4: %v\n", sli4) // [3 4 5]
var sli5 = sli1[:] // 数组初始化
fmt.Printf("sli5: %v\n", sli5)
切片的遍历
切片的遍历和数组非常相似,可以使用for
循环和for range
循环
// 切片的遍历
var sl1 = []int{1, 2, 3, 4, 5}
for i := 0; i < len(sl1); i++ {
fmt.Printf("sl1[%d] = %d\n", i, sl1[i])
}
// for range遍历
for i, v := range sl1 {
fmt.Printf("i: %v\n", i)
fmt.Printf("v: %v\n", v)
}
切片的添加和删除和复制
切片是一个动态数组,使用append([]type, ...elem)
添加元素。
go语言中没有提供切片删除方法,可以使用切片本身特性来删除元素。
go中提供了copy()
函数来拷贝切片。
示例:
package main
import "fmt"
func main() {
s1 := []int{}
// 添加
fmt.Printf("s1: %v\n", s1) // []
s1 = append(s1, 100)
fmt.Printf("s1: %v\n", s1) // [100]
s1 = append(s1, 200) // [100 200]
fmt.Printf("s1: %v\n", s1)
s1 = append(s1, 300, 400, 500) // [100 200 300 400 500]
fmt.Printf("s1: %v\n", s1)
// 删除
// 删除第2个索引下标元素
// 公式: a2 := append(a1[:index], a1[index+:]...)
s2 := []int{}
s2 = append(s2, s1[:2]...) // [100 200]
s2 = append(s2, s1[3:]...) // [400 500]
fmt.Printf("s2: %v\n", s2)
// 删除简写
s3 := append(s1[:2], s1[3:]...)
fmt.Printf("s3: %v\n", s3)
// 修改
s4 := []int{1, 2, 3, 4}
s4[1] = 100
fmt.Printf("s4: %v\n", s4) // [1 100 3 4]
// 查询
var key = 3
for i, v := range s4 {
if v == key {
fmt.Printf("i: %v\n", i) // 2
}
}
// 复制, 切片为引用类型,
var str1 = []int{1, 2, 3, 4}
fmt.Printf("str1: %v\n", str1)
var str2 = str1
fmt.Printf("str2: %v\n", str2)
str1[0] = 1000 // str1修改, str2也会修改
fmt.Printf("str2: %v\n", str2) // [1000 2 3 4]
// 需要str3长度 > str1,若长度小于 str1, 则只会拷贝str3长度的元素。
// var str3 = []int{0, 0, 0, 0, 0} // 法一
str3 := make([]int, 4) // 法二
copy(str3, str1) // copy函数,深拷贝
fmt.Printf("str3: %v\n", str3) // [1000 2 3 4]
str3[1] = 100
fmt.Printf("str3: %v\n", str3) // [1000 100 3 4]
fmt.Printf("str1: %v\n", str1) // [1000 2 3 4]
}
切片的地址
切片类型的变量实际上存放的是一个地址,该地址即为其引用的底层数组的第一个元素的地址,也可以说是这个数组的地址。
package main
import "fmt"
func main() {
i := []int{1, 2, 3}
fmt.Printf("i: %p\n", i) // 切片存储的是底层数组的第一个元素的地址,即数组的地址
fmt.Printf("i[0]: %v %p\n", i[0], i[0]) // 1 %!p(int=1) 对应的底层的第0号元素,而不是地址。
fmt.Printf("%p %p %p %p \n", &i, i, &i[0], &i[1]) // 切片变量的地址, 底层数组的地址,底层数组第0号位地址(同数组地址),底层数组第1号位地址
}
注意:
- 每个切片引用了一个底层的数组。
- 切片本身不存储任何数据,都是这个底层数组存储,所以修改切片也就是修改这个数组中的数据。
- 当向切片中添加数据时,如果没有超过容量,直接添加,如果超过容量,会自动扩容。
- 切片一旦扩容,就是重新指向一个新的底层数组。
- 切片的扩容机制:当cap<1024时,默认每次扩容为原来cap的2倍,超过1024后,每次扩容为原来的1.25倍。
Map
Map 是无序的,内部实现为哈希表(hash),是一种key: value
键值对的数据结构容器。map也是引用类型。
定义
格式:
var 变量名 map[key类型]value类型 // 声明变量
或者
make(map[key类型]value类型) // 使用make函数声明并初始化
在声明的时候不需要知道map
的长度,因为map
是可以动态增长的,未初始化的 map 的值是 nil,nil map 不能用来存放键值对。
使用函数 len()
可以获取map
中键值对的数目。
示例:
// 声明方式1: 声明为nil map
var names map[string]string // 声明为map 即nil map
names = make(map[string]string) // 初始化为空map
// 声明方式2:声明同时初始化为空map
var names2 = make(map[string]string)
// 声明方式3:使用短变量声明初始化
names3 := make(map[string]string)
// 声明方式4:声明并初始化为空map
var name4 = map[string]string{}
注意:
var names map[string]string // 此时声明为nil map
初始化
// 方式1:
var m1 = map[string]string{} // 空map
fmt.Printf("m1: %v\n", m1)
var m2 = map[string]string{"name": "zs", "age": "20"}
fmt.Printf("m2: %v\n", m2)
// 方式2:
m3 := make(map[string]string) // 定义并初始化为空map
m3["name"] = "zs"
m3["age"] = "20"
fmt.Printf("m3: %v\n", m3)
访问元素
通过key来访问指定元素。
// 访问元素
fmt.Printf("m2[\"name\"]: %v\n", m2["name"]) // zs
判断某个key是否存在
格式:
value, ok := map的变量[key]
// 如果ok为 true, 存在。 否则不存在
示例:
var m2 = map[string]string{"name": "zs", "age": "20"}
fmt.Printf("m2: %v\n", m2)
// 判断某个key是否存在
value, ok := m2["name"]
fmt.Printf("value: %v\n", value) // zs
fmt.Printf("ok: %v\n", ok) // true
value1, ok1 := m2["email"]
fmt.Printf("value1: %v\n", value1) // 为string类型的零值 即 ""
fmt.Printf("ok1: %v\n", ok1) // false
map的遍历
可以使用for range
遍历map
。
// map的遍历
var m4 = make(map[string]string)
m4["name"] = "ls"
m4["age"] = "20"
m4["email"] = "ls@qq.com"
for k := range m4 { // 只获取所有key
fmt.Printf("k: %v\n", k)
}
for k, v := range m4 { // 获取key和value
fmt.Printf("%v:%v\n", k, v)
}
for _, v := range m4 { // 只获取value
fmt.Printf("v: %v\n", v)
}
map的元素删除
使用delete()
对map的元素删除
// 元素删除
m4 := map[string]string{"name": "zs", "age": "20"}
delete(m4, "name")
fmt.Printf("m4: %v\n", m4) // map[age:20]
函数
go中函数特性
- go中有三种函数:普通函数,匿名函数,方法(定义在
struct
中) - go中不允许函数重载,即不允许函数同名。
- go中不能函数嵌套,但可以嵌套匿名函数。
- 函数是一个地址值,可以将其赋值给变量。
- 函数可以作为参数,传递给另一个函数
- 函数的返回值可以为一个函数。
- 函数调用的时候,如果有参数传递给函数,则先拷贝参数的副本,将副本给函数。(值传递)
- 函数参数可以没有名称。
函数定义和调用
定义
func 函数名([参数列表])[返回值]{
函数体
}
示例:
package main
import "fmt"
func sub(a int, b int) (ret int) {
ret = a - b
return ret
}
func comp(a int, b int) (ret int) {
if a > b {
return a
} else {
return b
}
}
func main() {
i := sub(1, 2)
fmt.Printf("i: %v\n", i)
f := comp(3, 2)
fmt.Printf("f: %v\n", f)
}
函数返回值
注意:
- go中返回值
return
需要指定数据类型,可以不指定返回变量名称。如果return
不带参数,则返回值部分必须带名称。 - 当返回值有名称时,必须使用括号包围,即使只有一个返回值。多个返回值,逗号分割。
- 返回值命名了,
return
也可以强制返回其他名称变量。 - 命名的返回值会预先声明,在函数内部直接使用,无用再次声明。返回值名称不能和函数参数相同。
return
可以返回表达式,但不能出现赋值表达式,如return c = a+b
。
示例:
// 没有返回值
func myReturn() {
fmt.Println("没有返回值")
}
// 有一个返回值
func myReturn1(a int, b int) int {
c := a + b
return c
}
// 有一个返回值,带返回值名称
func myReturn2(a int, b int) (ret int) {
ret = a + b
return ret
}
// 多个返回值,且return 不省略返回参数
func myReturn3() (name string, age int) {
name = "zs"
age = 20
return name, age // 指定返回的参数名称
}
// 多个返回值,return省略返回参数
func myReturn4() (name string, age int) {
name = "zs"
age = 20
return // 默认会返回name 和age
}
// 多个返回值,不返回指定的返回值名称,
func myReturn5() (name string, age int) {
n := "zs"
a := 20
return n, a // ok
}
注意:
- go中经常会返回一个返回值作为函数是否成功执行标记。 例如
return value, exists
、return value, ok
、return value, err
等 - 函数返回值过多时,例如有4个以上返回值,需要将这些返回值放入容器中。例如:同类型返回值放入
slice
中,不同类型返回值放入map
中。 - 当函数有多个返回值,有某几个不想使用时候,使用
_
来丢弃返回值。
函数参数
go语言通过传值方式传参的,即传递给函数的是拷贝的副本。
go语言中可以使用变长参数,格式为参数名...类型
方式,代表参数会保存到参数名
的slice
中,其元素类型相同。
变长参数需要在函数形参列表最后的位置,否则报错。
无名的形参要在有名形参之前,即func(int, a int, args ...int)
这一顺序。
示例:
// 函数传参
// 参数必须指定数据类型,可以没有参数名, 但没有意义
func myReturn6(int, a int, b int) { // 值传递
a = 100
b = 200
}
func main(){
a := 1
b := 2
myReturn6(1, a, b)
fmt.Printf("a: %v\n", a) // 1
fmt.Printf("b: %v\n", b) // 2
}
注意:
map
、slice
、interface
、channel
这些数据类型本身是指针类型,所以即使值传递,拷贝的也是指针地址,可以会修改函数外部的值。
示例:
// 指针类型的参数,可能会修改函数外的值
func myReturn7(a []int) {
a[1] = 100
}
func main(){
sli1 := []int{1, 2, 3}
myReturn7(sli1)
fmt.Printf("sli1: %v\n", sli1) // [1 100 3]
}
变长参数
// 变长参数
func myReturn8(arg ...int) {
fmt.Printf("arg: %v\n", arg) // [1 2 3 4 5]
}
func myReturn9(a int, b int, args ...int) {
fmt.Printf("a: %v\n", a)
fmt.Printf("b: %v\n", b)
fmt.Printf("args: %v\n", args) // [3 4 5]
}
func main(){
myReturn8(1, 2, 3, 4, 5)
myReturn9(1, 2, 3, 4, 5)
// 变长参数
var slice = make([]int, 5)
slice = append(slice, 1,2,3,4)
myReturen8(slice...) // 只能传递给变长参数
}
函数类型和函数变量
可以使用type
关键字定义一个函数类型。格式如下:
type f1 func(int, int) int
// 定义了一个名为f1的函数类型, 可以创建变量,将函数名赋值给变量
示例:
package main
import "fmt"
func sum(a int, b int) int {
ret := a + b
return ret
}
func comp(a int, b int) int {
if a > b {
return a
} else {
return b
}
}
func main() {
type f1 func(int, int) int
var ff f1 // 通过f1类型定义函数
ff = sum
ret := ff(1, 2)
fmt.Printf("ret: %v\n", ret)
ff = comp // 可以将参数和返回值相同类型,对其赋值,否则报错
i := ff(1, 2)
fmt.Printf("i: %v\n", i)
}
注意:
- 可以将参数和返回值相同类型,对其赋值,否则报错。
go的高阶函数
go中的函数,可以作为函数参数,传递给另一个函数,也可以作为函数的返回值。
函数作为参数
// 函数为参数
func sayHello(name string) {
fmt.Println("hello,", name)
}
func test(name string, f func(string)) {
f(name)
}
func main() {
test("tom", sayHello)
}
函数作为返回值
// 返回值为函数
func add(x, y int) int {
fmt.Printf("x: %T\n", x)
return x + y
}
func sub(x, y int) int {
return x - y
}
func cal(s string) func(int, int) int {
switch s {
case "+":
return add
case "-":
return sub
default:
return nil
}
}
func main(){
ff := cal("-")
ret := ff(1, 2)
fmt.Printf("ret: %v\n", ret)
}
匿名函数
go中函数不能嵌套,但在函数内部可以定义匿名函数,实现简单的功能调用。
格式
func ([参数列表]) ([返回值]){
}
// 也可以既没有参数,也没有返回值
示例:
package main
import "fmt"
func main() {
// 匿名函数
max := func(a int, b int) int {
if a > b {
return a
} else {
return b
}
}
i := max(10, 9)
fmt.Printf("i: %v\n", i)
// 匿名函数直接调用自己
sum := func(a int, b int) int {
return a + b
}(1, 2)
fmt.Printf("sum: %v\n", sum)
}
闭包
函数内部定义匿名函数,通过函数返回匿名函数对象,匿名函数引用到了函数内部的变量。即函数+引用环境
示例:
package main
import "fmt"
func add1() func(int) int {
var x int
return func(y int) int {
x = x + y
return x
}
}
func main() {
var f = add1()
fmt.Println(f(10)) // 10
fmt.Println(f(20)) // 10 + 20
f1 := add1() // 重新定义
fmt.Println(f1(10)) // 10
}
进阶示例:
// 示例3
func calc(base int) (func(int) int, func(int) int) {
add := func(i int) int {
fmt.Printf("base: %v\n", base)
base += i
return base
}
sub := func(i int) int {
fmt.Printf("base: %v\n", base)
base -= i
return base
}
return add, sub
}
func main(){
// 示例3
ff1, ff2 := calc(10)
fmt.Println(ff1(1), ff2(2)) // 11 9
fmt.Println(ff1(3), ff2(4)) // 12 = 9 +3 , 8 = 12 - 4
}
递归
package main
import "fmt"
// 阶乘
func f1(n int) int {
if n <= 0 {
return 1
}
return f1(n-1) * n
}
// 斐波那契数列
func fib(n int) int {
if n == 1 || n == 2 {
return 1
} else {
return fib(n-1) + fib(n-2)
}
}
func main() {
i := f1(3) // 5 * 4 * 3 *2
fmt.Printf("i: %v\n", i)
i2 := fib(10)
fmt.Printf("i2: %v\n", i2)
}
defer语句
go语言中的defer
语句将函数进行延迟处理,在defer
所在的函数return后,defer语句,会按照定义的顺序,逆序执行。(类似栈)
格式:
defer xxx(arg1, arg2, ...)
注意:
- 关键字
defer
实现了函数的延迟调用。 defer
调用直到函数结束才被执行。因此可以用于做资源清理。- 多个
defer
语句,按先进后出的顺序执行。
用途:
- 关闭文件句柄
- 锁资源的释放
- 数据库连接释放
示例:
package main
import "fmt"
func main() {
fmt.Println("start")
defer fmt.Println("step1")
defer fmt.Println("step2")
defer fmt.Println("step3")
fmt.Println("end")
}
// 示例2
func test(a int){
fmt.Println(a)
}
func main(){
a := 1
defer test(a) // 1 ,值在定义的传递时候确定了。
a = 100
fmt.Println(a) // 100
}
defer的执行时机
在go语言中的return
语句在底层并不是原子操作,它分为两步:给返回值赋值和RET指令。而defer
语句的执行时机就在返回值赋值之后,RET执行执行之前。
示例:
package main
import "fmt"
func f1() int { // 1. 将x赋值给返回值变量 2. defer 3. ret指令
x := 5
defer func() {
x++ // 修改的是x不是返回值
}()
return x // 将x赋值给返回值
}
func f2() (x int) { // 1. 将5赋值给返回值变量x 2.defer 3. ret指令
defer func() {
x++ // 修改的返回值
}()
return 5
}
func f3() (y int) { // 1, 将x赋值给y, 2. defer 3. ret
x := 5
defer func() {
x++ // 修改的是x
}()
return x
}
func f4() (x int) {
defer func(x int) { // 1. 将5赋值给x 2. defer 3. ret
x++ // 修改的是局部变量x,不是返回的x
}(x)
return 5
}
func f5() (x int) {
defer func(x *int) { // 1. 将5赋值给x 2. defer 3. ret
(*x)++
}(&x)
return 5
}
func main() {
i := f1()
fmt.Printf("i: %v\n", i) // 5
i2 := f2()
fmt.Printf("i2: %v\n", i2) // 6
i3 := f3()
fmt.Printf("i3: %v\n", i3) // 5
i4 := f4()
fmt.Printf("i4: %v\n", i4) // 5
i5 := f5()
fmt.Printf("i5: %v\n", i5) // 6
}
defer 中的变量确定
defer
语句中的**变量,在defer
定义时就决定了。 **
package main
import "fmt"
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
a := 1
b := 2
defer calc("1", a, calc("10", a, b)) // 会把calc("10", a, b)的值先算出来,然后在defer calc("1", a, 3)
a = 0
defer calc("2", a, calc("20", a, b)) // calc("2", 0, 2)
b = 1
}
/*
10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4
*/
init函数
go中有一个特殊的init
函数,该函数先于main
函数执行,用于实现包级别的一些初始化的操作
特点:
init
函数没有参数和返回值。init
函数会自动执行,不能被其他函数调用- 包的每个源文件可以有多个
init
函数,执行顺序按照定义先后。 - 同一个的包的
init
执行顺序,没有明确定义。 - 同一个go文件下,导入不同的包,
init
函数执行顺序,按照导入依赖的顺序决定。
go的初始化顺序: 变量的初始化 -> init() -> main()
示例:
package main
import "fmt"
var i = initVar() // 先执行
func init() {
fmt.Println("init 1")
}
func initVar() int {
fmt.Println("initVar 100")
return 100
}
func init() {
fmt.Println("init 2")
}
func main() {
fmt.Println("main")
}
/* 输出: 初始化变量 -> init -> main
initVar 100
init 1
init 2
main
*/
注意:
-
对于不同的package,如果存在依赖,调用顺序为最后被依赖的最先被初始化。例如: main导入A,A导入B,B导入C。即首先初始化pkg3的变量和常量和init函数,然后初始化pkg2的常量变量和init函数,然后初始化pkg1的常量变量和init函数,最后是main。
指针
go中函数传参都是值传递,想要修改某个变量时候,可以创建一个指向该变量地址的指针变量。
在go语言中,指针不能进行偏移和运算。
符号: 取地址符&
和 根据地址取值*
在go语言中,使用&
字符放在变量前面,对变量进行取地址操作。
go语言中的值类型int, float, bool, string, array, struct
。其都有对应的指针类型, 如*int, *int64, *string,*结构体
。
声明格式:
var 变量名 *变量类型
// var ip *int
// var ip *int = &num
示例:
package main
import "fmt"
func main() {
var ip *int
fmt.Printf("ip: %v\n", ip) // nil
fmt.Printf("ip: %T\n", ip) // *int
var i int = 100
ip = &i
fmt.Printf("ip: %v\n", ip) //0xc000014138
fmt.Printf("ip: %v\n", *ip) // 100
*ip = 200
fmt.Println(i) // 200
var ipp *int = &i // 声明同时初始化
fmt.Printf("ipp: %v\n", *ipp) // 100
// 字符串指针
var sp *string
// sp = &"123" // 不能对常量字符串取地址
var s string = "hello "
sp = &s
fmt.Printf("sp: %T\n", sp) // *string
fmt.Printf("sp: %v\n", *sp) // hello
// 布尔类型指针
var bp *bool
var b bool = true
bp = &b
fmt.Printf("bp: %v\n", bp) // 0xc000014150
fmt.Printf("bp: %T\n", bp) // *bool
}
指向数组的指针
数组的指针,指向的数组中的每一个元素。(因为go中指针不能运算)
声明格式:
var ptr [MAX]*int
示例:
// 指向数组的指针
var ptr [4]*int
fmt.Printf("ptr: %v\n", ptr) // [<nil> <nil> <nil> <nil>]
var arr = [4]int{1, 2, 3, 4}
for i := 0; i < len(arr); i++ {
ptr[i] = &arr[i]
}
for i := 0; i < len(ptr); i++ {
fmt.Printf("*ptr[%d]: %v\n", i, *ptr[i])
}
/*
*ptr[0]: 1
*ptr[1]: 2
*ptr[2]: 3
*ptr[3]: 4
*/
类型定义和类型别名
格式:
type 新的类型 原始类型 // 类型定义
// type myInt int
type 新的类型 = 原始类型 // 类型别名
// type MyInt = int
示例:
package main
import "fmt"
func main() {
// 类型定义
type MyInt int
var i MyInt
i = 100
fmt.Printf("i: %v\n", i) // 100
fmt.Printf("i: %T\n", i) // main.MyInt
// 类型别名
type MyInt2 = int
var ii MyInt2
ii = 100
fmt.Printf("ii: %v\n", ii) // 100
fmt.Printf("ii: %T\n", ii) // int
}
// 定义函数的类型
type my_func func(int, int)string
func func1()my_func{ // 返回一个函数
fun := func(a, b int)string{
return strconv.Itoa(a) + strconv.Itoa(b)
}
return fun
}
类型定义和类型别名区别
- 类型定义是一种全新的类型,与之前的类型不同。但是类型别名并没有定义一个新的类型,而是使用一个别名来替换之前的类型。
- 类型别名只会在代码中存在,在编译完成之后不会存在。
- 类型别名 和原来类型一致,所以原来类型用于的方法,类型别名也可以调用。但如果是重新定义类型,则不可以调用之前的任何方法。
非本地类型不能添加方法
能够随意为各种类型起名字,是否意味着可以在自己包中为这些类型任意添加方法?
package main
import "fmt"
type MyDuratin = time.Duration // 定义time.Duration 的别名MyDuration
// 为MyDuration添加一个函数
func (m MyDuration) EasySet(a string){
} // error canot define new methods on non-local type
type MyDuration time.Duration // ok
func (m MyDuration) EasySet(a string) {
fmt.Println("end...")
}
注意:
- 不能再一个非本地类型的time.Duration上定义新方法。非本地方法就是说使用time.Duration的代码所在的包,也就是main包。因为time.Duration是在time包中定义的,在main中使用。不能为不在一个包中的类型添加方法。
解决办法
- 将类型别名定义为:
type MyDuration time.Duration
- 将新方法定义在time包中。
结构体
go中没有面向对象概念。 使用type
和struct
关键字定义结构体类型。
结构体类型的声明定义
格式:
type 结构体名 struct{
变量名 类型
变量名 类型
...
}
示例:
// 例如:
type Person struct{
id int
name string
age int
email string
}
// 相同类型可以合并到一行
type Person struct{
id, age int
name,email string
}
结构体变量声明
声明一个结构体变量和声明变量相同。例如:
var tom Person
fmt.Priontln(tom) // {0 0 }
kite := Person{}
fmt.Println(kite) // {0 0 } 默认int为0, string为空字符串
访问结构体成员
可以使用点运算符.
来访问结构体成员。例如:
package main
import "fmt"
// 自定义结构体【类型】
type Person struct {
id int
age int
name string
email string
}
type Customer struct {
id, age int
name, email string
}
func main() {
var tom Person
fmt.Printf("tom: %v\n", tom) // {0 0 }
tom.id = 1 // 使用.访问成员变量
tom.name = "tom"
tom.age = 20
tom.email = "tom@qq.com"
fmt.Printf("tom: %v\n", tom) // {1 20 tom tom@qq.com}
}
匿名结构体
临时使用,不用起名字。
// 1. 匿名结构体
var dog struct {
id int
name string
}
dog.id = 1
dog.name = "go"
fmt.Printf("dog: %v\n", dog)
// 2. 短变量声明方式
dog2 := struct{
Name string
Ag int
}{} // 注意
例如:
s2 := struct {
name string
age int
}{
name: "ls",
age: 19,
}
匿名字段
package main
import "fmt"
type Person struct { // 匿名字段
string // 匿名字段不能冲突,即不能出现两个string
int
}
func main() {
p := Person{
"abd",
123,
}
fmt.Printf("p: %v\n", p)
fmt.Printf("p.string: %v\n", p.string)
fmt.Printf("p.int: %v\n", p.int)
}
type Person struct { // 匿名字段
string // 匿名字段不能冲突,即不能出现两个string
int
address string // 也可以一起定义。
}
结构体的初始化
没有初始化的结构体,变量默认值都是“零值”
使用键值对对结构体进行初始化
var tom1 Person
tom1 = Person{
id: 101,
name: "tom",
age: 20,
email: "tom@qq.com",
}
fmt.Printf("tom1: %v\n", tom1)
根据值的顺序的初始化
// 使用值列表顺序初始化
tom1 = Person{
2,
"tom",
20,
"tom@qq.com",
}
fmt.Printf("tom1: %v\n", tom1)
// 键值对和值顺序不能混合使用
tom1 = Person{
2,
"tom",
20,
"tom@qq.com",
// name: "abc", // error 不能混合使用
}
指定部分成员初始化
// 部分初始化, 格式:【key:value】
tom2 := Person{
id: 101,
name: "tom",
}
fmt.Printf("tom2: %v\n", tom2)
结构体指针
结构体指针和普通变量指针相同。
示例:
package main
import "fmt"
type Person struct {
id int
name string
age int
}
func main() {
tom := Person{
id: 101,
name: "tom",
age: 20,
}
var pPerson *Person // 声明结构体指针
pPerson = &tom
fmt.Printf("pPerson: %p\n", pPerson) // 0xc00006e020
fmt.Printf("pPerson: %v\n", *pPerson) // {101 tom 20}
}
可以使用new
创建任意类型的指针
使用new
关键字创建结构体指针,得到结构体的地址。
// 使用new关键字定义结构体指针
var tom1 = new(Person)
fmt.Printf("tom1: %T\n", tom1) // *main.Person
(*tom1).id = 102 // *可以省略, 自动解引用
tom1.name = "tom11" // tom1.name == (*tom1).name
fmt.Printf("*tom1: %v\n", *tom1) // {102 tom11 0}
注意:
new(Type)*Type 是go中专门用于创建【任意类型】指针的函数。
new() 返回的并不是nil空指针,而且指向开辟内存空间的位置,里面存储的零值。
结构体作为函数的参数
结构体可以像其他变量一样,作为函数的参数,这里有两种情况:
- 直接传递结构体(值传递),拷贝副本。
- 传递结构体指针,在函数内部,可以修改函数外部的结构体内容。
示例:
package main
import "fmt"
type Person struct {
id int
name string
}
// 直接传递结构体
func showPerson(person Person) {
person.id = 1
person.name = "kite"
fmt.Printf("person: %v\n", person)
}
// 传递结构体指针
func showPersonP(person *Person) {
person.id = 100
person.name = "tome"
fmt.Printf("person: %v\n", *person)
}
func main() {
person := Person{10, "tom"}
fmt.Printf("person: %v\n", person)
showPerson(person)
fmt.Printf("person: %v\n", person) // 结构体内容并没有被修改 {10 tom}
per := &person
showPersonP(per)
fmt.Printf("person: %v\n", person) // 结构体指针传参, 函数外结构体被修改 {100 tome}
}
结构体嵌套
go语言中可以使用结构体嵌套来实现继承。
方法:
- 一个结构体作为另一个结构体字段
- 一个结构体作为另一个结构体的匿名字段。
示例:
// 一个结构体dog作为另一个结构体字段。
package main
import "fmt"
func main() {
type Person struct {
id int
name string
dog Dog // 作为字段
}
type Dog struct {
name string
color string
age int
}
// 方法1
var dog = Dog{
"小白花",
"花色",
10,
}
var person1 = Person{
1,
"tom",
dog, // 值传递
}
persons1.dog.name = "小黑花" // 修改了person1.dog,原来的dog没有变
fmt.Printf("person1: %v\n", person1) // 小黑花
fmt.Printf("dog: %v\n", dog) // 原来的dog仍然是:小白花
// 方法2,直接使用dog属性
var person Person
person.dog.name = "花花"
person.dog.color = "花色"
person.dog.age = 2
person.name = "tom"
person.id = 20
fmt.Printf("person: %v\n", person) // {20 tom {花花 花色 2}}
}
// 引用传递
package main
import "fmt"
type Person struct {
name string
age int
book *Book // 定义book指针
}
type Book struct {
name string
}
func main() {
b := Book{
"西游记",
}
p := Person{
name: "abc",
age: 123,
book: &b,
}
p.book.name = "红楼梦"
fmt.Printf("p: %v\n", *p.book)
fmt.Printf("b: %v\n", b) // 原来的book也被修改
}
// 提升字段
// 如果嵌套中,使用匿名字段,可以直接通过 s1.匿名结构体的字段。
package main
import "fmt"
type Person struct {
name string
age int
}
type Student struct {
person Person
school string
}
type NewStudent struct { // 匿名字段
Person
school string
}
func main() {
s1 := Student{Person{"zs", 12}, "bjdx"}
fmt.Printf("s1: %v\n", s1)
// fmt.Println(s1.name) // error 没有提升字段
s2 := NewStudent{Person{"ls", 12}, "qfjy"}
fmt.Printf("s2: %v\n", s2)
fmt.Printf("s2.name: %v\n", s2.name) // ls 可以直接访问父结构体字段
fmt.Printf("s2.age: %v\n", s2.age) // 12
}
结构体方法
可以声明某一些方法,属于某个结构体。
go中的方法是一种特殊的函数,定义于struct
之上,被称为struct
的接受者(receiver)。通俗的将,方法就是有接受者参数的函数。
语法:
type 结构体类型名称 struct{}
func (recv 结构体类型名称) my_method([参数列表]) [返回值列表]{}
func (recv *结构体类型名称) my_method([参数列表]) [返回值列表]{}
示例:
package main
import "fmt"
// 属性和方法分开来写
type Person struct {
name string
}
// per 为接受者receiver
func (per Person) eat() {
fmt.Printf("%v, eat,,,\n", per.name)
}
func (per Person) sleep() {
fmt.Printf("%v, sleep...\n", per.name)
}
type Customer struct {
name string
}
func (customer Customer) login(name string, pwd string) bool {
fmt.Printf("customer.name: %v\n", customer.name)
if name == "tom" && pwd == "123" {
return true
}
return false
}
func main() {
var person Person
person.name = "hello world"
person.eat()
person.sleep()
// 示例2
var cus Customer
cus.name = "tom"
b := cus.login("tom", "123")
fmt.Printf("b: %v\n", b)
}
注意:
- 方法的receiver的类型不一定非要
struct
, type定义的类型别名,slice,map,channel,func类型等都可以。 struct
结合它的方法就等价于面向对象中的类。只不过struct
的属性和方法是分开定义的,且不一定写在同一个文件中,但必须在同一个包中。- 方法的
receiver
有两种类型,可以是T type
和T *type
。 - 方法就是函数,在go中没有方法重载的说法,所有方法名必须唯一。
- 方法和
struct
的变量分开,意味着实例的行为和数据存储是分开的,但可以通过receiver建立起关联关系。
结构体方法接受者类型
结构体实例有值类型和指针类型,那么方法的接受者也有值类型和指针类型。
区别: 就是接受者是否复制结构体副本。值类型复制,指针类型不复制。
实例:
package main
import "fmt"
type Person struct {
name string
}
func (per Person) showPerson1() {
per.name = "kite"
fmt.Printf("per: %v\n", per)
}
func (per *Person) showPerson2() {
per.name = "kite"
fmt.Printf("per: %v\n", per)
}
func main() {
p1 := Person{
name: "tom",
}
p2 := &Person{
name: "tom",
}
fmt.Printf("p1: %T\n", p1) // main.Person
fmt.Printf("p2: %T\n", p2) // *main.Person
p1.showPerson1()
fmt.Printf("p1: %v\n", p1) // tom
p2.showPerson2()
fmt.Printf("p1: %v\n", *p2) // kite
}
结构体继承的方法
package main
import "fmt"
type Person struct {
name string
age int
}
type Student struct {
Person // 匿名字段
school string
}
func (p Person) eat() {
fmt.Println("父类方法,吃。。。")
}
func (s Student) study() {
fmt.Println("子类的方法,学习")
}
func (s Student) eat() {
fmt.Println("子类重写父类方法, 吃")
}
func main() {
p1 := Person{"wangergo", 30}
fmt.Printf("p1.name: %v\n", p1.name)
fmt.Printf("p1.age: %v\n", p1.age)
p1.eat()
s1 := Student{Person{"rbyuy", 18}, "qf"}
fmt.Printf("s1.name: %v\n", s1.name) // 直接访问父类字段
fmt.Printf("s1.age: %v\n", s1.age)
fmt.Printf("s1.school: %v\n", s1.school)
s1.eat() // 如果没有重写父类方法,可以直接访问父类方法,重写后调用子类方法
s1.study() // 访问子类方法
}
接口
声明定义
接口是一种新的类型定义,把所有【方法】定义在一起,而不需要实现,任何其他类型只要实现了这些方法就是实现了接口。
格式:
type 接口名 interface{
方法名1([参数列表]) [返回类型]
方法名2([参数列表]) [返回类型]
...
}
// 定义结构体
type 结构体名称 struct{
// 变量名
}
// 实现接口方法
func (结构体实例 结构体类型) 方法名1()[返回类型]{
}
func (结构体实例 结构体类型) 方法名2()[返回类型]{
}
示例:
// 定义usb接口,定义mouse和flashdisk 两个结构体,实现接口
package main
import "fmt"
type USB interface {
start()
end()
}
type Mouse struct {
name string
}
type FlashDisk struct {
name string
}
func (m Mouse) start() {
fmt.Println("mouse start...")
}
func (m Mouse) end() {
fmt.Println("mouse end...")
}
func (f FlashDisk) start() {
fmt.Println("flash disk start...")
}
func (f FlashDisk) end() {
fmt.Println("flash disk end...")
}
func testInterface(usb USB) {
usb.start()
usb.end()
}
func main() {
m1 := Mouse{"LG"}
fmt.Printf("m1.name: %v\n", m1.name)
f1 := FlashDisk{"sd 64g"}
fmt.Printf("f1.name: %v\n", f1.name)
testInterface(m1) // 通过usb接口访问 mouse
testInterface(f1) // 通过usb接口访问flashdisk
}
// 也可以赋值给接口, 但接口不能调用结构体变量,只能访问接口方法
func main() {
var usb USB
m1 := Mouse{"LG"}
usb = m1 // ok
usb.start()
usb.end()
fmt.Printf("usb.name: %v\n", usb.name) // error
f1 := FlashDisk{"sd 64g"}
usb = f1
usb.start()
usb.end()
}
注意:
- 实现接口必须实现接口中的所有方法,否则编译报错
示例:
// 定义OpenClose接口,里面有两个方法open和close, 定义Door结构体
type OpenClose interface {
open()
close()
}
type Door struct {
}
func (d Door) open() {
fmt.Println("open...")
}
func main(){
// 如果只实现一个接口
var oc OpenClose
oc = Door{} // error 需要实现close方法
oc.open()
}
空接口类型
不包含任何方法,所有类型都实现了空接口,因此任意类型都可以赋值给空接口。
格式:
type A interface{}
// 匿名空接口
interface{}
示例:
package main
import "fmt"
type A interface {
}
type Cat struct {
color string
}
type Person struct {
name string
age int
}
func test(a A) { // 有名称空接口
fmt.Println(a)
}
func test2(a interface{}) { // 匿名空接口
fmt.Println("test2", a)
}
func main() {
var a A
a = 123 // int类型
fmt.Printf("a: %v\n", a)
a = "hello world" // string类型
fmt.Printf("a: %v\n", a)
a = Cat{"猫"} // 结构体类型
fmt.Printf("a: %v\n", a)
a = Person{"123", 344}
fmt.Printf("a: %v\n", a)
test("123")
test("hello")
test2(Cat{"hello"})
}
存储任意类型的map和slice
package main
import "fmt"
type Person struct {
name string
}
func main() {
// 任意类型的map
map1 := make(map[string]interface{})
map1["name"] = "zs"
map1["age"] = 19
map1["address"] = "北京市"
map1["person"] = Person{"张三"}
fmt.Printf("map1: %v\n", map1)
// 任意类型的切片
slice1 := make([]interface{}, 0)
slice1 = append(slice1, "zs")
slice1 = append(slice1, 19)
slice1 = append(slice1, "北京市")
slice1 = append(slice1, Person{"zs"})
fmt.Printf("slice1: %v\n", slice1)
test(slice1)
}
func test(slice1 []interface{}) {
for _, v := range slice1 {
fmt.Printf("v: %v\n", v)
}
}
接口值类型接收者和指针类型接收者
值类型接收者是一个拷贝,是一个副本。指针类型接收者,传递是指针。
示例:
package main
import "fmt"
type Peter interface {
// eat(string) string
eat2(string) string
}
type Dog struct {
name string
}
// func (d Dog) eat(name string) string { // 值类型
// d.name = "花花..."
// fmt.Printf("name: %v\n", name)
// return "吃的很好"
// }
func (d *Dog) eat2(name string) string { // 引用类型 名称不能重复
d.name = "花花黑白色..."
fmt.Printf("name: %v\n", name)
return "吃的很好"
}
func main() {
var peter Peter
dog := Dog{
name: "黑白",
}
// peter = dog
// peter.eat("猪肉")
// fmt.Printf("dog.name: %v\n", dog.name) // 未修改原来的值 =》 黑白
peter = &dog
peter.eat2("猪肉")
fmt.Printf("dog.name: %v\n", dog.name) // 修改了原来的值 =》花花黑白色...
}
接口和类型的关系
在go中,接口与类型的关系,如下:
- 一个类型可以实现多个接口
- 多个类型可以实现同一个接口(多态)
一个类型实现多个接口
示例:
package main
import "fmt"
type Player interface {
playMusic()
}
type Video interface {
playVideo()
}
type Mobile struct {
}
func (m Mobile) playMusic() {
fmt.Println("music...")
}
func (m Mobile) playVideo() {
fmt.Println("video...")
}
func main() {
var player Player
var video Video
var a = Mobile{} // 一个类型实现了多个结口
player = a
video = a
player.playMusic()
video.playVideo()
}
多个类型实现同一个接口 (多态)
// 多个类型实现同一个接口
type Pet interface {
eat()
}
type Dog struct {
}
type Cat struct {
}
func (d Dog) eat() {
fmt.Println("dog eat...")
}
func (c Cat) eat() {
fmt.Println("cat eat...")
}
func main(){
// 多个类型实现同一个接口
var pet Pet
pet = Dog{}
pet.eat()
pet = Cat{}
pet.eat()
}
接口嵌套
示例:
package main
import "fmt"
type A interface {
test1()
}
type B interface {
test2()
}
type C interface { // 匿名字段,嵌套A和B接口
A
B
test3()
}
type Cat struct {
name string
}
func (c Cat) test1() {
fmt.Println("test1..")
}
func (c Cat) test2() {
fmt.Println("test2..")
}
func (c Cat) test3() {
fmt.Println("test3..")
}
func main() {
// 定义cat,可以调用三个方法
var cat Cat = Cat{"hello world"}
cat.test1()
cat.test2()
cat.test3()
// 赋值给接口A,为A接口的实现,只能访问test1
var a A = cat
a.test1()
var b B = cat
b.test2() // 接口B的实现,只能访问test2
var c C = cat // 接口c的实现,三个方法都可以访问
c.test1()
c.test2()
c.test3()
// 接口赋值
// var c2 C = a // error 接口a没有实现接口c功能
var a2 A = c // ok
a2.test1() // 但只能调用test1()了
}
接口断言
也叫做类型断言。通过接口断言获取接口获取的值。
格式:
x.(断言类型)
// x:接口变量
// 返回值为 x转为断言类型后变量,第二个返回值为布尔值,true表示断言成功,否则断言失败。
方法:
-
方法一:
instance := 接口变量.(实际类型) // 不安全,会panic()
instance , ok := 接口变量.(实际类型) // 安全
-
方法二:
switch instance := 接口变量.(type){ // 固定写法
case 实际类型1:
...
case 实际类型2:
...
}
示例:
// 方法一:
func assign(a interface{}) {
if str, ok := a.(string); ok {
fmt.Printf("%T %v\n", str, str)
} else if i, ok := a.(int); ok {
fmt.Printf("i: %v\n", i)
} else {
fmt.Println("类型断言错误。")
}
}
// 方法二:
func assign2(a interface{}) {
switch v := a.(type) {
case string:
fmt.Printf("string: %T %v\n", v, v)
case int:
fmt.Printf("int: %T %v\n", v, v)
case []int:
fmt.Printf("[]int: %T %v\n", v, v)
default:
fmt.Printf("断言失败\n")
}
}
func main(){
assign(12)
assign("anc")
assign2(123)
assign2("hello")
assign2([...]int{1, 1, 2})
assign2([]int{1, 2, 3})
}
// 示例:
package main
import (
"fmt"
"math"
)
type Shape interface {
zhouchang() float64 // 周长
area() float64 // 面积
}
type Triangle struct {
a, b, c float64
}
func (t Triangle) zhouchang() float64 {
return t.a + t.b + t.c
}
func (t Triangle) area() float64 {
p := t.zhouchang() / 2
return math.Sqrt(p * (p - t.a) * (p - t.b) * (p - t.c))
}
type Circle struct {
radius float64
}
func (c Circle) zhouchang() float64 {
return 2 * math.Pi * c.radius
}
func (c Circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func testShpae(s Shape) {
fmt.Printf("周长:%.2f - 面积:%.2f\n", s.zhouchang(), s.area())
}
func assign(s Shape) { // 接口断言
if instance, ok := s.(Triangle); ok {
fmt.Println("是三角形", instance.a, instance.b, instance.c)
} else if instance, ok := s.(Circle); ok {
fmt.Println("是圆形", instance.radius)
} else if instance, ok := s.(*Triangle); ok { // var t2 *Triangle
fmt.Println("三角形,指针", instance.a, instance.b, instance.c)
} else {
fmt.Println("什么都不是")
}
}
func assign2(s Shape) {
switch ins := s.(type) {
case Triangle:
fmt.Println("三角形2:", ins.a, ins.b, ins.c)
case Circle:
fmt.Println("圆形2:", ins.radius)
default:
fmt.Println("什么都不是...")
}
}
func main() {
var t1 Triangle = Triangle{3, 4, 5}
var c1 Circle = Circle{4}
var s1 Shape // 定义接口类型
s1 = t1
fmt.Printf("s1.zhouchang(): %v\n", s1.zhouchang())
fmt.Printf("s1.area(): %v\n", s1.area())
// 使用函数
testShpae(t1)
testShpae(c1)
// 接口断言
assign(t1)
assign(c1)
var t2 *Triangle = &Triangle{3, 4, 2}
assign(t2) // *Triangle类型
// 接口断言,第二种方式
assign2(t1)
assign2(c1)
assign2(t2)
}
模拟构造函数
使用函数来模拟构造函数。
package main
import "fmt"
type Person struct {
name string
age int
}
func NewPerson(name string, age int) (*Person, error) { // 构造函数
if name == "" {
return nil, fmt.Errorf("name 不能为空")
}
if age < 0 {
return nil, fmt.Errorf("age不能小于0")
}
return &Person{name: name, age: age}, nil
}
func main() {
person, err := NewPerson("zs", 20)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("person.name: %v\n", person.name)
fmt.Printf("person.age: %v\n", person.age)
}
}
go的包管理
包可以区分命名空间(一个文件夹不能有两个同名文件),也可以更好的管理项目。在go中,一个包一般是一个文件夹,在该文件夹中的go文件,使用package
关键字声明包名称。
创建包
// 1. 创建一个名为dao的文件夹
// 2. 创建一个dao.go文件
// 3. 在该文件声明包
package dao
import "fmt"
func Test1(){
fmt.Println("test package")
}
导入包
要使用某个包的变量和方法,需要导入该包。导入包时,要导入从GOPATH
开始的包路径。
// 单个包的导入
import "package名称"
// 导入多个包
import (
"package名称1"
"package名称2"
)
// 示例:
package main
import "dao"
fun main(){
do.Test1()
}
注意:
- 一个文件夹下只能有个
package
,但可以嵌套另一个包(文件夹)。 import
语句其实从GOPATH
开始的相对路径。如果使用go.mod
就不一定了。import
一个路径,相当于导入了这个路径下的包。- 一个package里的文件不能出现在多个文件夹下。
- 一般文件夹的名称和包名一致。可以不一样。
- 同包下的函数不需要导入,可以直接使用。
点操作
在导入这个包的文件中,调用函数的时候可以省略包名。
import (
. "fmt"
)
func main(){
// fmt.Println("hello")
Println("hello") // 省略包名
}
起别名
import (
p1 "package1"
p2 "package2"
)
p1.Method() // 使用别名调用函数
下划线 _ 操作
匿名导入。仅仅需要导入包时执行init函数,不需要使用包的其他函数。
import (
_ "github.com/ziutek/xxxx"
)
go module 包管理工具
go modules是golang 1.11之后增加的新特性,主要用来管理包依赖关系。
使用方法:
// 初始化模块
go mod init <项目模块名称>
// 依赖关系处理,根据go.mod文件。下载需要的依赖,删除不需要依赖。
go mod tidy
// 将依赖复制到项目下的vendor目录
go mod vendor // 如果被墙,可以使用
// 显示依赖关系
go list -m all
// 显示详细的依赖关系
go list -m -json all
// 下载依赖
go mod download [path@version] // path@version不是必须的
示例:
// 1. 目录结构
dao/
user_dao.go
service/
user_service.go
customer_service.go
utils/
main.go
// 2. 在项目目录test_package_pro1下执行
go mod init test_package_pro1
// 3. main.go中导入
package main
import (
"fmt"
"test.com/test_package_pro1/service"
)
func main() {
fmt.Println("hello world")
service.TestUserService() // 调用service/user_service.go方法
service.TestCustomerService()
}
// 4. 安装gin
go get -u github.com/gin-gonic/gin
// 5. 使用gin
func main() {
fmt.Println("hello world")
service.TestUserService()
service.TestCustomerService()
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run()
}
// 6. 安装包相关依赖
go mod tidy
// 7. 运行并测试
go run main.go
curl http://localhost:8080/ping
管理外部包
方法一: 手动下载github中压缩包
将压缩包解压到$GOPATH/src
示例:
目录为: $GOPATH/src/github.com/go-sql-driver/mysql
方法二: 使用go get命令
示例:
go get github.com/go-sql-driver/mysql
示例:
import (
"database/sql"
_ "github.com/go-sql-driver/go" // 只需要执行init函数
)
func main(){
db, err := sql.Open("mysql", "root@123@tcp(127.0.0.1)/jobs?charset=utf8")
if err != nil{
log.Fatal(err)
}
fmt.Println(db)
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」