試験の内容は,(情報処理1・同実習1も含め)これまでに行った実習の全ての範囲である.
特によく理解しておいてほしいものは,
などである.
main
関数と同じソースファイルにすべての必要な関数を記述しておく方法は,小規模なソフトウェアであれば困ることはないが,
規模が大きくなるにつれて,見通しが悪くなり,不便になってくる.
また,複数人でプログラムを開発したり,自分が過去に作成したソースコードの一部を再利用したい場合などは,処理の機能別にファイルを分けておくと便利である.
理想的には,main
関数は,プロトタイプ宣言と最低限の変数の宣言と,処理内容を簡潔に示した関数を並べるだけ,にしたい.
このようにモジュール化しておくと,部品として使用する関数を,別名のソースファイル(実装ファイルという)とヘッダファイルとして分けて作っておけば,デバッグも容易となる.
// 典型的な main 関数の例
// もちろんこのままでは動作しない
int main(void)
{
Init(); /* 変数などの初期化処理 */
ReadData(); /* データの読み込み,設定など */
DoSomething(); /* 何か処理をする */
DoOtherthing(); /* 何か処理をする */
Output(); /* 結果の出力.画面やファイルなど */
return 0;
}
例として,消費税込み金額の計算をする関数を別ファイルに記述し,main関数から呼び出す例を示す.
ファイル名を sub.cpp,sub.h とする.
sub.cpp | sub.h |
|
|
これらを用いるmain関数を含むソースファイルは
main.cpp |
|
となる.
ヘッダファイル内の#ifndef _SUB_H, #define _SUB_H, #endif
の部分はインクルードガードと呼ばれ,このヘッダファイルが別のファイルに重複してインクルードされるのを防ぐ役割を果たす.(これが無いと,関数の宣言が複数出現し,コンパイルエラーとなる場合がある.)
コンパイル方法は,
bcc32 main.cpp sub.cpp
と,全ての実装ファイルを同時に指定(正確にはコンパイルとリンク)する必要がある.
(試しに,main.cpp のみコンパイルしてみよ.どのようなエラーが出るか.)
上のプログラムを作成し,コンパイル・実行して動作を確認してみよう.
(ソースファイル2個,ヘッダーファイルを1つ作成する.)
関数とは,ひとまとまりの処理の塊であり,処理したいデータを引数として受け取り,計算などの処理を行い,その結果を返すための文法である.
具体的に,足し算の関数で説明を行う.
double add_calc(double x, double y)
{
return x + y ;
}
この関数は,仮引数としてdouble
型の変数 x, y を用意し,受け取った値を足したものを,関数add_calcとしての戻り値(返り値とも言う)として結果を返す.
この関数は値を返すので,関数名の前にdoubleという戻り値の型が宣言されている.
引数がいらない場合,または,値を返さない場合は,void
と記述する.
関数の存在を他の関数に知らせ,利用可能とするためには,そのかたちを「宣言」する必要がある. 正確にはプロトタイプ宣言という.
例えば,main
関数内で上記の関数を呼び出したければ,main
関数よりも上方の行に,
double add_calc(double x, double y);
のように,関数名と引数および戻り値の型を宣言しておく.(ここにはセミコロンが必要)
関数のプロトタイプ宣言さえあれば,関数の定義は別の.cppファイルに書かれていても良い.
つまり,ファイルを分割してコンパイルすることができる.
もちろん,「コンパイル」の後の「リンク」時に,関数が全て揃っていないとエラーになる.
メイン関数や関数の中で,関数名および()で囲った引数を正しく記述すれば,関数の呼び出し(=関数への処理のジャンプ)ができる.
例えば,
#include <stdio.h>
int main(void)
{
double a = 7.8;
double b = 2.7;
printf("a+b= %f", add_calc(a, b) );
return 0;
}
となる.
もちろん,この記述中にはadd_calc
関数の定義が書かれていないため,
add_calc
関数の「定義」を書く.
add_calc
関数の「宣言」を(ヘッダファイル,または直接)書き,かつ,add_calc
関数が「定義」された別のcppファイルを同時にコンパイルする.
の,いずれかを行う必要がある.
後述の分割コンパイルの章を参照のこと.
配列は「同じ型」の複数の変数を扱うための手段である.
例えば,要素数100個の倍精度実数(double)の配列は
double x[100];
のような形で表される.2次元配列であれば,
double y[100][100];
と書ける.
ちょうど1次元配列ならベクトル,2次元配列なら行列のようにみなすこともできるが,
注意しなければならないのは,配列は「変数の集合体」に過ぎず,
各要素が「1個の変数」と考えておく必要がある.
言い換えると,数学的な「ベクトル」や「行列」の性質は無く,一括での初期化や代入はできない.
(従って,繰り返し文を使って,1つ1つ処理するのが基本である.)
さて,配列を使えば,「同じ型」のデータをひとまとめにできる,データ処理を行う際には複数の「異なる型」をひとまとめに使いたいことが往々にしてある. そこで利用するのが,構造体である.
/* 構造体の定義の例 */
/* 例1:住所録.型が異なる変数からなる.*/
struct address
{
char name[20];
int age;
char address[100];
int postal_code;
}
/* 例2:成績データ.すべて同じ型だが,意味が違う */
struct seiseki
{
int kouriki;
int math_a;
int math_d;
int english;
};
上記の例のように,異なる基本型を組み合わせて,新しい型を作ることができる.
構造体はデータ変数群まとめるための方法と考えておけばよく,扱いたいデータ構造の設計に非常に重要である.
一言で言えば,ポインタ変数は,「アドレス」を格納するための変数である.
int main(void)
{
int x = 0, y = 0;
int* p;
p = &x; /* 変数xのポインタ(アドレス)を p に代入.変数の型とポインタの型はそろえる */
*p = 100; /* ポインタの中身(つまり変数x)を代入 */
y = *p +50; /* ポインタの中身に50を足したものを変数yに代入 */
}
ポインタ変数(ここではp)を宣言するとき,pの値はアドレスそのものであり,アスタリスク演算子*を用いて*p
と記述すれば,ポインタの中身が参照できる.
ただし,予め p = &x
として,ポインタ p が別の変数 (ここでは,x)
を指すようにしておく必要がある.
上記の例では,初めx== 0 となっており,ポインタを使って*pに100を代入すると,pの指す変数
x も100となる.
逆に,x=100;と代入すれば,xの値は100となり,*pの値も100となる.
ポインタ演算子:&
は変数のアドレスを取り出す演算子,*
はそのアドレスに格納されているの変数の値を取り出す演算子.
ポインタの応用として,「関数」の引数をポインタとする意義をもう一度考えてみよう.
毎度おなじみの,2つの値を入れ替える関数 irekae
を考える.
まず,ポインタを使わないと,
#include <stdio.h>
void irekae(int, int); /* 関数のプロトタイプ宣言では,このように型名だけで変数名を省略することもできる. */
int main(void)
{
int a, b;
printf("整数2つ入力");
scanf("%d", &a );
scanf("%d", &b );
irekae( a, b );
printf("a = %d , b = %d \n", a, b );
return 0;
}
void irekae(int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
}
このプログラムを実行して,例えば100, 50と入力し,irekae関数を呼び出しても入れ替えは行われず, a
= 100, b = 50 と出力される.
これは,main
関数内の変数a,bに値が入力されたとき,関数の引数にそのまま「コピー」され,irekae
関数内で入れ替えてもmain
関数内に反映されないためである.
main関数内の変数 a, bと,irekae関数内の変数
a, b は,ローカル変数なので,同じ変数名であっても全く別物である.
では,ポインタを使ってみよう.
#include <stdio.h>
void irekae(int*, int*); /* 変数名は省略,型名のみ. */
int main()
{
int a, b ;
printf("整数2つ入力");
scanf("%d", &a );
scanf("%d", &b );
irekae( &a, &b ); /* アドレスを渡す */
printf("a = %d , b = %d \n", a, b );
return 0;
}
void irekae ( int* a , int* b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
一見,ポインタに*をつけて中身をやり取りしているから,同じことをしているように見えるかもしれないが, 先に述べたとおり,main関数内のa, bと関数内のa, bは文字は同じでも別物であるら,ポインタの意味合いを考える必要がある.
グローバル変数とは,ソースコードの中でどの関数からも共通して参照・代入が可能な変数である.
様々な解説書や記事に,「グローバル変数はなるべく使わないこと」と記述されていることが多いが,気を付けておけば便利な場合もある.
例えば,数学・物理定数など利用する場合などがそれにあたる.
円周率を考えてみよう. 一つの方法としては,直接与える方法で,double型が約15桁の精度を持っているとすると,
const double pi = 3.14159265358979;
としてもよい.
また,算術関数を用いて定数を作る方法もある.
const double pi = 4.0 * atan(1.0);
と書けばよい. atan(1)は45度,すなわち?/4であるので,これを4倍すれば?が算出できる.
C++言語では,構造体に「変数」だけで無く「関数」も含めることができる「クラス」という新しい概念が追加された.
ごく簡単な例を示しておくので,その概念は理解しておいてほしい.
#include <stdio.h> /* C の標準入出力ヘッダファイル */
#include <iostream> /* C++ の標準入出力ヘッダファイル */
using namespace std;
class Ckeisan /* struct の代わりに class キーワードを使用する. */
{
public: /* <-C++固有のキーワード.変数や関数を「公開」するという意味. */
/* メンバ「変数」.これは構造体と同じ */
int a;
int b;
/* メンバ「関数」. 構造体には無く,クラス特有のもの */
int add(void)
{
return a+b;
}
int sub(void)
{
return a-b;
}
};
int main(void)
{
Ckeisan k; /* 計算クラス.頭文字のCはクラスの意 */
k.a = 10; /* 構造体と同様,Ckeisanクラスのメンバ変数に値を代入 */
k.b = 5;
cout << "add = " << k.add() << endl; /* k.add()は,Ckeisanクラスのメンバ関数呼び出し */
printf("sub = %d\n", k.sub() ); /* k.sub()は,Ckeisanクラスのメンバ関数呼び出し */
}
上のプログラムを作成し,コンパイル・実行して動作を確認してみよう.