Chapter 2 — Structured Programming with C++

Course: INT1339 — C++ Programming Language

Textbook: Nguyen Duy Phuong, Nguyen Manh Son — C++ Programming Language Coursebook, 2020

Chapter 2 Contents

Structured Programming Methodology

Principles, Top-Down design strategy, advantages & disadvantages

Functions

Concept & classification, declaration & calling, parameter passing, scope & overloading

Struct Data Type

Defining structs, initialization & member access, struct pointers & arrays

Summary & Workshop MiniPOS V2.0.0

Summary table + Refactoring MiniPOS V1.0.0 → V2.0.0 with functions & structs

After This Chapter, Students Will Be Able To:

1

Structured Programming Principles

Understand structured programming principles and the Top-Down design strategy for decomposing problems.

2

Declare & Call Functions

Declare, define, and call functions correctly, including void and value-returning functions.

3

Parameter Passing Mechanisms

Distinguish 3 mechanisms: pass by value, pass by reference (&), and pass by pointer (*).

4

Scope & Overloading

Understand variable scope (local, global) and apply function overloading with different parameter lists.

5

Struct Data Types

Define and use struct data types, struct arrays, and struct pointers for grouping related data.

6

Refactor MiniPOS

Refactor MiniPOS V1.0.0 → V2.0.0 by decomposing monolithic code into modular functions operating on structs.

Recap — MiniPOS V1.0.0 and Its Limitations

MiniPOS V1.0.0 — The Problem

int main() {
  string names[100];
  double prices[100];
  int quantities[100];
  int total = 0;
  // main()
  // Add product
  // Display all
  // Search? 15
  // ALL mixed together!
}

Issues & Impact

All code in one main()

Hard to read, debug, and maintain

3 separate arrays

Must keep indices synchronized — error-prone

Code duplication

Same display logic repeated

Cannot reuse logic

Copy-paste needed for similar projects

Structured Programming Methodology

Principles

Top-Down Design

Advantages & Disadvantages

Principles and Characteristics of Structured Programming

Decomposition

Modularization

Separation of Data and Functions

What is Structured Programming?

Structured Programming is a methodology that organizes a program as a collection of functions (subroutines), where each function performs a specific, well-defined task.

Decomposition

Break a large problem into smaller sub-problems. "Store Management" → Add + Display + Search + Statistics

Modularization

Each function = 1 module = 1 responsibility (Single Responsibility): addProduct(), displayAll()

Data & Function Separation

Data is passed to functions via parameters, not hidden inside objects: void displayProduct(string name, double price)

Three Fundamental Control Structures (Böhm–Jacopini Theorem, 1966)

Sequence

Statements execute one after another, top-to-bottom.

Selection

if-else, switch-case for branching logic.

Iteration

for, while, do-while for repetition.

Historical Context: The "Software Crisis" and Structured Programming

🍝 The Problem — Spaghetti Code (1960s)

Programs used GOTO statements to jump arbitrarily between lines, creating tangled, unreadable code paths.

10 PRINT "ENTER NUMBER"
20 INPUT N
30 IF N < 0 GOTO 70
40 PRINT "POSITIVE"
50 GOTO 80
60 GOTO 30
70 PRINT "NEGATIVE"
80 END

💡 The Solution — Dijkstra's Letter (1968)

Edsger Dijkstra published "Go To Statement Considered Harmful" in Communications of the ACM, arguing GOTO creates programs nearly impossible to reason about.

He proposed replacing GOTO with structured control flow: sequence, selection, and iteration — launching the structured programming movement dominant throughout the 1970s–1990s.

Top-Down Design Strategy (Stepwise Refinement)

Decomposing Complex Problems into Manageable Sub-Problems

Top-Down Design: Divide and Conquer

Top-Down Design (Stepwise Refinement): Start with the overall problem, decompose into smaller sub-problems, and repeat until each sub-task is simple enough to implement as a single function.

Decomposition Levels

  • Level 0 (Root): The entire problem statement
  • Level 1: Major functional modules (3–5 modules)
  • Level 2+: Refine each module into implementable tasks
  • Stop condition: A task fits in ~10–30 lines within one function

Modern Parallel

Top-Down design maps directly to microservices architecture used at Netflix and Amazon — each microservice handles one domain (User Service, Payment Service, Recommendation Service), just as each function handles one task.

Applying Top-Down Design: Student GPA Calculator

Problem: Read scores for N students, calculate GPA, classify academic performance, and print a summary report.

Mapping to C++ Functions

Advantages and Disadvantages of Structured Programming

When to Use

Limitations

Transition to Object-Oriented Programming

Structured Programming: Strengths and Limitations

Advantages

  • Easy to understand — clear step-by-step flow
  • Natural problem decomposition (Top-Down)
  • Functions can be tested independently

Disadvantages

  • Hard to manage for large-scale systems
  • Data is not protected — any function can modify any variable passed to it
  • Data and functions are separate — no logical grouping
  • Adding new data fields requires modifying many functions

Checkpoint §2.1 — Quick Practice Quiz

Question 1

Which design strategy decomposes a large problem into smaller sub-problems, starting from the top level?

A. Bottom-Up Design  

B. Top-Down Design  

C. Left-Right Design  

D. Random Decomposition

Question 2

According to the Böhm–Jacopini Theorem, which 3 control structures are sufficient to implement any algorithm?

A. Sequence, Recursion, Exception  

B. Sequence, Selection, Iteration  

C. Input, Processing, Output  

D. Declaration, Assignment, Return

Question 3

What is the primary disadvantage of structured programming that OOP aims to solve?

A. Programs run too slowly  

B. Cannot use loops  

C. Data is not protected — any function can modify data freely   D. Cannot print output

Checkpoint §2.1 — Answers & Explanations

Q1: Answer — B. Top-Down Design ✓

Top-Down Design starts with the overall problem and repeatedly breaks it down into smaller sub-problems until each is simple enough to implement directly as a function.

Q2: Answer — B. Sequence, Selection, Iteration ✓

The Böhm–Jacopini Theorem (1966) proved any computable algorithm can be expressed using only Sequence (top-to-bottom), Selection (if-else), and Iteration (loops).

Q3: Answer — C. Data is not protected ✓

In structured programming, data and functions are separate — any function receiving data via parameters can modify it without restriction. OOP bundles data inside classes with access modifiers (private) to prevent unauthorized modification.

Functions

Concept & Classification
Declaration & Calling
Parameter Passing
Scope & Overloading

Concept, Role, and Classification of Functions

What Are Functions?
Library vs User-Defined
void vs Value-Returning

Functions — The Building Blocks of Structured Programs

A function is a named block of code that performs a specific task. It can accept input data (parameters), process it, and optionally return a result.

Without Functions

300 lines in main() — hard to navigate. One chef does everything: takes orders, cooks, washes dishes. A bug could be anywhere.

With Functions

Specialized stations: takeOrder(), grillMeat(), makeDessert(). main() reads like a table of contents — each function testable independently.

Classifying Functions in C++

1. By Origin

2. By Return Type

Code Examples

// void function — performs action
void greet(string name) {
 cout << "Hello, " << name << "!";
}

// Value-returning function
double calcCircleArea(double r) {
 return 3.14159 * r * r;
}

Function Declaration and Calling Process

Prototype

Definition

Call

Execution Flow

Three Steps: Declare → Define → Call

// STEP 1: FUNCTION PROTOTYPE (Declaration)
// Tells the compiler: "This function exists."
// Placed BEFORE main(), ends with semicolon
double calcArea(double radius);

// STEP 3: MAIN FUNCTION
int main() {
  double r = 5.0;
  // STEP 3: FUNCTION CALL
  double area = calcArea(r);  // Passes 5.0, receives result
  cout << "Area = " << area;  // Output: Area = 78.5398
  return 0;
}

// STEP 2: FUNCTION DEFINITION (Implementation)
double calcArea(double radius) {
  return 3.14159 * radius * radius;
}

Function Terminology Reference

How Does a Function Call Execute?

Execution Flow

main() starts
│
├── double r = 5.0;
├── double area = calcArea(r);
│   (Execution PAUSES here)
│         ↓
│   calcArea(double radius) {
│     radius = 5.0 (copy of r)
│     return 3.14159 * 5.0 * 5.0;
│     → returns 78.5398
│   }
│   area = 78.5398 ← RETURN
│   (Execution RESUMES)
├── cout << "Area = " << area;
└── return 0;

Key Observations

1

Pause on Call

When a function is called, the calling function pauses execution.

2

Control Transfers

Execution jumps to the called function's body and runs it completely.

3

Resume on Return

When return is hit, control transfers back and the caller resumes from where it left off.

Hands-On: Encrypting Messages with Caesar Cipher

Code

string encrypt(string text, int shift) {
  string result = text;
  for (int i = 0; i < result.length(); i++) {
    if (isalpha(result[i])) {
      char base = isupper(result[i]) ? 'A':'a';
      result[i] = (result[i]-base+shift)%26+base;
    }
  }
  return result;
}

// Decrypt by reversing — REUSES encrypt()!
string decrypt(string text, int shift) {
  return encrypt(text, 26 - shift);
}

Design Highlights

Elegant Reuse

decrypt() reuses encrypt() — shifting by 26 - shift reverses the encryption. No duplicate logic!

Clean main()

Only 6 meaningful lines in main() — easy to understand the entire program flow at a glance.

Caesar Cipher — Running the Program

main() — Calling the Functions

int main() {
  string msg = "Hello PTIT";
  int key = 3;
  string enc = encrypt(msg, key);
  string dec = decrypt(enc, key);
  cout << "Original:  " << msg;  // Hello PTIT
  cout << "Encrypted: " << enc;  // Khoor SWLW
  cout << "Decrypted: " << dec;  // Hello PTIT
}

How the Shift Works

Parameter Passing Mechanisms

Pass by Value (Giá trị)

Pass by Reference (Tham chiếu)

Pass by Pointer (Con trỏ)

When to Use Each

Three Ways to Pass Data to Functions

Pass by Value

void f(int x)

A copy of the value is passed. Cannot modify the original. High copy cost for large data.

Pass by Reference

void f(int &x)

An alias to the original variable. Can modify the original. No copy cost.

Pass by Pointer

void f(int *x)

The memory address is passed. Modify original via *x. No copy cost. Supports nullptr.

Pass by Value — The Safe Default

Code Example

void tryToDouble(int x) {
  x = x * 2;  // Modifies LOCAL COPY only!
  cout << "Inside: x = " << x;  // 20
}

int main() {
  int num = 10;
  tryToDouble(num);  // Passes a COPY of num
  cout << "After: num = " << num; // Still 10!
}

Output:

Inside function: x = 20
After call: num = 10  ← Original unchanged!

Memory Visualization

Before call:
┌────────────────┐
 main's num: 10 
└────────────────┘
During tryToDouble():
┌────────────────┐
 main's num: 10  (safe)
├────────────────┤
 copy x:    20   (modified)
└────────────────┘
After return:
┌────────────────┐
 main's num: 10 
└────────────────┘
(copy destroyed)

When to Use

  • When the function should NOT modify the original data
  • For small data types (int, double, char, bool) where copying is cheap

Pass by Reference — Direct Access to the Original

Code Example

void actuallyDouble(int &x) {  // &x = alias
  x = x * 2;  // Modifies ORIGINAL directly!
  cout << "Inside: x = " << x;  // 20
}

int main() {
  int num = 10;
  actuallyDouble(num);
  cout << "After: num = " << num; // Now 20!
}

// Practical: Swapping Two Variables
void swap(int &a, int &b) {
  int temp = a;
  a = b;
  b = temp;
}
// swap(x, y) — x and y swapped in main()!

Memory Visualization

Before call:
┌────────────────┐
 main's num: 10 
└────────────────┘

During actuallyDouble():
┌────────────────┐
 main's num: 20   &x
└────────────────┘
(x IS num  same memory cell)

After return:
┌────────────────┐
 main's num: 20 
└────────────────┘

When to Use

  • When the function MUST modify the original variable (e.g., swap, readInput)
  • When passing large data structures to avoid expensive copying

Pass by Pointer — Passing the Memory Address

Code Example

void doubleViaPointer(int *ptr) {
  *ptr = (*ptr) * 2;  // Dereference to modify
  cout << "Inside: *ptr = " << *ptr;  // 20
}

int main() {
  int num = 10;
  doubleViaPointer(&num);  // Pass ADDRESS of num
  cout << "After: num = " << num;  // Now 20!
}

Output:

Inside function: *ptr = 20
After call: num = 20  ← Original IS modified!

Memory Visualization

Before call:
┌──────────────────┐
 num: 10          
 Address: 0x1000  
└──────────────────┘

During doubleViaPointer():
┌──────────────────┐
 num: 20            *ptr writes here
 Address: 0x1000  
├──────────────────┤
 ptr: 0x1000       (contains num's address)
└──────────────────┘

When to Use

  • Dynamic memory (new/delete) or arrays (decay to pointers)
  • When you need to handle the nullptr case
  • When interfacing with C libraries using pointer-based APIs

Side-by-Side Comparison: Value vs Reference vs Pointer

Code Example

// Pass by Value — broken swap
void swapByValue(int a, int b) {
  int temp = a;
  a = b;
  b = temp;
  // Only local copies swapped!
}
// Pass by Reference — correct swap
void swapByRef(int &a, int &b) {
  int temp = a;
  a = b;
  b = temp;
}
// Pass by Pointer — correct swap
void swapByPtr(int *a, int *b) {
  int temp = *a;
  *a = *b;
  *b = temp;
}

Code Example

int main() {
  int x = 5, y = 10;
  swapByValue(x, y);  // x=5, y=10 (unchanged)
  swapByRef(x, y);    // x=10, y=5 (swapped!)
  swapByPtr(&x, &y);  // x=5, y=10 (swapped back!)
}

Comparison Table

Variable Scope and Function Overloading

Local vs Global Variables

Name Shadowing

Overloaded Functions

Variable Scope: Where Can a Variable Be Accessed?

Code Example

int globalCounter = 0; // GLOBAL
                       // accessible everywhere

void increment() {
    int localStep = 1; // LOCAL
                       // only inside increment()
    globalCounter += localStep; // OK ✓
}

int main() {
    int localVar = 100; // LOCAL
                        // only inside main()
    increment();
    cout << globalCounter; // OK ✓ (global)
    cout << localVar;      // OK ✓ (local here)
    // cout << localStep;  // ✗ ERROR!
                            // localStep not in scope
}

Scope Summary

Name Shadowing and Global Variable Pitfalls

Name Shadowing Example

int x = 100;  // Global x

void demo() {
  int x = 5;       // Local x SHADOWS global
  cout << x;       // Prints 5 (local)
  cout << ::x;     // Prints 100 (global, :: operator)
}

⚠️ Why Avoid Global Variables?

Global Variables — Best Practices

Prefer local variables

Pass data via parameters rather than relying on global state.

Use const for global constants

const int MAX_PRODUCTS = 100; — safe, readable, no mutation risk.

Avoid mutable global variables

They create hidden dependencies between functions — a major source of bugs.

Function Overloading — Same Name, Different Parameters

Three Overloads of calcPrice()

// Overload 1: Base price, no discount
double calcPrice(double price) {
  return price;
}

// Overload 2: Price with percentage discount
double calcPrice(double price, double discountPct) {
  return price * (1 - discountPct / 100);
}

// Overload 3: With maximum discount cap
double calcPrice(double price, double discountPct,
                 double maxDiscount) {
  double disc = price * discountPct / 100;
  return price - min(disc, maxDiscount);
}

How Does the Compiler Choose?

The compiler selects the correct overload based on the number and types of arguments at the call site — resolved at compile time, not runtime.

Overloading Rules

  • Different number of parameters → valid overload
  • Different types of parameters → valid overload
  • Different return type only → NOT a valid overload (compile error)

Function Overloading — Calling the Overloads

main() — Which Overload Gets Called?

int main() {
  cout << calcPrice(100000);            // 100000   → Overload 1
  cout << calcPrice(100000, 10);        // 90000    → Overload 2
  cout << calcPrice(100000, 50, 20000); // 80000    → Overload 3
}

Compiler Resolution Table

Checkpoint §2.2 — Quick Practice Quiz

Question 1

Given void update(int x) { x = x + 5; } and int a = 10; update(a); → What is the value of a after the call?

A. 10  

B. 15  

C. 5  

D. Compile Error

Question 2

Which parameter passing mechanism creates an alias to the original variable?

A. Pass by Value  

B. Pass by Pointer  

C. Pass by Reference  

D. Pass by Copy

Question 3

Which of the following is NOT a valid function overload pair?

A. void f(int x) and void f(double x)  

B. void f(int x) and void f(int x, int y)  

C. int f(int x) and double f(int x) (different return type only)  

D. void f(int x) and void f(string x)

Checkpoint §2.2 — Answers & Explanations

Q1: Answer — A. 10 ✓

Pass by value — x is a copy of a. Modifying x inside the function does not affect the original a in main(). To modify a, use void update(int &x).

Q2: Answer — C. Pass by Reference ✓

Pass by reference (&) makes the parameter an alias (another name) for the original variable. Pass by pointer passes the address; pass by value passes a copy.

Q3: Answer — C. int f(int x) and double f(int x) ✓

Function overloading requires different parameter lists. Changing only the return type is NOT sufficient — the compiler cannot determine which overload to call based on return type alone, since the return value might be ignored.

Struct Data Type

Defining Structs

Initialization & Access

Struct Pointers & Arrays

Defining Struct Data Types

Why Structs?

Syntax

Nested Structs

typedef

The Problem: Managing Related Data with Separate Arrays

MiniPOS V1.0.0 — Parallel Arrays

string names[100];     // Product name
double prices[100];    // Unit price
int quantities[100];   // Stock quantity

// To access Product #3:
cout << names[3] << " costs "
     << prices[3] << " VND";
// What if we use prices[4] with names[3]?
// → WRONG DATA!

Problems

  • Index synchronization required — names[i], prices[i] must always match
  • Adding a new field (e.g., category) = create a 4th array, update ALL functions
  • Sorting requires swapping across all arrays simultaneously

The Solution — Struct

// WITHOUT struct:
string names[100];
double prices[100];
int quantities[100];
string categories[100];
// 4 parallel arrays!

// WITH struct:
struct Product {
  string name;
  double price;
  int quantity;
  string category;
};
Product products[100];
// All data in ONE place!

Defining a Struct in C++

A struct (structure) is a user-defined data type that groups multiple variables of different types under a single name.

Syntax Template

struct StructName {
  DataType member1;
  DataType member2;
  DataType member3;
};  // ← Don't forget the semicolon!

Product Struct

struct Product {
  string name;      // Product name
  double price;     // Unit price
  int quantity;     // Stock quantity
  string category;  // Product category
};

Student Struct

struct Student {
  string id;        // "B21DCMM001"
  string fullName;  // Full name
  double gpa;       // Grade Point Average
  int credits;      // Total credits completed
};

Key Points

  • A struct definition creates a new data type (like int or double), not a variable
  • Members can be of different types — string, int, double, even another struct
  • ⚠️ The semicolon ; after the closing brace } is mandatory!

Nested Structs — Structs Inside Structs

Student with Nested Date

struct Date {
  int day;
  int month;
  int year;
};

struct Student {
  string id;
  string fullName;
  Date birthDate;   // Nested struct!
  double gpa;
};

// Usage:
Student s;
s.fullName = "Nguyen Van A";
s.birthDate.day = 15;
s.birthDate.month = 3;
s.birthDate.year = 2004;

Real-World Analogy: Shipping Order

A shipping order contains sender info, receiver info, and package details — each is a nested struct.

struct Address {
  string street;
  string city;
  string zipCode;
};

struct Order {
  int orderId;
  Address sender;    // Nested!
  Address receiver;  // Nested!
  double totalWeight;
};

Using typedef with Struct Definitions

C-Style: typedef

// C-style: must write "struct Product" every time
struct Product {
  string name;
  double price;
};
struct Product p1;  // C-style declaration

// Using typedef: create alias "Product"
typedef struct {
  string name;
  double price;
} Product;
Product p1;  // Cleaner declaration

Modern C++ (Recommended)

struct Product {
  string name;
  double price;
};

// Works directly in C++ — no typedef needed!
Product p1;
Product p2 = {"Laptop", 15000000};

Initialization and Member Access Operations

Creating Struct Variables

Dot Operator

Passing Structs to Functions

Creating and Initializing Struct Variables

struct Product { string name; double price; int quantity; string category; };

// Method 1: Brace initialization (must match member order)
Product p1 = {"Laptop", 15000000, 5, "Electronics"};

// Method 2: Default construction + individual assignment
Product p2;
p2.name = "Mouse";  p2.price = 250000;
p2.quantity = 20;   p2.category = "Accessories";

// Method 3: C++11 brace initialization
Product p3 = {"Keyboard", 500000, 10, "Accessories"};

// Method 4: Copy from another struct
Product p4 = p1;  // All members copied from p1

Member Access with the Dot Operator (.)

cout << p1.name;      // "Laptop"
cout << p1.price;     // 15000000
p1.quantity -= 1;     // Decrement stock
p1.price *= 0.9;      // Apply 10% discount

Passing Structs to Functions — Value vs Reference

Three Approaches

// Pass by Value (Creates a Copy — EXPENSIVE!)
void displayProduct(Product p) {
 cout << p.name << " | " << p.price;
}

// Pass by Reference (Recommended for Modification)
void applyDiscount(Product &p, double pct) {
 p.price *= (1 - pct / 100); // Modifies original!
}

// Pass by const Reference (Recommended for Read-Only)
void displayProduct(const Product &p) {
 cout << p.name << " | " << p.price;
 // p.price = 0; //

Struct Pointers and Struct Arrays

Arrow Operator (->)

Struct Arrays

Sorting Struct Arrays

Accessing Struct Members via Pointers

Arrow Operator (->)

Product laptop = {"Laptop", 15000000, 5, "Electronics"};
Product* ptr = &laptop;

// Two equivalent ways to access via pointer:
cout << ptr->name;      // Arrow operator (preferred)
cout << (*ptr).name;    // Dereference + dot (verbose)

// Modify through pointer:
ptr->price *= 0.85;     // Apply 15% discount
ptr->quantity -= 1;     // Decrease stock

Dot (.) vs Arrow (->)

Accessing Struct Members via Pointers

Memory Visualization

┌────────────────────────────┐
│ laptop (Address: 0x1000)   │
│  .name = "Laptop"          │
│  .price = 15000000         │
│  .quantity = 5             │
└────────────────────────────┘
        ▲ points to
┌────────────────────────────┐
│ ptr = 0x1000 (Pointer Addr: 0x2000)│
└────────────────────────────┘
ptr->name ≡ (*ptr).name ≡ laptop.name

Dot (.) vs Arrow (->)

Arrays of Structs — Managing Collections

Struct Array Example

const int MAX = 100;
Product inventory[MAX];  // Array of 100 Products
int total = 0;

// Add products:
inventory[total] = {"Keyboard", 500000, 10, "Accessories"};
total++;
inventory[total] = {"Monitor", 3500000, 3, "Electronics"};
total++;

// Display all products:
for (int i = 0; i < total; i++) {
  cout << i+1 << ". "
       << inventory[i].name << " | "
       << inventory[i].price << " VND | Qty: "
       << inventory[i].quantity << endl;
}

Memory Layout of Struct Array

inventory[0]
┌─────────────────────┐
 name: "Keyboard"     Address: 0x1000
 price: 500000       
 quantity: 10        
 category: "Access." 
└─────────────────────┘
inventory[1]
┌─────────────────────┐
 name: "Monitor"      Address: 0x1050
 price: 3500000      
 quantity: 3         
 category: "Electr." 
└─────────────────────┘

Sorting an Array of Structs

Sort by Price (Ascending)

void sortByPrice(Product inv[], int n) {
  for (int i = 0; i < n - 1; i++) {
    for (int j = 0; j < n - i - 1; j++) {
      if (inv[j].price > inv[j+1].price) {
        swap(inv[j], inv[j+1]); // Swap entire structs
      }
    }
  }
}
// Sort by name (alphabetical)
void sortByName(Product inv[], int n) {
  for (int i = 0; i < n - 1; i++) {
    for (int j = 0; j < n - i - 1; j++) {
      if (inv[j].name > inv[j+1].name) {
        swap(inv[j], inv[j+1]);
      }
    }
  }
}

Key Observation

swap(inv[j], inv[j+1]) swaps the entire struct (all members together) — no risk of desynchronized parallel arrays!

Compare with V1.0.0 where you'd need to swap names[j], prices[j], AND quantities[j] separately.

Before vs After Sort (by price ascending)

Preview: Structs as Building Blocks for Abstract Data Types

Structs serve as the foundation for implementing Abstract Data Types (ADTs) — data structures where implementation details are separated from the interface.

struct Stack {
  int data[100];
  int top;  // Index of top element (-1 = empty)
};

void push(Stack &s, int value) { s.data[++s.top] = value; }
int pop(Stack &s)               { return s.data[s.top--]; }

Checkpoint — Quick Practice Quiz

Question 1

Given Product p = {"Laptop", 15000000, 5, "Electronics"}; and Product *ptr = &p; → Which syntax correctly accesses the product name via the pointer?

A. ptr.name  

B. ptr->name  

C. *ptr->name  

D. ptr::name

Question 2

What is the primary advantage of using a struct array over parallel arrays?

A. Structs use less memory  

B. Structs run faster  

C. All related data is grouped together, eliminating index synchronization issues  

D. Structs are required by the C++ standard

Question 3

When passing a large struct to a function for read-only display, what is the recommended parameter type?

A. Product p (by value)  

B. Product &p (by reference)   C. const Product &p (const reference)  

D. Product *p (by pointer)

Checkpoint — Answers & Explanations

Q1: Answer — B. ptr->name ✓

The arrow operator -> is used to access struct members through a pointer. ptr.name is incorrect because ptr is a pointer, not a struct variable. (*ptr).name also works but ptr->name is the standard shorthand.

Q2: Answer — C. All related data is grouped together ✓

With parallel arrays, you must ensure names[i], prices[i], and quantities[i] always correspond to the same product. With a struct array, products[i] contains ALL data for one product — impossible to desynchronize.

Q3: Answer — C. const Product &p ✓

const Product &p passes by reference (no copying cost) while preventing accidental modification (const). Pass by value creates an unnecessary copy; pass by non-const reference allows unintended modifications.

Chapter 2 Summary & Workshop MiniPOS V2.0.0

Knowledge Summary

MiniPOS V1.0.0 → V2.0.0 Refactoring

Homework

Chapter 2 Knowledge Summary

Systematic Review of All Content

Chapter 2 Core Knowledge Summary

(Part 1 of 2)

Chapter 2 Core Knowledge Summary

(Part 2 of 2)

Workshop: MiniPOS V2.0.0

Refactoring MiniPOS V1.0.0 → V2.0.0 with Functions & Structs

🛒 MiniPOS V2.0.0 — Refactoring with Functions & Structs

V1.0.0: 3 Separate Arrays

string names[100];
double prices[100];
int quantities[100];

V2.0.0: Struct + 4 Function Prototypes

struct Product {
  string name;
  double price;
  int quantity;
};
Product inventory[MAX];

// 4 Function Prototypes:
void printProduct(const Product &p, int index);
void addProduct(Product inv[], int &total);
void displayAll(const Product inv[], int total);
void searchProduct(const Product inv[], int total,
                   string keyword);

MiniPOS V2.0.0 — Function Signatures

Prototypes & Struct Definition

#include <iostream>
#include <limits>
#include <string>
using namespace std;

// Struct dinh nghia kieu du lieu San pham
struct Product {
  string name;
  double price;
  int quantity;
};

const int MAX = 100;

// Khai bao nguyen mau ham (Function Prototypes)
void printProduct(const Product &p, int index);
void addProduct(Product inv[], int &total);
void displayAll(const Product inv[], int total);
void searchProduct(const Product inv[],
                   int total, string keyword);

Parameter Design Decisions

MiniPOS V2.0.0 — Adding a Product

addProduct() Implementation

// 1. Ham them san pham (truyen tham chieu int &total de cap nhat so luong tong)
void addProduct(Product inv[], int &total) {
  if (total < MAX) {
    cin.ignore(numeric_limits::max(), '\n');

    cout << "Ten SP: ";
    getline(cin, inv[total].name);

    do {
      cout << "Gia (>0): ";
      cin >> inv[total].price;
    } while (inv[total].price <= 0);

    do {
      cout << "SL (>0): ";
      cin >> inv[total].quantity;
    } while (inv[total].quantity <= 0);

    total++;

    cout << "=> Them thanh cong!\n";
  } else {
    cout << "MANG DA DAY!\n";
  }
}

Key Design Points

int &total

Must be a reference — total must increase by 1 in the caller's scope (main()).

total < MAX check

Prevents writing beyond array bounds — essential safety guard.

do-while validation

Re-prompts until user enters a valid positive value for price and quantity.

cin.ignore()

Flushes entire input buffer before getline() to avoid the skipped-line bug.

MiniPOS V2.0.0 — printProduct, displayAll & searchProduct

printProduct(), displayAll() & searchProduct()

// Ham ho tro in thong tin 1 san pham (Dung chung cho Hien thi & Tim kiem)
void printProduct(const Product &p, int index) {
  cout << index << ". " << p.name << " | Gia: " << p.price
       << " | SL: " << p.quantity << endl;
}

// 2. Ham hien thi danh sach san pham (Read-only)
void displayAll(const Product inv[], int total) {
  cout << "\n--- DANH SACH SAN PHAM ---\n";
  if (total == 0) {
    cout << "Chua co san pham nao.\n";
  } else {
    for (int i = 0; i < total; i++) {
      printProduct(inv[i], i + 1);
    }
  }
}

// 3. Ham tim kiem san pham theo ten (Read-only)
void searchProduct(const Product inv[], int total, string keyword) {
  bool found = false;
  cout << "\n--- KET QUA TIM KIEM ---\n";
  for (int i = 0; i < total; i++) {
    if (inv[i].name.find(keyword) != string::npos) {
      printProduct(inv[i], i + 1);
      found = true;
    }
  }
  if (!found) {
    cout << "Khong tim thay san pham.\n";
  }
}

Design Observations

printProduct() — Shared Helper

Both displayAll() and searchProduct() call printProduct() — output format is defined once, no duplication.

const Product inv[]

Both displayAll and searchProduct are read-only — const prevents accidental modification of any product.

Empty-State Handling

displayAll shows "Chua co san pham nao." when empty; searchProduct shows "Khong tim thay san pham." when no match.

string::find() & npos

find() returns string::npos when keyword is not found; otherwise returns the start index — enabling partial name matching.

MiniPOS V2.0.0 — Why Struct Replaces Parallel Arrays

V1.0.0 — Parallel Arrays

// Three separate arrays — must update ALL three
string names[100];
double prices[100];
int quantities[100];
int total = 0;

void addProduct(string name, double price, int qty) {
  names[total]      = name;   // array 1
  prices[total]     = price;  // array 2
  quantities[total] = qty;    // array 3
  total++;
  // Miss one → data is out of sync!
}

V2.0.0 — Struct Approach

// One struct bundles all fields together
struct Product {
  string name;
  double price;
  int quantity;
};

Product inventory[100];
int total = 0;

void addProduct(Product inv[], int &total) {
  // inv[total] is ONE struct — all fields together
  getline(cin, inv[total].name);
  cin >> inv[total].price;
  cin >> inv[total].quantity;
  total++;
  // One record — impossible to desync!
}

Atomic Updates

Swap or assign one struct instead of remembering to update three separate arrays in sync.

🔒 Type Safety

All fields belong to the same object — no index mismatch where names[2] accidentally pairs with prices[3].

📖 Readability

inventory[i].name is self-documenting and clearer than the fragmented names[i] across multiple arrays.

MiniPOS V2.0.0 — Clean Main Function

main() — The Coordinator

int main() {
  Product inventory[MAX];
  int total = 0;
  int choice;

  do {
    cout << "\n===== MINIPOS V2.0.0 =====\n";
    cout << "1. Them san pham\n";
    cout << "2. Hien thi danh sach\n";
    cout << "3. Tim kiem\n";
    cout << "0. Thoat\n";
    cout << "Chon: ";

    cin >> choice;
    switch (choice) {

    case 1:
      addProduct(inventory, total);
      break;
    case 2:
      displayAll(inventory, total);
      break;
    case 3: {
      cin.ignore(numeric_limits<streamsize>::max(), '\n');
      string keyword;
      cout << "Nhap ten san pham can tim: ";
      getline(cin, keyword);
      searchProduct(inventory, total, keyword);
      break;
    }

    case 0:
      cout << "Dang thoat chuong trinh...\n";
      break;

    default:
      cout << "Lua chon khong hop le!\n";
    }
  } while (choice != 0);
  return 0;
}

Key Observations — Clean main()

Coordinator Role

main() displays the menu and delegates each task to a specific function — each case is a single function call.

~35 Lines vs ~150 Lines

Compare with V1.0.0 where each case had 10–20 lines of inline code. V2.0.0 main() calls only 3 functions directly — addProduct, displayAll, and searchProduct. printProduct is called internally by displayAll and searchProduct, not by main().

Easy to Extend

To add a new feature: write a new function and add a new case. No need to modify existing logic.

MiniPOS Evolution: V1.0.0 → V2.0.0

Discussion: What Limitations Remain in MiniPOS V2.0.0?

MiniPOS Project Roadmap: V1.0.0 → V5.0.0

Homework Assignments

  1. Extend MiniPOS V2.0.0: Add editProduct() and deleteProduct() functions to the existing codebase.
  1. Student Management Program: Create a struct-based program managing student records (ID, name, GPA, classification) with functions for add, display, search, sort by GPA, and calculate class statistics.
  1. Pre-read: Chapter 3 — Object-Oriented Programming (Classes, Encapsulation, Inheritance).

Checkpoint — Comprehensive Chapter Quiz

(Part 1 of 2)

Question 1

In MiniPOS V2.0.0, why is total passed by reference (int &total) in addProduct() but by value (int total) in displayAll()?

A. By reference is always faster  

B. addProduct must increment total in the caller; displayAll only reads it  

C. displayAll cannot use references  

D. No difference

Question 2

In addProduct(), why is cin.ignore(numeric_limits<streamsize>::max(), '\n') called before getline()?

A. To slow down the program  

B. To flush the newline left in the buffer by the previous cin >> choice  

C. getline() requires the buffer to be filled  

D. It is optional

Question 3

Given struct Student { string name; double gpa; }; and void display(Student s), what happens when display(s1) is called?

A. s1 passed directly — modifications affect s1  

B. A copy of s1 is created — modifications do NOT affect s1  

C. Only s1.name is passed  

D. Compile error

Checkpoint — Comprehensive Chapter Quiz

(Part 2 of 2)

Question 4

In searchProduct(), the parameter is const Product inv[]. What does const prevent?

A. Iterating over the array  

B. Accidentally modifying any product's data  

C. The keyword from being found  

D. The array from being passed

Question 5

In MiniPOS V2.0.0, why does addProduct() use a do-while loop for price input instead of a regular if check?

A. do-while runs faster  

B. To keep re-prompting until a valid positive price is entered  

C. Because if cannot check <= 0  

D. To display the prompt exactly twice

Checkpoint §2.4 — Answers & Explanations

(Part 1 of 2)

Q1: B. addProduct must increment total ✓

addProduct adds a new product and must increment the counter visible in main() — requires pass by reference. displayAll only reads the count to loop — no modification needed.

Q2: B. Flush the newline left in the buffer ✓

cin >> choice reads only the integer and leaves '\n' in the buffer. If getline() runs immediately after, it reads that leftover newline and appears to skip input. cin.ignore(max, '\n') discards everything up to and including the newline.

Q3: B. A copy of s1 is created ✓

Structs follow the same pass-by-value rules as primitive types. Without &, the function receives a complete copy. To modify the original, use Student &s.

Checkpoint — Answers & Explanations

(Part 2 of 2)

Q4: B. Prevents accidental modification ✓

const Product inv[] tells the compiler that searchProduct is read-only. If any line tried to assign inv[i].name = ..., the compiler would immediately report an error — catching the bug at compile time.

Q5: B. Re-prompts until valid input ✓

A do-while loop guarantees the body executes at least once, then checks the condition. If the user enters 0 or a negative value, the loop continues prompting. A plain if check would only reject invalid input once.

Chapter 2 — Key Takeaways

(Part 1 of 2)

Structured Programming = Functions + Data Separation

Break large programs into single-responsibility functions, each handling one task.

Functions: Prototype → Define → Call

Always declare a prototype before main(), define the implementation, and call with matching arguments.

Know Your Parameter Passing

Value → safe copy; Reference (&) → modify original; Pointer (*) → modify via address.

Chapter 2 — Key Takeaways

(Part 2 of 2)

Structs Group Related Data

Replace parallel arrays with structs to keep related fields together — safer, cleaner, more maintainable.

Scope Matters

Local variables live inside their function; prefer local over global to avoid hidden dependencies.

Overloading = Flexibility

Same function name with different parameter lists: calcPrice(p), calcPrice(p, discount), calcPrice(p, discount, max).

From Structured to Object-Oriented: What Changes in Chapter 3?

Chapter 2 — Structured

struct Product { string name; double price; };
void display(const Product &p) {
  cout << p.name << ": " << p.price;
}
display(laptop);

Chapter 3 — OOP

class Product {
private:
 string name; double price;
public:
 void display() {
 cout << name << ": " << price;
 }
};
laptop.display(); // Object "knows" how to display!

Recommended Learning Resources

📚 Reference Documentation

  • cppreference.com — Complete C++ standard library reference
  • learncpp.com — Free, comprehensive C++ tutorial (Chapters 2, 11, 13 cover functions and structs)
  • cplusplus.com/doc/tutorial/functions — Function tutorial with examples

💻 Practice Platforms

  • code.ptit.edu.vn — PTIT Online Judge for lab exercises
  • onlinegdb.com — Online C++ IDE with debugger
  • leetcode.com — Algorithm practice (use C++ filter)

📖 Coursebook

Nguyen Duy Phuong, Nguyen Manh Son — C++ Programming Language Coursebook, 2020

  • Ch.1 §1.2: Structured Programming overview
  • Ch.3 §3.1–§3.4: Struct data type (definition, operations, pointers, arrays, ADTs)

📗 Supplementary Reading

Robert C. Martin — Clean Code, Chapter 3: "Functions" (Prentice Hall, 2008)

"Functions should do one thing. They should do it well. They should do it only." — Robert C. Martin