ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Go基础知识总结

2021-08-04 19:33:59  阅读:111  来源: 互联网

标签:总结 int fmt var 基础知识 Println func Go main


Go基础知识总结

变量声明

Go语言中的变量需要声明以后才可以使用(需要提前定义变量)并且声明后必须使用(不适用会报错)

标准声明

var 变量名 变量类型

example:

var name string
var id int
var isOk bool

多变量声明可以整合在一起

var (
	name string
	id int
	isOk bool

)

变量初始化

Go语言在声明变量的时候,会自动对变量对应的内存区进行初始化操作。

var 变量名 变量类型 = 表达式

example:

var name string = "A2rcher"
var id int = 123

类型推断

Go语言提供了一种简易的方式,不需要提供变量类型,可以根据后面的初始化操作后自动推断其变量类型

var name = "A2rcher"
var id = 123

短变量声明

在之后学习到的函数中,还可以用更简单的方式来声明一个变量(也是最常用的声明方法),使用:=声明并初始化变量

package main
import (
	"fmt"
)
var m = 100 //全局变量

func main(){
	n:=100
	m:=300//声明并初始化局部变量
	fmt.Println(m, n)
}

匿名变量

所谓匿名,通俗的讲就是没有名字的变量,用下划线_表示,匿名变量不占用命名空间(Go语言中变量声明后必须使用,匿名变量除外),不会分配内存,所以匿名变量之间不存在重复声明。

package main

import "fmt"

func foo() (string, int) {

	return "A2rcher", 20
}

func main() {
	x, _ := foo()
	_, y := foo()

	fmt.Println("x=", x, "y=", y)

}

image-20210731155018710

常量

变量中还有一中是常量,它相对于变量来讲是永恒不变的值,对于变量来说一开始可以不做赋值直接定义类型,但是对于常量来讲他需要在定义的时候就要赋值,用const

const p = 3.1415
const e = 2.7182

多常量声明可以整合在一起

const (
	p = 3.1415
	e = 2.7182
)

iota常量计算器

iota是Go语言中的常量计算器,在常量表达式中使用。

package main

import "fmt"

const (
	n1 = iota
	n2
	n3
	n4
)

func main() {

	fmt.Println(n1)//0
	fmt.Println(n2)//1
	fmt.Println(n3)//2
	fmt.Println(n4)//3
}

image-20210731155850749

iota在const关键字出现时将被重置为0。const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。 使用iota能简化定义,在定义枚举时很有用。

使用下划线_可以跳过其中一个

package main

import "fmt"

const (
	n1 = iota//0
	_		 //1
	n2		 //2
	n3		 //3
	n4		 //4
)

func main() {

	fmt.Println(n1)//0
	fmt.Println(n2)//2
	fmt.Println(n3)//3
	fmt.Println(n4)//4
}

image-20210731160041611

数据类型

整型

类型描述
uint8无符号 8位整型 (0 到 255)
uint16无符号 16位整型 (0 到 65535)
uint32无符号 32位整型 (0 到 4294967295)
uint64无符号 64位整型 (0 到 18446744073709551615)
int8有符号 8位整型 (-128 到 127)
int16有符号 16位整型 (-32768 到 32767)
int32有符号 32位整型 (-2147483648 到 2147483647)
int64有符号 64位整型 (-9223372036854775808 到 9223372036854775807)

特殊整型

类型描述
uint32位操作系统上就是uint32,64位操作系统上就是uint64
int32位操作系统上就是int32,64位操作系统上就是int64
uintptr无符号整型,用于存放一个指针

image-20210731161202158

浮点型

Go语言支持两种浮点型数:float32float64。这两种浮点型数据格式遵循IEEE 754标准: float32 的浮点数的最大范围约为 3.4e38,可以使用常量定义:math.MaxFloat32float64 的浮点数的最大范围约为 1.8e308,可以使用一个常量定义:math.MaxFloat64

打印浮点数时,可以使用fmt包配合动词%f,代码如下:

package main
import (
        "fmt"
        "math"
)
func main() {
        fmt.Printf("%f\n", math.Pi)
        fmt.Printf("%.2f\n", math.Pi)
}

复数

complex64和complex128

var c1 complex64
c1 = 1 + 2i
var c2 complex128
c2 = 2 + 3i
fmt.Println(c1)
fmt.Println(c2)

复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位。

布尔

字符串

byte和rune

运算符

运算符描述
+相加
-相减
*相乘
/相除
%求余
运算符描述
==检查两个值是否相等,如果相等返回 True 否则返回 False。
!=检查两个值是否不相等,如果不相等返回 True 否则返回 False。
>检查左边值是否大于右边值,如果是返回 True 否则返回 False。
>=检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。
<检查左边值是否小于右边值,如果是返回 True 否则返回 False。
<=检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。
运算符描述
&&逻辑 AND 运算符。 如果两边的操作数都是 True,则为 True,否则为 False。
||逻辑 OR 运算符。 如果两边的操作数有一个 True,则为 True,否则为 False。
!逻辑 NOT 运算符。 如果条件为 True,则为 False,否则为 True。
运算符描述
&参与运算的两数各对应的二进位相与。 (两位均为1才为1)
|参与运算的两数各对应的二进位相或。 (两位有一个为1就为1)
^参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。 (两位不一样则为1)
<<左移n位就是乘以2的n次方。 “a<<b”是把a的各二进位全部左移b位,高位丢弃,低位补0。
>>右移n位就是除以2的n次方。 “a>>b”是把a的各二进位全部右移b位。
运算符描述
=简单的赋值运算符,将一个表达式的值赋给一个左值
+=相加后再赋值
-=相减后再赋值
*=相乘后再赋值
/=相除后再赋值
%=求余后再赋值
<<=左移后赋值
>>=右移后赋值
&=按位与后赋值
|=按位或后赋值
^=按位异或后赋值

流程控制

if else

for 初始语句;条件表达式;结束语句{
	循环语句
}

example:

package main

import "fmt"

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

image-20210731164417709

for循环的初始语句可以被忽略,但是用作与隔开的分号;还是要有的。

package main

import "fmt"

var i = 0

func main() {
	for ; i < 10; i++ {
		fmt.Println(i)
	}
}

image-20210731164549595

for循环的结束语句也是可以省略掉的

package main

import "fmt"

var i = 0

func main() {
	for i < 10 {
		fmt.Println(i)
		i++
	}
}

image-20210731164654062

for range(键值循环)

键值循环可以用作与遍历,切片,map还有channel。遵循一下规律

  1. 数组、切片、字符串返回索引和值。
  2. map返回键和值。
  3. 通道(channel)只返回通道内的值。

switch

image-20210731164937175

image-20210731164947380

goto(跳转指定标签)

goto的使用可以这么理解,在一个循环中当运行到某一个位置时我不想让他继续运行下去而是跳到其他的地方,这个时候就可以使用goto,用来简化一些代码的实现过程。

func gotoDemo1() {
	var breakFlag bool
	for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			if j == 2 {
				// 设置退出标签
				breakFlag = true
				break
			}
			fmt.Printf("%v-%v\n", i, j)
		}
		// 外层for循环判断
		if breakFlag {
			break
		}
	}
}

使用goto语句能简化代码:

func gotoDemo2() {
	for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			if j == 2 {
				// 设置退出标签
				goto breakTag
			}
			fmt.Printf("%v-%v\n", i, j)
		}
	}
	return
	// 标签
breakTag:
	fmt.Println("结束for循环")
}

break

跳出循环

continue

继续下次循环

数组

数组定义

var 变量名 [元素数量]T
var a [3]int
var b [4]string

数组初始化

package main

import "fmt"

func main() {

	var testArray [3]int
	var numArray = [3]int{1, 2, 3}
	var strArray = [3]string{"a", "b", "c"}
	fmt.Println(testArray)
	fmt.Println(numArray)
	fmt.Println(strArray)
}

image-20210731171327280

使用[…]可以根据初始值自动判断元素有多少

package main

import "fmt"

func main() {

	var testArray [3]int
	var numArray = [...]int{1, 2, 3,4,5}
	var strArray = [...]string{"a", "b", "c", "d","f","v"}
	fmt.Println(testArray)
	fmt.Println(numArray)
	fmt.Println(strArray)
}

image-20210731171526777

数组遍历

for循环遍历

package main

import "fmt"

func main() {

	var testArray [3]int
	var numArray = [...]int{1, 2, 3, 4, 5}
	var strArray = [...]string{"a", "b", "c", "d", "f", "v"}
	fmt.Println(testArray)
	fmt.Println(numArray)
	fmt.Println(strArray)
	for i := 0; i < len(numArray); i++ {
		fmt.Println(i)
	}
	for v := 0; v < len(strArray); v++ {
		fmt.Println(strArray[v])

	}
}

image-20210731172017012

for range遍历

package main

import "fmt"

func main() {

	var testArray [3]int
	var numArray = [...]int{1, 2, 3, 4, 5}
	var strArray = [...]string{"a", "b", "c", "d", "f", "v"}
	fmt.Println(testArray)
	fmt.Println(numArray)
	fmt.Println(strArray)
	for i, v := range numArray {
		fmt.Println(i, v)
	}
	fmt.Println("----------------------------------------------------")
	for m, n := range strArray {
		fmt.Println(m, string(n))
	}
}

image-20210731172321234

运行结果可以明显看出他两个是有区别的。

for循环遍历的结果并没有把角标打印出来,而是直接出的结果。

for range循环遍历的结果时把对应的角标也打印了出来,所以我们可以改善一下,使用匿名变量可以让for range的结果for循环的结果一样。

package main

import "fmt"

func main() {

	var testArray [3]int
	var numArray = [...]int{1, 2, 3, 4, 5}
	var strArray = [...]string{"a", "b", "c", "d", "f", "v"}
	fmt.Println(testArray)
	fmt.Println(numArray)
	fmt.Println(strArray)
	for _, v := range numArray {
		fmt.Println(v)
	}
	fmt.Println("----------------------------------------------------")
	for _, n := range strArray {
		fmt.Println(string(n))
	}
}

image-20210731172626881

同样的原理,可以对多为数组进行遍历。

切片

切片跟数组很像,但是切片相当于在数组类型的基础上做了一层封装,相较于数组可以更快的操作一块数据集合。

切片定义

var 变量名 [元素个数]T

可以看到他的定义方式跟数组一模一样,所以他的初始化也是一样的。

切片长度和容量

用内置函数len(),cap()可以求出切片的长度容量

package main

import "fmt"

func main() {

	var test1Slice [3]int
	fmt.Println(len(test1Slice))
	fmt.Println(cap(test1Slice))
	var test2Slice = [...]string{"a", "b", "c", "d", "e", "f"}
	fmt.Println(len(test2Slice))
	fmt.Println(cap(test2Slice))
}

image-20210731174954269

表达式

切片表达式中有low和high两个定义来表示切片中的界限值

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	s := a[1:3]  // s := a[low:high]
	fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s))
}
a[2:]  // 等同于 a[2:len(a)]
a[:3]  // 等同于 a[0:3]
a[:]   // 等同于 a[0:len(a)]

make()函数构造切片

make([]T,元素数量,切片容量)

example:

func main(){
	a := make([]int,2,10)
}

参考

image-20210801154140458

判断切片是否为空

一定要使用len(s)==0来判断,不能使用nil比较来判断。

append()方法添加元素

append()方法为切片同台添加元素,可以一次添加一个或者多个,还可以添加另一个其他切片中的元素。

package main

import "fmt"

func main() {
	var s []int
	s = append(s, 1)
	s = append(s, 2, 3, 4)
	s2 := []int{2, 3, 4}
	s = append(s, s2...)
	fmt.Println(s)
}

image-20210801154725219

map使用

map:是一种无序的基于key-value的数据结构,必须初始化才可以使用。

map定义

map[keyType]valueType
  • KeyType:表示键的类型。
  • ValueType:表示键对应的值的类型。

map初始化

map默认初始值为nil,使用make()函数分配内存初始化:

make(map[keyType]valueType,[cap])

其中cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容量。

map使用

package main

import "fmt"

func main() {

	sourceMap := make(map[string]int, 20)
	sourceMap["A2rcher"] = 20
	sourceMap["emo"] = 30
	fmt.Println(sourceMap)
	fmt.Printf("type  %T\n", sourceMap)

}

image-20210801155647389

map可以在声明的时候就填充元素:

package main

import "fmt"

func main() {

	sourceMap := make(map[string]int, 20)
	sourceMap["A2rcher"] = 20
	sourceMap["emo"] = 30
	fmt.Println(sourceMap)
	fmt.Printf("type  %T\n", sourceMap)
	fmt.Println("***************")
	sourceTest := map[string]int{
		"lucher": 20,
		"xs":10,
	}
	fmt.Println(sourceTest)


}

image-20210801160742133

判断键值对是否存在

package main

import "fmt"

func main() {

	sourceMap := make(map[string]int, 20)
	sourceMap["A2rcher"] = 20
	sourceMap["emo"] = 30
	fmt.Println(sourceMap)
	fmt.Printf("type  %T\n", sourceMap)
	fmt.Println("***************")
	sourceTest := map[string]int{
		"lucher": 20,
		"xs":     10,
	}
	fmt.Println(sourceTest)
	fmt.Printf("type %T\n", sourceTest)

	fmt.Println("______________________________")
	v, ok := sourceMap["emo"]
	if !ok {
		fmt.Println("查无此人")
	} else {
		fmt.Println(v)
	}

}

image-20210801161216067

map遍历

for range遍历

package main

import "fmt"

func main() {

	sourceMap := make(map[string]int, 20)
	sourceMap["a"] = 90
	sourceMap["b"] = 100
	sourceMap["c"] = 60

	for k, v := range sourceMap {
		fmt.Println(k, v)
	}
}

如果只想遍历前面的key时,可以把v省略。但要是想遍历value时,就需要匿名变量的帮助了。

package main

import "fmt"

func main() {

	sourceMap := make(map[string]int, 20)
	sourceMap["赵"] = 90
	sourceMap["钱"] = 100
	sourceMap["孙"] = 60

	for k, v := range sourceMap {
		fmt.Println(k, v)
	}
	for k := range sourceMap {
		fmt.Println(k)
	}
	for _, m := range sourceMap {
		fmt.Println(m)

	}
}

image-20210801161812669

delete()函数删除键值对

image-20210801161851688

元素为map类型的切片

image-20210801162638230

这个可以理解为在切片里面,各个元素得类型是map。

例如:

var a = make([]map[string]int,3)

值为切片类型的map

image-20210801162706044

这个可以理解为在map函数里面key值是切片。

例如:

var a1 = make(map[string][]int,3)

函数

函数.最重要的一部分

函数定义

func 函数名(参数)(返回值){
	函数体
}

example:

func TestFunction(x int,y,int)int{
	return x + y
}

对于参数部分如果两个参数类型是一样的可以简化

func TestFunction(x,y int)int{
	return x + y
}

可变参数

可变参数就是参数数量不固定,参考数组...

image-20210801164303983

返回值

Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。

func calc(x, y int) (int, int) {
	sum := x + y
	sub := x - y
	return sum, sub
}

返回值命名

函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。

例如:

func calc(x, y int) (sum, sub int) {
	sum = x + y
	sub = x - y
	return
}

返回值补充

当我们的一个函数返回值类型为slice时,nil可以看做是一个有效的slice,没必要显示返回一个长度为0的切片。

func someFunc(x string) []int {
	if x == "" {
		return nil // 没必要返回[]int{}
	}
	...
}

全局变量

在函数外定义的变量,函数内可以访问到全局变量

局部变量

在函数内定义的变量,只能在函数内访问得到。

注意:

如果局部变量和全局变量成名,有限访问局部变量。

定义函数类型

type 函数类型 func(int ,int) int

函数作为参数

package main

import "fmt"

func add(x, y int) int {
	return x + y

}

func calc(x, y int, op func(int, int) int) int {
	return op(x, y)
}
func main() {
	ret2 := calc(10, 20, add)
	fmt.Println(ret2)

}

函数作为返回值

func do(s string) (func(int, int) int, error) {
	switch s {
	case "+":
		return add, nil
	case "-":
		return sub, nil
	default:
		err := errors.New("无法识别的操作符")
		return nil, err
	}
}

匿名函数

匿名函数就是没有名字的函数,可以把函数赋值给变量,也可以把函数作为返回值。

func(参数)(返回值){
	函数体

}

匿名函数没有办法像普通函数那样子调用,他需要保存在某一个变量中(就是赋值),然后在执行。

package main

import "fmt"

func test() {
	func () {
		fmt.Println("匿 名 函 数")
	}()
}
func main() {
	test()
}


匿名函数执行:

func test() {
	func () {
		fmt.Println("匿 名 函 数")
	}()//在后面加上括号就相当于执行
}

或者赋值:

package main

import "fmt"

func test() func() {
	return func() {
		fmt.Println("匿 名 函 数")
	}
}
func main() {
	r := test()
	r()

}

闭包

闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境

package main

import "fmt"

//闭包简单示例  //闭包概念 闭包=函数+外层变量的引用

func test() func() {
	name := "A2rcher"
	return func() {
		fmt.Println("匿 名 函 数",name) // 如果在匿名函数中找不到调用的变量,他就会向外层去找
	}									//这个外层变量并不是全局变量,而是函数test()中的局部变量
}
func main() {
	r := test() // r引用了函数test()中的变量还有匿名函数 ,可以说r此时就是一个闭包
	r()

}

闭包还可以这么写(比较典型的一个例子)

package main

import "fmt"

//闭包简单示例  //闭包概念 闭包=函数+外层变量的引用

func test(name string) func() {

	return func() {
		fmt.Println("匿 名 函 数", name) 
	}
}
func main() {
	r := test("A2rcher") 
	r()

}

example:

image-20210802160654350

两个很经典的例子 [参考博客](Go语言基础之函数 | 李文周的博客 (liwenzhou.com))

panic & recover & defer

image-20210802161948473

panic/recover:可以理解为异常处理模式(但是Go语言中并没有异常处理机制,只是这样方便理解),

package main

import "fmt"

//panic and recover

func a() {
	fmt.Println("func is a")
}

//recover必须配合defer使用,而且defer一定要在panic前定义。
func b() {
	defer func() {
		err := recover()
		if err != nil { //如果err不等于nil,说明这个程序出错
			fmt.Println("func b is err ")
		}
	}()
	panic("func is b")
}

func c() {
	fmt.Println("func is c")
}

func main() {
	a()
	b()
	c()

}

程序运行期间funcB中引发了panic导致程序崩溃,异常退出了。这个时候我们就可以通过recover将程序恢复回来,继续往后执行。

内置函数

内置函数介绍
close主要用来关闭channel
len用来求长度,比如string、array、slice、map、channel
new用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
make用来分配内存,主要用来分配引用类型,比如chan、map、slice
append用来追加元素到数组、slice中
panic和recover用来做错误处理

指针

Go语言中指针没有偏移跟运算,只需要搞清楚三个概念和记住两个符号:指针地址,指针类型,指针取值,&(取地址)和*(根据地址取值)。。

贴个链接吧,大佬还是大佬

结构体

类型别名和自定义类型

type定义关键字类型

type myInt int
//将myInt定义成int类型

结构体定义

type 类型名 struct{
	字段名 字段类型
	字段名 字段类型
	...
}

由于Go语言不是面向对象编程语言,是一个面向接口的变成语言,所以他本身不想java那样有多态继承等关系,在Go语言中通过struct来实现面向对象的。(struct YYDS!)

  • 类型名:标识自定义结构体的名称,在同一个包内不能重复。
  • 字段名:表示结构体字段名。结构体中的字段名必须唯一。
  • 字段类型:表示结构体字段的具体类型。

很简单的例子:

type Person struct{
	name string
	address string
	age int
	sex string
}

自定义一个Person类型,他有name,address,age,sex四个字段分别代表个自内容。如果我要使用Person中某一个字段,我可以直接调用Person就行了。

结构体实例化

结构体实例化后才可以分配内存使用(实例化后才可以使用自定义类型)

由于结构体本身就是一共类型,所以在声明的时候可以像声明变量一样声明结构体

var 结构体实例 结构体类型

example:

package main

import "fmt"

type Person struct {
	name string
	age  int
}

func main() {

	var p1 Person
    //通过.来访问结构体的字段(成员变量),例如p1.name和p1.age等。
	p1.name = "A2rcher"
	p1.age = 20

	fmt.Println(p1)
}

image-20210802170055087

匿名结构体

匿名结构体是用来处理一些临时的数据,比如说我现在A,B两个函数,但是我临时需要使用c数据,这个时候可以用到匿名结构体。

package main

import "fmt"

type Person struct {
	name string
	age  int
}

func main() {

	var p1 Person
	p1.name = "A2rcher"
	p1.age = 20

	var user struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	}
	user.Age = 30
	user.Name = "emo"

	fmt.Println(p1)
	fmt.Println(user)

}

image-20210802170532659

指针类型结构体

指针类型结构体是绝大部分情况下用到的,可以通过new关键字对结构体进行实例化得到结构体的地址。(new跟make的区别以及使用的场景参考函数中的内置函数以及指针部分)

方法和接收者

package main

import "fmt"

//Person是一个结构体
type Person struct {
	name string
	age  int
}

//构造函数 是一个Person类型的构造函数
func NewPerson(name string, age int) *Person {
	return &Person{
		name: name,
		age:  age,
	}
}

//定义方法
func (p Person) Dream() {
	fmt.Printf("%s的梦想是学好Go语言\n", p.name)

}
func main() {

	p1 := NewPerson("A2rcher", int(20))
	p1.Dream()
}

嵌套结构体

嵌套结构体就是字面意思,结构体中再套结构体

package main

import "fmt"

//嵌套结构体

type Address struct {
	Province string
	City     string
}
type Person struct {
	Name    string
	Gender  string
	Age     int
	Address Address //嵌套另外一个结构体

}

func main() {
	p1 := Person{
		Name:   "A2rcher",
		Gender: "男",
		Age:    20,
		Address: Address{
			Province: "aaa",
			City:     "fas",
		},
	}
	fmt.Printf("%#v \n", p1)
	fmt.Println(p1.Name, p1.Gender, p1.Age, p1.Address)

}

image-20210803142814712

对于结构体嵌套部分呢,如果结构体太多,嵌套起来看着就很乱,到最后调用的时候也有可能搞混,所以可以引入一个新的概念–>结构体的匿名字段

结构体的匿名字段

通俗的讲就是结构体里的字段是没有名字的,像这样

type Test struct{
	string
	int
}

正因为结构体的字段是匿名的(没有名字的),所以会出现一些特殊情况,比方说name跟gender都是string类型,如果使用结构体的匿名字段就会出现重复,就报错了。

那结构体的匿名字段用在什么情况下呢? 回到嵌套结构体部分,

image-20210803142814712

我们自己定义了一个类型名为Address,嵌套再Person中,那如果我们使用结构体的匿名字段是不是就可以省略掉前面定义的变量名而直接使用类型名,所以代码可以改成这样

package main

import "fmt"

//嵌套结构体

type Address struct {
	Province string
	City     string
}
type Person struct {
	Name    string
	Gender  string
	Age     int
	Address //嵌套另外一个结构体 结构体的匿名字段

}

func main() {
	p1 := Person{
		Name:   "A2rcher",
		Gender: "男",
		Age:    20,
		Address: Address{
			Province: "aaa",
			City:     "fas",
		},
	}
	fmt.Printf("%#v \n", p1)
	fmt.Println(p1.Province, p1.City)

}

依然可以调用。

结构体的嵌套可以模拟继承关系

//Animal 动物
type Animal struct {
	name string
}

func (a *Animal) move() {
	fmt.Printf("%s会动!\n", a.name)
}

//Dog 狗
type Dog struct {
	Feet    int8
	*Animal //通过嵌套匿名结构体实现继承
}

func (d *Dog) wang() {
	fmt.Printf("%s会汪汪汪~\n", d.name)
}

func main() {
	d1 := &Dog{
		Feet: 4,
		Animal: &Animal{ //注意嵌套的是结构体指针
			name: "乐乐",
		},
	}
	d1.wang() //乐乐会汪汪汪~
	d1.move() //乐乐会动!
}

接口

重要的事情说三遍!!!

结构是一种类型,一种抽象的类型!

结构是一种类型,一种抽象的类型!

结构是一种类型,一种抽象的类型!

为什么会存在接口?他跟其他语言种的接口有什么区别?

Go语言是一个面向接口变成,接口的定义方法:

type 接口类型名 interface{
	方法名1 (参数列表1) 返回值列表1
	......
}

接口的存在相当于把一些函数实现的重复的方法归并起来,可以理解为定义了一种规则,只关心(方法),不关心方法中的(数据)。

实现接口的条件

接口可以理解为需要实现方法的一个列表,通常定义接口名再方法名后面+er®

//定义一个mover接口
type mover interface{
	move()
}

接口类型变量

接口类型变量能够存储所有实现了该接口的实例。

func main() {
	var x mover // 声明一个mover类型的变量x
	a := cat{}  // 实例化一个cat
	b := dog{}  // 实例化一个dog
	x = a       // 可以把cat实例直接赋值给x
	x.say()     // 喵喵喵
	x = b       // 可以把dog实例直接赋值给x
	x.say()     // 汪汪汪
}

值接收者和指针接收者实现接口的区别

使用值接收者实现接口和使用指针接收者实现接口有什么区别呢?接下来我们通过一个例子看一下其中的区别。

我们有一个Mover接口和一个dog结构体。

type Mover interface {
	move()
}

type dog struct {}

值接收者实现接口

func (d dog) move() {
	fmt.Println("狗会动")
}

此时实现接口的是dog类型:

func main() {
	var x Mover
	var wangcai = dog{} // 旺财是dog类型
	x = wangcai         // x可以接收dog类型
	var fugui = &dog{}  // 富贵是*dog类型
	x = fugui           // x可以接收*dog类型
	x.move()
}

从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是dog结构体还是结构体指针*dog类型的变量都可以赋值给该接口变量。因为Go语言中有对指针类型变量求值的语法糖,dog指针fugui内部会自动求值*fugui

指针接收者实现接口

同样的代码我们再来测试一下使用指针接收者有什么区别:

func (d *dog) move() {
	fmt.Println("狗会动")
}
func main() {
	var x Mover
	var wangcai = dog{} // 旺财是dog类型
	x = wangcai         // x不可以接收dog类型
	var fugui = &dog{}  // 富贵是*dog类型
	x = fugui           // x可以接收*dog类型
}

此时实现Mover接口的是*dog类型,所以不能给x传入dog类型的wangcai,此时x只能存储*dog类型的值。

空接口

空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。

空接口类型的变量可以存储任意类型的变量。

func main() {
	// 定义一个空接口x
	var x interface{}
	s := "Hello"
	x = s
	fmt.Printf("type:%T value:%v\n", x, x)
	i := 100
	x = i
	fmt.Printf("type:%T value:%v\n", x, x)
	b := true
	x = b
	fmt.Printf("type:%T value:%v\n", x, x)
}

空接口应用:

  1. 作为函数的参数
  2. 作为mao的值
// 空接口作为函数参数
func show(a interface{}) {
	fmt.Printf("type:%T value:%v\n", a, a)
}
// 空接口作为map值
	var studentInfo = make(map[string]interface{})
	studentInfo["name"] = "A2rcher"
	studentInfo["age"] = 18
	studentInfo["married"] = false
	fmt.Println(studentInfo)

并发

goroutine

标签:总结,int,fmt,var,基础知识,Println,func,Go,main
来源: https://blog.csdn.net/qq_45674677/article/details/119391215

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有