20130703 

練習問題3


[next|prev|index]

1. 関数

C言語の関数は規格化されたサブルーチンです。関数は引数に与えられたデータを基に実行されるプログラムです。実行が終わると、1つ以下の結果を戻り値としてサブルーチンを呼び出した側に戻します。

C言語の関数は値を戻さないものもあります。C言語の関数を数学の関数からイメージしてはいけません。全く別のものです。外見を似せているだけです

関数は以下のような手順で実行されます。

  1. 仮引数を用意して、実引数の値を代入する。(呼び出し側で実行)
  2. 関数プログラムの先頭に移動して、関数のプログラムを実行する。(関数側で実行)
  3. return文に到達したら、戻り値を設定し、関数を呼び出した側に戻る(関数側で実行)
  4. 関数から戻ったら、必要なら戻り値を取り出す。(呼び出し側で実行)

関数の定義

関数はサブルーチンです。プログラムの中に同じ部分が何度も出てきたら、同じものを何度も書かないで、1か所に書いたサブルーチンをいろんな所から呼び出して実行出来るようにすれば、プログラムの記述が短くできます。

ちょっとだけ違っている場合でも、データを受け渡すことができれば、違いを吸収して1つにできます。

C言語の関数の場合、関数を呼びだす側はデータを仮引数に入れて渡し、関数の実行から帰ってくるとき、戻り値でデータを受け取ります。

サブルーチンに括り出す

長いプログラムの中で同じことをしている部分が在れば、この部分を関数にして何度も同じプログラムを書かなくて済むようにする。以下のmain関数だけのプログラムの指示された部分を関数に括りだしてプログラムを書き換えてください。

#include<stdio.h>

int main(void)
{ 
    /*以下の1行を関数startCに*/ 
    printf("/****************************************************\n");

    printf("おはようございます");

    /*以下の3行を関数endCに*/ 
    printf("\n");
    printf("*****************************************************/\n");
    printf("\n");

    printf("/****************************************************\n");
    printf("こんにちは");
    printf("\n");
    printf("*****************************************************/\n");
    printf("\n"); 

    printf("/****************************************************\n");
    printf("こんばんは");
    printf("\n");
    printf("*****************************************************/\n");
    printf("\n");

    return 0;
} 

関数の定義では、関数の名前と引数及び戻すデータ型が必要です。しかし、このstartCやendCではデータを渡す必要が無いので共にvoid型としてください。

解答例

引数を使う

前記のプログラムで「おはようございます」、「こんにちは」、「こんばんは」の部分が異なるのでstartCとendCの2つの関数に分けましたが、違う部分のデータを関数に引数で渡せば、分けなくて済みます。startCやendCの代わりにメッセージの文字列を引数で受け取る関数commentを定義してプログラムを書き換えてください。

 ヒント:文字列を受け取って書き出す関数の例。
受け取るだけなので戻り値型はvoid型、引数は文字列の先頭アドレスをもらうのでchar*型とします。

void fun(char* str)
{
    printf(str);
    return;
}

はfun("こんにちは");のように呼び出すことが可能です。

解答例

戻り値を使う

メッセージを書いてデータを読み込む下記のプログラムが在ります。データを読み取る関数readを作ってこのプログラムを書き換えなさい。readの戻り値は読み取ったdouble型の値、引数はメッセージの文字列にしましょう。

/*function_5.c*/
#include<stdio.h>

int main(void)
{
    double a,b,c,t;
    printf("直方体の体積を計算します\n");

    printf("縦の長さはいくらですか?\n");
    do
    {
        printf("正の値を入力してください>");
        fflush(stdout);
        scanf("%lf",&t);
    }while(t<0);
    printf("\n");
    a=t;

    printf("横の長さはいくらですか?\n");
    do
    {
        printf("正の値を入力してください>");
        fflush(stdout);
        scanf("%lf",&t);
    }while(t<0);
    printf("\n");
    b=t; 

    printf("高さはいくらですか?\n");
    do
    {
        printf("正の値を入力してください>");
        fflush(stdout);
        scanf("%lf",&t);
    }while(t<0);
    printf("\n");
    c=t; 

    printf("直方体の体積は %lf",a*b*c); 
    return; 
}
/*実行例
直方体の体積を計算します
縦の長さはいくらですか?
正の値を入力してください>10

横の長さはいくらですか?
正の値を入力してください>20

高さはいくらですか?
正の値を入力してください>30

直方体の体積は 6000.000000
*/

解答例

関数の順番

C言語は機械語への翻訳作業が頭から順番にソースを読みながら行えるように、関数を呼び出す記述の前に、関数の引数の型や戻り値の型が明記されていることを要求しています。ですから、関数呼び出し記述の前に、関数の定義かプロトタイプ宣言の記述が必要です。

main関数を最後に書くと、プロトタイプ宣言が省ける利点が在ります。しかし、何をするプログラムなのかを初めに示す方がプログラムを読みやすくなるので、main関数を最初に書くのは良い書き方です。

main関数を最初に書くと、main関数で呼び出す関数のプロトタイプ宣言がmani関数の記述の前に必要です。長いプログラムの場合はプロトタイプ宣言が沢山必要になるかもしれません。

※プロトタイプ宣言部分をヘッダーファイルとして別ファイルに分け、main関数の前に#includeで取り込むことも良く行われます。

※main関数は最初に呼び出される関数で形が決まっています。main関数については通常はプロトタイプ宣言を書きません。

プロトタイプ宣言1

次の三角関数sinの結果を表示するプログラムはコンパイル時に警告されますが実行可能ファイルは作られます。しかし、その実行結果は正しい値とはなりません。

#include<stdio.h>

int main(void)
{
    printf("sin(1)=%lf",sin(1));
    return 0;
}
/*実行結果は異常
sin(1)=0.000000
*/

sin関数自体は標準ライブラリにある関数です。
   sin関数はdouble型の引数1個を受け取りdouble型の値を戻します。

このプログラムの実行結果が正しくないのは、コンパイラがsin関数を可変個引数で戻り値はint型の関数として関数呼び出しを翻訳した結果です。コンパイラに正しい情報を与えるプロトタイプ宣言を追加して、

sin(1)=0.841471

 と出力するように書き直してください。

解答例

※事前に宣言されていない未知の関数の呼び出しが在ると、コンパイラは可変個引数で戻り値はint型の関数として呼び出しを翻訳します。これは今とは違うC言語の古い書き方に配慮した為です。

プロトタイプ宣言2

次のプログラムで最後に書かれているmain関数の定義をプログラムの始めの方に移動してください。このとき、関数のプロトタイプ宣言をmain関数の前に追加することが必要になります。

#include<stdio.h>
int factorial(int n)
{
    if(n==0)return 1;
    else return n*factorial(n-1);
}
int permutation(int n,int r)
{
    int p=1;
    while(r<n){
        p*=n;
        n--;
    }
    return p;
}
int combination(int n,int r)
{
    return permutation(n,r)/factorial(n-r);
}
int main(void)
{
    printf("5個の異なる文字から3個を重複なしに選ぶ選び方は");
    printf("%d個です\n",combination(5,3));
    return 0;
}

解答例

関数の並べ方

関数を呼びだす時は呼ぶ関数の定義かプロトタイプ宣言が事前に記述されていることが必要です。次のプログラムはこのままではコンパイルできません。コンパイルと正しい実行ができるように関数の記述順番を入れ替えなさい。プロトタイプ宣言を追加してはいけません。

このプログラムはfunction_2x.cからダウンロードできます。

/*function_2x.c*/
#include<stdio.h>
int length=5;
int array[5]={1,2,3,4,5};

int main(void)
{
    print();
    swap(1,2);
    print();
    sort();
    print();
    return 0;
}
void sort(void)
{
    int i;
    int m;
    for(i=0;i<length;i++)
    {
        m=max(i);
        if(m!=i) swap(i,m);
    }
    return;
}
int max(int k)
{
    int i;
    int m;
    m=k;
    for(i=k;i<length;i++)
    {
        if(array[m]<array[i])m=i;
    }
    return m;
}

void print(void)
{
    int i;
    for(i=0;i<length;i++)
    {
        printf("%d ",array[i]);
    }
    printf("\n");
    return;
}
void swap(int n,int m)
{
    int t;
    t=array[n];
    array[n]=array[m];
    array[m]=t;
    return;
}

解答例

プログラムを関数に分割する

同じようなことを何度も行うプログラムでは、同じ部分を関数にまとめて無駄な記述を省けば全体として短いプロフラムにできることを説明しました。この他に、関数にはもう一つの利点があります。それは長いプログラムを幾つかの部分に分けて作れることです。

人が把握できるプログラムの長さには限界があります。個人差はあるでしょうが100行のプログラムを一度に作るのはなかなか大変です。1000行になると作れない人が多くなり、できたものもミスが多いものとなります。逆に、20行程度の関数プログラムなら、一度に見れるので誰でも無理なく作れます。

100行のプログラムを各20行程度の5つの関数に分けると作り易く、かつミスも少なくなります。大規模なプログラムになるほどプログラムを分割して幾つかの関数に分けることが必要になります。

※しかし、うまく分けることが実は難しい^^。

入力、計算、出力に3分割する

以下のmain関数だけのプログラムを入力input、計算calc、出力outputの3つの関数と、それを順番に呼び出すmain関数に分けて書きなおしなさい。

関数の引数や戻り値を使わなくて済むように、データを格納する変数は全てグローバル変数にしてあります。
関数ごとに必要なローカル変数の定義は個々に行ってください。

/*function_3.c*/
#include<stdio.h>
#include<math.h>

/*グローバル変数*/
int length=10;
double array[10];
double average;
double standardDeviation;

int main(void)
{
    int i;
    double total;
    double d;

    /*入力 void input(void)に*/ 
    for(i=0;i<length;i++)
    {
        scanf("%lf",&array[i]);
    } 

    /*計算 void calc(void)に*/
    /*平均値*/
    total=0;
    for(i=0;i<length;i++)
    {
        total+=array[i];
    }
    average=total/length;
    /*標準偏差*/
    total=0;
    for(i=0;i<length;i++)
    {
        d=array[i]-average;
        total+=d*d;
    }
    standardDeviation=sqrt(total/(length-1));

    /*出力 void output(void)に*/
    printf("平均値%lf\n",average);
    printf("標準偏差%lf\n",standardDeviation);
    printf("偏差値\n");
    for(i=0;i<length;i++)
    {
        d=array[i]-average;
        printf("%.1lf\n",50+10*d/standardDeviation);
    } 

    return 0;
}
/*実行例
1 2 3 4 5 6 7 8 9 10の入力に対して

平均値5.500000
標準偏差3.027650
偏差値 
35.1
38.4
41.7
45.0
48.3
51.7
55.0
58.3
61.6
64.9
*/

ヒントとしてmain関数を最初に書く方法でプログラムの初めの部分を示します

#include<stdio.h>
#include<math.h>

/*グローバル変数*/
int length=10;
double array[10];
double average;
double standardDeviation;

/*プロトタイプ宣言*/
void input(void);
void calc(void);
void output(void);

int main(void)
{
    input( ); 
    calc( );
    output( ); 
    return 0;
}
...

解答例

グローバル変数を減らす

前記のプログラムでは引数や戻り値を使いませんでしたが、外部変数averageとstandardDeviationをmain関数のローカル変数にして、関数の引数や戻り値でデータを受け渡す形に書き直しなさい。

この時、関数は2つ以上の値を戻せないことがネックになります。そこでcalcを

の二つに分けましょう。

さらにoutput関数は平均値と標準偏差を引数で受け取る形にしてください。

ヒントとしてmain関数を最初に書く方法でプログラムの初めの部分を示します

#include<stdio.h>
#include<math.h>

/*グローバル変数*/
int length=10;
double array[10];

/*プロトタイプ宣言*/
void input(void);
double calcAverage(void);
double calcStandardDeviation(double average);
void output(double av, double sd);

int main(void)
{
    /*ローカル変数*/
    double average;
    double standardDeviation;
    
    input( ); 
    average = calcAverage( );
    standardDeviation = calcStandardDeviation( average );
    output( average, standardDeviation );
     
    return 0;
}
...

解答例

グローバル変数をなくす

配列データもmain関数のローカル変数にして、グローバル変数を使わない形に書き換えなさい

解答例