这篇文章来讲下Go语言的基础内容,整体包括golang初识,golang的第一个程序,go项目的编译和构建,最后就是go语言的基础知识(一些语法基础)
golang初识 :
golang没有那些内容:
- 没有类(class) : 用结构体替代
- 没有方法重载
- 没有继承
- 没有枚举类型
- 没有try/catch
- 没有泛型
golang有什么:
- struct(结构体)
- 结构体的内嵌
- 常量
- defer/recover/panic
- interface{}
- 接口
golang特性 :
- 头等函数,允许将函数分配给变量,作为其他函数的入参,返回值,函数可以是对象,函数也可以是类型
- 并行操作非常方便,通过go关键字启动协程
- 通过通讯共享内容
- 多路复用,channel的多路复用
golang语言特性
Golang的优势
极简单的部署方式:可直接编译成机器码、不依赖其他库、直接运行即可部署。
静态类型语言,编译的时候可以检查出大多数问题。
语言层面的并发:天生的基因支持、充分的利用多核
// Go 语言实现并发的代码
func goFunc(i int) {
fmt.Println("goroutine ", i, " ...")
}
func main() {
for i := 0; i < 1000; i++ {
go goFunc(i) // 开启一个并发协程
}
time.Sleep(time.Second)
}
强大的标准库
runtime 系统调度机制、高效的 CG 垃圾回收、丰富的标准库
Golang 的应用场景
1、云计算基础设施领域:
代表项目:docker、kubernetes、etcd、consul、cloud flare CDN、七牛云存储 等。
2、基础后端软件:
代表项目:tidb、influxdb、 cockroach 等。
3、微服务
代表项目:go-kit、 micro、 monzo bank 的 typhon、bilibili 等。
4、互联网基础设施
代表项目:以太坊、hyperledger 等。
有编程经验如何快速学习go
- 那些内容是golang没有的
- 使用golang的特性的替代方案
- golang面向对象编程
- golang多态的实现
golang的第一个程序 :
// hello.go
package main // 在go中,每个文件都必须归属于一个包
import "fmt" //引入一个包,包名fmt,引入该包后,就可以使用fmt包的函数,比如fmt.Println()
func main() { // func 是一个关键字,表示一个函数,main是函数名,是我们程序的入口
fmt.Println("Hello World!")//调用fmt包中的函数Println()输出Hello World
}
在vscode中执行go文件:
法一 :
-
通过go build 命令对go文件进行编译,生成.exe可执行文件
D:\myBianCheng\vscProject\goproject\src\go_code\project01\main>go build hello.go 也可以指定产生的exe文件名称 :
go build -o myhello.exe hello.go -
运行hello.exe即可
D:\myBianCheng\vscProject\goproject\src\go_code\project01\main>hello.go
法二 :
可以通过go run 命令可以直接运行hello.go程序
D:\myBianCheng\vscProject\goproject\src\go_code\project01\main>go run hello.go
Hello World!
go开发注意事项 :
- go区分大小写
- go的每条语句后面不用加分号
- go编译器是一行行进行编译的,所以不能将多条语句写在一行,否则报错
- go中如果定义了变量或者import了某个包,就一定要用,否则会报错
go项目构建及编译
一个Go工程中主要包含以下三个目录:
src:源代码文件
pkg:包文件
bin:相关bin文件
1: 建立工程文件夹 goproject
2: 在工程文件夹中建立src,pkg,bin文件夹
3: 在GOPATH中添加projiect路径 例 e:/goproject
4: 如工程中有自己的包examplepackage,那在src文件夹下建立以包名命名的文件夹 例 examplepackage
5:在src文件夹下编写主程序代码代码 goproject.go
6:在examplepackage文件夹中编写 examplepackage.go 和 包测试文件 examplepackage_test.go
7:编译调试包
go build examplepackage
go test examplepackage
go install examplepackage
这时在pkg文件夹中可以发现会有一个相应的操作系统文件夹如windows_386z, 在这个文件夹中会有examplepackage文件夹,在该文件中有examplepackage.a文件
8:编译主程序
go build goproject.go
成功后会生成goproject.exe文件
至此一个Go工程编辑成功。
1.建立工程文件夹 go
$ pwd
/Users/***/Desktop/go
2: 在工程文件夹中建立src,pkg,bin文件夹
$ ls
bin conf pkg src
3: 在GOPATH中添加projiect路径
$ go env
GOPATH="/Users/liupengjie/Desktop/go"
4: 那在src文件夹下建立以自己的包 example 文件夹
$ cd src/
$ mkdir example
5:在src文件夹下编写主程序代码代码 goproject.go
6:在example文件夹中编写 example.go 和 包测试文件 example_test.go
example.go 写入如下代码:
package example
func add(a, b int) int {
return a + b
}
func sub(a, b int) int {
return a - b
}
example_test.go 写入如下代码:
package example
import (
"testing"
)
func TestAdd(t *testing.T) {
r := add(2, 4)
if r != 6 {
t.Fatalf("add(2, 4) error, expect:%d, actual:%d", 6, r)
}
t.Logf("test add succ")
}
7:编译调试包
$ go build example
$ go test example
ok example 0.013s
$ go install example
$ ls /Users/***/Desktop/go/pkg/
darwin_amd64
$ ls /Users/***/Desktop/go/pkg/darwin_amd64/
example.a
8:编译主程序
oproject.go 写入如下代码:
package main
import (
"fmt"
)
func main(){
fmt.Println("go project test")
}
$ go build goproject.go
$ ls
example goproject.go goproject
成功后会生成goproject文件
至此一个Go工程编辑成功。
运行该文件:
$ ./goproject
go project test
go基础知识 :
go转义字符 :
\t
\n
\
\
\r
注释 :
提高代码的可阅读性
-
行注释 :
// 注释内容
-
块注释 :
/*
注释内容
*/
go代码格式化 :
// 显示格式化后的代码
gofmt hello.go
// 将格式化后的内容重写写入到文件中
gofmt -w hello.go
2.
tab : 实现缩进,默认向右移动
shift + tab : 整体向左移动
// 正确 :
func main() {
fmt.Println("Hello World!")
}
// 错误 :
func main()
{
fmt.Println("Hello World!")
}
4.一行最多不超过80个字符,超过请换行
dos常用指令
dos :
Disk Operating System 磁盘操作系统,
dos的基本操作原理 :
常见指令 :
-
dir : 查看当前目录详细信息
-
cd :
cd /d D:\ : 进入到d:\目录 cd \ : 返回到根目录 cd .. : 切换到上一级 -
md : 新建目录
md ys100 : 新建一个目录 md a1 b2 新建多个目录 -
rd : 删除目录
rd ok100 : 删除空目录 rd /q/s ok 200 : 删除目录以及下面的子目录文件,不带询问 rd /s ok300 : 删除目录以及下面的子目录和文件,带询问 -
echo : 新建或追加内容到文件 :
echo hello > d:\bac.txt -
del : 删除文件
go变量
Golang 变量使用的三种方式:
-
(1) 第一种:指定变量类型,声明后若不赋值,使用默认值
func main() { var i int fmt.Println("i=",i) } // 输出0,int 的默认值是0 -
(2) 第二种:根据值自行判定变量类型(类型推导)
var num = 9.14 fmt.Println("num = ",num) -
第三种:省略 var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误
s := "tom" fmt.Println("s = ",s)
多变量声明
package main
import "fmt"
func main(){
// 该案例演示golang如何一次性声明多个变量
//var nl,n2,n3 int
// fmt.Println("n1=" ,n1, "n2=", n2, "n3=", n3)
// 一次性声明多个变量的方式2
// var n1,name , n3 = 10, "tom" ,888
// fmt.Println("n1=",n1,"name=",name,"n3=",n3)
// 一次性声明多个变量的方式3,同样可以使用类型推导
n1,name , n3 :=100, "tom",888
fmt.Println("n1=",n1,"name=", name,"n3=",n3)
}
全局变量 :
在go中,在函数外部定义的变量就是全局变量
package main
import "fmt"
var n1 = 100
var n2 = 200
var n3 = 300
// 上面的定义可以改为一次性声明
var (
n4 = 400
n5 = 500
)
func main(){
fmt.Println("n1=",n1,"n2=",n2,"n3=",n3,"n4=",n4,"n5=",n5)
}
数据类型 :
值类型 :
变量直接存储值,内存通常在栈中分配
bool
int(32 or 64), int8, int16, int32, int64
uint(32 or 64), uint8(byte), uint16, uint32, uint64
float32, float64
string
complex64, complex128
array -- 固定长度的数组
引用类型(指针类型) :
变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆 上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由 GC 来回收
slice -- 序列数组(最常用)
map -- 映射
chan -- 管道
指针
interface
整形 :
分为int(有符号,默认类型) 和 uint(无符号)
小数/浮点型 :
var p float32 = 89.12
分为 :
- float32 : 单精度
- flaot64 : 双精度,golang默认声明类型
两种表现形式 :
- 十进制数形式:如:5.12 .512 (必须有小数点)
- 科学计数法形式:如:5.1234e2 = 5.12 * 10 的 2 次方 5.12E-2 = 5.12/10 的 2 次方
字符类型
Golang 中没有专门的字符类型,如果要存储单个字符(字母),一般使用 byte 来保存。
字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。也 就是说对于传统的字符串是由字符组成的,而 Go 的字符串不同,它是由字节组成的。
package main
import "fmt"
func main() {
var c1 byte = 'a'
var c2 byte = '0' //字符0
// 当我们直接输出byte值,就是直接输出对应的ascii码值
fmt.Println("c1 = ", c1)
fmt.Println("c2 = ", c2)
// 要输出对应字符,就要格式化输出
fmt.Printf("c1=%c c2 = %c\n", c1, c2)
}
- Go 语 言 的 字 符 使 用 UTF-8 编 码 , 如 果 想 查 询 字 符 对 应 的 utf8 码 值 www.mytju.com/classcode/t…
- 在 Go 中,字符的本质是一个整数,直接输出时,是该字符对应的 UTF-8 编码的码值。
- 可以直接给某个变量赋一个数字,然后按格式化输出时%c,会输出该数字对应的 unicode
bool类型
- bool类型只允许去true和false两个值
- bool类型占一个字节
string 类型
-
基本介绍: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本
package main import ( "fmt" _ "strings" ) func main() { var str string = "ysjhhasdjhfkj" // str[0] = 's' : 报错 fmt.Println(str) } -
在golang中,字符是不可变的
-
字符串的两种表现形式
- 双引号 : 会识别转义字符
- 反引号 : 以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果
-
字符串拼接
-
var str = "hello" + " world" str += " hh" -
字符串常见函数 :
package main import ( "fmt" "strings" ) // go里strings包中有很多字符串工具函数用strings.x()来实现 func main() { a := "hello" // Contains() : 判断一个字符串里面是否包含另一个字符串,返回结果类型为bool fmt.Println(strings.Contains(a, "ll")) // true // Count() : 字符串计数 fmt.Println(strings.Count(a, "l")) // 2 // HasPrefix() : strings.HasPrefix()函数用来检测字符串是否以指定的前缀开头 fmt.Println(strings.HasPrefix(a, "he")) // true // HasSuffix() : strings.HasSuffix()函数用来检测字符串是否以指定的字符串结尾 fmt.Println(strings.HasSuffix(a, "llo")) // true // Index() : 查找某一个字符串的位置 fmt.Println(strings.Index(a, "ll")) // 2 // Join() : 链接多个字符串 fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo // Repeat() : 重复多个字符串 fmt.Println(strings.Repeat(a, 2)) // hellohello // Replace() : 替换字符 fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo // Split() : 切片 fmt.Println(strings.Split("a-b-c", "-")) // [a b c] // ToLower() : 小写字符串 fmt.Println(strings.ToLower(a)) // hello // ToUpper() : 大写字符串 fmt.Println(strings.ToUpper(a)) // HELLO // len() : 求字符串的长度 fmt.Println(len(a)) // 5 b := "你好" // 一个中文字符可能对应多个字符 fmt.Println(len(b)) // 6 }
基本数据类型的默认值 :
int : 0
float32 : 0
string : ""
bool : false
数据类型相互转换
-
与java/cpp不同,go只能显示转换,也就是没有自动转换
-
基本语法 :
表达式 T(v) 将值 v 转换为类型 T T: 就是数据类型,比如 int32,int64,float32 等等 v: 就是需要转换的变量 例 : var i int32 = 100 var n1 float32 = float32(i) -
范围只能由小到大
-
被转换的是变量存储的数据(即值),变量本身的数据类型并没有变化!
基本数据类型和string的转换 :
基本类型转string :
-
fmt.Sprintf("%参数",表达式) : fmt.Sprintf().. 会返回转换后的字符串
// 例 : var num int = 789 var str string str = fmt.Sprintf("%d",num) fmt.Println("str = ", str) -
使用strconv包的函数 :
func FormatBool(b bool) string func FormatFloat(f float64, fmt byte, prec, bitSize int) string func FormatInt(int64, base int) string func FormatUint(i uint64, base int) string例 : var num int = 789 var str string str = strconv.FormatInt(int64(num), 10) fmt.Printf("str type %T str = %q\n", str, str)
string 类型转基本类型 :
func ParseBool(str string)(vale bool, enr error)
func ParseFloat(s string, bitSize int) (f float64, err error)
func ParseInt(s string,base int, bitSize int) (i int64, err error)
fune ParseUint(s string, b int,bitSize inty (n uint64, en error)
package main
import (
"fmt"
"strconv"
_ "strings"
)
func main() {
var str string = "true"
var b bool
// b,_ = strconv.ParseBool(str)
//说明
// 1. strconv.ParseBool(str)函数会返回两个值(value bool, err error)
//2.因为我只想获取到value bool ,不想获取err所以我使用_忽略
b, _ = strconv.ParseBool(str)
fmt.Printf("b type %T b=%v\n", b, b)
var str2 string = "12342112"
var n1 int64
var n2 int
n1, _ = strconv.ParseInt(str2, 10, 64)
n2 = int(n1)
fmt.Printf("n1 type %T n1=%v\n", n1, n1)
fmt.Printf("n2 type %T n2=%v\n", n2, n2)
var str3 string = "123.456"
var f1 float64
f1, _ = strconv.ParseFloat(str3, 64)
fmt.Printf("f1 type %T f1=%v\n", f1, f1)
}
简例 :
package main
//数字解析,数字和字符串之间的转换
// 在go语言中,关于数字和字符串之间的转换都在strconv(string convert)包下,
// 可以用parseInt或者parseFloat来解析一个字符串,parseint参数
// 可以用 Atoi把一个十进制字符串转成数字,可以用itoA把数字转换成字符串
// 如果输入不合法,那么这些函数都会返回error
import (
"fmt"
"strconv"
)
func main() {
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
注意 :
-
该区域的数据值可以在同一类型范围内不断变化
package main import "fmt" func main(){ var i int = 10 i = 20 i = 30 fmt.Println("i=",i) // i = 11.1 报错 } -
变量 = 变量名 + 值 + 数据类型
-
GoLang 中的变量如果没有赋初值,编译器会使用默认值,例如int 默认为0,string 默认为空,小数默认为0
go指针
作用 : 对函数传入参数进行修改
获取变量的地址,用&
例 :
var i int = 10
fmt.Println("i的地址 = ",&i)
指针类型 :
例 :
var ptr *int = &num
获取指针类型所指向的值,用*
var ptr *int
使用*ptr 获取 ptr 指向的值
例 :
package main
import (
"fmt"
)
func main() {
var i int = 10
fmt.Println("i的地址 = ", &i)
var ptr *int = &i
fmt.Printf("ptr = %v\n", ptr)
fmt.Printf("ptr 的地址= %v", &ptr)
fmt.Printf(" ptr 指向的值=%v", *ptr)
}
函数中传指针可以修改参数的值 :
package main
import "fmt"
func add2(n int) {
n += 2
}
func add2ptr(n *int) {
// 指针 对传入参数进行修改
*n += 2
}
func main() {
n := 5
add2(n)
fmt.Println(n) // 5
add2ptr(&n)
fmt.Println(n) // 7
}
go标识符 :
- 有a-z,A-Z,0-9,_组成
- 数字不能开头
- 严格区分大小写
- 下划线"_"本身在 Go 中是一个特殊的标识符,称为空标识符。可以代表任何其它的标识符,但 是它对应的值会被忽略(比如:忽略某个返回值)。所以仅能被作为占位符使用,不能作为标识符使用。
- 不能以系统保留关键字作为标识符(一共有 25 个),比如 break,if 等等
注意 :
-
保持package 的名字与目录保持一致
-
如果变量名、函数名、常量名首字母大写,则可以被其他的包访问;如果首字母小写,则只能 在本包中使用 ( 注:可以简单的理解成,首字母大写是公开的,首字母小写是私有的) ,在 golang 没有 public , private 等关键
可见性 : 1)声明在函数内部,是函数的本地值,类似private 2)声明在函数外部,是对当前包可见(包内所有.go文件都可见)的全局值,类似protect 3)声明在函数外部且首字母大写是所有包可见的全局值,类似publicgo的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 vargo的37个保留字
Constants: true false iota nil Types: int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr float32 float64 complex128 complex64 bool byte rune string error Functions: make len cap new append copy close delete complex real imag panic recover
运算符
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等
- 算术运算符
- 赋值运算符
- 比较运算符/关系运算符
- 逻辑运算符
- 位运算符
- 其它运算符
注意 :
-
GoLang的自增自减只能当做一个独立语言使用时,不能这样使用:
如 : 1.a = i++ 2.if(i++ > a){ } -
Golang 的++ 和 -- 只能写在变量的后面,不能写在变量的前面,即:只有 a++ a-- 没有 ++a --a
-
go不支持三元运算符
go键盘输入
步骤 :
-
导入fmt包
-
调用fmt包的fmt.Scanln() 或者 fmt.Scanf()
func Scanln(a...interface{}) (n int ,err error) func Scanf(format string,a...interface{}) (n int ,err error)
例 :
func main() {
var name string
var age int
fmt.Scanln(&name)
fmt.Scanf("%d", &age)
fmt.Printf("姓名 : %v ,年龄 : %v \n", name, age)
}
程序流程控制
-
顺序控制
-
分支控制 :
- if-else :
func main() { // 双分支 if 7%2 == 0 { fmt.Println("7 is even") } else { fmt.Println("7 is odd") } //单分支 if 8%4 == 0 { fmt.Println("8 is divisible by 4") } // 多分支 if num := 9; num < 0 { fmt.Println(num, "is negative") } else if num < 10 { fmt.Println(num, "has 1 digit") } else { fmt.Println(num, "has multiple digits") } } -
switch :
func main() { a := 2 switch a { case 1: fmt.Println("one") case 2: fmt.Println("two") case 3: fmt.Println("three") case 4, 5: fmt.Println("four or five") default: fmt.Println("other") } t := time.Now() switch { case t.Hour() < 12: fmt.Println("It`s before noon") default: fmt.Println("It`s after noon") } }switch中的注意 :
-
switch 穿透-fallthrough ,如果在 case 语句块后增加 fallthrough ,则会继续执行下一个 case,也 叫 switch 穿透
-
switch 后也可以不带表达式,类似 if --else 分支来使用。
-
default 语句不是必须的.
-
case 后面不需要带 break , 程序匹配到一个 case 后就会执行对应的代码块,然后退出 switch,如 果一个都匹配不到,则执行 defaul
-
Type Switch:switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际指向的 变量类型
例:
package main import fmt "fmt" func main() { var x interface{} var y = 10.0 x = y switch i := x.(type) { case nil: fmt.Printf("×的类型~:%T", i) case int: fmt.Printf("x是int 型") case float64: fmt.Printf("x是float64 型") case func(int) float64: fmt.Printf("x是func(int)型") case float32: fmt.Printf("x是float32 型") case bool: fmt.Printf("x是bool型") case string: fmt.Printf("x 是 string 型") default: fmt.Printf("未知型") } }
-
-
循环控制
-
for循环控制 :
例 :
func main() { for i := 1; i < 10; i++ { fmt.Println("i = ", i) } } -
for循环的第二种使用方式 :
for 循环判断条件 { // 循环执行语句 }
func main() { j := 1 for j <= 10 { fmt.Println("i = ", j) j++ } } -
for循环法三 :
for {
//循环执行语句
}
等价于for ;; {} 是一个无限循环,要配合break语句使用
func main() { k := 1 for { if k <= 10 { fmt.Println("ok~~", k) } else { break } k++ } } -
golang提供for-range方法,可以遍历字符串和数组
传统方式 :
for i:=0 ; i<len(str);i++ { fmt.Println("%c \n",str[i]) }for-range
func main() { var str string = "abc-ok" for index, val := range str { fmt.Printf("index = %d,val = %c \n", index, val) } }对于传统方法,是按照字节来遍历,有汉字的话,会出现乱码,所以需要将str转成[]rune切片
func main() { var str string = "abc-ok你好" str2 := []rune(str) // 将str转成[]rune for i := 0; i < len(str2); i++ { fmt.Printf("%c \n", str2[i]) } } -
golang中不支持while,但是能用for来代替
-
go常量 :
在go中,常量的声明方式与变量相似,只不过使用了const关键字,常量不能以:=语法来声明。
package main
import "fmt"
const Pi = 3.14
func main() {
const World = "世界"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")
const Truth = true
fmt.Println("Go rules?", Truth)
}
go---defer
defer
在go中,defer语句会将函数推迟到外层函数返回之后执行
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
输出 :
hello
world
defer栈
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
结果 :
counting
done
9
8
7
6
5
4
3
2
1
0
函数,包和错误
函数
基本语法 :
func 函数名 (形参列表) (返回值列表) {
执行语句
return 返回值列表
}
案列 :
package main
import "fmt"
func add(a int, b int) int {
//golang 变量类型后置
return a + b
}
func add2(a, b int) int {
return a + b
}
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
func main() {
//golang里面的函数原生支持返回多个值,
//一般两个,第一个是真正的结果,第二个是错误信息
res := add(1, 2)
fmt.Println(res) // 3
v, ok := exists(map[string]string{"a": "A"}, "a")
fmt.Println(v, ok) // A True
}
-
函数可以没有参数或者接受多个参数
-
类型在变量名之后
-
当连续两个或多个函数的已命名形参类型相同时,除最后一个类型以外,其他都可以省略。
如 :
x int ,y int // 可以简写为 : x , y int -
函数可以定义任意数量的返回值
-
函数的返回值 : go中的返回值可以被命名,没有参数的return 语句返回已命名的返回值,也就是直接返回,这种形式仅限于短函数中,在长函数中会影响代码的可读性。
package main import "fmt" func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return } func main() { fmt.Println(split(17)) } -
短变量声明 :
在函数中,简洁赋值语句
:=可在类型明确的地方代替var声明。函数外的每个语句都必须以关键字开始(
var,func等等),因此:=结构不能在函数外使用。
包
: go 的每一个文件都是属于一个包的,也就是说 go 是以包的形式来管理文件和项目目录结构 的
包的作用 :
- 区分相同名字的函数、变量等标识符
- 当程序文件很多时,可以很好的管理项目
- 控制函数、变量等访问范围,即作用域
打包 :
package 包名
引入包 :
import "包的路径"
例 :
utils.go
package utils
func Cal(n1 float64, n2 float64,operator byte) float64{
var res float64
switch operator {
case '+' :
res = n1+n2
case '-' :
res = n1-n2
case '*' :
res = n1*n2
case '/' :
res = n1/n2
}
return res
}
test.go :
package main
import (
"fmt"
"go_code/project01/utils"
)
func main() {
var n1 float64 = 1.2
var n2 float64 = 2.3
var operator byte = '+'
result := utils.Cal(n1,n2,operator)
fmt.Println("result = ",result)
}
-
在访问其它包函数,变量时,其语法是 包名.函数名,.
-
如果包名较长,Go 支持给包取别名, 注意细节:取别名后,原来的包名就不能使用了
-
包的导入有两种方式 :
-
以"分组"的形式导入
import ( "fmt" "math" ) -
编写多条导入语句
import "fmt" import "math"
-
-
导出名 : 在 Go 中,如果一个名字以大写字母开头,那么它就是已导出的。例如,
Pizza就是个已导出名,Pi也同样,它导出自math包。pizza和pi并未以大写字母开头,所以它们是未导出的。在导入一个包时,你只能引用其中已导出的名字。任何“未导出”的名字在该包外均无法访问。
go结构体
基础 :
结构体 : 带类型字段的集合
一个结构体(struct)就是一组字段(field)
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}
运行结果 :
{1,2}
结构体简单示例 :
package main
import "fmt"
type user struct {
// 包含两个字段:name和password
name string
password string
}
func main() {
a := user{name: "wang", password: "1024"}
b := user{"wang", "1024"}
c := user{name: "wang"}
c.password = "1024"
var d user
d.name = "wang"
d.password = "1024"
fmt.Println(a, b, c, d) //{wang 1024} {wang 1024} {wang 1024} {wang 1024}
fmt.Println(checkPassword(a, "haha")) // false
fmt.Println(checkPassword2(&a, "haha")) // false
}
func checkPassword(u user, password string) bool {
return u.password == password
}
func checkPassword2(u *user, password string) bool {
return u.password == password
}
结构体文法 :
结构体文法可以通过直接列出字段的值来新分配一个结构体
使用 name : vale 可以仅列出部分字段 ,且字段名的顺序无关
特殊的前缀 & 返回一个指向结构体的指针
package main
import "fmt"
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // 创建一个 Vertex 类型的结构体
v2 = Vertex{X: 1} // Y:0 被隐式地赋予
v3 = Vertex{} // X:0 Y:0
p = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)
func main() {
fmt.Println(v1, p, v2, v3)
}
{1 2} &{1 2} {1 0} {0 0}
注意 :
-
结构体字段通过点号来访问
-
结构体字段可以通过结构体指针来访问
如果我们有一个指向结构体的指针
p,那么可以通过(*p).X来访问其字段X。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写p.X就可以。package main import "fmt" type Vertex struct { X int Y int } func main() { v := Vertex{1, 2} p := &v p.X = 1e9 fmt.Println(v) } 运行结果 : {1000000000 2}
数组 :
类型 [n]T 表示拥有 n 个 T 类型的值的数组。
表达式 :
var a [10]int
简例 :
package main
import "fmt"
func main() {
var a [5]int
a[4] = 100
fmt.Println(a[4], len(a))
b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d:", twoD)
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
字符串
基础 :
package main
import (
"fmt"
"strings"
)
// go里strings包中有很多字符串工具函数用strings.x()来实现
func main() {
a := "hello"
// Contains() : 判断一个字符串里面是否包含另一个字符串,返回结果类型为bool
fmt.Println(strings.Contains(a, "ll")) // true
// Count() : 字符串计数
fmt.Println(strings.Count(a, "l")) // 2
// HasPrefix() : strings.HasPrefix()函数用来检测字符串是否以指定的前缀开头
fmt.Println(strings.HasPrefix(a, "he")) // true
// HasSuffix() : strings.HasSuffix()函数用来检测字符串是否以指定的字符串结尾
fmt.Println(strings.HasSuffix(a, "llo")) // true
// Index() : 查找某一个字符串的位置
fmt.Println(strings.Index(a, "ll")) // 2
// Join() : 链接多个字符串
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
// Repeat() : 重复多个字符串
fmt.Println(strings.Repeat(a, 2)) // hellohello
// Replace() : 替换字符
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
// Split() : 切片
fmt.Println(strings.Split("a-b-c", "-")) // [a b c]
// ToLower() : 小写字符串
fmt.Println(strings.ToLower(a)) // hello
// ToUpper() : 大写字符串
fmt.Println(strings.ToUpper(a)) // HELLO
// len() : 求字符串的长度
fmt.Println(len(a)) // 5
b := "你好"
// 一个中文字符可能对应多个字符
fmt.Println(len(b)) // 6
}
字符串格式化 :
- golang中的fmt.Printf()非常类似于c语言中的printf()函数
package main
import "fmt"
type point struct {
x, y int
}
func main() {
s := "hello"
n := 123
p := point{1, 2}
// fmt.Println() : 打印多个变量并换行
fmt.Println(s, n) // hello 123
fmt.Println(p) // {1 2}
// fmt.Printf()中可以用 %v 来代替任意类型的变量,不用像c语言中那样区分
fmt.Printf("s=%v\n", s) // s=hello
fmt.Printf("n=%v\n", n) // n=123
fmt.Printf("p=%v\n", p) // p={1 2}
// %+v : 可以得到更加详细的结构
fmt.Printf("p=%+v\n", p) //p={x:1 y:2}
// %#v : 进一步详细
fmt.Printf("p=%#v\n", p) //p=main.point{x:1 y:2}
f := 3.141592653
fmt.Println(f) // 3.141592653
fmt.Printf("%.2f\n", f) //3.14
}
切片
切片 通俗来讲 : 可任意更改长度的数组;
切片实际上是存储了一个长度,容量,指向数组的指针
每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。
类型 []T 表示一个元素类型为T的切片
切片通过两个下标来界定,一个上界一个下届
a[low:height]
左闭右开
比如 : a[1 : 4] 表示a中下标1到3的元素
切片简例 :
package main
import "fmt"
func main() {
//切片:不同于数组
//可以用make来创建一个切片,像数组一样取值,使用append来添加元素(必须把append的结果赋值回原数组)
//slice 原理:它有一个它存储了一个长度和容量,加一个指向数组的指针,
// 在你执行append 操作时,如果容量不够的话,会扩容并且返回新的slice
// slice在初始化的时候可以指定长度
//slice支持像 python 一样的索引操作,但不支持负数索引,左闭右开。
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2])
fmt.Println("len:", len(s))
s = append(s, "d") // append : 在长度不够时,会自动扩容,并将新的返回回/去,所以要用s来接收
s = append(s, "e", "f")
fmt.Println(s) //[a b c d e f]
c := make([]string, len(s))
copy(c, s)
fmt.Println(c) //[a b c d e f]
fmt.Println(s[2:5]) //[c d e]
fmt.Println(s[:5]) // [a b c d e]
fmt.Println(s[2:]) //[c d e f]
good := []string{"g", "o", "o", "d"}
fmt.Println(good) //[g o o d]
}
切片就像数组的引用 :
切片不会存储任何数据,他只是描述了底层数组中的一段
最重要的是 :
- 更改切片的元素会同步修改其底层数组中对应的元素
- 与它共享底层数组的切片也都会观测到这些变化
例 :
package main
import "fmt"
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}
输出结果 :
[John Paul George Ringo]
[John Paul] [Paul George]
[John XXX] [XXX George]
[John XXX George Ringo]
切片文法 :
切片文法类似于没有长度的·数组文法
这是一个数组文法 :
[3]bool{true,true,false}
下面这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片
[]bool{true,true,false}
切片的长度与容量 :
- 切片拥有 长度 和 容量。
- 切片的长度就是它所包含的元素个数。
- 切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
- 切片
s的长度和容量可通过表达式len(s)和cap(s)来获取。 - 你可以通过重新切片来扩展一个切片,给它提供足够的容量。试着修改示例程序中的切片操作,向外扩展它的容量,看看会发生什么。
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// 截取切片使其长度为 0
s = s[:0]
printSlice(s)
// 拓展其长度
s = s[:4]
printSlice(s)
// 舍弃前两个值
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
结果 :
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
切片注意 :
-
切片默认上界是切片的长度,下界是0
-
切片的零值是nil,(nil切片的长度和容量为0,且没有底层数组)
-
切片可包含任何数据类型,甚至包括其他的切片
package main import ( "fmt" "strings" ) func main() { // 创建一个井字板(经典游戏) board := [][]string{ []string{"_", "_", "_"}, []string{"_", "_", "_"}, []string{"_", "_", "_"}, } // 两个玩家轮流打上 X 和 O board[0][0] = "X" board[2][2] = "O" board[1][2] = "X" board[1][0] = "O" board[0][2] = "X" for i := 0; i < len(board); i++ { fmt.Printf("%s\n", strings.Join(board[i], " ")) } } 结果 : X _ X O _ X _ _ O -
向切片追加元素,用append(),当底层数组太小,会分配一个更大的数组
map
在其他编程语言中map可能叫做哈希或者字典,即 键值对集合
语法 :
map[key_type]value_type
简例 :
package main
import "fmt"
func main() {
// map : 哈希 或 字典 (数据结构)
//golang的map完全无序
// 可以用make来创建一个空的map(需要两个类型,key and value)
m := make(map[string]int) //[key:string value:int]
m["one"] = 1
m["two"] = 2
fmt.Println(m) //map[one:1 two:2]
fmt.Println(len(m)) // 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0 找不到返回0
r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false
delete(m, "one")
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
}
Range
for 循环的 range 形式可遍历切片或映射。
当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
package main
import "fmt"
func main() {
// range 对于一个slice 或map,可以用range来快速遍历
// range遍历的时候会返回两个值,第一个:索引 第二个:对应位置的值
// 不需要索引的话可以用下划线来忽略
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index:0 num:2
}
}
fmt.Println(sum) // 9
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v) //a A ; b B
}
for k := range m {
fmt.Println("key", k) // key a ; key b
}
}
- 不需要的值可以用 '_' 来替代
映射
映射 : 键值对,将键映射到值
映射的零值为nil,nil映射既没有键,也不能添加键
make 函数会返回给定类型的映射,并将其初始化备用。
package main
import "fmt"
func main() {
// map : 哈希 或 字典 (数据结构)
//golang的map完全无序
// 可以用make来创建一个空的map(需要两个类型,key and value)
m := make(map[string]int) //[key:string value:int]
m["one"] = 1
m["two"] = 2
fmt.Println(m) //map[one:1 two:2]
fmt.Println(len(m)) // 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0 找不到返回0
r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false
delete(m, "one")
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
}
修改映射 :
在映射 m 中插入或修改元素:
m[key] = elem
获取元素:
elem = m[key]
删除元素:
delete(m, key)
通过双赋值检测某个键是否存在:
elem, ok = m[key]
若 key 在 m 中,ok 为 true ;否则,ok 为 false。
若 key 不在映射中,那么 elem 是该映射元素类型的零值。
同样的,当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。
注 :若 elem 或 ok 还未声明,你可以使用短变量声明:
elem, ok := m[key]
函数
函数 :
- 函数是基本的代码块,用于执行一个任务。
- Go 语言最少有个 main() 函数。
- go中很多函数中会返回多个值,第一个值是真正的返回结果,第二个是错误信息
函数定义语法 :
func function_name( [parameter list] ) [return_types] {
函数体
}
函数定义解析:
- func:函数由 func 开始声明
- function_name:函数名称,参数列表和返回值类型构成了函数签名。
- parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
- return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
- 函数体:函数定义的代码集合。
实例 :
func max(num1, num2 int) int {
/* 声明局部变量 */
var result int
if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}
go中函数支持多个返回值 :
package main
import "fmt"
func add(a int, b int) int {
//golang 变量类型后置
return a + b
}
func add2(a, b int) int {
return a + b
}
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
func main() {
//golang里面的函数原生支持返回多个值,
//一般两个,第一个是真正的结果,第二个是错误信息
res := add(1, 2)
fmt.Println(res) // 3
v, ok := exists(map[string]string{"a": "A"}, "a")
fmt.Println(v, ok) // A True
}
匿名函数 :
匿名函数的使用:
func main() {
res := func(n1 int, n2 int) int {
return n1 * n2
}(10, 20)
fmt.Printf("res: %v\n", res)
}
将匿名函数赋值给变量,通过变量调用:
func main() {
ret := func(n1 int, n2 int) int {
return n1 + n2
}
// 变量调用
sum := ret(100, 20)
fmt.Printf("sum: %v\n", sum)
// 多次调用
sum2 := ret(1000, 30)
fmt.Printf("sum2: %v\n", sum2)
}
函数值
函数也是值,它们也可以像其他值一样传递。
函数值可以用作函数的参数或返回值
例 :
package main
import (
"fmt"
"math"
)
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))
fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}
函数的闭包 :
Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。
例如,函数 adder 返回一个闭包。每个闭包都被绑定在其各自的 sum 变量上。
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
fibonacci闭包 :
package main
import "fmt"
// 返回一个“返回int的函数”
func fibonacci() func() int {
a, b := 0, 1
return func() int {
result := a
a, b = b, a+b
return result
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
执行输出 :
0
1
1
2
3
5
8
13
21
34
type
利用 type 可以声明某个类型的别名(理解为声明一种新的数据类型)
方法
Golang中没有类。
为结构体类型定义方法
方法就是一类带特殊的 接收者 参数的函数,有点类似与java中的成员方法。
方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。
实例 :
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}
在此例中,Abs 方法拥有一个名为 v,类型为 Vertex 的接收者。
在golang中,方法只是一个带接收者参数的函数,其本质上其实也就是函数。
简例 :
package main
import "fmt"
// 在 Golang 里面可以去为结构体定义一些方法,会有点类似其他语言中的类成员函数,
// 例如: checkpassword()的实现,从一个普通函数,改成了结构体方法,这样的话,就可以像 a.checkPassword("xx")
// 去调用,具体代码修改,就是把第一个参数,加上括号,写到函数名称前面
// 在实现结构体方法的时候有两种写法:
// 1.带指针,可以对结构体中的内容进行修改
// 2.不带指针,实际操作就是一个 拷贝 ,无法对结构体进行修改
type user struct {
name string
password string
}
func (u user) checkPassword(password string) bool {
return u.password == password
}
func (u *user) resetPassword(password string) {
u.password = password
}
func main() {
a := user{"wang", "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
}
为非结构体类型声明方法。
在此例中,我们看到了一个带 Abs 方法的数值类型 MyFloat。
你只能为在同一包内定义的类型的接收者声明方法,而不能为其它包内定义的类型(包括 int 之类的内建类型)的接收者声明方法。
(译注:就是接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法。)
例 :
package main
import (
"fmt"
"math"
)
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}
对于 type MyFloat float64 这一行 :
这行代码定义了一个新的自定义类型 MyFloat,它是基于基本类型 float64 进行定义的。
MyFloat 类型是一个命名的浮点数类型,它具有 float64 的所有行为和特性。这种类型定义允许我们在程序中对特定类型的值进行更具描述性的操作并引入额外的方法。
通过定义自定义类型,我们可以为这个类型添加一些自定义的方法,以扩展它的功能。
下面是一个示例,我们为 MyFloat 类型添加了一个名为 Add 的方法来执行两个 MyFloat 值的相加操作:
package main
import "fmt"
type MyFloat float64
// 添加一个名为 Add 的方法用于相加操作
func (f MyFloat) Add(other MyFloat) MyFloat {
return MyFloat(f + other)
}
func main() {
f1 := MyFloat(3.14)
f2 := MyFloat(2.78)
result := f1.Add(f2)
fmt.Println(result) // 输出: 5.92
}
上面的代码示例中,我们通过为 MyFloat 类型定义了一个名为 Add 的方法来执行两个 MyFloat 值的相加操作。该方法接收一个类型为 MyFloat 的接收器参数 f,并返回一个新的 MyFloat 值,其值为 f 与 other 的和。
在 main 函数中,我们创建了两个 MyFloat 类型的变量 f1 和 f2,并初始化它们的值。然后,我们调用 f1.Add(f2) 来执行相加操作,并将结果打印出来。
这样,通过为自定义类型定义方法,我们可以为特定类型添加自定义的行为和功能
指针接收者
为指针接收者声明方法
指针接收者的方法可以修改接收者指向的值(就像 Scale 在这做的)。由于方法经常需要修改它的接收者,指针接收者比值接收者更常用。
通俗来讲,也就是传指针;
例 :
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs())
}
结果 : 50
去掉 '*' ,结果 : 5
将上面的方法重写为函数,也就是 :
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func Scale(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
Scale(&v, 10)
fmt.Println(Abs(v))
}
方法与指针重定向
比较前两个程序,你大概会注意到带指针参数的函数必须接受一个指针:
var v Vertex
ScaleFunc(v, 5) // 编译错误!
ScaleFunc(&v, 5) // OK
而以指针为接收者的方法被调用时,接收者既能为值又能为指针:
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
对于语句 v.Scale(5),即便 v 是个值而非指针,带指针接收者的方法也能被直接调用。 也就是说,由于 Scale 方法有一个指针接收者,为方便起见,Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5)。
例 :
package main
import "fmt"
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func ScaleFunc(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(2)
ScaleFunc(&v, 10)
p := &Vertex{4, 3}
p.Scale(3)
ScaleFunc(p, 8)
fmt.Println(v, p)
}
输出结果 :
{60 80} &{96 72}
同样的事情也发生在相反的方向。
接受一个值作为参数的函数必须接受一个指定类型的值:
var v Vertex
fmt.Println(AbsFunc(v)) // OK
fmt.Println(AbsFunc(&v)) // 编译错误!
而以值为接收者的方法被调用时,接收者既能为值又能为指针:
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
这种情况下,方法调用 p.Abs() 会被解释为 (*p).Abs()。
选择值或指针作为接收者
使用指针接收者的原因有二:
首先,方法能够修改其接收者指向的值。
其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效。
在本例中,Scale 和 Abs 接收者的类型为 *Vertex,即便 Abs 并不需要修改其接收者。
通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用。
错误处理
- 错误处理在 go 语言里面符合语言习惯的做法就是使用一个单独的返回值来传递错误信息。
- 不同于java自家使用的异常,go语言的处理方式,能够很清晰的知道那个函数返回了错误,并且能用简单的if else 语句来处理错误
- 在函数里面,我们可以在那个函数的返回值类型里面,后面加一个 error ,就代表这个函数可能会返回错误
- 在函数实现的时候,return需要同时 return 两个值.
- 要么就是如果出现错误的话,那么就可以return nil 和一个error,如果没有的话,那么返回 原本结果 和 nil;
package main
import (
"errors"
"fmt"
)
type user struct {
name string
password string
}
func findUser(users []user, name string) (v *user, err error) {
// users 结构体数组
for _, u := range users {
if u.name == name {
return &u, nil
}
}
// 如果出现 错误 ,返回nil,error
return nil, errors.New("not found")
}
func main() {
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err != nil { //如果err != nil,表示出现错误,那就结束 return
fmt.Println(err)
return
}
// 执行到这里,表示没有错误
fmt.Println(u.name) // wang
if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
}
接口
了解接口
- 接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。
- 接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)。
- 在go中,interface是一种类型。
- 接口是抽象的,当你看到一个接口类型的值时,你不知道它是什么,唯一知道的是通过它的方法能做什么。
go提倡面向接口编程
接口是一个或多个方法签名的集合。
任何类型的方法集中只要拥有该接口'对应的全部方法'签名。
就表示它 "实现" 了该接口,无须在该类型上显式声明实现了哪个接口。
这称为Structural Typing。
所谓对应方法,是指有相同名称、参数列表 (不包括参数名) 以及返回值。
当然,该类型还可以有其他方法。
接口只有方法声明,没有实现,没有数据字段。
接口可以匿名嵌入其他接口,或嵌入到结构中。
对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针。
只有当接口存储的类型和对象都为nil时,接口才等于nil。
接口调用不会做receiver的自动转换。
接口同样支持匿名字段方法。
接口也可实现类似OOP中的多态。
空接口可以作为任何类型数据的容器。
一个类型可实现多个接口。
接口命名习惯以 er 结尾。
go接口的使用
接口的定义
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
其中 :
1.接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
2.方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
3.参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
例 :
type writer interface{
Write([]byte) error
}
-
一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表。
-
类型通过实现一个接口的所有方法来实现该接口。既然无需专门显式声明,也就没有“implements”关键字。
隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中,无需提前准备。
因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。
实例 :
package main
import "fmt"
type I interface {
M()
}
type T struct {
S string
}
// 此方法表示类型 T 实现了接口 I,但我们无需显式声明此事。
func (t T) M() {
fmt.Println(t.S)
}
func main() {
var i I = T{"hello"}
i.M()
}
接口值
接口也是值。它们可以像其它值一样传递。
package main
import (
"fmt"
"math"
)
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
fmt.Println(t.S)
}
type F float64
func (f F) M() {
fmt.Println(f)
}
func main() {
var i I
i = &T{"Hello"}
describe(i)
i.M()
i = F(math.Pi)
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
空接口
interface {}
空接口可保存任何类型的值。(因为每个类型都至少实现了零个方法。)
空接口被用来处理未知类型的值。例如,fmt.Print 可接受类型为 interface{} 的任意数量的参数。
Stringer
type Stringer interface {
String() string
}
Stringer 是一个可以用字符串描述自己的类型。fmt 包(还有很多包)都通过此接口来打印值
error
- 错误处理在 go 语言里面符合语言习惯的做法就是使用一个单独的返回值来传递错误信息。
- 不同于java自家使用的异常,go语言的处理方式,能够很清晰的知道那个函数返回了错误,并且能用简单的if else 语句来处理错误
- 在函数里面,我们可以在那个函数的返回值类型里面,后面加一个 error ,就代表这个函数可能会返回错误
- 在函数实现的时候,return需要同时 return 两个值.
- 要么就是如果出现错误的话,那么就可以return nil 和一个error,如果没有的话,那么返回 原本结果 和 nil;
package main
import (
"errors"
"fmt"
)
type user struct {
name string
password string
}
func findUser(users []user, name string) (v *user, err error) {
// users 结构体数组
for _, u := range users {
if u.name == name {
return &u, nil
}
}
// 如果出现 错误 ,返回nil,error
return nil, errors.New("not found")
}
func main() {
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err != nil { //如果err != nil,表示出现错误,那就结束 return
fmt.Println(err)
return
}
// 执行到这里,表示没有错误
fmt.Println(u.name) // wang
if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
}
JSON操作
- go中json操作非常简单,对于一个已有的结构体,我们可以什么都不用做,只要保证每个字段的第一个字母是大写, 也就是公开字段,那么这个结构体就能用 json.marshaler去序列化,变成一个json的字符串
- 序列化之后的字符串也能够用json.unmarshler去反序列化到一个空的变量里面去
- 这样默认序列化出来的字符串的话,
- 它的风格就是大写字母开头,而不是下划线,我们可以在后期用json tag等语法来去修改输出json结果里面的字段名
package main
import (
"encoding/json"
"fmt"
)
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}
func main() {
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(buf) // [123 34 78 97...] : 打印出一些十六进制的编码
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))
/*
{
"Name": "wang",
"age": 18,
"Hobby": [
"Golang",
"TypeScript"
]
}
*/
var b userInfo
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
时间处理
package main
// 时间处理
import (
"fmt"
"time"
)
// 在goalng中最常用的就是用time.Now()来获取当前时间,也可以用time.Date()来构造一个带时区的时间
// 构造完,能用多种方法获取时分秒,能用sub去对两个时间做相减,得到一个时间段
// 在和某些系统交互时,我们经常会用到时间戳,那么可以用.UNIX来获取时间戳。time.format time.parse
func main() {
now := time.Now()
fmt.Println(now)
// 2023-02-03 13:39:51.5983539 +0800 CST m=+0.002820301
t := time.Date(2023, 2, 3, 1, 25, 36, 0, time.UTC)
// nesc : 纳秒 UTC : 协调世界时
t2 := time.Date(2023, 2, 3, 2, 30, 36, 0, time.UTC)
fmt.Println(t) // 2023-02-03 01:25:36 +0000 UTC
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2023 February 3 1 25
fmt.Println(t.Format("2006-01-02 15:04:05")) //2023-02-03 01:25:36
diff := t2.Sub(t)
fmt.Println(diff) // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
t3, err := time.Parse("2006-01-02 15:04:05", "2023-02-03 13:39:51")
if err != nil {
panic(err)
}
fmt.Println(t3 == t) // false
fmt.Println(now.Unix()) // 1675403530
}
以上内容主要来自于 :