Download : sample-001.c
/* * 2019/12/20 sample-001.c * * 局所変数の領域 */ #include <stdio.h> /* * sub1 */ void sub1(void) { int i; /* 関数 sub1 の中だけで有効 */ i = 123456; printf ( "sub1 : &i = %p\n", &i ); printf ( "sub1 : i = %d\n", i ); } /* * sub2 */ void sub2(void) { int j; /* 関数 sub2 の中だけで有効 */ /* 「当然(?)」 sub1 の i とは無関係 */ printf ( "sub2 : &j = %p\n", &j ); printf ( "j = %d\n", j ); } /* * main 関数 */ int main ( void ) { sub1(); sub2(); return 0; }
$ ./sample-001.exe sub1 : &i = 0x7fffbaefabcc sub1 : i = 123456 sub2 : &j = 0x7fffbaefabcc j = 123456 $
Download : sample-002.c
/* * 2019/12/20 sample-002.c * * ブロック内の局所変数 * 関数の本体もブロック * 関数の引数は、(例外的に..) 関数の本体と同じ扱い */ #include <stdio.h> /* * sub */ void sub(int arg) { int var = arg; printf ( "Level 1-1: arg = %d, var = %d\n", arg, var ); { /* 新しいブロック */ int var = 1; printf ( "Level 2-1: arg = %d, var = %d\n", arg, var ); arg = arg + 100; var = var + 100; printf ( "Level 2-2: arg = %d, var = %d\n", arg, var ); } printf ( "Level 1-2: arg = %d, var = %d\n", arg, var ); { int var = 2; printf ( "Level 2-3: arg = %d, var = %d\n", arg, var ); { int var = 10000; int arg = 20000; printf ( "Level 3-1: arg = %d, var = %d\n", arg, var ); var = var + 100000; arg = arg + 100000; printf ( "Level 3-2: arg = %d, var = %d\n", arg, var ); } printf ( "Level 2-4: arg = %d, var = %d\n", arg, var ); } printf ( "Level 1-3: arg = %d, var = %d\n", arg, var ); } /* * main 関数 */ int main ( void ) { sub(1234); return 0; }
$ ./sample-002.exe Level 1-1: arg = 1234, var = 1234 Level 2-1: arg = 1234, var = 1 Level 2-2: arg = 1334, var = 101 Level 1-2: arg = 1334, var = 1234 Level 2-3: arg = 1334, var = 2 Level 3-1: arg = 20000, var = 10000 Level 3-2: arg = 120000, var = 110000 Level 2-4: arg = 1334, var = 2 Level 1-3: arg = 1334, var = 1234 $
Download : sample-003.c
/* * 2019/12/20 sample-003.c * * 大域変数 */ #include <stdio.h> /* * 大域変数の宣言(定義) */ int gvar = 1234; /* 大域変数の宣言(定義) */ /* * sub1 */ void sub1(void) { printf ( "Sub1 1 : gvar = %d\n", gvar ); gvar = gvar + 10; printf ( "Sub1 1 : gvar = %d\n", gvar ); } /* * sub2 */ void sub2(void) { printf ( "Sub1 2 : gvar = %d\n", gvar ); gvar = gvar + 100; printf ( "Sub1 2 : gvar = %d\n", gvar ); } /* * main 関数 */ int main ( void ) { printf ( "Main 1 : gvar = %d\n", gvar ); gvar = gvar + 1; printf ( "Main 2 : gvar = %d\n", gvar ); sub1(); printf ( "Main 3 : gvar = %d\n", gvar ); sub2(); printf ( "Main 4 : gvar = %d\n", gvar ); return 0; }
$ ./sample-003.exe Main 1 : gvar = 1234 Main 2 : gvar = 1235 Sub1 1 : gvar = 1235 Sub1 1 : gvar = 1245 Main 3 : gvar = 1245 Sub1 2 : gvar = 1245 Sub1 2 : gvar = 1345 Main 4 : gvar = 1345 $
Download : sample-004.c
/* * 2019/12/20 sample-004.c * * ファイルにデータを書き出すプログラム */ /* * ファイルの内容の読み書きは * いったん、ファイルポインターを入手し、 * ファイルポインター経由で、操作を行う必要がある。 * 手順 * ファイルポインタを入手 * ファイルを開く(open) * ファイル操作(情報の R/W) * ファイルポインターを指定して、r/w の関数を呼ぶ * ファイルの利用が終わったら、ファイルポインターを開放 * ファイルの閉じる(close) */ /* ファイル abc.dat に、"abc" + 改行の 4 文字(4 byte) の 情報を書き込む */ #include <stdio.h> int main(int argc, char *argv[]) { FILE *fp; /* これから開く、ファイルへのファイルポインターを保存する */ /* 「*」がついているので、「ポインター型」である事が解る */ /* 幾つのファイルを開くは、予め解からない */ /* 動的なデータ型 -> 裏で alloc がされている */ fp = fopen( "abc.dat", "w" ); /* ライブラリ関数 fopen を利用して、 ファイル "abc.dat" に対するファイルポインターを入手 */ /* ファイルがない場合は、自動的に作られる */ /* すでにファイルがある場合は、以前のファイルの内容は消される */ /* 何等かの理由で、ファイルのオープンに失敗した場合は、 NULL が値として帰る */ if ( fp != NULL ) { /* 無事オープンできたらデータを書き込む */ /* データを書き込む */ fprintf ( fp, "abc\n" ); /* ライブラリ関数 fprintf を利用して、 最初の引数に指定したファイルポインターに 対応するファイルに、print する */ fclose ( fp ); /* ライブラリィ関数 fclose を用いて、ファイルをクローズ */ /* 後処理を行い、(C 言語のレベルでは..) 記録を完了させる */ /* (free の時と同じように..) いったん close した、 ファイルポインタは使えなくなる もし、同じファイルを操作したければ、 もういちど、ファイルをオープンして、 別のファイルポインタを入手する必要がある */ } else { printf ( "ファイル(abc.dat)のオープンに失敗しました\n" ); } return 0; }
$ ./sample-004.exe $
/* * 20191220-02-QQQQ.c * 二つのファイルを開いて、そのファイルの違う場所を表示する * ファイル名はコマンドライン引数で指定する */ #include <stdio.h> /* * main */ int main ( int argc, char *argv[] ) { if ( argc != 3 ) { /* argc == 3 の時、引数が二つ */ printf ( "ファイル名を二つ指定して下さい。\n" ); } else { FILE *fp1; /* 変数宣言はブロックの先頭で行う事ができる */ /* ブロック内だけで有効 */ /* !!! 関数の本体もブロックなので、先頭で変数宣言ができる */ if ( ( fp1 = fopen ( argv[1], "r" ) ) == NULL ) { printf ( "ファイル(%s)を開く事ができませんでした。\n", argv[1] ); } else { FILE *fp2; if ( ( fp2 = fopen ( argv[2], "r" ) ) == NULL ) { printf ( "ファイル(%s)を開く事ができませんでした。\n", argv[2] ); } else { int position; /* 違ったところ */ int ch1; /* 二つのファイルからの入力 */ int ch2; for ( position = 0; (ch1 = fgetc (fp1)) != EOF; position++ ) { /* 一つめのファイルから文字を一つ入力 */ ch2 = fgetc( fp2 ); /* 二つめのファイルから文字を一つ入力 */ if ( ch1 != ch2 ) { /* 入力した文字が違っていたら.. */ break; /* break で for を抜ける */ } } /* ここにくるのは、次の二つのパターン */ /* ch1 が EOF */ /* ch1 != ch2 でのブレーク */ /* ch2 が EOF の場合 */ /* ch2 が EOF でない場合 */ if ( ch1 == EOF ) { /* ch2 はまだよみこんでないので.. */ ch2 = fgetc( fp2 ); /* 同じ文字数だけ読みこむ */ } if ( ch1 == ch2 ) { /* 一致したら、ともに EOF : ファイル終了 */ printf ( "二つのファイル(%s,%s)は同じ内容です。\n", argv[1], argv[2] ); } else if ( ch1 == EOF ) { /* ch2 != EOF => 二つ目はまだ、データが残っている */ printf ( "ファイル(%s) の方がサイズが大きいです。\n", argv[2] ); } else if ( ch2 == EOF ) { /* ch1 != EOF になっていない => 一つ目の方が長い */ printf ( "ファイル(%s) の方がサイズが大きいです。\n", argv[1] ); } else { /* ともに EOF でなく、しかも、文字が違う */ printf ( "%d byte 目で %s は %c, %s は %c という違いがありました。\n", position, argv[1], ch1, argv[2], ch2 ); } fclose ( fp2 ); } fclose ( fp1 ); } } return 0; }
#include <stdio.h> int main(int argc, char *argv[]) { int iary[10]; printf ( "iary = %p, &iary[5]=%p, iary + 5 = %p\n", iary, &iary[5], iary + 5 ); return 0; }
#include <stdio.h> int main(int argc, char *argv[]) { int i; /* 1 byte +-------+ &i => | | +-------+ | | +-------+ | | +-------+ | | +-------+ */ i = 0x12345678; /* C 言語では、 0x を数字列の前に先行する事により、 16 進数を表す事ができる */ /* 1 byte +-------+ &i => |0x78 | &i -----+ <== (char *)&i 1 byte 分 +-------+ | ((char *)&i)+1=> |0x56 | | +-------+ | int * なので 4 byte |0x34 | | +-------+ | |0x12 | <-------+ +-------+ &i + 1 => |??? | +-------+ |??? | +-------+ |??? | +-------+ |??? | +-------+ */ printf ( "i = %x\n", i ); /* 書式指定で %x とすると 16 進数として表示 */ printf ( "0x12345678 = %x\n", 0x12 * 0x1000000 + 0x34 * 0x10000 + 0x56 * 0x100 + 0x78 * 0x1 ); printf ( "&i = %p, ((char *)&i)+1=%p\n", &i, ((char *)&i)+1 ); /* &i -> int * &i + 1 -> アドレス値(番地番号) は +4 (+sizeof(int)) される (char *)&i -> char * アドレス値そのものは変化しない (char *)&i + 1 -> char * アドレス値は +1 (+sizeof(char)) される */ printf ( "*&i=%x\n", *&i ); /* printf ( "*&i=%x\n", i ) と同じ */ printf ( "*((char *)&i)=%x\n", *((char *)&i) ); printf ( "*(((char *)&i)+1)=%x\n", *(((char *)&i)+1) ); return 0; }
#include <stdio.h> typedef struct { int x; int y; int z; } Point3D; /* xy 平面に対して、面対称な点を求める関数 => z 座標だけ、符号が反転する */ Point3D xy_mirror( Point3D pt ) { pt.z = - pt.z; /* -X <-> (-1)*X の事 */ return pt; } int main(int argc, char *argv[]) { Point3D pa; Point3D pb; pa.x = 1; pa.y = 2; pa.z = 3; pb = xy_mirror ( pa ); printf ( "pa=(%d, %d, %d) -> pb=(%d,%d,%d)\n", pa.x, pa.y, pa.z, pb.x, pb.y, pb.z ); return 0; }
#include <stdio.h> /* xy 平面に対して、面対称な点を求める関数 => z 座標だけ、符号が反転する */ void xy_mirror( int *pt ) { /* int pt[3] <-> int pt[] <-> int *pt */ /* 仮引数変数 pt (整数へのポインタ型)には、 main の 配列 pa の先頭へのポインタ値が代入されている */ pt[2] = - pt[2]; /* 間接参照 * を利用して、配列要素が変更されている */ /* main 関数の配列 pa が関数内で共有できている */ } int main(int argc, char *argv[]) { int pa[3]; /* pa = (pa[0],pa[1],pa[2]) */ pa[0] = 1; pa[1] = 2; pa[2] = 3; printf ( "pa=(%d, %d, %d)\n", pa[0], pa[1], pa[2] ); xy_mirror ( pa ); /* pa (配列名):配列の最初の要素へのポインタ値 !!! 配列の要素 (pa[0], pa[1], pa[2]) の値が渡されているわけではない => 直接配列 pa の z 要素(pa[2])が書き換えられているだけ x 要素 (pa[0]) は何もされていない !!! 関数とのやり取りで、コピーされる情報が少ないので、 !!! 効率が良い !!! <= リスク : 同じ領域を共有/使いまわすので、ミスが起きやすい */ printf ( "pa=(%d, %d, %d)\n", pa[0], pa[1], pa[2] ); return 0; }
#include <stdio.h> #include <malloc.h> /* malloc/free を利用する場合 */ int main(int argc, char *argv[]) { int *iary = (int *)malloc( sizeof(int) * 10 ); /* iary[10] と実質同じだが、動的に領域を確保 */ int i; for ( i = 0; i < 10; i++ ) { iary[i] = i * i; } for ( i = 0; i < 10; i++ ) { printf ( "iary[%d] = %d\n", i, iary[i] ); } free( iary ); /* 動的に確保したものは、最後に free で開放する */ return 0; }
#include <stdio.h> #include <malloc.h> /* malloc/free を利用する場合 */ int main(int argc, char *argv[]) { int *iary; int n; int i; printf ( "データサイズを入力してください : " ); scanf ( "%d", &n ); iary = (int *)malloc( sizeof(int) * n ); /* 領域 (整数型の配列) のサイズを、実行時に決める事ができる */ /* => 動的領域確保の利点 */ for ( i = 0; i < n; i++ ) { iary[i] = i * i; } for ( i = 0; i < n; i++ ) { printf ( "iary[%d] = %d\n", i, iary[i] ); } free( iary ); /* 動的に確保したものは、最後に free で開放する */ return 0; }
#include <stdio.h> /* unix ( linx [Ubuntu] ) の cat コマンドと同様な振る舞いをする プログラムを作成してみる Version 1 */ int main(int argc, char *argv[]) { int ch; /* 入力される文字コードを保存する */ /* getchar() が返す EOF も記録できるように int 型にする必要がある */ /* getchar() : キーボードから 1 文字に入力し、その文字コードを返す puchar(ch) : 画面に引数で指定しれた文字コードに対応する文字を出力する */ /* cat は、キーボードから入力された文字を、画面に出力する getchar() したものを putchar()する事を繰り返す => 「繰り返し」終了条件 => 「入力の終わり」を表すコードを getchar() が返した時 EOF ( End Of File ) が「入力の終わり」 # unix(linux[ubuntu]) Ctrl-D をキーボードから入力すると # EOF になる # 実は、EOF は、文字コードではなく、 # 文字コードにはならない、整数値 # 変数 ch は、(文字型 char ではなく) 整数型にする */ while ( ( ch = getchar() ) != EOF ) { /* 入力が EOF (入力終わり)に達するまで繰り返し */ /* C 言語では、「=」は、「代入演算子」で、「変数 = 式」もまた、「式」と同じ 値を持つ式になる */ putchar ( ch ); /* 入力された文字を出力するだけ */ } return 0; }
#include <stdio.h> /* unix ( linx [Ubuntu] ) の cat コマンドと同様な振る舞いをする プログラムを作成してみる Version 2 */ void cat( FILE *out, FILE *in ) { int ch; /* stdin 標準入力を表すファイルポインターを保持する大域変数 stdout 標準出力を表すファイルポインターを保持する大域変数 [new word] ファイルポインター 「ファイルの内容を指しているポインター」 !!! これを利用して(間接的に..)ファイルに保存されているデータを参照できる !!! 「間接的」に「参照」できるから「ポインター」だが !!! C 言語の意味での「ポインタ」.. 「*stdin」が「入力データ」に対応するわけでではない !!! stdin を利用して、文字をと出したい場合は、fgetc(stdin)とする !!! stdin そのものは、どんな値をもつかというと !!! ファイルを操作するために必要な情報を集めた、ファイル構造体へのポインター値を保持 !!! !!! ファイルの管理情報は、OS 毎に異なる !!! !!! => ファイル構造体の定義も異なる !!! !!! 構造体へのポインタ値は !!! !!! (ポインタ値なので) 実行時は、(構造体が違っていても..)その要素を !!! !!! 要素を参照しないかぎり、気にしなくてもよいxs ファイルに結ぶついていて、 ライブラリがその結びついているファイルを操作するための情報をもつ プログラマは、「ファイルとの結びつけ」と、「操作をするファイルの区別」をするために、 ファイルポインタを扱えばよい 大域変数 プログラム内でいつでも参照できる変数 */ while ( ( ch = fgetc(in) ) != EOF ) { /* fgetc(stdin) <-> getchar で標準入力 */ fputc ( ch, out ); /* fputc ( ch, stdout ) <-> putchar で標準出力 */ } } int main(int argc, char *argv[]) { /* ファイルポインタを明示化した => ファイルポインタを操作する事が可能 */ cat( stdout, stdin ); return 0; }
#include <stdio.h> /* unix ( linx [Ubuntu] ) の cat コマンドと同様な振る舞いをする プログラムを作成してみる Version 3 引数で指定されたファイル名を画面に出力する Version 2 で、ファイルポインターを指定すれば、そこから入力された Version 3 では、引数で指定されたファイル名を利用して、 そのファイルに結びついた、ファイルポインターを与えればよい */ void cat( FILE *out, FILE *in ) { int ch; while ( ( ch = fgetc(in) ) != EOF ) { /* fgetc(stdin) <-> getchar で標準入力 */ fputc ( ch, out ); /* fputc ( ch, stdout ) <-> putchar で標準出力 */ } } int main(int argc, char *argv[]) { FILE *in; /* argc はコマンドライン引数の個数 + 1 になっている 常に 「argc > 0 」が成立 argv は、コマンドライン引数を空白文字で区切った、「文字列」の配列 特に、 argv[0] はコマンド名 argv[1] ? argv[argc-1] は引数 argv[argv] には NULL が入っている */ if ( argc > 1 ) { /* 少なくても 1 つのコマンドライン引数が指定されている */ /* 一つ目の引数 argv[1] が、ファイル名であると想定する */ in = fopen ( argv[1], "r" ); /* ライブラリ関数 fopen は、 第一引数で指定された文字列をファイル名として、 そのファイルに対応するファイルポインターを返す 第二引数は、オープン(ファイルを開く)するモードを示す文字の列 r : read (読み込みモード) # この文字は複数指定できるので、文字列として与える */ /* ファイルをリードモードで開く場合は、 すでに存在するファイルの場合だけ、適切なファイルポインタが返る 指定されたファイルが存在しないか、あるいは(何等かの利用であるが、)読めない場合 は NULL が返る */ if ( in == NULL ) { printf ( "ファイル(%s)はオープンできません\n", argv[1] ); } else { cat ( stdout, in ); /* 標準出力の代わりに、in を使う */ fclose( in ); /* ファイルの利用が終わったら fclose する */ /* ファイル構造体は動的に確保されてる .. */ } } else { /* 引数がなければ、そのまま標準入力 */ cat( stdout, stdin ); } return 0; } /* FILE I/O の API ファイルをオープンして、ファイルポインタを入手 fopen そのファイルポインタを経由して、I/O を行う fputc, fgetc, fprintf, fscanf, etc.. 最後に、そのファイルポインタを閉じる fclose */
#include <stdio.h> /* unix ( linx [Ubuntu] ) の cat コマンドと同様な振る舞いをする プログラムを作成してみる Version 4 引数が複数ある場合は、それを(続けて)すべて表示する */ void cat( FILE *out, FILE *in ) { int ch; while ( ( ch = fgetc(in) ) != EOF ) { /* fgetc(stdin) <-> getchar で標準入力 */ fputc ( ch, out ); /* fputc ( ch, stdout ) <-> putchar で標準出力 */ } } int main(int argc, char *argv[]) { FILE *in; int i; /* argc はコマンドライン引数の個数 + 1 になっている 常に 「argc > 0 」が成立 argv は、コマンドライン引数を空白文字で区切った、「文字列」の配列 特に、 argv[0] はコマンド名 argv[1] ? argv[argc-1] は引数 argv[argv] には NULL が入っている */ if ( argc > 1 ) { for ( i = 1; i < argc; i++ ) { /* 引数の個数だけ繰り返す */ in = fopen ( argv[i], "r" ); if ( in == NULL ) { printf ( "ファイル(%s)はオープンできません\n", argv[1] ); } else { cat ( stdout, in ); /* 標準出力の代わりに、in を使う */ fclose( in ); /* ファイルの利用が終わったら fclose する */ /* ファイル構造体は動的に確保されてる .. */ } } } else { /* 引数がなければ、そのまま標準入力 */ cat( stdout, stdin ); } return 0; } /* FILE I/O の API ファイルをオープンして、ファイルポインタを入手 fopen そのファイルポインタを経由して、I/O を行う fputc, fgetc, fprintf, fscanf, etc.. 最後に、そのファイルポインタを閉じる fclose */
/* cp コマンドの機能 cp ファイル1 ファイル2 => ファイル1 の内容を ファイル 2 に書き込む */ #include <stdio.h> void cat( FILE *out, FILE *in ) { int ch; while ( ( ch = fgetc(in) ) != EOF ) { fputc ( ch, out ); } } int main(int argc, char *argv[]) { /* 引数は、必ず 2 つ指定してもらう事にする */ FILE *in; FILE *out; if ( argc != 3 ) { /* argv == 3 の時は、引数が二つ指定されている */ printf ( "引数を二つ指定してください\n" ); } else { /* argv[1] -- コピー元のファイル名 argv[2] -- コピー先のファイル名 */ if ( ( in = fopen ( argv[1], "r" ) ) != NULL ) { /* 読み込みのファイルを開く事ができた */ if ( ( out = fopen ( argv[2], "w" ) ) != NULL ) { /* 出力の場合は、"w" : write mode で開く */ /* write mode の場合、すでに同名のファイルがある場合、 そのファイルの中身は失われる */ cat ( out, in ); /* ファイルのコピー */ fclose ( out ); } else { printf ( "ファイル(%s)を開く事ができませんでいた\n", argv[2] ); } fclose ( in ); } else { printf ( "ファイル(%s)を開く事ができませんでいた\n", argv[1] ); } } }
2019/12/20 前回(2019/12/13)の内容 ポインター値 : メモリモデルのアドレスの抽象化 # 単なるアドレス値(番地番号)だけでなく、「型の情報」も持っている ポインター値 = アドレス値(番地番号) + (その番地にあるセルの並び[「変数」に対応]の保持するデータの)型情報 => ポインター値を利用して、「変数」を参照(値の取り出しや代入) ポインター値そのものの操作が可能 +n (n が整数の時) => ポインター値の持つ型の配列の n 個数先の要素の番地番号になる => 配列との相性がよい ary[n] <-> *(ary + n) 例 : int iary[10]: iary[5] <-> *(iary + 5) 左側: 整数型配列の 5 番目の要素を参照 右側: 配列の先頭要素に「5」を加えたポインター値が指す領域 => 番地としては、iary に対して 5 * sizeof(int) だけずれたものになる ポインター値の演算 整数値の加減算 : +1 するとアドレス値が領域サイズだけ増える アドレス値(番地番号)の操作 キャスト : 型情報を変更する事ができる 型情報を制御 => 「配列名」=> 配列の先頭の要素を指すポインタ値 配列 iary[10] の時、「iary <-> &iary[0] = &*(iary+0) = &*(iary) = iary」となる => 「(ポインタ)値」なので、関数の引数や、返り値に使う事ができる <= 間接参照(*)を利用して、配列の要素を、関数の呼び出し側と呼び出される側で共有できる !!! 配列は、「値の並び」に対応した「値」を表現している !!! 配列がもつ「値」を変更する場合は、 !!! 配列の要素のすべての変更する場合より、 !!! 少ない個数(多くの場合は一つの)要素を変更する場合が多い !!! cf. DataBase の表 !!! => 配列の表す値を変更する場合は、(全体コピーせず)一部を変更すると効率が良い # ポインタ値と、配列の関係 # ポインタ値が、配列の一部を参照している場合 動的なメモリ管理 cf. 変数宣言(配列宣言を含む..) => プログラム内に、明示的に、そのサイズを指定する => 表現したいデータサイズが、予めわかっていないと、宣言(プログラム作成)ができない => プログラムの実行時も、その固定されたサイズしか利用してはいけない !!! そのサイズを超える要素を(実行時に..)参照すると、いろいろなトラブルの原因になる !!! <= 間接参照を利用する場合は、「宣言していない領域が参照できてしまう..(セキュリティホールの遠因になる) 実行時に、領域を動的(必要なサイズをその状況に応じて)に確保する malloc/calloc で動的に領域を確保 / free で利用済み領域を開放 利点: 領域を実行時に決める事できるので、柔軟性(必要なサイズだけとれば、効率性)がよくなる 欠点: ポインター変数(ポインターへの理解)が必要 free を忘れないようにする [2019/12/20] ファイル I/O C 言語で記述されたプログラムで、「ファイル」を扱いたい # C 言語そのものには、ファイルを扱う機能はない # <= ファイルを扱う「標準」ライブラリがあり、それを呼び出す # # ファイルを扱うライブラリの API を学ぶ !!! 「ファイル」<= 何等かの情報をまとめて(恒久的に..)保存してくれるもの !!! => OS によって、細かい部分が異なる !!! <= OS の違いによる、扱いの違いは、基本、ライブラリが吸収する !!! => どの OS でも、同じ形で、「(本来は異なる)ファイル」が扱える !!! <ポイント> !!! ライブラリを利用して、OS に依存しないファイル処理をできるようにする !!! ライブラリの概念によって !!! 「『異なる』対象」を「『共通の』利用」をする方法が提供される C 言語で作成するプログラム内では、 ファイルポインタを入手し、そのファイルポインタを指定して、操作を行う fprintf は printf のファイルポインター利用版で、 最初の引数にファイルポインターを指定する以外は、printf と同じ fscanf も同様 以下、 getchar, putchar, (gets), puts に対して、fgetc, fputc, fgets, fputs 等がある ファイルポインタは、fopen を利用して、(ファイルを開くと同時に..) その返り値である ファイルポインタを得る事ができる 利用が済んだファイルは、fclose を用いて、ファイルポインターを指定して、閉じる。 fclose => 書き込みに関して、ある程度、やってくれる(完全ではない) => ファイルへの書き込みを完全にするには、きちんと shutdown する必要がある !!! scanf は危険 ( <= キーボードから人間が入力する場合が多く、人間は誤って入力をしてしまう可能性がある ) !!! fscanf は (指定するファイルポインタがファイルへのファイルポインタであれば..) !!! (ファイルの中身は適切だと仮定したうえで..) 安全に入力できる
課題プログラム内の「/*名前:ここ*/」の部分を書き換え「/*この部分を完成させなさい*/」の部分にプログラムを追加して、プログラムを完成させます。
Download : 20191220-01.c
/* * 20191220-01-QQQQ.c * 整数のキュー(queue) */ #include <stdio.h> #include <malloc.h> /* malloc を利用する場合 */ /* * 整数のキュー(queue) * * 利用方法 * コンパイル * cc -o BASENAME.exe FILENAME * 実行 * ./BASENAME.exe */ /* * Queue は、List の最後に追加し、先頭の要素を削除する事で実現 * * 整数 queue : [ 1, 2, .., 10 ] の表現 * * ICell * +-------+ +-------+ +-------+ * next +-> | *-----> | *-----> ... --> | NULL | * value | | | | | +-> | | * | +-------+ +-------+ ... | +-------+ * | | 1 | | 2 | | | 10 | * | +-------+ +-------+ | +-------+ * IQueue | | * +-----------+ | | * head | *-------+ | * +-----------+ | * tail | *---------------------------------------+ * +-----------+ * * 空な整数 queue : [] の表現 * 空な整数 queue は、head/tail 共に NULL とする * * IQueue * +-----------+ * head | NULL | * +-----------+ * tail | NULL | * +-----------+ * */ typedef struct icell { struct icell *next; /* 次のセルへのポインター */ int value; /* このセルが持つ値(整数値) */ } ICell; typedef struct { ICell *head; /* リストの先頭の要素へのポインター */ ICell *tail; /* リストの最後の要素へのポインター */ } IQueue; /* */ #define NORMAL (0) /* 処理に成功した場合の値 */ #define ERROR (-1) /* 処理に失敗した場合の値 */ /* */ #define FALSE (0) /* 論理値 (偽値) */ #define TRUE (!FALSE) /* 論理値 (真値) */ /* * alloc_icell * 指定した整数値(data) を保持する icell を作成する * return 値 * 作成された icell へのポインター値 * alloc に失敗した場合は NULL 値が返る */ ICell *alloc_icell ( int data ) { ICell *result = (ICell *)malloc(sizeof(ICell)); if ( result != NULL ) { /* 次の要素 (next) は NULL にする */ result -> next = NULL; /* 保持する値 (value) は 指定された値 (data) にする */ /* ** この部分を完成させなさい */ } return result; } /* * free_icell * 指定した整数値を保持する icell (icp) を開放する * return 値 * 開放に成功したかどうか ( 成功 -> NORMAL / 失敗 -> ERROR ) */ int free_icell ( ICell *icp ) { int result = ERROR; if ( icp != NULL ) { free ( icp ); result = NORMAL; } return result; } /* * alloc_iqueue * 空っぽな整数 queeue (iqueue) を作成する * return 値 * 作成された iqueue へのポインター値 * alloc に失敗した場合は NULL 値が返る */ IQueue *alloc_iqueue ( void ) { IQueue *result = (IQueue *)malloc(sizeof(IQueue)); if ( result != NULL ) { /* 空な整数 queue は、先頭も最後も NULL にする */ /* ** この部分を完成させなさい */ } return result; } /* * is_empty * 指定した整数 queue が空かどうかを判定する * return 値 * 空の場合 TRUE * 空でない場合 TRUE 以外 */ int is_empty ( IQueue *iqp ) { if ( iqp != NULL ) { if ( iqp -> head == NULL ) { return TRUE; } } return FALSE; } /* * enque * 指定された 整数 queue (iqp) に指定された整数値 (data) を追加する * return 値 * 追加できた場合 NORMAL * それ以外 ERROR */ int enque ( IQueue *iqp, int data ) { int result = ERROR; /* 途中で、何か上手く行かなければ ERROR */ if ( iqp != NULL ) { ICell *icp = alloc_icell ( data ); /* 新しい ICell を作成 */ if ( icp != NULL ) { /* 作成できたら */ if ( is_empty ( iqp ) ) { /* 空っぽの場合 */ iqp -> head = icp; /* 先頭も新しい要素 */ } else { iqp -> tail -> next = icp; /* 最後の要素の次に新しい要素を追加 */ } /* 最後の要素は、追加した要素 */ /* ** この部分を完成させなさい */ result = NORMAL; /* 追加に成功 (全て OK) */ } } return result; } /* * dequeue * 指定された 整数 queue (iqp) から、要素を取り出す * return 値 * 取り出せた場合 取り出した値 * 取り出せない場合 ERROR * <<注意>> * 整数 queue の中に ERROR があると、 * 取り出しに成功したか失敗したか区別できない (不適切な設計 !!) */ int deque ( IQueue *iqp ) { int result = ERROR; if ( iqp != NULL ) { if ( !is_empty ( iqp ) ) { /* queue が空でない */ ICell *top = iqp -> head; /* 先頭の要素(ICell)を取り出す */ result = top -> value; /* 返す値は、先頭にある */ if ( ( iqp -> head = top -> next) == NULL ) { /* 新しい先頭は、先頭の次の要素 */ /* queue が空になった場合は tail も NULL にする */ /* ** この部分を完成させなさい */ } free ( top ); /* 先頭の要素は開放 */ } } return result; } /* * free_que * 指定された 整数 queue (iqp) を全て開放する */ void free_que ( IQueue *iqp ) { if ( iqp != NULL ) { ICell *icp = iqp -> head; /* 先頭の要素を、次の処理対象に */ ICell *next; /* 次の要素へのポインタ値を保持 */ while ( icp != NULL ) { /* 未だ、要素があれば.. */ next = icp -> next; /* 次のために、次の要素へのポインタ値をバックアップ */ free_icell ( icp ); /* 注目している要素を開放 */ /* 次の要素を、次の処理対象にする */ /* ** この部分を完成させなさい */ } free ( iqp ); /* queue そのものを開放 */ } } /* * print_que * 指定された整数 que の内容を表示する */ void print_que ( IQueue *iqp ) { ICell *icp = iqp -> head; printf ( "[" ); for ( icp = iqp -> head; icp != NULL; icp = icp -> next ) { /* 現在着目している要素 (icp) が保持する値を表示 */ /* ** この部分を完成させなさい */ if ( icp -> next != NULL ) { /* 次の要素があれば .. */ printf ( "," ); /* 「,」を出力 */ } else { printf ( " " ); /* 「 」を出力 */ } } printf ( "]" ); } /* * main */ int main ( int argc, char *argv[] ) { IQueue *iqp = alloc_iqueue(); /* 空の queue の作成 */ print_que ( iqp ); putchar ( '\n' ); /* 空の queue [] */ enque ( iqp, 1 ); /* que に 1 を追加 */ print_que ( iqp ); putchar ( '\n' ); /* 要素が 1 つの queue [ 1 ] */ enque ( iqp, 2 ); enque ( iqp, 3 ); /* que に 2, 3 を追加 */ print_que ( iqp ); putchar ( '\n' ); /* 要素が 3 つの queue [ 1, 2, 3 ] */ printf ( "%d\n", deque ( iqp ) ); /* 先頭の要素を削除 */ print_que ( iqp ); putchar ( '\n' ); /* 要素が 2 つの queue [ 2, 3 ] */ free_que ( iqp ); /* que の開放 */ return 0; }
$ ./20191220-01-QQQQ.exe [] [ 1 ] [ 1, 2, 3 ] 1 [ 2, 3 ] $
Download : 20191220-02.c
/* * 20191220-02-QQQQ.c * */ #include <stdio.h> /* * main */ int main ( int argc, char *argv[] ) { if ( argc != 3 ) { printf ( "ファイル名を二つ指定して下さい。\n" ); } else { FILE *fp1; if ( ( fp1 = fopen ( argv[1], "r" ) ) == NULL ) { printf ( "ファイル(%s)を開く事ができませんでした。\n", argv[1] ); } else { FILE *fp2; if ( ( fp2 = fopen ( argv[2], "r" ) ) == NULL ) { printf ( "ファイル(%s)を開く事ができませんでした。\n", argv[2] ); } else { int position; int ch1; int ch2; for ( position = 0; (ch1 = fgetc (fp1)) != EOF; position++ ) { ch2 = fgetc( fp2 ); if ( ch1 != ch2 ) { break; } } if ( ch1 == EOF ) { ch2 = fgetc( fp2 ); } if ( ch1 == ch2 ) { printf ( "二つのファイル(%s,%s)は同じ内容です。\n", argv[1], argv[2] ); } else if ( ch1 == EOF ) { /* ** この部分を完成させなさい */ } else if ( ch2 == EOF ) { printf ( "ファイル(%s) の方がサイズが大きいです。\n", argv[1] ); } else { printf ( "%d byte 目で %s は %c, %s は %c という違いがありました。\n", position, argv[1], ch1, argv[2], ch2 ); } fclose ( fp2 ); } fclose ( fp1 ); } } return 0; }
$ ./20191220-02-QQQQ.exe ファイル名を二つ指定して下さい。 $