go 程序设计语言 1-5

go 关键字:
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 的值:
true
false
iota
nil

go类型:
int int8 int16 int32 int64

uint uint8 uint16 uint32 uint64 uintptr

float32 float64 complex128 complex64

bool byte rune string error

make len cap new append copy close delete

complex real image panic recover


变量 常量 类型 函数


// 变量定义
var b,f,s = true,2.3,"four"

// 变量交换
i,j=j,i


短变量声明最少声明一个新变量,否则,代码编译将无法通过。
f,err :=os.Open(file)

f,err:=os.Open(file)


go的内存分配,要考虑是否存在内存逃逸,以决定分配 堆还是栈,跟关键字 new没直接关系。


计算两个数的最大公约数

func gcd(x,y int)int{
for y != 0{
x,y = y,x%y
}
return x
}


斐波那契数 dp
func fib(n int)int{
x,y := 0,1
for i:=0; i< n; i++{
x,y = y,x+y
}
return x
}


多重赋值
i,j,k = 2,3,5


隐式赋值
medals := []string{"gold", "silver","bronze"}

nil 可以赋值 接口类型 \ 引用类型


各类型 默认 初始化

从 包外 调用函数,需要使用包名,如;image.Decode / utf16.Decode

 

func init(){/*---------*/}
在每一个文件里,当程序启动的时候,init函数按照它们声明的顺序自动执行

 

作用域 编译时期的属性
生命周期 运行时的属性


go 的四大类型:
基础类型 --> 数字 字符串 布尔型

聚合类型 --> 数组 / 结构体

引用类型 --> 指针 slice map funcation channel

接口类型 --> interface

 

rune 类型是 int32类型的同义词,常常用于指明一个值的Unicode码点(code point)这两个名称可互换使用。
byte 类型是 uint8类型的同义词,强调一个值的原始数据,而非量值。


go 操作符优先级
* / % << >> & &^
+ - | ^
== != < <= > >=
&&
||


% 运算符只能用于整数


fmt.Printf("%08b\n",x) %b以二进制形式输出数值


无符号位右移,左面填充0 有符号数右移,左面填充1(因为有符号数据是以 补码的形式存在)

 


打印:
o:=0666
fmt.Printf("%d %[1]o %#[1]o\n",o)
打印结果:
438 666 0666
%后的副词[1]告知printf 重复使用第一个操作数o / %后的#告知 Printf输出相应的前缀0、0x、0X 。

newline := '\n'
fmt.Printf("%d %[1]q\n", newline) --> 10 '\n' %q 将字符带单引号原样输出


浮点数:
十进制下,float32 有效位 6位 float64 的有效位 15位,绝大多数情况下,优选float64
打印:
%g 自动保持足够的精度 %e(指数) %f(无指数)的形式
x:= 8.12345
fmt.Printf("%8.3f\n",x)

feeStr := ""
fee,_ := strconv.ParseFloat(feeStr,32)
fee2 := fee + 123

fee 的值为NaN Not a Number
math.IsNaN 函数判断其参数是否是非数值, math.NaN则返回非数值 NaN


bool值
短路行为: 如果左边的操作数已经能直接确定总体结果,则右边的操作数不会计算在内
s != "" && s[0]=='x' 利用短路行为,避开了因s为空,S[0] 越界的情况

字符串:
s := "hello,world"

+ 实现字符串的拼接
fmt.Println("goodbye" + s[5:]) --> "goodbye,world"

s += ",my world"
fmt.Println(s) --> hello,world,my world 将原来的内存丢弃,与新的字符串",my world" 创建新内存,将数据存进去,构成一个新的s

s是字符串,内容不可变,如 s[0]='L' 错误,但是其byte切片 slice 可以 a := []byte(s[:5]) a[0]='L' 这样操作,把字符串拷贝到一个新的内存中,生成新的字符串

【重】原生的字符串字面量 的书写形式是 `wweerrtg\n` ,使用的是 反引号 而不是 双引号;原生的字符串字面量内,转义序列不起作用。

Unicode
Unicode第8版,使用的数据类型为 int32 ,为go语言所采用, rune(文字符号) 类型作为int32 类型的别名

UTF-8 字节为单位对 Unicode 码点做变长编码,他是现行的一种Unicode标准
结构; 每个文字符号用 1~4个字节表示,首字节的高位 1 指明了后面还有多少个字节,若最高位为0,表示为Ascii码,如下:
01234567 文字符号0~127 ascii
11012345 10123456 128~2047 其他编码
11101234 10123456 10123456 2048~65535 其他编码
11101234 10123456 10123456 10123456 65536~0x10ffff 其他编码

go 文件默认的编码格式 utf-8

将一个整数转换成字符串,其值按文字符号类型解读,并且产生代表该文字符号值的utf-8码:
fmt.Println(string(65)) --> "A"
fmt.Println(string(0x4eac)) --> "京"

fmt.Println(string(12345678)) --> "\?\" 没有对应的码值,所以打印 '\uFFFD'的值

字符串可以和字节 slice 相互转换:
s := "abc"
b := []byte(s)
s2 := string(b)


字符串数字相互转换:
x := 123
y := fmt.Sprintf("x=%d",x) --> x=123 sprintf 可以组合,但是效率应该比较差
fmt.Println(y,strconv.Itoa(x)) --> x=123 123

strconv.FormatInt(int64(x),2) --> 二进制表示 1111011

x,err := strconv.Atoi("123")
y,err := strconv.ParseInt("123",10,64)第三个参数指定匹配哪种数据 16-->int16 0-->int ...


常量:
const(
a = 1
b
c = 2
d
)

fmt.Println(a,b,c,d) 1 1 2 2

常量生成器 iota
const(
_ = 1 << (10 * iota)
Kib // 1024
Mib // 1024 * 1024
)


无类型的常量比基本类型的数字精度更高,且算术精度高于原生的机器精度。

i:=0 --> i:=int(0) 隐式转换
r:='\000' --> r:=rune('\000')
fmt.Printf("%T\n",0.0) --> float64


【复合类型】
数组 拥有相同数据类型元素的序列,由于数组的长度固定,所以在GO里面很少使用 / 一般用slice
var a [3]int32
q := [...]int{1,2,3}
fmt.Println("%T\n",q) --> "[3]int"

// 按索引设值
symbol:=[...]string{1:"a",2:"b",3:"c"}

r := [...]int{99,-1} --> 定义了一个拥有100个元素的数组,最后一个元素值为-1,其他元素值为0

a := [2]int{1,2}
b := [...]int{1,2}
a == b --> true 类型相同,元素个数一致的数组可比较相等性,除此之外,不能比较,编译不能通过

数组传递是以值传递,效率低,数组传参,可以使用 数组的指针,如下:
func zero(ptr *[32]byte){
*ptr=[32]byte{}
}


slice:
拥有相同类型元素的可变长度的序列
底层是一个数组,他有三个属性: 指针 长度 容量

内置函数 len() 返回 slice 的长度
内置函数 cap() 返回 slice 的容量

s := x[m:n]
如果x是字符串,s为字符串
如果x是数组或者slice,s为slice

func reverse(s []int){
for i,j :=0, len(s)-1; i <j;i,j = i+1,j-1{
s[i],s[j] = s[j],s[i]
}
}
// 将一个slice 左移2个元素,调用3次reverse函数实现
s := []int{0,1,2,3,4,5}
reverse(s[:2])
reverse(s[2:])
reverse(s) --> [2 3 4 5 0 1]

slice 不能直接使用 == 判断相等性,不能用slice 作为map 的key
slice 最安全的写法,是不进行直接比较。

判断slice是否为空,使用len(s) == 0, 不能使用 s == nil 来判断

s := make([]T,len) --> len == cap
s := make([]T,len,cap) --> len / cap 各自设值

append 函数:
var x []int
x = append(x,1,2,3)

// 接收可变长度参数列表的 函数
func appendInt(x []int, y ...int){
var z []int
zlen :=len(x) + len(y)

// 内置 copy 函数
copy(z[len(x):],y)
return z
}


// 不重新分配底层数组,去掉slice里面的空格
func nonempty(strings []string) []string{
i:=0
for _,s := range strings{
if s != " "{
strings[i] = s
i++
}
}
return strings[:i]
}

// 使用 append 函数
func nonempty2(strings []string)[]string{
out := string[:0]
for _,s := range strings{
if s != " "{
out = append(out,s)
}
}

return out
}

slice 实现栈:

// push
stack = apeend(stack,v)

// top
top :=stack[len(stack)-1] --> 栈顶

// pop
stack = stack[:len(stack)-1] // 弹出元素


// 删除中间某一个元素
func remove(slice []int, i int) []int{
copy(slice[i:],slice[i+1:])
return slice[:len(slice)-1]
}


map:

//使用new创建一个map指针
ma := new(map[string]int)
//第一种初始化方法
*ma = map[string]int{}
(*ma)["a"] = 44
fmt.Println(*ma)

//第二种初始化方法
*ma = make(map[string]int, 0)
(*ma)["b"] = 55
fmt.Println(*ma)

//第三种初始化方法
mb := make(map[string]int, 0)
mb["c"] = 66
*ma = mb
(*ma)["d"] = 77
fmt.Println(*ma)

// 其他

ages := make(map[string]int)
ages["A"]=1
ages["B"]=2

ages := map[string]int{
"A":1,
"B":2,
}

key限定: slice float32 float64 不能使用
value 没有限定

var a map[string]int{}

删除元素:
delete(ages,"A")

age["A"]++

取得map值的地址不可行,因为map追加元素后。地址重新散列到新的存储位置,取得的地址无效
_ = &ages["bob"]

// 对 map 进行排序
import "sort"
var names []string
for name := range ages{
names = append(names,name)
}

sort.Strings(names)
for _,name := range names{
fmt.Printf("%s\t%d\n",name,ages[name])
}

// map 初始化
//s := make([]string,0,len(ages))

map设置元素之前,必须初始化 map

if age,ok :=ages["bob"];!ok{
fmt.Println("bob 不在字典中")
}

// 比较 map 的相等性
func equal(x,y map[string]int) bool{
if len(x) != len(y){
return false
}
for k,xv := range x{
if yv,ok := y[k]; !ok || yv != xv{
return false
}
}
--> !ok 判断必不可少

结构体:
type Employee struct{
ID int
Name string
Address string
Dob time.Time
Position string
Salary int
MID int
}

// 结构体定义变量
var dilbert Employee
var em *Employee = &dilbert

// 指针隐式转型
em.Position <==> (*em).Position


【重】如果一个结构体的成员变量名称是首字母大写的,那么这个变量是可导出的,这个是GO 最主要的访问控制机制,一个结构体中可以同时包含可导出和不可导出的成员变量

利用结构体创建一些 递归的数据结构,比如链表和树

【以下代码利用二叉树实现插入排序】

type tree struct{
value int
left,right *tree
}
// 就地排序
func Sort(values []int){
var root *tree
for _,v := range values{
root = add(root, v)
}
appendValues(values[:0],root)
}

// appendValues 将元素按照顺序追加到 values 里面,然后返回结果false
func appendValues(values []int, t *tree)[]int{
if t != nil{
values = appendValues(values, t.left)
values = append(values,t.value)
values = appendValues(values,t.right)
}
return values
}

// 遍历左右子树追加节点
func add(t *tree, value int) *tree{
if t == nil{
// 等价于返回 &tree{value:value}
t = new(tree)
t.value = value
return t
}

if value < t.value{
t.left = add(t.left,value)
}else{
t.right = add(t.right,value)
}

return t
}

空结构体:
struct{}
seen := make(map[string]struct{})
// ...
if _,ok := seen[s];!ok{
seen[s] = struct{}{}
// ...
}
这种节约内存很少并且语法复杂,所以一般尽量避免使用


type Point struct{x,y int}
p := Point{1,2}
p := Point{x:1,y:2}


ss := &Point{1,2} <==> ss := new(Point) *ss = Point{1,2}


结构体可以直接比较,按结构体内部的成员顺序依次比较

结构体可作为 map 的 key
type address struct{
hostname string
port int
}

hits := make(map[address]int)
hits[address{"golang.org", 443}] = 1

结构体 匿名成员
type Point struct{
x int
Y int // 包外可用
}

type Circle struct{
Point
Radius int
}

type Wheel struct{
Circle
spokes int
}

var w Wheel
w.x = 8 <==> w.Circle.Point.x = 8
w.Y = 10 // 同上


w = Wheel{Circle{Point{8,8,},5},20}
w = Wheel{
Circle:Circle{
Point:Point{x:8,Y:8},
Radius:5, // 后面的逗号是必须的
},
Spokes:20,
}


上面的 w.x 称之为 以快捷方式访问匿名成员的内部变量
同样,也可以使用这种方法访问 匿名成员的内部方法


这种组合方法获取 匿名成员的内部变量+相关方法
该机制是 简单类型组合成复杂类型的主要方式。

组合是 go 面向对象编程的核心

Json
json 类型:
boolean true
number -273.15
string "She said \" Hello, 世界\""
array ["gold","silver","bronze"]
object {"year":1980, "event":"archery","medals":["gold","silver","bronze"]}

go 的json 包 encoding/json

json 转 go 结构体 --> json.Unmarshal(data, &titles)
go 结构体转 json --> data,err := json.Marshal(var)


文本与HTML 模板
包: text/template html/template
几乎用不到,用到的时候再去查询

【函数】
格式:

func name(参数列表)(返回值列表){
函数体
}


func add(x int, y int)int {return x+y} // 注意参数列表中 空格 与 逗号 的 作用


函数的类型作为函数的签名,类型值 参数类型+返回值类型,形参名字+返回值名字 则不影响函数签名。

多值返回:
一个函数吐过有 命名的返回值,可以省略return 语句操作数,称之为 裸返回,如下:
func SetData(in int)(err error){
err = ...
return
}
裸返回不能直观的看出返回值,应该 保守 使用


[错误处理策略]:
resp,err := http.Get(url)
if err != nil{
fmt.Printf("%v",err) // err 是一个接口类型,打印使用格式控制符 %v
return nil,err
}

错误信息 字符串的首字母不应该大写而且应该避免换行,错误结果可能会很长,但能够使用grep 这样的工具找到我们需要的的信息
第二种错误策略: 在短暂时间间隔后对操作进行重试,超过一定的重试次数和限定的时间后再报错,然后退出
第三种,库函数将错误信息传给调用者,调用者判断这个错误信息,
使用log库记录/打印 日志信息
if err := WaitForServer(url); err != nil{
log.Fatalf("Site is down:%v\n",err)
}

自己定义log 包的输出前缀
log.SetPrefix("wait: ")
log.SetFlags(0)

第四,记录错误信息之后,继续运行程序
第五,在某些罕见的情况下,我们可以直接安全地忽略掉整个日志

[文件结束标识符]
io 包保证任何由文件结束引起的读取错误,始终都将会得到一个与众不同的错误 --> io.EOF

[函数变量]
func square(n int) int {return n*n}
f:= square
fmt.Println(f(3))

fmt.Printf("%*s</%s>\n",depth*2, " ", text) --> %*s 中的*号 输出带有可变数量空格的字符串,输出的宽度和字符串则由参数 depth*2 和 " "提供


[匿名函数]
匿名函数在函数内部使用
func squares() func() int{
var x int // 默认初始值 0
// 匿名函数 --> 闭包?
return func() int{
x++
return x*x
}
}

func main(){
f:= squares()
f() // 1 --> go 程序员通常把函数变量称为 闭包
f() // 4
f() // 9
f() // 16
}
x 的生命周期不是有函数的作用域决定的,而是由外面的闭包 f 决定的

隐藏的陷阱:--> 考察函数的延迟调用
var rndirs []func()
for _,dir := range tempDirs(){
os.MkdirAll(dir,0755)
rmdirs = append(rmdirs, func(){
os.RemoveAll(dir) // 这样写不正确,目前只是匿名函数放到一个slice 中,还没有执行,最终传入的 dir 参数会被替换掉,只剩最后一次的值了,所以不能这样写!
})
}

==> 应修改如下:
var rndirs []func()
for _,dir := range tempDirs(){
dirTemp:=dir // 应追加此行
os.MkdirAll(dir,0755)
rmdirs = append(rmdirs, func(){
os.RemoveAll(dirTemp)
})
}

[变长函数]
func sum(vals ...int) int {
total := 0
for _,val := range vals{
total += val
}

return total
}
sum(1,2,3,4) --> 10

values := []int{1,2,3,4}
sum(values...)


[延迟函数调用] defer 关键字
defer 语句经常使用于成对的操作,如:
open() / close()
connect() / close()
lock() / unlock()
即使再复杂的控制流,使用defer 语句也能够正确释放资源

注意点, defer 语句的地方是在成功获得资源后,也就是说, if err==nil 之后,才使用 defer 语句

defer 是在代码块生命周期结束后才去调用的,这点需要去注意

func double(x int)(ret int){
defer func(){fmt.Printf("double(%d) = %d\n",x,ret)}()
return x+x
}

double(4) --> "double(4)=8"

defer 注意点,如下函数:
for _,filename := range filenames{
f,err := os.Open(filename)
if err != nil{
return err
}

defer f.Close() // 注意: 可能会用尽文件描述符
// do other thing
...
}

在许多文件系统中,尤其是NFS,写错误往往不是立即返回而是推迟到文件关闭的时候,才把错误信息报给调用者,
关闭文件,使用 defer语法,往往无法检测到关闭操作结果,就会导致一些信息丢失,此时,可以先用
io.Copy 函数操作,判断错误,忽略掉io.Close()的错误,这等于没说,不知道书上这段话何意。奶奶的。

[异常]
异常发生时,goroutine中的所有延迟函数会执行
每一个 goroutine 都会在宕机的时候显示一个函数调用的栈跟踪消息
除非你能够提供有效的错误消息或者能够很快地检测出错误,否则在运行时检测断言条件就毫无意义

一些预期的错误,比如错误的输入、配置或者IO失败,这个时候最好使用错误值来区分,对一些调用者知道,不能输错的东西,如果错了,则直接使用 panic 发出异常
发生异常,程序退栈,runtime.Stack 及defer 延迟函数先执行后,再去进行退栈

[捕获异常、或者叫 恢复]
func Parse(in string)(s *Syntxt, err error){
defer func(){
if p := recover(); p != nil {
err = fmt.Errorf("internal error: %v", p)
}
}()
// ....
}

注意点: 其他包里的 panic 不要去 recover
谨慎使用 recover,否则会 导致资源泄漏或使失败的处理函数处于未定义的状态从而导致其他问题

 

posted @ 2020-09-24 11:28  雪域蓝心  阅读(154)  评论(0编辑  收藏  举报