Go 学习笔记(35)— Go 接口 interface (接口声明、接口初始化、接口方法调用、接口运算、类型断言、类型查询、空接口)
1. 接口概念
接口是双方约定的一种合作协议。接口实现者不需要关心接口会被怎样使用,调用者也不需要关心接口的实现细节。接口是一种类型,也是一种抽象结构,不会暴露所含数据的格式、类型及结构。
接口内部存放的具体类型变量被称为接口指向的“实例”。接口只有声明没有实现,所以定义一个新接口,通常又变成声明一个新接口, 定义接口和声明接口二者通用,代表相同的意思。
最常使用的接口字面量类型就是空接口 interface{}
,由于空接口的方法集为空,所以任意类型都被认为实现了空接口,任意类型的实例都可以赋值或传递给空接口,包括非命名类型的实例。
Go
接口背后的本质是一种“契约”,通过契约我们可以将代码双方的耦合降至最低。Go
惯例上推荐尽量定义小接口,一般而言接口方法集合中的方法个数不要超过三个,单一方法的接口更受 Go
社区青睐。小接口有诸多优点,比如,抽象程度高、易于测试与实现、与组合的设计思想一脉相承、鼓励你编写组合的代码,等等。
这种“小接口”的 Go
惯例也已经被 Go
社区项目广泛采用。作者统计了早期版本的 Go
标准库(Go 1.13
版本)、Docker
项目(Docker 19.03 版本)以及 Kubernetes
项目(Kubernetes 1.17 版本)中定义的接口类型方法集合中方法数量,你可以看下:
从图中我们可以看到,无论是 Go
标准库,还是 Go
社区知名项目,它们基本都遵循了“尽量定义小接口”的惯例,接口方法数量在 1~3 范围内的接口占了绝大多数。
图片来源:https://time.geekbang.org/column/article/471952
注意:非命名类型由于不能定义自己的方法, 所以方法集为空,因此其类型变量除了传递给空接口,不能传递给任何其他接口。
Go
语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
/* 定义接口 */
type interface_name interface {method_name1 [return_type]method_name2 [return_type]method_name3 [return_type]...method_namen [return_type]
}/* 定义结构体 */
type struct_name struct {/* variables */
}/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_t
ype] {/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_t
ype] {/* 方法实现*/
}
使用示例:
package mainimport ("fmt"
)type Phone interface {call()
}type NokiaPhone struct {}
type IPhone struct {}func (nokiaPhone NokiaPhone) call() {fmt.Println("I am Nokia, I can call you!")
}func (iPhone IPhone) call() {fmt.Println("I am iPhone, I can call you!")
}
func main() {var phone Phonephone = new(NokiaPhone)phone.call() // I am Nokia, I can call you!phone = new(IPhone)phone.call() // I am iPhone, I can call you!
}
2. 接口声明
Go
语言的接口分为接口字面量类型和接口命名类型, 接口的声明使用 interface
关键字。
接口字面量类型的声明语法如下:
interface {MethodSignature1MethodSignature2
}
使用接口字面量的场景很少,一般只有空接口 interface{}
类型变量的声明才会使用。
接口命名类型使用 type
关键字声明,语法如下:
type InterfaceName interface {方法名1( 参数列表1 ) 返回值列表1方法名2( 参数列表2 ) 返回值列表2
}
对各个部分的说明:
- 接口类型名:使用
type
将接口定义为自定义的类型名。Go
语言的接口在命名时,一般会在单词后面添加er
,如有写操作的接口叫Writer
,有字符串功能的接口叫Stringer
,有关闭功能的接口叫Closer
等; - 方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问;
- 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略;
2.1 方法声明
接口定义使用方法声明,而不是方法签名,因为方法名是接口的组成部分。例如:
// 方法声明=方法名+方法签名
MethodName (InputTypeList )OutputTypeList
接口中的“方法声明”非常类似于 C
语言中的函数声明的概念, Go
编译器在做接口匹配判断时是严格校验方法名称和方法签名的。
接口定义大括号内可以是方法声明的集合,也可以嵌入另一个接口类型匿名字段,还可以是二者的混合。接口支持嵌入匿名接口宇段,就是一个接口定义里面可以包括其他接口, Go
编译器会自动进行展开处理,有点类似 C
语言中宏的概念。例如:
type Reader interface {Read(p []byte) (n int , err error)
}type Writer interface {Write(p []byte) (n int , err error)
}// 如下3 种声明是等价的,最终的展开模式都是第 3 种格式
type ReadWriter interface {ReaderWriter
}type ReadWriter interface {ReaderWrite(p []byte) (n int , err error)
}type ReadWriter interface {Read(p []byte) (n int, err error)Write(p []byte) (n int, err error)
}
Writer
这个接口可以调用 Write()
方法写入一个字节数组( []byte
),返回值告知写入字节数( n int
)和可能发生的错误( err error
)
我们在接口类型的方法集合中声明的方法,它的参数列表不需要写出形参名字,返回值列表也是如此。也就是说,方法的参数列表中形参名字与返回值列表中的具名返回值,都不作为区分两个方法的凭据。
type MyInterface interface {M1(int) errorM2(io.Writer, ...string)
}
比如下面的 MyInterface
接口类型的定义与上面的 MyInterface
接口类型定义都是等价的:
type MyInterface interface {M1(a int) errorM2(w io.Writer, strs ...string)
}type MyInterface interface {M1(n int) errorM2(w io.Writer, args ...string)
}
不过,Go
语言要求接口类型声明中的方法必须是具名的,并且方法名字在这个接口类型的方法集合中是唯一的。
Go 1.14
版本以后,Go
接口类型允许嵌入的不同接口类型的方法集合存在交集,但前提是交集中的方法不仅名字要一样,它的函数签名部分也要保持一致,也就是参数列表与返回值列表也要相同,否则 Go
编译器照样会报错。
比如下面示例中 Interface3
嵌入了 Interface1
和 Interface2
,但后两者交集中的 M1
方法的函数签名不同,导致了编译出错:
type Interface1 interface {M1()
}
type Interface2 interface {M1(string) M2()
}type Interface3 interface{Interface1Interface2 // 编译器报错:duplicate method M1M3()
}
2.2 新接口类型声明特点
- 接口的命名一般以“
er
”结尾; - 接口定义的内部方法声明不需要
func
引导; - 在接口定义中,只有方法声明没有方法实现;
3. 接口初始化
接口类型一旦被定义后,它就和其他 Go
类型一样可以用于声明变量,比如:
var err error // err是一个error接口类型的实例变量
var r io.Reader // r是一个io.Reader接口类型的实例变量
接口只有被初始化为具体的类型时才有意义。没有初始化的接口变量,其默认值是 nil
。
var i io.Reader
fmt.Printf("%T\n", i) // nil
这些类型为接口类型的变量被称为接口类型变量,如果没有被显式赋予初值,接口类型变量的默认值为 nil
。如果要为接口类型变量显式赋予初值,我们就要为接口类型变量选择合法的右值。
接口绑定具体类型的实例的过程称为接口初始化。接口变量支持两种直接初始化方法, 具体如下。
3.1 实例赋值接口
如果具体类型实例的方法集是某个接口的方法集的超集,则称该具体类型实现了接口,可以将该具体类型的实例直接赋值给接口类型的变量,此时编译器会进行静态的类型检查。接口被初始化后,调用接口的方法就相当于调用接口绑定的具体类型的方法,这就是接口调用的语义。
3.2 接口变量赋值接口变量
已经初始化的接口类型变量 a
直接赋值给另一种接口变量 b
,要求 b
的方法集是 a
的方法集的子集。此时 Go
编译器会在编译时进行方法集静态检查。这个过程也是接口初始化的一种方式,此时接口变量 b
绑定的具体实例是接口变量 a
绑定的具体实例的副本。例如:
file , := os .OpenFile ( "notes.txt", os .O_RDWR | os .O_CREATE , 0755 )
var rw io.ReadWriter = file
//io.ReadWriter 接口可以直接赋值给 io.Writer 接口变量
var w io.Writer = rw
4. 接口方法调用
接口方法调用和普通的函数调用是有区别的。接口方法调用的最终地址是在运行期决定的,将具体类型变量赋值给接口后,会使用具体类型的方法指针初始化接口变量,当调用接口变量的方法时,实际上是间接地调用实例的方法。接口方法调用不是一种直接的调用,有一定的运行时开销。
直接调用未初始化的接口变量的方法会产生 panic
。例如:
package maintype Printer interface {Print()
}type S struct{}func (s S) Print() {println("print")
}func main() {var i Printer// 没有初始化的接口调用其方法会产生 panic// panic: runtime error: invalid memory address or nil pointer dereference// i.Print()// i 必须初始化i = S{}i.Print()
}
5. 接口运算
编程过程中有时需要确认已经初始化的接口变量指向实例的具体类型是什么,也需要检查运行时的接口类型。 Go
语言提供两种语法结构来支持这两种需求,分别是类型断言和类型查询。
Go
的语言中提供了断言的功能。Go
中的所有程序都实现了 interface{}
的接口,这意味着,所有的类型如 string
, int
, int64
甚至是自定义的 struct
类型都就此拥有了 interface{}
的接口,那么在一个数据通过 func funcName(interface{})
的方式传进来的时候,也就意味着这个参数被自动的转为 interface{}
的类型。
func funcName(a interface{}) string {return string(a)
}
编译器报错:
cannot convert a (type interface{}) to type string: need type as
sertion
此时,意味着整个转化的过程需要类型断言。
5.1 类型断言
接口类型断言的语法形式如下:
var i interface
i.(T)
i
必须是接口变量,如果是具体类型变量,则编译器会报non - interface type xxx on left
T
可以是接口类型名,也可以是具体类型名。
那么这句代码的含义就是断言存储在接口类型变量 i
中的值的类型为 T
。
func main() {a := 1v := a.(int)fmt.Println(a)
}
报错:
invalid type assertion: a.(int) (non-interface type int on left)
修改后的代码:
func main() {a := 1v, ok := interface{}(a).(int) // 将 a 转换为接口类型if ok {fmt.Printf("v type is %T\n", v)}fmt.Println(a)
}
在 Go
语言中,interface{}
代表空接口,任何类型都是它的实现类型。现在你只要知道,任何类型的值都可以很方便地被转换成空接口的值就行了。
你可能会对这里的 {}
产生疑惑,为什么在关键字 interface
的右边还要加上这个东西?
请记住,一对不包裹任何东西的花括号,除了可以代表空的代码块之外,还可以用于表示不包含任何内容的数据结构(或者说数据类型)。
比如你今后肯定会遇到的 struct{}
,它就代表了不包含任何字段和方法的、空的结构体类型。而空接口 interface{}
则代表了不包含任何方法定义的、空的接口类型。
当然了,对于一些集合类的数据类型来说,{}
还可以用来表示其值不包含任何元素,比如空的切片值 []string{}
,以及空的字典值 map[int]string{}
。
接口查询的两层语义
- 如果
TypeNname
是一个具体类型名,则类型断言用于判断接口变量i
绑定的实例类型是否就是具体类型TypeNname
。 - 如果
TypeName
是一个接口类型名,则类型断言用于判断接口变量i
绑定的实例类型是否同时实现了TypeName
接口。
Go
中的 interface
类型是不能直接转换成其他类型的,需要使用到断言。
package mainfunc main() {var itf interface{} = 1i, ok := itf.(string)println("值:", i, "; 断言结果", ok)j, ok := itf.(int)println("值:", j, "; 断言结果", ok)
}
接口断言的两种语法表现:
5.1.1 直接赋值模式
o := i.(TypeName)
分析:
TypeName
是具体类型名,此时如果接口i
绑定的实例类型就是具体类型TypeName
,则变量o
的类型就是TypeName
, 变量o
的值就是接口绑定的实例值的副本(当然实例可能是指针值,那就是指针值的副本) 。TypeName
是接口类型名, 如果接口i
绑定的实例类型满足接口类型TypeName
,则变量o
的类型就是接口类型TypeName
,o
底层绑定的具体类型实例是i
绑定的实例的副本(当然实例可能是指针值,那就是指针值的副本〉。- 如果上述两种情况都不满足, 则程序抛出
panic
。
示例代码:
package mainimport "fmt"type Inter interface {Ping()Pang()
}type Anter interface {InterString()
}type St struct {Name string
}func (St) Ping() {println("ping")
}func (*St) Pang() {println("pang")
}func main() {st := &St{"abcd"}var i interface{} = st// 判断i 绑定的实例是否实现了接口类型Intero := i.(Inter)o.Ping()o.Pang()/*如下语句会引发 panic ,因为 i 没有实现接口Anterp := i.(Anter)p.String()*/// 判断 i 绑定的实例是否就是具体类型 Sts := i.(*St)fmt.Printf("%s", s.Name)
}
由于可能出现 panic
,所以我们并不推荐使用这种类型断言的语法形式。
关于类型断言,需要注意两点:
- 如果
i
是一个非接口值,那么必须在做类型断言之前把它转换为接口值。因为Go
中的任何类型都是空接口类型的实现类型,所以一般会这样做:interface{}(i).(TypeNname)
。 - 如果类型断言的结果为否,就意味着该类型断言是失败的,失败的类型断言会引发 panic(运行时异常),解决方法是:
var i1, ok := interface{}(i).(TypeNname)
其中 ok 值体现了类型断言的成败,如果成功,i1
就会是经过类型转换后的 TypeNname
类型的值,否则它将会是 TypeNname
类型的零值(或称为默认值)
func main() {a := "1"// var b intb, ok := interface{}(a).(int)if ok {fmt.Printf("b type is %v", b)} else {fmt.Printf("b is %v", b) // b is 0}
}
5.1.2 comma,ok 表达式模式
if o, ok := i.(TypeName); ok {}
语义分析:
TypeName
是具体类型名,此时如果接口i
绑定的实例类型就是具体类型TypeName
,则ok
为true
, 变量o
的类型就是TypeName
,变量o
的值就是接口绑定的实例值的副本(当然实例可能是指针值,那就是指针值的副本) 。TypeName
是接口类型名, 此时如果接口i
绑定的实例的类型满足接口类型TypeName
, 则ok
为true
,变量o
的类型就是接口类型TypeName
,o
底层绑定的具体类型实例是i
绑定的实例的副本(当然实例可能是指针值,那就是指针值的副本)。- 如果上述两个都不满足,则
ok
为false
, 变量o
是TypeName
类型的“零值”,此种条件分支下程序逻辑不应该再去引用。因为此时的。没有意义。
value, ok := a.(string)
总的来说:如果断言失败,那么 ok
的值将会是 false
,但是如果断言成功 ok
的值将会是 true
,同时value
将会得到所期待的正确的值。
var a int64 = 13
var i interface{} = a
v1, ok := i.(int64)
fmt.Printf("v1=%d, the type of v1 is %T, ok=%t\n", v1, v1, ok) // v1=13, the type of v1 is int64, ok=true
v2, ok := i.(string)
fmt.Printf("v2=%s, the type of v2 is %T, ok=%t\n", v2, v2, ok) // v2=, the type of v2 is string, ok=false
v3 := i.(int64)
fmt.Printf("v3=%d, the type of v3 is %T\n", v3, v3) // v3=13, the type of v3 is int64
v4 := i.([]int) // panic: interface conversion: interface {} is int64, not []int
fmt.Printf("the type of v4 is %T\n", v4)
修改上述代码:
func main() {st := &St{"abcd"}var i interface{} = st// 判断i 绑定的实例是否实现了接口类型Interif o, ok := i.(Inter); ok {o.Ping()o.Pang()}// i 没有实现接口 Anterif p, ok := i.(Anter); ok {p.String()}// 判断 i 绑定的实例是否就是具体类型 Stif s, ok := i.(*St); ok {fmt.Printf("%s", s.Name)}
}
另外一个完整的示例如下:
package mainimport "fmt"/*
func funcName(a interface{}) string {
return string(a)
}
*/
func funcName(a interface{}) string {value, ok := a.(string)if !ok {fmt.Println("It is not ok for type string")return ""}fmt.Println("The value is ", value)return value
}
func main() {// str := "123"// funcName(str)//var a interface{}//var a string = "123"var a int = 10funcName(a)
}
5.2 类型查询
类型查询,就是根据变量,查询这个变量的类型。为什么会有这样的需求呢?
Go
中有一个特殊的类型 interface{}
,这个类型可以被任何类型的变量赋值,如果想要知道到底是哪个类型的变量赋值给了 interface{}
类型变量,就需要使用类型查询来解决这个需求,示例代码如下:
func main() {var x interface{} = 13switch x.(type) {case nil:println("x is nil")case int:println("the type of x is int")case string:println("the type of x is string")case bool:println("the type of x is string")default:println("don't support the type")}
}
输出结果:
the type of x is int
不过,通过 x.(type)
,我们除了可以获得变量 x
的动态类型信息之外,也能获得其动态类型对应的值信息,现在我们把上面的例子改造一下:
func main() {var x interface{} = 13switch v := x.(type) {case nil:println("v is nil")case int:println("the type of v is int, v =", v)case string:println("the type of v is string, v =", v)case bool:println("the type of v is bool, v =", v)default:println("don't support the type")}
}
这里我们将 switch
后面的表达式由 x.(type)
换成了 v := x.(type)
。对于后者,你千万不要认为变量 v
存储的是类型信息,其实 v
存储的是变量 x
的动态类型对应的值信息,这样我们在接下来的 case
执行路径中就可以使用变量 v
中的值信息了。
输出结果
the type of v is int, v = 13
package mainimport ("fmt"
)func main() {// 定义一个interface{}类型变量,并使用string类型值”abc“初始化var a interface{} = "abc"// 在switch中使用 变量名.(type) 查询变量是由哪个类型数据赋值。switch v := a.(type) {case string:fmt.Println("字符串")case int:fmt.Println("整型")default:fmt.Println("其他类型", v)}
}
如果使用 .(type)
查询类型的变量不是 interface{}
类型,则在编译时会报如下错误:
cannot type switch on non-interface value a (type string)
如果在 switch
以外地方使用 .(type)
,则在编译时会提示如下错误:
use of .(type) outside type switch
所以,使用 type
进行类型查询时,只能在 switch
中使用,且使用类型查询的变量类型必须是 interface{}
。
接口类型查询的语法格式如下:
switch v := i.(type) {case typel :xx xxcase type2 :xx xxdefault :xx xx
}
类型查询和类型断言
- 类型查询和类型断言具有相同的语义,只是语法格式不同。二者都能判断接口变量绑定的实例的具体类型,以及判断接口变量绑定的实例是否满足另一个接口类型。
- 类型查询使用
case
字句一次判断多个类型,类型断言一次只能判断一个类型,当然类型断言也可以使用if-else-if
语句达到同样的效果。
示例如下:
func main() {var t interface{}t = functionOfSomeType()switch t := t.(type) {default:fmt.Printf("unexpected type %T", t) // %T prints whatever type t hascase bool:fmt.Printf("boolean %t\n", t) // t has type boolcase int:fmt.Printf("integer %d\n", t) // t has type intcase *bool:fmt.Printf("pointer to boolean %t\n", *t) // t has type *boolcase *int:fmt.Printf("pointer to integer %d\n", *t) // t has type *int}
}
或者使用 if-else-if
代替
func sqlQuote(x interface{}) string {if x == nil {return "NULL"} else if _, ok := x.(int); ok {return fmt.Sprintf("%d", x)} else if _, ok := x.(uint); ok {return fmt.Sprintf("%d", x)} else if b, ok := x.(bool); ok {if b {return "TRUE"}return "FALSE"} else if s, ok := x.(string); ok {return sqlQuoteString(s) // (not shown)} else {panic(fmt.Sprintf("unexpected type %T: %v", x, x))}
}
5.3 类型断言和查询总结
package mainimport ("fmt"
)var container = []string{"aaa", "bbb", "ccc"}func main() {container := map[string]string{"a": "aaa", "b": "bbb", "c": "ccc"}// 方式1。类型断言_, ok1 := interface{}(container).([]string)_, ok2 := interface{}(container).(map[string]string)// %T 表示该值的 Go 类型if !(ok1 || ok2) {fmt.Printf("Error: unsupported container type: %T\n", container)return}fmt.Printf("The element is %#v , (container type: %T)\n", container["a"], container)// 方式2。elem, err := getElement(container)if err != nil {fmt.Printf("Error: %s\n", err)return}fmt.Printf("The element is %#v , (container type: %T)\n", elem, container)
}// 空接口包含所有的类型,输入的参数均会被转换为空接口
// 函数入参已经声明 containerI 为 interface 类型,所以不需要再次进行 interface{}(container) 转换
func getElement(containerI interface{}) (elem string, err error) {// 变量类型会被保存在t中// 方式2。类型查询switch t := containerI.(type) {case []string:elem = t[1]case map[string]string:elem = t["a"]default:err = fmt.Errorf("unsupported container type: %T", containerI)return}return
}
6. 空接口
如果一个接口类型定义中没有一个方法,那么它的方法集合就为空,比如下面的 EmptyInterface
接口类型:
type EmptyInterface interface {}
这个方法集合为空的接口类型就被称为空接口类型,但通常我们不需要自己显式定义这类空接口类型,我们直接使用 interface{}
这个类型字面值作为所有空接口类型的代表就可以了。
Go
语言没有泛型, 如果一个函数需要接收任意类型的参数, 则参数类型可以使用空接口类型,空接口不是真的为空,接口有类型和值两个概念。
package mainimport "fmt"type Inter interface {Ping()Pang()
}type St struct{}func (St) Ping() {println("ping")
}func (*St) Pang() {println("pang")
}func main() {var st *St = nilvar it Inter = stfmt.Printf("%p\n", st) // 0x0fmt.Printf("%p\n", it) // 0x0if it != nil {it.Pang() // pang// 下面的语句会导致panic// 方法转换为函数调用,第一个参数是St 类型,由于 *St 是nil ,无法获取指针所指的对象值,所以导致panic// it.Ping()}
}
这个程序暴露出 Go
语言的一点瑕疵, fmt.Printf("%p\n", it)
的结果是 0x0
,但 it! = nil
的判断结果却是 true
。
空接口有两个字段, 一个是实例类型, 另一个是指向绑定实例的指针,只有两个都为 nil
时,空接口才为 nil
。
Go
规定:如果一个类型 T
的方法集合是某接口类型 I
的方法集合的等价集合或超集,我们就说类型 T
实现了接口类型 I
,那么类型 T
的变量就可以作为合法的右值赋值给接口类型 I
的变量。
如果一个变量的类型是空接口类型,由于空接口类型的方法集合为空,这就意味着任何类型都实现了空接口的方法集合,所以我们可以将任何类型的值作为右值,赋值给空接口类型的变量,
var any interface{}
any = true
any = 12.34
any = "hello"
any = map[string]int{"one": 1}
any = new(bytes.Buffer)// orvar i interface{} = 15 // ok
i = "hello, golang" // ok
type T struct{}
var t T
i = t // ok
i = &t // ok
参考书籍:
- Go 语言核心编程
- Go 语言圣经
Go 学习笔记(35)— Go 接口 interface (接口声明、接口初始化、接口方法调用、接口运算、类型断言、类型查询、空接口)相关推荐
- java 接口重载_java,_java 接口中如何声明类似于重载的方法?,java - phpStudy
java 接口中如何声明类似于重载的方法? 我们的作业要写一个web宠物医院管理系统,因为医生.客户等都包含浏览.添加.删除操作,所以对于service层,我希望有个统一的接口以供servlet中的类 ...
- mysql没法修改数据_MySQL学习笔记之数据的增、删、改实现方法
本文实例讲述了MySQL学习笔记之数据的增.删.改实现方法.分享给大家供大家参考,具体如下: 一.增加数据 插入代码格式: insert into 表明 [列名-] values (值-) creat ...
- vue学习笔记(四)- cmd无法识别vue命令解决方法
vue学习笔记(四)- cmd无法识别vue命令解决方法 参考文章: (1)vue学习笔记(四)- cmd无法识别vue命令解决方法 (2)https://www.cnblogs.com/suRimn ...
- oracle事务数统计,【学习笔记】Oracle数据库收集统计信息的两种方法介绍案例
天萃荷净 分享一篇关于Oracle数据库收集统计信息的办法,Oracle DBMS_STATS与Oracle analyze使用方法案例 今天群里面讨论DBMS_STATS和analyze,这里进行了 ...
- 博弈论学习笔记——纳什均衡与社会最优、破坏均衡的方法
系列文章 博弈论学习笔记--博弈收益期望的计算与决策 博弈论学习笔记--纳什均衡与社会最优.破坏均衡的方法 博弈论学习笔记--拍卖原理 简介 在囚徒困境的情境中,二者的博弈会达到一个纳什均衡,即都会选 ...
- 高等数值计算方法学习笔记第6章【解线性代数方程组的迭代方法(高维稀疏矩阵)】
高等数值计算方法学习笔记第6章[解线性代数方程组的迭代方法(高维稀疏矩阵)] 一.引言 1.例题(说明迭代法的收敛性研究的重要性) 2.定义(迭代法,迭代法收敛)&解误差 二.基本迭代法 1. ...
- oracle停止job任务视图,【学习笔记】Oracle dba_datapump_jobs中not running作业的清除方法...
天萃荷净 Oracle研究中心学习笔记:分享一篇关于Oracle数据库JOBS作业控制的文章.关于dba_datapump_jobs中的not running的作业的清除的方法. not runnin ...
- Java面向对象基础学习笔记(构造、重载、继承、多态、抽象类、接口、模块)
Java面向对象编程包含哪些内容? 怎么理解面向对象编程? 现实生活中,我们定义了"人"的抽象概念,这就是类class,生活中的每一个具体的人就是实例instance. class ...
- 影像组学视频学习笔记(35)-基于2D超声影像的影像组学特征提取、Li‘s have a solution and plan.
作者:北欧森林 链接:https://www.jianshu.com/p/f82d30289d68 来源:简书,已获转载授权 RadiomicsWorld.com "影像组学世界" ...
- c语言编程实例解析精粹,C语言实例解析精粹学习笔记——35(报数游戏)
实例35: 设由n个人站成一圈,分别被编号1,2,3,4,--,n.第一个人从1开始报数,每报数位m的人被从圈中推测,其后的人再次从1开始报数,重复上述过程,直至所有人都从圈中退出. 实例解析: 用链 ...
最新文章
- 鸿蒙法则的能力,真正厉害的人,都懂得这五个做事法则,如能悟透,成功不难...
- ArcGIS放射状流向地图
- 招不招新人?IT经理很纠结.
- navigation
- SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase, Ha
- 使用sklearn进行数据预处理 —— 归一化/标准化/正则化
- Android开发之约束布局平均分布|ConstraintLayout平均分布|约束布局均匀分布|ConstraintLayout均匀分布
- bootstrapselect使用 Bootstrap's dropdowns require Popper.js
- H264 流媒体 编码汇总
- python堆排序的库_Python:堆排序
- 解决apache启动错误httpd:Could not reliably determine...
- 千兆以太网_接收模块设计_udp_rgmii_rx
- [电影]《指环王》新老三部曲完全赏析(双塔)
- vmware虚拟机扩展磁盘空间
- 为fuchsia编译qemu
- linux微信最新版无法打开问题解决
- 前端性能优化 七个方面
- 北京大学计算机学院推免生名单,2014年北京大学软微学院保研录取名单
- Go语言 大话数据结构——图
- JAVA_eclipse插件绘制GUI界面过程