Go 言語 によって提供される構造は、さまざまなデータ型を使用して定義されたさまざまな変数を組み合わせた高度なデータ型です。
type Rect struct { width float64 length float64}
上で長方形の構造を定義しました。まず重要なのは、type が新しいデータ型を定義することを意味し、次に新しいデータ型の名前 Rect であるということです。 、最後に struct キーワードは、この高度なデータ型が構造体型であることを示します。上記の例では、幅と長さは同じデータ型であるため、次の形式で記述することもできます。
type Rect struct { width, length float64}
さて、構造体を使って長方形の面積を計算してみましょう。
package main import ( "fmt" ) type Rect struct { width, length float64 } func main() { var rect Rect rect.width = 100 rect.length = 200 fmt.Println(rect.width * rect.length) }
上の例からわかるように、構造体型は実際には基本データ型と同じように使用されますが、唯一の違いは、構造体型が内部メンバーにアクセスできることです。内部メンバーへの値の割り当てと、内部メンバーの値の読み取りが含まれます。
上記の例では、var キーワードを使用して最初に Rect 変数を定義し、次にそのメンバーに値を割り当てます。初期化を使用して、Rect 変数の内部メンバーに値を割り当てることもできます。
package main import ( "fmt" ) type Rect struct { width, length float64 } func main() { var rect = Rect{width: 100, length: 200} fmt.Println(rect.width * rect.length) }
もちろん、構造体のメンバーが定義される順序がわかっている場合は、key:value を使用せずに、定義された順序で直接構造体のメンバーに値を割り当てることもできます。方法。
package main import ( "fmt" ) type Rect struct { width, length float64 } func main() { var rect = Rect{100, 200} fmt.Println("Width:", rect.width, "* Length:", rect.length, "= Area:", rect.width*rect.length) }
出力結果は
幅: 100 * 長さ: 200 = 面積: 20000
構造パラメータの転送方法
Go 関数のパラメータの受け渡し方法は値の受け渡しであると述べましたが、この文は構造体にも当てはまります。
package main import ( "fmt" ) type Rect struct { width, length float64 } func double_area(rect Rect) float64 { rect.width *= 2 rect.length *= 2 return rect.width * rect.length } func main() { var rect = Rect{100, 200} fmt.Println(double_area(rect)) fmt.Println("Width:", rect.width, "Length:", rect.length) }
上記の例の出力は次のとおりです:
80000 Width: 100 Length: 200
つまり、double_area 関数で構造体の幅と長さを 2 倍にしますが、それでもect 変数には影響しません。 main関数の幅と長さ。
構造結合関数
上記ではmain関数で長方形の面積を計算しましたが、長方形の面積が「長方形構造の内部関数」として利用するのが良いでしょう。このようにして、幅と長さを個別に計算することなく、この長方形の面積が何であるかを直接言うことができます。では、構造体の「内部関数」の定義方法を見てみましょう。
package main import ( "fmt" ) type Rect struct { width, length float64 } func (rect Rect) area() float64 { return rect.width * rect.length } func main() { var rect = Rect{100, 200} fmt.Println("Width:", rect.width, "Length:", rect.length, "Area:", rect.area()) }
え?これはどのような「内部メソッド」ですか? Rect データ型内ではまったく定義されていません?
確かに、main 関数の rect 変数は関数 area() を直接呼び出して長方形領域を取得できますが、area() 関数は実際には Rect 構造体内で定義されていないことがわかります。 C言語とC言語は大きく異なります。 Go は合成関数を使用して、構造体の構造メソッドを定義します。上記の area() 関数の定義を詳しく見てみましょう。
最初のパラメータは、これが関数であることを示すキーワード func で、2 番目のパラメータは構造体の型とインスタンス変数、3 番目は関数名、4 番目は関数の戻り値です。ここで、 area() 関数と通常の関数定義の違いは、 area() 関数には追加の構造型修飾があることです。このようにして、Go はこれが構造に対して定義されたメソッドであることを認識します。
ここで注意すべき点は、構造体上で定義された関数は一般にメソッドと呼ばれることです。
構造体とポインター
ポインターのセクションで、ポインターの主な機能は関数内で渡された変数の値を変更することであると述べました。四角形の面積を計算する上記の例では、コードを次のように変更できます。
package main import ( "fmt" ) type Rect struct { width, length float64 } func (rect *Rect) area() float64 { return rect.width * rect.length } func main() { var rect = new(Rect) rect.width = 100 rect.length = 200 fmt.Println("Width:", rect.width, "Length:", rect.length, "Area:", rect.area()) }
上記の例では、新しい関数を使用して構造体ポインター rect が作成されます。 rect のタイプは *Rect です。構造体がポインターに遭遇した場合、* を使用して構造体のメンバーにアクセスする必要はなく、.reference を使用するだけです。したがって、上記の例では、rect.width=100 と rect.length=200 を直接使用して構造体のメンバー値を設定しています。この時点では、rect は構造体ポインタであるため、area() 関数を定義するとき、構造体は *Rect に限定されます。
実際、面積を計算するこの例では、長方形の幅や長さを変更する必要がないため、面積関数を定義するときに、構造体の種類を Rect に制限しても大丈夫です。以下のように:
package main import ( "fmt" ) type Rect struct { width, length float64 } func (rect Rect) area() float64 { return rect.width * rect.length } func main() { var rect = new(Rect) rect.width = 100 rect.length = 200 fmt.Println("Width:", rect.width, "Length:", rect.length, "Area:", rect.area()) }
Go はここでは十分に賢いので、rect.area() も可能です。
構造体ポインタを使用しない場合とポインタを使用しない場合、出発点は同じです。つまり、関数内で渡されたパラメータの値を変更しようとしているかどうかです。別の例は次のとおりです。
package main import ( "fmt" ) type Rect struct { width, length float64 } func (rect *Rect) double_area() float64 { rect.width *= 2 rect.length *= 2 return rect.width * rect.length } func main() { var rect = new(Rect) rect.width = 100 rect.length = 200 fmt.Println(*rect) fmt.Println("Double Width:", rect.width, "Double Length:", rect.length, "Double Area:", rect.double_area()) fmt.Println(*rect) }
この例の出力は次のとおりです。
{100 200}
Double width: 200 Double Length: 400 Double Area: 80000
{200 400}
構造体埋め込み型
構造体内に別の構造体型のメンバーを定義できます。たとえば、iPhone も電話です。例を見てみましょう:
package main import ( "fmt" ) type Phone struct { price int color string } type IPhone struct { phone Phone model string } func main() { var p IPhone p.phone.price = 5000 p.phone.color = "Black" p.model = "iPhone 5" fmt.Println("I have a iPhone:") fmt.Println("Price:", p.phone.price) fmt.Println("Color:", p.phone.color) fmt.Println("Model:", p.model) }
出力結果は次のとおりです:
I have a iPhone: Price: 5000 Color: Black Model: iPhone 5
在上面的例子中,我们在结构体IPhone里面定义了一个Phone变量phone,然后我们可以像正常的访问结构体成员一样访问phone的成员数据。但是我们原来的意思是“iPhone也是(is-a)Phone”,而这里的结构体IPhone里面定义了一个phone变量,给人的感觉就是“iPhone有一个(has-a)Phone”,挺奇怪的。当然Go也知道这种方式很奇怪,所以支持如下做法:
package main import ( "fmt" ) type Phone struct { price int color string } type IPhone struct { Phone model string } func main() { var p IPhone p.price = 5000 p.color = "Black" p.model = "iPhone 5" fmt.Println("I have a iPhone:") fmt.Println("Price:", p.price) fmt.Println("Color:", p.color) fmt.Println("Model:", p.model) }
输出结果为
I have a iPhone: Price: 5000 Color: Black Model: iPhone 5
在这个例子中,我们定义IPhone结构体的时候,不再定义Phone变量,直接把结构体Phone类型定义在那里。然后IPhone就可以像访问直接定义在自己结构体里面的成员一样访问Phone的成员。
上面的例子中,我们演示了结构体的内嵌类型以及内嵌类型的成员访问,除此之外,假设结构体A内部定义了一个内嵌结构体B,那么A同时也可以调用所有定义在B上面的函数。
package main import ( "fmt" ) type Phone struct { price int color string } func (phone Phone) ringing() { fmt.Println("Phone is ringing...") } type IPhone struct { Phone model string } func main() { var p IPhone p.price = 5000 p.color = "Black" p.model = "iPhone 5" fmt.Println("I have a iPhone:") fmt.Println("Price:", p.price) fmt.Println("Color:", p.color) fmt.Println("Model:", p.model) p.ringing() }
输出结果为:
I have a iPhone: Price: 5000 Color: Black Model: iPhone 5 Phone is ringing...
接口
我们先看一个例子,关于Nokia手机和iPhone手机都能够打电话的例子。
package main import ( "fmt" ) type NokiaPhone struct { } func (nokiaPhone NokiaPhone) call() { fmt.Println("I am Nokia, I can call you!") } type IPhone struct { } func (iPhone IPhone) call() { fmt.Println("I am iPhone, I can call you!") } func main() { var nokia NokiaPhone nokia.call() var iPhone IPhone iPhone.call() }
我们定义了NokiaPhone和IPhone,它们都有各自的方法call(),表示自己都能够打电话。但是我们想一想,是手机都应该能够打电话,所以这个不算是NokiaPhone或是IPhone的独特特点。否则iPhone不可能卖这么贵了。
再仔细看一下接口的定义,首先是关键字type,然后是接口名称,最后是关键字interface表示这个类型是接口类型。在接口类型里面,我们定义了一组方法。
Go语言提供了一种接口功能,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口,不一定非要显式地声明要去实现哪些接口啦。比如上面的手机的call()方法,就完全可以定义在接口Phone里面,而NokiaPhone和IPhone只要实现了这个接口就是一个Phone。
package main import ( "fmt" ) type Phone interface { call() } type NokiaPhone struct { } func (nokiaPhone NokiaPhone) call() { fmt.Println("I am Nokia, I can call you!") } type IPhone struct { } func (iPhone IPhone) call() { fmt.Println("I am iPhone, I can call you!") } func main() { var phone Phone phone = new(NokiaPhone) phone.call() phone = new(IPhone) phone.call() }
在上面的例子中,我们定义了一个接口Phone,接口里面有一个方法call(),仅此而已。然后我们在main函数里面定义了一个Phone类型变量,并分别为之赋值为NokiaPhone和IPhone。然后调用call()方法,输出结果如下:
I am Nokia, I can call you!
I am iPhone, I can call you!
以前我们说过,Go语言式静态类型语言,变量的类型在运行过程中不能改变。但是在上面的例子中,phone变量好像先定义为Phone类型,然后是NokiaPhone类型,最后成为了IPhone类型,真的是这样吗?
原来,在Go语言里面,一个类型A只要实现了接口X所定义的全部方法,那么A类型的变量也是X类型的变量。在上面的例子中,NokiaPhone和IPhone都实现了Phone接口的call()方法,所以它们都是Phone,这样一来是不是感觉正常了一些。
我们为Phone添加一个方法sales(),再来熟悉一下接口用法。
package main import ( "fmt" ) type Phone interface { call() sales() int } type NokiaPhone struct { price int } func (nokiaPhone NokiaPhone) call() { fmt.Println("I am Nokia, I can call you!") } func (nokiaPhone NokiaPhone) sales() int { return nokiaPhone.price } type IPhone struct { price int } func (iPhone IPhone) call() { fmt.Println("I am iPhone, I can call you!") } func (iPhone IPhone) sales() int { return iPhone.price } func main() { var phones = [5]Phone{ NokiaPhone{price: 350}, IPhone{price: 5000}, IPhone{price: 3400}, NokiaPhone{price: 450}, IPhone{price: 5000}, } var totalSales = 0 for _, phone := range phones { totalSales += phone.sales() } fmt.Println(totalSales) }
输出结果:
14200
上面的例子中,我们定义了一个手机数组,然后计算手机的总售价。可以看到,由于NokiaPhone和IPhone都实现了sales()方法,所以它们都是Phone类型,但是计算售价的时候,Go会知道调用哪个对象实现的方法。
接口类型还可以作为结构体的数据成员。
假设有个败家子,iPhone没有出的时候,买了好几款Nokia,iPhone出来后,又买了好多部iPhone,老爸要来看看这小子一共花了多少钱。
import ( "fmt" ) type Phone interface { sales() int } type NokiaPhone struct { price int } func (nokiaPhone NokiaPhone) sales() int { return nokiaPhone.price } type IPhone struct { price int } func (iPhone IPhone) sales() int { return iPhone.price } type Person struct { phones []Phone name string age int } func (person Person) total_cost() int { var sum = 0 for _, phone := range person.phones { sum += phone.sales() } return sum } func main() { var bought_phones = [5]Phone{ NokiaPhone{price: 350}, IPhone{price: 5000}, IPhone{price: 3400}, NokiaPhone{price: 450}, IPhone{price: 5000}, } var person = Person{name: "Jemy", age: 25, phones: bought_phones[:]} fmt.Println(person.name) fmt.Println(person.age) fmt.Println(person.total_cost()) }
这个例子纯为演示接口作为结构体数据成员,如有雷同,纯属巧合。这里面我们定义了一个Person结构体,结构体内部定义了一个手机类型切片。另外我们定义了Person的total_cost()方法用来计算手机花费总额。输出结果如下:
Jemy
25
14200
更多go语言知识请关注PHP中文网go语言教程栏目。
以上がGo言語の構造結合関数の紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。