W1. Introduction to C++, Namespaces, Arrays and Vectors, References and Constants, Type Deduction

Author

Eugene Zouev, Munir Makhmutov

Published

January 29, 2026

1. Summary

1.1 Course Organization

The Software Systems Analysis and Design course is structured into several components designed to give you both theoretical knowledge and practical experience with C++ programming.

1.1.1 Course Components

The course consists of three main components:

  • Lectures cover theoretical concepts and language fundamentals. These introduce programming concepts and C++ language features at a conceptual level.
  • Tutorials provide additional examples and detailed explanations of lecture topics. They explore particular aspects and practical applications of the theory.
  • Labs allow hands-on programming practice. You’ll work on exercises and programming assignments to gain practical experience.
1.1.2 Assessment Structure

Your grade is calculated from multiple components:

  • Mid-term Exam: 25% (Moodle Quiz, scheduled for March 10)
  • Final Exam: 30% (Written form)
  • Assignments: 40% (4 assignments evaluated regularly)
  • Lab attendance: 5%
  • Bonus: 5%

The grading scale is: A [90, 100], B [75, 90), C [60, 75), D [0, 60).

1.2 How C++ Programs Work
1.2.1 From Source Code to Executable

Understanding how C++ programs run helps clarify many concepts we’ll encounter. Here’s the journey your code takes:

  1. Source Code: You write C++ code in .cpp files (human-readable text)
  2. Preprocessing: The preprocessor handles directives like #include (includes header files) and #define (defines macros)
  3. Compilation: The compiler translates your C++ code into machine code (binary instructions the CPU can execute)
  4. Linking: The linker combines your compiled code with library code (like the C++ Standard Library) to create an executable
  5. Execution: The operating system loads and runs your executable

Key distinction:

  • Compile time: What happens during steps 2-4 (before the program runs). The compiler checks types, evaluates constant expressions, and generates machine code.
  • Runtime: What happens during step 5 (when the program actually executes). Values from user input, dynamic memory allocation, and program logic happen here.
1.2.2 What is a Compiler?

A compiler is a program that translates source code (human-readable) into machine code (CPU-executable). Common C++ compilers include:

  • GCC (GNU Compiler Collection) - free, widely used
  • Clang - modern, good error messages
  • MSVC (Microsoft Visual C++) - for Windows development

The compiler’s job includes:

  • Checking syntax (is the code grammatically correct?)
  • Type checking (are you using types correctly?)
  • Optimization (making your code run faster)
  • Code generation (creating machine instructions)
1.2.3 C++ Standards

C++ evolves over time. Different versions add new features:

  • C++98: First standardized version
  • C++11: Major update (auto, range-based for loops, nullptr)
  • C++14: Minor improvements
  • C++17: Structured bindings, optional, variant
  • C++20: Concepts, ranges, coroutines, modules
  • C++23 & C++26: Even more features

For this course: We’ll use C++20 standard, which means we have access to all features up to and including C++20.

1.3 C++ Programming Fundamentals
1.3.1 First C++ Program

A basic C++ program consists of several essential components:

#include <iostream>
int main()
{
    std::cout << "Hello world" << std::endl;
    return 0;
}

Let’s break down each part:

  • #include <iostream> is a preprocessor directive that includes the contents of the standard input/output library. This gives us access to entities like cout for console output.
  • int main() is the main function where program execution begins. Every C++ program must have exactly one main function. The operating environment calls this function to start your program.
  • std::cout is the standard output stream for printing to the console. It’s part of the std (standard) namespace.
  • << is the stream insertion operator, overloaded to work with output streams. While normally a shift operator for integers, it has been redefined to handle different types for I/O operations.
  • std::endl represents an end-of-line character and flushes the output buffer.
  • return 0; terminates the main function and returns 0 to the environment, conventionally indicating successful completion.
1.3.2 Memory Model

Every C++ program uses three distinct types of memory, each serving a different purpose:

  1. Program Memory contains the compiled machine code instructions. This memory is read-only; programs cannot modify their own code (self-modifying programs are not allowed in C++).
  2. Heap (Dynamic Memory) stores dynamically allocated objects created at runtime using operators like new. The programmer controls when heap memory is allocated and deallocated. The discipline of using the heap is defined by the program’s dynamic semantics (runtime behavior).
  3. Stack stores local variables and function call information. The stack is automatically managed based on program scope and function calls. The discipline of using the stack is defined by static program structure (determined at compile time).

%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'Helvetica', 'primaryColor': '#e8f4f8', 'primaryTextColor': '#1f2d3d', 'primaryBorderColor': '#355c7d', 'lineColor': '#355c7d', 'secondaryColor': '#d6eef5', 'tertiaryColor': '#fff3cd', 'background': '#ffffff', 'mainBkg': '#e8f4f8', 'secondBkg': '#d6eef5', 'tertiaryBkg': '#fff3cd', 'clusterBkg': '#f9fbfd', 'clusterBorder': '#355c7d', 'edgeLabelBackground': '#ffffff' }}}%%
%%| fig-cap: "Typical process memory layout: code, static storage, heap, and stack"
%%| fig-width: 5.5
%%| fig-height: 4
flowchart TB
    Code["Code / Text Segment<br/>compiled instructions"]
    Static["Static / Global Storage<br/>global and static objects"]
    Heap["Heap<br/>dynamic allocation via new"]
    Stack["Stack<br/>local variables and call frames"]
    Code --> Static --> Heap --> Stack

1.4 The Type System
1.4.1 What is a Type?

A type is a fundamental concept in C++ that defines three key aspects of an entity:

  1. Values: The set of possible values an object of that type can hold
  2. Operations: The set of operations that can be performed on objects of that type
  3. Relationships: How the type relates to other types (conversions, inheritance, etc.)

For example, the type int:

  • Values: Integer numbers within a specific range (typically -2,147,483,648 to 2,147,483,647)
  • Operations: Creation, destruction, copying, moving, arithmetic operators (+, -, *, /), comparison operators (==, <, >), bitwise operations, shifts
  • Relationships: Can be converted to bool, float, double, etc.
1.4.2 C++ Type Hierarchy

C++ types are organized hierarchically:

Fundamental Types:

  • Atomic types: Integers (int, short, long, long long), characters (char, wchar_t, char16_t, char32_t, char8_t), floating-point (float, double, long double), boolean (bool)
  • Pointers: Address variables that store memory locations

User-defined Types:

  • Compound types: Arrays, structures, unions, classes, enumerations
1.4.3 Syntax vs. Semantics

An important distinction in programming languages:

  • Syntax defines the rules for how programs and constructs are structured (the grammar of the language).
  • Semantics defines the meaning of constructs:
    • Static semantics: How programs are compiled and type-checked
    • Dynamic semantics: How programs execute at runtime

Important principle: While syntax is relatively easy to learn, the semantics of C++ is vast and complex. Focus on understanding what code means and how it behaves, not just the syntax rules.

1.5 Namespaces
1.5.1 Motivation and Purpose

In large programs, different parts of code may use the same names for different purposes, leading to name clashes. C++ provides namespaces as a mechanism to group related declarations and avoid naming conflicts.

A namespace is a declarative region that provides scope for identifiers (names of types, functions, variables, etc.) inside it. Namespaces partition the global scope into distinct named scopes.

1.5.2 Namespace Syntax

You define a namespace using the namespace keyword:

namespace Subsystem1
{
    class C1 { ... };
    int a, b;
    void f() { ... }
}

namespace Subsystem2
{
    class C2 { ... };
    int a;  // This is a DIFFERENT 'a' from Subsystem1::a
}
1.5.3 Accessing Namespace Members

To access entities inside a namespace from outside, use qualified naming with the scope resolution operator :::

Format: namespace-name::entity-name

int x = Subsystem1::a;
Subsystem1::f();
1.5.4 The Global Namespace

The entire program exists within an unnamed (global) namespace. To explicitly access global scope, use :: without a namespace name:

int a = 5;  // Global variable

namespace Subsystem
{
    int a = 10;  // Different variable
}

int x = Subsystem::a;  // x = 10
int y = a;             // y = 5 (global a)
int z = ::a;           // z = 5 (explicitly global a)
1.5.5 The Standard Library Namespace

All entities in the C++ standard library are declared within the std namespace. This includes common elements like:

  • std::cout, std::cin (input/output streams)
  • std::vector, std::list, std::map (data structures)
  • std::string (string class)
  • std::endl (end-of-line manipulator)
1.5.6 Using Declarations

To simplify code, you can bring namespace members into the current scope with using declarations:

using namespace std;  // Brings ALL std members into scope
cout << "Hello" << endl;  // No need for std:: prefix

However, this is considered bad practice for large programs because it defeats the purpose of namespaces. A better approach is to selectively import only what you need:

using std::cout;
using std::endl;
cout << "Hello" << endl;  // Only cout and endl are imported
1.5.7 Advanced Namespace Features

Multi-file namespaces: A single namespace can span multiple translation units (source files):

// File 1
namespace Subsystem1 {
    class C1 { ... };
}

// File 2
namespace Subsystem1 {
    class C2 { ... };  // Still part of Subsystem1
}

Nested namespaces: Namespaces can be nested for hierarchical organization:

namespace OurBigSystem
{
    namespace MySubsystem
    {
        int a;
    }
}

int x = OurBigSystem::MySubsystem::a;
1.5.8 Why Namespaces Matter

Consider a real-world scenario: you’re building a large application that uses two different libraries. Library A has a function called print() for printing documents, and Library B has a function called print() for printing debug messages. Without namespaces, these would conflict—the compiler wouldn’t know which print() you mean.

With namespaces:

LibraryA::print(document);  // Prints a document
LibraryB::print("Debug info");  // Prints debug message

This is why the standard library uses std:: for everything—to avoid conflicting with your own code.

1.6 Pointers
1.6.1 What is a Pointer?

Before we discuss arrays, we need to understand pointers, as they’re fundamental to how C++ works with memory and arrays.

A pointer is a variable that stores a memory address. Instead of holding a value like 5 or 3.14, a pointer holds the location where that value is stored in memory.

Analogy: Think of your computer’s memory as a huge apartment building. Each apartment (memory location) has:

  • An address (like “Building A, Floor 3, Apartment 301”)
  • Contents (the data stored there, like a person living in the apartment)

A regular variable gives you direct access to the contents. A pointer gives you the address, and you can use that address to access the contents.

1.6.2 Pointer Syntax

Declaring a pointer:

int* ptr;        // ptr is a pointer to an integer
double* dptr;    // dptr is a pointer to a double
char* cptr;      // cptr is a pointer to a character

The * in the declaration means “pointer to”. Read int* ptr as “ptr is a pointer to int”.

1.6.3 Pointer Operators

Two critical operators work with pointers:

The address-of operator (&): Gets the memory address of a variable.

int x = 42;
int* ptr = &x;   // ptr now holds the address of x

Think of &x as “give me the address where x lives”.

The dereference operator (*): Accesses the value at the address stored in a pointer.

int x = 42;
int* ptr = &x;   // ptr points to x
int value = *ptr;  // value = 42 (get the value at the address)
*ptr = 100;        // Changes x to 100 (modify the value at the address)

Think of *ptr as “go to the address in ptr and give me what’s there”.

%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'Helvetica', 'primaryColor': '#e8f4f8', 'primaryTextColor': '#1f2d3d', 'primaryBorderColor': '#355c7d', 'lineColor': '#355c7d', 'secondaryColor': '#d6eef5', 'tertiaryColor': '#fff3cd', 'background': '#ffffff', 'mainBkg': '#e8f4f8', 'secondBkg': '#d6eef5', 'tertiaryBkg': '#fff3cd', 'clusterBkg': '#f9fbfd', 'clusterBorder': '#355c7d', 'edgeLabelBackground': '#ffffff' }}}%%
%%| fig-cap: "Pointer basics: a pointer stores an address, and dereferencing reaches the value at that address"
%%| fig-width: 6
%%| fig-height: 2.8
flowchart LR
    P["p : int*<br/>value = &a"]
    A["a : int<br/>value = 5"]
    P -- "stores address of" --> A
    D["*p == 5"]
    A --> D

1.6.4 Pointers in Action

Complete example:

int a = 5;
int* p = &a;     // p points to a

cout << a;       // Prints: 5 (direct access)
cout << p;       // Prints: address of a (e.g., 0x7fff5fbff5ac)
cout << *p;      // Prints: 5 (indirect access through pointer)

*p = 10;         // Changes a to 10
cout << a;       // Prints: 10

Key insight: When you modify *p, you’re modifying a, because p holds the address of a.

1.6.5 Why Pointers Matter

Pointers are essential for:

  1. Dynamic memory allocation: Creating objects whose size or lifetime isn’t known at compile time
  2. Efficient function parameters: Passing large objects by address instead of copying them
  3. Data structures: Building linked lists, trees, graphs, etc.
  4. Low-level programming: Interfacing with hardware, operating systems
1.6.6 Null Pointers

A pointer can be set to nullptr (or NULL in older code), indicating it doesn’t point to any valid memory location:

int* ptr = nullptr;  // ptr doesn't point anywhere

if (ptr == nullptr) {
    cout << "Pointer is null" << endl;
}

// *ptr = 5;  // DANGEROUS! Would crash the program

Always initialize pointers to either a valid address or nullptr. An uninitialized pointer contains garbage and can cause serious bugs.

1.7 Arrays vs. Vectors
1.7.1 C-Style Arrays

Now that we understand pointers, we can properly understand arrays. Arrays are a fundamental, low-level feature inherited from C. An array declaration specifies a fixed-size collection of elements of the same type.

Syntax: T arrayName[size];

  • T is the type of array elements
  • size must be a constant expression (known at compile time)
int Array[10];           // Array of 10 integers
const int x = 7;
void* Ptrs[x*2+5];      // Array of 19 void pointers
int Matrix[10][100];     // 2D array

Key characteristics:

  • Fixed size: Cannot change after declaration
  • No bounds checking: Accessing out-of-bounds elements causes undefined behavior
  • Decay to pointers: Array names are treated as constant pointers to the first element
1.7.2 Arrays and Pointers

Here’s where pointers and arrays connect: an array name is essentially a constant pointer to its first element. This is a crucial concept in C++.

int Array[10];
// Array is equivalent to: const int* Array

Accessing elements:

  • Array[0] is equivalent to *Array
  • Array[i] is equivalent to *(Array + i) (pointer arithmetic)

Why this matters: Understanding that arrays decay to pointers explains many array behaviors:

  • When you pass an array to a function, you’re actually passing a pointer
  • Arrays don’t “know” their own size (you must track it separately)
  • Array bounds checking is your responsibility (no automatic safety)

This tight relationship between arrays and pointers is powerful but also a source of many bugs—which is why C++ offers a better alternative: vectors.

1.7.3 C++ Vectors

Vectors are part of the C++ Standard Library and provide a much safer and more flexible alternative to arrays. They are dynamically-sized arrays with rich functionality.

Key differences from arrays:

Feature Arrays Vectors
Declaration int A[20]; vector<int> A;
Size Fixed at compile time Dynamic, changes at runtime
Bounds checking None Available via .at() method
Functionality Minimal (only indexing) Rich (many member functions)
Performance Fastest Nearly as fast as arrays
Safety Unsafe (easy to cause bugs) Much safer
1.7.4 Vector Operations

Declaration and initialization:

vector<int> v1;                    // Empty vector
vector<int> v2 = { 1, 2, 3 };     // Initialized with values
vector<int> v3(10);                // 10 elements, default-initialized
vector<int> v4(10, 7);             // 10 elements, all with value 7

Adding elements:

vector<int> v;
v.push_back(10);  // Add 10 to the end
v.push_back(20);  // Add 20 to the end
v.push_back(30);  // Now v = {10, 20, 30}

Accessing elements:

vector<int> v = { 1, 2, 3 };
int x = v[0];     // Access first element (no bounds checking)
v[2] = 777;       // Modify third element
int y = v.at(1);  // Access with bounds checking (throws exception if out of range)

Getting size:

cout << v.size() << endl;  // Returns number of elements

Other useful operations: v.clear(), v.empty(), v.front(), v.back(), v.erase(), v.insert(), and many more.

1.7.5 Range-Based For Loops

C++11 introduced a cleaner syntax for iterating over containers like vectors:

Basic iteration (read-only):

vector<int> v = { 1, 2, 3, 4 };
for (int elem : v) {
    cout << elem << " ";  // elem is a copy of each element
}

Iteration with modification (requires reference):

for (int& elem : v) {
    elem = elem * 10;  // Modifies actual vector elements
}

Type deduction with auto:

for (auto& elem : v) {
    elem = elem * 10;  // Compiler deduces type automatically
}

The range-based for loop also works with C-style arrays and initializer lists:

int arr[] = {1, 2, 3};
for (int n : arr) {
    cout << n << " ";
}

for (int n : {0, 1, 2, 3}) {  // Initializer list
    cout << n << " ";
}
1.8 References
1.8.1 What is a Reference?

After understanding pointers, references are easier to grasp. A reference is an alias (synonym) for an existing object. Think of it as an alternative name for the same variable.

Once initialized, a reference always refers to the same object, and any operation on the reference is actually performed on the referenced object.

Syntax: T& refName = object;

int x = 5;
int& r = x;  // r is now a reference to x

r = 7;       // Same as: x = 7
x = 777;
int v = r;   // v = 777 (reading x through r)

Key insight: r is not a copy of x—it IS x under a different name. They share the same memory location.

1.8.2 References vs. Pointers

While both references and pointers deal with indirection, they have important differences:

Feature Pointers References
Declaration int* p; int& r = x;
Nature Objects (occupy memory) Not objects (just aliases)
Values Store addresses Have no independent value
Initialization Can be null or uninitialized Must be initialized; no null references
Reassignment Can point to different objects Always refers to same object
Operators Explicit & (address-of) and * (dereference) No special operators needed
Syntax *p = 5; to modify r = 5;<> to modify
1.8.3 Why Use References?

The primary motivation is efficiency: passing large objects to functions by value causes expensive copying. References allow functions to work with the original object without copying.

void f(Huge a) { ... }      // Expensive: copies entire Huge object
void f(Huge& a) { ... }     // Efficient: passes reference (just an address)

Analogy: Imagine lending someone a book. Passing by value is like photocopying the entire book (expensive). Passing by reference is like giving them the book itself (efficient, and they can annotate it).

1.8.4 Reference Rules and Restrictions

Since references are not objects:

  • No pointers to references
  • No arrays of references
  • No references to references
  • References must be initialized when declared

However, you CAN have:

  • References to pointers: int* p; int*& rp = p;
  • Pointers to references are not possible, but you can have a reference to a pointer
1.9 Constant Types
1.9.1 The const Qualifier

The const qualifier creates constant types - types whose objects cannot be modified after initialization.

const T represents the set of immutable objects of type T. Note that T and const T are different types, though they represent the same set of possible values.

const int b = 777;     // b cannot be modified
b = 5;                 // ERROR: cannot modify const variable

Why use const?

  1. Safety: Prevents accidental modification of values that shouldn’t change
  2. Intent: Communicates to other programmers that a value is fixed
  3. Optimization: Compiler can optimize code when it knows values won’t change
1.9.2 Compile-Time vs. Run-Time Constants

Compile-time constants: Initialized with constant expressions (values known at compile time):

const int b = 777;           // Constant expression
const int c = 2 * b + 5;    // Also constant expression

The compiler can evaluate these at compile time and potentially optimize code by replacing uses with the actual values.

Run-time constants: Initialized with expressions whose values are only known at runtime:

int input;
cin >> input;
const int x = input + 5;  // Value not known until runtime

These are still immutable (can’t be changed), but the compiler cannot evaluate them at compile time.

Practical difference: Compile-time constants can be used in more contexts (like array sizes), while run-time constants provide immutability without compile-time knowledge.

1.9.3 Constant Expressions

Constant expressions are expressions that can be evaluated at compile time. They are required in certain contexts:

  • Array sizes: const int size = 10; int arr[size];
  • Template parameters
  • Case labels in switch statements
1.9.4 Constants and Pointers

Four different combinations are possible with pointers and const:

  1. T* ptr; - Normal pointer to mutable object
  2. const T* ptr; - Pointer to constant object (cannot modify through pointer)
  3. T* const ptr = &obj; - Constant pointer (cannot point elsewhere)
  4. const T* const ptr = &obj; - Constant pointer to constant object
int x = 5;
const int y = 10;

int* p1 = &x;           // Can modify x through p1
const int* p2 = &y;     // Cannot modify through p2
int* const p3 = &x;     // p3 always points to x
const int* const p4 = &y;  // p4 always points to y, cannot modify

*p1 = 7;     // OK
*p2 = 7;     // ERROR
p3 = &y;     // ERROR

Reading tip: Read pointer declarations from right to left:

  • const int* ptr - “ptr is a pointer to int that is const”
  • int* const ptr - “ptr is a const pointer to int”
1.9 Type Deduction with auto
1.9.1 The auto Specifier

The auto keyword (repurposed from its old meaning as a storage-class specifier) tells the compiler to automatically deduce the type of a variable from its initializer. This is called type deduction or type inference.

auto x = 7;               // x has type int
auto y = 3.14;            // y has type double
auto z = "hello";         // z has type const char*
auto v = new vector<int>; // v has type vector<int>*

Why this is useful: Sometimes types in C++ can be extremely long and complex. auto lets you focus on the logic rather than typing out elaborate type names.

1.9.2 Type Deduction Rules

For auto var = expression;:

Type of expression Deduced type of var
T* or const T* T* or const T* (pointers preserved)
T, const T, T&, or const T& T (const and reference dropped)

For auto& var = expression;:

Type of expression Deduced type of var
T Error
const T const T&
T& T&
int x = 5;
const int y = 10;
int& rx = x;

auto a = x;      // a has type int (not int&)
auto b = y;      // b has type int (const dropped)
auto c = rx;     // c has type int (reference dropped)
auto& d = x;     // d has type int&
auto& e = y;     // e has type const int&

Important: The auto keyword doesn’t mean “any type”—it means “deduce the correct type from the initializer.”

1.9.3 Advantages of auto

Simplified syntax: Especially useful with complex types:

// Instead of:
vector<double*>* v = new vector<double*>(77);

// Write:
auto v = new vector<double*>(77);

Maintainability: If the return type of a function changes, code using auto automatically adapts.

Correctness: Guarantees exact type matching (no unintended conversions).

Caution: While auto is convenient, overusing it can make code harder to understand. Use it when the type is obvious from context or when it’s too complex to write out.

1.9.4 Limitations and Rules
  • Must have an initializer: auto x; is an error
  • Cannot mix different types in single declaration: auto a = 5, b = {1, 2}; is an error
  • Not a storage specifier anymore: auto int x; is an error
1.10 Structured Binding (C++17)
1.10.1 Basic Syntax

Structured binding (introduced in C++17) allows you to decompose objects into their constituent parts and bind those parts to named variables in a single declaration.

Syntax:

auto [var1, var2, var3] = expression;
auto [var1, var2, var3] { expression };
auto [var1, var2, var3] ( expression );

This introduces variables var1, var2, var3 into the current scope and binds them to subobjects or elements of the object from expression.

Analogy: Structured binding is like unpacking a suitcase—you take one packed object and separate it into its individual items, giving each item a name.

1.10.2 Structured Binding with Arrays

When binding to an array:

int a[2] = { 1, 2 };

auto [x, y] = a;        // x and y are copies of a[0] and a[1]
auto& [xr, yr] = a;     // xr and yr are references to a[0] and a[1]

Value binding (auto [x, y] = a;):

  • Creates a temporary copy of the array
  • x and y refer to elements of the copy
  • Modifying x or y doesn’t affect the original array

Reference binding (auto& [x, y] = a;):

  • No copy is made
  • x and y are direct references to array elements
  • Modifying x or y modifies the original array
1.10.3 Structured Binding with Structs

For structures, structured binding unpacks members:

struct S {
    int x;
    const double y;
};

S s = {1, 3.14};
auto [a, b] = s;        // a is int, b is const double
const auto [c, d] = s;  // c is const int, d is const double
1.10.4 Structured Binding with Tuples

Works with std::tuple and std::pair:

std::tuple<int, int&> f() { ... }

auto [x, y] = f();       // x is int, y is int&
const auto [z, w] = f(); // z is const int, w is int& (reference not affected by const)

2. Definitions

  • Pointer: A variable that stores the memory address of another variable; declared with * (e.g., int* ptr).
  • Address-of operator (&): An operator that returns the memory address of a variable (e.g., &x gives the address of x).
  • Dereference operator (*): An operator that accesses the value at the address stored in a pointer (e.g., *ptr accesses the value).
  • Null pointer (nullptr): A special pointer value indicating the pointer doesn’t point to any valid memory location.
  • Pointer arithmetic: Mathematical operations on pointers (e.g., ptr++ moves to the next element).
  • Namespace: A declarative region that provides scope for identifiers, preventing name clashes by grouping related declarations.
  • Qualified name: A name that includes its namespace, written as namespace::identifier.
  • Scope resolution operator (::): The operator used to access members of a namespace or class.
  • Using declaration: A statement that brings namespace members into the current scope (e.g., using std::cout;).
  • Array: A fixed-size, contiguous collection of elements of the same type; size must be known at compile time.
  • Array decay: The automatic conversion of an array name to a pointer to its first element.
  • Vector: A dynamic array from the C++ Standard Library that can grow and shrink at runtime; part of the <vector> header.
  • Range-based for loop: A C++11 loop construct that iterates over elements of a container or array using syntax for (element : container).
  • Reference: An alias for an existing object; always refers to the same object after initialization and is not itself an object.
  • Constant type: A type qualified with const, indicating objects of that type cannot be modified after initialization.
  • Compile-time constant: A constant whose value is determined at compile time from a constant expression.
  • Run-time constant: A constant whose value is determined at runtime but cannot be modified thereafter.
  • Constant expression: An expression that can be evaluated by the compiler at compile time.
  • Type deduction: The compiler’s ability to automatically determine a variable’s type from its initializer.
  • auto specifier: A type specifier that instructs the compiler to deduce the type automatically.
  • Structured binding: A C++17 feature that decomposes objects into multiple named variables in a single declaration.
  • Preprocessor directive: A command (beginning with #) processed before compilation, such as #include for file inclusion.
  • Stream insertion operator (<<): An overloaded operator used to output data to streams like std::cout.
  • Heap: A region of memory for dynamically allocated objects, managed explicitly by the programmer.
  • Stack: A region of memory for local variables, managed automatically based on scope.
  • Type: A classification that specifies the possible values, operations, and relationships for objects of that classification.
  • lvalue: An expression that refers to a memory location and can appear on the left side of an assignment.
  • Indirection: Accessing a value indirectly through a pointer or reference rather than directly.

3. Examples

3.1. Time Period Converter (Lab 1, Task 1)

Write a program that accepts a time period given in seconds and returns it in the following format: hours : minutes : seconds

Example:

Input Output
124660 34:37:40
Click to see the solution

Key Concept: Use integer division to extract hours, minutes, and seconds. Use the modulo operator (%) to get remainders.

#include <iostream>
using namespace std;

int main()
{
    int totalSeconds;
    cin >> totalSeconds;
    
    // Calculate hours, minutes, and seconds
    int hours = totalSeconds / 3600;
    int minutes = (totalSeconds % 3600) / 60;
    int seconds = totalSeconds % 60;
    
    // Print in required format
    cout << hours << ":" << minutes << ":" << seconds << endl;
    
    return 0;
}

Explanation:

  1. Calculate hours: Divide total seconds by 3600 (seconds per hour). Integer division automatically truncates.
    • \(124660 \div 3600 = 34\) hours
  2. Calculate remaining seconds after hours: Use modulo operator % to get remainder.
    • \(124660 \mod 3600 = 2260\) seconds remaining
  3. Calculate minutes: Divide remaining seconds by 60.
    • \(2260 \div 60 = 37\) minutes
  4. Calculate seconds: Get remainder after extracting minutes.
    • \(2260 \mod 60 = 40\) seconds

Answer: For input 124660, output is 34:37:40

Note: For proper formatting with leading zeros (e.g., 34:07:05), you would need to use std::setw and std::setfill from <iomanip>.

3.2. Swap Values Using Pointers (Lab 1, Task 2a)

Write your own function for swapping values of two integers using passing by pointer.

Click to see the solution

Key Concept: Pointers store addresses. To modify the original variables, we dereference the pointers to access the values at those addresses.

#include <iostream>
using namespace std;

// Swap function using pointers
void swapByPointer(int* a, int* b)
{
    int temp = *a;    // Store value pointed to by a
    *a = *b;          // Set value at a to value at b
    *b = temp;        // Set value at b to saved temp value
}

int main()
{
    int x = 5, y = 10;
    
    cout << "Before swap: x = " << x << ", y = " << y << endl;
    
    swapByPointer(&x, &y);  // Pass addresses of x and y
    
    cout << "After swap: x = " << x << ", y = " << y << endl;
    
    return 0;
}

Explanation:

  1. Function signature: void swapByPointer(int* a, int* b)
    • Parameters are pointers to integers
    • int* a means “a is a pointer to an integer”
  2. Dereferencing: Use * operator to access the value at the address
    • *a gets the value stored at the address contained in a
    • *a = *b copies the value from location b to location a
  3. Calling the function: Pass addresses using & operator
    • swapByPointer(&x, &y) passes the addresses of x and y

Answer: The values of the original variables are swapped.

3.3. Swap Values Using References (Lab 1, Task 2b)

Write your own function for swapping values of two integers using passing by reference.

Click to see the solution

Key Concept: References are aliases. When you modify a reference, you directly modify the original variable.

#include <iostream>
using namespace std;

// Swap function using references
void swapByReference(int& a, int& b)
{
    int temp = a;     // a is a reference, directly accesses the original
    a = b;            // Assign value of b to a
    b = temp;         // Assign saved value to b
}

int main()
{
    int x = 5, y = 10;
    
    cout << "Before swap: x = " << x << ", y = " << y << endl;
    
    swapByReference(x, y);  // Pass variables directly (not addresses)
    
    cout << "After swap: x = " << x << ", y = " << y << endl;
    
    return 0;
}

Explanation:

  1. Function signature: void swapByReference(int& a, int& b)
    • Parameters are references to integers
    • int& a means “a is a reference to an integer”
  2. No dereferencing needed: References act as direct aliases
    • a and b ARE the original variables, not copies
    • No need for * operator
  3. Calling the function: Pass variables directly
    • swapByReference(x, y) makes a an alias for x and b an alias for y

Comparison with pointers:

  • Syntax: Cleaner - no * or & operators needed in function body
  • Safety: No null references (references must refer to valid objects)
  • Clarity: More readable - intent is clearer

Answer: The values of the original variables are swapped, just like with pointers.

3.4. Remove Duplicates Using Arrays (Lab 1, Task 3a)

Write a program that accepts a number of elements (\(N\)) of the array of integers and then \(N\) elements. After user inserts the array, your program should remove all duplicates from it using arrays.

Example:

Input Output
8
1 3 5 3 3 4 1 2
1 3 5 4 2
Click to see the solution

Key Concept: Keep track of unique elements by checking if each element has been seen before. Use a separate array to mark seen elements or compare with previous unique elements.

#include <iostream>
using namespace std;

int main()
{
    int n;
    cin >> n;
    
    int arr[n];  // Note: Variable-length arrays are not standard C++
                 // but supported by some compilers
    
    // Read input
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    
    // Array to store unique elements
    int unique[n];
    int uniqueCount = 0;
    
    // For each element in original array
    for (int i = 0; i < n; i++) {
        bool isDuplicate = false;
        
        // Check if element already exists in unique array
        for (int j = 0; j < uniqueCount; j++) {
            if (arr[i] == unique[j]) {
                isDuplicate = true;
                break;
            }
        }
        
        // If not a duplicate, add to unique array
        if (!isDuplicate) {
            unique[uniqueCount] = arr[i];
            uniqueCount++;
        }
    }
    
    // Print unique elements
    for (int i = 0; i < uniqueCount; i++) {
        cout << unique[i];
        if (i < uniqueCount - 1) cout << " ";
    }
    cout << endl;
    
    return 0;
}

Explanation:

  1. Read input: Store all \(N\) elements in an array
  2. Create unique array: Maintain a second array to store only unique elements
  3. Iterate through original: For each element in the original array:
    • Check if it already exists in the unique array
    • If not found, add it to the unique array and increment uniqueCount
  4. Print result: Output all elements in the unique array

Time complexity: \(O(n^2)\) due to nested loops

Space complexity: \(O(n)\) for the unique array

Answer: For input 8 followed by 1 3 5 3 3 4 1 2, output is 1 3 5 4 2

Note: Variable-length arrays (int arr[n]) are not standard C++, though some compilers support them. For proper C++, use dynamic allocation or vectors (see next problem).

3.5. Remove Duplicates Using Vectors (Lab 1, Task 3b)

Write a program that accepts a number of elements (\(N\)) of the array of integers and then \(N\) elements. After user inserts the array, your program should remove all duplicates from it using vectors.

Example:

Input Output
8
1 3 5 3 3 4 1 2
1 3 5 4 2
Click to see the solution

Key Concept: Vectors provide dynamic sizing and useful methods like push_back(). The algorithm is similar to the array version but with cleaner syntax.

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    int n;
    cin >> n;
    
    vector<int> arr(n);  // Vector of size n
    
    // Read input
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    
    vector<int> unique;  // Vector to store unique elements
    
    // For each element in original vector
    for (int i = 0; i < n; i++) {
        bool isDuplicate = false;
        
        // Check if element already exists in unique vector
        for (int j = 0; j < unique.size(); j++) {
            if (arr[i] == unique[j]) {
                isDuplicate = true;
                break;
            }
        }
        
        // If not a duplicate, add to unique vector
        if (!isDuplicate) {
            unique.push_back(arr[i]);
        }
    }
    
    // Print unique elements
    for (int i = 0; i < unique.size(); i++) {
        cout << unique[i];
        if (i < unique.size() - 1) cout << " ";
    }
    cout << endl;
    
    return 0;
}

Explanation:

  1. Vector declaration: vector<int> arr(n) creates a vector with \(n\) elements
    • Can also use vector<int> arr; and push_back() for input
  2. Dynamic sizing: vector<int> unique; starts empty and grows automatically
  3. push_back(): Adds elements to the end of the vector
  4. size(): Returns the current number of elements (unlike arrays which don’t track size)

Advantages over arrays:

  • No need to track size manually
  • Can grow dynamically
  • Safer (can use .at() for bounds checking)
  • More intuitive syntax

Alternative using range-based for loop:

// Checking for duplicates using range-based for
for (int elem : arr) {
    bool isDuplicate = false;
    for (int uElem : unique) {
        if (elem == uElem) {
            isDuplicate = true;
            break;
        }
    }
    if (!isDuplicate) {
        unique.push_back(elem);
    }
}

Answer: For input 8 followed by 1 3 5 3 3 4 1 2, output is 1 3 5 4 2

More efficient approach: For better performance (\(O(n \log n)\)), you could sort the vector first and then remove consecutive duplicates, or use a std::set which automatically maintains uniqueness.

3.6. Auto Type Deduction (Lecture 1, Example 1)

Determine the types of variables in the following declarations:

  1. auto x = 7;
  2. auto a[] = { 1, 2, 3 };
  3. const auto *v = &x;
  4. static auto y = 0.0;
  5. auto int r;
  6. auto m;
  7. auto a=5, b={1,2};
Click to see the solution

Key Concept: Understanding auto type deduction rules.

Answers:

(a) auto x = 7;

  • Type of x: int
  • The literal 7 is an int, so x is deduced as int

(b) auto a[] = { 1, 2, 3 };

  • Type of a: std::initializer_list<int>
  • Braced initializer lists are deduced as initializer_list

(c) const auto *v = &x; (assuming int x from part a)

  • Type of v: const int*
  • &x has type int*
  • const auto* adds const to the pointed-to type
  • Result: pointer to const int

(d) static auto y = 0.0;

  • Type of y: double
  • The literal 0.0 is a double
  • static is a storage specifier, doesn’t affect type deduction

(e) auto int r;

  • ERROR: auto is not a storage specifier in modern C++
  • Cannot combine auto with explicit type int

(f) auto m;

  • ERROR: auto requires an initializer
  • Compiler cannot deduce type without initialization

(g) auto a=5, b={1,2};

  • ERROR: Inconsistent types in single declaration
  • a would be int, but b would be initializer_list<int>
  • Cannot mix different deduced types in one declaration

Answer Summary:

  • Valid: (a) int, (b) initializer_list<int>, (c) const int*, (d) double
  • Invalid: (e), (f), (g) are all compilation errors
3.7. Reference Examples (Lecture 1, Example 2)

Analyze the following code involving references:

void f ( double& a )
{ a += 3.14; }

double d = 7.0;
f(d);

What is the value of d after calling f(d)?

Also analyze:

int v[20];
int& f ( int i ) { return v[i]; }

f(3) = 7;

What happens in this code?

Click to see the solution

Key Concept: References allow functions to modify the original argument and can be used as lvalues (on the left side of assignment).

Part 1: Function modifying through reference

void f ( double& a )
{ a += 3.14; }

double d = 7.0;
f(d);  // After this call, d = 10.14

Explanation:

  1. f takes a reference to double
  2. When called with d, parameter a becomes an alias for d
  3. a += 3.14 modifies the original d
  4. Answer: d has value 10.14 after the call

Compare with pass-by-value:

void f ( double a )  // No reference
{ a += 3.14; }

double d = 7.0;
f(d);  // d still equals 7.0 (unchanged)

Part 2: Returning reference as lvalue

int v[20];
int& f ( int i ) { return v[i]; }

f(3) = 7;  // This assigns 7 to v[3]

Explanation:

  1. Function f returns a reference to v[i]
  2. f(3) returns a reference to v[3]
  3. This reference can be used as an lvalue (left side of assignment)
  4. f(3) = 7 is equivalent to v[3] = 7
  5. Answer: After execution, v[3] has value 7

Why this works:

  • Returning by reference gives you the actual array element, not a copy
  • References can appear on the left side of =
  • This allows functions to behave like variables

Common use case:

This pattern is used in operator overloading, e.g.:

matrix[i][j] = 5;  // operator[] returns reference to allow assignment

Answer: References enable both input modification and lvalue semantics for function returns.

3.8. Pointers and Constants (Lecture 1, Example 3)

Given the following declarations, determine which operations are valid:

int x = 5;
const int y = 10;

int* p1 = &x;
const int* p2 = &y;
int* const p3 = &x;
const int* const p4 = &y;

*p1 = 7;     // Operation A
*p2 = 7;     // Operation B
p3 = &y;     // Operation C
Click to see the solution

Key Concept: Understanding the four combinations of pointers and const.

Type explanations:

  1. int* p1 - pointer to int (both pointer and pointee are mutable)
  2. const int* p2 - pointer to const int (cannot modify through pointer, but pointer can be reassigned)
  3. int* const p3 - const pointer to int (pointer is fixed, but can modify pointee)
  4. const int* const p4 - const pointer to const int (nothing can change)

Reading tip: Read right-to-left:

  • int* const p - “p is a const pointer to int”
  • const int* p - “p is a pointer to const int”

Operation analysis:

Operation A: *p1 = 7;

  • p1 is int* (pointer to mutable int)
  • VALID: Can modify through p1
  • Result: x becomes 7

Operation B: *p2 = 7;

  • p2 is const int* (pointer to const int)
  • INVALID: Cannot modify through pointer to const
  • Compilation error: “assignment of read-only location”

Operation C: p3 = &y;

  • p3 is int* const (const pointer)
  • INVALID: Cannot reassign a const pointer
  • Also has type mismatch: p3 is int* but &y is const int*
  • Compilation error: “assignment of read-only variable”

Additional valid operations:

p2 = &x;       // VALID: can reassign p2
*p3 = 100;     // VALID: can modify through p3
int z = *p2;   // VALID: can read through p2
int w = *p4;   // VALID: can read through p4

Summary table:

Pointer Type Can modify pointee? Can reassign pointer?
int* Yes Yes
const int* No Yes
int* const Yes No
const int* const No No

Answer: Only Operation A is valid. Operations B and C cause compilation errors.

3.9. Find Value in Array (Version 1) (Tutorial 1, Example 1)

Find a given value in an array with fixed size.

int find1 ( int array[20], int x )
{
    for ( int i = 0; i < 20; i++ )
    {
        if ( array[i] == x ) return i; // success
    }
    return -1; // fail
}
Click to see the solution

Key Concept: Linear search through array. Returns index if found, -1 if not found.

Analysis:

  • Problem: The size is hardcoded (20), making the function inflexible
  • Return value: Returns index on success, -1 on failure
  • Time complexity: \(O(n)\) where \(n = 20\)

Limitations:

  • Only works for arrays of exactly 20 elements
  • Cannot be reused for different array sizes
  • The magic number “20” appears twice, violating the DRY principle

Answer: This version works but is too rigid. See the next version for improvement.

3.10. Find Value in Array (Version 2) (Tutorial 1, Example 2)

Find a given value in an array with variable size, using pointers.

int* find2 ( int* array, int n, int x )
{
    const int* p = array;
    for ( int i = 0; i < n; i++ )
    {
        if ( *p == x ) return p; // success
        p++;
    }
    return nullptr; // fail
}

Usage:

int A[20];
// ... fill array ...
int* res = find2(A, 20, 5);
Click to see the solution

Key Concept: Use pointers to make the function work with any array size. Return pointer to found element or nullptr if not found.

Improvements over Version 1:

  1. Flexible size: Accepts array size as parameter \(n\)
  2. Returns pointer: Returns pointer to found element (more flexible than index)
  3. Demonstrates pointer arithmetic: Uses p++ to move to next element

How it works:

  1. int* array receives array as pointer to first element
  2. const int* p = array creates a pointer to traverse the array
  3. Loop checks each element via dereferencing: *p == x
  4. p++ moves pointer to next element (pointer arithmetic)
  5. Returns nullptr if element not found (null pointer indicates failure)

Pointer arithmetic:

  • p++ advances pointer by one int (typically 4 bytes)
  • array + i computes address of element at index \(i\)
  • *(array + i) is equivalent to array[i]

Answer: This version is much more flexible and reusable than Version 1.

3.11. Vector Element Modification (Tutorial 1, Example 3)

What are values of v6 elements after the following loop has finished?

vector<int> v6 = { 1, 2, 3, 4 };
for ( int elem : v6 )
    elem = elem * 10;
Click to see the solution

Key Concept: Understanding value vs. reference semantics in range-based for loops.

Answer: The vector elements did NOT change. They remain { 1, 2, 3, 4 }.

Explanation:

In the loop for (int elem : v6):

  • elem is a copy of each vector element
  • When you modify elem, you’re modifying the copy, not the original
  • The changes are lost when the loop iteration ends

Correct version using references:

vector<int> v6 = { 1, 2, 3, 4 };
for ( int& elem : v6 )      // Note the & (reference)
    elem = elem * 10;
// Now v6 = { 10, 20, 30, 40 }

Even better - using auto:

for ( auto& elem : v6 )     // Compiler deduces type
    elem = elem * 10;

Rule of thumb:

  • Use for (Type elem : container) for read-only iteration
  • Use for (Type& elem : container) to modify elements
  • Use for (const Type& elem : container) for read-only without copying (efficient for large objects)
3.12. Structured Binding with Arrays (Tutorial 1, Example 4)

Given the following code, what are the values and relationships?

int a[2] = { 1, 2 };
auto [x, y] = a;
auto& [xr, yr] = a;
Click to see the solution

Key Concept: Understanding value vs. reference semantics in structured bindings.

Analysis:

Line 1: auto [x, y] = a;

  • Creates a temporary copy of array a
  • x and y refer to elements of the copy, not the original
  • Values: x = 1, y = 2
  • Modifying x or y does NOT affect a

Line 2: auto& [xr, yr] = a;

  • Creates references to the original array elements
  • xr is a reference to a[0]
  • yr is a reference to a[1]
  • Values: xr = 1, yr = 2
  • Modifying xr or yr DOES affect a

Example:

x = 100;    // Only changes x, not a[0]
xr = 200;   // Changes both xr AND a[0]

cout << a[0];  // Prints: 200
cout << x;     // Prints: 100

Answer:

  • x and y are independent copies
  • xr and yr are aliases for a[0] and a[1]
3.13. Structured Binding with Structs (Tutorial 1, Example 5)

Given the following code, what are the types of the variables?

struct S {
    int x;
    const double y;
};

S f() { return S{5, 3.14}; }

const auto [x, y] = f();
Click to see the solution

Key Concept: Structured binding with const auto preserves and adds const-qualification.

Analysis:

The function f() returns a struct S with members:

  • int x
  • const double y

The structured binding const auto [x, y] = f(); decomposes the struct.

Answer:

  • x has type const int (const added by const auto)
  • y has type const double (already const in struct, remains const)

Explanation:

  1. f() returns a temporary S object
  2. const auto [x, y] binds to that object
  3. const applies to the entire binding
  4. x becomes const int (const added)
  5. y becomes const double (already const, const preserved)

Without const:

auto [x, y] = f();
// x would be: int
// y would be: const double (const from member preserved)

Key rule: Member const-qualification is always preserved in structured bindings. Top-level const is added based on the binding specifier.