プログラミング演習3
Cプログラミング
-第1回資料&課題
花泉 弘
この回の目標
1.テキストファイルからのデータの読み出し
・ファイルのopenとclose
・1文字ずつの入力
・1行ずつの入力
C言語に限らず、ファイルからデータを読み込む場合、必要になるのは、 ・ファイル名と場所(どのディレクトリにあるか) ・プログラム上でデータを受け取るバッファ(変数の型に注意) ・読み込み方(一気に全部読むのか、少しずつ(例えば1行ずつ)読むのか) といったことです。 C言語でのファイル入出力はファイルポインタを介して行われます。 ファイルポインタは、今どこまで読み込んだかを指し示しています。 ファイルとファイルポインタとの対応付けは、オープン文によって行い、 その解放はクローズ文によって行います。以下の関数を使います。 #include <stdio.h> //使用時に宣言すべきインクロード文 FILE *fopen(char *path, char *mode); //ファイルのオープンint fclose(FILE *fp); //ファイルのクローズ
int fgetc(FILE *fp); //ファイルからの1文字読み込み Int feof(FILE *fp); //エンド・オブ・ファイルの検出
C プログラムの基本構造
/************************************ Programming 3H
Example 0-1:整数を読み込んで書き出す。 Produced by Taro Hosei on 26 May, 2014 ************************************/ #include <stdio.h>
int main(int argc, char *argv[]) { int k; printf(“enter k=“); //入力を促す scanf(“%d”, &k); //kを入力 printf(“k=%d¥n”, k); //kを出力 } 最初にはプログラムに関するコメントを書きます。 作成目的(何をするプログラムか)と作成日、 作成者は必須です。 include 文はここに書きます。 main 文です。コマンドラインから入力を得るため argcとargv[]とを定義しておきます。 argc にはコマンドライン引数の数が、argv[]にはその 文字列が格納されており、プログラム内で使用 できます。 使用する変数と型の定義 処理を行う関数群 それぞれの文は “;”セミコロンで終わります。 コメントは入れすぎるぐらいに入れましょう。 コメント以外では全角文字を使ってはいけません。 C プログラムの構造は、上にあるように、コメント、include文、main文、変数宣言、 関数群から成り、各行はセミコロンで終わります。全体としてはmain( ){ } のように なっています。このほか、自分で関数を作ることも可能です。 Intk.c として保存
例題1‐1
ファイルが存在するかどうか(あるはずのファイルのスペルミスも
含めて)を確認し、
ないときには 「 ファイル名 : not found」を出力し、
存在するときには「ファイル名:found」を出力する。
方針:
コマンドラインからファイル名を入力し、fopen() を用いて
そのファイルのオープンを試みる。もし、存在しなければ、返り値は
NULLとなるので、存在が判定できる。
ソースコードの作成とコンパイル、動作確認
①work内に例題用のディレクトリを作成して 移動。(以下ex101と読み替えてください) ②ファイル名をexist.cとして、emacs で開く ③ソースコードを入力して保存 ④コンパイル ⑤起動 → Usage の表示 ⑥存在するファイルを入力 ⑦スペルミス ⑧コマンドとして~/prj3/binに登録 ① ② ③ ④ ⑤ ⑥、⑦ コマンドライン上のファイル名は ここに入る 上を入力してから [control] + x, [control] + s を行うと書き込まれる。★ printf(“ …”)とfprintf(stderr, “…”)について
linux上のgccでは、文字等を出力するのに、標準出力 stdout と標準エラー出力 stderr とを 持っていて区別して使うことができます。混在させて使うと見た目にはわかりませんが、 出力のリダイレクトの利用を考えると、区別して使う方が便利です。特に、画像を表示させる 際に、画像 viewer が標準入力 stdin からの入力に対応していると、画像にかかわる
出力をprintf()で、それ以外を fprintf() を用いて区別すると作業効率が上がります。 なお、fprintf(stdout, “…”) はprintf(“…”) と同じ動作をします。
$ cat image.jpg | xv - & ↓
を思い出してください。このやり方をパイプといいますが、画像データをそのまま標準出力に 出力し、それをファイルを経ることなく、直接 xv の標準入力に入力します。xv の後ろの – は、 「標準入力から入力する」ことを指定するオプションです。
例題1‐2
指定したテキストファイルのバイト数を数える
方針:
コマンドラインからファイル名を入力し、fopen() を用いて
そのファイルをオープンする。存在すれば、1文字ずつ読みだして
その数を数える。
①work内に例題用のディレクトリを作成して 移動。(ex102と読み替えてください) ②ファイル名をfilesize.cとして、emacs で開く ③ソースコードを入力して保存 ④コンパイル ⑤動作確認 → 462バイト ⑥確認のため「$ ls -l」を行った結果→461バイト 1バイトの差は何が原因でしょう? ① ② ④ ⑤ ⑥ ③
ソースコードの作成とコンパイル、動作確認
例題1‐3
指定したテキストファイルの行数を数える
方針:
コマンドラインからファイル名を入力し、fopen() を用いて
そのファイルをオープンする。存在すれば、1文字ずつ読みだして
行くが、行の終わりのコードが’¥n’(数値としては0x0a)であること
から、その数を数える。
ソースコードの作成とコンパイル、動作確認
①work内に例題用のディレクトリを作成して 移動。(ex103と読み替えてください) ②ファイル名をfilesize.cとして、emacs で開く ③ソースコードを入力して保存 ④コンパイル ⑤動作確認 → 24ライン emacsで確認すると、下の帯に 「lines.c All L24」と表示されており、 正しいことがわかります。例題1‐2とは異なり 正しい値が得られていますが、例題1-2での 1バイトの差を説明できますか? ① ② ④ ⑤ ③ 読み込んだ文字が’¥n’の時だけ カウントしている例題1‐4
指定したテキストファイルから、指定した個数の文字を読みだす。
方針:
コマンドラインからファイル名と読み出す文字数を入力する。
fopen() を用いてそのファイルをオープンする。存在すれば、
1文字ずつ読みだして指定した個数分をバッファに蓄え、
main関数に戻る関数readn()を作成する。readn()の戻り値は
正常読み出し時に0、EOFが検出されたら -1を返すように設定する。
*余裕を持たせて長めにある長さのバッファ内に、それよりは短い
データが格納された場合、どこがデータの終端かがわからなくなる。
C では、最後のデータの後ろに 0x00 を書き込んで明示的にデータ
の終わりがわかるようにしています。個々の例題のように文字列を
扱うときには、必ず文字列データの最後に 0x00 を加えてください。
ソースコードの作成とコンパイル、動作確認
② ④ ⑤ ③ ①work内に例題用のディレクトリを作成して 移動。(ex104と読み替えてください) ②ファイル名をreadn.cとして、emacs で開く ③ソースコードを入力して保存 ④コンパイル ⑤動作確認 プロトタイプ宣言 最後に 0x00 を書き込んで文字列は ここまでであることを明示します。プロトタイプ宣言
C では、作成した関数を呼び出す際に、呼び出す関数よりも呼び出される関数が先に 定義されている必要がありました。あまりに不便なので、main 関数の前に ①戻り値の型、②関数名、③引数リスト だけを宣言しておくと順序は問われないように改善されました。前のページの例では、 main 文の前にint readn(FILE *fp, char *buf, int n);
が書いてありますが、これがプロトタイプ宣言です。 ①戻り値の型 : int
②関数名 : readn( )
③引数リスト : FILE *fp, char *buf, int n ということになります。
なお、引数を使用しない関数であれば、 int func(void);
のように、引数リストの位置にはvoid だけ書いておきます。
例題1‐5
指定したテキストファイルから、指定した個数の文字を読みだす。
main関数をreadnmain.c , 関数readn()をreadn.cと別に作成する。
*gccにはコンパイルの機能とリンカーの機能とを有するので、
別々にコンパイルしてリンクすることを学ぶ
*関数を使う場合に、call by reference と call by value との違いを
理解する
方針:
コマンドラインからファイル名と読み出す文字数を入力する。
fopen() を用いてそのファイルをオープンする。存在すれば、
1文字ずつ読みだして指定した個数分をバッファに蓄え、
main関数に戻る関数readn()を作成する。readn()の戻り値は
正常読み出し時に0、EOFが検出されたら -1を返すように設定する。
コンパイルとリンク
コンパイル:ソースファイル(.c)を参照しながら、オブジェクトファイル(.o )を作成する。 内容は、ソースコードをマシン後に翻訳したもの。ただし、このままでは 動かない。未定義の関数があっても後で出てくるだろうと仮定して作業が 進む。 リンク:実際にプログラムが動くのに必要なチェックを行う。複数の .o ファイルの中 から参照すべき関数を見つけたりする操作。これによって、実行可能 ファイルが作成される。 $ gcc source.c -o source -lm ↓ とすると、コンパイルとリンクが続けて行われる。文法的なエラーはコンパイル時に でるが、(打ち間違いなどで)見つからない関数が合ったりするとリンク時にエラーが出る。 コンパイルのみ行う場合は次のページの例のように –c を付ける。$ gcc -c source1.c –o source1.o ↓ $ gcc -c source2.c –o source2.o ↓
リンクを行って、実行可能ファイル source_exec を作成するには $ gcc -o source_exec source1.o source2.o -lm ↓
ソースコードの作成とコンパイル、動作確認
② ④ ⑤ ③-1 ①work内に例題用のディレクトリを作成して 移動。(ex105と読み替えてください) ②ファイル名を指定して、emacs で開く ③ソースコードを入力して保存 ④それぞれをコンパイル、統合してリンク ⑤動作確認 ③-2ソースファイルの書き方について
ここで想定しているソースファイルの書き方について、補足します。
下図のような構造のプログラムを仮定します。すなわち、mianはfunc01とfunc02とをよび、 さらに、func02はfunc03をよぶわけです。
このとき、4つの関数をそれぞれ別の名前で xxxx_main.c, xxyy_01.c, xyyy_02.c, yyyy_03.c (名前は単なる例です)作成してあることを想定しています。それぞれのファイルの先頭には それぞれに必要なinclude文を書き、そのファイル内で定義していない関数については プロトタイプ宣言が必要です。これらのファイルを別々にコンパイルし、リンクして1つに まとめ実行可能ファイルを作ります。 なお、お勧めはしませんが、ひとつの .c ファイルの中に、include文、すべてのプロトタイプ 宣言、main文、関数1、関数2、・・・関数Nをまとめて書いておくことも可能です。この場合は、 $ gcc example.c -o example –lm ↓ のようにしてコンパイルとリンクを同時に行うことも可能です。ただし、大規模なプログラム 作成には向きません。
int main() int func01()
unsigned char *func02()
C は call by value、変数内の数値そのものを渡す
例5で、関数readn() について見ていきます。main 関数からは、 c = readn(fp, buf, n); のようによんでいます。call by value では、数値そのものを関数に渡すだけですので、 関数内で処理した結果をどのようにしてmain関数に戻したらよいでしょうか。戻り値で 返すことも可能ですが、大量のデータを戻すのには不向きです。 そのような場合、Cでは、その変数が格納されているメモリ上の位置(アドレス)を渡して やります。呼ばれた関数側では、数値として受け取ったメモリ上のアドレスに直接 アクセスして計算結果を格納します。main側から見れば、関数から戻ってきた後に その変数を見ると、内容が書き換わっている、ということになります。 このように、数値そのものではなくそれらのアドレスを表すものをポインタとよび、 そのための変数をポインタ変数とよび、宣言時には*を付けて表します。なお、配列は すでにポインタとして扱われています。char cc, buf[1024], *pa;
と宣言した時、cc 用に1バイト、buf 用に1024バイト分の領域、およびポインタ pa 用の 領域が確保されますが、buf 用の領域の先頭アドレスは buf に格納されています。 変数 cc のアドレスを得るには、演算子 & を用いて pa = &cc; のように、ポインタ変数で受けます。逆に、ポインタで示される変数に書き込むには、 演算子 * を用いて *pa = 0x0a; のようにします。この例だと、cc = 0x0a になっています。
ポインタのつづき
宣言時には、*をつけます。 int *k, c , d ; 変数使用時には、他の変数とは異なり、変数の値ではなくそれが格納されている番地( アドレス)が代入されます。 今、変数 c には5が入っていたとします。変数 c の番地は &c で求めることができます。 k = &c; とすることによって、k には c の番地が格納されました。プログラム中でポインタに*を 付けると、今度はそのポインタ変数が指す番地に格納されている数値を読み出すことが できます。ここの例では、 d = *k; とすることで変数 d には5が格納されます。実行結果
例題1‐6 ポインタの動作理解
宣言、他の型でも同じです pa に変数 cc のアドレスを得て、 cc に値を代入 cc と *pa とが同じ値になっていることがわかります *pa に値を代入すると cc の値も 変わっていることがわかります。 配列の場合は名前 そのものがポインター ポインタ変数は なかなか便利 ex106 を作成し、その中に 作成する。課題1-1
以下のコードの動作を詳しく説明しなさい。
最後に総文字数と行数とを出力するように変更を加えて、
動作確認を行いなさい。
p101 のようなディレクトリを
作成してその中で、作業すること。
#include <stdio.h> #include <stdlib.h>int main(int argc, char *argv[]) {
int c; FILE *fp; if(argc < 2){
fprintf(stderr, “Usage : %s input_file.txt¥n”, argv[0]); return EXIT_FAILURE;
}
if((fp = fopen(argv[1], “r”)) == NULL){
fprintf(stderr, “file not found : %s¥n”, argv[1]); return EXIT_FAILURE; } while( !feof(fp) ){ c = fgetc(fp); printf(“%c”, (char)c); } fclose(fp); return EXIT_SUCCESS; }
課題1-2
次の仕様に基づいて、関数get_line()を作成し、get_line.c として保存しなさい。 コンパイルはメイン関数と別々に行って、リンク時に結合させなさい。
ソースコードなど、確認できるファイルを読み込ませて、動作確認も行いなさい。 ・形式 int get_line(FILE *fp, char *pa, int cc)
FILE *fp はファイルに対応するファイルポインタです。char *paは、読み込んだ1行分の データを保存するバッファの先頭を指すポインタです。int ccは区切り文字のasciiコードを 示します。1行ずつ読み込む場合には、上で示したようにcc = 0x0a;(もしくはcc = ‘¥n’;)と します。読みだしたデータがccに一致する場合には、呼び出し側に戻るわけですが、 その際の戻り値は、EOFの場合これ以上データがないので -1 とし,そうでない場合は 0 と してください。p102 のようなディレクトリを作成し、その中で作業してください。 ヒント: fopen()やfclose()、および読み込んだ文字列を格納するバッファbuf[1024]は呼び出し側で 定義します。型はchar で、1024という数値は一例です。