Lesson 18 min read

Variables & Data Types

C++ is C's younger sibling who learned from C's mistakes β€” same DNA, better manners

Meet C++ β€” C's Evolved Sibling

If C is the rugged pioneer who built the roads, C++ is the sibling who paved them, added lane markings, and installed guardrails. They share the same DNA β€” pointers, manual memory, compiled speed β€” but C++ learned from C's rough edges and added better types, safer defaults, and modern conveniences.

Nowhere is this more obvious than in how you declare variables. C makes you spell everything out. C++ says: "I can figure most of that out for you."

The Fundamental Types

C++ inherits all of C's numeric types and adds a few quality-of-life upgrades:

  • int β€” whole numbers: 42, -7, 0. Typically 32 bits.
  • double β€” decimal numbers with high precision: 3.14159. This is your default for floating-point math.
  • float β€” less precise decimals, uses half the memory of double. Use when memory matters (like graphics).
  • char β€” a single character: 'A', '7', '\n'. Under the hood, it's just a small integer.
  • bool β€” true or false. In C, you needed #include <stdbool.h> β€” in C++, bool is a native, first-class type. No header needed!
  • std::string β€” a real string type! No more juggling char[] arrays and strlen(). It grows, shrinks, and manages its own memory.

Basic Declarations β€” C++ Style

#include <iostream>
#include <string>
using namespace std;
int main() {
int age = 25;
double pi = 3.14159;
float gravity = 9.81f; // f suffix for float literals
char grade = 'A';
bool isPassing = true; // native bool β€” no header needed!
string name = "Alice"; // real string type, not char[]
cout << name << " is " << age << " years old" << endl;
cout << "Pi: " << pi << endl;
cout << "Passing: " << boolalpha << isPassing << endl;
return 0;
}
Output
Alice is 25 years old
Pi: 3.14159
Passing: true

The auto Keyword β€” Let the Compiler Do the Work

Imagine you're at a restaurant and the waiter already knows your order because you come every day. That's auto β€” you skip the type name, and the compiler deduces it from whatever you assign.

auto was introduced in C++11 and quickly became one of the most-used features. It's especially handy when the type name is long and ugly (like iterators), but it works with simple types too.

auto in Action

#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main() {
auto count = 10; // int
auto price = 9.99; // double
auto letter = 'Z'; // char
auto isValid = false; // bool
auto greeting = string("Hello"); // std::string
// auto really shines with complex types:
vector<int> nums = {1, 2, 3, 4, 5};
auto it = nums.begin(); // vector<int>::iterator β€” saved you 25 characters!
cout << "count is " << count << " (type deduced as int)" << endl;
cout << "price is " << price << " (type deduced as double)" << endl;
cout << "*it is " << *it << endl;
return 0;
}
Output
count is 10 (type deduced as int)
price is 9.99 (type deduced as double)
*it is 1
Note: auto doesn't mean "untyped" β€” the compiler figures out the exact type at compile time. It's still strongly typed! Once deduced, the type is locked in. auto x = 5; makes x an int, and you can't later assign a string to it. Think of auto as shorthand, not as JavaScript's var.

const vs constexpr β€” Two Flavors of "Don't Touch"

C has const, and C++ keeps it β€” but adds constexpr for compile-time constants. Think of it this way:

  • const β€” "This value won't change at runtime." The value might be computed when the program runs, but once set, it's locked.
  • constexpr β€” "This value is known at compile time." The compiler computes it before the program even runs, which means it can be used in places that require compile-time constants (like array sizes).

const vs constexpr

#include <iostream>
using namespace std;
int main() {
const int maxRetries = 3; // runtime constant β€” value set once
constexpr double pi = 3.14159265; // compile-time constant β€” baked into the binary
constexpr int arraySize = 10;
int scores[arraySize]; // βœ… constexpr works as array size
// int other[maxRetries]; // βœ… also works here because value is known
// The real difference shows with functions:
// constexpr values MUST be computable at compile time
// const values just can't be reassigned
const int userInput = 42; // βœ… const β€” value assigned at runtime is fine
// constexpr int x = userInput; // ❌ error β€” userInput isn't a compile-time constant
cout << "Max retries: " << maxRetries << endl;
cout << "Pi: " << pi << endl;
cout << "Array size: " << arraySize << endl;
return 0;
}
Output
Max retries: 3
Pi: 3.14159265
Array size: 10

Uniform Initialization with {}

C++ has a frustrating history of too many ways to initialize things. C++11 introduced uniform initialization using curly braces {} to bring order to the chaos. The key benefit? It prevents narrowing conversions β€” silent data loss that C happily allows.

Think of {} as the strict parent: it won't let you silently stuff a double into an int and lose the decimals.

Uniform Initialization with {}

#include <iostream>
#include <string>
using namespace std;
int main() {
// Old C-style initialization
int a = 42;
double b = 3.14;
// Uniform initialization (C++11)
int c{42};
double d{3.14};
string name{"Bob"};
bool flag{true};
// The big win β€” prevents narrowing:
// int narrow{3.14}; // ❌ compiler ERROR β€” would lose .14
int oldWay = 3.14; // ⚠️ compiles fine, silently truncates to 3!
cout << "c = " << c << endl;
cout << "d = " << d << endl;
cout << "name = " << name << endl;
cout << "oldWay = " << oldWay << " (silently lost .14!)" << endl;
return 0;
}
Output
c = 42
d = 3.14
name = Bob
oldWay = 3 (silently lost .14!)

C++ string vs C's char[]

In C, strings are just arrays of characters ending with a null byte '\0'. You had to manually manage lengths, allocate buffers, and pray you didn't overrun them. C++'s std::string handles all of that for you β€” it grows automatically, knows its own length, and supports comparison with == instead of strcmp().

C++ string β€” A Real String Type

#include <iostream>
#include <string>
using namespace std;
int main() {
string first = "Hello";
string second = "World";
// Concatenation β€” just use +
string greeting = first + ", " + second + "!";
cout << greeting << endl;
// Length β€” no strlen() needed
cout << "Length: " << greeting.length() << endl;
// Comparison β€” just use ==
if (first == "Hello") {
cout << "Strings match!" << endl;
}
// Substring, find, and more built-in
cout << "Substring: " << greeting.substr(0, 5) << endl;
cout << "Found 'World' at index: " << greeting.find("World") << endl;
return 0;
}
Output
Hello, World!
Length: 13
Strings match!
Substring: Hello
Found 'World' at index: 7
Challenge

Quick check

What does auto x = 3.14; deduce the type of x as?
← Why Learn C++?Operators & Expressions β†’