C/C++ - 言語仕様編 第3回 〜構造体〜

基本型

整数型

int型は、処理系でもっとも自然なバイト長で表現される整数を表す型である。 32ビットの処理系ならば、4バイトを用いて整数を表現する。

0,1 で記録するわけで、2の補数表現を用いて、-2 147 483 648から 2 147 483 647までを表現可能である。

24とか、-5234などというごくごく普通に書いた整数リテラルや、 0xdeadbeefみたいな16進数、020070425みたいな8進数などは、int型リテラルである。

short int型は、int型以下のバイト長で表現される整数であり、long int型は、int型以上のバイト長で 表現される整数である。処理系によってバイト長はまちまちだけど、普通、32ビット処理系なら、 short型は、2バイト、long型は4バイトになる。

整数リテラルにL接尾辞をつけるとlong型リテラルとなる。 

unsigned int型は、MSBを符号として用いない。要するに、0から4 294 967 295までが表現可能である。

整数リテラルにU接尾辞をつけると、unsigned型リテラルとなる。

浮動小数点数型

double型は、8バイトを用いて 2.225 074 10^-308 から 1.797 693 10^+308までを表現する、 32ビットの処理系でもっとも自然な浮動小数点数型である。

3.14や.1、4.、1.4142e+3など、小数点を持つ数値は、double型リテラルである。

float型は、4バイトを用いて、1.175 494 10^-38 から 3.402 823^10+38までが表現可能である。

浮動小数点数リテラルにF接尾辞をつけると、float型リテラルとなる。

文字型

char型変数は、1バイトで表現される。ASCII文字など、8ビット以下のデータを記憶するのに適し、 値を取り扱うときの最小単位でもある。

'a'や'3'、'\n'や'\0' などはすべて文字リテラルである。

ポインタ変数型

int型と同じ長さのバイト数を用いて変数のポインタを記録する。それぞれの由来する変数の型に対して 宣言された変数のシンボル名の先頭に*をつけることで表現する。

*をn個つけて宣言した場合、その変数をT型変数として取り扱うには、*オペレータをn個必要とする。

void 型

関数が変数を返さないことを表す型。void *型は任意のポインタ変数型を代入可能とする 汎用変数型である。

NULLは、どこの変数のポインタでもないことを表すvoid *型リテラルである。

typedef と弱い型宣言

typedef oldT newT;

と書くことで、oldT型をnewT型として取り扱うことが可能となる。oldT型変数とnewT型変数は、 どちらもoldT型として同等であり、区別されない。

size_t型やclock_t型、その他 typedef により各種ヘッダファイルで多くの型が宣言される。

構造体

typedefは区別ができない弱い型宣言しか行えない。したがって、新しい型をつくる、 つまり、強い型宣言を行うには、struct を用いることとなる。

宣言

基本型、あるいは別の構造体を用いて、

struct T{
	int num;
	double value;
	char *str;
};

などと宣言することで新しい型、struct T が構築される。

弱い型宣言である typedef と組み合わせることで、

typedef struct TB{
	int num;
	double value;
	char *str;
}T;

などとすると、T 型が、struct TB 構造体型として宣言される。

以下、T 型は、struct TB 構造体型として typedef されているという前提で話を進める。

メンバ変数

構造体内に列挙された基本型、あるいは構造体の変数をメンバ変数と呼ぶ。 T 型変数 t に対して、int 型メンバ変数 num は、

t.num;

と表現される。t.num は、int 型変数と同じ扱いとなる。

ポインタ変数であるメンバ変数 char *str に対しても、扱いは同等で、

t.str;

で、char *型変数として機能する。これをchar 型変数とする場合は、* オペレータを用いて

*t.str;

と指定すればよい。

初期化と代入

T 型変数の宣言時に、

T t={4,2.,NULL};

などとすることで、対応するメンバを初期化する。

また、T 型変数 t1,t2 に対して、

t1=t2;

と代入すると、それぞれの対応するメンバに対して代入を行う。

構造体ポインタ

T *型ポインタ変数を動的変数として、

T *t=(T *)malloc(sizeof(T));

として宣言すると、t は、構造体ポインタ型となる。

構造体ポインタ型に対しては、当然、

(*t).num;

などとすれば、メンバ変数にアクセス可能であるが、これと全く同じことが、

t->num;

と表現でき、かつ多用される。

構造体ポインタと自己参照構造体

また、struct TB のメンバ変数として、struct TB 型変数を用いることはできないが、 struct TB *型ならばメンバ変数として用いることができる。

このような自分自身の構造体のポインタ型変数をメンバ変数として持つ構造体を、自己参照構造体と呼ぶ。

当然、自己参照構造体のメンバに対して、malloc()を用いれば、自己参照構造体のメンバから、 別の自己参照構造体をつくり出すことができる。これを繰り返すことで、 自己参照構造体同士をつなぎ合わせることができ、この構造をリスト構造と呼ぶ。

構造体ポインタと再解釈キャスト

型Tは、int 型メンバ変数、double 型メンバ変数、char *型メンバ変数がその順序で並ぶ。 したがって、sizeof(T)=4+8+4=16バイトとなる。

int *型変数 p とT *型変数 t に対して、p=(int *)t; とすると、*pは
t->num と同等である。

char *型変数 q、double *型変数 v とし、

q=(char *)t;
v=(double *)(q+4);

とするとき、*vはt->valと同等である。

ただし、各メンバ変数が4バイトの倍数でない場合は、それぞれが4バイトの倍数となるように パディングが行われ、必ずしも連続してメンバ変数が並ぶとは限らない。

構造体配列

整数リテラルnに対して、

T *t=(T *)calloc(n,sizeof(T));
T t[n];

のように、配列として定義することも当然可能である。

当たり前ではあるが、メンバ変数へのアクセスは、t[m].num;などのようにする。 t->numとすると、tが参照子として機能し、t[0].numが取得される。

列挙型

列挙型は、複数の整数リテラル群にシンボルをつけることが出来る。 基本的には整数リテラルである以上、int型変数に値を格納することができる。

enum E{
	LENGTH1=3; LENGTH2=4;
}; 

などとしておくと、シンボルLENGTH1は、整数リテラル3として機能する。

一応、念のためもう一度書いておくと、自動変数配列の宣言、T t[n];とするときの、nは、整数リテラルであり、 決してint型変数ではない。

したがって、列挙型の1つ目の使い方として、整数リテラルのシンボル化が挙げられる。 必ず整数リテラルが必要となる、自動変数配列の宣言などでは重宝することとなる。

第2の使い方が、switch構文のcaseに対する値の定義である。 eがenum E型であるとき、

switch(e){
case LENGTH1:

break;
case LENGTH2:

break;
}

などとして分岐の基準を明示化する事が可能となる。

まぁ、とにかく整数リテラルを直接使うよりは、シンボルをつけた方がいろいろと便利である。 複数個所で同じ値を使う場合に、直接整数リテラルを書いていると、一括して変更することが面倒であるが、 列挙型として定義しておけば、1箇所の値の書き換えですべてが書き換えられる。

共用体

共用体は、基本的に構造体と同じような宣言を行う。 基本型や構造体などに対して、

typedef union UB{
	int num;
	double value;
	char *str;
}U;

などとすると、U型が、union UB共用体型として宣言される。

sizeof(struct TB)が4+8+4=16であったのに比べ、sizeof(union UB)は、8である。 つまり、UBに対して、numとvalue、strのそれぞれの変数のポインタは全てU型変数のポインタに一致する。

それ以外は、メンバ変数へのアクセスの仕方などをはじめ、全く同じである。


文章作成 : yukki-ts (-+-twilight serenade-+- [stage])