静岡理工科大学 情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引

第7章 関数

  1. 7.0 標準関数
    1. 7.0.1 scanf と printf
    2. 7.0.2 文字列操作
      1. (プログラム例 7.0 ) 文字列操作
  2. 7.1 簡単な関数
    1. (プログラム例 7.1 ) 階乗の計算
    2. (プログラム例 7.2 ) 階乗の計算(関数の利用)
    3. (プログラム例 7.3 ) 階乗の計算(再帰呼び出し)
    4. (プログラム例 7.4 ) nrの計算
  3. 7.2 変数の有効範囲(スコープ)
    1. 7.2.1 型宣言
    2. 7.2.2 有効範囲(スコープ)
      1. (プログラム例 7.5 ) 変数の有効範囲(同じファイル)
      2. (プログラム例 7.6 ) 変数の有効範囲(別々のファイル)
      3. (プログラム例 7.7 ) 変数の有効範囲(C++)
    3. 7.2.3 名前空間( namespace )(C++)
      1. (プログラム例 7.56 ) 名前空間
  4. 7.3 データの受け渡し
    1. 7.3.1 データとアドレス
      1. (プログラム例 7.8 ) 複数結果の受け渡し
      2. (プログラム例 7.9 ) デフォルト引数(C++)
    2. 7.3.2 配列
      1. 7.3.2.1 1 次元配列
        1. (プログラム例 7.10 ) 1 次元配列の受け渡し
      2. 7.3.2.2 2 次元以上の配列
        1. (プログラム例 7.11 ) 2 次元配列の受け渡し(方法 1)
        2. (プログラム例 7.12 ) 2 次元配列の受け渡し(方法 2)
        3. (プログラム例 7.13 ) 2 次元配列の受け渡し(方法 3)
        4. (プログラム例 7.58 ) 2 次元配列の受け渡し(再帰呼び出し)
    3. 7.3.3 関数名
      1. (プログラム例 7.14 ) 関数名の受け渡し(ニュートン法)
      2. (プログラム例 7.15 ) 関数名の配列
    4. 7.3.4 参照渡し(C++)
      1. (プログラム例 7.16 ) 参照渡し
      2. (プログラム例 7.17 ) 参照渡し(参照型関数)
  5. 7.4 main 関数
    1. (プログラム例 7.18 ) main 関数の引数(数字の和)
    2. (プログラム例 7.19 ) main 関数の引数(環境変数の出力)
  6. 7.5 その他(C++)
    1. 7.5.1 関数名のオーバーロード
      1. (プログラム例 7.20 ) 関数名のオーバーロード
    2. 7.5.2 インライン関数(Javaを除く)
      1. (プログラム例 7.21 ) インライン関数と #define マクロ
    3. 7.5.3 例外処理
      1. (プログラム例 7.22 ) 例外処理
  7. 7.6 様々な例題
    1. 7.6.1 数値計算
    2. (プログラム例 7.23 ) 連立線形方程式,逆行列(ガウス・ジョルダン)
    3. (プログラム例 7.28 ) 非線形方程式(二分法)
    4. (プログラム例 7.29 ) 非線形方程式(セカント法)
    5. (プログラム例 7.63 ) 非線形方程式(ニュートン法)
    6. (プログラム例 7.40 ) 代数方程式(ベアストウ)
    7. (プログラム例 7.43 ) 行列の固有値(フレーム法+ベアストウ法)
    8. (プログラム例 7.44 ) 実対称行列の固有値・固有ベクトル(ヤコビ法)
    9. (プログラム例 7.45 ) 最大固有値と固有ベクトル(べき乗法)
    10. (プログラム例 7.25 ) 数値積分(台形則)
    11. (プログラム例 7.26 ) 数値積分(シンプソン則)
    12. (プログラム例 7.27 ) 微分方程式(ルンゲ・クッタ)
    13. (プログラム例 7.41 ) 補間法(ラグランジュ)
    14. (プログラム例 7.42 ) 補間法(スプライン)
    15. (プログラム例 7.46 ) 補間法(ベジエ曲線)
    16. 7.6.2 最適化
    17. (プログラム例 7.61 ) 最適化(線形計画法)
    18. (プログラム例 7.30 ) 最適化(黄金分割法)
    19. (プログラム例 7.60 ) 最適化(多項式近似法)
    20. (プログラム例 7.31 ) 最適化(最急降下法)
    21. (プログラム例 7.47 ) 最適化(共役勾配法)
    22. (プログラム例 7.48 ) 最適化( Newton 法)
    23. (プログラム例 7.49 ) 最適化(準 Newton 法)
    24. (プログラム例 7.59 ) 最適化(シンプレックス法)
    25. (プログラム例 7.50 ) 最適化(動的計画法)
    26. 7.6.3 確率と統計
    27. (プログラム例 7.32 ) ガンマ関数
    28. (プログラム例 7.64 ) 二項分布
    29. (プログラム例 7.65 ) ポアソン分布
    30. (プログラム例 7.66 ) 一様分布
    31. (プログラム例 7.67 ) 指数分布
    32. (プログラム例 7.33 ) 正規分布
    33. (プログラム例 7.34 ) χ2 分布
    34. (プログラム例 7.35 ) t 分布
    35. (プログラム例 7.36 ) F 分布
    36. (プログラム例 7.57 ) Fisher の直接確率
    37. (プログラム例 7.37 ) 乱数の発生
    38. 7.6.4 多変量解析
    39. (プログラム例 7.24 ) 最小二乗法
    40. (プログラム例 7.51 ) 重回帰分析
    41. (プログラム例 7.52 ) 正準相関分析
    42. (プログラム例 7.53 ) 主成分分析
    43. (プログラム例 7.54 ) 因子分析
    44. (プログラム例 7.55 ) クラスター分析
    45. (プログラム例 7.62 ) 分散分析
    46. 7.6.5 その他
    47. (プログラム例 7.38 ) ソート(並べ替え)
    48. (プログラム例 7.39 ) 基本アルゴリズム(その1)
  8. 演習問題7

7.0 標準関数

  C/C++ のプログラムは関数( function )を基本としています.今まで書いてきたプログラムも main 関数という関数の一種です.ただ,main 関数は,その名前も main と決まっていますし,プログラム内に 1 つだけ必ず存在しなければなりません.しかし,他の関数に対しては,変数名と同じように任意の名前を付けられますし,また,複数存在しても構いません.ただし,同じ名前の関数が複数存在することは許されません( C++ の場合は可能です − 7.5.1 節参照 −).

  関数は,数学の関数と同じように,データを与えると,その結果を返してくれます.しかし,数学の関数の場合は,単に値を返すだけですが,C/C++ の関数の場合は,データを返すことはもちろん,それ以外の複雑な作業の実行も可能です.今までの例で使用した printf,scanf,sqrt 等はシステムが用意している関数(標準関数付録参照−)です.我々は,これらの関数を利用して様々なプログラムを書くことができます.

  たとえば,変数 x に入っている値の平方根を計算したいときは,
	y = sqrt(x);
		
と書くだけで,変数 y に x の平方根が代入されます.付録の中の「sqrt」という関数に関する説明の[形式]の中に,
	#include <math.h>
	double sqrt(double x)
		x : 倍精度浮動小数点数
		
という部分があります.最初の行は,この関数を使用するためには,「math.h」というヘッダファイルをインクルードしなければならないということを意味しています.

  sqrt の前に書かれた「double」は,この関数が結果として double 型の値を返すこと,また,括弧の中の double は,計算に使用するデータ(引数)は double 型でなければならないということを意味しています.

  従って,ある値の平方根を計算するためには,以下のような記述をすれば良いことになります.
	double x = 3.14, y;
	y = sqrt(x);   // y = sqrt(3.14) としても良い
		
7.0.1 scanf と printf

  個々の関数に対する説明は付録を参照してもらうとして,ここでは,scanfprintf の説明だけを多少詳しく行っておきます.なお,Java においても,printf 関数(メソッド)が存在し,かつ,似たような機能を有しています(Console クラスPrintStream クラスPrintWriter クラスFormatter クラス参照)が,scanf に対応するメソッドは存在しません.

  標準入出力装置(一般的には,キーボードとディスプレイ)に対して入出力を行う場合,対象とするものは文字列です.キーボードから入力する場合,たとえ数値であっても,それを例えば「123.45」のように,我々が読むことができる文字列として入力します.しかし,数値データを,入力された文字列としてそのまま記憶したならば,演算等を行うことができません.そのため,入力された文字列をコンピュータ内部で使用する表現方法に変換して記憶してやる必要があります.例えば,「123.45」という文字列を数値として扱いたければ,浮動小数点表現に変換して記憶しておく必要があります.もちろん,「123.45」を文字列として内部的に扱いたければ,文字列として記憶しておく必要があります.

  また,ディスプレイに出力する場合も,メモリ(変数)に記憶されているデータを我々が解釈しやすい文字列に変換して出力します.たとえば,整数 3 の内部表現である 00 … 011 がそのまま出力されれば(そのまま出力する方法もある),我々自身がその結果を解釈しなければなりません.

  std::cin や std::cout を使用する場合は,これらの変換を自動的に行ってくれますが,scnaf や printf を使用する場合,これらの変換方法を指定してやる必要があります.

  例えば,scanf は,以下のようにして使用します.
	double d_data;
	int i_data;
	char c_data[10];
	scanf("%lf %d %s", &d_data, &i_data, c_data);
		
「"%lf %d %s"」の部分が,入力されたデータをどのように変換するかを指定する部分です.「%」に続く文字列が 3 つありますので,3 つのデータが入力されるされること,また,「%」に続く文字列間のスペースは,各データがスペースで区切られていることを意味します.入力された文字列に対して,「%lf」は double 型の浮動小数点表示に変換して,「%d」は int 型の整数に変換して,また,「%s」は文字列としてそのまま,記憶することを意味しています.これら 3 つの「%」で始まる文字列は,次に続く 3 つのデータに順番に対応していますので,この scanf 関数に対して,例えば,
	3.141592654 123 abc
		
または,
	3.141592654
	123
	abc
		
のように入力すると(改行も,スペースと同じ入力データに対する区切り文字として解釈される),d_data に「3.141592654」が double 型に変換され,i_data に「123」が int 型に変換され,また,c_data に文字列「abc」が,記憶されます.各変数に対して,そのアドレスを指定しなければならない点に注意してください.

  printf は,scanf とは逆に,記憶されたデータを文字列に変換する操作を行います.例えば,以下のようにして使用します.
	double d_data;
	int i_data;
	char c_data[10];
	 ・・・・・
	printf("結果は %f %10.3f %d %5d %s %c\n", d_data, d_data, i_data, i_data, c_data, c_data[1]);
		
この結果,まず,「結果は 」という文字列が出力されます.printf においては,「%」で始まる文字列とエスケープシーケンス以外は,記述された内容がそのまま出力されます.次に,以下の順序で 6 つのデータが出力されます( % で始まる文字列が 1 つの半角スペースで区切られているため,各データ間には,1 つのスペースが入る).

  1. 「%f」は double 型のデータを固定小数点表現の文字列に変換します( 1.23x10-3 のような表現方法に対応する 1.23e-03 という方法−浮動小数点表現−も存在します).「%」と「f」の間に何も記述しなければ,全体の桁数や小数点以下の桁数はシステムの標準形式に従います.この場合,d_data の内容が,システムの標準形式に従って出力されます.

  2. 最初のデータと変換方法は同じですが,この場合は,出力形式を指定しています.d_data の内容が,小数点以下3桁,全体の桁数 10 桁で出力されます(例: △△△-12.345 ).桁数が 10 桁に満たない場合は,左側にスペースが挿入されます.全体の桁数を指定しない場合は,「10」の部分を省略しても構いません.

  3. 「%d」は int 型のデータを文字列に変換します.「%」と「d」の間に何も記述しなければ,システムの標準形式に従って出力されます.この場合,i_data の内容が,システムの標準形式に従って出力されます.

  4. 上の i_data と変換方法は同じですが,この場合は,出力形式を指定しています.i_data の内容が,全体の桁数 5 桁で出力されます(例: △△-12 ).桁数が 5 桁に満たない場合は,左側にスペースが挿入されます.

  5. c_data の内容が文字列として出力されます.

  6. c_data の 2 番目の文字 b が出力されます.

  先に述べた scanf によって入力されたデータを,この printf で出力すると,その結果は以下のようになります.
	結果は 3.141593      3.142 123   123 abc b
		
7.0.2 文字列操作

  問題によっては,文字列を扱う場合も多くあります.以下に示す例で使用されている各関数について簡単に説明すると,以下のようになります.

  1. size_t strlen(const char *str)
    str : NULL 文字('\0')で終了する文字列
      文字列のバイト単位の長さを返します.この長さには NULL 文字('\0')は含まれません.

  2. char *strcpy(char *str1, const char *str2)
    str1 : コピー先の文字列
    str2 : コピー元の文字列
      ある文字列を別の文字列に NULL 文字('\0')を含めてコピーし,コピー先の文字列へのポインタを返します.これらの引数の文字列には,NULL 文字が入っているものと想定しています.

  3. char *strcmp(const char *str1, const char *str2)
    str1, str2 : 比較する文字列
      2 つの文字列を比較し,以下の結果を返します.これらの引数の文字列には,NULL 文字が入っているものと想定しています.
    • 負 : 文字列 1 が文字列 2 より小さい
    • 0 : 文字列 1 と文字列 2 は等しい
    • 正 : 文字列 1 が文字列 2 より大きい

  4. char *strcat(char *str1, const char *str2)
    str1 : 結合先の文字列
    str2 : 結合する文字列
      ある文字列に別の文字列を結合します.その結果の文字列の終端に NULL 文字('\0')を付加してから,連結された文字列へのポインタを返します.これらの引数の文字列には,NULL 文字('\0')が入っているものと想定しています.

  5. char *strstr(const char *str1, const char *str2)
    str1 : 文字列
    str2 : 探索する文字列
      文字列の中から指定された文字列を探索します.文字列が見つかった場合は,最初に見つかった位置へのポインタを返し,見つからなかった場合は,NULL を返します.

(プログラム例 7.0 ) 文字列操作 

/****************************/
/* 文字列操作の例           */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>
#include <string.h>

int main()
{
	char abc[] = "abc";
	char *defgh = "defgh";
	char emp[20] = "", *str;
	int i1, c1, c2, len1, len2, len3;
				// 現在の文字列の内容を出力
	printf("-abc- %s\n", abc);   // 文字列として出力
	printf("-abc- ");            // 1文字ずつ出力
	for (i1 = 0; i1 < 3; i1++)
		printf("%c", abc[i1]);
	printf("\n");
				// 文字列の長さ
	len1 = strlen(abc);
	len2 = strlen(defgh);
	len3 = strlen(emp);
 	printf("各文字列の長さ %d %d %d\n", len1, len2, len3);
				// 文字列のコピー
   	str  = strcpy(emp, abc);
   	len1 = strlen(emp);
   	printf("-str- %s -emp- %s 長さ %d\n", str, emp, len1);
				// 文字列の比較
   	c1 = strcmp(emp, abc);
   	c2 = strcmp(emp, defgh);
   	printf("-empとabc- %d -empとdefgh- %d\n", c1, c2);
				// 文字列の結合
   	str  = strcat(emp, " ");
   	str  = strcat(emp, defgh);
   	len1 = strlen(emp);
   	printf("-str- %s -emp- %s 長さ %d\n", str, emp, len1);
				// 文字列の検索
   	str  = strstr(emp, "ef");
   	len1 = strlen(str);
   	printf("-str- %s 長さ %d -emp- %s\n", str, len1, emp);

   	return 0;
}
		

  上のプログラムを実行すると,以下のような出力が得られます.
	-abc- abc
	-abc- abc
	各文字列の長さ 3 5 0
	-str- abc -emp- abc 長さ 3
	-empとabc- 0 -empとdefgh- -1
	-str- abc defgh -emp- abc defgh 長さ 9
	-str- efgh 長さ 4 -emp- abc defgh
		
7.1 簡単な関数

  関数は,自分自身で作成することもできます.関数の一般的な定義方法は以下の通りです.
	関数の型 関数名 ( 引数リスト )
		
ここで,引数とは,関数を呼び出した側と関数との間で,情報の受け渡しに使用される変数や定数のことです.また,関数は計算した結果を何らかの値として戻しますが,関数の型とはその値の型のことです.詳細については,以下の例を参照して下さい.

  プログラムの複数の場所で同じことを繰り返す場合や,プログラムが長くなりすぎる場合は,関数を積極的に利用することによって,わかりやすいプログラムを書くことが可能です.まず,この節では,簡単な例によって,関数の基本的な概念について説明します.

---------------------(C++)関数宣言-------------------------

C++ の関数・関数宣言は,C の場合と以下のような点で多少異なります.

  1. 関数を使用する前に必ず宣言(または,定義)しなければなりません.
  2. 宣言 func() は引数が無いことを意味しています.ただし,func(void) も許されます.
  3. 引数をチェックして欲しくないときは明示的に func(...) と書きます.
  4. 例えば,
    func(int x, double data ...)
    と記述することにより,引数の内最初の 2 つの引数だけをチェックさせることが可能です.特に,可変個数の引数を持つ関数の定義等に使用されます.
  5. 引数が省略された場合のデフォルト値を設定できます(後述).
  6. 引数の参照渡しが可能です(後述).
  7. 関数名のオーバーロード(多重定義)が可能です(後述).
  8. インライン関数を定義できます(後述).

----------------------(C++)関数宣言終わり--------------------

(プログラム例 7.1 ) 階乗の計算 

  この例は,入力されたデータ n (整数)の階乗を計算するためのプログラムです.今までのプログラムと同じように,標準関数を除いて,main 関数だけを使用して書いています.階乗の値は,通常,非常に大きくなるため,double 型で計算しています.
/****************************/
/* nの階乗の計算           */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
	double kai;
	int i1, n;
/*
	 データの入力
*/
	printf("nの値を入力して下さい ");
	scanf("%d", &n);
/*
	 階乗の計算
*/
	kai = 1.0;			   /* 初期設定 */
	for (i1 = 1; i1 <= n; i1++)
		kai *= (double)i1;
/*
	 結果の出力
*/
	printf("   %dの階乗は=%f\n", n, kai);

	return 0;
}
		

(プログラム例 7.2 ) 階乗の計算(関数の利用) 

  このプログラムでは,プログラム例 7.1 と同じ内容の計算を,main 関数から,階乗を計算するための関数 kaijo を呼び出して行っています.以下の説明により,関数の基本的事項を理解して下さい.なお,この例においては,main と kaijo を同じファイルに記述されたものとして説明していますが,kaijo( 30 行目以降)を別のファイルに記述することも可能です(以下の例においても同様).

01	/****************************/
02	/* nの階乗の計算           */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	#include <stdio.h>
06
07	double kaijo(int);
08
09	int main()
10	{
11		double kai;
12		int n;
13	/*
14	     データの入力
15	*/
16		printf("nの値を入力して下さい ");
17		scanf("%d", &n);
18	/*
19	     階乗の計算
20	*/
21		kai = kaijo(n);
22	/*
23	     結果の出力
24	*/
25		printf("   %dの階乗は=%f\n",n,kai);
26
27		return 0;
28	}
29
30	/**************************/
31	/* mの階乗               */
32	/*      m : データ        */
33	/*      return : mの階乗 */
34	/**************************/
35	double kaijo(int m)
36	{
37		double s;
38		int i1;
39
40		s = 1.0;
41		for (i1 = 1; i1 <= m; i1++)
42			s *= (double)i1;
43
44		return s;
45	}
		
7 行目

  関数の宣言をしています.この例では,関数 kaijo が double 型の値を返し,また,int 型の値を 1 つ関数に引き渡すことを宣言しています.なお,30 行目以降の関数本体をこの場所に記述すれば,この行は必要なくなります.

  変数と同じように,関数における処理の結果に対しても様々な型が存在します.従って,関数毎にその関数が返す値の型を宣言してやる必要があります.もし,値を返さない場合は void と宣言する必要があります.

  基本的に,各関数は,全く別のプログラムであると考えた方が理解しやすいと思います.従って,関数を呼び出した側の情報を,呼び出された関数に伝えるには何らかの処理が必要です.例えば,階乗を計算する場合,階乗の対象となる n 自身の値が不明であれば,計算不可能です.情報を伝える 1 つの方法が引数です.この宣言では,int 型の引数( n )を 1 つ渡すことを宣言しています.もちろん,引き渡す情報を必要としなければ,引数も必要ありませんし,また,複数の引数を渡すことも可能です.

21 行目

  関数 kaijo にデータ n を渡し,関数を呼び出し,その結果を変数 kai に代入しています.この行は,25 行目と一緒にして,次のようにも書けます.
	printf("   %dの階乗は=%f\n", n, kaijo(n));
			
30 〜 34 行目

  30 から 45 行目までが,関数 kaijo の本体(関数の定義)です.これらの行は,関数の機能についての注釈です.このように,少なくとも,関数の機能,引数の意味,返す値に対する説明は必ず書いておいて下さい.

35 行目

  各関数定義の最初に記述する文です.返す値や引数の型は,必ず,7 行目の宣言や 21 行目の呼び出し時と一致していなければなりません.しかし,この例のように,引数の名前は呼び出し時と異なっても構いません(もちろん,同じでも構いません).main 関数における n の値が関数 kaijo の m にコピーされて関数が実行されます.

  しかし,その値のコピーが渡されるため,たとえ関数 kaijo で同じ変数名 n を使用していても,main の変数 n と kaijo の変数 n とは異なる変数です.従って,もし関数 kaijo 内で変数 n の値を変更しても,main の変数 n の値は変化しません.

  関数内で使用している他の変数についても同様です.ある関数と別の関数において,同じ名前の変数が使用されていても,それらは全く関係ありません.ある関数で,ある変数の値を変化させた場合,たとえそれが別の関数の変数と同じ名前であっても,その情報が何らかの方法で受け渡されない限り,別の関数ではその影響を全く受けません.

44 行目

  関数での処理を終了し,その結果を返すための文です.void 型の関数でない限り,必ずこの文が必要になります.

(プログラム例 7.3 ) 階乗の計算(再帰呼び出し) 

  先の例では,main から kaijo という関数を呼び出しただけでしたが,関数の中で,例えば kaijo の中で別の関数を呼び出すことも可能です.特別の場合として,自分自身を呼び出すことも可能です.それを,再帰呼び出しrecursive call )といいます.再帰呼び出しを頻繁に使用すると,プログラムが分かり難くなる場合が多々あります.やむを得ない場合以外,再帰呼び出しの使用は避けた方がよいと思います.このプログラムは,その概念を理解してもらうために,階乗の計算に再帰呼び出しを利用しています.自分で,実行手順を追ってみて下さい.
/****************************/
/* nの階乗の計算           */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

double kaijo(int);

int main()
{
	double kai;
	int n;
/*
	 データの入力
*/
	printf("nの値を入力して下さい ");
	scanf("%d", &n);
/*
	 階乗の計算
*/
	kai = kaijo(n);
/*
	 結果の出力
*/
	printf("   %dの階乗は=%f\n",n,kai);

	return 0;
}

/**************************/
/* mの階乗               */
/*      m : データ        */
/*      return : nの階乗 */
/**************************/
double kaijo(int m)
{
	double s;

	if (m > 1)
		s = m * kaijo(m-1);	 /* 自分自身を呼んでいる */
	else
		s = 1;

	return s;
}
		

(プログラム例 7.4 ) nrの計算 

  今までの例では,関数を使用する便利さがあまり明確でなかったかもしれません.そこで,この例では,上で作成した階乗を計算する関数 kaijo を利用して,nr(= n! / (r! (n - r)!) )の計算をしてみます.nrの計算では,階乗の計算を多く行いますので,関数 kaijo を利用すると,下に示すように,非常に簡単に書くことができます.

  また,この例のように,関数を呼び出すときの引数として式を書くこともできます.
/****************************/
/* nCrの計算               */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

double kaijo(int);

int main()
{
	double sn, sr, snr;
	int n, r;
/*
	 データの入力
*/
	printf("nとrの値を入力して下さい ");
	scanf("%d %d", &n, &r);
/*
	 nCrの計算と出力
*/
	sn  = kaijo(n);
	sr  = kaijo(r);
	snr = kaijo(n-r);
	printf("   %dC%dは=%f\n", n, r, sn/(sr*snr));

	return 0;
}

/**************************/
/* mの階乗               */
/*      m : データ        */
/*      return : nの階乗 */
/**************************/
double kaijo(int m)
{
	double s;
	int i1;

	s = 1.0;
	for (i1 = 1; i1 <= m; i1++)
		s *= (double)i1;

	return s;
}
		

7.2 変数の有効範囲(スコープ)

  7.1 節において述べたように,基本的に,各関数はそれぞれ独立したプログラムであり,各関数で使用されている変数等は,たとえ同じ名前の変数であっても,全く別のものです.7.1 節では,各関数で必要とする情報の受け渡しの方法として,引数と関数の返す値(戻り値)について説明しました.

  しかし,実際は,変数や関数の宣言方法やその宣言場所によっては,引数や戻り値以外の方法で情報の伝達が可能になる場合があります.そこで,本節においては,変数や関数の有効範囲(スコープ,scope )について説明します.

7.2.1 型宣言

  有効範囲の説明に移る前に,第 3 章で述べた型宣言についてより詳細に述べておきます.変数の型を宣言する一般形式は以下の通りです.ただし,Java においては,すべての変数がクラスと結びついているため,以下に述べるような意味における記憶クラスや型修飾はないと言っても良いと思います.詳しくは,第 10 章を参照して下さい.
	[記憶クラス] [型修飾]データ型 変数名, 配列名, 関数名, ・・・
		
  記憶クラスの指定は,以下の 4 つの中から選択されます.
	auto    :内部
	register:内部
	static  :内部,外部
	extern  :内部,外部
		
  記憶クラス auto は,関数の内部で宣言する場合だけに使用可能です.auto として宣言された変数,自動変数は,必要時だけに記憶領域が確保されます.関数が呼び出されると,関数内で宣言された自動変数がスタック上に作成され,関数の実行が終わるとすべて消去されます.自動変数に対し初期設定をする必要がある場合,関数が呼び出される度に行われます.初期設定に対するオーバーヘッドを少なくするためには,static 宣言を利用します.関数内で変数を宣言する場合,記憶クラスを省略すると auto になります.

  記憶クラス register も,関数内部だけで使用可能です.使用頻度の高い変数をレジスタに格納し,実行速度を速めるために使用されます.

  記憶クラス static 及び extern は,関数の内部及び外部の宣言で使用できます.static 宣言された変数,静的変数は,メモリ内に固定的に割り当てられます.関数外部における宣言の場合,static 宣言をしても,また,記憶クラスを省略して宣言しても,静的変数になります.しかし,その内容は少し異なります.省略して宣言した場合,その変数を他の翻訳単位(ソースファイル)や,同じ翻訳単位内のその変数を宣言した箇所より前の部分で参照や変更が可能( extern 宣言が必要)となりますが,static 宣言をした場合は,同じ翻訳単位内のその変数を宣言した箇所より後ろの部分だけで参照や変更が可能となります.

  extern 宣言された変数,外部変数は,他の翻訳単位や同じ翻訳単位内の変数宣言の前の部分で,変数の参照や変更を行うために使用されます.外部変数は,プログラム内のいずれかにおいて,関数の外部で,記憶クラスを省略した宣言が行われていなければなりません.

  型修飾の方法には,
	const
	volatile
		
の 2 種類があります.const は,値の変更できない変数を指定するものです.また,volatile 型を宣言すると,コンパイラは,その変数が,未知の方法によって,また,どの時点においてもアクセスできるような変数であるとみなし,最適化の対象としません.

------------------------(C++)const-------------------------

  C++ における const 指定は,定数を定義するプリプロセッサ #define と同じような役割を果たします.例えば,以下のように,const 指定した変数で配列の定義も可能です.なお,n は定数ですので,プログラム内でその値を変更することはできません.
	const int n = 10;
	double x[n];
		
----------------------(C++)const終了-----------------------

7.2.2 有効範囲(スコープ)

  関数の内部で,記憶クラスが省略されたり,auro,register,static,または,extern 宣言された変数は,その関数の内部だけで参照・変更が可能です.したがって,異なる関数で,上のように宣言された同じ変数名を使用していても,それらの変数間には全く関係がありません.ただし,同じ extern 宣言をした変数は,同じ変数を指すことになりますので,ある関数でその値を変更すれば,その変数を利用している他の関数においてもその影響を受けます.

  関数の外部で,記憶クラスが省略されたり,static,または,extern 宣言された変数は,その変数が宣言された場所以降のすべての関数内で参照・変更が可能になります.ただし,関数内で,外部で宣言された変数と同じ名前の変数名が使用された場合は,内部で宣言された変数が優先されます.

  関数の有効範囲に関しても,基本的に,関数の外部で宣言された変数の場合と同じです.ただし,関数の定義がなされている場合は,それも宣言の一種とみなされます.従って,例えば,プログラム例 7.2 において,関数 kaijo の定義(関数 kaijo の本体)が main 関数の前にあれば,7 行目の宣言は不必要になります.

------------------(C++)Cとの有効範囲の違い------------------

  先に述べましたように,C++ においては,変数を最初に使用する場所で変数宣言をすることが可能です.その場合,基本的には,その宣言をした場所から関数の最後(ブロック,{ } で囲まれた部分)までが有効範囲になります.特に,if 文,for 文,switch 文等の各ブロック内で宣言をした場合は,宣言された場所から,そのブロックの終わりまでが有効範囲になりますので,注意する必要があります.

  また,より内側のブロックで同じ変数名が宣言された場合は,その変数の方が優先されます.従って,外側のブロックで宣言された変数を見ることができなくなります.ただし,関数の外側で宣言された変数(外部変数)については,変数名の前にスコープ解決演算子 :: を付加することによって,その変数にアクセスできるようになります.例えば,変数 x を外部変数としたとき,その値を変数 y に代入するには,
	y = ::x;
		
のように記述します.

----------------(C++)Cとの有効範囲の違い終わり--------------

(プログラム例 7.5 ) 変数の有効範囲(同じファイル)

  次の例は,main 関数と 3 つの関数を同じファイルに書いた場合における変数の有効範囲を説明するためのプログラムです.

01	/**********************************/
02	/* 変数の有効範囲(同じファイル) */
03	/*      coded by Y.Suganuma       */
04	/**********************************/
05	#include <stdio.h>
06
07	void sub3(void);
08
09	extern int j;   // 26 行目で宣言された j
10
11	void sub1(void)
12	{
13		extern int i;   // 25 行目で宣言された i
14		int k = 3;
15		printf("%d %d %d\n", i, j, k);
16	}
17
18	void sub2(void)
19	{
20		int i = 5;
21		int k = 6;
22		printf("%d %d %d\n",i,j,k);
23	}
24
25	int i = 1;
26	int j = 2;
27
28	int main()
29	{
30		sub2();
31		sub1();
32		sub3();
33
34		return 0;
35	}
36
37	void sub3(void)
38	{
39		int k = 7;
40		printf("%d %d %d\n", i, j, k);
41	}
		
7 行目

  main 関数において,関数 sub1,sub2,及び,sub3 を参照していますが,関数 sub3 の記述は main 関数の後になっています.従って,この関数に対する型宣言が必要になります.他の関数に関しては,その記述以降にその関数を参照していますので必要ありません.

9 行目

  関数 sub1 及び sub2 において変数 j を参照( 15,22 行目)していますが,その宣言は,26 行目でなされています.この宣言により,sub1,及び,sub2 で参照している変数 j は,26 行目で宣言されている変数 j と同じものになります.もちろん,関数 sub3 の変数 j は,変数 j の関数外部における宣言の後ろにありますので,同じものになります.従って,新しい宣言を加えずに,いずれかの関数において変数 j の値を修正すれば,その影響は他のすべての関数に及びます.

13 行目

  関数 sub1 の内部において 25 行目で宣言されている変数 i と同じものを参照するため,この宣言を行っています.9 行目と同じ extern 宣言ですが,関数内部で行われているため,その有効範囲は関数 sub1 の内部に限られます.もちろん,関数 sub3 で使用している変数 i とは同じものですので,関数 sub1 において変数 i の値を変更すれば,関数 sub3 における出力結果も異なってきます.しかし,関数 sub2 で宣言されている変数 i は,内部で行われた宣言が優先されますので全く別のものです.

  また,各関数において,変数 k が宣言されています.その有効範囲は各関数内部に限られ,同じ変数名ではありますが全く関係ありません.

(プログラム例 7.6 ) 変数の有効範囲(別々のファイル)

  次のプログラムは,プログラム例 7.5 の各関数を別々の 4 つのファイルに分け,同じ結果を得るように書き直したものです.先の例では必要なかった宣言が必要になることに注意して下さい.この例により,変数の有効範囲の概念を十分理解して下さい.なお,Java においては,先に述べましたように,すべての関数はいずれかのクラスに含まれています.したがって,クラスまたはそのオブジェクトを介して参照することになり,この例のような場合は存在しません.
------------------------file 1----------------------------
/************************************/
/* 変数の有効範囲(別々のファイル) */
/*      coded by Y.Suganuma         */
/************************************/
void sub1(void);
void sub2(void);
void sub3(void);

int i = 1;
int j = 2;

int main()
{
	sub2();
	sub1();
	sub3();
	return 0;
}
------------------------file 2----------------------------
#include <stdio.h>

void sub1(void)
{
	extern int i;   // file 1 で宣言された i
	extern int j;   // file 1 で宣言された j
	int k = 3;
	printf("%d %d %d\n", i, j, k);
}
------------------------file 3----------------------------
#include <stdio.h>

void sub2(void)
{
	extern int j;   // file 1 で宣言された j
	int i = 5;
	int k = 6;
	printf("%d %d %d\n", i, j, k);
}
------------------------file 4----------------------------
#include <stdio.h>

void sub3(void)
{
	extern int i;   // file 1 で宣言された i
	extern int j;   // file 1 で宣言された j
	int k = 7;
	printf("%d %d %d\n", i, j, k);
}
		

(プログラム例 7.7 ) 変数の有効範囲(C++) 

  次のプログラムは,C と C++ との有効範囲の違いを示すためのものです.その違いは,特に,C++ において,任意の場所で型宣言や初期化を行えることから来ています.

01	/*****************************/
02	/* C++における変数の有効範囲 */
03	/*      coded by Y.Suganuma  */
04	/*****************************/
05		#include <stdio.h>
06
07	int x = 10;   // 以下に記述されたすべての関数で有効
08
09	int main()
10	{
11		int x = 20;   // main 関数内で有効
12		int y;        // main 関数内で有効
13
14		y = ::x;   // 7 行目の x を参照
15		printf("x %d y %d\n", x, y);
16
17		if (x > 5) {
18			int y = 30;   // 18,19 行目だけで有効
19			printf("x %d y %d\n", x, y);
20		}
21
22		printf("x %d y %d\n", x, y);
23
24		for (int x = 1; x <= 3; x++) {   // x は 24 〜 27 行目で有効
25			int y = x + 1;   // 25,26 行目だけで有効
26			printf("x %d y %d\n", x, y);
27		}
28
29		printf("x %d y %d\n", x, y);
30
31		return 0;
32	}
		

  このプログラムを実行すると以下のような出力が得られます.
	x 20 y 10
	x 20 y 30
	x 20 y 10
	x 1 y 2
	x 2 y 3
	x 3 y 4
	x 20 y 10
		
  この結果からも明らかなように,15 行目の段階では,変数 x には 11 行目で宣言したときの初期値,また,変数 y には 7 行目で宣言された x の値が,スコープ解決演算子による 14 行目の代入文によって入っています.また,19 行目の段階では,18 行目で宣言された変数 y が優先されています.しかし,この変数の有効範囲は 20 行目までですので,22 行目では再び 15 行目の状態に戻っています.同様に,24 から 27 行目においても,ブロック内で宣言された x および y が優先されます.

7.2.3 名前空間( namespace )(C++)

  たとえば,大きなプログラムを複数の人で作成するような場合,全く同じ名前で,かつ,同じ引数を持つ関数を,異なる人が異なる目的で作成してしまうような場合が考えられます.関数の有効範囲は基本的にグローバル(大域的なスコープを持っている)ですから,そのまま放置すればとんでもないことになってしまいます.そこで,関数や後に説明するクラスなどに対しては,名前空間を利用してその有効範囲を制御することができます.

  名前空間の定義は,
	namespace 名前空間名 { リスト };
		
のように行います.たとえば,
	namespace name1
	{
		int func1( ・・・ ) { ・・・ }
			・・・・・
	};
		
のようにして,名前空間 name1 を定義し,その中に関数 func1 を定義しておけば,関数 func1 は,この名前空間内だけで有効な関数となります.グローバルに定義された同じ名前の関数が存在したとしても,
	x = name1::func1( ・・・ );
		
のように,名前空間名とスコープ解決演算子を利用して,名前空間 name1 内に定義された関数を指定することができます.

(プログラム例 7.56 ) 名前空間

  この例では,名前も引数も同じ 2 つの関数が定義されています.名前空間を利用することによって,適切な関数を利用することができます.なお,この例では,クラスを使用していますが,今のところ,Seisu とは 2 つの整数の集まりからなる型だと思って下さい.

#include <stdio.h>

namespace Test1 {
	class Seisu   // 2 つの整数の集まりからなる型
	{
		public:
			int k1;
			int k2;
	};
	void print(int k)
	{
		printf("%d\n", k);
	}
}

namespace Test2 {
	void print(int k)
	{
		if (k < 0)
			printf("\n");
		else
			printf(" %d", k);
	}
}

/****************/
/* main program */
/****************/
int main()
{
	Test1::Seisu kk;   // Seisu 型変数 kk を定義

	kk.k1 = 10;   // 変数 kk の最初の整数の値を設定
	kk.k2 = 20;   // 変数 kk の 2 番目の整数の値を設定

	Test1::print(kk.k1);
	Test1::print(kk.k2);

	Test2::print(kk.k1);
	Test2::print(kk.k2);
	Test2::print(-1);

	return 0;
}
		

  このプログラムを実行すると,以下に示すような出力が得られます.
10
20
10 20
		
  この例における main 以下の部分は,using を利用して,以下に示すように,名前空間 Test1 に対するスコープ解決演算子を省略することも可能です.同様な方法で,名前空間 Test2 に対するスコープ解決演算子を省略することも可能ですが,2 つとも省略することはできません.また,名前空間内に定義されている関数やクラスを,スコープ解決演算子や using を使用せずに参照することは不可能です.
/****************/
/* main program */
/****************/
using namespace Test1;

int main()
{
	Seisu kk;

	kk.k1 = 10;
	kk.k2 = 20;

	print(kk.k1);
	print(kk.k2);

	Test2::print(kk.k1);
	Test2::print(kk.k2);
	Test2::print(-1);

	return 0;
}
		

7.3 データの受け渡し

7.3.1 データとアドレス

  関数間で情報を受け渡す方法は,大きく分けて,2 つあります.1 つは,7.1 節で述べたように,引数を利用する方法です.あと 1 つは,7.2 節で述べた変数の記憶クラスを利用する方法です.しかし,この方法を多用すると,関数の独立性が失われ,後にプログラムの修正をしなければならないようなとき,1 つの関数の修正が他の多くの関数に影響を及ぼすようなことが発生する可能性があります.従って,読み易さの範囲で,できるだけ引数を利用する方法を使った方がよいと思います.

  最も基本的な関数は,複数の引数を渡して,1 つの結果を呼び出した側に返します.引数として与えられた変数(定数)は,相手側にそのコピーが受け渡されるだけであり,呼び出された関数内でその値を変更して,その結果を呼び出した関数に返すことはできません.しかし,場合によっては,関数から複数の結果を返してもらいたいような場合が起こります.そのような場合は,変数のアドレスを渡すという方法で解決できます.例えば,
	{
		 ・・・
		b = 5;
		n = sub(a, &b);
		 ・・・
	}
	int sub(int a, int *b)
	{
		 ・・・
		*b = 10;
		 ・・・
	}
		
のようにすると,関数 sub を呼び出した後,変数 b の値は 10 に変化します.

(プログラム例 7.8 ) 複数結果の受け渡し 

  関数 sub ( main と異なるファイル)は,3 つのデータ a,b,及び,n を受け取り,(a + b) / n の計算をしています.関数 sub に必要なデータを extern 宣言と引数を利用して渡しています.n が 0 でない場合は除算を実行し,0 を戻り値として返します.n が 0 の場合は 1 を戻り値として返すと共に,n の値を 100 に設定し,除算を実行しません.また,除算の結果はそのアドレスが渡された変数 res に入れて返しています.

  変数 n は,extern 宣言によって宣言されているので,main 関数の上で宣言されている変数 n と同じものになり,値を変更できます.ただし,あくまで,例として行っているのであり,一般的には引数として渡すべきです.

  なお,変数 ind は,main 及び sub で使用されていますが,名前は同じでも異なる変数であり,関数 sub から return 文で返されない限り main の変数 ind には値が入らないことに注意して下さい.

/****************************/
/* 複数結果の受け渡し       */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int sub(int, int, int *);

int n;	/* 以下の関数にすべて有効 */

int main()
{
	int a, b, res, ind;
/*
	 データの入力
*/
	printf("a,b,及び,nの値を入力して下さい ");
	scanf("%d %d %d", &a, &b, &n);
/*
	 関数の呼び出し
*/
	ind = sub(a, b, &res);   /* 変数resはアドレス渡し */
/*
	 結果の出力
*/
	if (ind == 0)
		printf("n %d result %d", n, res);
	else
		printf("n = 0 (n %d)\n", n);

	return 0;
}
/*****************************/
/* (a+b)/nの計算             */
/*      a,b : データ         */
/*      res : 計算結果       */
/*      return : =0 : normal */
/*               =1 : n = 0  */
/*****************************/
int sub(int a, int b, int *res)
{
	extern int n;
	int ind;

	if (n == 0) {
		ind = 1;
		n   = 100;
	}
	else {
		ind  = 0;
		*res = (a + b) / n;
	}

	return ind;
}
		

  このプログラムを実行し,変数 a,b,n の値として,2,4,3,及び,2,4,0 を与えたときの結果は,それぞれ以下のようになります.
	n 3 result 2
	n = 0 (n 100)
		
  上のプログラムにおいては,除算の結果に対してだけ,アドレスを使って受け渡しをしましたが,
	sub(a, b, &res, &ind);
		
のように,変数 ind に対してもアドレスを使用することができます.その場合,関数 sub の宣言は以下のようになります.
	void sub(int a, int b, int *res, int *ind)
		
  また,複数結果を受け取る方法として,6.4.2 節で述べた new 演算子を使用して結果を保存する領域を確保し,その領域に対するポインタを返す方法があります.以下に示すプログラムは,上記と同じ内容をこの方法で書いたものです.
/****************************/
/* 複数結果の受け渡し       */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int *sub(int, int);

int n;	/* 以下の関数にすべて有効 */

int main()
{
	int a, b, *res;
/*
	 データの入力
*/
	printf("a,b,及び,nの値を入力して下さい ");
	scanf("%d %d %d", &a, &b, &n);
/*
	 関数の呼び出し
*/
	res = sub(a, b);   /* resに結果が入る */
/*
	 結果の出力
*/
	if (res[0] == 0)
		printf("n %d result %d", n, res[1]);
	else
		printf("n = 0 (n %d)\n", n);

	delete [] res;

	return 0;
}
/**************************************/
/* (a+b)/nの計算                      */
/*      a,b : データ                  */
/*      return : res[0] : =0 : normal */
/*                        =1 : n = 0  */
/*               res[1] : 計算結果    */
/**************************************/
int *sub(int a, int b)
{
	extern int n;
	int *res = new int [2];

	if (n == 0) {
		res[0] = 1;
		n      = 100;
	}
	else {
		res[0] = 0;
		res[1] = (a + b) / n;
	}

	return res;
}
		
  さらに,次節で述べるように,2 つの要素を持つ 1 次元配列
	int res[2]
		
を定義し,この配列を介してデータを受け渡すことも可能です.この場合における関数呼び出しは,
	sub(a, b, res);
		
のようになり,関数 sub の宣言は,
	void sub(int a, int b, int *res)   // void sub(int a, int b, int res[]) でも良い
		
のようになります.関数の内容は,「new 演算子を使用しない」,「 return 文を使用しない」点を除き,上述のプログラムとほとんど同じです.

----------------------(C++)デフォルト引数-------------------

  C++ の場合,関数の宣言または定義中に,関数の呼び出しで引数が省略されたときのデフォルト値default value )を設定しておくことができます( Java ではできません).例えば,
	int func(int, int = 5, char * = "test");
		
と宣言しておくことにより,
	func(x)
	func(x, 10);
		
は,それぞれ,
	func(x, 5, "test")
	func(x, 10, "test");
		
と解釈されます.

  デフォルト引数は引数の後ろから順に設定でき,中間の引数をデフォルト引数に設定したり,また,関数を呼び出すとき,中間の引数を省略することはできません.

(プログラム例 7.9 ) デフォルト引数(C++)
01	/****************************/
02	/* デフォルト引数           */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	#include <stdio.h>
06
07	void sub(int, int = 5, FILE * = stdout);
08
09	int main()
10	{
11		sub(10);              // sub(10, 5, stdout)と解釈
12		sub(10, 10);          // sub(10, 10, stdout)と解釈
13		sub(10, 20, stdout);
14		return 0;
15	}
16	/*********************************/
17	/* 整数の足し算                  */
18	/*      x,y : データ             */
19	/*      fp : 出力先              */
20	/*           coded by Y.Suganuma */
21	/*********************************/
22	void sub(int x, int y, FILE *fp)
23	//void sub(int x, int y = 5, FILE *fp = stdout)
24	{
25		fprintf(fp, "結果は=%d\n", x+y);
26	}
		

------------------(C++)デフォルト引数終わり------------------

7.3.2 配列

7.3.2.1 1 次元配列

  多量のデータを引数として渡したい場合,最も簡単な方法はそれらのデータを配列に入れて引き渡すことです.この節では,配列を引数とする場合について考えます.

  1 次元配列の場合は簡単です.例えば,以下のような方法によって行われます.
	{
		int b[5];
		  ・・・
		n = sub(・・・, b, ・・・);
		  ・・・
	}
	int sub(・・・, int c[], ・・・)
	{
		  ・・・
	}
		
このように記述することにより,呼び出した側の配列 b と関数 sub 内の配列 c は同じものになり,どちらでも,配列データの参照・修正が可能になります.また,関数側で,配列の添え字を書く必要はありません.先に述べましたように,変数 c は実質上ポインタですので,関数 sub の定義を
	int sub(・・・, int *c, ・・・)
		
と書いても構いません.

(プログラム例 7.10 ) 1 次元配列の受け渡し 

  関数 wa は,n 次元ベクトルの和を計算するためのものです.この例では,行列( 2 次元配列 a )の各行を 1 つのベクトルとみなし,それらの和を計算しています.&(a[0][0]),及び,&(a[1][0]) という記述は,配列変数 a の 1 行目,及び,2 行目の先頭アドレスを示しています.このような方法により,配列の一部を参照することも可能です.
/****************************/
/* 1次元配列の受け渡し     */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

void wa(int, int *, int *, int *);

int main()
{
	int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
	int i1, b[3];
/*
	 関数の呼び出し
*/
	wa(3, &(a[0][0]), &(a[1][0]), b);   /* 1行目と2行目のベクトル和の計算 */
/*
	 結果の出力
*/
	for (i1 = 0; i1 < 3; i1++)
		printf("%d ", b[i1]);
	printf("\n");

	return 0;
}
/***********************************/
/* n次元ベクトルの和              */
/*      n : 次元数                 */
/*      a,b : 和を計算するベクトル */
/*      c : 計算結果が入るベクトル */
/***********************************/
void wa(int n, int *a, int *b, int *c)
{
	int i1;

	for (i1 = 0; i1 < n; i1++)
		c[i1] = a[i1] + b[i1];
}
		

7.3.2.2 2 次元以上の配列

  2 次元の配列を受け渡す場合も,基本的には,1 次元の場合と同様です.しかし,受け渡される関数の側で,列の数を必ず記述しなければなりません.3 次元以上の場合も同様に,最初の添え字(次元)だけは省略できますが,後の次元はすべて記述してやる必要があります.例えば,上で述べた例において,配列 b が
	int b[4][10];
		
のように 2 次元配列として記述してあったとします.このとき,関数 sub 側では,
	int sub(・・・, int c[][10], ・・・)
		
または,
	int sub(・・・, int (*c)[10], ・・・)
		
と書く必要があります.2 番目の方法において,(*c)[10] を *c[10] と書かないように注意して下さい.後者は,10 個のポインタの配列を意味します.

  このように,多次元配列の場合,添え字の一部を必ず記述しなければならないため,もし,呼び出す側で配列のサイズを変更すると,呼び出される側の関数においても修正が必要になります.このようなことを避ける方法については,プログラム例 7.12 以降を参照して下さい.

(プログラム例 7.11 ) 2 次元配列の受け渡し(方法 1)

  関数 seki は,n 行 m 列の行列と m 行 l 列の行列の積を計算するためのものです.なお,行列 A ( n 行 m 列)と B ( m 行 l 列)の行列のかけ算の定義は以下の通りです.

  C = AB, cij = Σkaikbkj  k = 1, ・・・, m

ただし,cij 等は,行列 C 等の i 行 j 列要素とします.
/**********************************/
/* 2次元配列の受け渡し(方法1) */
/*      coded by Y.Suganuma       */
/**********************************/
#include <stdio.h>

void seki(int, int, int, int [][3], int [][2], int [][2]);

int main()
{
	int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
	int b[3][2] = {{1, 0}, {0, 1}, {0, 0}};
	int i1, i2, c[2][2];
/*
	 関数の呼び出し
*/
	seki(2, 3, 2, a, b, c);
/*
	 結果の出力
*/
	for (i1 = 0; i1 < 2; i1++) {
		for (i2 = 0; i2 < 2; i2++)
			printf("%d ", c[i1][i2]);
		printf("\n");
	}

	return 0;
}
/**************************************/
/* 行列の積                           */
/*      n,m,l : 次元数                */
/*      a : n x m                     */
/*      b : m x l                     */
/*      c : 計算結果が入る行列,n x l */
/**************************************/
void seki(int n, int m, int l, int a[][3], int b[][2], int c[][2])
{
	int i1, i2, i3;

	for (i1 = 0; i1 < n; i1++) {
		for (i2 = 0; i2 < l; i2++) {
			c[i1][i2] = 0;
			for (i3 = 0; i3 < m; i3++)
				c[i1][i2] += a[i1][i3] * b[i3][i2];
		}
	}
}
		

(プログラム例 7.12 ) 2 次元配列の受け渡し(方法 2) 

  new 演算子( malloc 等でも構いません)を使用して配列を定義し,先の例の関数をポインタを使用した記述になおしたものです.このようにすれば,配列の大きさが変化しても,次の例と同様,関数自身を修正する必要がありません.
/**********************************/
/* 2次元配列の受け渡し(方法2) */
/*      coded by Y.Suganuma       */
/**********************************/
#include <stdio.h>

void seki(int, int, int, int **, int **, int **);

int main()
{
	int i1, i2, **a, **b, **c;
/*
	 領域の確保と値の設定
*/
	a = new int * [2];
	b = new int * [3];
	c = new int * [2];

	for (i1 = 0; i1 < 2; i1++) {
		a[i1] = new int [3];
		c[i1] = new int [2];
		if (i1 == 0) {
			a[i1][0] = 1;
			a[i1][1] = 2;
			a[i1][2] = 3;
		}
		else {
			a[i1][0] = 4;
			a[i1][1] = 5;
			a[i1][2] = 6;
		}
	}

	for (i1 = 0; i1 < 3; i1++) {
		b[i1] = new int [2];
		if (i1 == 0) {
			b[i1][0] = 1;
			b[i1][1] = 0;
		}
		else {
			if (i1 == 1) {
				b[i1][0] = 0;
				b[i1][1] = 1;
			}
			else {
				b[i1][0] = 0;
				b[i1][1] = 0;
			}
		}
	}
/*
	 関数の呼び出し
*/
	seki(2, 3, 2, a, b, c);
/*
	 結果の出力
*/
	for (i1 = 0; i1 < 2; i1++) {
		for (i2 = 0; i2 < 2; i2++)
			printf("%d ", c[i1][i2]);
		printf("\n");
	}

	return 0;
}
/**************************************/
/* 行列の積                           */
/*      n,m,l : 次元数                */
/*      a : n x m                     */
/*      b : m x l                     */
/*      c : 計算結果が入る行列,n x l */
/**************************************/
void seki(int n, int m, int l, int **a, int **b, int **c)
{
	int i1, i2, i3;

	for (i1 = 0; i1 < n; i1++) {
		for (i2 = 0; i2 < l; i2++) {
			c[i1][i2] = 0;
			for (i3 = 0; i3 < m; i3++)
				c[i1][i2] += a[i1][i3] * b[i3][i2];
		}
	}
}
		

(プログラム例 7.13 ) 2 次元配列の受け渡し(方法 3)

  プログラム例 7.11 の関数 seki は,各行列の列の数が,プログラム内に記述してある値より小さいときは,main 関数を書き直すだけで使用できますが,それ以上の値の場合は,main 関数だけでなく,関数 seki も書き換える必要があります.

  しかし,プログラム例 7.10 の関数 wa は,1 次元配列を使用しているが故に,ベクトルの次元がどのように変化しても修正すること無しに使用可能です.そこで,6.3 節で説明したように,2 次元配列であっても連続した領域に確保されていますので,その並び方に注意しさえすれば,1 次元配列として取り扱うことができるという性質を利用して書き直したのが下のプログラムです.このプログラムの関数 seki は,任意の n,m,及び,l の値に対して修正無しで使用可能となっています.
/**********************************/
/* 2次元配列の受け渡し(方法3) */
/*      coded by Y.Suganuma       */
/**********************************/
#include <stdio.h>

void seki(int, int, int, int *, int *, int *);

int main()
{
	int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
	int b[3][2] = {{1, 0}, {0, 1}, {0, 0}};
	int i1, i2, c[2][2];
/*
	 関数の呼び出し
*/
	seki(2, 3, 2, &a[0][0], &b[0][0], &c[0][0]);
/*
	 結果の出力
*/
	for (i1 = 0; i1 < 2; i1++) {
		for (i2 = 0; i2 < 2; i2++)
			printf("%d ", c[i1][i2]);
		printf("\n");
	}

	return 0;
}
/**************************************/
/* 行列の積                           */
/*      n,m,l : 次元数                */
/*      a : n x m                     */
/*      b : m x l                     */
/*      c : 計算結果が入る行列,n x l */
/**************************************/
void seki(int n, int m, int l, int *a, int *b, int *c)
{
	int i1, i2, i3;

	for (i1 = 0; i1 < n; i1++) {
		for (i2 = 0; i2 < l; i2++) {
			c[l*i1+i2] = 0;
			for (i3 = 0; i3 < m; i3++)
				c[l*i1+i2] += a[m*i1+i3] * b[l*i3+i2];   /* 添え字に注意 */
		}
	}
}
		

(プログラム例 7.58 ) 2 次元配列の受け渡し(再帰呼び出し) 

  プログラム例 7.3 では,階乗の計算に再帰呼び出しを使用する例を示しましたが,再帰呼び出しの例としてはあまり良い例とはいえませんでした.ここでは,探索の問題に再帰呼び出しを使用してみます.この問題の場合は,再帰呼び出しを使用するのが最も簡単な方法だと思います.問題は,下の左側に示す 3 × 6 個の正方形のタイルから構成される図形の一部,又は,全部を,正方形のタイル 1 × 3 個に相当する長方形のタイル 2 枚,及び,1 × 2 個に相当する長方形のタイル 6 枚から選択したタイルで置き換えることによって得られる異なった図形の種類を計算することです.ただし,3 × 6 という全体の形は保持する必要があります.下の右側に示す図形は,すべてのタイルを,1 × 3 のタイル 2 枚と 1 × 2 のタイル 6 枚で置き換えた例です.

  この問題を解くためには,まず,1 × 3,及び,1 × 2 のタイルを何枚ずつ選択するかを決めなければなりません.その組み合わせは以下のようになります.

1 × 3 を 0 枚選択した場合 1 × 2 は,1 〜 6 枚の 6 通り
1 × 3 を 1 枚選択した場合 1 × 2 は,0 〜 6 枚の 7 通り
1 × 3 を 2 枚選択した場合 1 × 2 は,0 〜 6 枚の 7 通り

しかし,これだけではすべての組み合わせになりません.例えば,

1 × 3 を 0 枚選択し,1 × 2 を 1 枚選択

の場合においても,そのタイルをどこへ配置するのか,また,縦に置くのか横に置くのかによって異なるからです.また,

1 × 3 を 2 枚選択し,1 × 2 を 6 枚選択

の場合ですら,上の図に示した方法だけではありません.

 このプログラムにおいては,以下に示すような方針でプログラムを作成しています.まず,1 × 3 を 1 行 1 列に縦(又は,横)に置きます( 1 × 3 を使用しない場合は,1 × 3 に対応する部分を実行しない).次に,2 枚目の 1 × 3 をこれ以降の配置可能な位置に置きます(ここで,再帰呼び出しが使用されます).1 × 3 の配置を終了すると,1 × 2 を 1 行 1 列以降の配置可能な位置に順に配置していきます.すべてのタイルの配置が終了した時点で,図形の種類の数を 1 だけ増加させます.
#include <stdio.h>

#define row 3
#define col 6
#define n13 2
#define n12 6

unsigned int set(int, int, int, int, int, int, int, int, unsigned int, int a[][col]);

int main()
{
	int i1, i2, i3, i4, i5, i6, k, k3, k2, s, a[row][col];
	unsigned int n;

	for (i1 = 0; i1 <= n13; i1++) {
		if (i1 == 0) {
			k  = 1;
			k3 = 0;
			k2 = 1;
			s  = 1;
		}
		else {
			k  = 0;
			k3 = 1;
			k2 = 0;
			s  = 0;
		}
		for (i2 = k; i2 <= n12; i2++) {
			for (i3 = 0; i3 < row; i3++) {
				for (i4 = 0; i4 < col; i4++)
					a[i3][i4] = 0;
			}
			n = 0;
			for (i5 = 0; i5 < row; i5++) {
				for (i6 = 0; i6 < col; i6++) {
					n = set(i1, i2, k3, k2, s, 0, i5, i6, n, a);
					n = set(i1, i2, k3, k2, s, 1, i5, i6, n, a);
				}
			}
			printf(" %d (%d %d)\n", n, i1, i2);
		}
	}

	return 0;
}

/*******************************/
/* タイルの設定                */
/*      n3 : 1 x 3 の枚数      */
/*      n2 : 1 x 2 の枚数      */
/*      k3 : 1 x 3 の設定数    */
/*      k2 : 1 x 2 の設定数    */
/*      s : =0 : 1 x 3 の設定  */
/*          =1 : 1 x 2 の設定  */
/*      vh : =0 : 縦           */
/*           =1 : 横           */
/*      y,x : 設定位置(行と列) */
/*      m : 現時点の場合の数   */
/*      a : 設定状況           */
/*******************************/
unsigned int set(int n3, int n2, int k3, int k2, int s, int vh, int y, int x, unsigned int m, int a[][col])
{
	int i1, i2, k, kk, b[row][col], sw = 0;
	unsigned int n = m;
					// 初期設定
	for (i1 = 0; i1 < row; i1++) {
		for (i2 = 0; i2 < col; i2++)
			b[i1][i2] = a[i1][i2];
	}
					// タイルの設定
							// 1 x 3 の設定
	if (s == 0) {
		if (vh == 0) {   // 縦
			if (y < row-2) {
				if (b[y][x] == 0 && b[y+1][x] == 0 && b[y+2][x] == 0) {
					b[y][x]   = 1;
					b[y+1][x] = 1;
					b[y+2][x] = 1;
					sw        = 1;
				}
			}
		}
		else {   // 横
			if (x < col-2) {
				if (b[y][x] == 0 && b[y][x+1] == 0 && b[y][x+2] == 0) {
					b[y][x]   = 1;
					b[y][x+1] = 1;
					b[y][x+2] = 1;
					sw        = 1;
				}
			}
		}
	}
							// 1 x 2 の設定
	else {
		if (vh == 0) {   // 縦
			if (y < row-1) {
				if (b[y][x] == 0 && b[y+1][x] == 0) {
					b[y][x]   = 1;
					b[y+1][x] = 1;
					sw        = 1;
				}
			}
		}
		else {   // 横
			if (x < col-1) {
				if (b[y][x] == 0 && b[y][x+1] == 0) {
					b[y][x]   = 1;
					b[y][x+1] = 1;
					sw        = 1;
				}
			}
		}
	}
					// 設定できた場合
	if (sw > 0) {
							// すべてのタイル設定終了
		if (k3 == n3 && k2 == n2)
			n++;
							// 次のタイル
		else {
			k  = col * y + x + 1;
			kk = row * col - k;
			sw = 0;
			if (k3 < n3) {
				for (i1 = k; i1 < row * col; i1++) {
					if (b[i1/col][i1%col] > 0)
						kk--;
				}
				if (3*(n3-k3) > kk)
					sw = -1;
				else
					k3++;
			}
			else {
				if (s == 0) {
					sw = 1;
					s  = 1;
					k2++;
				}
				else {
					for (i1 = k; i1 < row * col; i1++) {
						if (b[i1/col][i1%col] > 0)
							kk--;
					}
					if (2*(n2-k2) > kk)
						sw = -1;
					else
						k2++;
				}
			}
			if (sw > 0) {
				for (i1 = 0; i1 < row; i1++) {
					for (i2 = 0; i2 < col; i2++) {
						n = set(n3, n2, k3, k2, s, 0, i1, i2, n, b);
						n = set(n3, n2, k3, k2, s, 1, i1, i2, n, b);
					}
				}
			}
			else if (sw == 0) {
				for (i1 = k; i1 < row * col; i1++) {
					n = set(n3, n2, k3, k2, s, 0, i1/col, i1%col, n, b);
					n = set(n3, n2, k3, k2, s, 1, i1/col, i1%col, n, b);
				}
			}
		}
	}

	return n;
}
		

7.3.3 関数名

  場合によっては,関数名を引数としたい場合があります.例えば,非線形方程式の根を求める関数を作成したいとします.また,根を求めるアルゴリズム自体は,方程式が異なっても変わらないとします.このようなとき,方程式の計算を別の関数で行い,その関数名を根を求める関数に受け渡すように作成すれば,方程式が変わっても,同じ根を求める関数を利用できます.

  関数名を引数とするには,基本的に,関数のアドレスを使用すれば可能です.例えば,
	int (*sub)(double, char *)
		
という記述は,sub が,double 及び char に対するポインタという 2 つの引数をもち,int を返す関数へのアドレスであることを表しています.

(プログラム例 7.14 ) 関数名の受け渡し(ニュートン法) 

  次のプログラムの関数 newton は,ニュートン法により非線形方程式 f(x) = 0 (この例では,ex - 3x = 0 )の解を求めるためのものです.f(x) 及び f(x) の微分を計算する関数名( snx と dsnx )を引数としています.ただし,ニュートン法とは,関数 f(x) が単調連続で変曲点が無く,かつ,微分可能であるとき利用できる方法であり,根の適当な初期値 x0 から始めて,反復公式
	xn+1 = xn - f(xn) / f'(xn)
		
を繰り返すことによって,非線形方程式の解を求める方法です.
/****************************/
/* 関数名の受け渡し         */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

double newton(double(*)(double), double(*)(double), double, double,
			  double, int, int *);
double snx(double);
double dsnx(double);

int main()
{
	double eps1, eps2, x, x0;
	int max, ind;

	eps1 = 1.0e-7;
	eps2 = 1.0e-10;
	max  = 20;
	x0   = 0.0;

	x = newton(snx, dsnx, x0, eps1, eps2, max, &ind);

	printf("ind=%d  x=%f  f=%f  df=%f\n",ind,x,snx(x),dsnx(x));

	return 0;
}

/*****************************************************/
/* Newton法による非線形方程式(f(x)=0)の解            */
/*      fn : f(x)を計算する関数名                    */
/*      dfn : f(x)の微分を計算する関数名             */
/*      x0 : 初期値                                  */
/*      eps1 : 終了条件1(|x(k+1)-x(k)|<eps1)   */
/*      eps2 : 終了条件2(|f(x(k))|<eps2)       */
/*      max : 最大試行回数                           */
/*      ind : 実際の試行回数                         */
/*            (負の時は解を得ることができなかった) */
/*      return : 解                                  */
/*****************************************************/
#include <math.h>

double newton(double(*f)(double), double(*df)(double), double x0,
			  double eps1, double eps2, int max, int *ind)
{
	double g, dg, x, x1;
	int sw;

	x1   = x0;
	x    = x1;
	*ind = 0;
	sw   = 0;

	while (sw == 0 && *ind >= 0) {

		sw    = 1;
		*ind += 1;
		g     = (*f)(x1);

		if (fabs(g) > eps2) {
			if (*ind <= max) {
				dg = (*df)(x1);
				if (fabs(dg) > eps2) {
					x = x1 - g / dg;
					if (fabs(x-x1) > eps1 && fabs(x-x1) > eps1*fabs(x)) {
						x1 = x;
						sw = 0;
					}
				}
				else
					*ind = -1;
			}
			else
				*ind = -1;
		}
	}

	return x;
}

/************************/
/* 関数値(f(x))の計算 */
/************************/
double snx(double x)
{
	double y;
	y = exp(x) - 3.0 * x;
	return y;
}

/********************/
/* 関数の微分の計算 */
/********************/
double dsnx(double x)
{
	double y;
	y = exp(x) - 3.0;
	return y;
}
		

(プログラム例 7.15 ) 関数名の配列 

  次のプログラムでは,関数名(関数に対するアドレス)を配列に入れ,条件によって異なる関数を呼び出しています.

/****************************/
/* 関数名の配列             */
/*      coded by y.suganuma */
/****************************/
#include <stdio.h>
/*
	 FPは2つのintの引数をもらいintを帰す関数へのポインタであると宣言
*/
typedef int (*FP) (int, int);
/*
	 4つの関数を宣言
*/
int add(int, int);
int sub(int, int);
int mul(int, int);
int div(int, int);

int main()
{
	int i1, x = 12;
	FP f_tb[] = {&add, &sub, &mul, &div};   /* 関数へのポインタ配列 */

	for (i1 = 0; i1 < 4; i1++)
		printf("result %d\n", f_tb[i1](x, i1+1));

	return 0;
}

/********/
/* 加算 */
/********/
int add(int x, int y)
{
	return (x + y);
}

/********/
/* 減算 */
/********/
int sub(int x, int y)
{
	return (x - y);
}

/********/
/* 乗算 */
/********/
int mul(int x, int y)
{
	return (x * y);
}

/********/
/* 除算 */
/********/
int div(int x, int y)
{
	return (x / y);
}
		

  上のプログラムを実行すると下のような結果が得られます.
	result 13
	result 10
	result 36
	result 3
		

7.3.4 参照渡し(C++)

  参照型変数の宣言方法は以下の通りです.
	データ型 &別名 = 式;
		
例えば,
	int &y = x;
		
と宣言することにより,参照型変数 y は変数 x の別名としてふるまい,
	x = 10;
	y = 10;
		
の 2 つの文は全く同じ意味になります.

  関数への引数を参照型(参照渡しcall by reference )にすることが可能です.この機能を利用することにより,呼ばれた側の関数で,呼んだ側と同様の記述が可能になります.また,定数や式を参照することも可能です.C++ コンパイラは,一時変数に定数や式の値をおさめ,この一時変数を参照の実体とします.この機能により,参照型の引数の位置に定数や式を書いても,関数側で特別な処理をしなくても良いわけです.さらに,参照を返す関数を作ることも可能です.詳しくは,以下に述べるプログラム例を見て下さい.

  単純変数( int や double 等)に対して,値渡しの替わりに参照渡しを利用することにはそれほどのメリットを感じませんが,後の述べる構造体やクラスのオブジェクトを引数として渡したいときには意味を持ってきます.非常に大きな構造体やクラスオブジェクトに対して値渡しをすれば,そのコピーを作成するために大きな領域を必要とすると共に,クラスオブジェクトの場合にはコンストラクタやデストラクタが呼ばれます.参照渡しによって,これらのオーバーヘッドを避けることが可能になります.もちろん,ポインタで渡すことも可能ですが,変数や関数を参照するための記述が多少見にくくなります.

  参照渡しをすれば,関数内で値の修正が可能となりますが,もし修正を許さないならば,その引数に対して,
	const
		
の指定をしておくべきです.

  Java においては,参照型変数のような宣言方法はありません.しかし,関数の引数において,int や double のような基本的なデータ型以外はすべてそのアドレスが渡されていると考えた方が良いと思います.ただし,関数内での表現方法に差が無いため,参照渡しのようにも見えます.いずれにしろ,関数内でその値を変更することが可能です.

(プログラム例 7.16 ) 参照渡し

/****************************/
/* 参照渡し                 */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

void sub(int &, int, int *, char *&, char *);

int main()
{
	int x = 10, y = 20, z = 0;
	char *data1 = "data1", *data2 = "data2";

	printf("x %d y %d z %d data1 %s data2 %s\n", x, y, z, data1, data2);

	sub(x, y, &z, data1, data2);

	printf("x %d y %d z %d data1 %s data2 %s\n", x, y, z, data1, data2);

	return 0;
}

/****************************/
/* 参照渡しの例             */
/*      a : 参照渡し        */
/*      b : データ渡し      */
/*      c : アドレス渡し    */
/*      c1 : 配列の参照渡し */
/*      c2 : 通常の配列渡し */
/****************************/
void sub(int &a, int b, int *c, char *&c1, char *c2)
{
	a += 5;
	b += 5;
	c1++;
	c2++;
	*c = a + b;

	printf("a %d b %d c %d c1 %s c2 %s\n", a, b, *c, c1, c2);
}
		

  上のプログラムを実行すると下のような結果が得られます.この結果からも明らかなように,通常の引き渡し( y と data2 )では,値がコピーされて渡されるだけ(配列の場合は,アドレスのコピー)ですので渡された内容自身を呼ばれた関数側で変えることができません.しかし,参照渡しですと,別名ですが,変数それ自身と同じものです.従って,関数を呼んだ側と同じ処理で,その内容も変更されます.データでなくアドレスで渡した変数 z (関数側では c )との記述方法の違いにも注意して下さい.
	x 10 y 20 z 0 data1 data1 data2 data2
	a 15 b 25 c 40 c1 ata1 c2 ata2
	x 15 y 20 z 40 data1 ata1 data2 data2
		
(プログラム例 7.17 ) 参照渡し(参照型関数)

  以下では,参照を返す関数を使用しています.この場合,関数自身が return される変数の別名になっています.従って,関数を代入演算子の右辺にも左辺にも書くことができます.

/****************************/
/* 参照渡し(参照型関数)   */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int x = 10;

int &sub(int);

int main()
{
	int y = 20;

	printf("x %d y %d\n", x, y);
/*
	 現在の変数xの値の参照(式を右辺)
*/
	y = sub(5);
	printf("x %d y %d\n", x, y);
/*
	 変数xに結果を代入(式を右辺)
*/
	sub(100) += 3;
	printf("x %d y %d\n", x, y);

	return 0;
}

/*********************/
/* 関数が変数xの別名 */
/*********************/
int &sub(int a)
{
	x += a;

	return x;
}
		

  このプログラムを実行した結果は,以下のようになります.
    x 10 y 20
    x 15 y 15
    x 118 y 15
		

7.4 main 関数

  main も一種の関数です.今までのプログラムでは,
	int main()
		
と記述していたため,引数を持ちませんでした.しかし,例えば,
	add 2 3
		
とキーボードから入力すると,2 つの値 2 と 3 の和を計算し,その結果をディスプレイに出力したいような場合が存在します.このような場合,2 や 3 が main 関数(メインプログラム主プログラムmain program )の引数とみなされます.ただし,main 関数の場合は,引数の受け渡し方が以下のように決まっています.
	int main ( int argc, char *argv[], char *envp[] )
		
argc : コマンドラインからプログラムへ渡された引数の数を指定する整数.プログラム名も引数と見なされるので, argc は 1 以上の値となります.「 add 2 3 」のような場合は,3 となります.

argv : null 文字で終わる文字列の配列. char へのポインタの配列 ( char *argv[] ),または,char へのポインタへのポインタ ( char **argv ) として宣言できます. 最初の文字列 ( argv[0] ) はプログラム名で,その後に続く各文字列はコマンドラインからプログラムへ渡される引数(文字列)です. 最後のポインタ( argv[argc] )は NULL です.

envp : 環境文字列の配列へのポインタ. char へのポインタの配列 ( char *envp[] ),または,char へのポインタへのポインタ ( char **envp ) として宣言できます. 配列の最後は NULL ポインタで示します.

(プログラム例 7.18 ) main 関数の引数(数字の和) 

  次の例は,複数個の整数を加えるプログラムです.なお,関数 atoi は,文字列を整数に変換する関数です.

/******************************/
/* main関数の引数(数字の和) */
/*      coded by Y.Suganuma   */
/******************************/
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[], char *envp[])
{
	int i1, k, sum = 0;
/*
	 引数の内容の出力
*/
	printf("	 引数の数 %d\n",argc);
	printf("	 プログラム名 %s\n",argv[0]);

	for (i1 = 1; i1 < argc; i1++) {
		k = atoi(argv[i1]);				  /* 文字を整数に変換 */
		printf("     %d 番目の引数 %d\n", i1+1, k);
		sum += k;
	}
/*
	 結果の表示
*/
	printf("結果=%d\n", sum);

	return 0;
}
		

  例えば,「add 2 3」と入力すると,以下のような出力が得られます.
	     引数の数 3
	     プログラム名 b:/temp/add.exe
	     1番目の引数 2
	     2番目の引数 3
	結果=5
		

(プログラム例 7.19 ) main 関数の引数(環境変数の出力)

  次は,定義されている環境変数を出力するプログラムです.
/*******************************/
/* main関数(環境変数の出力)  */
/*      coded by Y.Suganuma    */
/*******************************/
#include <stdio.h>

int main(int argc, char *argv[], char *envp[])
{
	int i1 = 0;
	while (envp[i1] != NULL) {
		printf("%d %s\n", i1+1, envp[i1]);
		i1++;
	}

	return 0;
}
		

7.5 その他(C++)

7.5.1 関数名のオーバーロード

  C++ においては,2 つ以上の関数に同じ名前を付けることができます.それを,関数名のオーバーロード多重定義function name overloaded )と呼びます.この機能はどのようなことに利用できるでしょうか.

  例えば,与えられたデータを出力する関数を printf 関数を使用して作成したいとします.printf 関数は,出力するデータの型が異なると,それに対応して % に続く文字も変えてやらなければなりませんので,データ毎に呼び出す関数名を変更しなければなりません.しかし,関数名のオーバーロード機能を使用すれば,同じ関数を呼び出すことによって処理できます.コンパイラが,与えられたデータの型によって適当な関数を選んでくれるからです.

  次のプログラム例は,関数名のオーバーロード機能を使用して,上のことを実現しています.

(プログラム例 7.20 ) 関数名のオーバーロード 
/****************************/
/* 関数名のオーバーロード   */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

/****************/
/* 文字列の出力 */
/****************/
void print(char *moji)
{
	printf("%s\n", moji);
}

/*****************/
/* double の出力 */
/*****************/
void print(double dbd)
{
	printf("%f\n", dbd);
}

/**************/
/* int の出力 */
/**************/
void print(int ind)
{
	printf("%d\n", ind);
}

/********/
/* main */
/********/
int main()
{
	print("moji-retu");   // 以下,データの型に対応した関数が選択される
	print(3.14);
	print(100);

	return 0;
}
		

7.5.2 インライン関数(Javaを除く)

  インライン関数( inline function )とは,関数の本体をコンパイル時にプログラム内にインライン展開してしまう関数です.インライン関数を定義するには,関数定義の際に,例えば,
	inline int sub(int x, ・・・)
		
のように記述すれば可能です.インライン展開されるので,関数を呼び出すオーバーヘッドが無くなり,実行速度が速くなります.ただし,プログラムサイズは,当然,大きくなります.

  機能としては,プリプロセッサの #define 疑似命令でマクロを定義するのとほとんど同じです.#define マクロの場合は,型のチェックや副作用等についてユーザーが注意しなければなりませんが,インライン関数の場合は,普通の関数と同じ感覚で書き,また,使用することが可能です.

(プログラム例 7.21 ) インライン関数と #define マクロ

  次のプログラムは,「 sqrt( x * x + y * y ) 」の計算を,#define マクロ,インライン関数,及び,普通の関数で 1000000 回計算し CPU 時間を比較しています.ただし,コンパイラ等の処理の方法によって CPU 時間は異なると思いますので,結果の値にはあまりこだわらないで下さい.

/*********************************/
/* インライン関数と#defineマクロ */
/*      coded by Y.Suganuma      */
/*********************************/
#include <stdio.h>
#include <math.h>
#include <time.h>

#define ookisa(x, y) sqrt(x * x + y * y)   /* マクロによる計算 */

inline double length1(double, double);
double length2(double, double);

int main()
{
	double a = 3.0;
	double b = 4.0;
	double x;
	long i1;
	clock_t c1, c2;
/*
	 マクロによる計算
*/
	c1 = clock();
	for (i1 = 0; i1 < 1000000; i1++)
		x = ookisa(a, b);
	c2 = clock();

	printf("計算時間は %f 秒です(マクロ)\n", (double)(c2-c1)/CLOCKS_PER_SEC);
/*
	 インライン関数
*/
	c1 = c2;
	for (i1 = 0; i1 < 1000000; i1++)
		x = length1(a, b);
	c2 = clock();
	printf("計算時間は %f 秒です(インライン関数)\n", (double)(c2-c1)/CLOCKS_PER_SEC);
/*
	 普通の関数
*/
	c1 = c2;
	for (i1 = 0; i1 < 1000000; i1++)
		x = length2(a, b);
	c2 = clock();
	printf("計算時間は %f 秒です(普通の関数)\n", (double)(c2-c1)/CLOCKS_PER_SEC);

	return 0;
}
/***************************/
/* sqrt(x*x+y*y)(inline) */
/*     x,y : 2つのデータ  */
/*     return : 結果       */
/***************************/
inline double length1(double x, double y)
{
	return sqrt(x * x + y * y);
}
/*******************************/
/* sqrt(x*x+y*y)(普通の関数) */
/*     x,y : 2つのデータ      */
/*     return : 結果           */
/*******************************/
double length2(double x, double y)
{
	return sqrt(x * x + y * y);
}
		

  上のプログラムを実行すると下のような結果が得られます.
	計算時間は 35 秒です(マクロ)
	計算時間は 39 秒です(インライン関数)
	計算時間は 40 秒です(普通の関数)
		

7.5.3 例外処理

  C++ には,プログラム実行中に起こったエラーを見知して,ユーザ固有の処理を行うための方法があります.それが,例外処理(exception)です.

  まず,try ブロックはブロック内で発生する例外を捕まえます.つまり,try ブロックには,例外が発生する可能性のある処理を書きます.例外が発生すると,throw 文によって例外が try ブロックに渡され,try ブロックの後ろにある throw された型と同じ型を受け取る catch ブロックで処理されます( catch ブロックは複数書くことができます).詳しくは,次のプログラム例を見て下さい.

(プログラム例 7.22 ) 例外処理

/****************************/
/* 例外処理                 */
/*      coded by Y.Suganuma */
/****************************/
#include <iostream>
#include <math.h>
using namespace std;

void sq(double x, double y)
{
	if (x < 0.0 && y < 0.0) {
		char *str = "両方とも負\n";
		throw str;
	}
	else if (x < 0.0 || y < 0.0) {
		char *str = "片方が負\n";
		throw str;
	}

	double z = sqrt(x+y);
	cout << z << endl;
}

int main()
{
	try {
		double x, y;
		cout << "1 つ目のデータは? ";
		cin >> x;
		cout << "2 つ目のデータは? ";
		cin >> y;
		sq(x, y);
	}

	catch (char *str)
	{
		cout << str;
	}

	return 0;
}
		

  このプログラムに,例えば,-1 と -2 を入力し実行すると,以下のような結果が出力されます.
	1.73205
	両方とも負
		
  上の例では,char * 型を送出しましたが,次の例のように,クラス(クラスに関しては,第 10 章以降を参照してください)のオブジェクトを送出することも可能です.なお,あまり意味はありませんが,1 番目の引数だけが負の場合は,その値を正に修正して再実行しています.

/****************************/
/* 例外処理                 */
/*      coded by Y.Suganuma */
/****************************/
#include <iostream>
#include <math.h>
#include <string.h>
using namespace std;

class Negative {
	public:
		char *str;
		double x, y;
		Negative(char *str, double x, double y) {
			this->str = new char [100];
			strcpy(this->str, str);
			this->x = x;
			this->y = y;
		}
		void message(int sw) {
			if (sw == 0)
				cout << "    1 番目の値を正にして再実行しました\n";
			else
				cout << "    データを修正してください\n";
		}
};

void sq(double x, double y)
{
	if (x < 0.0 && y < 0.0)
		throw Negative("両方とも負\n", x, y);
	else if (x < 0.0 || y < 0.0)
		throw Negative("片方が負\n", x, y);

	double z = sqrt(x+y);
	cout << z << endl;
}

int main()
{
	try {
		double x, y;
		cout << "1 つ目のデータは? ";
		cin >> x;
		cout << "2 つ目のデータは? ";
		cin >> y;
		sq(x, y);
	}

	catch (Negative &ng)
	{
		cout << ng.str;
		if (ng.y > 0.0) {
			ng.message(0);
			sq(-ng.x, ng.y);
		}
		else
			ng.message(1);
	}

	return 0;
}
		

  このプログラムに,例えば,-1 と 2 を入力し実行すると,以下のような結果が出力されます.
1.73205
片方が負
    1 番目の値を正にして再実行しました
1.73205
		

7.6 様々な例題

  この節では,主として科学技術計算に使用される様々なプログラム例を与えます.可能な限り,各関数(クラス)は一般的に使用できるように書いたつもりです.基本的に,C/C++ によるプログラムは C の範囲で記述しますが,先に述べたように,メモリの動的確保が必要な場合 malloc 関数の替わりに new 演算子を使用したり,注釈に「//」を使用して書きます.なお,プログラムの具体的使用法に関しては,各手法の説明の箇所も参照してください.

7.6.1 数値計算

(プログラム例 7.23 ) 連立線形方程式,逆行列(ガウス・ジョルダン) 

  添付したプログラムは,連立線形方程式の解(逆行列)をガウスの消去法によって求めるプログラムです.アプレット版及び JavaScript 版では,任意のデータに対して画面上で結果を得ることができます.なお,アプレット版及び JavaScript 版では,連立方程式の解,逆行列,行列の乗算,及び,行列式の値を計算することが可能になっています.

(プログラム例 7.28 ) 非線形方程式(二分法) 

  添付したプログラムは,f(x) = exp(x) - 3x = 0 の根を二分法で求めた例です.JavaScript 版では,JavaScript の仕様に適合した形で解を求めたい式を入力することによって,任意の非線形方程式の解を画面上で求めることができます.

(プログラム例 7.29 ) 非線形方程式(セカント法) 

  添付したプログラムは,f(x) = exp(x) - 3x = 0 の根をセカント法で求めた例です.JavaScript 版では,JavaScript の仕様に適合した形で解を求めたい式を入力することによって,任意の非線形方程式の解を画面上で求めることができます.

(プログラム例 7.63 ) 非線形方程式(ニュートン法) , (多次元)

  添付したプログラムは,f(x) = exp(x) - 3x = 0 の根をニュートン法で求めた例です.多次元の場合に対するプログラムは,3 点 (0.5,1.0),(0.0,1.5),(0.5,2.0) を通る円の中心座標と半径を多次元のニュートン法で求めた例です.JavaScript 版(多次元の場合に対する JavaScript 版)では,JavaScript の仕様に適合した形で解を求めたい式を入力することによって,任意の非線形方程式の解を画面上で求めることができます.

(プログラム例 7.40 ) 代数方程式(ベアストウ) 

  添付したプログラムは,実係数代数方程式の解を,ベアストウ法で解くためのものです.アプレット版及び JavaScript 版では,任意のデータに対して画面上で解を得ることができます.

(プログラム例 7.43 ) 行列の固有値(フレーム法+ベアストウ法) 

  添付したプログラムは,行列の固有値をフレーム法とベアストウ法によって求めるためのものです.アプレット版及び JavaScript 版では,任意のデータに対して画面上で解を得ることができます.

(プログラム例 7.44 ) 実対称行列の固有値・固有ベクトル(ヤコビ法) 

  添付したプログラムは,実対称行列の固有値及び固有ベクトルを,ヤコビ法で求めるためのものです.アプレット版及び JavaScript 版では,任意のデータに対して画面上で解を得ることができます.各固有値に対応する固有ベクトルは,各列に表示されます.

(プログラム例 7.45 ) 最大固有値と固有ベクトル(べき乗法) 

  添付したプログラムは,行列の固有値と固有ベクトルを,固有値の絶対値が最大のものから順に求めていく方法(べき乗法)です.アプレット版及び JavaScript 版では,任意のデータに対して画面上で解を得ることができます.

(プログラム例 7.25 ) 数値積分(台形則) 

  添付したプログラムは,台形則により sin(x) を 0 から π/2 までの積分するプログラム例です.シンプソン則による方法と比較してみてください.JavaScript 版では,JavaScript の仕様に適合した形で積分したい式を入力することによって,任意の関数の積分を画面上で求めることができます.

(プログラム例 7.26 ) 数値積分(シンプソン則) 

  添付したプログラムは,シンプソン則により sin(x) を 0 から π/2 までの積分するプログラム例です.JavaScript 版では,JavaScript の仕様に適合した形で積分したい式を入力することによって,任意の関数の積分を画面上で求めることができます.

(プログラム例 7.27 ) 微分方程式(ルンゲ・クッタ) 

  添付したプログラムは,以下の微分方程式をルンゲ・クッタ法によって,0 から 1 秒まで解いた例です.JavaScript 版では,JavaScript の仕様に適合した形で微分方程式を入力することによって,任意の微分方程式の解を画面上で求めることができます.

  d2y/dt2 + 3dy/dt + 2y = 1  初期条件はすべて0
  (x[0] = y, x[1] = dy/dt)

(プログラム例 7.41 ) 補間法(ラグランジュ) 

  添付したプログラムは,ラグランジュ補間法のプログラムです.アプレット版及び JavaScript 版では,n 次補間多項式による計算結果を画面上で求めることができます.

(プログラム例 7.42 ) 補間法(スプライン) 

  添付したプログラムは,3次スプライン関数によってスプライン補間するためのものです.アプレット版及び JavaScript 版では,任意のデータに対して,スプライン補間法による計算結果を画面上で求めることができます.

(プログラム例 7.46 ) 補間法(ベジエ曲線) 

  添付したプログラムは,ベジエ多角形を B0 = (1 1),B1 = (2 3),B2 = (4 3),B3 = (3 1) としたとき,対応するベジエ曲線を描くためのものです.アプレット版及び JavaScript 版では,任意のデータに対して,ベジエ曲線上の座標を画面上に出力することができます.

7.6.2 最適化

(プログラム例 7.61 ) 最適化(線形計画法) 

  添付したプログラムは,線形計画法に対するプログラム例です(この例に限って,クラスを使用して記述してあります).たとえば,
目的関数,
	z = c1x1 + c2x2 + ・・・ + cmxm
を,制約条件,
	a11x1 + a12x2 + ・・・ + a1mxm ≦ b1
	a21x1 + a22x2 + ・・・ + a2mxm ≦ b2
		・・・・・
	an1x1 + an2x2 + ・・・ + anmxm ≦ bn
のもとで,最大にする.
		
のような問題は,以下に示すようなデータを含むファイルを作成し,入力に対するリダイレクト機能を使用すれば実行できます(コメント部分は除く).なお,制約条件式において,不等号が逆(≧)の場合は「 > 」,また,等号の場合は「 = 」を使用して下さい.さらに,各変数の値はすべて 0 以上という条件は,自動的に付加されます.
m n   // 変数の数と制約条件の数
c1 c2 ・・・ cm   // 目的関数の係数
a11 a12 ・・・ a1m < b1   // 制約条件式(以下,同様)
a21 a22 ・・・ a2m < b2
	・・・・・
an1 an2 ・・・ anm < bn
		
  このプログラムを実行すると,最適解とともに,各ステップにおける単体表も出力させることが可能です(関数 optimize の引数を 1 とした場合).たとえば,単体表が以下のようになる場合は,その 2 行目以降が表と同じ順番で出力されます.

基底変数 基底可能解 x1 x2 x3 x4 x5
x3 9 3 1 1 0 0
x4 12.5 2.5 2 0 1 0
x5 8 1 2 0 0 1
z 0 -3 -2 0 0 0

(プログラム例 7.30 ) 最適化(黄金分割法) 

  添付したプログラムは,f(x) = x4 + 3x3 + 2x2 + 1 の最小値を黄金分割法で求めた例です.JavaScript 版では,JavaScript の仕様に適合した形で最小値を求めたい式を入力することによって,任意の関数の最小値を画面上で求めることができます.

(プログラム例 7.60 ) 最適化(多項式近似法) 

  添付したプログラムは,f(x) = x4 + 3x3 + 2x2 + 1 の最小値を多項式近似法で求めた例です.JavaScript 版では,JavaScript の仕様に適合した形で最小値を求めたい式を入力することによって,任意の関数の最小値を画面上で求めることができます.

(プログラム例 7.31 ) 最適化(最急降下法) 

  添付したプログラムは,最急降下法を使用して,非線形関数の最小値を求めるためのものです( C/C++ 及び Java によるプログラムの使用方法).JavaScript 版では,JavaScript の仕様に適合した形で最小値を求めたい式を入力することによって,任意の関数の最小値を画面上で求めることができます.JavaScript 版に表示されている例は,(1,1) で最小値 0.0 をとる関数 f = 100(y - x2)2 + (1 - x)2 に対するデータです.

(プログラム例 7.47 ) 最適化(共役勾配法) 

  添付したプログラムは,共役勾配法を使用して,非線形関数の最小値を求めるためのものです( C/C++ 及び Java によるプログラムの使用方法).JavaScript 版では,JavaScript の仕様に適合した形で最小値を求めたい関数を入力することによって,任意の関数の最小値を画面上で求めることができます.JavaScript 版に表示されている例は,(1,1) で最小値 0.0 をとる関数 f = 100(y - x2)2 + (1 - x)2 に対するデータです(一次元最適化を行わない場合は,刻み幅を 0.003 にしてみて下さい).

(プログラム例 7.48 ) 最適化(Newton 法) 

  添付したプログラムは,Newton 法を使用して,非線形関数の最小値を求めるためのものです( C/C++ 及び Java によるプログラムの使用方法).JavaScript 版では,JavaScript の仕様に適合した形で最小値を求めたい関数を入力することによって,任意の関数の最小値を画面上で求めることができます.JavaScript 版に表示されている例は,(1,1) で最小値 0.0 をとる関数 f = 100(y - x2)2 + (1 - x)2 に対するデータです.

(プログラム例 7.49 ) 最適化(準 Newton 法) 

  添付したプログラムは,準 Newton 法を使用して,非線形関数の最小値を求めるためのものです( C/C++ 及び Java によるプログラムの使用方法).JavaScript 版では,JavaScript の仕様に適合した形で最小値を求めたい関数を入力することによって,任意の関数の最小値を画面上で求めることができます.JavaScript 版に表示されている例は,(1,1) で最小値 0.0 をとる関数 f = 100(y - x2)2 + (1 - x)2 に対するデータです( BFGS 法において,一次元最適化を行わない場合は刻み幅を 0.02,行う場合は 0.002 にしてみて下さい).

(プログラム例 7.59 ) 最適化(シンプレックス法) 

  添付したプログラムは,シンプレックス法を使用して,非線形関数の最小値を求めるためのものです( C/C++ 及び Java によるプログラムの使用方法).JavaScript 版では,JavaScript の仕様に適合した形で最小値を求めたい関数を入力することによって,任意の関数の最小値を画面上で求めることができます.JavaScript 版に表示されている例は,(1,1) で最小値 0.0 をとる関数 f = 100(y - x2)2 + (1 - x)2 に対するデータです.

(プログラム例 7.50 ) 最適化(動的計画法) 

  添付したプログラムは,動的計画法を使用して,資源配分問題を解くためのものです.

7.6.3 確率と統計

(プログラム例 7.32 ) ガンマ関数 

  添付したプログラムは,ガンマ関数の値を計算するプログラムです.アプレット版及び JavaScript 版では,任意のデータに対するガンマ関数の値を画面上で求めることができます.

(プログラム例 7.64 ) 二項分布 

  添付したプログラムは,グラフ出力を指定すると二項分布の密度関数および分布関数の値を指定した範囲だけファイルに出力します.また,グラフ出力を指定しないと,指定された値における密度関数および分布関数の値を出力するプログラムです.Java 版ではグラフも表示されます.アプレット版及び JavaScript 版では,同様の処理を画面上で実行することが可能であり,結果はテキストエリアに出力されると共に,「確率(複数点)」を選択すればグラフも表示されます.なお,グラフに関しては,20.5.2 グラフの表示を参照して下さい.

(プログラム例 7.65 ) ポアソン分布 

  添付したプログラムは,グラフ出力を指定するとポアソン分布の密度関数および分布関数の値を指定した範囲だけファイルに出力します.また,グラフ出力を指定しないと,指定された値における密度関数および分布関数の値を出力するプログラムです.Java 版ではグラフも表示されます.アプレット版及び JavaScript 版では,同様の処理を画面上で実行することが可能であり,結果はテキストエリアに出力されると共に,「確率(複数点)」を選択すればグラフも表示されます.なお,グラフに関しては,20.5.2 グラフの表示を参照して下さい.

(プログラム例 7.66 ) 一様分布 

  添付したプログラムは,グラフ出力を指定すると一様分布の密度関数および分布関数の値を指定した範囲だけファイルに出力します.また,グラフ出力を指定しないと,指定された値における密度関数および分布関数の値,または,%値を出力するプログラムです.Java 版ではグラフも表示されます.アプレット版及び JavaScript 版では,同様の処理を画面上で実行することが可能であり,結果はテキストエリアに出力されると共に,「確率(複数点)」を選択すればグラフも表示されます.なお,グラフに関しては,20.5.2 グラフの表示を参照して下さい.

(プログラム例 7.67 ) 指数分布 

  添付したプログラムは,グラフ出力を指定すると指数分布の密度関数および分布関数の値を指定した範囲だけファイルに出力します.また,グラフ出力を指定しないと,指定された値における密度関数および分布関数の値,または,%値を出力するプログラムです.Java 版ではグラフも表示されます.アプレット版及び JavaScript 版では,同様の処理を画面上で実行することが可能であり,結果はテキストエリアに出力されると共に,「確率(複数点)」を選択すればグラフも表示されます.なお,グラフに関しては,20.5.2 グラフの表示を参照して下さい.

(プログラム例 7.33 ) 正規分布 

  添付したプログラムは,グラフ出力を指定すると正規分布の密度関数および分布関数の値を指定した範囲だけファイルに出力します.また,グラフ出力を指定しないと,指定された値における密度関数および分布関数の値,または,%値を出力するプログラムです.Java 版ではグラフも表示されます.アプレット版及び JavaScript 版では,同様の処理を画面上で実行することが可能であり,結果はテキストエリアに出力されると共に,「確率(複数点)」を選択すればグラフも表示されます.なお,グラフに関しては,20.5.2 グラフの表示を参照して下さい.

(プログラム例 7.34 ) χ2 分布 

  添付したプログラムは,グラフ出力を指定すると χ2 分布の密度関数および分布関数の値を指定した範囲だけファイルに出力します.また,グラフ出力を指定しないと,指定された値における密度関数および分布関数の値,または,%値を出力するプログラムです.Java 版ではグラフも表示されます.アプレット版及び JavaScript 版では,同様の処理を画面上で実行することが可能であり,結果はテキストエリアに出力されると共に,「確率(複数点)」を選択すればグラフも表示されます.なお,グラフに関しては,20.5.2 グラフの表示を参照して下さい.

(プログラム例 7.35 ) t 分布 

  添付したプログラムは,グラフ出力を指定すると t 分布の密度関数および分布関数の値を指定した範囲だけファイルに出力します.また,グラフ出力を指定しないと,指定された値における密度関数および分布関数の値,または,%値を出力するプログラムです.Java 版ではグラフも表示されます.アプレット版及び JavaScript 版では,同様の処理を画面上で実行することが可能であり,結果はテキストエリアに出力されると共に,「確率(複数点)」を選択すればグラフも表示されます.なお,グラフに関しては,20.5.2 グラフの表示を参照して下さい.

(プログラム例 7.36 ) F 分布 

  添付したプログラムは,グラフ出力を指定すると F 分布の密度関数および分布関数の値を指定した範囲だけファイルに出力します.また,グラフ出力を指定しないと,指定された値における密度関数および分布関数の値,または,%値を出力するプログラムです.Java 版ではグラフも表示されます.アプレット版及び JavaScript 版では,同様の処理を画面上で実行することが可能であり,結果はテキストエリアに出力されると共に,「確率(複数点)」を選択すればグラフも表示されます.なお,グラフに関しては,20.5.2 グラフの表示を参照して下さい.

(プログラム例7.57) Fisher の直接確率 

  添付したプログラムは,Fisher の直接確率を計算する例です.なお,Java に対してはアプレットを使って計算していますので,表示して実行可能です.

(プログラム例7.37) 乱数の発生 

  添付したプログラムは,一様乱数,指数乱数,および,正規乱数の発生方法の例です.

7.6.4 多変量解析

(プログラム例 7.24 ) 最小二乗法(多項式近似) 

  添付したプログラムは,最小二乗法(多項式近似)を実行するためのものです.アプレット版及び JavaScript 版では,任意のデータに対して画面上で実行することができます.

(プログラム例 7.51 ) 重回帰分析 

  添付したプログラムは,重回帰分析を行うためのものです.アプレット版及び JavaScript 版では,任意のデータに対して画面上で実行することができます.

(プログラム例 7.52 ) 正準相関分析 

  添付したプログラムは,正準相関分析を行うためのものです.アプレット版及び JavaScript 版では,任意のデータに対して画面上で実行することができます.

(プログラム例 7.53 ) 主成分分析 

  添付したプログラムは,主成分分析を行うためのものです.アプレット版及び JavaScript 版では,任意のデータに対して画面上で実行することができます.

(プログラム例 7.54 ) 因子分析 

  添付したプログラムは,因子分析を行うためのものです.アプレット版及び JavaScript 版では,任意のデータに対して画面上で実行することができます.

(プログラム例 7.55 ) クラスター分析 

  添付したプログラムは,クラスター分析を行うためのものです.アプレット版及び JavaScript 版では,任意のデータに対して画面上で実行することができます.

(プログラム例 7.62 ) 分散分析 

  添付したプログラムは,分散分析を行うためのものです.アプレット版及び JavaScript 版では,任意のデータに対して画面上で実行することができます.

7.6.5 その他

(プログラム例 7.38 ) ソート(並べ替え) 

  添付したプログラムは,関数名の引き渡し(比較する関数名を渡している),および,再帰呼び出しの例として,4 種類の方法(バブルソート,選択ソート,クイックソート,および,バケツソート)のいずれかでソートを行う例を書いたものです.

(プログラム例 7.39 ) 基本アルゴリズム(その1) 

  添付したプログラムは,特に数学的な基本アルゴリズム(角度の和,行列式,最大公約数,三角形の面積,三点を通る平面,素数,点と直線の距離,点と平面の距離,二直線の交点,二直線間の最短距離,座標軸の回転,入出力)に関するプログラムです.探索手法に関しては,基本アルゴリズム(その2)を参照して下さい.

演習問題7

:以下の問題において,main 関数以外では基本的に入出力を行わないこと.なお,配列を使用する場合は,可能ならば,任意の大きさの配列に対応できるように作成すること.

[問1]演習問題 5 の問 1 から問 18 までを(問 6,7,11,12,13,及び,16 を除く),関数を利用して書け.

[問2]演習問題 6 の問 1 から問 13 まで(ただし,問 8,9,及び,12 を除く)を,関数を利用して書け.

[問3]演習問題 5 の問 22 と問 23 を,関数を利用して書け.ただし,f(x) の計算を別の関数で行い,二分法及び台形則による積分を実行する関数にその名前を引数として受け渡せ.

[問4]演習問題 6 の問 16 を,関数を利用して書け.

[問5]n 行 m 列の行列の足し算を行うプログラムを,関数を利用して書け.なお,関数は,任意の n 及び m に対応できるようにせよ.

[問6]キーボードから,「 English This is a pen 」 のように English の後に英文を入力すると,英文を構成する単語数及びすべての文字数を出力するプログラムを main 関数の引数を利用して書け.

静岡理工科大学 情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引