コンテンツにスキップ

C++/演算子と式

出典: フリー教科書『ウィキブックス(Wikibooks)』
< C++


はじめに[編集]

演算子と式は、プログラミング言語において非常に重要な概念です。これらは、プログラム内でデータを操作し、計算を行うための基本的な手段を提供します。本節では、演算子と式の役割について概要を述べます。

演算子と式の役割の概要[編集]

演算子は、プログラムで操作を行うための記号やキーワードです。これらは、データに対して特定の操作を行うために使用されます。一方、式は、演算子やオペランドから構成される計算や処理の一連の記述です。式は、データを組み合わせて計算を行い、結果を生成します。

演算子と式は、プログラム内でさまざまな目的で使用されます。主な役割は次のとおりです。

計算と演算
演算子と式は、数値や変数に対して算術演算(加算、減算、乗算、除算など)を行い、計算を実行します。これにより、プログラムは数値計算やデータ処理を行うことができます。
比較と論理演算
演算子と式は、値や変数の比較を行い、条件を評価します。これにより、プログラムは条件分岐やループなどの制御構造を実現します。
ビット操作
ビット演算子を使用して、データのビットレベルでの操作を行うことができます。これにより、ビットマスク、ビットシフト、ビットフラグの設定などが可能になります。
代入と複合代入
代入演算子を使用して、変数に値を代入します。また、複合代入演算子を使用して、変数に対して演算を行い、結果を変数に再代入します。

演算子と式は、プログラムの中核を成す要素であり、プログラムの動作や振る舞いを制御する上で不可欠です。そのため、これらの概念を理解し、効果的に活用することが重要です。

基本的な演算子[編集]

プログラム内でデータを操作し、計算を行うために、演算子は不可欠な要素です。ここでは、基本的な演算子について解説します。

代入演算子[編集]

代入演算子は、右辺の値を左辺の変数に代入するために使用されます。C++における代入演算子は「=」です。例えば、x = 5;は、変数xに値5を代入します。

算術演算子[編集]

算術演算子は、数値の計算に使用されます。C++には、加算、減算、乗算、除算、剰余の算術演算子があります。

  • 加算演算子(+)は、2つの数値を加算します。
  • 減算演算子(-)は、右辺の値を左辺の値から減算します。
  • 乗算演算子(*)は、2つの数値を掛け合わせます。
  • 除算演算子(/)は、左辺の値を右辺の値で割ります。
  • 剰余演算子(%)は、左辺の値を右辺の値で割った余りを求めます。

比較演算子[編集]

比較演算子は、2つの値を比較するために使用されます。結果は真(true)か偽(false)の論理値となります。C++には、以下の比較演算子があります。

  • 等しい(==
  • 等しくない(!=
  • 大なり(>
  • 大なりイコール(>=
  • 小なり(<
  • 小なりイコール(<=

論理演算子[編集]

論理演算子は、論理式の評価に使用されます。C++には、以下の論理演算子があります。

  • 論理積(AND):&&。両方の条件が真の場合に真となります。
  • 論理和(OR):||。どちらかの条件が真の場合に真となります。
  • 論理否定(NOT):!。条件を反転させます。

ビット演算子[編集]

ビット演算子は、ビットレベルでの演算に使用されます。C++には、ビット演算子が用意されており、ビット単位でのAND、OR、XOR、NOTなどの操作が可能です。

これらの演算子を適切に使用することで、プログラム内でのデータの操作や計算を効率的に行うことができます。

複合代入演算子[編集]

複合代入演算子は、変数に対する演算と代入を同時に行うための便利な方法です。代入演算子と他の演算子を組み合わせることで、コードの可読性を向上させることができます。

+= (加算して代入)[編集]

+= 演算子は、左辺の変数に右辺の値を加えて、その結果を左辺の変数に代入します。つまり、a += b;a = a + b; と同じです。

int a = 5;
int b = 3;
a += b; // a = a + b; と同じ
// 結果は a = 8

-= (減算して代入)[編集]

-= 演算子は、左辺の変数から右辺の値を引いて、その結果を左辺の変数に代入します。つまり、a -= b;a = a - b; と同じです。

int a = 5;
int b = 3;
a -= b; // a = a - b; と同じ
// 結果は a = 2

*= (乗算して代入)[編集]

*= 演算子は、左辺の変数に右辺の値を掛けて、その結果を左辺の変数に代入します。つまり、a *= b;a = a * b; と同じです。

int a = 5;
int b = 3;
a *= b; // a = a * b; と同じ
// 結果は a = 15

/= (除算して代入)[編集]

/= 演算子は、左辺の変数を右辺の値で割って、その結果を左辺の変数に代入します。つまり、a /= b;a = a / b; と同じです。

int a = 6;
int b = 3;
a /= b; // a = a / b; と同じ
// 結果は a = 2

%= (剰余して代入)[編集]

%= 演算子は、左辺の変数を右辺の値で割った余りを、左辺の変数に代入します。つまり、a %= b;a = a % b; と同じです。

int a = 10;
int b = 3;
a %= b; // a = a % b; と同じ
// 結果は a = 1

複合代入演算子は、短く、シンプルで、効率的なコードを書くための重要な手段です。使い方をマスターすると、コードの可読性と保守性が向上し、効率的な開発が可能になります。

インクリメントとデクリメント演算子[編集]

インクリメント演算子 ++ とデクリメント演算子 -- は、変数の値を1つ増減させるために使用されます。これらの演算子には、前置と後置の2つの形式があります。それぞれの違いと使用例について説明します。

++、-- の前置と後置の違い[編集]

前置形式
演算子が変数の前に置かれる (++i, --i)。変数の値が変更された後、式全体の値が評価されます。
後置形式
演算子が変数の後に置かれる (i++, i--)。式全体の値が評価された後に、変数の値が変更されます。

例えば、i が 5 の場合、前置インクリメント ++ii を 1 増やした後の値を返しますが、後置インクリメント i++i の値を返した後に i を 1 増やします。

使用例と注意点[編集]

前置形式の使用例[編集]

int i = 5;
int a = ++i; // i を 1 増やした後に a に代入する
// 結果: i = 6, a = 6

後置形式の使用例[編集]

int i = 5;
int a = i++; // a に i を代入した後に i を 1 増やす
// 結果: i = 6, a = 5

注意点[編集]

  • ループや条件文などでインクリメントやデクリメント演算子を使う際に、前置形式と後置形式を混同しないように注意する必要があります。特に、同じ式内で複数回使用する場合には、意図しない結果が生じる可能性があります。
  • 一般的に、前置形式の方が後置形式よりも効率的です。後置形式は一時変数を使用しているため、パフォーマンスが低下する可能性があります。ただし、ほとんどの場合、この違いは微小であり無視できます。

条件演算子[編集]

条件演算子 ?: は、C++ において条件に応じて異なる値を返すために使用されます。これは三項演算子としても知られています。条件演算子の構文は以下の通りです:

</syntaxhighlight> condition ? expression1 : expression2 </syntaxhighlight>

condition が真の場合、expression1 の値が返されます。偽の場合は expression2 の値が返されます。

使用例と説明
int a = 5;
int b = 10;

int max_value = (a > b) ? a : b; // 条件 (a > b) が真であれば a を返し、偽であれば b を返す

この例では、変数 ab の値を比較して、ab より大きい場合は max_valuea の値が代入され、そうでない場合は b の値が代入されます。

条件演算子は if-else 文と同様の機能を提供しますが、単純な条件の場合にコードを短く保ち、可読性を向上させるのに役立ちます。ただし、複雑な条件や複数の分岐を持つ場合は、if-else 文の使用が適しています。

ビットシフト演算子[編集]

ビットシフト演算子 <<>> は、ビット単位での左シフトと右シフトを行うために使用されます。

<< 演算子(左シフト)[編集]

<< 演算子は、対象のビット列を左に指定されたビット数だけシフトします。右側には 0 が追加されます。左シフトは、対象の値を 2 進数表現で左に移動させる効果があります。

unsigned int value = 5; // 0000 0101 (2進数表記)

unsigned int result = value << 2; // 0001 0100 (2進数表記)

>> 演算子(右シフト)[編集]

>> 演算子は、対象のビット列を右に指定されたビット数だけシフトします。左側には、符号ビットのコピー(符号付き整数の場合)または 0 が追加されます。右シフトは、対象の値を 2 進数表現で右に移動させる効果があります。

unsigned int value = 20; // 0001 0100 (2進数表記)

unsigned int result = value >> 2; // 0000 0101 (2進数表記)

使用例と応用[編集]

ビットシフト演算子は、データのビット操作に使用されます。特に、パフォーマンスの向上や特定のビットパターンの抽出など、さまざまな応用があります。また、ビットマスクの作成、データの圧縮、暗号化アルゴリズムなど、さまざまなアプリケーションで使用されます。

その他の演算子[編集]

sizeof 演算子[編集]

sizeof 演算子は、指定された式やデータ型のバイト数を取得するために使用されます。これは、コンパイル時に解決される演算子であり、ランタイム中に実行されるわけではありません。

int array[5];
size_t size = sizeof(array); // arrayのサイズを取得

アドレス演算子 &[編集]

& 演算子は、変数のアドレスを取得します。これにより、変数がメモリ内のどこに格納されているかを示すポインタが生成されます。

int value = 10;
int *ptr = &value; // ptrにはvalueのアドレスが格納される

ポインタ演算子 *[編集]

* 演算子は、ポインタが指すメモリの値を取得します。これは間接参照演算子とも呼ばれます。

int value = 10;
int *ptr = &value; // ptrにはvalueのアドレスが格納される
int dereferenced_value = *ptr; // ptrが指すメモリの値を取得

メンバアクセス演算子 . と ->[編集]

. 演算子は、オブジェクトのメンバにアクセスするために使用されます。構造体やクラスのインスタンスのメンバにアクセスする際に使用されます。

struct Person {
    int age;
    string name;
};

Person person;
person.age = 25; // メンバアクセス演算子でageにアクセス

-> 演算子は、ポインタが指す構造体やクラスのメンバにアクセスするために使用されます。

Person *ptr = &person;
ptr->age = 30; // メンバアクセス演算子でageにアクセス

これらの演算子は、C++で重要な概念であり、効果的なプログラミングに不可欠です。

式の評価順序[編集]

演算子の優先順位と結合性[編集]

C++の演算子は、優先順位と結合性を持っています。優先順位が高い演算子ほど、式の評価が早く行われます。また、結合性は、同じ優先順位を持つ演算子の間で評価の順序を示します。

一般的な演算子の優先順位は次の通りです(高い順から低い順):

  1. ()(括弧)
  2. ++, --(前置)
  3. ++, --(後置)
  4. +, -(単項プラス、単項マイナス、論理否定)
  5. *, /, %
  6. +, -(加算、減算)
  7. <<, >>
  8. <, <=, >, >=
  9. ==, !=
  10. &
  11. ^
  12. |
  13. &&
  14. ||
  15. ? :(条件演算子)
  16. =, +=, -=, *=, /=, %=, <<=, >>=, &=, ^=, |=

式の評価順序の例[編集]

int a = 5;
int b = 10;
int c = 15;

int result = a + b * c; // 乗算が先に評価される

int x = a++ * b--; // aとbの値が評価された後にインクリメント、デクリメントが行われる

int y = (a > b) ? a : b; // 条件演算子では条件の評価後に適切な分岐が選択される

これらの例は、演算子の優先順位と結合性が式の評価順序に影響する方法を示しています。正しい評価順序を理解することで、意図した結果を得るために効果的に式を構築できます。

演算子オーバーロード[編集]

カスタムクラスでの演算子のオーバーロードの説明[編集]

C++では、ユーザー定義のクラスや構造体に対して、組み込み型と同様に演算子をオーバーロードすることができます。これにより、ユーザー定義の型に対して演算子を使用する際に、より直感的な動作を提供することができます。

演算子のオーバーロードは、通常、メンバ関数として実装されますが、一部の演算子はグローバル関数としてもオーバーロードできます。

例えば、+演算子をオーバーロードすることで、ユーザー定義のクラスに対して加算操作を定義できます。また、==演算子をオーバーロードすることで、クラスのインスタンス同士の等価性を比較できます。

代表的な演算子のオーバーロードの使用例[編集]

#include <iostream>

class Complex {
 private:
  double real;
  double imaginary;

 public:
  Complex(double r, double i) : real{r}, imaginary{i} {}

  // + 演算子のオーバーロード
  auto operator+(const Complex& other) const -> Complex {
    return {real + other.real, imaginary + other.imaginary};
  }

  // == 演算子のオーバーロード
  auto operator==(const Complex& other) const -> bool {
    return (real == other.real) && (imaginary == other.imaginary);
  }

  // 演算子 << のオーバーロード(ストリームへの出力)
  friend auto operator<<(std::ostream& os, const Complex& c) -> std::ostream& {
    os << c.real << " + " << c.imaginary << "i";
    return os;
  }
};

auto main() -> int {
  Complex a{2, 3};
  Complex b{4, 5};

  Complex sum{a + b};  // + 演算子のオーバーロード
  std::cout << "Sum: " << sum << std::endl;

  bool isEqual = (a == b);  // == 演算子のオーバーロード
  std::cout << "Is equal: " << std::boolalpha << isEqual << std::endl;

  return 0;
}

この例では、Complexクラスに対して+演算子と==演算子をオーバーロードしています。また、<<演算子をグローバル関数として友達関数として定義しています。これにより、std::ostreamに対してComplexオブジェクトを出力できます。

演算子の特殊な使用例[編集]

C++では、いくつかの演算子に対して特殊な関数(演算子関数または特殊メンバ関数と呼ばれる)を定義することができます。これらの演算子関数は、特殊な構文を使用して定義され、特定の演算子が適用されたときに自動的に呼び出されます。

代表的な演算子関数の特殊な使用例[編集]

  1. コピーコンストラクタと代入演算子
    #include <iostream>
    
    class MyClass {
     private:
        int* data;
    
     public:
        // コンストラクタ
        MyClass(int value) {
            data = new int(value);
        }
    
        // コピーコンストラクタ
        MyClass(const MyClass& other) {
            data = new int(*other.data);
            std::cout << "Copy constructor called" << std::endl;
        }
    
        // 代入演算子
        MyClass& operator=(const MyClass& other) {
            if (this != &other) {
                delete data;
                data = new int(*other.data);
            }
            std::cout << "Assignment operator called" << std::endl;
            return *this;
        }
    
        // デストラクタ
        ~MyClass() {
            delete data;
        }
    
        int getValue() const {
            return *data;
        }
    };
    
    int main() {
        MyClass obj1(5); // コンストラクタ
        MyClass obj2 = obj1; // コピーコンストラクタ
        MyClass obj3(10);
        obj3 = obj1; // 代入演算子
    
        std::cout << "Value of obj1: " << obj1.getValue() << std::endl;
        std::cout << "Value of obj2: " << obj2.getValue() << std::endl;
        std::cout << "Value of obj3: " << obj3.getValue() << std::endl;
    
        return 0;
    }
    
    この例では、コピーコンストラクタと代入演算子を使用して、オブジェクトのコピーおよび代入が行われるときに特定の動作を行います。コピーコンストラクタは、オブジェクトのコピーが作成されるときに呼び出され、代入演算子は既存のオブジェクトに別のオブジェクトの内容が代入されるときに呼び出されます。

演算子の注意点とベストプラクティス[編集]

演算子の注意点や使用時のベストプラクティスのまとめ[編集]

演算子の使用に関する注意点やベストプラクティスを以下にまとめます。

演算子のオーバーロードを慎重に行う
カスタムクラスで演算子をオーバーロードする場合、その意味を明確にするために、慎重な設計とコメントが必要です。また、予期せぬ挙動を避けるために、演算子のオーバーロードを慎重に行う必要があります。
代入演算子の自己代入チェック
クラスで代入演算子をオーバーロードする場合、自己代入を防ぐために、代入演算子内で this != &other のチェックを行う必要があります。
メンバアクセス演算子 .-> の適切な使用
ポインターを介してメンバーにアクセスする場合、-> 演算子を使用します。また、オブジェクトのメンバにアクセスする場合は、. 演算子を使用します。
演算子の優先順位と結合性の理解
演算子の優先順位と結合性を正確に理解し、必要に応じてカッコを使用して式の評価順序を明示的に指定します。
ビット演算子の使用に注意
ビット演算子は低レベルの操作を行うため、特にプラットフォーム依存のコードや効率的なアルゴリズムの実装において使用されますが、ビット演算子の過度の使用はコードの可読性を低下させる可能性があります。
インクリメントとデクリメント演算子の前置と後置の違い
インクリメントとデクリメント演算子の前置と後置の違いを理解し、適切に使用します。前置演算子は操作を行った後の値を返し、後置演算子は操作を行う前の値を返します。
三項演算子の適切な使用
三項演算子は単純な条件分岐を行う際に便利ですが、過度に複雑な式を含めると可読性が低下する可能性があります。必要な場合にのみ使用し、分かりやすい形式で書くことを心がけます。
演算子の特殊な使用例に注意
特殊な演算子関数(演算子オーバーロード)を使用する場合、その挙動が他の開発者にとって明確であることを確認します。また、標準的な演算子の挙動と一貫性があることを確認します。

これらのポイントに留意することで、より安全で効率的なコードを記述し、コードの品質と保守性を向上させることができます。

まとめ[編集]

演算子と式の重要な概念のまとめ[編集]

演算子と式に関する重要な概念をまとめます。

演算子
プログラミング言語において、演算子は値や変数に対して演算を行うための記号やキーワードです。代入演算子、算術演算子、比較演算子、論理演算子などがあります。
式は演算子やオペランドから構成される式であり、計算可能な値や式を生成します。式は評価されると値を生成します。
演算子のオーバーロード
カスタムクラスやデータ型に対して、標準の演算子の挙動を変更することができます。これは演算子のオーバーロードと呼ばれ、C++などの言語でサポートされています。
演算子の結合性と優先順位
演算子には結合性と優先順位があり、式の評価順序を制御します。演算子の結合性は、複数の演算子が同じ式内で使用された場合に、どの演算子が優先されるかを示します。優先順位は、演算子が式内でどのような順序で評価されるかを決定します。
演算子の注意点とベストプラクティス
演算子のオーバーロードやビット演算子の適切な使用、インクリメントとデクリメント演算子の使い分け、三項演算子の適切な使用などに留意することで、コードの品質と保守性を向上させることができます。

これらの概念を理解し、適切に使用することで、効果的なプログラミングが可能となります。