このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。
前回はファイルの処理と言うことで getc() 関数や fprintf() 関数 を使って文字の入出力を行いました。 演習最後の問題として、 10 文字ずつ区切って出力するという問題を出しまし た。 それを解く上で、段階的に考えるため、以下のようにさらに二問の問題を解い ていくことにします。
まず、ファイル中の行とは、 '\n' で終る文字列です。 厳密性を考えなければこれは '\n' がいくつあるかを数えるプログラムを書け ば良いです。 したがって、このプログラムは次のようになります。
#include <stdio.h>
main(){
FILE *fh;
int c;
int n;
if((fh=fopen("filename.txt","r"))==NULL){
fprintf(stderr,"ファイルを読み込めませんでした\n");
return 1;
}
n=0;
while((c=getc(fh))!=EOF){
if(c=='\n'){
n++;
}
}
printf("%d 行\n",n);
fclose(fh);
}
では、次の問題です。
入力の種類 | 作業内容 | 次の動作 |
---|---|---|
EOF | 終了処理 | 終了 |
'\n' | 文字数の出力、カウンタのリセット | 繰り返しへ |
普通の文字 | カウンタを増やす | 繰り返しへ |
この分析だと、EOF 以外は繰り返しを変化させる条件ではありません。 従って while の条件には EOF だけを書けば良いことがわかります。 あと、EOF が行の途中に来ることを考えるとうまくありません。 その時はそこまでの文字数を出力するのが自然な動きだと思われます。 従って、EOF で繰り返しが終ったあと、行の途中までの文字数を出力するよう にします。 これらをまとめると次のプログラムになります。
#include <stdio.h>
main(){
FILE *fh;
int c;
int n;
if((fh=fopen("filename.txt","r"))==NULL){
fprintf(stderr,"ファイルを読み込めませんでした\n");
return 1;
}
n=0;
while((c=getc(fh))!=EOF){
if(c=='\n'){
printf("%d文字\n",n);
n=0;
}else{
n++;
}
}
if(n>0){
printf("%d文字\n",n);
}
fclose(fh);
}
このプログラムでも、文字数を数えます。10文字未満の時と、10文字 になった時で動作が変わります。また、改行文字が来ても動作が変わります。 さらに EOF が来ても変わります。 文字として、改行、EOF、その他の文字が来る場合の三通り、一方、10文字以 下と10 文字の二通りで、六通りの場合が存在しますので、表にして動作を考 えます。
その他の文字 | 改行 | EOF | |
---|---|---|---|
10文字より少ない | 文字を表示、カウンタ増やす | 改行、カウンタをクリア | 終了 |
10文字 | 改行、文字を表示、カウンタを1 に | 改行、カウンタをクリ ア | 終了 |
まず、最後まで文字を読み続ける一番外側の while 文に関係あるのは、終了 条件である EOF のみです。 また、10 文字という条件に関係なく改行が来たら改行してカウンタをクリア します。 その他の文字が来た場合ですが、 「10 文字」の条件を 「改行、カウンタをクリア、文字を表示、カウンタを更新」と変えると、ちょ うど 10 文字の時、改行してカウンタをクリアした後は通常と同じ処理で済ま せられます。 以上により、このプログラムは次のようになります。
#include <stdio.h>
main(){
FILE *fh;
int c;
int n;
if((fh=fopen("filename.txt","r"))==NULL){
fprintf(stderr,"ファイルを読み込めませんでした\n");
return 1;
}
n=0;
while((c=getc(fh))!=EOF){
if(c=='\n'){
printf("\n");
n=0;
}else{
if(n>=10){
printf("\n");
n=0;
}
printf("%c",c);
n++;
}
}
fclose(fh);
}
プログラム中で特定の部分をまとめて一つの関数とすることがで きます。 関数を作ると、関数名を指定することにより何度でもその部分を呼び出して実 行することができます。 例えば数を二乗する関数は次のように作れます。
double square(double x){
return x*x;
}
double times(double x, double y){
return x*y;
}
main(){
printf("%f %f ",square(3.0),square(4.0));
printf("%f",times(5.0,6.0));
}
最初の double は返す値の型をしめしています。 次の square は関数の名前を示してます。 カッコの中は引数のリストで、型とこの関数の中で使う変数名を対にします。 もし、二つ以上引数があったら double x, double y と型を省略せずにカンマ で区切って列挙します。 なお、引数がない関数は定義時に void を指定します。 また、関数定義の最後に返す値の型と同じ値を return 文で指定して終ります。 何も返さない関数を定義する時は void を宣言し、 return には引数を指定し ません。
void hello(void){
printf("Hello\n");
return ;
}
なお、今まで main() {} の中に C 言語のプログラムを書いていましたが、
これ自身も関数宣言です。
C 言語では main(){} という宣言は、引数なしを示す void が省略され、また、
返す値の型は int であると言う暗黙の規則があります。
したがって、この main(){} という記述は int main(void){} と等価です。
また、本来は return 命令で整数値を返す必要があります。
main 関数が返す値は起動した OS などに渡されます。今まで return を省略
して値を返しませんでしたが、こうすると OS に返される値は不定になってま
した。
実際は使ってなかったので、問題はありませんでしたが、今後は本来の決め事
に従って、正式な記法で記述して行きます。
なお、 main 関数には起動時にパラメータを渡すために、
int main(int argv, char* args[]){}
という別の定義もありま
すが、この講義では取り上げません。
これを利用すると直角三角形の二辺から斜辺の長さを求めるプログラムは次の ようになります。
#include <stdio.h>
#include <math.h>
double square(double x){
return x*x;
}
int main(void){
double x,y;
scanf("%lf %lf",&x, &y);
printf("直角三角形の斜辺は%f\n",sqrt(square(x)+square(y)));
return 0;
}
Visual Studio .Net では関数ごとに別ファイルを作ることができます。 すると同じ関数を使用する複数のプロジェクトを作ることができます。 その方法を学びます。
関数ごとに別々のファイルにするにはソリューションエクスプローラでプロジェ クトを右クリックして「追加」→「新しい項目の追加」と、新規にプログラム を作る際と同様の手順で新たに .c ファイルを追加します。
単純に関数が別ファイルだとコンパイル時に関数が未定義であるとエラーが出 ます。 そのため、各ファイルには関数の入出力の仕様を書く必要があります。 これをプロトタイプと言います。 上記の square であれば、次のように関数宣言で実際の定義部の代わりにセミ コロンで終るものを書きます。
double square(double x);
これをこの square 関数を使うファイルで必ず宣言します。
多くのファイルで同様の宣言をするのは煩瑣なので、これを一つのファイルに
まとめます。
共通の宣言をまとめたファイルを C 言語では ヘッダファイルと
呼び、拡張子 .h のファイルとして作成します。
そして、ファイルの先頭で #include "ヘッダファイル名"
と ""
記号でファイル名を指定する include 文を書きます。
上記の直角三角形の二辺から斜辺の長さを求めるプログラムをこの手順で関数 毎に分割してみましょう。
#include <stdio.h>
#include <math.h>
#include "square.h"
int main(void){
double x,y;
scanf("%lf %lf",&x, &y);
printf("直角三角形の斜辺は%f\n",sqrt(square(x)+square(y)));
return 0;
}
#include "square.h"
double square(double x){
return x*x;
}
double square(double x);
次に作成した関数をテストするための別のプログラムを追加することを考 えましょう。 別のプログラムを作成して実行することになるので、新たなプロジェクトを作 ることになります。
テストしたい関数の C ファイルや H ファイルをプロジェクトに追加する。 C ファイルは「ソースファイル」フォルダで右クリックして、「追加」→「既 存項目の追加」を選び、該当ファイルを選択する。 H ファイルは「ヘッダファイル」フォルダで右クリックして、「追加」→「既 存項目の追加」を選び、同様に該当ファイルを選択する。
なお、 Windows のエクスプローラを起動し、作成した C ファイルや H ファイルをソリューションエクスプローラの中の新しいプロジェクトの中のソー スファイルフォルダ、ヘッダーファイルフォルダにそれぞれドラッグ&ド ロップすることでも追加できる。
#include ""
を打つ#include ""
の""の真中にカーソルを移動して Ctrl-V を
打つ
#include <stdio.h>
#include "..\最初のプロジェクト名\square.h"
int main(void){
int in[]={0,1,2,3,-1};
int out[]={0,1,4,9,-1};
int i,kekka;
for(i=0;in[i]!=-1;i++){
kekka=square(in[i]);
if(kekka==out[i]){
printf("Ok\n");
}else{
printf("NG\n");
}
}
return 0;
}
C 言語では関数の内部で宣言された変数は、その関数の外部(他の関数など)か らはアクセスできません。 これをローカル変数と言います。 一方、関数の外側でも変数を宣言できます。 これをグローバル変数と言います。 グローバル変数へはどんな関数でもアクセスできます。
#include <stdio.h>
int g; /* グローバル変数 */
int incg(void){
return ++g; /* グローバル変数はどこからでもアクセス可 */
}
int main(void){
int i; /* ローカル変数 */
g=0; /* グローバル変数はどこからでもアクセス可 */
for(i=0;i<5;i++){
printf("%d\n",incg());
}
return 0;
}
なお、グローバル変数と同じ名前の変数をローカル変数として宣言可能です。 おなじ名前になった時は、グローバル変数にはアクセスできず、ローカル変数 にだけアクセスできます。 また、関数の宣言の時に宣言する引数は、値を読み出すことはできますが、そ れは値が与えられたローカル変数なので、書き換えた値は呼び出した側には影 響しません。
但し、グローバル変数の利用には注意が必要です。 グローバル変数は全ての関数からアクセスが可能なので、プログラムミ スなどによりグローバル変数の値がおかしくなった時など、原因の追求が大変 です。
またファイルをまたいでグローバル変数を使用する場合、
extern
と宣言する必要がある。
通常この宣言はインクルードファイルに入れる。
次のプログラムがどのように動くか予想し、また、実際に動かして動作を確か めなさい。
#include <stdio.h>
int i;
int a(int i){
i++;
return i;
}
int b(int j){
i++;
return i;
}
int c(int k){
int i;
i=0;
return i;
}
int main(void){
i=1;
printf("%d\n",i);
printf("%d ",a(i));
printf("%d\n",i);
printf("%d ",b(i));
printf("%d\n",i);
printf("%d ",c(i));
printf("%d\n",i);
return 0;
}
square 関数を利用して、 1 の 2 乗、 2 の 2 乗 ... 10 の 2 乗を表示する プログラムを書きなさい。
double square(double x){
return x*x;
}
なお、square が double 型の変数を取り扱うことが都合が悪い場合、別の square 関数(isquare) を定義しても良い。
square 関数を利用して、円の面積を求める関数 circle を作りなさい。
double square(double x){
return x*x;
}
double circle(double x){
/* この部分を作る */
}
そして、半径が 1, 2, ... ,10 の円の面積を表示するプログラムを書きなさい。
グローバルに定義されている配列 double
x[]={1.0,3.0,5.0,2.0,-1.0}
のうち -1.0 は番兵とする。
void larger(double y)
を作りなさい。
また、この larger をテストするため、
larger に x をそれぞれ 3.2 倍、 5.0 倍したものを表示させる main 関数
を書き、動作を確かめなさい。
void times(double y)
を作りなさい。
また、 times に x を 3.2 倍させ、さらに 5.0 倍させる main 関数を
書き、動作を確かめなさい。
なお、表示には larger(1.0) を使用して良い。
なお、関数ごとにファイルに分割する場合、次のヘッダファイルを使用してく ださい。
extern double x[];
void larger(double y);
void times(double y);
そして、 main 関数の直前で配列と初期化を宣言してください。