C言語の研究6

第6章 ライブラリ関数

以前、printf関数を説明したときに、「printfを使うためには、その前に標準ライブラリ関数のヘッダーファイル、stdio.hを取り込んでおく必要がある。おっと、また訳のわからないことを言ってしまったかな。これについては、後の章で説明するつもりだ。」とか書いたが、これについて説明できる時期がやっと来た。

ANSI規格では、ライブラリとして標準的な関数がコンパイラに提供されている。C言語の基本は必要な関数は自分でつくるところにあるのだが、プログラムの効率を図る意味で、標準ライブラリの中に有益で根本的な関数が提供されている。

情報処理技術者試験の場合は、JIS規格ということになるだろう。しかし、基本的な使い方さえ知っておけば、これをわざわざ記憶する必要はない。試験の時にもマニュアルが付いて来るので、それを見て使い方を知れば良いわけだ。そりゃ、一応目を通している方が早く理解できる。

ANSIとJISは実質的に同一で、私は両規格の違いをはっきり知らないし、また、知りたくもないない

ここでは、知っておいた方がいいに思われるライブラリ関数と、その大まかな使い方について見ていこう。

1 標準入出力

通常、標準入力はキーボードから、標準出力はCRTを指す。

(1) 書式付き出力 printf関数

これについては、もうずっと使用してきたし、説明も前にしているのでとばすが、

[ヘッダ] stdio.h

[形 式] int printf(const char *format,・・);

という形だったね。もし忘れていたら、もう一度第1章で確認しよう。

(2) 書式付き入力 scanf関数

書式を指定して、キーボードから入力を行うための関数だ。

これから、データをキーボードから入力するプログラムを演習編で使う予定でもあるし、よく使う関数なので心して理解しておこう。

[ヘッダ] stdio.h

[形 式] int scanf(const char *format,・・);

[引 数] format:書式を示す文字列へのポインタ

[返却値] 読み込んだデータの個数を返す。入力できなかった場合はEOF(End of Fileの略)を返す。

書き方としては、printfと同じだが、引数がポインタになっているところが異なる。

早速テストしてみよう。

#include <stdio.h>

void main()
{
    int a;
   
    printf("整数を入力してください : ");
    scanf("%4d", &a); /*最大桁数を4桁にする、引数がポインタであることに注意*/
    printf("入力した整数は%dです\n",a);
}

どうだろうか、うまくメッセージに従って整数が入力できただろうか。

今度は文字列でテストだ。

#include <stdio.h>

void main()
{
    char a[10];
   
    printf("文字列を入力してください : ");
    scanf("%s", a);
    printf("入力した文字列は%sです\n",a);
}

うん、これは難なく行った。 では文字列を示すポインタにscanf関数を使ってみるか。

#include <stdio.h>

void main()
{
    char *a;
   
    printf("文字列を入力してください : ");
    scanf("%s", a);
    printf("入力した文字列は%sです\n",a);
}

なんと、実行途中で不正処理のメッセージが出てストップしてしまったね。やはり、実体のないポインタなんかを信用してscanf関数は使えないね。

ブルース・リーの言うとおりであった。

(3) 1文字入力 getchar関数

キーボードから1文字読み取り、その値を返す。

[ヘッダ] stdio.h

[形 式] int getchar(void);

[引 数] なし

[返却値] 読み取った文字を返す。エラーはEOFを返す。

テストだ。

#include <stdio.h>

void main()
{
    char c;

    printf("getchar関数に何か1文字入れたってくれ : ");
    c = getchar();
    printf("getchar関数は%cを読み取った!\n",c);
}

この程度のものさ、私のレベルなんて。

(4) 1文字出力 putchar

CRTに1文字出力する。

[ヘッダ] stdio.h

[形 式] int putchar(int c);

[引 数] 出力したい文字

[返却値] 読み取った文字を返す。エラーはEOFを返す。

これもサンプルで試してみよう。

#include <stdio.h>

void main()
{
    char c;

    printf("getchar関数に何か1文字を入れたってくれ : ");
    c = getchar();
        putchar('T');
        putchar('h');
        putchar('a');
        putchar('n');
        putchar('k');
        putchar(' ');
        putchar('Y');
        putchar('o');
        putchar('u');
        putchar('-');
        putchar(c);
        putchar('\n');
}

アホみたいと思うだろうが、これが私の今の実力と言えよう。

(5) 1行入力 gets関数

キーボードから1行分の文字列を読み取る。

[ヘッダ] stdio.h

[形 式] char *gets(char *s);

[引 数] s:読み取った文字を格納する文字型配列へのポインタ

[返却値] 読み取りが成功すればs を返す。エラーはNULLポインターを返す。

            EOFを検出するか改行を読み取るまでは文字を入力して配列に格納する。

      改行はマークは捨てて文字列の最後にチャンとNULLを付けてくれる。

これもテスト&テスト

#include <stdio.h>

void main(int argc, char **argv)
{
    char a[20];
    char *pa;

    printf("gets関数に何か文字列を入れたってくれ\n");

    pa=gets(a);

    if (pa != NULL) puts(pa);
    else printf("can't input\n");
}

こんなもんさ。

(6) 1行出力 puts関数

CRTに文字列を1行出力する。

[ヘッダ] stdio.h

[形 式] int puts(const char *s);

[引 数] s:出力したい文字列を指すポインタ

[返却値] 出力エラーが発生すればEOFを返す。成功時は0か正の値を返す。

      NULLは捨てて、文字列の最後に改行はマークをチャンと付けてくれる。

例として、さっきのバカみたいな例を利用して、最後にputs関数で表示しよう。「ご協力ありがとう」

#include <stdio.h>

void main(int argc, char **argv)
{
    char a[20];
    char *pa;

    printf("gets関数に何か文字列を入れたってくれ\n");

    pa=gets(a);

    if (pa != NULL) puts(pa);
    else printf("can't input\n");

    puts("ご協力ありがとう");
}

どうかな、簡単だろう。C言語なんてしょせんはこの程度さ。

それではちょっと例題をやってみよう。

今度はscanf関数を使って、3人分の学生の学生番号と4科目の得点をキーボードから入力して、平均点と評価を求めて表示させよう。

<例題>

#include <stdio.h>

#define KAMOKU 4
#define NINZU 3

typedef struct seiseki {
    int    bango;
    int    ten[KAMOKU];
    double    ave;
    char    hyoka;
} DATA;

double get_ave(DATA *);
char get_hyoka(DATA *);
void main(int argc,char **argv)
{
    int i;
    DATA s[NINZU];
    DATA *ps;
    ps = s;

    for(i=0; i <NINZU; i++){          /*学生3人分のデータをキーボードから入力 scanf関数*/
        printf("学生番号を入れてください : ");
        scanf("%d",&(ps+i)->bango);
        printf("4科目の得点をスペースで区切って入れてください : ");
        scanf("%d %d %d %d",&(ps+i)->ten[0], &(ps+i)->ten[1],
            &(ps+i)->ten[2], &(ps+i)->ten[3]);
    }
    printf("\n");

    printf("    国語    算数     理科    社会    平均     評価\n");

    for(i=0; i<NINZU ;i++) {
        (ps+i)->ave = get_ave(ps+i);
        (ps+i)->hyoka = get_hyoka(ps+i);

        printf("%4d    %4d     %4d    %4d    %4d    %5.2f     %1c\n",
            (ps+i)->bango,(ps+i)->ten[0],(ps+i)->ten[1],(ps+i)->ten[2],(ps+i)->ten[3],
            (ps+i)->ave,(ps+i)->hyoka);
    }
}

double get_ave(DATA *s)
{
    int i;
    double ave=0.0;

    for (i=0; i<KAMOKU; i++) ave+= (s->ten[i]);
    return ave/=KAMOKU;
}

char get_hyoka(DATA *s)
{
    char hyoka;

    if (s->ave < 60.0) hyoka = 'D';
    else if (s->ave < 70.0) hyoka = 'C';
    else if (s->ave < 80.0) hyoka = 'B';
    else hyoka = 'A';
    return hyoka;
}

どうだろうか、ちょっとは本物に近いプログラムの感じがしてきたね。

2 ファイル操作

大体、プログラムはデータをファイルから読み出したり、書き込んだりするものが大半だろう。

70〜80年代は現在みたいに、Microsoft Accessのような良質なデータベースがなかったので、COBOL言語などを駆使して、索引編成ファイルを作り、データ入出力や検索なんかをしていた。

そんな時代が懐かしいけふ九重ににほひぬるかなである。〜なんのこっちゃ?

さて、私が言いたかったのはC言語にも標準ライブラリにファイル操作のための関数が用意されているということである。

ここらへんは覚えておいても損はない。早速見て行くことにしよう。

(1) ファイルのオープン fopen関数

ファイルをオープンさせる。

[ヘッダ] stdio.h

[形 式] FILE *fopen(const char *filename, const char *mode);

[引 数] filename:ファイル名を表す文字列を指すポインタ

      mode:オープンモード(直後に説明)を表す文字列へのポインタ

[返却値] ファイルオープン成功時は、構造体FILEの実体へのポインタを返す。失敗時はNULLポインタ。

ファイルはまず、アクセスに先だってファイルをオープンしなければ話にならない。C言語の場合、このfopen関数を使うことによって、stdio.hの中に定義されている構造体FILEの実体が作成されることになる。

さて、引数のうちオープンモードmodeの指す文字列は次のうちのいずれかに限られている。

"r" 読み取り専用のテキストファイルをオープン

    ファイルが存在しなかったり、読めこめない場合はエラーになる。

"w" 書き込み専用のテキストファイルをオープン

    ファイルが存在する場合はファイル内容を全部消してしまうから注意!

    ファイルが存在しない場合は自動的に作成する。

"a" 追加書き込み用のテキストファイルをオープンする。

    ファイルが存在しない場合は自動的に作成する。書き込みはファイルの最後EOF(End of File)から行われる。

"rb" 読み取り専用のバイナリファイルをオープンする。

    ファイルが存在しなかったり、読めこめない場合はエラーになる。

"wb" 書き込み専用のバイナリファイルをオープンする。 

    ファイルが存在する場合はファイル内容を全部消してしまうから注意!

    ファイルが存在しない場合は自動的に作成する。

"ab" 追加書き込み用のバイナリファイルをオープンする。

    ファイルが存在しない場合は自動的に作成する。書き込みはファイルの最後EOF(End of File)から行われる。

"r+" 更新(読み取り、書き込み両方)用のテキストファイルをオープンする。

"w+" 更新用のテキストファイルをオープンする。

    ファイルが存在する場合はファイル内容を全部消してしまうから注意!

    ファイルが存在しない場合は自動的に作成する。

"a+" 追加更新用のテキストファイルをオープンする。

    ファイルが存在しない場合は自動的に作成する。書き込みはファイルの最後EOF(End of File)から行われる。

"r+b"または"rb+" 更新(読み取り、書き込み両方)用のバイナリファイルをオープンする。

"w+b"または"wb+" 更新用のバイナリファイルをオープンする。

    ファイルが存在する場合はファイル内容を全部消してしまうから注意!

    ファイルが存在しない場合は自動的に作成する。

"a+b"または"ab+" 追加更新用のバイナリファイルをオープンする。

    ファイルが存在しない場合は自動的に作成する。書き込みはファイルの最後EOF(End of File)から行われる。

フゥーと疲れるぜ。それじゃ早速試してみよう、おっと、その前にファイルクローズを先に見ておこう。

ファイルを開けりゃ必ず閉めないとファイルくんがコワレちまうからな。

(2) ファイルのクローズ fclose関数

ファイルをクローズする。

[ヘッダ] stdio.h

[形 式] int fclose(FILE *pt);

[引 数] pt:クローズするファイルへのポインタ

[返却値] ファイルクローズ成功時は0(ゼロ)を返す。失敗時はEOFを返す。

さあ試してみよう。ここでは、ファイルが存在しない場合に自動的に作成する"w"、つまり書き込み用のテキストファイルをオープンしてクローズさせよう。

次のプログラムで青字の部分を好きなディレクトリに変更しておいてくれ。

#include <stdio.h>

void main()
{
    FILE *fp;

    if ((fp = fopen("C:\\c\\akr.txt","w") ) == NULL)
        printf("ファイルオープン失敗\n");
    else
        printf("ファイルを1つ作りました\n");

    fclose(fp);
}

どうだろうか、青字で指定したディレクトリに0KBのakr.txtというテキストファイルが出来たと思う。

バカみたい簡単なプログラムだが、ここで問題、赤字の部分でディレクトリの指定を\\としているが、なぜ\記号が2つになっているか分かるかね? 明智君。

C言語の場合\は文字定数とはみなされない。\は記号であり次の記号を表現するための手段にすぎない。

たとえば、 '  を表示させたい時などprintf(" \ ' "); と書く。

従いまして、コンパイラはprintf("C:\c");では C:cとしか受け取ってくれない。ディレクトリ記号の\を表示したいから、"C:\\c"と書いたんだ。

(3) 書式付き出力 fprintf関数

書式を指定してファイルに出力する。

[ヘッダ] stdio.h

[形 式] int fprintf(FILE *pt, const char *format);

[引 数] pt:出力するファイルへのポインタ

     format:printf関数に同じ

[返却値] 成功時は出力した文字数を返す。失敗時は負の値を返す。

これはprintf関数と全く同じで、出力する相手がCRTディスプレイではなく、ファイルであるというだけだ。

ではひとつ、さっき作った0KBのakr.txtに何か出力してやろう。gets関数で1行入力をEOFが入力されるまで繰り返すようにしておこう。EOFはキーボードではCTRLキーを押しながらZのキーを押すといい。

#include <stdio.h>

void main()
{
    FILE *fp;
    char a[20];
    char *pa;

    if ((fp = fopen("C:\\c\\akr.txt","a") ) == NULL)
        printf("ファイルオープン失敗\n");
    else{
        printf("データを入力してください、終了はCTRL+Z\n");
        while( (pa = gets(a) ) != NULL)
            fprintf(fp, "%s\n",pa);
    fclose(fp);
    }
}

どうだろうか、0KBのファイルが1KBくらいになっただろうか。ファイルの内容を、メモ帳かなにかテキストエディタで確認しておこう。

(4) 書式付き入力 fscanf関数

書式を指定してファイルから入力する。

[ヘッダ] stdio.h

[形 式] int fscanf(FILE *pt, const char *format);

[引 数] pt:入力するファイルへのポインタ

     format:scanf関数に同じデータの%sとかの形式と、格納する対象

[返却値] 成功時は入力データの個数返す。失敗時はEOFを返す。

この関数は便利だが、データの形式や個数を指定した通りにファイルに入れておく必要がある。特にデータ間の区切りは1つ以上のスペースで区切っておこう。

このテストとして、適当なテキストファイルを作って保存しておこう。たとえば、test.txtというファイルで内容は

Pell 123

Akira 234

Printf 1234

と文字列と整数を1つのスペースで区切ったデータを作って、c2というディレクトリに保存したと仮定して、次のプログラムを実行しよう。すると、

#include <stdio.h>

void main()
{
    FILE *fp;
    typedef struct test{
        char a[20];
        int n;
    }DATA;
   
    DATA pell;
    DATA *pa = &pell; /*別にポインタを設定しなくてもいいが、ポインタを使いたいのだ*/

    if ((fp = fopen("C:\\c2\\test.txt","r") ) == NULL)
        printf("ファイルオープン失敗\n");
    else{
        while( fscanf(fp, "%s %d",pa->a, &pa->n) != EOF) /*入力データを格納する対象*/
            printf("ファイルのデータは%sと%dです\n",pa->a, pa->n);

        fclose(fp);
    }
}

どうだろうか、test.txtの内容がEOFになるまでCRTに出力されただろうか。ファイルからの読み取り方法として最も多用されるのが、このfscanf関数といっても過言ではないだろう。

(5) 1文字入力 getc関数 またはfgetc関数

ファイルから1文字を読み取り、その値を返す関数で、getc関数とfgetc関数は等価である。

[ヘッダ] stdio.h

[形 式] int getc(FILE *pt);

                int fgetc(FILE *pt);

[引 数] pt:入力するファイルへのポインタ

[返却値] 成功時は入力した1文字を返す。失敗時はEOFを返す。

例として、さっきfscanf関数作ったtest.txtを今回はgetc関数で読み取って表示させてみよう。

#include <stdio.h>

void main()
{
    FILE *fp;
    int n;

    if ((fp = fopen("C:\\c2\\test.txt","r") ) == NULL)
        printf("ファイルオープン失敗\n");
    else{
        while( (n = getc(fp)) != EOF)
            printf("%c",n);
        printf("\n");
        fclose(fp);
    }
}

いかがだろうか、改行コードではちゃんと改行して、先ほどと同じ内容が表示がされたことを確認いただけただろうか。

(6) 1文字出力 putc関数 またはfputc関数

ファイルへ1文字書き込む、putc関数とfputc関数は等価である。

[ヘッダ] stdio.h

[形 式] int putc(int c, FILE *pt);

                int fputc(int c, FILE *pt);

[引 数] c:書き込む文字(unsigned char に変換されて書き込まれる)

            pt:入力するファイルへのポインタ

[返却値] 成功時は非負の値を返す。失敗時はEOFを返す。

今度の例としては、先ほどと同じtest.txtをputc関数を使って1文字ずつ、別のテキストファイルにコピーしてみようと思う。

#include <stdio.h>

void main()
{
    FILE *fp_in, *fp_out;
    int n;

    if ((fp_in = fopen("C:\\c2\\test.txt","r") ) == NULL)
        printf("入力ファイルオープン失敗\n");
    else{
        if ((fp_out = fopen("C:\\c2\\test2.txt","w") ) == NULL)
            printf("出力ファイルオープン失敗\n");
        else{
            while( (n = getc(fp_in)) != EOF){
                putc(n, fp_out);
                printf("%c",n);
            }
        fclose(fp_out);
        printf("\n");
        printf("text2にコピーしました\n");
        }
        fclose(fp_in);
    }
}

いかがだろうか、getc関数でEOFが来るまで1文字ずつtest.txtから読み込みながら、putc関数で1文字ずつ別のファイル、この場合はtest2.txtに書き込んでいく方法が分かっていただけただろうか。

(7) 1行入力 fgets関数

ファイルから1行分の文字列を読み込む。

[ヘッダ] stdio.h

[形 式] char *fgets(char *s, int n,  FILE *pt);

[引 数] s:文字列を格納する文字型配列へのポインタ

            n:読み取る最大の文字数(最後のNULLを含む)

      pt:入力するファイルへのポインタ

[返却値] 成功時は文字列sへのポインタを返す。失敗時はNULLポインタを返す。

改行コードかEOFに出会うまで読み取りを続ける関数で、getcで1文字ずつ読み取るより効率はいいだろう。

またここで、例としてtest.txtをfgets関数で1行ずつ読み取らせて表示させてみよう。

#include <stdio.h>

void main()
{
    FILE *fp;
    char a[20];
    char *pa;

    if ((fp = fopen("C:\\c2\\test.txt","r") ) == NULL)
        printf("入力ファイルオープン失敗\n");
    else{
        while( (pa = fgets(a, 20, fp)) != NULL)
            printf("%s",pa); /*改行コードも読み取った文字列に含まれている*/
        printf("\n");
        fclose(fp);
    }
}

まあ、こんな感じだろう。

(8) 1行出力 fputs関数

ファイルに1行分の文字列を書き込む。

[ヘッダ] stdio.h

[形 式] int fputs(const char *s, FILE *pt);

[引 数] s:書き込みたい文字列を指すポインタ

      pt:出力するファイルへのポインタ

[返却値] 成功時は非負の値を返す。失敗時はEOFを返す。

ここは早速テストしてみよう。さきほどfgets関数で入力ファイルtest.txtから1行分読み込んだ文字列を、そのまま別の出力ファイルtest2.txtに書き込んで行こう。

#include <stdio.h>

void main()
{
    FILE *fp_in, *fp_out;
    char a[20];
    char *pa;

    if ((fp_in = fopen("C:\\c2\\test.txt","r") ) == NULL)
        printf("入力ファイルオープン失敗\n");
    else{
        if ((fp_out = fopen("C:\\c2\\test2.txt","w") ) == NULL)
            printf("出力ファイルオープン失敗\n");
        else{
            while( (pa = fgets(a, 20, fp_in)) != NULL){
                fputs(pa, fp_out);
                printf("%s",pa);
            }
            fclose(fp_out);
        }
        printf("\n");
        printf("test.txtをtest2.txtにコピーしました\n");
        fclose(fp_in);
    }
}

いかがだろうか。ちゃんと行ったかな?

 

それではここで、ファイル処理用ライブラリ関数を利用した応用例題を見てみよう。

カレントディレクトリ(実行ファイル、つまり拡張子にexeがつくファイルと同じディレクトリ)にある、学生番号、名前、4科目の得点が記録されているファイルをfscanf関数で読み込んで、平均点と評価を算出してfprintf関数で別のファイルに書き込むという、極めて現実的な例題だ。

ここで注意すべきは、出力用レコードとして構造体を使用するが、名前の部分は文字列へのポインタを構造体のメンバとするが、ブルース・リー氏の教えに従い、ポインタには直接fscanf関数を使わないようにしよう。そのためには、別に名前を入れる文字列配列を用意しておき、構造体のメンバこれを実体として指さすようにしておこう。

それともう一つ、本当に注意してほしいのが、scanf関数もそうだが、fscanf関数を使う場合、データの並びと個数が、プログラムで示したとおりにデータファイルがなっているかを確認しておこう。

私もよくこいつに泣かされたが、たとえばfscanf関数で %d %s %d %dと4種類のデータをスペースで区切るとしておいて、データファイルの方がたとえば、1001 Akira 98 88 78のように最後の78が余分に付いてしまってたりしていたら、もう悲惨だ。コンパイルは出来ても実行時のエラーが出てどうしようも I can notとなってしまうから、本当に注意してね。

今回は、次のデータをrei.datと名付けて、次のプログラムの実行ファィル(exeファィル)が出来るディレクトリに置いておこう。

1001 Akira 98 88 78 99
1002 Pell 100 99 98 100
1003 Putaro 34 23 12 67
1004 Jiro 56 89 77 55
1005 Sae 87 66 73 44
1006 Tom 45 23 10 60

それではこれら、わが師ブルース・リーの教えを守って次の例題を見て実行してみよう。

ちょっと長いが、構造体を思い出して考えてみてね。

#include <stdio.h>

#define KAMOKU 4

typedef struct seiseki{
    int bango;
    char *name;
    int ten[KAMOKU];
    double ave;
    char hyoka;
} DATA;

char namae[20];

void seiseki_syori(FILE *,FILE *);

void main()
{
    FILE *fp_in, *fp_out;

    if( (fp_in=fopen("rei.dat","r"))==NULL)
        printf("入力ファイルなし\n");
    else{
        if(( fp_out=fopen("kekka.dat","w"))==NULL)
            printf("出力ファイルエラー\n");
        else{
            seiseki_syori(fp_in,fp_out);
            fclose(fp_out);
        }
        fclose(fp_in);
    }
}

void seiseki_syori(FILE *fp_in, FILE *fp_out)
{
    DATA s;
    DATA *ps;
    int a;
    ps=&s;

    ps->name = namae;

    fprintf(fp_out,"番号    氏名     国語    算数    理科     社会    平均点    評価\n");

    while(fscanf(fp_in,"%d %s %d %d %d %d",&ps->bango, namae,
        &ps->ten[0], &ps->ten[1], &ps->ten[2], &ps->ten[3]) != EOF){
       
        printf("%d %s %d %d %d %d\n",ps->bango, ps->name, ps->ten[0],
            ps->ten[1], ps->ten[2], ps->ten[3]);

        ps->ave=0;
        for (a=0; a<KAMOKU; a++) ps->ave += ps->ten[a];
        ps->ave /= KAMOKU;

        if (ps->ave < 60.0) ps->hyoka='D';
        else if (ps->ave < 70.0) ps->hyoka='C';
        else if (ps->ave < 80.0) ps->hyoka='B';
        else ps->hyoka='A';

        fprintf(fp_out,"%4d    %s     %4d    %4d    %4d    %4d     %5.2f    %1c\n",ps->bango,
            ps->name, ps->ten[0], ps->ten[1], ps->ten[2], ps->ten[3],
            ps->ave, ps->hyoka);
    }
}

どうだろうか、うまく結果のファイルkekka.datが勝手に同じディレクトリに作成されただろうか。

この場合、ディレクトリを指定していないから、ファイルはカレントディレクトリにあるものとして、勝手にコンパイラくんが判断してしまうんだ。

(9) ファイル位置表示子を得る ftell関数

ファイルの現在における位置表示子を得る。

[ヘッダ] stdio.h

[形 式] long int ftell( FILE *pt);

[引 数] pt:ファイルへのポインタ

[返却値] 成功時はファイル位置(不定値であるがfseek関数で利用できる値)を返す。ただし使用される       ファイルがバイナリファイルの場合は、ファイルの先頭からの文字数のバイト数が返される。

            失敗時は-1Lを返す。

その昔、COBOLで索引編成ファイルを作ったことがある。これと同じことがC言語ではもっと簡単にできることを知って驚いている。

それでは、前に使ったtest.txtの1行ごとのデータのファイル位置を見てみよう。

#include <stdio.h>

void main()
{
    FILE *fp;
    char a[20], *pa;
    long int pos;

    if ((fp = fopen("C:\\c2\\test.txt","r") ) == NULL)
        printf("ファイルオープン失敗\n");
    else{
        pos = ftell(fp);
        while( (pa = fgets(a,20,fp)) != NULL){
            printf("ファイル位置は%-5ld データは%s",pos,pa);
            pos = ftell(fp);
        }
        printf("\n");
        fclose(fp);
    }
}

どうだろう、ファイル位置が見事に表示されただろうか。

(10) ファイル位置表示子変更 fseek関数

ファイルの現在における位置表示子を変更する。

[ヘッダ] stdio.h

[形 式] int fseekl( FILE *pt, long int offset, int whence);

[引 数] pt:ファイルへのポインタ

            offset:変更したい位置

            whence:次の3つのいずれかを指定、

       SEEK_SET ファイルの初め

       SEEK_CUR 現在値

       SEEK_END ファイルの終わり

[返却値] 成功時は0(ゼロ)を返す。失敗時は0(ゼロ)以外を返す。

バイナリファイルの場合はSEEK_ENDの値は不定であり使わない方がいいが、しかし、whence+offsetで新しい位置が設定できるので簡単である。

テキストファイルの場合は、offsetが0か、ftell関数で求めた値でなければ動かない。またテキストファイルの場合でftell関数で求めた値を使用するとき、whenceはSEEK_SETのみが使用できる。

これも早速テストしてみよう。まず前のftell関数で求めた値を表示させて、好きな位置にファイル位置を変更できるしよう。scanf関数を使ってファイル位置を入力させるようにして、EOF(CTRLとZを押す)が入力されるまで、入力を促す表示をさせるようにしよう。

#include <stdio.h>

void main()
{
    FILE *fp;
    char a[20], *pa;
    long int pos;

    if ((fp = fopen("C:\\c2\\test.txt","r") ) == NULL)
        printf("ファイルオープン失敗\n");
    else{
        pos = ftell(fp);
        while( (pa = fgets(a,20,fp)) != NULL){
            printf("ファイル位置は%-5ld データは%s",pos,pa);
            pos = ftell(fp);
        }
        printf("\n\n");

        printf("ファイルの変更位置を入力してください:");
        while(scanf("%ld",&pos) != EOF){
        fseek(fp, pos, SEEK_SET);
        pa = fgets(a,20,fp);
        printf("指定位置は%-5ld データは%s\n",pos,pa);
        printf("ファイルの変更位置を入力してください:");
        }
        fclose(fp);
    }
}

どうだろうか、いろいろなファイル位置を入れてみて、データのないファイル位置の場合は「データは(null)」と表示されるのを確認していただけただろうか。

私もよく失敗するので人のことは言えないが、scanf関数とprintf関数の引数をごっちゃにしないようにしよう。scanf関数の場合の引数は実体ではなく、ポインタつまり実体のアドレスであります。

3 文字列操作

標準ライブラリで最後に紹介するのは、文字列を複写したり、連結したり、比較したりと、使用頻度の高い文字列操作の関数である。

(1) 文字列の複写 strcpy関数

文字列配列に格納されている文字列を、他の文字列配列へ複写する。

[ヘッダ] string.h

[形 式] char *strcpy(char *s1, const char *s2);

[引 数] s1:複写を格納する文字列配列へのポインタ

               s2:複写元の文字列配列へのポインタ

[返却値] s1の値を返す

s2の実体である文字列を末尾のNULLを含めて複写する。仮にs1とs2の実体がメモリ空間で重なっている場合の動作は定義されていない。

さっそくテストをしてみよう。

#include <stdio.h>
#include <string.h> /*これをincluding*/

void main()
{

    char s[20];
    char m[20];
    char *mei;
    mei=m;

    printf("あなたのお名前なんていうの? s[20] :");
    scanf("%s",s);
    strcpy(m, s);
    printf("%sをm[20]にコピーしました\n",mei);
}

いかがだろうか。コピーできましたかな?

(2) 文字数を指定した文字列の複写 strncpy関数

文字列配列に格納されている文字列を、他の文字列配列へ複写する。

[ヘッダ] string.h

[形 式] char *strncpy(char *s1, const char *s2, size_t n);

[引 数] s1:複写を格納する文字列配列へのポインタ

               s2:複写元の文字列配列へのポインタ

           n:   複写する最大の文字数

[返却値] s1の値を返す

s1の実体である文字列に、s2の実体から何文字複写するかを指定する。この何文字にNULLが含まれない場合はNULLは複写されないので注意。

s2の実体の文字列が最大文字数として指定したnより小さいときは、s1の実体である文字配列の残りの部分の全部にNULLを入れる。

これまた早速テストしてみよう。

#include <stdio.h>
#include <string.h> /*これをincluding*/

void main()
{

    char s[20];
    char m[20];
    char *mei;
    mei=m;
    int n;

    printf("あなたのお名前なんていうの? s[20] :");
    scanf("%s",s);
    printf("複写する最大文字数を入れてください :");
    scanf("%d",&n);
    strncpy(m, s, n);
    printf("%sをm[20]にコピーしました\n",mei);
}

あらかじめ、文字配列を大きめに確保しておくことがミソだね。

(3) 文字列の連結 strcat関数

文字列配列に格納されている文字列を、他の文字列配列の後ろへ複写する。

[ヘッダ] string.h

[形 式] char *strcat(char *s1, const char *s2, size_t n);

[引 数] s1:複写を格納する文字列配列へのポインタ

               s2:複写元の文字列配列へのポインタ

[返却値] s1の値を返す

String concatenate(文字連結)からとったstrcatで、一見すると文字列をカットするのかと思ってしまうが、その反対で連結する方なので注意すること。

s2の実体をs1の実体の後方に連結する。この場合、s1の実体である文字列の末尾にあるNULLは、s2の最初の文字で上書きされる。

また、s1の実体の後にs2の実体を追加して、s1の実体の配列の大きさを超えてしまったときの動作は定義されていない。このように、C言語は最低限の機能しか定義されていない比較的低水準な言語と言えよう。

さあ、テストだ。

#include <stdio.h>
#include <string.h> /*これをincluding*/

void kugiri(char *);
void main()
{

    char s[20];
    char m[20];
    char *sei;
    sei = s;

    printf("あなたの姓はなんていうの? s[20] :");
    scanf("%s",s);
    kugiri(sei);
    printf("あなたの名はなんていうの? m[20] :");
    scanf("%s",m);
    strcat(s, m);
    printf("あなたの姓名は%sです\n",sei);
}

void kugiri(char *s)
{
    int i;
    for(i=0; i < 18 ; i++){
        if(*(s+i) == NULL){
            *(s + i++) = ' ';
            *(s+i) = NULL;
            break;
        }
    }
}

姓と名の間にスペースを入れるために、関数kugiriを作ってみた。このように色々工夫してC言語を楽しもう。

(4) 文字数を指定した文字列の連結 strncat関数

文字列配列に格納されている文字列の先頭からn文字を、他の文字列配列の後部へ結合する。

[ヘッダ] string.h

[形 式] char *strcpy(char *s1, const char *s2, size_t n);

[引 数] s1:複写を格納する文字列配列へのポインタ

               s2:複写元の文字列配列へのポインタ

           n:   複写する最大の文字数

[返却値] s1の値を返す

複写されるs2の実体の文字列がn文字未満の場合はNULLまでを複写して連結する。s1の実体の文字列最後を示すNULLは、s2の先頭文字に上書きされる。

さあ、これも例を見ようか。先ほどの例を使おう。

#include <stdio.h>
#include <string.h> /*これをincluding*/

void kugiri(char *);
void main()
{

    char s[20];
    char m[20];
    char *sei;
    int n;
    sei = s;

    printf("あなたの姓はなんていうの? s[20] :");
    scanf("%s",s);
    kugiri(sei);
    printf("あなたの名はなんていうの? m[20] :");
    scanf("%s",m);
    printf("姓の文字数を入れてください :");
    scanf("%d",&n);
    strncat(s, m, n);
    printf("あなたの姓名は%sです\n",sei);
}

void kugiri(char *s)
{
    int i;
    for(i=0; i < 18 ; i++){
        if(*(s+i) == NULL){
            *(s + i++) = ' ';
            *(s+i) = NULL;
            break;
        }
    }
}

いかがだろうか。ここらへんは特に問題はないかと思われる。

(5) 文字列の比較 strcmp関数

2つの文字列を、辞書順に比較する。

[ヘッダ] string.h

[形 式] char *strcmp(const char *s1, const char *s2);

[引 数] s1, s2:文字列へのポインタ

[返却値] s1の実体 > s2の実体のときは正の値

       s1の実体 == s2の実体のときは0

       s1の実体 < s2の実体のときは負の値

文字列の比較、これもよく使う関数である。比較順序はASCIIコードの順である。

さあテストしてみよう。

#include <stdio.h>
#include <string.h>

void main()
{
    char a[20],b[20],*pa,*pb;
    int n;
    pa=a;
    pb=b;

    printf("a[]の文字列を入れてください : ");
    scanf("%s",pa);
    printf("b[]の文字列を入れてください : ");
    scanf("%s",pb);

    printf("a[]=%s\n",pa);
    printf("b[]=%s\n",pb);

    printf("a[]とb[]を比較すると、");
    if((n=strcmp(pa,pb)) >0) printf("a[] > b[]\n");
    else if (n== 0) printf("a[] = b[]\n");
    else printf("a[] < b[]\n");
}

いかがだろうか。なかなか面白いね。

(6) 文字数を指定しての文字列の比較 strncmp関数

2つの文字列の先頭からn文字を、辞書順に比較する。

[ヘッダ] string.h

[形 式] char *strncmp(const char *s1, const char *s2, size_t n);

[引 数] s1, s2:文字列へのポインタ

            n:比較する文字数

[返却値] s1の実体 > s2の実体のときは正の値

       s1の実体 == s2の実体のときは0

       s1の実体 < s2の実体のときは負の値

比較する文字列つまり、s1とs2の実体がn未満のときは、NULLまでについて比較する。

さあ、先の例をちょっと変えてみよう。

#include <stdio.h>
#include <string.h>

void main()
{
    char a[20],b[20],*pa,*pb;
    int m, n;
    pa=a;
    pb=b;

    printf("a[]の文字列を入れてください : ");
    scanf("%s",pa);
    printf("b[]の文字列を入れてください : ");
    scanf("%s",pb);
    printf("比較する文字数を入れてください : ");
    scanf("%d",&m);

    printf("a[]=%s\n",pa);
    printf("b[]=%s\n",pb);

    printf("a[]とb[]を%d文字について比較すると、" ,m);
    if((n=strncmp(pa,pb,m)) >0) printf("a[] > b[]\n");
    else if (n== 0) printf("a[] = b[]\n");
    else printf("a[] < b[]\n");
}

いかがだろうか、これも特にコメントはいらない感じだね。

(7) 文字列の長さ strlen関数

文字列の長さを求める。

[ヘッダ] string.h

[形 式] size_t strlen(const char *s);

[引 数] s:文字列へのポインタ

[返却値] NULLを除く文字列の長さを返す

これも例をみよう。その方が早い。

#include <stdio.h>
#include <string.h>

void main()
{
    char a[20],*pa;
    unsigned int n;
    pa=a;

    printf("何か文字列を入れてください : ");
    scanf("%s",pa);

    n = strlen(pa);

    printf("%sの長さは%dです\n",pa, n);
}

ここで、size_tを説明しておこう。先ほどからstrncpy 、strncatの各関数の文字数にも使用してきたが、size_tは、符号なしの整数型で、以前説明したsizeof演算子の結果を示すものとして、標準ライブラリの<stddef><stdio.h><string.h><time.h><stdlib.h>で定義済みのものであり、これらのライブラリを使う限り特に意識する必要はない。

さて、またここらへんで例題をやってみよう。さっきのstrcat関数で姓と名の間にスペースを入れるのに、わざわざ関数を作ったが、strcat関数を使えば簡単にスペースを姓に追加できる。

これを利用して、姓と名を2回入力させて、姓と名を結合した上で、2つの姓名を比較して比較等号記号を加えて、両方の姓名を表示してみよう。

#include <stdio.h>
#include <string.h>

void main()
{

    char s[2][20];
    char m[2][20];
    char *sei[2];
    char *mei[2];
    char **p_s;
    char **p_m;
    int i;

    sei[0] = s[0]; /*ここでしっかりとポインタの実体を各配列ごとに設定してやること*/ 
    mei[0] = m[0];
    sei[1] = s[1];
    mei[1] = m[1];
    p_s = sei;
    p_m = mei;

    for(i = 0; i < 2; i++){
        printf("あなたの姓はなんていうの? :");
        scanf("%s", *(p_s+i));
        strcat(*(p_s+i), " ");
        printf("あなたの名はなんていうの? :");
        scanf("%s",*(p_m+i));
        strcat(*(p_s+i), *(p_m+i));
        printf("\n");
    }

    if(strcmp(*p_s , *(p_s+1)) < 0)
        printf("%s < %s\n", *p_s, *(p_s+1));
    else if(strcmp(*p_s , *(p_s+1)) == 0)
        printf("%s = %s\n", *p_s, *(p_s+1));
    else
        printf("%s > %s\n", *p_s, *(p_s+1));
}

どうだろうか、ここではわざわざポインタ型の2次元配列へのポインタを宣言したが、かえって複雑なプログラムにしてしまって反省している。

しかし、これはポインタを真に理解していただきたいためにやったことである。ポインタはいかに実体がないものであるか、そしてポインタに実体を指差せるようにしてやることがどんなに大切なことであるかを知っておいてほしかった、それだけのことである。

4 文字数値変換

キーボードやファイルからデータ入力する方法を見てきたが、scanf関数やfscanf関数でフォーマットを指定して入力する以外は全て文字列として入力される。

これを数値型に変更するにはどうしたらいいのだろうか。

(1) 文字列を整数へ変換 atoi関数

文字列をint型の数値に変換する。

[ヘッダ] stdlib.h

[形 式] int atoi(const char *s);

[引 数] s:文字列へのポインタ

[返却値] 文字列をint型に変換した値を返す

文字列のはじめに空白があれば無視する。'0' 〜 '9' およびNULLが検出されるまで数値に変換する。

さっそくテストしよう。

#include <stdio.h>
#include <stdlib.h> /*これをincluding*/

void main()
{
    char a[20], *pa;
    int n;

    pa=a;

    printf("文字列を入力してください : ");
    scanf("%s",pa);
    printf("入力文字列は%sです\n",pa);

    n=atoi(pa);

    printf("10進数では %5d\n",n);
    printf("16進数では %8.8x\n",n);
}

たとえば、"123"と入れると、数値に変換されて表示される。いろいろ入れてみよう。

(2) 文字列をlong型整数へ変換 atol関数

文字列をlong int型の数値に変換する。

[ヘッダ] stdlib.h

[形 式] int atol(const char *s);

[引 数] s:文字列へのポインタ

[返却値] 文字列をlong int型に変換した値を返す

これもテストで実感しよう。とくに問題点はないだろう。

#include <stdio.h>
#include <stdlib.h> /*これをincluding*/

void main()
{
    char a[20], *pa;
    long int n;

    pa=a;

    printf("文字列を入力してください : ");
    scanf("%s",pa);
    printf("入力文字列は%sです\n",pa);

    n=atol(pa);

    printf("長整数型10進数では %10ld\n",n);
    printf("16進数では %8.8x\n",n);
}

いかがだろうか。いろいろ試してみよう。

(3) 文字列を浮動小数へ変換 atof関数

文字列を浮動小数型の数値に変換する。

[ヘッダ] stdlib.h

[形 式] int atof(const char *s);

[引 数] s:文字列へのポインタ

[返却値] 文字列をdouble型に変換した値を返す

これもすぐに試してほしいアイテムだ。出力は小数点のついた実数が返される。

たとえば、"31.4e-1"を入れるとどうなるかな?

#include <stdio.h>
#include <stdlib.h> /*これをincluding*/

void main()
{
    char a[20], *pa;
    double n;

    pa=a;

    printf("文字列を入力してください : ");
    scanf("%s",pa);
    printf("入力文字列は%sです\n",pa);

    n=atof(pa);

    printf("10進数では %8.2f\n",n);
}

どうだろうか、ちゃんと3.14と表示されただろうか。

ここで、ひさしぶりにargcとargvを使ってatoi関数の例題をやってみよう。

次のプログラムはコマンドラインの引数からたとえば、プログラム名 10 + 10と入力された場合、

10 + 10 = 20と表示するプログラムだ。

また、MS-DOSコマンド画面を起動して、実行ファイルのあるディレクトリにchdirで変えた上で実行されたい。

#include <stdio.h>
#include <stdlib.h>

void main(int argc, char **argv)
{
    int x1 ,x2 ,y;
    char op;

    if(argc != 4) printf("入力データエラー\n");
    else{
        printf("プログラム名は%sです\n",*argv);
        argv++;
        x1=atoi(*argv++);
        op= **argv++;
        x2=atoi(*argv);

        switch(op){
        case '+':
            y=x1 + x2;
            break;
        case '-':
            y=x1 - x2;
            break;
        case '*':
            y=x1 * x2;
            break;
        case '/':
            y=x1 / x2;
            break;
        default:
            printf("演算子エラー");
            break;
        }
        printf("%d %c %d = %d\n",x1,op, x2, y);
    }
}

どうだろうか、コマンドラインの3番目の仮引数により四則演算を選択させ、得意のbreakでswitch文から脱出していることが確認できただろうか。

しかし、なつかしいね。void main(int argc, char **argv)は。

5 記憶領域管理

プログラム実行中にメモリ領域の確保を手動で行う関数を見てみよう。これらは通常ヒープ領域と呼ばれる割り当て可能なメモリ領域に確保される。

(1) 記憶域の確保 malloc関数

指定された大きさの記憶領域を動的に割り付ける。

[ヘッダ] stdlib.h

[形 式] void *malloc(size_t size);

[引 数] size:確保したい領域の大きさ

[返却値] 成功時は割り付けた領域へのポインタを返す。失敗時はNULLポインタを返す。

これについてテストをする前に、手動で確保したヒープ領域を開放する関数を同時に見ておこう。

そうしないと、大切なリソースを無駄にするおそれがある。

(2) 記憶域の解放 free関数

動的に割り付けた記憶領域を解放する。

[ヘッダ] stdlib.h

[形 式] void free(void *pt);

[引 数] pt:解放したい領域へのポインタ

[返却値] なし。

ここで、ポインタptは、以前にmalloc関数などの記憶域確保により返されたポインタでなければならない。

ptがNULLポインタである場合は何もしない。

以上のことを念頭において、例題を見てみよう。

最初に文字数nをscanf関数で入力させて、char型の領域をNULLの分も含めて、n+1個の記憶領域を動的に割り付けて、最後に解放しよう。

#include <stdio.h>
#include <stdlib.h> /*これをincluding*/

void main()
{
    int n,m;
    void *pt;
    char a;

    printf("文字数を入力してください : ");
    scanf("%d",&n);

    m=sizeof(a) * (n+1); /*必要な記憶領域の大きさを計算*/
    pt=malloc(m); /*記憶域の動的割り付け*/
    if (pt!=NULL){
        printf("〜記憶域を%d確保しました\n",m);
        printf("%d個の文字を入力してください : ",n);
        scanf("%s",pt);
        printf("入力データは%s\n",pt);
        free(pt); /*記憶域の解放*/
        printf("〜記憶域を解放しました\n",m);
    }
    else
        printf("アロケーション不可\n");
}

いかがだろうか。記憶領域が動的に割り付けられて、解放されたメッセージが無事に出ただろうか。変数aを1回も使っていないから、コンパイルちゃんから警告を出されたかも知れないね。

前回はchar型であることがコンパイラにも分かっていたからいいが、たとえば自分で設計した構造体なんかのポインタに記憶領域を動的割り付けする場合は、malloc関数にキャスト演算子で教えておいてやる必要がある。

これが以前に北斗の拳が言った「ポインタには領域などな〜い」の使い方である。

次にその方法を例題として見てみよう。

n人分の学生の4科目の得点をキーボードから入力させて、平均点を関数で計算し、それによって関数で評価を付け、学生番号、各科目の得点、評価を表示するものである。

最初にscanf関数により学生人数nを入力させる。学生の記録は構造体によるレコードとし、malloc関数で構造体の人数分の記憶領域を動的に割り当てよう。そして最後にこれらを解放しておこう。

かつ、平均点を求める関数、評価を求める関数および表示する関数は構造体のアドレスによるそれぞれの関数への渡しを採用し、平均点を求める関数、評価を求める関数は構造体のアドレスを返す関数にしておこう。

それでは見てみよう。

#include <stdio.h>
#include <stdlib.h>

#define KAMOKU 4

typedef struct seiseki {
    int bango;
    int ten[KAMOKU];
    double ave;
    char hyoka;
}DATA;

DATA *get_ave(DATA *);
DATA *get_hyoka(DATA *);
void print_out(DATA *);

void main()
{
    int i;
    char a[10];
    int n, m;
    DATA *pt;

    printf("人数を入れてください : ");
    n = atoi(gets(a));
    m = sizeof(DATA)*n;
    pt = (DATA *)malloc(m); /*記憶領域の動的割り付け*/

    for(i=0; i<n; i++){
        printf("学生番号を入力してください(%d/%d) : ",i+1, n);
        scanf("%d",&((pt+i)->bango));
        printf("4科目の点をスペースで区切って入力してください : ");
        scanf("%d %d %d %d",&((pt+i)->ten[0]), &((pt+i)->ten[1]),
            &((pt+i)->ten[2]), &((pt+i)->ten[3]));
        printf("\n");
    }
    printf("\n番号    国語    算数     理科    社会    平均点     評価\n");

    for(i=0; i<n; i++){
        get_ave((pt+i));
        get_hyoka((pt+i));
        print_out((pt+i));
    }
    free(pt); /*記憶領域の解放*/
}

DATA *get_ave(DATA *pt)
{
    int i;

    pt->ave=0.0;
    for(i=0; i<KAMOKU; i++)    pt->ave += pt->ten[i];
    pt->ave /= 4;
    return pt;
}

DATA *get_hyoka(DATA *pt)
{
    pt->hyoka = ' ';

    if ( pt->ave < 60.0) pt->hyoka='D';
    else if ( pt->ave < 70.0) pt->hyoka='C';
    else if ( pt->ave < 80.0) pt->hyoka='B';
    else pt->hyoka='A';
    return pt;
}

void print_out(DATA *pt)
{
    printf("%4d    %4d    %4d     %4d    %4d    %5.2f     %1c\n",pt->bango, pt->ten[0],
        pt->ten[1], pt->ten[2], pt->ten[3], pt->ave, pt->hyoka);
}

どうだろうか。実行していただければ、隅々まで細かい配慮があることが分かっていただけるだろうか。

 

それでは、ライブラリ関数の終わりに、もう一度例題を見ておこう。

ここでもやはり構造体の記憶領域を動的に割り当てる方法を採用した。

最初に学生の人数nをscanf関数により入力させて、n人分の学生の番号、名前、得点をn回キーボードから入力してもらい、平均点を表示の上、学生n人各自の記録に平均との差を加えて表示するものである。

#include <stdio.h>
#include <stdlib.h>

typedef struct seiseki{
    int bango;
    char name[10];
    int ten;
    int sa;
}DATA;

int get_heikin(DATA *, int);
void print_out(DATA *);
void main()
{
    int i, ave, nin;
    DATA *pt;

    printf("学生の人数を入れてください :");
    scanf("%d", &nin);

    pt = (DATA *)malloc(sizeof(DATA) * nin);
   
    for(i=0; i < nin; i++ ){
        printf("-----%d/%d-----\n", i+1, nin);
        printf("番号を入力してください : ");
        scanf("%d",&((pt+i)->bango));
        printf("名前を入力してください : ");
        scanf("%s", (pt+i)->name);
        printf("得点を入力してください : ");
        scanf("%d", &((pt+i)->ten));
        printf("\n");
    }

    ave = get_heikin(pt, nin);
    printf("平均点は%dです\n", ave);
   
    printf("\n番号    氏名    得点     差\n");
    for(i=0; i < nin; i++){
        (pt+i)->sa = ((pt+i)->ten) - ave;
        print_out(pt+i);
    }
    free(pt);
}

int get_heikin(DATA *p, int n)
{
    int i;
    int ave = 0;

    for(i=0; i < n; i++) ave += (p+i)->ten;

    ave /= n;

    return ave;
}

void print_out(DATA *pt)
{
    printf("%4d    %s    %4d     %4d\n", pt->bango, pt->name, pt->ten, pt->sa);
}

いかがだろうか。平均の出し方は最初から整数同士の計算で、小数点以下は切り捨てているのでご了承いただきたい。これを小数点以下第一位で四捨五入する方法など、考えられたら面白いだろう。

 

さあ、お疲れさまでした。以上をもって、やっと基本編は終了しました。

次はいよいよ演習編。これによって、C言語の実力を確固たるものにしよう!

 

基本編の最後に当たって、VOIDについてのブルース・リー氏の考えを見よう。

voidは何も返さない一番単純なもの。すなわち「戻り値なし」ということは事実であるが、それだけではない。

voidこそがC言語、C++の全てである。いや、人として生きる上の全てである。

それはブルース・リー氏のたえ間ない努力によって修得された人生観と言えよう。

それでは早速見てみよう。

The void by Bruce Lee


back  next


HOME