情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引

第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.7.1 ) 名前空間
  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)
    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 インライン関数
      1. (プログラム例 7.21 ) インライン関数と #define マクロ
    3. 7.5.3 例外処理
      1. (プログラム例 7.22 ) 例外処理
  7. 7.6 様々な例題
    1. 7.6.1 数値計算
    2. (プログラム例 7.23 ) 連立線形方程式,逆行列(ガウス・ジョルダン)
    3. (プログラム例 7.24 ) 非線形方程式(二分法)
    4. (プログラム例 7.25 ) 非線形方程式(セカント法)
    5. (プログラム例 7.26 ) 非線形方程式(ニュートン法)
    6. (プログラム例 7.27 ) 代数方程式(ベアストウ)
    7. (プログラム例 7.28 ) 行列の固有値(フレーム法+ベアストウ法)
    8. (プログラム例 7.29 ) 実対称行列の固有値・固有ベクトル(ヤコビ法)
    9. (プログラム例 7.30 ) 最大固有値と固有ベクトル(べき乗法)
    10. (プログラム例 7.31 ) 数値積分(台形則)
    11. (プログラム例 7.32 ) 数値積分(シンプソン則)
    12. (プログラム例 7.33 ) 微分方程式(ルンゲ・クッタ)
    13. (プログラム例 7.34 ) 補間法(ラグランジュ)
    14. (プログラム例 7.35 ) 補間法(スプライン)
    15. (プログラム例 7.36 ) 補間法(ベジエ曲線)
    16. 7.6.2 最適化
    17. (プログラム例 7.37 ) 最適化(線形計画法)
    18. (プログラム例 7.38 ) 最適化(黄金分割法)
    19. (プログラム例 7.39 ) 最適化(多項式近似法)
    20. (プログラム例 7.40 ) 最適化(最急降下法)
    21. (プログラム例 7.41 ) 最適化(共役勾配法)
    22. (プログラム例 7.42 ) 最適化( Newton 法)
    23. (プログラム例 7.43 ) 最適化(準 Newton 法)
    24. (プログラム例 7.44 ) 最適化(シンプレックス法)
    25. (プログラム例 7.45 ) 最適化(動的計画法)
    26. 7.6.3 確率と統計
    27. (プログラム例 7.46 ) ガンマ関数
    28. (プログラム例 7.47 ) 二項分布
    29. (プログラム例 7.48 ) ポアソン分布
    30. (プログラム例 7.49 ) 一様分布
    31. (プログラム例 7.50 ) 指数分布
    32. (プログラム例 7.51 ) 正規分布
    33. (プログラム例 7.52 ) χ2 分布
    34. (プログラム例 7.53 ) t 分布
    35. (プログラム例 7.54 ) F 分布
    36. (プログラム例 7.55 ) Fisher の直接確率
    37. (プログラム例 7.56 ) 乱数の発生
    38. 7.6.4 多変量解析
    39. (プログラム例 7.57 ) 最小二乗法
    40. (プログラム例 7.58 ) 重回帰分析
    41. (プログラム例 7.59 ) 正準相関分析
    42. (プログラム例 7.60 ) 主成分分析
    43. (プログラム例 7.61 ) 因子分析
    44. (プログラム例 7.62 ) クラスター分析
    45. (プログラム例 7.63 ) 分散分析
    46. 7.6.5 その他
    47. (プログラム例 7.64 ) ソート(並べ替え)
    48. (プログラム例 7.65 ) 基本アルゴリズム(その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」というヘッダファイルをインクルードしなければならないということを意味しています.C++ の場合は,<cmath> を使用しても構いません.ただし,<cmath> をインクルードした時は,std 名前空間内の関数を利用しますが(「累乗,冪乗,絶対値」参照),<math.h>をインクルードした時は C の標準関数(グローバル関数)を利用します.他にも,<iostream> のように,「 .h 」が付加されていないヘッダファイルが存在しますが,その場合も,関数名は std 名前空間内に所属するようになります.

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

  従って,ある値の平方根を計算するためには,以下のような記述をすれば良いことになります.
double x = 3.14;
double y = sqrt(x);   // double y = sqrt(3.14) としても良い		
  標準関数としては,以下に示すように,様々な種類のものが準備されています.この節では,入出力関数の中の scanf と printf,及び,主な文字列操作関数について簡単に説明しておきます.

  1. 入出力関係  入出力.

  2. 数学関係の関数  三角関数,逆三角関数,対数関数,指数関数,双曲線関数,逆走曲線関数,べき乗等

  3. 乱数  乱数の生成等

  4. 時間関係の関数  現在時刻,経過時間等

  5. 文字・文字列関数 文字列の探索,コピー,結合,変換等

  6. メモリ領域の確保  メモリの動的確保

  7. バッファ関係  バッファ操作

  8. ファイルとディレクトリ操作  ファイルとディレクトリの生成,削除,コピー等

  9. ネットワーク  ネットワーク通信

  10. その他  ソート,探索,プロセスの生成・消滅,コマンドの実行,可変個引数関数の作成等
7.0.1 scanf と printf

  個々の関数に対する説明は付録を参照してもらうとして,ここでは,scanfprintf の説明だけを多少詳しく行っておきます.なお,この節における内容は,プログラム例 3.1 に対する説明とかなり重複しますので,お急ぎの場合は次節へ進んでください.

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

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

  C++ における 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 型( float 型は %f,long double 型は %Lf )の浮動小数点表示に変換して,%d は int 型( long int 型は %ld であるが,int 型と long int 型のサイズが等しいときはいずれも %d とする.また,long long int 型は %lld となる)の整数に変換して,また,%s は文字列としてそのまま,記憶することを意味しています.これら 3 つの % で始まる文字列は,次に続く 3 つのデータに順番に対応していますので,この scanf 関数に対して,例えば,
3.141592654 123 abc		
または,
3.141592654
123
abc		
のように入力すると(改行も,スペースと同じ入力データに対する区切り文字として解釈される),d_data に 3.141592654 が double 型に変換され,i_data に 123 が int 型に変換され,また,c_data に文字列 "abc" が,記憶されます.各変数に対して,そのアドレスを指定しなければならない点に注意してください.c_data にはアドレス演算子が付加されていませんが,c_data が配列,つまり,アドレスであるからです.

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

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

  3. %d は int 型( long int 型は %ld であるが,int 型と long int 型のサイズが等しいときはいずれも %d とする.また,long long int 型は %lld となる)のデータを文字列に変換します.% と 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		
  では,scanf と printf を理解するために,次の演習問題を行ってください( A ~ I の部分を埋めよ).

(プログラム)
#include <stdio.h>
int main()
{
    double x[2];
    int y;
    char str[10];
    scanf("%lf %lf %d %s", &x[0], &x[1], &y, str);
    printf("result %s %.4f %c %f%6d\n", str, x[0], str[2], x[1], y);
    return 0;
}
			

(出力)
result printf 3.1416 i 2.500000   123			

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";
	printf("-abc- %s\n", abc);   // 文字列として出力
	printf("-abc- ");            // 1文字ずつ出力
	int i1;
	for (i1 = 0; i1 < 3; i1++)
		printf("%c", abc[i1]);
	printf("\n");
				// 文字列の長さ
	int len1, len2, len3;
	char *defgh  = "defgh";   // 警告メッセージ(この表現は,避けた方が良い)
	char emp[20] = "";
	len1 = strlen(abc);
	len2 = strlen(defgh);
	len3 = strlen(emp);
 	printf("各文字列の長さ %d %d %d\n", len1, len2, len3);
				// 文字列のコピー
   	char *str = strcpy(emp, abc);
   	len1      = strlen(emp);
   	printf("-str- %s -emp- %s 長さ %d\n", str, emp, len1);
				// 文字列の比較
   	int c1 = strcmp(emp, abc);
   	int 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		

---------------------(C++)string クラス-------------------------

  勿論,C++ においても上で示したプログラムは正しく動作しますが,C++ においては,文字列を扱うのに string クラスを利用する場合が多いと思いますので,上と同じような例を string クラスを利用して記述してみます.
/****************************/
/* 文字列操作の例(C++)      */
/*      coded by Y.Suganuma */
/****************************/
#include <iostream>
#include <string>

using namespace std;

int main()
{
				// 現在の文字列の内容を出力
	string abc = "abc";
	cout << "-abc- " << abc << endl;   // 文字列として出力
	cout << "-abc- ";            // 1文字ずつ出力
	for (int i1 = 0; i1 < 3; i1++)
		cout << abc[i1];
	cout << endl;
				// 文字列の長さ
	string defgh = "defgh";
	string emp   = "";
	int len1 = abc.size();
	int len2 = defgh.size();
	int len3 = emp.size();
 	cout << "各文字列の長さ " << len1 << " " << len2 << " " << len3 << endl;
				// 文字列のコピー
   	emp = abc.assign(abc);
   	len1 = emp.size();
   	cout << "-emp- " << emp << " 長さ " << len1 << endl;
				// 文字列の比較
   	cout << boolalpha << "emp == abc ? " << (emp == abc) << endl;
   	cout << "emp >= defgh ? " << (emp >= defgh) << endl;
				// 文字列の結合
   	string str = emp + " " + defgh;
   	len1 = str.size();
   	cout << "-str- " << str << " 長さ " << len1 << endl;
				// 文字列の検索
   	int n = str.find("ef");
	cout << (n+1) << " 番目以降の文字列 " << str.substr(5) << endl;

   	return 0;
}
		
(出力)
-abc- abc
-abc- abc
各文字列の長さ 3 5 0
-emp- abc 長さ 3
emp == abc ? true
emp >= defgh ? false
-str- abc defgh 長さ 9
6 番目以降の文字列 efgh		

----------------------(C++)string クラス終わり--------------------

7.1 簡単な関数

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

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

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

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

  1. 引数が省略された場合のデフォルト値を設定(デフォルト引数)できます(後述).

  2. 引数の参照渡しが可能です(後述).

  3. 関数名のオーバーロード関数名の多重定義)が可能です(後述).

  4. インライン関数を定義できます(後述).

  5. constexpr(C++11) は,汎用的に定数式を表現するための機能です.constexpr を関数や変数の前に付加することで,コンパイル時に値が決定する定数,コンパイル時に実行される関数,コンパイル時にリテラルとして振る舞うクラスを定義できます.
    	例 : constexpr int func(・・・・・)			
----------------------(C++)関数宣言終わり--------------------

(プログラム例 7.1 ) 階乗の計算 ( A ~ B の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

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

(プログラム)
/****************************/
/* nの階乗の計算           */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

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

	return 0;
}
			

(プログラム例 7.2 ) 階乗の計算(関数の利用) ( A ~ C の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

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

(プログラム)
/****************************/
/* nの階乗の計算           */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

double kaijo(int);

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

	return 0;
}

/**************************/
/* mの階乗               */
/*      m : データ        */
/*      return : mの階乗 */
/**************************/
double kaijo(int m)
{
	double s = 1.0;
	int i1;
	for (i1 = 1; i1 <= m; i1++)
		s *= (double)i1;

	return s;
}
			
7 行目

  関数の宣言をしています.この例では,関数 kaijo が double 型の値を返し,また,int 型の値を 1 つ関数に引き渡すことを宣言しています.関数を異なるファイルに記述したり,この例のように,同じファイル内であっても,関数を呼び出している箇所( 20 行目)より前に関数本体の定義が記述されていない場合は,必ずこの行が必要になります.したがって,29 行目以降の関数本体をこの場所に記述すれば,この行は必要なくなります.

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

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

20 行目

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

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

34 行目

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

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

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

41 行目

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

(プログラム例 7.3 ) 階乗の計算(再帰呼び出し) ( A ~ C の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

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

(プログラム)
/****************************/
/* nの階乗の計算           */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int kaijo(int);

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

	return 0;
}

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

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

	return s;
}
			

(プログラム例 7.4 ) nrの計算 ( A ~ C の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

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

  また,この例のように,関数を呼び出すときの引数として式を書くこともできます.

(プログラム)
/****************************/
/* nCrの計算               */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

double kaijo(int);

int main()
{
/*
	 データの入力
*/
	int n, r;
	printf("nとrの値を入力して下さい ");
	scanf("%d %d", &n, &r);
/*
	 nCrの計算と出力
*/
	double sn  = kaijo(n);
	double sr  = kaijo(r);
	double 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 = 1.0;
	int i1;
	for (i1 = 1; i1 <= m; i1++)
		s *= (double)i1;

	return s;
}
			

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

  7.1 節において述べたように,基本的に,各関数はそれぞれ独立したプログラムであり,各関数で使用されている変数等は,たとえ同じ名前の変数であっても,全く別のものです.特に,今まで述べてきた例においては,変数の型宣言を関数内部で行ってきました.このような変数をローカル変数local variable )と呼び,その有効範囲は関数内またはその一部だけです.そのため,7.1 節では,各関数で必要とする情報の受け渡しの方法として,引数と関数の返す値(戻り値)について説明しました.

  しかし,実際は,変数の型宣言を関数の外部で行い,複数の関数間で情報を共有することも可能です.このような変数をグローバル変数global variable )と呼びます.このように,変数や関数の宣言方法やその宣言場所によっては,引数や戻り値以外の方法で情報の伝達が可能になる場合があります.そこで,本節においては,変数や関数の有効範囲スコープscope )について説明します.

7.2.1 型宣言

  有効範囲の説明に移る前に,3.2.2 節で述べた型宣言についてより詳細に述べておきます.変数の型を宣言する一般形式は以下の通りです.
[記憶クラス] [型修飾]データ型 変数名, 配列名, 関数名, ・・・		
  記憶クラスの指定は,以下の 4 つの中から選択されます.

  記憶クラス auto は,関数の内部で宣言する場合だけに使用可能です.auto として宣言された変数,自動変数は,必要時だけに記憶領域が確保されます.関数が呼び出されると,関数内で宣言された自動変数がスタック上に作成され,関数の実行が終わるとすべて消去されます.自動変数に対し初期設定をする必要がある場合,関数が呼び出される度に行われます.初期設定に対するオーバーヘッドを少なくするためには,static 宣言を利用します.関数内で変数を宣言する場合,記憶クラスを省略すると auto になります.

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

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

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

  型修飾の方法には,

の 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 宣言された変数(グローバル変数)は,その変数が宣言された場所以降のすべての関数内で参照・変更が可能になります.ただし,関数内で,外部で宣言された変数と同じ名前の変数名が extern 宣言無しで使用された場合は,内部で宣言された変数が優先されます.

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

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

  一般に,変数の有効範囲は,型宣言された位置からその変数が所属するブロックの終わりまでになります.ただし,あるブロックの内側のブロックで,外側のブロックと同じ名前の変数が宣言された場合は,内側のブロック内のその変数が型宣言された位置以降では,その変数の方が優先されます.従って,ブロックの内側で同じ名前の変数が宣言された以降では,外側のブロックで宣言された変数を参照することができなくなります.ただし,関数の外側で宣言された変数(外部変数)については,変数名の前にスコープ解決演算子 :: を付加することによって,その変数にアクセスできるようになります( C を除く).例えば,変数 x を外部変数としたとき,その値を変数 y に代入するには,
y = ::x;		
のように記述します.具体的な使用方法に関しては,プログラム例 7.7 を参照してください.

----------------(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 の値を修正すれば,その影響は他のすべての関数に及びます.ただし,ある関数内部において「 int j; 」のような宣言がなされていると,その関数内の宣言以降ではこの宣言が優先されます.

13 行目

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

  また,各関数において,変数 k が宣言されています.その有効範囲は各関数内部に限られ,同じ変数名ではありますが全く関係ありません.
(出力)
5 2 6
1 2 3
1 2 7			
  このプログラムを実行すると以下のような出力が得られます.A ~ I の空白部分を埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.

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

  次のプログラムは,プログラム例 7.5 の各関数を別々の 4 つのファイルに分け,同じ結果を得るように書き直したものです.先の例では必要なかった宣言が必要になることに注意して下さい.この例により,変数の有効範囲の概念を十分理解して下さい.

------------------------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/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 を参照( C では許されない)
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 行目で有効
		                                 // この表現は,C では許されない
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	}
		
  15 行目の段階では,変数 x には 11 行目で宣言したときの初期値,また,変数 y には 7 行目で宣言された x の値が,スコープ解決演算子による 14 行目の代入文によって入っています.また,19 行目の段階では,18 行目で宣言された変数 y が優先されています.しかし,この変数の有効範囲は 20 行目までですので,22 行目では再び 15 行目の状態に戻っています.同様に,24 から 27 行目においても,ブロック内で宣言された x および y が優先されます.
(出力)
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			
  このプログラムを実行すると以下のような出力が得られます.A ~ N の空白部分を埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.

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

  たとえば,大きなプログラムを複数の人で作成するような場合,全く同じ名前で,かつ,同じ引数を持つ関数を,異なる人が異なる目的で作成してしまうような場合が考えられます.関数の有効範囲は基本的にグローバル(大域的なスコープを持っている)ですから,そのまま放置すればとんでもないことになってしまいます.そこで,グローバル変数,関数,後に説明するクラス名などに対しては,名前空間を利用してその有効範囲を制御することができます.例えば,<math.h>をインクルードして使用する sqrt, sin などの数学関数は,C の標準関数内のグローバル関数とみなされます.しかし,<cmath> をインクルードして使用すると,std 名前空間内に所属する関数が対象となります(「三角関数,逆三角関数」,「双曲線関数,逆双曲線関数」,「指数関数,対数関数」,「累乗,冪乗,絶対値」,「切り上げ,切り捨て,四捨五入」,「剰余」,「誤差関数,ガンマ関数」,「特殊関数」参照).

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

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

  この例では,名前も引数も同じ 2 つの関数が定義されていますが,名前空間を利用することによって,適切な関数を利用することができます.

#include <stdio.h>

namespace Test1 {
	void print(int k1, int k2)
	{
		printf("%d %d\n", k1, k2);
	}
}

namespace Test2 {
	void print(int k1, int k2)
	{
		printf("%d / %d", k1, k2);
	}
}

/****************/
/* main program */
/****************/
int main()
{
	Test1::print(10, 20);
	Test2::print(10, 20);

	return 0;
}
		

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

/****************/
/* main program */
/****************/
using namespace Test1;

int main()
{
	print(10, 20);
	Test2::print(10, 20);

	return 0;
}
		

7.3 データの受け渡し

7.3.1 データとアドレス

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

  最も基本的な関数は,7.1 節で述べたように,引数を渡して,1 つの結果を呼び出した側に返します.引数として与えられた変数(定数)は,相手側にそのコピーが受け渡されるだけであり,呼び出された関数内でその値を変更して,その結果を引数を通して呼び出した関数に返すことはできません.つまり,関数の実行結果として得られるのは戻り値として設定された値だけです.しかし,場合によっては,関数から複数の結果を返してもらいたいような場合が起こります.その一つの解決方法は,変数のアドレスを利用する方法です.例えば,

01	{
02		 ・・・
03		int a = 1;
04		int b = 5;
05		int n = sub(a, &b);
06		 ・・・
07	}
08	int sub(int a, int *c)   // int* c でも可,c には b のアドレスのコピー
09	{
10		 ・・・
11		*c = 10;   // 4 行目の b の値を変更
12		 ・・・
13		a = 100;   // 3 行目の a の値は変化しない
14		 ・・・
15	}
		

のようにすると,5 行目の関数呼び出しによって,変数 b のアドレスが関数 sub の変数 c にコピーされます(右図参照).変数 c の値は b のアドレスと同じですので,その指し示す位置は変数 b そのものになります.従って,11 行目に示すように,間接演算子を使用して,変数 c の指し示す場所の値を変更すれば,変数 b の値が変更されます.この結果,関数 sub を呼び出した後,4 行目の変数 b の値は 10 に変化します.scanf においてアドレス演算子 & を使用する必要があるのも,指定した変数の値を入力値によって変更したいからです.

(プログラム例 7.8 ) 複数結果の受け渡し ( A ~ E の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

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

  関数 sub の実行に必要なデータのうち,変数 n は関数外部で定義し,関数 sub 内で extern 宣言を行うという方法を利用して関数 sub に渡しています(以下の例において,全てが同じファイル内に記述されているときは,extern 指定は必要ない).この結果,関数内の変数 n は,関数の外側で宣言されている変数 n と同じものになり,値を変更できます.あくまで,例として行っているのであり,一般的には引数として渡すべきです.また,他のデータは引数を利用して渡しています.ただし,除算の結果を受け取る変数 res は,関数 sub 内でその値を変更したいため,そのアドレスを渡しています.

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

(プログラム)
/****************************/
/* 複数結果の受け渡し       */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int sub(int, int, int *);   // int* でも可

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

int main()
{
/*
	 データの入力
*/
	int a, b;
	printf("a,b,及び,nの値を入力して下さい ");
	scanf("%d %d %d", &a, &b, &n);
/*
	 関数の呼び出し
*/
	int res;
	int ind = sub(a, b, &res);   /* 変数resはアドレス渡し */
/*
	 結果の出力
*/
	if (ind == 0)
		printf("n %d result %d\n", 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)   // 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)		
  上のプログラムにおいては,除算の結果に対してだけ,アドレスを使って受け渡しをしましたが,変数 ind に対してもアドレスを使用することができます.

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

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

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

int main()
{
/*
	 データの入力
*/
	int a, b;
	printf("a,b,及び,nの値を入力して下さい ");
	scanf("%d %d %d", &a, &b, &n);
/*
	 関数の呼び出し
*/
	int res, ind;
	sub(a, b, &res, &ind);
/*
	 結果の出力
*/
	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 : 計算結果    */
/*      ind : =0 : normal */
/*            =1 : n = 0  */
/**************************/
void sub(int a, int b, int *res, int *ind)
{
	extern int n;

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

  また,複数結果を受け取る方法として,6.4.2 節で述べた new 演算子を使用して結果を保存する領域を確保し,その領域に対するポインタを返す方法があります.以下に示すプログラムは,上記と同じ内容をこの方法で書いたものです.

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

int *sub(int, int);

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

int main()
{
/*
	 データの入力
*/
	int a, b;
	printf("a,b,及び,nの値を入力して下さい ");
	scanf("%d %d %d", &a, &b, &n);
/*
	 関数の呼び出し
*/
	int *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 次元配列 res[2] を定義し,この配列を介してデータを受け渡すことも可能です.勿論,new 演算子を使用して配列を定義しても構いません.

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

void sub(int, int, int *);

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

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

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

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

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

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

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

(プログラム例 7.9 ) デフォルト引数(C++)

  この例では,加算する一つの値と出力先にデフォルト値( 5 と標準出力)を設定しています.関数本体を 07 行目の位置に記述する場合は,26 行目に示すように記述する必要があります.

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		FILE *out = fopen("test.txt", "w");
15		sub(10, 30, out);   // ファイル test.txt へ出力
16		fclose(out);
17		return 0;
18	}
19	
20	/*********************************/
21	/* 整数の足し算                  */
22	/*      x,y : データ             */
23	/*      fp : 出力先              */
24	/*********************************/
25	void sub(int x, int y, FILE *fp)
26	//void sub(int x, int y = 5, FILE *fp = stdout)
27	{
28		fprintf(fp, "結果は=%d\n", x+y);
29	}
		

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

7.3.2 配列

7.3.2.1 1 次元配列

  多量のデータを関数間でやりとりしたい場合,最も簡単なのは配列を利用する方法です.配列を関数外部で宣言し,extern 宣言等を利用することも可能ですが,この節では,配列を引数とする場合について考えます.

  1 次元配列の場合は簡単です.例えば,以下のような方法によって行われます.
int sub(・・・, int c[], ・・・)   // int sub(・・・, int *c, ・・・) でも可
{
	  ・・・
}

int main {
	int b[5];
	  ・・・
	n = sub(・・・, b, ・・・);
	  ・・・
}		
このように記述することにより,呼び出した側の配列 b と関数 sub 内の配列 c は同じものになり,どちらでも,配列データの参照・修正が可能になります.このことは,6.2 節で述べましたように,変数 b が配列宣言で確保された領域の先頭アドレスを指していることを考えれば当然です.つまり,前節で述べた変数のアドレスを引数として与える場合と同じことになります.なお,関数側の宣言において,配列の添え字を省略することも可能ですし,また,そのコメントに示されているように記述しても構いません.

(プログラム例 7.10 ) 1 次元配列の受け渡し ( A ~ D の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

  関数 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}}, b[3];
/*
	 関数の呼び出し
*/
	wa(3, &(a[0][0]), &(a[1][0]), b);   /* 1行目と2行目のベクトル和の計算 */
/*
	 結果の出力
*/
	int i1;
	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];
}
			

  このプログラムを実行すると,以下に示すような結果が得られます.
5 7 9		

---------------------(C++11)初期化子リストの受け渡し-------------------------

  コンテナの一種である vector クラス クラスなど,初期化子リスト(C++11)を利用可能なオブジェクトの場合は,関数の引数として初期化子リストを渡すことが可能です.残念ながら,配列に対しては適用できません.
/****************************/
/* 初期化子リストの受け渡し */
/*      coded by Y.Suganuma */
/****************************/
#include <iostream>
#include <vector>

using namespace std;

void wa(vector<pair<int, int>>, int *);

int main()
{
	int res[2] = {0};
	vector<pair<int, int>> v {{1, 2}, {3, 4}, {5, 6}};
	cout << "v";
	for (auto x : v)
		cout << "  (" << x.first << ", " << x.second << ")";
	cout << endl;

	wa(v, res);
//	wa({{1, 2}, {3, 4}, {5, 6}}, res);   // 初期化子リスト

	cout << "  result " << res[0] << " " << res[1] << endl;

	return 0;
}

/*********************************************/
/* ペアの和                                  */
/*      r[0] : vector の各要素の first の和  */
/*      r[1] : vector の各要素の second の和 */
/*********************************************/
void wa(vector<pair<int, int>> v, int *r)
{
	for (auto x : v) {
		r[0] += x.first;
		r[1] += x.second;
	}
}
		
(出力)
v  (1, 2)  (3, 4)  (5, 6)
  result 9 12		

----------------------(C++11)初期化子リストの受け渡し--------------------

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) ( A ~ G の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

  関数 seki は,n 行 m 列の行列と m 行 l 列の行列の積を計算するためのものです.上で述べたように,関数 seki は,配列 a[2][3] の[3] の部分,または,配列 b[3][2] の [2] の部分が変更になると,使用できなくなります.つまり,関数 seki は,その内容を修正しない限り,任意のサイズの行列の乗算に対応していないことになります.なお,行列 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, double [][3], double [][2], double [][2]);

int main()
{
/*
	 関数の呼び出し
*/
	double a[2][3] = {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}};
	double b[3][2] = {{1.0, 0.0}, {0.0, 1.0}, {0.0, 0.0}};
	double c[2][2];
	seki(2, 3, 2, a, b, c);
/*
	 結果の出力
*/
	int i1;
	for (i1 = 0; i1 < 2; i1++) {
		int i2;
		for (i2 = 0; i2 < 2; i2++)
			printf("%f ", 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, double a[][3], double b[][2], double 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];
		}
	}
}
			

  このプログラムを実行すると,以下に示すような結果が得られます.
1 2
4 5		

(プログラム例 7.12 ) 2 次元配列の受け渡し(方法 2) ( A ~ I の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

  new 演算子( malloc 等でも構いません)を使用して配列を定義し,先の例の関数をポインタを使用した記述になおしたものです.このようにすれば,関数を呼び出す側で配列の大きさが変化しても,次の例と同様,呼び出される側の関数 seki を修正する必要がありません.つまり,関数 seki は,任意のサイズの行列の乗算に対応していることになります.

(プログラム)
/**********************************/
/* 2次元配列の受け渡し(方法2) */
/*      coded by Y.Suganuma       */
/**********************************/
#include <stdio.h>

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

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

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

	for (int i1 = 0; i1 < 3; i1++) {
		if (i1 == 0)
			b[i1] = new double [2] {1, 0};
		else if (i1 == 1)
			b[i1] = new double [2] {0, 1};
		else
			b[i1] = new double [2] {0, 0};
	}
/*
	 関数の呼び出し
*/
	seki(2, 3, 2, a, b, c);
/*
	 結果の出力
*/
	for (int i1 = 0; i1 < 2; i1++) {
		for (int i2 = 0; i2 < 2; i2++)
			printf("%f ", 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, double **a, double **b, double **c)
{
	for (int i1 = 0; i1 < n; i1++) {
		for (int i2 = 0; i2 < l; i2++) {
			c[i1][i2] = 0;
			for (int i3 = 0; i3 < m; i3++)
				c[i1][i2] += a[i1][i3] * b[i3][i2];
		}
	}
}
			

(プログラム例 7.13 ) 2 次元配列の受け渡し(方法 3) ( A ~ C の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

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

  しかし,プログラム例 7.10 の関数 wa は,1 次元配列を使用しているが故に,ベクトルの次元がどのように変化しても修正すること無しに使用可能です.6.3 節で説明したように,2 次元配列であっても連続した領域に確保されています.従って,2 次元配列 x の要素 x[i][j] は,x を 1 次元配列 y とみなしたとき,y[(i×k) + j] に対応していますので( k は配列 x の列数),その並び方に注意しさえすれば,2 次元配列を 1 次元配列として取り扱うことができます.この性質を利用して書き直したのが下のプログラムです.このプログラムの関数 seki は,任意の n,m,及び,l の値に対して修正無しで使用可能となっています.

(プログラム)
/**********************************/
/* 2次元配列の受け渡し(方法3) */
/*      coded by Y.Suganuma       */
/**********************************/
#include <stdio.h>

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

int main()
{
/*
	 関数の呼び出し
*/
	double a[2][3] = {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}};
	double b[3][2] = {{1.0, 0.0}, {0.0, 1.0}, {0.0, 0.0}};
	double c[2][2];
	seki(2, 3, 2, &a[0][0], &b[0][0], &c[0][0]);
/*
	 結果の出力
*/
	int i1, i2;
	for (i1 = 0; i1 < 2; i1++) {
		for (i2 = 0; i2 < 2; i2++)
			printf("%f ", 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, double *a, double *b, double *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.3.3 関数名

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

  関数名を引数とするには,基本的に,関数のアドレスを使用すれば可能です.例えば,
int (*sub)(double, char *)		
という記述は,sub が,double 及び char に対するポインタという 2 つの引数をもち,int を返す関数へのアドレスであることを表しています.このことから,以下の例に示すように,関数のアドレスを別の変数に代入して,その変数を同じ関数のように使用することも可能です.
int add(int a, int b)
{
	return a + b;
}

int main()
{
	int (*fun)(int, int) = &add;
	printf("和 %d\n", fun(2, 3));
	return 0;
}		

(プログラム例 7.14 ) 関数名の受け渡し(ニュートン法) ( A ~ D の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

  次のプログラムの関数 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 = 1.0e-7;
	double eps2 = 1.0e-10;
	double x0   = 0.0;
	int max = 20, ind;

	double 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 x1 = x0;
	double x  = x1;
	int sw    = 0;
	*ind      = 0;

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

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

		if (fabs(g) > eps2) {
			if (*ind <= max) {
				double 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 = exp(x) - 3.0 * x;
	return y;
}

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

  このプログラムを実行すると,以下に示すような結果が得られます.
ind=5  x=0.619061  f=0.000000  df=-1.142816		

---------------------(C++11)関数オブジェクトとラムダ式-------------------------

  C++ の場合は,関数オブジェクトラムダ式を同じ目的で使用することが可能です.以下に示すものは,関数オブジェクト,または,ラムダ式を利用して,上記のプログラムを書き直したものです.
/******************************/
/* 関数オブジェクトとラムダ式 */
/*      coded by Y.Suganuma   */
/******************************/
#include <stdio.h>
#include <math.h>

class Snx
{
	public:
		double operator() (double x)
		{
			return (double)(exp(x) - 3.0 * x);
		}
};

class DSnx
{
	public:
		double operator() (double x)
		{
			return (double)(exp(x) - 3.0);
		}
};

double newton1(auto, auto, double, double, double, int, int *);   // ラムダ式
double newton2(Snx, DSnx, double, double, double, int, int *);   // 関数オブジェクト

int main()
{
	double eps1 = 1.0e-7;
	double eps2 = 1.0e-10;
	double x0   = 0.0;
	int max     = 20, ind;
			// ラムダ式
	auto snx1  = [](double x){ return exp(x) - 3.0 * x; };
	auto dsnx1 = [](double x){ return exp(x) - 3.0; };
	double x   = newton1(snx1, dsnx1, x0, eps1, eps2, max, &ind);
	printf("ind=%d  x=%f  f=%f  df=%f\n",ind, x, snx1(x), dsnx1(x));
			// 関数オブジェクト
	Snx snx2;
	DSnx dsnx2;
	x = newton2(snx2, dsnx2, x0, eps1, eps2, max, &ind);
	printf("ind=%d  x=%f  f=%f  df=%f\n",ind, x, snx2(x), dsnx2(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 : 解                                  */
/*****************************************************/
			// ラムダ式
double newton1(auto f, auto df, double x0,  double eps1, double eps2, int max, int *ind)
{
	double x1 = x0;
	double x  = x1;
	int sw    = 0;
	*ind      = 0;

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

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

		if (fabs(g) > eps2) {
			if (*ind <= max) {
				double 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;
}
			// 関数オブジェクト
double newton2(Snx f, DSnx df, double x0,  double eps1, double eps2, int max, int *ind)
{
	double x1 = x0;
	double x  = x1;
	int sw    = 0;
	*ind      = 0;

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

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

		if (fabs(g) > eps2) {
			if (*ind <= max) {
				double 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;
}
		

----------------------(C++11)関数オブジェクトとラムダ式終わり--------------------

(プログラム例 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			
  上のプログラムを実行すると下のような結果が得られます.A ~ D の空白部分を埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.

7.3.4 参照渡し(C++)

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

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

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

  参照渡しをすれば,関数内で値の修正が可能となりますが,もし修正を許さないならば,その引数に対して,
const		
の指定をしておくべきです.ただし,プログラム例 6.10 に示したように,内部で new 演算子を使用しているようなクラスのオブジェクトを引数とした場合,例え const 指定していても,そのアドレスが指す場所の値を関数内で修正可能である点に注意してください.

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

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

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

int main()
{
	int x = 10, y = 20, z = 0;
	char *d1 = new char[6] {'d', 'a', 't', 'a', '1'};
	char *d2 = new char[6] {'d', 'a', 't', 'a', '2'};
	char d3[6] = "data3";

	printf("x %d y %d z %d d1 %s d2 %s d3 %s\n", x, y, z, d1, d2, d3);

	sub(x, y, &z, d1, d2, d3);

	printf("x %d y %d z %d d1 %s d2 %s d3 %s\n", x, y, z, d1, d2, d3);

	return 0;
}

/********************************/
/* 参照渡しの例                 */
/*      a : intの参照渡し       */
/*      b : intのデータ渡し     */
/*      c : intのアドレス渡し   */
/*      c1 : アドレスの参照渡し */
/*      c2 : アドレスの通常渡し */
/*      c3 : 配列の通常渡し     */
/********************************/
void sub(int &a, int b, int *c, char *&c1, char *c2, char *c3)
// void sub(const int a, ・・・) とすれば,a の値を変更できない
{
	a += 5;
	b += 5;
	*c = a + b;
	c3[1] = 'x';
	c1++;
	c2++;
	c3++;

	printf("a %d b %d c %d c1 %s c2 %s c3 %s\n", a, b, *c, c1, c2, c3);
}
		
(出力)
x 10 y 20 z 0 d1 data1 d2 data2 d3 data3
a 15 b 25 c 40 c1 ata1 c2 ata2 c3 xta3
x 15 y 20 z 40 d1 ata1 d2 data2 d3 dxta3			
  上のプログラムを実行すると下のような結果が得られます( A ~ R の空白部分を埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.).この結果からも明らかなように,通常の引き渡し( y,d2,d3 )では,値がコピーされて渡されるだけ(配列の場合は,アドレスのコピー)ですので,呼び出した側の対応する変数(アドレス)の内容を,呼び出された関数側で変えることができません.しかし,参照渡しですと,別名ですが,変数それ自身と同じものです.従って,関数を呼んだ側と同じ処理で,その内容も変更可能です.データでなくアドレスで渡した変数 z (関数側では c )との記述方法の違いにも注意して下さい.

(プログラム例 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;   // 以下の 2 行でも可
//	int& (*z)(int) = ⊂
//	z(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			
  このプログラムを実行した結果は,以下のようになります.A ~ F の空白部分を埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.

---------------------(C++11)右辺値参照・ムーブセマンティクス-------------------------

  ここで述べる内容は,非常に分かり難いと思います.コンパイラを設計するような人は別として,一般的なユーザが以下に述べるようなことを知る必要があるのか疑問を感じます.以下のようなことを仕様に含めることは,C++ 離れをますます加速するだけではないでしょうか.とは言っても,一応簡単な説明だけは行っておきます.まず,右辺値Rvalues )と左辺値Lvalues )について,非常におおざっぱな概念的説明を行ってみます.例えば,
int x1 = 10;
int& x2 = 10;   // error
int& x2 = x1;   // 左辺値参照
int&& y1 = x1;   // error
int&& y2 = 20;   // 右辺値参照		
において,x1 が左辺値,10 が右辺値になります.上で述べた参照型変数は,左辺値参照に相当します.右辺値参照は,コピーコストの削減を主な目的としており,その詳細は,以下に示す例によって説明していきます.なお,この例においても,まだ学習していないクラスを使用していますので,それらを学習した後,見直してください.
01	#include <iostream>
02	#include <string>
03	
04	using namespace std;
05	
06	class Test {
07		public:
08			int *data;
09				// コンストラクタ
10			Test() {
11				data = new int [10000];
12				cout << " (コンストラクタ)";
13			}
14				// copy コンストラクタ
15			Test(const Test& da) {
16				data = new int [10000];
17				copy(da.data, da.data+10000, data);
18				cout << " (copyコンストラクタ)";
19			}
20				// move コンストラクタ
21			Test(Test&& t)
22			{
23				data   = t.data;
24				t.data = nullptr;
25				cout << " (moveコンストラクタ)";
26			}
27				// デストラクタ
28			~Test() {
29				delete [] data;
30			}
31	};
32	
33	int main()
34	{
35				// 右辺値参照
36		string&& x = "abc";
37		string   y = x;
38	//	string&& z = y;   // エラー
39	//	string&& z = x;   // エラー
40		cout << "右辺値参照 x " << x << " &x " << &x << " y " << y << " &y " << &y << endl;
41		x = "xxx";
42		cout << "   xを変更 x " << x << " &x " << &x << " y " << y << " &y " << &y << endl;
43		y = "yyy";
44		cout << "   yを変更 x " << x << " &x " << &x << " y " << y << " &y " << &y << endl;
45				// move
46		string m3 = "abc";
47		string m4 = m3;
48		cout << "m3 を m4 に代入(普通)m3 " << m3 << " &m3 " << &m3 << " m4 " << m4 << " &m4 " << &m4 << endl;
49	//	move(m3);   // 何も起こらない
50		string m5 = move(m3);
51		cout << "m3 を m5 に代入(move)m3 " << m3 << "    &m3 " << &m3 << " m5 " << m5 << " &m5 " << &m5 << endl;
52				// 右辺値参照の利用
53		cout << "tm を宣言";
54		Test tm{};   // Test tm = Test();
55		tm.data[10] = 10;
56		cout << " tm.data[10] = " << tm.data[10] << endl;
57	
58		cout << "tm を t1 に代入,tm の data 変更";
59		Test t1 = tm;
60		tm.data[10] = 20;
61		cout << " t1.data[10] = " << t1.data[10] << " tm.data[10] = " << tm.data[10] << endl;
62	
63		cout << "tm を move し t2 へ";
64		Test t2 = move(tm);   // tmpは以後使用しない
65		cout << " t2.data[10] = " << t2.data[10] << " tm.data = nullptr\n";
66	
67		return 0;
68	}
		
36 ~ 44 行目

  右辺値参照の変数 x と普通の変数 y に対して,x を y に代入した後,x や y の値を変更しています.下に示す出力結果からは,普通の変数と変わらない結果が得られています.
   右辺値参照 x abc &x 0x61fe88 y abc &y 0x61fea0
      xを変更 x xxx &x 0x61fe88 y abc &y 0x61fea0
      yを変更 x xxx &x 0x61fe88 y yyy &y 0x61fea0			
46 ~ 51 行目

  move 関数の効果を調べています.46 ~ 47 行目は,普通の代入ですので,m3 の内容が m4 にコピーされることになります.下に示す出力例の 1 行目に示すように,m3 にも m4 にも特別な変化はありません.move 関数の機能は,右辺値の所有権を移動(ムーブ)させることです.m3 の右辺値を,m3 を m5 に代入した後使用しないのであれば,それをコピーして m5 に代入しなくても,右辺値の所有権を m3 から m5 に移動させるだけで良いはずです.move 関数はそのようなことを実行します.49 行目のような記述では何も起こりませんが,50 行目の記述によって,所有権が m5 になります.その結果,下に示す出力の 2 行目に示すように,m5 の値は "abc" になりますが,m3 は不定になります.
   m3 を m4 に代入(普通)m3 abc &m3 0x61feb8 m4 abc &m4 0x61fed0
   m3 を m5 に代入(move)m3     &m3 0x61feb8 m5 abc &m5 0x61fee8			
53 ~ 61 行目

  53 ~ 56 行目において,大きなデータ領域 data を持つクラス Test のオブジェクト tm を生成しています.その際,10 ~ 13 行目のコンストラクタが呼ばれ,10000 個の int 型データが入る領域が確保されます.その後,参考のため,tm.data[10] の値を 10 に設定しています.下に示す出力の 1 行目がそれを示しています.

  59 行目のように tm を他の Test クラスのオブジェクトに代入する場合は,47 行目に示した単純変数の代入の場合と同じように,tm のコピーが t1 に代入されます.しかし,コピーされるのは,ポインタ data の値だけで,data が指す領域はコピーされません.従って,t1 の data と tm のデータは同じ領域を指すことになり,t1.data[i]( tm.data[i] )の値を変更すれば,同じように,tm.data[i]( t1.data[i] )の値も変化します.しかし,単純変数の場合と同じように,全てのデータをコピーして,t1 と tm が異なる変数として振る舞ってもらいたい場合も存在します.これを実現するために,15 ~ 19 行目に copy を行うためのコンストラクタが定義されています.そのため,下に示す出力の 2 行目に示すように,59 行目の代入によって copy コンストラクタが呼ばれています.

  しかし,データの量が多くなると,コピーにも時間がかかります.もし,tm を t3 に代入した後,tm の右辺値を使用しないのであれば,tm の右辺値の所有権を t3 に変更するだけで済むはずです.これを実現しているのが,64 行目と対応するコンストラクタ( 21 ~26 行目)です.コンストラクタの引数が右辺値参照( Test&& t )になっていることに注意してください.
   tm を宣言 (コンストラクタ) tm.data[10] = 10
   tm を t1 に代入,tm の data 変更 (copyコンストラクタ) t1.data[10] = 10 tm.data[10] = 20
   tm を move し t2 へ (moveコンストラクタ) t2.data[10] = 20 tm.data = nullptr			

----------------------(C++11)右辺値参照・ムーブセマンティクス終わり--------------------

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[] )		

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

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

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

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

	int i1, k, sum = 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;
}
		
(出力)
     引数の数 3
     プログラム名 test
     2 番目の引数 2
     3 番目の引数 3
結果=5			
  例えば,「test 2 3」と入力すると(実行可能プログラム名を test とした場合),以下のような出力が得られます.A ~ C の空白部分を埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.

(プログラム例 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 ) 関数名のオーバーロード ( A ~ C の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.なお,A に対しては,ポインタ表現で答えてください.)

(プログラム)
/****************************/
/* 関数名のオーバーロード   */
/*      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;
}
			

  このプログラムを実行すると,以下に示すような結果が得られます.
moji-retu
3.140000
100		

7.5.2 インライン関数

  インライン関数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 と -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 番目の値を正にして再実行しました
1.73205		

---------------------(C++11)例外処理-------------------------

  例えば,以下のように記述することによって,関数 func が例外を送出する可能性がない関数であることを表します.
int function( ・・・・・ ) noexcept
{
	・・・・・
}		

----------------------(C++11)例外処理終わり--------------------

7.6 様々な例題

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

7.6.1 数値計算

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

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

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

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

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

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

(プログラム例 7.26 ) 非線形方程式(ニュートン法)

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

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

  添付したプログラムは,実係数代数方程式 (x + 1)(x - 2)(x - 3)(x2 + x + 1) = 0 の解を,ベアストウ法で求めた例です.JavaScript 版では,任意のデータに対して画面上で解を得ることができます.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  添付したプログラム(クラスを使用したプログラム例も含みます)は,3次スプライン関数によってスプライン補間するためのものです.JavaScript 版では,任意のデータに対して,スプライン補間法による計算結果を画面上で求めることができます.

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

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

7.6.2 最適化

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

  添付したプログラム(クラスを使用して記述してあります)は,線形計画法に対するプログラム例です.実行に関しては,使用方法を参考にしてください.JavaScript 版では,画面上で実行することができます.

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

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

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

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

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

  添付したプログラムは,最急降下法を使用して,非線形関数の最小値を求めるためのものです(プログラムの使用方法).JavaScript 版では,JavaScript の仕様に適合した形で最小値を求めたい式を入力することによって,任意の関数の最小値を画面上で求めることができます.

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

  添付したプログラムは,共役勾配法を使用して,非線形関数の最小値を求めるためのものです(プログラムの使用方法).JavaScript 版では,JavaScript の仕様に適合した形で最小値を求めたい関数を入力することによって,任意の関数の最小値を画面上で求めることができます.

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

  添付したプログラムは,Newton 法を使用して,非線形関数の最小値を求めるためのものです(プログラムの使用方法).JavaScript 版では,JavaScript の仕様に適合した形で最小値を求めたい関数を入力することによって,任意の関数の最小値を画面上で求めることができます.

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

  添付したプログラムは,準 Newton 法を使用して,非線形関数の最小値を求めるためのものです(プログラムの使用方法).JavaScript 版では,JavaScript の仕様に適合した形で最小値を求めたい関数を入力することによって,任意の関数の最小値を画面上で求めることができます.

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

  添付したプログラムは,シンプレックス法を使用して,非線形関数の最小値を求めるためのものです(プログラムの使用方法).JavaScript 版では,JavaScript の仕様に適合した形で最小値を求めたい関数を入力することによって,任意の関数の最小値を画面上で求めることができます.

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

  添付したプログラムは,動的計画法を使用して,資源配分問題,0-1 ナップザック問題,及び,グラフ上の最短経路問題を解くためのものです.JavaScript 版では,画面上で実行することが可能です.

7.6.3 確率と統計

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

  添付したプログラムは,ガンマ関数の値を計算するプログラムです.JavaScript 版では,任意のデータに対するガンマ関数の値を画面上で求めることができます.なお,C++11 の場合は,標準ライブラリの中にある関数 tgamma を利用できます.

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

  添付したプログラムは,グラフ出力を指定すると,ベルヌーイ試行を n 回行い,0 ~ n 回成功する場合に対する二項分布の密度関数および分布関数の値をファイルに出力します.また,グラフ出力を指定しないと,指定された値における密度関数および分布関数の値を出力します.JavaScript 版では,同様の処理を画面上で実行することが可能であり,結果はテキストエリアに出力されると共に,「確率(複数点)」を選択すればグラフも表示されます.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  添付したプログラムは,Fisher の直接確率を計算する例です.

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

  添付したプログラムは,一様乱数,指数乱数,および,正規乱数の発生方法の例です.このプログラムにおいては,標準関数の rand を使用していますが,rand は,16 ビットを使用しているためその周期が短く(最大でも,32767 ),あまり質が良い乱数を生成してくれません.そこで,メルセンヌ・ツイスタ法を使用した例( MT.h を含む)も添付しておきます.

  なお,C++11 の場合は,メルセンヌ・ツイスタ法を使用した乱数生成関数が,標準ライブラリ内に含まれています.さらに,標準ライブラリ内には,ベルヌーイ分布乱数,二項分布乱数,幾何分布乱数,負の二項分布乱数一様分布乱数ポワソン分布乱数,指数分布乱数,ガンマ分布乱数,ワイブル分布乱数,極値分布乱数正規分布乱数,対数正規分布乱数,カイ二乗分布乱数,コーシー分布乱数,フィッシャーの F 分布乱数,ステューデントの t 分布乱数確率分布生成器,重み付き確率分布生成器,線形重み付き確率分布生成器を生成する関数も含まれています.

7.6.4 多変量解析

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

  添付したプログラムは,最小二乗法(多項式近似)を実行するためのものです.データの入力方法に関しては,プログラムの最後に提示してある入力例に対する説明を参考にして下さい.JavaScript 版では,任意のデータに対して画面上で実行することができます.

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

  添付したプログラムは,重回帰分析を行うためのものです.データの入力方法に関しては,プログラムの最後に提示してある入力例に対する説明を参考にして下さい.JavaScript 版では,任意のデータに対して画面上で実行することができます.

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

  添付したプログラムは,正準相関分析を行うためのものです.データの入力方法に関しては,プログラムの最後に提示してある入力例に対する説明を参考にして下さい.JavaScript 版では,任意のデータに対して画面上で実行することができます.

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

  添付したプログラムは,主成分分析を行うためのものです.データの入力方法に関しては,プログラムの最後に提示してある入力例に対する説明を参考にして下さい.JavaScript 版では,任意のデータに対して画面上で実行することができます.

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

  添付したプログラムは,因子分析を行うためのものです.データの入力方法に関しては,プログラムの最後に提示してある入力例に対する説明を参考にして下さい.JavaScript 版では,任意のデータに対して画面上で実行することができます.

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

  添付したプログラムは,クラスター分析を行うためのものです.データの入力方法に関しては,プログラムの最後に提示してある入力例に対する説明を参考にして下さい.JavaScript 版では,任意のデータに対して画面上で実行することができます.

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

  添付したプログラムは,分散分析を行うためのものです.データの入力方法に関しては,プログラムの最後に提示してある入力例に対する説明を参考にして下さい.JavaScript 版では,任意のデータに対して画面上で実行することができます.

7.6.5 その他

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

  添付したプログラムは,関数名の引き渡し,および,再帰呼び出しを使用して,5 種類(バブルソート,選択ソート,バケツソート,ヒープソート,および,クイックソート)のソートを行,その実行時間を比較したものです.

(プログラム例 7.65 ) 基本アルゴリズム(その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 関数の引数を利用して書け.

情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引