C++ - Variables & Constants

Overview

Estimated time: 40–60 minutes

Understand initialization styles, const and constexpr, references vs pointers, and const-correctness—the building blocks for robust C++ code.

Learning Objectives

  • Use direct, copy, and list initialization appropriately.
  • Declare constants with const and constexpr; know when to use each.
  • Distinguish references and pointers; apply const-correctness to both.

Prerequisites

Initialization styles

#include <iostream>
int main(){
  int a = 42;     // copy init
  int b(42);      // direct init
  int c{42};      // list init (guards against narrowing)
  double d{3.14}; // list init
  std::cout << a+b+c+d << "\n";
}

Expected Output (example): 129.14

const and constexpr

#include <iostream>
constexpr int square(int x){ return x*x; }
int main(){
  const int N = 10;          // runtime constant (cannot modify)
  constexpr int M = square(4); // compile-time constant if args are constexpr
  std::cout << N << " " << M << "\n";
}

Expected Output: 10 16

References vs pointers and const-correctness

#include <iostream>
int main(){
  int x = 5;
  int& ref = x;        // reference (alias), must bind at init, non-null
  int* ptr = &x;       // pointer, can be null, can re-point
  const int cx = 7;    // const object
  const int& cref = x; // reference to const (doesn't allow modification via cref)
  std::cout << ref << " " << *ptr << " " << cx << "\n";
}

Expected Output: 5 5 7

Pointer constness and nullptr

#include <iostream>
int main(){
  int v = 10;
  const int* p1 = &v;   // pointer to const int (can't modify *p1)
  int* const p2 = &v;   // const pointer to int (can't change p2, can modify *p2)
  int const* const p3 = &v; // const pointer to const int
  int* pn = nullptr;    // null pointer
  std::cout << (pn == nullptr ? "null" : "nonnull") << "\n";
}

Scope, lifetime, and dangling references

#include <string>
#include <iostream>
const std::string& bad(){
  std::string s = "temp"; // destroyed at function end
  return s;                // dangling reference! undefined behavior if used
}
int main(){
  // const std::string& r = bad(); // don't do this
  std::string safe = []{ std::string s = "ok"; return s; }(); // returns by value
  std::cout << safe << "\n";
}

Common Pitfalls

  • Narrowing conversions with list initialization: int x{3.14} is an error, which is good—avoid loss of data.
  • Forgetting const on member functions that don’t modify state; this breaks usage with const objects.

Checks for Understanding

  1. When should you prefer list initialization?
  2. What’s the difference between const and constexpr?
Show answers
  1. When you want to avoid narrowing and write uniform initialization.
  2. const is runtime immutability; constexpr requires compile-time evaluability.

Exercises

  1. Write a constexpr function cube and use it to initialize a constexpr variable.
  2. Create a function that takes a const std::string& and prints it; try passing a temporary and a named variable.