C言語覚書 (変数と構造体)

2013/03/22


◆ 変数の型

種類 型名 サイズ 範囲
文字型 char 1 byte -128 ~ 127
unsigned char 1 byte 0 ~ 255
整数型 short int 2 byte -32768 ~ 32767
unsigned short int 2 byte 0 ~ 65535
int 4 byte -2147483648 ~ 2147483647
unsigned int 4 byte 0 ~ 4294967295
long int 4 byte -2147483648 ~ 2147483647
unsigned long int 4 byte 0 ~ 4294967295
実数型 float 4 byte 3.4E-38 ~ 3.4E+38
倍精度実数型 double 8 byte 1.7E-308 ~ 1.7E+308
long double 8 byte 1.7E-308 ~ 1.7E+308

コンパイラによっては int が 2 byte、double が 4 byte 等の場合がある。

文字の変数については別頁参照


◆ const(定数)

 const int i = 10;    // この宣言以降、変数 i は 10 となり変更できない。 

◆ グローバル変数とstatic(静的変数)

関数のカッコ内で宣言された変数は、その関数内でのみ有効であり、関数の実行が終了すれば変数は解放される。
ただしカッコ外で宣言される変数は、ソース内すべてで扱うことができ、解放されない。これをグローバル変数という。
また、カッコ内でも stutic をつけて宣言された変数は、その関数の実行が終了しても変数は解放されない。
グローバル変数は、先頭部分でまとめて宣言した方がソースの可読性が高くなる。また先頭の一文字をGにしたりすれば、ソース内でもグローバル変数とローカル変数の区別がつきやすい。


◆ 異なる型の計算とcast(型変換)

異なる型の変数を代入する場合には値は左辺の型に変換される。
計算式内では最も精度の高い方に統一される。
強制的に別の型に変換して計算する場合 cast(明示的型変換)を行う。

 int a;
 double pi = 3.1415;
 a = pi;  // a = 3 になる。
 int i = 5;
 long l = 5;
 double d =  12.3;
 double ans;
 ans = i * l + d;  // i * l はlong型で計算され、+ d はdouble型で計算される。
                 // ans = 37.3
 int a = 3;
 int b = 2;
 float f;
 f = a / b;          // f = 1.0
 f = (float) a / b;  // f = 1.5

◆ typedef(型名の宣言)

既存の変数型に別名を付ける。 長い型名やわかりやすい形で表記できる。

 typedef unsigned short int byte;
 // 以降 byte で宣言された変数は、unsigned short int になる。

◆ union(共用体)

一つのデータを異なる変数型利用する。 同じメモリ領域を使用するのでポインタで扱う場合、共用体で定義された変数は同じアドレスを指し示す。 また、変数の値を変更した場合共用体内のすべての変数が変わる。

たとえば4バイトの共用体変数を扱った後、2バイトの共用体変数を扱った場合、下位2バイトの値はメモリ中に残るので再度4バイトの共用体変数を呼び出すととんでもない値となる。

 // 定義
 union val {  // 共用体 val を定義
   char c;           // 1 byte
   unsigned char b;  // 1 byte
   short int i;      // 2 byte
 };

 // 関数内で宣言
 union val dat;  // 変数 dat を共用体で宣言

 dat.i = 0;

 // 使用例
 dat.c = 10;  // dat.c = 10
              // dat.b = 10
              // dat.i = 10
 dat.b = -5;  // dat.c = 251
              // dat.b = -5
              // dat.i = 251
 dat.i = -5;  // dat.c = -24 上位1バイトのみの値になる
              // dat.b = 232
              // dat.i = 1000

◆ enum(列挙型宣言)

通常は #define で宣言するが列挙型宣言で一括して宣言することができる。switch文で使われる場合がある。

 #define SUN 0
 #define MON 1
 #define TUE 2
 #define WED 3
 #define THU 4
 #define FRI 5
 #define SAT 6

 // enum で宣言
 enum week { SUN, MON, TUE, WED, THU, FRI, SAT };

 // SUN = 1, MON = 2・・・ の場合
 enum week { SUN = 1, MON, TUE, WED, THU, FRI, SAT };  // ひとつ前の値に+1される。

 // 例 SUN=0 の宣言の場合
   enum week w1; // w1 を列挙型で宣言
   int w2;

   w1 = WED;  // w1=3
   w2 = FRI;  // w2=5

   w1 = 3;  // コンパイル時にエラー(警告)が出る場合がある。
   w2 = 5;

 }

◆ struct(構造体)

構造体は異なる型のデータをグループ(レコード)化して使用できる。配列(データベース)に用いられることが多い。

構造体の利用には定義、宣言の手順が必要となる。

構造体内のデータはメンバと呼ばれる。

 // 定義
 struct ichi {
   int nubmer;     // 位置番号
   char name[20];  // 半角19文字の位置名
   double x;       // 座標 x
   double y;       // 座標 y
   double z;       // 座標 z
 };

 // 関数内で宣言
 struct ichi zahyo;

 struct ichi zahyo = { 5, "SOUKO", 10.102, 35.440, 8.116};

 struct ichi zahyo[10] = {  // 配列で利用
   { 1, "KABE",    6.118, -12.344, 8.581},
   { 2, "GENKAN", 12.690,  52.055, 7.405},
   { 3, "DOURO",  22.637,  -7.058, 7.900}
 };

 // zahyo[2].name は DOURO

 zahyo[3].z = 8.226;  // DOURO の座標 z を修正

◆ ポインタ(アドレス変数)

変数はメモリ上に配置され、プログラム内で変数を参照することはメモリを参照することである。2バイトの変数であればその変数が保存されているメモリの位置から2バイト取り出し、4バイトの変数であれば4バイト取り出します。 プログラム内では解りやすい変数名を用いてますが、実際の動作では「アドレス○○番から○バイト取り出す」や「○バイト書き込む」といった作業が行われ、そのアドレスはコンパイル時に決定されます。 

 short int a; // ○○番アドレスから2バイトは変数 a 用にメモリを確保
 short int b; // ○○番アドレスから2バイトは変数 b 用にメモリを確保

 b = 10;      // 変数 b 用○○番アドレスを先頭に2バイトに10を書き込む
 a = b;       // 変数 b 用○○番アドレスを先頭に2バイト読み込み、
              // 変数 a 用○○番アドレスを先頭に読み込んだ2バイトを書き込む

 // ポインタの宣言

 int *p;  // * がついているのが普通の変数と違う所
 // int* p,q;  と宣言してもよい。

 int a;

 a = 10;
 p = &a;  // p には変数 a 用に確保された○○番の先頭アドレスが保存される。
 *p = 5;  // p に 5 は保存されず、p に保存されたアドレスへ 5 が保存される。
          // このあと変数 a を参照すると 5 になっている。
 p = a;   // p には 10 が保存される。
 *p = 5;  // アドレス 10 の位置に 5 が保存される。
          // もし、アドレス 10 の位置がプログラムや他の変数で使用されている場合
          // 「暴走?」となってしまう。

関数へ文字列を渡す場合。 C言語には文字変数(char型)はあっても、文字列型は存在しない。文字列は全て配列となっている。 関数へ文字列を渡したい場合には、文字列の先頭アドレスを渡せば、受け取った関数ではアドレス以降文字の終了コードの位置までを文字列として認識する。

 // 関数へ文字列を渡す。
 void sub(char* p) {
   printf("%s\n"p);
 }

 char txt[] = "ABCDE";
 printf("%s\n", txt);
 sub(txt);             
// sub(&txt[0]) と同じ意味

関数の戻り値は一つだけだが、関数内で計算された複数の値を戻り値として利用したい場合がある。 その場合あらかじめ受け取る変数を用意しておき関数へ受け取る変数のアドレスをポインタで渡す。 関数内では、受け取ったアドレスへ値を書き込むことで複数の戻り値と同じ結果を実現する。

 // 複数の戻り値
 int a, b;
 calc(5, 3, &a, &b); 
// & をつけて変数のアドレスを関数へ渡す

 void calc (int s1, int s2, int *s3, int *s4) {
   *s3 = s1 + s2; 
// 計算結果を ポインタ変数に格納されているアドレスへ書き込む。
   *s4 = s1 - s2;
 }

演算子の * がポインタを表す記号として使用されているので直感的に分かりにくい表記となっている。 要は Windows のショートカットや、Linux のシンボリック・リンクのようなものと理解しておこう。