ここでは,体系的に関数について紹介し,自分でいろいろな関数を作成する方法を学ぶ.また,これらの関数を組み合わせて大きなプログラムを組む方法を学ぶ.
標準C++ライブラリ(Standard C++ Library)
ヘッダーファイル | 内容 |
<cassert> | Assert () 関数を定義する |
<cctype> | 文字をテストする関数を定義する |
<cfloat> | 不動小数点に適する定数を定義する |
<climits> | ローカルシステムの整数のリミットを定義する |
<cmath> | 数学で用いる関数を定義する |
<cstdio> | 標準入出力に用いる関数を定義する |
<cstdlib> | ユーティリティ関数を定義する |
<string> | stringを扱う関数を定義する |
<ctime> | 時間や日にちを扱う関数を定義する |
<iostream> | ストリーム入出力 |
<fstream> | ファイルの入出力に用いる関数を定義する |
例えば,4章ですでに行なったように,ランダム数を発生させる関数rand()を使うには,rand()が定義されているcstdlibをメイン関数の前にインクルードする必要がある.
#include cstdlib
標準C++ライブラリ関数(Standard C++ Library Functions)
cmathで定義されている関数
acos(x), asin(x), atan(x), ceil(x), cos(x), exp(x), fabs(x),
floor(x), log(x), log10(x), pow(x,p), sin(x), sqrt(x), tan(x) |
例えば,sqrt()関数は y = sqrt(x) のようにして用いる.このような方法を関数呼び出しという.なぜならば,sqrt(x)は関数sqrt()を呼び出すからである.また,括弧の中のxは引数(argument)またはパラメタ(parameter)と呼ばれ,関数へ値を渡すので,値渡し(passed by value)と呼ばれる.例えば,x = 3;であるとすると,下の図のようにして,sqrt(3)の値を得ることができる.
ユーザ関数(User-Defined Functions)
ユーザ関数は,ヘッドとボディでできていて,
データ型 関数名(データ型 仮引数1,データ型,仮引数2,)
{ 実行文; return(式); } |
int cube(int x)
{ return x*x*x; } |
関数は値を返すという性質を持っているので,その関数がどのような型のデータを返すかを関数名の前に,関数のデータ型として宣言する必要がある.return(式文)の式文の値が関数の返す値として呼び出し元に返されるので,式の値と関数のデータ型は一致しなければならない.値を戻さない関数の場合は,関数のデータ型としてvoid型を指定し,式の記述のないreturn文を指定する.また,値を返さない関数の場合,return文自体を省略することができる.
この例は関数の中でも最も単純なものの一つである.一般に関数のボディはもっと大きいが,関数のヘッドはほとんどの場合一行で収まる.
main()も関数であり,そのヘッドは
int main()
である.そのボディはプログラム自身である.main()の戻り値の型はintであり,関数名はmain,そして引数は空である.
関数のreturn文は関数の実行を終了する役目と関数を呼んだプログラムに値を戻す役目がある.そして,その構文は
return 式文;
となる.
テストドライバ(Test Drivers)
ユーザ関数を定義したら,すぐにその関数をテストしてみる.このテストに用いるプログラムをテストドライバ(test driver)という.テストドライバは"quick and dirty"でよい.つまり,普通のプログラムのような,ユーザプロンプト,出力用のラベル,ドキュメントの必要はない.
解答 ユーザ関数がmax(int x, int y)より,テストドライバでは
int m,n;
max(m,n);
でユーザ関数max(int x, int y)を呼び出すことができる.
#include <iostream>
using namespace std; int max(int x, int y) { if (x < y) return y; else return x; } int main(){ int m,n; do { cin >> m >> n; cout << "max("<< m << "," << n << ") = " << max(m,n) << endl; }while (m != 0); } |
実行結果
練習問題 5..1
Power()関数
を計算するユーザ関数Power(double x, int n)を定義し,Power(double x, int n)関数のテストドライバを作成せよ. |
関数の宣言と定義(Function Declarations and Definitions)
上の例では,メイン関数の前にユーザ関数のすべてが表示されている.これは,最も単純なプログラムの書き方で,テストドライバにはこれでもよい. もっと一般的な書き方は,関数のヘッダだけメイン関数の前に書き,ヘッドとボディをメイン関数の後に書く方法である.これにより,関数を別々にコンパイルすることができ,大きなプログラムへと発展できる.
解答
#include <iostream>
using namespace std; int max(int , int); int main(){ int m,n; cout << "2つの整数を入力してください" << "0を入力すると終了します" << endl; do { cin >> m >> n; cout << "max("<< m << "," << n << ") = " << max(m,n) << endl; }while (m != 0); } int max(int x, int y) { if (x < y) return y; else return x; } |
実行結果
分割コンパイル(Separate Compilation)
まず,テストドライバ
test_max.cppを
int max(int, int);
int main() { int m,n; cout << "2つの整数を入力してください" << "0を入力すると終了します" << endl; do { cin >> m >> n; cout << "\t max(" << m << "," << n << ") = " << max(m,n) << endl; }while (m != 0); } |
次に,ユーザ関数
max.cppを
int max(int x, int y)
{ if (x < y) return y; else return x; } |
UNIX,Cygwinで分割コンパイルを行なうには,
ターミナルで >> g++ -c max.cpp >> g++ -c test_max.cpp >> g++ -o test_max test_max.o max.o >> test_max と打てばよい. |
Visual Stdio. NETでは,test_max.cppを今までどおり作成する.その後,新しいファイルを開くでファイルを開き,そこに,max.cppを作成する.ソースファイルを右クリックし,ソースの追加で新たなソースの追加を行なう.コンパイルはF7で今まで通り行なうことができる.
練習問題 5..2
分割コンパイル
練習問題で定義したPower( )関数とメイン関数を別々にコンパイルせよ. |
解答
プロトタイプ宣言 |
さて,関数school()は何をすればいいのだろう.school()は学校名を表示するのだから,戻す値は何もない.また,メイン関数から何も渡されない.したがって,プロトタイプ宣言は
void school(void);
と書けばよい.
#include <iostream>
using namespace std; void school(void); // プロトタイプ宣言 void address(void); int main() { school(); address(); } // 関数schoolの定義 void school(void) { cout << "広島工業大学" << endl; } // 関数addressの定義 void address(void) { cout << "広島市佐伯区三宅2-1-1" << endl; } |
実行結果
学校名を表示する関数school()は,school()が呼ばれたら学校名を表示すればよい.同様にaddress()も住所を表示すればよい.これらの関数を呼び出すにはmain関数でschool(),address()とすればよい.
このプログラムを実行すると,main関数から呼び出された関数scholl()に制御が移り,学校名を表示する.次に制御はmain関数に移り,今度は関数address()を呼び出して住所を表示する.
関数school(void)のように( )の中にvoidと書いた関数を引数のない関数という.
上のプログラムは関数を使わず,coutだけでも同じ結果を得ることができる.では,なぜ関数を使ったのかを考えよう.例えば,学校名,所在地以外に自分の名前や学生番号などを表示したいとしよう.もしプログラムが関数を使わずcoutで書かれていたとしよう.すると,自分の名前や学生番号を表示するためには,main関数の中身を修正する必要があり,エラーが発生したときにmain関数の中をすべてチェックしなければならなくなる.しかし,上のプログラムのように関数を用いて書いておけば,少なくとも学校名と所在地を表す関数にはエラーがないことは分かっているので,エラーが発生した場合は追加した自分の名前と学生番号を表示する関数だけチェックすればエラー修正ができる.さらに,プログラムを読んだとき,main関数だけで何をしているのか分かり,読みやすいプログラムを作成することができる.
関数の呼び出し(Function Call)
呼び出し側と呼び出される関数の間でのデータの受け渡しは引数を用いて行なわれる.呼び出し側で使用する引数を実引数,関数側で使用する引数を仮引数という.関数の定義において,引数のデータ型を宣言する必要がある.実引数には,定数,変数,式を記述することができるが,仮引数はデータを受け取る器として使用されるので,記述できるのは変数だけである.また,実引数と仮引数のデータ型は必ず一致していなければならない.
引数のある関数(値渡し法(Pass by Value))
C++で引数(実引数)の値を関数(仮引数)へ渡す方法を値渡し(pass by value)という.関数が呼び出されたとき,実引数の値は仮引数のメモリ上にコピーされ,関数側で仮引数にコピーされた値を変更しても,呼び出し側の実引数の値には影響が及ばない方法である.値渡しによるデータの受け渡しは,呼出し側から呼び出される関数側への一方通行となる.
解答
1.2つの値x,yの和を計算する関数をsum(),差を計算する関数をdiff()とすると,関数sum()とdiff()は次のように表せる.
float sum(float x, float y){ | float diff(float x, float y){ |
float s; | float d; |
s = x+y; | d = x-y; |
return (s); | return (d); |
} | } |
2.main関数から読み込んだa,bの値を関数sum()に渡す.同様に関数diff()に渡す.
3.計算結果はreturn文を使ってmain関数に戻す.この戻される値を戻り値という.この戻り値は実数なので,関数の前にfloatがついている.ではプログラムを作成しよう.
#include <iostream>
using namespace std; float sum(float, float); // プロトタイプ宣言 float diff(float, float); int main() { float a,b; cout << "2つの数字を入力してください" << endl; cin >> a >> b; cout << "入力した2つの数" << a << "と" << b << "の和は" << sum(a,b) << "です" << endl; cout << "入力した2つの数" << a << "と" << b << "の差は" << diff(a,b) << "です" << endl; } // 関数sumの定義 float sum(float x,float y) { float s; s = x+y; return (s); } // 関数diffの定義 float diff(float x,float y) { float d; d = x-y; return (d); } |
実行結果
練習問題 5..3
平均を求める
main関数から実数データa,bを関数aveに渡して,平均を計算して表示するプログラムを作成せよ. |
実引数と仮引数
関数sum(a,b)を呼び出すときに使用する( )の中のa,bを実引数という.また,呼び出される関数sum(float x,float y)の中のx,yを仮引数という.仮引数x,yには実引数の値a,bが渡されて計算に使用され,その結果をreturn文でmain関数に戻す.このとき,実引数と仮引数の型は同じでなければならない.このように実引数の値を仮引数に渡す方法を値呼び出し法(call by value)という.この方法では仮引数の値を実引数に渡すことはできないし,戻せる値は1つである.では,仮引数を実引数に渡すにはどうすればよいのだろうか.その答えは,アドレス渡し(pass by address)または,もっと分かりやすい参照渡し(pass by reference)を用いることである.
そこで,アドレス渡しと参照渡しの説明の前に,アドレスと参照について簡単に紹介する.詳しくは,7章で行なう.
アドレス演算子(Address Operator)
int x;
|
&x
|
参照とはある変数の別名を持った変数であり,次の構文で宣言される.
データ型& 参照する名前 = 変数名;
|
int n = 44; int& rn = n; |
C++では,参照演算子&は2つの異なる目的に利用される.オブジェクト名の前に用いられると,そのオブジェクトのアドレスを意味する.しかし,型Tの後に用いられると"Tへの参照型"という派生型の意味になる.
参照演算子&は変数のメモリアドレスを返す.このアドレスを格納できる変数の型をポインタという.ポインタ変数は"型Tへのポインタ"という派生型を持っていて,T*と表される.例えば,整数型の変数のアドレスを格納するポインタ変数はint*の型を持っている.
&xで得られたアドレスを保持できるポインタ変数pを定義すると
int* p;
|
これで,ある程度の準備ができたので,アドレスを関数に渡す方法について説明する.
引数のある関数(アドレス渡し(Pass by Address))
アドレス渡しは,引数のアドレスを関数に渡す方法である.アドレス渡しでは,*(間接)演算子を用いることで,呼び出し側の値を関数側で変更することができる.アドレス渡しでは,実引数と仮引数の実体が同じものなので,仮引数の内容を変えると,実引数の内容も変わることになる.アドレス渡しを使用した場合,引数を介して呼び出し側と関数側の間で双方向にデータの受け渡しが可能である.
解答
例題5.5との違いは1つの関数で和と差を計算させ,その結果をmainに戻すことである.つまり,ユーザ関数bin_opは和,差を計算し,その結果をmainに戻さなければならない.値渡し法では,return文を使って1つの値しか戻すことができない.そこで,main関数の実引数を,変数のアドレスとし,ユーザ関数bin_opの仮引数は渡されたアドレスを格納できるように,ポインタを対応させる.もう少し詳しく説明する.
main()関数内で
bin_op(a,b,&sum,&diff);
cout<< sum << diff << endl; |
と書き,ユーザ関数を
void bin_op(float x, float y, float* add, float* subtract)
{ *add = x + y; *subtract = x - y; } |
と書くと,実引数a,b,&sum,&diffが仮引数x,y,add,subtractに渡される.ここで,&sumは変数sumの配置アドレスを意味する.その後,add = x+yにより,x+yの結果がポインタ変数addの中に格納されているアドレスを番地とする変数sumに代入される.float* addの*は,変数addは配置アドレスを格納できる変数であることを表している.このような変数をポインタ変数という.よって,coutでsumの値が表示されることになる.関数bin_opは値を返さないのでvoid bin_op(float x, float y, float* add, float* subtract)となる.では,プログラムを作成しよう.
#include <iostream>
using namespace std; void bin_op(float, float, float *, float *); // プロトタイプ宣言 int main() { float a,b,sum,diff; cout << "2つの数字を入力してください" << endl; cin >> a >> b; bin_op(a,b,&sum,&diff); cout << "入力した2つの数" << a << "と" << b << "の和は" << sum << "です" << endl; cout << "入力した2つの数" << a << "と" << b << "の差は" << diff << "です" << endl; } // 関数bin_opの定義 void bin_op(float x,float y, float *add, float *subtract) { *add = x + y ; *subtract = x - y; } |
実行結果
この方法はC言語で用いられていたものを,そのままC++言語に拡張したものである.何度も言うが,C++にはもっと分かりやすい参照渡し(pass by reference)と呼ばれる方法がある.
練習問題 5..4
アドレス渡しによるswap関数
整数a,bを読み込み,main関数から&a,&bをユーザ関数swap(int* a,int* b)に渡して,*aと*bの値を入れ替える演算を行い,その結果をmainに戻して表示するプログラムをアドレス渡しを用いて作成せよ. |
引数のある関数(参照渡し(Pass by Reference))
C++には,ポインタに関連する機能として,参照渡しが用意されている.参照とは,あらゆる点で変数の別名として動作する暗黙的なポインタのことである.参照は,ユーザ関数の仮引数の前に&をつける必要がある.これにより,参照仮引数が宣言できる.
解答
例題5.6との違いはユーザ関数bin_opの仮引数の前に&をつけるだけで良いことである.なぜなら,C++では,コンパイラに指示してポインタ値による呼び出しコードを自動的に生成させ,関数の特定のパラメターに参照で値を渡すことが出来るからである.つまり,
main()関数内で
main()関数内で
bin_op(a,b,sum,diff); cout << sum << diff << endl; |
void bin_op(float x, float y, float &add, float &subtract)
{ add = x + y; subtract = x - y; } |
関数bin_op()は値を返さないのでvoid bin_op(float x, float y, float& add, float& subtract)となる.では,プログラムを作成しよう.
#include <iostream>
using namespace std; void bin_op(float, float, float &, float &); // プロトタイプ宣言 int main() { float a,b,sum,diff; cout << "2つの数字を入力してください" << endl; cin >> a >> b; bin_op(a,b,sum,diff); cout << "入力した2つの数" << a << "と" << b << "の和は" << sum << "です" << endl; cout << "入力した2つの数" << a << "と" << b << "の差は" << diff << "です" << endl; } // 関数bin_opの定義 void bin_op(float x,float y, float &add, float &subtract) { add = x + y ; subtract = x - y; } |
実行結果
練習問題 5..5
参照を用いたswap関数
整数a,bを読み込み,main関数からa,bをユーザ関数swap(&a,&b)に渡して,aとbの値を入れ替える演算を行い,その結果をmainに戻して表示するプログラムを参照を用いて作成せよ. |
インライン関数(Inline Function)
関数の呼び出しには大量のオーバーヘッドを必要とする.つまり,実引数の受け渡し,仮引数の格納場所,変数の格納,mainプログラムを実行する場所と関数の使用には,余分な時間と格納場所を必要とする.時には,関数をインライン関数にすることによりこれらのことを避けることができる.インライン関数を用いると,プログラマは数学の関数を用いるようにして,関数を扱うことができる.インライン関数は関数名の前に,inlineをつける.また,メイン関数の前におく必要がある.
#include <iostream>
using namespace std; inline int cube(int x) { return x*x*x; } int main() { cout << cube(3) << endl; int x,y; cin >> x; y = cube(2*x-3); cout << y << endl; } |
実行結果
文字列操作関数(詳しくは8章と9章参照)
C++言語はシステム言語として生まれたC言語を継承しているので,文字列を操作する標準関数が用意されている.
下に挙げる関数を用いるには,ヘッダファイルcstringをインクルードする必要がある.
関数名 | 機能 |
strlen (string lengthの略) | 文字列の文字の個数を求める |
strcmp (string comparisonの略) | ある文字列と他の文字列を比較する |
strcpy (string copyの略) | ある文字列を他の文字列に複写する |
strcat (string concatinationの略) | ある文字列に他の文字列を連結する |
"hello"は文字列とよばれ各文字が1文字1バイトである配列として保存される.このとき文字列の内部表現では最後にナル文字(null character)'0'が置かれるので,必要な物理的記憶容量は,実際に書かれた文字の個数よりも1つ多くなる.
h | e | l | l | o | \0 |
関数strlen(s)は \0 を除いた文字配列sの長さを求めるものである.
int strlen(char s[ ])
{ int i; i = 0; while (s[i] != 0) ++i; return i; } |
解答 文字列str1[ ]に"hello world"を代入し,文字列str2[ ]に"ハロー ワールド"を代入する.次に,関数strlen()に引数として配列名str1とstr2を渡し,計算結果を表示すればよい.では,プログラムを作成しよう.
#include <iostream>
#include <cstring> using namespace std; int main() { int n1,n2; const char str1[ ] = "hello world"; const char str2[ ] = "ハロー ワールド"; n1 = strlen(str1); n2 = strlen(str2); cout << "文字列1の文字数=" << n1 << endl; cout << "文字列2の文字数=" << n2 << endl; } |
実行結果
練習問題 5..6
strcat関数
文字列1"ABCD"に文字列2"XYZ"をstrcat()で連結し,文字列1を表示するプログラムを作成せよ. |
関数の多重定義(Overloading functions)
関数のオーバーロードとは,引数の型や数の違いによって,同じ名前の関数を用いることである.関数をオーバーロードすると,関連する複数の操作を同じ名前で参照できるので,プログラムの複雑さを軽減することが可能である.
例えば,Cのライブラリには,整数の絶対値を求めるためのabs()関数,長整数の絶対値を求めるためのlabs()関数,そして,浮動小数点数の絶対値を求めるためのfabs()関数と3つの関数が含まれている.
しかし,これらの3つの関数はみな絶対値を返し,違いはデータ型だけである.このようなとき,C++では,3つの異なるデータ型に対して,次のようにして1つの名前をオーバーロードすることができる.
int myabs(int n);
long myabs(long n);
double myabs(double n);
解答
#include <iostream>
#include <cmath> using namespace std; int myabs(int); long myabs(long); double myabs(double); int main() { cout << "-10の絶対値は" << myabs(-10) << endl; cout << "-10Lの絶対値は" << myabs(-10L) << endl; cout << "-10.34の絶対値は" << myabs(-10.34) << endl; } int myabs(int n)R { return n<0 ? -n : n; } long myabs(long n) { return n<0 ? -n : n; } double myabs(double d) { return d<0 ? -d : d; } |
実行結果
ローカル変数と関数(Local Variables and Functions)
ローカル変数はブロック内で宣言された変数である.したがって,そのブロック内からしかアクセスできない.関数自身,一つのブロックであるので,関数内で宣言された変数はローカル変数である.また,関数の引数もローカル変数とみなされる.
階乗は例題4.6で紹介した.nの階乗はnにn以下の全ての正の整数を掛けたものである.
n! = n(n-1)(n-2) ... (3)(2)(1)
|
long fact(int n)
{ if(n < 0) return 0; int f = 1; while (n > 1) f *= n$--$; return f; } |
よく用いる関数
==============================================
//
を求めるプログラム
unsigned int Sum(unsigned int n)
{
unsigned int result = 0;
for (unsigned int i = 1; i = n; ++i)
result += i;
return result;
}
==============================================
==============================================
//
をHorner法を用いて求めるプログラム
int Horner(int a[], unsigned int n, int x)
{
int result = a[n];
for (int i = n-1; i = n; i)
result = result*x + a[i];
return result;
}
==============================================
==============================================
// を再帰を用いて求めるプログラム
より,F(n)をとおくと,
と表せる.このように,自分自身の繰り返しで求めることを再帰(recursion)という.再帰を用いるときには,終了条件を忘れないようにする.
unsigned int Factorial(unsigned int n)
{
if (n == 0)
return 1;
else
return n * Factorial(n-1);
}
==============================================
==============================================
//
の中から線形サーチを用いて最大値を求めるプログラム
unsigned int FindMaximum(unsigned int a[], unsigned int n)
{
unsgined int result = a[0];
for (int i = 1; i = n; ++i){
if (a[i] result)
result = a[i];
}
return result;
}
==============================================
==============================================
//を求めるプログラム
int Power(int x, unsigned int n)
{
if (n == 0)
return 1;
else if (n % 2 == 0)
return Power(x*x, n/2);
else
return x * Power(x*x, n/2);
}
==============================================
これらの関数は,太字で書かれたことだけを行なう関数なので,分かりやすい関数である.このように機能ごとに分けられた関数をモジュール(module)という.プログラムを作成する際に,必要なモジュールに分けてプログラムを構築することを,モジュラプログラミング(modular programming)という.この方法を用いると次のような利点がある.
一般にモジュラプログラミングは次のような手順で行なわれる.
1. プログラムをモジュラ化するために関数を用いる利点を述べよ.
2. 関数の宣言はどこにおかれるべきか
3. 関数の定義を別のファイルに置くことの利点を述べよ.
4. 引数による受け渡しと参照による受け渡しの違いを述べよ.
5. 次の宣言の間違いを直せ.
int f(int a, int b=0, int c);
1. 次のうるう年を判定するプログラムコードはどちらが効率がよいか調べよ.
y % 4 == 0 && y % 100 != 0 || y % 400 == 0
y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)
2. 4つの整数から最も小さい整数を見つける次の関数を完成しなさい.
int min(int, int, int, int)
3. n個からk個を選び順番をつけて並べる並べ方P(n,k)は
4. 例題5−10のように,min(int, int)とmin(double, double)の多重定義関数を作成し,min(10,20)とmin(10.34, 20,45)を出力せよ.