项目初始
IDEA配置
-o bin.main
引用其他包文件
package calc
func Add(a, b int) int {
return a + b
}
* Add 函数必须要大写,大写属于public 才能够让其他包引用 ,不是大写 属于私有方法
* 同一个文件下面的函数变量名称不能重复
*
- GIF 动态画面
package main
import (
"image"
"image/color"
"image/gif"
"io"
"log"
"math"
"math/rand"
"net/http"
"os"
"time"
)
var palette = []color.Color{color.White, color.Black}
const (
whiteIndex = 0 // 画板中第一种颜色
blackIndex = 1 // 画板中第二种颜色
)
func lissajous(out io.Writer) {
const (
cycles = 5
res = 0.001
size = 100
nframes = 64
delay = 8
)
freq := rand.Float64() * 3.0
anim := gif.GIF{LoopCount: nframes}
phase := 0.0
for i := 0; i < nframes; i++ {
rect := image.Rect(0, 0, 2*size+1, 2*size+1)
img := image.NewPaletted(rect, palette)
for t := 0.0; t < cycles*2*math.Pi; t += res {
x := math.Sin(t)
y := math.Sin(t*freq + phase)
img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), blackIndex)
}
phase += 0.1
anim.Delay = append(anim.Delay, delay)
anim.Image = append(anim.Image, img)
}
err := gif.EncodeAll(out, &anim)
if err != nil {
return
}
}
func main() {
rand.Seed(time.Now().UTC().UnixNano())
if len(os.Args) > 1 && os.Args[1] == "web" {
handler := func(w http.ResponseWriter, r *http.Request) {
lissajous(w)
}
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("localhost:8080", nil))
return
}
lissajous(os.Stdout)
}
- 构建简单 统计的服务器
package main
import (
"fmt"
"log"
"net/http"
"sync"
)
// 构建一个简单的 web 服务 能够流量计数统计
var mu sync.Mutex
var count int
func handler(w http.ResponseWriter, r *http.Request) {
mu.Lock()
count++
mu.Unlock()
_, err := fmt.Fprintf(w, "URL.path = %q\n", r.URL.Path)
if err != nil {
return
}
}
func counter(w http.ResponseWriter, r *http.Request) {
mu.Lock()
_, err := fmt.Fprintf(w, "Count %d\n", count)
if err != nil {
return
}
mu.Unlock()
}
func main() {
// 回声请求的调用处理函数
http.HandleFunc("/", handler)
http.HandleFunc("/counter", counter)
log.Fatal(http.ListenAndServe("localhost:8080", nil))
}
- 变量第一区别
var :多用于全局变量
:= 简短声明多用于局部变量
new() 分配内存函数 返回函数地址
make() 只使用于 map,chan, slice
- init()函数
1. 同一个包下面可以有多个init 函数
2. 自动执行,不需要调用 类似于Java中构造器函数
3. 方法名称 init() ,init()在main() 函数前执行, 多个init() 初始化化原则(依赖顺序初始化)
- go 语言中异常处理 通过是使用 if 方式
if f,err := os.Open(fname); err != nil {
return err
}else {
f.Stat()
f.Close()
}
- 常量
4. 使用const 定义,如果在定义过程中没有初始化值 自动找前一个赋值常量
const (
a = 1
b
c = 2
d
) // 1 1 2 2
5. 常量生成器: iota :从0 开始取值 逐项+1
使用在枚举类中数字表示
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
)
- Go 中函数有多个返回值
func op(x, y int) (int, error) {
if (0 == y){
return -1, errors.New("ch除数不能为0")
}else {
return x /y, nil
}
}
Errors.New(""), fmt.Errorf("parseing %s")Go语言有异常机制,但是针对程序BUG导致的预料之外的错误,对于常规的错误方法初始化通过返回字符串类型进行处理
6. s使用 fmt.Errorf 格式化一条错误消息并且返回一个新的错误值,在原始的错误消息不断添加额外的上下文信息 创建一个可读的错误描述,-- 清晰的因果链;
7. 错误消息频繁串联起来,消息字符串首字母 不应该大写并且避免换行
Go语言中错误的消息设计时候 慎重,最好包含充足的相关信息,并且保持一致性,保持统一的形式和错误的处理方式
- 对于不固定或者不可预测的错误
比如对服务器在短时间内进行 指数退避策略进行重试
if err := WaitForSrver(url); err != nil {
fmt.Fprintf(os.Stderr, "site is down: %v\n", err)
os.Exit(1)
}
Go 语言错误处理有特定的规律, 成功的逻辑不会放在else 块,在外层的作用域,一般看到 if err 之后,不会有太多的else
如果不定义一个临时变量进行存储,最终删除知识最后一次
var rmdirs [] func()
for _, d := range tempDirs(){
dir :=d // 必须要使用局部变量 声明内部的dir ,并且外部的dir 进行初始化
os.MkdirAll(dir, 0775)
rmdirs = append(rmdirs, func() {
os,RemoveAll(dir)
})
}
defer go中一种延迟调用的机制,defer 函数只有在当前函数执行完毕后才会执行,遵守的栈的结果 场景在于 释放资源, 保留是入栈时候的值
8. go 中 return 语句并不是原子性操作, defer 在1之后 2之前
1. 将返回值赋值个一个变量
2. 执行RET指令
9. -- 输出结果 10 保存的是入栈那一刻的值
x := 10
defer func(a int) {
fmt.Printf("%d", a)
}(x)
x++
10. 输出结果 11 传入的是地址
x := 10
defer func(a *int) {
fmt.Printf("%d", *a)
}(&x)
x++
11. 输出结果 11 是具名函数。即返回值带有名字。这样我们在执行defer的时候相当于修改了返回值的值。所以为11
func test()(x int) { -- 属于具体名称函数 ,返回值带有函数名称
x = 10
defer func() {
x++
}()
12. 闭包函数 返回值 10
13. func test1() int {
x := 10
defer func() {
x++
}()
// ans = x
// -------- defer x = x+1
// return x
return x
}
倒序方式打印出堆栈函数recorver 终止当前的paninc 状态并且返回paninc 函数, 函数在paninc地方跳转到recover函数 ,与 Java try..catch 有不一样
package main
import "fmt"
func main() {
defer func() {
err := recover()
// 恢复
fmt.Println("recorver error:", err)
}()
for i := 0; i < 10; i++ {
if i == 5 {
// painc 程序退出
panic("i == 5")
}
}
}
-- recover 函数 捕获到 paninc 函数 ,与 Java try..catch 不一样,在 i == 5 跳出主函数 ,执行 延迟函数中 recover(),没有执行 i= 6; 不会执行for 循环
组合封装
方法声明
- 方法声明与普通函数相似,只是与普通的函数多了一个参数, 将 Distance 方法属于某个类型方式, 普通函数属于 包级别方法
func Distance(p, q Point) float64 {
return p * q
}
// Point类型函数, p 称为方法接收者, 使用类型的简写
func (p Point) Distance(q Point) float64 {
return p.x-q.x + p.Y- q.Y
}
类型方法调用: p:= Point{1,2}
q:= Point{3,4}
p.Distance(q) -- 选择子 ,注意 方法和名称属于同一个命名空间,在Point类型中声明一个与属性相同的方法名称会报错
类型任何一个方法使用指针接收者,其他方法使用指针接收者
主函数会赋值每一个实参变量,如果想更新一个变量,或者变量太大不像时刻复制实参,需要使用指针传递变量地址, 这样使用 指针类型的接收者;
1. 定义一个指针 & 取其地址
2. p Point 类型普通变量 Run 属于指针接收者
p.Run() // 编译器 隐式转换 (&p).Run() -- 普通类型变量调用指针方法
3. p3 = &Point{"jack", 1} p3.Say() ,其中 Say() 普通的方法
p3.Say() // 编译器隐式转换 (*p3).Say() -- 指针调用非指针函数
P3:实参类型,Run()方法 形参类型
3. 变量类型与方法 属于同一类型 调用OK
类型中如果允许nil 接收者需要在注释中 显示的表明
type IntList struct {
value int
tail *IntList
}
func (list *IntList) Sum() int {
if nil ==list {
return 0
}
return list.value + list.tail.sum()
}
- 封装
在Go中丹云是包 不是类型也不是类, 封装类似Java中 私有变量,protect变量
变量和方法不能通过对象方位到,这种方式叫做封装,也叫数据隐藏Go 只有一种方式控制命名可见性,定义时候 首字母大写的标识符,是可以从包中导出, 同样也适用于结构体内字段和类型中方法, 要封装一个对象,必须使用结构体
// 只能在定义InSet 包内使用
type InSet struct {
words [] unit64
}
// 其他包内被使用,重新定义 IntSet 为 一个Slice 类型 功能基本相同,想要修改 words值 需要使用 *s 指针类型
type InSet [] unit64
// bytes.Buffer 类型 结构体有 buf 首字母不是大写,其他包不能引用,这块空间额外的[64]byte ,使用者感觉性能提升但是不会关系内部实现
type Bffer struct {
buf [] byte
initial [64] byte
}
接口
Go 一个具体类型,无需进行接口声明,只需要提供实现接口方法即可
1. B 类型需要实现A类型的所有方法,才能认为B类型实现A接口
2. 传入任何的参数,需要通过接口 进行接口断言
3. flag.value进行参数解析,进行命令行参数的解析
动态类型和动态值
var w io.Writer
w = os.Stdout // 动态类型:os.File 类型描述符, 动态值为os.Stdout 副本
w= new(bytes.Buffer)
分配了指针但是其值没有调用使用发生paninc动态值是nil,但是其动态类型存在 在判断时候发现是正确的解决方案: 保持接口的动态类型 一致; 不要 定义的变量接口类型和函数动态类型不一致,导致发生空指针的非空接口, w != nil 比较动态类型
- 类型断言
类型断言作用在接口值上操作,x(T) ,X表示一个接口类型的表达式,断言检查 动态类型是否满足指定的断言类型
var w io.Writer
w = os.Stdout
// 判断接口的类型断言
f := w.(*os.File) // 成功 f == os.Stdout
var w io.Writer
w = os.Stdout
// T 是具体类型, 检查X的动态类型是否就是T
// x.(T) 如果T 是接口类型,检查 x 的动态类型是否满足 T
f := w.(*os.File)
fmt.Println("%T", f.Name())
- 使用类型断言识别错误
4. 使用字符串方式判断是否包含某种错误字符串 不建议
5.