C言語 ~7日目~

関数

関数とは、特定の機能を提供するために、再利用可能な部品としてまとめられた処理のことです。関数を使用することで、コードの重複を避け、プログラムをより簡潔にすることができます。C言語は多くの関数で構成されており、printf()関数もその一つです。

この章では、関数の定義と利用方法について解説します。

関数の定義

関数は以下のように定義します。

戻り値の型 関数名(引数) {
 処理;
 return 戻り値;
 }
戻り値の型
関数が呼び出された際に返される値の型を指定します。戻り値が存在しない場合は、voidと記述します。
関数名
関数を呼び出す際に使用する名前のことを指します。関数名は変数名と同様の命名規則に従って付けられます。
引数
関数に渡される値を引数といいます。引数には型と変数名を定義します。複数の引数がある場合、それぞれの型と変数名をカンマで区切って記述します。引数が不要な場合は、voidと記述します。
処理
関数が実行する具体的な処理を記述します。
return⽂
これは関数の実行を終了させ、戻り値を返すための文です。戻り値の型がvoidである場合は、この文を記述する必要はありません。

呼び出し側は以下のように記述します。

関数名(引数);

以下は関数main()から関数sum()を呼び出し、結果を取得するプログラムの例です。処理の流れを図で示しています。

  1. 関数main()の先頭より関数sum()までを実行
  2. x,yの値を引数として、関数sum()を呼び出す
  3. 関数sum()内部の処理を実行し、計算結果をreturn文により戻す
  4. 関数を抜けて呼び出し元へ戻り、関数sum()の戻り値を取得
  5. 関数sum()以降の処理を実行する

プログラムコードは以下の通りです。

#include <stdio.h>

int sum(int a,int b);

int main(void){
	int x = 1;
	int y = 2;
	int z;
	
	z = sum(x,y);
	printf("%d + %d = %d\n",x,y,z);
	return 0;
}

int sum(int a,int b){
	int s = a + b;
	return s;
}

各行の動作を見ていきましょう

3行目:int sum(int a,int b);
プロトタイプ宣言といいます。プロトタイプ宣言は、関数main()よりも前に記述します。この宣言を通じて、関数の型、引数の数、返却値の型が正しいかどうかをコンパイラがチェックすることができます。
10行目:z = sum(x,y);
関数main()内で定義された変数xとyの値を引数にして関数sum()を呼び出し、関数sum()からreturn文で返された値を変数zに代入します。
15行目:int sum(int a,int b)
関数sum()は、xとyの値を仮引数aとbとして受け取ります。
17行目:return s;
関数は仮引数aとbの和を計算し、その結果を戻り値として返した後、終了します。

ローカル変数とグローバル変数

変数には「ローカル変数」とグローバル変数」の2種類が存在します。
関数を作成する際には、ローカル変数とグローバル変数のスコープを理解することが重要です。

ローカル変数
関数内で定義された変数をローカル変数と言い、それはその関数内でのみアクセス可能です。
異なる関数で同じ名前の変数を宣言しても、それらは別々の変数として認識されます。
関数内で用意された変数は、関数の実行が終了するとメモリから削除されます。
グローバル変数
グローバル変数とは、すべての関数から直接アクセスできる変数のことで、main()関数の外部で定義されます。
プログラムが起動すると、一度だけ初期化され、プログラムが終了するまでメモリに残り続けます。
変数名はプログラム全体で一意でなければなりません。

ローカル変数とグローバル変数を使ったサンプルを以下に示します。

#include <stdio.h>

int sum(int a,int b);
int g_count = 0;

int main(){
  int x = 10;
  int y = 20;
  int z;
  
  z = sum(x,y);
  printf(%d+%d~%d\n",x,y,z);
  
  printf("g_count=%d\n",g_count);
  return 0;
}

int sum(int a, int b){
  int x = a + b;
  g_count = g_count + 1;
  return x;
}
  

以下は、このサンプルプログラムの、グローバル変数とローカル変数の有効範囲(スコープ)を図解しています。

関数main()で宣言された変数xと関数sum()で宣言された変数xは、それぞれが関数内で定義されたローカル変数です。そのため、関数sum()の内部で値を変更しても関数main()側の変数には影響しません。一方で、g_countはグローバル変数であるため、関数main()や関数sum()で変数の宣言がされていなくても、参照や更新が可能です。
グローバル変数は関数の引数として渡す必要がなく、値の参照や更新するのに非常に便利です。しかし、予期せぬ箇所で値が変更されるリスクもあり、安全性が低下する可能性があります。
大規模なプログラムでは、グローバル変数がどこで変更されているかを追跡するのが難しくなります。さらに、グローバル変数が存在すると、関数を単独で再利用することができず、部品としての利用が制限される場合があります。
可能な限りローカル変数を使用し、関数間のやり取りは引数と戻り値で行い、グローバル変数は必要不可欠な場合にのみ使用するよう心がけましょう。

実行結果
10+20=30
g_count=1

再帰関数

関数から別の関数を呼び出すことができ、自分自身を呼び出す関数を再帰関数と呼びます。
再帰関数を作成する際には、再帰処理が終了する条件を必ず定義する必要があります。
以下は、5の階乗を計算するサンプルプログラムです。

#include <stdio.h>
int factorial(int n); // 関数のプロトタイプ宣言
int main(void){
	// 関数の呼び出し
	int num = 5;
	int result = factorial(num);
	printf("%d! = %d¥n", num, result);
	return 0;
}
// 関数の実体の定義
int factorial(int n) {
	if (n == 0) { // nが0なら再帰を終了する
		return 1; // 1を返す
	} else { // そうでなければ
		// nとn-1の積を返す
		return n * factorial(n-1);
	}
}

関数main()から階乗を計算する値を引数として関数factorial()を呼び出しています。
関数factorial()は、nが0になるまでn × factorial(n-1)を繰り返し実行し、階乗を計算します。
以下に各処理の動作を示します。

再帰を用いずにfor文を使って階乗を計算する場合、以下のように記述できます。

int factorial(int n) {
   int result = 1;
   for(int i=n;i>0;i--){
       result = result * i;
   }
}

再帰処理を用いたプログラムは、処理を簡潔に記述することができるという利点があります。
一方で、再帰が深くなりすぎるとメモリ不足を引き起こしたり、実行速度が低下することがあるため、状況に応じて適切に使用することが重要です。
まずは、再帰関数が存在することを知っておきましょう。

最後に、ユークリッドの互除法を用いて最大公約数を計算する再帰関数の例を示します。

ユークリッドの互除法(ユークリッドのごじょほう、英: Euclidean Algorithm)は、2 つの自然数の最大公約数を求める手法の一つである。

2 つの自然数 a, b (a ≧ b) について、a の b による剰余を r とすると、 a と b との最大公約数は b と r との最大公約数に等しいという性質が成り立つ。この性質を利用して、 b を r で割った剰余、 除数 r をその剰余で割った剰余、と剰余を求める計算を逐次繰り返すと、剰余が 0 になった時の除数が a と b との最大公約数となる。


(問題)1071と1029の最大公約数を求める。
1071 を 1029 で割った余りは 42
1029 を 42 で割った余りは 21
42 を 21 で割った余りは 0
よって、最大公約数は21である。

出典:https://ja.wikipedia.org/wiki/%E3%83%A6%E3%83%BC%E3%82%AF%E3%83%AA%E3%83%83%E3%83%89%E3%81%AE%E4%BA%92%E9%99%A4%E6%B3%95

これをソースコードにしたものを以下に記述します。

#include <stdio.h>

int gcd(int a,int b){
   if(b==0){
        return a;
    }else{
        printf("m=%d:n=%d:d=%d\n",a,b,a%b);
       return gcd(b,a%b);
    }
}

int main()
{
    int m=1071;
    int n=1029;
    
    int d = gcd(m,n);
    printf("二つの数の最大公約数は最大公約数は%dです。\n", d);
    return 0;
}

gcd()関数を再帰関数を使用しない場合以下のように記述することもできます。

int gcd(int a,int b){
    int d;
    do{
        d = a % b;
        a = b;
        b = d;
        printf("m=%d:n=%d:d=%d\n",a,b,d);
     } while (d !0);
    return a;
}

コメント

この記事へのコメントはありません。

関連記事

C言語でデータセットを使用する 後編

C言語 ~2日目~

C言語 ~6日目~

PAGE TOP