Chapter8 関数の作り方

これまで学んできたキーボードからのデータの入力や処理結果の表示に使ったscanfprintfの命令は、実は関数といわれるものである。あるまとまった処理をするためにいくつかの命令を組み合わせた小さなプログラムを関数という。C言語には標準関数という便利な多くの関数が用意されている。また、ユーザが処理したい目的に合わせて関数を作ることもできる。この関数をユーザ関数という。

これまでにもすでにswap関数など作成し使ってきたが、ここでは、体系的に関数について紹介し、自分でいろいろな関数を作成する方法を学び、これらの関数を組み合わせて大きなプログラムを組む方法を学ぶ。

 

標準関数

<math.h>で定義されている関数

acos(x), asin(x), atan(x), ceil(x),cos(x),exp(x),fabs(x),floor(x),log(x),log10(x),pow(x,p),sin(x),sqrt(x),tan(x)

 

ユーザ関数

ユーザ関数は、ヘッドとボディでできている。例えば、

int cube(int x)

{

 return x*x*x;

}

において、int cube(int x)がヘッドで、return-type name(parameter-list)の形をとる。ボディは

{ return x*x*x; }である。

ユーザ関数を定義したら、すぐにその関数をテストしてみる。このテストに用いるプログラムをテスト ドライバという。テスト ドライバは”quick and dirty”でよい。つまり、普通のプログラムのような、ユーザプロンプト、出力用のラベル、ドキュメントの必要はない。

 

例題8.1

2つの値のうち大きいほうを返すmax( )関数のテストドライバを作成しよう。

 

#include<iostream.h>

int max(int x, int y)

{

             if (x < y) return y;

             else return x;

}

 

int main()

{

             int m,n;

             do

             { cin >> m >> n;

              cout << “max(“<< m << “,” << n << “) = “ << max(m,n) << endl;

             }

             while (m != 0);

}

実行例 

 

この例では、メイン関数の前にユーザ関数のすべてが表示されている。これは、最も単純なプログラムの書き方で、テストドライバにはこれでもよい。

もっと一般的な書き方は、関数のヘッダだけメイン関数の前に書き、ヘッドとボディをメイン関数の後に書く方法である。これにより、関数を別々にコンパイルすることができ、大きなプログラムへと発展できる。

 

例題8.2

上で定義したmax( )関数とメイン関数を別々にコンパイルしてみよう。

 

test_max.cpp

int max(int, int);

 

int main()

{

             int m,n;

             do

             { cin >> m >> n;

              cout << “\t max(“ << m << “,” << n << “) = “ << max(m,n) << endl;

             }

             while (m != 0);

}

 

 

max.cpp

int max(int x, int y)

{ if (x < y) return y;

 else return x;

}

 

UNIXでは

>> gcc -c max.c

>> gcc -c test_max.c

>> gcc -o test_max test_max.o max.o

>> test_max

と打つ。

 

引数のない関数

例題8.3

学校名、所在地を表示するプログラムを作成しよう。ただし、学校名、所在地を表示するそれぞれの関数を作成して使用する。

実行例

               

考え方

学校名を表示する関数をschool、所在地を表示する関数をaddressとする。これらをmain関数から呼び出して使用するプログラムを書けばよい。
 

#include <stdio.h>

void school(void); /* プロトタイプ宣言 */

void address(void);

void main(void)

{

             school();

             address();

}

/* 関数schoolの定義*/

void school(void) 

{

             printf("広島工業大学\n");

}

/* 関数addressの定義*/

void address(void)

{

             printf("広島市佐伯区三宅2-1-1\n");

}

解説 学校名を表示する関数schoolは、schoolが呼ばれたら学校名を表示すればよい。同様にaddressも住所を表示すればよい。これらの関数を呼び出すにはmain関数でschool( )address( )とすればよい。

このプログラムを実行すると、main関数から呼び出された関数schollに制御が移り、学校名を表示する。次に制御はmain関数に移り、今度は関数addressを呼び出して住所を表示する。

関数school(void)のように( )の中にvoidと書いた関数を引数のない関数という。

上のプログラムは関数を使わず、printfだけでも同じ結果を得ることができる。では、なぜ関数を使ったのかを考えてみよう。例えば、学校名、所在地以外に自分の名前や学生番号などを表示したいとしよう。もしプログラムが関数を使わずprintfで書かれていたとしよう。すると、自分の名前や学生番号を表示するためには、main関数の中身を修正する必要があり、エラーが発生したときにmain関数の中をすべてチェックしなければならなくなる。しかし、上のプログラムのように関数を用いて書いておけば、少なくとも学校名と所在地を表す関数にはエラーがないことは分かっているので、エラーが発生した場合は追加した自分の名前と学生番号を表示する関数だけチェックすればエラー修正ができる。さらに、プログラムを読んだとき、main関数だけで何をしているのか分かり、読みやすいプログラムを作成することができる。

引数のある関数(値呼び出し法)

例題8.4 データを読み込んで和と差を求める

main関数から実数データa,bを関数sumと関数diffに渡して、その和と差を計算して表示するプログラムを作成しよう。

実行例

               

考え方

1.2つの値x,yの和を計算する関数をsum、差を計算する関数をdiffとすると、関数sumdiffは次のようになる。 

float sum(float x, float y)

{

             float s;

             s = x+y;

             return (s);

}

float diff(float x, float y)

{

             float d;

             d = x-y;

             return (d);

}

2.main関数から読み込んだa,bの値を関数sumに渡す。同様に関数diffに渡す。

3.計算結果はreturn文を使ってmain関数に戻す。この戻される値を戻り値という。この戻り値は実数なので、関数の前にfloatがついている。ではプログラムを書いてみよう。

解答 

#include <stdio.h>

float sum(float, float); /* プロトタイプ宣言 */

float diff(float, float);

void main(void)

{

             float a,b;

             printf("2つの数字を入力してください\n");

             scanf("%f%f",&a,&b);

             printf("入力した2つの数%f%fの和は%fです\n",a,b,sum(a,b));

             printf("入力した2つの数%f%fの差は%fです\n",a,b,diff(a,b));

}

/* 関数sumの定義*/

float sum(float x,float y)

{

             float s;

             s = x+y;

             return (s);

}

/* 関数diffの定義*/

float diff(float x,float y)

{

             float d;

             d = x-y;

             return (d);

}

実引数と仮引数

関数sum(a,b)を呼び出すときに使用する( )の中のa,b実引数という。また、呼び出される関数sum(float x,float y)の中のx,y仮引数という。仮引数x,yには実引数の値a,bが渡されて計算に使用され、その結果をreturn文でmain関数に戻す。このとき、実引数と仮引数の型は同じでなければならない。このように実引数の値を仮引数に渡す方法を値呼び出し法call by value)という。この方法では仮引数の値を実引数に渡すことはできないし、戻せる値は1つである。では、仮引数を実引数に渡すにはどうすればよいのだろうか。その答えは、参照による呼び出し(アドレス渡し)を行うことである。

関数の再利用

これまで、数々の関数を作成してきた。新しいプログラムを作成するとき、すでに作成した関数を使いたいことがある。ここでは、すでに作成した関数を再利用する方法を紹介する。

1.        例題8.4で用いた関数sumdiffを別々に名前を付けて保存しておく。ここでは、sum.cdiff.cとしておく。次に、main関数だけをreidai8-4.cとして保存し、次のようにコンパイルしてみよう。

              gcc reidai8-4.c sum.c diff.c -o reidai8-4.exe

すると、例題8-4と同じ結果を得ることができる。このように、Cプログラムでも、同時に2個以上のプログラムをコンパイルすることができる。

2.        まず、yokota.hというヘッダファイルを作成し、sum.cdiff.cyokota.hにコピーしておく。次に、yokota.hCドライブのprogramというフォルダに格納したとする。その後、main関数に#include “C:\program\yokota.h”を追加しておく。ここで次のようにコンパイルしてみよう。

              gcc reidai8-4.c -o reidai8-4.exe

すると、例題8-4と同じ結果を得ることができる。このように、Cプログラムでは、自分の作成した関数を必要なときにインクルードすることができる。

配列の引渡し

例題8.5 データを配列に読み込んで平均を求める

配列a[ ]n個のデータを読み込み、データの平均を求めて表示するプログラムを作成しよう。ただし、データの平均は関数ave_dataで求めるものとする。また、データの個数nも入力するものとする。

実行例

               

考え方

main関数で配列aにデータを入力し、そのデータを平均値を求める関数ave_dataに渡す。配列とポインタとは兄弟のようなものであることを7章で学んだ。つまり、配列aを関数ave_dataに渡すには配列aのアドレスを渡してやればよい。配列aのアドレスは配列名aまたは先頭アドレス&a[0]である。つまり、main関数の中で

              ave = ave_data(a,n);

と書き、関数ave_data

float ave_data(float x[ ],int n)

{

              float s=0,

              int i;

              for (i = 0;i < n;i++)

              {

                            s = s + x[i];

              }

              return (s/n);

}

とすれば、main関数の中でave_dataが呼ばれたとき、ave_dataは配列aのアドレスと読み込んだデータの数nを仮引数x[ ]nに渡す。その後、関数ave_dataは平均を計算し、その値をmain関数に戻す。では、プログラムを書いてみよう。

解答 

#include <stdio.h>

float ave_data(float * ,int );

#define MAX 10

void main(void)

{

              int i,n;

              float ave,a[MAX];

              printf("データの個数を入力してください\n");

              scanf("%d",&n);

              for(i=0;i<n;i++)

              {

                           printf("a[%d] =",i);

                           scanf("%f",&a[i]);

             }

             ave = ave_data(a,n);

             printf("%f\n",ave);

}

float ave_data(float x[],int n)

{

             float s=0;

             int i;

             for (i=0;i<n;i++)

             {

                           s = s+x[i];

             }

             return s/n;

}

引数のある関数(参照による呼び出し)

例題8.6 2つの数a,b を読み込み、main関数からa,bをユーザ関数bin_opに渡して、和、差を計算し、その結果をmainに戻して表示するプログラムを作成しよう。

実行例

               

考え方 

例題8.4との違いは1つの関数で和と差を計算させその結果をmainに戻すことである。つまり、ユーザ関数bin_opは和、差を計算し、その結果をmainに戻さなければならない。値呼び出し法では、return文を使って1つの値しか戻すことができない。そこで、main関数の実引数は、変数のアドレスとし、ユーザ関数bin_opの仮引数は渡されたアドレスを格納できるように、ポインタを対応させる。もう少し詳しく説明しよう。

Main関数内で

bin_op(a,b,&sum,&diff);

printf(“%f%f”,sum,diff);

と書き、ユーザ関数を

void bin_op(float x, float y, float *add, float *subtract)

{

              *add = x + y;

              *subtract = x - y;

}

と書くと、実引数a,b,&sum,&diffが仮引数x,y,*add,*subtractに渡される。その後、x+yの結果がポインタ変数*addの中に格納されているアドレスを番地とする変数sumに代入される。よって、printfsumの値が表示されることになる。関数bin_opは値を返さないのでvoid bin_op(float x, float y, float *add, float *subtract)となる。では、プログラムを書いてみよう。

解答 

#include <stdio.h>

void bin_op(float, float, float *, float *); /* プロトタイプ宣言 */

void main(void)

{

             float a,b,sum,diff;

             printf("2つの数字を入力してください\n");

             scanf("%f%f",&a,&b);

             bin_op(a,b,&sum,&diff);

             printf("入力した2つの数%f%fの和は%fです\n",a,b,sum);

             printf("入力した2つの数%f%fの差は%fです\n",a,b,diff);

              

}

/* 関数bin_opの定義*/

void bin_op(float x,float y, float *add, float *subtract)

{

             *add = x + y ;

             *subtract = x - y;

}

文字列操作関数

C言語は、システム言語として生まれたので、文字列を操作する標準関数が用意されている。

下に挙げる関数を用いるには、ヘッダファイルstring.hをインクルードする必要がある。 

関数名

機能

strlen (string lengthの略)

文字列の文字の個数を求める

strcmp (string comparisonの略)

ある文字列と他の文字列を比較する

strcpy (string copyの略)

ある文字列を他の文字列に複写する

strcat (string concatinationの略)

ある文字列に他の文字列を連結する

“hello world”は文字列よばれ各要素が1文字1バイトである配列として保存される。このとき文字列の内部表現では最後にナル文字(null character)’\0’が置かれるので、必要な物理的記憶容量は、実際に書かれた文字の個数よりも1つ多くなる。関数strlen(s)’\0’を除いた文字列引数sの長さを求めるものである。

 

/* strlen関数: sの長さを返す*/

int strlen(char s[ ])

{

             int i;

              

             i = 0;

             wile (s[i] != 0)

             ++I;

             return i;

}

例題8.7 strlen関数を用いて文字数を求める

hello world”ハロー ワールドの文字数を求めるプログラムを作成しよう。

実行例

               

考え方

文字列str1[ ]”hello world”を代入し、文字列str2[ ]"ハロー ワールド"を代入する。次に、関数strlenに引数として配列名str1str2を渡し、計算結果を表示すればよい。では、プログラムを書いてみる。

解答 

/*文字列の長さの計算*/

#include <stdio.h>

#include <string.h>

void main(void)

{

             int n1,n2;

             const char str1[ ] = "hello world";

             const char str2[ ] = "ハロー ワールド";

             n1 = strlen(str1);

             n2 = strlen(str2);

             printf("文字列1の文字数=%d\n",n1);

             printf("文字列2の文字数=%d\n",n2);         

}

C入門  / 次の章