Lesson 28 min read

Operators & Expressions

All of C's operators plus a few new tricks up its sleeve

C's Toolbox, Upgraded

If C gave you a solid toolbox β€” hammers, screwdrivers, wrenches β€” C++ took that same toolbox and added a power drill, a laser level, and a label maker. Every operator you know from C works exactly the same in C++. But C++ introduces a handful of new ones that make the language more expressive.

Let's start with the familiar, then meet the newcomers.

The Familiar Operators

All of C's operators carry over unchanged:

  • Arithmetic: +, -, *, /, % (modulo)
  • Comparison: ==, !=, <, >, <=, >=
  • Logical: && (AND), || (OR), ! (NOT)
  • Bitwise: &, |, ^, ~, <<, >>
  • Assignment: =, +=, -=, *=, /=, %=, &=, |=, ^=
  • Increment/Decrement: ++, -- (prefix and postfix)
  • Ternary: condition ? valueIfTrue : valueIfFalse

These work identically to C. No surprises here.

Familiar Operators in C++

#include <iostream>
using namespace std;
int main() {
int a = 17, b = 5;
cout << "a + b = " << (a + b) << endl; // 22
cout << "a / b = " << (a / b) << endl; // 3 (integer division!)
cout << "a % b = " << (a % b) << endl; // 2 (remainder)
// Comparison returns bool (true/false), not just 0/1 like C
cout << boolalpha;
cout << "a > b = " << (a > b) << endl; // true
cout << "a == b = " << (a == b) << endl; // false
// Ternary
string result = (a > b) ? "a wins" : "b wins";
cout << result << endl;
return 0;
}
Output
a + b  = 22
a / b  = 3
a % b  = 2
a > b  = true
a == b = false
a wins

New Kid: Scope Resolution (::)

The :: operator is C++'s way of saying "I mean this specific one." It resolves ambiguity when names collide β€” like having two people named "Alex" in a room and specifying "Alex from accounting" vs "Alex from engineering."

You'll see :: everywhere: accessing namespace members (std::cout), defining class methods outside the class, and reaching global variables hidden by local ones.

Scope Resolution Operator (::)

#include <iostream>
using namespace std;
int value = 100; // global variable
namespace Math {
double pi = 3.14159;
}
int main() {
int value = 42; // local variable shadows the global one
cout << "Local value: " << value << endl; // 42
cout << "Global value: " << ::value << endl; // 100 β€” :: reaches the global
cout << "Math::pi: " << Math::pi << endl; // 3.14159
// std::cout is also using :: to access cout from the std namespace
// (we skip it because of 'using namespace std;' at the top)
return 0;
}
Output
Local value:  42
Global value: 100
Math::pi:     3.14159

Stream Operators: << and >>

Here's where C++ does something clever. In C, << and >> are bitwise shift operators β€” they shift bits left or right. C++ keeps that behavior for integers, but overloads them for I/O streams.

Think of << as "push data onto the stream" (like putting items on a conveyor belt heading to the screen) and >> as "pull data off the stream" (like picking items off a belt coming from the keyboard).

cout << Chaining β€” Putting Items on the Belt

#include <iostream>
using namespace std;
int main() {
string name = "Charlie";
int age = 30;
double gpa = 3.85;
// << chaining β€” each << pushes one more item onto the output stream
cout << "Name: " << name << ", Age: " << age << ", GPA: " << gpa << endl;
// But << is still bitwise shift for integers:
int x = 1;
cout << "1 << 3 = " << (1 << 3) << endl; // bitwise: 1 shifted left 3 = 8
cout << "16 >> 2 = " << (16 >> 2) << endl; // bitwise: 16 shifted right 2 = 4
return 0;
}
Output
Name: Charlie, Age: 30, GPA: 3.85
1 << 3 = 8
16 >> 2 = 4
Note: << and >> are bitwise shift in C, but in C++ they're overloaded for I/O with streams. Context matters! When used with cout/cin, they're stream operators. When used with integers, they're still bitwise shift. The compiler knows which to use based on the operand types.

new and delete β€” Manual Memory C++ Style

C uses malloc() and free(). C++ introduces new and delete β€” operators (not functions!) that allocate memory and call constructors/destructors. They're type-safe and cleaner, though modern C++ prefers smart pointers over raw new/delete.

new and delete

#include <iostream>
using namespace std;
int main() {
// Allocate a single int on the heap
int* p = new int{42};
cout << "*p = " << *p << endl;
delete p; // free the memory
// Allocate an array on the heap
int* arr = new int[5]{10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
cout << endl;
delete[] arr; // use delete[] for arrays!
return 0;
}
Output
*p = 42
10 20 30 40 50

sizeof and typeid β€” Inspecting Types

sizeof works just like C β€” it gives you the size in bytes. But C++ adds typeid (from <typeinfo>) which lets you inspect the actual type of a variable at runtime. This is especially useful with auto when you're not sure what the compiler deduced.

sizeof and typeid

#include <iostream>
#include <typeinfo>
#include <string>
using namespace std;
int main() {
auto x = 42;
auto y = 3.14;
auto z = 'A';
auto w = true;
auto s = string("hello");
cout << "sizeof(int): " << sizeof(int) << " bytes" << endl;
cout << "sizeof(double): " << sizeof(double) << " bytes" << endl;
cout << "sizeof(char): " << sizeof(char) << " byte" << endl;
cout << "sizeof(bool): " << sizeof(bool) << " byte" << endl;
cout << endl;
// typeid reveals what auto deduced:
cout << "x is: " << typeid(x).name() << endl;
cout << "y is: " << typeid(y).name() << endl;
cout << "z is: " << typeid(z).name() << endl;
cout << "w is: " << typeid(w).name() << endl;
return 0;
}
Output
sizeof(int):    4 bytes
sizeof(double): 8 bytes
sizeof(char):   1 byte
sizeof(bool):   1 byte

x is: i
y is: d
z is: c
w is: b

Note: typeid().name() output is compiler-dependent. GCC gives short codes like i (int), d (double), c (char), b (bool). MSVC gives more readable names. Either way, it tells you what the compiler thinks the type is.

Challenge

Quick check

What does the :: operator do in C++?
← Variables & Data TypesInput & Output β†’