Go言語 ~基礎編~ 関数

go

学習記録

今回は、Go言語の関数について学びます。

関数の意義

B男
B男

そもそも関数って何の為にあるの?

プログラム言語には必ず「関数」という概念が存在ます。どのような形であれ、私たちはこの「関数」をプログラムを構成する「パーツ」として作成し、プログラム処理の中の至る所で呼び出します。

その為、関数を作成する意義は、「再利用性」にあると私は考えています。

例えば、関数を”使用していない“次のサンプルコードを見てください。

このプログラムは、問題なく動作します。

しかし、コードの重複が目立ちます。

この重複を関数に置き換えて、削除するとスッキリしそうです。

関数の作成

関数を作成するにはどうすればいいのかな?

A子
A子

Go言語で関数を作成するには、funcキーワードを使用します。

私が推奨しているPythonの書き方も合わせて載せておきます。

Go言語はご覧の通り、中括弧({})で関数の処理を包み込みます。この書き方は、javaScriptやPHPと同じであるため、多くの開発者には馴染みがあるでしょう。

関数の命名規則は、変数の命名規則と同じです。

・名前の先頭は、文字で始める(数字不可)
・名前の先頭大文字 -> 外部ファイルからの参照可
・名前の先頭小文字 -> 同一パッケージ内のみ参照可
・キャメルケースを推奨(例: func runTogether(){})

例をあげて見ましょう。

やってみよう

サンプルコードを重複無しの状態にしてみてください。

改修したサンプルコードを載せておきます。

関数で置き換えるとだいぶスッキリして見えますね。

PrintfとSprintf

先ほどのサンプルプログラムには、「%.2f」という記号のようなものが使用されていました。これは、Go言語のフォーマット整形関数の仕様にしたがって記述したフォーマットタイプです。

フォーマット整形関数には、「Printf」と「Sprintf」の2つの関数が存在します。

それぞれの使い方を見てみましょう。

この2つの関数は、実質「同じこと」をしています。

サンプルコードの中で使用されている%fは、引き取る変数のタイプによって以下のように変化します。

フォーマットタイプ出力タイプ
%f浮動小数点
%d整数
%s文字列
%tTrue/False(boolean)
%v任意のタイプ
%#vGoプログラムコードに表示される形式の任意の値
%T引数のタイプ情報(int, stirng, etc…)

実際にコードを見た方が、理解が早いかもしれませんね。

%# は、単なる%よりも多くの情報を出力しますね。

このフォーマットタイプは、出力文字数を指定して綺麗に整えることも可能です。

数値の制御も可能です。

関数のスコープ

関数にもスコープが存在します。

例えば、下記の例では、paintNeeded関数内に宣言された「area」変数にmain関数からアクセスを試みていますが、コンパイルエラーになります。

関数外から参照したい場合は、もっと上のレベルで変数を宣言する必要があります。

関数の戻り値

関数には、戻り値を定義できます。()(丸括弧)の直後にあるタイプ(float64)が戻り値の型です。

関数の「return」キーワードで値を返却します。そして、return実行後、即座に関数から抜けます。

それじゃあ、returnの後に処理を書いても、無駄なんだ?

B男
B男
KOUKI
KOUKI

無駄というか、Go言語のコンパイラは賢いので、コンパイラエラーで教えてくれます。

おお〜賢いね。こんなルールもあるんだ。

B男
B男
KOUKI
KOUKI

分からねえことにルールを探す。そのくっそ地道な努力を、化学って呼んでるだけだ(by Dr.STONE 千空)

話それますが、今アニメでやっているDr.STONE、面白くないですか?

千空氏の化学に向ける情熱、少しは見習いたいものです。

やってみよう

float64の戻り値を返すdouble関数を定義してみましょう。

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

エラーを発生させる方法

A子
A子

知ってた?Go言語には、try-catch(except)構文がないのよ

そうなんです。Go言語には、try-catchと呼ばれる構文が存在しません

代わりに、functionの戻り値に「error」オブジェクトを設定し、受け取った値が、errorかnilかで判定します。

エラーを発生させる方法の一つをご紹介します。

ご覧の通り、エラーは、Errorパッケージ内で定義されています。

ちなみに、Printfみたいにフォーマットされた状態で出力することも可能です。

では、エラーを関数に実装してみます。

try-catchの方法の代わりに「if err !=nil」でエラーチェックします。

戻り値の型には、()(中括弧)の,(カンマ区切り)で指定していますが、引数と同様に名前をつけることもできます。

KOUKI
KOUKI

とはいえ、戻り値に名前をつけてもプログラムが読みやすくなるわけでも、何かが便利になるわけでもありません。むしろ、つけないほうが多いです(私はつけてません)。

関数の引数の実態

KOUKI
KOUKI

関数の引数は、オリジナルからコピーされた値が渡されます。

…? どういうこと?

B男
B男

関数に引数が設定されていた場合、関数の呼び出しの際には、パラメータを渡す必要があります。その際に、パラメータはオリジナルから「コピーしたもの」を渡します。

Go言語は、基本的に値渡し(pass-by-value)の言語なのです。

多くの場合は値渡しでも問題ありませんが、バグを引き起こすことがあります。

具体例をお見せしましょう。

このサンプルは、単純な計算プログラムです。main関数からdoubleパラメータに任意の数値を渡すとその数を2倍にします。

main関数で計算後の値を出力できれば、この関数は問題なく動作していることになります。

では、実行してみましょう。

double関数内で出力したamountは意図した通り、「12」になりました。一方で、main関数内の出力したamountに関しては、「6」になっています。

本来であれば「12」が出力されて欲しいところですが、「オリジナルの値がコピーされたパラメータ」が関数に渡されただけなので、計算結果に反映されません。

KOUKI
KOUKI

この手のバグは、実際の現場でも発生しやすい「バグ」の一つです。テストコードの書き方を覚えて、ガンガン潰していくしかないです。

ポインタ

つまり…関数内で処理した値を呼び出し元で使いたいときは、戻り値として受け取るしかないってことよね?

A子
A子
KOUKI
KOUKI

実は、そうでもないのです。「ポインタ」を使えば、この問題は回避できます。

いよいよ「ポインタ」が出てきました。C言語を使っている人には馴染み深いかもしれないですね。

ポインタを説明する前に、まず変数についてもう少し詳しく説明します。

私たちがプログラム内で変数を定義したとき、メモリ上に保存領域を確保します。

この領域には、一つ一つ、「アドレス」がついており、メモリの位置情報がわかる様になっています。

Go言語では「&(アンパサンド)」を使うとこのアドレスを取得できるのです。

KOUKI
KOUKI

この説明だけでは、意味がわからないですよね?具体例を見ていきましょう。

サンプルプログラムを用意します。

10を格納した変数addressのメモリ上のアドレスは、「0xc00007a008 」と出ました。

この事を頭に入れて、値渡しをしていたサンプルコードのアドレスを見てみましょう。

amount変数が、それぞれ違うアドレスを指していますね?つまり、「別物の変数」として扱われているという事です。アドレスは、変数の場所を指し示しているとも言えます。

A子
A子

アドレスって、現実で言うと住所みたいなものなんだ。東京都練馬区1丁目の佐藤さん宅、2丁目の鈴木さん宅、みたいな感じね。だから、値渡しした変数の中身は書き変わらないのね。

え、じゃあ、「アドレスを指定すれば」変数の書き換えは可能じゃないか?

B男
B男

その通りです。それを可能にするのが「ポインタ」なのです。

Go言語では、変数のアドレスを指し示す値をポインタと読んでいます。理由は、変数の場所を「指し示している」からです。

KOUKI
KOUKI

Pythonなどのスクリプト言語に馴染みのある方には、ポインタは聞き慣れない用語かもしれません。何故なら、私がそうでしたから!「変数のアドレスを差し示すもの、それはポインタ!」と上の画像と一緒に覚えておいてください。ポインタは、Go言語の中でも特に重要な概念の一つです。

ポインタタイプ

Go言語には、int, string, boolなど複数のタイプが存在します。ポインタのタイプには*(アスタリスク)シンボルがタイプの前に付与されます。

もしかしたら見慣れない表記かもしれませんが、「pointer to int」と何度か声に出して、読んでみてください。直ぐに慣れます。

サンプルプログラムでも確認して見ましょう。

確かに*がついてるね。だから何?って感じだけど

B男
B男

どうでもいいことに見えるかもしれませんが、実はこれは結構大切なのです。

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

このサンプルでは、変数のアドレスをポインタタイプに格納しています。ポインタタイプでないと「変数のアドレスを格納できない」からです。

値の変更(by ポインタ)

A子
A子

なんだか、わかってきたわ。変数のアドレスを指し示しているのがポインタで、ポインタを扱うにはポインタタイプで変数を宣言しなくてはならないと。

待って!呼び出し元でアドレスを関数に渡し、呼び出し先の引数をポインタタイプにすれば、値の書き換えが可能になるんじゃないか?

B男
B男
A子
A子

!

一つ一つの仕組みを理解すると「応用が効く」ようになります。B男君は前回の発言を挽回できるほどのひらめきを見せてくれました。

計算結果が意図したものに変わりましたね?

A子
A子

素晴らしい。いや、そのアルキなんとかの知恵がじゃない君のその、一歩一歩問題解決へと楔を打ち続ける、揺らがぬ信念がだよ(by コハク)

本日は、ここまでになります。プログラミング学習は、難しいし眠くなりますよね?Dr.STONEを読んでモチベーションをあげましょう。

参考書籍

次回

次回は、パッケージについて学びましょう^^

コメントを残す