Go语言基本语法
数据类型:
值类型:int、float、bool、string、array、结构体
引用类型:slice、map、function、pointer
格式化字符串
Go 语言中使用 fmt.Sprintf 格式化字符串并赋值给新串:
package main
import (
"fmt"
)
func main() {
// %d 表示整型数字,%s 表示字符串
var stockcode=123
var enddate="2020-12-31"
var url="Code=%d&endDate=%s"
var target_url=fmt.Sprintf(url,stockcode,enddate)
fmt.Println(target_url)
}
输出结果为:
Code=123&endDate=2020-12-31
数组
package main import "fmt" func main(){ var scores [5]int scores[0] = 95 scores[1] = 89 scores[2] = 92 scores[3] = 96 sum := 0 for i := 0; i<len(scores); i++{ sum += scores[i] } avg := sum/len(scores) fmt.Printf("成绩总和为:%v,成绩成绩为:%v", sum, avg) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package main import "fmt" func main(){ var scores [6]int fmt.Println(len(scores)) fmt.Println(scores) fmt.Printf( "scores的地址为:%p" , &scores) fmt.Printf( "scores的地址为:%p" , &scores[0]) fmt.Printf( "scores的地址为:%p" , &scores[1]) fmt.Printf( "scores的地址为:%p" , &scores[2]) } // 6 // [0 0 0 0 0 0] // scores的地址为:0xc00000c300 // scores的地址为:0xc00000c300 // scores的地址为:0xc00000c308 // scores的地址为:0xc00000c310 |
循环遍历数组的两种方式:
1、普通for循环
1 2 3 4 5 6 7 8 9 10 11 | package main import "fmt" func main(){ var scores [5]int scores[0] = 95 scores[1] = 89 scores[2] = 92 scores[3] = 96 for i := 0; i |
2、range
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package main import "fmt" func main(){ var scores [5]int scores[0] = 95 scores[1] = 89 scores[2] = 92 scores[3] = 96 for key, value := range scores{ fmt.Printf( "第%d个学生的成绩为:%d\n" , key+1, value) } } |
定义数组的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package main import "fmt" func main(){ var arr1 [3]int = [3]int{3, 6, 9} fmt.Println(arr1) var arr2 = [3]int{1, 4 ,7} fmt.Println(arr2) var arr3 = [...]int{4, 5, 6, 7, 8, 9} fmt.Println(arr3) var arr4 = [...]int{2: 66, 0: 33, 1: 99, 3:88} fmt.Println(arr4) } |
注意事项:
1、长度属于数组类型的一部分:
package main
import "fmt"
func main(){
var arr1 [3]int = [3]int{3, 6, 9}
fmt.Printf("数组的类型为%T\n", arr1)
var arr2 = [3]int{1, 4 ,7}
fmt.Printf("数组的类型为%T\n", arr2)
var arr3 = [...]int{4, 5, 6, 7, 8, 9}
fmt.Printf("数组的类型为%T\n", arr3)
var arr4 = [...]int{2: 66, 0: 33, 1: 99, 3:88}
fmt.Printf("数组的类型为%T\n", arr4)
}
//数组的类型为[3]int
//数组的类型为[3]int
//数组的类型为[6]int
//数组的类型为[4]int
2、Go中数组属于值类型,在默认情况下是值传递,因此会进行值拷贝:
package main
import "fmt"
func main(){
var arr1 [3]int = [3]int{3, 6, 9}
test(arr1)
fmt.Println(arr1) // [3 6 9]
}
func test(arr [3]int){
arr[0] = 7
}
3、如果想在其它函数中去修改原来的数组,可以使用引用传递(指针方式):
package main
import "fmt"
func main(){
var arr1 [3]int = [3]int{3, 6, 9}
test(&arr1)
fmt.Println(arr1) // [7 6 9]
}
func test(arr *[3]int){
(*arr)[0] = 7
}
切片
切片是golang中一种特有的数据类型;
数组有其特定的用处,但是确有一些呆板(数组长度固定不可变),所以在Go语言的代码里不是特别常见。相对的切片却是随处可见,切片是一种建立在数组类型之上的抽象,并提供更强大的能力和便捷;
切片是对数组一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口。
package main
import "fmt"
func main(){
var arr1 [6]int = [6]int{3, 6, 9, 1, 4, 7}
slice := arr1[1:3]
fmt.Println("arr1:", arr1);
fmt.Println("slice:", slice);
fmt.Println("slice的元素个数:", len(slice));
fmt.Println("slice的元素容量:", cap(slice));
}
切片定义方式:
1、定义一个切片,然后让切片去引用一个已经创建好的数组:
var arr1 [6]int = [6]int{3, 6, 9, 1, 4, 7}
// slice := arr1[1:3]
var slice []int = arr1[1:3]
// [6 9]
2、通过make内置函数来创建切片,基本语法:var 切片名 []type = make([]type, len, [cap])
var slice2 []int = make([]int, 4, 20)
fmt.Println(slice2)
fmt.Println("切片的长度:", len(slice2))
fmt.Println("切片的容量:", cap(slice2))
slice2[0] = 66
slice2[1] = 88
fmt.Println(slice2)
// [0 0 0 0]
// 切片的长度: 4
// 切片的容量: 20
// [66 88 0 0]
make底层创建一个数组,对外不可见,所以不可直接操作这个数组,要通过slice间接的去访问各个数组,不可以直接的对数组进行维护和操作
3、方式三:定义一个切片,直接就指定具体数组,使用原理类似make的方法
slice := []int{1, 4, 7}
fmt.Println(slice)
fmt.Println("切片的长度:", len(slice))
fmt.Println("切片的容量:", cap(slice))
//[1 4 7]
//切片的长度: 3
//切片的容量: 3
切片的遍历:同数组
切片的注意事项:
1、切片定义后不可直接使用,需要将其引用到一个数组,或者make一个空间供切片来使用;
2、切片使用不能越界;
3、简写形式:
var slice1 := arr[0:end] // <=> var slice1 := arr[:end]
var slice2 := arr[start:len(arr)] // <=> var slice2 := arr[start:]
var slice3 := arr[0:len(arr)] // <=> var slice3 := arr[:]
4、切片可以继续切片;
5、切片可以动态增长:
package main
import "fmt"
func main(){
var arr [6]int = [6]int{1, 4, 7, 3, 6, 9}
var slice []int = arr[1:4]
fmt.Println(slice)
fmt.Println("slice切片的长度:", len(slice))
slice2 := append(slice, 88, 50)
fmt.Println("slice2:", slice2)
fmt.Println("slice:", slice)
slice = append(slice, 88, 50)
fmt.Println("slice:", slice)
}
底层追加的逻辑是:创建一个新数组,将老数组中的元素复制到新数组中,新的切片中数组指向的是新数组。底层的新数组不能直接维护,必须通过切片间接维护操作。
可以通过append函数将切片追加给切片:
var arr [6]int = [6]int{1, 4, 7, 3, 6, 9}
var slice []int = arr[1:4]
slice2 := []int{11, 22, 33}
slice2 = append(slice2, slice...)
fmt.Println("slice:", slice)
fmt.Println("slice2:", slice2)
// slice: [4 7 3]
// slice2: [11 22 33 4 7 3]
6、切片的拷贝:
var a []int = []int{1, 4, 7, 3, 6, 9}
var b []int = make([]int, 10)
copy(b, a)
fmt.Println(b)
// [1 4 7 3 6 9 0 0 0 0]
map:
类似JavaScript中的对象或其它语言中的集合;
基本语法:
var 变量名 map[keytype]valuetype
变量名 = make(map[keytype]valuetype, len)
var map1 map[string]string = make(map[string]string, 10)
map1["1001"] = "Lucy"
map1["2001"] = "Tom"
map1["3001"] = "Green"
fmt.Println(map1)
// map[1001:Lucy 2001:Tom 3001:Green]
特点:
1、map集合在使用前一定要make;
2、map的key-value是无序的;
3、key是不可以重复的,如遇到重复的,后一个value会替换前一个value;
4、value是可以重复的。
map创建的三种方式:
// 方式1
// 只申明map没分配空间
var a map[string]string
// 必须通过make函数进行初始化,才会分配空间
a = make(map[string]string, 10)
a["1001"] = "Lucy"
a["2001"] = "Tom"
a["3001"] = "Green"
fmt.Println(a)
// 方式2
b := make(map[int]string)
b[2001] = "2001"
b[2002] = "2002"
fmt.Println(b)
// 方式3
c := map[int]string{
20001: "20001",
20002: "20002",
}
fmt.Println(c)
// map[1001:Lucy 2001:Tom 3001:Green]
// map[2001:2001 2002:2002]
// map[20001:20001 20002:20002]
map相关的操作:
1、增加和更新操作:
map["key"] = value // 如果key没有就是增加,如果存在就是更新
2、删除操作:
delete(map, "key") // delete是一个内置函数,如果key存在就删除key-value,如果key不存在则不操作也不报错
3、清空操作:
Go中没有一个专门的方法来删除所有key,可以遍历一下key逐个删除。
或者make一个新的,让原来的成为垃圾,被gc回收
4、查找操作:
value, bool = map[key]
bool为是否返回,要么为true,要么为false
5、获取长度:len
6、遍历:for-range
面向对象
结构体定义:
结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)
package main
import "fmt"
type Teacher struct{
// 变量名大写,表示外界可以访问这个属性
Name string
Age int
School string
}
func main(){
var t1 Teacher
t1.Name = "Tom"
t1.Age = 40
t1.School = "清华大学"
fmt.Println(t1)
}
package main
import "fmt"
type Teacher struct{
Name string
Age int
School string
}
func main(){
var t1 Teacher = Teacher{"Green", 30, "哈佛大学"}
fmt.Println(t1)
}
package main
import "fmt"
type Teacher struct{
Name string
Age int
School string
}
func main(){
// 返回的是结构体指针
var t1 *Teacher = new(Teacher)
(*t1).Name = "Lucy"
(*t1).Age = 30
(*t1).School = "斯坦福大学"
fmt.Println(*t1)
}
package main
import "fmt"
type Student struct{
Age int
}
type Person struct{
Age int
}
func main(){
var s Student = Student{10}
var p Person = Person{11}
s = Student(p)
fmt.Println(s)
fmt.Println(p)
}
结构体进行type重新定义,相当于取别名,Golang认为是一种新的数据类型,但是相互间可以强转。
package main
import "fmt"
type Student struct{
Age int
}
type Stu Student
func main(){
var s1 Student = Student{19}
var s2 Stu = Stu{11}
s1 = Student(s2)
fmt.Println(s1)
fmt.Println(s2)
}
方法
方法是作用在指定的数据类型上,和指定的数据类型绑定,因此自定义类型都可以有方法,而不仅仅是struct
package main
import "fmt"
type Student struct{
Age int
}
func (s Student) test(){
fmt.Print(s.Age)
}
func main(){
var s1 Student = Student{11}
s1.test()
}
注意:
1、test方法中参数名字随便起;
2、结构体Student和test方法绑定,调用test方法必须靠指定的类型:Student;
3、如果其它类型变量调用test方法一定会报错;
4、结构体对象传入test方法中,值传递,和函数参数传递一样;
封装
继承
多态
接口
接口是通过定义抽象方法来约定实现者的规则,概念和其它语言中有点类似,但对于 Go 语言中接口的实现与接口之间耦合性更低,灵活性更高。
定义
现在定义一个 People 的接口,并定义吃喝两个动作。
type People interface {
// 可不写参数名:Eat(string) error
Eat(thing string) error
Drink(thing string) error
}
- Eat 和 Drink 方法不需要具体实现。
- 方法前不需要 func 关键字。
- 方法的参数名称和返回名称可以不写。
定义后就可以直接声明一个该接口类型变量。
var p People
p 变量没有初始化,此时值为 nil 。
实现接口
接口实现的工作是交给自定义类型的,自定义类型实现了接口所有的方法,就实现接口了。
type LaoMiao struct {
Name string
Age int
}
func (l LaoMiao) Eat(thing string) error {
fmt.Println("在公司偷吃" + thing)
return nil
}
func (l LaoMiao) Drink(thing string) error {
fmt.Println("在公司偷喝" + thing)
return nil
}
- LaoMiao 实现了 People 接口中的所有方法,就说明实现了该接口。
- 无需使用其它语言中 implements 关键字显示的去实现。
- 可同时实现多个接口。
再看张图,可能就更清晰了,如下:
图中“实现者”都包含了 A 和 B 接口的方法,那它都实现了这两个接口。
接口的使用
实现了接口之后,就可以将该类型的实例化赋值给接口类型。
var p People = LaoMiao{}
p.Eat("桃子")
// 输出
在公司偷吃桃子
p 为接口类型,实际的实现是 LaoMiao 类型。
你可能好奇,我为啥不直接调用呢,类似如下:
m := LaoMiao{}
m.Eat("桃子")
上面代码没有使用 People 接口类型,但如果我再定义一个类型去实现 People 接口,好处就体现出来了。
type LaoSun struct {
Name string
Age int
}
func (l LaoSun) Eat(thing string) error {
fmt.Println("在车上吃" + thing)
return nil
}
func (l LaoSun) Drink(thing string) error {
fmt.Println("在车上喝" + thing)
return nil
}
又增加了一个类型去实现,看清楚这个是 LaoSun ,上面的那个类型是 LaoMiao 。
现在开始想个问题,如果我想调用这两个类型的方法,并且调用的代码只写一遍,该如何做?喘口气,我告诉你,自然是用接口。
// interface/main.go
// ...
func Run(p People) {
thing1, thing2 := "桃子", "可乐"
p.Eat(thing1)
p.Drink(thing2)
}
func main() {
Run(LaoMiao{})
Run(LaoSun{})
}
// 输出
在公司偷吃桃子
在公司偷喝可乐
在车上吃桃子
在车上喝可乐
https://www.zhihu.com/question/318138275/answer/2127814257
断言
在Go语言的interface中可以是任何类型,所以Go给出了类型断言来判断某一时刻接口中所含有的类型,例如现在给出一个接口,名为InterfaceText:
x,err:=interfaceText.(T)//T是某一种类型
上式是接口断言的一般形式,因为此方法不一定每次都可以完好运行,所以err的作用就是判断是否出错。所以一般接口断言常用以下写法:
if v,err:=InterfaceText.(T);err {//T是一种类型
possess(v)//处理v
return
}
如果转换合法,则v为InterfaceText转换为类型T的值,err为ture,反之err为false。
值得注意的是:InterfaceText必须是接口类型!!!
有些时候若是想仅判断是否含有类型T,可以写为:
if _,err:=InterfaceText.(T);err{
//..
return
}
io
协程
概念
协程是轻量级的(用户态的并发)线程,创建成本很低,与线程不同的是其不受操作系统调度,协程的调度由用户程序提供,go语言中的协程调度器将协程调度到线程中运行,用户使用go关键字即可创建协程。
在一个函数调用前加上go关键字,这次调用就会在一个新的goroutine中并发执行。当被调用的函数返回时,这个goroutine也自动结束。需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃。
goroutine什么时候结束?
goroutine对应的函数结束了,goroutine也就结束。
main函数执行完了,由main函数创建的那些goroutine也会结束。
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func f1(i int){
defer wg.Done()
time.Sleep(time.Second)
fmt.Println(i)
}
func main() {
for i := 0; i<10; i++{
wg.Add(1)
go f1(i)
}
wg.Wait()
}
管道
channle本质就是一个数据结构-队列
数据是先进先出【FIFO : first in first out】
线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
channel时有类型的,一个string的channel只能存放string类型数据。
网络编程
反射
reflect包实现了运行时反射,允许程序操作任意类型的对象。典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。Zero接受一个Type类型参数并返回一个代表该类型零值的Value类型值。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!