最近因为实习原因要学习go语言,这里把语法之类的都进行简要的记录。
go对redis的操作参考 redisdoc.com
语法基础
(一)几种变量定义的方法
通常方法
var i int
fmt.Printf("i默认为:%v\n", i)//i=0
i = 6;
fmt.Println("i =", i)
var j int = 10
fmt.Println("j =", j)
根据值自行判断
var num = 6
fmt.Println("num =", num)
省略var
name := "HKC"
fmt.Println("name =", name)
查看变量类型
//只能在fmt.Printf()中使用%T
name := "HKC"
fmt.Printf("type(name) = %T", name)
(二)fmt包
Printf()
常用
%v 查看变量值
%T 查看变量数据类型
%b 查看对应二进制
%d 查看对应十进制
%o 查看对应八进制
%x 查看对应十六进制
(三)常量与iota
const(
a = "start" //iota = 0
b = iota //iota = 1
c = "hello" //iota = 2
d = 1.456 //iota = 3
e = iota //iota = 4
)
fmt.Println("a:", a) //a: start
fmt.Println("b:", b) //b: 1
fmt.Println("c:", c) //c: hello
fmt.Println("d:", d) //d: 1.456
fmt.Println("e:", e) //e: 4
(四)单元测试——testing
需要注意的两点
1.代码文件命名:必须要以“_test.go”结尾
2.函数命名形式必须为
//func TestXxxXxx(*testing T)
//举例:
//测试对象
func addUpper(n int) int{
ret := 0
for i:=1; i<=n; i++{
ret += i
}
return ret
}
//测试函数
func TestAddUpper(t *testing.T){
ret := addUpper(10)
if ret != 55 {
t.Fatalf("error")
}
t.Logf("正确")
}
3.运行命令
//在目录下运行
go test -v
//将自动运行所有名称以_test.go结尾的文件
4.测试单个文件
//在目录下运行
go test -v xxx_test.go xxx.go
5.测试单个方法
//在目录下运行
go test -v -test.run TestXxxXxx
(五)JSON化后写入文件与逆向操作
JSON化后写入文件
//import必要的包
import (
"encoding/json"
//用于读写文件
"io/ioutil"
)
实现如下
func (this *Monster) Store() bool {
//先序列化
data, err := json.Marshal(this)
//此时data为二进制串
if err != nil {
fmt.Println("marshal err =", err)
return false
}
//保存到文件
filePath := "./monster.ser"
//0666代表权限
//0 owner group other
//0 -rwx -rwx -rwx
//0 6 6 6
err = ioutil.WriteFile(filePath, data, 0666)
if err != nil {
fmt.Println("write file err =", err)
return false
}
return true
}
从文件读出序列后对byte串反序列化
func (this *Monster) ReStore() bool {
//1. 先从文件中,读取序列化的字符串
filePath := "./monster.ser"
data, err := ioutil.ReadFile(filePath)
if err != nil {
fmt.Println("ReadFile err =", err)
return false
}
//此时data为字符串
//2.对反序列化
err = json.Unmarshal(data, this)
//将data反序列化并存入this指针
if err != nil {
fmt.Println("UnMarshal err =", err)
return false
}
return true
}
(六)strconv
import(
"strconv"
)
func test(){
str := strconv.Itoa(100)
val := strconv.Atoi(str)
time.Sleep(time.Second)
}
(七)runtime
import (
"runtime"
"fmt"
)
func main(){
cpuNum := runtime.NumCPU()
fmt.Println("cpuNum=", cpuNum)
//可以自己设置使用多个cpu
runtime.GOMAXPROCS(cpuNum - 1)
fmt.Println("ok")
}
(八)channel
1.简单使用方法
package main
import (
"fmt"
)
func main(){
var intChan chan int
intChan = make(chan int, 3)
fmt.Println("intChan = %v, &intChan = %p", intChan, &intChan)
intChan<- 10
num := 20
intChan<- num
intChan<- 50
//此时不能再往管道中加入数据了,否则报错DeadLock
//intChan<- 90
fmt.Printf("len(intChan) = %v cap(intChan)=%v \n", len(intChan), cap(intChan))//3 3
//取数据操作
front := <-intChan
front = <-intChan
front = <-intChan
fmt.Printf("front = %v\n", front)
//此时队列已经空了,再从管道中拿数据出来报错DeadLock
//front = <-intChan
}
2.Channel数据类型
map类型
func main() {
//定义一个存放任意数据类型的管道 3个数据
//var mapChan chan map[string]string
mapChan := make(chan map[string]string, 3)
m1 := make(map[string]string, 10)
m1["city1"] = "西安"
m1["city2"] = "北京"
m2 := make(map[string]string, 10)
m2["historical1"] = "兵马俑"
m2["historical2"] = "故宫"
mapChan<- m1;
mapChan<- m2;
}
interface类型
package main
import (
"fmt"
)
type Cat struct {
Name string
Age int
}
func main() {
//定义一个存放任意数据类型的管道 3个数据
//var allChan chan interface{}
allChan := make(chan interface{}, 3)
allChan<- 10
allChan<- "tom jack"
cat := Cat{"小花猫", 4}
allChan<- cat
//我们希望获得到管道中的第三个元素,则先将前2个推出
<-allChan
<-allChan
// newCat := <-allChan //从管道中取出的Cat是什么?
// fmt.Printf("newCat=%T , newCat=%v\n", newCat, newCat)
// //下面的写法是错误的!编译不通过
// //error: newCat.Name undefined (type interface{} has no field or method Name)
// fmt.Printf("newCat.Name=%v", newCat.Name)
//使用类型断言
front := <-allChan //从管道中取出的Cat是什么?
fmt.Printf("front=%T , front=%v\n", front, front)
newCat := front.(Cat)//类型断言
//必须使用类型断言,否则这一句将报错
fmt.Printf("newCat.Name=%v\n", newCat.Name)
}
Channel的close
package main
import (
"fmt"
)
func main() {
intChan := make(chan int, 3)
intChan<- 100
intChan<- 200
close(intChan) // close
//这时不能够再写入数到channel
//intChan<- 300
fmt.Println("already closed")
//当管道关闭后,读取数据是可以的
n1 := <-intChan
fmt.Println("n1=", n1)
//遍历管道
intChan2 := make(chan int, 100)
for i := 0; i < 100; i++ {
intChan2<- i * 2 //放入100个数据到管道
}
//遍历管道不能使用普通的 for 循环
// for i := 0; i < len(intChan2); i++ {}
//在遍历时,如果channel没有关闭,则会出现deadlock的错误
//在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历
close(intChan2)//关闭数据一般由发送方发起
for v := range intChan2 {
fmt.Println("v=", v)
}
}
Channel中Select的用法
有时我们不确定是否要关闭一个管道,但是有需要从中读取数据。此时可以用Select避免DeadLock。
package main
import (
"fmt"
"time"
)
func main() {
//使用select可以解决从管道取数据的阻塞问题
//1.定义一个管道 10个数据int
intChan := make(chan int, 10)
for i := 0; i < 10; i++ {
intChan<- i
}
//2.定义一个管道 5个数据string
stringChan := make(chan string, 5)
for i := 0; i < 5; i++ {
stringChan <- "hello" + fmt.Sprintf("%d", i)
}
//传统的方法在遍历管道时,如果不关闭会阻塞而导致 deadlock
//问题,在实际开发中,可能我们不好确定什么关闭该管道.
//可以使用select 方式可以解决
//label:
for {
select {
//注意: 这里,如果intChan一直没有关闭,不会一直阻塞而deadlock
//,会自动到下一个case匹配
case v := <-intChan :
fmt.Printf("从intChan读取的数据%d\n", v)
time.Sleep(time.Second)
case v := <-stringChan :
fmt.Printf("从stringChan读取的数据%s\n", v)
time.Sleep(time.Second)
default :
fmt.Printf("都取不到了,不玩了, 程序员可以加入逻辑\n")
time.Sleep(time.Second)
return
//break label
}
}
}
单向Channel及其应用场景
package main
import (
"fmt"
)
//单向channel一般配合函数使用
//参数列表定义为只可写channel
func send(ch chan<- int){
for i:=1; i<=500; i++{
ch<- (i*3-19)/2
}
close(ch)
}
//参数列表定义为从一个只可读channel读出数据后
//存入一个只可写channel
func recv(ch <-chan int, recvCh chan<- int, exitCh chan bool){
for{
num, ok := <-ch
if !ok {
break
}
recvCh <- num
}
fmt.Println("协程退出!")
exitCh<- true
}
func main(){
dataCh := make(chan int, 1000)
exitCh := make(chan bool, 8)
recvCh := make(chan int, 1000)
go send(dataCh)
for i:=0; i!=8; i++{
go recv(dataCh, recvCh, exitCh)
}
go func(){
for i:=0; i!=8; i++{
<-exitCh
}
close(recvCh)
}()
for{
num, ok := <-recvCh
if !ok{
break
}
fmt.Println("收到数据:", num)
}
fmt.Println("主线程退出")
}
gorountine和channel协同工作案例
package main
import (
"fmt"
"time"
)
func writeData(intChan chan int){
for i:=1; i<=50; i++{
intChan<- i
fmt.Println("write data:", i)
time.Sleep(time.Second)
}
close(intChan)
}
func readData(intChan chan int, exitChan chan bool){
for{
num, ok := <-intChan
if !ok{
break;
}
time.Sleep(time.Second)
fmt.Println("got value: ", num)
}
exitChan<- true
close(exitChan)
}
func main(){
intChan := make(chan int, 10)
exitChan := make(chan bool, 1)
go writeData(intChan)
go readData(intChan, exitChan)
//为了使主线程在两个下协程执行完毕后再退出
for{
_, ok := <-exitChan
if !ok{
//执行到这里说明readData()函数中将exitChan关闭了
break
}
}
}
(九)reflect
类似于指针的应用场景
package main
import (
"reflect"
"fmt"
)
//通过反射,修改,
// num int 的值
// 修改 student的值
func reflect01(b interface{}) {
//2. 获取到 reflect.Value
rVal := reflect.ValueOf(b)
// 看看 rVal的Kind是
fmt.Printf("rVal kind=%v\n", rVal.Kind())//kind = ptr
//3. rVal
//Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装
rVal.Elem().SetInt(20)
}
func main() {
var num int = 10
reflect01(&num)
fmt.Println("num=", num) // 20
//你可以这样理解rVal.Elem()
// num := 9
// ptr *int = &num
// num2 := *ptr //=== 类似 rVal.Elem()
}