連載
» 2010年10月20日 10時00分 公開

目指せ! Cプログラマ(8):よく使う処理は関数にしよう (1/2)

繰り返し登場する処理を1つにまとめる関数を定義することで、プログラムの保守性が高まり、開発効率も良くなります。

[長沼立巳, 小山博史,@IT]

 前回は、プログラムの実行を制御する方法について説明をしました。選択文、繰返し文、複合文、分岐文などを使うことにより、自分が考えたとおりに処理の流れを決めることができました。これで、どんなプログラムでも組めるようになりましたが、これまでの知識だけでプログラムを組もうとすると、同じような処理を何度も書くことになります。これは大変ですから、そういったことをしなくても済むようにしたいところです。

 ということで、プログラミング言語Cには、効率よく開発をするためのいろいろな機能が備わっています。今回は、そのうちの1つである“関数”について説明をします。

 関数を使うと、同じ処理を1つにまとめることができるので、プログラムの保守性が高まり、開発効率も良くなります。また、複雑な処理を1つの関数としてまとめることができるようになるので、処理の見通しがよくなります。一方でいろいろな場所で同じ関数が使われるようになるので、その点を意識しながら作成する必要があるという難しさもあります。

 それでは、関数の文法事項、仕組みをしっかり理解して、活用できるようになりましょう。

入力、処理、出力

 関数は、ある機能を実現するための一連の手続きを定めたものです。

 Cを前提とした設計では、プログラムを作るときに「まずあれをして、次にこれをして」のように、処理の手順を中心に考えます。このとき、「あれ」や「これ」の内容が簡単で短いときもありますが、複雑で長い処理になるときもあります。

 処理が複雑になってくると、ある程度の大きさでまとめて扱った方がプログラマにとって便利になってきます。日本語の文章ではいくつかの文を、段落や章といった要素にまとめるように、Cプログラムでは“関数”にまとめて扱うのです。

 普段、私たちが何かの仕事をするときには、入力があって、処理があって、出力があります。サッカーの選手は、パスを受けて、ドリブルし、パスを出す、という処理を繰り返します。学校の宿題は、先生から宿題を出され、家で問題を解いたり練習したりして、その結果を次の日に提出します。高等教育や社会人になると、最初の部分が自分で課題を作ったりすることが変わっていきますが、いずれにせよ、「入力−処理−出力」という流れは同じです。

 Cにおける関数は、何らかの処理をある程度の大きさでまとめたものです。この処理をするためには入力が必要ですし、処理した結果は出力として得ることができる必要があります。関数では入力のことを“引数(ひきすう)”で表します。また、出力結果を表すための“戻り値”(“返却値”ともいいます)というものを持ちます。

引数と戻り値

 さっそく関数を使ってプログラムを作ってみましょう。次のコードは、引数(入力)としてint型の値を2つ受け取り、2つの値の和を計算し、結果をint型の値として返す、「adder」という名前の関数を定義しています。

int adder(int a1, int a2) {
  return a1 + a2;
}

 1行目の行頭にあるintは、戻り値の型がintであることを表しています。adderという関数名に続く括弧内では、2つのint型の変数をa1およびa2という引数として受け取ることを表します。

 この例からなんとなく予想がつくかもしれませんが、関数は次のように書きます。

戻り値の型 関数名 ( 引数並び ) ブロック
Cの関数の基本形 Cの関数の基本形

 関数を定義すると、他の場所からこの関数を利用することができるようになります。次のサンプルプログラムでは、adderを呼び出しています。

#include <stdlib.h>
#include <stdio.h>
int adder(int a1, int a2) {
  return a1 + a2;
}
 
int main(void) {
  int x;
  x = adder(1, 2);
  printf("x = %d\n", x); // 3
  return EXIT_SUCCESS;
}

 9行目でadder関数を呼び出しています。ここで使われている括弧は“関数呼出し演算子”と呼びます。この演算子の前に関数名、中に呼び出す関数に渡す引数を書きます。例のように書くと、変数a1には1が、変数a2には2が入れられて、関数adderが実行されます。

 関数の実行が終わると、プログラムの実行はまた呼び出した場所に戻ってきます。9行目の評価の順番は次のようになります。

  1. 代入演算子(=)よりも関数呼び出し演算子の方が優先順位が高いため、関数adderを呼び出します。
  2. 関数adderが実行されます。
  3. 関数adderの戻り値を使って、代入演算子を評価します。
優先順位は関数呼び出し演算子のほうが高いので、代入に先立ってadder関数が呼ばれる 優先順位は関数呼び出し演算子のほうが高いので、代入に先立ってadder関数が呼ばれる

 関数を定義するときに書いた引数(adderではa1とa2)を“仮引数”、関数呼び出しの括弧に囲まれた関数に渡す引数(ここでは1と2)を“実引数”と呼びます。仮引数と実引数の数は一致しなければなりません。関数adderは2つの仮引数を持ちますから、呼び出すときには2つの実引数を与えます。

 また、関数は“return”文を使うことにより、値を呼び出し側に戻すことができます。return文に続いて戻り値を書くと、呼び出し側ではその値を受け取ることができます。この値は、関数呼び出し演算子の演算結果、つまり関数呼び出し式の値ということになります。

 return文に指定する値の型は、関数定義で指定した戻り値の型と一致しなければなりません。一致しない場合は暗黙の型変換(*1)が行われ、それでも一致しない場合はコンパイルエラーとなります。

(*1) 暗黙の型変換については第5回で解説しました。


void

 入力がなくても何らかの処理を実行したいこともあります。そんなときは、仮引数を持たない関数を作ることになります。こういった関数では、仮引数を書くべきところに“void”と書きます。同じように、戻り値を持たない関数を用意するときは、戻り値の型をvoidにします。

 違いが分かるように例を見てみましょう。「/* int型の値 */」には「計算結果がint型の値となる式」などを書きます。

// int型の仮引数を2つ持ち、int型の値を返す関数
int adder(int a1, int a2) { return /* int型の値 */; }
// 仮引数を持たず、int型の値を返す関数
int function1(void) { return /* int型の値 */; }
// int型の仮引数を2つ持ち、戻り値を持たない関数
void function2(int a1, int a2) {}
// 仮引数も、戻り値も持たない関数
void function3(void) {}

 戻り値を持たない(void)関数を定義する場合には、戻り値がないので返す値を「return」で指定する必要はありません。ですから、関数の中でreturn文は必要ないように思えますが、実際は途中で処理を終了させたいときもあります。そんなときのために値を指定せずに「return;」と書くことができるようになっています。このため、戻り値を持たない関数は、関数ブロックの終わりを示す“}”に到達した時点か「return;」に到達した時点で、関数の実行を終了します。

 以上のことを理解した上で、関数内に実際の処理を書いた例を見てみましょう。

// 引数xの値が偶数なら1を、奇数なら0を返す関数。
int is_even(int x) {
  if (x % 2 == 0) {
    // return文は関数内のどこでも書くことができます。
    return 1;
  }
  // 値を返す関数は、必ずreturn文で関数を抜けなければなりません。
  return 0;
}
// 引数eが0なら"odd"、1なら"even"と表示する関数。
void print_odd_or_even(int e) {
  if (!e) { // !  は論理否定演算子で、eが0なら1、0以外なら0になります。
    printf("odd\n");
    // 値を返さない関数は、値を指定せずにreturn文を呼び出します。
    return;
  }
  printf("even\n");
  // 値を返さない関数は、return文を省略することもできます。
}
int main(void) {
  int even;
  // 関数の戻り値は呼び出し側で受け取ることができます。evenの値は0になります。
  even = is_even(3);
  // 値を返さない関数は値を受け取ることができません。
  print_odd_or_even(even);
  return EXIT_SUCCESS;
}

コラム●void型

関数の引数や戻り値がないことを表すために“void”というキーワードを使います。このvoidは型の一種でもあります。

ところで、型とは何でしょうか。型については第3回で、次のように説明しました。「変数の宣言には型というものが必要で、型によりその変数に入れることのできる値が制限されます」。変数に代入することのできる値は、型によって制限されています。これはつまり、型ごとに取り得る値が決まっていると言い換えることができます。例えば、Pleiadesにおけるshort型は -128〜127 の256種類の値を取ることができますが、これはつまり、それ以外の値を取ることはできないということでもあります。

このように、ある型が取り得る値のことを“型の値の集合”と呼びます。Pleiadesにおけるshort型の値の集合は { -128, -127, ... -1, 0, 1 ... 126, 127 } です。

さて、void型について値の集合はどうなっているでしょうか。実はvoid型の値の集合は空(から)です。あえて書くなら {} 、つまりvoid型の値というものは存在しないのです。

そのほかvoid型には次のような特徴があります。

  • void型の変数を宣言することはできません。(void型の値がないので)
  • ある型からvoid型にキャスト(強制型変換)することはできますが、その値を使うことはできず、値は捨てられます。
  • 他の型と同じようにtypedefで別の型名を定義するのに利用できます。
  • void型を使って他の型との互換性を持つポインタ(*2)が定義できます。

(*2) ポインタについては今後の記事で解説する予定です。


Index

よく使う処理は関数にしよう

Page1
入力、処理、出力
引数と戻り値
void

Page2
main関数
関数プロトタイプ
inline
オペランドの評価順序
今回学んだこと


       1|2 次のページへ

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。