構造体へのポインタ

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
struct s_type {
int i;
char str[80];
} s, *p;
struct s_type { int i; char str[80]; } s, *p;
struct s_type {
    int i;
    char str[80];
} s, *p;

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
p = &s;
p = &s;
p = &s;

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
p->i = 100;
p->i = 100;
p->i = 100;

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#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;
}
#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; }
#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つ以上の変数で構成される複合型。構造体の持つ変数をメンバと呼び、それぞれのメンバは異なる型でも構わない。
構造体を定義するときの一般的な形式は次のとおり。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
struct タグ名 {
型 メンバ1;
型 メンバ2;
型 メンバ3;
...
型 メンバN;
} 変数リスト;
struct タグ名 { 型 メンバ1; 型 メンバ2; 型 メンバ3; ... 型 メンバN; } 変数リスト;
struct タグ名 {
    型 メンバ1;
    型 メンバ2;
    型 メンバ3;
    ...
    型 メンバN;
} 変数リスト;

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
struct catalog {
char name[40];
char title[40];
char pub[40];
unsigned date;
unsigned char ed;
};
struct catalog var1, var2, var3;
struct catalog { char name[40]; char title[40]; char pub[40]; unsigned date; unsigned char ed; }; struct catalog var1, var2, var3;
struct catalog {
    char name[40];
    char title[40];
    char pub[40];
    unsigned date;
    unsigned char ed;
};

struct catalog var1, var2, var3;

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
struct catalog cat[100];
struct catalog cat[100];
struct catalog cat[100];

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
cat[4]
cat[4]
cat[4]

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
var1.date = 1776;
cat[50].ed = 2;
var1.date = 1776; cat[50].ed = 2;
var1.date = 1776;
cat[50].ed = 2;

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#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;
}
#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; }
#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 を使う。各メンバのサイズの合計を計算しようとしてはいけない。環境によっては構造体のサイズがメンバのサイズの合計と同じにならないからだ。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#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;
}
#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; }
#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.