基础知识
- 驼峰命名法
骆驼式命名法就是当变量名或函数名是由一个或多个单词连结在一起,而构成的唯一识别字时,第一个单词以小写字母开始;从第二个单词开始以后的每个单词的首字母都采用大写字母,例如:myFirstName、myLastName,这样的变量名看上去就像骆驼峰一样此起彼伏,故得名。
通常是动词和介词加上名词,如scanWord - 类型打印方法
//该方法可以打印出参数(var)的类型
fmt.Println(reflect.TypeOf(var))
学习过程:https://studygolang.com/articles/2644
个人感觉比较重要的占位符:- %d 打印十进制整形数
- %f 打印浮点型数,值得注意的点(对于 %g/%G 而言,精度为所有数字的总数,例如:123.45,%.4g 会打印123.5,(而 %6.2f 会打印123.45)。)
- %s 打印字符串
- %v 所有类型皆可打印
- %T 打印当前参数类型
- 几种类型转换
Go强制要求显示类型转换,所以我们能够确定语句表达式的明确含义。 - 字典(哈希表)操作
- 基础知识
学习过程:https://www.jb51.net/article/106596.htm
字典和切片相似,一旦容量不够,它会自动扩容
在创建时预先准备好足够的空间有助于提高性能,减少扩张时的内存分配和重新哈希操作
如果某个任务正在对字典进行写操作,那么其他任务就不能对该字典执行并发操作,否则会导致进程崩溃 - 新增
- 基础知识
map1 := make(map[string]string, 5)
map2 := make(map[string]string)
map3 := map[string]string{}
map4 := map[string]string{"a": "1", "b": "2", "c": "3"}
上图的四个方法是新增一个字典的方法,key值包含在【】中后面的是value
-
增加/删除/修改键值
-
遍历
for key,value:=range map1{
fmt.Printf("key=%d",key)
fmt.Printf("value=%s\n",value)
}
- 判断key值是否存在
value,exist:=map["test"]//判断“test”键是否存在,value为test对应的数值
- 匿名(闭包)函数
func main() {
i := 0
str := "mike"
//方式1
f1 := func() { //匿名函数,无参无返回值
//引用到函数外的变量
fmt.Printf("方式1:i = %d, str = %s\n", i, str)
}
f1() //函数调用
//方式1的另一种方式
type FuncType func() //声明函数类型, 无参无返回值
var f2 FuncType = f1
f2() //函数调用
//方式2
var f3 FuncType = func() {
fmt.Printf("方式2:i = %d, str = %s\n", i, str)
}
f3() //函数调用
//方式3
func() { //匿名函数,无参无返回值
fmt.Printf("方式3:i = %d, str = %s\n", i, str)
}() //别忘了后面的(), ()的作用是,此处直接调用此匿名函数
//方式4, 匿名函数,有参有返回值
v := func(a, b int) (result int) {
result = a + b
return
}(1, 1) //别忘了后面的(1, 1), (1, 1)的作用是,此处直接调用此匿名函数, 并传参
fmt.Println("v = ", v)
}
————————————————
版权声明:本文为CSDN博主「Mike江」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tennysonsky/article/details/78147905
-
错误处理
最基础的错误处理是抛出err todo -
随机数生成
背下代码就行异曲同工,使用时间种子定义确保生成的不同
import (
"fmt"
"math/rand"
"time"
)
func main() {
var arr [5]int
rand.Seed(time.Now().Unix()) //产生Seed
rand.Seed(time.Now().UnixNano())//两种方法都可以
for i:= 0;i<len(arr) ;i++ {
arr[i] = rand.Intn(100) //生成[0,100)的随机数
}
fmt.Println(arr)
}
两个生成方法的区别不大
- go的包管理
首先go是编译形的语言,全部放置到src文件夹下,因为依赖也算源代码,之需要二进制文件即可运行 - context(重要)//还需要学习!!!!!!
学习过程:https://www.jianshu.com/p/6def5063c1eb
context.Context
是一个接口,该接口定义了四个需要实现的方法。具体签名如下:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
- Kafka消息队列
存储步骤:
- Duration类型转换
值得注意的是不能使用int在sleep函数中使用 - 原子操作
原子操作就是不可中断的操作,外界是看不到原子操作的中间状态,要么看到原子操作已经完成,要么看到原子操作已经结束。在某个值的原子操作执行的过程中,CPU绝对不会再去执行其他针对该值的操作,那么其他操作也是原子操作。
Go语言中提供的原子操作都是非侵入式的,在标准库代码包sync/atomic中提供了相关的原子函数。
func main() {
var counter int64 = 23
atomic.AddInt64(&counter,-3)
fmt.Println(counter)
}
---output---
20
一些基础的操作。比如Store,Load,操作如下,需先定义atomic.Value变量名
func main() {
var Atomicvalue atomic.Value
Atomicvalue.Store([]int{1,2,3,4,5})
anotherStore(Atomicvalue)
fmt.Println("main: ",Atomicvalue)
}
func anotherStore(Atomicvalue atomic.Value) {
Atomicvalue.Store([]int{6,7,8,9,10})
fmt.Println("anotherStore: ",Atomicvalue)
}
---output---
anotherStore: {[6 7 8 9 10]}
main: {[1 2 3 4 5]}
- golang操作mysql
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"os"
)
var db *sql.DB
type info struct {//注意大写
Id int `db:"id"`
Name string `db:"name"`
}
func initDB()error{
var err error
db, err = sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/learn_mysql?charset=utf8");
//上面的语句代表了连接的几点,首先mysql:数据库驱动;其次root:用户名;123456:密码;learn_mysql:库名
if err!=nil{
return err
}
err=db.Ping()
fmt.Println("connect success")
if err!=nil{
return err
}
db.SetMaxOpenConns(10)//设置最大的连接池中的数
return err
}
func insertDB(name string){
sqlStr:=`insert into class(name) values (?)`
ret,err:=db.Exec(sqlStr,name)
if err!=nil{
fmt.Println("insert err: ",err)
return
}
id,err:=ret.LastInsertId()//插入的位置
effectRow,err:=ret.RowsAffected()//影响的行数
if err!=nil{
fmt.Println("insert err: ",err)
return
}
fmt.Printf("insert in id:%d,effect row:%d\n",id,effectRow)
}
func updateDB(name string,id int){
sqlStr:=`update class set name=? where id =?`
ret,err:=db.Exec(sqlStr,name,id)//多个参数的输入,?为占位符
if err!=nil{
fmt.Println("update err: ",err)
return
}
ids,err:=ret.LastInsertId()
effectRow,err:=ret.RowsAffected()
if err!=nil{
fmt.Println("update err: ",err)
return
}
fmt.Printf("update in id:%d,effect row:%d\n",ids,effectRow)
}
func deleteDB(name string){
sqlStr:=`delete from class where name=?`
ret,err:=db.Exec(sqlStr,name)//多个参数的输入,?为占位符
if err!=nil{
fmt.Println("delete err: ",err)
return
}
ids,err:=ret.LastInsertId()
effectRow,err:=ret.RowsAffected()
if err!=nil{
fmt.Println("delete err: ",err)
return
}
fmt.Printf("delete in id:%d,effect row:%d\n",ids,effectRow)
}
func queryOneRowDB (id int){
sqlColm:=`select * from class where id=?`
var a info
err:=db.QueryRow(sqlColm,id).Scan(&a.Id,&a.Name)
if err!=nil{
fmt.Println("query err: ",err)
}
fmt.Println("one :",a)
}
func queryMoreRowDB (id int){
sqlColm:=`select * from class where id>?`
rows,err:=db.Query(sqlColm,id)
if err!=nil{
fmt.Println("query more err: ",err)
}
defer func(){//一定要记得关闭
err:=rows.Close()
if err!=nil{
fmt.Println("close err: ",err)
}
}()
for rows.Next(){//能够取值就读取下一行
var s info
err=rows.Scan(&s.Id,&s.Name)
fmt.Println("more: ",s)
}
}
func prepareDB (){
sqlStr:=`insert into class(name) values (?)`
stm,err:=db.Prepare(sqlStr)
if err!=nil{
fmt.Println("prepare insert err: ",err)
return
}
defer func() {
err:=stm.Close()
if err!=nil{
fmt.Println("close err: ",err)
}
}()
namelist:=[]string{"xyp","xy","szp","dzx"}
for _,value:=range namelist{
_,err:=stm.Exec(value)
if err!=nil{
fmt.Println("prepare insert err: ",err)
return
}
}
}
func transactionDemo() {
tx, err := db.Begin() // 开启事务
if err != nil {
if tx != nil {
tx.Rollback() // 回滚
}
fmt.Printf("begin trans failed, err:%v\n", err)
return
}
sqlStr1 := "insert into class(name) values (?)"
_, err = tx.Exec(sqlStr1, "xixixi")
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec sql1 failed, err:%v\n", err)
return
}
_, err = tx.Exec(sqlStr1, "xixixi2")
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec sql1 failed, err:%v\n", err)
return
}
err = tx.Commit() // 提交事务
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("commit failed, err:%v\n", err)
return
}
fmt.Println("exec trans success!")
}
func main() {
err:=initDB()//自定义初始化数据库函数
if err!=nil{
fmt.Println("init error: ",err)
os.Exit(-1)//直接退出
}
queryOneRowDB(3 )//获取单行
queryMoreRowDB(0)//获取多行
insertDB("xy")//插入
updateDB("li",5)//更新
deleteDB("szp")//删除
prepareDB()//预操作,实现多个语句同时插入
transactionDemo()//对于一个事物的操作,分为begin,commit,rollback
}
指针
学习过程:https://blog.csdn.net/qq_36431213/article/details/82967982
通过&【参数】来获取参数的地址
通过*【参数】来打印出指针变量的具体数值
如果不想通过下图的方法分配地址需要通过【指针类型参数】=new(int) 这种方式来分配地址
并发的使用(进程,线程,协程)
- 并发:同一时间段内执行多个任务
- 并行:同一时刻进行多个任务
- golang中是goroutine实现并发(通过runtime调度),线程是通过操作系统调度完成
- goroutine是用户态的线程,channel能够实现多个goroutine之间的通信
- 在golang中通过goroutine实现并发只需要在函数前加一个go参数就可以实现并发
一段最简单的并发goroutine线程
func testRoutine (i int){
fmt.Println("输出",i)
}
func main() {
for i:=0;i<100;i++{//开启一百个goroutine线程
go testRoutine(i)
}
time.Sleep(time.Second*1)
fmt.Println("main")
}
注意此处画圈的地方,为什么会出现此情况呢,因为启动goroutine需要时间,但f主线程的for循环不会等待goroutine线程启动,i会持续递增,当goroutine启动起来后才会再在外部取值,导致取得多次相同的i
- 使用自带的sync包,能够优雅的结束多线程
func wgRoutine(i int){
defer wg.Done()//退出时执行done,进行计数器减一
fmt.Println("test",i)
}
var wg sync.WaitGroup//初始化计数器,全局变量
func main() {
for i:=0;i<100;i++{//开启一百个goroutine线程
wg.Add(1)//计数器加一
go wgRoutine(i)
}
wg.Wait()//等待计数器减为零
fmt.Println("main")
- runtime包可以对运行的核进行一些更改,默认跑满全部的核
- 需要更系统的学习下GMP(调度模型)面试常问
- ==M:N == 把m个goroutine分配到n个操作系统线程去执行
- 通道必须初始化才能使用(make可以初始化map,slice,channel)
var testChannel chan int //定义一个int类型的channel
testChannel=make(chan int)//初始化一个地址
通道的操作
学习过程:https://www.jianshu.com/p/24ede9e90490
channel的定义:channel 提供了一种通信机制,通过它,一个 goroutine 可以想另一 goroutine 发送消息。channel 本身还需关联了一个类型,也就是 channel 可以发送数据的类型。例如: 发送 int 类型消息的 channel 写作 chan int 。
通道是一个指针
- 发送
ch<-10 //箭头代表数据流动的方向10流到了ch
- 接受
x:=<-ch1 //将通道中的值赋给x
<-ch1 //不保存通道中的数值直接将数值扔掉
- 关闭
close(ch1)
- 通道的一些基础操作
ch := make(chan int, 10)
ch <- 11
ch <- 12
close(ch)//关闭后仍可以读取数据
for x := range ch {//遍历
fmt.Println(x)
}
x, ok := <- ch//判断是否通道是否开启
fmt.Println(x, ok)
-----
output:
11
12
0 false
通道的类型有两种:不带缓存的 channel 和带缓存的 channel
- 不带缓存的channel:从无缓存的 channel 中读取消息会阻塞,直到有 goroutine 向该 channel 中发送消息;同理,向无缓存的 channel 中发送消息也会阻塞,直到有 goroutine 从 channel 中读取消息。
testChannel=make(chan int)
wg.Add(1)
go func() {
defer wg.Done()
a:=<-testChannel
fmt.Println("a is ",a)
}()
testChannel<-3//如果不往通道中写入则在上方goroutine线程则会一直等待输入
wg.Wait()
- 有缓存的 channel
有缓存的 channel 的声明方式为指定 make 函数的第二个参数,该参数为 channel 缓存的容量
ch := make(chan int, 10)
有缓存的 channel 类似一个阻塞队列(采用环形数组实现)。当缓存未满时,向 channel 中发送消息时不会阻塞,当缓存满时,发送操作将被阻塞,直到有其他 goroutine 从中读取消息;相应的,当 channel 中消息不为空时,读取消息不会出现阻塞,当 channel 为空时,读取操作会造成阻塞,直到有 goroutine 向 channel 中写入消息。
-
单向通道
chan<- int 表示一个只可写入的 channel,<-chan int 表示一个只可读取的 channel。上面这个函数约定了 foo 内只能从向 ch 中写入数据,返回只一个只能读取的 channel,虽然使用普通的 channel 也没有问题,但这样在方法声明时约定可以防止 channel 被滥用,这种预防机制发生再编译期间。 -
select语句
每次选择一个能走通的case,全部走不通再走default- select 可以同时监听多个 channel 的写入或读取 - 执行 select 时,若只有一个 case 通过(不阻塞),则执行这个case 块 - 若有多个 case 通过,则随机挑选一个 case 执行 - 若所有 case 均阻塞,且定义了 default 模块,则执行default 模块。 - 若未定义 default 模块,则 select 语句阻塞,直到有 case 被唤醒。 - 使用 break 会跳出 select 块。
ch3:=make(chan int ,1)
for i:=0;i<10;i++{
select {
case ch3<-i:
println("put ",i)
case x:=<-ch3:
println("selct",x)
default:
println("test")
}
}
----------output----------
put 0
selct 0
put 2
selct 2
put 4
selct 4
put 6
selct 6
put 8
selct 8
基于指针对象的方法
golang中对象的定义和使用方法
对于指针对象的使用
学习一个热门框架
grpc
Gin
学习过程:https://www.liwenzhou.com/
学习视频:https://space.bilibili.com/4638193/video
创建项目时选择gomoudle,可以选择代理https://goproxy.cn
——————————————分割线———————————————
go自带的http包是net/http
使用net/http创建的一个很简单的网站,可以写一些html在上面
这是最开始的时候没有进行前后端分离的样子
——————————分割线————————————
gin中文文档:https://gin-gonic.com/zh-cn/docs/
Restful风格api:http://www.ruanyifeng.com/blog/2018/10/restful-api-best-practices.html
很基础的一个返回json串的后端编写//一般使用postman进行测试,因为浏览器不支持put,delete的请求
学习一个开源项目
landeng(滑稽)
正在学习gorpc https://github.com/lubanproj/gorpc,走过路过点个红心,目前是contributor