摘抄:https://www.luozhiyun.com/archives/211java
以下:数组
type AnimalCategory struct { kingdom string // 界。 phylum string // 门。 class string // 纲。 order string // 目。 family string // 科。 genus string // 属。 species string // 种。 } func (ac AnimalCategory) String() string { return fmt.Sprintf("%s%s%s%s%s%s%s", ac.kingdom, ac.phylum, ac.class, ac.order, ac.family, ac.genus, ac.species) }
咱们在Go中通常构建一个结构体由上面代码块所示。AnimalCategory结构体中有7个string类型的字段,下边有个名叫String的方法,这个方法其实就是java类中的toString方法。其实这个结构体就是java中的类,结构体中有属性,有方法。安全
category := AnimalCategory{species: "cat"} fmt.Printf("The animal category: %s\n", category)
咱们在上面的代码块中初始化了一个AnimalCategory类型的值,并把它赋给了变量category,经过调用fmt.Printf方法调用了category实例内的String方法,⽽⽆需 显式地调⽤它的String⽅法。dom
由于在Go中是没有继承一说,因此使用了嵌入字段的方式来实现类型之间的组合,实现了方法的重用。函数
这里继续用到上面的结构体AnimalCategoryui
type Animal struct { scientificName string // 学名。 AnimalCategory // 动物基本分类。 }
字段声明AnimalCategory表明了Animal类型的⼀个嵌⼊字段。Go语⾔规范规定,若是⼀个字段 的声明中只有字段的类型名⽽没有字段的名称,那么它就是⼀个嵌⼊字段,也能够被称为匿名字段。嵌⼊字段的类型既是类型也是名称。atom
若是要像java中引用字段里面的属性,那么能够这么写:线程
func (a Animal) String() string { return a.AnimalCategory.String() }
这里仍是和java是同样的,可是接下来要讲的却和java有很大区别指针
因为咱们在AnimalCategory中写了一个String的方法,若是咱们没有给Animal写String的方法,那么咱们直接打印会获得什么结果?code
category := AnimalCategory{species: "cat"} animal := Animal{ scientificName: "American Shorthair", AnimalCategory: category, } fmt.Printf("The animal: %s\n", animal)
在这里fmt.Printf函数至关于调用animal的String⽅法。在java中只有父类才会作到方法的覆盖,可是在Go中,嵌⼊字段的⽅法集合会被⽆条件地合并进被嵌⼊类型的⽅法集合中。
若是为Animal类型编写⼀个String⽅法,那么会将嵌⼊字段AnimalCategory的String⽅法被“屏蔽”了,从而调用Animal的String方法。
只 要名称相同,⽆论这两个⽅法的签名是否⼀致,被嵌⼊类型的⽅法都会“屏蔽”掉嵌⼊字段的同名⽅法。也就是说无论返回值类型或者方法参数如何,只要名称相同就会屏蔽掉嵌⼊字段的同名⽅法。
上面咱们的例子其实都是值方法,下面咱们举一个指针方法的例子:
func main() { cat := New("little pig", "American Shorthair", "cat") cat.SetName("monster") // (&cat).SetName("monster") fmt.Printf("The cat: %s\n", cat) cat.SetNameOfCopy("little pig") fmt.Printf("The cat: %s\n", cat) } type Cat struct { name string // 名字。 scientificName string // 学名。 category string // 动物学基本分类。 } //构造一个cat实例 func New(name, scientificName, category string) Cat { return Cat{ name: name, scientificName: scientificName, category: category, } } //传指针设置cat名字 func (cat *Cat) SetName(name string) { cat.name = name } //传入值 func (cat Cat) SetNameOfCopy(name string) { cat.name = name } func (cat Cat) String() string { return fmt.Sprintf("%s (category: %s, name: %q)", cat.scientificName, cat.category, cat.name) }
在这个例子中,咱们为Cat设置了两个方法,SetName是传指针的方法,SetNameOfCopy是传值的方法。
⽅法SetName的接收者类型是Cat。Cat左边再加个表明的就是Cat类型的指针类型。
咱们经过运行上面的例子能够得出,值⽅法的接收者是该⽅法所属的那个类型值的⼀个副本。⽽指针⽅法的接收者,是该⽅法所属的那个基本类型值的指针值的⼀个副本。咱们在这样的⽅法内对该副本指向的值进⾏ 修改,却⼀定会体如今原值上。
type Pet interface { SetName(name string) Name() string Category() string }
当数据类型中的方法实现了接口中的全部方法,那么该数据类型就是该接口的实现类型,以下:
type Pet interface { Name() string Category() string SetName(name string) } type Dog struct { name string // 名字。 } func (dog *Dog) SetName(name string) { dog.name = name } func (dog Dog) Name() string { return dog.name } func (dog Dog) Category() string { return "dog" }
在这里Dog类型实现了Pet接口。
接口变量赋值也涉及了值传递和指针传递的概念。以下:
// 示例1 dog := Dog{"little pig"} fmt.Printf("The dog's name is %q.\n", dog.Name()) var pet Pet = dog dog.SetName("monster") fmt.Printf("The dog's name is %q.\n", dog.Name()) fmt.Printf("This pet is a %s, the name is %q.\n", pet.Category(), pet.Name()) fmt.Println() // 示例2。 dog = Dog{"little pig"} fmt.Printf("The dog's name is %q.\n", dog.Name()) pet = &dog dog.SetName("monster") fmt.Printf("The dog's name is %q.\n", dog.Name()) fmt.Printf("This pet is a %s, the name is %q.\n", pet.Category(), pet.Name())
返回
The dog's name is "little pig". The dog's name is "monster". This pet is a dog, the name is "little pig". The dog's name is "little pig". The dog's name is "monster". This pet is a dog, the name is "monster".
在示例1中,赋给pet变量的其实是dog的一个副本,因此当dog设置了name的时候pet的name并没发生改变。
在实例2中,赋给pet变量的是一个指针的副本,因此pet和dog同样发生了编发。
能够经过接口间的嵌入实现接口的组合。接⼝类型间的嵌⼊不会涉及⽅法间的“屏蔽”。只要组合的接⼝之间有同名的⽅法就会产⽣冲突,从⽽⽆ 法经过编译,即便同名⽅法的签名彼此不一样也会是如此。
type Animal interface { // ScientificName 用于获取动物的学名。 ScientificName() string // Category 用于获取动物的基本分类。 Category() string } type Named interface { // Name 用于获取名字。 Name() string } type Pet interface { Animal Named }
若是一个变量是不可变的,那么基于它的索引或切⽚的结果值都是不可寻址的,由于即便拿到了这种值的内存地址也改变不了什么。
如:
const num = 123 //_ = &num // 常量不可寻址。 //_ = &(123) // 基本类型值的字面量不可寻址。 var str = "abc" _ = str //_ = &(str[0]) // 对字符串变量的索引结果值不可寻址。 //_ = &(str[0:2]) // 对字符串变量的切片结果值不可寻址。 str2 := str[0] _ = &str2 // 但这样的寻址就是合法的。
在咱们把临时结果值赋给任何变量或常量以前,即便能拿到它的内存地址也是没有任何意义的。因此也是不可寻址的。
咱们能够把各类对值字⾯量施加的表达式的求值结果都看作是 临时结果。
如:
* ⽤于得到某个元素的索引表达式。
* ⽤于得到某个切⽚(⽚段)的切⽚表达式。
* ⽤于访问某个字段的选择表达式。
* ⽤于调⽤某个函数或⽅法的调⽤表达式。
* ⽤于转换值的类型的类型转换表达式。
* ⽤于判断值的类型的类型断⾔表达式。
* 向通道发送元素值或从通道那⾥接收元素值的接收表达式。
⼀个须要特别注意的例外是,对切⽚字⾯量的索引结果值是可寻址的。由于不论怎样,每一个切⽚值都会持有⼀个底层数组,⽽ 这个底层数组中的每一个元素值都是有⼀个确切的内存地址的。
//_ = &(123 + 456) // 算术操做的结果值不可寻址。 //_ = &([3]int{1, 2, 3}[0]) // 对数组字面量的索引结果值不可寻址。 //_ = &([3]int{1, 2, 3}[0:2]) // 对数组字面量的切片结果值不可寻址。 _ = &([]int{1, 2, 3}[0]) // 对切片字面量的索引结果值倒是可寻址的。 //_ = &([]int{1, 2, 3}[0:2]) // 对切片字面量的切片结果值不可寻址。 //_ = &(map[int]string{1: "a"}[0]) // 对字典字面量的索引结果值不可寻址。
可是,这样的函数和⽅法都是不可寻址的。⼀个缘由是函数就是代码,是不可变的。另⼀个缘由是,拿到指向⼀段代码的指针是不安全的。
此外,对函数或⽅法的调⽤结果值也是不可寻址的,这是由于它们都属 于临时结果。
如:
//_ = &(func(x, y int) int { // return x + y //}) // 字面量表明的函数不可寻址。 //_ = &(fmt.Sprintf) // 标识符表明的函数不可寻址。 //_ = &(fmt.Sprintln("abc")) // 对函数的调用结果值不可寻址。
在Go语言中,协程是由go函数进行触发的,当程序执⾏到⼀条go语句的时候,Go语⾔ 的运⾏时系统,会先试图从某个存放空闲的G的队列中获取⼀个G(也就是goroutine),它只有在找不到空闲G的状况下才会 去建立⼀个新的G。
故已存在的goroutine老是会被优先复⽤。
在拿到了⼀个空闲的G以后,Go语⾔运⾏时系统会⽤这个G去包装当前的那个go函数(或者说该函数中的那些代码),而后再 把这个G追加到某个存放可运⾏的G的队列中。
在Go语⾔并不会去保证这些goroutine会以怎样的顺序运⾏。因此哪一个goroutine先执⾏完、哪一个goroutine后执⾏完每每是不可预知的,除⾮咱们使⽤了某种Go语⾔提供的⽅式进⾏了⼈为 ⼲预。
因此,怎样让咱们启⽤的多个goroutine按照既定的顺序运⾏?
下面咱们先看个例子:
func main() { for i := 0; i < 10; i++ { go func() { fmt.Println(i) }() } }
在下面的代码中,因为Go语言并不会按顺序去执行调度,因此无法知道fmt.Println(i)会在何时被打印,也不知道fmt.Println(i)打印的时候i是多少,也有可能main方法执行完了,可是没有一条输出。
因此咱们须要进行以下改造:
func main() { var count uint32 trigger := func(i uint32, fn func()) { for { if n := atomic.LoadUint32(&count); n == i { fn() atomic.AddUint32(&count, 1) break } time.Sleep(time.Nanosecond) } } for i := uint32(0); i < 10; i++ { go func(i uint32) { fn := func() { fmt.Println(i) } trigger(i, fn) }(i) } trigger(10, func() {}) }
咱们在for循环中声明了一个fn函数,fn函数里面只是简单的执行打印i的值,而后传入到trigger中。
trigger函数会不断地获取⼀个名叫count的变量的值,并判断该值是否与参数i的值相同。若是相同,那么就⽴即调⽤fn代 表的函数,而后把count变量的值加1,最后显式地退出当前的循环。不然,咱们就先让当前的goroutine“睡眠”⼀个纳秒再进 ⼊下⼀个迭代。
由于会有多个线程操做trigger函数,因此使用的count变量是经过原子操做来进行获取值和加一操做。
因此过函数实际执行顺序会根据count的值依次执行,这里实现了一种自旋,未知足条件的时候会不断地进行检查。
最后防止主协程在其余协程没有运行完的时候就关闭,加上一个trigger(10, func() {})
代码。