列挙型

列挙型(enumeration)は、名前付きの整数定数のリストで、整数が使用できるところならどこでも使用できる。列挙型の宣言の一般的な形式は次のとおり。

enum タグ名 {列挙リスト} 変数リスト;

「タグ名」と「変数リスト」のどちらかは省略できる。構造体と同様、「タグ名」が列挙型の型名となる(より厳密には「enum タグ名」で型を表す)。

次のプログラムは、列挙型 color_type とその変数 color を使って、値を出力している。
列挙型の実体は整数で、デフォルトではコンパイラによって 0 から割り当てられる。プログラムの出力を見ると、そのようになっているのがわかる。

#include

enum color_type {red, green, blue};

int main(void)
{
    enum color_type color;

    color = red;
    printf("%d\n", color);

    color = green;
    printf("%d\n", color);

    color = blue;
    printf("%d\n", color);

    return 0;
}
takatoh@nightschool $ ./sample_11_3
0
1
2

型修飾子

前にデータ型修飾子というのが出てきた。「型修飾子」は名前が似てて紛らわしいけど、別のもの。変数を宣言するときに、その変数の性質をコンパイラに伝えることができる。

const

宣言時に型の前に const をつけると、その変数はプログラム中で変更できなくなる。ただし、宣言時に初期化することはできる。次の例は正しい例。

#include

int main(void)
{
    const int i = 10;

    printf("%d\n", i);

    return 0;
}
takatoh@nightschool $ ./sample_11_2a
10

こっちは正しくない例。コンパイル時に read-only の変数に代入しているというエラーが出ている。

#include

int main(void)
{
    const int i = 10;

    i = 20;
    printf("%d\n", i);

    return 0;
}
takatoh@nightschool $ gcc sample_11_2b.c -o sample_11_2b
sample_11_2b.c: In function ‘main’:
sample_11_2b.c:8:5: error: assignment of read-only variable ‘i’
     i = 20;
     ^

宣言時に初期化しないで、あとから代入したらどうだろう?

#include

int main(void)
{
    const int i;

    i = 20;
    printf("%d\n", i);

    return 0;
}
takatoh@nightschool $ gcc sample_11_2c.c -o sample_11_2c
sample_11_2c.c: In function ‘main’:
sample_11_2c.c:8:5: error: assignment of read-only variable ‘i’
     i = 20;
     ^

同じエラーが出た。const で修飾した変数は、宣言時に初期化しないといけない。

volatile

宣言時に、型の前に volatile をつけると、その変数がプログラム中で明示されない方法で変更される可能性があることを、コンパイラに伝えることになる。「プログラム中で明示されない方法」というのは、例えば外部からの割り込みによって変更されるような場合だ。
コンパイラはプログラムをコンパイルするときに最適化を行うけど、volatile で修飾された変数は参照するたびに内容を調べ直すようになる。

記憶クラス指定子

C には、変数の格納方法を指定する「記憶クラス指定子」として、次の4つがある。

  • auto
  • static
  • extern
  • register

auto

auto は自動変数を宣言するためのもので、自動変数(automatic variables)とは、要するに単なるローカル変数のこと。ローカル変数はデフォルトで auto になるので、明示的に auto を使うことはまずない。

static

ローカル変数は、関数から抜けるとメモリ領域から削除されるけど、static を指定すると、ローカル変数の内容を複数の関数呼び出しにまたがって保持することができる。関数呼び出しのたびに初期化される普通のローカル変数と違って、static な変数の初期化は1回しか行われない。そして、値はプログラムが終了するまで保持される。
次のプログラムは、関数 f を10回呼び出している。関数 f の中の static な変数 count の値が、呼び出しのたびに初期化されることなく、前回の呼び出しの時の値を保持していることがわかる。

#include

void f(void);

int main(void)
{
    int i;

    for (i = 0; i < 10; i++) {
        f();
    }

    return 0;
}

void f(void)
 {
    static int count = 0;
    count++;
    printf("count: %d\n", count);
}
takatoh@nightschool $ ./sample_11_1a
count: 1
count: 2
count: 3
count: 4
count: 5
count: 6
count: 7
count: 8
count: 9
count: 10

extern

プログラムのサイズが大きくなった場合、C ではファイルを複数に分けることができる。このとき複数のファイルからアクセスする必要があるグローバル変数があったとする。グローバル変数は1回しか宣言することが出来ないので、複数のファイルそれぞれに宣言を書くことは出来ない。かといって、宣言がないとコンパイラが変数の宣言を見つけられずにエラーになってしまう。
こういう場合、通常の方法でグローバル変数を宣言するのは1つのファイルだけにしておいて、その他のファイルでは extern 指定子を使って宣言する。extern はそのグローバル変数が別のファイルで宣言されていることをコンパイラに伝える役割をする。次の例は、ファイル sample_11_1b1.c で宣言されたグローバル変数 count を、ファイル sample_11_1b2.c でも使えるように extern 指定子を使っている。

#include

int count;

void f1(void);

int main(void)
{
    int i;

    f1(); /* set value to count */

    for (i = 0; i < count; i++) {
        printf("%d\n", i);
    }

    return 0;
}
#include

extern int count;

void f1(void)
{
    count = rand();
}
takatoh@nightschool $ gcc sample_11_1b1.c -c
takatoh@nightschool $ gcc sample_11_1b2.c -c
takatoh@nightschool $ gcc sample_11_1b1.o sample_11_1b2.o -o sample_11_1b
takatoh@nightschool $ ./sample_11_1b
1
2
3
4
5
(略)

register

register を指定すると、コンパイラは変数をメモリではなく CPU のレジスタに格納しようとする。初期の C では、register を適用できるのは char か int のローカル変数あるいはポインタに限られ、これらの変数は CPU のレジスタに格納されていた。ANSI C 標準ではこれらの制限が緩められて必ず CPU のレジスタに格納されるという要件は撤廃されているけど、依然として register 変数をアクセス時間が最小になるような方法で格納するように規定されている。だから、char と int はレジスタに格納されることになる。
ただし、レジスタの数は限られているので、レジスタに格納できない変数は、たとえ register が指定されていても通常の変数に変えられてしまう。だから本当に高そくにアクセスしたい変数を慎重に選ぶ必要がある。一般的に、1つの関数につき2つの変数は register 指定できると考えていいようだ。

共用体

共用体(union)は構造体と似ているけど、メンバ変数が同じメモリ領域を共有するもの。メモリを共有するメンバ変数が同じ型である必要はないけど、同時に使うことは出来ない。
定義の方法も構造体とよく似ていいて、一般的な形式は次のとおり。

union タグ名 {
    型 メンバ1;
    型 メンバ2;
    ...
    型 メンバN;
} 変数リスト;

「タグ名」と「変数リスト」のどちらかを省くことができるのも構造体と一緒。

次の共用体は、整数、文字配列、double 型の3つのメンバを持つ。

union u_type {
    int i;
    char c[4];
    double d;
} sample;

このとき、各メンバはメモリ領域を共有していて、d(double 型、8バイト)の上位4バイトを i (int 型、4バイト)が共有している。また、同時に同じ上位4バイトの領域を c[4] も共有している。各メンバが別々のメモリ領域を占めるのではない。
共用体のサイズはコンパイル時に決定され、最大のメンバが入る大紀佐の憑依機が割り当てられる。サイズを求めるときには sizeof 演算子を使うこと。メンバのうちの最大サイズが共用体のサイズとは限らない。環境によってはワード境界に揃えられることがあるから(ワード境界ってなんだ?)。

共用体のメンバにアクセスするには、構造体と同様にドット演算子を使う。

sample.d = 123.456;

また、ポインタを介してのアクセスにはアロー演算子だ。

p->i = 10;

なんとなくわかったような気がするけど、どうも使いドコロがわからないな。

次のプログラムは、整数(short int)の上下バイトを入れ替えて暗号化・復号化する。暗号化・復号化するためのデータとして共用体を使っている。

#include

short encode(short i);

int main(void)
{
    short i;

    i = encode(10);
    printf ("Encode 10 -> %d\n", i);
    i = encode(i);
    printf("Decode i -> %d\n", i);

    return 0;
}

short encode(short i)
{
    union crypt_type {
        short num;
        char c[2];
    } crypt;

    unsigned char ch;

    crypt.num = i;

    /* Swap bytes of short int */
    ch = crypt.c[0];
    crypt.c[0] = crypt.c[1];
    crypt.c[1] = ch;

    return crypt.num;
}
takatoh@nightschool $ ./sample_10_5
Encode 10 -> 2560
Decode i -> 10

ビットフィールド

C では、構造体のメンバにビットフィールドが使える。ビットフィールドというのは、バイト単位ではなくビット単位のデータで、1つあるいは複数のビットに名前をつけてアクセスすることができる。
例えば、真と偽しか取らないようなメンバでは1ビットあれば用が足りるので、そんなときにビットフィールドを使えばメモリの節約になる。
ビットフィールドの定義の一般的な形式は次のとおり。

型 名前: サイズ;

「型」には int か unsigned を指定する。「サイズ」はビットフィールドのビット数。「名前」と「サイズ」はコロンで区切る。
次の構造体は、在庫情報を格納するためにビットフィールドを使っている。

struct b_type {
    unsigned department: 3;    /* 担当部署(最大7部署) */
    unsigned instock: 1;       /* 1: 在庫あり 0: 在庫なし */
    unsigned backorderd : 1;   /* 1: 未納 0: 納品済み */
    unsigned lead_time: 3;     /* 発注から納品までの月数 */
} inv[MAX_ITEM];

この例では、構造体すべての情報を8ビット(1バイト)に詰め込んでいる。

ビットフィールドの参照方法は、普通の構造体メンバと変わらない。ドット演算子を使えばいい。

inv[9].department = 3;

ビットフィールドはの合計をバイト単位に合わせる必要はない。次にようなものでも構わない。

struct b_type {
    int a: 2;
    int b: 3;
};

同じ構造体の中に、普通のメンバとビットフィールドを混在させても構わない。次の例は最初の例に、品目名の文字列を追加している。

struct b_type {
    char name[40];             /* 品目名 */
    unsigned department: 3;    /* 担当部署(最大7部署) */
    unsigned instock: 1;       /* 1: 在庫あり 0: 在庫なし */
    unsigned backorderd : 1;   /* 1: 未納 0: 納品済み */
    unsigned lead_time: 3;     /* 発注から納品までの月数 */
} inv[MAX_ITEM];

構造体のネスト

構造体を、別の構造体のメンバにすることもできる。これを「構造体のネスト」あるいは「ネストした構造体(nested structure)」と呼ぶ。
次の例は、10 人の作業員を配置する製造ラインの情報を格納するために、構造体をネストしている。

#define NUM_ON_LINE 10

struct worker {
    char name[80];
    int avg_units_per_hour;
    int avg_errs_per_hour;
};

struct asm_line {
    int product_code;
    double material_cost;
    struct worker wkers[NUM_ON_LINE];
} line1, line2;

製造ライン line1 の構造体配列 wkers の 2 番目の様相のメンバ avg_units_per_hour に値 12 を代入するには次のようにする。

line1.wkers[1].avg_units_per_hour = 12;

要は外から順にたどっていけばいい。

構造体へのポインタ

構造体へのポインタも、ほかの型の場合と同じように作ることができる。

struct s_type {
    int i;
    char str[80];
} s, *p;

上の例では、s_type という名前の構造体を定義し、その変数 s とポインタ p を宣言している。このとき p に s のアドレスを代入するには、

p = &s;

とする。これもほかの型の場合と一緒だな。
ただし、構造体へのポインタから構造体のメンバにアクセスするときは、ドット演算子ではなく、アロー演算子(->)を使う。

p->i = 100;

次のプログラムは、構造体へのポインタの使い方の簡単な例だ。

#include
#include

struct s_type {
    int i;
    char str[80];
} s, *p;

int main(void)
{
    p = &s;

    s.i = 10;
    p->i = 20;
    strcpy(p->str, "I like struct.");

    printf("%d %d %s\n", s.i, p->i, p->str);

return 0;
}

s_type 構造体の変数 s とポインタ p を宣言し、s のアドレスを p に代入しているので、p は構造体 s を指していることになる。
15 行目でドット演算子を使ってメンバに代入しているけど、次の行で今度はポインタからアロー演算子を使って違う値を代入している。結果として上書きしているわけだ。

takatoh@nightschool $ ./sample_10_2a
20 20 I like struct.

期待通り、s.i と p->i は同じ値を出力している。

構造体

構造体(structure)は互いに関連する2つ以上の変数で構成される複合型。構造体の持つ変数をメンバと呼び、それぞれのメンバは異なる型でも構わない。
構造体を定義するときの一般的な形式は次のとおり。

struct タグ名 {
    型 メンバ1;
    型 メンバ2;
    型 メンバ3;
    ...
    型 メンバN;
} 変数リスト;

タグ名は構造体につける名前だ。いってみれば型名のようなもの。
変数リストは、定義したい変数のリスト。
タグ名と変数リストは、どちらかは省略できる。タグ名を省略すると、名前のない構造体の変数を定義することになる。一方、変数リストを省略すると、タグ名のついて構造体だけの定義となって、実際に変数を使うときにはあとから改めて宣言する必要がある。
普通はタグ名だけつけていおいて、変数はあとで宣言するのがいいんではないかな。次の例では catalog というタグ名の構造体を定義して、あとからその変数を宣言している。

struct catalog {
    char name[40];
    char title[40];
    char pub[40];
    unsigned date;
    unsigned char ed;
};

struct catalog var1, var2, var3;

構造体を配列にすることもできる。

struct catalog cat[100];

配列の個々の構造体にアクセスするには、配列名に添字をつける。

cat[4]

構造体のメンバにアクセスするには、ドット演算子を使う。これは、メンバを参照するときもメンバに代入するときも同じ。構造体が配列になっている場合も同じ。

var1.date = 1776;
cat[50].ed = 2;

構造体を関数に渡すこともできるし、関数の戻り値とすることもできる。
また、両方の方が同じなら、構造体の変数(インスタンス)を別の変数にそのまま代入することもできる。
これはちょっと試してみよう。

#include

int main(void)
{
    struct s_type {
        int a;
        float f;
    } var1, var2;

    var1.a = 10;
    var1.f = 100.23;

    printf("var1: %d %f\n", var1.a, var1.f);

    var2 = var1;

    printf("var2: %d %f\n", var2.a, var2.f);

    var2.a = 5;
    var2.f = 10.56;

    printf("var1: %d %f\n", var1.a, var1.f);
    printf("var2: %d %f\n", var2.a, var2.f);

return 0;
}

構造体 var1 と var2 を宣言して var1 のメンバに値を代入したあと、var1 をそのまま var2 に代入する。それから var2 のメンバの値を変更してみる。

takatoh@nightschool $ ./sample_10_1a
var1: 10 100.230003
var2: 10 100.230003
var1: 10 100.230003
var2: 5 10.560000

最初の2行は var2 に var1 を代入した直後の出力。当然同じ値になっている。
次の2行は、var2 のメンバを変更したあとの出力。var2 のメンバは変更されているのに対して、var1 のメンバは変更されていない。てことは、構造体の代入では値がコピーされるってことでいいのかな。

構造体のサイズを調べるときには sizeof を使う。各メンバのサイズの合計を計算しようとしてはいけない。環境によっては構造体のサイズがメンバのサイズの合計と同じにならないからだ。

#include

struct s_type {
    int i;
    char ch;
    int *p;
    double d;
} s;

int main(void)
{
    printf("size of s_type is %ld bytes.\n", sizeof(struct s_type));

    return 0;
}
takatoh@nightschool $ ./sample_10_1b
size of s_type is 24 bytes.

標準ストリーム

C のプログラムが実行を開始すると、自動的に3つのストリーム、stdin(標準入力)、stdout(標準出力)、stderr(標準エラー出力)が開かれて使えるようになる。これらのストリームは FILE ポインタなので、例えばストリームに出力する fprintf() を使って画面に出力することもできる。

fprintf(stdout, "%d %c %s\n", 100, 'c', "This is a string.");

ただし、stdin、stdout、stderr は変数ではないことに注意。fopen() で割荒れることは出来ないし、fclose() で閉じることも出来ない。自由に利用できるけど、変更してはいけない。

rename()、remove()、rewind()

rename()

rename() はファイル名を変更する。

int rename(char *旧ファイル名, char *新ファイル名);

成功すると 0 を返し、エラーが発生すると 0 以外の値を返す。

#include
#include

int main(int argc, char *argv[])
{
    if (rename(argv[1], argv[2])) {
        printf("Error.\n");
        exit(1);
    } else {
        printf("Renamed successfully.\n");
    }

    return 0;
}
takatoh@nightschool $ ls *.txt
myfile.txt
takatoh@nightschool $ ./sample_9_7a myfile.txt yourfile.txt
Renamed successfully.
takatoh@nightschool $ ls *.txt
yourfile.txt

remove()

remove() はファイルを削除する。

int remove(char *ファイル名);

成功すると 0 を返し、エラーが発生すると 0 以外の値を返す。

#include
#include

int main(int argc, char *argv[])
{
    if (rename(argv[1], argv[2])) {
        printf("Error.\n");
        exit(1);
    } else {
        printf("Renamed successfully.\n");
    }

    return 0;
}
takatoh@nightschool $ ls *.txt
yourfile.txt
takatoh@nightschool $ ./sample_9_7b yourfile.txt
Remove successfully.
takatoh@nightschool $ ls *.txt
ls: *.txt にアクセスできません: そのようなファイルやディレクトリはありません

rewind()

rewind() はストリームの現在位置を先頭に巻き戻す。

void rewind(FILE *ストリーム);

rewind() に戻り値はない。開くことが出来たファイルはすべて巻き戻すこともできるから。
次のプログラムは、ファイルの内容を表示したあと、先頭まで巻き戻してもう一度表示する。

#include
#include

int main(int argc, char *argv[])
{
    FILE *fp;

    if (argc != 2) {
        printf("Filename not given.\n");
        exit(1);
    }

    if ((fp = fopen(argv[1], "r")) == NULL) {
        printf("Cannot open file: %s\n", argv[1]);
        exit(1);
    }

    /* display file contents */
    while (!feof(fp)) {
        putchar(getc(fp));
    }

    /* rewind */
    rewind(fp);

    /* display once more */
    while (!feof(fp)) {
        putchar(getc(fp));
    }

    fclose(fp);

    return 0;
}
takatoh@nightschool $ ./sample_9_7c myfile
Hello
I'm takatoh
How are you?
�Hello
I'm takatoh
How are you?
�takatoh@nightschool $

なんかファイルの最後に変な文字が出力されてるな。何だろ?

[追記]

上の「変な文字」にいついてコメントで教えてもらった。詳しくはコメントを見て欲しいけど、要するに getc() が返した EOF を出力している、てことみたいだ。
次のように getc() の戻り値をチェックするように変更したらでなくなった。

#include
#include

int main(int argc, char *argv[])
{
    FILE *fp;
    char ch;

    if (argc != 2) {
        printf("Filename not given.\n");
        exit(1);
    }

    if ((fp = fopen(argv[1], "r")) == NULL) {
        printf("Cannot open file: %s\n", argv[1]);
        exit(1);
    }

    /* display file contents */
    while (!feof(fp)) {
        ch = getc(fp);
        if (ch == EOF) {
            break;
        }
        putchar(ch);
    }

    /* rewind */
    rewind(fp);

    /* display once more */
    while (!feof(fp)) {
        ch = getc(fp);
        if (ch == EOF) {
            break;
        }
        putchar(ch);
    }

    fclose(fp);

    return 0;
}
takatoh@nightschool $ ./sample_9_7d myfile
Hello
I'm takatoh
How are you?
Hello
I'm takatoh
How are you?