Go言語 ~基礎編~ Slices

go
KOUKI
KOUKI

Sliceを学んでいきましょう。

前回は、Arrayについて学びました。

Arrayは、大量のデータを扱う時にとても便利なのですが、一つ問題があります。それは、「一度宣言したArrayの要素数を超えて、データを格納することができない」ことです。

この制約は、システム開発する上で、バグを引き起こすトリガーになりえます。

そこで、Sliceの出番です。

Sliceは、Arrayと同じく大量のデータを扱うことを目的に作られたコレクションタイプの構造体で、データの追加機能を備えています

Sliceは、Go言語の中でも重要なキーワードの一つなので、しっかり学んでいきましょう^^

学習記録

Slices

変数宣言後に、データ追加を可能にするGoのデータ構造体を「Slice(スライス)」と呼びます。

宣言方法は、次の通りです。

Arrayとは違って、要素数の指定は必要ありません。

KOUKI
KOUKI

一見便利そうなSliceですが、使い方に注意が必要です。

Arrayと違ってSliceは、宣言しても自動的にsliceが作成されません。次のサンプルコードを見てください。

このサンプルコードは、ArrayとSliceを初期値なしで宣言したものです。どのように出力されるでしょうか?

ご覧の通り、Arrayの場合は、宣言されたTypeの初期値でArrayが作成されています。一方、Sliceの場合は、”空”になっていますね。

Sliceを作成するためには、make関数を利用します。

もちろん、SliceにもShort Variable Declarationが使えます。

また、Sliceにもlen関数が使えます。

もちろん、forやfor…rangeも使えます。

Sliceリテラルを利用すると初期値を設定できます。

先ほどの宣言と合わせて、サンプルコードを置いておきます。

Sliceについては、大体わかったわ。でも、Sliceを使えば、Arrayは必要なくないかしら?

A子
A子
KOUKI
KOUKI

確かにほとんどの場合は、Sliceを使うと思います。しかし、Arrayを理解しているとSliceの理解が早くなるので、先にArrayを攻略することをお勧めします。

Arrayについては、こちらの記事を参考にしてください。

Sliceオペレータ

Sliceを作成するためには、「make関数」か「Sliceリテラル」を使用する必要がありますが、以下の方法でも作成することが可能です。

サンプルコードでお見せします。

KOUKI
KOUKI

気をつけたいポイントは、myArray[i:j]のjですね。指定した数-1の要素まで返却されます

直感的ではないこの挙動。実は、私が大好きなPythonでも同様です

KOUKI
KOUKI

ちなみに、要素外にアクセスした場合は、Arrayと同様にエラーになります。

要素変更の注意点

ArrayやSliceで要素が変更する時に注意点があります。

ひとまず、次のサンプルコードをみてください。

myArrayには、「HELLO」の文字列を格納しましたが、その後、「H」->「F」に書き換えました。

この出力結果は、次のようになります。

KOUKI
KOUKI

おやおや、myArrayだけではなく、mySliceに格納した値も変更されています

このことから、Sliceは、Arrayのデータを参照しているだけであると分かります。

ArrayからSliceを作成できますが、上記のような不思議な動作になるので、バグになりやすいです

できれば、「make関数」か「slice リテラル」を使いましょう。

Sliceに要素を追加する

Arrayと違って、Sliceには要素を追加できる「append関数」があります。

サンプルコードで挙動を確認してみましょう。

実行してみましょう。

append関数の第一引数にSliceを指定し、第二引数以降に追加したい要素を順番に設定していくだけです(簡単ですよね)。

しかし、append関数はバグを生み出しやすいです。次のサンプルをみてください。

joyくん、meryさん、 saraさん、genくんのスライスを用意しました。

その後、genくんの一番目の要素には、TAGUCHIくんを格納しています。

出力結果はどうなると思いますか?

試してみましょう。

genくんの一番目の要素にTAGUCHIくんを入力しました。その影響が、saraさんの一番目の要素にも現れています。

これは、saraさんとgenくんが同じ戻り値の情報を元に作成されたものだからです。その証拠にmeryさんとjoyくんは変更が共有されません。

上記の挙動から参照情報は上書きされていくと思われます。

meryさんの一番目の要素が変更されたら、meryさんを元に作られたsaraさんの要素にも影響がありそうですが、実際は影響なしだからです。

この挙動は、本当にわかりずらいですよね?

バグを回避するためには、append関数の戻り値は、「同じ変数名」で受け取ることが大切です。

Sliceの初期値

Sliceの初期値には、指定したtypeの初期値が指定されます。

サンプルコードを見てみましょう。

これは、Arrayと同じ性質です。しかし、Arrayと違って、slice変数自身は、nilを持ちます。

この状態の場合、len関数を用いて要素数を出力すると0が出力されます。

よって、条件判定を行いたい場合は、次のようにすれば良いでしょう。

やってみよう1

KOUKI
KOUKI

久々のやってみようのコーナーです。

プログラミング学習は、アウトプットが大切です。

次のプログラムを作ってみましょう。

要件

・dataファイルを読み込む
・dataファイルには、整数が格納されている
・dataファイルには、整数以外の値が含まれている場合がある
・読み込んだ変数の平均値を求める
  

KOUKI
KOUKI

簡単なプログラムですが、調べないとわからないと思います。余力がある人は、独力でやってみてください。

まずは、作業フォルダとファイルを作成します。

data.txtの中身は次の通りです。

data/ints.goには、data.txtを受け取って中身を取り出す処理を実装します。

呼び出し元から「data.txt」を受け取ったら中身を取り出して、数値の場合はint型のスライスに格納します。

strconv.Atoi関数で文字列->数値に変換しており、数値に変換できない場合は、エラーが発生します。

続いて、average/main.goを実装しましょう。

averageフォルダ内で、「go run main.go」を実行してみましょう。

問題なさそうですね。文字列(四十)もエラー処理できているようです。

コマンドライン引数

Sliceのついでにコマンドライン引数についても軽く触れておきましょう。

下記のフォルダとファイルを作成してください。

コマンドラインからの入力を受け取りたい場合は、「os.Args」関数を用います。

terminal上で実行してみましょう。

このように簡単にユーザーの入力項目が取得できました。

os.Argsは、コマンドラインの引数を「String typeのSlice」として受け取ります。

やってみよう2

やってみよう1では、既存のファイルから値を読み取り、平均値を算出しました。今度は、コマンドライン引数から平均値を算出してみましょう。

サンプルを載せておきます。

可変長引数

可変長引数について少しだけ触れておきます。

次のサンプルコードを見てください。

これを実行します。

予想通りの出力結果が出ましたね。

では、次の関数を実行するとどうなるでしょうか?

ざっくり説明すると2つの引数を受け取るargsTest関数に、”複数個“の引数を渡しています。

これは当然コンパイルエラーになります。

最初のサンプルの実行では、”複数の引数を指定した実行は可能“でした。この違いは何なのでしょうか?

実は、これは「可変長引数」と呼ばれる実装によるものです。

可変長引数は、「呼び出し側の引数がいくら増えても呼び出し先で受け止めてくれる」便利な引数です。

次のように実装します。

最後のパラメータのtypeに「…」を付与することで、値をスライスで受け取れるようになります。

では、先ほどの関数を可変長引数バージョンに書き直して見ましょう。

実行して見ましょう。

可変長引数で受け取った値が、スライスで取得できていることがわかると思います。

ちなみに、引数を渡さないで実行した場合は、空のスライスが返却されます。

やってみよう3

やってみよう2のサンプルコードを可変長引数版に書き直してみましょう。

for … rangeで数値を合計していた箇所を別関数に切り離し、そこに可変長引数をセットする感じで実装してみます。

恐らく、可変長引数へのスライスの渡し方に若干手こずると思います。

os.Argsから受け取った値は、スライスで変数に格納されます。これを可変長引数に渡したい場合は、「変数名…」のように省略記号を用いる必要があります。

上記の例では、「sum := addNumbers(params…)」がそれに該当します。

プログラムを実行してみましょう。

問題なくプログラムが動いていますね。

まとめ

KOUKI
KOUKI

最後に本章で学習したことをまとめて終わりにしましょう。

Sliceまとめ
・ スライスの宣言方法 -> var mySlice []int
・ Arrayと同様にインデックスで値にアクセスする
・ Arrayと同様にlen関数やfor…rangeループ処理が可能
・ Sliceを作成するには、make関数かSliceリテラルが有効
・ Sliceリテラルを使うと初期化&値格納が同時にできる -> []int{1, 2, 3}
・ sliceオペレーターが使える -> s[i:j]など
・ os.Argsを使うとコマンドライン引数をスライスで受け取れる
・ 可変長引数を設定した関数は、呼び出し元から何個でも引数を受け取れる
・ 可変長引数を設定するには、最後のパラメーターに「…」を設定する
・ 可変長引数は、スライスで受け取る
・ 呼び出し元がスライスを渡す場合は、省略記号を用いる->mySlice…

参考書籍

次回

次回は、Mapについて学んでいきましょう。

コメントを残す