Go 基础入门
基础语法
变量的定义
package main
// 快速声明多个全局变量
var (
a = 1
b = 2
)
func main() {
//变量使用注意事项
//1.变量定义方式先定义类型之后赋值
var i int
i = 10
println("i=", i)
//2.直接赋值自动类型判断
var j = "kiri"
println("hello", j)
//省略var赋值
k := "tom"
println("你的名字是什么", k)
// 多变量声明 var q,w,e=1,2,3
q, w, e := 1, 2, 3
println("3个数", q, w, e)
println(a, b)
}
变量的类型
基本类型
整型 int int8(-128-127) int32 int64
布尔 bool
字符类型 byte 类似 char
字符串 string
- 复杂类型:
指针 pointer
结构体 struct
接口 interface
管道 Channel
数组 和 切片slice
集合 map
- 查看类型
//格式化输出 查看变量的类型
y := 1
fmt.Printf("y 的类型是 %T 变量的字节数%d", y, unsafe.Sizeof(y))
-
注意事项
格式化输出 查看变量的类型,float默认赋值类型是float64,保证float小数点精度
没有char 类型 使用 byte 类型 0-255 超过255 的字符用 int 类型
字符类型就是将字符转化为 对应的码值之后转化为二进制
var c byte = 'c'
fmt.Printf("c 的类型是 %T 打印出来是%c", c, c)
// 可以输入一个int 输出一个字符,要用%c 打印
var c2 int = 22296
fmt.Printf("c2 的值是 %c", c2)
-
布尔类型只能用true 和 false 表示
-
字符串类型是使用utf-8 来识别 unicode 编码
-
go中的字符串是不可变的,一旦赋值就不能变
-
字符串的赋值方式双引号 "" 可以识别里面的转译符 可以是 `` 反引号
-
多行字符拼接的时候要把加号放上面,拼接部分放下面
-
golang的类型转换,只能显式转化不能自动转化
-
golang的类型只是改变数值,并不会改变类型
基本类型转化
var f2 float32 = 12.72
var i2 int = int(f2)
fmt.Printf("i2的类型%T,值是%d f2的类型%T,值是%f", i2, i2, f2, f2)
//i2的类型int,值是12 f2的类型float32,值是12.720000
- 当大数值存放到小的类型会出现溢出现象,但是不会出现报错
- 左右赋值类型不一样会出现红线报错
var n1 int32 =12
var n2 int64
var n3 int8
n2=n1+20 // 右边是int32 左边是64位不统一
n3=n1+20 // 右边是int32 左边是8位不统一
// 进行类型转化就可以
n2 = int64(n1) + 20
n3 = int8(n1) + 20
println(n1, n2, n3)
}
- 基本数据类型和string 类型的转化,类型转化要记住相应的占位符,虽然int 和int 64 在 64位机都是64bit位 但是不是同一个类型
// 将int转化为string类型
str = fmt.Sprintf("%d", num1)
fmt.Printf("str type %T str=%v\n", str, str)
// 将float转化string类型
str = fmt.Sprintf("%f", num2)
fmt.Printf("str type %T str=%v\n", str, str)
// 将bool 转化维string类型string
str = fmt.Sprintf("%t", b)
fmt.Printf("str type %T str=%v", str, str)
//str type string str=99
//str type string str=23.456000
//str type string str=true
// 使用strcov 函数
var num3 int = 99
var b2 bool = false
var num4 float64 = 12.111
fmt.Printf("num3 的类型是%T,变量的字节数%d\n", num3, unsafe.Sizeof(num3))
// base 表示进制 前面第一个数是int64
str = strconv.FormatInt(int64(num3), 10)
fmt.Printf("str type %T str=%v\n", str, str)
str = strconv.FormatBool(b2)
fmt.Printf("str type %T str=%v\n", str, str)
// prec 表示小数位 bitSize
str = strconv.FormatFloat(num4, 'f', 10, 64)
fmt.Printf("str type %T str=%v\n", str, str)
//Itoa 的使用这里会带引号
var num5 int64 = 2324
str = strconv.Itoa(int(num5))
fmt.Printf("str type %T str=%q\n", str, str)
string 类型到基本类型,不能转化的会自动到默认值
// 实现string 到基本类型
var str2 string = "true"
var b4 bool
b4, _ = strconv.ParseBool(str2)
fmt.Printf("b type %T value of b=%v\n", b4, b4)
// 实现string 到int
var str3 string = "12"
var n5 int64
n5, _ = strconv.ParseInt(str3, 10, 64)
fmt.Printf("n5 type %T value of n5 =%v\n", n5, n5)
// 实现string 到 float
var str4 string = "123.123"
var f4 float64
f4, _ = strconv.ParseFloat(str4, 64)
fmt.Printf("f1 type %T value of f1 =%v\n", f4, f4)
指针
- 基本类型是值类型
- 指针是一种变量,该变量存放其他变量的地址 &变量 可以获得变量地址 指针变量只存放其他变量的内存地址
- 指针变量的 类型与存放的其他变量的类型有关 *其他变量的类型
- 取出指针变量指向的变量的值 * 指针变量
- 通过指针变量修改指向的变量 *指针=新值
var i5 int = 10
fmt.Println("i5 的地址=", &i5)
var i6 *int = &i5
fmt.Println("i5 的地址=", i6)
var i7 int = *i6
fmt.Println("i5 的值=", i7)
// p1 修改 i8
var i8 int = 12
fmt.Println("i8 的地址和值分别是:", &i8, i8)
var p1 *int = &i8
*p1 = 100
fmt.Println("i8 的地址和值分别是:", &i8, i8)
值类型和引用类型
- 常用的值类型: string ,int ,bool,struct,数组,值变量大部分情况在栈里面
- 引用类型:slice 指针类型,map 管道 interface ,引用变量大部分情况下在堆区
标识符的命名规范
- 区分大小写
- 不要用 下滑线作为变量,只能用在占位
- 不要用保留字
包名的规范
-
包名与外面文件夹的名字保持一致
package main 的名字最后和这个一致
运算符
golang 里面的运算符与其他语言有一些不同
fmt.Println(10 / 4) //运算符如果计算的都是整型那么,那么输出的数会截掉小数
fmt.Println(10.0 / 4) // 有小数参与才会返回小数
fmt.Println("-10%3=", -10%3) // 取模运算符合下面公式a%b =a-a/b*b
number1 := 1
number1++ //自增只能独立使用,不能用在表达式里面,不能赋值和比较,只有后++ --
fmt.Println(number1)
&& || 可以实现逻辑判断也可以是短路功能
无第三个数交换
a1 = a1 + b1
b1 = a1 - b1
a1 = a1 - b1
fmt.Printf("交换后的a1=%d和b1=%d", a1, b1)
用户输入
//单行用户输入
var username string
fmt.Printf("输入你的姓名:")
fmt.Scanln(&username)
fmt.Println("姓名是:", username)
//一起输入
var name string
var age int
var salary float32
var isStudent bool
fmt.Printf("请输入你的姓名,年龄,薪水,是否为学生,使用空格隔开")
fmt.Scanf("%s %d %f %t", &name, &age, &salary, &isStudent)
fmt.Printf("姓名%v ,年龄%v ,薪资%v 是否为学生%v", name, age, salary, isStudent)
选择语句
//else 要放在前面的大括号后面
if userage > 18 {
fmt.Println("你已经成年了!")
} else {
fmt.Println("你还未成年哦!")
}
多个选择
if score >= 80 {
println("你的成绩是优秀!")
} else if score < 80 && score >= 60 {
println("你的成绩是中等!")
} else {
println("你的成绩是不及格!")
}
case 多分支
var key byte
fmt.Println("请输入你的 a,b,c")
fmt.Scanf("%c", &key)
switch key {
case 'a':
println("a输入成功")
break
case 'b':
println("b输入成功")
break
case 'c':
println("c输入成功")
break
default:
println("输入格式错误!")
}
for 循环用法
//for 循环的使用
for i := 1; i < 5; i++ {
fmt.Println("可以下班了!")
}
//go 里面没有while 和do while 用for 和break
k := 1
for {
k++
fmt.Println("现在的数是", k)
if k > 3 {
fmt.Println("死循环结束了~")
break
}
}
包的使用
pakage 包名 定义文件所在的包
import "包名" 表示 引入包 包名在从根路径里面开始
定义外部可以调用的函数时要大写函数开头字母
import 别名 "包名" 可以通过包名调用
相同的包下面不可使用相同的函数名
main 包只能一个,最后将main包变为可执行文件
函数类型
-
函数是一种类型可以赋值,函数的赋值可以作为函数的实参,可以多个返回值,返回值可以设置别名
-
函数可以传递值也可以传递地址 引用类型就是传递地址
-
可以设置可变参数 如... arg int 作为形参 可以通过 arg【i】进行遍历 arg 只能放最后面
init 函数 ,在main 之前运行
func init(){
}

函数的中init的执行顺序
匿名函数的使用
函数不需要命名可以,如果将匿名函数赋值给全局变量,就是全局匿名函数
n1 := 20
n2 := 30
//得到匿名函数的赋值
res := func(n1 int, n2 int) int {
return n1 + n2
}(n1, n2)
// 将匿名函数赋值给其中一个变量
a := func(n1 int, n2 int) int {
return n1 * n2
}
fmt.Printf("a的类型%T\n", a)
fmt.Println(res)
// 全局匿名函数
var (
Fun1 = func(n1 int, n2 int) int {
return n1 + n2
}
)
闭包
函数及其引用环境组成的实体,相对于将普通函数变为类一样可以添加属性,减少初始属性赋值带来的冗余
在下面的案例中返回的是一个匿名函数,由于匿名函数使用了外部的变量,因此 i 不会被清除,会跟着匿名函数,类似于匿名函数的的一个字段n只会初始化一次
多次调用就会不断赋值
func UppAdder() func(int) int {
var i int = 10
return func(x int) int {
i = i + x
return i
}
}
func main{
f := UppAdder()
fmt.Println(f(1))//1
}
func main(){
str1 := ".jpg"
str2 := "pic"
f := makesufiix(str1)
fmt.Println(f(str2))
}
// 添加到一个补全后缀名
func makesufiix(suffix string) func(string) string {
return func(sub string) string {
if !strings.HasSuffix(sub, suffix) {
sub += suffix
}
return sub
}
}
延时调用
defer 放在函数里面,延时执行单独压入一个defer 栈中,只会影响函数内执行顺序,同时会将里面值进行拷贝,即使里面的值发生了变化还是原来的值
defer 一般用来关闭连接 和关闭资源
defer fmt.Printf("输出n1 %v", n1)
defer fmt.Printf("输出n2 %v", n2)
fmt.Println("求和test", n1+n2)
求和test 3 输出n2 2输出n1 1
函数作用域
定义在函数里面是局部变量
定义在函数是函数外面就是,整个文件的全局变量,首字母大写就可以被其他的包调用
for if 里面定义的自增和自减的变量只能在for/if 里面使用
变量的调用按照就近的原则
函数外是不能进行赋值语句的
常用的字符串处理
1求字符长度,多用于数组长度和字符等
str := "1123"
fmt.Println(len(str))
2 有中文的字符串变切片,遍历数组
str2 := "123北京"
r := []rune(str2)
for i := 0; i < len(r); i++ {
fmt.Printf("%c\n", r[i])
}
3 字符串转Int
n, _ := strconv.Atoi("13")
fmt.Println(n)
// 需要错误处理
n, err := strconv.Atoi("13LIU")
if err != nil {
fmt.Println("转化错误", err)
} else {
fmt.Println("输出", n)
}
4 整形转字符串
str := strconv.Itoa(23)
fmt.Println(str)
5 string 转化为 []byte
var bytes = []byte("hello go")
fmt.Println("test", bytes)
6 将bytes 数组转化
var str = string([]byte{104, 101, 108, 108, 111, 32, 103, 111})
fmt.Println(str)
7 进制转化
var str = strconv.FormatInt(123, 2)
fmt.Printf("123 的二进制%v\n", str)
var str2 = strconv.FormatInt(123, 8)
fmt.Printf("123 的8进制%v\n", str2)
var str3 = strconv.FormatInt(123, 16)
fmt.Printf("123 的16进制%v\n", str3)
8 判断字符串是子串
b := strings.Contains("hello", "llo")
fmt.Println(b)
9 子串重复了几次
b := strings.Contains("hello", "llo")
fmt.Println(b)
10 字符串大小写比较
常用 == 的大小写比较是分辨大小 要不分大小写 就需要使用
b := strings.EqualFold("ABC", "abc")
fmt.Println(b)
11 返回子串在字符串中第一次出现的index,没有就返回就是-1
i := strings.Index("HELLO", "p")
fmt.Println(i)
12 返回子串最后出现在字符串的index
i := strings.LastIndex("iindex", "i")
fmt.Println(i)
13 字串替代
str := strings.Replace("golang is best 我要学 golang", "golang", "go 语言", 2) 最后的n表示替换个数, -1 表示全部 替换
fmt.Println("打印string:", str)
14 字符切割为字符数组
strArr := strings.Split("123-233-123", "-")
fmt.Println(strArr)
15 字符串大小写
str := strings.ToUpper("baba")
println(str)
str2 := strings.ToLower("CCSa")
print(str2)
16 去除两边的空格
str2 := strings.TrimSpace(" string ")
println(str2)
17 判断是有某些字段开头
str := strings.HasPrefix("ftp:2332", "ftp")
println(str)
18 是否有某些结尾
str := strings.HasSuffix("www.swe.com", "com")
println(str)
时间处理
now := time.Now()
fmt.Printf(" %v \n", now)
fmt.Printf("%v \n", now.Year())
fmt.Printf("%v \n", now.YearDay())
fmt.Printf("%v \n", now.Month())
fmt.Printf("%v \n", now.Day())
fmt.Printf("%v \n", now.Hour())
fmt.Printf("%v \n", now.Minute())
fmt.Printf("%v \n", now.Second())
//格式化
formattedTime := now.Format("2006-01-02 15:04:05")
//打印时间戳
println(now.Unix())
使用time 里面的固定参数 time.second 表示一秒,millsecond
i := 0
for {
i++
//time.Sleep(time.Second)
//毫秒
time.Sleep(time.Millisecond * 1000)
fmt.Println(i)
if i >= 10 {
break
}
}
// 时间戳的程序计时作用
n1 := time.Now().Unix()
for i := 1; i < 100000; i++ {
println(i)
}
n2 := time.Now().Unix()
fmt.Println(n2 - n1)
new 和 make
b:= new(类型) 创建一个新的类型指针 比如 new(int)可以创建一个 int 指针 b 存放的是一个地址 *b 可以取出地址里的值
make 是用于定义引用类型的内存
错误处理
在没有错误处理之前出现程序错误就会 panic
panic: runtime error: integer divide by zero
goroutine 1 [running]: main.test(...)
现在使用defer 和 recover 进行错误处理
n1 := 1
n2 := 0
test(n1, n2)
func test(n1 int, n2 int) {
defer func() {
err := recover()
if err != nil {
fmt.Println("err", err)
}
}()
println(n1 / n2)
}
自定义错误
自定义错误 使用panic 函数报错使用 errors.New 定义错误
str := "hell"
res := readfile(str)
if res != nil {
panic(res)
}
println("test")
func readfile(name string) (err error) {
if name == "hello" {
return nil
} else {
return errors.New("有一个输入错误")
}
}
数组
在go 里面 数组是值类型,声明的的时候是要固定长度的
var arr [2]int// 声明数组
arr[0] = 1 // 数组元素赋值
arr[1] = 1
sum := 0
// var arr = [...]int{1, 2, 3, 1} 直接声明赋值
for i := 0; i < len(arr); i++ {
println(arr[i])
sum += arr[i]
}
println("总和为 ", sum)
数组的遍历 for range
for index, value := range arr {
fmt.Printf("i=%v v=%v\n ", index, value)
}
go 是一个值作为实参和其他值赋值的时候会重新拷贝一份
要改变原来的值需要传入数组地址进行修改
changearr(&arr)
func changearr(arr *[4]int) {
(*arr)[0] = 0
}
反转数组
func reverarr(arrs *[5]int) {
for i, j := 0, len(arrs)-1; i < j; i, j = i+1, j-1 {
(*arrs)[i], (*arrs)[j] = (*arrs)[j], (*arrs)[i]
}
}
二维数组
// 数组声明
var arr2 [2][2]int
arr2[0][0] = 1
// 声明赋值var arr3 = [2][2]int{{1, 0}, {2, 2}}
arr4 := [2][2]int{{1, 2}, {2, 3}}
// 遍历
for _, v := range arr4 {
for _, v2 := range v {
println(v2)
}
}
切片
切片的引用类型,切片用于创建一个关于数组的部分引用,如果是自己创建的就是建立一个不可见的数组.len方法,遍历方法和数组一致,切片可以再次切片,依然是引用类型
- 切片里内存结构包含 第一数指向的地址,切片长度,切片的容量(一个动态变化的值)
- 切片还可以二次切片,依然是引用类型
- 切片可以添加使用 append 相当于创建一个新的数组,建立对应的切片,这个切片可以赋值给其他的变量也可以给原变量
- 拷贝必须是两个切片 被拷贝的放在前面,拷贝后的放在后面,空间不够会自动填满不会扩容
- 用数组生成的切片,如果切片发生了改变,就会改变原数组
方法一
var arr = [4]int{1, 2, 3, 4}
slice := arr[1:3]
for _, v := range slice {
println(v)
}
方法二
var slice = make([]int, 3, 6)
for _, v := range slice {
println(v)
}
方法三
var slice = []int{1, 2, 4}
for _, v := range slice {
println(v)
}
var slice = []int{1, 2, 4}
//切片的切片
slice1 := slice[0:1]
//for _, v := range slice1 {
// println(v)
//}
//切片的添加
//slice3 := append(slice1, 2, 2, 2)
//for _, v := range slice3 {
// println(v)
//}
slice1 = append(slice1, 1, 2, 3)
for _, v := range slice1 {
println(v)
}
// 切片的copy
newslice := make([]int, 10)
copy(slice1, newslice)
for _, v := range newslice {
println(v)
}
string 的切片
string 本质是一个 byte 数组因此也可以进行切片
str := "1234132"
strs := str[0:2]
print(strs)
// 修改字符数组
strs := "1224"
// 变为切片
str := []byte(strs)
//类型要对
str[2] = '3'
//变为string
strs = string(str)
println(strs)
// 修改中文字符
str := []rune(strs)
str[0] = '好'
strs = string(str)
println(strs)
Map 映射
map 就是键值对,可以理解为object,可以进行嵌套
// 声明一个map
var m = make(map[string]string)
//一个个赋值
m["n1"] = "kiri"
m["n2"] = "kiri2"
fmt.Println(m)
//声明和赋值
m2 := map[string]string{
"n1": "kri",
}
fmt.Println(m2)
map crud
// 读取键值对
res := m["n1"]
// 判断是否 ok 是用来判断不存在的时候
res2, ok := m["n23"]
fmt.Println(res)
fmt.Println(res2, ok)
// 修改键值对的值
m["n1"] = "jim"
res3 := m["n1"]
fmt.Println(res3)
// 遍历key value
for k, v := range m {
fmt.Println("遍历", k, v)
}
// 删除key value
delete(m, "n1")
fmt.Println(m)
// 求长度
map 切片
mapslice := []map[string]string{}
// 在mapslice 里面添加
mapslice = append(mapslice, map[string]string{
"age": "kiri",
"num": "123",
})
//使用stuct 管理
type stu struct {
Name string
Age int
Address string
}
//第一个map
students := make(map[string]stu)
students["no1"] = stu{"kiri", 12, "shanghai"}
students["no2"] = stu{"Tom", 23, "biejin"}
fmt.Println(students)
OOP 实现 --结构体的使用
定义一个结构体, 实例化的结构体是一个值类型
type Cat struct {
Name string
Age int
}
var cat Cat
cat.Age = 23
cat.Name = "123"
fmt.Println(cat)
结构体里面如果设置了 slice 和 map 类的属性,在使用的时候一般要先make 里面的属性
结构体里的属性地址是连续的
结构体实例是值,进行结构体赋值的时候是进行值拷贝不会影响其他
//结构体oop的声明
// 第一种声明方式
var p1 Person
fmt.Println(p1)
// 声明并且进行赋值,推荐使用
p2 := Person{"kiri", 1, 22}
fmt.Println(p2)
// 声明变量p3的指针
var p3 *Person = new(Person)
p3.Age = 1
// 快捷写法
p3.Name = "tom"
//标准写法
(*p3).Height = 235
fmt.Println(*p3)
// 声明方法
var p4 *Person = &Person{}
p4.Age = 23
p4.Name = "jim"
p4.Height = 23
fmt.Println(*p4)
结构体作为一种类型可以进行类型转化,但是要求结构体类型一样 结构体的数量,名字,排列顺序一样
将struct 进行处理变为Json格式
// 结构体里要大写 要小写的json输出要在后面添加
type Monster struct {
Name string `json:"name"`
Age int `json:"age"`
}
// 进行序列化
monster := Monster{"kiri", 12}
res, err := json.Marshal(monster)
if err != nil {
fmt.Println(err)
}
fmt.Println("result:", string(res))
在结构体里面绑定函数
type Dog struct {
Name string
Gender string
}
func (d Dog) barking() {
fmt.Printf("%v 正在叫", d.Name)
}
dog := Dog{"ziri", "m"}
// 绑定后的函数必须使用下面方法进行调用结构体里的函数
dog.barking()
接口
接口只是定义了要实现方法,只有实现了接口里面所有方法被称为实现了接口,机构体 只有实现了接口里面的同名方法,才可以实现多态
type skill interface{
saying(),
running(),
}
OOP使用案例
一个简单用户管理,可以实现增删改查
定义用户的模型
model/customer.go
package model
type Customer struct {
Id int
Name string
Phone string
Age int
}
//new a Customer
func NewCustomer(id int,
name string,
phone string,
age int) Customer {
return Customer{
Id: id,
Name: name,
Phone: phone,
Age: age,
}
}
定义各种服务,crud
service/customers.go
package service
import (
"GoProject/src/project1/model"
)
type CustomerService struct {
customers []model.Customer
num int
}
// 建立构造函数
func NewCustomerService() *CustomerService {
return &CustomerService{
customers: []model.Customer{},
num: 0,
}
}
// 添加用户
func (this *CustomerService) AddCustomer(name string,
phone string,
age int) {
this.num += 1
this.customers = append(this.customers, model.NewCustomer(this.num, name, phone, age))
}
// 更新
func (this *CustomerService) UpdateCustomer(id int, name string,
phone string,
age int) {
this.customers[id] = model.NewCustomer(id+1, name, phone, age)
}
// 删除
func (this *CustomerService) DeleteCustomer(id int) {
var indexToRemove int = -1
for i, _ := range this.customers {
if i == id-1 {
indexToRemove = i
break
}
}
if indexToRemove != -1 {
this.customers = append(this.customers[:indexToRemove], this.customers[indexToRemove+1:]...)
}
this.num -= 1
}
// 列表显示
func (this *CustomerService) ListCustomer() ([]model.Customer, int) {
return this.customers, this.num
}
建立现实页面
view/mainMenu.go
package view
import (
"GoProject/src/project1/service"
"fmt"
"strconv"
)
type UserMenu struct {
user string
pwd string
key string
details string
loop bool
service service.CustomerService
}
func NewUser(user string, pwd string) *UserMenu {
return &UserMenu{
user: user,
pwd: pwd,
details: "编号\t姓名\t年龄\t电话",
key: "",
loop: true,
service: *service.NewCustomerService(),
}
}
func (this *UserMenu) login(user string, pwd string) bool {
if user == this.user && pwd == this.pwd {
return true
} else {
return false
}
}
func (this *UserMenu) showList() {
fmt.Println("--------------用户列表--------------")
customers, _ := this.service.ListCustomer()
temp := ""
for _, customer := range customers {
temp += fmt.Sprintf("\n%v \t%v \t%v \t%v", customer.Id, customer.Name, customer.Age, customer.Phone)
}
fmt.Println(this.details + temp)
}
func (this *UserMenu) addUser() {
fmt.Println("--------------添加用户--------------")
fmt.Println("请输入用户姓名?")
fmt.Scanln(&this.key)
name := this.key
fmt.Println("请输入电话号码?")
fmt.Scanln(&this.key)
phone := this.key
fmt.Println("请输入年龄?")
fmt.Scanln(&this.key)
tempage, _ := strconv.ParseInt(this.key, 10, 64)
age := int(tempage)
this.service.AddCustomer(name, phone, age)
fmt.Println("添加成功~~")
}
func (this *UserMenu) updateUser() {
fmt.Println("--------------修改用户--------------")
fmt.Println("请输入用户id?")
fmt.Scanln(&this.key)
tempid, _ := strconv.ParseInt(this.key, 10, 64)
id := int(tempid)
fmt.Println("请输入用户姓名?")
fmt.Scanln(&this.key)
name := this.key
fmt.Println("请输入电话号码?")
fmt.Scanln(&this.key)
phone := this.key
fmt.Println("请输入年龄?")
fmt.Scanln(&this.key)
tempage, _ := strconv.ParseInt(this.key, 10, 64)
age := int(tempage)
this.service.UpdateCustomer(id-1, name, phone, age)
fmt.Println("修改成功~~")
}
func (this *UserMenu) deleteUser() {
fmt.Println("--------------删除用户--------------")
fmt.Println("请输入用户id?")
fmt.Scanln(&this.key)
tempid, _ := strconv.ParseInt(this.key, 10, 64)
id := int(tempid)
this.service.DeleteCustomer(id)
fmt.Println("删除成功~~")
}
func (this *UserMenu) ShowMenu() {
for {
fmt.Println("请输入用户名?")
fmt.Scanln(&this.key)
user := this.key
fmt.Println("请输入密码?")
fmt.Scanln(&this.key)
pwd := this.key
if this.login(user, pwd) {
break
} else {
fmt.Println("登录失败")
}
}
fmt.Printf("******欢迎用户%v登录系统******\n", this.user)
for {
fmt.Printf("--------------用户管理--------------\n")
fmt.Printf(" 1.用户列表\n")
fmt.Printf(" 2.添加用户\n")
fmt.Printf(" 3.修改用户\n")
fmt.Printf(" 4.删除用户\n")
fmt.Printf(" 5.退出系统\n")
fmt.Printf(" --------------请选择1-5-------------- \n")
fmt.Scanln(&(this.key))
switch this.key {
case "1":
this.showList()
case "2":
this.addUser()
case "3":
this.updateUser()
case "4":
this.deleteUser()
case "5":
this.loop = false
fmt.Println("--------------退出系统--------------")
default:
fmt.Println("--------------请选择1-5-------------- ")
}
if !this.loop {
break
}
}
}
main.go
package main
import (
"GoProject/src/project1/view"
)
func main() {
view.NewUser("tom", "1234").ShowMenu()
}
goroutine
go 协程机制,其他语言大多用多线程
并发:在单cpu里面进行多个进程或线程计算微观来看是一个个进行运算
并行:是在多个cpu 里面进行运算
进程:相当于一个厨房,是分配资源的最小单位
线程:相当于一个个主厨师,是执行的最小单位
协程:相当于实习厨师,属于轻量的线程
主线程结束,其他的协程也结束了,要读写同一个数据就必须
goroutine
1.使用的cpu的核数
//启动多协程
go test()
//显示核数
num:=runtime.NumCPU()
//使用核数
runtime.GOMAXPROCS(num)
要实现多个协程进行一个读写变量就需要一个机制进行防止冲突和阻塞
1.设置一个全局lock 变量,协程进行变量操作的时候就需要进行上锁机制,但是这样主线程不知道什么时候线程结束
2.使用管道机制进行,类似一个管道符合先进先出原则,可以把规定的类型变量存放到管道,通过存入和取出变量实现协程的合理运行
3.管道是一种引用类型
通道机制
通道的建立和写入和读出
package main
import (
"fmt"
)
func count1(s []int, ch chan int) {
sum := 0
for _, v := range s {
sum += v
}
ch <- sum
}
func main() {
slice := []int{1, 2, 3, 4, 5, 6}
ch := make(chan int)
go count1(slice[:len(slice)/2], ch)
go count1(slice[len(slice)/2:], ch)
x := <-ch
y := <-ch
fmt.Println(x, y, x+y)
}
通道如果只读进去或者只写的话会出现报错,尤其是超过缓冲容量的时候,缓冲容量决定可以存多少个数
var intchan chan int
intchan = make(chan int, 3)
fmt.Printf("intchan的值是=%v intchan本身的地址%p", intchan, &intchan)
intchan <- 10
num := 200
intchan <- num
intchan <- 20
intchan <- 10
fmt.Printf("channel len %v 和cap %v", len(intchan), cap(intchan))
<-intchan
fmt.Printf("channel len %v 和cap %v", len(intchan), cap(intchan))
不设置缓冲的容量只能存和取同步进行(多个协程同步进行),不然会error
intchan = make(chan int)
fmt.Printf("intchan的值是=%v intchan本身的地址%p", intchan, &intchan)
intchan <- 10
chan 可以设置多种类型,不过有时要进行类型断言进行类型匹配
allchan := make(chan interface{}, 3)
name := "jim"
age := 13
cat1 := Cat{name: name, age: age}
allchan <- name
allchan <- age
allchan <- cat1
fmt.Println(<-allchan, <-allchan)
newc := <-allchan
// 因为是interface 类型所以要进行类型转化,使用类型断言
//fmt.Println(newc.name)
a := newc.(Cat)
fmt.Println(a.name)
channel 的是可以进行关闭的,一旦进行关闭,可以进行读但是不可以写
// 关闭了channel 就不能写入了
intchan := make(chan int, 3)
intchan <- 2
intchan <- 3
close(intchan)
//intchan <- 4
fmt.Print(<-intchan, <-intchan)
channel 元素的遍历 使用的是for range,普通的for 循环会因为channel 的长度变化不好进行变化,遍历的时候要先关闭
intchan := make(chan int, 100)
for i := 0; i < 100; i++ {
intchan <- i * 2
}
// 需要先关闭
close(intchan)
for v := range intchan {
fmt.Print(v)
}
使用gorutine 和channel 进行配合,实现两个协程的读和写
package main
import "fmt"
func write(ch1 chan int) {
for i := 0; i < 50; i++ {
ch1 <- i
}
close(ch1)
}
func read(ch1 chan int, ch2 chan bool) {
for {
v, ok := <-ch1
if !ok {
break
}
fmt.Println(v)
}
ch2 <- true
close(ch2)
}
func main() {
ch1 := make(chan int, 50)
ch2 := make(chan bool, 1)
go write(ch1)
go read(ch1, ch2)
for {
_, ok := <-ch2
if !ok {
break
}
}
}
-
go 必须是有写入必须有读,不然会报错
-
2000个素数判断案例
package main import "fmt" // 写入数据 func write(ch1 chan int) { for i := 0; i < 2000; i++ { ch1 <- i } close(ch1) } // 进行数据判断 func jungle(ch1 chan int, res chan int, flag chan bool) { var num int for i := 0; i < 500; i++ { issu := true num = <-ch1 for j := 2; j < num; j++ { if num%j == 0 { issu = false break } } if issu { res <- num } } flag <- true } func main() { //flag := make(chan bool,1) ch1 := make(chan int, 2000) res := make(chan int, 1000) flag := make(chan bool, 4) go write(ch1) for i := 0; i < 4; i++ { go jungle(ch1, res, flag) } go func() { for i := 0; i < 4; i++ { <-flag } close(res) }() for { v, ok := <-res if !ok { break } fmt.Println(v) } }-
管道可以设置只读和只写
//定义为只能写的chan var intchan chan<- int //定义为只能读的chan var intchan <-chan int // 主要是不同的函数里面的对同一个channel 的操作不一样,为防止误操作 //比如 f1 ch1 只有写操作到形参里面定义一个在可写的型参
-
-
协程出现panic 会崩溃,要进行错误处理
反射
反射的使用
- 序列化和反序列化的名称替换
- 函数适配器,重载函数的实现
Go实践
redis 快速入门
redis 是Nosql 数据库,不是条件数据库
redis的数据结构
redis 数据库可以认为事数据结构数据库,数据库里面的数据类型与开发时候的数据类型比较相似。
redis主要的数据结构包括: kv 对 hash list set zset
redis 初始有 0,1,...15 16 个数据库,初始默认的是0号数据库
先打开 redis-server 之后打开 redis-cli
redis的基本操作
添加 key-value 指令 set key value
删除 key-value 指令 DEL key
查看当前redis数据库的所有的值 key *
切换数据库 select number
清空当前数据库 key-value flushdb 或者清空所有数据库 flushall
查看数据库规模 dbsize
-
string
string 类型就是 key-value 对 ,string 的最大大小事 512M
创建或者修改 set key value 创建带有效期的 setex key time value
设置多个key-value mset key value
查看key-value get key
查看多个key-value mget key key
删除 del key
-
hash
hash 类似于map通过hash 实现多个k-v对的存放,类似于一个json 存放了多个属性,key 不可以重复
创建一个user 信息 hset user1 name tom hset user1 age 23
设置多个user hmset user1 name tom age 23
读取user 里面的部分属性 hget user1 name 或多个属性 hmget user1 name age
获取一个hash里面所有键值对 hgetall user1
删除hash hdel user1
看有多少键值对 hlen user1
判断字符串是否存在 hexists user1 name
-
list
list 类似于一个链表组成的栈结构,先进后出,并且有顺序
可以把数据结构可以看成是,把数据放入一个管道,lpush 从左边放入
rpush 从右边放入
lpush listname beijin shanghai
左边读取列表,lrange listname start end 根据index 从左边取数(index 是从左数的顺序)
左边弹出读数 lpop 读出并且删除 rpop 右边弹出
根据index的来取数 lindex listname index
根据 llen list 获取列表长度
如果list 都pop 完了就不存在list了
删除列表 del list
-
set
set是没有顺序的元素集合,元素的值不能重复
比如存放电子邮件
sadd emails xxxx@com
取出 set 里面的所有值
smembers emails
判断是否有某一个成员
sismember emails xxxx@com
redigo 插件安装
一般在gopath目录下进入控制台
先配置代理和导入模块
go env -w GO111MODULE=on
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
go get github.com/gomodule/redigo/redis
入门案例
使用 string 类型 进行读写
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
)
func main() {
c, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println("connect redis fail", err)
return
}
defer c.Close()
_, err = c.Do("Set", "key1", 99)
if err != nil {
fmt.Println(err)
return
}
r, err := redis.Int(c.Do("Get", "key1"))
if err != nil {
fmt.Println(err)
return
}
fmt.Println(r)
}
基本顺序:
1.建立连接 2.执行 3.关闭连接
使用hash 类型进行读写
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
)
func main() {
c, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println("connect redis fail", err)
return
}
fmt.Println("connect success")
// 建立一个string
_, err = c.Do("HSet", "user1", "name", "tom")
if err != nil {
fmt.Println("set err", err)
return
}
_, err = c.Do("HSet", "user1", "age", "124")
if err != nil {
fmt.Println("set err", err)
return
}
// 读取string
res, errors := redis.String(c.Do("HGet", "user1", "age"))
if errors != nil {
fmt.Println("GET ERROR", errors)
return
}
fmt.Println(res)
defer c.Close()
}
// 多个hset
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
)
func main() {
c, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println("connect redis fail", err)
return
}
fmt.Println("connect success")
// 建立多个string
_, err = c.Do("HMSet", "user1", "name", "jim", "age", 23)
if err != nil {
fmt.Println("set err", err)
return
}
// 读取string
res, errors := redis.Strings(c.Do("HMGet", "user1", "name", "age"))
if errors != nil {
fmt.Println("GET ERROR", errors)
return
}
for k, v := range res {
fmt.Println(k, v)
}
连接池原理
1.初始连接池,通过初始若干连接
2.当程序需要进行连接的时候就从连接池里面拿出一个连接来
3.不需要要的时候放回连接就可以
4.一般在初始函数里面设置连接池
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
)
var pool *redis.Pool
func init() {
pool = &redis.Pool{
MaxIdle: 8,
MaxActive: 0,
IdleTimeout: 100,
Dial: func() (redis.Conn, error) {
return redis.Dial("tcp", "localhost:6379")
},
}
}
func main() {
var name string
var age int
var address string
c := pool.Get()
//if err != nil {
// fmt.Println("连接失败", err)
//}
defer c.Close()
fmt.Println("connect success")
fmt.Println("请输入您的姓名:")
fmt.Scan(&name)
fmt.Println("请输入您的年龄:")
fmt.Scan(&age)
fmt.Println("请输入您的地址:")
fmt.Scan(&address)
//将一数据存放到hash
_, err := c.Do("Hset", "user1", "name", name)
if err != nil {
fmt.Println("hset erro", err)
}
_, err = c.Do("Hset", "user1", "age", age)
if err != nil {
fmt.Println("hset erro", err)
}
_, err = c.Do("Hset", "user1", "address", address)
if err != nil {
fmt.Println("hset erro", err)
}
res, errors := redis.Strings(c.Do("HMget", "user1", "name", "age", "address"))
if errors != nil {
fmt.Println("hget error")
}
// 遍历数据
for k, v := range res {
fmt.Println(k, v)
}
}