4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Wanoグループ Advent Calendar 2019

Day 21

goの配列・sliceを書いたら苦悩しましたこと

Last updated at Posted at 2019-12-20

#Java/C#風に書いたら駄目でした

型付きだから、もうPerlの世界じゃないと思い、Java/C#風に書いたら駄目でした

func arr1(){
	a1 := [2]int{1, 2}
	a2 := [3]int{1, 2, 3}

	a1 = a2 //ここは焦点

	fmt.Println(a1, a2)
}
// mainからの呼び出し部分は省略。下同。
$ go run array_slice.go                                                                                                                             
./array_slice.go:9:5: cannot use a2 (type [3]int) as type [2]int in assignment

型違いと言われているので、下記とおり修正したら、うまくいきました:

func arr2(){
	a1 := [3]int{1, 2} //a1の長さも3に変更
	a2 := [3]int{1, 2, 3}

	a1 = a2 //ここは焦点

	fmt.Println(a1, a2)
}
// output: [1 2 3] [1 2 3]

どっかで見かけたようでそこまで気にしなかったこと: 配列の長さも、その型定義の一部なので、要素型の同じ配列でも 長さが違うと別型扱いとなります。

これは、コンパイルすれば教えてくれるものなので、まだまだそこまで鬱陶しいものではないかもしれません。問題なのは後述のこっそりさられる系⬇

v := [長さn]Type{要素数m} の新発見

上記コンパイルエラー対応するため、慌ててa1の長さを2から3に変更することで通させたんですが、ソースコードa1 := [3]int{1, 2}をよく見てみると、長さの指定3と中身の記述{1, 2}個数がずれています。興味持って、いくつかパタン試し:

func arr3(){
	a1 := [2]int{} // output: [0 0]
	a2 := [2]int{1, 2} // output: [1 2]
	a3 := [2]int{1, 2, 3} // array index 2 out of bounds [0:2]
	fmt.Println(a1, a2, a3)
}

(実は言うまでもないかもしれない)新発見v := [長さn]Type{要素数m}に対して

  • n < m の場合、コンパイルエラー(out of bounds)
  • n >= m の場合、コンパイルOK(m+1 ~ n 個目要素の値は、その要素型の0値となると言われてます)

#新発見由来の苦悩
でも、a2 := [2]int{1, 2}のような記述を見ると、なんとなく違和感があるように思えます。
そもそも、{}に要素記述するれば、配列長さは自動的に検知できるものでしょう、なのにわざわざnを指定するのは、例のDRY原則と相違するじゃないか。
そう思って、n無し版a2 := []int{1, 2}試してみたら、無事コンパイルできました! じゃぁ、今後はもうn指定なんかやめる!(このとき自分は既にarrayからsliceへと別物作っていることとは知らなかった)
でも、さらにいろいろ試してみたら、予想通りに動かないところがありました:

func arr_or_slice_1() {
	v1 := [1]string{"元の値"} // n指定
	v2 := v1
	v2[0] = "新しい値"
	fmt.Printf("v1[0]:%v\n", v1[0]) // output: v1[0]:元の値
}
func arr_or_slice_2() {
	v1 := []string{"元の値"} // n指定なし
	v2 := v1
	v2[0] = "新しい値"
	fmt.Printf("v1[0]:%v\n", v1[0]) // output: v1[0]:新しい値
}

n指定有りなしで、結果が変わってます。これは考えもしなかった結果です。 今までのコーディング経験のベースのところと衝突しているくらいなので、しばらくは目を疑い、更にパソコン壊れているかを疑いました。自分ひとりではどうしても抜き出せないところでしたので、色んな方にこの悩みを聞くことにしました。 当時は、確かだれかからsliceになっているからと言われた記憶がありますが、sliceだからarrayと違うという前提知識は頭になかったから、理解できませんし聞き逃しました。

#配列は値型、スライスは参照型
しばらくして、またgoのことを勉強・やってみようと思い、ある記事を呼んでみたら、配列は値型、スライスは参照型との記述に目を留め、実際に手を動かして見たら、この前の苦悩は、一気に解消しました:

func arr_and_slice() {

	//n指定の場合は 配列
	a1 := [1]int{1}
	a2 := a1
	a2[0] = 2
	fmt.Println(a1, a2) // output: [1] [2]

	//n指定無しの場合は slice
	s1 := []int{1}
	s2 := s1
	s2[0] = 2
	fmt.Println(s1, s2) // output: [2] [2]
}

ということで,以下2のことが分かったことで苦悩解消:

  • n指定の場合は 配列,指定なしの場合はslice
  • 配列は値型、スライスは参照型

上記書いた悩みなどは、自分が勉強不足で発生したもので、まだまだ知っていれば大丈夫レベルです。
それより、sliceのappendとcapとの間に纏わる知っていても、気が付かないとやられることも書きたかったですが、記事書くことは本当に予想以上に時間・頭かかることで、取り敢えずここまでとすることにしました。
今後も是非記事書くことに頑張っていきたいと思います。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?