
プログラミング言語の中でも長い歴史と広い影響力を持つC言語。その強力さと効率性の源は、適材適所で活用できる多彩な演算子にある。より効率的に、より読みやすく演算子を使いこなしたいところだ。
条件演算子による簡潔な条件分岐、インクリメント・デクリメント演算子の微妙な違い、そしてsizeof演算子によるメモリ管理の基礎を理解してC言語の表現力を高めていこう。
単純な条件分岐には条件演算子を活用する
プログラムにおける条件分岐は、処理の分かれ道を制御する基本構造の1つである。多くの場面でif…elseが使われるが、単純な2択を処理するだけならば、条件演算子(三項演算子)を使う方が効果的である。条件演算子は、次のような構文で構成される。
条件式 ? 条件が真の場合の式 : 条件が偽の場合の式
この形式は、処理を1行で簡潔に記述できるだけでなく、式の一部として利用可能であるため、関数の引数や代入文の中でも柔軟に使える。たとえば、りんごの数に応じて複数形の「s」を付ける処理は、次のように書ける。
printf("I ate %d pear%s\n", numPear, (numPear > 1) ? "s" : "");
#include <stdio.h>
int main(void) {
int numApple = 3; // 食べたりんごの数。ここは好きな数に変更してOK
printf("I ate %d apple%s\n", numApple, (numApple > 1) ? "s" : "");
return 0;
}
I ate 3 apples
I ate 1 apple
このように、条件演算子はifが使えない位置でも自然に埋め込める。if文ではこのような処理を行うために複数行に分けたり、一時変数を導入したりする必要がある。
さらに、数値の条件に基づく加算や乗算にも条件演算子は効果的である。次の例では、購入金額が5000円以下なら10%の税込価格を、それ以上なら5%の税込価格を求めている。
total = (total <= 5000.0) ? total * 1.10 : total * 1.05;
この三項演算子(条件演算子)を使ったコードは、金額 total に応じて異なる割合の増加を計算するものです。これをシンプルな例として完成させ、わかりやすい変数名と数値に置き換えると、以下のようになります:
#include <stdio.h>
int main() {
double price = 4000.0; // 元の価格
double finalPrice;
finalPrice = (price <= 5000.0) ? price * 1.10 : price * 1.05;
printf("Final price: %.2f\n", finalPrice);
return 0;
}
Final price: 5250.00
Final price: 2200.00
このように記述すれば、変数の変更が明確で、意図の読み取りも簡単になる。ただし、すべてのif…elseを条件演算子に置き換えるのは避けるべきである。複数の条件を含む分岐や、ブロック内で複数の処理を行う場合は、if文の方が読みやすく、保守性も高い。
したがって、条件演算子は「1つの値を条件に応じて切り替える」ような単純な場合に絞って使うのが最も効果的である。冗長なif構文を避けると同時に、コードの可読性と実行効率を両立できる手段として、積極的に取り入れる価値がある。
インクリメントとデクリメントの仕組みと注意点
変数の値を1だけ増やす、あるいは減らす操作は、日常的な処理で頻繁に登場する。たとえば、ページビューのカウントや、在庫数の調整、繰り返し処理のカウンタ更新などが典型的な例である。このような用途には、インクリメント演算子(++)とデクリメント演算子(–)が活躍する。
++は変数の値を1つ増やし、–は1つ減らす。どちらの演算子も変数に対してのみ適用できる。リテラルや計算式に直接使うと、構文エラーを引き起こす。たとえば、–14;のような書き方は無効である。
これらの演算子には、プレフィックス(前置)とポストフィックス(後置)の2種類の書き方がある。++count;や–count;のように変数の前に記述する場合が前置、count++;やcount–;のように後ろに記述する場合が後置である。
単独で使う限り、どちらの形式でも同じ結果が得られる。しかし、他の式と組み合わせたときには動作順序の違いが結果に影響する。次のコードを見てみよう。
int i = 2, j = 5, n;
n = ++i * j;
#include <stdio.h>
int main() {
int i = 2, j = 5, n;
n = ++i * j;
printf("i = %d, j = %d, n = %d\n", i, j, n);
return 0;
}
i = 3, j = 5, n = 15
この例では、++iが先に評価されるため、iは3に増加したうえで、n = 3 * 5という計算が行われる。したがって、nには15が格納される。
一方、次のように後置形式を使った場合は結果が異なる。
int i = 2, j = 5, n;
n = i++ * j;
#include <stdio.h>
int main() {
int i = 2, j = 5, n;
n = i++ * j;
printf("i = %d, j = %d, n = %d\n", i, j, n);
return 0;
}
i = 3, j = 5, n = 10
この場合、iの現在の値(2)が先に使われて計算が行われ、n = 2 * 5の結果が格納されたあとに、iが3に増加する。評価順序の違いによって計算結果が変わる点は、初学者が誤解しやすい部分である。どちらも計算後は i = 3 になるが、計算中に使われる値が異なるのがポイントだ。
このように、前置と後置の違いを理解しないまま使うと、意図しない結果を招く可能性がある。とくにループや複雑な式の中でインクリメントやデクリメントを用いるときは、評価のタイミングと処理の影響を明確に意識して使うことが求められる。
sizeof演算子によるメモリサイズの把握
C言語では、データ型ごとに異なるメモリ領域を確保する必要がある。コンパイラやハードウェアのアーキテクチャによって、各型のサイズは変動する。そのため、型に対して確保されるメモリの正確なバイト数を確認したいときには、sizeof演算子を使用する。
sizeofは演算子の一種であり、関数ではない。使用すると、指定したデータ型または変数に必要なメモリのバイト数を返す。たとえば、sizeof(int)はint型が占めるバイト数を返す。32ビット環境では通常4バイトとなるが、16ビット環境では2バイト、64ビット環境ではコンパイラの設計方針により異なる結果となることもある。
具体的な使い方としては、以下のように記述する。
int i = sizeof(int); // int型のバイト数をiに代入
int f = sizeof(float); // float型のバイト数をfに代入
#include <stdio.h>
int main() {
int i = sizeof(int); // int型のバイト数をiに代入
int f = sizeof(float); // float型のバイト数をfに代入
printf("int型のサイズ: %dバイト\n", i);
printf("float型のサイズ: %dバイト\n", f);
return 0;
}
型そのものに対してsizeofを適用する場合は、()を使ってsizeof(int)のように書く。変数に対して使用する場合は、sizeof xのように括弧を省略することも可能であるが、可読性や一貫性を考慮して括弧付きで記述するのが一般的だ。
sizeofはコンパイル時に評価されるため、プログラム実行中のオーバーヘッドは生じない。つまり、実行時のメモリ使用量の測定ではなく、型に基づいた静的なサイズ確認を目的としている。
構造体(struct)のサイズを確認すると、意図したサイズより大きくなることがある。これはメンバ間のアライメント(整列)によりパディング(詰め物)が挿入されるためである。この点は、低レベルプログラミングや組み込み開発において重要となる。
sizeof演算子は、標準化されたコードを書くうえで必須の知識であり、異なる環境でも安定した動作を実現するための基本となる。固定サイズの配列の長さを求める際や、メモリ確保関数(mallocなど)と併用する場面でも頻繁に用いられる。