Operators in C++

Operators are special symbols that perform specific operations on one, two, or three operands and return a result. C++ provides a rich set of built-in operators to perform various tasks.

1. Arithmetic Operators

Arithmetic operators perform mathematical operations on numeric operands.

OperatorNameDescriptionExample
+AdditionAdds two operandsa + b
-SubtractionSubtracts second operand from the firsta - b
*MultiplicationMultiplies two operandsa * b
/DivisionDivides first operand by seconda / b
%ModulusRemainder after divisiona % b
++IncrementIncreases value by 1++a or a++
--DecrementDecreases value by 1--a or a--

Examples:

int a = 10, b = 3;
cout << "a + b = " << a + b << endl;  // 13
cout << "a - b = " << a - b << endl;  // 7
cout << "a * b = " << a * b << endl;  // 30
cout << "a / b = " << a / b << endl;  // 3 (integer division)
cout << "a % b = " << a % b << endl;  // 1 (remainder)

// Pre-increment and post-increment
int c = 5;
cout << "++c = " << ++c << endl;  // 6 (increments c first, then returns)
cout << "c++ = " << c++ << endl;  // 6 (returns c first, then increments)
cout << "c = " << c << endl;      // 7

Important Points:

  1. Integer Division: When dividing two integers, the result is truncated (decimal part is discarded).

    int result = 5 / 2;  // result = 2 (not 2.5)
  2. Modulus Operation: Only works with integer operands.

    int remainder = 10 % 3;  // remainder = 1
    // double mod = 10.5 % 3;  // Error: Invalid operands
  3. Pre vs. Post Increment/Decrement:

    • Pre-increment (++x): Increments the value, then returns it
    • Post-increment (x++): Returns the original value, then increments it
    int x = 5;
    int y = ++x;  // y = 6, x = 6
    
    x = 5;
    int z = x++;  // z = 5, x = 6

2. Relational Operators

Relational operators compare two values and return a boolean result (true or false).

OperatorNameDescriptionExample
==Equal toReturns true if operands are equala == b
!=Not equal toReturns true if operands are not equala != b
>Greater thanReturns true if left operand is greatera > b
<Less thanReturns true if left operand is smallera < b
>=Greater than or equal toReturns true if left operand is greater than or equal to righta >= b
<=Less than or equal toReturns true if left operand is smaller than or equal to righta <= b

Examples:

int a = 10, b = 5;
cout << "a == b: " << (a == b) << endl;  // 0 (false)
cout << "a != b: " << (a != b) << endl;  // 1 (true)
cout << "a > b: " << (a > b) << endl;    // 1 (true)
cout << "a < b: " << (a < b) << endl;    // 0 (false)
cout << "a >= b: " << (a >= b) << endl;  // 1 (true)
cout << "a <= b: " << (a <= b) << endl;  // 0 (false)

Important Points:

  1. Boolean Results: Relational operators return true (1) or false (0).

  2. Operator Precedence: Use parentheses to clarify complex conditions.

    if ((a > b) && (c < d)) {
        // Code here
    }
  3. Comparing Floating-Point Numbers: Due to precision issues, it’s often better to check if the absolute difference is smaller than a tolerance value.

    double x = 0.1 + 0.2;  // Might be 0.30000000000000004
    double y = 0.3;
    
    // Bad practice (may not work due to floating-point precision)
    if (x == y) { /* ... */ }
    
    // Better practice
    const double epsilon = 0.0001;  // Small tolerance value
    if (abs(x - y) < epsilon) { /* ... */ }

3. Logical Operators

Logical operators perform logical operations on boolean values.

OperatorNameDescriptionExample
&&Logical ANDReturns true if both operands are truea && b
||Logical ORReturns true if at least one operand is truea || b
!Logical NOTReturns true if operand is false!a

Examples:

bool a = true, b = false;
cout << "a && b: " << (a && b) << endl;  // 0 (false)
cout << "a || b: " << (a || b) << endl;  // 1 (true)
cout << "!a: " << (!a) << endl;          // 0 (false)
cout << "!b: " << (!b) << endl;          // 1 (true)

Truth Tables:

AND (&&) Operator:

ABA && B
falsefalsefalse
falsetruefalse
truefalsefalse
truetruetrue

OR (||) Operator:

ABA || B
falsefalsefalse
falsetruetrue
truefalsetrue
truetruetrue

NOT (!) Operator:

A!A
falsetrue
truefalse

Short-Circuit Evaluation:

  • AND (&&): If the first operand is false, the second operand is not evaluated because the result will be false regardless.
  • OR (||): If the first operand is true, the second operand is not evaluated because the result will be true regardless.
// Short-circuit with &&
if (ptr != nullptr && ptr->value > 0) {
    // Second part only evaluated if ptr is not null
}

// Short-circuit with ||
if (isError() || processData()) {
    // processData() only called if isError() returns false
}

4. Bitwise Operators

Bitwise operators perform operations on binary representations of the operands.

OperatorNameDescriptionExample
&Bitwise ANDPerforms AND operation on corresponding bitsa & b
|Bitwise ORPerforms OR operation on corresponding bitsa | b
^Bitwise XORPerforms XOR operation on corresponding bitsa ^ b
~Bitwise NOTInverts all bits~a
<<Left shiftShifts bits to the lefta << n
>>Right shiftShifts bits to the righta >> n

Examples:

int a = 5;  // 0101 in binary
int b = 3;  // 0011 in binary

cout << "a & b: " << (a & b) << endl;   // 1 (0001 in binary)
cout << "a | b: " << (a | b) << endl;   // 7 (0111 in binary)
cout << "a ^ b: " << (a ^ b) << endl;   // 6 (0110 in binary)
cout << "~a: " << (~a) << endl;         // -6 (1...1010 in binary, 2's complement)
cout << "a << 1: " << (a << 1) << endl; // 10 (1010 in binary)
cout << "a >> 1: " << (a >> 1) << endl; // 2 (0010 in binary)

Common Uses of Bitwise Operators:

  1. Setting Flags and Masks:

    // Using flags with OR (|) to set bits
    unsigned int flags = 0;
    const unsigned int READ = 1;     // 0001
    const unsigned int WRITE = 2;    // 0010
    const unsigned int EXECUTE = 4;  // 0100
    
    // Set read and write permissions
    flags = flags | READ | WRITE;  // flags becomes 3 (0011)
    
    // Check if read permission is set (using AND &)
    if (flags & READ) {
        cout << "Read permission is set" << endl;
    }
  2. Power of Two:

    // Left shift to compute powers of 2
    int powerOfTwo = 1 << n;  // Computes 2^n
  3. Optimizing Division and Multiplication:

    // Right shift divides by 2^n
    int result = value >> 2;  // Equivalent to value / 4
    
    // Left shift multiplies by 2^n
    int product = value << 3;  // Equivalent to value * 8

5. Assignment Operators

Assignment operators are used to assign values to variables.

OperatorDescriptionExample
=Simple assignmenta = b
+=Add and assigna += b (same as a = a + b)
-=Subtract and assigna -= b (same as a = a - b)
*=Multiply and assigna *= b (same as a = a * b)
/=Divide and assigna /= b (same as a = a / b)
%=Modulus and assigna %= b (same as a = a % b)
<<=Left shift and assigna <<= b (same as a = a << b)
>>=Right shift and assigna >>= b (same as a = a >> b)
&=Bitwise AND and assigna &= b (same as a = a & b)
|=Bitwise OR and assigna |= b (same as a = a | b)
^=Bitwise XOR and assigna ^= b (same as a = a ^ b)

Examples:

int a = 10;
a += 5;  // a becomes 15
a -= 3;  // a becomes 12
a *= 2;  // a becomes 24
a /= 4;  // a becomes 6
a %= 4;  // a becomes 2
a <<= 1; // a becomes 4
a >>= 1; // a becomes 2
a &= 3;  // a becomes 2
a |= 1;  // a becomes 3
a ^= 3;  // a becomes 0

6. Miscellaneous Operators

6.1 Conditional (Ternary) Operator (? :)

The conditional operator is the only ternary operator (taking three operands) in C++.

Syntax: condition ? expression1 : expression2

If the condition is true, expression1 is evaluated; otherwise, expression2 is evaluated.

int a = 10, b = 20;
int max = (a > b) ? a : b;  // max = 20

6.2 Comma Operator (,)

The comma operator allows multiple expressions to be evaluated in a single statement. The value of the entire expression is the value of the rightmost expression.

int a = 1, b = 2;
int c = (a++, b++, a + b);  // a = 2, b = 3, c = 5

6.3 sizeof Operator

The sizeof operator returns the size (in bytes) of its operand.

cout << "Size of int: " << sizeof(int) << " bytes" << endl;
int arr[10];
cout << "Size of array: " << sizeof(arr) << " bytes" << endl;

6.4 Scope Resolution Operator (::):

The scope resolution operator is used to identify and disambiguate identifiers used in different scopes.

namespace MyNamespace {
    int value = 10;
}

int value = 20;
cout << "Global value: " << value << endl;             // 20
cout << "Namespace value: " << MyNamespace::value << endl;  // 10

class MyClass {
public:
    static int value;
    void printValue();
};

int MyClass::value = 30;  // Defining static member variable
void MyClass::printValue() {
    cout << "Class value: " << value << endl;
}

6.5 Member Access Operators

  • Dot Operator (.): Accesses members of an object
  • Arrow Operator (->): Accesses members of an object through a pointer
struct Person {
    string name;
    int age;
};

Person person1;
person1.name = "Alice";  // Using dot operator

Person* person2 = new Person;
person2->name = "Bob";   // Using arrow operator
(*person2).age = 25;     // Equivalent to person2->age = 25

delete person2;  // Don't forget to free memory

6.6 Pointer Operators

  • Address-of Operator (&): Returns the memory address of a variable
  • Dereference Operator (*): Returns the value at the address stored in a pointer
int value = 10;
int* ptr = &value;  // ptr holds the address of value
cout << "Address of value: " << ptr << endl;
cout << "Value stored at the address: " << *ptr << endl;  // 10

*ptr = 20;  // Modifies the value through the pointer
cout << "New value: " << value << endl;  // 20

6.7 Type Casting Operators

C++ provides several type casting operators:

  1. Static Cast (static_cast<>): For “well-behaved” conversions

    double d = 3.14;
    int i = static_cast<int>(d);  // i = 3
  2. Dynamic Cast (dynamic_cast<>): For safe downcasting of polymorphic types

    Base* basePtr = new Derived();
    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
  3. Const Cast (const_cast<>): To add or remove const qualification

    const int c = 10;
    int* ptr = const_cast<int*>(&c);
  4. Reinterpret Cast (reinterpret_cast<>): For low-level reinterpreting of bit patterns

    int* p = reinterpret_cast<int*>(0x1000);

7. Operator Precedence and Associativity

Operators have different precedence levels that determine the order of evaluation in an expression.

Precedence (from highest to lowest):

  1. Scope resolution: ::
  2. Postfix increment/decrement: a++, a--
  3. Prefix increment/decrement, unary: ++a, --a, +a, -a, !, ~, (type), *, &, sizeof
  4. Member access: ., ->
  5. Multiplication/division/modulus: *, /, %
  6. Addition/subtraction: +, -
  7. Shift operators: <<, >>
  8. Relational operators: <, <=, >, >=
  9. Equality operators: ==, !=
  10. Bitwise AND: &
  11. Bitwise XOR: ^
  12. Bitwise OR: |
  13. Logical AND: &&
  14. Logical OR: ||
  15. Conditional (ternary): ?:
  16. Assignment operators: =, +=, -=, etc.
  17. Comma operator: ,

Associativity:

  • Left-to-right: Most binary operators (like +, -, *, /)
  • Right-to-left: Unary operators (like !, ~, ++), assignment operators, conditional operator

Example of Precedence:

int result = 5 + 3 * 2;  // 5 + (3 * 2) = 11 (not 16)

To override precedence, use parentheses:

int result = (5 + 3) * 2;  // (5 + 3) * 2 = 16

8. Overloading Operators

In C++, you can define custom behavior for operators when used with user-defined types through operator overloading.

class Complex {
private:
    double real, imag;
    
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}
    
    // Overload + operator for adding two Complex objects
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }
    
    // Overload << for output stream
    friend ostream& operator<<(ostream& os, const Complex& c) {
        os << c.real;
        if (c.imag >= 0)
            os << "+" << c.imag << "i";
        else
            os << c.imag << "i";
        return os;
    }
};

// Usage
Complex a(1, 2);
Complex b(3, 4);
Complex c = a + b;  // c becomes 4+6i
cout << c << endl;  // Outputs: 4+6i

Best Practices

  1. Use parentheses for clarity: Even when not strictly necessary, parentheses can make your code more readable.

    if ((a > b) && (c < d)) {
        // More readable than: if (a > b && c < d)
    }
  2. Be careful with side effects: Increment/decrement operators and assignment have side effects that can lead to unexpected behavior.

    int i = 5;
    int j = i++ + ++i;  // Undefined behavior in C++
  3. Use compound assignment when appropriate: a += b is generally preferable to a = a + b.

  4. Be mindful of integer division: Remember that dividing two integers truncates the result.

    double result = static_cast<double>(a) / b;  // To get floating-point division
  5. Understand short-circuit evaluation: Use it to your advantage for efficiency and to avoid potential errors.

    if (ptr != nullptr && ptr->isValid()) {
        // Safe, because if ptr is nullptr, ptr->isValid() won't be called
    }

Understanding operators in C++ is crucial for writing effective and efficient code. By mastering these operators and their behaviors, you’ll be able to express complex logic in a concise and clear manner.