go基础语法练习
go基础
字符串
1.字符串var str string = ``(使用反引号可以实现字符串中出现双引号也不用转义字符“\”进行转义)
2,将其他数据类型转为字符串
两种方式:
//方法一
var by byte = 'a'
var strs = fmt.Sprintf("%c", by)
fmt.Printf("%T", strs)
fmt.Println()
//方法二
var boo bool = true
formatBool := strconv.FormatBool(boo)
fmt.Printf("%q", formatBool)
基本的格式化动词
| 格式化动词 | 说明 | 示例代码 | 输出示例 |
|---|---|---|---|
%v | 默认格式的值 | fmt.Printf("%v", 42) | 42 |
%+v | 打印结构体时包含字段名(更详细) | fmt.Printf("%+v", person) | {Name:Alice Age:20}(带字段名) |
%#v | Go 语法表示的值(包括类型) | fmt.Printf("%#v", 42) | 42 fmt.Printf("%#v", "hi")→ "hi" fmt.Printf("%#v", struct{Name string}{"Tom"})→ struct { Name string }{Name: "Tom"} |
%T | 打印值的类型 | fmt.Printf("%T", 42) | int |
%t | 布尔值(true/false) | fmt.Printf("%t", true) | true |
标识符和运算符:
变量名尽量使用驼峰命名法
一个go文件用的包是尽量用package main,可以在每次新建一个包下再建一个main包,实现go文件中包名与父包名相同
go语言与Java语言中“++”操作不能混淆,Java中++可以放在变量前也可以放在变量后面,但是在go语言中++就是简单的变量自增加一,并且只能放在变量的后面,不能放在变量的前面
switch分支注意事项:
注意事项:
case后面可以跟多个值 比如case:var1,var2,var3
case后面的常量值的数据类型要和switch后面的数据类型一致
case后面的的常量值不能重复,否则会报错
case后面不用跟break(与Java做区分,Java中case后面需要跟break)
default不是必须的,可有可无
switch后面不用非要跟表达式,可以当“if”使用,可以在case后面跟表达式
switch后面可以声明变量以分号结束,不推荐
switch b := 10; {
case b > 9:fmt.Println("....")
case b > 12:fmt.Println("///////")
}
switch穿透(fullthrough关键字)可以执行多个case分支(Java中不能实现switch的穿透,只能执行一个case分支)
for循环:
使用for循环遍历字符串的两种方式:
var name = "sncjdnghdmdfkg"
//方式一
for i := 0; i < len(name); i++ {fmt.Printf("%c", name[i])fmt.Println()
}//方式二
for i, j := range name {fmt.Printf("索引是%v,直是%c", i, j)fmt.Println()
}
循环终止关键字:break
break + 标签 可以实现指定终止那一层循环
continue加标签也可以实现
goto + 标签可以无条件跳转到程序的指定行
//使用一个标签用于指定终止那一层循环
label1:for i := 0; i < 3; i++ {for j := 0; j < 3; j++ {if i == 4 && j == 5 {break label1}fmt.Println(i, "==========", j)}}
}
函数:
函数的特点:
不支持方法重载
方法的参数,是将原参数复制一份,传参,简而言之就是方法体参数与原始数据的地址不一致
基本数据类型和数组默认都是值传递,即进行值拷贝,在函数内修改,不会影响原来的值
golang中支持传递可变参数
go中函数可看作数据类型 案例如下:
//函数也可以作为一个特殊的数据类型func test(num int) {fmt.Println(num)
}func main() {a := testa(10)var num = 20test2(num, a)
}
//给func(int)这一数据类型起别名
type myFunc func(int)func test2(num int, a myFunc) {a(num)
}
go函数中支持对返回值命名!!
可以给包进行起别名 如下:
import (//将报名起个aaa的别名aaa "Test/8funcType/utils""fmt"
)//函数也可以作为一个特殊的数据类型func test(num int) {fmt.Println(num)
}func main() {a := testa(10)var num = 20test2(num, a)//起完别名后,就只能用别名来调用包中的方法了aaa.ExchangeNum3(10, 20, 30, 40)
}type myFunc func(int)func test2(num int, a myFunc) {a(num)
}
值传递的两种方法:
func main() {var numa int = 10var numb int = 20fmt.Println(numa, numb)numa, numb = exchangeNum(numa, numb)fmt.Println(numa, numb)fmt.Println("值交换第二种方法==============================")exchangeNum2(&numa, &numb)fmt.Println(numa, numb)
}// 传地址
func exchangeNum2(a *int, b *int) {var temp inttemp = *a*a = *b*b = temp
}// 传数值
func exchangeNum(numa int, numb int) (int, int) {var temp inttemp = numanuma = numbnumb = tempreturn numa, numb
}
自定义类型和类型别名:
//自定义数据类型type Code0 int
type Code1 = intconst (SuccessCode1 Code0 = 1SuccessAliasCode Code1 = 2
)func main() {fmt.Printf("%v,%T\n", SuccessCode1, SuccessCode1)fmt.Printf("%v,%T\n", SuccessAliasCode, SuccessAliasCode)
}
差异:
1,类型别名无法绑定方法 自定义类型可以
2,类型别名的打印出来的类型还是原始类型
3,类型别名与原始数据类型进行比较时,类型别名不用转换直接可以比较
init函数:
初始化函数,可以用来进行一些初始话操作,每个源文件都可以包含一个init函数,该函数会在main函数执行前,被GO运行框架调用
匿名函数:
匿名函数不用定义函数名
// 匿名函数
func main() {//这里func后面只有参数类型result := func(a int, b int) int {return a + b}(10, 20)fmt.Println(result)sum := func(a int, b int) int {return a + b}result2 := sum(10, 20)fmt.Println(result2)result3 := sum(30, 40)fmt.Println(result3)
}
函数闭包:
注意:num会一直存在于匿名函数中
匿名函数中引用的那个变量会一直保存在内存中,可以一直使用
闭包的本质:
闭包本质依旧是一个匿名函数,只是这个函数引入外界的变量/参数
匿名函数+引用的变量/参数 = 闭包
func aaa() func(int) int {var num = 10return func(i int) int {num = num + ireturn num}
}
// 匿名函数
func main() {//函数闭包f := aaa()fmt.Println(f(1))//第一次的值是11fmt.Println(f(1))//第二次调用函数输出值是12
}
不应用闭包的时候,我想保留的值,不可以反复使用
闭包应用场景:闭包可以保留上次引用的某个值,我们传入一次就可以反复使用了
特点:
1.返回的是一个匿名函数,但是这个匿名函数引用到函数外的变量/参数,因此这个匿名函数就和变量/参数形成一个整体,构成闭包
2.闭包中使用的变量/参数会一直保存在内存中,所以会一直使用,意味着闭包不可滥用
defer关键字:
**栈的特点:**先进后出
在go中程序遇到defer关键字,不会立即执行defer的语句,而是将defer后的语句压入一个栈中,然后继续执行
在函数执行完毕后,从栈中取出语句开始执行,按照先进后出的规则执行语句
func add(num1 int, num2 int) int {//将num1 的值先拷贝一份,压入栈中,再将num2中的值拷贝一份压入栈中,最后输出时先输出num2 在输出num1defer fmt.Println(num1) defer fmt.Println(num2)fmt.Println(num1 + num2)return num1 + num2
}// defer 关键字
func main() {fmt.Println(add(10, 20))
}
注意:
遇到defer关键字,会将后面的代码语句压入栈中,也会将相关的值同时拷贝入栈中,不会随着函数后面的变化而变化
系统函数:
字符串函数:
对于字符串普通符号占一个字节,汉字一个字占3个字节
方法一二再for循环中找
方法三:
[]rune(str)
字符串转为切片并遍历
var str string = "xhanfhjamngkamd魏正想"// 常见的系统函数
func main() {strs := []rune(str)for i := 0; i < len(strs); i++ {fmt.Printf("%c\n", strs[i])}
}
字符串转数字:
strconv.Atoi(nnn)
var nnn string = "234"
//方法一
result, _ := strconv.ParseInt(nnn, 10, 16)
fmt.Println(result)
//方法二
result1, _ := strconv.Atoi(nnn)
fmt.Println(result1)
数字转字符串:
strconv.Itoa(123)
result2 := strconv.Itoa(123)
fmt.Printf("%T\n", result2)
fmt.Println(result2)
判断某个子串是否在字符串中:
strings.Contains(“golang”, “go”)
//判断go是否在golang字符串中
flag := strings.Contains("golang", "go")
fmt.Println(flag)
判断某个子串在字符串中出现的次数:
strings.Count(“gogogogogogogogo”, “go”)
//判断字符串中有多少个指定字符
counts := strings.Count("gogogogogogogogo", "go")
fmt.Println(counts)
不区分大小写比较字符串
//不区分大小写比较字符串
flag1 := strings.EqualFold("golang", "GOLANG")
fmt.Println(flag1)
找到字符串中给定子串第一次出现的索引值
//找到字符串中给定子串第一次出现的索引值
index := strings.Index("sndjdngjcuengicneng", "qi")
fmt.Println(index)
字符串的替换:
//字符串的替换
//参数分析,1,原字符串 2,被替换的子串 3,新的子串 4,替换几个老子串
result3 := strings.Replace("goadnjavagogo", "go", "golang", 2)
fmt.Println("字符串的替换")
fmt.Println(result3)
字符串其他操作:
//字符串的分割
result4 := strings.SplitAfter("gog-ogog-ogogogsuejf-idmg", "-")
fmt.Printf("字符串分割后的数据类型%T", result4)
fmt.Println()
fmt.Println(result4)//字符串大小写字母转换
upper := strings.ToUpper("lulu")
fmt.Println(upper)
lower := strings.ToLower("LULU")
fmt.Println(lower)//去掉字符串两边空格
space := strings.TrimSpace(" WeiZhengXiang ")
fmt.Println(space)//将字符串两边指定的字符去掉
trim := strings.Trim("!wzx!-", "!")
fmt.Println(trim)//指定字符串左右边指定字符去掉
left := strings.TrimLeft("!sndjgn!", "!")
right := strings.TrimRight("!sndjgn!", "!")
fmt.Println(left)
fmt.Println(right)//判断字符串是否以指定的字符开头
prefix := strings.HasPrefix("https://www.bilibili.com/video/"+"BV1ng41147AT/?p=70&spm_id_from=333.1007.top_right_"+"bar_window_history.content.click&vd_"+"source=546576a6f524d3e58d9b93d26e97c920", "https")
fmt.Println("判断字符串是否以指定的子串开头", prefix)
时间函数:
func main() {now := time.Now()fmt.Println(now)fmt.Println(now.Second())fmt.Println(now.Year())december := time.Decemberfmt.Println(int(december))fmt.Println(now.Month())fmt.Println(now.Second())//将获得的时间存储起来,方便使用result := fmt.Sprintf("%d-%d-%d %d:%d:%d", now.Year(), int(now.Month()), now.Day(), now.Hour(), now.Month(), now.Second())fmt.Println(result)//时间格式化format := now.Format("Mon Jan 2 15:04:05 -0700 MST 2006")fmt.Println(format)//2006-01-02 15:04:05format2 := now.Format("2006-01-02 15:04:05")fmt.Println(format2)}
new函数:
分配内存,主要分配值类型(int系列,float系列,bool,string,数组和结构体struct)
i := new(int)
fmt.Printf("类型%T,数值%v地址%v指针指向的值%v", i, i, &i, *i)
defer+recover机制处理错误,自定义错误:
defer+recover机制处理错误
func test(num, num1 int) int {defer func() {//使用recover内置函数捕获错误err := recover()//如果不等于nil则捕获错误 没有捕获到错误的话值默认值是零值:nilif err != nil {fmt.Println("捕获到的错误是:", err)}}()return num / num1
}func main() {//i := new(int)//fmt.Printf("类型%T,数值%v地址%v指针指向的值%v", i, i, &i, *i)//fmt.Println()var num = 10var num1 = 0test(num, num1)
}
自定义错误:
func test(num, num1 int) error {if num1 == 0 {err := errors.New("除数不能等于0")//掉用panic函数,将函数停止,后面的程序将不再执行fmt.Println("aaaaaaaaaaaaaaaaaaaaaaaaaaa")return err}return nil
}func main() {//i := new(int)//fmt.Printf("类型%T,数值%v地址%v指针指向的值%v", i, i, &i, *i)//fmt.Println()var num = 10var num1 = 0err := test(num, num1)if err != nil {fmt.Println(err)//如果将下面这行注掉程序会继续往下面执行panic(err)}fmt.Println("bbbbbbbbbbbbbb")
}
数组array:
注意:数组的长度是数组类型的一部分
数组的遍历:
1、普通for循环
2、是用for range(go所特有的key value遍历方式)
数组初始化四种方式:
//数组初始化一
var nums0 []int = []int{1, 2, 3}
fmt.Println(nums0)
//数组初始化二
var nums1 = []int{1, 2, 3}
fmt.Println(nums1)
//数组初始化三
var nums2 = [...]int{1, 2, 3}
fmt.Println(nums2)
//数组初始化四
var nums3 = [...]int{0: 1, 1: 2, 2: 3}
fmt.Println(nums3)
切片slice:
长度!=容量
// 切片练习
func main() {var array = [4]int{1, 2, 3, 4}var slice []intslice = array[0:3]fmt.Println(len(slice))fmt.Println(cap(slice))
}
切片会改变原数组切片中的数值
调用append函数给切片追加数据 调用该函数后切片会指向一个新的数组地址
func main() {var array = [4]int{1, 2, 3, 4}var slice []intslice = array[0:3]fmt.Println(len(slice))fmt.Println(cap(slice))//切片会改变原数组切片中的数值//原数组中的array[0]的值由原来的1变为10slice[0] = 10fmt.Println(array)//append函数 调用该函数后切片会指向一个新的数组地址slice = append(slice, 3, 4, 120)fmt.Println(slice)fmt.Println(array)}
切片拷贝切片追加:
// 切片练习
func main() {var array = [4]int{1, 2, 3, 4}var slice []intslice = array[0:3]fmt.Println(len(slice))fmt.Println(cap(slice))//切片会改变原数组切片中的数值slice[0] = 10fmt.Println(array)//append函数 调用该函数后切片会指向一个新的数组地址slice = append(slice, 3, 4, 120)fmt.Println(slice)fmt.Println(array)//使用make自定义切片 参数:数据类型 切片长度 切片容量slice2 := make([]int, 4, 5)fmt.Println(slice2)//切片后面追加切片 另一个切片后面附上三个点slice = append(slice, slice2...)fmt.Println(slice)//切片的拷贝var slice3 = make([]int, 10)//将后者拷贝到前者中copy(slice3, slice)fmt.Println(slice3)
}
映射map:
map集合在使用之前一定要make
map的key-value是无序的
key是不可以重复的,如果遇到重复,后一个value会替换前一个value
value可以重复
map的三种创建方式:
//1
var map1 = make(map[int]string, 10)
fmt.Println(map1)
//2
var map2 = make(map[int]string)
fmt.Println(map2)
//3
var map3 = map[int]string{1: "2323",2: "23232",3: "skewje",
}
fmt.Println(map3)
map集合的基本操作:
//3var map3 = map[int]string{1: "2323",2: "23232",3: "skewje",}
//删除一个元素
delete(map3, 1)
fmt.Println(map3)
//go中没有一个一次性删除全部map的方法
//删除操作1:逐一删除 2,附一个空的map将原来的抛弃
//2
map3 = make(map[int]string)
fmt.Println(map3)
对象struct:
go语言面向对象说明:
go也支持面向对象编程(oop),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言,所以我们说golang支持面向对象编程特性是比较准确的。
go没有类,go语言结构体和其他编程语言的类有同等的地位,你可以理解golang是基于struct来实现oop特性的。
go面向对象编程非常简洁,去掉了传统oop语言的方法重载,构造函数和析构函数,隐藏的this指针等等
go仍然有面向对象编程的继承,封装,多态的特性,只是实现方式和其他oop语言不一样,比如继承go没有extends关键字,继承是通过匿名字段实现。
type Teacher struct {Name stringAge intJob string
}func main() {//创建实例//方法一var a Teachera.Name = "魏正想"a.Age = 18a.Job = "农民"fmt.Println(a)//创建实例未赋值,默认为零值var b Teacherfmt.Println(b)//b := Teacher{// Name: "",// Age: 0,// Job: "",//}//方法二var c Teacher = Teacher{Name: "www", Age: 30, Job: "nnn"}fmt.Println(c)var e Teacher = Teacher{"www", 30, "nnn"}fmt.Println(e)//方法三://创建指针//new函数 分配内存,主要分配值类型(int系列,float系列,bool,string,数组和结构体struct)var d *Teacher = new(Teacher)d.Name = "111"d.Age = 22d.Job = "///"fmt.Println(*d)
}
结构体之间的转换:
1,需要满足结构体中数据字段类型,数量相同
2,转换时,要用到强制类型转换
接口:
go和Java的接口区别:
go的接口方法是被发现的,而Java语言的接口是需要对象继承并重写的
go接口:隐式实现:只要一个类型(结构体或其他)实现了接口中定义的所有方法,就自动实现了该接口
Java接口:显式实现:类必须声明实现了接口,并且必须实现接口中所有抽象方法(除非是抽象类)
//创建一个接口
type AnimalInter interface {sing()
}
// 创建结构体
type Cat struct {Name string
}type Dog struct {Name string
}func (c Cat) sing() {fmt.Println(c.Name, "在唱歌")
}func (c Dog) sing() {fmt.Println(c.Name, "在唱歌")
}func sing(c AnimalInter) {c.sing()
}func main() {var animal1 = Cat{Name: "cat"}var animal2 = Dog{Name: "dog"}sing(animal1)sing(animal2)
}
类型断言:
func sing(c AnimalInter) {//断言 判断接口对象是否是猫ch, ok := c.(Cat)fmt.Println(ch, ok)switch server := c.(type) {case Cat:fmt.Println(server)case Dog:fmt.Println(server)default:fmt.Println("我也不知道是个啥")}c.sing()//fmt.Println(c.getName())
}
空接口:
空接口能被任意调用
// 空接口
//第一种空接口方式
type EmpytInter interface {
}func Point(nn interface{}) {fmt.Println("你好")
}
//interface和any等价
//第二中空接口方式
func Point(nn any) {fmt.Println("你好")
}func main() {var animal1 = Cat{Name: "cat"}var animal2 = Dog{Name: "dog"}Point(animal1)
}
协程和channel
什么是协程?
-
Goroutine 是 Go 语言中的轻量级线程,由 Go 运行时(runtime)管理。
-
相比操作系统线程(thread),goroutine 更轻量,启动成本极低,可以轻松创建成千上万个。
-
Goroutine 运行在 Go 的并发模型之上,由 Go 调度器(scheduler)在少量操作系统线程上多路复用。
第一种定义sync.WaitGroup变量的方法:
var wait sync.WaitGroup
//第一种定义sync.WaitGroup变量的方法func shopping(name string) {fmt.Println(name, "来购物")time.Sleep(1 * time.Second)fmt.Println(name, "购物结束")wait.Done()
}func main() {var now = time.Now()wait.Add(3)go shopping("张三")go shopping("里斯")go shopping("王五")wait.Wait()fmt.Println(time.Since(now))
}
第二种定义sync.WaitGroup变量的方法:
//函数中的参数都是值传递
func shopping(name string, wait *sync.WaitGroup) {fmt.Println(name, "来购物")time.Sleep(1 * time.Second)fmt.Println(name, "购物结束")wait.Done()
}func main() {var wait sync.WaitGroupvar now = time.Now()wait.Add(3)go shopping("张三", &wait)go shopping("里斯", &wait)go shopping("王五", &wait)wait.Wait()fmt.Println(time.Since(now))
}
使用channel来传递数据:
channel的作用**:**
Go 中的 channel(信道)是用于 *goroutine(协程)之间通信与同步 的主要机制,它提供了一种*类型安全、线程安全的数据传输方式,是 Go 并发模型(CSP 模型)的基石。
// channel知识
var moneyChan = make(chan int)func shopping(name string, money int, wait *sync.WaitGroup) {fmt.Println(name, "来购物")time.Sleep(1 * time.Second)//将钱放到信道中moneyChan <- moneyfmt.Println(name, "购物结束")wait.Done()
}func main() {var wait sync.WaitGroupvar now = time.Now()wait.Add(3)go shopping("张三", 2, &wait)go shopping("里斯", 4, &wait)go shopping("王五", 5, &wait)//创建匿名函数负责进行并行线程的监听,不能和下面的for循环调换顺序,否则启动不了该协程go func() {wait.Wait()close(moneyChan)}()//这段必须要有,信道信息有人传还要有人接!!!for {money, ok := <-moneyChanfmt.Println(money)if !ok {break}}//wait.Wait()fmt.Println(time.Since(now))
}
e(now))
}
使用channel来传递数据:
channel的作用**:**
Go 中的 channel(信道)是用于 *goroutine(协程)之间通信与同步 的主要机制,它提供了一种*类型安全、线程安全的数据传输方式,是 Go 并发模型(CSP 模型)的基石。
// channel知识
var moneyChan = make(chan int)func shopping(name string, money int, wait *sync.WaitGroup) {fmt.Println(name, "来购物")time.Sleep(1 * time.Second)//将钱放到信道中moneyChan <- moneyfmt.Println(name, "购物结束")wait.Done()
}func main() {var wait sync.WaitGroupvar now = time.Now()wait.Add(3)go shopping("张三", 2, &wait)go shopping("里斯", 4, &wait)go shopping("王五", 5, &wait)//创建匿名函数负责进行并行线程的监听,不能和下面的for循环调换顺序,否则启动不了该协程go func() {wait.Wait()close(moneyChan)}()//这段必须要有,信道信息有人传还要有人接!!!for {money, ok := <-moneyChanfmt.Println(money)if !ok {break}}//wait.Wait()fmt.Println(time.Since(now))
}
