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类型值。

posted @   ricnman  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示