Subsections

6章 関数 その1

関数は, 同じような処理を何度もする場合に, 一ヶ所にまとめることができる機能で, 全体が簡潔に見通しよく記述できる. さらに, 関数内部で宣言した変数は外部から遮蔽されるので, 関数を呼び出す場合は, 入力と出力だけを考えればよい. これは大きなプログラムを構築する上で重要な「構造化」の機能を提供する.

6.1 本日の課題

  1. 以下で与えられた量を求める関数を作成し, それを用いたプログラムを幾つかを作成する. 教科書などを写しただけでは 身につかないので,自らプログラムで実現したい機能を考え,それを実現するオリ ジナルのプログラムを作成すること. 題材の例として, 「変数と演 算」[3.5]および「for, while, do を用いたプログラ ム」[4.9]などに関数を用いない場合が載せてあるので参考にしてもよい.

    長さ
    正方形の周囲の長さ. 長方形の周囲の長さ. 円周の長さ. 円弧の長さ.
    面積
    三角形の面積. 台形の面積. 正方形の面積. 長方形の面積. 扇形の面積.
    体積
    三角柱の体積. 四角柱の体積. 円柱の体積.
    体積
    三角錐の体積. 四角錐の体積. 円錐の体積.
    関数値
    $x$が与えられた時の 関数 $f(x) = x^2 + 1$ の値. $x, y$ が与えられた時の関数 $f(x, y) = x^2 + y^2$ の値.
    1次方程式の解
    $a, b$ が与えられた時の 1次方程式 $a x + b = 0$ の解.
    2次方程式の判別式
    $a, b, c$ が与えられた時の 2次方程式 $a x^2 + b x + c = 0$ の判別式.
    テイラー展開
    テイラー展開により関数の値を近似的に求める.
    統計
    5つの実数の和. 5つの実数の平均値.
    エネルギー
    質量, 速度が与えられたとき質点の運動エネルギー.
    単位の変換
    時間を分に変換. J を cal に変換.
    など

  2. 変数の有効範囲を理解し, 以下のプログラムにおいて, (1)から(13)までの各printfによりaの値を出力するとき, それぞれ [A]から[G]のうちどのaの値が出力されるか答えよ. また, 各変数が有効な範囲を行番号で答えよ.
    #include <stdio.h>
    
    double a = 10.0; /* [A] */
    
    void func1(double a) /* [B] */
    {
    	 printf("a = %g\n", a); /* (1) */
    
    	 {
    		  double a = 10.0; /* [C] */
    		  printf("a = %g\n", a); /* (2) */
    	 }
    
    	 printf("a = %g\n", a); /* (3) */
    }
    
    void func2(void)
    {
    	 printf("a = %g\n", a); /* (4) */
    	 {
    		  int a = 10.0; /* [D] */
    		  printf("a = %g\n", a); /* (5) */
    	 }
    	 printf("a = %g\n", a); /* (6) */
    }
    
    void func3(double x)
    {
    	 printf("a = %g\n", a); /* (7) */
    	 {
    		  int a = 10.0; /* [E] */
    		  printf("a = %g\n", a); /* (8) */
    	 }
    	 printf("a = %g\n", a); /* (9) */
    }
    
    int main(void)
    {
    	 double a = 10.0; /* [F] */
    	 printf("a = %g\n", a); /* (10) */
    	 func1(a);
    	 func2();
    	 func3(a);
    	 printf("a = %g\n", a); /* (11) */
    	 {
    		  double a = 10.0; /* [G] */
    		  printf("a = %g\n", a); /* (12) */
    	 }
    	 printf("a = %g\n", a); /* (13) */
    
    	 return 0;
    }
    

    次のように, printf 関数に渡すフォーマットに, どのprintfの文を実行しているかが わかるように, 数字などを入れておくと文かり易いやす. また, 各変数aに異なる値を 代入しておくと, どの変数が参照されたかを確認することができる.

    #include <stdio.h>
    
    double a = 100.0; /* [A] */
    
    void func1(double a) /* [B] */
    {
    	 printf("(01) a = %g\n", a); /* (1) */
    
    	 {
    		  double a = 110.0; /* [C] */
    		  printf("(02) a = %g\n", a); /* (2) */
    	 }
    
    	 printf("(03) a = %g\n", a); /* (3) */
    }
    
    void func2(void)
    {
    	 printf("(04) a = %g\n", a); /* (4) */
    	 {
    		  int a = 120; /* [D] */
    		  printf("(05) a = %d\n", a); /* (5) */
    	 }
    	 printf("(06) a = %g\n", a); /* (6) */
    }
    
    void func3(double x)
    {
    	 printf("(07) a = %g\n", a); /* (7) */
    	 {
    		  int a = 130; /* [E] */
    		  printf("(08) a = %d\n", a); /* (8) */
    	 }
    	 printf("(09) a = %g\n", a); /* (9) */
    }
    
    int main(void)
    {
    	 double a = 140.0; /* [F] */
    	 printf("(10) a = %g\n", a); /* (10) */
    	 func1(a);
    	 func2();
    	 func3(a);
    	 printf("(11) a = %g\n", a); /* (11) */
    	 {
    		  double a = 150.0; /* [G] */
    		  printf("(12) a = %g\n", a); /* (12) */
    	 }
    	 printf("(13) a = %g\n", a); /* (13) */
    
    	 return 0;
    }
    

  3. 以下を講義終了時にメールで提出する. 本文に, 学籍番号, 氏名を明記すること.

6.2 学習のポイント

関数定義
p.115 Fig.6-2 関数定義を自分でできるようにする. 返却値型, 関数名, 仮引数の宣言, return を理解する.

関数呼出
関数名に()をつけることで, 関数呼出をする. 関数宣言の仮引数と, 関数呼出式における実引数の並び順および型は 完全に一致している必要がある. 誤っているとプログラムが誤動作する. gcc を使用している場合には, -Wallオプションをつけてコンパイルすると, この型チェックを行い, 誤っていると警告が出力される.

関数の中から関数を呼び出す.
関数中からさらに別の関数を呼び出すことができる. また, のちに学ぶように関数の中からその関数自身を再度呼び出すことができる(再帰呼出).

void
List 6-7 - List 6-9. 値を返さない関数, 仮引数を受け取らない関数を理解する.

有効範囲
第6-3節(p.140). 変数の有効範囲を理解する. [20.3.3]を参照のこと.

記憶域期間
第6-3節の後半(p.142-). 変数の「記憶域期間」を理解する. 「自動記憶域期間」と「静的記憶域期間」を区別して使用すること. [6.5]を参照のこと.

6.3 関数を用いた例

/*
  円の面積を求める関数 circle を定義し,
  その関数を用いて円の面積を求めるプログラム.
 */

#include <stdio.h>

/*
 o 関数は呼び出す前([*]印を付けた文より前)に定義する必要がある.
 o 関数は関数の中で定義できない. 従って int main(void) の前に
    関数定義を記述することになる.
*/

/*
以下は関数 circle の定義. この関数は, 半径rを引数として受け取り,
円の面積を求めている.
関数(名)の宣言は 「戻り値の型 関数名(引数の宣言)」 のように記述する.
その後ろの { から 対応する } までがその関数で行う処理の内容.
*/
double circle(double r)
{
	double area;

	area = r * r * 3.14;

	return area; // 関数の値/戻り値 は return で指定する.
}

/*
  main はプログラム実行時に最初に呼び出される関数.
  OS から void を受け取り, OS に int を返すことを指定している.
  ここで, void は引数として何も受け取らないことを明示するキーワード.
 */
int main(void)
{
	double S;

    // 関数を呼び出す時に r = 5.0 の代入が起きる.
	S = circle(5.0);  // [*]
	// 関数内での計算が終わった後, 関数の値が戻される.
	// ここでは その値を S に代入している.
	
	printf("S = %f\n", S);
		
	 return 0;
}
/*
  長方形の面積を求める関数 rectangle を定義し,
  その関数を用いて長方形の面積を求めるプログラム.
 */

#include <stdio.h>
#include <stdlib.h>

double rectangle(double a, double b)
{
	double area;

	area = a * b;
	return area;
}

int main(void)
{
	double x, y, area;

	x = 3.0;
	y = 4.0;

	// 引数に与えられた値は, 前から順に代入が起きる.
	// ここでは, a = x, b = y の代入がおき, その後関数内の処理が行われる.
	area = rectangle(x, y);
	printf("area = %f\n", area);


	// 関数は何度でも呼ぶことができる.
	area = rectangle(10.0, 3.2);
	printf("area = %f\n", area);

	// 関数の戻り値は, 関数の引数として用いることもできる.
	printf("area = %f\n", rectangle(10.0, 3.2));
	
	 return EXIT_SUCCESS;
}


6.4 変数の有効範囲

変数の型宣言を行うと, その変数に必要な領域がメモリ上に確保される.

型宣言はメモリ上に領域を確保することを指示する命令である. 変数の有効範囲はブロック{ }の中のみである.


6.5 記憶域期間

/*
  static 変数
  ==========
  static を付けて変数を宣言する。
  寿命は, プログラム開始から終了までの間となる。
  初期化は, プログラム開始時に1度だけ行われる。

  関数内で宣言されていても、前回その関数を呼出した時の値が保持されていて
  その値を用いることができる。
  
  自動変数 (auto変数)
  ================
  static を付けずに変数を宣言する。
  寿命は、変数が宣言されてから、その変数のブロック(スコープ)が終了するまで。
  関数の先頭で宣言されている場合は、関数から戻るまでになる。
  初期化は、変数が宣言されるたびに毎回行われる。

  関数から戻るときに、変数の値を保持する為の領域がなくなるので、
  前回その関数を呼出したときの値は保持されない。
*/
 

#include <stdio.h>

// funcA ではstatic を付けて int aが宣言されている。
// プログラム開始時のみ, aが1に初期化され、
// 呼出されるたびにaの値が2倍になり、その値が記憶される。

int funcA(void)
{
  static int a = 1;
  a *= 2;
  return a;
}

// funcB ではstatic を付けないで int aが宣言されている。
// 関数が呼び出され、変数を宣言するたびに, bが1に初期化される。
// 次回の呼び出し時まで値が保持されることはない。

int funcB(void)
{
  int b = 1;
  b *= 2;
  return b;
}

int main(void)
{
  int i, val;

  for(i=0; i<10; i++){
    val = funcA();
    printf("%d\n", val);
  }

  for(i=0; i<10; i++){
    val = funcB();
    printf("%d\n", val);
  }
}

(c)1999-2013 Tetsuya Makimura