3rsh1
/single dog/college student/ctfer
3rsh1's Blog

go语言入门

go入门

https://studygolang.com/pkgdoc
https://godoc.org/golang.org/x/net/dns/dnsmessage#Resource

变量

var 变量名 数据类型

批量声明未初始化的变量:

var (
    a int
    v string
)
var a=20
a:=20

多重赋值:

a,b=b,a

按自左到右的顺序进行赋值。

基本数据类型

整型:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20210204204205.png

浮点型:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20210204204301.png

字符串:

字符串在go中是以基本数据类型出现的,所以就可以像使用别的基本数据类型一样使用字符串类型。多行字符串用反引号括起来。其余的可以用双引号括起来。

var s string
s = "hello world~"

字符:

字符串中的每个元素就叫做字符,用单引号。go中有两种字符:

byte    表示utf-8编码的单个字符 uint8 的别称
rune    便是unicode编码的单个字符 int32的别称

语法

for循环

语法形式一:

package main

import "fmt"

func main(){
    for i:=0;i<10;i++{
        fmt.Print(i)
    }
}

循环的三个部分都是可选的,但是分号不可以省略。第一部分是初始化语句,第二部分是执行前判断语句,控制循环的开关,第三部分是执行完之后的执行代码。

package main

import "fmt"

func main(){
    var i int =0
    for ;i<10;i++{
        fmt.Print(i)
    }
}

省略循环前判断语句,循环体内用break跳出。

package main

import "fmt"

func main(){
    var i int =0
    for ;;i++{
        if i == 10{
            break
        }else {
            fmt.Print(i)
        }
    }
}

语法形式二:

只有循环前判断语句,类似于while

package main

import "fmt"

func main(){
    var i int = 0
    for i < 10{
        fmt.Println(i)
        i++
    }
}

语法形式三:

package main

import "fmt"

func main(){
    var i string = "test"
    for a,value := range i{
        fmt.Printf("%d,%c\n",a,value)
    }
}

这里的range格式可以对stringslicearraymapchannel进行迭代循环。map返回键值对,其余的大部分只有索引和值。

breakcontinue类似于cbreakcontinue

gotoLOOP

这里的goto可以无条件的跳到代码中的任何一行,通常和循环语句配合使用。

import "fmt"

func main(){
    var C int
    C=1
    test:
        for C<50{
            C=C+1
            for i:=2;i<C;i++{
                if C%i==0{
                    goto test
                }
            }
            fmt.Println(C)
        }
}

打印出来50以内的素数。

函数和指针

go语言中全局变量和局部变量的名字可以相同,但函数中的局部变量会被优先使用。一个小例子:

package main

import "fmt"

var a1 int =7
var b1 int =9

func main(){
    a1,b1,c1:=10,20,0
    fmt.Println(a1)//10
    fmt.Println(b1)//20
    fmt.Println(c1)//0
    sum(a1,b1)
    fmt.Println(c1)//0
}
func sum(a1,b1 int)(c1 int){
    a1++
    b1+=2
    c1=a1+b1
    fmt.Println(a1)//11
    fmt.Println(b1)//22
    fmt.Println(c1)//33
    return c1
}

因为函数也算是一个变量类型所以类,函数也可以当作函数的形参传入:

package main

import (
    "fmt"
    "strings"
)

type myFunc func(string) string

func func_1(arg1 string)(mid1 string){
    for _,value := range arg1{
        mid1 = mid1 + strings.ToLower(string(value))
    }
    return mid1
}

func test(arg1 string, f myFunc)(string){
    return f(arg1)
}
func main(){
    str1:=test("ABCDE",func_1)
    fmt.Println(str1)
}

也可以用type来声明一个新的类型:

type newtype oldtype
type myFunc func(int) string

匿名函数

func(参数列表)(返回参数列表){
    //函数体
}

函数也可以当作值赋值给别的变量:

package main

import "fmt"

func main(){
    func1:= func(a,b int)(int) { return a+b }
    fmt.Println(func1(1,2))
}

另外一种调用方式:

package main

import "fmt"

func main(){
    fmt.Println(func(a,b int)(int) { return a+b }(1,2))
}

还有一种就是直接当作函数的参数:

package main

import (
    "fmt"
    "strings"
)

type myFunc func(string) string

func func_1(arg1 string)(mid1 string){
    for _,value := range arg1{
        mid1 = mid1 + strings.ToLower(string(value))
    }
    return mid1
}

func test(arg1 string, f myFunc)(string){
    return f(arg1)
}
func main(){
    str1:=test("ABCDE",func (arg1 string)(mid1 string){
        for _,value := range arg1{
            mid1 = mid1 + strings.ToLower(string(value))
        }
        return mid1
    })
    fmt.Println(str1)
}

其实就是把之前的函数参数换成了匿名函数,看起来要简洁一点。

闭包

我的理解就是A函数中引用了另外一个函数B,那么当A内启动一个循环之后,B在循环体内。每次循环A都是一个崭新的调用。但是我们可能想要的是B函数状态的叠加,因此可能会用到闭包。

package main

import "fmt"

func main(){
    func1:=test()
    for i:=0 ; i<10 ; i++{
        func1(i)
    }
}
func test()(func(int) int){
    sum:=0
    return func(x int)(int){
        fmt.Printf("%d , ",sum)
        sum=sum+x
        fmt.Printf("%d\n",sum)
        return sum
    }
}

把函数当作返回值的时候似乎可以解决这个问题,因为当函数本身作为返回值被赋给某个变量的时候,这个函数的状态是可以叠加的。因为若是一个全新的函数可能就需要重新赋给变量,但这显然并不是。叠加的状态被赋给变量。

可变参数

func(参数名 ...参数类型)[(返回值列表)]{
    //函数体
}

可以表示函数接受多个参数,参数可以逐个传递,也可以传递一个切片过去。

package main

import "fmt"

func main(){
    fmt.Println(test(10,20,30,40,50,60,10))
    scores := []int{10,20,30,40,50,60,10}
    fmt.Println(test(scores...))
}
func test(scores ...int)(float32){
    var total int
    var count int
    for _,values := range scores{
        total = total + values
        count++
    }
    avg:= float32(total)/float32(count)
    return (avg)
}

这里的切片好像数组,...表示解压缩切片,其实多参数传递过去也是一个切片。一个函数只能有一个可变参数,若是有其他参数需要写在可变函数后面。

指针

指针变量是存储另外一个变量内存地址的变量。变量指向的是内存地址,而指针变量指向的是存放另外一个变量内存地址的内存空间。

& 取地址 //取出来的应该是值的地址,因为变量指向的就是一块存放数据的内存地址

这里需要注意的是go内的指针无法运算。

var ptr1 *int   //表示一个指针变量
*ptr1   //指针变量取值
package main

import "fmt"

func main(){
    var a int =10
    var b *int
    b = &a
    fmt.Println(&a)
    fmt.Println(b)
    fmt.Println(&b)
    fmt.Println(*b)
}

这里的b内存储的是a指向的int数的地址。都是一个萝卜一个坑,不过有的坑里埋的可能是另外某个萝卜的坐标。坑不重要,萝卜才重要。这里的空指针表示为nil

a*b指向的都是同一块内存,若是用其中一个变量作为中介进行修改的话,也会影响另外一个变量指向的内容。这里对于函数也是如此:

package main

import "fmt"

func main(){
    var a int =10
    fmt.Println(a)
    test(&a)
    fmt.Println(a)
}
func test(val *int){
    *val = 20;
}

指针数组,内部存放的都是指针的数组。这里就不演示了,因为和c++中是一样的:

package main

import "fmt"

func main(){
    var a int =10
    var arr1 [1]*int
    arr1[0]=&a
    fmt.Println(arr1)
    fmt.Println(&a)
    fmt.Println(*arr1[0])
}
/*
[0xc00000a0a0]
0xc00000a0a0
10
*/

双重指针,一个萝卜一个坑,这个坑里埋的是另外一个有坐标的坑的坐标。

package main

import "fmt"

func main(){
    var a int =10
    var ptr *int
    var ptr2 **int
    ptr = &a
    ptr2 = &ptr
    fmt.Println(a,&a,ptr,&ptr,ptr2,&ptr2,*ptr2)
}
/*
10 0xc00000a0a0 0xc00000a0a0 0xc000006028 0xc000006028 0xc000006030 0xc00000a0a0
*/

go中严格来说的话,全部都是值传递,没有引用传递。但是通过传递指针可以实现传递引用,这样就仅仅是复制一个内存地址过去,性能要高一些。

GO的内置容器

数组

数组的内存表现是连续的一段区域所以检索非常的快。数组是值类型,不是引用类型。

var 变量名 [数组长度] 数据类型
var a =[4]int{1,2,3,4}
var a =[...]int{1,2,3,4}

获取数组长度:

len(a)

遍历:

for _,value := range a{
    fmt.Println(value)
}

定义多维数组:

var a = [3][4]int
a = {{1,2,3,3},{4,5,6,6},{7,8,9,9}} 

遍历二维数组:

for i:=0;i<len(a);i++{
    for j:=0;j<len(a[0]);j++{
        fmt.Println(a[i][j])
    }
}

切片

切片是长度可变的序列,切片中的每个数据的类型都是相同的。切片引用了数组的对象,因此可以实现追加元素。个人理解可能切片就是指针的集合,指针指向的是数组对象,若是想要增加元素,只需在底层新建一个数组即可。切片的数据结构可以理解为一个结构体,包含了三个元素:指针,指向了数组中切片指定的开始的地方,长度,切片长度,容量,即切片开始位置到数组最后结尾的位置。

var 变量名 []类型
var 变量名 = make([]type,length,capacity)
var a = []int
var b = make([]int,3,5)

初始化的话:

#直接初始化
s:= []int{1,2,3,4}

#截取数组初始化
arr:=[5]int{1,2,3,4,5}
s:=arr[:]
arr[startIndex:endIndex]

左闭右开。

len()

获取其中元素的数量。

cap()

获取容量。数组的长度和容量是相同的。因为前面已经说过了切片只是数组的引用,那么,在切片上的修改会修改数组上的内容吗?

package main

import "fmt"

func main(){
    var a = [5]int{1,2,3,4,5}
    s:=a[:]
    s[0]=2
    fmt.Println(a,&a[0],&s[0])
}
/*
[2 2 3 4 5] 0xc00000c3c0 0xc00000c3c0
*/

果然会修改,神奇。切片是个引用类型。切片的长度和容量似乎会和底层数组对象的长度有关。

append() & copy()

这里的删除似乎只是修改了底层对数组对象的引用。append可以添加元素或者切片。copy是直接复制切片内容到新的切片中,是值的传递。

package main

import "fmt"

func main(){
    var numbers = make([]int,0,20)
    numbers= append(numbers, 0)
    prints(numbers)
    numbers=append(numbers,1)
    prints(numbers)
    numbers=append(numbers,2,3,4,5,6,7)
    prints(numbers)
    mid_1:=[]int{100,200,300,400}
    numbers=append(numbers,mid_1...)
    prints(numbers)
    numbers=numbers[1:]
    prints(numbers)
    numbers=numbers[:len(numbers)-1]
    prints(numbers)
}
func prints(a []int){
    fmt.Println(&a[0],a,len(a),cap(a))
}
/*
0xc000112000 [0] 1 20
0xc000112000 [0 1] 2 20
0xc000112000 [0 1 2 3 4 5 6 7] 8 20
0xc000112000 [0 1 2 3 4 5 6 7 100 200 300 400] 12 20
0xc000112008 [1 2 3 4 5 6 7 100 200 300 400] 11 19
0xc000112008 [1 2 3 4 5 6 7 100 200 300] 10 19
*/

当时自后向前删除元素的时候容量是不会变的,因为这里的容量是建立在底层数组的基础上的,只要你不改变数组长度,或者改变切片第一个元素的位置,那么他的容量就不会变。

map

地图顾名思义,一个地图上的元素对应一个现实中的元素,不多不少,刚好一对一,键值对的集合。因为map的键在一个map中需要是独一无二的,所以就需要map的键是可以比较的,切片和函数等引用类型不能作为map的键的数据类型,但是value可以是任意类型,而且map也是一个引用类型。

声明:

var 变量名 = map[key类型]value类型

var来声明的话,未初始化的map默认值为nilnil map无法存放键值对。所以说需要在声明的时候就初始化,或者使用make来分配内存空间。

var a = make(map[string]string)

这种方式如果不初始化mapmap也不等于nil。查看key对应的数组值是否存在:

value,ok:=map[key]
#ok是bool型的,value是值类型的
import "fmt"

func main(){
    s:=make(map[string]int)
    s["test1"]=1
    s["test2"]=2

    value,ok:=s["test1"]
    if ok {
        fmt.Println(value)
    }
    if value,ok:=s["test2"];ok{
        fmt.Println(s["test2"])
    }
}

delete

delete(map,key)

用于删除一个集合中的某个元素,不返回任何值。

map是引用类型,所以如果赋值给别的变量的话,只是指针的转移,他们指向的是同一块内存。

类与对象

go采用更加灵活的结构体来代替类,都是可以存在属性的数据结构。主要学习结构体,方法和接口。

结构体

type 类型名 struct{
    成员属性1 类型1
    成员属性2 类型2 
    成员属性3,成员属性4 类型3
    ...
}

类型名唯一,因为是当作结构体名字来使用的,成员属性也唯一。结构体只是一种内存布局的描述,只有当实例化结构体之后才算是真正使用了结构体,也是开始占用内存的时候。实例化就是根据结构体的描述新建一块内存区域而已,不用的结构体的实例并没有啥必要的联系。

一个例子:

package main

import (
    "fmt"
)

type teacher struct {
    name string
    age uint8
    sex byte
}

func main(){
    // 1 
    var t1 teacher
    fmt.Println(t1)
    t1.name="test1"
    t1.age = 10
    t1.sex = 1
    fmt.Println(t1)
    // 2 
    t2:=teacher{}
    t2.sex = 0
    t2.name = "test2"
    t2.age = 11
    fmt.Println(t2)
    // 3
    t3:=teacher{
        name: "test3",
        age:  12,
        sex:  1,
    }
    fmt.Println(t3)
    // 4 
    t4 := teacher{"test4",13,0}
    fmt.Println(t4)
}

大概四种初始化方式,如上,第一种是先声明一个结构体的实例然后再进行赋值的操作,第二种是第一种的变形,差不多。第三种是用teacher进行初始化,类似于对象的新建。第四种是第三种的变形,要简单一些不用再指明属性进行赋值。但是需要按顺序来,果然还是推荐第三种方法。

语法糖,对于语言的功能没啥作用,但是可以让程序员编写的时候更加的方便,增加程序的可读性,减少代码出错的机会。

可以使用new()方法对结构体进行实例化,返回值是结构体的指针。这里的new需要的一个参数就是需要实例化的结构体名称。

package main

import "fmt"

type teacher struct {
    name string
    age int8
    sex byte
}

func main(){
    t1 := new(teacher)
    fmt.Printf("%p\n",t1)

    (*t1).age = 10
    (*t1).name = "test"
    (*t1).sex = 0
    fmt.Println(t1)

    t1.name = "test2"
    fmt.Println(t1)

    a1:= [4]int{10,20,30,40}
    a2:=&a1
    fmt.Printf("%p\n",a2)
    fmt.Println(a2[1])
}
/*
0xc000098420
&{test 10 0}
&{test2 10 0}
0xc0000a0140
20
*/

这里的语法糖似乎就是可以把指针当作指针指向的内容。

这里的结构体是值类型,其实已经很显然了,因为结构体的实例被不同的变量引用是没有意义的。你每新建一个结构体的实例就会新分配一块内存,若是被当作函数的参数进行使用,会把实例copy过去。

深拷贝和浅拷贝

值类型就是深拷贝,直接赋值内存区域过去。引用类型是浅拷贝,浅拷贝只是赋值了对象的指针,这里只给出一个例子:

package main

import "fmt"

type teacher struct {
    name string
    age int8
    sex byte
}

func main(){
    t1 := teacher{name: "test",age:10,sex:1}
    t2 := teacher{name: "test",age:10,sex:1}
    fmt.Println(t1)
    copy_test1(&t1)
    fmt.Println(t1)
    fmt.Println(t2)
    copy_test2(t2)
    fmt.Println(t2)
}
func copy_test1(t1 *teacher)  {
    t1.name = "test2"
}
func copy_test2(t1 teacher) {
    t1.name = "test3"
}
/*
{test 10 1}
{test2 10 1}
{test 10 1}
{test 10 1}
*/

匿名结构体

匿名结构体就是没有名字的结构体,不用通过type结构体进行重命名就可以使用。

变量名 := struct{
    //定义成员属性
}(//初始化成员属性)

结构体内的属性也可以是匿名,即没有变量名,只包含变量的类型。默认使用类型名作为字段名,一个类型只能有一个匿名字段。

import "fmt"

type teacher struct {
    name string
    age int8
    sex byte
}

func main(){
    v1:= struct {
        int
        string
    }{10,"test"}
    fmt.Println(v1)
    fmt.Println(v1.int)
    fmt.Println(v1.string)

}
/*
{10 test}
10
test
*/

另外一个例子:

package main

import "fmt"

type teacher struct {
    name string
    age int8
    sex byte
}

func main(){
    func1 := func(a,b int) int {return a+b}
    fmt.Println(func1(1,2))
    struct1:= struct {
        name string
        age int
    }{name:"test",age: 10}
    fmt.Println(struct1)

}
/*
3
{test 10}
*/

这里的初始化方法和普通实例初始化方法相同。

匿名结构体嵌套

这里的结构体嵌套模拟了面向对象编程中的两个关系:一个类作为一个类的属性,一个类作为另外一个类的子类,即父类和子类之间的关系。那么这里可能涉及到两种类型,一个是值类型,另外一个是引用类型。对于一个类作为另外一个类的属性的时候我们更强调的是属性的值所以此时更适合用值类型?而考虑类的继承关系的时候更多考虑的是两个类的相关性,或许更应该用引用类型?

package main

import "fmt"

type address struct {
    p,c string
}

type person struct {
    name string
    age uint8
    addr *address
}

func main(){
    t1:=person{}
    addr1 := address{
        p: "shandong",
        c: "linyi",
    }
    t1.name = "test"
    t1.age = 10
    t1.addr = &addr1
    fmt.Println(t1,t1.addr)

    t1.addr.c = "qingdao"
    fmt.Println(t1,t1.addr)

    t1.addr.c = "jinan"
    fmt.Println(t1,t1.addr)
}

/*
{test 10 0xc000098420} &{shandong linyi}
{test 10 0xc000098420} &{shandong qingdao}
{test 10 0xc000098420} &{shandong jinan}
*/

使用匿名字段可以模仿类之间的继承关系,但是模拟聚合关系的时候一定要采用有名字的结构体作为字段。

package main

import "fmt"

type address struct {
    p,c string
}

type person struct {
    *address
    name string
    age uint8
}

func main(){
    t1:=person{}
    addr1 := address{
        p: "shandong",
        c: "linyi",
    }
    t1.name = "test"
    t1.age = 10
    t1.address = &addr1
    fmt.Println(t1,t1.address)

    t1.address.c = "qingdao"
    fmt.Println(t1,t1.address)

    t1.address.c = "jinan"
    fmt.Println(t1,t1.address)
}

这样的话,子类内父类的实例只能存在一个,似乎有点像继承关系了。若是给予address类型字段名的话,那么person类中就不知存在一个address结构体的实例了。而继承需要的是子类可以使用父类的方法和属性。由于函数也是可以赋值的,那么是不是可以像定义字段一样来定义结构体的方法呢。

方法

方法和函数之间是被包含的关系,函数包含方法。函数是一段具有独立功能的代码,可以被反复多次调用。而方法是一个类的行为功能,只有该类的对象才能调用。

go的方法是有特定接受者的函数,这里接收者的概念类似于this。方法具有接收者,但是函数没有接收者。函数不可以重名,但是方法可以重名。

方法的格式:

func (接收者变量 接收者类型) 方法名(参数列表) (返回值列表){
    //方法体
}

接收者可以是指针类型也可以不是指针类型,可以是struct也可以是非struct

package main

import "fmt"

type employee struct {
    name,currency string
    salary float64
}

func main(){
    emp1 := employee{"test","",2000}
    emp1.printSalary()
    printSalary(emp1)
}

func (e employee) printSalary(){
    fmt.Println(e.name,e.currency,e.salary)
}

func printSalary(e employee)  {
    fmt.Println(e.name,e.currency,e.salary)
}
/*
test 2000
test $ 2000
*/

方法和函数的区别已经很清楚了,若是e employee换成e *employee,那么对于实例的修改就会应用到特定的实例中去,就不多说了。

继承

方法是可以继承的如果匿名结构体的实例中实现了一个方法,那么继承了该结构体的子结构体可以直接使用匿名结构体的方法。

package main

import "fmt"

type A struct {
    id int
    name string
}
type B struct {
    A
    name string
}

func main(){
    a1:=A{
        id:   10,
        name: "test123",
    }
    b1:=B{a1,"test456"}
    a1.func1()
    b1.func1()
}

func (test1 A) func1() {
    fmt.Println("test1.func1")
}

/*
test1.func1
test1.func1
*/

重写

如果父结构体和子结构体中存在名字一样的方法的话,父结构体的方法就会被重写。当结构体存在继承关系的时候,方法的调用采用就近原则。

接口

和面向对象的接口差不多,在接口中指定了类型该具有的方法。然后在类型中决定了方法的实现,如果类型中实现了接口中指明的方法的所有细节的话,就称此方法实现了该接口。go是隐式实现接口的。

定义一个接口:

type 接口名字 interface {
    方法1([参数列表]) [返回值]
    方法2([参数列表]) [返回值]
}

实现接口:

func (变量名 结构体类型) 方法2(参数列表) 返回值2
.....

奇奇怪怪的接口:

package main

import "fmt"

type A struct {
    id int
}

type B struct {
    id int
}

type C struct {
    id int
}
type jiek interface {
    call()
}

func main(){
    var jk1 jiek
    t1:=B{10}
    jk1=&t1
    jk1.call()

}

func (b B) call() {
    fmt.Println(b.id)
}

这样定义似乎也是没问题的,我们需要做的是把实例的指针赋值给接口类型的变量那么我们就可以实现接口的隐式实现了,这样似乎只能通过接口变量来执行接口中的方法,而且通过接口变量没办法对实现该接口的实例进行属性值的修改,emmm,算是一种保护措施??

duck typing

此类型的编程语言注重外在而不过分关注内在,更多的关注是如何使用的,此类语言经常被归类为动态类型语言解释型语言。遇到错误的时候往往只有我们开始运行的时候才会发现,而不像静态语言,在编译时候就会发现这类错误。

所以go采取了折中的方式:

结构体不需要显式地声明它实现了某某接口。只要类型T实现了接口I规定的方法,他就自动实现了接口I

将结构体类型的变量显式地或者隐式地转换为接口类型的变量,那么在编译的时候就会和静态语言一样检查参数的合理性。

一个例子:

package main

import "fmt"

type A struct {
    id int
}

type B struct {
    id int
}

type C struct {
    id int
}
type jiek interface {
    call()
}

func main(){
    var jk1 jiek
    t1:=B{10}
    jk1=t1
    jk1.call()
    test(t1)
}

func (b B) call() {
    fmt.Println(b.id)
}
func (b A) call() {
    fmt.Println(b.id)
}
func test(i1 jiek) {
    i1.call()
}
/*
10
10
*/

凡是以接口变量为参数的函数,其参数可以是任意实现了该接口的结构体。定义的接口类型的变量可以被任意实现了该接口的结构体的实例赋值。那么如果实现了一个接口类型的切片或者数组的话,该容器可以实现存储任何一个实现接口的类对象。

多态

go中的多态是在接口的帮助下实现的,其实借助的就是前一节说的那个特性,接口类型的变量可以被任何实现了该接口的结构体的实例赋值。ps,难道多态不是指的是同一个结构体中的吗。创建一个参数是接口类型的函数,那么只要是实现了该接口的结构体实例就可以传给此函数,那么不同的实例会有不同的行为,虽然我们使用的都是此函数。这里可能给出的是一个统一的输入的地方,就不需要我们分实例去调用他们的函数了。

package main

import "fmt"

type A struct {
    id int
}

type B struct {
    id int
}

type jiek interface {
    call()
}

func main(){
    var t1 = make([]jiek,2,4)
    var a=A{10}
    var b=B{11}
    t1[0]=a
    t1[1]=b
    fmt.Println(t1)
    test(t1)
}

func (b B) call() {
    fmt.Println(b.id)
}
func (b A) call() {
    fmt.Println(b.id)
}
func test(i1 []jiek) {
    for _,value :=range i1{
        value.call()
    }
}
/*
[{10} {11}]
10
11
*/

空接口

就是不包含任何方法的接口,任何结构体都实现了此接口,那么相应的空接口可以表示任何结构体的实例。比如说fmt.Println的参数就是空接口,因为可以输出任意类型的实例。定义一个mapkeystring类型,values是空接口即任意类型。定义一个切片其中可以存储任意类型的数据。

package main

import "fmt"

type s1 struct {
    id int
}

func main(){
    var m1=make(map[string]interface{})
    m1["t1"]=1
    m1["t2"]="test"
    m1["t3"]=s1{10}
    fmt.Println(m1)

    var q1 = make([]interface{},2,10)
    q1[0]=1
    q1[1]="test"                                      
    q1[2]=s1{11}
    fmt.Println(q1)
}
/*
map[t1:1 t2:test t3:{10}]
[1 test {11}]
*/

接口对象转型

如果接口对象是对应的实际类型,那么instance就是转型后的对象,ok的值为true,配合if..else..使用。

instance,ok:=接口对象.实际类型
package main

import "fmt"

type s1 struct {
    id int
}
type i1 interface {
    call()
}
func main(){
    var test1 i1
    instance,_:=test1.(s1)
    instance.id=10
    fmt.Println(instance)
}
func (t1 s1) call() {
    fmt.Println(t1.id)
}
/*
{10}
*/

常用方法

字符串的遍历:

分为按字符遍历和按字节遍历:

package main

import "fmt"

func main() {
    var s string
    s="hello 我是不是在哪见过你"
    fmt.Println(len(s))
    for i,j :=range s{
        fmt.Printf("%d:%c",i,j)
    }
    //按字节遍历
    fmt.Println("")
    for i,j :=range []byte(s){
        fmt.Printf("%d:%x ",i,j)
    }

    //按字符遍历
    fmt.Println("")
    for i,j :=range []rune(s){
        fmt.Printf("%d:%c ",i,j)
    }
}


/*
33
0:h1:e2:l3:l4:o5: 6:我9:是12:不15:是18:在21:哪24:见27:过30:你
0:68 1:65 2:6c 3:6c 4:6f 5:20 6:e6 7:88 8:91 9:e6 10:98 11:af 12:e4 13:b8 14:8d 15:e6 16:98 17:af 18:e5 19:9c 20:a8 21:e5 22:93 23:aa 24:e8 25:a7 26:81 27:e8 28:bf 29:87 30:e4 31:bd 32:a0 
0:h 1:e 2:l 3:l 4:o 5:  6:我 7:是 8:不 9:是 10:在 11:哪 12:见 13:过 14:你 
*/

这里的len()函数计算的是字节的数量,汉字貌似是三个字节。

strings包内字符串处理函数

检索字符串:

package main

import (
    "fmt"
    "strings"
)

func main() {
    var s string
    s="hello 我是不是在哪见过你"
    fmt.Println(strings.Contains(s,"hel"))
    fmt.Println(strings.ContainsAny(s,"abcde"))
    fmt.Println(strings.ContainsRune(s,'a'))
    fmt.Println(strings.Count(s,"l"))
    fmt.Println(strings.HasPrefix(s,"hel"))
    fmt.Println(strings.HasSuffix(s,"你"))
    //若是Index前面加上Last就表示最后一次出现
    fmt.Println(strings.Index(s,"e"))
    fmt.Println(strings.IndexAny(s,"hel"))
    fmt.Println(strings.IndexByte(s,'l'))
    fmt.Println(strings.IndexRune(s,'你'))
    fmt.Println(strings.IndexFunc(s, func(r rune) bool {
        return r=='o'
    }))

}
/*
true
true
false
2
true
true
1
0
2
30
4
*/

分割字符串:

发表评论

textsms
account_circle
email

3rsh1's Blog

go语言入门
go入门 https://studygolang.com/pkgdoc https://godoc.org/golang.org/x/net/dns/dnsmessage#Resource 变量 var 变量名 数据类型 批量声明未初始化的变量: var ( a int …
扫描二维码继续阅读
2021-02-09