ごまなつ Blog

楽しく働ける世界を目指して

Go言語学習記録

ポインタ

C言語と同じようにポインタがある。

ポインタ型変数宣言は

var p *int = &n

ポインタの処理はCと同じ。メモリ番地と値の関係になっている。 アドレスの値を見たいときは*nのようにする。

s := make([]int, 0)は[]int
m := make(map[stirng]int)はmap[string]int
ch := make(chan int)はchan int
var p *int = new(int)は*int
var st = new(struct{})は*struct{}

makeとnewの違いは、ポインタを返すならnew, そうでないならmakeということ。

構造体

type Vertex struct{
  X, Y int
}
func main(){
  v := Vertex{X: 1, Y: 2}
}
fmt.Println(v.X, v.Y)

で1 2が表示される。 構造体の要素の名前が大文字だとpublic,小文字だとprivateになる。

var v4 Vertex v4 := Vertex{}
v6 := new(Vertex)とv6 := &Vertex{}

はそれぞれ同じ意味になる。

func changeVertex(v Vertex){
  v.X = 1000
}

v := Vertex{1, 2, "test"}
changeVertex(v)
fmt.Println(v)

では、{1 2 test}が表示される。値は変わっていない。構造体の値を渡しただけで、本体は変わっていないため。

v2 := &Vertex{1, 2, "test"}
changeVertex(v2)
fmt.Println(v2)

では、&{1000 2 test}が表示される。fmt.Println(*v2)にすれば、{1000 2 test}が表示される

メソッド、ポインタレシーバー

//関数の書き方
func  Area(v Vertex)int{
  return v.X*v.Y
}
//メソッドの書き方
func (v Vertex) Area() int{
  return v.X*v.Y
}

構造体vがArea()という関数を持つようにするイメージ。 呼び出すときはfmt.Println(v.Area())でできる。 メソッドがvに紐づけられるので、v. とすると補完でArea()が出てくるようになる

func (v *Vertex) Scale(i int){
  v.X = v.X * i
  v.Y = v.Y * i
}

v.Scale(10)で呼び出せるが、もしこの関数のVertexのをなくすとこの関数が読まれなくなる。 この関数をポインタレシーバーという。普通の関数は値をもらうので値レシーバーということがある。

Embedded

goには継承がないが、Embeddedというものがある

type Vertex struct{
  x, y int
}

type Vertex3D struct{
  Vertex
  z int
}

こうすると、Vertexにⅹとyは組み込まれているので、Vertex3Dはx,y,zを持っている. 書き方は

func (v Vertex3d) Area3D() int{
  return v.x * v.y * v.z
}

func New(x, y, z int) *Vertex3D{
  return &{Vertex{x, y}, z}
}

goには継承がないが、Embeddedで同じことができる。

type MyInt int

func (i MyInt) Double() int{
  return int(i * 2)
}

func main(){
  myInt := MyInt(10)
  fmt.Println(myInt.Double())
}

structではない自分だけの型のメソッドを作ることもできる。func (i MyInt) Double() intの中のiの型はmain.MyIntになっている。

ダックタイピング

type Human interface {
    Say() string
}

type Person struct {
    Name string
}

func (p *Person) Say() string {
    p.Name = "Hi, " + p.Name
    fmt.Println(p.Name)
    return p.Name
}

func DriveCar(human Human) {
    if human.Say() == "Hi, Mike" {
        fmt.Println("Run")
    }else{
        fmt.Println("Back off")
    }
}

func main() {
    var mike Human = &Person{"Mike"}
    var x Human = &Person{"X"}
    DriveCar(mike)
    DriveCar(x)
}

interfaceで指定したメソッド(今回はSay())を持っていないとエラーになる。絶対持っていてほしいメソッドを指定するときに使う。 Say()はポインタレシーバーになっているので、アドレスを渡す。func (p *Person)に&Person{"Mike"}を渡すということ。 この例では、 DriveCar(human Human)の時に、Humanは絶対にSay()を持っていなければならないというものになっている。 interfaceで指定したメソッドを絶対に持っていなければならないようにすることをダックタイピングという。

interface型、タイプアサーション

func do (i interface{}){}

のようにinterfaceを引数にするとどの型も受け取れるようになる。しかし、

func do (i interface{}){
  ii := i * 2
}

はエラーとなる。interface型は四則演算ができない。そのため、

ii := i.(int)
ii *= 2

としてタイプアサーションをしなければならない。つまり、interfaceには何でも入るが、入った段階ではinterface型なので、タイプアサーションをするひつようがあるということ。 stringは

ss := i.(string)

とする。 switch type文があり、これを用いて

func do (i interface{}){
  switch v := i.(type)
  {
    case int:
      fmt.Println(v * 2)
    case string:
      fmt.Println(v * "!")
    default:
      fmt.Printf("I dont know %T\n, v)
  }
}

とすると、どんな型でも受け取れる関数ができる。 goでは、一般的なキャストをタイプコンバージョンということもある。interfaceの要素に型をつけるのは、タイプアサーション

type Person struct {
  Name string
  Age  int
}

func (p Person) String() string{
  return fmt.Sprintf("My name is %v", p.Name)
}

func main(){
  mike := Person{"Mike", 22}
  fmt.Println(mike)
}

こうすると、出力結果はMy name is Mikeになり、Ageを出力しないようにできる

カスタムエラー

type UserNotFound struct{
  Username string
}

func (e *UserNotFound) Error() string{
  return fmt.Sprintf("User not found: %v", e.Username)
}

func myFunc() error{
  //something wring
  ok := false
  if ok{
    return nil
  }
  return &UserNotFound{Username: "mike"}
}

func main(){
  if err := myFunc(); err != nil{
fmt.Println(err)

とすると、自分で決めたエラーメッセージを表示できる。UserNotFoundがポインタになっているが、これをポインタにしなくても実行はできる。しかし、エラー内容の比較を行う際に問題が起きる。違うエラーとして定義して比較してもtrueになる。 ファイル入出力に使うライブラリにioがあり、その中にio.EOFがある。(EOFはEnd Of File)自分でErrors.New("EOF")として作っても、io.EOFとは違うEOFになることに注意する。エラーにはポインタを用いること。