C++ Variables & Data Types: Complete Guide to int, double, char, bool & More
What Are Variables?
A variable is a named piece of memory that holds a value of a specific type. When you write int age = 25;, you are telling the compiler: allocate enough memory to hold an integer, name that memory location age, and store the value 25 in it. Unlike Python, where a variable is just a name pointing to an object, a C++ variable is the storage itself — a fixed number of bytes at a known address.
Every variable in C++ has three properties: a name (the identifier you use in code), a type (which determines how many bytes it occupies and how the bits are interpreted), and a value (the data currently stored). The type is set at compile time and never changes — C++ is statically typed.
Declaring and Initializing Variables
C++ gives you multiple ways to create variables. Modern C++ (C++11 and later) prefers brace initialization because it prevents accidental narrowing conversions:
#include <iostream>
#include <string>
int main() {
// 1. C-style initialization (works, but old-fashioned)
int a = 10;
double b = 3.14;
// 2. Constructor-style initialization
int c(20);
double d(2.718);
// 3. Brace initialization (PREFERRED in modern C++)
int e{30};
double f{1.618};
// 4. Brace initialization catches narrowing errors
// int g{3.14}; // ERROR: narrowing conversion from double to int
// This is a FEATURE — it protects you from accidental data loss
// 5. auto with brace init
auto h = 42; // int
auto i = 3.14; // double
auto j = 'A'; // char
auto k = true; // bool
std::cout << "a=" << a << " e=" << e << " h=" << h << "\n";
return 0;
}
Rule of thumb: Use int x{value}; for simple types and auto x = value; when the type is obvious from the right-hand side. Avoid leaving variables uninitialized — in C++, reading an uninitialized variable is undefined behavior.
Integer Types — int, short, long, long long
C++ provides several integer types with different sizes. The exact size depends on the platform, but the C++ standard guarantees minimum sizes:
#include <iostream>
#include <climits> // INT_MAX, LONG_MAX, etc.
#include <cstdint> // Fixed-width integers
int main() {
// Standard integer types
short s = 32767; // At least 16 bits
int i = 2147483647; // At least 16 bits (usually 32)
long l = 2147483647L; // At least 32 bits
long long ll = 9223372036854775807LL; // At least 64 bits
// Unsigned variants (no negative numbers, double positive range)
unsigned int ui = 4294967295U;
unsigned long long ull = 18446744073709551615ULL;
std::cout << "short: " << s << " (max: " << SHRT_MAX << ")\n";
std::cout << "int: " << i << " (max: " << INT_MAX << ")\n";
std::cout << "long long: " << ll << "\n";
// Fixed-width integers (RECOMMENDED for portability)
int8_t i8 = 127; // Exactly 8 bits
int16_t i16 = 32767; // Exactly 16 bits
int32_t i32 = 2147483647; // Exactly 32 bits
int64_t i64 = 9223372036854775807LL; // Exactly 64 bits
uint8_t u8 = 255; // Unsigned 8 bits
uint32_t u32 = 4294967295U; // Unsigned 32 bits
std::cout << "int32_t: " << i32 << "\n";
std::cout << "uint8_t: " << static_cast<int>(u8) << "\n"; // cast to print as number
return 0;
}
Use int for most things. Use int64_t when you need guaranteed 64-bit values. Use size_t (from <cstddef>) for array indices and sizes — it is an unsigned type sized to fit any object in memory.
Floating-Point Types — float, double, long double
#include <iostream>
#include <iomanip> // For std::setprecision
#include <cmath> // For math functions
#include <cfloat> // FLT_MAX, DBL_MAX
int main() {
float f = 3.14159f; // 32 bits, ~7 decimal digits precision
double d = 3.141592653589793; // 64 bits, ~15 decimal digits precision
long double ld = 3.14159265358979323846L; // 80+ bits (platform-dependent)
std::cout << std::setprecision(20);
std::cout << "float: " << f << "\n";
std::cout << "double: " << d << "\n";
std::cout << "long double: " << ld << "\n";
// Floating-point gotcha: precision loss
float a = 0.1f + 0.2f;
std::cout << "0.1f + 0.2f = " << a << "\n";
// Output: 0.30000001192092895508 (NOT exactly 0.3!)
// Never compare floats with ==
if (std::abs(a - 0.3f) < 1e-6f) {
std::cout << "Approximately equal (correct way)\n";
}
// Special values
double inf = 1.0 / 0.0; // Infinity
double nan = 0.0 / 0.0; // NaN (Not a Number)
std::cout << "inf: " << inf << " nan: " << nan << "\n";
std::cout << "isnan: " << std::isnan(nan) << "\n";
return 0;
}
Use double by default. It has enough precision for virtually everything and is the default floating-point type in C++. Only use float when memory is tight (GPU programming, large arrays) or when an API specifically requires it.
Character and Boolean Types
#include <iostream>
int main() {
// char — stores a single character (1 byte, typically ASCII)
char letter = 'A';
char digit = '7';
char newline = '\n';
std::cout << letter << " has ASCII value: " << static_cast<int>(letter) << "\n";
// Output: A has ASCII value: 65
// char arithmetic works because chars are just small integers
char next = letter + 1; // 'B'
char lower = letter + 32; // 'a' (ASCII trick: uppercase + 32 = lowercase)
std::cout << next << " " << lower << "\n";
// bool — true or false (1 byte, but only uses 1 bit of information)
bool isReady = true;
bool isEmpty = false;
std::cout << "isReady: " << isReady << "\n"; // Prints 1
std::cout << std::boolalpha << "isReady: " << isReady << "\n"; // Prints true
// bool converts to int: true=1, false=0
int count = isReady + isEmpty + true + false;
std::cout << "count: " << count << "\n"; // 2
// Any nonzero value is true, zero is false
if (42) std::cout << "42 is truthy\n";
if (!0) std::cout << "0 is falsy\n";
return 0;
}
Strings in C++ — std::string
#include <iostream>
#include <string>
int main() {
// std::string — dynamic, safe, easy to use
std::string greeting = "Hello";
std::string name = "World";
// Concatenation with +
std::string message = greeting + ", " + name + "!";
std::cout << message << "\n";
// Length
std::cout << "Length: " << message.length() << "\n"; // or .size()
// Accessing characters
std::cout << "First char: " << message[0] << "\n"; // H
std::cout << "Safe access: " << message.at(0) << "\n"; // H (bounds-checked)
// Substring
std::string sub = message.substr(0, 5); // "Hello"
// Find
size_t pos = message.find("World");
if (pos != std::string::npos) {
std::cout << "Found 'World' at position " << pos << "\n";
}
// Comparison (works naturally with ==, !=, <, >)
if (greeting == "Hello") {
std::cout << "Strings compare correctly!\n";
}
// Iteration
for (char c : message) {
std::cout << c << " ";
}
std::cout << "\n";
return 0;
}
Always use std::string instead of C-style char* strings. It manages memory automatically, prevents buffer overflows, and supports all the operations you would expect. For the C-style string functions, see the C string functions lesson — but in C++, you rarely need them.
Type Deduction with auto
#include <iostream>
#include <vector>
#include <string>
int main() {
// auto deduces the type from the initializer
auto x = 42; // int
auto pi = 3.14; // double
auto ch = 'Z'; // char
auto flag = true; // bool
auto msg = std::string("Hello"); // std::string
// auto shines with complex types
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
// Without auto — verbose
for (std::vector<std::string>::iterator it = names.begin(); it != names.end(); ++it) {
std::cout << *it << " ";
}
std::cout << "\n";
// With auto — clean
for (auto it = names.begin(); it != names.end(); ++it) {
std::cout << *it << " ";
}
std::cout << "\n";
// Even cleaner with range-based for
for (const auto& name : names) {
std::cout << name << " ";
}
std::cout << "\n";
return 0;
}
Use auto when the type is obvious from context or when the full type name is verbose. Do not use auto when it would make the code harder to understand — auto x = getData(); is unclear because you cannot tell what type x is.
const and constexpr — Immutable Values
#include <iostream>
int main() {
// const — value cannot be changed after initialization
const int MAX_USERS = 1000;
const double PI = 3.14159265358979;
// MAX_USERS = 2000; // ERROR: cannot assign to const variable
// constexpr — value computed at COMPILE TIME (stronger than const)
constexpr int BUFFER_SIZE = 1024;
constexpr double TAU = 2.0 * 3.14159265358979;
// constexpr functions
// The result is computed during compilation, not at runtime
constexpr int square(int x) { return x * x; }
constexpr int area = square(5); // Computed at compile time = 25
// Arrays need compile-time sizes — constexpr guarantees this
int buffer[BUFFER_SIZE]; // OK: BUFFER_SIZE is constexpr
std::cout << "MAX_USERS: " << MAX_USERS << "\n";
std::cout << "Area: " << area << "\n";
std::cout << "TAU: " << TAU << "\n";
return 0;
}
Rule: If a value should never change, make it const. If it can be computed at compile time, make it constexpr. This makes your intent clear, prevents bugs, and lets the compiler optimize aggressively.
sizeof and Type Sizes
#include <iostream>
int main() {
std::cout << "bool: " << sizeof(bool) << " byte(s)\n";
std::cout << "char: " << sizeof(char) << " byte(s)\n";
std::cout << "short: " << sizeof(short) << " byte(s)\n";
std::cout << "int: " << sizeof(int) << " byte(s)\n";
std::cout << "long: " << sizeof(long) << " byte(s)\n";
std::cout << "long long: " << sizeof(long long) << " byte(s)\n";
std::cout << "float: " << sizeof(float) << " byte(s)\n";
std::cout << "double: " << sizeof(double) << " byte(s)\n";
std::cout << "long double: " << sizeof(long double) << " byte(s)\n";
std::cout << "pointer: " << sizeof(int*) << " byte(s)\n";
// sizeof works on variables too
int arr[10];
std::cout << "arr size: " << sizeof(arr) << " bytes ("
<< sizeof(arr) / sizeof(arr[0]) << " elements)\n";
return 0;
}
// Typical output on 64-bit system:
// bool: 1 byte(s)
// char: 1 byte(s)
// short: 2 byte(s)
// int: 4 byte(s)
// long: 8 byte(s) (4 on Windows!)
// long long: 8 byte(s)
// float: 4 byte(s)
// double: 8 byte(s)
// pointer: 8 byte(s)
Integer Overflow and Undefined Behavior
#include <iostream>
#include <climits>
int main() {
// Signed overflow is UNDEFINED BEHAVIOR — the compiler can do anything
int max_int = INT_MAX; // 2147483647
std::cout << "INT_MAX: " << max_int << "\n";
// int overflow = max_int + 1; // UB! Don't do this.
// Unsigned overflow is DEFINED — it wraps around (modular arithmetic)
unsigned int u = 0;
u = u - 1; // Wraps to UINT_MAX
std::cout << "0u - 1 = " << u << "\n"; // 4294967295
// Detecting overflow before it happens
int a = 2000000000;
int b = 1000000000;
if (a > INT_MAX - b) {
std::cout << "Addition would overflow!\n";
} else {
std::cout << "Safe: " << a + b << "\n";
}
return 0;
}
Type Conversion and Casting
#include <iostream>
int main() {
// Implicit conversion (automatic, sometimes dangerous)
int i = 42;
double d = i; // int → double (safe, no data loss)
std::cout << "int to double: " << d << "\n";
double pi = 3.14159;
int truncated = pi; // double → int (DATA LOSS: decimal part removed)
std::cout << "double to int: " << truncated << "\n"; // 3
// Explicit casting (modern C++ style)
double result = static_cast<double>(7) / 2; // 3.5 (not 3!)
std::cout << "7/2 = " << result << "\n";
// Without cast: integer division
int wrong = 7 / 2; // 3 (truncated!)
std::cout << "7/2 (int) = " << wrong << "\n";
// char to int (ASCII value)
char ch = 'A';
int ascii = static_cast<int>(ch);
std::cout << "'" << ch << "' = " << ascii << "\n"; // 65
return 0;
}
Always use static_cast<T>(value) for explicit conversions in C++. Avoid C-style casts like (int)3.14 — they bypass type safety checks and are harder to search for in code.
Naming Conventions and Best Practices
// Good variable names — descriptive, readable
int userCount = 0;
double accountBalance = 1500.75;
std::string firstName = "Chirag";
bool isAuthenticated = false;
const int MAX_RETRIES = 3;
// Bad variable names — unclear, too short
int n = 0; // What does n mean?
double x = 1500.75; // What is x?
std::string s; // Useless name
bool flag; // What flag?
// Common C++ conventions:
// camelCase for variables and functions: userName, getTotal()
// PascalCase for classes: HttpClient, DatabaseConnection
// UPPER_SNAKE_CASE for constants: MAX_BUFFER_SIZE, PI
// snake_case is also common (STL uses it): my_variable, file_path
Practice Exercises
// Exercise 1: Declare variables to store your name, age, height (in meters),
// and whether you are a student. Print them all.
// Exercise 2: Write a program that swaps two integers without using a third variable.
// Hint: Use arithmetic or XOR.
// Exercise 3: Print the size in bytes of every fundamental type using sizeof.
// Exercise 4: Demonstrate integer overflow with unsigned int.
// Start from 0, subtract 1, and print the result.
// Exercise 5: Write a temperature converter:
// - Ask for a temperature in Celsius
// - Convert to Fahrenheit (F = C * 9.0/5.0 + 32)
// - Convert to Kelvin (K = C + 273.15)
// - Print all three values with 2 decimal places (use std::fixed and std::setprecision)
Summary
C++ variables are typed, fixed-size regions of memory. You learned the fundamental types — int, double, char, bool, and std::string — along with their sizes, ranges, and gotchas. You know how to use auto for type deduction, const and constexpr for immutability, static_cast for safe conversions, and brace initialization to prevent narrowing bugs. In the next lesson, we cover input and output — reading from the keyboard and formatting output to the terminal.