計算機プログラミング基礎
(Cクラス)
河口 信夫
前回の講義
再帰とタートルグラフィック
Dragon 曲線の作成
コマンドライン引数
文字列比較
本日の目標
ファイル入出力
– 文字ストリームのコンセプト
– 標準入出力
ポインタ
– 数字・文字列との対応
動的メモリ確保
構造体
– リスト構造
ファイル入出力
ファイルのオープンとクローズ
ファイル入出力の手順 ファイルのオープン ファイルの読み書き ファイルのクローズ ファイル処理の前準備 データの読み書き ファイル処理の後始末ファイルのオープンとクローズ
fopen 関数と fclose 関数
【一般的な書式】 FILE *fp; fp=fopen(<ファイル名文字列>,<アクセスモード文字列>); fclose(fp); r 既存ファイルの読み込み用(read) w 新規ファイルへの書き出し用 (ファイルが存在すれば上書き)(write) a 既存ファイルへの追加書き出し用(append) r+,w+,a+ それぞれのモードで読み込み/書き出しの両用 アクセスモード 文字列 t ASCIIファイル b binaryファイル ファイル種別 注) ASCIIファイルと binaryファイルを区別する OSと区別しないOSがある ファイルポインタ(オープンされた ファイルの管理に使用)ファイルの読み込み
#include <stdio.h> #include <stdlib.h> int main(void) { FILE *fin; char infile[40]; printf("input file = "); scanf("%s", infile); if ( (fin=fopen(infile,"rt")) == NULL ) { fprintf(stderr,"Can't open %s.¥n",infile); exit(1); } /* ここでファイルの中身を読む */ fclose(fin); return(0); } 「 “infile” で指定したファイルを入力用にファイル ポインタ “fin” を使ってオープンしなさい。ファイル オープンに失敗したらNULL が返されるので、 その場合はエラーメッセージを表示しなさい。」ファイルの中身の表示
#include <stdio.h> #include <stdlib.h>
int main(int argc, char *argv[]) { FILE *fin;
int c;
if ( (fin=fopen(argv[1],"rt")) == NULL ) { fprintf(stderr,"Can't open %s.¥n",argv[1]); exit(1);
}
while( (c=fgetc(fin)) != EOF){ putchar(c);/* fputc(c,stdout);*/ } fclose(fin); return(0); } #include <stdio.h> #include <stdlib.h>
int main(int argc, char *argv[]) { FILE *fin,*fout;
int c; /* これだとエラーが区別できない */ if ( (fin=fopen(argv[1],"rt") ) == NULL ||
(fout=fopen("file.out", "wt"))==NULL){ fprintf(stderr,"Can't open %s.¥n",argv[1]); exit(1);
}
while( (c=fgetc(fin)) != EOF){ fputc(c,fout); } fclose(fin);fclose(fout); return(0); } #include <stdio.h>
int main(int argc, char *argv[]) { FILE *fin,*fout;
int c;
char *ofname = "file.out";
if ( (fin=fopen(argv[1],"rt") ) == NULL){ fprintf(stderr,"Can't open %s.¥n",argv[1]);
exit(1); }
if((fout=fopen(ofname, "wt"))==NULL){
fprintf(stderr,"Can't write file.out¥n”); exit(1);
}
while( (c=fgetc(fin)) != EOF){ fputc(c,fout); } fclose(fin);fclose(fout); return(0); }
ファイルの書き出し
#include <stdio.h> #include <stdlib.h> int main(void) { FILE *fout; char outfile[40]; printf("output file = "); scanf("%s", outfile); if ( (fout=fopen(outfile,"wt")) == NULL ) { fprintf(stderr,"Can't open %s.¥n",outfile); exit(1); } /* ここでファイルに書き出す */ fclose(fout); return(0); } 「 “outfile” で指定したファイルを出力用にファイル ポインタ “fout” を使ってオープンしなさい。ファイル オープンに失敗したらNULL が返されるので、 その場合はエラーメッセージを表示しなさい。」 注) 書き出し用にファイルを オープンする際、すでに同名 のファイルが存在した場合は、 ファイルオープンを行った時点 で、そのファイルの中身は すべて消去されます。標準入出力
stdin, stdout, stderr
– Cのプログラムが実行を開始すると、自動的に 利用できるようになるストリーム – ストリームとは、Cプログラムとファイルとを結ぶ、 データの流れのこと – C言語では、入出力デバイス(キーボードや画面)も 特別なファイルとして扱う stdin 標準入力ストリームポインタ(キーボード入力) stdout 標準出力ストリームポインタ(画面出力) stderr 標準エラーストリームポインタ(画面出力)
ファイル入出力関数
ファイル入出力関数 コンソール入出力関数putc または fputc
putchar
getc または fgetc
getchar
fputs
puts
fgets
gets
fprintf
printf
fscanf
scanf
例) char s[256]; fgets(s, 256, fin); fputs(s, fout); 「fin が指すファイルから最大256文字まで、 文字列s に1行入力しなさい。 文字列s を、 fout が指すファイルへ 1行出力しなさい。」演習問題1
引数としてファイル名をとり、その中身をファイル
“file.out”(ファイル名) に出力するプログラムを書こう
fopen(“file.out”,”wt”) で書き出し用に開けます
コマンドライン引数 ファイルのオープンとクローズ ファイルの読み込み ファイルの書き出し, ファイル入出力関数 の説明をよく読んで理解しましょう ポイントは、「読み込み用のファイル」と「書き出し用のファイル」を 2つ同時に開くことです。 その後、読み込み用のファイルがEOFに到達するまで 読み込み用のファイルから1文字読み込み、fgetc(fin) 書き出し用のファイルに1文字書きだす fputc(c, fout) ことを、繰り返します。さて、ここからが
C言語本番です
ポインタとアドレス
変数には、値を格納するためのメモリが 割り当てられます。 int a; ポインタは、「コンピュータのメモリ上に 格納されているデータの場所を表すデータ」 です。データ a のポインタを p とすると、 「p は、実体 a を指し示すポインタ」 と表現することができます。 これをC言語では次のように表します。 int *p; p = &a; 「int 型のデータをポインタ p で参照します」 「ポインタ p が参照するのは、変数 a のアドレス」 13 12 11 220 10 9 8 7 6 5 10 4 内容 アドレス int 型変数 a の場所 ポインタ変数p の場所ポインタ演算子
ポインタの示すアドレスにある値を取り出す
*
変数のアドレスを取り出す
&
意味
演算子
ポインタを通じて、変数にアクセスすることが
できます
ポインタの練習
#include <stdio.h>
int main(void){
int a,b;
int *p;
a=123; p=&a; b=*p;
printf("a=%d, b=%d, *p=%d¥n",a,b,*p);
a=200; b=300;
printf("a=%d, b=%d, *p=%d¥n",a,b,*p);
p=&b;
printf("a=%d, b=%d, *p=%d¥n",a,b,*p);
return(0);
}
#include <stdio.h>
int main(void){
int a,b;
int *p;
a=123; p=&a; b=*p;
printf("a=%d, b=%d, *p=%d¥n",a,b,*p);
a=200; b=300;
printf("a=%d, b=%d, *p=%d¥n",a,b,*p);
p=&b;
printf("a=%d, b=%d, *p=%d¥n",a,b,*p);
return(0);
}
※ 上記のアドレス値(1000,1004)は例です 1000 1004 1000 p b a アドレス変換表 1000番地 1004番地 123 123 実メモリ#include <stdio.h>
int main(void){
int a,b;
int *p;
a=123; p=&a; b=*p;
printf("a=%d, b=%d, *p=%d¥n",a,b,*p);
a=200; b=300;
printf("a=%d, b=%d, *p=%d¥n",a,b,*p);
p=&b;
printf("a=%d, b=%d, *p=%d¥n",a,b,*p);
return(0);
}
※ 上記のアドレス値(1000,1004)は例です 1000 1004 1000 p b a アドレス変換表 1000番地 1004番地 300 200 実メモリ#include <stdio.h>
int main(void){
int a,b;
int *p;
a=123; p=&a; b=*p;
printf("a=%d, b=%d, *p=%d¥n",a,b,*p);
a=200; b=300;
printf("a=%d, b=%d, *p=%d¥n",a,b,*p);
p=&b;
printf("a=%d, b=%d, *p=%d¥n",a,b,*p);
return(0);
}
※ 上記のアドレス値(1000,1004)は例です 1000 1004 1004 p b a アドレス変換表 1000番地 1004番地 300 200 実メモリ文字列とポインタ
(
1)文字列先頭アドレスのコピー
#include <stdio.h> #include <string.h> int main(void){ char st[10]; char *p; strcpy(st,"NAGOYA"); p=st; printf("st=%s p=%s¥n",st,p); return(0); } 1000番地 1001番地 ’A’ ’N’ 実メモリ 1002番地 ’G’ 1003番地 ’O’ 1004番地 ’Y’ 1005番地 ’A’ 1006番地 ¥0 p 1000 1001 1002 st[2] st[1] st[0] アドレス変換表 1003 st[3] 1004 st[4] 1005 st[5] 1006 st[6] 1007 st[7] 1008 st[8] 1009 st[9] p 1000文字列とポインタ
(
2)ポインタで任意のアドレスを示す
#include <stdio.h> #include <string.h> int main(void){ char st[10]; char *p; strcpy(st,"NAGOYA"); p=st; putchar(*p); putchar(*(p+1)); putchar(*(p+2)); putchar('¥n'); return(0); } putchar(c) は、 文字変数c (1文字) を画面に表示する ための関数 1000番地 1001番地 ’A’ ’N’ 実メモリ 1002番地 ’G’ 1003番地 ’O’ 1004番地 ’Y’ 1005番地 ’A’ 1006番地 ¥0 p 1000 1001 1002 st[2] st[1] st[0] アドレス変換表 1003 st[3] 1004 st[4] 1005 st[5] 1006 st[6] 1007 st[7] 1008 st[8] 1009 st[9] p 1000 p+1 p+2文字列とポインタ
(3)ポインタで値を書き換える
#include <stdio.h> #include <string.h> int main(void){ char st[10]; char *p; strcpy(st,"NAGOYA"); p=st; *p='T'; *(p+2)='K'; printf("st=%s¥n",st); return(0); } 1000番地 1001番地 ’A’ ’T’ 実メモリ 1002番地 ’K’ 1003番地 ’O’ 1004番地 ’Y’ 1005番地 ’A’ 1006番地 ¥0 p 1000 1001 1002 st[2] st[1] st[0] アドレス変換表 1003 st[3] 1004 st[4] 1005 st[5] 1006 st[6] 1007 st[7] 1008 st[8] 1009 st[9] p 1000 p+2文字列とポインタ
(4)ポインタを更新する
#include <stdio.h> #include <string.h> int main(void){ char st[10]; char *p; strcpy(st,"NAGOYA"); printf("st=%s¥n",st); p=st; while(*p){ *p=*p+1; ++p; } printf("st=%s¥n",st); return(0); } *p=*p+1 1000 1001 1002 st[2] st[1] st[0] アドレス変換表 1003 st[3] 1004 st[4] 1005 st[5] 1006 st[6] 1007 st[7] 1008 st[8] 1009 st[9] p 1000 1000番地 1001番地 ’A’ ’N’ 実メモリ 1002番地 ’G’ 1003番地 ’O’ 1004番地 ’Y’ 1005番地 ’A’ 1006番地 ¥0 ’B’ ’O’ ’H’ ’P’ ’Z’ ’B’ ¥0 ++p で、ポインタ自身の値 を+1、すなわちアドレス値 に1 を加算演習問題2
コマンドライン引数で入力した文字列を 逆さ向きに表示するコマンドを作ってみよう コマンドラインargv[1] を使う • 逆向きにするためには、文字の長さが必要 • 文字列の長さを取得 strlen(argv[1]) で取得(string.h 必要) 例(revという名にした場合)> rev harahore -> eroharahと表示
Hint : 1文字の表示は “putchar(c)” でOK – char *str = argv[1]; // こうするとわかりやすい – n文字目の文字は str[n] – ループは n文字目から0文字目に向かって実行( n = strlen(str)-1) for( i = n ; i >= 0 ; i--){ // これで逆向きのループが可能 }
構造体
配列は、同種のデータをひとまとめに扱うためのデータ構造 構造体は、異種のデータをまとめて取り扱うためのデータ構造 – レコード : データ構造の単位 – メンバ : レコードを構成する個々のデータ struct タグ名 { 型 メンバ1; 型 メンバ2; 型 メンバ3; .... 型 メンバN; } 変数リスト; 一般的な形式 タグ名と変数リストは どちらか一方を 省略することができる 変数リストは 構造体の実体を表す構造体の定義例
氏名
, 学籍番号, 年齢を含む構造体 member を
定義し、その変数として64個の要素を格納できる
配列 classC を宣言する
struct member { char name[40]; char ID[10]; int age; } classC[64]; struct member { char name[40]; char ID[10]; int age; }; int main(void) {struct member classC[64]; ... どちらでも可
構造体メンバへのアクセス
ドット演算子(.)とアロー演算子(->)
– 構造体型変数で構造体メンバにアクセスする場合には ドット演算子を使い、ポインタで構造体メンバにアクセス する場合にはアロー演算子を使う ... struct member { char name[40]; char ID[10]; int age; }; int main(void) {struct member classC[64], *p; classC[0].age = 18;
p = &classC[0];
printf("%d %d¥n", classC[0].age, p->age ); return 0; }
動的なメモリ割り当て
プログラムの実行中に必要に応じてメモリを
割り当てる処理(stdlib.h が必要)
–malloc 関数 ・・・ メモリを割り当てる –free 関数 ・・・ 割り当て済みのメモリを解放する例) 「
80バイトまでの文字列を扱う配列を宣言する」
char *p; p = (char *)malloc(80); free(p); ※ 実際には、malloc() の呼び出しが(メモリ確保が) 成功したかどうかを確認する必要があります。構造体に対する動的メモリ
sizeof 演算子 =型の大きさを決める
#include <stdio.h> struct member { char name[40]; char ID[10]; int age; }; int main(void) { struct member *c;c = (struct member *)malloc(sizeof(struct member)); if( c == NULL ) exit(1);
c->age = 18;
printf("%d¥n", c->age ); free(c);
return 0; }
ここからプログラミング本番
テキストベースの
名簿管理のシステムを作ってみよう
プログラムの構造
構造体を使ったデータ管理
動的なメモリの確保、ソーティング、削除
名簿管理システム仕様
データ投入
– キーからの入力
– ファイルからの入力
表示
– ソーティング
– 追加・削除
保存
– ファイルへの保存
キーからのデータ入力
2種類の入力方法
– これまでは scanf を利用
– 1行(空白文字も含めて)を入力ためには
fgets(char *buf, int leng, FILE *fp);
を利用 (input.c を参考)
詳細はマニュアル
man を見てみよう
ファイルからの入力
ファイルも、文字入力も同じ
FILE *
(これをストリームと呼ぶ)
同じ扱いで
OK
– fscanf (FILE *fp, char *format)
もしくは
fgets(char *buf, int leng, FILE *fp);
を使う
ファイルの最後は
EOF (end of file)
ファイルへの出力
出力も同じようにストリーム
– 画面表示も stdoutというストリーム
fopen で開いて fclose で閉じる
fprintf(FILE *fp, “format”, args…);
で 出力可能
一覧表示
いろいろなソーティングを行う
表示方法を切り替える仕組みも
あるとよい
(ソーティング関数で切り替え?)
ソーティング・アルゴリズム
並べ替えを行う
対象によって様々な種類が
– バブルソート
– マージソート
– クイックソート
高度なソーティングは講義の範疇を超える
ので、単純なバブルソート、
マージソートのみを解説
配列のソーティング
単純なバブルソート
void main(){int i,j, tmp, val[10] = {3,6,2,8,1,5,7,9,0,4}; for(i=0; i< 9; i++){
for(j = i+1; j < 10; j++){ if( val [i] > val [j] ) {
tmp = val[i]; val[i] = val[j]; val[j] = tmp; }}} for(i =0; i < 9; i++) printf(“val[%d]=%d ¥n”,i,val[i]); }
リストを使った挿入ソート
リストに値を入れる段階で
ソーティングして入れる
異なるソーティング法をとりたいときは、
新しいリストを作成するのが簡単
リスト構造
順序関係を持つ1次元的なデータ構造
– リストを構成するひとつのレコードをセルという
– セルは、データと次のセルへのポインタを要素
として持つ
– ポインタをたどることによって、リストを走査する
ことができる
A B C D/
データ 次のセルへのポインタリストの挿入・削除
リストの挿入(セルAとBの間にセルEを挿入)
リストの削除(セルCを削除)
A B C D/
E×
1) セルEを作成 2) AのポインタをEに向ける 3) EのポインタをBに向ける A B×
C×
D/
1) BのポインタをDに向ける 2) Cを消滅させるList構造 解説
mylist.c
まずは リスト構造の1単位(cell)の定義
struct LIST
{
int data;
struct LIST *nextptr;
};
/
data
nextptr
main関数
int main(){ LIST *root=NULL; int newdata; while(1){ printf("data > "); scanf("%d",&newdata); if(newdata<0) break; insertList(&root,newdata); traverseList(root); } return 0; }main関数
int main(){ LIST *root=NULL; int newdata; while(1){ printf("data > "); scanf("%d",&newdata); if(newdata<0) break; insertList(&root,newdata); traverseList(root); } return 0; } LISTのroot LISTへの挿入 LISTの表示 ここに注目リスト表示関数(TraverseList)
void traverseList(struct LIST *ptr)
{
while(ptr!=NULL)
{
printf("%d ",ptr->data);
ptr=ptr->nextptr;
}
putchar('¥n');
}
NULLになるまで ループ Listを順に たどって表示リストへの挿入
void insertList(struct LIST **ptr, int newdata) {
if(*ptr==NULL || (*ptr)->data > newdata) { struct LIST *newcell;
newcell=(struct LIST *)malloc(sizeof(struct LIST)); newcell->data=newdata; newcell->nextptr=*ptr; *ptr=newcell; }else{ insertList(&(*ptr)->nextptr, newdata); } }
リストへの挿入
void InsertList(struct LIST **ptr, int newdata) {
if(*ptr==NULL || (*ptr)->data > newdata) { struct LIST *newcell;
newcell=(struct LIST *)malloc(sizeof(struct LIST)); newcell->data=newdata; newcell->nextptr=*ptr; *ptr=newcell; }else{ InsertList(&(*ptr)->nextptr, newdata); } } ここに注目 ポインタへのポインタ 値が大きい セルが存在しない 新しいセルを 作成して挿入 ここに注目 ポインタの更新 セルが存在し、値が小さい場合は次に
ポインタへのポインタ?
struct LIST **ptr; とは何を意味しているか?
3 6/
struct LIST *root
InsertList( &root, 4 ) を考える
if(*ptr==NULL || (*ptr)->data > newdata)
→ *ptr = root != NULL
→ (*ptr)->data = root->data = 3 < 4 より
条件成立せず。
if 文の else 節を実行し、
ポインタへのポインタ?
struct LIST **ptr; とは何を意味しているか?
3 6
/
struct LIST *root
if(*ptr==NULL || (*ptr)->data > newdata)
→ (*ptr)->data = 6 > 4 より条件成立
InsertList( &(root->nextptr), 4 ) を考える
newcell=(struct LIST *)malloc(sizeof(struct LIST)); newcell->data=newdata;
newcell->nextptr=*ptr; *ptr=newcell;
セルへの挿入
3 6
/
struct LIST *root
newcell=(struct LIST *)malloc(sizeof(struct LIST)); newcell->data=newdata ; /* (= 4) */ newcell->nextptr=*ptr; *ptr=newcell; ptr = &(root->nextptr) が前提
newcell
4/
ptr
ここまでの状況(下図)セルへの挿入
3 6/
struct LIST *root
newcell=(struct LIST *)malloc(sizeof(struct LIST)); newcell->data=newdata ; /* (= 4) */ newcell->nextptr=*ptr; *ptr=newcell; ptr = &(root->nextptr) が前提
newcell
4ptr
ちなみに
root = NULL の場合
(ptr = &root )
root = NULL
newcell
4/
ptr
newcell=(struct LIST *)malloc(sizeof(struct LIST)); newcell->data=newdata ; /* (= 4) */ newcell->nextptr=*ptr; *ptr=newcell;