次のページ 前のページ 目次へ
2. C言語
moreソフトの開発言語は、Add in (BASIC)とは異なり C言語 です
(遠くない内に C++ が使えるようになりそう)。
基本的にはCもBASICも、どちらも手続き型の言語であって大筋に差はないのですが、
やはり書式の違いや細かいルールの違い、ポインタ等の概念、インタプリタとコンパイラの違いなど色々あります。
とりあえず以下に Add in BASIC と比較した C言語の相違点をさらっと挙げてみますので参考にしてください
(間違いは指摘していただけると助かります)。
※なお、C言語については市販の本(共立出版 『プログラミング言語C』 など)を読まれると良いでしょう。
2.1 変数は使う前に必ず宣言しないといけない
BASICではいきなり変数を使えますが、
Cでは使用する前に変数の型と名前を宣言しなければなりません。
大文字小文字も区別されますので、a と A は別物になります。
(if や for などの制御文も、IF や FOR では通じませんのでご注意を)
[BASIC]
10 a = 50
[C]
main()
{
int a; /* int型 の a を宣言 */
a = 50; /* 上で宣言してるので a を利用可能 */
}
2.2 Cに行番号とマルチステートメントはない
BASICは全ての行に[行番号]を付けますが、Cには行番号はありません。
行という概念も実はありませんので、好きな位置で改行して結構です。
BASICのようにマルチステートメント(コロンで区切って複数の文を書く)の必要はなく、
if文の後など1文しか書けない所では、中括弧 { }
で囲んでブロックを作り、その中に複数の文を書いていけばいいのです。
(Cでは各文の後にセミコロン ; を付けるのが決まりです)
[BASIC]
10 IF a=1 THEN w=320 : h=240 ELSE b=640 : h=480
[C]
if ( a == 1 ) {
w = 320;
h = 240;
} else {
w = 640;
h = 480;
}
2.3 グローバル変数とローカル変数
BASICの変数は、プログラム全体で常に同じ物を使います。
こうした変数をグローバル変数と呼びますが、
メインルーチンで使っている変数を間違ってサブルーチンで書き換えてしまう事もでき、
再帰呼び出しができないなど色々と不都合があります。
Cでは、ブロックの中で宣言した変数はそのブロック内でだけ有効であるという
ローカル変数の概念がありますので、上記のような問題を回避する事ができます。
例えば下記の場合、グローバル変数 a が 値 20 で定義されています。
test2()関数では宣言無しに a を使っていますが、
これはグローバル変数の a を参照しているんですね。
しかし、test1() 関数の中では int a; としてローカル変数を定義していますから、
49 を代入しているのは test1() 関数内のローカル変数であって、
グローバル変数の a ではないのです!
この為、main() を実行すると、20, 20, -1 という結果になるのです。
変数の寿命(値が有効な期間)という点では、
ローカル変数は『関数なりブロックを抜けるまで』の一時的なもの、
グローバル変数は『プログラムを終了するまで』の永続的なものとなります。
このように、グローバル変数はプログラム全体で共通に使う変数を定義するもので、
ローカル変数は各関数内での処理の為に一時的に使うものなのです。
[C]
#include <stdio.h>
int a = 20; /* グローバル変数 a */
main()
{
printf( "%d\n", a ); /* a の値を表示 : 20 */
test1(); /* test1() 関数を実行 */
printf( "%d\n", a ); /* a の値を表示 : 20 のまま変化なし! */
test2(); /* test2() 関数を実行 */
printf( "%d\n", a ); /* a の値を表示 : -1 になってます! */
}
test1()
{
int a; /* main関数のローカル変数 a */
a = 49; /* ここで書き換えたのはローカル変数のa */
}
test2()
{
a = -1; /* ここで書き換えたのはグローバル変数のa */
}
2.4 変数に色々な型がある
BASICでは、数値型、文字列型の2種類しかありませんでしたが、
Cでは次のような基本型があり、更にそれに別名をつけて使うことが普通です
(例えば、typedef unsigned char UCHAR として UCHAR型 を作るなど)。
別名が付けられた型も、基本型と同様に普通に変数の宣言に利用できるようになっています。
char 8bit 符号付き整数 (-128 〜 +127)
unsigned char 8bit 符号無し整数 (0 〜 255)
short 16bit符号付き整数 (-32768 〜 +32767)
unsigned short 16bit符号無し整数 (0 〜 65535)
int 32bit符号付き整数 (-2G 〜 +2G)
unsigned int 32bit符号無し整数 (0 〜 +4G)
long 32bit符号付き整数 (-2G 〜 +2G)
unsigned long 32bit符号無し整数 (0 〜 +4G)
float 単精度浮動小数点数
double 倍精度浮動小数点数
※bit数や値の範囲はSZABでの話で、正確には実装に依存します。
当然、変数を配列として利用することも可能ですが、
BASICでは、最大添字の値を指定して配列の宣言をするのに対し、
Cでは、要素数そのものを指定する点で異なっています。
例えば、a(0),a(1),a(2) の 3つの要素を持つ配列を作りたい場合、
BASICでは DIM a(2) のように宣言しますが、C では int a[3]; と、
要素の個数を指定します。
[BASIC]
10 DIM a(5) ' a(0) 〜 a(5) の 6個分
20 DIM b(3,2) ' b(0,0) 〜 b(3,2) までの 12個分
[C]
int a[6]; /* a[0] 〜 a[5] の 6個分 */
int b[4][3]; /* b[0][0] 〜 b[3][2] の 12個分 */
ところで、BASICでは文字列を扱うのに a$ といった変数を用いますが、
C では文字(char型)の配列変数という扱いになっています。
文字列の最大文字数も、BASICでは 255文字までといった制限があるのに対し、
C では『最後に NULL (=0) が現れるまで』となっている為、
非常に長い文字列を扱う事も可能です(但し配列の大きさを超えないように注意)。
2.5 演算時の型には要注意
CではBASICと違い、演算時に不要に型の拡張を行う事はありません。
BASICの場合、5 / 2 を計算させると 2.5 という答が返りますが、
C の場合、5 / 2 の結果は 2 となり、整数部のみの計算となるのです。
これでは使えないと思うでしょうが、それは間違いです。
なぜなら、小数点以下の値も欲しいのならば初めから 5.0 / 2 のように
計算式を書けばいいだけだからです。
つまり、単に 5 と書いた場合は int 型として扱われ、
int / int の計算は整数除算になる・・・というだけの話です。
これが 5.0 / 2 とした場合は、
float / int の為、双方をより大きい範囲の物(この場合は float)に
変換するので正しくfloat値として 結果 = 2.5 が返るわけです。
このように値の型変換が行われるのは、異なる型の変数への代入や、異なる型同士の演算、
関数呼び出し時の引数変換、そして別な型への強制変換を指示する型キャストなどがあります。
型キャストというのは、型名を ( ) でくくった物を定数や変数の前につけて、
強制型変換を行わせるものです。
先の例でいくと、(float)5 / 2 のようにすれば望みの結果が得られるわけです。
型キャストもあちこちで使われますので覚えておいてください。
2.6 変数をまとめて1パッケージにした構造体がある
普通は変数が1つだけで済む事はなく、
幾つかの変数を組にして何かの情報を表します。
BASICにはそうした構造はない為、変数名などを揃えるなどして使っていましたが、
Cには構造体という変数のセットを定義するものがあります。
例えばアドレス帳の情報を考えた時、そこには名前や住所、
電話番号といった幾つもの情報が1セットとなり、
それが人数分そろっていると考えられます。
その1人分の情報をまとめて、1つの変数型としてしまえるのが構造体というわけです。
[c]
typedef struct { /* 構造体 MY_ADRSBK の型定義 */
char name[64];
char address[128];
char telno[16];
} MY_ADRSBK;
main()
{
MY_ADRSBK adrs1; /* MY_ADRSBK型 ローカル変数の宣言 */
MY_ADRSBK adrs2; /* 同上 */
strcpy( adrs1.name, "名前" );
strcpy( adrs1.address, "住所" );
strcpy( adrs1.telno, "XXX-XXXX-XXXX" );
adrs2 = adrs1; /* このようにして同じ型の変数にコピー可 */
}
この例では、name, address, telno という3つの項目を持つ
MY_ADRSBK 型の構造体を定義し、以降ははじめからある変数型と同様に使っています。
構造体の場合その中のメンバ(nameとかaddress等)を個々に指定したい場合は、
上記のように、構造体変数名.メンバ名 として使う事ができます。
2.7 変数や関数のアドレスを示すポインタがある
C で挫折する人の多くは、このポインタでつまづくと言われますが、
大して面倒な物ではありませんので、しっかりと覚えましょう
(アセンブラをご存知の方は、レジスタ間接アクセスだと言えばわかりますね?)。
まず、今のコンピュータは主記憶装置としてメモリを持ち、
その上にプログラムやらデータ、変数などを置いて動いています。
通常は、a だの b だのと変数名を指定して扱えば済みますが、
時にその変数がどこにあるか知りたい事があります。
はっきり言うと、メモリの何番地に置いてあるか、
そのアドレスこそがポインタの値で、アドレスを保存し間接参照できる変数がポインタ変数なのです。
main()
{
char a; /* 普通の char 型の変数 a */
char *b; /* char型変数を指すポインタ変数 b */
a = 5;
b = &a; /* 変数a のあるアドレスを、ポインタ変数 b に格納 */
printf( "%d\n", a ); /* a の値を表示 */
printf( "%d\n", *b ); /* bのポイントする値を表示 */
}
例えば上記の場合、
仮に変数 a が 0x183c 番地にあったとすると、
このアドレスに 5 が入っています。
char * 型の 変数 b はというと、
b = &a; によって 変数 a のアドレスを代入されてますので、
その値は 0x183c という事になります。
このように変数の値そのものではなく、
格納されているアドレスを持つからポインタと呼ばれるのです。
ポインタ変数を使う場合、
そのまま b と書いたらアドレス値そのものを出してきますので、
その先に入っている物を見たければ、変数名の前にアスタリスクをつけて、
*b のように表現します。これで、0x183c ではなく、
メモリの 0x183c 番地に入っている char 型の値 5 を持ってくるのです。
ところで、Cでは関数へ引数を渡す際に値そのものをコピーして渡します。
この時、構造体や配列など大きな物(数百バイト〜)を毎回コピーしていたのでは
大変ですので、通常はそれの先頭アドレスを受け渡すようにしています。
文字列を表示する場合も、その文字配列の先頭アドレス・・・つまりポインタを渡すわけですから、
ポインタの扱いは C では必須なのです。
2.8 関数(サブルーチン)
C ではサブルーチンを関数と呼びます。
関数というだけあって、これらは引数と戻り値を持つことができ、
通常の計算式や代入文に組み込めるのが特長です。
int add( int a, int b ) {
return( a + b );
}
この例では関数名は add で、その戻り値は int型 です。
続く ( ) の中は add関数を呼び出す時に必要な引数の種類と数を示しており、
引数の数は2つ、int 型の a と int型の b だとわかります。
この時、関数の定義で使用している int a, int b を仮引数と呼びます。
実際に add関数を呼び出す際は別に a, b である必要はありません。
例えば add( 3, 5 ); ように呼び出された場合、
add() 関数の中で、a = 3, b = 5 という扱いで計算が行われ、
その計算結果を return() 文で呼び出し元に戻しています。
従って、次のように書いてあった場合、結果として 8 が表示されるわけです。
main()
{
printf( "%d\n", add(3, 5) );
}
int add( int a, int b )
{
return( a + b );
}
なお、この時 add() 関数の中で a,b を変更したとしても、
それは呼び出し元には全く影響を与えません。
なぜなら、仮引数はローカル変数と同じ扱いとなるからです。
2.9 文字列の扱い方
C では、1文字を扱う場合には char型(時には unsigned char 型)を使いますが、
文字列を扱う場合は char型の配列を用い、通常はその先頭アドレスである
配列名で表します( char mes[32]; なら mes と書くことで char型のポインタと同じ扱いになります)。
配列ですから、要素数が幾つまで許されるかはプログラムを組む側が決めるもので、
BASICのようにインタプリタにまかせて済むというわけにはいきません。
この為、使用する文字列が入るくらい十分な大きさの配列を準備する事、
はみ出さないように気をつける事が重要です。
[BASIC]
10 mes1$ = "test message"
20 mes2$ = " --- !! "
30 mes3$ = mes1$ + mes2$
[C]
char mes1[16];
char mes2[16];
char mes3[32];
strcpy( mes1, "test message" ); /* mes1 に "test message" をコピー */
strcpy( mes2, " --- !! " );
strcpy( mes3, mes1 ); /* mes3 に mes1 の文字列を繋げる */
strcat( mes3, mes2 ); /* mes3 に mes2 の文字列を繋げる */
この例では、BASIC でも C でも、mes1 という変数に [test message] という文字列を、
mes2 には [ --- !! ] という文字列を入れた上で、
mes3 に mes1 と mes2 を繋げた文字列 [test message --- !! ] を作成するというものですが、
C の方はずいぶんと面倒そうですね。
これは、C が文字列を特別扱いしていない為に、
文字列をコピーするといった操作でさえ strcpy() 関数を呼ばないといけないのが原因です。
更に面倒なのは、mes1,mes2,mes3 の各配列は、
あらかじめ文字列を格納するだけの十分なサイズがないといけない事です。
上の例では [test message] は12文字ですから、
終端のNULL文字を合わせて 12 + 1 = 13byte あれば間に合います。
同様に mes2 は最低 8+1 = 9byte、
mes3 は 2つの文字列を繋ぐ為 20 + 1 = 21byte が最低必要です。
一応 16, 16, 32 だけ確保しているので今回は問題ありませんが、
もしも確保した配列をオーバーしてしまった場合、
C では何のエラーチェックもされない為に暴走し、
リセットを余儀なくされます
(C ではメモリの範囲チェックや管理はプログラマの仕事です!)。
もっとも、こうした所を理解して安全なソースを書けば、
C はかなり自由なプログラミングができる言語です。
文字列にしても、文字の配列なのですから、例えば "0123456789"[6] とすれば、
0から数えて6番目の要素である '6' という文字を取り出す事がいとも簡単にできます。
そうした所も理解して使っていきましょう。
2.10 演算子などの相違点
BASICもCも基本的な算術演算子(+,-,*,/)は一緒ですが、
優先順位や、条件演算子などには幾つか違いが見られます。
両者の比較という形で載せておきましょう。
算術演算子 |
\ | BASIC | C |
加算 | + | + |
減算 | - | - |
乗算 | * | * |
除算 | / | / |
整数除算 | \ | / (整数型の演算時) |
剰余 | mod | % |
関係演算子 |
\ | BASIC | C |
超過 | > | > |
以上 | >= | >= |
以下 | <= | <= |
未満 | < | < |
等しい | = | == |
異なる | <> | != |
論理演算子 |
\ | BASIC | C |
AND | AND | && |
OR | OR | || |
bitごとの演算子 |
\ | BASIC | C |
bit AND | AND | & |
bit OR | OR | | |
bit XOR | XOR | ^ |
bit NOT | NOT | ~ |
左シフト | (該当無し) | << |
右シフト | (該当無し) | >> |
論理値(真・偽) |
\ | BASIC | C |
真 | -1 | 1 (0以外) |
偽 | 0 | 0 |
C でよく間違うのが、
if文の中で条件判断するつもり( == )で代入( = )してしまうという物や、
論理条件を見るつもりが( && , || )、
bit演算を行ってしまった( & , | )などです。
これらは文法的には全く間違っていない為、
コンパイラでエラーチェックする事はできず、
時に見つけにくいバグとなり得ます。
この他、代入文の優先順位が関係演算子より低い事で、
作者の思惑から外れた判定になった場合もなかなか見つけづらい物ですね。
もっとも演算の優先順位については、
不安なところは括弧でくくってやれば済むだけの話です。
次のページ 前のページ 目次へ