フリーキーズ | 独学プログラミング

【初心者向け】C++の演算子まとめ!算術・比較・論理を完全攻略

最終更新日
リンドくん

リンドくん

たなべ先生、C++の演算子ってたくさんあって混乱してしまいます。
特に&&とか||とか、なんだか暗号みたいで...

たなべ

たなべ

確かにC++の演算子は種類が多いね!でも、これらは実はとても論理的で、日常生活の考え方に近いものなんだ。
「足し算」や「大きい・小さい」の比較、それに「かつ」「または」といった論理...これらは全部、我々が普段から使っている考え方なんだよ。

なぜC++の演算子を理解する必要があるのか

プログラミングを学び始めたばかりの方にとって、C++の演算子(+, -, *, /, ==, !=, &&, || など)は、まるで暗号のように見えるかもしれません。
しかし、これらの演算子は、プログラムの「計算」と「判断」を担う重要な要素です。

演算子を理解することは、C++でプログラミングする上での基礎中の基礎といえます。
数学の四則演算を知らずに複雑な計算ができないように、プログラミングでも演算子の使い方を知らなければ、思い通りのプログラムを作ることはできません。

この記事では、C++の演算子を算術演算子比較演算子論理演算子の3つのカテゴリーに分けて、それぞれの使い方と実際のコード例を紹介していきます。
さらに、初心者がつまずきやすいポイントや、実践的なテクニックについても解説します。

算術演算子 - 計算の基本

リンドくん

リンドくん

算術演算子って、数学の計算記号みたいなものですか?

たなべ

たなべ

その通り!基本的には私たちが学校で習った四則演算とほぼ同じなんだ。
ただし、プログラミングならではの特殊な演算子もあるよ。

基本的な算術演算子とその使い方

C++の算術演算子は、数値を計算するための基本的なツールです。
主な算術演算子には以下のようなものがあります。

  • +(加算) = 2つの値を足し合わせます
  • -(減算) = 左の値から右の値を引きます
  • *(乗算) = 2つの値をかけ合わせます
  • /(除算) = 左の値を右の値で割ります
  • %(剰余・モジュロ) = 割り算の余りを求めます

これらの演算子は、私たちが普段使っている算数の記号とほぼ同じですので、比較的理解しやすいのではないでしょうか。

// 算術演算子の基本的な使い方
int a = 10;
int b = 3;

int sum = a + b;      // 13
int difference = a - b;  // 7
int product = a * b;   // 30
int quotient = a / b;   // 3(整数同士の除算は整数部分のみ)
int remainder = a % b;  // 1(10÷3の余り)

std::cout << "和: " << sum << std::endl;
std::cout << "差: " << difference << std::endl;
std::cout << "積: " << product << std::endl;
std::cout << "商: " << quotient << std::endl;
std::cout << "余り: " << remainder << std::endl;

注意すべき特殊なケース

算術演算子を使う際に、初心者がよく混乱するポイントがいくつかあります。

1. 整数同士の除算

C++では、整数型同士の除算は結果も整数になります。小数部分は切り捨てられるのです。

int x = 5;
int y = 2;
double z = x / y;  // 2.0(期待する2.5ではない!)

この結果を正確な小数にしたい場合は、どちらかの値を浮動小数点型にキャスト(型変換)する必要があります。

int x = 5;
int y = 2;
double z = static_cast<double>(x) / y;  // 2.5(正確な結果)

2. ゼロによる除算

プログラミングでは、ゼロで割るとエラーになります。これは数学的にも定義されていない操作だからです。

int result = 10 / 0;  // 実行時エラー!

常に分母が0になる可能性がある場合は、条件分岐で事前にチェックすることが重要です。

3. 剰余演算子の符号

負の数での剰余演算は、言語によって結果が異なることがありますが、C++では基本的に左オペランド(割られる数)の符号に従います。

int result1 = 5 % 3;    // 2
int result2 = -5 % 3;   // -2
int result3 = 5 % -3;   // 2

算術演算子の基本を理解すれば、単純な計算から複雑なアルゴリズムまで、様々なプログラミングの課題に取り組むことができます。
次は、値の比較に使う比較演算子について見ていきましょう。

比較演算子 - 値の関係を判断する

リンドくん

リンドくん

比較演算子は、2つの値がどういう関係にあるか調べるものですか?

たなべ

たなべ

そうだね!例えば「この数は5より大きいか?」「この2つの変数は同じ値か?」といった関係性を確認するためのものなんだ。
これらは条件分岐で特に重要になるよ。

6種類の比較演算子とその意味

比較演算子は、2つの値の関係を比較し、その結果が「真」か「偽」かを返します。
C++には以下の6種類の比較演算子があります。

  • ==(等しい) = 左右の値が等しければtrue、そうでなければfalse
  • !=(等しくない) = 左右の値が異なればtrue、等しければfalse
  • >(より大きい) = 左の値が右の値より大きければtrue
  • <(より小さい) = 左の値が右の値より小さければtrue
  • >=(以上) = 左の値が右の値以上であればtrue
  • <=(以下) = 左の値が右の値以下であればtrue

これらの演算子は、条件分岐(if文など)やループ(while, forなど)の制御に欠かせません。

int age = 18;
bool canVote = age >= 18;  // true

std::string password = "abc123";
bool isCorrect = (password == "abc123");  // true

double height = 175.5;
bool isTall = height > 180.0;  // false

よくある間違いとその対策

比較演算子を使う際によくある間違いとその対策を見ていきましょう。

1. 等値比較(==)と代入(=)の混同

C++初心者がよく間違えるのが、等値比較演算子(==)と代入演算子(=)の混同です。

// 間違った例
if (x = 5)
{  // xに5を代入し、その結果(5)を評価
    // この条件は常に真となる
}

// 正しい例
if (x == 5)
{  // xが5と等しいかを比較
    // xが5のときだけ実行される
}

2. 浮動小数点数の比較

浮動小数点数(float, double)は、丸め誤差があるため、単純な等値比較が思わぬ結果になることがあります。

// 問題がある例
double a = 0.1 + 0.2;
if (a == 0.3)
{  // これはfalseになることがある!
    // ...
}

// より安全な方法
double a = 0.1 + 0.2;
double epsilon = 0.0001;  // 許容する誤差
if (std::abs(a - 0.3) < epsilon)
{
    // ...
}

3. 文字列の比較

C++では、std::string型の文字列は==演算子で直接比較できますが、C言語のスタイルの文字列(char配列)は単純に比較できません。

// std::stringの比較
std::string str1 = "hello";
std::string str2 = "hello";
if (str1 == str2)
{  // 正しく動作する
    // ...
}

// C言語スタイルの文字列比較(これは間違い)
char str3[] = "hello";
char str4[] = "hello";
if (str3 == str4)
{  // アドレスの比較になるので、通常falseになる
    // ...
}
// 正しくはstrcmpを使う
if (strcmp(str3, str4) == 0)
{
    // ...
}

比較演算子は、プログラムのロジックを構築する上で非常に重要です。
次は、複数の条件を組み合わせるための論理演算子について見ていきましょう。

論理演算子 - 複数の条件を組み合わせる

リンドくん

リンドくん

「かつ」や「または」のような条件の組み合わせが論理演算子なんですね。
これがなぜ&&||という記号になるんですか?

たなべ

たなべ

これらの記号はコンピュータの歴史から来ているんだよ。
例えば「&&」はアンパサンド2つで「AND」を、「||」はパイプ2つで「OR」を表しているんだ。数学の論理学からの表記をプログラミング用にアレンジしたものなんだよ。

3つの主要な論理演算子

C++の論理演算子は、複数の条件を組み合わせて、より複雑な条件式を作るために使用します。
主な論理演算子は以下の3つです。

  • &&(論理AND) = 左右の条件が両方ともtrueの場合のみtrueを返す
  • ||(論理OR) = 左右の条件のいずれかがtrueであればtrueを返す
  • !(論理NOT) = 条件の真偽を反転させる

これらの演算子を使うことで、複数の条件を組み合わせた複雑な条件式を作ることができます。

int age = 25;
bool hasLicense = true;

// 年齢が18以上かつ免許を持っていれば運転可能
bool canDrive = (age >= 18) && hasLicense;  // true

// 学生証か社員証のどちらかを持っていれば入館可能
bool hasStudentID = false;
bool hasEmployeeID = true;
bool canEnter = hasStudentID || hasEmployeeID;  // true

// 未成年でなければお酒を購入可能
bool isMinor = (age < 20);
bool canBuyAlcohol = !isMinor;  // true(25歳なので未成年ではない)

短絡評価とその活用法

C++の論理演算子には短絡評価(short-circuit evaluation)という重要な特性があります。
これは以下のような仕組みです。

  • &&(AND) = 左側がfalseなら、右側は評価せずにfalseを返す
  • ||(OR) = 左側がtrueなら、右側は評価せずにtrueを返す

この特性を利用すると、効率的なコードや安全なチェックを行うことができます。

// 配列の境界チェックとアクセスを安全に行う例
int arr[5] = {1, 2, 3, 4, 5};
int index = getSomeIndex();  // 何らかの関数でインデックスを取得

// indexが有効範囲内の場合のみ配列にアクセス
if (index >= 0 && index < 5 && arr[index] > 3)
{
    // 安全に処理を実行
}

上記の例では、もしindexが0未満または5以上であれば、arr[index]の評価は行われません。
これにより配列の範囲外アクセスによるクラッシュを防ぐことができます。

複雑な条件式を読みやすくする工夫

論理演算子を使った条件式が複雑になるほど、可読性が低下する傾向があります。
以下のような工夫で可読性を高めましょう。

1. 括弧を適切に使う

論理演算子の優先順位(!が最も高く、次に&&、最後に||)を意識し、必要な場合は括弧を使って明確にします。

// 優先順位があいまいな例
bool result = a && b || c && d;

// 括弧で明確にした例
bool result = (a && b) || (c && d);

2. 複雑な条件式を分割する

非常に複雑な条件式は、中間変数を使って分割すると理解しやすくなります。

// 複雑な条件式
if ((age >= 18 && hasLicense) || (isEmergency && hasPermission && !isSuspended))
{
    // ...
}

// 分割して読みやすくした例
bool normalDrivingOK = (age >= 18 && hasLicense);
bool emergencyDrivingOK = (isEmergency && hasPermission && !isSuspended);

if (normalDrivingOK || emergencyDrivingOK)
{
    // ...
}

論理演算子をマスターすることで、複雑な条件を持つプログラムを効率的に書くことができるようになります。
次は、これらの演算子を実際のコードで効果的に使う方法について見ていきましょう。

代入演算子 - コードをコンパクトに

リンドくん

リンドくん

プログラムを見ていると、「+=」とか「*=」とかよく出てきますが、あれは何ですか?

たなべ

たなべ

あれは代入演算子の一種で、「複合代入演算子」と呼ばれるものだよ。
x += 5」は「x = x + 5」の省略形なんだ。コードをより短く書けて便利なんだよ!

複合代入演算子の種類と使い方

代入演算子の中でも、特によく使われるのが複合代入演算子です。
これらは算術演算と代入を一度に行うための簡潔な表現です。

  • +=(加算して代入): a += ba = a + b と同じ
  • -=(減算して代入): a -= ba = a - b と同じ
  • *=(乗算して代入): a *= ba = a * b と同じ
  • /=(除算して代入): a /= ba = a / b と同じ
  • %=(剰余を代入): a %= ba = a % b と同じ

これらの演算子を使うと、コードをよりコンパクトに書くことができます。

int score = 100;

// スコアを10増やす
score += 10;  // score = score + 10 と同じ(110になる)

// スコアを2倍にする
score *= 2;   // score = score * 2 と同じ(220になる)

// スコアを半分にする
score /= 2;   // score = score / 2 と同じ(110になる)

std::cout << "最終スコア: " << score << std::endl;

インクリメント・デクリメント演算子

さらにC++では、変数の値を1だけ増やしたり減らしたりする特殊な演算子があります。

  • ++(インクリメント) = 変数の値を1増やす
  • --(デクリメント) = 変数の値を1減らす

これらの演算子は、前置形式(++a)と後置形式(a++)があり、挙動が異なります。

  • 前置形式(++a = まず変数の値を増やし、その後、増やした値を式の値として使う
  • 後置形式(a++ = 式の値として現在の変数の値を使った後で、変数の値を増やす
int a = 5;
int b = 5;

int resultA = ++a;  // aを6に増やし、resultAに6を代入
int resultB = b++;  // resultBに5を代入し、その後bを6に増やす

std::cout << "a: " << a << ", resultA: " << resultA << std::endl;  // a: 6, resultA: 6
std::cout << "b: " << b << ", resultB: " << resultB << std::endl;  // b: 6, resultB: 5

効率的なコード記述のためのテクニック

代入演算子を効果的に使うためのテクニックをいくつか紹介します。

1. ループカウンターでのインクリメント

for文のカウンター変数の増加には、インクリメント演算子がよく使われます。

// 0から9までの数字を表示
for (int i = 0; i < 10; ++i)
{
    std::cout << i << " ";
}
// 出力: 0 1 2 3 4 5 6 7 8 9

2. 累積計算での複合代入演算子

合計や積などの累積値を計算する際に、複合代入演算子が便利です。

// 配列の合計を計算
int arr[] = {5, 10, 15, 20, 25};
int sum = 0;
for (int i = 0; i < 5; ++i)
{
    sum += arr[i];  // 各要素を合計に加算
}
std::cout << "合計: " << sum << std::endl;  // 合計: 75

代入演算子、特に複合代入演算子やインクリメント/デクリメント演算子を適切に使うことで、コードをより簡潔に、読みやすく書くことができます。

ビット演算子 - 低レベル操作

リンドくん

リンドくん

ビット演算子って聞いたことありますが、具体的にどんな時に使うんですか?難しそうで...

たなべ

たなべ

確かに最初は少し難しく感じるかもしれないね。
でも例えば、フラグの管理やデータ圧縮、暗号化アルゴリズムなど、効率的なプログラミングには欠かせない場面があるんだよ。基本を押さえておくと、将来役立つことがきっとあるよ。

ビット演算子の基本

ビット演算子は、整数型のデータの個々のビット(0か1)を操作するための演算子です。
主なビット演算子には以下のようなものがあります。

  • &(ビットAND) = 対応するビットが両方とも1なら1、それ以外は0
  • |(ビットOR) = 対応するビットのいずれかが1なら1、両方とも0なら0
  • ^(ビットXOR) = 対応するビットが異なれば1、同じなら0
  • ~(ビット反転) = 全てのビットを反転(0は1に、1は0に)
  • <<(左シフト) = ビットを左に指定した数だけずらす
  • >>(右シフト) = ビットを右に指定した数だけずらす
unsigned char a = 0b00001100;  // 二進数で12
unsigned char b = 0b00010010;  // 二進数で18

unsigned char resultAnd = a & b;    // 0b00000000 (0)
unsigned char resultOr = a | b;     // 0b00011110 (30)
unsigned char resultXor = a ^ b;    // 0b00011110 (30)
unsigned char resultNot = ~a;       // 0b11110011 (243)
unsigned char resultLeftShift = a << 2;  // 0b00110000 (48)
unsigned char resultRightShift = a >> 1; // 0b00000110 (6)

ビット演算の実用的な使い方

ビット演算は、一見難解に思えますが、様々な実用的な場面で活躍します。

1. フラグの管理

複数のブール値(フラグ)を1つの整数で効率的に管理できます。

// フラグの定義
const unsigned char FLAG_READ = 0b00000001;    // 1: 読み取り権限
const unsigned char FLAG_WRITE = 0b00000010;   // 2: 書き込み権限
const unsigned char FLAG_EXEC = 0b00000100;    // 4: 実行権限

// 権限の設定
unsigned char permissions = 0;
permissions |= FLAG_READ;   // 読み取り権限を追加
permissions |= FLAG_WRITE;  // 書き込み権限を追加

// 権限のチェック
if (permissions & FLAG_READ)
{
    std::cout << "読み取り可能" << std::endl;
}

// 権限の削除
permissions &= ~FLAG_WRITE;  // 書き込み権限を削除

// 権限の切り替え
permissions ^= FLAG_EXEC;   // 実行権限をトグル(ない場合は追加、ある場合は削除)

2. 高速な乗除算

左シフト(<<)と右シフト(>>)を使うと、2のべき乗による乗除算を高速に行えます。

int num = 5;
int multipliedBy2 = num << 1;    // 5 * 2 = 10
int multipliedBy4 = num << 2;    // 5 * 4 = 20
int multipliedBy8 = num << 3;    // 5 * 8 = 40

int dividedBy2 = num >> 1;       // 5 / 2 = 2(整数除算)

ビット演算は、低レベルのプログラミングやパフォーマンスが重要な場面で特に威力を発揮します。すべてのC++プログラマがマスターすべき重要な概念の一つです。

演算子の優先順位とコードの可読性

リンドくん

リンドくん

演算子が複数ある式だと、どの順番で計算されるか分からなくなりませんか?

たなべ

たなべ

いい質問だね!C++には演算子の優先順位のルールがあるんだ。
でも実は、括弧()を使って明示的に順序を示すのがベストプラクティスなんだよ。そうすれば読む人も迷わないからね。

C++の演算子優先順位

C++には多くの演算子があり、それぞれ優先順位(precedence)が決まっています。
一般的に以下の順序で評価されます(優先順位が高いもの順)。

  1. スコープ解決、メンバアクセス(::, ., ->
  2. 後置インクリメント/デクリメント(a++, a--
  3. 前置インクリメント/デクリメント(++a, --a)、単項演算子(+, -, !, ~
  4. 乗算、除算、剰余(*, /, %
  5. 加算、減算(+, -
  6. シフト演算子(<<, >>
  7. 比較演算子(<, <=, >, >=
  8. 等値比較演算子(==, !=
  9. ビットAND(&
  10. ビットXOR(^
  11. ビットOR(|
  12. 論理AND(&&
  13. 論理OR(||
  14. 条件演算子(? :
  15. 代入演算子(=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=

例えば、以下の式では優先順位に従って評価されます。

int result = 5 + 3 * 2;  // 3 * 2が先に計算されて6、次に5 + 6で11

括弧を使った明示的な順序指定

複雑な式では、演算子の優先順位を覚えているか自信がなくても、括弧を使って計算順序を明示的に指定できます。
これにより、可読性が向上し、バグも防げます。

// 曖昧な式
int result1 = a + b * c - d / e;

// 括弧を使って明確にした式
int result2 = (a + (b * c)) - (d / e);

複雑な式の分割

非常に複雑な式は、中間変数を使って分割するとさらに読みやすくなります。

// 複雑で読みにくい式
int result = (a + b) * c / (d - e) + (f & g) || (h && i);

// 分割して読みやすくした例
int part1 = (a + b) * c;
int part2 = d - e;
int division = part1 / part2;
bool bitCondition = (f & g);
bool logicalCondition = (h && i);
int result = division + bitCondition || logicalCondition;

可読性のあるコードを書くためのベストプラクティス

  1. 過度に複雑な式を避ける
    一つの式で多くのことを行おうとせず、論理的なステップに分割しましょう。

  2. 括弧を適切に使う
    特に異なる種類の演算子が混在する場合は、括弧で意図を明確にします。

  3. 命名規則を一貫させる
    変数や関数の名前は、その目的や内容を明確に表すものにしましょう。

  4. コメントを効果的に使う
    複雑なロジックには、なぜそのようなコードになっているのかを説明するコメントを加えましょう。

以上のルールを意識すると、自分だけでなく、チームメンバーや将来のメンテナーにとっても理解しやすいコードになります。
コードは書く時間よりも読まれる時間の方が長いものです。

まとめ

リンドくん

リンドくん

今日は色々な演算子について学びました!
どうすれば演算子を上手に使いこなせるようになりますか?

たなべ

たなべ

良い質問だね!演算子を使いこなすコツは、実際に手を動かしてコードを書くことなんだ。
最初は基本的な算術演算子から始めて、徐々に複雑な演算子にチャレンジしていくといいよ。そして何より、読みやすいコードを意識することが大切だね。

C++の演算子は、プログラミングの基本的かつ強力なツールです。今回の記事では、C++の様々な演算子について詳しく見てきました。

C++の演算子は、正しく使えば表現力豊かで効率的なコードを書くための素晴らしいツールです。
一方で、誤用すれば混乱やバグの原因ともなります。この記事で紹介した基本原則を念頭に置きながら、実際のコーディングで演算子を活用していただければ幸いです。

関連するコンテンツ