カテゴリー
C

C(Step4-2)

関数の利用②

関数の利用①に続いて、関数についての知識を深めていきましょう。

ライブラリ関数

C言語ではプログラミングを簡単にするために、あらかじめ多くの関数が用意されていると説明しました。 このようなにあらかじめ用意されている関数をライブラリ関数といいます。printf() もライブラリ関数のひとつですが、他の例として数値計算を行う際によく使う関数を以下に挙げておきます。

関数名演算内容
sin(x)正弦。引数xはラジアンで角度を示したもの。
cos(x)余弦。引数xはラジアンで角度を示したもの。
tan(x)正接。引数xはラジアンで角度を示したもの。
fabs(x)xの絶対値。
sqrt(x)xの平方根。
数値計算に使う関数

これらの関数を呼び出すためには、「stdio.h」と同じように以下の記述をしなければなりません。

#include <math.h>

次のプログラムを実行して確認してみましょう。

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

int main() {
    printf("sin(0.0)     = %f\n", sin(0.0));
    printf("cos(0.0)     = %f\n", cos(0.0));
    printf("tan(0.0)     = %f\n", tan(0.0));
    printf("fabs(-1.41)  = %f\n", fabs(-1.41));
    printf("sqrt(4.0)    = %f\n", sqrt(4.0));

    return 0;
}

実行結果は、以下のようになります。

<math.h>の利用実行結果

関数の再帰呼び出し

再帰というのは、関数が自分自身を呼び出すことで、このような関数を再帰関数(recursive function)と呼びます。特に漸化式で表される数学的な関数など繰り返し行う処理を直接プログラミングすることができます。以下のような書式で、記述します。

戻り値の型 関数名(仮引数の宣言付きリスト)
{
    関数名(実引数リスト);

        return 式;
}

また、典型的な書き方と再帰関数を扱う際の注意点を下に示しておきます。

int 関数名(int n)
{
    if(n == 0){   //①
       /*再帰関数の土台*/
       ans = ??????
    }else{
       /*再帰部分②*/
       ans = 関数名(n - 1);
    }
    return ans;
}
  1. 必ず、再帰の土台部分、抜け道を作って置くこと。
  2. 再帰呼び出しをするときは、必ず抜け道に近づくようにすること。

上のことを守らないと再帰関数は、終わらないプログラムになってしまうので、注意が必要です。

次のプログラムを実行して確認してみましょう。

#include <stdio.h>

int fcl(int n)
{
  int m;

  if (n == 0)
    return 1; //0! = 1
  /*nが0でないとき*/
  m = fcl(n - 1); //(n-1)!を求めてそれをmとおく。ここのfcl(n-1)が再帰呼出し。
  return n * m;   //n! = n * m
}

int main()
{
  int i, num;

  scanf("%d", &i);
  num = fcl(i);
  printf("%dの階乗は%dです。\n", i, num);
  return 0;
}
再帰関数を使った階乗の実行結果

一番、簡単な例として階乗のプログラムを作りました。書いている中で思ったかもしれませんが、再帰の書き方は本質的には繰り返し処理と似ています。再帰の呼び出しでしていることは、for文やwhile文などの繰り返しの処理で実現できます。そのため、どちらを使うのか適切に判断しなければなりません。

voidを使った関数

ここまでの関数は「int 関数名()」という記述をしています。しかし、他のサイトや本で、関数を「void 関数名()」として紹介している場合もあります。このように書く時は「何もない型」として表しています。これがどうして必要なのかというと、関数を定義するときは関数の「型」を定義する必要があります。しかし、returnで何も値を返さない関数を作りたいときは、関数の定義でも「何もない型=void」として利用すれば良いです。記述する際は以下のような形になります。

void 関数名(…)
{
     :
}
void main()
{
     :
  関数名(…);
     :
}

実際にプログラム内で使うと以下のようになります。

#include <stdio.h>

void putText(){
  printf("Hello World!\n");
}
 
int main(){
 
  /*関数の呼び出し*/
  putText();
 
  return 0;
}
void関数実行結果

上のようになにも知らずにvoidだけを使った方がはじめはわかりやすいかもしれませんが、大きいプログラムを開発するときを考えると。「int 関数名()」などの記述に慣れていた方が良いです。

値渡しと参照渡し

まず、値渡しとは変数などのオブジェクトの値を引数や戻り値とすることです。対して、オブジェクトのアドレスを引数や戻り値とすることを参照渡しといいます。参照渡しでは関数の引数や戻り値としてアドレスを用います。そのため、関数内の処理では引数や戻り値のアドレスを操作することになります。詳しくは後のポインタの説明で行いますが、簡単に触れておきます。

ここでは、複数のオブジェクトの値を変更する際に、引数で参照渡しにすれば、その複数のオブジェクトの値を処理変更できると覚えておきましょう。

次のサンプルを実行して確認してみましょう。

#include <stdio.h>
 
float pi = 3.14;
  
//関数の定義 
void calc(float r, float *area, float *len) {
    *area = pi * r * r;
    *len = pi * 2 * r;
}
 
void prt(float r, float s, float l) {
    printf("半径%.1fの円の面積は%.2f、円周の長さは%.2f\n", r, s, l);
}

//関数の呼び出し、実行 
int main() {
    float r = 10.0, s = 0, len = 0; //変数の初期化
    calc(r, &s, &len);
    prt(r, s, len);
 
    return 0;
}
参照渡し実行結果

引数や戻り値に配列を指定

引数や戻り値に配列を指定したい場合がある場合はどうしたらいいのか?ただ、C言語では、配列を直接指定できません。ポインタを使って配列のアドレスを指定しなければなりません。

次のサンプルを実行して確認してみましょう。

#include <stdio.h>
 
float pi = 3.14;

//関数の定義
float *data(float *nums) {
    static float data[3];
    data[0] = nums[0];
    data[1] = pi * nums[0] * nums[0];
    data[2] = pi * 2 * nums[0];
    return data;
}
 
void prt(float r, float s, float l) {
    printf("半径%.1fの円の面積は%.2f、円周の長さは%.2f\n", r, s, l);
}

//関数の呼び出し、実行
int main() {
    //引数:ポインタ型(配列)
    float nums[3] = {10.0, 0, 0,};
    float *new_nums = data(nums);
    prt(new_nums[0], new_nums[1], new_nums[2]);
 
    return 0;
}
配列を用いた参照渡し実行結果

配列を戻り値とする関数には、関数名に「*(アスタリスク)」を付けます。関数は配列自体を返せず、配列のポインタを返却します。また、関数は配列を引数とすることはできません。配列を引数にするには配列のポインタを引数にしなければなりません。

ここで出てきたポインタの使い方については次のStepで説明しますので、参考にしてください。