Go 言語では暗黙的な型変換が許可されていないことはわかっています。つまり、=
異なる型の変数を両側に出現させることは許可されていません。
类型转换
、类型断言
本質は、あるタイプを別のタイプに変換することです。違いは、型アサーションがインターフェイス変数で動作することです。
型変換
の場合类型转换
、変換前と変換後の 2 つの型に互換性がある必要があります。型変換の構文は次のとおりです。
<結果のタイプ> := <ターゲットのタイプ> ( <式> )
package main
import "fmt"
func main() {
var i int = 9
var f float64
f = float64(i)
fmt.Printf("%T, %v\n", f, f)
f = 10.8
a := int(f)
fmt.Printf("%T, %v\n", a, a)
// s := []int(i)
}
int
上記のコードでは、 type とtype の変数を定義しfloat64
、その前に相互に変換しようとしましたが、結果は成功し、int
type と type はfloat64
相互に互換性があります。
コードの最後の行のコメントを解除すると、コンパイラは型の非互換性エラーを報告します。
cannot convert i (type int) to type []int
断言
前に述べたように、空のインターフェイスではinterface{}
関数が定義されていないため、Go のすべての型は空のインターフェイスを実装します。関数の仮パラメータが の場合interface{}
、関数内でその真の型を取得するには、仮パラメータに対してアサーションを行う必要があります。
アサーションの構文は次のとおりです。
<対象の型の値>, <ブール型パラメータ> := <式>.(対象の型) // 安全な型のアサーション <対象の型
の値> := <式>.(対象の型) // 安全でない型のアサーション
型変換は型アサーションに似ていますが、異なる点は、型アサーションがインターフェイス上で動作することです。
簡単な例を見てみましょう:
package main
import "fmt"
type Student struct {
Name string
Age int
}
func main() {
var i interface{} = new(Student)
s := i.(Student)
fmt.Println(s)
}
それを実行します:
panic: interface conversion: interface {
} is *main.Student, not main.Student
直接的にはpanic
、これは型ではなく型でi
あるため、アサーションが失敗します。これは直接発生したものです。オンライン コードはこれには適していない可能性があります。「安全性アサーション」構文を使用できます。*Student
Student
panic
func main() {
var i interface{} = new(Student)
s, ok := i.(Student)
if ok {
fmt.Println(s)
}
}
こうすることで、たとえアサーションが失敗したとしても、失敗しませんpanic
。
switch
実際には、ステートメントを使用してインターフェイスのタイプを決定するために使用されるアサーションの別の形式があります。それぞれをcase
順番に検討していきます。a がヒットすると、のステートメントがcase
実行されます。複数の一致が存在する可能性があるため、ステートメントの順序は非常に重要です。case
case
case
コード例は次のとおりです。
func main() {
//var i interface{} = new(Student)
//var i interface{} = (*Student)(nil)
var i interface{}
fmt.Printf("%p %v\n", &i, i)
judge(i)
}
func judge(v interface{}) {
fmt.Printf("%p %v\n", &v, v)
switch v := v.(type) {
case nil:
fmt.Printf("%p %v\n", &v, v)
fmt.Printf("nil type[%T] %v\n", v, v)
case Student:
fmt.Printf("%p %v\n", &v, v)
fmt.Printf("Student type[%T] %v\n", v, v)
case *Student:
fmt.Printf("%p %v\n", &v, v)
fmt.Printf("*Student type[%T] %v\n", v, v)
default:
fmt.Printf("%p %v\n", &v, v)
fmt.Printf("unknow\n")
}
}
type Student struct {
Name string
Age int
}
main
関数には 3 つの異なる宣言行があります。毎回 1 行を実行し、他の 2 行をコメント化して、3 セットの実行結果を取得します。
// --- var i interface{
} = new(Student)
0xc4200701b0 [Name: ], [Age: 0]
0xc4200701d0 [Name: ], [Age: 0]
0xc420080020 [Name: ], [Age: 0]
*Student type[*main.Student] [Name: ], [Age: 0]
// --- var i interface{
} = (*Student)(nil)
0xc42000e1d0 <nil>
0xc42000e1f0 <nil>
0xc42000c030 <nil>
*Student type[*main.Student] <nil>
// --- var i interface{
}
0xc42000e1d0 <nil>
0xc42000e1e0 <nil>
0xc42000e1f0 <nil>
nil type[<nil>] <nil>
ステートメントの最初の行については、次のようになります。
var i interface{} = new(Student)
i
これは*Student
3 番目のケースに一致する型で、印刷された 3 つのアドレスから判断すると、この 3 か所の変数は実際には異なります。main
関数 にはローカル変数があります。i
関数を呼び出すと、パラメータのコピーが実際にコピーされます。そのため、関数 には、v
そのi
コピーである別の変数があります。アサーションの後に、新しいコピーが生成されます。したがって、最終的に出力される 3 つの変数のアドレスは異なります。
ステートメントの 2 行目については、次のようになります。
var i interface{} = (*Student)(nil)
ここで説明したいのは、i
ここでの動的型は であり(*Student)
、データはnil
であり、その型は ではなくnil
、それとnil
比較すると得られる結果も であるということですfalse
。
ステートメントの最後の行:
var i interface{}
今回はそのタイプi
です。nil
【拡張1】
fmt.Println
関数のパラメータは ですinterface
。組み込み型の場合、関数は徹底的な方法を使用してその真の型を見つけ出し、それを印刷用の文字列に変換します。カスタム型の場合は、まずその型がString()
メソッドを実装しているかどうかを確認します。実装している場合は、メソッドの結果が直接出力されますString()
。そうでない場合は、オブジェクトのメンバーがリフレクションを介して走査され、出力されます。
もう一度短い例を見てみましょう。比較的単純なので、緊張しないでください。
package main
import "fmt"
type Student struct {
Name string
Age int
}
func main() {
var s = Student{
Name: "qcrao",
Age: 18,
}
fmt.Println(s)
}
Student
構造体はString()
メソッドを実装していないため、fmt.Println
リフレクションを使用してメンバー変数を 1 つずつ出力します。
{
qcrao 18}
String()
メソッドの実装を追加します。
func (s Student) String() string {
return fmt.Sprintf("[Name: %s], [Age: %d]", s.Name, s.Age)
}
結果を出力します。
[Name: qcrao], [Age: 18]
弊社がカスタマイズした方法で印刷します。
【拡張2】
上記の例について、以下のように変更すると、
func (s *Student) String() string {
return fmt.Sprintf("[Name: %s], [Age: %d]", s.Name, s.Age)
}
2 つの関数のレシーバーの型が異なることに注意してください。現在、Student
構造体にはレシーバーの型を持つ関数が 1 つだけあります。結果を出力します指针类型
。String()
{
qcrao 18}
なぜ?
型には
T
レシーバが および であるT
メソッドのみがあり、型に*T
はレシーバがT
および であるメソッドがあります*T
。構文的には、T
直接調整できるメソッドは、 の糖衣構文に*T
すぎません。Go
したがって、Student
構造体がString()
レシーバーの型が値型であるメソッドを定義する場合、
fmt.Println(s)
fmt.Println(&s)
すべてカスタマイズされた形式で印刷できます。
Student
構造体でString()
レシーバーの型がポインター型であるメソッドが定義されている場合、そのメソッドは渡すことしかできません。
fmt.Println(&s)
カスタム形式で印刷します。