golang 系列 (三) 结构体,接口,指针,以及各种循环与判断的高级语法

个人博客首发 http://www.geekqian.com/post/919b1b0c.html

结构体和方法

Go 中可以在 结构体 (Struct) 中封装属性和操作, 概念类似 java 中的类. 定义方式如下:

type Student struct {
	Name string
	Age int
	Gender string
}

实现方式如下:

s1 := Student{Name:"Mike", Age:11, Gender:"boy"}
// 可以如此简写, 但必须注意参数顺序的一一对应.
s2 := Student{"Rose", 12, "girl"}
s3 := new (Student)
fmt.Println(s1.Name, s2.Gender, s3.Age) // 值为 Mike, girl, 0

匿名结构体:

s3 := struct {
    Name string
	Age int
	Gender string
}{"Robert", "boy", 13}

注意: 匿名结构体无法定义方法.

因为为结构体定义方法要在结构体的外部. 如下:

type Student struct {
	Name string
	Age int
	Gender string
}

// * 是指向 Student 结构体类型
func (s *Student) fmtName() {
	fmt.Println(s.Name)
} 

func main() {
	s3 := new (Student)
	s3.Name = "王二狗"
	s3.fmtName()
}

结构体继承

直接看代码:

// 定义结构体 Person
type Person struct {
	Country string
}

// 定义结构体 Student 并继承 Person
type Student struct {
	Name string
	Age int
	Gender string
	Person // 继承 Person 所有属性与方法
}

// * 是指向 Student 结构体类型
func (s *Student) fmtName() {
	fmt.Println(s.Name)
} 

// 打印国家
func (p *Person) fmtCountry() {
	fmt.Println(p.Country)
} 

func main() {
	s1 := new (Student)
	// 这是 Person 的属性与方法
	s1.Country = "中国"
	s1.fmtCountry()
	// 这是 Student 的属性与方法
	s1.Name = "王二狗"
	s1.fmtName()
}

接口

接口是用来保存共性方法的容器. 看如下代码定义:

// 定义接口
type Skill interface {
	Say() string
	Eat()
}

// 定义结构体 Person
type Person struct {
	// 属性
	Country string
}

// 定义结构体 Student 并继承 Person
type Student struct {
	Name string
	Age int
	Gender string
	Person // 继承 Person 所有属性与方法
}

// 实现接口方法
func (s Student) Say() string{
	fmt.Println("我叫" + s.Name + ",我来自" + s.Country)
	return "over"
}

func (s Student) Eat() {
	fmt.Println("我吃茶叶蛋")
}

func main() {
	s1 := new (Student)
	// 这是 Person 的属性与方法
	s1.Country = "中国"
	s1.fmtCountry()
	// 这是 Student 的属性与方法
	s1.Name = "王二狗"
	msg := s1.Say()
	fmt.Println(msg)
	s1.Eat()
}

指针

指针涉及到两个操作符 &* , & 代表 取址符 * 代表 声明符 / 取值符

取址符 指的是取得内存地址的意思. 例如:

var a int = 1
fmt.Println("变量a的内存地址是: " , &a) 

输出结果为:

// 变量a的内存地址是:  0xc00004a090

声明符 在使用指针时需要为指针值声明一个类型, 这个类型就是用 声明符 来定义的. 例如:

// 声明指针变量p 指针类型为int
var p *int 

取值符 使用指针变量获取指针值

// 获取指针值
var b = *p

例子演示 :

// 定义变量a并赋值
var a int = 1
// 声明指针变量p 指针类型为int
var p *int 
// 获取变量a的内存地址赋值给p
p = &a

fmt.Println("变量a的内存地址是: " , &a)
fmt.Println("指针变量p的存储地址是: " , p)
fmt.Println("指针变量p的值是: " , *p)

注意: 这里 p 的存储地址与内存地址是有区别的, 我们可以把 p 看做是一个容器, 存储了一个 a 的内存地址, 而 p 本身也是指向一个内存地址的, 所以如果使用 &p 这种写法的话就会得到 p 本身所指向的内存地址.而不是储存的 a 的地址.

声明指针数组

使用下面这种方式声明:

const len int = 3
var ptr [len]*int  

实例:

func main() {
	// 定义常量长度
	const len int = 3;
	// 定义指针数组
	var ptr [len]*int 
	// 定义整数数组
	arr := []int{1, 2, 3}
	
	var i int
	for i = 0; i < len; i ++{
		// 把数组值的内存地址储存到指针数组中
		ptr[i] = &arr[i]
	}

	// 遍历打印
	for  i = 0; i < len; i++ {
		fmt.Printf("a[%d] = %d\n", i, *ptr[i] )
	}
}

打印结果为:

a[0] = 1
a[1] = 2
a[2] = 3

指向指针的指针

顾名思义, 指针也可以储存另一个指针的地址. 定义方式如下:

var ptr **int;

实例:

func main() {
	// 定义变量a
	var a int = 1
	// 定义指针
	var ptr *int
	// 定义指向指针的指针
	var pptr **int

	// 定义指针 ptr 地址
	ptr = &a
	// 定义指针 pptr 地址
	pptr = &ptr

	fmt.Printf("指针变量 *ptr = %d\n", *ptr )
	fmt.Println("指针变量 *pptr = ", *pptr )
}	

打印结果如下:

指针变量 *ptr = 1
指针变量 *pptr =  0xc00004a068	

if、switch、for、select

各种判断选择语句

if

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	// 程序重启后,产生的随机数和上次一样。是因为程序使用了相同的种子, 使用rand.Seed(seed)来设置一个不同的种子值。
	// 利用当前时间的UNIX时间戳初始化rand包
	rand.Seed(time.Now().UnixNano()) 
	
	// 定义变量a,b 并用随机数赋值
	var a int = rand.Intn(100)
	var b int = rand.Intn(100)

	// 使用if判断两个数值
	if a > b {
		fmt.Printf("结果是 a (%d) > b (%d)", a, b)
	} else if b > a {
		fmt.Printf("结果是 b (%d) > a (%d)", b, a)
	}
}

也可以使用这种方式

func main() {
	// 定义变量 i
	var i int
	// 判断是否小于 100
	if i := 1; i < 100 {
		fmt.Printf("结果是 i (%d) < 100 \n", i)
	}
	
	// 打印 i 的值
	fmt.Printf("i = %d", i)
}

打印结果:

结果是 i (1) < 100
i = 0	

由此可得出结论, 在 if 中定义的 变量 i 是与外部定义的变量 i 是相互独立的.

switch

func main() {
	// 定义变量 s
	s := "golang"
	
	switch s {
		case "java" :
			fmt.Println("使用的语言是java")
		case "PHP" :
			fmt.Println("使用的语言是PHP")
		case "golang" :
			fmt.Println("使用的语言是golang")
		default :
			fmt.Println("不是匹配的语言")
	}
}

if 相同, 也可以这么定义:

func main() {
	// 定义变量 s
	arr := []string{"java", "golang", "PHP", "Python"}
	
	switch s := arr[3]; s {
		case "java" :
			fmt.Println("使用的语言是java")
		case "PHP" :
			fmt.Println("使用的语言是PHP")
		case "golang" :
			fmt.Println("使用的语言是golang")
		default :
			fmt.Printf("%v不是匹配的语言", s)
	}
}

打印的结果为

Python不是匹配的语言变量

类型switch 判断类型的语句.

func main() {
	// 定义变量 i
	v := 1

	// 使用类型 switch 判断
	switch i := interface{}(v).(type) {
		case int, int8, int16, int32, int64:
		    fmt.Printf("变量 %d 的类型为 %T. \n", i, v)
		case uint, uint8, uint16, uint32, uint64:
		    fmt.Printf("变量 %d 的类型为 %T. \n", i, v)
		default:
		    fmt.Printf("%v不是匹配的类型", i)
	}
}

打印的结果为

变量 1 的类型为 int.

for

// 定义条件循环. 
for i := 0; i < 10; i++ {
    fmt.Print(i, " ")
}

// 定义死循环
for {
	fmt.Print("abc")
}

range

特殊的范围表达式, 用法如下:

func main() {
	// 定义数组
	arrs := []int{1, 2, 3 ,4}

	// 使用 range 表达式会返回一个 key, value 形式的返回值
	for k , v := range arrs {
		fmt.Printf("key = %d, value = %d \n", k, v)
	}
}

打印结果为 :

key = 0, value = 1 
key = 1, value = 2 
key = 2, value = 3 
key = 3, value = 4 

再举几个栗子 :

栗子1

// 如果只想获取值, 可使用空白符 _ 代表不接收 key
for _ , v := range arrs {
	fmt.Printf("value = %d \n", v)
}

value = 1 
value = 2 
value = 3 
value = 4 

栗子2

// 遍历字符串
for k, v := range "go语言"{
	fmt.Printf("key = %d, value = %c\n", k , v)
}

key = 0, value = g
key = 1, value = o
key = 2, value = 语
key = 5, value = 言

注意: key 为何不是连续的? 因为一个中文字符在经过UTF-8编码之后会表现为三个字节。如下图:

select

select 语句类似于 switch 语句,但是 select 会随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行.

select 语句的语法特点:

  • 每个 case 都必须是一个通信
  • 所有 channel 表达式都会被求值
  • 所有被发送的表达式都会被求值
  • 如果任意某个通信可以进行,它就执行;其他被忽略。
  • 如果有多个 case 都可以运行,select 会随机公平地选出一个执行。其他不会执行。

否则:

  1. 如果有 default 子句,则执行该语句。
  2. 如果没有 default 字句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。

栗子1

// 测试select是否随机执行case
for i := 0 ; i < 5 ; i++ {
	c1 := make(chan int , 1)
	c1 <- 1
	c2 := make(chan int , 1)
	c3 := make(chan int , 1)
	c3 <- 3
	var i1, i2 int
	select {
	case i1 = <- c1 :
		fmt.Printf("i1 = %d \n", i1)
	case c2 <- i2 :
		fmt.Printf("发送 i2 (%d) 到通道 c2\n", i2)
	case i3, ok := <- c3 :
		if ok{
			fmt.Printf("i3 = %d \n", i3)
		} else {
			fmt.Printf("c3 is closed")
		}
	default:
		fmt.Printf("---------当前没有通信--------\n\n")
	}
}

打印结果 :

发送 i2 (0) 到通道 c2
i1 = 1 
i3 = 3 
发送 i2 (0) 到通道 c2
发送 i2 (0) 到通道 c2

栗子2

// 测试select是否在匹配完所有合适的case后进入默认语句
for i := 0 ; i < 3 ; i++ {
	c1 := make(chan int , 1)
	c1 <- 1
	c2 := make(chan int , 1)
	c3 := make(chan int , 1)
	c3 <- 3
	var i1, i2 int
	for i := 0 ; i < 4 ; i++ {
		select {
			case i1 = <- c1 :
				fmt.Printf("i1 = %d \n", i1)
			case c2 <- i2 :
				fmt.Printf("发送 i2 (%d) 到通道 c2\n", i2)
			case i3, ok := <- c3 :
				if ok{
					fmt.Printf("i3 = %d \n", i3)
				} else {
					fmt.Printf("c3 is closed")
				}
			default:
				fmt.Printf("---------当前没有通信--------\n")
		}
	}
}

打印结果 :

发送 i2 (0) 到通道 c2
i1 = 1 
i3 = 3 
---------当前没有通信--------
i1 = 1 
i3 = 3 
发送 i2 (0) 到通道 c2
---------当前没有通信--------
发送 i2 (0) 到通道 c2
i3 = 3 
i1 = 1 
---------当前没有通信--------

你可能感兴趣的:(Go)