けんつの煽られ駆動開発記

何か作るときは大抵誰かに煽られた時です。

C言語 構造体

6章 構造体

構造体とは、操作しやすいように一つの名前でまとめられた
1つ以上の異なった型の集まりである

この構造体と呼ばれるものは大きなプログラムで特に複雑なデータを組織化するのに役立つ
それは関連したデータを実体としてでなく一つの単位として扱えるという性質から来ている

6.1 構造体の基本事項

まずは構造体を使って二次元平面上の点を構造体を使って表してみる

int x,y;

普通はこう表すかもしれないけど、構造体でひとつの単位としてまとめるとこうなる

struct point{
    int x;
    int y;
};

これでpointという平面上での座標を表す一つの単位が出来た
前者に比べて後者のほうが何を示しているかわかりやすくなる

ここで少し説明するとstructという予約語の後に続いている名前は構造体タグと言われるものである
この構造体タグなるものは宣言以後、カッコ内の宣言部分の略称としてしようすることができる

構造体の中で命名された変数はメンバーと言われる
そして構造体タグとメンバー名は同名であってもぶつかることはない

構造体を使いたいときは以下のような記述をする

struct point pt;

これによりptはstruct point型であると宣言される

また以下のように各メンバーに対する定数式による初期化式の並びを定義の後につけることで構造体を初期化することができる

struct point max_pt = {100,100};

さらに宣言だけしておいて以下のように別々に初期化することも出来る

struct point min_pt;
min_pt.x = -100;
min_pt.y = -100;

上の例でわかるように

構造体型の変数名.メンバー名
>||
でその構造体のメンバにアクセスできるため、値を代入するときだけじゃなくて
値を呼び出したいときにも使用することが出来る


構造体は入れ子にすることも出来て
以下のようなメンバーを持つ構造体を宣言することも出来る
>|c|
struct rect{
     struct point pt1;
     struct point pt2;
};

この構造体は2つのpoint構造体をメンバとして含んでいる
ここでscreenを以下のように宣言しすると

struct rect screen

これでxが呼び出せる

screen.pt1.x

ここで紹介した以外にも構造体は以下のようにも宣言できる

struct{}x,y;

実は構造体タグは省略可能

6.2 構造体と関数

構造体に対して許される唯一の演算はコピーすること
&でそのアドレスを求めること

コピーと代入には関数に渡して関数から値を返すことも含まれている
しかし構造体の比較はできない

ここでは関数の値として構造体を返すことを例にあげてみる

struct point makePoint(int x,int y){
    struct point tmp;
    tmp.x = x;
    tmp.y = y;
    return tmp;
}

この関数はstruct point型の構造体を返すもので、前に書いたようにメンバー名と引数名が衝突していないことにも注目したい
ここで作成したmakePoint関数は任意の構造体を動的に初期化することができる

次に示すのは関数の引数と戻り値が構造体の場合のとき

struct point addPoint(struct point pt1,struct point pt2){
    pt1.x += pt2.x;
    pt1.y += pt2.y;
    return pt1;
}

ここは明示的な一時変数を使わないでpt1のメンバに加算しているが
これは構造体のパラメータが他のものと同じように値によって渡されることを強調するためである

これまでは構造体をそのまま渡してきたが関数に対して大きな構造体を渡す場合は構造体全体をコピーするより
ポインタを渡すほうが一般に能率的である
構造体ポインタは普通の変数へのポインタと変わらず以下のように宣言できる

struct point *pp;

これは変数ppがstruct point型へのポインタであることを示す
この時構造体ポインタのメンバにアクセスする場合は以下のようにする

(*pp).x;
(*pp).y;

この時注意すべきことは*は.より優先順位が低いため()が必要になる

これを用いて以下のようなコードが書ける

struct point base;
struct point *pointer;

pointer = &base;
printf("%d,%d",(*pointer).x,(*pointer).y);

また構造体のポインタへの参照はこれ以外にも別の方法がある
上の例に続いて、pointerが構造体へのポインタだとすると

pointer -> メンバー

のようにすることでメンバーを参照出来る

そこで2つ上の例は以下のように出力処理を書き直せる

printf("%d %d",pointer->x,pointer->y);

6.3 構造体配列

構造体自体は配列にすることができる
ここまでのまとめに準ずるなら

#define NUM 10
struct point pt[NUM];

もしくは

#define NUM 10
struct point{
     int x;
     int y;
}pt[NUM];

更にいうのであれば

struct point{
    int x;
    int y;
}pt[] = {
    1,1,
    2,1,...
};

のようにも定義出来る

この時構造体は配列になっているので、構造体のメンバーにアクセスしたい場合は

//iを0以上の任意の整数として
point[i].x
point[i].y

見たいな風にすれば、その構造体のインデックスに対応したメンバーを参照することが出来る

6.4 typedef

ここで少し話は逸れるがtypedefという新しいデータ型を宣言するための機能を紹介する
ただこの説明ではわからないと思うので実際に例を上げる

typedef int Length;

これでLengthをint型と同意義のものとして定義する

なので以下のような構文が書ける

Length len = 10;
Length maxLen = 20;

ということは文字列関連でいつもめんどくさいなと思っていたことを解消できる
それは文字列である。文字列はC言語においてポインタや配列で実現するのが通例になっているが
JavaC++,C#といった言語を先に経験した人にとってはこれは煩わしいものであったと思う
それをtypedefを使って以下のようにすれば

typedef char *String;

他の言語と同じように文字列を宣言できる

String str = "hello,world!!";

ちなみに同意義のものとして新しい型を既存の型と結びつけるので
typedefで宣言した新しい型でキャストすることもできる

例えば

String str;
str = (String)malloc(100);

のように

6.5 共用体

共用体(union)と言われるものは構造体とすごく似ているが、異なる型を同一のメモリで扱うことが出来る
というのも共用体のすべてのメンバにおいてオフセットが0である構造体のようなものだからである
共用体のメンバは構造体と同じように参照できる
ただ共用体自体の宣言が少し違う

union SampleUnion{
};

これでひと通りの構造体、それに類似するもののまとめは終わり