1. 结构体
一个结构体(struct)就是一个字段的集合。属于值类型。
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}
type 是定义类型。不是必须使用的。
var s struct{
x string
y int
}
func main() {
s.x = "title"
s.y = 10
}
以上是个匿名结构体,它一般用来组织全局变量,或作为临时数据模板。
// 定义并初始化并赋值给 data
data := struct {
name string
age int
}{
"Richard",
18,
}
2. 函数
golang 中,函数是一等公民,也是一种类型
func foo(x int, y int) { // 无返回值
fmt.Println(x + y)
}
func add(x, y int) int { // 有返回值,形参可简写
return x + y
}
func split(sum int) (x, y int) { // 多返回值
x = sum * 4 / 9
y = sum - x
return // 命名返回值,可以不再显示写出
}
// foo("Hello", 1, 2, 3, 4, 5)
func foo(title string, y ...int) { // 可变参数,这个 y 是 slice 类型
fmt.Println(title, y)
}
多值返回:函数可以返回任意数量的返回值 命名返回值:Go 的返回值可以被命名,并且像变量那样使用
defer 语句会延迟函数的执行直到上层函数返回。 延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。
延迟的函数调用被压入一个栈中。当函数返回时,会按照后进先出的顺序调用被延迟的函数调用
init函数
init() 函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等; 一个包可以出线多个 init() 函数,一个源文件也可以包含多个 init() 函数; 同一个包中多个 init() 函数的执行顺序没有明确定义,但是不同包的init函数是根据包导入的依赖关系决定的; init() 函数在代码中不能被显示调用、不能被引用(赋值给函数变量),否则出现编译错误; 一个包被引用多次,如 A import B,C import B,A import C,B 被引用多次,但 B 包只会初始化一次; 引入包,不可出现死循坏。即 A import B,B import A,这种情况编译失败;
函数可变形参
func hello(num ...int) num 是int类型的切片,接收参数为:hello([]int{5, 6, 7}...) 或 hello(4,5,6) 可以将 slice 传进可变函数,不会创建新的切片,修改 num 将改变传入的 slice 的底层数组。
指针形参
使用指针作为形参,通常用来修改值,如进行基本数据交换
func swap(a, b *int) {
temp := *a // 缓存a的值而不是地址
*a = *b
*b = temp
}
指针形参在传入值的时候,是复制了一份地址,在函数内修改指针的地址,将不会对原指针进行修改,如下例展示:
func ChangeAddr(a *int) {
fmt.Printf("Addr: %v Value: %d \n", a, *a)
b := 20
a = &b
fmt.Printf("Addr: %v Value: %d \n", a, *a)
}
func main() {
var a = 10
var pa = &a
fmt.Printf("Addr: %v Value: %d \n", pa, *pa)
ChangeAddr(pa)
fmt.Printf("Addr: %v Value: %d \n", pa, *pa)
}
输出如下,可以看到函数结束后原变量pa并没被改变。
Addr: 0xc0000140d8 Value: 10
Addr: 0xc0000140d8 Value: 10
Addr: 0xc0000140f8 Value: 20
Addr: 0xc0000140d8 Value: 10
方法
除了常见的结构体可以定义方法外,其他类型也都可以定义:
type N int
func (n N) test(){
fmt.Println(n)
}
func main() {
var n N = 10
f := n.test
f()
f1 := N.test
f1(n) // 等价: N.test(n)
f2 := (*N).test
f2(&n) // 等价:(*N).test(&n)
}
题:方法调用
type People struct {
Name string
}
func (p *People) test() {
println("haha")
}
func main() {
var a *People
a.test() // OK
People{}.test() // Error: People{} 不可寻址
}
3. interface
在 golang 中,接口是第一公民,属于值类型。
实现接口采用 duck typing: 长得像鸭子的就认为它是鸭子。
// 只要实现了 speak 方法的,都是 speaker 类型
type speaker interface {
speak()
}
type cat struct{}
type dog struct{}
func (c cat) speak() {}
func (d dog) speak() {}
func say(x speaker) {
x.speak()
}
// cat dog 都属于 speaker 类型
var c cat
var d dog
say(c)
say(d)
空接口 interface{}
空接口:指没有定义任何方法的接口。因此任何类型都实现了空接口。
map[string]interface{} 这样就可以将 map 的 value 使用任何类型了。
func show(a interface{}) {} 形参就可以传入任意类型的值了。
interface{} 可以接收任何类型的参数,即使是接收指针类型也用 interface{},而不是使用 *interface{}
方法的指针接收者
方法的接收者有两种,一种是值接收者,一种是指针接收者
type People interface {
Speak(string) string
}
type Student struct{}
func (stu *Student) Speak(think string) (talk string) {
...
}
func main() {
var peo People = Student{} // 编译错误,实现接口的是 *Student
fmt.Println(peo.Speak("speak"))
}
另外值接收者,是接收者的类型是一个值,是一个副本,方法内部无法对其真正的接收者做更改;指针接收者,接收者的类型是一个指针,是接收者的引用,对这个引用可修改之间影响真正的接收者。
type Person interface {
getName() string
setName(string)
}
type Student struct {
name string
}
func(p Student) getName() string {
return p.name
}
func(p Student) setName(name string) { // #1
p.name = name
}
func main() {
var student Person = Student{name: "Unknown"} // #2
fmt.Printf("未初始化默认值:s1:%s\n", student.getName())
student.setName("Richard")
fmt.Printf("设置值后:s1:%s\n", student.getName())
}
// 以上无法修改名字
// 如果要能正确使用,将 #1 改为指针接收者:
// func(p *Student) setName(name string) { // #1
// 同时初始化改为:
// var student Person = &Student{name: "Unknown"} // #2
类型断言
类型断言 x.(T)
它返回两个值,第一个为变量的值,第二个为布尔值
例如:v, ok := x.(string) ok 表示是否为 string 类型, v 为具体的值。只能用在接口。
类型断言 i.(T) 只能用在接口上。其中 i 是接口,Type 是类型或接口。编译时会自动检测 i 的动态类型与 Type 是否一致。但是,如果动态类型不存在,则断言总是失败
func main() {
x := interface{}(nil)
y := (*int)(nil)
a := y == x
b := y == nil
_, c := x.(interface{})
println(a, b, c)
fmt.Printf("%T %T \n", x, y)
}
// false true false
// <nil> *int