関数

プログラミングBのページへ戻る

 今までにも printf や scanf などの入出力関数,そして sin や cos などの数学関数など,ライブラリ関数と呼ばれる既に用意されている関数をいくつか使ってきた. ここでは,関数とは何か,ということを理解して,それを自分で作れるようになることを目標とする.

関数とは

 関数を理解するには,まず,関数を呼び出す側(すなわち関数の外側)の世界と 関数の側(すなわち関数の内側)の世界と,という二つの世界の区別をしなければならない.

 この二つの世界は,ほぼ完全に隔離されている世界だと思えばよい. 一方の世界で起こったことは,(特別なことをしない限り)他方には影響しない. その二つの世界を繋ぐために, 引数というものと戻り値というものとがある. 引数は外側の世界から内側の世界への橋渡しであり, 戻り値はその逆の方向の橋渡しである. たとえば, sin(x) という関数では, x が引数であり,sin(x) の値が戻り値である.

関数呼び出しの概念図

 一言で引数と言っても,外の世界と中の世界とでは,その意味が違うことに注意しよう.

 関数を使う側の世界では,同じ関数を sin(x) として使ったり sin(y) として使ったり, さらには sin(x+y) などのように使ったりする. これらの場合,引数はそれぞれ, x, y, x+y である. これらを,実際に引き渡す引数という意味で, 実引数と呼ぶ.

 それに対して,関数の側の引数は,関数が呼び出されたときに 実引数の値が引き渡されるための入れ物であり, 仮引数と呼ばれる.

関数の定義の方法

 関数を定義する際には,その戻り値の型と仮引数の型を指定し, それに続いて,関数の本体を書き表す.

戻り値の型 関数名(引数の型 仮引数名, 引数の型 仮引数名,  ...)
{
    関数内の局所変数の宣言;

    関数内での計算の記述;
    
    return 戻り値;
}

たとえば,次のようなものである.

/* x の n 乗を返す関数 */

double power(double x, int n)
{
    int i;         /* 局所変数 */
    double y;      /* 局所変数 */
    
    y = 1.0;
    for (i = 0; i < n; i++) {
        y *= x;
    }
    
    return y;
}

 この関数を定義した後では,次のようにそれを使うことができる.

int main()
{
    double x, y, z;

    x = 2.0;
    y = power(x, 3);       /* 関数呼び出し */
    z = power(y+x, 2);     /* 関数呼び出し */
    printf("%f\n", z);    
	
    return 0;
}

 power の定義内に y という局所変数があるが,それと main にある変数 y とは無関係であり,片方の値を変更しても,もう片方のものには影響がない.

練習問題

 1から n までの整数の和を返す関数 sum を作る. 和の公式を使わずに,ここでは1 から n までの値を足し合わせるように書くことにする (多分, for 文を使うことになる).

int sum(int n)
{
    必要な局所変数の宣言

    計算

    return ???;
}

int main()
{
    printf("%d\n", sum(10));
    printf("%d\n", sum(100));
}
55
5050

戻り値がない関数

 値を何も返さない関数もある.そのような関数の場合,戻り値の型を指定する代わりに, 戻り値が無いという意味で void と指定する.

たとえば,次の関数は引数に与えられた年月日を表示するだけなので, 値を返す必要がない.

void printDate(int year, int month, int day)
{
    printf("%d 年 %d 月 %d 日\n", year, month, day);
    
    return;
}

値を返さないときには,関数の最後の return; は無くても良い. なぜなら,関数内に実行すべき文がなくなれば, 関数は自動的に終了するからだ.

値を返さない関数を使うときには,式の中ではなくて, 一つの実行文のように独立に使えばよい.

int main()
{
    printDate(2005, 11, 1);

    return 0;
}

引数がない関数

 引数を必要としない関数もある. そのような関数の場合,仮引数を指定する代わりに void と指定する.

たとえば,次の関数は,一文字をキーボードから受け取り,それが 'y' なら 1 を それ以外なら 0 を返す関数であり,引数を必要としない.

int yes_or_no(void)
{
    int c;
    
    printf("yes or no? >");
    c = getchar();             /* キーボード(標準入力)から1文字を入力 */
    if (c == 'y') return 1;
    else return 0;
}
int main()
{
    do {
        printf("\nHello!  Are you OK?\n");
    } while (yes_or_no() == 0);
    
    return 0;
}

ページ先頭に戻る

引数について

「関数の引数」と言ったとき,その意味には二通りある.

その一つには,関数を定義するときに使用する引数である.これを「仮引数」と言う.

double power(double x, int n)
{
    略
}

後一つは,関数を使用するときに関数に引き渡す引数である.これを「実引数」と言う.

int main()
{
    double v = 2.4, pow;
    int k = 3;
    
    pow = power(v, k);

    return 0;
}

C言語では,関数への引数の引き渡しは, すべて「値の引き渡し」である. その意味は,実引数そのものを仮引数とするのではなく, 実引数の値を仮引数にコピーする,ということである. よって,関数の内部でその仮引数の値を変更したとしても, 実引数には何の影響も及ぼさない. すなわち,関数の内部で実引数の値を変更することはできない

例えば,次のような関数を使用したとしても, a の値は変化しない.

void setTen(int x)
{
    x = 10;
}

int main()
{
    int a = 0;
    
    setTen(a);
    printf("%d\n", a);

    return 0;
}
0

ページ先頭に戻る

ポインタ型の引数

引数の型をポインタ型とするときがある.そのような場合について説明しよう.

関数の外部にある変数の値を変更したいとき

先ほど,関数の内部で実引数の値を変更することはできない,と言ったが,それでも, 関数を使う方にある変数の値を,関数の内部で変更したいときがある.

そのようなときには,次の例のように, 関数の仮引数をポインタ型(例えば int *x )として宣言しておいて, 変更したい変数(例えば a )へのポインタ( &a )を関数に引き渡すことにより, 関数内部でそのポインタ値が指す場所にある変数の値を変更( *x = 10 )することで, その変数の値を変更することができる.

void setTen(int *x)
{
    *x = 10;
}

int main()
{
    int a = 0;
    
    setTen(&a);
    printf("%d\n", a);

    return 0;
}
10

では,ポインタ型引数を使う場合を挙げよう.

2個以上の値を返したいとき

関数が戻り値として返せる値は1つの値だけである. 複数個の値を返したいときには,戻り値として返すのではなく, それらの値を返すべき場所を指すポインタ値を関数に引数として渡すことで可能となる.

たとえば,次の関数 min_max(int x, int y, int *minPtr, int *maxPtr) は, 整数値 x, y のうちの小さい方を *minPtr に入れて,大きい方を *maxPtr に入れて, 終了する(戻り値はない).

void min_max(int x, int y, int *minPtr, int *maxPtr)
{
    if (x < y) {
        *minPtr = x;
        *maxPtr = y;
    } else {
        *minPtr = y;
        *maxPtr = x;
    }
}

このようなポインタ値を引数にとる関数を使用するときには, それに与える実引数は変数へのポインタ(アドレス)である必要がある. 例えば,次のようになる.

int main() 
{
    int a=10, b=20, min=0, max=0;
    
    min_max(a, b, &min, &max);
    printf("min = %d, max = %d\n", min, max);
    
    return 0;
}
min = 10, max = 20

数値などをキーボード(標準入力)から読み込むときに使用する, 標準ライブラリ関数である scanf も,読み込んだ値を返す場所の指定のために, ポインタ値を引数にとっている.

#include <stdio.h>

int main()
{
    int a;
    
    scanf("%d", &a);
    printf("%d\n", a);
}

演習問題1

二つの整数型変数の値を入れ替える関数 swap を作ろう.

void swap( ....... )
{
   .....
}

int main()
{
    int a = 10, b = 20;
    
    swap( ...... );
    
    printf("%d %d\n", a, b);

    return 0;
}

上のプログラムを完成させて,次のような出力になればいい.

20 10

配列を引数として渡すとき

配列を引数として関数に渡すときには,配列の先頭要素へのポインタ値として渡される. その関数定義と使用方法は,例えば次のようになる.

/* array[0] から array[n-1] までの総和を返す関数 */

double totalSum(double *array, int n)
{
    double s;
    int i;
    
    s = 0.0;
    for (i = 0; i < n; i++) {
        s += array[i];
    }
    
    return s;
}

【注意事項】 配列の要素数は, 別の整数型引数(ここでは n )として関数に与える必要がある. 配列を引数に与えたときには, 関数に引き渡されるのはその先頭要素へのポインタ値だけであり, 要素数は引き渡されないからである.

int main()
{
    double data[4] = {1.2, 2.3, 3.4, 4.5};
    double total;
    
    total = totalSum(data, 4);
    printf("total = %f\n", total);
    
    return 0;
}

【注意事項】 配列名だけを使用した場合それは先頭要素へのポインタ値となる,という 約束があったので,第1実引数には data と書くだけでいい. また, &data[0] としても同じことである.

また,関数定義において,次のように,*array の代わりに array[] と書いてもよい. この方が,引数が配列らしく見えるので分かりやすいかもしれない. (これは,趣味の問題で,どちらでもよい)

double totalSum(double array[], int n)

ただし,仮引数で array[4] のように要素数を指定しても,これは無視される. すなわち,コンパイルもできて関数も動くが,要素数が4個ということには コンパイラも関数も関知しない.

double totalSum(double array[4], int n)
                             ↑この要素数指定は全く無意味である

配列を引数としたときの注意

配列を引数としてときには, それは実引数として与えた配列の先頭へのポインタ値であるから, 関数の内部で,その配列の要素を変更すると, 関数の外部にある実引数の配列の内容が変化する.

このことは,配列を操作する上では便利であるが, 関数の内部で仮引数として与えられた配列の要素を不用意に変更することの無いよう, 注意する必要がある.

例えば,次は,このことを利用して引数に与えられた配列の要素をすべて 0 に初期化する関数と, その使用例である.

void initAllZero(int array[], int n)
{
    int i;
    
    for (i = 0; i < n; i++) {
        array[i] = 0;
    }
}

int main()
{
    int a[10];
    
    initAllZero(a, 10);
    
    return 0;
}

演習問題2

二つのベクトルの和を求める関数 vectorSum を作ろう.

vectorSum(v1, v2, v3, n) は要素数 n のベクトル(要素数 n の配列) v1 と v2 とを加えて, それを v3 に代入する.

void vectorSum(double v1[], double v2[], double v3[], int n)
{
    ..... ここに書き加える
}

int main()
{
    double a[3] = {1.1, 2.2, 3.3};
    double b[3] = {1.2, 3.4, 5.6};
    double c[3];
    
    vectorSum(a, b, c, 3);
    
    printf("(%f, %f, %f)\n", c[0], c[1], c[2]);
    
    return 0;
}
(2.30000, 5.60000, 8.90000) 

2次元以上の配列を引数にする場合

2次元以上の配列を引数にする場合には, 2番目の次元以降のサイズを指定しておく必要がある.

次の関数 determinant2 は,2×2の行列の行列式を戻り値とする関数である.

#include <stdio.h>

int determinant2(int mat[][2])
{
    int det;
    
    det = mat[0][0]*mat[1][1]-mat[0][1]*mat[1][0];
    return det;
}

int main()
{
    int a[2][2] = {{1, 2}, {3, 4}};
    int det;
    
    det = determinant2(a);
    printf("det = %d\n", det);

    return 0;
}

ページ先頭に戻る

レポート問題

配列内の最小値と最大値とを返す関数 arrayMinMax を作れ. arrayMinMax は整数型配列 array と,その要素数 n と,最小値, 最大値を返すべき変数へのポインタ値 minPtr, maxPtr とを引数に取る. 戻り値はない.

最大値最小値を求める方法は,ここを参照

#include <stdio.h>

void arrayMinMax(int array[], int n, int *minPtr, int *maxPtr) 
{
    ... ここを書き加える
}

int main()
{
    int data[10] = {4, 2, 5, 9, 2, 1, 4, 5, 4, 5};
    int min, max;
    
    ... ここを書き加える
    
    printf("min = %d, max = %d\n", min, max);

    return 0;
}
min = 1, max = 9

ページ先頭に戻る