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.
| Operator | Name | Description | Example |
|---|---|---|---|
+ | Addition | Adds two operands | a + b |
- | Subtraction | Subtracts second operand from the first | a - b |
* | Multiplication | Multiplies two operands | a * b |
/ | Division | Divides first operand by second | a / b |
% | Modulus | Remainder after division | a % b |
++ | Increment | Increases value by 1 | ++a or a++ |
-- | Decrement | Decreases 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:
-
Integer Division: When dividing two integers, the result is truncated (decimal part is discarded).
int result = 5 / 2; // result = 2 (not 2.5) -
Modulus Operation: Only works with integer operands.
int remainder = 10 % 3; // remainder = 1 // double mod = 10.5 % 3; // Error: Invalid operands -
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 - Pre-increment (
2. Relational Operators
Relational operators compare two values and return a boolean result (true or false).
| Operator | Name | Description | Example |
|---|---|---|---|
== | Equal to | Returns true if operands are equal | a == b |
!= | Not equal to | Returns true if operands are not equal | a != b |
> | Greater than | Returns true if left operand is greater | a > b |
< | Less than | Returns true if left operand is smaller | a < b |
>= | Greater than or equal to | Returns true if left operand is greater than or equal to right | a >= b |
<= | Less than or equal to | Returns true if left operand is smaller than or equal to right | a <= 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:
-
Boolean Results: Relational operators return
true(1) orfalse(0). -
Operator Precedence: Use parentheses to clarify complex conditions.
if ((a > b) && (c < d)) { // Code here } -
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.
| Operator | Name | Description | Example |
|---|---|---|---|
&& | Logical AND | Returns true if both operands are true | a && b |
|| | Logical OR | Returns true if at least one operand is true | a || b |
! | Logical NOT | Returns 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:
| A | B | A && B |
|---|---|---|
| false | false | false |
| false | true | false |
| true | false | false |
| true | true | true |
OR (||) Operator:
| A | B | A || B |
|---|---|---|
| false | false | false |
| false | true | true |
| true | false | true |
| true | true | true |
NOT (!) Operator:
| A | !A |
|---|---|
| false | true |
| true | false |
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.
| Operator | Name | Description | Example |
|---|---|---|---|
& | Bitwise AND | Performs AND operation on corresponding bits | a & b |
| | Bitwise OR | Performs OR operation on corresponding bits | a | b |
^ | Bitwise XOR | Performs XOR operation on corresponding bits | a ^ b |
~ | Bitwise NOT | Inverts all bits | ~a |
<< | Left shift | Shifts bits to the left | a << n |
>> | Right shift | Shifts bits to the right | a >> 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:
-
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; } -
Power of Two:
// Left shift to compute powers of 2 int powerOfTwo = 1 << n; // Computes 2^n -
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.
| Operator | Description | Example |
|---|---|---|
= | Simple assignment | a = b |
+= | Add and assign | a += b (same as a = a + b) |
-= | Subtract and assign | a -= b (same as a = a - b) |
*= | Multiply and assign | a *= b (same as a = a * b) |
/= | Divide and assign | a /= b (same as a = a / b) |
%= | Modulus and assign | a %= b (same as a = a % b) |
<<= | Left shift and assign | a <<= b (same as a = a << b) |
>>= | Right shift and assign | a >>= b (same as a = a >> b) |
&= | Bitwise AND and assign | a &= b (same as a = a & b) |
|= | Bitwise OR and assign | a |= b (same as a = a | b) |
^= | Bitwise XOR and assign | a ^= 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:
-
Static Cast (
static_cast<>): For “well-behaved” conversionsdouble d = 3.14; int i = static_cast<int>(d); // i = 3 -
Dynamic Cast (
dynamic_cast<>): For safe downcasting of polymorphic typesBase* basePtr = new Derived(); Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); -
Const Cast (
const_cast<>): To add or remove const qualificationconst int c = 10; int* ptr = const_cast<int*>(&c); -
Reinterpret Cast (
reinterpret_cast<>): For low-level reinterpreting of bit patternsint* 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):
- Scope resolution:
:: - Postfix increment/decrement:
a++,a-- - Prefix increment/decrement, unary:
++a,--a,+a,-a,!,~,(type),*,&,sizeof - Member access:
.,-> - Multiplication/division/modulus:
*,/,% - Addition/subtraction:
+,- - Shift operators:
<<,>> - Relational operators:
<,<=,>,>= - Equality operators:
==,!= - Bitwise AND:
& - Bitwise XOR:
^ - Bitwise OR:
| - Logical AND:
&& - Logical OR:
|| - Conditional (ternary):
?: - Assignment operators:
=,+=,-=, etc. - 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
-
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) } -
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++ -
Use compound assignment when appropriate:
a += bis generally preferable toa = a + b. -
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 -
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.