A computer is an electronic machine that accepts data (input), processes it according to a list of instructions (a program), stores data and results, and produces information (output). Data means raw facts, such as the marks 72, 85 and 64. Information is processed data that has meaning, such as “the average mark is 73.67”.
Every computer, from a mobile phone to a bank server, follows the same basic model:
+---------+ +-------------------+ +----------+
| INPUT | ---> | PROCESSING | ---> | OUTPUT |
| keyboard| | CPU follows the | | screen, |
| mouse | | program's steps | | printer |
+---------+ +-------------------+ +----------+
^ |
| v
+-------------------+
| MEMORY / STORAGE |
+-------------------+
Think of a momo restaurant in Baneshwor. The customer’s order is the input. The kitchen staff follow a recipe (the program) to process raw ingredients. The fridge and store room are storage. The plate of momo delivered to the table is the output. The recipe is useless without the kitchen, and the kitchen does nothing without a recipe. In the same way, hardware needs software.
Hardware is the physical part of a computer that you can touch.
| Component | Job | Examples |
|---|---|---|
| Input devices | Send data into the computer | Keyboard, mouse, scanner, microphone |
| Central Processing Unit (CPU) | Executes instructions, does calculations and decisions | Intel Core i5, AMD Ryzen 5 |
| Main memory (RAM) | Holds the program and data while they are being used | 8 GB DDR4 RAM |
| Secondary storage | Keeps programs and files permanently | SSD, hard disk, USB flash drive |
| Output devices | Show or deliver results | Monitor, printer, speaker |
The CPU is the “brain”. It has three important parts:
RAM (Random Access Memory) is fast but volatile: its contents disappear when the power goes off. That is why you lose unsaved work during load-shedding. Secondary storage (SSD, hard disk) is slower but non-volatile: files stay after power off. ROM (Read-Only Memory) holds small start-up programs (firmware) that do not change often.
Memory is measured in bytes:
| Unit | Size |
|---|---|
| 1 bit | a single 0 or 1 |
| 1 byte | 8 bits |
| 1 KB (kilobyte) | 1024 bytes |
| 1 MB (megabyte) | 1024 KB |
| 1 GB (gigabyte) | 1024 MB |
| 1 TB (terabyte) | 1024 GB |
(Storage manufacturers often use 1000 instead of 1024, which is why a “500 GB” disk shows a little less in Windows.)
Software is a set of programs that tells the hardware what to do. It is of two main kinds:
A program is a set of instructions written to solve a specific problem. Programming is the activity of designing and writing programs.
Computers are built from electronic switches that are either OFF or ON, so internally everything is stored with just two digits, 0 and 1. This is the binary (base-2) system. Humans use decimal (base-10). Programmers also use octal (base-8) and hexadecimal (base-16, digits 0–9 and A–F) because they are short ways to write binary.
| Decimal | Binary | Octal | Hexadecimal |
|---|---|---|---|
| 0 | 0000 | 0 | 0 |
| 5 | 0101 | 5 | 5 |
| 8 | 1000 | 10 | 8 |
| 10 | 1010 | 12 | A |
| 15 | 1111 | 17 | F |
| 25 | 11001 | 31 | 19 |
Decimal to binary: divide by 2 repeatedly and write the remainders from bottom to top. For 25:
25 / 2 = 12 remainder 1 ^
12 / 2 = 6 remainder 0 |
6 / 2 = 3 remainder 0 | read upward
3 / 2 = 1 remainder 1 |
1 / 2 = 0 remainder 1 |
Answer: 25 (decimal) = 11001 (binary)
Binary to decimal: multiply each digit by its place value (1, 2, 4, 8, 16, …) and add. For 11001: 1×16 + 1×8 + 0×4 + 0×2 + 1×1 = 25.
Binary to hexadecimal: group bits in fours from the right. 1101 0110 → D 6 → D6.
You do not need to master number systems to learn C++, but you should understand why memory sizes are powers of 2 and why programmers sometimes print numbers in hexadecimal.
Here is a sneak preview of a C++ program that prints the number 25 in
three bases. You do not need to understand every word yet; you will
study cout in Input and Output with cin,
cout, getline and iomanip.
#include <iostream>
using namespace std;
int main() {
int number = 25;
cout << "Decimal : " << dec << number << endl;
cout << "Octal : " << oct << number << endl;
cout << "Hexadecimal : " << hex << uppercase << number << endl;
return 0;
}Decimal : 25
Octal : 31
Hexadecimal : 19When you double-click a program (or run it from the terminal), these steps happen:
hello.exe) is stored on
the disk.A second preview program shows the input–process–output model in action. It adds two fixed numbers (processing) and displays the result (output).
#include <iostream>
using namespace std;
int main() {
int marksMath = 72; // data stored in memory
int marksScience = 85; // data stored in memory
int total = marksMath + marksScience; // processing by the ALU
cout << "Total marks = " << total << endl; // output
return 0;
}Total marks = 157number is changed to 31?A programming language is a formal language with strict rules (called syntax) used to write instructions for a computer. Just as Nepali and English have grammar, a programming language has grammar, but it is much stricter: one missing semicolon can stop the whole program from being built.
Languages are usually grouped into three generations or levels:
| Level | What it looks like | Advantages | Disadvantages |
|---|---|---|---|
| Machine language (1GL) | 10110000 01100001 |
Runs directly on the CPU; fastest | Very hard to read and write; different for each CPU type |
| Assembly language (2GL) | MOV AL, 61h |
Uses short words (mnemonics); still very fast | Still CPU-specific; long programs for small tasks |
| High-level language (3GL) | total = math + science; |
Close to English and maths; portable; easy to learn | Must be translated; slightly less direct control |
Low-level languages (machine and assembly) are close to the hardware. High-level languages (C, C++, Java, Python) are close to the human way of thinking. C++ is a high-level language, but it also allows low-level control of memory, which is why it is sometimes called a “middle-level” language.
The CPU understands only machine language. Any program written in another language must be translated. The program you write is called source code; the translated machine-language result is called object code or machine code.
| Translator | Translates | How | Example |
|---|---|---|---|
| Assembler | Assembly → machine code | One assembly line → one machine instruction | NASM, GNU as |
| Compiler | Whole high-level program → machine code | Translates the whole file first, reports all errors, then produces an executable file | g++, clang++, MSVC |
| Interpreter | High-level program → executed line by line | Translates and runs one statement at a time; stops at the first error | Python, JavaScript in a browser |
| Linker | Joins object files and library code | Combines your object code with library code (for example the code of
cout) into one executable |
ld (used by g++) |
Compiler vs interpreter is a classic comparison that every programmer should know:
| Compiler | Interpreter |
|---|---|
| Translates the whole program at once | Translates one line at a time |
Produces a separate executable file (.exe) |
No separate executable is produced |
| Program runs faster after compiling | Program usually runs slower |
| Errors are listed after compiling the whole file | Stops at the first error it meets |
| Examples: C, C++ | Examples: Python, classic BASIC |
An analogy: a compiler is like a translator who translates a whole Nepali book into English and gives you the printed English book. An interpreter is like a live interpreter at a meeting, translating each sentence as it is spoken.
A loader (part of the OS) then copies the executable into memory to run it, as you saw in Computer Fundamentals.
| Year | Event |
|---|---|
| 1972 | Dennis Ritchie creates the C language at Bell Labs |
| 1979 | Bjarne Stroustrup starts “C with Classes” at Bell Labs |
| 1983 | The language is renamed C++ (“++” means “one more than C”) |
| 1985 | First commercial release and the book The C++ Programming Language |
| 1998 | First ISO standard: C++98 |
| 2011 | C++11: a major modern update (auto, nullptr, threads, and more) |
| 2014, 2017 | C++14 and C++17 (this tutorial uses C++17) |
| 2020, 2023 | C++20 and C++23 continue to add features |
C++ is used in operating systems, game engines (Unreal Engine), web browsers (Chrome, Firefox), databases, embedded systems (like small IoT boards), banking systems and competitive programming.
vector, algorithms like
sort, threads.Total and
total are different names.| C | C++ |
|---|---|
| Procedural only | Procedural and object-oriented (and generic) |
| No classes or objects | Classes, objects, inheritance, polymorphism |
Input/output with printf and scanf |
Adds stream I/O with cout and cin |
| No function overloading | Function and operator overloading |
Memory with malloc and free |
Adds new and delete, plus smart
pointers |
| No references | References (int &r = x;) |
| No namespaces | Namespaces (like std) |
File extension .c |
File extension .cpp |
The same “sum” program written in C++ style:
#include <iostream>
using namespace std;
int main() {
int a = 15, b = 27;
cout << "Sum of " << a << " and " << b << " is " << a + b << endl;
return 0;
}Sum of 15 and 27 is 42C++ can still use C’s printf through the
<cstdio> header. This shows that C++ grew out of
C:
#include <cstdio>
int main() {
int a = 15, b = 27;
printf("Sum of %d and %d is %d\n", a, b, a + b);
return 0;
}Sum of 15 and 27 is 42In this tutorial we use cout and cin,
because they are type-safe (the compiler checks the types for you) and
easy to extend.
In procedural programming, a program is a list of steps grouped into functions (procedures). Data and the functions that use the data are kept separately. C is a procedural language. It works well for small programs, but in large programs any function can change any data, which makes bugs hard to find.
In object-oriented programming (OOP), we model the program as a set of objects, just like things in the real world. An object combines data (for example, a bank account’s balance) and functions that work on that data (deposit, withdraw) into one unit called a class. OOP makes large programs easier to organise, reuse and protect.
| Procedural | Object-oriented |
|---|---|
| Focus on functions (what to do) | Focus on objects (who does it) |
| Data moves freely between functions | Data is hidden inside objects (encapsulation) |
| Top-down design | Bottom-up design, building from classes |
| Harder to reuse in big projects | Reuse through inheritance |
| Example: C | Example: C++, Java |
You will write procedural C++ in the first eight chapters (up to Pointers and Dynamic Memory) and move to OOP in the Classes and Objects, Inheritance and Polymorphism and Templates chapters.
#include <iostream.h> from very old
books. This header is not standard C++. Write
#include <iostream>.cout program in this
lesson if a = 8 and b = 12?Sum of 8 and 12 is 20. 6. Any two rows of
the procedural–OOP table.Many beginners open the editor and start typing immediately. This is like building a house in Kathmandu without a naksa (building plan): walls end up in the wrong place and must be broken down. Professional programmers first understand the problem and design a solution on paper. Only then do they write code. The computer cannot think for you; it only follows your instructions exactly. If your plan is wrong, the program will be wrong, no matter how perfect the typing is.
| Step | Name | What you do | Example: average of three marks |
|---|---|---|---|
| 1 | Understand / define the problem | Read carefully; identify inputs, outputs and conditions | Input: three marks. Output: the average |
| 2 | Analyse | Find the formula or method; note special cases | average = (m1 + m2 + m3) / 3; marks may have decimals |
| 3 | Design the solution | Write an algorithm, pseudocode or flowchart | Steps in plain language |
| 4 | Code | Translate the design into C++ | Write the .cpp file |
| 5 | Test and debug | Run with sample data; compare with hand-calculated answers; fix errors | 70, 80, 90 → 80 |
| 6 | Document and maintain | Add comments and notes; update when requirements change | Explain formula in a comment |
A useful habit is to write the IPO chart (Input–Process–Output) for every problem:
| Input | Process | Output |
|---|---|---|
| m1, m2, m3 | total = m1 + m2 + m3; average = total / 3 | average |
An algorithm is a finite, ordered list of clear steps that solves a problem. The word comes from the name of the Persian mathematician al-Khwarizmi. A cooking recipe, the steps to withdraw cash from an ATM, and the steps to fill a college admission form are all everyday algorithms.
Properties of a good algorithm:
Example 1: Algorithm to find the average of three marks
Step 1: Start
Step 2: Read m1, m2, m3
Step 3: total = m1 + m2 + m3
Step 4: average = total / 3
Step 5: Display average
Step 6: Stop
Example 2: Algorithm to calculate simple interest
Step 1: Start
Step 2: Read principal P, rate R (per year, in %), time T (in years)
Step 3: SI = (P * T * R) / 100
Step 4: Display SI
Step 5: Stop
Example 3: Algorithm to check whether a person can vote in Nepal (age 18 or above)
Step 1: Start
Step 2: Read age
Step 3: If age >= 18 then
Display "Eligible to vote"
Otherwise
Display "Not eligible to vote"
Step 4: Stop
Example 4: Algorithm to find the sum of numbers from 1 to N
Step 1: Start
Step 2: Read N
Step 3: sum = 0, i = 1
Step 4: Repeat while i <= N
sum = sum + i
i = i + 1
Step 5: Display sum
Step 6: Stop
Example 4 contains repetition (a loop). Example 3 contains a decision (selection). Every algorithm is built from just three basic control structures:
| Structure | Meaning | Example step |
|---|---|---|
| Sequence | Steps run one after another | Read, calculate, display |
| Selection | Choose one path based on a condition | If age >= 18 … otherwise … |
| Repetition (iteration) | Repeat steps while a condition is true | Repeat while i <= N |
Pseudocode (“false code”) is an informal, English-like description of a program. It looks like code but ignores exact language grammar. There is no single official standard, but these keywords are widely used:
| Purpose | Keywords |
|---|---|
| Input / output | INPUT (or READ), OUTPUT (or PRINT, DISPLAY) |
| Assignment | SET x TO 5, or x ← 5 |
| Selection | IF … THEN … ELSE … ENDIF |
| Repetition | WHILE … DO … ENDWHILE, FOR i ← 1 TO N … ENDFOR |
| Program boundary | BEGIN … END |
Pseudocode for simple interest:
BEGIN
INPUT principal, rate, time
SET interest TO (principal * time * rate) / 100
OUTPUT interest
END
Pseudocode for the sum from 1 to N:
BEGIN
INPUT N
SET sum TO 0
FOR i ← 1 TO N
SET sum TO sum + i
ENDFOR
OUTPUT sum
END
Indentation makes the structure visible: everything inside the FOR is indented.
Tracing means following an algorithm by hand with sample input, writing the value of each variable after each step. It is the best way to find mistakes before coding. Trace Example 4 with N = 4:
| Step | i | sum | Condition i <= N |
|---|---|---|---|
| Start of loop | 1 | 0 | true |
| After 1st pass | 2 | 1 | true |
| After 2nd pass | 3 | 3 | true |
| After 3rd pass | 4 | 6 | true |
| After 4th pass | 5 | 10 | false → exit |
Output: 10. Check: 1 + 2 + 3 + 4 = 10. Correct.
You will learn the details in the coming chapters, but it is motivating to see that each algorithm step becomes one or two lines of C++. Here is the simple-interest algorithm as a program. The user’s typed input is shown in the sample run.
#include <iostream>
using namespace std;
int main() {
double principal, rate, time; // Step 2: inputs
cout << "Enter principal (Rs.): ";
cin >> principal;
cout << "Enter rate (% per year): ";
cin >> rate;
cout << "Enter time (years): ";
cin >> time;
double interest = (principal * time * rate) / 100; // Step 3
cout << "Simple interest = Rs. " << interest << endl; // Step 4
return 0;
}Enter principal (Rs.): 50000
Enter rate (% per year): 8.5
Enter time (years): 3
Simple interest = Rs. 12750Hand check: 50000 × 3 × 8.5 / 100 = 12750. The program agrees with the algorithm.
And the sum from 1 to N (Example 4), using a while loop
that exactly mirrors “Repeat while i <= N”:
#include <iostream>
using namespace std;
int main() {
int n;
cout << "Enter N: ";
cin >> n;
int sum = 0;
int i = 1;
while (i <= n) {
sum = sum + i;
i = i + 1;
}
cout << "Sum of 1 to " << n << " = " << sum << endl;
return 0;
}Enter N: 4
Sum of 1 to 4 = 10The output 10 matches our trace table exactly.
SI = (P * T * R) / 100.sum = 0 before adding to it.i = i + 1 inside the loop). This breaks finiteness.A flowchart is a diagram that shows the steps of an algorithm using standard symbols connected by arrows. The arrows show the flow of control, that is, the order in which steps are carried out. A flowchart is a “map” of the program: just as a map of the Ring Road helps you see the whole route at a glance, a flowchart helps you see all the paths through a program, including decisions and loops.
Advantages:
Limitations:
| Symbol (shape) | Name | Use |
|---|---|---|
| Oval / rounded rectangle | Terminal | Start and Stop of the flowchart |
| Parallelogram | Input/Output | Read input or display output |
| Rectangle | Process | Calculation or assignment, e.g. sum = a + b |
| Diamond | Decision | A yes/no question, e.g. a > b ? Two exits |
| Arrow | Flow line | Shows the direction of flow |
| Small circle | On-page connector | Joins parts of a chart on the same page |
| Pentagon (home-plate shape) | Off-page connector | Continues the chart on another page |
| Rectangle with double side lines | Predefined process | Calls a sub-process (function) defined elsewhere |
In this tutorial we draw flowcharts as text. We use ( )
for terminals, / / for input/output, [ ] for
processes and < > for decisions.
( Start )
|
v
/ Read a, b /
|
v
[ sum = a + b ]
|
v
/ Display sum /
|
v
( Stop )
A number is even if the remainder after dividing by 2 is 0. In C++
the remainder operator is %.
( Start )
|
v
/ Read n /
|
v
< n % 2 == 0 ? >
|Yes |No
v v
/ Display / / Display /
/ "Even" / / "Odd" /
| |
+-----+-----+
|
v
( Stop )
The code follows the flowchart shape directly. The diamond becomes an
if … else.
#include <iostream>
using namespace std;
int main() {
int n;
cout << "Enter an integer: ";
cin >> n;
if (n % 2 == 0) {
cout << n << " is Even" << endl;
} else {
cout << n << " is Odd" << endl;
}
return 0;
}Enter an integer: 17
17 is OddAlgorithm:
Step 1: Start
Step 2: Read a, b, c
Step 3: If a > b then
If a > c then largest = a else largest = c
Otherwise
If b > c then largest = b else largest = c
Step 4: Display largest
Step 5: Stop
Flowchart:
( Start )
|
v
/ Read a, b, c /
|
v
< a > b ? >
Yes | | No
v v
< a > c ? > < b > c ? >
Yes| |No Yes| |No
v v v v
[large=a] [large=c] [large=b] [large=c]
| | | |
+-------+----+-----+-------+
|
v
/ Display large /
|
v
( Stop )
Code:
#include <iostream>
using namespace std;
int main() {
int a, b, c, largest;
cout << "Enter three numbers: ";
cin >> a >> b >> c;
if (a > b) {
if (a > c) {
largest = a;
} else {
largest = c;
}
} else {
if (b > c) {
largest = b;
} else {
largest = c;
}
}
cout << "Largest = " << largest << endl;
return 0;
}Enter three numbers: 45 92 67
Largest = 92Test with all orders (largest first, second, third) and with equal
values such as 5 5 3. Tracing 5 5 3:
a > b is false (5 is not greater than 5), so we check
b > c (5 > 3, true) → largest = 5. Correct.
A loop is drawn as an arrow that goes back up to an earlier decision.
( Start )
|
v
/ Read N /
|
v
[ sum = 0, i = 1 ]
|
v
+-----> < i <= N ? > ---No---+
| | Yes |
| v |
| [ sum = sum + i ] |
| | |
| v |
| [ i = i + 1 ] |
| | |
+------------+ v
/ Display sum /
|
v
( Stop )
The code for this flowchart is the while program you saw
in Problem-Solving Techniques, Algorithms and
Pseudocode.
| Algorithm step | Flowchart symbol | C++ code |
|---|---|---|
| Start | Terminal (oval) | int main() { |
| Read a, b | Parallelogram | cin >> a >> b; |
| sum = a + b | Rectangle | sum = a + b; |
| If a > b | Diamond | if (a > b) { … } else { … } |
| Repeat while i <= N | Diamond with back arrow | while (i <= n) { … } |
| Display sum | Parallelogram | cout << sum; |
| Stop | Terminal (oval) | return 0; } |
8 3 8?Largest = 8. 6. Easy to
understand, shows logic clearly / good documentation; limitation: large
charts become complex and hard to modify.// hello.cpp - my first C++ program
#include <iostream>
using namespace std;
int main() {
cout << "Namaste, Kathmandu!" << endl;
cout << "Welcome to C++ programming." << endl;
return 0;
}Namaste, Kathmandu!
Welcome to C++ programming.Let us study it line by line.
| Line | Name | Meaning |
|---|---|---|
// hello.cpp - my first... |
Comment | A note for humans. The compiler ignores everything after
// on that line |
#include <iostream> |
Preprocessor directive | Tells the preprocessor to insert the contents of the standard header
iostream, which declares cout,
cin and endl |
using namespace std; |
Using directive | Lets us write cout instead of std::cout
(explained below) |
int main() |
The main function | Every C++ program starts running at main.
int means it returns a whole number to the operating
system |
{ … } |
Braces (block) | Mark the start and end of the body of main |
cout << "…" << endl; |
Output statement | Sends text to the screen. << is the insertion
operator; endl ends the line |
return 0; |
Return statement | Ends main and reports “success” (0) to the operating
system |
Important details:
;. The
semicolon is like the full stop (।) at the end of a Nepali
sentence.Main,
COUT and Return are wrong."…" is a
string literal and is printed exactly as written.main only, if you leave out return 0;,
the compiler behaves as if it were there. We still write it for
clarity.void main() is not valid standard C++.
Always write int main().using namespace std;A namespace is a named “family” of names that
prevents clashes. There may be two people named Ram in the same group,
so we say “Ram Thapa” and “Ram Shrestha”. Similarly, all names from the
C++ standard library live in the namespace std (standard),
so the full name of cout is std::cout. The
:: is called the scope resolution
operator.
The line using namespace std; tells the compiler: “when
I write cout, look inside std too”. This is
convenient and fine for small beginner programs, and we use it
throughout this tutorial. In large professional projects, programmers
usually write std:: explicitly to avoid name clashes. Both
styles are correct:
#include <iostream>
int main() {
std::cout << "Written without using namespace std;" << std::endl;
return 0;
}Written without using namespace std;+-------------------------------------------+
| Documentation section (comments) | // name, purpose, date
+-------------------------------------------+
| Preprocessor directives | #include <iostream>
+-------------------------------------------+
| using directive / global declarations | using namespace std;
+-------------------------------------------+
| Function declarations (prototypes) | (Functions chapter)
+-------------------------------------------+
| int main() { ... } | execution starts here
+-------------------------------------------+
| Other function definitions | (Functions chapter)
+-------------------------------------------+
A slightly bigger program that uses variables and a calculation:
// bill.cpp - total cost of momo plates
// Author: Aarav Shrestha
#include <iostream>
using namespace std;
int main() {
int plates = 3;
int pricePerPlate = 180; // Rs. per plate
int total = plates * pricePerPlate;
cout << "Plates ordered : " << plates << endl;
cout << "Price per plate: Rs. " << pricePerPlate << endl;
cout << "Total bill : Rs. " << total << endl;
return 0;
}Plates ordered : 3
Price per plate: Rs. 180
Total bill : Rs. 540A build turns your source file
(hello.cpp) into an executable (hello.exe on
Windows, hello on Linux). The single command
g++ hello.cpp -o hello actually runs four stages:
hello.cpp +--------------+ expanded +----------+
(source) ---> | Preprocessor | ---> code -->| Compiler |
+--------------+ +----------+
|
hello.o (object code)
|
library code (iostream) ---+ v
+----> +--------+
| Linker |
+--------+
|
v
hello.exe (executable) --> Loader
runs it
| Stage | Input → output | What happens | g++ option to stop here |
|---|---|---|---|
| 1. Preprocess | .cpp → expanded source |
Handles lines starting with #: inserts header files,
replaces macros, removes comments |
g++ -E hello.cpp |
| 2. Compile | expanded source → object file .o |
Checks syntax and types, translates to machine code | g++ -c hello.cpp |
| 3. Link | .o + libraries → executable |
Joins your code with library code (for example the real code of
cout) |
g++ hello.o -o hello |
| 4. Execute | executable → running program | The OS loader puts the program in RAM and the CPU runs it | ./hello (Linux) or hello (Windows) |
In Code::Blocks, “Build” does stages 1–3 and “Run” does stage 4. In this tutorial we compile with:
g++ -std=c++17 -Wall hello.cpp -o hello
-std=c++17 selects the C++17 standard,
-Wall turns on most useful warnings, and
-o hello names the output file. A warning
does not stop the build, but it usually points to a real problem, so
treat warnings seriously and fix them.
A bug is a mistake in a program. Debugging is the process of finding and fixing bugs. Errors are classified by when they are detected.
| Error type | Detected by | When | Example |
|---|---|---|---|
| Syntax (compile-time) error | Compiler | During compiling | Missing ;, misspelt cout, missing
} |
| Linker error | Linker | During linking | Function declared but never defined; Main instead of
main |
| Runtime error | The running program / OS | While running | Division by zero, reading a missing file |
| Logic error | The programmer (by testing) | Program runs but gives wrong answer | Wrong formula, wrong condition |
1. Syntax error – the code breaks the grammar rules of C++.
#include <iostream>
using namespace std;
int main() {
cout << "Namaste, Kathmandu!" << endl
return 0;
}hello.cpp: In function 'int main()':
hello.cpp:6:42: error: expected ';' before 'return'
6 | cout << "Namaste, Kathmandu!" << endl
| ^
| ;
7 | return 0;
| ~~~~~~
How to read it: hello.cpp:6:42 means file
hello.cpp, line 6, column 42. The compiler
even shows where the ; is expected. Note that the compiler
often notices the problem at the next token, so also look at
the line before the one reported.
Undeclared names are another common syntax error:
#include <iostream>
using namespace std;
int main() {
int total = 0;
Cout << "Total = " << totl << endl;
return 0;
}hello.cpp:7:5: error: 'Cout' was not declared in this scope
hello.cpp:7:27: error: 'totl' was not declared in this scope; did you
mean 'total'?
Tip: always fix the first error first, then rebuild. One mistake can cause many later messages.
2. Linker error – the code compiles, but the linker cannot find something it needs.
#include <iostream>
using namespace std;
void showWelcome(); // declared but never defined
int main() {
showWelcome();
return 0;
}/usr/bin/ld: /tmp/cchFBwTu.o: in function `main':
hello.cpp:(.text+0x9): undefined reference to `showWelcome()'
collect2: error: ld returned 1 exit status
The words ld (the linker) and
undefined reference tell you it is a linker error. Writing
int Main() instead of int main() gives a
similar “undefined reference to main” (on Windows/MinGW it
may mention WinMain).
3. Runtime error – the program builds, but fails while running, usually because of bad input or an impossible operation.
#include <iostream>
using namespace std;
int main() {
int totalMarks = 450;
int students;
cout << "Enter number of students: ";
cin >> students;
int average = totalMarks / students; // crashes if students is 0
cout << "Average = " << average << endl;
return 0;
}Enter number of students: 0
Floating point exception (core dumped)
Integer division by zero is undefined behaviour in
C++: the standard does not say what happens. On Linux the program is
usually killed with the message above; on Windows it usually stops with
no output or a “has stopped working” message. The fix is to check the
input before dividing (you will learn if in The if and if–else Statements).
4. Logic error – the program runs and prints an answer, but the answer is wrong. The compiler cannot help; only careful testing finds it.
#include <iostream>
using namespace std;
int main() {
double m1 = 60, m2 = 70, m3 = 80;
double wrongAverage = m1 + m2 + m3 / 3; // logic error
double rightAverage = (m1 + m2 + m3) / 3; // correct
cout << "Wrong average: " << wrongAverage << endl;
cout << "Right average: " << rightAverage << endl;
return 0;
}Wrong average: 156.667
Right average: 70Division is done before addition, so the wrong version computes
60 + 70 + (80 / 3). The hand-calculated answer (70) exposes
the bug. This is why step 5 of problem solving (test with known answers)
is essential.
Programs affect real people: school results, bank balances, hospital records, electricity bills. A programmer therefore has ethical responsibilities from the very first day.
// Adapted from: textbook Example 4.2, and make sure you
understand every line.#include <iostream.h> or
void main() from very old books. Use
#include <iostream> and int main().; at the end of a statement, or putting
; after #include <iostream>
(preprocessor lines do not end with ;).“Hello”)
instead of straight quotes ("Hello").main function, where
execution begins.#include <iostream> gives access to
cout and cin;
using namespace std; lets us skip the std::
prefix.file:line:column: error: message, and fix the first error
first.#include <iostream>? Why
does it not end with a semicolon?cout << "Hi" << endl; so that it
works without using namespace std;.retrun 0; (b) the program
prints area = 0 for every rectangle (c) “undefined reference to
calculateTax()” (d) the program crashes when the user
enters 0.bill.cpp program if
plates = 5?cout,
cin, endl; it is a preprocessor directive, not
a C++ statement. 2.
std::cout << "Hi" << std::endl; 3. Preprocess,
compile, link, execute. 4. (a) syntax, (b) logic, (c) linker, (d)
runtime. 5. Plates ordered : 5 / Price per plate: Rs. 180 / Total bill :
Rs. 900. 6. It is academic dishonesty (presenting someone else’s work as
your own); you also do not learn the skill.A Nepali sentence is built from letters, letters form words, and words form sentences. A C++ program is built the same way:
characters --> tokens --> statements --> functions --> program
(letters) (words) (sentences) (paragraphs) (essay)
The character set is the set of characters that can be used to write C++ source code.
| Category | Characters |
|---|---|
| Letters | A–Z, a–z |
| Digits | 0–9 |
| Special symbols | + - * / % = < > ! & | ^ ~ ? : ; , . ' " ( ) [ ] { } # _ \ |
| White space | space, tab, new line |
White space separates tokens; the compiler ignores extra white space. That means these two lines are the same to the compiler:
int total=marks+bonus;
int total = marks + bonus;The second is much easier for humans to read, so always use spaces around operators. Inside a string literal, however, every space is printed exactly.
A token is the smallest meaningful unit of a program, like a word in a sentence. The compiler first breaks the source code into tokens. C++ has five kinds:
| Token type | Meaning | Examples |
|---|---|---|
| Keywords | Reserved words with fixed meaning | int, double, if,
return |
| Identifiers | Names chosen by the programmer | marks, totalBill, main |
| Literals (constants) | Fixed values written directly | 25, 3.14, 'A',
"Ram", true |
| Operators | Symbols that perform operations | +, -, *, =,
<<, == |
| Punctuators (separators) | Symbols that organise code | ;, ,, { }, ( ),
[ ], # |
Let us count the tokens in one statement:
int total = marks + 50 ;
1 2 3 4 5 6 7 -> 7 tokens
int is a keyword, total and
marks are identifiers, = and +
are operators, 50 is a literal, and ; is a
punctuator.
Keywords (reserved words) have a special meaning to
the compiler, so they cannot be used as names. All keywords are written
in lowercase. C++17 has about 84 keywords (the exact count depends on
how alternative spellings such as and are counted). You do
not need to memorise them all; you will learn them gradually. The ones
used in this tutorial:
| Group | Keywords |
|---|---|
| Data types | int, char, float,
double, bool, void,
short, long, signed,
unsigned, auto |
| Values | true, false, nullptr |
| Control | if, else, switch,
case, default, for,
while, do, break,
continue, goto, return |
| Constants and storage | const, constexpr, static,
extern |
| Memory | new, delete, sizeof |
| OOP | class, struct, public,
private, protected, this,
virtual, friend, operator,
template, typename, enum |
| Others | namespace, using, inline,
static_cast |
Note: main, cout, cin,
endl and std are not
keywords. main is the name of a special function;
cout and cin are objects defined in the
standard library. They are identifiers.
An identifier is a name given to a variable, function, class or other item.
Rules (the compiler enforces these):
_
only.-, @,
$, .) are allowed. (Some compilers, including
g++, accept $ as an extension, but it is not standard; do
not use it.)marks,
Marks and MARKS are three different
names._Total), or containing a double underscore
(my__var), are reserved for the compiler and library. Avoid
them.| Identifier | Valid? | Reason |
|---|---|---|
studentName |
Valid | Letters only |
marks_2025 |
Valid | Letters, underscore, digits |
_count |
Valid (but avoid leading _) |
Starts with underscore |
2ndSemester |
Invalid | Starts with a digit |
total marks |
Invalid | Contains a space |
net-salary |
Invalid | Contains - (would mean subtraction) |
float |
Invalid | Keyword |
Float |
Valid | Different from float (case-sensitive), but
confusing |
rate% |
Invalid | Contains % |
Good naming conventions (style, not rules):
electricityBill
is better than eb or x.totalMarks) or
snake_case (total_marks) consistently.
This tutorial uses camelCase.i,
j) or maths formulas (r for radius).#define MAX_STUDENTS 60), as covered in Variables, Data Types, sizeof, Literals and
Constants.This program uses valid and meaningful identifiers. Notice that
marks and Marks really are different
variables:
#include <iostream>
using namespace std;
int main() {
int marks = 75; // lowercase
int Marks = 90; // capital M: a different variable
int totalMarks = marks + Marks;
int student_count = 2;
cout << "marks = " << marks << endl;
cout << "Marks = " << Marks << endl;
cout << "totalMarks = " << totalMarks << endl;
cout << "student_count = " << student_count << endl;
return 0;
}marks = 75
Marks = 90
totalMarks = 165
student_count = 2(Using both marks and Marks in a real
program is bad style; it is shown here only to prove
case-sensitivity.)
An invalid identifier gives a compile error:
#include <iostream>
using namespace std;
int main() {
int 2ndSemester = 2;
int float = 3;
return 0;
}names.cpp:6:9: error: expected unqualified-id before numeric constant
6 | int 2ndSemester = 2;
| ^~~~~~~~~~~
names.cpp:7:15: error: expected unqualified-id before '=' token
7 | int float = 3;
| ^
An “unqualified-id” is simply a name. The first message says the
compiler expected a name but found a number (it reads
2ndSemester as starting with the number 2).
The second says a name was expected after int, but
float is a keyword, so it could not be a name. The lesson:
when an error message looks strange, look for a simple rule violation on
that line.
A comment is a note in the source code for human readers. The compiler removes comments during preprocessing, so they do not affect the program’s size or speed.
| Type | Syntax | Use |
|---|---|---|
| Single-line | // text until end of line |
Short notes, explanation of one line |
| Multi-line | /* text ... can span lines */ |
Headers, longer explanations |
/*
Program : area.cpp
Author : Sita Gurung
Purpose : Calculate the area of a circular garden
*/
#include <iostream>
using namespace std;
int main() {
double radius = 7.0; // radius in metres
double area = 3.14159 * radius * radius;
cout << "Area of garden = " << area << " square metres" << endl;
// cout << "This line is disabled and will not run" << endl;
return 0;
}Area of garden = 153.938 square metresThe last cout was “commented out”, a quick way to
disable a line while debugging.
Good commenting habits:
i = i + 1; // add 1 to i is useless;
rate = 0.13; // VAT rate in Nepal is helpful./* outer /* inner */ still here */ ends at the first
*/, which causes an error.int 1stMarks; → use
firstMarks or marks1.total marks,
net-pay → totalMarks,
netPay.int new = 5; or
double long;.totalMarks and later
writing TotalMarks.*/, which
comments out the rest of the file.a1, xx,
abc; it makes debugging much harder.main and
cout are not keywords.// for single-line and /* */ for
multi-line comments; comment the why.cout << "Total" << sum;? Name each.bus_fare, 2025batch, net salary,
Double, char, _rate.main a keyword? Is cout? Explain.#include <iostream>
using namespace std;
int main() {
int value = 10;
// value = 20;
/* value = 30; */
cout << value << endl;
return 0;
}bill = units * 12; // multiply units by 12.int, identifier total, literal
100, operator +, punctuator ;. 2.
Six tokens: cout (identifier), <<
(operator), "Total" (literal), <<
(operator), sum (identifier), ; (punctuator).
3. 2025batch (starts with digit), net salary
(space), char (keyword) are invalid; Double is
valid (case-sensitive) but confusing; _rate is valid but
leading underscores are best avoided. 4. No; main is the
special function where execution starts, and cout is a
standard library object; both are identifiers. 5. 10 (both
assignments are commented out). 6.
bill = units * 12; // Rs. 12 per unit (flat domestic rate).A variable is a named location in memory (RAM) that stores a value which can change while the program runs. Think of the lockers at a college library: each locker has a label (the name), a size (small or large, the data type) and contents (the value). You can take the contents out and put new contents in, but the label and size stay the same.
| Property | Meaning | Example: int marks = 85; |
|---|---|---|
| Name | Identifier used in the program | marks |
| Type | What kind of data, how much memory, which operations | int (whole number, typically 4 bytes) |
| Value | The data currently stored | 85 |
| Address | Location in memory (see the Pointers and Dynamic Memory chapter) | e.g. 0x7ffd5c3a |
Declaration tells the compiler the name and type:
int marks; Initialisation gives a first
value at the time of declaration: int marks = 85;
Assignment gives a new value later:
marks = 90;
int age; // declaration only (value is garbage!)
int year = 2025; // declaration with initialisation
double price = 180.50, discount = 0.1; // two variables at once
char grade{'A'}; // brace initialisation (C++11 and later)
age = 19; // assignmentImportant: a local variable that is declared but not
initialised contains an unpredictable “garbage” value. Reading it is
undefined behaviour. Always initialise variables. (-Wall
often warns about this: “may be used uninitialized”.)
| Type | Stores | Example values | Typical size (64-bit PC) |
|---|---|---|---|
int |
Whole numbers | 0, -45, 2025 |
4 bytes |
float |
Decimal numbers, single precision (about 6–7 significant digits) | 3.14f, -0.5f |
4 bytes |
double |
Decimal numbers, double precision (about 15–16 significant digits) | 3.14159265, 1.5e6 |
8 bytes |
char |
A single character (stored as a small integer code) | 'A', '7', '$' |
1 byte (always) |
bool |
Truth value | true, false |
1 byte (typically) |
void |
“No value” (used for functions; see the Functions chapter) | none | none |
Which to choose? Use int for counts
(students, units, days), double for money, measurements and
averages (prefer double over float because it
is more precise), char for single letters like grades, and
bool for yes/no facts like isPassed.
A char is really a small integer. The character
'A' is stored as the number 65 in ASCII (the American
Standard Code for Information Interchange). 'a' is 97 and
'0' is 48.
Modifiers change the size or sign of the basic types.
| Modifier | Effect | Example |
|---|---|---|
short |
Smaller integer (at least 2 bytes) | short int temperature; |
long |
Larger integer (at least 4 bytes) | long population; |
long long |
Even larger (at least 8 bytes) | long long nationalDebt; |
unsigned |
No negative values; doubles the positive range | unsigned int units; |
signed |
Allows negatives (default for int) |
signed int balance; |
long double |
Extended-precision decimal | long double pi; |
The C++ standard fixes only minimum sizes. The
actual sizes depend on the compiler and platform. The
sizeof operator tells you the size in bytes on
your system:
#include <iostream>
using namespace std;
int main() {
cout << "char : " << sizeof(char) << " byte" << endl;
cout << "bool : " << sizeof(bool) << " byte" << endl;
cout << "short : " << sizeof(short) << " bytes" << endl;
cout << "int : " << sizeof(int) << " bytes" << endl;
cout << "long : " << sizeof(long) << " bytes" << endl;
cout << "long long : " << sizeof(long long) << " bytes" << endl;
cout << "float : " << sizeof(float) << " bytes" << endl;
cout << "double : " << sizeof(double) << " bytes" << endl;
cout << "long double : " << sizeof(long double) << " bytes" << endl;
int marks = 80;
cout << "variable marks uses " << sizeof(marks) << " bytes" << endl;
return 0;
}The output below is from g++ on 64-bit Linux.
char : 1 byte
bool : 1 byte
short : 2 bytes
int : 4 bytes
long : 8 bytes
long long : 8 bytes
float : 4 bytes
double : 8 bytes
long double : 16 bytes
variable marks uses 4 bytesOn 64-bit Windows (MinGW or Visual C++),
long is usually 4 bytes, not 8, and
long double may be 16 (MinGW) or 8 (Visual C++). This is
exactly why we say “typically” and use sizeof instead of
guessing.
The range of a type follows from its size. With n bits, an unsigned type holds 0 to 2^n − 1, and a signed type holds −2^(n−1) to 2^(n−1) − 1.
| Type (typical size) | Typical range |
|---|---|
char (1 byte) |
−128 to 127 (or 0 to 255 if char is unsigned on that
platform) |
unsigned char |
0 to 255 |
short (2 bytes) |
−32,768 to 32,767 |
unsigned short |
0 to 65,535 |
int (4 bytes) |
−2,147,483,648 to 2,147,483,647 (about ±2.1 billion) |
unsigned int |
0 to 4,294,967,295 |
long long (8 bytes) |
about ±9.2 × 10^18 |
float (4 bytes) |
about ±3.4 × 10^38, 6–7 significant digits |
double (8 bytes) |
about ±1.7 × 10^308, 15–16 significant digits |
The header <climits> gives the exact limits for
your compiler:
#include <iostream>
#include <climits>
using namespace std;
int main() {
cout << "Smallest int : " << INT_MIN << endl;
cout << "Largest int : " << INT_MAX << endl;
cout << "Largest unsigned int : " << UINT_MAX << endl;
cout << "Largest short: " << SHRT_MAX << endl;
cout << "Largest long long : " << LLONG_MAX << endl;
return 0;
}Smallest int : -2147483648
Largest int : 2147483647
Largest unsigned int : 4294967295
Largest short: 32767
Largest long long : 9223372036854775807Nepal’s population (about 3 crore, i.e. 30 million) fits in an
int, but the world population (about 8 billion) does
not; use long long for it.
A literal is a fixed value written directly in the code.
| Kind | Examples | Notes |
|---|---|---|
| Integer | 25, -7, 0 |
Decimal |
012 (octal = 10), 0x1F (hex = 31),
0b101 (binary = 5, C++14) |
A leading 0 means octal! |
|
25u, 25L, 25LL |
Suffixes: unsigned, long, long long | |
1'000'000 |
Digit separator ' (C++14) for readability |
|
| Floating-point | 3.14, 0.5, 2.0,
1.5e3 (= 1500) |
Type double by default |
3.14f |
Suffix f makes it float |
|
| Character | 'A', '9', '\n' |
Single quotes, exactly one character |
| String | "Kathmandu", "Rs. 500" |
Double quotes; any number of characters |
| Boolean | true, false |
Printed as 1 and 0 by default |
Note the difference: '7' is a character (code 55),
7 is an integer, "7" is a string.
#include <iostream>
using namespace std;
int main() {
int decimalValue = 31;
int octalValue = 037; // leading 0 means octal
int hexValue = 0x1F; // 0x means hexadecimal
int binaryValue = 0b11111; // 0b means binary (C++14)
long long oneCrore = 10'000'000LL; // one crore
double speed = 1.5e3; // 1.5 x 10^3
char grade = 'A';
bool isPassed = true;
cout << decimalValue << " " << octalValue << " "
<< hexValue << " " << binaryValue << endl;
cout << "One crore = " << oneCrore << endl;
cout << "Speed = " << speed << endl;
cout << "Grade = " << grade << ", code = " << int(grade) << endl;
cout << "isPassed = " << isPassed << endl;
return 0;
}31 31 31 31
One crore = 10000000
Speed = 1500
Grade = A, code = 65
isPassed = 1All four integers are the same number, 31, written in different
bases. int(grade) shows the character’s code.
A constant is a value that cannot change after it is set. Constants make programs safer (no accidental change) and easier to update (change one line, not twenty).
1. const (preferred for beginners):
const double VAT_RATE = 0.13; // must be initialised
const int MAX_STUDENTS = 48;
VAT_RATE = 0.15; // ERROR: assignment of read-only variable2. constexpr (C++11): like
const, but guarantees that the value is known at compile
time. For simple constants such as
constexpr double PI = 3.14159; it behaves the same as
const. We mention it here; you will see it again later.
3. #define macro (older C style):
#define PI 3.14159 // no = sign, no semicolonThe preprocessor replaces every PI with
3.14159 before compiling. Macros have no type and ignore
scope, so modern C++ prefers const or
constexpr. You will still see #define in older
code.
| Feature | const |
constexpr |
#define |
|---|---|---|---|
| Has a type | Yes | Yes | No |
| Handled by | Compiler | Compiler (value computed at compile time) | Preprocessor (text replacement) |
| Obeys scope | Yes | Yes | No |
| Syntax | const int N = 5; |
constexpr int N = 5; |
#define N 5 |
A complete example using constants:
#include <iostream>
using namespace std;
#define SHOP_NAME "Bhatbhateni Mini Mart"
int main() {
const double VAT_RATE = 0.13; // 13% VAT in Nepal
constexpr int ITEMS = 3;
double pricePerItem = 450.0;
double amount = ITEMS * pricePerItem;
double vat = amount * VAT_RATE;
double total = amount + vat;
cout << SHOP_NAME << endl;
cout << "Amount : Rs. " << amount << endl;
cout << "VAT 13%: Rs. " << vat << endl;
cout << "Total : Rs. " << total << endl;
return 0;
}Bhatbhateni Mini Mart
Amount : Rs. 1350
VAT 13%: Rs. 175.5
Total : Rs. 1525.5If the VAT rate changes, only one line needs editing.
int total; total = total + 5; → start with
int total = 0;.int for money or averages:
int average = 245 / 3; loses the decimal part. Use
double.char:
char grade = "A"; is an error. Write
char grade = 'A';.int roll = 012; stores
10, because 012 is octal.#define PI = 3.14; → every PI
becomes = 3.14;. Correct:
#define PI 3.14.const without a value:
const int N; is an error.long is always 8 bytes. It is usually 4 bytes
on Windows. Use long long when you need 8 bytes.int, double (preferred over
float), char, bool; modifiers
short, long, unsigned,
signed.char (always 1
byte); use sizeof to check.25, 0x19, 3.5,
3.5f, 'A', "Ram",
true.const/constexpr over
#define for constants.int is always 4 bytes”? How can you
find its size on your machine?cout << 010 + 0x10 << endl;?const float pi; char name = "Ram"; int 2x = 5;FARE_PER_KM = 12.5 and prints the fare for 8 km.int x;; initialisation
int x = 5;; assignment x = 7;. 2. (a)
int, (b) double, (c) char, (d)
bool, (e) long long. 3. The standard only
fixes minimum sizes; actual size depends on compiler/platform; use
sizeof(int). 4. 010 = 8, 0x10 = 16 → prints
24. 5. const float pi; needs a value;
"Ram" is a string, not a char (use std::string
or 'R'); 2x starts with a digit. 6.
const double FARE_PER_KM = 12.5; cout << "Fare = Rs. " << FARE_PER_KM * 8 << endl;
→ Fare = Rs. 100.In C++, input and output are done through streams. A
stream is a flow of characters, like water flowing through a pipe. The
header <iostream> provides:
| Object | Meaning | Connected to |
|---|---|---|
cout |
Character output stream | Screen (standard output) |
cin |
Character input stream | Keyboard (standard input) |
cerr |
Error output stream | Screen (standard error, unbuffered) |
keyboard --> cin --(>>)--> variable (extraction: OUT of stream)
variable --(<<)--> cout --> screen (insertion: INTO stream)
A memory trick: the arrows point in the direction the data flows.
cout << x sends x towards cout;
cin >> x sends data from cin into x.
<< is the insertion operator. You
can chain many items in one statement; they are printed
left to right:
int age = 19;
cout << "Aarav is " << age << " years old." << endl;
// prints: Aarav is 19 years old.cout does not add spaces or new lines by itself. You
must include them.
A long statement can be split across lines. The semicolon comes only at the very end:
cout << "Name: " << name
<< ", Age: " << age
<< ", City: " << city << endl;Both move the cursor to the start of the next line. The difference:
endl |
'\n' (or "\n") |
|---|---|
| Inserts a new line and flushes the output buffer (forces text to appear immediately) | Inserts a new line only |
| Slower when printing thousands of lines | Faster |
A manipulator from <iostream> |
An escape sequence (a character) |
A buffer is a small temporary storage area.
cout collects characters in its buffer and sends them to
the screen in batches, which is efficient. Flushing empties the buffer
at once. For ordinary programs either choice is fine; for large outputs
prefer '\n'. (When the program waits for input with
cin, cout is flushed automatically, so your
prompt always appears before the user types.)
An escape sequence is a backslash \
followed by a character, used inside character or string literals to
represent special characters.
| Escape | Meaning | Example | Prints |
|---|---|---|---|
\n |
New line | "Ram\nSita" |
Ram, then Sita on the next line |
\t |
Horizontal tab | "Name\tMarks" |
Name, tab, Marks |
\\ |
Backslash | "C:\\labs" |
C:\labs |
\" |
Double quote | "He said \"Namaste\"" |
He said "Namaste" |
\' |
Single quote | '\'' |
' |
\a |
Alert (beep) | "\a" |
may beep (terminal-dependent) |
\0 |
Null character | used to end C-strings (see C-Style Strings) | nothing visible |
#include <iostream>
using namespace std;
int main() {
cout << "Name\tSubject\tMarks\n";
cout << "Sita\tMath\t92\n";
cout << "Ram\tNepali\t78\n";
cout << "Path: C:\\cpp-labs\\lab03\n";
cout << "Teacher said: \"Practice daily!\"" << endl;
return 0;
}Name Subject Marks
Sita Math 92
Ram Nepali 78
Path: C:\cpp-labs\lab03
Teacher said: "Practice daily!">> is the extraction operator. It
reads a value from the keyboard, converts it to the variable’s type and
stores it.
int age;
cin >> age; // one value
double length, breadth;
cin >> length >> breadth; // chaining: two valuesImportant behaviour of cin >>:
12 8 on one line or 12 and 8 on
separate lines; both work.string, it reads only one word.
Typing Sita Sharma stores just Sita;
Sharma stays in the input buffer for the next read.int), cin enters a fail
state: the variable is set to 0 (since C++11) and further reads
are ignored until the error is cleared. You will handle this with loops
in The do–while Loop and Input Validation.Always print a prompt before reading, so the user knows what to type.
To read text with spaces, such as a full name or an address, use
getline from <string>:
string fullName;
getline(cin, fullName); // reads everything up to the Enter keygetline reads all characters up to the new line, removes
the new line and stores the rest. The type string holds
text of any length (studied fully in The std::string
Class).
#include <iostream>
#include <string>
using namespace std;
int main() {
string fullName, address;
int age;
cout << "Enter your full name: ";
getline(cin, fullName);
cout << "Enter your address: ";
getline(cin, address);
cout << "Enter your age: ";
cin >> age;
cout << "\n--- Student Details ---\n";
cout << "Name : " << fullName << '\n';
cout << "Address: " << address << '\n';
cout << "Age : " << age << '\n';
return 0;
}Enter your full name: Sita Kumari Sharma
Enter your address: New Baneshwor, Kathmandu
Enter your age: 19
--- Student Details ---
Name : Sita Kumari Sharma
Address: New Baneshwor, Kathmandu
Age : 19A very common beginner bug: reading a number with
cin >> and then a line with getline. The
>> leaves the Enter key’s new-line character
'\n' in the buffer. The following getline sees
that '\n' immediately and returns an empty
string, so it seems to be “skipped”.
User types: 19⏎Sita Sharma⏎
cin >> age reads "19", leaves ⏎Sita Sharma⏎
getline reads up to the first ⏎ --> empty string!
Fix: before getline, skip the leftover
white space with cin >> ws; (the ws
manipulator discards white space), or discard the rest of the line with
cin.ignore(1000, '\n');.
#include <iostream>
#include <string>
using namespace std;
int main() {
int rollNo;
string name;
cout << "Enter roll number: ";
cin >> rollNo;
cout << "Enter full name: ";
cin >> ws; // skip the leftover newline
getline(cin, name);
cout << "Roll " << rollNo << ": " << name << endl;
return 0;
}Enter roll number: 12
Enter full name: Priya Rai
Roll 12: Priya RaiRemove the cin >> ws; line and try again: the name
will be empty.
<iomanip>A manipulator is a special value you insert into a
stream to change how the next items are printed. endl is
one. The header <iomanip> gives more:
| Manipulator | Effect | Lasts for |
|---|---|---|
setw(n) |
Print the next item in a field at least n characters wide | Next item only |
left / right |
Align text inside the field (default is right) | Until changed |
setfill(c) |
Fill empty space with character c (default space) | Until changed |
fixed |
Show decimal numbers in fixed-point notation (no
e) |
Until changed |
setprecision(n) |
With fixed: n digits after the decimal
point. Without fixed: n
significant digits in total |
Until changed |
The difference between setprecision with and without
fixed confuses many beginners, so let us see it:
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double value = 1234.56789;
cout << "Default : " << value << '\n';
cout << "setprecision(3) : " << setprecision(3) << value << '\n';
cout << fixed;
cout << "fixed, precision 3: " << value << '\n';
cout << "fixed, precision 2: " << setprecision(2) << value << '\n';
cout << "[" << setw(10) << 42 << "]" << '\n';
cout << "[" << left << setw(10) << 42 << "]" << '\n';
cout << "[" << right << setfill('*') << setw(10) << 42 << "]\n";
return 0;
}Default : 1234.57
setprecision(3) : 1.23e+03
fixed, precision 3: 1234.568
fixed, precision 2: 1234.57
[ 42]
[42 ]
[********42]Without fixed, setprecision(3) gives 3
significant digits, so 1234.56789 becomes 1.23e+03. With
fixed, it gives 3 decimal places. For money, always use
fixed << setprecision(2).
setw is the tool for aligned tables. Remember it applies
only to the next item, so repeat it for every column.
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main() {
cout << fixed << setprecision(2);
cout << left << setw(12) << "Name"
<< right << setw(8) << "Units" << setw(12) << "Bill (Rs.)" << '\n';
cout << "--------------------------------\n";
cout << left << setw(12) << "Aarav"
<< right << setw(8) << 120 << setw(12) << 120 * 9.5 << '\n';
cout << left << setw(12) << "Sita"
<< right << setw(8) << 75 << setw(12) << 75 * 9.5 << '\n';
cout << left << setw(12) << "Bikash"
<< right << setw(8) << 310 << setw(12) << 310 * 9.5 << '\n';
return 0;
}Name Units Bill (Rs.)
--------------------------------
Aarav 120 1140.00
Sita 75 712.50
Bikash 310 2945.00Text columns look best left-aligned, number columns right-aligned so the decimal points line up.
cin << x; or
cout >> x;. Remember: cout <<,
cin >>.cout << "Total:" << total; prints
Total:250; include a space inside the quotes.cin >> name; stores only
the first word. Use getline.getline right after cin >>
without cin >> ws; or cin.ignore(...),
giving an empty string.setw to apply to all following items. It
applies to the next item only.setprecision(2) for money
without fixed, which may print
1.2e+03 instead of 1234.57."C:\new"
contains \n (a new line!). Write
"C:\\new".cout << a << b; prints;
cin >> a >> b; reads. Both are in
<iostream>.endl = new line + flush; '\n' = new line
only.\n, \t, \\,
\", \'.cin >> reads one word and skips white space;
getline(cin, s) reads a whole line.cin >>, use cin >> ws;
before getline.<iomanip>: setw (next item only),
left/right, setfill,
fixed, setprecision.endl and
'\n'?cout statement that prints exactly:
She said "Ramro cha!" (with the quotes).Hari Bahadur for
cin >> name; where name is a
string. What is stored?#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double fare = 27.456;
cout << fixed << setprecision(1) << fare << endl;
cout << "[" << setw(5) << 7 << "]" << endl;
return 0;
}cin >> age; getline(cin, name);, and give a fix.endl also flushes the buffer; '\n' only
adds a new line. 2.
cout << "She said \"Ramro cha!\"" << endl; 3.
Hari (Bahadur remains in the buffer). 4.
27.5 then [ 7]. 5.
cin >> age leaves '\n' in the buffer;
getline reads up to it and returns empty. Fix:
cin >> ws; (or cin.ignore(1000, '\n');)
before getline. 6.
string item; double price; getline(cin, item); cin >> price; cout << item << ": Rs. " << fixed << setprecision(2) << price << endl;
(with <iomanip> and <string>
included).An operator is a symbol that performs an operation. An operand is a value the operator works on. An expression is a combination of operands and operators that produces a value.
total = price * quantity
\___/ \___/ ^ \______/
operand operand | operand
operator (*)
-x,
++count.a + b, a * b.?: (see
else-if Ladder, Nested if and the Conditional
Operator).Main operator groups in C++: arithmetic, relational, logical,
assignment, increment/decrement, bitwise, conditional, and some special
ones (sizeof, ,, ::). This lesson
covers arithmetic operators; Assignment,
Increment/Decrement, Type Conversion and Overflow covers assignment
and increment; Relational and Logical Operators
covers relational and logical operators.
| Operator | Meaning | Example (a = 17, b = 5) | Result |
|---|---|---|---|
+ |
Addition | a + b |
22 |
- |
Subtraction | a - b |
12 |
* |
Multiplication | a * b |
85 |
/ |
Division | a / b |
3 (integer division!) |
% |
Modulus (remainder) | a % b |
2 |
- (unary) |
Negation | -a |
-17 |
+ (unary) |
Plus (no change) | +a |
17 |
There is no power operator in C++. ^ is
the bitwise XOR operator, not “power”. Use pow from
<cmath> (see The cmath Library, Worked
Problems and Debugging) or multiply: r * r.
When both operands of / are integers,
the result is an integer: the fractional part is
discarded (truncated toward zero). It is
not rounded.
| Expression | Result | Why |
|---|---|---|
17 / 5 |
3 | 3.4 → 3 |
19 / 5 |
3 | 3.8 → 3 (not rounded to 4!) |
-17 / 5 |
-3 | truncated toward zero (since C++11) |
17.0 / 5 |
3.4 | one operand is double → real division |
17 / 5.0 |
3.4 | same |
1 / 2 |
0 | a famous trap |
The 1 / 2 trap: double half = 1 / 2; stores
0, because the division is done in integers before the
result is stored in half. Write 1.0 / 2.
a % b gives the remainder when a is
divided by b. It works only with integer operands (for
decimals, use fmod from <cmath>).
| Expression | Result |
|---|---|
17 % 5 |
2 |
20 % 5 |
0 |
3 % 7 |
3 (7 goes 0 times, remainder 3) |
-17 % 5 |
-2 (the sign follows the first operand, since C++11) |
17 % -5 |
2 |
x % 0 |
undefined behaviour (like division by zero) |
The rule (a / b) * b + a % b == a is always true for
integers (when b is not 0).
Common uses of / and %:
n % 2 == 0 means
even.n % 10. Remove
last digit: n / 10.n % 3 == 0 means n is
divisible by 3.135 / 60 hours and 135 % 60 minutes.#include <iostream>
using namespace std;
int main() {
int a = 17, b = 5;
cout << "a + b = " << a + b << endl;
cout << "a - b = " << a - b << endl;
cout << "a * b = " << a * b << endl;
cout << "a / b = " << a / b << " (integer division)" << endl;
cout << "a % b = " << a % b << " (remainder)" << endl;
cout << "17.0 / 5 = " << 17.0 / 5 << endl;
cout << "-17 / 5 = " << -17 / 5 << ", -17 % 5 = " << -17 % 5 << endl;
int number = 4735;
cout << "Last digit of " << number << " = " << number % 10 << endl;
cout << "Without last digit = " << number / 10 << endl;
return 0;
}a + b = 22
a - b = 12
a * b = 85
a / b = 3 (integer division)
a % b = 2 (remainder)
17.0 / 5 = 3.4
-17 / 5 = -3, -17 % 5 = -2
Last digit of 4735 = 5
Without last digit = 473When an expression has several operators, precedence decides which operator is applied first (like BODMAS in school maths). Associativity decides the order when operators have the same precedence.
| Precedence (high to low) | Operators | Associativity |
|---|---|---|
| 1 | ( ) parentheses |
innermost first |
| 2 | unary +, unary -, ++,
-- (prefix), ! |
right to left |
| 3 | *, /, % |
left to right |
| 4 | +, - (binary) |
left to right |
| 5 | <<, >> (also stream
insertion/extraction) |
left to right |
| 6 | <, <=, >,
>= |
left to right |
| 7 | ==, != |
left to right |
| 8 | && |
left to right |
| 9 | || |
left to right |
| 10 | ?: |
right to left |
| 11 | =, +=, -=, *=,
/=, %= |
right to left |
(This is a simplified table. Postfix ++/--
and some other operators are even higher; a full table is in any C++
reference.)
Key facts:
*, / and % have
equal precedence and are done left to
right.+ and - are equal and done left to right,
after *, /, %.Example 1: 10 + 20 * 3 - 8 / 2 (all
integers)
10 + 20 * 3 - 8 / 2
10 + 60 - 8 / 2 (* first, leftmost of * and /)
10 + 60 - 4 (/ next)
70 - 4 (+ and - left to right)
66
Example 2: 20 / 3 * 3 versus
20 * 3 / 3
20 / 3 * 3 -> 6 * 3 -> 18 (left to right; 20/3 = 6)
20 * 3 / 3 -> 60 / 3 -> 20
Same operators, different order, different answers because of integer division.
Example 3: 2 + 13 % 4 * 3 - 1
2 + 13 % 4 * 3 - 1
2 + 1 * 3 - 1 (% first: 13 % 4 = 1)
2 + 3 - 1 (* next)
5 - 1
4
Example 4: (5 + 3) * (10 - 4) / 4
(5 + 3) * (10 - 4) / 4
8 * 6 / 4 (parentheses first)
48 / 4 (left to right)
12
Example 5 (associativity of assignment):
a = b = 5; is evaluated right to left: first
b = 5, then a = b, so both become 5.
The program below prints the same expressions so you can check your hand calculations:
#include <iostream>
using namespace std;
int main() {
cout << "10 + 20 * 3 - 8 / 2 = " << 10 + 20 * 3 - 8 / 2 << endl;
cout << "20 / 3 * 3 = " << 20 / 3 * 3 << endl;
cout << "20 * 3 / 3 = " << 20 * 3 / 3 << endl;
cout << "2 + 13 % 4 * 3 - 1 = " << 2 + 13 % 4 * 3 - 1 << endl;
cout << "(5 + 3) * (10 - 4) / 4 = " << (5 + 3) * (10 - 4) / 4 << endl;
cout << "7 / 2 * 2.0 = " << 7 / 2 * 2.0 << endl;
cout << "7 / 2.0 * 2 = " << 7 / 2.0 * 2 << endl;
return 0;
}10 + 20 * 3 - 8 / 2 = 66
20 / 3 * 3 = 18
20 * 3 / 3 = 20
2 + 13 % 4 * 3 - 1 = 4
(5 + 3) * (10 - 4) / 4 = 12
7 / 2 * 2.0 = 6
7 / 2.0 * 2 = 7The last two lines show how the position of a single .0
changes the answer: in 7 / 2 * 2.0, the integer division
7 / 2 = 3 happens first.
Mathematical formulas must be written in a single line with explicit operators:
| Maths | C++ |
|---|---|
| (a + b) / 2 | (a + b) / 2.0 |
| ab + c | a * b + c |
| x² + 2x + 1 | x * x + 2 * x + 1 |
| (P × T × R) / 100 | (p * t * r) / 100 |
| 5(F − 32) / 9 | 5 * (f - 32) / 9 (with f a
double) |
| a / (b × c) | a / (b * c) (not a / b * c) |
Note: 5 / 9 * (f - 32) is always 0 if
written with integers, because 5 / 9 is 0. Write
5.0 / 9 * (f - 32) or 5 * (f - 32) / 9 with
f as double.
A shopkeeper in Asan must return Rs. 1,785 in change using the fewest notes of Rs. 1000, 500, 100, 50, 10, 5 and 1 coins. Integer division and modulus solve this neatly.
#include <iostream>
using namespace std;
int main() {
int amount;
cout << "Enter change to return (Rs.): ";
cin >> amount;
int n1000 = amount / 1000; amount = amount % 1000;
int n500 = amount / 500; amount = amount % 500;
int n100 = amount / 100; amount = amount % 100;
int n50 = amount / 50; amount = amount % 50;
int n10 = amount / 10; amount = amount % 10;
int n5 = amount / 5; amount = amount % 5;
int n1 = amount;
cout << "Rs. 1000 x " << n1000 << endl;
cout << "Rs. 500 x " << n500 << endl;
cout << "Rs. 100 x " << n100 << endl;
cout << "Rs. 50 x " << n50 << endl;
cout << "Rs. 10 x " << n10 << endl;
cout << "Rs. 5 x " << n5 << endl;
cout << "Rs. 1 x " << n1 << endl;
return 0;
}Enter change to return (Rs.): 1785
Rs. 1000 x 1
Rs. 500 x 1
Rs. 100 x 2
Rs. 50 x 1
Rs. 10 x 3
Rs. 5 x 1
Rs. 1 x 0Check: 1000 + 500 + 2×100 + 50 + 3×10 + 5 = 1785.
7 / 2 to give 3.5. With two ints
it gives 3. Use 7.0 / 2 or
static_cast<double>(7) / 2 (see Assignment, Increment/Decrement, Type Conversion and
Overflow).x^2 for x squared. ^ is XOR. Write
x * x or pow(x, 2).% with double: 5.5 % 2
is a compile error. Use fmod.a + b / 2 is not the average of
a and b; write (a + b) / 2.0.a / b * c when you mean
a / (b * c).2x or (a+b)(a-b)
are errors; write 2 * x and
(a + b) * (a - b).+ - * / %; unary -
and +.int / int gives an int (fraction
discarded, truncation toward zero).% gives the remainder and works only on integers; the
sign of the result follows the left operand.* / % →
+ - → … → assignment.int): (a) 25 / 4 (b)
25 % 4 (c) 4 % 25 (d)
-25 % 4.8 + 12 / 4 * 2 - 3 % 2.cout << 9 / 2 * 2 << " " << 9 / 2.0 * 2 << endl;int seconds = 4000;, write expressions for the
number of whole minutes and the remaining seconds.double c = 5 / 9 * (f - 32); wrong for
converting Fahrenheit to Celsius? Correct it.8 9. 4. (a) 0.5 * b * h (b)
(a + b) * (a + b) (c) (x + y + z) / 3.0. 5.
seconds / 60 → 66; seconds % 60 → 40. 6.
5 / 9 is integer division = 0, so c is always 0; use
double c = 5.0 / 9 * (f - 32);.= is the assignment operator. It
evaluates the expression on the right and stores the
result in the variable on the left. It is not
the mathematical “equals” sign.
int count = 5;
count = count + 1; // read count (5), add 1, store 6 back into countIn maths, count = count + 1 is impossible; in C++ it
means “the new value of count is the old value plus 1”. The left side
must be something that can store a value (a variable).
5 = count; is an error.
Assignment is itself an expression whose value is the assigned value,
and it is right-associative, so
a = b = c = 0; sets all three to 0 (evaluated as
a = (b = (c = 0))).
These are short forms that combine an arithmetic operation with assignment.
| Operator | Example | Same as | If x = 20 before, x after is |
|---|---|---|---|
+= |
x += 5; |
x = x + 5; |
25 |
-= |
x -= 5; |
x = x - 5; |
15 |
*= |
x *= 5; |
x = x * 5; |
100 |
/= |
x /= 3; |
x = x / 3; |
6 (integer division) |
%= |
x %= 3; |
x = x % 3; |
2 |
Careful: x *= a + b; means x = x * (a + b);
because the whole right side is evaluated first.
++ adds 1 and -- subtracts 1 from a
variable. They are used so often (especially in loops) that C++ gives
them their own operators.
| Form | Name | Meaning |
|---|---|---|
++x |
Prefix increment | Increase x first, then use the new value |
x++ |
Postfix increment | Use the current value, then increase x |
--x |
Prefix decrement | Decrease x first, then use the new value |
x-- |
Postfix decrement | Use the current value, then decrease x |
When used alone as a statement
(count++; or ++count;) both forms do exactly
the same thing. The difference appears only when the result is used
inside a bigger expression.
A memory trick: read left to right. In ++x you meet
++ first, so the increment happens first. In
x++ you meet x first, so the old value is used
first.
#include <iostream>
using namespace std;
int main() {
int a = 5;
int b = ++a; // a becomes 6, then b = 6
cout << "After b = ++a : a = " << a << ", b = " << b << endl;
int c = 5;
int d = c++; // d = 5, then c becomes 6
cout << "After d = c++ : c = " << c << ", d = " << d << endl;
int e = 10;
int f = e--; // f = 10, then e becomes 9
int g = --e; // e becomes 8, then g = 8
cout << "e = " << e << ", f = " << f << ", g = " << g << endl;
int visitors = 0;
visitors++; // used alone: same as ++visitors
visitors++;
cout << "Visitors today: " << visitors << endl;
return 0;
}After b = ++a : a = 6, b = 6
After d = c++ : c = 6, d = 5
e = 8, f = 10, g = 8
Visitors today: 2Trace table for the e, f,
g part:
| Statement | e | f | g |
|---|---|---|---|
int e = 10; |
10 | – | – |
int f = e--; |
9 | 10 | – |
int g = --e; |
8 | 10 | 8 |
Warning – undefined behaviour: never modify the same
variable twice in one expression, or modify it and read it separately in
the same expression, for example i = i++ + ++i; or
cout << i << i++;. The C++ standard either
leaves the result undefined or (in some C++17 cases) defines it in ways
that surprise even experts. Different compilers can give different
answers. Puzzle questions that ask for the output of such code have no
single correct answer; write clear code instead: one ++ per
statement.
A type conversion changes a value from one data type to another. C++ does this in two ways.
1. Implicit conversion (automatic, also called coercion)
bool, char, short --> int --> long --> long long
|
float --> double <-----------+
(if any operand is a decimal type)
Rules to remember:
char, short and bool are
promoted to int in arithmetic. So 'A' + 1 is
the int 66.double, the other is converted to
double. So 7 / 2.0 → 7.0 / 2.0 →
3.5.7 / 2 * 2.0, 7 / 2 is done first in
int (= 3), then 3 * 2.0 in double
(= 6.0).| Assignment | Stored value | Note |
|---|---|---|
double d = 7; |
7.0 | Safe widening |
int i = 9.99; |
9 | Fraction lost (truncated, not rounded) |
int i = -9.99; |
-9 | Truncated toward zero |
char c = 66; |
'B' |
66 is the ASCII code of B |
bool b = 25; |
true |
Any non-zero number becomes true |
Converting to a smaller or less precise type is called
narrowing. It may lose information. -Wall
does not warn about every narrowing, but brace initialisation does:
int i{9.99}; is an error. This is one
reason modern C++ likes {} initialisation.
2. Explicit conversion (casting)
When you want to convert on purpose, use a cast. The
modern C++ form is static_cast<type>(expression):
int total = 254, subjects = 3;
double avg1 = total / subjects; // 84 (wrong!)
double avg2 = static_cast<double>(total) / subjects; // 84.6667In avg1, integer division happens before the result is
stored, so the decimal part is lost. In avg2,
total is converted to double first, so the
division is real division.
The older C-style cast (double)total
and function-style cast double(total) also
work and appear in many textbooks. static_cast is preferred
because it is easy to find in code and the compiler checks it more
strictly.
#include <iostream>
using namespace std;
int main() {
int totalMarks = 254;
int subjects = 3;
double wrong = totalMarks / subjects;
double right = static_cast<double>(totalMarks) / subjects;
double cStyle = (double)totalMarks / subjects; // older style
cout << "Without cast : " << wrong << endl;
cout << "static_cast : " << right << endl;
cout << "C-style cast : " << cStyle << endl;
double price = 349.95;
int rupees = static_cast<int>(price); // drops .95
cout << "Whole rupees : " << rupees << endl;
char letter = 'A';
cout << "Code of A : " << static_cast<int>(letter) << endl;
cout << "Code 97 is : " << static_cast<char>(97) << endl;
cout << "'A' + 2 : " << letter + 2 << endl;
cout << "As a char : " << static_cast<char>(letter + 2) << endl;
return 0;
}Without cast : 84
static_cast : 84.6667
C-style cast : 84.6667
Whole rupees : 349
Code of A : 65
Code 97 is : a
'A' + 2 : 67
As a char : CNotice that letter + 2 prints 67, not C,
because the char was promoted to int. To print
a character, cast back to char.
Rounding instead of truncating: add 0.5 before
casting for positive numbers,
static_cast<int>(x + 0.5), or better, use
round(x) from <cmath> (see The cmath Library, Worked Problems and Debugging).
Every integer type has a maximum value (see Variables, Data Types, sizeof, Literals and Constants). If a calculation goes beyond it, overflow occurs.
int,
long long), overflow is undefined
behaviour. In practice on most PCs the value “wraps around” to
a large negative number, but the compiler is allowed to assume it never
happens, so results can be strange.0u - 1 is the largest
unsigned int, 4294967295.It is like the odometer of an old motorbike: after 99999 km it rolls over to 00000.
#include <iostream>
#include <climits>
using namespace std;
int main() {
int population = 30000000; // about 3 crore
int perPersonPaisa = 100;
long long correct = static_cast<long long>(population) * perPersonPaisa;
cout << "Using long long: " << correct << endl;
unsigned int units = 0;
units = units - 1; // unsigned wrap-around
cout << "0 - 1 as unsigned: " << units << endl;
cout << "INT_MAX = " << INT_MAX << endl;
long long big = INT_MAX;
big = big + 1; // fine in long long
cout << "INT_MAX + 1 as long long = " << big << endl;
return 0;
}Using long long: 3000000000
0 - 1 as unsigned: 4294967295
INT_MAX = 2147483647
INT_MAX + 1 as long long = 2147483648If we had written
int total = population * perPersonPaisa;, the true answer
3,000,000,000 is larger than INT_MAX (2,147,483,647), so
the int overflows. The fix is to do the multiplication in a
larger type: cast one operand to long long
before multiplying. Writing
long long total = population * perPersonPaisa; is
not enough, because the multiplication is still done in
int and only the (already wrong) result is converted.
Floating-point types do not overflow for everyday values, but they
have limited precision. 0.1 + 0.2 is not
exactly 0.3 in binary; it is 0.30000000000000004. That is
why we never compare decimals with == (see Relational and Logical Operators) and why banks store
money as whole paisa in integers.
= (assignment) with ==
(comparison; see Relational and Logical
Operators).x =+ 5; instead of x += 5;. The
first means x = +5; and compiles without error.double avg = sum / n; to give decimals when
sum and n are ints. Cast one
operand first.static_cast<double>(sum / n) still does integer
division first.long long x = a * b; with int a
and b and expecting no overflow. Cast before multiplying.++ on one variable in one expression,
such as i++ + ++i.unsigned variable; it
becomes a huge positive number.= stores the right-hand value in the left-hand
variable; it is right-associative.x += y means x = x + y; similarly
-=, *=, /=, %=.++x) changes then uses; postfix
(x++) uses then changes. Alone, they are the same.static_cast<type>(value) for explicit
conversions; cast an operand, not the finished result.int x = 10;, what is x after each separate
statement? (a) x += 4; (b) x *= 2 + 3; (c)
x %= 4; (d) x /= 4;#include <iostream>
using namespace std;
int main() {
int m = 3, n;
n = m++ * 2;
cout << m << " " << n << endl;
n = ++m * 2;
cout << m << " " << n << endl;
return 0;
}int a = 7.8; (b)
double b = 7 / 2; (c) double c = 7 / 2.0; (d)
char d = 'a' + 1;int p = 5, q = 2; cout << p / q;long long big = 50000 * 50000; a problem, and
how do you fix it?4 6 then 5 10.
3. (a) 7 (b) 3.0 (prints 3) (c) 3.5 (d) 'b'. 4.
cout << static_cast<double>(p) / q; 5.
50000 * 50000 is computed in int and overflows
(2.5 billion > INT_MAX) before being stored; write
50000LL * 50000 or cast one operand to
long long. 6. Implicit: automatic,
e.g. double d = 5; or 7 / 2.0; explicit:
programmer requests, e.g. static_cast<int>(3.9).<cmath> libraryA library function is a ready-made function supplied
with the compiler. To use the maths functions, write
#include <cmath>. A function is
called by writing its name followed by its
arguments in parentheses; it returns a
result.
double root = sqrt(144.0); // sqrt is called with argument 144.0
// it returns 12.0, which is stored in root| Function | Meaning | Example | Result |
|---|---|---|---|
sqrt(x) |
Square root (x ≥ 0) | sqrt(81.0) |
9 |
pow(x, y) |
x to the power y | pow(2.0, 10) |
1024 |
abs(x) |
Absolute value (int or double) | abs(-7) |
7 |
fabs(x) |
Absolute value of a double | fabs(-2.5) |
2.5 |
ceil(x) |
Round up to the next whole number | ceil(4.1) |
5 |
floor(x) |
Round down | floor(4.9) |
4 |
round(x) |
Round to nearest (halves away from zero) | round(4.5) |
5 |
trunc(x) |
Drop the fraction | trunc(-4.7) |
-4 |
sin(x), cos(x), tan(x) |
Trigonometry; x in radians | sin(0.0) |
0 |
log(x) |
Natural logarithm (base e) | log(1.0) |
0 |
log10(x) |
Logarithm base 10 | log10(1000.0) |
3 |
exp(x) |
e to the power x | exp(1.0) |
2.71828 |
fmod(x, y) |
Remainder for decimals | fmod(7.5, 2) |
1.5 |
hypot(x, y) |
sqrt(xx + yy) | hypot(3.0, 4.0) |
5 |
Notes:
double (for double
arguments). ceil(4.1) returns the double 5.0,
which prints as 5.PI constant
(M_PI exists on many compilers but is not standard; C++20
adds std::numbers::pi). We define our own:
const double PI = 3.14159265358979;sqrt of a negative number gives nan (“not
a number”), and log(0) gives -inf. These are
signs of a bug or bad input.x * x is simpler and exact;
pow works with decimals and may introduce tiny rounding
errors for large integers.#include <iostream>
#include <cmath>
using namespace std;
int main() {
const double PI = 3.14159265358979;
cout << "sqrt(144) = " << sqrt(144.0) << endl;
cout << "pow(2, 10) = " << pow(2.0, 10) << endl;
cout << "pow(27, 1.0/3) = " << pow(27.0, 1.0 / 3) << endl;
cout << "abs(-15) = " << abs(-15) << endl;
cout << "ceil(4.1) = " << ceil(4.1) << endl;
cout << "floor(4.9) = " << floor(4.9) << endl;
cout << "round(4.5) = " << round(4.5) << endl;
cout << "round(-4.5) = " << round(-4.5) << endl;
cout << "sin(30 deg) = " << sin(30 * PI / 180) << endl;
cout << "log10(1000) = " << log10(1000.0) << endl;
cout << "hypot(3, 4) = " << hypot(3.0, 4.0) << endl;
cout << "sqrt(-4) = " << sqrt(-4.0) << endl;
return 0;
}sqrt(144) = 12
pow(2, 10) = 1024
pow(27, 1.0/3) = 3
abs(-15) = 15
ceil(4.1) = 5
floor(4.9) = 4
round(4.5) = 5
round(-4.5) = -5
sin(30 deg) = 0.5
log10(1000) = 3
hypot(3, 4) = 5
sqrt(-4) = -nan(sqrt(-4) prints -nan with g++ on Linux;
other systems may print nan or -nan(ind).)
For every problem in this lesson we follow the same five steps: (1) IPO chart, (2) formula, (3) choose types, (4) code with prompts and formatted output, (5) test with a hand-calculated case.
Circle: area = π r², circumference = 2 π r.
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main() {
const double PI = 3.14159265358979;
double radius;
cout << "Enter radius of the circular pond (m): ";
cin >> radius;
double area = PI * pow(radius, 2);
double circumference = 2 * PI * radius;
cout << fixed << setprecision(2);
cout << "Area = " << area << " sq. m" << endl;
cout << "Circumference = " << circumference << " m" << endl;
return 0;
}Enter radius of the circular pond (m): 3.5
Area = 38.48 sq. m
Circumference = 21.99 mHand check: 3.14159 × 12.25 ≈ 38.48. Correct.
Simple interest and compound interest: SI = P × T × R / 100; amount with yearly compounding A = P × (1 + R/100)^T.
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main() {
double principal, rate, years;
cout << "Principal (Rs.): ";
cin >> principal;
cout << "Rate (% per year): ";
cin >> rate;
cout << "Time (years): ";
cin >> years;
double simpleInterest = principal * years * rate / 100;
double compoundAmount = principal * pow(1 + rate / 100, years);
double compoundInterest = compoundAmount - principal;
cout << fixed << setprecision(2);
cout << "Simple interest : Rs. " << simpleInterest << endl;
cout << "Compound interest : Rs. " << compoundInterest << endl;
return 0;
}Principal (Rs.): 100000
Rate (% per year): 10
Time (years): 2
Simple interest : Rs. 20000.00
Compound interest : Rs. 21000.00Hand check: SI = 100000 × 2 × 10 / 100 = 20000. CI = 100000 × 1.1² − 100000 = 21000.
Temperature: F = C × 9 / 5 + 32 and C = (F − 32) × 5 / 9.
Seconds to h:m:s: hours = total / 3600; remaining =
total % 3600; minutes = remaining / 60; seconds = remaining % 60. These
use integer division and modulus, so we use int.
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double celsius;
cout << "Temperature in Kathmandu today (C): ";
cin >> celsius;
double fahrenheit = celsius * 9 / 5 + 32;
cout << celsius << " C = " << fahrenheit << " F" << endl;
int totalSeconds;
cout << "Bus journey time in seconds: ";
cin >> totalSeconds;
int hours = totalSeconds / 3600;
int remaining = totalSeconds % 3600;
int minutes = remaining / 60;
int seconds = remaining % 60;
cout << "Journey time = " << hours << ":"
<< setfill('0') << setw(2) << minutes << ":"
<< setw(2) << seconds << " (h:mm:ss)" << endl;
return 0;
}Temperature in Kathmandu today (C): 24.5
24.5 C = 76.1 F
Bus journey time in seconds: 22350
Journey time = 6:12:30 (h:mm:ss)Hand check: 22350 / 3600 = 6 h, remainder 750; 750 / 60 = 12 min, remainder 30 s → 6:12:30.
The distance between two points (x1, y1) and (x2, y2) is sqrt((x2 −
x1)² + (y2 − y1)²). A school trip needs
ceil(students / seats) buses, since a partly filled bus is
still a bus.
#include <iostream>
#include <cmath>
using namespace std;
int main() {
double x1 = 2, y1 = 3, x2 = 8, y2 = 11;
double distance = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
cout << "Distance between points = " << distance << " km" << endl;
int students = 130, seatsPerBus = 42;
int busesWrong = students / seatsPerBus;
int busesNeeded = static_cast<int>(
ceil(static_cast<double>(students) / seatsPerBus));
cout << "Integer division gives " << busesWrong << " buses (wrong)"
<< endl;
cout << "Buses needed = " << busesNeeded << endl;
return 0;
}Distance between points = 10 km
Integer division gives 3 buses (wrong)
Buses needed = 4Note that ceil(students / seatsPerBus) would still be
wrong, because 130 / 42 is integer division (3) before
ceil sees it. The operand must be converted to
double first.
Debugging is a skill you will use for the rest of your career. A systematic method:
;,
missing #include (for example, using sqrt
without <cmath>, or setw without
<iomanip>), misspelt names, unmatched
{ } or ( ).cout lines to see what each variable holds:double fahrenheit = celsius * 9 / 5 + 32;
cout << "DEBUG celsius=" << celsius << " fahrenheit=" << fahrenheit << endl;Debugging table: common errors in programs from the Variables, Data Types, Operators and I/O chapter
| Symptom | Likely cause | Fix |
|---|---|---|
'sqrt' was not declared in this scope |
Missing header | #include <cmath> |
'setw' was not declared in this scope |
Missing header | #include <iomanip> |
'getline' ... no matching function or
'string' was not declared |
Missing header | #include <string> |
| Average always a whole number | Integer division | static_cast<double>(sum) / n |
| Celsius always 0 | 5 / 9 is 0 |
5.0 / 9 |
| Huge or negative result | Overflow | Use long long and cast before multiplying |
| Name input “skipped” | '\n' left by cin >> |
cin >> ws; before getline |
Output nan |
sqrt or log of invalid value |
Check input and formula |
| Random large number printed | Uninitialised variable | Initialise all variables |
invalid operands ... to binary operator% |
% used with double |
Use int or fmod |
A buggy program and its fixed version illustrate the method. The buggy version compiles but gives the wrong answer (a logic error):
#include <iostream>
using namespace std;
int main() {
// BUGGY: average of three marks and conversion to percentage of 300
int m1 = 70, m2 = 75, m3 = 81;
double average = (m1 + m2 + m3) / 3;
double percent = (m1 + m2 + m3) / 300 * 100;
cout << "Average = " << average << endl;
cout << "Percent = " << percent << endl;
return 0;
}Average = 75
Percent = 0Expected (by hand): total = 226, average = 75.33, percent = 75.33.
The printed average lost its fraction, and the percentage is 0 because
226 / 300 is 0 in integer division. The fix:
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
// FIXED version
int m1 = 70, m2 = 75, m3 = 81;
int total = m1 + m2 + m3;
double average = static_cast<double>(total) / 3;
double percent = total * 100.0 / 300;
cout << fixed << setprecision(2);
cout << "Average = " << average << endl;
cout << "Percent = " << percent << endl;
return 0;
}Average = 75.33
Percent = 75.33#include <cmath>. Some compilers
accept sqrt anyway because another header included it, but
that is not portable; always include what you use.sin/cos:
sin(30) means 30 radians. Convert:
sin(30 * PI / 180).pow(x, 1/3) for a cube root; 1/3
is 0, so the result is 1. Write pow(x, 1.0 / 3) (or
cbrt(x)).ceil(a / b) with integer a and b; the division is
already truncated. Convert first.setfill('0') << setw(2).cout lines too early, or forgetting to
remove them before submission.#include <cmath> for sqrt,
pow, abs, fabs,
ceil, floor, round,
sin, cos, log,
log10, hypot, fmod.PI
constant in C++17./ and % with integers solve time and unit
conversions.ceil(7.01) (b)
floor(-2.5) (c) round(2.5) (d)
pow(5.0, 3) (e) sqrt(0.25).sin(90) not equal to 1? Correct it.'pow' was not declared in this scope.
What is the fix?#include <iostream>
#include <cmath>
using namespace std;
int main() {
int pages = 250, pagesPerDay = 30;
cout << pages / pagesPerDay << " "
<< ceil(pages / 30.0) << endl;
return 0;
}a and b.sin expects radians;
90 radians ≠ 90°. Use sin(90 * PI / 180). 3.
int t = 10000; cout << t / 3600 << " h " << t % 3600 / 60 << " m " << t % 60 << " s";
→ 2 h 46 m 40 s. 4. Add #include <cmath>. 5.
8 9. 6. double c = sqrt(a * a + b * b); (or
hypot(a, b)).Think about how you decide what to do in the morning: “If it is raining, I take an umbrella.” The part “it is raining” is a condition. A condition is a question whose answer is either yes or no. In C++ we say the answer is either true or false.
A computer can only make a decision if we give it a condition that it can check. C++ gives us two families of operators for writing conditions:
The result of every condition is a value of type
bool.
In Variables, Data Types, sizeof, Literals and
Constants you met the bool data type. A
bool variable can hold only two values: true
and false. These are keywords in C++.
When you print a bool with cout, C++ prints
1 for true and 0 for false by default. If you
want the words true and false, send the
manipulator boolalpha to cout first. Also
remember that C++ converts numbers to bool automatically:
zero becomes false, and any non-zero value becomes
true. This rule will be important later.
#include <iostream>
using namespace std;
int main() {
bool isRaining = true;
bool hasUmbrella = false;
cout << "isRaining = " << isRaining << '\n';
cout << "hasUmbrella = " << hasUmbrella << '\n';
cout << boolalpha; // from now on print true/false as words
cout << "isRaining = " << isRaining << '\n';
cout << "hasUmbrella = " << hasUmbrella << '\n';
bool fromNumber = 25; // any non-zero value becomes true
bool fromZero = 0; // zero becomes false
cout << "bool(25) = " << fromNumber << '\n';
cout << "bool(0) = " << fromZero << '\n';
return 0;
}isRaining = 1
hasUmbrella = 0
isRaining = true
hasUmbrella = false
bool(25) = true
bool(0) = falseA relational operator compares two values and gives true
or false.
| Operator | Meaning | Example (a = 10, b = 20) | Result |
|---|---|---|---|
< |
less than | a < b |
true |
<= |
less than or equal to | a <= 10 |
true |
> |
greater than | a > b |
false |
>= |
greater than or equal to | b >= 20 |
true |
== |
equal to | a == b |
false |
!= |
not equal to | a != b |
true |
Note carefully:
== (two equal signs) compares.
= (one equal sign) assigns. Mixing them up
is the most famous beginner mistake in C and C++. You will study it
fully in Problem Solving with Selection and Common
Errors.<=, >=,
==, !=) must be written with no
space between the characters. < = is a syntax
error.=< or => operator in
C++. Always write the < or > first.When you print a relational expression with cout, put it
inside brackets, because << has higher precedence
than <, >, == and the
others:
cout << (a < b) << '\n'; // correct: prints 1
cout << a < b << '\n'; // wrong: does not compile#include <iostream>
using namespace std;
int main() {
int sitaMarks = 72;
int ramMarks = 65;
cout << boolalpha;
cout << "sitaMarks < ramMarks : " << (sitaMarks < ramMarks) << '\n';
cout << "sitaMarks > ramMarks : " << (sitaMarks > ramMarks) << '\n';
cout << "sitaMarks <= 72 : " << (sitaMarks <= 72) << '\n';
cout << "ramMarks >= 40 : " << (ramMarks >= 40) << '\n';
cout << "sitaMarks == ramMarks : " << (sitaMarks == ramMarks) << '\n';
cout << "sitaMarks != ramMarks : " << (sitaMarks != ramMarks) << '\n';
char grade = 'B';
cout << "grade == 'B' : " << (grade == 'B') << '\n';
cout << "'A' < 'B' : " << ('A' < 'B') << '\n';
return 0;
}sitaMarks < ramMarks : false
sitaMarks > ramMarks : true
sitaMarks <= 72 : true
ramMarks >= 40 : true
sitaMarks == ramMarks : false
sitaMarks != ramMarks : true
grade == 'B' : true
'A' < 'B' : trueCharacters are compared using their character codes (on almost every
system these are ASCII codes: 'A' is 65, 'B'
is 66, 'a' is 97). So 'A' < 'B' is true,
and every capital letter is “less than” every small letter.
float and double values are stored in
binary, and many decimal fractions (such as 0.1) cannot be stored
exactly. So a calculation that “should” give 0.3 may give
0.30000000000000004. Comparing such values with == can give
a surprising false. For now, the safe rule is: do
not use == with double results of
calculations. Instead, check whether the difference is very
small, for example fabs(x - 0.3) < 1e-9
(fabs is in <cmath>).
Often one comparison is not enough. To get a scholarship, a student may need marks of at least 80 and attendance of at least 90%. To enter a cinema at half price, a person must be a child or a senior citizen. C++ has three logical operators:
| Operator | Name | Meaning |
|---|---|---|
&& |
logical AND | true only if both sides are true |
| || | logical OR | true if at least one side is true |
! |
logical NOT | reverses the value: true becomes false, false becomes true |
(The OR operator is two vertical-bar characters, found above the Enter key on most keyboards.)
A truth table lists every possible combination of inputs and the result. Let A and B be two conditions.
| A | B | A && B | A || B |
|---|---|---|---|
| false | false | false | false |
| false | true | false | true |
| true | false | false | true |
| true | true | true | true |
| A | !A |
|---|---|
| false | true |
| true | false |
An easy way to remember: AND is strict (everybody must agree), OR is generous (one “yes” is enough).
In mathematics we write 40 <= marks <= 100. In C++
this does not mean what you think. C++ first evaluates
40 <= marks, which gives true (1) or
false (0). Then it compares that 0 or 1 with 100, which is
always true! The correct way is to join two comparisons with
&&:
if (40 <= marks <= 100) // WRONG: always true
if (marks >= 40 && marks <= 100) // RIGHTC++ evaluates && and || from
left to right, and it stops as soon as the
answer is known. This is called short-circuit
evaluation.
A && B: if A is false, the whole result
must be false, so B is not evaluated at all.A || B: if A is true, the whole result must be
true, so B is not evaluated at all.This is not just a speed trick; it lets us write safe conditions. Consider dividing by a number that might be zero:
if (count != 0 && total / count > 50) // safe: division only if count != 0If count is 0, the left side is false, and the division
is never performed, so the program does not crash. The next program
proves that the right-hand side is skipped. It uses an increment inside
the condition only to show the effect; in real programs, avoid
side effects like this inside conditions.
#include <iostream>
using namespace std;
int main() {
int checks = 0;
bool result;
result = (5 > 10) && (++checks > 0); // left is false: right skipped
cout << boolalpha << "AND result: " << result
<< ", checks = " << checks << '\n';
result = (5 < 10) || (++checks > 0); // left is true: right skipped
cout << "OR result: " << result << ", checks = " << checks << '\n';
result = (5 < 10) && (++checks > 0); // left is true: right evaluated
cout << "AND result: " << result << ", checks = " << checks << '\n';
int count = 0, total = 300;
bool goodAverage = (count != 0) && (total / count > 50);
cout << "goodAverage: " << goodAverage << " (no division by zero)\n";
return 0;
}AND result: false, checks = 0
OR result: true, checks = 0
AND result: true, checks = 1
goodAverage: false (no division by zero)checks stayed 0 after the first two lines, which proves
the right-hand sides were never run.
We now extend the precedence table from Arithmetic Operators, Precedence and Expression Evaluation. Higher rows are evaluated first.
| Level | Operators | Associativity |
|---|---|---|
| 1 (highest) | !, unary -, ++,
-- |
right to left |
| 2 | *, /, % |
left to right |
| 3 | +, - |
left to right |
| 4 | <<, >> |
left to right |
| 5 | <, <=, >,
>= |
left to right |
| 6 | ==, != |
left to right |
| 7 | && |
left to right |
| 8 | || | left to right |
| 9 (lowest) | =, +=, -=, etc. |
right to left |
So arithmetic happens first, then comparison, then AND, then OR.
Example with a = 5, b = 3, c = 8:
a + b > c || a * 2 == 10 && !(c < b)
= 8 > 8 || 10 == 10 && !(false) arithmetic first
= false || true && true comparisons
= false || true && before ||
= true
Because && has higher precedence than
||, the expression A || B && C means
A || (B && C). When in doubt, add
brackets — they cost nothing and make the meaning clear to
every reader.
Sometimes you need the opposite of a condition. De Morgan’s laws tell you how:
| Original | Opposite (NOT of it) |
|---|---|
| A && B | !A || !B |
| A || B | !A && !B |
Example: “passed” means
marks >= 40 && attendance >= 80. So “not
passed” is marks < 40 || attendance < 80. In words:
you fail if your marks are low or your attendance is
low.
#include <iostream>
using namespace std;
int main() {
int age;
double percentage;
cout << "Enter age: ";
cin >> age;
cout << "Enter +2 percentage: ";
cin >> percentage;
bool ageOk = (age >= 17 && age <= 25);
bool marksOk = (percentage >= 45.0);
bool eligible = ageOk && marksOk;
cout << boolalpha;
cout << "Age within 17-25? " << ageOk << '\n';
cout << "Percentage >= 45? " << marksOk << '\n';
cout << "Eligible for BIT? " << eligible << '\n';
cout << "Not eligible? " << !eligible << '\n';
return 0;
}Enter age: 19
Enter +2 percentage: 52.5
Age within 17-25? true
Percentage >= 45? true
Eligible for BIT? true
Not eligible? falseEnter age: 27
Enter +2 percentage: 68
Age within 17-25? false
Percentage >= 45? true
Eligible for BIT? false
Not eligible? trueWe have stored each condition in a well-named bool
variable. This makes long conditions much easier to read. In the next
lesson, The if and if–else Statements, you will use
such conditions with if to actually choose what to
do.
= instead of ==.
if (x = 5) assigns 5 to x and is always true. Write
if (x == 5).0 < x < 10 is
wrong; write x > 0 && x < 10.=< or =>. Correct:
<= and >=.! =,
& &. Write !=, &&
with no space.& or |. These are
bitwise operators, not logical ones; they do not short-circuit.
Use && and ||.cout << a > b; fails to compile. Write
cout << (a > b);.double values with
==.true or
false (type bool).<, <=,
>, >=, ==,
!=.&& (both), ||
(at least one), ! (reverse).cout prints 1/0 unless you use boolalpha.&& and || short-circuit: the right
side is evaluated only if needed.! > arithmetic > relational >
equality > && > || >
assignment. Use brackets for clarity.= and
==?int a = 4, b = 9;, what is printed by
cout << (a > b) << (a != b) << (a * 2 < b);?!(a > 2) || b == 9 && a < 3 for
a = 4, b = 9. Show the steps.(x != 0) && (y / x > 2),
what happens when x is 0? Why is this safe?temperature is between 15 and 30 (both inclusive).age >= 18 && hasCitizenship.= assigns a value; == compares two values
and gives true/false. 2. 011 (false, true, true). 3.
!(true) = false; b == 9 = true;
a < 3 = false; true && false =
false; false || false = false (0). 4. The
left side is false, so the division is never evaluated (short-circuit);
no division by zero. 5.
temperature >= 15 && temperature <= 30. 6.
age < 18 || !hasCitizenship.Every program is built from three basic control structures:
| Structure | Meaning | Example in daily life |
|---|---|---|
| Sequence | Do statements one after another | Wake up, brush teeth, eat breakfast |
| Selection | Choose a path depending on a condition | If it rains, take an umbrella |
| Repetition (loop) | Repeat statements while a condition holds | Keep walking until you reach college |
So far we have used only sequence. This chapter is about selection, also called decision making or branching. The Control Structures: Loops chapter covers repetition.
A control statement is a statement that decides
which statement runs next. The simplest one is if.
Syntax:
if (condition)
statement;Rules:
if is written in small letters.( ).if.Flowchart of a simple if:
|
v
/-----------\ false
< condition >-----------+
\-----------/ |
| true |
v |
+-------------+ |
| statement | |
+-------------+ |
| |
v |
+<-----------------+
|
v
next statement
Example: a shop in New Road gives a free carry bag when the bill is Rs. 1000 or more.
#include <iostream>
using namespace std;
int main() {
double bill;
cout << "Enter bill amount (Rs.): ";
cin >> bill;
if (bill >= 1000)
cout << "Congratulations! You get a free carry bag.\n";
cout << "Thank you for shopping with us.\n";
return 0;
}Enter bill amount (Rs.): 1250
Congratulations! You get a free carry bag.
Thank you for shopping with us.Enter bill amount (Rs.): 600
Thank you for shopping with us.In the second run the condition is false, so only the last message is
printed. Notice that “Thank you” is printed in both
runs, because it is not controlled by the if.
An if controls exactly one statement.
If you need to run several statements when the condition is true, put
them inside curly braces { }. A group of statements in
braces is called a compound statement or a
block, and C++ treats the whole block as one
statement.
if (bill >= 1000) {
discount = bill * 0.05;
bill = bill - discount;
cout << "5% discount given.\n";
}Look at what happens if you forget the braces:
if (bill >= 1000)
discount = bill * 0.05;
bill = bill - discount; // NOT part of the if!
cout << "5% discount given.\n"; // NOT part of the if!The indentation looks as though all three lines belong to
the if, but the compiler ignores indentation. Only the
first line is controlled. The other two always run. For this reason many
professional style guides say: always use braces, even for one
statement. We follow this rule from now on in most
examples.
A simple if either does something or does nothing. Very
often we want to do one thing when the condition is true, and a
different thing when it is false. For that we use
if–else.
Syntax:
if (condition) {
statements-if-true; // the "if block"
} else {
statements-if-false; // the "else block"
}Exactly one of the two blocks runs — never both, never neither.
Flowchart:
|
v
true /-----------\ false
+------< condition >------+
| \-----------/ |
v v
+-------------+ +-------------+
| if block | | else block |
+-------------+ +-------------+
| |
+------------>+<------------+
|
v
next statement
Example: pass or fail. Suppose the pass mark in a subject is 40 out of 100.
#include <iostream>
using namespace std;
int main() {
int marks;
cout << "Enter marks (0-100): ";
cin >> marks;
if (marks >= 40) {
cout << "Result: PASS\n";
cout << "Well done!\n";
} else {
cout << "Result: FAIL\n";
cout << "You need " << (40 - marks) << " more marks to pass.\n";
}
return 0;
}Enter marks (0-100): 67
Result: PASS
Well done!Enter marks (0-100): 31
Result: FAIL
You need 9 more marks to pass.Even or odd. A whole number is even if the remainder
after dividing by 2 is 0. We use the modulus operator %
from Arithmetic Operators, Precedence and Expression
Evaluation.
#include <iostream>
using namespace std;
int main() {
int number;
cout << "Enter an integer: ";
cin >> number;
if (number % 2 == 0) {
cout << number << " is even.\n";
} else {
cout << number << " is odd.\n";
}
return 0;
}Enter an integer: -7
-7 is odd.This works for negative numbers too: in C++ (since C++11)
-7 % 2 is -1, which is not 0, so -7 is
correctly reported as odd. This is why we test
number % 2 == 0 rather than number % 2 == 1 —
the second test would wrongly call -7 “not odd”.
Larger of two numbers.
#include <iostream>
using namespace std;
int main() {
int first, second;
cout << "Enter two integers: ";
cin >> first >> second;
int larger;
if (first > second) {
larger = first;
} else {
larger = second;
}
cout << "The larger number is " << larger << '\n';
return 0;
}Enter two integers: 45 82
The larger number is 82(If the two numbers are equal, first > second is
false and larger gets second, which is the
same value — so the answer is still correct.)
Because any non-zero value converts to true, you will
sometimes see code like if (count) which means
if (count != 0). Both are correct, but the second form is
clearer for beginners. Likewise, if (!found) means
if (found == false) — here the short form is actually
preferred for bool variables, because it reads like
English: “if not found”.
When a condition is long, store it in a well-named bool
variable first. The if then reads almost like an English
sentence.
#include <iostream>
using namespace std;
int main() {
double units;
char paidOnTime;
cout << "Units consumed this month: ";
cin >> units;
cout << "Paid within 7 days? (y/n): ";
cin >> paidOnTime;
bool earlyPayment = (paidOnTime == 'y' || paidOnTime == 'Y');
double bill = units * 10.0; // flat Rs. 10 per unit (example)
if (earlyPayment) {
double rebate = bill * 0.02; // 2% rebate for early payment
bill = bill - rebate;
cout << "Early payment rebate: Rs. " << rebate << '\n';
} else {
cout << "No rebate.\n";
}
cout << "Amount to pay: Rs. " << bill << '\n';
return 0;
}Units consumed this month: 150
Paid within 7 days? (y/n): y
Early payment rebate: Rs. 30
Amount to pay: Rs. 1470Notice how the program accepts both 'y' and
'Y' using ||. (The rate and rebate here are
simple example values, not the real NEA tariff; the lesson Problem Solving with Selection and Common Errors builds
a proper slab-based bill.)
A trace (or dry run) is when you follow the program
by hand, writing down the value of each variable. Tracing is the most
important debugging skill. Trace this fragment with
x = 7:
int y = 0;
if (x > 5) {
y = x * 2;
} else {
y = x + 2;
}
y = y + 1;| Step | Statement | x | y | Note |
|---|---|---|---|---|
| 1 | int y = 0; |
7 | 0 | |
| 2 | x > 5 |
7 | 0 | 7 > 5 is true, take the if block |
| 3 | y = x * 2; |
7 | 14 | else block is skipped |
| 4 | y = y + 1; |
7 | 15 | runs in every case |
Final value: y = 15. With x = 3, the else
block runs: y = 5, then y = 6.
{ on the same line as
if / else (as in this tutorial) or on the next
line — either is fine, but be consistent.} else { on one line so the else is
clearly attached to its if.if (marks >= 40); ends the if immediately
with an empty statement. The next line always runs. Wrong:
if (marks >= 40); cout << "Pass"; — right:
if (marks >= 40) cout << "Pass";.if marks >= 40 is a syntax error. The brackets are
required.else (marks < 40) is wrong. else never has
a condition; it covers “everything else”. (A condition needs
else if, covered in the next lesson.)= instead of ==:
if (choice = 1) is always true.If, Else
are not keywords. C++ is case-sensitive.if runs a statement only when its condition is
true.if–else chooses exactly one of two blocks.{ } to group
several statements into a block.if (condition); else
has no condition.What is the difference between a simple if and an
if–else?
What is printed when n = 4?
if (n > 5)
cout << "A";
cout << "B";
cout << "C";What is printed when age = 15?
if (age >= 18); cout << "Adult";
Write an if–else that prints “Positive or zero” if
num >= 0, otherwise “Negative”.
Write a program that reads the price of an item and the money given by a customer, then prints the change, or prints “Not enough money” if the money is less than the price.
Trace the fragment in the “Tracing” section with
x = 5. What is the final value of y?
if does something or nothing;
if–else always does one of two things. 2. BC
(only cout << "A" belongs to the if). 3.
Adult — the semicolon makes the if empty. 4.
if (num >= 0) { cout << "Positive or zero"; } else { cout << "Negative"; }
5. Read price, given;
if (given >= price) cout << "Change: Rs. " << given - price; else cout << "Not enough money";
6. x > 5 is false (5 is not greater than 5), so
y = 7, then y = 8.if–else gives two paths. But many problems have more
than two outcomes. Marks are converted to grades A, B, C, D or F. A
number is positive, negative or zero. A traffic light is red, yellow or
green. For these we use a chain of if–else statements
called an else-if ladder.
Syntax:
if (condition1) {
block1;
} else if (condition2) {
block2;
} else if (condition3) {
block3;
} else {
default block; // runs if no condition above was true
}How it works:
else block runs. The
final else is optional; without it, nothing happens when
all conditions are false.So at most one block of the ladder is executed
(exactly one if there is a final else).
Flowchart (three conditions):
/------\ true +--------+
< cond1 >------->| block1 |----------------------+
\------/ +--------+ |
| false |
/------\ true +--------+ |
< cond2 >------->| block2 |--------------------->+
\------/ +--------+ |
| false |
/------\ true +--------+ |
< cond3 >------->| block3 |--------------------->+
\------/ +--------+ |
| false |
+------------+ |
| else block |----------------------------------->+
+------------+ |
v
next statement
Example: positive, negative or zero.
#include <iostream>
using namespace std;
int main() {
int number;
cout << "Enter an integer: ";
cin >> number;
if (number > 0) {
cout << number << " is positive.\n";
} else if (number < 0) {
cout << number << " is negative.\n";
} else {
cout << "The number is zero.\n";
}
return 0;
}Enter an integer: 0
The number is zero.Here is a grading scheme (a simplified version of a percentage-to-grade table):
| Percentage | Grade |
|---|---|
| 80 and above | A |
| 65 to below 80 | B |
| 50 to below 65 | C |
| 40 to below 50 | D |
| below 40 | F |
Because the ladder stops at the first true condition, we can test from the highest range downwards and write only the lower limit each time:
#include <iostream>
using namespace std;
int main() {
double percent;
cout << "Enter percentage: ";
cin >> percent;
char grade;
if (percent >= 80) {
grade = 'A';
} else if (percent >= 65) { // here we already know percent < 80
grade = 'B';
} else if (percent >= 50) {
grade = 'C';
} else if (percent >= 40) {
grade = 'D';
} else {
grade = 'F';
}
cout << "Grade: " << grade << '\n';
return 0;
}Enter percentage: 72.5
Grade: BWhen we reach percent >= 65, the program already
knows that percent >= 80 was false, so there is no
need to write
percent >= 65 && percent < 80.
Now see what happens if the order is wrong:
if (percent >= 40) grade = 'D'; // 72.5 >= 40 is true...
else if (percent >= 50) grade = 'C'; // ...so these are never
else if (percent >= 65) grade = 'B'; // reached for 72.5
else if (percent >= 80) grade = 'A';
else grade = 'F';With 72.5 the first test is already true and the student gets a D.
Every student with 40 or more gets a D. This is a logic
error: the program compiles and runs, but gives the wrong
answer. Rule: when you test only the lower limit, start from the
largest value (or, if you start from the smallest, test the
upper limit with <).
A good program checks that the input makes sense before using it. We can put the “invalid” case at the top of the ladder:
if (percent < 0 || percent > 100) {
cout << "Invalid percentage!\n";
} else if (percent >= 80) {
...A nested if is an if (or
if–else) written inside another if or
else block. We use it when a second question makes sense
only after the first answer is known.
Example: a student gets the scholarship only if she passed (marks at least 40). Among students who passed, those with 80 or more get a full scholarship and the others get a half scholarship.
#include <iostream>
using namespace std;
int main() {
int marks;
cout << "Enter marks: ";
cin >> marks;
if (marks >= 40) {
cout << "Passed. ";
if (marks >= 80) {
cout << "Full scholarship.\n";
} else {
cout << "Half scholarship.\n";
}
} else {
cout << "Failed. No scholarship.\n";
}
return 0;
}Enter marks: 85
Passed. Full scholarship.Enter marks: 55
Passed. Half scholarship.Another classic nested example: the largest of three numbers.
#include <iostream>
using namespace std;
int main() {
int a, b, c;
cout << "Enter three integers: ";
cin >> a >> b >> c;
int largest;
if (a >= b) {
if (a >= c) {
largest = a;
} else {
largest = c;
}
} else {
if (b >= c) {
largest = b;
} else {
largest = c;
}
}
cout << "Largest = " << largest << '\n';
return 0;
}Enter three integers: 12 45 30
Largest = 45The same problem can also be written as a ladder with
&&:
if (a >= b && a >= c) largest = a;
else if (b >= a && b >= c) largest = b;
else largest = c;Both are correct. The ladder is shorter; the nested version performs fewer comparisons in some cases. Choose whichever is clearer to you.
Look at this code, where braces are missing:
if (marks >= 40)
if (marks >= 80)
cout << "Distinction\n";
else
cout << "Failed\n";The indentation suggests that else belongs to the first
if (marks >= 40). But the C++ rule is:
An
elsealways belongs to the nearest precedingifin the same block that does not already have anelse.
So the else actually belongs to
if (marks >= 80). With marks = 60, the
program prints “Failed” — clearly wrong. With marks = 30,
it prints nothing. This is called the dangling else
problem. The cure is simple: use braces.
if (marks >= 40) {
if (marks >= 80) {
cout << "Distinction\n";
}
} else {
cout << "Failed\n";
}(g++ -Wall warns about the unbraced version with the
message “suggest explicit braces to avoid ambiguous ‘else’”. Take
compiler warnings seriously.)
C++ has one operator with three operands, so it is called the ternary operator:
condition ? expression1 : expression2If the condition is true, the whole expression takes the value of
expression1; otherwise it takes the value of
expression2. It is a short form of a simple
if–else that produces a value.
int larger = (a > b) ? a : b;
// is the same as:
int larger;
if (a > b) larger = a; else larger = b;#include <iostream>
using namespace std;
int main() {
int marks = 58;
int number = 7;
int x = 25, y = 40;
cout << "Result: " << (marks >= 40 ? "Pass" : "Fail") << '\n';
cout << number << " is " << (number % 2 == 0 ? "even" : "odd") << '\n';
int smaller = (x < y) ? x : y;
cout << "Smaller of " << x << " and " << y << " = " << smaller << '\n';
int count = 1;
cout << "You have " << count << (count == 1 ? " message" : " messages")
<< '\n';
int age = 70;
double fare = 30.0; // bus fare in Rs.
double payable = (age >= 60) ? fare * 0.5 : fare;
cout << "Bus fare payable: Rs. " << payable << '\n';
return 0;
}Result: Pass
7 is odd
Smaller of 25 and 40 = 25
You have 1 message
Bus fare payable: Rs. 15Notes on ?::
?: expression in brackets when you use it
with cout <<, because ?: has very low
precedence.a ? b : c ? d : e) quickly becomes unreadable;
use an if–else ladder instead.| Situation | Best choice |
|---|---|
| Do something or nothing | simple if |
| Exactly two alternatives | if–else |
| Choose a value from two alternatives | ?: |
| Many alternatives based on ranges or different conditions | else-if ladder |
| A second question only makes sense after the first | nested if |
| Many alternatives based on exact values of one integer or char | switch (next lesson) |
else if without a space as
elseif — this is not a keyword; it is a syntax error.else: else (x < 0) — wrong.else without braces
attaches to the nearest if, not the one you intended.
Always use braces in nested if.ifs instead of a ladder:
writing
if (p >= 80) grade = 'A'; if (p >= 65) grade = 'B'; ...
— for 90 every test is true and the last one wins. Use
else if so only one branch runs.?: without brackets inside
cout.else-if ladder tests conditions top to bottom and
runs only the first true branch.if is an if inside another
if/else block.else matches the nearest unmatched if;
braces remove all doubt.condition ? a : b gives a if the condition
is true, else b. Use it only for simple value choices.else-if ladder with four conditions and a final
else, what is the maximum number of blocks that can run?
The minimum?n = 15?
if (n > 10) cout << "X"; else if (n > 5) cout << "Y"; else cout << "Z";n = 15?
if (n > 10) cout << "X"; if (n > 5) cout << "Y"; else cout << "Z";if (temp > 30) status = 'H'; else status = 'N';if does the else belong to? Rewrite
with braces so that it belongs to the first if.
if (a > 0) if (b > 0) cout << "both"; else cout << "a not positive";else-if ladder that prints “Child” (age below
13), “Teenager” (13–19), “Adult” (20–59) or “Senior” (60 and
above).else). 2.
X. 3. XY — these are two separate
if statements. 4.
status = (temp > 30) ? 'H' : 'N'; 5. It belongs to
if (b > 0). Fixed:
if (a > 0) { if (b > 0) cout << "both"; } else cout << "a not positive";
6.
if (age < 13) cout << "Child"; else if (age <= 19) cout << "Teenager"; else if (age <= 59) cout << "Adult"; else cout << "Senior";Suppose a program reads a day number 1–7 and prints the day’s name.
With an else-if ladder we would write seven conditions all
of the form day == 1, day == 2, and so on.
That is long and repetitive. When we compare one
variable with many exact values, C++ gives a
neater statement: switch.
switch (expression) {
case constant1:
statements;
break;
case constant2:
statements;
break;
...
default:
statements;
}How it works:
expression inside the brackets is evaluated
once.case label. When a
label matches, execution jumps to that label and starts
running statements from there.break ends the switch immediately; the
program continues after the closing brace }.default (if
there is one). default is optional, like the final
else of a ladder.Think of a switch like the lift in a building: you press
a floor number, the lift goes directly to that floor.
case labels are the floors, default is the
ground-floor reception for “none of these”.
#include <iostream>
using namespace std;
int main() {
int day;
cout << "Enter day number (1-7): ";
cin >> day;
switch (day) {
case 1:
cout << "Sunday\n";
break;
case 2:
cout << "Monday\n";
break;
case 3:
cout << "Tuesday\n";
break;
case 4:
cout << "Wednesday\n";
break;
case 5:
cout << "Thursday\n";
break;
case 6:
cout << "Friday\n";
break;
case 7:
cout << "Saturday\n";
break;
default:
cout << "Invalid day number\n";
}
return 0;
}Enter day number (1-7): 6
FridayEnter day number (1-7): 9
Invalid day numberThe last branch (default here) does not need a
break, because the switch ends there anyway.
Many programmers still add one for safety, in case another
case is added below it later.
| Rule | Allowed | Not allowed |
|---|---|---|
| Type of the switch expression | int, char, bool,
short, long, enumeration (integral types) |
double, float,
std::string |
| Case labels | constant values: case 5:, case 'A': |
variables: case x: |
| Ranges | — | case 1-10: or case >= 80: (not standard
C++) |
| Duplicate labels | — | two case 3: labels in one switch |
| Order of cases | any order; default may be anywhere |
— |
The most important limitation: switch tests only
for equality with constants. It cannot test ranges like “marks
between 65 and 79” or conditions with &&. Those
need an else-if ladder. (Some compilers accept
case 1 ... 10: as a GNU extension, but it is not
standard C++ and will not work everywhere, so we do not use it.)
What does the compiler say if you use a double?
double price = 50.0;
switch (price) {
case 50.0:
cout << "Fifty\n";
}error: switch quantity not an integer
If a case has no break, execution does
not stop at the next label — it simply falls
through and runs the statements of the following cases too, until
it meets a break or the end of the switch.
Labels are only entry points, not walls.
#include <iostream>
using namespace std;
int main() {
int level = 2;
cout << "Security checks for level " << level << ":\n";
switch (level) {
case 3:
cout << " - fingerprint scan\n";
[[fallthrough]];
case 2:
cout << " - ID card check\n";
[[fallthrough]];
case 1:
cout << " - sign the visitor book\n";
break;
default:
cout << " - no entry\n";
}
return 0;
}Security checks for level 2:
- ID card check
- sign the visitor bookLevel 2 enters at case 2, runs its statement, falls into
case 1, and stops at break. This time
fall-through is deliberate: a higher level includes all checks
of lower levels. The line [[fallthrough]]; (C++17) tells
both the reader and the compiler “I meant to fall through here”. With
-Wextra g++ warns about fall-through that is not marked
this way.
Usually, however, a missing break is a
bug. You will see this in Problem
Solving with Selection and Common Errors.
A very common and useful form of fall-through is to put several
labels on top of each other when they share the same action. No
[[fallthrough]] is needed when a label has no statements of
its own.
#include <iostream>
using namespace std;
int main() {
char letter;
cout << "Enter a letter: ";
cin >> letter;
switch (letter) {
case 'a': case 'e': case 'i': case 'o': case 'u':
case 'A': case 'E': case 'I': case 'O': case 'U':
cout << letter << " is a vowel.\n";
break;
default:
cout << letter << " is not a vowel.\n";
}
return 0;
}Enter a letter: E
E is a vowel.A switch can also take the result of a calculation. A
clever trick converts a percentage range into a single integer so that
switch can be used: percent / 10 with integer
division gives 10 for 100, 9 for 90–99, 8 for 80–89, and so on.
#include <iostream>
using namespace std;
int main() {
int percent;
cout << "Enter percentage (0-100): ";
cin >> percent;
if (percent < 0 || percent > 100) {
cout << "Invalid percentage\n";
return 0;
}
char grade;
switch (percent / 10) {
case 10: case 9: case 8:
grade = 'A';
break;
case 7:
grade = 'B';
break;
case 6: case 5:
grade = 'C';
break;
case 4:
grade = 'D';
break;
default:
grade = 'F';
}
cout << "Grade: " << grade << '\n';
return 0;
}Enter percentage (0-100): 76
Grade: BNote that this scheme uses 70 as the B boundary; it works only because the boundaries are multiples of 10. For boundaries like 65, use a ladder.
A menu-driven program shows the user a list of
numbered options, reads a choice, and performs the chosen operation.
switch is perfect for this, because each choice is an exact
value. In the Control Structures: Loops chapter you
will put the menu inside a loop so that it repeats; for now, it runs
once.
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double first, second;
int choice;
cout << "===== Simple Calculator =====\n";
cout << "1. Add\n2. Subtract\n3. Multiply\n4. Divide\n";
cout << "Enter your choice (1-4): ";
cin >> choice;
cout << "Enter two numbers: ";
cin >> first >> second;
cout << fixed << setprecision(2);
switch (choice) {
case 1:
cout << "Sum = " << first + second << '\n';
break;
case 2:
cout << "Difference = " << first - second << '\n';
break;
case 3:
cout << "Product = " << first * second << '\n';
break;
case 4:
if (second != 0) {
cout << "Quotient = " << first / second << '\n';
} else {
cout << "Error: division by zero\n";
}
break;
default:
cout << "Invalid choice\n";
}
return 0;
}===== Simple Calculator =====
1. Add
2. Subtract
3. Multiply
4. Divide
Enter your choice (1-4): 4
Enter two numbers: 45 4
Quotient = 11.25===== Simple Calculator =====
1. Add
2. Subtract
3. Multiply
4. Divide
Enter your choice (1-4): 4
Enter two numbers: 10 0
Error: division by zeroNotice that an if–else is nested inside
case 4. Any statements, including other selection
statements, can appear inside a case.
If you want to declare a new variable inside a case, put
the case’s statements in braces. Without braces, the compiler complains
that the jump to a later label “crosses initialization” of the
variable.
case 1: {
double total = first + second; // local to this block
cout << total;
break;
}| Point | switch | else-if ladder |
|---|---|---|
| What it tests | equality of one integral expression with constants | any conditions (ranges, &&, ||, doubles, strings) |
| Readability for many exact values | very clear | long and repetitive |
Needs break |
yes, or it falls through | no |
| Floating-point / string values | not allowed | allowed |
| Typical use | menus, day/month names, character commands | grades by range, tax slabs, comparisons |
break, so the program runs
the next case as well (unintended fall-through).case marks >= 80: — not allowed. Use a ladder.double or string
as the switch expression — compile error.case limit: — the label must be a constant.case 1 without
: — syntax error.case A: instead of case 'A':.switch compares one integral expression (int, char,
bool, enum) with constant case labels.break or the closing brace.default handles “none of the above”; it is
optional.break causes fall-through; use
[[fallthrough]]; when it is intentional.case 'a': case 'A':) to share code.switch for menus and exact values; use an
else-if ladder for ranges and complex conditions.switch expression?
Name one type that cannot.int x = 2; switch (x) { case 1: cout << "A"; case 2: cout << "B"; case 3: cout << "C"; break; default: cout << "D"; }x = 7 in question 2?switch be used directly to decide “Pass” for
marks 40–100?switch:
if (c == 'r') cout << "Stop"; else if (c == 'y') cout << "Wait"; else if (c == 'g') cout << "Go"; else cout << "Unknown";int, char,
bool, short, long, enums. Not
allowed: double, float,
std::string. 2. BC (falls from case 2 into
case 3, stops at break). 3. D. 4. Case labels must be
single constant values; ranges are not allowed (use a ladder, or the
marks / 10 trick with grouped cases). 5.
switch (c) { case 'r': cout << "Stop"; break; case 'y': cout << "Wait"; break; case 'g': cout << "Go"; break; default: cout << "Unknown"; }
6.
switch (m) { case 2: days = 28; break; case 4: case 6: case 9: case 11: days = 30; break; default: days = 31; }
(with validation of 1–12 first).In Problem-Solving Techniques, Algorithms and Pseudocode you learned the general steps of problem solving. For problems that involve decisions, add two extra habits:
switch. Ranges or complex conditions → ladder. A question
that depends on an earlier answer → nested if.We now apply this method to three classic problems. (The grade
calculator was already solved in else-if Ladder, Nested
if and the Conditional Operator with a ladder and in The switch Statement and Menu-Driven Programs with a
switch.)
A leap year has 366 days (February has 29 days). The rule in the Gregorian calendar is:
So 2024 is a leap year, 2023 is not, 1900 is not (divisible by 100 but not by 400), and 2000 is (divisible by 400).
Written as one logical condition:
leap = (year divisible by 4 AND NOT divisible by 100)
OR (year divisible by 400)
| Year | % 4 == 0 | % 100 != 0 | % 400 == 0 | Leap? |
|---|---|---|---|---|
| 2024 | true | true | false | yes |
| 2023 | false | true | false | no |
| 1900 | true | false | false | no |
| 2000 | true | false | true | yes |
#include <iostream>
using namespace std;
int main() {
int year;
cout << "Enter a year (AD): ";
cin >> year;
bool isLeap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
if (isLeap) {
cout << year << " is a leap year (366 days).\n";
} else {
cout << year << " is not a leap year (365 days).\n";
}
return 0;
}Enter a year (AD): 1900
1900 is not a leap year (365 days).Enter a year (AD): 2000
2000 is a leap year (366 days).The same logic can be written as a nested if that
follows the rule sentence by sentence:
if (year % 400 == 0) isLeap = true;
else if (year % 100 == 0) isLeap = false;
else if (year % 4 == 0) isLeap = true;
else isLeap = false;Here the order is essential: the most specific rule (400) is tested first. (Note: this rule is for the AD/Gregorian calendar. The Bikram Sambat calendar used in Nepal has a different system of month lengths.)
A quadratic equation has the form a*x*x + b*x + c = 0
with a not zero. The roots are found using the discriminant
d = b*b - 4*a*c:
| Case | Condition | Roots |
|---|---|---|
| Not quadratic | a == 0 |
treat as linear b*x + c = 0 (if b is not 0) |
| Two real, different roots | d > 0 |
(-b + sqrt(d)) / (2*a) and
(-b - sqrt(d)) / (2*a) |
| Two equal real roots | d == 0 |
-b / (2*a) |
| Two complex roots | d < 0 |
real part -b / (2*a), imaginary part
sqrt(-d) / (2*a) |
Algorithm:
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main() {
double a, b, c;
cout << "Enter a, b and c: ";
cin >> a >> b >> c;
if (a == 0) {
cout << "a is zero: this is not a quadratic equation.\n";
return 0;
}
double d = b * b - 4 * a * c;
cout << fixed << setprecision(3);
cout << "Discriminant = " << d << '\n';
if (d > 0) {
double root1 = (-b + sqrt(d)) / (2 * a);
double root2 = (-b - sqrt(d)) / (2 * a);
cout << "Real and different roots: " << root1
<< " and " << root2 << '\n';
} else if (d == 0) {
double root = -b / (2 * a);
cout << "Real and equal roots: " << root << '\n';
} else {
double realPart = -b / (2 * a);
double imagPart = sqrt(-d) / (2 * a);
cout << "Complex roots: " << realPart << " + " << imagPart << "i and "
<< realPart << " - " << imagPart << "i\n";
}
return 0;
}Enter a, b and c: 1 -5 6
Discriminant = 1.000
Real and different roots: 3.000 and 2.000Enter a, b and c: 1 2 1
Discriminant = 0.000
Real and equal roots: -1.000Enter a, b and c: 1 2 5
Discriminant = -16.000
Complex roots: -1.000 + 2.000i and -1.000 - 2.000iA note on accuracy: in Relational and Logical
Operators we said “do not use == with computed
doubles”. Here d == 0 is acceptable for simple practice
inputs made of small whole numbers, because b*b - 4*a*c is
then computed exactly. For general scientific input, a professional
program would test fabs(d) < 1e-9 instead. Similarly,
a == 0 compares the value the user typed, which is
exact.
Electricity tariffs in Nepal are charged in slabs: the first few units are cheap, and each further block of units costs more per unit. The important idea is that each slab rate applies only to the units inside that slab, not to all units. We use the following simplified example tariff (these are made-up teaching rates, not the official NEA tariff):
| Slab | Units | Rate per unit (Rs.) |
|---|---|---|
| 1 | first 20 units | 3.00 |
| 2 | next 30 units (21–50) | 6.50 |
| 3 | next 50 units (51–100) | 8.00 |
| 4 | above 100 units | 11.00 |
Plus a monthly service charge of Rs. 30 if units are 50 or less, otherwise Rs. 50.
Example by hand for 130 units:
first 20 units : 20 x 3.00 = 60.00
next 30 units : 30 x 6.50 = 195.00
next 50 units : 50 x 8.00 = 400.00
remaining 30 : 30 x 11.00 = 330.00
energy charge 985.00
service charge 50.00
total 1035.00
In code, each branch of the ladder adds up the full cost of all lower slabs (as constants) plus the units in the current slab. Full cost of slab 1 is 20 x 3 = 60; slabs 1–2 is 60 + 195 = 255; slabs 1–3 is 255 + 400 = 655.
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
int units;
cout << "Enter units consumed: ";
cin >> units;
if (units < 0) {
cout << "Invalid meter reading.\n";
return 0;
}
double energy;
if (units <= 20) {
energy = units * 3.00;
} else if (units <= 50) {
energy = 60 + (units - 20) * 6.50;
} else if (units <= 100) {
energy = 255 + (units - 50) * 8.00;
} else {
energy = 655 + (units - 100) * 11.00;
}
double service = (units <= 50) ? 30 : 50;
double total = energy + service;
cout << fixed << setprecision(2);
cout << "Energy charge : Rs. " << setw(8) << energy << '\n';
cout << "Service charge: Rs. " << setw(8) << service << '\n';
cout << "Total bill : Rs. " << setw(8) << total << '\n';
return 0;
}Enter units consumed: 130
Energy charge : Rs. 985.00
Service charge: Rs. 50.00
Total bill : Rs. 1035.00Enter units consumed: 45
Energy charge : Rs. 222.50
Service charge: Rs. 30.00
Total bill : Rs. 252.50The first run matches our hand calculation exactly. Check the second: 60 + 25 x 6.50 = 222.50, plus Rs. 30 = 252.50.
Most selection bugs are logic errors: the program compiles and runs, but gives wrong results. Learn to recognise these five patterns.
Error 1: = instead of
==.
int choice = 2;
if (choice = 1) { // assigns 1; value 1 means true
cout << "You chose Tea\n"; // always printed!
}choice = 1 is an assignment. Its value is 1,
which converts to true, so the if block always
runs — and choice is also changed to 1.
g++ -Wall warns:
warning: suggest parentheses around assignment used as truth value
Fix: if (choice == 1). Some programmers write the
constant first, if (1 == choice), so that a mistyped
1 = choice becomes a compile error. With -Wall
turned on, the normal order is fine as long as you read your
warnings.
Error 2: stray semicolon after the condition.
if (balance < 500);
cout << "Low balance!\n"; // always printedThe ; is an empty statement that forms the whole body of
the if. g++ may warn “this ‘if’ clause does not guard…”.
Fix: remove the semicolon.
Error 3: dangling else. An else belongs
to the nearest unmatched if (see else-if
Ladder, Nested if and the Conditional Operator). Fix: always use
braces in nested ifs.
Error 4: missing break in switch.
#include <iostream>
using namespace std;
int main() {
int choice = 1;
switch (choice) {
case 1:
cout << "Momo ordered\n"; // break forgotten here!
[[fallthrough]]; // (marked only to silence g++)
case 2:
cout << "Chowmein ordered\n";
break;
default:
cout << "Invalid choice\n";
}
return 0;
}Momo ordered
Chowmein orderedThe customer asked for momo only (choice 1) but also received
chowmein! In real buggy code the [[fallthrough]]; line
would not be there; we added it only so that this file compiles without
warnings. Fix: add break; at the end of
case 1.
Error 5: wrong order / overlapping conditions.
Testing marks >= 40 before marks >= 80
in a ladder (see else-if Ladder, Nested if and the
Conditional Operator), or using separate ifs where a
ladder was needed. Fix: order from most specific (or highest) to least,
and use else if.
Summary table for quick revision:
| Symptom | Likely cause | Fix |
|---|---|---|
| Branch always runs | = instead of ==, or ; after
if (...) |
use ==; remove ; |
Wrong branch for nested if |
dangling else |
add braces |
| Several cases run in switch | missing break |
add break |
| Higher grades never given | wrong order in ladder | test highest range first |
| Several messages printed for one input | separate ifs instead of ladder |
use else if |
| Range test always true | a < x < b |
x > a && x < b |
A program with selection has several paths. Good testing runs every path at least once (branch coverage) and checks the edges between ranges (boundary testing). For the electricity bill the test table is:
| Units | Why | Expected total (Rs.) |
|---|---|---|
| -5 | invalid input | error message |
| 0 | lower edge | 30.00 |
| 20 | end of slab 1 | 90.00 |
| 21 | start of slab 2 | 96.50 |
| 50 | end of slab 2, service boundary | 285.00 |
| 51 | start of slab 3, service changes | 313.00 |
| 100 | end of slab 3 | 705.00 |
| 130 | slab 4 | 1035.00 |
Run the program with each value and confirm. If any answer is wrong, trace the program by hand for that input.
year % 4 == 0 only (fails
for 1900, 2100).== to compare computed double values
from general input.-Wall.if, ladder, nested
if or switch to match the cases.(y % 4 == 0 && y % 100 != 0) || y % 400 == 0.a == 0 first; then branch on the
discriminant.= for ==,
stray ;, dangling else, missing
break, wrong order.a = 2, b = 4, c = 2, what is the discriminant and
what type of roots does the equation have?int x = 0; if (x = 5) cout << "Five " << x; else cout << "Not five";if (units > 100) rate = 11; if (units > 50) rate = 8;
for 130 units. What rate is used, and how should it be fixed?else-if ladder with ranges such as
ch >= 'A' && ch <= 'Z'.2100 % 4 == 0 true, 2100 % 100 != 0
false, so the first part is false; 2100 % 400 == 0 false;
result false. 2. d = 16 - 16 = 0: real and equal roots, x = -1. 3. 255 +
25 x 8 = 455 energy, + 50 service = Rs. 505.00. 4. Five 5 —
x = 5 assigns 5 (true). 5. Both ifs run, so
rate ends as 8 (wrong). Use else if, or
reverse the order with a ladder. 6.
if (ch >= 'A' && ch <= 'Z') ... else if (ch >= 'a' && ch <= 'z') ... else if (ch >= '0' && ch <= '9') ... else ...Suppose you must print “Welcome to BIT” five times. You could write:
cout << "Welcome to BIT\n";
cout << "Welcome to BIT\n";
cout << "Welcome to BIT\n";
cout << "Welcome to BIT\n";
cout << "Welcome to BIT\n";This works for 5, but what about 500? Or a number the user types at run time? We cannot know in advance how many lines to write. We need a way to say “repeat this statement n times” or “repeat this until something happens”. That is exactly what a loop does.
A loop is a control structure that executes a group of statements repeatedly as long as a condition is true. Each single execution of the loop body is called an iteration (one “round” of the loop).
Everyday examples of loops:
Almost every loop has four parts. Learn to spot them — it is the key to writing correct loops.
| Part | Purpose | Example |
|---|---|---|
| Initialisation | give the loop variable its starting value (done once, before the loop) | int count = 1; |
| Condition | checked before each iteration; the loop continues while it is true | count <= 5 |
| Body | the statements to repeat | cout << "Welcome\n"; |
| Update | change the loop variable so the condition will eventually become false | count++; |
If the update is missing, the condition never changes, and the loop repeats forever. This is called an infinite loop (see break, continue, goto and Infinite Loops).
Syntax:
initialisation;
while (condition) {
body;
update;
}How it works:
Because the condition is tested before the body,
while is called an entry-controlled (or
pre-test) loop. If the condition is false the very first time,
the body runs zero times.
Flowchart:
initialisation
|
v
+--->/-----------\ false
| < condition >----------+
| \-----------/ |
| | true |
| v |
| +-----------+ |
| | body | |
| +-----------+ |
| | |
| +-----------+ |
+----| update | |
+-----------+ |
v
statement after loop
When we know in advance how many times to repeat (or the user tells us at run time), we use a variable called a counter that counts the iterations. This is a counter-controlled (or definite) loop.
#include <iostream>
using namespace std;
int main() {
int count = 1; // initialisation
while (count <= 5) { // condition
cout << count << ". Welcome to BIT\n"; // body
count++; // update
}
cout << "Loop finished. count is now " << count << '\n';
return 0;
}1. Welcome to BIT
2. Welcome to BIT
3. Welcome to BIT
4. Welcome to BIT
5. Welcome to BIT
Loop finished. count is now 6Notice that after the loop, count is 6: the loop stops
when the condition 6 <= 5 becomes false.
Let us trace the first few iterations:
| Iteration | count before test | count <= 5 | Printed | count after update |
|---|---|---|---|---|
| 1 | 1 | true | 1. Welcome to BIT | 2 |
| 2 | 2 | true | 2. Welcome to BIT | 3 |
| 3 | 3 | true | 3. Welcome to BIT | 4 |
| 4 | 4 | true | 4. Welcome to BIT | 5 |
| 5 | 5 | true | 5. Welcome to BIT | 6 |
| — | 6 | false | (loop ends) | — |
A very common use of a loop is to accumulate (add up) values. We use a variable called an accumulator, which must start at 0 before the loop. Each iteration adds one value to it.
Example: read the marks of n students and print the total and average.
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
int n;
cout << "How many students? ";
cin >> n;
int student = 1;
double total = 0; // accumulator starts at 0
while (student <= n) {
double marks;
cout << "Marks of student " << student << ": ";
cin >> marks;
total = total + marks; // accumulate
student++;
}
if (n > 0) {
cout << fixed << setprecision(2);
cout << "Total = " << total << '\n';
cout << "Average = " << total / n << '\n';
} else {
cout << "No students entered.\n";
}
return 0;
}How many students? 4
Marks of student 1: 72
Marks of student 2: 65.5
Marks of student 3: 88
Marks of student 4: 54
Total = 279.50
Average = 69.88We checked n > 0 before dividing, so the program does
not divide by zero if the user types 0. In that case the loop body runs
zero times — a good illustration of the entry-controlled behaviour of
while.
A similar pattern is the counter pattern: start a
counter at 0 and add 1 whenever something happens. For example, count
how many of the students passed: inside the loop,
if (marks >= 40) passCount++;.
The update does not have to be ++. Any change that moves
toward ending the loop is fine:
#include <iostream>
using namespace std;
int main() {
// even numbers from 2 to 20
int number = 2;
while (number <= 20) {
cout << number << ' ';
number += 2;
}
cout << '\n';
// countdown for a rocket launch
int seconds = 5;
while (seconds > 0) {
cout << seconds << "... ";
seconds--;
}
cout << "Lift off!\n";
// powers of 2 below 1000
int power = 1;
while (power < 1000) {
cout << power << ' ';
power *= 2;
}
cout << '\n';
return 0;
}2 4 6 8 10 12 14 16 18 20
5... 4... 3... 2... 1... Lift off!
1 2 4 8 16 32 64 128 256 512 Sometimes we do not know how many values the user will enter. A shopkeeper does not know how many items a customer will buy. In this case we agree on a special value that means “stop”. This value is called a sentinel (a “guard” value). It must be a value that cannot be real data — for prices, 0 or a negative number works well. A loop that stops on a sentinel is called a sentinel-controlled (or indefinite) loop.
The standard shape is: read the first value before the loop; test it in the condition; at the end of the body, read the next value. This is sometimes called a priming read.
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double price;
double bill = 0;
int items = 0;
cout << "Enter item prices (0 to finish).\n";
cout << "Price: ";
cin >> price; // priming read
while (price != 0) {
bill += price;
items++;
cout << "Price: ";
cin >> price; // read the next value
}
cout << fixed << setprecision(2);
cout << "Items: " << items << ", Total bill: Rs. " << bill << '\n';
return 0;
}Enter item prices (0 to finish).
Price: 250
Price: 120.50
Price: 75
Price: 0
Items: 3, Total bill: Rs. 445.50The sentinel (0) is not added to the bill and not counted as an item, because the loop tests the value before processing it. If the first value is already 0, the loop runs zero times and the bill is Rs. 0.00.
A while loop can use any condition. Example: how many
years until a fixed deposit of Rs. 100,000 at 8% annual interest
(compounded yearly) doubles?
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double balance = 100000;
double target = 2 * balance;
int years = 0;
while (balance < target) {
balance = balance + balance * 0.08;
years++;
}
cout << fixed << setprecision(2);
cout << "Doubled after " << years << " years. Balance: Rs. "
<< balance << '\n';
return 0;
}Doubled after 10 years. Balance: Rs. 215892.50Here nobody knew the number of iterations in advance; the loop simply continued while the balance was below the target.
count++): the
condition never changes → infinite loop.while (count <= 5); — the empty statement becomes the
body, and the loop never ends (since count never changes).
Remove the ;.int total; has an unpredictable (garbage)
value; write int total = 0;.count < 5 runs 4
times when starting from 1; count <= 5 runs 5 times.
Trace the first and last iteration to check.{ } only the
first statement is the body; the update may end up outside the
loop.while is entry-controlled: the condition is tested
first; the body may run zero times.int i = 10; while (i > 0) { cout << i; i -= 3; }.int k = 5; while (k < 5) { k++; }while loop that prints the odd numbers from 1
to 15 on one line.int i = 10; condition
i > 0; body cout << i; update
i -= 3. 2. 10741. 3. Zero times (5 < 5 is
false at the start). 4. A special value that means “stop”, which cannot
be real data. Marks are never negative, so -1 is safe; temperatures can
be -1, so -1 would stop the loop on real data. 5.
int n = 1; while (n <= 15) { cout << n << ' '; n += 2; }
6. Priming read into x;
count = 0; largest = x; then
while (x != -1) { count++; if (x > largest) largest = x; cin >> x; };
print (handle count = 0 separately).The while loop tests its condition before the
body. Sometimes we want the body to run at least once
and only then decide whether to repeat. Examples:
For these situations C++ has the do–while loop.
Syntax:
do {
body;
update;
} while (condition); // note the semicolon!How it works:
Because the test is at the end, do–while is
called an exit-controlled (or post-test) loop.
The body always runs at least once, even if the
condition is false from the beginning.
Important: the do–while statement ends
with a semicolon after while (condition).
This is the opposite of the while loop, where a semicolon
after the condition is a bug. Forgetting this semicolon gives a compile
error such as expected ';' before ....
Flowchart:
initialisation
|
v
+--> +-----------+
| | body |
| +-----------+
| | update |
| +-----------+
| |
| true /-----------\
+-----< condition >
\-----------/
| false
v
statement after loop
#include <iostream>
using namespace std;
int main() {
int count = 1;
do {
cout << "Iteration " << count << '\n';
count++;
} while (count <= 3);
int start = 10;
do {
cout << "This line prints once even though 10 > 3.\n";
start++;
} while (start <= 3);
return 0;
}Iteration 1
Iteration 2
Iteration 3
This line prints once even though 10 > 3.The second loop proves the key property: the condition
10 <= 3 was false, yet the body ran once.
The following two loops look similar, but behave differently when
n starts at 0:
#include <iostream>
using namespace std;
int main() {
int n = 0;
cout << "while loop: ";
while (n > 0) {
cout << n << ' ';
n--;
}
cout << "(done)\n";
n = 0;
cout << "do-while loop: ";
do {
cout << n << ' ';
n--;
} while (n > 0);
cout << "(done)\n";
return 0;
}while loop: (done)
do-while loop: 0 (done)| Point | while | do–while |
|---|---|---|
| Type | entry-controlled (pre-test) | exit-controlled (post-test) |
| Condition tested | before each iteration | after each iteration |
| Minimum number of iterations | 0 | 1 |
Semicolon after while (...) |
no (it would be a bug) | yes (required) |
| Typical use | counting, sentinel loops, “maybe zero times” | menus, input validation, “at least once” |
A simple way to choose: ask yourself, “Must the body run at least
once?” If yes, do–while is natural. If the body may
need to run zero times, use while.
Users make mistakes. They type 150 when marks must be between 0 and
100, or a negative age. A robust program does not accept bad input; it
asks again. This is called input
validation, and do–while fits it perfectly: we
must ask at least once, and we repeat while the input is invalid.
#include <iostream>
using namespace std;
int main() {
int marks;
do {
cout << "Enter marks (0-100): ";
cin >> marks;
if (marks < 0 || marks > 100) {
cout << "Invalid! Marks must be between 0 and 100.\n";
}
} while (marks < 0 || marks > 100);
cout << "Accepted marks: " << marks << '\n';
return 0;
}Enter marks (0-100): 150
Invalid! Marks must be between 0 and 100.
Enter marks (0-100): -5
Invalid! Marks must be between 0 and 100.
Enter marks (0-100): 78
Accepted marks: 78The loop condition is exactly the “invalid” condition. It repeats while the input is bad and stops as soon as a valid value arrives.
What if the user types a letter, such as abc, when the
program expects an int? Then cin goes into a
fail state: the read fails, marks is set
to 0 (since C++11), and every later cin >> also fails
immediately. A validation loop could then spin forever. Handling this
completely requires cin.clear() and
cin.ignore(), which you will see when you study streams in
more detail (in the Files and Operations chapter).
For now, a simple safety check is to stop when cin has
failed:
if (!cin) { // true if the last input failed
cout << "Input error. Exiting.\n";
return 1;
}An ATM gives you three attempts. The loop must stop if the PIN is
correct or the attempts run out. We combine two
conditions with &&:
#include <iostream>
using namespace std;
int main() {
const int correctPin = 4321;
const int maxAttempts = 3;
int pin;
int attempts = 0;
do {
cout << "Enter PIN: ";
cin >> pin;
attempts++;
if (pin != correctPin && attempts < maxAttempts) {
cout << "Wrong PIN. " << maxAttempts - attempts
<< " attempt(s) left.\n";
}
} while (pin != correctPin && attempts < maxAttempts);
if (pin == correctPin) {
cout << "Welcome! Access granted.\n";
} else {
cout << "Card blocked. Please contact your bank.\n";
}
return 0;
}Enter PIN: 1111
Wrong PIN. 2 attempt(s) left.
Enter PIN: 4321
Welcome! Access granted.Enter PIN: 1111
Wrong PIN. 2 attempt(s) left.
Enter PIN: 2222
Wrong PIN. 1 attempt(s) left.
Enter PIN: 3333
Card blocked. Please contact your bank.After the loop we check why it stopped: correct PIN or no more attempts. This “check after the loop” pattern is common whenever a loop has two ways to finish.
In The switch Statement and Menu-Driven Programs,
our calculator menu ran only once. Real menu programs show the menu
again and again until the user chooses “Exit”. Since the menu must be
shown at least once, do–while is the natural choice.
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double balance = 5000.0;
int choice;
cout << fixed << setprecision(2);
do {
cout << "\n--- Mini Bank ---\n";
cout << "1. Check balance\n2. Deposit\n3. Withdraw\n4. Exit\n";
cout << "Choice: ";
cin >> choice;
double amount;
switch (choice) {
case 1:
cout << "Balance: Rs. " << balance << '\n';
break;
case 2:
cout << "Deposit amount: ";
cin >> amount;
if (amount > 0) {
balance += amount;
cout << "Deposited. New balance: Rs. " << balance << '\n';
} else {
cout << "Amount must be positive.\n";
}
break;
case 3:
cout << "Withdraw amount: ";
cin >> amount;
if (amount > 0 && amount <= balance) {
balance -= amount;
cout << "Withdrawn. New balance: Rs. " << balance << '\n';
} else {
cout << "Invalid amount or insufficient balance.\n";
}
break;
case 4:
cout << "Thank you for banking with us.\n";
break;
default:
cout << "Invalid choice. Try again.\n";
}
} while (choice != 4);
return 0;
}
--- Mini Bank ---
1. Check balance
2. Deposit
3. Withdraw
4. Exit
Choice: 2
Deposit amount: 1500
Deposited. New balance: Rs. 6500.00
--- Mini Bank ---
1. Check balance
2. Deposit
3. Withdraw
4. Exit
Choice: 3
Withdraw amount: 8000
Invalid amount or insufficient balance.
--- Mini Bank ---
1. Check balance
2. Deposit
3. Withdraw
4. Exit
Choice: 3
Withdraw amount: 2500
Withdrawn. New balance: Rs. 4000.00
--- Mini Bank ---
1. Check balance
2. Deposit
3. Withdraw
4. Exit
Choice: 7
Invalid choice. Try again.
--- Mini Bank ---
1. Check balance
2. Deposit
3. Withdraw
4. Exit
Choice: 4
Thank you for banking with us.Study how the pieces fit: the do–while repeats the whole
menu; the switch chooses the action; if–else
inside the cases validates the amounts. The loop ends only when
choice is 4. (Note that break inside the
switch only leaves the switch, not the loop.
You will learn more about this in break, continue, goto
and Infinite Loops.)
Another everyday use of do–while is to repeat a whole
calculation while the user answers y:
#include <iostream>
using namespace std;
int main() {
char again;
do {
double celsius;
cout << "Temperature in Celsius: ";
cin >> celsius;
cout << "= " << celsius * 9 / 5 + 32 << " Fahrenheit\n";
cout << "Convert another? (y/n): ";
cin >> again;
} while (again == 'y' || again == 'Y');
cout << "Goodbye!\n";
return 0;
}Temperature in Celsius: 25
= 77 Fahrenheit
Convert another? (y/n): y
Temperature in Celsius: -10
= 14 Fahrenheit
Convert another? (y/n): n
Goodbye!while (condition) in a do–while — compile
error.while (condition) in a plain while loop —
infinite loop or empty body.while (marks >= 0 && marks <= 100) keeps
asking when the input is correct. The loop must continue while input is
bad.&& where || is
needed:
while (marks < 0 && marks > 100) is never
true (no number is both), so validation never repeats.do { int choice; ... } while (choice != 4);
— choice is not visible in the condition (compile error).
Declare it before do.while for a menu without
initialising choice first.do–while runs the body first and tests the condition
afterwards: at least one iteration.while is entry-controlled.do { ... } while (condition); — the semicolon is
required.do–while for input validation (repeat while input
is invalid) and for repeating menus.do block.do–while runs? Of a while?int x = 5; do { cout << x << ' '; x += 5; } while (x < 5);do–while condition to repeat while
age is not in the range 1 to 120.do { int n; cin >> n; } while (n < 0);do–while:
int i = 1; while (i <= 3) { cout << i; i++; }. Do
both always give the same output? When would they differ?y to “Add another?”, then
prints the total.5 (body runs once, then 10 < 5 is
false). 3. while (age < 1 || age > 120); 4.
n is declared inside the block, so it is not in scope in
the condition; declare int n; before do. 5.
int i = 1; do { cout << i; i++; } while (i <= 3);
Same output here (123); they would differ if i
started above 3 (the do–while would still print once). 6.
double total = 0; char ans; do { double x; cin >> x; total += x; cout << "Add another? "; cin >> ans; } while (ans == 'y' || ans == 'Y'); cout << total;In the while loop, the four parts of the loop are spread
over several lines: the initialisation is above the loop, the condition
is at the top, and the update is hidden at the bottom of the body. In a
long body it is easy to forget the update. The for loop
puts initialisation, condition and update together in one
line, so a counting loop can be understood at a glance.
for (initialisation; condition; update) {
body;
}Note the two semicolons inside the brackets; they separate the three parts. There is no semicolon after the closing bracket.
Order of execution:
for ( initialisation ; condition ; update )
(1) (2) (4)
{ body }
(3)
order: 1, 2, 3, 4, 2, 3, 4, 2, 3, 4, ... 2 (false) -> exit
Like while, the for loop is
entry-controlled: if the condition is false at the
start, the body runs zero times.
Our first example — the “Welcome” program from Introduction to Loops and the while Loop — becomes:
#include <iostream>
using namespace std;
int main() {
for (int count = 1; count <= 5; count++) {
cout << count << ". Welcome to BIT\n";
}
return 0;
}1. Welcome to BIT
2. Welcome to BIT
3. Welcome to BIT
4. Welcome to BIT
5. Welcome to BITAny for loop can be rewritten as a while
loop, and the other way round:
| for loop | equivalent while loop |
|---|---|
for (int i = 1; i <= n; i++) { |
int i = 1; then while (i <= n) { |
body; |
body; |
} |
i++; then } |
So why have both? It is about readability. When the
number of iterations is known (counting from a to b),
a for loop shows everything in one line. When the loop
depends on an event (a sentinel, a correct PIN, a balance reaching a
target), a while loop reads more naturally.
When the loop variable is declared inside the for
header, as in for (int i = 1; ...), it exists only
inside the loop. After the loop it is gone:
for (int i = 1; i <= 3; i++) {
cout << i;
}
cout << i; // error: 'i' was not declared in this scopeThis is usually a good thing: the counter cannot be misused later,
and you can reuse the name i in another loop. If you need
the final value after the loop, declare the variable before the loop:
int i; for (i = 1; i <= 3; i++) ....
#include <iostream>
using namespace std;
int main() {
int n;
cout << "Enter n: ";
cin >> n;
// sum of 1 to n
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
cout << "Sum 1.." << n << " = " << sum << '\n';
// factorial n! = 1 x 2 x ... x n (product starts at 1, not 0)
long long factorial = 1;
for (int i = 2; i <= n; i++) {
factorial *= i;
}
cout << n << "! = " << factorial << '\n';
// multiplication table
for (int i = 1; i <= 10; i++) {
cout << n << " x " << i << " = " << n * i << '\n';
}
return 0;
}Enter n: 6
Sum 1..6 = 21
6! = 720
6 x 1 = 6
6 x 2 = 12
6 x 3 = 18
6 x 4 = 24
6 x 5 = 30
6 x 6 = 36
6 x 7 = 42
6 x 8 = 48
6 x 9 = 54
6 x 10 = 60Two points to notice:
13! is larger than the
maximum int on typical systems (about 2.1 billion), so we
used long long, which is typically 8 bytes and holds values
up to about 9.2 x 10^18 (enough up to 20!). This connects
to the overflow you saw in Assignment,
Increment/Decrement, Type Conversion and Overflow.Counting down:
for (int s = 10; s >= 1; s--) {
cout << s << ' '; // 10 9 8 ... 1
}Different step size:
for (int year = 2000; year <= 2030; year += 4) {
cout << year << ' '; // 2000 2004 ... 2028
}Multiple variables using the comma operator in initialisation and update:
for (int low = 1, high = 10; low < high; low++, high--) {
cout << "(" << low << "," << high << ") ";
}Omitted parts. Any of the three parts may be left
empty, but the two semicolons must stay. If the condition is omitted, it
is treated as true (so for (;;) is an infinite
loop — see break, continue, goto and Infinite
Loops).
int i = 1;
for ( ; i <= 5; ) { // behaves like while (i <= 5)
cout << i << ' ';
i++;
}The next program shows several variations running together:
#include <iostream>
using namespace std;
int main() {
cout << "Countdown : ";
for (int s = 10; s >= 1; s--) {
cout << s << ' ';
}
cout << '\n';
cout << "Leap years: ";
for (int year = 2000; year <= 2030; year += 4) {
cout << year << ' ';
}
cout << '\n';
cout << "Pairs : ";
for (int low = 1, high = 10; low < high; low++, high--) {
cout << "(" << low << "," << high << ") ";
}
cout << '\n';
cout << "Letters : ";
for (char ch = 'A'; ch <= 'J'; ch++) {
cout << ch;
}
cout << '\n';
cout << "Halves : ";
for (double x = 0.0; x <= 2.0; x += 0.5) {
cout << x << ' ';
}
cout << '\n';
return 0;
}Countdown : 10 9 8 7 6 5 4 3 2 1
Leap years: 2000 2004 2008 2012 2016 2020 2024 2028
Pairs : (1,10) (2,9) (3,8) (4,7) (5,6)
Letters : ABCDEFGHIJ
Halves : 0 0.5 1 1.5 2 A loop variable can be a char, because characters are
stored as small integer codes. A double loop variable works
here because 0.5 is stored exactly in binary, but in general
prefer integer loop counters:
for (double x = 0; x != 1.0; x += 0.1) may never stop,
because 0.1 is not exact and x may never be
exactly 1.0.
For for (int i = a; i <= b; i++) the body runs
b - a + 1 times (if b >= a). For
for (int i = a; i < b; i++) it runs b - a
times. For a step of s, it is roughly the range divided by
s. Always check the first and last values:
| Loop header | Values of i | Iterations |
|---|---|---|
for (int i = 1; i <= 10; i++) |
1, 2, …, 10 | 10 |
for (int i = 0; i < 10; i++) |
0, 1, …, 9 | 10 |
for (int i = 1; i < 10; i++) |
1, 2, …, 9 | 9 |
for (int i = 0; i <= 10; i += 2) |
0, 2, 4, 6, 8, 10 | 6 |
for (int i = 10; i > 0; i -= 3) |
10, 7, 4, 1 | 4 |
for (int i = 5; i < 5; i++) |
none | 0 |
Using <iomanip> with a for loop, we
can produce neat tables. Here is the growth of a Rs. 50,000 deposit at
7.5% simple interest for 1 to 5 years:
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
const double principal = 50000.0;
const double rate = 7.5; // percent per year
cout << setw(6) << "Year" << setw(14) << "Interest"
<< setw(14) << "Amount" << '\n';
cout << fixed << setprecision(2);
for (int year = 1; year <= 5; year++) {
double interest = principal * rate * year / 100;
cout << setw(6) << year << setw(14) << interest
<< setw(14) << principal + interest << '\n';
}
return 0;
} Year Interest Amount
1 3750.00 53750.00
2 7500.00 57500.00
3 11250.00 61250.00
4 15000.00 65000.00
5 18750.00 68750.00| Question about the problem | Suggested loop | Example |
|---|---|---|
| Is the number of repetitions known before the loop starts? | for |
print table 1 to 10; sum of n numbers |
| Does the loop stop because of an event or special value? | while |
sentinel input; balance reaching a target |
| Must the body run at least once? | do–while |
menus; input validation; “try again?” |
These are guidelines, not laws. All three loops are equally powerful; choose the one that makes the program easiest to read.
for (int i = 0, i < 5, i++) — compile error.for (int i = 1; i <= 5; i++); — the body becomes empty;
the block below runs just once, after the loop.i < n versus
i <= n; starting at 0 versus 1.long long fact = 0; makes every factorial 0.!= with a floating-point loop
variable; use an integer counter instead.for (init; condition; update) body — init once; then
condition → body → update, repeated.for is entry-controlled and equivalent to a
while loop.for when the number of iterations is known;
while for event-controlled loops; do–while for
at-least-once loops.for (A; B; C) { D; }
executed for a loop that runs twice?for (int i = 3; i <= 15; i += 4) cout << i << ' ';for (int i = 20; i >= 0; i -= 5)
run? List the values.for loop:
int k = 10; while (k > 0) { cout << k; k -= 2; }int i; for (i = 0; i < 3; i++); cout << i;for loop that prints the sum of all multiples
of 3 between 1 and 100.3 7 11 15. 3. 5
times: 20, 15, 10, 5, 0. 4.
for (int k = 10; k > 0; k -= 2) cout << k; 5.
3 — the semicolon makes the body empty; the loop just
counts to 3, then cout runs once. 6.
int sum = 0; for (int m = 3; m <= 100; m += 3) sum += m; cout << sum;
(answer 1683).C++ has four statements that transfer control (“jump”) to another
place: break, continue, goto and
return. You already know that return ends
main() (and, as you will see in the Functions chapter, any function). In this lesson you
will learn the other three.
You met break in switch (in The switch Statement and Menu-Driven Programs). Inside a
loop, break ends the loop immediately.
Control jumps to the first statement after the loop, even if the loop
condition is still true.
Typical use: searching. Once we have found what we are looking for, there is no point continuing.
#include <iostream>
using namespace std;
int main() {
int number;
cout << "Enter a number greater than 1: ";
cin >> number;
int smallestFactor = number; // assume none found
for (int d = 2; d < number; d++) {
if (number % d == 0) {
smallestFactor = d;
break; // found it: stop searching
}
}
if (smallestFactor == number) {
cout << number << " has no factor other than 1 and itself.\n";
} else {
cout << "Smallest factor of " << number << " is "
<< smallestFactor << '\n';
}
return 0;
}Enter a number greater than 1: 91
Smallest factor of 91 is 7For 91, the loop tests 2, 3, 4, 5, 6, finds that 7 divides 91, and
stops. Without break it would keep testing up to 90 and
(worse) overwrite smallestFactor with 13, the
largest factor it meets.
Flow of break inside a loop:
loop condition true
|
v
+--------------+
| statements |
| if (found) |-------- break ---------+
| break; | |
| statements | |
+--------------+ |
| |
back to condition v
first statement
after the loop
continue does not end the loop. It
skips the rest of the current iteration and goes on to
the next one.
while or do–while loop, control jumps
to the condition test.for loop, control jumps to the
update part, then the condition.Typical use: ignore certain values. Example: add the marks of students, but skip invalid entries.
#include <iostream>
using namespace std;
int main() {
// print numbers 1 to 20 that are NOT multiples of 3
for (int i = 1; i <= 20; i++) {
if (i % 3 == 0) {
continue; // skip the cout for multiples of 3
}
cout << i << ' ';
}
cout << '\n';
// add 5 marks, ignoring invalid ones
int total = 0, valid = 0;
cout << "Enter 5 marks: ";
for (int k = 1; k <= 5; k++) {
int marks;
cin >> marks;
if (marks < 0 || marks > 100) {
cout << "Skipping invalid value " << marks << '\n';
continue;
}
total += marks;
valid++;
}
cout << "Valid entries: " << valid << ", total: " << total << '\n';
return 0;
}1 2 4 5 7 8 10 11 13 14 16 17 19 20
Enter 5 marks: 45 -3 78 120 66
Skipping invalid value -3
Skipping invalid value 120
Valid entries: 3, total: 189Notice that k still counts all 5 inputs (the update
k++ runs even after continue), but only the 3
valid marks are added.
In a for loop, continue still performs the
update, so the loop moves on. In a while loop the update is
usually at the end of the body — and
continue skips it!
int i = 1;
while (i <= 10) {
if (i % 3 == 0) {
continue; // jumps to the condition: i++ is skipped!
}
cout << i << ' ';
i++;
}When i becomes 3, continue jumps back to
the test with i still 3, and again, and again: an infinite
loop. Fix: update before continue, or use a
for loop.
int i = 0;
while (i < 10) {
i++; // update first
if (i % 3 == 0) continue;
cout << i << ' ';
}| Point | break | continue |
|---|---|---|
| Effect | ends the loop completely | ends only the current iteration |
| Where control goes | first statement after the loop | next iteration (condition, or update in for) |
| Also used in | switch |
loops only |
| Typical use | stop searching when found; exit a menu | skip unwanted values |
Both affect only the innermost loop (or
switch) that contains them. This matters with nested loops
(see Nested Loops, Patterns and Tables) and with a
switch inside a loop.
In a menu loop, a break inside a case ends
the switch, not the loop. This is why, in The do–while Loop and Input Validation, we controlled
the menu loop with the condition choice != 4. If you want
an “Exit” case to leave the loop directly, use a bool
flag:
#include <iostream>
using namespace std;
int main() {
bool running = true;
while (running) {
cout << "1. Say Namaste 2. Say Dhanyabad 0. Exit : ";
int choice;
cin >> choice;
switch (choice) {
case 1:
cout << "Namaste!\n";
break; // leaves the switch only
case 2:
cout << "Dhanyabad!\n";
break;
case 0:
running = false; // loop condition becomes false
break;
default:
cout << "Invalid choice\n";
}
}
cout << "Program ended.\n";
return 0;
}1. Say Namaste 2. Say Dhanyabad 0. Exit : 1
Namaste!
1. Say Namaste 2. Say Dhanyabad 0. Exit : 2
Dhanyabad!
1. Say Namaste 2. Say Dhanyabad 0. Exit : 5
Invalid choice
1. Say Namaste 2. Say Dhanyabad 0. Exit : 0
Program ended.An infinite loop is a loop whose condition never becomes false. There are two kinds:
break (or return) inside to exit when
some condition happens. Game loops, servers and embedded devices (for
example, small IoT microcontroller boards) often run this way.Common causes of accidental infinite loops:
| Code | Why it never ends |
|---|---|
int i = 1; while (i <= 10) { cout << i; } |
no update: i stays 1 |
for (int i = 1; i <= 10; i--) |
update goes the wrong way |
while (i <= 10); |
stray semicolon: empty body repeats forever |
while (i = 5) |
assignment: value 5 is always true |
for (int i = 1; i != 10; i += 2) |
i jumps from 9 to 11 and never equals 10 |
continue before the update in a while
loop |
update is skipped |
The standard ways to write an intentional infinite loop are
while (true) and for (;;). They must contain a
break:
#include <iostream>
using namespace std;
int main() {
int total = 0;
while (true) { // intentional infinite loop
int value;
cout << "Enter a positive number (0 to stop): ";
cin >> value;
if (value == 0) {
break; // the only way out
}
if (value < 0) {
cout << "Negative ignored.\n";
continue;
}
total += value;
}
cout << "Total = " << total << '\n';
return 0;
}Enter a positive number (0 to stop): 15
Enter a positive number (0 to stop): -4
Negative ignored.
Enter a positive number (0 to stop): 25
Enter a positive number (0 to stop): 0
Total = 40This style is an alternative to the priming-read sentinel loop from
Introduction to Loops and the while Loop. It avoids
writing the cin statement twice. Its disadvantage is that
the reader must look inside the body to find how the loop ends, so keep
such loops short and clear.
goto jumps unconditionally to a label —
a name followed by a colon — somewhere in the same function.
#include <iostream>
using namespace std;
int main() {
int i = 1;
start: // a label
cout << i << ' ';
i++;
if (i <= 5) {
goto start; // jump back to the label
}
cout << '\n';
return 0;
}1 2 3 4 5 This works — it behaves like a loop — but it is much harder to read
than for (int i = 1; i <= 5; i++). In large programs,
gotos that jump backwards and forwards produce what
programmers call “spaghetti code”: the flow of control
is tangled like a plate of noodles, making the program very hard to
understand, test and fix. In 1968 the computer scientist Edsger Dijkstra
published a famous letter, “Go To Statement Considered
Harmful”, which helped start the structured
programming movement. Structured programming says that every
program can be written using only three structures — sequence, selection
and repetition — each with one entry and one exit.
Good rules to follow:
goto in your programs.
Every problem in this tutorial can be solved with loops,
break and continue.goto is very rarely used,
occasionally to jump out of deeply nested loops.)goto to jump over the
initialisation of a variable into its scope.break inside a switch to leave
the surrounding loop — it leaves only the switch.continue in a while loop before the
update, causing an infinite loop.while (true) without any reachable
break.break outside a loop or switch —
compile error: “break statement not within loop or switch”.!= in a loop condition when the counter may jump
over the target value; prefer < or
<=.goto instead of a proper loop.break ends the innermost loop or switch
immediately.continue skips the rest of the current iteration; in
for the update still runs, in while it may be
skipped.while (true),
for (;;)) must contain a break or
return.goto jumps to a label; it leads to spaghetti code and
is not used in this tutorial.break and
continue?for (int i = 1; i <= 10; i++) { if (i == 6) break; cout << i; }for (int i = 1; i <= 10; i++) { if (i % 2 == 0) continue; cout << i; }int n = 1; while (n != 20) { n += 3; }goto code using a while loop:
int k = 10; again: cout << k; k -= 2; if (k > 0) goto again;while (true) loop that reads numbers and stops
when the user enters a number divisible by 7, then prints how many
numbers were read.break ends the whole loop; continue ends
only the current iteration and moves to the next. 2. 12345.
3. 13579. 4. n takes 1, 4, 7, 10, 13, 16, 19,
22 … and never equals 20. Use while (n < 20). 5.
int k = 10; do { cout << k; k -= 2; } while (k > 0);
(a do–while matches exactly, since the goto
version prints before testing; while (k > 0) gives the
same output here because k starts at 10). 6.
int count = 0; while (true) { int x; cin >> x; count++; if (x % 7 == 0) break; } cout << count;A nested loop is a loop placed inside the body of
another loop. The first one is called the outer loop
and the one inside is the inner loop. Any kind of loop
can be nested inside any other kind (for inside
while, while inside for, and so
on), but nested for loops are the most common.
Think of a clock. The minute hand goes all the way round (60 steps) for each single step of the hour hand. The hour hand is the outer loop; the minute hand is the inner loop. Or think of a timetable: for each day (outer), you attend every period (inner).
The rule: for every single iteration of the outer loop, the inner loop runs completely — from its initialisation until its condition becomes false.
#include <iostream>
using namespace std;
int main() {
for (int day = 1; day <= 3; day++) { // outer loop
cout << "Day " << day << ": ";
for (int period = 1; period <= 4; period++) { // inner loop
cout << "P" << period << ' ';
}
cout << '\n'; // new line after each day
}
return 0;
}Day 1: P1 P2 P3 P4
Day 2: P1 P2 P3 P4
Day 3: P1 P2 P3 P4 Trace of the first two outer iterations:
| day | period values in inner loop | printed on that line |
|---|---|---|
| 1 | 1, 2, 3, 4 (then 5 fails) | Day 1: P1 P2 P3 P4 |
| 2 | 1, 2, 3, 4 (then 5 fails) | Day 2: P1 P2 P3 P4 |
| 3 | 1, 2, 3, 4 (then 5 fails) | Day 3: P1 P2 P3 P4 |
Notice that period starts again at 1
for every new day, because the inner for header — including
its initialisation — is executed anew each time the outer body runs.
Total iterations. If the outer loop runs m times and the inner loop runs n times for each, the inner body runs m x n times. Above: 3 x 4 = 12 “P” items printed.
Printing patterns of stars and numbers is the best exercise for mastering nested loops. Use this four-step method for every pattern:
for (int row = 1; row <= n; row++).row.
Make a small table.The key insight is the table from step 2.
* * * * *
* * * * *
* * * * *
Rows: 3. Stars per row: always 5 (does not depend on
row).
*
* *
* * *
* * * *
| row | stars |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
So stars in row = row: the inner loop is
for (int star = 1; star <= row; star++).
* * * *
* * *
* *
*
Stars in row = n - row + 1 (for n = 4: 4, 3, 2, 1). Or
run the outer loop backwards:
for (int row = n; row >= 1; row--) with row
stars.
The following program prints all three patterns for a size read from the user.
#include <iostream>
using namespace std;
int main() {
int n;
cout << "Enter size: ";
cin >> n;
cout << "Rectangle (" << n << " x 5):\n";
for (int row = 1; row <= n; row++) {
for (int col = 1; col <= 5; col++) {
cout << "* ";
}
cout << '\n';
}
cout << "Right triangle:\n";
for (int row = 1; row <= n; row++) {
for (int star = 1; star <= row; star++) {
cout << "* ";
}
cout << '\n';
}
cout << "Inverted triangle:\n";
for (int row = 1; row <= n; row++) {
for (int star = 1; star <= n - row + 1; star++) {
cout << "* ";
}
cout << '\n';
}
return 0;
}Enter size: 4
Rectangle (4 x 5):
* * * * *
* * * * *
* * * * *
* * * * *
Right triangle:
*
* *
* * *
* * * *
Inverted triangle:
* * * *
* * *
* *
* Instead of stars we can print numbers. What we print depends on which variable we use:
| Pattern (n = 4) | What is printed in each position | Inner statement |
|---|---|---|
1 / 1 2 / 1 2 3 /
1 2 3 4 |
the column number | cout << col << ' '; |
1 / 2 2 / 3 3 3 /
4 4 4 4 |
the row number | cout << row << ' '; |
1 / 2 3 / 4 5 6 /
7 8 9 10 |
a counter that never resets (Floyd’s triangle) | cout << num++ << ' '; |
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
const int n = 4;
cout << "Column numbers:\n";
for (int row = 1; row <= n; row++) {
for (int col = 1; col <= row; col++) {
cout << col << ' ';
}
cout << '\n';
}
cout << "Row numbers:\n";
for (int row = 1; row <= n; row++) {
for (int col = 1; col <= row; col++) {
cout << row << ' ';
}
cout << '\n';
}
cout << "Floyd's triangle:\n";
int num = 1; // declared OUTSIDE both loops
for (int row = 1; row <= n; row++) {
for (int col = 1; col <= row; col++) {
cout << setw(3) << num;
num++;
}
cout << '\n';
}
return 0;
}Column numbers:
1
1 2
1 2 3
1 2 3 4
Row numbers:
1
2 2
3 3 3
4 4 4 4
Floyd's triangle:
1
2 3
4 5 6
7 8 9 10For Floyd’s triangle, num must be declared
before the outer loop. If it were declared inside the
outer loop, it would reset to 1 on every row.
*
***
*****
*******
Each row has some leading spaces, then some stars. Make the table for n = 4:
| row | spaces | stars |
|---|---|---|
| 1 | 3 | 1 |
| 2 | 2 | 3 |
| 3 | 1 | 5 |
| 4 | 0 | 7 |
Formulas: spaces = n - row; stars =
2 * row - 1. So each row needs two inner
loops, one after the other (not one inside the other).
#include <iostream>
using namespace std;
int main() {
int n;
cout << "Number of rows: ";
cin >> n;
for (int row = 1; row <= n; row++) {
for (int space = 1; space <= n - row; space++) {
cout << ' ';
}
for (int star = 1; star <= 2 * row - 1; star++) {
cout << '*';
}
cout << '\n';
}
return 0;
}Number of rows: 5
*
***
*****
*******
*********Nested loops are ideal for two-dimensional tables. The outer loop
chooses the row (first number); the inner loop chooses the column
(second number); the body prints the product. setw keeps
the columns straight.
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
const int size = 10;
cout << " x|"; // header row
for (int col = 1; col <= size; col++) {
cout << setw(4) << col;
}
cout << '\n' << "----+";
for (int col = 1; col <= size; col++) {
cout << "----";
}
cout << '\n';
for (int row = 1; row <= size; row++) {
cout << setw(4) << row << '|';
for (int col = 1; col <= size; col++) {
cout << setw(4) << row * col;
}
cout << '\n';
}
return 0;
} x| 1 2 3 4 5 6 7 8 9 10
----+----------------------------------------
1| 1 2 3 4 5 6 7 8 9 10
2| 2 4 6 8 10 12 14 16 18 20
3| 3 6 9 12 15 18 21 24 27 30
4| 4 8 12 16 20 24 28 32 36 40
5| 5 10 15 20 25 30 35 40 45 50
6| 6 12 18 24 30 36 42 48 54 60
7| 7 14 21 28 35 42 49 56 63 70
8| 8 16 24 32 40 48 56 64 72 80
9| 9 18 27 36 45 54 63 72 81 90
10| 10 20 30 40 50 60 70 80 90 100Nested loops can also be while loops, but then you must
reset the inner counter yourself before each run of the
inner loop:
int row = 1;
while (row <= 3) {
int col = 1; // reset for each row
while (col <= 4) {
cout << '*';
col++;
}
cout << '\n';
row++;
}Remember from break, continue, goto and Infinite
Loops that break and continue affect only
the innermost loop. A break inside the
inner loop ends that row’s inner loop, but the outer loop continues with
the next row. If you need to leave both loops, use a bool
flag that the outer loop condition also checks.
If the outer loop runs 1,000 times and the inner loop runs 1,000 times, the inner body runs 1,000,000 times. Three nested loops of 1,000 each would give 1,000,000,000 iterations, which takes noticeable time even on a fast computer. Nested loops are powerful, but we should be aware of how quickly the work grows. You will study this idea formally (as time complexity) in Data Structures and Algorithms.
cout << '\n'; inside the inner loop prints every star
on its own line; putting it outside the outer loop prints everything on
one line.i for both); the inner loop
overwrites the outer counter.n instead of
row gives a rectangle instead of a triangle.while loops.setw to align numeric tables.break/continue affect only the innermost
loop.How many times is cout << "#"; executed?
for (int i = 1; i <= 4; i++) for (int j = 1; j <= 6; j++) cout << "#";
What is printed?
for (int i = 1; i <= 3; i++) { for (int j = 1; j <= i; j++) cout << i * j << ' '; cout << '\n'; }
For an inverted pyramid of n = 4 rows (7, 5, 3, 1 stars), write
the formulas for spaces and stars in row row.
What does this print, and why?
for (int i = 1; i <= 3; i++) { for (int j = 1; j <= 3; j++) { if (j == 2) break; cout << i << j << ' '; } }
Write nested loops to print the pattern:
A
A B
A B C
A B C DWrite a program that prints the multiplication tables of 2 to 5 side by side for multipliers 1 to 10 (10 rows, 4 columns).
1; row 2: 2 4;
row 3: 3 6 9. 3. spaces = row - 1, stars =
2 * (n - row) + 1. 4. 11 21 31 —
break leaves only the inner loop each time, when
j becomes 2. 5.
for (int row = 1; row <= 4; row++) { for (char ch = 'A'; ch < 'A' + row; ch++) cout << ch << ' '; cout << '\n'; }
6. Outer loop m from 1 to 10 (rows); inner loop
t from 2 to 5; print
t << " x " << m << " = " << setw(2) << t * m
followed by a tab.Many number problems need the individual digits of an integer. Two operators from Arithmetic Operators, Precedence and Expression Evaluation do the work:
n % 10 gives the last digit of
n (for positive n). Example:
4735 % 10 is 5.n / 10 (integer division) removes the last
digit. Example: 4735 / 10 is 473.Repeating these two steps in a loop until n becomes 0
visits every digit, from right to left:
n = 4735 -> digit 5, n becomes 473
n = 473 -> digit 3, n becomes 47
n = 47 -> digit 7, n becomes 4
n = 4 -> digit 4, n becomes 0 (stop)
The loop pattern is:
while (n > 0) {
int digit = n % 10;
// ... use digit ...
n = n / 10;
}Because this loop destroys n, we first copy the original
number into another variable if we need it later.
reversed = reversed * 10 + digit. Multiplying by 10 shifts
the digits left, making room for the new digit on the right.#include <iostream>
using namespace std;
int main() {
int number;
cout << "Enter a positive integer: ";
cin >> number;
int n = number; // work on a copy
int sum = 0, count = 0;
long long reversed = 0; // reverse may be larger than an int
while (n > 0) {
int digit = n % 10;
sum += digit;
count++;
reversed = reversed * 10 + digit;
n /= 10;
}
cout << "Number of digits : " << count << '\n';
cout << "Sum of digits : " << sum << '\n';
cout << "Reversed number : " << reversed << '\n';
if (reversed == number) {
cout << number << " is a palindrome.\n";
} else {
cout << number << " is not a palindrome.\n";
}
return 0;
}Enter a positive integer: 4735
Number of digits : 4
Sum of digits : 19
Reversed number : 5374
4735 is not a palindrome.Enter a positive integer: 12321
Number of digits : 5
Sum of digits : 9
Reversed number : 12321
12321 is a palindrome.A tracing table (dry-run table) records the value of
every variable after each iteration. It is the most reliable way to
understand a loop and to find a bug without a computer. Here is the
trace for number = 4735:
| Iteration | n (start) | digit | sum | count | reversed | n (end) |
|---|---|---|---|---|---|---|
| — | — | — | 0 | 0 | 0 | 4735 |
| 1 | 4735 | 5 | 5 | 1 | 5 | 473 |
| 2 | 473 | 3 | 8 | 2 | 53 | 47 |
| 3 | 47 | 7 | 15 | 3 | 537 | 4 |
| 4 | 4 | 4 | 19 | 4 | 5374 | 0 |
The condition n > 0 is then false and the loop ends.
Every value matches the program output. Note one edge case: for input 0,
the loop runs zero times, so the program reports 0 digits. A fully
correct digit counter would treat 0 as a special case (1 digit).
A prime number is a whole number greater than 1
whose only factors are 1 and itself (2, 3, 5, 7, 11, 13, …). To test
n, try dividing it by every candidate d. If
any d divides exactly, n is not prime.
We do not need to try all numbers up to n - 1. If
n = a x b, then one of a or b
must be at most the square root of n. So it is enough to
test d while d * d <= n. For n = 1,000,003
this means about 1,000 tests instead of a million. (We write
d * d <= n rather than d <= sqrt(n) to
stay with exact integer arithmetic.)
The program below tests one number, then lists all primes up to 50 using a nested loop.
#include <iostream>
using namespace std;
int main() {
int n;
cout << "Enter a number: ";
cin >> n;
bool isPrime = (n >= 2); // 0, 1 and negatives: not prime
for (int d = 2; d * d <= n; d++) {
if (n % d == 0) {
isPrime = false;
break; // one factor is enough
}
}
cout << n << (isPrime ? " is prime.\n" : " is not prime.\n");
cout << "Primes up to 50: ";
for (int candidate = 2; candidate <= 50; candidate++) {
bool prime = true;
for (int d = 2; d * d <= candidate; d++) {
if (candidate % d == 0) {
prime = false;
break;
}
}
if (prime) {
cout << candidate << ' ';
}
}
cout << '\n';
return 0;
}Enter a number: 97
97 is prime.
Primes up to 50: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 This is the flag pattern: a bool
variable starts with an assumption (true) and the loop
changes it if the assumption is proved wrong. After the loop, the flag
tells us the result.
Factorial (n! = 1 x 2 x ... x n, and
0! = 1) was done in The for Loop with a
product accumulator starting at 1.
The Fibonacci series starts 0, 1, and each next term is the sum of the previous two: 0, 1, 1, 2, 3, 5, 8, 13, 21, … To generate it we keep the last two terms in variables and “slide” them forward each iteration:
| Step | first | second | next = first + second |
|---|---|---|---|
| start | 0 | 1 | 1 |
| slide | 1 | 1 | 2 |
| slide | 1 | 2 | 3 |
| slide | 2 | 3 | 5 |
#include <iostream>
using namespace std;
int main() {
int terms;
cout << "How many Fibonacci terms? ";
cin >> terms;
long long first = 0, second = 1;
for (int i = 1; i <= terms; i++) {
cout << first << ' ';
long long next = first + second;
first = second; // slide the window forward
second = next;
}
cout << '\n';
int n = 15;
long long factorial = 1;
for (int i = 2; i <= n; i++) {
factorial *= i;
}
cout << n << "! = " << factorial << '\n';
return 0;
}How many Fibonacci terms? 12
0 1 1 2 3 5 8 13 21 34 55 89
15! = 1307674368000The order of the three statements inside the Fibonacci loop
matters: if you write first = second; before computing
next, you lose the old value of first. (In the
Functions chapter you will also write factorial and
Fibonacci using recursion.)
The GCD (greatest common divisor, also called HCF) of two numbers is the largest number that divides both. The LCM (least common multiple) is the smallest number that both divide.
Euclid’s algorithm (about 2,300 years old and still
the best simple method): replace the pair (a, b) by
(b, a % b) repeatedly until b becomes 0. Then
a is the GCD. Once we have the GCD,
LCM = a * b / GCD (for positive a and b).
Trace for a = 48, b = 18:
| a | b | a % b |
|---|---|---|
| 48 | 18 | 12 |
| 18 | 12 | 6 |
| 12 | 6 | 0 |
| 6 | 0 | stop: GCD = 6 |
LCM = 48 x 18 / 6 = 144.
#include <iostream>
using namespace std;
int main() {
int first, second;
cout << "Enter two positive integers: ";
cin >> first >> second;
int a = first, b = second;
while (b != 0) {
int remainder = a % b;
a = b;
b = remainder;
}
int gcd = a;
long long lcm = static_cast<long long>(first) / gcd * second;
cout << "GCD(" << first << ", " << second << ") = " << gcd << '\n';
cout << "LCM(" << first << ", " << second << ") = " << lcm << '\n';
return 0;
}Enter two positive integers: 48 18
GCD(48, 18) = 6
LCM(48, 18) = 144We compute first / gcd * second (divide first) to keep
intermediate values small and reduce the risk of overflow.
When a loop gives wrong output, do not change code at random. Follow these steps:
< or
<=)? Does the update move toward ending the loop? Is the
update inside the braces?cout << "DEBUG i=" << i << " sum=" << sum << '\n';.
Compare with what you expected. Remove debug prints afterwards.Common loop bugs at a glance:
| Symptom | Likely cause |
|---|---|
| Program never finishes | missing/wrong update; ; after while (...);
continue skips the update; != jumps over the
target |
| One iteration too many or too few | < versus <=; starting at 0 versus
1 |
| Sum or product is garbage | accumulator not initialised |
| Product is always 0 | product accumulator started at 0 |
| Loop body runs only once | ; after for (...) or missing braces |
| Result correct for small input, wrong for large | overflow: use long long |
| Counter restarts unexpectedly | variable declared inside the loop instead of before it |
Example: find the bug using a debug print.
#include <iostream>
using namespace std;
int main() {
// Goal: average of the numbers 1..5, which should be 3.
int sum = 0;
int i;
for (i = 1; i < 5; i++) {
sum += i;
cout << "DEBUG i=" << i << " sum=" << sum << '\n';
}
cout << "Average = " << sum / 5.0 << '\n';
return 0;
}DEBUG i=1 sum=1
DEBUG i=2 sum=3
DEBUG i=3 sum=6
DEBUG i=4 sum=10
Average = 2The debug lines show that i never reached 5, so the sum
is 10 instead of 15. The condition should be i <= 5.
After fixing it, the average is 3, and the debug line should be
removed.
reversed or sum without
initialising it to 0.d < n and no break
— correct but very slow for large numbers.a * b / gcd with int for large
numbers (overflow).n % 10 gives the last digit; n / 10
removes it. Loop while n > 0.reversed = reversed * 10 + digit. Palindrome:
number equals its reverse.n >= 2 and no divisor d
with d * d <= n; use a bool flag and
break.(a, b) -> (b, a % b) until b is
0; LCM = a / gcd * b.n = 908. What are the sum of
digits and the reversed number?n when checking for primes?for (...) { cout << a; a = b; b = a + b; }n = a x b
with both factors greater than the square root, their product would
exceed n; so any factorisation has a factor at most the
square root. 3. (84, 36) → (36, 12) → (12, 0): GCD = 12; LCM = 84 / 12 x
36 = 252. 4. After a = b, the old a is lost,
so b = a + b doubles b instead of adding the
previous two terms. Use a temporary next. 5.
int product = 1; while (n > 0) { product *= n % 10; n /= 10; }
6.
for (int n = 100; n <= 999; n++) { int t = n, s = 0; while (t > 0) { int d = t % 10; s += d * d * d; t /= 10; } if (s == n) cout << n << ' '; }
— output 153 370 371 407.A function is a named block of code that does one specific job. You give it a name, and whenever you need that job done, you call the function by its name.
You have already used functions without thinking about it.
main() is a function. sqrt() and
pow() from The cmath Library, Worked
Problems and Debugging are functions. getline() from Input and Output with cin, cout, getline and iomanip is a
function.
A good real-life analogy is a restaurant kitchen in Thamel. The
waiter (the main() function) does not cook everything
himself. He passes an order to the momo cook, another order to the tea
maker, and another to the person who makes the bill. Each worker has one
job. The waiter only needs to know what each worker does, not
how they do it.
| Kitchen | Program |
|---|---|
| Waiter taking orders | main() function |
| Momo cook | a function such as makeMomo() |
| Order slip given to the cook | arguments (input data) |
| Plate of momo returned | return value (result) |
| Cook’s secret recipe | function body (hidden details) |
Modular programming means dividing a large program into smaller, independent parts called modules. In C++ the smallest module is a function. (Later we will also group functions into files and classes.)
The idea is sometimes called “divide and conquer”: a big problem is divided into small problems, and each small problem is solved by one function.
For example, a student result system could be divided like this:
+------------------+
| main() |
+------------------+
| | |
+---------+ | +----------+
| | |
+---------------+ +----------------+ +----------------+
| readMarks() | | calcAverage() | | printResult() |
+---------------+ +----------------+ +----------------+
|
+----------------+
| findGrade() |
+----------------+
| Advantage | Meaning |
|---|---|
| Reusability | Write once, call many times (even in other programs). |
| Readability | main() reads like a list of steps: read, calculate,
print. |
| Easier testing | Each function can be tested alone. |
| Easier debugging | An error is usually inside one small function, not everywhere. |
| Teamwork | Different programmers can write different functions at the same time. |
| Less repetition | The same code is not copied in five places. |
#include the
correct header file and call them.<cmath>The header <cmath> contains many mathematical
functions. Most of them take double values and return a
double.
| Function | Meaning | Example | Result |
|---|---|---|---|
sqrt(x) |
square root | sqrt(49.0) |
7 |
pow(x, y) |
x raised to power y | pow(2.0, 10.0) |
1024 |
fabs(x) |
absolute value (real) | fabs(-3.5) |
3.5 |
ceil(x) |
round up to whole number | ceil(4.1) |
5 |
floor(x) |
round down to whole number | floor(4.9) |
4 |
round(x) |
round to nearest whole number | round(4.5) |
5 |
sin(x), cos(x) |
trigonometry (x in radians) | sin(0.0) |
0 |
log(x) |
natural logarithm (base e) | log(1.0) |
0 |
log10(x) |
logarithm base 10 | log10(1000.0) |
3 |
Remember the difference between ceil, floor
and round. A taxi driver who charges for every started
kilometre is using ceil: 4.1 km is charged as 5 km.
#include <iostream>
#include <cmath>
using namespace std;
int main() {
double side = 5.0, distanceKm = 4.1;
cout << "sqrt(144) = " << sqrt(144.0) << '\n';
cout << "pow(2, 10) = " << pow(2.0, 10.0) << '\n';
cout << "fabs(-7.25) = " << fabs(-7.25) << '\n';
cout << "ceil(4.1) = " << ceil(distanceKm) << '\n';
cout << "floor(4.9) = " << floor(4.9) << '\n';
cout << "round(4.5) = " << round(4.5) << '\n';
cout << "log10(1000) = " << log10(1000.0) << '\n';
// Diagonal of a square: side * sqrt(2)
cout << "Diagonal of square with side 5 = "
<< side * sqrt(2.0) << '\n';
// Taxi fare: Rs. 100 per started km
int fare = static_cast<int>(ceil(distanceKm)) * 100;
cout << "Taxi fare for 4.1 km = Rs. " << fare << '\n';
return 0;
}sqrt(144) = 12
pow(2, 10) = 1024
fabs(-7.25) = 7.25
ceil(4.1) = 5
floor(4.9) = 4
round(4.5) = 5
log10(1000) = 3
Diagonal of square with side 5 = 7.07107
Taxi fare for 4.1 km = Rs. 500<cstdlib>| Function | Meaning |
|---|---|
abs(n) |
absolute value of an integer, e.g. abs(-12) gives
12 |
rand() |
returns a pseudo-random integer from 0 to
RAND_MAX |
srand(seed) |
sets the starting point (seed) for rand() |
exit(code) |
ends the program immediately with a status code |
RAND_MAX is a constant. Its value depends on the
compiler: it is at least 32767, and on g++ for Linux it is 2147483647.
Never assume one fixed value.
<cctype>These functions test or change a single character.
| Function | Returns true (non-zero) when the character is… |
|---|---|
isalpha(ch) |
a letter (A–Z, a–z) |
isdigit(ch) |
a digit (0–9) |
isalnum(ch) |
a letter or a digit |
isspace(ch) |
a space, tab or newline |
isupper(ch) |
an uppercase letter |
islower(ch) |
a lowercase letter |
toupper(ch) and tolower(ch) return the
converted character. They return an int, so we convert the
result back to char with
static_cast<char> before printing it; otherwise
cout prints a number.
#include <iostream>
#include <cctype>
#include <string>
using namespace std;
int main() {
string password = "Ktm@2081 nepal";
int letters = 0, digits = 0, spaces = 0, others = 0;
for (char ch : password) {
if (isalpha(ch))
letters++;
else if (isdigit(ch))
digits++;
else if (isspace(ch))
spaces++;
else
others++;
}
cout << "Text : " << password << '\n';
cout << "Letters : " << letters << '\n';
cout << "Digits : " << digits << '\n';
cout << "Spaces : " << spaces << '\n';
cout << "Others : " << others << '\n';
cout << "In capitals: ";
for (char ch : password)
cout << static_cast<char>(toupper(ch));
cout << '\n';
return 0;
}Text : Ktm@2081 nepal
Letters : 8
Digits : 4
Spaces : 1
Others : 1
In capitals: KTM@2081 NEPAL(The for (char ch : password) loop visits each character
of the string in turn. It is called a range-based for loop;
strings are covered fully in the Strings and
Structures chapter.)
rand(), srand() and time()Games, quizzes and simulations need random numbers.
rand() gives a pseudo-random number: it
looks random but is really produced by a fixed formula starting from a
number called the seed. The same seed always gives the
same sequence.
To get a different sequence each time the program runs, we seed with
the current time. time(nullptr) from
<ctime> returns the number of seconds since 1 January
1970, which changes every second.
srand(time(nullptr)); // call ONCE, at the start of main()
int r = rand(); // 0 .. RAND_MAXTo get a number in a range, use the modulus operator
%:
| Want | Formula | Possible values |
|---|---|---|
| 0 to n-1 | rand() % n |
e.g. rand() % 6 gives 0–5 |
| 1 to n | rand() % n + 1 |
e.g. rand() % 6 + 1 gives 1–6 (a die) |
| low to high | rand() % (high - low + 1) + low |
e.g. 10 to 20 |
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
srand(time(nullptr)); // seed once
cout << "Rolling a die 5 times: ";
for (int i = 1; i <= 5; i++)
cout << rand() % 6 + 1 << ' ';
cout << '\n';
int token = rand() % (999 - 100 + 1) + 100; // 100 to 999
cout << "Your bank queue token: " << token << '\n';
int toss = rand() % 2;
cout << "Coin toss: " << (toss == 0 ? "Head" : "Tail") << '\n';
return 0;
}Your numbers will be different, because the seed is the current time.
Rolling a die 5 times: 3 1 1 3 6
Your bank queue token: 755
Coin toss: HeadTwo notes for accuracy. First, rand() % n is slightly
unfair when n does not divide evenly into
RAND_MAX + 1, but for simple practice games it is good
enough. Second, modern C++ has a better random library called
<random>; we use rand() here because it
is simple and still very common in textbooks and older code.
Forgetting the header. Using sqrt without
#include <cmath> may give “‘sqrt’ was not declared in
this scope”.
Calling srand(time(nullptr)) inside a loop. Inside a
fast loop the time does not change, so every number is the same. Call
srand once at the start of
main().
Wrong range formula. rand() % 6 gives 0–5, not 1–6.
Add 1 for a die.
Printing toupper(ch) directly:
cout << toupper('a'); // wrong: prints 65
cout << static_cast<char>(toupper('a')); // right: prints AUsing abs() for real numbers in old code. For
double values, use fabs() (or
std::abs with <cmath> included) so the
fractional part is not lost.
Thinking pow(2, 3) gives an exact integer. It
returns a double. For whole numbers it is usually fine, but
when you need an int, use
static_cast<int>(round(pow(2.0, 3.0))) or multiply in
a loop.
<cmath>: sqrt, pow,
fabs, ceil, floor,
round, log10, trigonometric functions.<cctype>: isalpha,
isdigit, isspace, isupper,
islower, toupper, tolower.rand() % (high - low + 1) + low gives a number from
low to high; seed once with
srand(time(nullptr)).cout << ceil(7.2) << ' ' << floor(7.8) << ' ' << round(7.5);?srand() print the
same “random” numbers every time?cout << static_cast<char>(tolower('Q')); print?
What would it print without static_cast<char>?<cctype> functions.8 7 8. 3.
rand() % 11 + 50. 4. Without a seed, rand()
always starts from the same default seed, so the sequence is identical.
5. q; without the cast it prints the integer code 113. 6.
Read char ch; cin >> ch; then
if (isdigit(ch)) ... else if (isalpha(ch)) ... else ....Library functions cover general jobs such as square roots. But no
library contains calculateElectricityBill() for the Nepal
Electricity Authority tariff or findGrade() for your
college. For these jobs we write user-defined
functions.
Every user-defined function involves three things:
| Part | Purpose | Example |
|---|---|---|
| Prototype (declaration) | Tells the compiler the function’s name, return type and parameter
types, before it is used. Ends with ;. |
double circleArea(double radius); |
| Definition | The actual code of the function (header plus body). | double circleArea(double radius) { ... } |
| Call | Uses (runs) the function. | a = circleArea(7.0); |
The general form of a definition is:
returnType functionName(type1 param1, type2 param2)
{
// body: statements that do the job
return value; // send the result back (not needed for void)
}int, double, bool,
char, string, or void if there is
no result.printLine, findMax, isEven.().{ }.#include <iostream>
using namespace std;
double circleArea(double radius); // prototype
int main() {
double r1 = 7.0, r2 = 2.5;
double a1 = circleArea(r1); // call, result stored
cout << "Area with radius " << r1 << " = " << a1 << '\n';
// A call can be used directly inside an expression
cout << "Area with radius " << r2 << " = "
<< circleArea(r2) << '\n';
cout << "Total area = "
<< circleArea(r1) + circleArea(r2) << '\n';
return 0;
}
double circleArea(double radius) { // definition
const double PI = 3.14159;
double area = PI * radius * radius;
return area; // send the result back
}Area with radius 7 = 153.938
Area with radius 2.5 = 19.6349
Total area = 173.573These two words are often mixed up, so define them clearly:
circleArea(double radius), radius is the
parameter.circleArea(r1), r1 (which holds
7.0) is the argument.A simple way to remember: the parameter is the empty box in the form; the argument is what you write in the box. The number, order and types of arguments must match the parameters.
The C++ compiler reads a file from top to bottom. When it sees a call, it must already know the function’s name, return type and parameter types. There are two ways to make this true:
main(). Then no separate prototype is needed.main() and the
definition below main(). Many programmers prefer this
because main() comes first and the program reads like a
table of contents.If neither is done, the compiler stops with an error:
#include <iostream>
using namespace std;
int main() {
cout << square(5) << '\n'; // square is not known yet
return 0;
}
int square(int n) {
return n * n;
}error: 'square' was not declared in this scope
In a prototype the parameter names are optional:
double circleArea(double); is also valid. Writing the names
is better because it documents what each parameter means.
return statementreturn does two things:
A function can contain more than one return, but only
one of them runs each time. This is common in functions that return
bool:
bool isEven(int n) {
if (n % 2 == 0)
return true;
return false; // only reached when n is odd
}A function with a non-void return type must return a
value on every path. If you forget, g++ -Wall warns
“control reaches end of non-void function”, and the result is undefined
(it can be any garbage value). Always fix this warning.
void functionsA void function does a job but does not send back a
value. Printing is a typical example. A void function may
use return; (with no value) to leave early, or simply reach
its closing brace.
A function may also take no parameters. Write empty parentheses in both the definition and the call.
#include <iostream>
#include <string>
using namespace std;
void printLine() { // no parameters
cout << "--------------------------------\n";
}
void printReceipt(string name, int units) {
printLine(); // a function calling another
cout << "Customer : " << name << '\n';
cout << "Units : " << units << '\n';
if (units == 0) {
cout << "No consumption this month.\n";
printLine();
return; // leave early
}
cout << "Status : Bill generated\n";
printLine();
}
int main() {
printReceipt("Sita Sharma", 125);
printReceipt("Ram Thapa", 0);
return 0;
}--------------------------------
Customer : Sita Sharma
Units : 125
Status : Bill generated
--------------------------------
--------------------------------
Customer : Ram Thapa
Units : 0
No consumption this month.
--------------------------------A void function call is a complete statement by itself.
You cannot write x = printLine(); or
cout << printLine();, because there is no value to
store or print.
When a function is called, the program “jumps” to the function, runs its body, and then comes back to the exact place of the call. The following program prints messages so that we can see the order.
#include <iostream>
using namespace std;
int addMarks(int theory, int practical) {
cout << " [addMarks] started with " << theory
<< " and " << practical << '\n';
int total = theory + practical;
cout << " [addMarks] returning " << total << '\n';
return total;
}
int main() {
cout << "1. main starts\n";
int result = addMarks(62, 18);
cout << "2. back in main, result = " << result << '\n';
result = addMarks(45, 20);
cout << "3. back in main, result = " << result << '\n';
cout << "4. main ends\n";
return 0;
}1. main starts
[addMarks] started with 62 and 18
[addMarks] returning 80
2. back in main, result = 80
[addMarks] started with 45 and 20
[addMarks] returning 65
3. back in main, result = 65
4. main endsThe steps for the first call are:
| Step | Where | What happens |
|---|---|---|
| 1 | main |
prints “1. main starts” |
| 2 | main |
calls addMarks(62, 18); control jumps to the
function |
| 3 | addMarks |
theory = 62, practical = 18 (copies of the
arguments) |
| 4 | addMarks |
computes total = 80, prints messages |
| 5 | addMarks |
return total; sends 80 back, function ends |
| 6 | main |
80 is stored in result; execution continues on the next
line |
calculateBill
should calculate; a separate printBill should print. A
function that is hard to name is probably doing too much.isPassed(marks) is better
than check(m).circleArea returns
the area; the caller decides what to do with it.Putting a semicolon after the definition header:
int square(int n); // this is a prototype, and the { } block
{ // below it is NOT attached to any function
return n * n;
}Remove the ; in the definition.
Prototype and definition that do not match. If the parameter
types differ (for example double in the prototype and
int in the definition), the compiler treats them as two
different functions, and the linker reports “undefined reference”. If
only the return types differ, the compiler reports conflicting
declarations. Copy the header exactly.
Forgetting return in a non-void
function (warning: control reaches end of non-void function).
Writing the type in the call: circleArea(double r1);
is wrong; the call is circleArea(r1).
Defining a function inside another function. C++ does not allow a
function definition inside main().
Ignoring the returned value: circleArea(5.0);
compiles, but the result is thrown away.
return ends the function and sends one value back; a
void function returns no value.Write a prototype for a function calcBMI that takes
weight (kg) and height (m) as double and returns a
double.
What is the difference between a parameter and an argument? Give an example of each.
What is the output?
int triple(int x) { return x * 3; }
// inside main:
cout << triple(2) + triple(triple(1)) << '\n';Why is int y = printLine(); an error if
printLine is declared as
void printLine()?
Write a function bool isPassed(int marks) that
returns true if marks are 40 or more.
What does g++ -Wall warn about in
int f(int a) { if (a > 0) return 1; }, and why is it
dangerous?
double calcBMI(double weight, double height); 2.
Parameter is the variable in the header (double radius);
argument is the value in the call (circleArea(7.0)). 3.
triple(2) = 6, triple(triple(1)) =
triple(3) = 9, output 15. 4. A
void function returns no value, so there is nothing to
store in y. 5.
bool isPassed(int marks) { return marks >= 40; } 6.
“control reaches end of non-void function”: when a <= 0
no value is returned and the result is undefined.In every function we have written so far, the parameters received copies of the arguments. This is called pass by value (or call by value), and it is the default in C++.
When the call addMarks(62, 18) runs, the computer
creates new variables theory and practical
that belong to the function, and copies 62 and 18 into them. The
function works only with its own copies. When the function ends, the
copies are destroyed.
Analogy: you give a photocopy of your marksheet to the college office. The clerk may write notes on the photocopy, but your original marksheet at home does not change.
#include <iostream>
using namespace std;
void addBonus(int marks) {
cout << " inside addBonus, before: marks = " << marks << '\n';
marks = marks + 5; // changes only the copy
cout << " inside addBonus, after : marks = " << marks << '\n';
}
int main() {
int aaravMarks = 70;
cout << "In main, before call: " << aaravMarks << '\n';
addBonus(aaravMarks);
cout << "In main, after call : " << aaravMarks << '\n';
return 0;
}In main, before call: 70
inside addBonus, before: marks = 70
inside addBonus, after : marks = 75
In main, after call : 70The memory picture during the call looks like this. The two variables are in different places in memory:
| Variable | Belongs to | Value before marks = marks + 5 |
Value after |
|---|---|---|---|
aaravMarks |
main |
70 | 70 |
marks |
addBonus |
70 (a copy) | 75 |
If we want main to receive the new value, the function
must return it, and main must store
it:
int addBonus(int marks) {
return marks + 5;
}
// in main:
aaravMarks = addBonus(aaravMarks); // now aaravMarks becomes 75(In Passing Data by Reference you will see a second way: pass by reference.)
| Advantage | Limit |
|---|---|
| Safe: the function cannot accidentally damage the caller’s data. | The function cannot change the caller’s variables. |
The argument can be any expression: square(3 + 4),
square(x * 2), square(10). |
Only one value can come back through return. |
| Easy to understand and debug. | Copying large objects (long strings, big structures) takes time and memory. |
For small types such as int, double,
char and bool, copying is extremely cheap, so
pass by value is the normal choice.
Because the parameter is just a new variable that receives a value, any expression that produces the right type can be an argument. The expression is evaluated first, then the result is copied.
double a = circleArea(7.0); // literal constant
double b = circleArea(radius); // variable
double c = circleArea(diameter / 2); // expression
double d = circleArea(sqrt(16.0)); // result of another functionA function can take many parameters, each with its own type. Separate them with commas, and write the type for every parameter:
double simpleInterest(double principal, double rate, int years); // right
double simpleInterest(double principal, rate, int years); // wrongArguments are matched to parameters by position, not by name. The first argument goes to the first parameter, the second to the second, and so on. If you swap them, the program still compiles, but the answer is wrong. This is a logic error (see Structure of a C++ Program, the Build Process, Errors and Ethics).
#include <iostream>
using namespace std;
// Amount to repay = principal + simple interest
// simple interest = P * T * R / 100
double amountToRepay(double principal, double rate, int years) {
double interest = principal * years * rate / 100.0;
return principal + interest;
}
int main() {
double loan = 200000.0; // Rs.
double rate = 12.5; // percent per year
int years = 3;
cout << "Correct order : Rs. "
<< amountToRepay(loan, rate, years) << '\n';
// Wrong order: rate and principal exchanged (logic error)
cout << "Wrong order : Rs. "
<< amountToRepay(rate, loan, years) << '\n';
return 0;
}Correct order : Rs. 275000
Wrong order : Rs. 75012.5The second call compiles without any error or warning, because both
arguments are double. But it treats Rs. 12.5 as the loan
and 200000 percent as the rate, and gives a wrong answer. The compiler
cannot catch this kind of mistake; only careful checking (and testing
with values you have calculated by hand) can. Always pass arguments in
the order the function expects.
If an argument’s type differs from the parameter’s type, C++ converts it automatically where possible (the same implicit conversions you saw in Assignment, Increment/Decrement, Type Conversion and Overflow):
int argument to a double parameter: safe.
circleArea(7) receives 7.0.double argument to an int parameter: the
fraction is cut off. square(2.9) with
int square(int n) receives 2. Plain
-Wall does not warn about this; the option
-Wconversion does. Be careful.Real programs use many small functions that call each other. Below is a simplified electricity bill calculator. (The slab rates are simplified example figures, not the official Nepal Electricity Authority tariff.)
| Units consumed | Rate per unit |
|---|---|
| first 20 units | Rs. 3 |
| next 30 units (21–50) | Rs. 7 |
| above 50 units | Rs. 9 |
| service charge (every bill) | Rs. 50 |
#include <iostream>
#include <string>
using namespace std;
const int SERVICE_CHARGE = 50;
int energyCharge(int units) {
if (units <= 20)
return units * 3;
else if (units <= 50)
return 20 * 3 + (units - 20) * 7;
else
return 20 * 3 + 30 * 7 + (units - 50) * 9;
}
int totalBill(int units) {
return energyCharge(units) + SERVICE_CHARGE;
}
void printBill(string customer, int previous, int current) {
int units = current - previous;
cout << customer << ": " << units << " units, bill = Rs. "
<< totalBill(units) << '\n';
}
int main() {
printBill("Sita", 1200, 1215); // 15 units
printBill("Ram", 3400, 3445); // 45 units
printBill("Priya", 870, 950); // 80 units
return 0;
}Sita: 15 units, bill = Rs. 95
Ram: 45 units, bill = Rs. 285
Priya: 80 units, bill = Rs. 590Notice the chain of calls: main → printBill
→ totalBill → energyCharge. Each function
receives copies of the values it needs and returns one result. Each
function is short enough to check by hand. For Priya: 20 × 3 = 60, 30 ×
7 = 210, 30 × 9 = 270, energy charge 540, plus 50 service charge =
590.
A classic exercise is to build max3 from
max2. This shows reuse: the logic for
comparing two numbers is written once.
#include <iostream>
using namespace std;
int max2(int a, int b) {
return (a > b) ? a : b;
}
int max3(int a, int b, int c) {
return max2(max2(a, b), c);
}
int main() {
cout << "Largest of 45, 78, 62 is " << max3(45, 78, 62) << '\n';
cout << "Largest of -4, -9, -1 is " << max3(-4, -9, -1) << '\n';
return 0;
}Largest of 45, 78, 62 is 78
Largest of -4, -9, -1 is -1Note that the parameter names a and b in
max2 and max3 are completely separate
variables. Each function has its own copies, even when the names are the
same.
Expecting a value parameter to change the caller’s variable (as
in addBonus above). Return the new value, or use a
reference (see Passing Data by Reference).
Writing one type for several parameters:
int f(int a, b) is an error; write
int f(int a, int b).
Passing arguments in the wrong order. The compiler cannot catch this when the types match.
Passing a double to an int parameter
and silently losing the fraction.
Calling a function with the wrong number of arguments:
error: too few arguments to function 'int max3(int, int, int)'int,
double, char, bool).In one sentence, what does “pass by value” mean?
What is the output?
void change(int n) { n = n * 10; }
// inside main:
int x = 4;
change(x);
cout << x << '\n';Rewrite change from question 2 so that
main can obtain the value 40. Show the call.
Find the error:
double average(int a, b, c) { return (a + b + c) / 3.0; }
Using the bill program, what is the bill for 50 units? For 51 units?
Write a function
double percentage(int obtained, int fullMarks) and call it
to print the percentage of 437 out of 500.
4. 3.
int change(int n) { return n * 10; } and
x = change(x); 4. Every parameter needs a type:
double average(int a, int b, int c). 5. 50 units: 60 + 210
+ 50 = Rs. 320; 51 units: 60 + 210 + 9 + 50 = Rs. 329. 6.
return obtained * 100.0 / fullMarks; and
cout << percentage(437, 500); prints
87.4.A reference is another name (an alias) for
an existing variable. It is declared with & after the
type:
#include <iostream>
using namespace std;
int main() {
int marks = 70;
int& m = marks; // m is another name for marks
m = 85; // changes marks!
cout << "marks = " << marks << ", m = " << m << '\n';
marks = 90;
cout << "marks = " << marks << ", m = " << m << '\n';
return 0;
}marks = 85, m = 85
marks = 90, m = 90Think of a person with a nickname. At home Aarav is called “Babu”. If
Babu gets a haircut, Aarav gets a haircut; they are the same person. In
the same way, m and marks are the same memory
location with two names. Two rules:
int& m; alone is an error.(The & here is not the “address-of”
operator. The same symbol has a different meaning in a declaration.
Addresses and pointers are covered in the Pointers and
Dynamic Memory chapter.)
If a function parameter is a reference, it does not receive a copy. It becomes another name for the caller’s variable. Now any change inside the function changes the original. This is called pass by reference (or call by reference).
The only change in the code is the & in the
parameter list. The call looks exactly the same.
void addBonus(int& marks) { // & makes marks a reference
marks = marks + 5; // changes the caller's variable
}
// in main:
int aaravMarks = 70;
addBonus(aaravMarks); // aaravMarks becomes 75Analogy (continuing from Passing Data by Value): instead of a photocopy, you now give the clerk your original marksheet. Whatever the clerk writes on it is there when you get it back.
Swapping two values is the standard test of whether you understand the difference between value and reference. The program below contains both versions.
#include <iostream>
using namespace std;
void swapByValue(int a, int b) {
int temp = a;
a = b;
b = temp; // only the copies are swapped
}
void swapByReference(int& a, int& b) {
int temp = a;
a = b;
b = temp; // the caller's variables are swapped
}
int main() {
int x = 10, y = 20;
swapByValue(x, y);
cout << "After swapByValue : x = " << x << ", y = " << y << '\n';
swapByReference(x, y);
cout << "After swapByReference: x = " << x << ", y = " << y << '\n';
return 0;
}After swapByValue : x = 10, y = 20
After swapByReference: x = 20, y = 10Memory picture during swapByReference(x, y):
Name in main |
Name in function | Value before | Value after |
|---|---|---|---|
x |
a (same box) |
10 | 20 |
y |
b (same box) |
20 | 10 |
| (none) | temp (own box) |
10 | 10, then destroyed |
(The standard library already has std::swap(x, y) in
<utility>, which does exactly this. We write our own
to learn how references work.)
A function can return only one value. But a function can
change as many reference parameters as it likes. We use this to send
back several results. A common style is: inputs by value, outputs by
reference.
#include <iostream>
using namespace std;
// Converts total seconds into hours, minutes and seconds.
// totalSeconds is input (by value); h, m, s are outputs (by reference).
void splitTime(int totalSeconds, int& h, int& m, int& s) {
h = totalSeconds / 3600;
m = (totalSeconds % 3600) / 60;
s = totalSeconds % 60;
}
// Finds the smallest and largest of three marks.
void findMinMax(int a, int b, int c, int& smallest, int& largest) {
smallest = a;
largest = a;
if (b < smallest) smallest = b;
if (c < smallest) smallest = c;
if (b > largest) largest = b;
if (c > largest) largest = c;
}
int main() {
int hours, minutes, seconds;
splitTime(9015, hours, minutes, seconds);
cout << "Bus journey Kathmandu-Pokhara: " << hours << " h "
<< minutes << " min " << seconds << " s\n";
int low, high;
findMinMax(67, 91, 58, low, high);
cout << "Lowest mark = " << low << ", highest mark = " << high << '\n';
return 0;
}Bus journey Kathmandu-Pokhara: 2 h 30 min 15 s
Lowest mark = 58, highest mark = 91The variables hours, minutes and
seconds do not need starting values, because the function
fills them in. (9015 seconds = 2 × 3600 + 30 × 60 + 15.)
Because a reference parameter becomes another name for the argument,
the argument must be a real variable, not a literal or an expression.
There is nothing to “rename” in 20 or
x + 1.
#include <iostream>
using namespace std;
void addBonus(int& marks) { marks += 5; }
int main() {
addBonus(70); // 70 is not a variable
return 0;
}error: cannot bind non-const lvalue reference of type 'int&' to an
rvalue of type 'int'
(An lvalue is roughly “something with a name and a memory
location that can appear on the left of =”, such as a
variable. An rvalue is a temporary value such as
70 or x + 1.)
const reference:
fast and safeCopying a small int is cheap. But copying a long
string (for example a paragraph of text) or, later, a large
structure costs time and memory. Pass by reference avoids the copy, but
then the function could change the original by mistake.
The solution is a const reference:
const string& text. It gives the speed of a reference
(no copy) and the safety of pass by value (the function cannot modify
it). Any attempt to change it is a compile-time error.
#include <iostream>
#include <string>
using namespace std;
// No copy is made, and the function promises not to change 'text'.
int countWord(const string& text, const string& word) {
int count = 0;
size_t pos = text.find(word);
while (pos != string::npos) {
count++;
pos = text.find(word, pos + 1);
}
return count;
}
int main() {
string news = "Nepal won the match. Nepal fans in Kathmandu "
"celebrated. Well played, Nepal!";
cout << "'Nepal' appears " << countWord(news, "Nepal")
<< " times.\n";
return 0;
}'Nepal' appears 3 times.(find searches a string and returns the position of the
match, or the special value string::npos when there is
none. It is covered in the Strings and Structures
chapter; here, focus on the parameter types.)
Notice that countWord(news, "Nepal") passes a literal
"Nepal" to const string& word. This is
allowed: a const reference can bind to a temporary
value, because it promises not to change it.
If the function tries to change a const reference, the
compiler stops it:
void shout(const string& text) {
text += "!"; // not allowed: text is const
}error: no match for 'operator+=' (operand types are 'const std::string'
and 'const char [2]')
(The real g++ message is longer; it has been shortened here. The
important words are const std::string: you cannot change a
const object.)
| Feature | By value int x |
By reference int& x |
By const reference const T& x |
|---|---|---|---|
| What the function receives | a copy | the original (alias) | the original (alias), read-only |
| Can the function change the caller’s variable? | No | Yes | No (compile error if it tries) |
| Extra copy made? | Yes | No | No |
| Argument can be a literal or expression? | Yes | No, must be a variable | Yes |
| Typical use | small inputs (int, double,
char) |
outputs; values to modify (swap) | large inputs (string, structures, objects) |
How to choose (rule of thumb):
string) that the function only
reads: pass by const reference.This is the first place in this tutorial where you make a deliberate decision about memory use: avoiding unneeded copies is part of using memory well.
& in the parameter. The program
compiles, but the caller’s variable does not change (a logic error, not
a compile error).& in the call instead of the parameter:
swapByReference(&x, &y) is wrong here (that passes
addresses, a topic of the Pointers and Dynamic Memory
chapter). The call is just swapByReference(x, y).const
reference: addBonus(70) does not compile.int and double a reference is not faster, and
it makes the function able to change data it should not. Use references
for a reason.int& r; is an error.const on a large read-only parameter.
int countWord(string text) works, but copies the whole
string on every call.type&.const reference must be bound to a variable; a
const reference can also bind to a literal or
temporary.const reference.What is a reference? Why must it be initialised when declared?
What is the output?
void f(int a, int& b) { a = a + 1; b = b + 1; }
// inside main:
int p = 5, q = 5;
f(p, q);
cout << p << ' ' << q << '\n';Why does swapByValue fail to swap the caller’s
variables?
Write a function
void divide(int a, int b, int& quotient, int& remainder)
and a call that divides 47 by 5.
Which parameter style would you choose for (a) the radius in
circleArea, (b) a student’s full address as a
string that is only printed, (c) a counter that the
function must increase? Explain briefly.
Will void show(const int& n) accept the call
show(3 + 4)? Will
void show(int& n)?
5 6. 3. It swaps
only its own copies a and b, which are
destroyed when it returns. 4.
quotient = a / b; remainder = a % b; called as
divide(47, 5, q, r); giving 9 and 2. 5. (a) value — small
read-only; (b) const string& — large read-only; (c)
int& — must modify. 6. Yes for
const int& (binds to a temporary); no for
int& (not a variable).For every variable, a programmer should be able to answer two questions:
Analogy: a college student ID card has a scope: it works only inside that college’s gates, not at another college. It also has a lifetime: it is valid from admission until graduation.
A variable declared inside a function (including the
function’s parameters) is a local variable. It can be
used only inside that function. Other functions cannot see it, even
main().
#include <iostream>
using namespace std;
void setFare() {
int fare = 25; // local to setFare
}
int main() {
setFare();
cout << fare; // fare is not visible here
return 0;
}error: 'fare' was not declared in this scope
This is why two functions can both have a variable called
total without any conflict: they are different variables in
different scopes.
A block is any group of statements inside braces
{ }: the body of an if, a loop, or just a pair
of braces. A variable declared inside a block exists only in that block.
The loop variable of a for loop belongs to the loop.
for (int i = 1; i <= 3; i++) {
int square = i * i; // block scope: new each iteration
cout << square << ' ';
}
// cout << i; error: i is not visible here
// cout << square; error: square is not visible hereDeclaring variables in the smallest block where they are needed is good practice. It prevents accidental use of a variable in the wrong place.
A variable declared outside all functions (usually near the top of the file) is a global variable. It can be used by every function below its declaration.
Global variables look convenient, but they cause problems in larger
programs: any function can change them, so when a global variable holds
a wrong value, the mistake could be in any function. A good
rule: global constants (such as
const double PI = 3.14159;) are fine; global
variables should be avoided unless there is a very good reason.
Pass data through parameters and return values instead.
:: operatorIf a local variable has the same name as a global variable, the local
one hides (shadows) the global inside its scope. The
inner name always wins. To reach the hidden global, write
::name. The :: is called the scope
resolution operator. (You will see it again in the Classes and Objects chapter to define member functions
outside a class.)
#include <iostream>
using namespace std;
int score = 100; // global
void show() {
cout << "In show(): score = " << score << '\n'; // global
}
int main() {
int score = 5; // local to main, hides global
cout << "main local score = " << score << '\n';
cout << "global ::score = " << ::score << '\n';
{
int score = 1; // block variable, hides main's
cout << "inner block score = " << score << '\n';
}
cout << "after block, score = " << score << '\n';
show();
return 0;
}main local score = 5
global ::score = 100
inner block score = 1
after block, score = 5
In show(): score = 100Notice that show() prints 100. It cannot see
main’s local variables at all, so the name
score inside show() refers to the global.
Using the same name at several levels is legal but confusing. We show it here so that you can read such code (and spot such bugs); in your own code, choose different names.
| Kind of variable | Created | Destroyed | Initial value if not given |
|---|---|---|---|
| Local (automatic) | each time the block is entered | when the block ends | garbage (unpredictable) |
| Global | when the program starts | when the program ends | zero |
static local |
the first time control reaches its declaration | when the program ends | zero |
Two facts in this table are especially important for finding errors:
int sum; sum += 5; gives an unpredictable result.
g++ -Wall often warns “‘sum’ is used uninitialized”. Always
initialise local variables.A storage class tells the compiler the lifetime and
visibility of a variable (and where it is typically stored). Old
textbooks list four: auto, register,
static and extern. Modern C++ has changed two
of them, so read old books carefully.
| Keyword | Meaning today (C++17) | Scope | Lifetime | Default value |
|---|---|---|---|---|
| (none) / “automatic” | ordinary local variable | block | block | garbage |
auto |
not a storage class any more; since C++11 it means
“deduce the type” (auto x = 5; makes x an
int) |
— | — | — |
register |
removed in C++17 (was only a hint to keep the variable in a CPU register) | — | — | — |
static (local) |
local variable that keeps its value between calls | block | whole program | zero |
static (global) |
global variable visible only in this one source file | file | whole program | zero |
extern |
declares a global variable that is defined somewhere else (often in another file) | file/program | whole program | zero |
A note on the old keywords: auto int x = 5; from old
books is now a compile error (“two or more data types in declaration of
‘x’”). register int x; gives the g++ warning “ISO C++17
does not allow ‘register’ storage class specifier”. Simply leave these
keywords out; modern compilers choose registers automatically and do it
better than a programmer’s hint.
(C++11 also added thread_local, which gives each thread
its own copy of a variable. It becomes meaningful in the Multithreading chapter.)
static local variablesA static local variable is created and initialised
only once. It keeps its value between calls, but its
name is still visible only inside its function. It is like a notebook
kept in one office drawer: only that office uses it, but it is not
thrown away at the end of the day.
#include <iostream>
using namespace std;
void normalCounter() {
int visits = 0; // automatic: created fresh every call
visits++;
cout << "normal visits = " << visits << '\n';
}
void staticCounter() {
static int visits = 0; // static: initialised only once
visits++;
cout << "static visits = " << visits << '\n';
}
int generateToken() {
static int nextToken = 101; // remembers the last token
return nextToken++;
}
int main() {
for (int i = 1; i <= 3; i++) {
normalCounter();
staticCounter();
}
cout << "Token for Sita : " << generateToken() << '\n';
cout << "Token for Ram : " << generateToken() << '\n';
cout << "Token for Priya: " << generateToken() << '\n';
return 0;
}normal visits = 1
static visits = 1
normal visits = 1
static visits = 2
normal visits = 1
static visits = 3
Token for Sita : 101
Token for Ram : 102
Token for Priya: 103The token example (like the token machine at a bank or a government
office) shows a good use of static: the function needs a
memory of the past, but no other function should be able to change it.
This is safer than a global variable.
externextern says “this global variable exists, but it is
defined (given memory) somewhere else”. It is mainly used when a program
is split across several .cpp files: one file defines the
variable, and other files declare it with extern so they
can use it.
// file: config.cpp
int maxStudents = 48; // definition (memory is created)
// file: main.cpp
extern int maxStudents; // declaration only (no new memory)extern can also be seen in a single file, when a
function wants to use a global that is defined further down:
#include <iostream>
using namespace std;
extern int busFare; // declaration: defined below
void printFare() {
cout << "Ratnapark to Kirtipur bus fare: Rs. " << busFare << '\n';
}
int busFare = 30; // definition
int main() {
printFare();
busFare = 35; // fare increased
printFare();
return 0;
}Ratnapark to Kirtipur bus fare: Rs. 30
Ratnapark to Kirtipur bus fare: Rs. 35A variable can be declared many times but defined only once in the whole program. Two definitions give the linker error “multiple definition of …”.
Using a local variable of one function inside another function (“was not declared in this scope”). Pass it as an argument instead.
Using a loop variable after the loop: in
for (int i = 0; ...), i does not exist after
the loop.
Forgetting to initialise a local variable, then using it (garbage value).
Accidentally declaring a new variable instead of assigning the old one:
int total = 0;
if (paid) {
int total = 500; // WRONG: new block variable hides the outer one
}
// total is still 0 hereWrite total = 500; (no type) to assign the existing
variable.
Using global variables to pass data between functions. It works, but makes bugs hard to find.
Copying auto int x; or register int x;
from old books.
::name reaches the
global.static local variable is initialised once and keeps
its value between calls.auto means type deduction and
register is removed; extern declares a global
defined elsewhere.Define scope and lifetime in one sentence each.
What is the output?
int x = 1;
int main() {
int x = 2;
{
int x = 3;
cout << x << ' ' << ::x << ' ';
}
cout << x << '\n';
}What does this function print when called three times?
void f() {
static int n = 10;
n = n + 5;
cout << n << ' ';
}What is the default value of an uninitialised global
int? Of an uninitialised local int?
Why is a static local variable often better than a
global variable for a counter?
An old textbook shows auto int age = 20;. What
happens when you compile it with -std=c++17, and how do you
fix it?
3 1 2. 3. 15 20 25. 4. Global: 0.
Local: garbage (unpredictable). 5. It keeps its value between calls like
a global, but only its own function can see and change it. 6. Compile
error (“two or more data types in declaration”); write
int age = 20; (or auto age = 20; in modern
style).This lesson covers three features that C++ added on top of C. None of them lets you do something impossible before; all three make functions more convenient and programs more readable.
A default argument is a value given to a parameter in the function declaration. If the caller does not supply that argument, the default is used automatically.
Analogy: when you order tea at a canteen, the default is “milk tea with sugar”. You only say something extra when you want something different (“black tea”, “no sugar”).
#include <iostream>
using namespace std;
// Defaults are written in the prototype
void printLine(char symbol = '-', int length = 30);
double busFare(double km, double ratePerKm = 2.5, double minimum = 20);
int main() {
printLine(); // symbol '-', length 30
printLine('*'); // symbol '*', length 30
printLine('=', 15); // symbol '=', length 15
cout << "3 km trip : Rs. " << busFare(3) << '\n';
cout << "12 km trip : Rs. " << busFare(12) << '\n';
cout << "12 km, Rs. 3/km: Rs. " << busFare(12, 3) << '\n';
cout << "12 km, Rs. 3/km, min 50: Rs. " << busFare(12, 3, 50) << '\n';
return 0;
}
// No defaults repeated in the definition
void printLine(char symbol, int length) {
for (int i = 0; i < length; i++)
cout << symbol;
cout << '\n';
}
double busFare(double km, double ratePerKm, double minimum) {
double fare = km * ratePerKm;
return (fare < minimum) ? minimum : fare;
}------------------------------
******************************
===============
3 km trip : Rs. 20
12 km trip : Rs. 30
12 km, Rs. 3/km: Rs. 36
12 km, Rs. 3/km, min 50: Rs. 50Rules for default arguments
Defaults must be given from right to left. Once a parameter has a default, every parameter to its right must also have one.
void f(int a, int b = 2, int c = 3); // OK
void g(int a = 1, int b, int c = 3); // ERROR: b has no defaultArguments are filled from left to right. You
cannot skip a middle argument: busFare(12, , 50) is not
allowed. To change minimum, you must also pass
ratePerKm.
Write the defaults once, normally in the prototype. Repeating them in the definition is an error (“default argument given for parameter 2 of … after previous specification”). If there is no separate prototype, put them in the definition.
When to use: when a parameter has a value that is correct in most calls (a standard rate, a usual separator character, a common number of decimal places).
Every function call has a small cost: the program saves its current place, jumps to the function, copies the arguments, and jumps back. For a big function this cost is tiny compared with the work done. For a one-line function called millions of times, the cost can matter.
An inline function is a request to the compiler: “instead of jumping to this function, copy its body directly into the place where it is called”.
inline int square(int n) {
return n * n;
}Important facts:
inline is only a request. The compiler
may ignore it (for example for large or recursive functions), and modern
compilers often inline small functions automatically when optimisation
is on (-O2), even without the keyword.inline has a second, important meaning:
an inline function may be defined in a header file that is included in
several .cpp files without causing “multiple definition”
errors. You will meet this when you write larger programs.#define
macro?C programmers used macros for small “functions”. A macro is plain text replacement done by the preprocessor, before compilation. It does not understand types or expressions, and this causes surprising bugs. An inline function behaves exactly like a normal function.
#include <iostream>
using namespace std;
#define SQUARE_MACRO(x) x * x
inline int squareInline(int x) {
return x * x;
}
int main() {
int n = 3;
// The macro becomes: 2 + 3 * 2 + 3 (text replacement!)
cout << "SQUARE_MACRO(2 + 3) = " << SQUARE_MACRO(2 + 3) << '\n';
cout << "squareInline(2 + 3) = " << squareInline(2 + 3) << '\n';
cout << "squareInline(n) = " << squareInline(n) << '\n';
return 0;
}SQUARE_MACRO(2 + 3) = 11
squareInline(2 + 3) = 25
squareInline(n) = 9The macro gives 11 instead of 25 because the text 2 + 3
was pasted in without brackets, and multiplication was done first. The
inline function evaluates the argument first (5) and then squares it.
Prefer inline functions (or constexpr functions)
over macros.
In C, every function must have a unique name, so you see names like
areaCircle, areaRectangle,
areaTriangle. C++ allows several functions to have the
same name, as long as their parameter lists are
different. This is called function
overloading.
The parameter lists can differ in:
The return type alone is not enough to overload a function.
Analogy: the word “open” in English. You can open a door, open a bank
account, or open a file. The action has the same name, but what happens
depends on what you give it. In the same way, the compiler
looks at the arguments to decide which area to run.
#include <iostream>
#include <cmath>
#include <string>
using namespace std;
double area(double radius) { // circle
return 3.14159 * radius * radius;
}
double area(double length, double breadth) { // rectangle
return length * breadth;
}
double area(double a, double b, double c) { // triangle (Heron)
double s = (a + b + c) / 2;
return sqrt(s * (s - a) * (s - b) * (s - c));
}
int larger(int a, int b) {
return (a > b) ? a : b;
}
double larger(double a, double b) {
return (a > b) ? a : b;
}
string larger(const string& a, const string& b) { // longer text
return (a.length() >= b.length()) ? a : b;
}
int main() {
cout << "Circle, r = 7 : " << area(7.0) << '\n';
cout << "Rectangle, 12 x 8 : " << area(12.0, 8.0) << '\n';
cout << "Triangle, 3, 4, 5 : " << area(3.0, 4.0, 5.0) << '\n';
cout << "larger(15, 42) : " << larger(15, 42) << '\n';
cout << "larger(3.75, 3.5) : " << larger(3.75, 3.5) << '\n';
cout << "larger(\"Ram\", \"Priya\"): "
<< larger(string("Ram"), string("Priya")) << '\n';
return 0;
}Circle, r = 7 : 153.938
Rectangle, 12 x 8 : 96
Triangle, 3, 4, 5 : 6
larger(15, 42) : 42
larger(3.75, 3.5) : 3.75
larger("Ram", "Priya"): PriyaAt each call, the compiler compares the arguments with every function of that name and picks the best match. A simplified order is:
| Priority | Kind of match | Example with larger(int, int) /
larger(double, double) |
|---|---|---|
| 1 | Exact match | larger(15, 42) → int version |
| 2 | Promotion (for example char → int,
float → double) |
larger('a', 'b') → int version |
| 3 | Standard conversion (for example int →
double, double → int) |
used when no better match exists |
If no function matches, or if two functions match equally well, the compiler reports an error. An equally good match is called an ambiguous call:
#include <iostream>
using namespace std;
int larger(int a, int b) { return (a > b) ? a : b; }
double larger(double a, double b) { return (a > b) ? a : b; }
int main() {
cout << larger(15, 3.5); // int + double: which one?
return 0;
}error: call of overloaded 'larger(int, double)' is ambiguous
The fix is to make the types match exactly, for example
larger(15.0, 3.5) or
larger(static_cast<double>(15), 3.5).
Return type alone does not overload. The compiler chooses a function by looking at the call, and a call does not show the return type:
int getValue();
double getValue(); // same parameter list, only return type differserror: ambiguating new declaration of 'double getValue()'
Overloading plus default arguments can also be
ambiguous. With void show(int a); and
void show(int a, int b = 0);, the call show(5)
matches both, so it is an error. Use one or the other, not both, for the
same situation.
| Feature | Use it when | Example |
|---|---|---|
| Default arguments | the same logic, but some inputs are usually the same | printLine('=', 20) or printLine() |
| Overloading | the same idea needs different logic for different types or numbers of inputs | area of circle / rectangle / triangle |
| Inline | a tiny function is called very often | square, isEven |
Overloading is our first example of polymorphism (“many forms”): one name, many behaviours. It is called compile-time polymorphism because the compiler decides which function to call. The Polymorphism and Templates chapter builds on this idea.
void f(int a = 1, int b); is an error.larger(15, 3.5) when
both int and double versions exist.show(int) and show(int, int = 0)).inline to be obeyed always, or making large
functions inline.#define SQUARE(x) x * x. Use an inline function
instead.inline requests that the function body be placed at the
call site; it is a hint, suitable only for tiny functions, and safer
than macros.void f(int a, int b = 5); (b)
void f(int a = 1, int b); (c)
void f(int a = 1, int b = 2);int volume(int l, int b = 2, int h = 3) { return l * b * h; },
what do volume(4), volume(4, 5) and
volume(4, 5, 6) return?SQUARE_MACRO(1 + 1) with
#define SQUARE_MACRO(x) x * x not give 4? What does it
give?int total(int a, int b) and
double total(int a, int b) exist in the same program?
Why?void show(int) and
void show(double), which function is called by
show('A'), and by show(2.5f)?printInfo(string name)
and printInfo(string name, int age).1 + 1 * 1 + 1 = 3. 4. No; they differ only in return type,
so a call cannot tell them apart. 5. show('A') calls
show(int) (promotion char → int);
show(2.5f) calls show(double) (promotion
float → double). 6.
void printInfo(string name) { cout << name << '\n'; }
and
void printInfo(string name, int age) { cout << name << ", " << age << '\n'; }A function that calls itself is called a recursive function, and the technique is called recursion.
This sounds strange at first: how can a function use itself before it is finished? The trick is that each call works on a smaller version of the same problem, and eventually the problem becomes so small that the answer is known directly.
Analogy: you are standing in a long queue at a bank in New Road and want to know your position. You ask the person in front, “What is your position?” They do not know either, so they ask the person in front of them, and so on. The person at the very front knows the answer: “I am number 1.” That answer travels back: the next person says “2”, then “3”, and finally you get your answer. The person at the front is the base case; everyone else asking the one in front is the recursive case.
Every correct recursive function has:
If either part is missing or wrong, the recursion never stops.
Mathematically, n! = n × (n−1) × … × 2 × 1, and
0! = 1. Notice that 5! = 5 × 4!. In
general:
factorial(n) = 1 if n is 0 or 1 (base case)
factorial(n) = n * factorial(n - 1) if n > 1 (recursive case)
This definition translates almost word for word into C++:
#include <iostream>
using namespace std;
long long factorial(int n) {
if (n <= 1) // base case
return 1;
return n * factorial(n - 1); // recursive case
}
int main() {
for (int n = 0; n <= 6; n++)
cout << n << "! = " << factorial(n) << '\n';
cout << "20! = " << factorial(20) << '\n';
return 0;
}0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
20! = 2432902008176640000We use long long because factorials grow very fast.
long long is at least 64 bits, so 20! (about 2.4 × 10^18)
still fits, but 21! does not and would overflow.
Each time a function is called, the computer creates a new stack frame (also called an activation record): a small area of memory holding that call’s parameters, local variables and the place to return to. Frames are placed on the call stack, like plates stacked in a canteen: the last plate put on is the first taken off.
In recursion, each call has its own frame with its
own copy of n. Here is
factorial(4):
| Step | Phase | Stack (top on the right) | What happens |
|---|---|---|---|
| 1 | winding | f(4) | 4 is not ≤ 1, so compute 4 * f(3); wait |
| 2 | winding | f(4), f(3) | compute 3 * f(2); wait |
| 3 | winding | f(4), f(3), f(2) | compute 2 * f(1); wait |
| 4 | base case | f(4), f(3), f(2), f(1) | 1 ≤ 1, so return 1 |
| 5 | unwinding | f(4), f(3), f(2) | 2 * 1 = 2, return 2 |
| 6 | unwinding | f(4), f(3) | 3 * 2 = 6, return 6 |
| 7 | unwinding | f(4) | 4 * 6 = 24, return 24 |
During winding, calls are made and frames pile up; nothing is multiplied yet. During unwinding, each call finishes and returns its result to the call below it.
We can make the program show this by printing on the way in and on
the way out. The depth parameter is used only to indent the
output.
#include <iostream>
#include <string>
using namespace std;
int factorialTrace(int n, int depth) {
string indent(depth * 4, ' ');
cout << indent << "call factorial(" << n << ")\n";
if (n <= 1) {
cout << indent << "base case, return 1\n";
return 1;
}
int result = n * factorialTrace(n - 1, depth + 1);
cout << indent << "return " << n << " * ... = " << result << '\n';
return result;
}
int main() {
int answer = factorialTrace(4, 0);
cout << "Answer: " << answer << '\n';
return 0;
}call factorial(4)
call factorial(3)
call factorial(2)
call factorial(1)
base case, return 1
return 2 * ... = 2
return 3 * ... = 6
return 4 * ... = 24
Answer: 24If the base case is missing or never reached, the function keeps calling itself. Each call adds a frame to the stack, and the stack has a limited size (often around 1–8 MB, depending on the system). Soon the stack is full. This is a stack overflow, and the program crashes (on Linux usually “Segmentation fault”; on Windows the program simply stops with an error code).
long long badFactorial(int n) {
return n * badFactorial(n - 1); // no base case: never stops
}
long long alsoBad(int n) {
if (n == 1) return 1;
return n * alsoBad(n - 2); // alsoBad(4) -> 2 -> 0 -> -2 ...
} // skips the base case!The second example is a subtle bug: the recursive case moves in steps
of 2, so for even n it jumps over n == 1
forever. Always check that every input reaches the base
case. Also decide what should happen for invalid input (for example
negative n). Using n <= 1 as the base case,
as in our factorial, is safer than n == 1.
g++ can sometimes warn about this problem with “infinite recursion
detected” (-Winfinite-recursion, included in
-Wall in recent versions), but not in every case, so do not
rely on it.
Power. x^n = x × x^(n−1), and
x^0 = 1.
Sum of digits. The last digit is
n % 10; the rest of the number is n / 10. So
sumDigits(n) = n % 10 + sumDigits(n / 10), and
sumDigits(0) = 0.
Fibonacci. The series 0, 1, 1, 2, 3, 5, 8, 13, …
where each term is the sum of the two before it:
fib(n) = fib(n−1) + fib(n−2), with base cases
fib(0) = 0 and fib(1) = 1. This function has
two recursive calls.
#include <iostream>
using namespace std;
double power(double x, int n) { // n >= 0
if (n == 0)
return 1;
return x * power(x, n - 1);
}
int sumDigits(int n) { // n >= 0
if (n == 0)
return 0;
return n % 10 + sumDigits(n / 10);
}
long long callCount = 0; // counts calls to fib
long long fib(int n) {
callCount++;
if (n <= 1) // fib(0) = 0, fib(1) = 1
return n;
return fib(n - 1) + fib(n - 2);
}
int main() {
cout << "power(2, 10) = " << power(2, 10) << '\n';
cout << "power(1.5, 3) = " << power(1.5, 3) << '\n';
cout << "sumDigits(2081) = " << sumDigits(2081) << '\n';
cout << "Fibonacci series: ";
for (int i = 0; i <= 10; i++)
cout << fib(i) << ' ';
cout << '\n';
for (int n : {10, 20, 30}) {
callCount = 0;
long long value = fib(n);
cout << "fib(" << n << ") = " << value << " needed "
<< callCount << " calls\n";
}
return 0;
}power(2, 10) = 1024
power(1.5, 3) = 3.375
sumDigits(2081) = 11
Fibonacci series: 0 1 1 2 3 5 8 13 21 34 55
fib(10) = 55 needed 177 calls
fib(20) = 6765 needed 21891 calls
fib(30) = 832040 needed 2692537 calls(The loop for (int n : {10, 20, 30}) simply runs the
body with n equal to 10, then 20, then 30.)
Look at the call counts. fib(30) needs more than 2.6
million calls to compute one number! The reason is that the same values
are computed again and again. The call tree for fib(4)
shows the repetition:
fib(4)
/ \
fib(3) fib(2)
/ \ / \
fib(2) fib(1) fib(1) fib(0)
/ \
fib(1) fib(0)
fib(2) is computed twice, fib(1) three
times. For larger n the waste grows exponentially. An
iterative (loop) version computes fib(30) with only about
30 additions.
Every recursive function can be rewritten using a loop (iteration), and many loops can be written recursively. Here is factorial both ways:
long long factorialLoop(int n) { // iteration
long long result = 1;
for (int i = 2; i <= n; i++)
result *= i;
return result;
}
long long factorialRec(int n) { // recursion
if (n <= 1) return 1;
return n * factorialRec(n - 1);
}| Point | Recursion | Iteration (loops) |
|---|---|---|
| Idea | function calls itself on a smaller problem | a block of statements repeats |
| Stops when | base case is reached | loop condition becomes false |
| Memory | one stack frame per active call (more memory) | fixed, small memory |
| Speed | slower (call overhead; sometimes repeated work) | usually faster |
| Danger | stack overflow if too deep or no base case | infinite loop if the condition never becomes false |
| Code | often shorter and closer to the maths definition | sometimes longer, but straightforward |
| Best for | problems that are naturally recursive: tree structures, Tower of Hanoi, divide-and-conquer sorting (merge sort, quick sort) | simple counting, sums, series, most everyday tasks |
A practical rule: use a loop when the loop is simple. Use recursion when the problem is naturally defined in terms of smaller copies of itself and the recursion depth stays small. Understanding recursion is essential for later subjects (data structures and algorithms), even when a loop would be used in production code.
Recursion is also closely connected to memory use: every recursive call uses stack memory, and that memory is automatically released when the call returns. Deep recursion can use up the stack even though each frame is small.
No base case, or a base case that some inputs never reach (stack overflow).
The recursive call does not make the problem smaller,
e.g. return n * factorial(n);.
Forgetting to return the result of the recursive
call:
int sumDigits(int n) {
if (n == 0) return 0;
n % 10 + sumDigits(n / 10); // WRONG: value is thrown away
} // warning: no return statementUsing int for factorial: 13! already
overflows a 32-bit int.
Using the plain recursive Fibonacci for large n (for
example 45 or more) and thinking the program has “hung”. It is just
doing billions of calls.
Not handling invalid input such as negative numbers, which may never reach the base case.
Name the two essential parts of a recursive function and say what each does.
What is the output?
void show(int n) {
if (n == 0) return;
cout << n << ' ';
show(n - 1);
cout << n << ' ';
}
// inside main:
show(3);Trace sumDigits(345) showing each call and return
value.
Write a recursive function int sumToN(int n) that
returns 1 + 2 + … + n.
How many calls does fib(5) make in total (using the
recursive version from this lesson)?
Give two reasons why iteration is often preferred over recursion for computing Fibonacci numbers.
3 2 1 1 2 3 (printing before the call
happens while winding, after the call while unwinding). 3.
sumDigits(345) = 5 + sumDigits(34) = 5 + (4 +
sumDigits(3)) = 5 + 4 + (3 + sumDigits(0)) = 5
+ 4 + 3 + 0 = 12. 4.
if (n <= 0) return 0; return n + sumToN(n - 1); 5. 15
calls (calls(n) = calls(n−1) + calls(n−2) + 1: 1, 1, 3, 5, 9, 15). 6.
The recursive version repeats the same work many times (exponential
calls) and uses stack memory for every call; a loop uses n additions and
constant memory.Suppose Sita’s teacher wants to store the marks of five students and print them. With what we know so far, she must write:
int mark1 = 78, mark2 = 85, mark3 = 62, mark4 = 91, mark5 = 70;
cout << mark1 << " " << mark2 << " " << mark3 << " "
<< mark4 << " " << mark5 << '\n';This works for five students. But what about 48 students? Or 500? We
would need 500 variable names and 500 lines of input and output. Worse,
we cannot use a loop, because mark1, mark2, …
are completely separate names. The computer does not know that they are
related.
An array solves this problem. We create one name,
marks, and the computer reserves space for many values
under that name. Each value is reached by a number called its
index (or subscript).
An array is a collection of a fixed number of elements, all of the same data type, stored one after another in memory, and accessed with a single name and an index.
The key words are:
| Property | Meaning |
|---|---|
| Same type | All elements are int, or all are double,
etc. You cannot mix types. |
| Fixed size | The number of elements is decided when the array is created and cannot change later. |
| Contiguous memory | Elements are stored side by side, with no gaps, in one block of memory. |
| Indexed | Each element has a position number, starting from 0. |
A useful analogy is a row of lockers in a school corridor. All lockers are the same size (same type), there is a fixed number of them (fixed size), they stand side by side (contiguous), and each has a number painted on it (index). To use a locker you only need the name of the row and the locker number.
The general form of an array declaration is:
data_type array_name[size];For example:
int marks[5]; // 5 integers
double prices[10]; // 10 real numbers
char grade[4]; // 4 charactersHere int marks[5]; creates an array called
marks that can hold 5 integers. The number inside the
square brackets is the size (or
length) of the array.
In standard C++, the size must be a constant
expression: a value known when the program is compiled. A
literal number such as 5 is fine, and so is a
const or constexpr variable:
const int NUM_STUDENTS = 48;
int marks[NUM_STUDENTS]; // OK: size is a constantUsing a named constant is good practice. If the class size changes, you change only one line.
Note: g++ also accepts
int n; cin >> n; int marks[n]; (a “variable-length
array”), but this is a compiler extension, not standard
C++. Other compilers such as Microsoft Visual C++ reject it. Do not use
it in your programs. When the size is only known at run time, you will
use std::vector (see Matrix Operations, 2D
Arrays in Functions, and std::vector) or dynamic memory (see the Pointers and Dynamic Memory chapter).
The elements of int marks[5]; are:
marks[0] marks[1] marks[2] marks[3] marks[4]
The first element has index 0 and the
last element has index size - 1, which is
4 here. There is no marks[5]. This is the
most important rule of the whole unit.
Why start at 0? The index is really an offset: how many elements you must move from the start of the array. The first element is 0 steps from the start, the second is 1 step from the start, and so on. The computer calculates the address of an element with:
address of marks[i] = starting address + i * (size of one element)
For marks[0] this gives the starting address itself,
which is why the first index is 0.
Suppose int takes 4 bytes (typical on modern 64-bit
systems, but it depends on the compiler and platform) and the array
marks starts at address 1000. Then memory looks like
this:
| Element | marks[0] | marks[1] | marks[2] | marks[3] | marks[4] |
|---|---|---|---|---|---|
| Value | 78 | 85 | 62 | 91 | 70 |
| Address | 1000 | 1004 | 1008 | 1012 | 1016 |
The whole array takes 5 × 4 = 20 bytes. Real addresses are large
hexadecimal numbers and change each time the program runs, but the
pattern (each address 4 more than the previous one) is always the same
for int.
Initialisation means giving values at the moment of declaration. C++ offers several forms:
int a[5] = {78, 85, 62, 91, 70}; // all five values given
int b[5] = {10, 20}; // b = 10, 20, 0, 0, 0
int c[5] = {}; // all elements are 0
int d[] = {4, 8, 15, 16, 23, 42}; // size taken from list: 6
int e[5]; // local: values are garbage!The rules are:
{} (empty braces) sets every element to 0. This is the
easiest way to get a clean array.int f[3] = {1, 2, 3, 4};) is a compile error.An array element behaves exactly like a normal variable of its type.
You can assign to it, read it with cin, print it, and use
it in expressions:
marks[2] = 65; // change the third element
marks[0] = marks[0] + 5; // add 5 grace marks
cout << marks[4]; // print the last element
int total = marks[0] + marks[1]; // use in an expressionThe index can be any integer expression, such as
marks[i] or marks[i + 1]. This is what makes
arrays powerful: a loop variable can walk through all positions.
Our first complete program stores five marks and prints them with a loop:
#include <iostream>
using namespace std;
int main() {
const int SIZE = 5;
int marks[SIZE] = {78, 85, 62, 91, 70};
cout << "Index Mark\n";
for (int i = 0; i < SIZE; i++) {
cout << " " << i << " " << marks[i] << '\n';
}
marks[2] = 65; // teacher corrects the third mark
cout << "Corrected third mark: " << marks[2] << '\n';
return 0;
}Index Mark
0 78
1 85
2 62
3 91
4 70
Corrected third mark: 65Notice the loop condition i < SIZE, not
i <= SIZE. The loop runs for
i = 0, 1, 2, 3, 4, exactly the valid indexes.
The sizeof operator gives the number of bytes used by a
variable or type. For an array, sizeof(array) gives the
size of the whole array. Dividing by the size of one
element gives the number of elements:
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double bills[] = {1250.50, 980.00, 1435.75, 1100.25};
int count = sizeof(bills) / sizeof(bills[0]);
cout << "Bytes in one double : " << sizeof(bills[0]) << '\n';
cout << "Bytes in whole array: " << sizeof(bills) << '\n';
cout << "Number of elements : " << count << '\n';
cout << fixed << setprecision(2);
for (int i = 0; i < count; i++) {
cout << "Month " << i + 1 << ": Rs. " << bills[i] << '\n';
}
return 0;
}Bytes in one double : 8
Bytes in whole array: 32
Number of elements : 4
Month 1: Rs. 1250.50
Month 2: Rs. 980.00
Month 3: Rs. 1435.75
Month 4: Rs. 1100.25A double is typically 8 bytes, so 4 doubles take 32
bytes, and 32 / 8 = 4 elements. In C++17 you may also write
std::size(bills) (from the header
<iterator>), which gives the element count directly.
The sizeof trick only works where the array was declared.
In Arrays and Functions; Linear and Binary Search you
will see that it does not work inside a function that
receives the array.
Arrays can be made of any type. This program uses a char
array for the grade of each subject and a bool array to
record whether each subject was passed:
#include <iostream>
using namespace std;
int main() {
const int SUBJECTS = 4;
char grades[SUBJECTS] = {'A', 'B', 'C', 'F'};
bool passed[SUBJECTS] = {}; // all false (0)
for (int i = 0; i < SUBJECTS; i++) {
passed[i] = (grades[i] != 'F');
}
for (int i = 0; i < SUBJECTS; i++) {
cout << "Subject " << i + 1 << ": grade " << grades[i]
<< (passed[i] ? " (pass)" : " (fail)") << '\n';
}
return 0;
}Subject 1: grade A (pass)
Subject 2: grade B (pass)
Subject 3: grade C (pass)
Subject 4: grade F (fail)Built-in arrays have some limits that surprise beginners:
=. Writing
b = a; is a compile error. You must copy element by element
in a loop.== to check whether
their contents are equal (it compares addresses, not contents).int array with
cout << marks;. This prints an address such as
0x7ffd5c3e1a40, not the values.std::vector, introduced in Matrix
Operations, 2D Arrays in Functions, and std::vector, removes most of
these limits.
size for the last element.
Wrong: marks[5] for int marks[5];. Right:
marks[4], or in general marks[SIZE - 1].<= instead of
<. Wrong:
for (int i = 0; i <= SIZE; i++). Right:
for (int i = 0; i < SIZE; i++).int count[10]; then count[i]++. Right:
int count[10] = {};.int n = 5; int a[n];. Right:
const int N = 5; int a[N];.=. Wrong:
copyMarks = marks;. Right: copy each element in a
loop.marks[2] is the value stored at position 2, not the number
2.type name[SIZE]; where SIZE
is a constant expression.0 to SIZE - 1.{} sets all elements to zero; a partial list fills the
rest with zeros; an uninitialised local array contains garbage.sizeof(arr) / sizeof(arr[0]) gives the number of
elements, but only where the array is declared.double temp[7];, what is the index of the first
element and of the last element? How many bytes does it use if a
double takes 8 bytes?int x[6] = {3, 9};?int a[] = {5, 10, 15, 20}; cout << a[1] + a[3] << " " << sizeof(a) / sizeof(a[0]);int b[4] = {1, 2, 3, 4, 5}; an error?3 9 0 0 0 0. 4.
30 4. 5. There are 5 initial values but only 4 elements;
too many initialisers is a compile error. 6. Declare
int fares[7] = {...}; and loop
for (int i = 0; i < 7; i++) cout << "Day " << i + 1 << ": Rs. " << fares[i] << '\n';.Traversing an array means visiting every element
once, usually from index 0 to index SIZE - 1. Almost every
array program uses this pattern:
for (int i = 0; i < SIZE; i++) {
// do something with arr[i]
}The loop variable i is the index. On each pass,
arr[i] is the current element. Read the loop aloud as “for
every index i from 0 up to, but not including, SIZE”.
C++11 and later also offer the range-based for loop, which visits each element without an index:
for (int m : marks) { // "for each m in marks"
cout << m << ' ';
}This is short and safe (it can never go out of bounds), but you do
not get the index, and changing m does not change the
array. To change elements inside a range-based loop, use a reference:
for (int& m : marks) m += 5;. For now we will mostly
use the normal indexed loop, because searching and sorting need
indexes.
To fill an array from the keyboard, put cin inside the
traversal loop. Printing a prompt with i + 1 gives friendly
numbers (1 to 5) to the user while the program uses indexes 0 to 4.
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
const int SIZE = 5;
int marks[SIZE];
for (int i = 0; i < SIZE; i++) {
cout << "Enter marks of student " << i + 1 << ": ";
cin >> marks[i];
}
int sum = 0;
for (int i = 0; i < SIZE; i++) {
sum += marks[i];
}
double average = static_cast<double>(sum) / SIZE;
cout << "Marks entered: ";
for (int m : marks) {
cout << m << ' ';
}
cout << "\nTotal = " << sum << '\n';
cout << fixed << setprecision(2);
cout << "Average = " << average << '\n';
return 0;
}Enter marks of student 1: 78
Enter marks of student 2: 85
Enter marks of student 3: 62
Enter marks of student 4: 91
Enter marks of student 5: 70
Marks entered: 78 85 62 91 70
Total = 386
Average = 77.20Here the array marks is not initialised, but that is
fine because every element is given a value by cin before
it is used. Note the static_cast<double>: without it,
sum / SIZE would be integer division and the average 77.2
would become 77.
The variable sum above is an
accumulator: it starts at 0 and each element is added
to it. Two rules:
int sum = 0;).sum += marks[i];).If you forget step 1, sum starts with garbage. If you
put sum = 0 inside the loop, it is reset on every pass and
ends up equal to the last element only.
To find the largest value, we start by assuming the
first element is the largest, then compare every other element with our
current best. We start with arr[0], not with 0, because all
the values might be negative, or all might be greater than some number
we guessed.
The table shows how highest changes for the monthly
electricity units {120, 95, 180, 150, 60, 175}:
| i | units[i] | units[i] > highest? | highest after step |
|---|---|---|---|
| start | – | – | 120 |
| 1 | 95 | No | 120 |
| 2 | 180 | Yes | 180 |
| 3 | 150 | No | 180 |
| 4 | 60 | No | 180 |
| 5 | 175 | No | 180 |
We usually also want to know where the maximum is, so we store its index too:
#include <iostream>
using namespace std;
int main() {
const int MONTHS = 6;
int units[MONTHS] = {120, 95, 180, 150, 60, 175};
int highest = units[0], lowest = units[0];
int highIndex = 0, lowIndex = 0;
for (int i = 1; i < MONTHS; i++) {
if (units[i] > highest) {
highest = units[i];
highIndex = i;
}
if (units[i] < lowest) {
lowest = units[i];
lowIndex = i;
}
}
cout << "Highest use: " << highest << " units in month "
<< highIndex + 1 << '\n';
cout << "Lowest use : " << lowest << " units in month "
<< lowIndex + 1 << '\n';
return 0;
}Highest use: 180 units in month 3
Lowest use : 60 units in month 5The loop starts at i = 1 because element 0 has already
been used as the starting value.
Counting is like summing, but we add 1 only when a condition is true. For example, to count how many students passed (marks ≥ 40) and how many scored above the class average, we need two passes: the first pass finds the average, and the second pass counts.
A very useful trick is to use the value of one piece
of data as the index into a second array of counters.
Suppose 12 customers of a Thamel café rated the service from 1 to 5
stars. We make an array count of size 6 (indexes 0 to 5) so
that count[r] holds the number of customers who gave rating
r. Index 0 is simply unused, which keeps the code easy to
read.
#include <iostream>
using namespace std;
int main() {
const int CUSTOMERS = 12;
int ratings[CUSTOMERS] = {5, 4, 4, 3, 5, 2, 4, 5, 1, 4, 3, 5};
int count[6] = {}; // count[1] .. count[5], all zero
for (int i = 0; i < CUSTOMERS; i++) {
int r = ratings[i];
if (r >= 1 && r <= 5) { // protect against bad data
count[r]++;
}
}
int above3 = 0;
for (int i = 0; i < CUSTOMERS; i++) {
if (ratings[i] > 3) {
above3++;
}
}
cout << "Stars Customers\n";
for (int star = 5; star >= 1; star--) {
cout << " " << star << " ";
for (int k = 0; k < count[star]; k++) {
cout << '*';
}
cout << " (" << count[star] << ")\n";
}
cout << above3 << " of " << CUSTOMERS
<< " customers gave more than 3 stars.\n";
return 0;
}Stars Customers
5 **** (4)
4 **** (4)
3 ** (2)
2 * (1)
1 * (1)
8 of 12 customers gave more than 3 stars.Notice the check if (r >= 1 && r <= 5)
before count[r]++. If the data contained a 7 by mistake,
count[7] would be outside the array. Always check a value
before using it as an index.
C++ does not check array indexes. If you write
marks[5] or marks[-1] for
int marks[5];, the compiler usually accepts it and the
program reads or writes whatever memory happens to be next to the array.
This is called undefined behaviour: the C++ standard
says nothing about what will happen. The program may:
int marks[5] = {78, 85, 62, 91, 70};
int total = 0;
for (int i = 0; i <= 5; i++) { // BUG: i reaches 5
total += marks[i]; // marks[5] does not exist
}This “off-by-one” bug is the most common array error. Because the compiler cannot always detect it, you must guard against it:
i < SIZE.index >= 0 && index < SIZE.Out-of-bounds access is also a memory-safety problem. Many real
security attacks began with a program that wrote past the end of an
array. In the Pointers and Dynamic Memory chapter you
will use the tool -fsanitize=address, which catches these
errors while the program runs.
Often we do not know in advance exactly how many values the user will
enter. The usual solution with built-in arrays is to declare the array
with a maximum size and keep a separate variable
n for the number of elements actually used. The rest of the
array is simply ignored.
#include <iostream>
using namespace std;
int main() {
const int MAX = 50;
double fares[MAX];
int n;
cout << "How many bus trips this week (1-" << MAX << ")? ";
cin >> n;
while (n < 1 || n > MAX) {
cout << "Please enter a number from 1 to " << MAX << ": ";
cin >> n;
}
for (int i = 0; i < n; i++) {
cout << "Fare for trip " << i + 1 << " (Rs.): ";
cin >> fares[i];
}
double total = 0;
for (int i = 0; i < n; i++) {
total += fares[i];
}
cout << "Total spent on " << n << " trips: Rs. " << total << '\n';
return 0;
}How many bus trips this week (1-50)? 60
Please enter a number from 1 to 50: 4
Fare for trip 1 (Rs.): 25
Fare for trip 2 (Rs.): 30
Fare for trip 3 (Rs.): 25
Fare for trip 4 (Rs.): 35
Total spent on 4 trips: Rs. 115The validation loop stops the user from entering 60 when only 50
elements exist. From now on, every loop uses i < n, not
i < MAX.
int highest = 0; (fails if all values are negative). Right:
int highest = arr[0];.for (...) { sum = 0; sum += a[i]; }. Right:
sum = 0; before the loop.double avg = sum / n;. Right:
double avg = static_cast<double>(sum) / n;.i <= n.
Right: i < n.n. Reading
n = 100 into an array of size 50 writes past the end.count[r]++; with no range check.for (int i = 0; i < n; i++); the
range-based for is a safe alternative when the index is not
needed.n,
validated against the maximum size.highest be initialised with
arr[0] and not with 0? Give an example array
where starting with 0 gives a wrong answer.int a[5] = {2, 7, 1, 8, 3}; int s = 0; for (int i = 0; i < 5; i += 2) s += a[i]; cout << s;a[10] of
int a[10];.double temp[30] are below 10.0.count were
declared as int count[5] = {};?a[0] + a[2] + a[4] = 2 + 1 + 3 = 6. 4. Behaviour not
defined by the C++ standard; the program may overwrite another variable,
print garbage, crash, or seem to work. 5.
int below = 0; for (int i = 0; i < 30; i++) if (temp[i] < 10.0) below++;.
6. Valid indexes would be 0–4, so count[5]++ for a 5-star
rating would be out of bounds (undefined behaviour); the printing loop
would also read count[5].In Traversing and Processing One-Dimensional
Arrays, main did everything: reading, summing, finding
the maximum, printing. As programs grow, this becomes hard to read and
hard to reuse. In the Functions chapter you learnt to
split work into functions. Now we combine the two ideas: small functions
such as readArray, printArray and
findMax, each doing one job on an array.
To receive an array, a function parameter is written with empty square brackets. Because the function cannot find out the size by itself (see below), we always pass the number of elements as a second parameter:
void printArray(const int arr[], int n); // prototype
printArray(marks, 5); // call: no bracketsIn the call we write only the array name, marks, without
[]. Writing marks[5] would pass a single (and
invalid) element instead.
When you pass an int by value, the function gets a
copy (see Passing Data by Value).
Arrays are different. When an array name is passed, C++ passes the
address of its first element. This automatic conversion
is called array-to-pointer decay. The function
therefore works on the caller’s original array, not on a copy. There are
two important results:
arr[0] = 100;, the caller’s array changes. This is
useful for functions such as readArray or
addGraceMarks.arr is really a pointer, so sizeof(arr) gives
the size of a pointer (typically 8 bytes on a 64-bit system), not the
size of the array.The compiler even warns about the second point:
int countElements(int arr[]) {
return sizeof(arr) / sizeof(arr[0]); // WRONG inside a function
}warning: 'sizeof' on array function parameter 'arr' will return size
of 'int*' [-Wsizeof-array-argument]
On a typical 64-bit system this function returns 2 (8 / 4) for any
int array, whatever its real size. This is why we pass
n separately. You will study pointers and decay in detail
in Pointer Arithmetic, Pointers and Arrays.
If a function only reads an array (printing,
summing, searching), declare the parameter as
const int arr[]. Then any accidental assignment such as
arr[i] = 0; becomes a compile error instead of a hidden
bug. This is the array version of passing by const
reference (see Passing Data by Reference). Functions
that must change the array (reading input, sorting)
leave out const.
#include <iostream>
using namespace std;
void printArray(const int arr[], int n) {
for (int i = 0; i < n; i++) {
cout << arr[i] << ' ';
}
cout << '\n';
}
int sumArray(const int arr[], int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
return sum;
}
int indexOfMax(const int arr[], int n) {
int best = 0;
for (int i = 1; i < n; i++) {
if (arr[i] > arr[best]) {
best = i;
}
}
return best;
}
// Changes the caller's array: no const here.
void addGraceMarks(int arr[], int n, int grace) {
for (int i = 0; i < n; i++) {
arr[i] += grace;
if (arr[i] > 100) {
arr[i] = 100;
}
}
}
int main() {
const int SIZE = 6;
int marks[SIZE] = {45, 67, 98, 32, 76, 59};
cout << "Original : ";
printArray(marks, SIZE);
cout << "Total : " << sumArray(marks, SIZE) << '\n';
int top = indexOfMax(marks, SIZE);
cout << "Topper is student " << top + 1
<< " with " << marks[top] << '\n';
addGraceMarks(marks, SIZE, 5);
cout << "After +5 : ";
printArray(marks, SIZE);
return 0;
}Original : 45 67 98 32 76 59
Total : 377
Topper is student 3 with 98
After +5 : 50 72 100 37 81 64 After addGraceMarks, the array in main has
changed (98 became 100 because of the cap), which proves the function
worked on the original array. Also note that a function cannot
return a built-in array; it can return one element, an index,
or a result such as a sum. To “return” many values, a function fills an
array that the caller passes in.
Searching means finding whether a given value, called the key, is present in an array, and if so, at which index. We study two methods.
Linear search (also called sequential search) checks the elements one by one from the start. As soon as an element equals the key, it returns that index. If the loop finishes without a match, the key is not present, and the function returns -1 (a value that can never be a valid index).
Algorithm:
Linear search works on any array, sorted or not. Its disadvantage is speed: for n elements it may need up to n comparisons. For 10 elements this is nothing, but for 10 lakh (1,000,000) elements it can be slow.
Binary search works only on a sorted array (smallest to largest). It is the method you use to find a word in a dictionary: open near the middle, decide whether your word is in the left half or the right half, and throw away the other half. Repeat with the remaining half.
We keep two indexes, low and high, which
mark the part of the array still under consideration:
We write low + (high - low) / 2 instead of
(low + high) / 2 because, for very large arrays,
low + high could overflow the int range. Both
give the same answer for small arrays.
Dry run. Search for key 72 in the sorted array of 10 roll numbers:
| Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| Value | 11 | 19 | 24 | 37 | 45 | 58 | 63 | 72 | 86 | 94 |
| Step | low | high | mid | arr[mid] | Decision |
|---|---|---|---|---|---|
| 1 | 0 | 9 | 4 | 45 | 45 < 72, so low = 5 |
| 2 | 5 | 9 | 7 | 72 | Found at index 7 |
Only 2 comparisons were needed. Linear search would need 8. Now search for 30, which is absent:
| Step | low | high | mid | arr[mid] | Decision |
|---|---|---|---|---|---|
| 1 | 0 | 9 | 4 | 45 | 45 > 30, so high = 3 |
| 2 | 0 | 3 | 1 | 19 | 19 < 30, so low = 2 |
| 3 | 2 | 3 | 2 | 24 | 24 < 30, so low = 3 |
| 4 | 3 | 3 | 3 | 37 | 37 > 30, so high = 2 |
| 5 | 3 | 2 | – | – | low > high: not found |
Each step halves the search area, so for 1,000,000 sorted elements binary search needs at most about 20 comparisons (because 2 to the power 20 is about 1,000,000).
This program counts the comparisons each method makes, so we can see the difference:
#include <iostream>
using namespace std;
int linearSearch(const int arr[], int n, int key, int& steps) {
steps = 0;
for (int i = 0; i < n; i++) {
steps++;
if (arr[i] == key) {
return i;
}
}
return -1;
}
int binarySearch(const int arr[], int n, int key, int& steps) {
int low = 0, high = n - 1;
steps = 0;
while (low <= high) {
int mid = low + (high - low) / 2;
steps++;
if (arr[mid] == key) {
return mid;
} else if (arr[mid] < key) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
int main() {
const int N = 10;
int rolls[N] = {11, 19, 24, 37, 45, 58, 63, 72, 86, 94};
int keys[] = {72, 11, 94, 30};
for (int key : keys) {
int s1, s2;
int p1 = linearSearch(rolls, N, key, s1);
int p2 = binarySearch(rolls, N, key, s2);
cout << "Key " << key << ": linear -> index " << p1
<< " (" << s1 << " steps), binary -> index " << p2
<< " (" << s2 << " steps)\n";
}
return 0;
}Key 72: linear -> index 7 (8 steps), binary -> index 7 (2 steps)
Key 11: linear -> index 0 (1 steps), binary -> index 0 (3 steps)
Key 94: linear -> index 9 (10 steps), binary -> index 9 (4 steps)
Key 30: linear -> index -1 (10 steps), binary -> index -1 (4 steps)The steps parameter is passed by reference (see Passing Data by Reference) so that each function can
report a second result. Notice that searching for 11 (the first element)
is fastest with linear search. Binary search is not always faster for
every key, but its worst case is far better.
| Feature | Linear search | Binary search |
|---|---|---|
| Array must be sorted? | No | Yes (ascending here) |
| Method | Check each element in order | Check middle, discard half |
| Worst-case comparisons for n elements | n | about log2(n) + 1 |
| For n = 1,000 | up to 1,000 | up to 10 |
| Code difficulty | Very easy | Easy, but boundary details matter |
| Best use | Small or unsorted data | Large sorted data searched often |
If the data is unsorted and you need to search it only once, linear search is best. If the data will be searched many times, it can be worth sorting it once (see Sorting — Bubble Sort and Selection Sort) and then using binary search.
printArray(marks); with a one-parameter function that uses
sizeof. Right: printArray(marks, SIZE);.printArray(marks[], SIZE); or
printArray(marks[SIZE], SIZE);. Right:
printArray(marks, SIZE);.if (arr[i] == key) return i; else return -1; inside the
loop (it only checks element 0). Right: return -1 after
the loop.while (low < high) (misses the case when one element
remains). Right: while (low <= high).+ 1 / - 1.
Writing low = mid; can cause an infinite loop.type name[]; the call
uses only the array name.sizeof does not
work on an array parameter.const for array parameters that should only be
read.int passed by value?sizeof(arr) give inside
void f(int arr[]) on a typical 64-bit system? Why?{11, 19, 24, 37, 45, 58, 63, 72, 86, 94}. Show low, high
and mid at each step.for (int i = 0; i < n; i++) { if (a[i] == key) return i; else return -1; }int countOccurrences(const int arr[], int n, int key) that
returns how many times key appears.int by value is
copied. 2. The size of a pointer, typically 8 bytes, because the
parameter is really int*. 3. (low 0, high 9, mid 4, 45 <
58 → low 5); (5, 9, mid 7, 72 > 58 → high 6); (5, 6, mid 5, 58 found)
→ index 5 in 3 steps. 4. It returns -1 after checking only element 0;
return -1; must come after the loop. 5.
int c = 0; for (int i = 0; i < n; i++) if (arr[i] == key) c++; return c;.
6. 7 (log2(64) + 1).Sorting means arranging the elements of an array in order. Ascending order goes from smallest to largest (12, 25, 64); descending order goes from largest to smallest (64, 25, 12). We sort data all the time in daily life: a merit list sorted by marks, a phone contact list sorted by name, a bank statement sorted by date. In programming, sorting has two big benefits:
There are many sorting algorithms. We study two simple ones that every programmer should be able to write and trace by hand. Both sort the array in place, which means they rearrange the elements inside the same array without needing a second array.
Both algorithms repeatedly swap (exchange) two elements. To swap, we need a temporary variable, just as you need an empty glass to exchange the contents of a glass of milk and a glass of juice:
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;The standard library also provides std::swap(a, b) (in
<utility>, and available through
<iostream> in practice, though it is safer to include
<utility>). It does exactly the same thing. In Passing Data by Reference you wrote your own
swap using pass by reference.
Bubble sort compares each pair of neighbouring elements and swaps them if they are in the wrong order. After one complete pass from left to right, the largest element has “bubbled up” to the last position, just as the biggest bubble rises to the top of a glass of soda. The next pass does the same for the remaining elements, placing the second largest in the second-last position, and so on.
Algorithm (ascending order, n elements):
The inner loop gets shorter each pass (n - 1 - pass)
because the last elements are already in their final places.
Sort {5, 1, 4, 2, 8}. Brackets show the pair being compared.
| Pass | Comparison | Action | Array after step |
|---|---|---|---|
| 1 | [5, 1] | swap | 1 5 4 2 8 |
| 1 | [5, 4] | swap | 1 4 5 2 8 |
| 1 | [5, 2] | swap | 1 4 2 5 8 |
| 1 | [5, 8] | no swap | 1 4 2 5 8 |
| 2 | [1, 4] | no swap | 1 4 2 5 8 |
| 2 | [4, 2] | swap | 1 2 4 5 8 |
| 2 | [4, 5] | no swap | 1 2 4 5 8 |
| 3 | [1, 2] | no swap | 1 2 4 5 8 |
| 3 | [2, 4] | no swap | 1 2 4 5 8 |
Pass 3 made no swaps, so the algorithm stops early. Without the improvement it would also run pass 4 for nothing.
This program prints the array after every pass so you can compare the output with the dry-run table:
#include <iostream>
using namespace std;
void printArray(const int arr[], int n) {
for (int i = 0; i < n; i++) {
cout << arr[i] << ' ';
}
cout << '\n';
}
void bubbleSort(int arr[], int n) {
for (int pass = 1; pass < n; pass++) {
bool swapped = false;
for (int j = 0; j < n - pass; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
cout << "After pass " << pass << ": ";
printArray(arr, n);
if (!swapped) {
cout << "No swaps, so the array is sorted.\n";
break;
}
}
}
int main() {
int data[] = {5, 1, 4, 2, 8};
const int N = 5;
cout << "Before : ";
printArray(data, N);
bubbleSort(data, N);
return 0;
}Before : 5 1 4 2 8
After pass 1: 1 4 2 5 8
After pass 2: 1 2 4 5 8
After pass 3: 1 2 4 5 8
No swaps, so the array is sorted.The inner loop condition j < n - pass is the same as
“j goes up to n − 1 − pass”. It is also what keeps
arr[j + 1] inside the array: when pass is 1,
the largest j is n - 2, so j + 1
is at most n - 1, the last valid index. Writing
j < n here would read arr[n], an
out-of-bounds error.
Selection sort works like choosing players for a team in order of height. In pass 1 you select the smallest element in the whole array and swap it into position 0. In pass 2 you select the smallest element among positions 1 to n − 1 and swap it into position 1. Continue until the array is sorted.
Algorithm (ascending order):
After pass i, positions 0 to i hold the smallest
elements in sorted order.
Sort {64, 25, 12, 22, 11}:
| Pass (i) | Search range | Smallest found | Swap | Array after pass |
|---|---|---|---|---|
| 0 | index 0–4 | 11 at index 4 | arr[0] and arr[4] | 11 25 12 22 64 |
| 1 | index 1–4 | 12 at index 2 | arr[1] and arr[2] | 11 12 25 22 64 |
| 2 | index 2–4 | 22 at index 3 | arr[2] and arr[3] | 11 12 22 25 64 |
| 3 | index 3–4 | 25 at index 3 | none needed | 11 12 22 25 64 |
Four passes (n − 1) sort five elements. The last element is automatically in place.
This version sorts the marks of a class in
descending order to prepare a merit list. For
descending order we select the largest element in each
pass, so the comparison changes from < to
>.
#include <iostream>
#include <utility>
using namespace std;
void selectionSortDesc(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int maxIndex = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] > arr[maxIndex]) {
maxIndex = j;
}
}
if (maxIndex != i) {
swap(arr[i], arr[maxIndex]);
}
}
}
int main() {
const int N = 7;
int marks[N] = {67, 89, 45, 92, 73, 58, 81};
selectionSortDesc(marks, N);
cout << "Merit list (highest first)\n";
cout << "Rank Marks\n";
for (int i = 0; i < N; i++) {
cout << " " << i + 1 << " " << marks[i] << '\n';
}
return 0;
}Merit list (highest first)
Rank Marks
1 92
2 89
3 81
4 73
5 67
6 58
7 45To sort in ascending order instead, change > to
< (and, for clarity, rename maxIndex to
minIndex). The same one-character change turns ascending
bubble sort into descending bubble sort.
For an array of n elements:
| Feature | Bubble sort | Selection sort |
|---|---|---|
| Basic idea | Swap neighbours that are out of order | Select the smallest, put it in place |
| Comparisons (worst case) | n(n − 1)/2 | n(n − 1)/2 (always) |
| Swaps (worst case) | n(n − 1)/2 | at most n − 1 |
| Already sorted input | 1 pass, n − 1 comparisons (with swapped flag) | still n(n − 1)/2 comparisons |
| For n = 1,000 (comparisons) | up to 499,500 | 499,500 |
| Best for | Nearly sorted data, teaching | When swapping is expensive |
The expression n(n − 1)/2 grows roughly like n squared: doubling the
data makes the work about four times larger. That is fine for a class
list of 50 students, but too slow for millions of records. Professional
programs use faster algorithms. In C++ you can simply call
std::sort(arr, arr + n); from
<algorithm>, which you will meet again in Templates and an Introduction to the STL. In job
interviews, however, you are often expected to write bubble and
selection sort yourself, so practise them until you can write them
without notes.
Often we have related data in two arrays with the same index, for
example rolls[i] and marks[i] for the same
student. These are called parallel arrays. When sorting
by marks, you must swap both arrays at the same time,
otherwise the roll numbers no longer match the marks:
if (marks[j] < marks[j + 1]) { // descending by marks
swap(marks[j], marks[j + 1]);
swap(rolls[j], rolls[j + 1]); // keep the pair together
}In Structures and Enumerations you will learn structures, which keep a student’s roll number and marks together in one variable, so this problem disappears.
for (int j = 0; j < n; j++) with arr[j + 1]
(reads past the end). Right: j < n - pass or
j < n - 1 - i.arr[j] = arr[j + 1]; arr[j + 1] = arr[j]; (both become the
same value). Right: use temp or swap.int minIndex = 0;. Right:
int minIndex = i;.swapped flag lets bubble sort stop as soon as a pass
makes no swaps.> to < switches between
ascending and descending order.int a[] = {4, 2, 3}; for (int j = 0; j < 2; j++) if (a[j] > a[j+1]) swap(a[j], a[j+1]); cout << a[0] << a[1] << a[2];234. 6. Change if (arr[j] > arr[j + 1]) to
if (arr[j] < arr[j + 1]).Many kinds of data naturally form a table. A class mark sheet has one row per student and one column per subject. A bus timetable has one row per bus and one column per stop. A cinema hall has rows of seats. A one-dimensional array is a single row of boxes; for a table we need a two-dimensional (2D) array, which has rows and columns.
Consider the marks of 3 students in 4 subjects:
| Col 0 (C++) | Col 1 (Maths) | Col 2 (English) | Col 3 (Digital Logic) | |
|---|---|---|---|---|
| Row 0 (Aarav) | 78 | 65 | 82 | 70 |
| Row 1 (Priya) | 88 | 91 | 76 | 85 |
| Row 2 (Ram) | 55 | 48 | 67 | 60 |
Each value is identified by two indexes: the row index (which student) and the column index (which subject). Priya’s Maths mark is in row 1, column 1.
The general form is:
data_type array_name[ROWS][COLS];For the mark sheet:
const int STUDENTS = 3;
const int SUBJECTS = 4;
int marks[STUDENTS][SUBJECTS]; // 3 rows, 4 columns, 12 intsThe total number of elements is ROWS × COLS = 12. Both sizes must be
constant expressions, just like 1D arrays. Row indexes run from 0 to
ROWS − 1 and column indexes from 0 to COLS − 1. An element is written
marks[row][col], with two separate pairs of
brackets. Writing marks[1, 1] is wrong; it does
not do what you expect (the comma operator makes it
marks[1], a whole row).
Use nested braces, one inner pair per row. This is the clearest form:
int marks[3][4] = {
{78, 65, 82, 70}, // row 0: Aarav
{88, 91, 76, 85}, // row 1: Priya
{55, 48, 67, 60} // row 2: Ram
};Other rules are the same as for 1D arrays:
{ {1, 2}, {3} } for
int t[2][3] gives rows 1 2 0 and
3 0 0.int t[3][4] = {}; sets all 12 elements to 0.int t[][4] = { {1, 2, 3, 4}, {5, 6, 7, 8} };. The
column size can never be left out.int t[2][3] = {1, 2, 3, 4, 5, 6}; is legal
(it fills row by row), but the nested form is easier to read.Computer memory is one long line of bytes, not a table. So how is a
table stored? C++ stores 2D arrays in row-major order:
all of row 0 first, then all of row 1, then row 2, and so on. For
int m[2][3]:
Logical view (table): Memory view (one line):
col0 col1 col2
row 0 [ a b c ] a b c d e f
row 1 [ d e f ] [0][0] [0][1] [0][2] [1][0] [1][1] [1][2]
The position of element [i][j] counted from the start
is:
position = i * COLS + j
For example, in int m[2][3], element [1][2]
is at position 1 × 3 + 2 = 5, the sixth element. If int is
4 bytes and the array starts at address 2000, then m[1][2]
is at 2000 + 5 × 4 = 2020. This formula explains why the compiler must
know the number of columns: without it, it cannot
calculate where each row begins. You will use this fact in Matrix Operations, 2D Arrays in Functions, and
std::vector when we pass 2D arrays to functions.
A 2D array is, in fact, an array of arrays:
int m[2][3] is an array of 2 elements, and each element is
itself an array of 3 ints. So m[1] is the whole second
row.
To visit every element, use nested loops (see Nested Loops, Patterns and Tables): the outer loop picks the row, the inner loop walks along the columns of that row.
for (int i = 0; i < ROWS; i++) { // each row
for (int j = 0; j < COLS; j++) { // each column in that row
cout << m[i][j] << ' ';
}
cout << '\n'; // end of row
}The '\n' after the inner loop starts a new line after
each row, so the output looks like a table.
A row total adds along a row (one student’s total). A column total adds down a column (one subject’s total for the class). For column totals, the loops are swapped: the outer loop picks the column and the inner loop runs down the rows.
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
const int STUDENTS = 3;
const int SUBJECTS = 4;
int marks[STUDENTS][SUBJECTS] = {
{78, 65, 82, 70},
{88, 91, 76, 85},
{55, 48, 67, 60}
};
cout << "Student Sub1 Sub2 Sub3 Sub4 Total\n";
for (int i = 0; i < STUDENTS; i++) {
int total = 0;
cout << setw(7) << i + 1;
for (int j = 0; j < SUBJECTS; j++) {
cout << setw(6) << marks[i][j];
total += marks[i][j];
}
cout << setw(7) << total << '\n';
}
cout << "Average";
cout << fixed << setprecision(1);
for (int j = 0; j < SUBJECTS; j++) {
int colTotal = 0;
for (int i = 0; i < STUDENTS; i++) {
colTotal += marks[i][j];
}
cout << setw(6) << static_cast<double>(colTotal) / STUDENTS;
}
cout << '\n';
return 0;
}Student Sub1 Sub2 Sub3 Sub4 Total
1 78 65 82 70 295
2 88 91 76 85 340
3 55 48 67 60 230
Average 73.7 68.0 75.0 71.7setw from <iomanip> (see Input and Output with cin, cout, getline and iomanip)
lines up the columns. Notice the two different loop orders: row totals
use i outside and j inside; column averages
use j outside and i inside.
Input works the same way, with cin >> arr[i][j]
inside nested loops. This example uses a char 2D array as
the seat map of a small cinema hall in Kathmandu. '.' means
free and 'X' means booked. The user books seats by typing a
row and seat number (1-based), and the program checks the numbers before
using them as indexes.
#include <iostream>
using namespace std;
const int ROWS = 3;
const int SEATS = 5;
void showHall(const char hall[][SEATS]) {
cout << " Seat: 1 2 3 4 5\n";
for (int r = 0; r < ROWS; r++) {
cout << " Row " << r + 1 << ": ";
for (int s = 0; s < SEATS; s++) {
cout << hall[r][s] << ' ';
}
cout << '\n';
}
}
int main() {
char hall[ROWS][SEATS];
for (int r = 0; r < ROWS; r++) {
for (int s = 0; s < SEATS; s++) {
hall[r][s] = '.';
}
}
int row, seat;
cout << "Enter row and seat to book (0 0 to finish): ";
cin >> row >> seat;
while (row != 0) {
if (row < 1 || row > ROWS || seat < 1 || seat > SEATS) {
cout << "No such seat.\n";
} else if (hall[row - 1][seat - 1] == 'X') {
cout << "Sorry, already booked.\n";
} else {
hall[row - 1][seat - 1] = 'X';
cout << "Booked row " << row << " seat " << seat << ".\n";
}
cout << "Enter row and seat to book (0 0 to finish): ";
cin >> row >> seat;
}
showHall(hall);
return 0;
}Enter row and seat to book (0 0 to finish): 2 3
Booked row 2 seat 3.
Enter row and seat to book (0 0 to finish): 2 4
Booked row 2 seat 4.
Enter row and seat to book (0 0 to finish): 2 3
Sorry, already booked.
Enter row and seat to book (0 0 to finish): 4 1
No such seat.
Enter row and seat to book (0 0 to finish): 1 5
Booked row 1 seat 5.
Enter row and seat to book (0 0 to finish): 0 0
Seat: 1 2 3 4 5
Row 1: . . . . X
Row 2: . . X X .
Row 3: . . . . . The function showHall receives the 2D array with the
parameter const char hall[][SEATS]: the column size must be
written. The next lesson, Matrix Operations, 2D Arrays in
Functions, and std::vector, explains this in detail.
C++ allows three or more dimensions, for example
int sales[12][31][5]; for 12 months × 31 days × 5 shops.
Such arrays are rarely needed in beginner programs and use a lot of
memory quickly, so we will not study them further. Everything shown for
2D arrays extends in the same way.
marks[1, 2]. Right: marks[1][2].for (i = 0; i < COLS; i++) for (j = 0; j < ROWS; j++) m[i][j]
when ROWS ≠ COLS; this goes out of bounds. Right: the first index always
uses the row limit.int t[][] = {...}; or parameter int t[][].
Right: int t[][4].total = 0; inside the outer loop but before the inner
loop.type name[ROWS][COLS]; with
ROWS × COLS elements.name[row][col]; rows 0 to ROWS − 1,
columns 0 to COLS − 1.[i][j] = i × COLS + j.m[i] is row i.double temp[7][24]; have? What
might it store?int
array holding 1 to 6 row by row.int m[4][5], at what position (counting from 0) is
m[2][3] stored in memory? If int is 4 bytes
and m starts at address 1000, what is its address?int a[2][3] = { {1, 2, 3}, {4, 5, 6} }; int s = 0; for (int i = 0; i < 2; i++) s += a[i][i]; cout << s;int sales[5][3] (5 days, 3 shops).int t[][4] = {...}; but not
int t[3][] = {...};?int a[2][3] = { {1, 2, 3}, {4, 5, 6} };. 3. 2 × 5 + 3 = 13;
1000 + 13 × 4 = 1052. 4. a[0][0] + a[1][1] = 1 + 5 = 6. 5.
for (int j = 0; j < 3; j++) { int t = 0; for (int i = 0; i < 5; i++) t += sales[i][j]; cout << t << ' '; }.
6. The compiler needs the column count to compute where each row starts
(row-major formula); the number of rows can be counted from the
initialiser.In mathematics, a matrix is a rectangular table of numbers with m rows and n columns, called an “m × n matrix”. A 2D array is the natural way to store a matrix in C++. Matrices are used in computer graphics (moving and rotating images), engineering calculations, electrical circuits, statistics and machine learning. In this lesson you will program the three most common matrix operations.
When passing a 2D array, the parameter must state the number of columns; the number of rows may be left empty and is usually passed as a separate argument:
const int COLS = 3;
void printMatrix(const int m[][COLS], int rows);Why? As you saw in Two-Dimensional Arrays, to find
m[i][j] the compiler calculates i * COLS + j.
Inside the function it must know COLS to do this. As with
1D arrays, the function receives the address of the array (not a copy),
so it can change the caller’s matrix unless the parameter is
const.
The limitation is that printMatrix above only works for
matrices with exactly 3 columns. In this lesson we use a fixed maximum
size for simplicity; later, std::vector (end of this
lesson) and templates (see Templates and an Introduction
to the STL) remove this limit.
Two matrices can be added only if they have the same size (same rows and same columns). Each element of the result is the sum of the elements in the same position:
C[i][j] = A[i][j] + B[i][j]
Example:
| 1 2 3 | | 6 5 4 | | 7 7 7 |
| 4 5 6 | + | 3 2 1 | = | 7 7 7 |
Subtraction works the same way with -.
The transpose of an m × n matrix A is an n × m matrix T whose rows are the columns of A:
T[j][i] = A[i][j]
Example: the transpose of the 2 × 3 matrix
| 1 2 3 / 4 5 6 | is the 3 × 2 matrix
| 1 4 / 2 5 / 3 6 |. Notice that the sizes swap, so the
result needs a differently shaped array.
The following program uses functions for printing, adding and transposing:
#include <iostream>
#include <iomanip>
using namespace std;
const int R = 2;
const int C = 3;
void printMatrix(const int m[][C], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < C; j++) {
cout << setw(4) << m[i][j];
}
cout << '\n';
}
}
void addMatrices(const int a[][C], const int b[][C], int sum[][C],
int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < C; j++) {
sum[i][j] = a[i][j] + b[i][j];
}
}
}
// t has C rows and R columns
void transpose(const int a[][C], int t[][R]) {
for (int i = 0; i < R; i++) {
for (int j = 0; j < C; j++) {
t[j][i] = a[i][j];
}
}
}
int main() {
int a[R][C] = { {1, 2, 3}, {4, 5, 6} };
int b[R][C] = { {6, 5, 4}, {3, 2, 1} };
int sum[R][C];
int t[C][R];
addMatrices(a, b, sum, R);
cout << "A + B:\n";
printMatrix(sum, R);
transpose(a, t);
cout << "Transpose of A (" << C << " x " << R << "):\n";
for (int i = 0; i < C; i++) {
for (int j = 0; j < R; j++) {
cout << setw(4) << t[i][j];
}
cout << '\n';
}
return 0;
}A + B:
7 7 7
7 7 7
Transpose of A (3 x 2):
1 4
2 5
3 6The result matrices sum and t are created
in main and filled by the functions. This is the usual way
to “return” an array from a function in C++ with built-in arrays.
Matrix multiplication is not done element by element. To multiply A (m × n) by B (n × p):
C[i][j] = A[i][0]*B[0][j] + A[i][1]*B[1][j] + ... + A[i][n-1]*B[n-1][j]
Example, A is 2 × 3 and B is 3 × 2, so C is 2 × 2:
A = | 1 2 3 | B = | 7 8 |
| 4 5 6 | | 9 10 |
| 11 12 |
C[0][0] = 1*7 + 2*9 + 3*11 = 7 + 18 + 33 = 58
C[0][1] = 1*8 + 2*10 + 3*12 = 8 + 20 + 36 = 64
C[1][0] = 4*7 + 5*9 + 6*11 = 28 + 45 + 66 = 139
C[1][1] = 4*8 + 5*10 + 6*12 = 32 + 50 + 72 = 154
In code this needs three nested loops:
i over rows of A, j over columns of B, and
k along the shared dimension n.
This program also checks the size rule, which beginners often forget:
#include <iostream>
#include <iomanip>
using namespace std;
const int MAX = 10;
void multiply(const int a[][MAX], int m, int n,
const int b[][MAX], int p, int c[][MAX]) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < p; j++) {
c[i][j] = 0; // start each sum at 0
for (int k = 0; k < n; k++) {
c[i][j] += a[i][k] * b[k][j];
}
}
}
}
int main() {
int a[MAX][MAX] = { {1, 2, 3}, {4, 5, 6} }; // 2 x 3
int b[MAX][MAX] = { {7, 8}, {9, 10}, {11, 12} }; // 3 x 2
int c[MAX][MAX];
int rowsA = 2, colsA = 3, rowsB = 3, colsB = 2;
if (colsA != rowsB) {
cout << "Cannot multiply: columns of A must equal rows of B.\n";
return 1;
}
multiply(a, rowsA, colsA, b, colsB, c);
cout << "A x B (" << rowsA << " x " << colsB << "):\n";
for (int i = 0; i < rowsA; i++) {
for (int j = 0; j < colsB; j++) {
cout << setw(5) << c[i][j];
}
cout << '\n';
}
return 0;
}A x B (2 x 2):
58 64
139 154Here the arrays are declared with a maximum size (10 × 10) and the actual sizes are kept in separate variables, the same “partly filled array” idea from Traversing and Processing One-Dimensional Arrays. This lets one function handle any matrix up to 10 × 10. Note also that in general A × B is not equal to B × A; here B × A would even be a 3 × 3 matrix.
By now you have seen several weaknesses of built-in arrays:
= and
==.The C++ Standard Library offers a better tool for most everyday uses:
std::vector.
A vector is a sequence of elements, like an array,
that knows its own size and can grow
while the program runs. It is declared in the header
<vector>. The type of element goes inside angle
brackets:
#include <vector>
vector<int> marks; // empty vector of int
vector<double> bills(12); // 12 doubles, all 0.0
vector<int> rolls = {11, 19, 24}; // 3 elements
vector<vector<int>> matrix(3, vector<int>(4, 0)); // 3 x 4 of zerosThe most useful operations are:
| Operation | Meaning |
|---|---|
v.push_back(x) |
Add x at the end; the vector grows by one |
v.size() |
Number of elements now in the vector |
v[i] |
Element i, no bounds check (fast, like an array) |
v.at(i) |
Element i, with bounds check |
v.pop_back() |
Remove the last element |
v.empty() |
true if the vector has no elements |
v1 = v2 |
Copy a whole vector (not allowed for built-in arrays) |
size() returns an unsigned type (size_t).
To avoid a -Wall warning about comparing signed and
unsigned numbers, loop with size_t or use a range-based
for.
This program reads any number of electricity bills until the user types 0. With a built-in array we would have to guess a maximum; with a vector we do not:
#include <iostream>
#include <vector>
using namespace std;
double average(const vector<double>& values) {
double total = 0;
for (double v : values) {
total += v;
}
return total / values.size();
}
int main() {
vector<double> bills;
double amount;
cout << "Enter monthly bills in Rs. (0 to stop):\n";
cin >> amount;
while (amount != 0) {
bills.push_back(amount);
cin >> amount;
}
if (bills.empty()) {
cout << "No bills entered.\n";
return 0;
}
cout << "You entered " << bills.size() << " bills.\n";
for (size_t i = 0; i < bills.size(); i++) {
cout << "Bill " << i + 1 << ": Rs. " << bills.at(i) << '\n';
}
cout << "Average bill: Rs. " << average(bills) << '\n';
return 0;
}Enter monthly bills in Rs. (0 to stop):
1200
950
1430
0
You entered 3 bills.
Bill 1: Rs. 1200
Bill 2: Rs. 950
Bill 3: Rs. 1430
Average bill: Rs. 1193.33Notice two things. First, the function average receives
the vector by const reference
(const vector<double>&, Passing
Data by Reference). A vector passed by value would be copied, which
is slow for large vectors, and unlike a built-in array it would
not decay to a pointer. Second, the function does not
need a size parameter: values.size() gives it.
With v[i], a vector behaves like an array: a wrong index
is undefined behaviour. With v.at(i), the vector checks the
index and, if it is out of range, stops the program with a clear message
instead of silently corrupting memory:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> marks = {78, 85, 62, 91, 70};
cout << marks.at(2) << '\n';
cout << marks.at(5) << '\n'; // index 5 does not exist
return 0;
}This compiles without errors, but when run with g++ it prints:
62
terminate called after throwing an instance of 'std::out_of_range'
what(): vector::_M_range_check: __n (which is 5) >= this->size() (which is 5)
The message tells you exactly what went wrong: index 5 was used but
the size is 5. (The exact wording differs between compilers.) The
mechanism behind this message is called an exception.
For now, the lesson is simple: while developing and debugging, prefer
.at(); it turns a hidden memory error into a visible
one.
Built-in arrays are still important: they appear in older code, in
many textbook examples, and they are the basis for pointers (the Pointers and Dynamic Memory chapter) and C-style strings
(see C-Style Strings). But for new programs where the
number of elements is not fixed, std::vector is the
recommended choice.
void f(int m[][]). Right:
void f(int m[][COLS]).c[i][j] = a[i][j] * b[i][j];. Right: the triple loop with
k.c[i][j] to 0 before
accumulating the products, so garbage is added.for (int i = 0; i < v.size(); i++) (warning with
-Wall). Right: size_t i or a range-based
for.const vector<int>&.type m[][COLS].t[j][i] = a[i][j]; an m × n matrix becomes n
× m.std::vector knows its size, can grow with
push_back, can be copied with =, and offers
checked access with .at().int m[][4]?| 2 0 / 1 4 | and B =
| 1 3 / 5 2 | (the / separates rows).vector<int> v = {5, 6}; v.push_back(7); v.push_back(8); v.pop_back(); cout << v.size() << " " << v.at(2);int trace(const int m[][3], int n)
that returns the sum of the main diagonal of an n × n matrix (n ≤
3).std::vector over a built-in
array.m[i][j]
(i × 4 + j) in row-major storage. 2. No (2 ≠ 3); yes, the result is 3 ×
4. 3. Row 1: 2×1 + 0×5 = 2, 2×3 + 0×2 = 6; row 2: 1×1 + 4×5 = 21, 1×3 +
4×2 = 11; so A × B = | 2 6 / 21 11 |. 4. 3 7.
5.
int s = 0; for (int i = 0; i < n; i++) s += m[i][i]; return s;.
6. Any two of: size can change at run time; knows its size;
.at() checks bounds; can be copied/assigned with
=; no decay when passed.A string is a sequence of characters, such as
"Kathmandu", "Sita Sharma" or
"9841234567". You have been using string
literals (text in double quotes) since the first Hello
World program. Now we ask: how is a string stored in a variable?
C++ has two ways:
char,
inherited from the C language. They are used in older code, in some
libraries, and they help us understand memory.std::string: a class from the C++
Standard Library (see The std::string Class). It is
easier and safer, and is what you should use in new programs.This lesson covers C-style strings.
A C-style string is an array of characters that ends
with the special null character, written
'\0' (a backslash followed by zero). Its value is 0. It is
not the digit '0' (whose ASCII code is 48) and it is not a
space. The null character marks where the text ends,
because an array does not remember how many of its elements are in
use.
When you write:
char city[] = "Kathmandu";the compiler creates an array of 10 characters, not
9. It copies the 9 letters and automatically adds '\0' at
the end:
| Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| Character | K | a | t | h | m | a | n | d | u | \0 |
The rule to remember: an array that holds a string of n characters needs at least n + 1 elements.
Every string function relies on the '\0'.
cout << city prints characters one by one
until it meets '\0'. If the null character
is missing, cout keeps printing whatever bytes follow the
array in memory, producing garbage or crashing.
char a[] = "Ram"; // size 4, compiler adds '\0'
char b[20] = "Ram"; // size 20; b[3] is '\0', rest '\0'
char c[] = {'R', 'a', 'm', '\0'}; // same as a, written the long way
char d[] = {'R', 'a', 'm'}; // NOT a string: no '\0'!
char e[3] = "Ram"; // ERROR in C++: needs 4 elementsArray b has room for longer text later (up to 19
characters plus '\0'). Array d is just three
characters; printing it with cout << d is undefined
behaviour.
The next program shows the difference between the array size
(sizeof) and the length of the text, and prints every
character with its ASCII code:
#include <iostream>
#include <cstring>
using namespace std;
int main() {
char city[] = "Kathmandu";
char name[20] = "Priya";
cout << "city = " << city << '\n';
cout << "sizeof(city) = " << sizeof(city)
<< ", strlen(city) = " << strlen(city) << '\n';
cout << "sizeof(name) = " << sizeof(name)
<< ", strlen(name) = " << strlen(name) << '\n';
cout << "Characters of name with ASCII codes:\n";
for (int i = 0; name[i] != '\0'; i++) {
cout << " name[" << i << "] = '" << name[i] << "' ("
<< static_cast<int>(name[i]) << ")\n";
}
cout << " name[5] = " << static_cast<int>(name[5])
<< " (the null terminator)\n";
return 0;
}city = Kathmandu
sizeof(city) = 10, strlen(city) = 9
sizeof(name) = 20, strlen(name) = 5
Characters of name with ASCII codes:
name[0] = 'P' (80)
name[1] = 'r' (114)
name[2] = 'i' (105)
name[3] = 'y' (121)
name[4] = 'a' (97)
name[5] = 0 (the null terminator)Look at the loop condition name[i] != '\0'. This is the
standard way to walk through a C-style string: we do not need its
length, we simply stop at the null character. strlen itself
works exactly like this: it counts characters until it reaches
'\0'.
cin >> word; reads characters until the first
space, tab or newline (whitespace). So if the user
types Sita Sharma, only Sita is stored. Worse,
cin >> does not know the size of the array: typing 30
letters into char word[10] overflows the array.
To read a whole line, including spaces, safely, use
cin.getline:
char fullName[40];
cin.getline(fullName, 40); // reads at most 39 chars, adds '\0'The second argument is the size of the array. getline
stops at the end of the line or when the array is full, and always adds
'\0', so it cannot overflow.
A common trap: after cin >> age;, the newline the
user typed stays in the input. A following cin.getline then
reads that empty line immediately. The fix is
cin.ignore(1000, '\n');, which throws away up to 1000
characters up to and including the newline:
#include <iostream>
using namespace std;
int main() {
int age;
char fullName[40];
char address[60];
cout << "Age: ";
cin >> age;
cin.ignore(1000, '\n'); // remove the leftover newline
cout << "Full name: ";
cin.getline(fullName, 40);
cout << "Address: ";
cin.getline(address, 60);
cout << fullName << " (" << age << ") lives in "
<< address << ".\n";
return 0;
}Age: 19
Full name: Sita Sharma
Address: Baneshwor, Kathmandu
Sita Sharma (19) lives in Baneshwor, Kathmandu.Try this yourself: remove the cin.ignore line and run
the program again — the program will skip the “Full name” input, because
getline reads the empty remainder of the age line.
<cstring>
libraryBecause a C-style string is an array, the operators =,
+ and == do not work on its
contents (you saw in Introduction to Arrays that
arrays cannot be assigned or compared). Instead, the header
<cstring> provides functions:
| Function | Meaning | Example |
|---|---|---|
strlen(s) |
Number of characters before '\0' |
strlen("Ram") is 3 |
strcpy(dest, src) |
Copy src into dest (including
'\0') |
strcpy(name, "Hari"); |
strcat(dest, src) |
Append src to the end of dest |
strcat(name, " Thapa"); |
strcmp(s1, s2) |
Compare: 0 if equal, negative if s1 comes first, positive if s1 comes after | strcmp("Ram", "Sita") < 0 |
strcmp compares character by character using the
character codes (ASCII), so it is case-sensitive: all capital letters
(65–90) come before all small letters (97–122).
strcmp("apple", "Apple") is positive. The standard only
promises the sign of the result (negative, zero,
positive), not the exact number, so always test < 0,
== 0 or > 0.
#include <iostream>
#include <cstring>
using namespace std;
void showOrder(const char s1[], const char s2[]) {
int result = strcmp(s1, s2);
if (result == 0) {
cout << '"' << s1 << "\" and \"" << s2 << "\" are equal\n";
} else if (result < 0) {
cout << '"' << s1 << "\" comes before \"" << s2 << "\"\n";
} else {
cout << '"' << s1 << "\" comes after \"" << s2 << "\"\n";
}
}
int main() {
char first[20] = "Aarav";
char last[20] = "Shrestha";
char full[50];
strcpy(full, first); // full = "Aarav"
strcat(full, " "); // full = "Aarav "
strcat(full, last); // full = "Aarav Shrestha"
cout << "Full name: " << full << " (" << strlen(full)
<< " characters)\n";
showOrder("Ram", "Sita");
showOrder("Sita", "Ram");
showOrder("Ram", "Ram");
showOrder("ram", "Ram");
return 0;
}Full name: Aarav Shrestha (14 characters)
"Ram" comes before "Sita"
"Sita" comes after "Ram"
"Ram" and "Ram" are equal
"ram" comes after "Ram"Notice that full was declared with 50 elements: enough
for “Aarav” (5) + space (1) + “Shrestha” (8) + '\0' (1) =
15, with room to spare. You are responsible for making
the destination large enough; strcpy and
strcat do not check.
C-style strings are a common source of serious bugs and security problems:
strcpy,
strcat and cin >> write past the end of
the array if the text is too long. This overwrites other variables
(undefined behaviour). In some cases g++ can detect it at compile
time:char small[5];
strcpy(small, "Kathmandu"); // 10 bytes into 5warning: 'void* __builtin_memcpy(void*, const void*, long unsigned int)'
writing 10 bytes into a region of size 5 overflows the destination
[-Wstringop-overflow=]
But when the text comes from the user at run time, the compiler
cannot help. (Microsoft Visual C++ even warns that strcpy
is “unsafe” and suggests other functions.)
Missing null terminator. Printing or measuring a
char array without '\0' reads past its end.
Using = and ==.
city = "Pokhara"; does not compile:
error: incompatible types in assignment of 'const char [8]' to 'char [20]'
and if (a == b) compares the addresses
of the two arrays, not their text; g++ warns
comparison between two arrays [-Warray-compare]. Use
strcpy and strcmp instead.
Safe habits when you must use C-style strings: always leave room for
'\0', use cin.getline(buffer, size) rather
than cin >>, check lengths with strlen
before strcpy/strcat, and compile with
-Wall. In practice, the best habit is to use
std::string, which removes all four dangers. That is the
topic of the next lesson, The std::string Class.
'\0'. Wrong:
char code[3] = "BIT";. Right:
char code[4] = "BIT"; (or
char code[] = "BIT";).'\0' with '0'.
'\0' has value 0; '0' has value 48.=. Wrong:
name = "Hari";. Right:
strcpy(name, "Hari");.==. Wrong:
if (password == "nepal123"). Right:
if (strcmp(password, "nepal123") == 0).strcmp as a boolean. Wrong:
if (strcmp(a, b)) to mean “equal” (it is true when they are
different). Right: == 0.cin >> for names with
spaces. Use cin.getline, and
cin.ignore after a previous cin >>.sizeof for the length.
sizeof gives the array size; strlen gives the
text length.char array terminated by
'\0'; a string of n characters needs n + 1 elements."Ram" automatically include the
'\0'.for (int i = 0; s[i] != '\0'; i++).cin >> stops at whitespace;
cin.getline(buf, size) reads a whole line safely.<cstring>: strlen,
strcpy, strcat, strcmp (test the
sign of the result).sizeof(s) and strlen(s) for
char s[15] = "Pokhara";?char w[10] = "BIT"; strcat(w, "2026"); cout << w << " " << strlen(w);if (name == "Ram") not check whether
name contains “Ram”? Write the correct condition.Hari Prasad for
cin >> name;. What is stored in name?
What is left in the input?sentence, without using strlen.'\0' (value 0) placed after the last
character; it marks the end of the text because the array does not store
the length. 2. 15 and 7. 3. BIT2026 7. 4. It compares the
address of the array with the address of the literal; correct:
strcmp(name, "Ram") == 0. 5. Hari;
Prasad and the newline remain in the input buffer. 6.
int spaces = 0; for (int i = 0; sentence[i] != '\0'; i++) if (sentence[i] == ' ') spaces++;.In C-Style Strings you saw that C-style strings
need a fixed size, a null terminator, special functions for copying and
comparing, and great care to avoid overflow. The C++ Standard Library
solves all of this with the string class,
declared in the header <string>. (Its full name is
std::string; with using namespace std; we can
write just string.)
A string object:
=, +, +=,
==, < and the other operators
naturally,You met string briefly in Input and Output
with cin, cout, getline and iomanip when reading a name with
getline. Now we study it properly.
#include <string>
string empty; // "" (length 0)
string city = "Kathmandu"; // from a literal
string copy = city; // copy of another string
string line(20, '-'); // 20 dashes: "--------------------"Unlike a local int, a string that is not
initialised is not garbage: it is simply the empty
string "".
cout << city; prints a string. For input there are
two choices, just as with C-style strings:
cin >> word; reads one word
(stops at whitespace).getline(cin, line); reads a whole
line, including spaces.Note the different form: for std::string we write
getline(cin, s) (a free function), whereas for a char array
we wrote cin.getline(buf, size). There is no size argument
because the string grows as needed. The cin.ignore rule
from C-Style Strings still applies: after
cin >> x, discard the leftover newline before calling
getline.
| Operation | Meaning | Example (s = “Ram”) |
|---|---|---|
s.length() or s.size() |
Number of characters | 3 |
s.empty() |
true if length is 0 |
false |
s1 + s2 |
New string with s2 joined to s1 (concatenation) | s + " Thapa" gives “Ram Thapa” |
s += t |
Append t to s | s += "!" makes “Ram!” |
==, != |
Same text or not | s == "Ram" is true |
<, >, <=,
>= |
Dictionary (lexicographic) order | s < "Sita" is true |
s[i] |
Character at index i (no check) | s[0] is 'R' |
s.at(i) |
Character at index i (checked) | s.at(5) stops with an error |
Comparison is lexicographic: character by character
using character codes, like strcmp. So it is
case-sensitive, and "Zebra" < "apple" is
true because 'Z' (90) is less than
'a' (97).
One small trap: at least one side of + must be a
string object. "Ram" + " Thapa" tries to add
two C-style literals and does not compile;
string("Ram") + " Thapa" works.
#include <iostream>
#include <string>
using namespace std;
int main() {
string firstName, lastName;
cout << "First name: ";
getline(cin, firstName);
cout << "Last name: ";
getline(cin, lastName);
string fullName = firstName + " " + lastName;
cout << "Hello, " << fullName << "!\n";
cout << "Your name has " << fullName.length()
<< " characters (including the space).\n";
cout << "Your initials are " << firstName[0] << '.'
<< lastName.at(0) << ".\n";
string password;
cout << "Create a password: ";
getline(cin, password);
if (password.length() < 8) {
cout << "Too short: use at least 8 characters.\n";
} else if (password == firstName || password == lastName) {
cout << "Do not use your own name as a password.\n";
} else {
cout << "Password accepted.\n";
}
if (firstName < lastName) {
cout << firstName << " comes before " << lastName
<< " in dictionary order.\n";
} else {
cout << lastName << " comes before " << firstName
<< " in dictionary order.\n";
}
return 0;
}First name: Sita
Last name: Karki
Hello, Sita Karki!
Your name has 10 characters (including the space).
Your initials are S.K.
Create a password: Bagmati#77
Password accepted.
Karki comes before Sita in dictionary order.Compare this with the C-style version in C-Style
Strings: no array sizes, no strcpy, no
strcat, no strcmp. The code reads like the
problem statement.
These member functions work on part of a string. Positions are indexes starting from 0, as for arrays.
| Function | Meaning |
|---|---|
s.substr(pos, len) |
Returns a new string of len characters starting at
pos (to the end if len is left out) |
s.find(t) |
Index of the first occurrence of t (a string or a
char), or string::npos if not found |
s.find(t, from) |
Same, but starts searching at index from |
s.insert(pos, t) |
Inserts t before index pos |
s.erase(pos, len) |
Removes len characters starting at
pos |
s.replace(pos, len, t) |
Replaces len characters at pos with
t |
string::npos is a special constant meaning “no
position”. Always compare the result of find with
string::npos before using it as an index. Store the result
in a variable of type size_t (an unsigned type), which is
what find returns.
This program takes apart an email address and a phone number:
#include <iostream>
#include <string>
using namespace std;
int main() {
string email = "sita.sharma@thamescollege.edu.np";
size_t at = email.find('@');
if (at == string::npos) {
cout << "Not a valid email address.\n";
return 1;
}
string user = email.substr(0, at);
string domain = email.substr(at + 1);
cout << "User name : " << user << '\n';
cout << "Domain : " << domain << '\n';
size_t dot = user.find('.');
if (dot != string::npos) {
cout << "First name: " << user.substr(0, dot) << '\n';
}
if (email.find("gmail") == string::npos) {
cout << "This is not a Gmail address.\n";
}
string phone = "9841234567";
phone.insert(3, "-"); // 984-1234567
phone.insert(8, "-"); // 984-1234-567
cout << "Phone : " << phone << '\n';
string msg = "Class is on Sunday at 7 AM.";
msg.replace(msg.find("Sunday"), 6, "Monday");
msg.erase(msg.find(" at"), 8); // remove " at 7 AM"
cout << "Message : " << msg << '\n';
return 0;
}User name : sita.sharma
Domain : thamescollege.edu.np
First name: sita
This is not a Gmail address.
Phone : 984-1234-567
Message : Class is on Monday.A string can be traversed like an array. Use an index
loop with size_t (to avoid a signed/unsigned warning), or a
range-based for. The header <cctype>
(see Modular Programming and Library Functions) gives
useful character tests and conversions:
| Function | Returns true / result |
|---|---|
isalpha(c) |
letter |
isdigit(c) |
digit 0–9 |
isspace(c) |
space, tab, newline |
isupper(c), islower(c) |
capital / small letter |
toupper(c), tolower(c) |
the converted character (as an int) |
To change the characters of the string itself in a range-based loop,
use a reference: for (char& c : s).
The next program counts different kinds of characters, converts to capitals, and checks whether a word is a palindrome (reads the same forwards and backwards, ignoring case):
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
bool isPalindrome(const string& word) {
size_t left = 0, right = word.length();
while (left + 1 < right) {
if (tolower(word[left]) != tolower(word[right - 1])) {
return false;
}
left++;
right--;
}
return true;
}
int main() {
string sentence;
cout << "Enter a sentence: ";
getline(cin, sentence);
int letters = 0, vowels = 0, digits = 0, spaces = 0;
string vowelList = "aeiou";
for (char c : sentence) {
if (isalpha(c)) {
letters++;
char small = static_cast<char>(tolower(c));
if (vowelList.find(small) != string::npos) {
vowels++;
}
} else if (isdigit(c)) {
digits++;
} else if (isspace(c)) {
spaces++;
}
}
cout << "Letters: " << letters << ", vowels: " << vowels
<< ", digits: " << digits << ", spaces: " << spaces << '\n';
string upper = sentence;
for (char& c : upper) {
c = static_cast<char>(toupper(c));
}
cout << "In capitals: " << upper << '\n';
string words[] = {"Madam", "Nepal", "Level", "kayak"};
for (const string& w : words) {
cout << w << (isPalindrome(w) ? " is" : " is not")
<< " a palindrome\n";
}
return 0;
}Enter a sentence: BIT batch 2026 starts in Baneshwor
Letters: 25, vowels: 7, digits: 4, spaces: 5
In capitals: BIT BATCH 2026 STARTS IN BANESHWOR
Madam is a palindrome
Nepal is not a palindrome
Level is a palindrome
kayak is a palindromeNotice the array of strings, string words[]: arrays (the
Arrays chapter) can hold strings just like numbers,
and each element is a complete string. Strictly speaking,
isalpha and friends expect values that fit in
unsigned char; for ordinary English text as in this
tutorial, passing a char is fine.
Two groups of functions are often useful:
to_string(x) converts a number to a string:
to_string(2026) gives "2026".stoi(s) and stod(s) convert a string to an
int or double: stoi("45") gives
45.For example, string roll = "BIT-" + to_string(42);
builds "BIT-42".
| Feature | C-style string (char[]) |
std::string |
|---|---|---|
| Header | <cstring> |
<string> |
| Size | Fixed, chosen in advance | Grows automatically |
| End marker | Needs '\0' |
Managed internally |
| Length | strlen(s) |
s.length() |
| Copy | strcpy(d, s) |
d = s |
| Join | strcat(d, s) |
d + s or d += s |
| Compare | strcmp(a, b) == 0 |
a == b |
| Read a line | cin.getline(buf, size) |
getline(cin, s) |
| Overflow risk | Yes | No (for these operations) |
If some old function needs a C-style string, s.c_str()
gives a read-only C-style version of a std::string. You
will use this in the Files and Operations chapter
with older file functions.
#include <string>. It
sometimes works without it because <iostream> happens
to include it, but that is not guaranteed. Always include it.cin >> and
getline without
cin.ignore(1000, '\n') in between.find without checking
npos. Wrong: s.substr(s.find('@'))
when there may be no @. Right: store in
size_t pos and test pos != string::npos.substr as an
end index. s.substr(2, 3) means “3 characters
starting at index 2”.string s = "Ram" + " Thapa";. Right:
string s = "Ram"; s += " Thapa";.size_t i with s.length(), or a range-based
for."ram" == "Ram" is false; convert both to one
case first.std::string (header <string>) grows
automatically and is the recommended string type in C++.cin >> s, a line with
getline(cin, s).length(), +, +=,
==, <, [], at()
work naturally.substr(pos, len), find (check
string::npos), insert, erase,
replace work with parts of a string.for (char c : s), or
for (char& c : s) to modify them; use
<cctype> to test and convert.to_string and stoi/stod
convert between numbers and strings.std::string over C-style
strings.string s = "Kathmandu"; cout << s.length() << " " << s.substr(0, 4) << " " << s.find('m');s.find("xyz") return if “xyz” is not in
s? How should you test for it?string a = "Hari", b = "hari"; cout << (a == b) << (a < b);getline and
prints it with every letter in capitals.int countChar(const string& s, char ch) that returns
how many times ch appears in s.=/+/== work, no null-terminator
handling, no overflow when copying/joining. 2. 9 Kath 4. 3.
string::npos; test
if (s.find("xyz") == string::npos). 4. 01 (not
equal; 'H' (72) < 'h' (104), so
a < b is true). 5.
string name; getline(cin, name); for (char& c : name) c = static_cast<char>(toupper(c)); cout << name;.
6.
int n = 0; for (char c : s) if (c == ch) n++; return n;.A student record in a college office has several pieces of
information: roll number (an int), name (a
string), marks (a double), and perhaps a grade
(a char). These values belong together, but they have
different types, so an array cannot hold them (an array
needs one type).
In Sorting — Bubble Sort and Selection Sort we
used parallel arrays (rolls[i],
names[i], marks[i]) and saw the danger: when
sorting, if we forget to swap one of the arrays, the data gets mixed up.
What we really want is one variable that holds a whole
student record. That is exactly what a structure gives us.
A structure (keyword struct) is a
user-defined data type that groups related variables, possibly of
different types, under one name. Each variable inside it is called a
member (or field).
A good analogy is an admission form: the form is the structure type (it has fixed boxes for Name, Roll number, Marks), and each filled-in form for a particular student is a structure variable.
struct Student {
int roll;
string name;
double marks;
}; // <- the semicolon is required!This creates a new type named Student. It does
not create any variable or use any memory yet; it is
only a design, like a blank form. By convention, structure type names
start with a capital letter. The definition is usually placed
before main (at global scope), so that all
functions can use the type.
Once the type exists, we declare variables of it exactly like
int variables:
Student s1; // members not initialised
Student s2 = {102, "Priya Gurung", 88.5}; // values in member order
Student s3{}; // all members zero / emptyTo reach a member we use the dot operator
(.), also called the member access operator:
variable.member.
s1.roll = 101;
s1.name = "Aarav Shrestha";
s1.marks = 76.0;
cout << s1.name << " scored " << s1.marks;Each member behaves like a normal variable of its own type:
s1.marks is a double, s1.name is
a string (so s1.name.length() works).
Unlike built-in arrays, whole structures can be
assigned with =: s3 = s2; copies
every member. However, == is not
automatically defined for structures; to compare two students, compare
members (s1.roll == s2.roll). (More Operator
Overloading — Friends, Stream and Comparison Operators shows how to
define == yourself.)
#include <iostream>
#include <string>
using namespace std;
struct Student {
int roll;
string name;
double marks;
};
int main() {
Student s1;
s1.roll = 101;
s1.name = "Aarav Shrestha";
s1.marks = 76.0;
Student s2 = {102, "Priya Gurung", 88.5};
Student backup = s2; // copies all three members
s2.marks = 90.0; // change the original only
cout << "Roll Name Marks\n";
cout << s1.roll << " " << s1.name << " " << s1.marks << '\n';
cout << s2.roll << " " << s2.name << " " << s2.marks << '\n';
cout << "Backup still has " << backup.marks << " for "
<< backup.name << '\n';
if (s2.marks > s1.marks) {
cout << s2.name << " scored more than " << s1.name << ".\n";
}
return 0;
}Roll Name Marks
101 Aarav Shrestha 76
102 Priya Gurung 90
Backup still has 88.5 for Priya Gurung
Priya Gurung scored more than Aarav Shrestha.The backup keeps 88.5 because backup = s2 made an
independent copy.
The members of a structure variable are stored together in one block
of memory, in the order they are declared. sizeof(Student)
gives the total size. It is often a little larger than
the sum of the member sizes, because the compiler may add unused
“padding” bytes so that each member starts at an address the CPU can
read efficiently. The exact size is compiler- and platform-dependent, so
never calculate it by hand; use sizeof. This matters later,
when you write structures to binary files in Binary Files
and Random Access.
A class has many students, so we make an array of
structures. Each element is a whole Student, and
we combine indexing with the dot operator: list[i].marks
means “the marks member of element i”.
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
struct Student {
int roll;
string name;
double marks;
};
int main() {
const int N = 4;
Student list[N] = {
{101, "Aarav Shrestha", 76.0},
{102, "Priya Gurung", 88.5},
{103, "Ram Thapa", 39.0},
{104, "Sita Karki", 91.0}
};
double total = 0;
int top = 0;
cout << left << setw(6) << "Roll" << setw(18) << "Name"
<< "Marks Result\n";
for (int i = 0; i < N; i++) {
cout << left << setw(6) << list[i].roll
<< setw(18) << list[i].name
<< setw(7) << list[i].marks
<< (list[i].marks >= 40 ? "Pass" : "Fail") << '\n';
total += list[i].marks;
if (list[i].marks > list[top].marks) {
top = i;
}
}
cout << fixed << setprecision(2);
cout << "Class average: " << total / N << '\n';
cout << "Topper: " << list[top].name << " (roll "
<< list[top].roll << ")\n";
return 0;
}Roll Name Marks Result
101 Aarav Shrestha 76 Pass
102 Priya Gurung 88.5 Pass
103 Ram Thapa 39 Fail
104 Sita Karki 91 Pass
Class average: 73.62
Topper: Sita Karki (roll 104)Now sorting is safe: swapping list[j] and
list[j + 1] moves the whole record, so a
roll number can never be separated from its name and marks.
left (from <iomanip>) aligns text to the
left inside setw.
A member of a structure can itself be a structure. This is called a
nested structure. For example, a date of birth has a
day, month and year, so we define a Date structure and use
it inside Student. Access uses two dots:
s.dob.year.
Structures can be passed to and returned from functions like any other type:
| Way of passing | Syntax | Effect |
|---|---|---|
| By value | void show(Student s) |
Function gets a copy; changes are lost; copying can be slow for big structures |
| By const reference | void show(const Student& s) |
No copy, read-only; the usual choice for reading |
| By reference | void addGrace(Student& s) |
No copy; changes affect the caller’s variable |
| Return a structure | Student readStudent() |
A function can build and return a whole record |
Because a structure can be returned, one function can effectively return several values at once, which a normal function cannot do.
Sometimes a variable should only hold one of a small set of named values: the days of the week, the level of a student (first year, second year…), or the faculty (BIT, BCA, BBA). An enumeration creates such a type.
enum Day { SUN, MON, TUE, WED, THU, FRI, SAT }; // SUN = 0, MON = 1...
enum class Faculty { BIT, BCA, BBA }; // scoped enum (C++11)enum, the names (called
enumerators) are integer constants starting from 0,
unless you give values:
enum Slab { LOW = 20, MID = 50, HIGH = 100 };. The names
are visible everywhere, so two enums cannot both use the name
LOW, and they convert silently to int.enum class (scoped enumeration) is
the modern, safer form. You must write the type name:
Faculty::BIT. It does not convert to int
automatically (use static_cast<int> if you need the
number), so it cannot be mixed up with ordinary numbers by mistake.
Prefer enum class in new code.Enumerations make code self-explaining:
if (f == Faculty::BIT) is much clearer than
if (f == 0). They work very well with switch
(see The switch Statement and Menu-Driven
Programs).
The final example combines everything: a nested structure, an
enum class member, a function that returns a structure, and
functions taking const and non-const
references.
#include <iostream>
#include <string>
using namespace std;
enum class Faculty { BIT, BCA, BBA };
struct Date {
int day, month, year;
};
struct Student {
int roll;
string name;
Date dob; // nested structure
Faculty faculty;
double marks;
};
string facultyName(Faculty f) {
switch (f) {
case Faculty::BIT: return "BIT";
case Faculty::BCA: return "BCA";
case Faculty::BBA: return "BBA";
}
return "Unknown";
}
Student makeStudent(int roll, string name, Date dob, Faculty f) {
Student s;
s.roll = roll;
s.name = name;
s.dob = dob;
s.faculty = f;
s.marks = 0.0;
return s; // the whole record is returned
}
void setMarks(Student& s, double m) { // changes the caller's data
if (m >= 0 && m <= 100) {
s.marks = m;
}
}
void printStudent(const Student& s) { // read-only, no copy
cout << s.roll << ": " << s.name << " (" << facultyName(s.faculty)
<< "), born " << s.dob.year << "-" << s.dob.month << "-"
<< s.dob.day << ", marks " << s.marks << '\n';
}
int main() {
Student a = makeStudent(201, "Sita Karki", {15, 4, 2006},
Faculty::BIT);
Student b = makeStudent(202, "Hari Magar", {2, 11, 2005},
Faculty::BCA);
setMarks(a, 84.5);
setMarks(b, 120); // rejected: outside 0-100
printStudent(a);
printStudent(b);
if (a.dob.year > b.dob.year) {
cout << a.name << " is younger than " << b.name << ".\n";
}
cout << "Faculty code of " << a.name << " is "
<< static_cast<int>(a.faculty) << '\n';
return 0;
}201: Sita Karki (BIT), born 2006-4-15, marks 84.5
202: Hari Magar (BCA), born 2005-11-2, marks 0
Sita Karki is younger than Hari Magar.
Faculty code of Sita Karki is 0The brace list {15, 4, 2006} creates a Date
directly in the function call. setMarks(b, 120) leaves
Hari’s marks at 0 because the value failed the check: a small example of
protecting data, an idea that classes (the Classes and
Objects chapter) take much further. In fact, a C++
class is a structure whose members are private by default,
so everything you learn about struct here carries straight
into object-oriented programming.
struct Student { int roll; } followed by
int main(). Right: };. The error message often
points to the next line, which confuses beginners.Student.marks = 80;. Right:
s1.marks = 80;.list.marks. Right: list[i].marks.==.
Not defined by default; compare members.Student&.Faculty f = BIT;. Right:
Faculty f = Faculty::BIT;.enum class directly with
cout. It does not compile; convert with a function
(like facultyName) or
static_cast<int>.struct groups related members of possibly different
types into one new type; end the definition with ;.var.member; for
arrays: arr[i].member; nested:
var.inner.member.=, passed to and returned from functions.const reference to read, by reference to
modify.enum class defines a safe set of named constants; use
Type::Name.Book with members title
(string), author (string), price (double) and
pages (int), and declare a variable initialised with your
favourite book.struct P { int x, y; }; P a = {3, 4}; P b = a; b.x = 10; cout << a.x + b.x << " " << a.y + b.y;Student list[30];, write an expression for the
first character of the name of the fifth student.void addGrace(Student s) unable to change the
caller’s marks? How do you fix it?enum class Level { Beginner, Intermediate, Advanced }; and
write a switch that prints a message for each level... 2.
struct Book { string title; string author; double price; int pages; };
then
e.g. Book b = {"Muna Madan", "Laxmi Prasad Devkota", 150.0, 60};.
3. 13 8. 4. list[4].name[0]. 5. It receives a
copy; change the parameter to Student& s. 6.
switch (lv) { case Level::Beginner: cout << "Start here"; break; case Level::Intermediate: ...; case Level::Advanced: ...; }.In Computer Fundamentals you learned that RAM (main memory) holds the program and its data while the program runs. We can picture RAM as a very long row of small boxes. Each box holds exactly one byte (8 bits). Each box also has a number, called its address. Addresses start from 0 and go up to a very large number (billions on a modern PC).
When we write int marks = 85;, the compiler reserves
enough bytes for an int (typically 4 bytes on modern
systems) and remembers the address of the first byte. The name
marks is just a friendly label for that address.
| Address (example) | Contents | Label |
|---|---|---|
| 1000 | part of 85 | marks (byte 1) |
| 1001 | part of 85 | marks (byte 2) |
| 1002 | part of 85 | marks (byte 3) |
| 1003 | part of 85 | marks (byte 4) |
| 1004 | … | next variable |
The real addresses are much larger numbers and are usually printed in
hexadecimal (base 16), for example
0x7ffd5c3e1a2c. The 0x prefix means “this
number is written in hexadecimal”. The exact addresses change every time
you run a program, and they are different on different computers. We
never write addresses by hand; we let the program find them.
&Putting & in front of a variable name gives the
address of that variable. cout prints addresses in
hexadecimal.
#include <iostream>
using namespace std;
int main() {
int marks = 85;
double fare = 25.5;
char grade = 'A';
cout << "marks = " << marks << ", stored at " << &marks << '\n';
cout << "fare = " << fare << ", stored at " << &fare << '\n';
// A char address must be cast, otherwise cout tries to print a string
cout << "grade = " << grade << ", stored at "
<< static_cast<void*>(&grade) << '\n';
cout << "sizeof(marks) = " << sizeof(marks) << " bytes\n";
cout << "sizeof(fare) = " << sizeof(fare) << " bytes\n";
return 0;
}marks = 85, stored at 0x7ffe17eef67c
fare = 25.5, stored at 0x7ffe17eef680
grade = A, stored at 0x7ffe17eef67b
sizeof(marks) = 4 bytes
sizeof(fare) = 8 bytesYour addresses will be different — that is normal. Notice the special
case for char: when cout receives a
char* it assumes it is a C-style string (see C-Style Strings) and prints characters until it finds
'\0'. Casting to void* (“pointer to anything”)
tells cout to print the address instead.
A pointer is a variable whose value is an address. The type of a pointer says what kind of value is stored at that address.
int* ptr; // ptr can hold the address of an int
double* dp; // dp can hold the address of a double
char* cp; // cp can hold the address of a charRead int* ptr from right to left: “ptr is a
pointer to int”. The spaces do not matter:
int* ptr, int *ptr and int * ptr
all mean the same thing. However, be careful with several names on one
line:
int* a, b; // a is a pointer to int, but b is a plain int!
int *c, *d; // both c and d are pointersTo avoid confusion, declare one pointer per line.
We store an address in a pointer using &:
int marks = 85;
int* ptr = &marks; // ptr now "points to" marksThe dereference operator * (also called
the indirection operator) goes to the address stored in the pointer and
gives us the variable found there. So *ptr is
marks. We can read it and also change it.
#include <iostream>
using namespace std;
int main() {
int marks = 85;
int* ptr = &marks;
cout << "marks = " << marks << '\n';
cout << "*ptr = " << *ptr << '\n';
cout << "ptr == &marks? " << (ptr == &marks ? "yes" : "no") << '\n';
*ptr = 92; // change marks through the pointer
cout << "After *ptr = 92, marks = " << marks << '\n';
marks = marks + 3; // change marks directly
cout << "After marks += 3, *ptr = " << *ptr << '\n';
return 0;
}marks = 85
*ptr = 85
ptr == &marks? yes
After *ptr = 92, marks = 92
After marks += 3, *ptr = 95The symbol * has three different jobs in C++, which
confuses many beginners:
Where * appears |
Meaning | Example |
|---|---|---|
| In a declaration, after a type | “this is a pointer” | int* ptr; |
| In an expression, before a pointer | “dereference: go to the address” | *ptr = 5; |
| Between two numbers | multiplication | a * b |
Similarly, & in a declaration after a type means
“reference” (see Passing Data by Reference), while
& before a variable in an expression means “address
of”.
A pointer diagram is a simple drawing: a box for each variable with its value inside, and an arrow from each pointer to the variable it points to. Drawing these is the best way to understand pointer code. For the program above:
ptr marks
+--------+ +--------+
| o----+----------->| 92 |
+--------+ +--------+
(holds the address (an int stored at
of marks) that address)
When two pointers hold the same address, both arrows point to the
same box, and changing *p1 also changes what
*p2 sees.
Sometimes a pointer should not point to anything yet. C++11
introduced the keyword nullptr, the
null pointer, for this. A pointer holding nullptr
is guaranteed not to point to any object, and we can test for it.
#include <iostream>
using namespace std;
int main() {
int busFare = 30;
int* farePtr = nullptr; // points to nothing for now
if (farePtr == nullptr) {
cout << "farePtr is null, nothing to show.\n";
}
farePtr = &busFare;
if (farePtr != nullptr) {
cout << "Bus fare is Rs. " << *farePtr << '\n';
}
// A pointer can be used directly as a condition:
// non-null means true, null means false.
farePtr = nullptr;
if (!farePtr) {
cout << "farePtr is null again.\n";
}
return 0;
}farePtr is null, nothing to show.
Bus fare is Rs. 30
farePtr is null again.Older books use NULL or 0 for the null
pointer. In modern C++ always use nullptr: it has its own
type and cannot be mistaken for the integer 0 (this matters with
function overloading).
Never dereference a null pointer.
*farePtr when farePtr is nullptr
is undefined behaviour — the C++ standard gives no
guarantee about what happens. On most desktop systems the program
crashes with a “segmentation fault”, but you must not rely on even
that.
A local pointer declared without a value (int* p;)
contains garbage — some random address. Using it is undefined behaviour
and may silently overwrite other data. The rule is simple:
Every pointer must be initialised either with a valid address or with
nullptr.
A pointer is itself a variable, so it also has an address. A pointer
that stores the address of another pointer has type int**
(“pointer to pointer to int”). We will not need these much in this
tutorial, but you should recognise the syntax. Also, the size of a
pointer does not depend on what it points to: on a typical 64-bit system
every data pointer is 8 bytes; on a 32-bit system it is 4 bytes.
#include <iostream>
using namespace std;
int main() {
int units = 150; // electricity units used
int* p = &units;
int** pp = &p; // pointer to a pointer
cout << "units = " << units << '\n';
cout << "*p = " << *p << '\n';
cout << "**pp = " << **pp << '\n';
**pp = 175; // change units through two levels
cout << "units after **pp = 175: " << units << '\n';
cout << "sizeof(int*) = " << sizeof(int*) << '\n';
cout << "sizeof(double*) = " << sizeof(double*) << '\n';
cout << "sizeof(char*) = " << sizeof(char*) << '\n';
return 0;
}units = 150
*p = 150
**pp = 150
units after **pp = 175: 175
sizeof(int*) = 8
sizeof(double*) = 8
sizeof(char*) = 8(This output is from a 64-bit Linux system; a 32-bit system would show 4.)
& when assigning an address:
int* p = marks; is an error (cannot convert
int to int*). Right:
int* p = &marks;.double d = 2.5; int* p = &d; is an
error. A pointer to int may only hold the address of an
int.int* p; *p = 10; is
undefined behaviour. Right: int* p = nullptr; and point it
somewhere valid before use.nullptr: always check
if (p != nullptr) when a pointer might be null.int* a, b; and expecting two pointers. Only
a is a pointer.p (the address) with *p (the
value at that address). p = 5; is an error;
*p = 5; is correct.&x gives
the address of x.int*,
double*…) says what type lives there.*p means “the variable that p points to”;
it can be read and assigned.nullptr means “points to nothing”. Initialise every
pointer, and never dereference a null or garbage pointer.What is a pointer? How is it different from an ordinary
int variable?
Give the meaning of * in each of these:
int* q;, *q = 4;,
x = y * 2;.
What is the output?
int a = 10, b = 20;
int* p = &a;
*p = *p + 5;
p = &b;
*p = *p * 2;
cout << a << " " << b;Why is int* p; cout << *p; dangerous? How
would you fix it?
Write a short program that declares
double temperature = 18.5;, a pointer to it, and uses only
the pointer to add 2.0 to the temperature and print it.
In int* a, b;, what are the types of a
and b?
int stores a whole-number value. 2.
Declaration of a pointer; dereference (store 4 at the address in q);
multiplication. 3. 15 40. 4. p is
uninitialised and holds a garbage address, so dereferencing it is
undefined behaviour; initialise it,
e.g. int x = 0; int* p = &x; or
int* p = nullptr; and check before use. 5.
double temperature = 18.5; double* tp = &temperature; *tp = *tp + 2.0; cout << *tp;
prints 20.5. 6. a is int*, b is
int.From Introduction to Arrays you know that the
elements of an array are stored in consecutive
(side-by-side) memory locations. For int marks[5], if
marks[0] starts at address 1000 and an int is
4 bytes, then marks[1] starts at 1004,
marks[2] at 1008, and so on.
| Element | marks[0] | marks[1] | marks[2] | marks[3] | marks[4] |
|---|---|---|---|---|---|
| Value | 70 | 85 | 62 | 91 | 78 |
| Address (example) | 1000 | 1004 | 1008 | 1012 | 1016 |
Because elements are side by side, a pointer can move from one element to the next. This is called pointer arithmetic.
If p is a pointer to int holding address
1000, then p + 1 is not 1001. It is the
address of the next int, that is 1000 + 1 × sizeof(int) = 1004.
In general:
address of (p + n) = address in p + n * sizeof(type pointed to)
The compiler does the multiplication for you. That is why the
pointer’s type matters: a double* moves 8 bytes at a time
(typically), a char* moves 1 byte.
The allowed operations are:
| Operation | Meaning | Result type |
|---|---|---|
p + n, p - n |
pointer n elements forward/back | pointer |
p++, ++p, p--,
--p |
move p by one element | pointer |
p += n, p -= n |
move p by n elements | pointer |
q - p (same array) |
number of elements between them | integer (ptrdiff_t) |
p == q, p < q, etc. |
compare positions in the same array | bool |
You cannot add two pointers, or multiply/divide pointers — those have no meaning (what would “house number + house number” mean?).
#include <iostream>
using namespace std;
int main() {
int marks[5] = {70, 85, 62, 91, 78};
int* p = &marks[0];
cout << "p points to " << *p << '\n';
cout << "p + 1 points to " << *(p + 1) << '\n';
cout << "p + 3 points to " << *(p + 3) << '\n';
// Addresses differ by sizeof(int), not by 1
cout << "Address in p: " << p << '\n';
cout << "Address in p + 1: " << (p + 1) << '\n';
p++; // now points to marks[1]
cout << "After p++, *p = " << *p << '\n';
int* last = &marks[4];
cout << "Elements from p to last: " << (last - p) << '\n';
return 0;
}p points to 70
p + 1 points to 85
p + 3 points to 91
Address in p: 0x7fff35a9bfd0
Address in p + 1: 0x7fff35a9bfd4
After p++, *p = 85
Elements from p to last: 3Look at the two addresses: the second one is exactly 4 more than the
first (compare the last hexadecimal digit), and 4 is
sizeof(int) on this system.
In most expressions, the name of an array automatically
converts (“decays”) to a pointer to its first element.
So marks means the same as &marks[0]. This
leads to a very important rule that the compiler itself uses:
marks[i] is exactly the same as *(marks + i)
That is why array indexes start from 0: marks[0] is
*(marks + 0), the first element. Both forms also work on a
pointer: if int* p = marks; then p[2] is the
same as *(p + 2).
| Expression | Meaning | Value (marks above) |
|---|---|---|
marks |
address of marks[0] | an address |
*marks |
marks[0] | 70 |
*(marks + 2) |
marks[2] | 62 |
marks + 4 |
address of marks[4] | an address |
p[3] (p = marks) |
marks[3] | 91 |
But an array is not the same thing as a pointer:
sizeof(marks) gives the size of the whole array (5 × 4
= 20 bytes typically), while sizeof(p) gives the size of a
pointer (typically 8).marks++ is an error,
but p++ is fine.The following program finds the total and highest marks of a student
using only pointers. The loop runs while p is before the
“one past the end” address marks + SIZE.
#include <iostream>
using namespace std;
int main() {
const int SIZE = 5;
int marks[SIZE] = {70, 85, 62, 91, 78};
int total = 0;
int highest = *marks; // same as marks[0]
int* end = marks + SIZE; // one past the last element
for (int* p = marks; p != end; ++p) {
cout << *p << ' ';
total += *p;
if (*p > highest) {
highest = *p;
}
}
cout << '\n';
cout << "Total = " << total << '\n';
cout << "Highest = " << highest << '\n';
// Pointer difference gives the index of an element
int* found = marks;
while (found != end && *found != 91) {
++found;
}
if (found != end) {
cout << "91 found at index " << (found - marks) << '\n';
}
return 0;
}70 85 62 91 78
Total = 386
Highest = 91
91 found at index 3The idea of a one-past-the-end pointer is used
everywhere in C++, including the standard library (begin()
and end() of std::vector, Templates and an Introduction to the STL). It is legal
to form and compare the address
marks + SIZE, but it is not legal to
dereference it, because there is no element there.
marks + 10 or
marks - 1) is undefined behaviour even if you never
dereference it.<, >) two
pointers is only defined when both point into the same array.A C-style string (see C-Style Strings) is an array
of char ending with the null character
'\0'. A string literal such as "Kathmandu" is
an array of const char, so we point to it with
const char*. Many classic string functions are written by
moving a char* forward until it reaches
'\0'.
#include <iostream>
using namespace std;
// Count characters before the null terminator (like strlen)
int myLength(const char* s) {
const char* p = s;
while (*p != '\0') {
++p;
}
return static_cast<int>(p - s);
}
// Count vowels using pointer traversal
int countVowels(const char* s) {
int count = 0;
for (const char* p = s; *p != '\0'; ++p) {
char c = *p;
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' ||
c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U') {
++count;
}
}
return count;
}
int main() {
const char* city = "Kathmandu";
char name[] = "Aarav"; // modifiable copy in an array
cout << city << " has " << myLength(city) << " characters and "
<< countVowels(city) << " vowels\n";
// Print one character per line using a pointer
for (const char* p = name; *p; ++p) {
cout << *p << ' ';
}
cout << '\n';
// Change the array through a pointer: make it upper case
for (char* p = name; *p; ++p) {
if (*p >= 'a' && *p <= 'z') {
*p = *p - 'a' + 'A';
}
}
cout << "Upper case: " << name << '\n';
return 0;
}Kathmandu has 9 characters and 3 vowels
A a r a v
Upper case: AARAVNotice *p used as a loop condition: '\0'
has the value 0, which is false, so the loop stops at the end of the
string.
Why const char* for "Kathmandu"? String
literals are stored in memory that the program must not modify. Since
C++11 the standard does not allow char* city = "Kathmandu";
(g++ reports “ISO C++ forbids converting a string constant to ’char*’”
as a warning; other compilers, such as MSVC in standard mode, report an
error). Treat it as an error. If you need to change the characters, copy
them into a char array (like name above) or,
better, use std::string.
char*As you saw in Memory, Addresses and Pointer
Basics, cout << p where p is a
char* or const char* prints the
string starting at that address, not the address. Printing
p + 3 prints the string starting from the 4th character — a
neat trick:
#include <iostream>
using namespace std;
int main() {
const char* place = "Bhaktapur";
cout << place << '\n';
cout << place + 5 << '\n'; // starts at index 5
cout << *(place + 5) << '\n'; // just one character
cout << static_cast<const void*>(place) << '\n'; // the address
return 0;
}Bhaktapur
apur
a
0x563910679004p + 1 adds one byte. It adds one
element (sizeof(type) bytes).for (int* p = marks; p <= marks + SIZE; ++p) reads one
element too many. Use p != marks + SIZE or
p < marks + SIZE.marks++; is an error.
Use a separate pointer: int* p = marks; p++;.char* s = "Pokhara"; — not allowed since C++11
(g++ warns, other compilers give an error). Write
const char* s = "Pokhara";.*p++ with (*p)++.
*p++ reads *p then moves the pointer;
(*p)++ increases the value and does not move the pointer.
Use brackets to make your meaning clear.sizeof(p) to find the number of elements when
p is a pointer; it gives only the pointer’s size.a[i] means *(a + i); an array name
converts to a pointer to its first element.'\0'; walk them with a
char* until *p == '\0'. Point to string
literals with const char*.If double* dp holds address 2000 and
sizeof(double) is 8, what address does dp + 3
hold?
What is the output?
int a[] = {5, 10, 15, 20};
int* p = a + 1;
cout << *p << " " << *(p + 2) << " " << p[1] << " " << (p - a);Rewrite
for (int i = 0; i < n; i++) sum += arr[i]; using a
pointer instead of i.
Why does a modern C++ compiler complain about
char* s = "Hello";? What should you write?
What is printed by
const char* s = "Nepal"; cout << s + 2;?
Explain the difference between *p++ and
(*p)++.
10 20 15 1. 3.
for (int* p = arr; p != arr + n; ++p) sum += *p; 4. A
string literal is an array of const char; converting it to
char* would allow modifying read-only data, so C++11
forbids it. Write const char* s = "Hello"; (or use
std::string). 5. pal. 6. *p++
gives the value at p and then moves p to the next element (postfix ++
applies to p); (*p)++ increments the value that p points to
and p does not move.In Passing Data by Value we saw that pass by value
gives the function a copy. Changes to the copy do not affect
the caller. In Passing Data by Reference we fixed
that with references (int&). There is a third way,
inherited from the C language, that is still very common: pass
by pointer. The caller sends the address of its
variable, and the function uses * to reach the
original.
#include <iostream>
using namespace std;
void swapByValue(int a, int b) { // works on copies only
int temp = a;
a = b;
b = temp;
}
void swapByPointer(int* a, int* b) { // works on the originals
int temp = *a;
*a = *b;
*b = temp;
}
void swapByReference(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int sita = 10, ram = 20;
swapByValue(sita, ram);
cout << "After swapByValue: " << sita << " " << ram << '\n';
swapByPointer(&sita, &ram); // note the & at the call
cout << "After swapByPointer: " << sita << " " << ram << '\n';
swapByReference(sita, ram);
cout << "After swapByReference: " << sita << " " << ram << '\n';
return 0;
}After swapByValue: 10 20
After swapByPointer: 20 10
After swapByReference: 10 20The pointer version looks different in two places: the
call must pass addresses (&sita), and
the body must dereference (*a). Inside
swapByPointer, a holds the address of
sita, so *a is sita.
main: sita [10] ram [20]
^ ^
| |
swapByPointer: a [o] b [o] temp [10]
| Feature | Pass by value | Pass by reference | Pass by pointer |
|---|---|---|---|
| Parameter | int x |
int& x |
int* x |
| Call | f(n) |
f(n) |
f(&n) |
| Can change caller’s variable? | No | Yes | Yes |
| Can be “empty”? | No | No (must refer to something) | Yes (nullptr) |
| Syntax inside function | x |
x |
*x |
| Copies large data? | Yes | No | No |
A practical rule for modern C++:
int, double, char).const reference for large data you
only read (const string&).nullptr), when working with arrays and
C-style code, or when using dynamic memory (the next lesson, Dynamic Memory Allocation with new and delete).A function that takes a pointer should check for nullptr
if a null pointer is possible:
#include <iostream>
using namespace std;
// Adds a late fee to a bill if the pointer is valid.
// Returns true on success, false if there is no bill.
bool addLateFee(double* bill, double fee) {
if (bill == nullptr) {
return false;
}
*bill += fee;
return true;
}
int main() {
double electricityBill = 1450.0;
double* noBill = nullptr;
if (addLateFee(&electricityBill, 50.0)) {
cout << "New bill: Rs. " << electricityBill << '\n';
}
if (!addLateFee(noBill, 50.0)) {
cout << "No bill to update.\n";
}
return 0;
}New bill: Rs. 1500
No bill to update.When we pass an array to a function (see Arrays and Functions; Linear and Binary Search), what is actually passed is a pointer to its first element. These three prototypes are exactly the same to the compiler:
double average(const int arr[], int size);
double average(const int arr[10], int size); // the 10 is ignored!
double average(const int* arr, int size);That is why the function cannot know the size by itself and we must
pass size, and why sizeof(arr) inside the
function gives only the size of a pointer.
A function may return a pointer, for example to the largest element of an array:
#include <iostream>
using namespace std;
// Returns a pointer to the largest element (size must be at least 1)
int* largest(int* arr, int size) {
int* best = arr;
for (int* p = arr + 1; p != arr + size; ++p) {
if (*p > *best) {
best = p;
}
}
return best;
}
int main() {
int scores[] = {67, 88, 72, 95, 80};
int* top = largest(scores, 5);
cout << "Top score " << *top << " at index " << (top - scores) << '\n';
*top = 100; // we can even change it through the pointer
cout << "scores[3] is now " << scores[3] << '\n';
return 0;
}Top score 95 at index 3
scores[3] is now 100This is safe because top points into
scores, which still exists in main.
Never return the address of a local variable: the local
variable is destroyed when the function returns, and the pointer becomes
a dangling pointer (see Memory Errors,
AddressSanitizer and Smart Pointers).
int* badFunction() {
int result = 42;
return &result; // WRONG: result dies when the function returns
} // g++ warns: address of local variable returnedIn Structures and Enumerations we accessed
structure members with the dot operator: s.name. If we have
a pointer to a structure, we first dereference it and then use
the dot: (*ptr).name. The brackets are needed because
. has higher precedence than *. Since this is
written so often, C++ gives us a shortcut, the arrow
operator ->:
ptr->member means (*ptr).member
#include <iostream>
#include <string>
using namespace std;
struct Student {
string name;
int roll;
double marks;
};
void addBonus(Student* s, double bonus) {
s->marks += bonus; // same as (*s).marks += bonus;
if (s->marks > 100) {
s->marks = 100;
}
}
void printStudent(const Student* s) {
cout << s->roll << " " << s->name << " " << s->marks << '\n';
}
int main() {
Student priya = {"Priya Sharma", 7, 88.5};
Student* ptr = &priya;
cout << (*ptr).name << " (using (*ptr).name)\n";
cout << ptr->name << " (using ptr->name)\n";
addBonus(&priya, 5);
printStudent(&priya);
// Pointer to an element of an array of structures
Student group[3] = {{"Aarav", 1, 72}, {"Sita", 2, 97}, {"Ram", 3, 65}};
for (Student* p = group; p != group + 3; ++p) {
addBonus(p, 5);
printStudent(p);
}
return 0;
}Priya Sharma (using (*ptr).name)
Priya Sharma (using ptr->name)
7 Priya Sharma 93.5
1 Aarav 77
2 Sita 100
3 Ram 70The arrow operator will be used all the time with classes (the Classes and Objects chapter) and dynamic objects.
With a pointer there are two things that could be constant: the value being pointed to, and the pointer itself. Read the declaration from right to left:
| Declaration | Read as | Change *p? |
Change p? |
|---|---|---|---|
int* p |
p is a pointer to int | Yes | Yes |
const int* p |
p is a pointer to const int | No | Yes |
int* const p |
p is a const pointer to int | Yes | No |
const int* const p |
p is a const pointer to const int | No | No |
(int const* p is another way of writing
const int* p.)
The most useful form is pointer to const
(const int*). Use it for function parameters that only
read the data, like printStudent above. The
compiler then protects the caller’s data:
#include <iostream>
using namespace std;
void show(const int* arr, int size) {
for (int i = 0; i < size; ++i) {
cout << arr[i] << ' ';
}
arr[0] = 0; // not allowed: arr points to const int
}
int main() {
int fares[] = {20, 25, 30};
int* const fixedPtr = fares;
fixedPtr = fares + 1; // not allowed: fixedPtr itself is const
show(fares, 3);
return 0;
}Compiler messages (g++):
main.cpp:9:12: error: assignment of read-only location '* arr'
main.cpp:15:14: error: assignment of read-only variable 'fixedPtr'
A pointer to non-const can be passed where a pointer to const is expected (adding protection is fine), but not the other way round (removing protection is an error).
& in the call:
swapByPointer(sita, ram); is an error (cannot convert
int to int*).* in the function: a = b;
inside swapByPointer only swaps the local pointer copies,
not the values.*s.marks instead of s->marks or
(*s).marks. Because . binds tighter,
*s.marks means *(s.marks), which is an
error.nullptr.const in the wrong place:
int* const p does not protect the data, it
only fixes the pointer.&var, use
*param inside; the function can change the original.nullptr) is a valid choice.ptr->member is shorthand for
(*ptr).member.const int* p: data is read-only.
int* const p: pointer is fixed. Read declarations from
right to left.Write the prototype and call for a function doubleIt
that doubles an int using pass by pointer.
What is the output?
void change(int* p, int q) { *p = *p + 10; q = q + 10; }
// in main:
int a = 1, b = 2;
change(&a, b);
cout << a << " " << b;If Student* sp = &priya;, write two different
expressions that access priya’s roll number.
For each declaration, say whether *p = 5; and
p = &y; are allowed: (a)
const int* p = &x; (b)
int* const p = &x;.
Why is returning &localVariable from a function
wrong?
Why do functions that receive arrays also need a size parameter?
void doubleIt(int* n) { *n = *n * 2; } called as
doubleIt(&value);. 2. 11 2. 3.
sp->roll and (*sp).roll (also
priya.roll). 4. (a) *p = 5 not allowed,
p = &y allowed; (b) *p = 5 allowed,
p = &y not allowed. 5. The local variable is destroyed
when the function returns, so the returned address is dangling; using it
is undefined behaviour. 6. The array decays to a pointer to its first
element, which carries no length information.Suppose a school wants to store the marks of a class, but the number of students is typed in at run time: 35 in one section, 48 in another. Standard C++ requires the size of an ordinary array to be a compile-time constant:
int n;
cin >> n;
int marks[n]; // NOT standard C++ (a "variable-length array")g++ accepts this as an extension (with a warning only if you
add -pedantic), but other compilers such as Microsoft
Visual C++ reject it, and a large n can crash the program
because the array is placed on the small stack. The standard solution is
dynamic memory allocation: asking for memory while the
program runs.
A running C++ program uses different areas of memory. Two are important for us:
| Feature | Stack | Heap (free store) |
|---|---|---|
| What lives there | local variables, function parameters | objects created with new |
| Who allocates | automatic, when the variable is defined | the programmer, with new |
| Who frees | automatic, when the block/function ends | the programmer, with delete |
| Size | small (often about 1–8 MB, system-dependent) | large (limited by available RAM) |
| Size decided | at compile time | can be decided at run time |
| Speed | very fast | slower (the system must find free space) |
Objects on the heap have no name. The only way to
reach them is through the pointer that new returns. That is
why dynamic memory and pointers always go together.
int* p = new int; // allocate one int (value not initialised)
int* q = new int(500); // allocate one int and initialise it to 500
int* r = new int{}; // allocate one int initialised to 0
delete p; // give the memory back
delete q;
delete r;new T does three things: finds enough free memory on the
heap for a T, creates the object there, and returns its
address. delete p destroys the object and returns its
memory to the system. After delete, the pointer
p still holds the old address, but the memory no longer
belongs to us. It is good practice to set it to nullptr
immediately.
#include <iostream>
#include <string>
using namespace std;
struct Book {
string title;
double price;
};
int main() {
double* rate = new double(13.5); // tax rate in percent
cout << "Tax rate: " << *rate << "%\n";
Book* book = new Book{"Muna Madan", 250.0};
cout << book->title << " costs Rs. " << book->price << '\n';
double tax = book->price * (*rate) / 100;
cout << "Price with tax: Rs. " << book->price + tax << '\n';
delete book; // one delete for each new
book = nullptr;
delete rate;
rate = nullptr;
cout << "Memory released.\n";
return 0;
}Tax rate: 13.5%
Muna Madan costs Rs. 250
Price with tax: Rs. 283.75
Memory released.Deleting a null pointer is safe and does nothing, so
delete p; when p is nullptr does
no harm. Deleting the same non-null address twice is a serious
error (see Memory Errors, AddressSanitizer and Smart
Pointers).
To allocate an array of n elements on the heap, use
new T[n]. The size n can be any expression
computed at run time. To release it you must use
delete[] (with the square brackets).
int n = 40;
int* marks = new int[n]; // n ints, values not initialised
double* fares = new double[n]{}; // n doubles, all set to 0.0
// ... use marks[0] .. marks[n-1] like a normal array ...
delete[] marks;
delete[] fares;| Allocated with | Must be freed with |
|---|---|
new T |
delete p |
new T[n] |
delete[] p |
Mixing them (for example new[] with plain
delete) is undefined behaviour.
Here is a complete program that stores marks for a class whose size is typed by the user:
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
int n;
cout << "How many students? ";
cin >> n;
if (n <= 0) {
cout << "Invalid number of students.\n";
return 1;
}
int* marks = new int[n]; // array sized at run time
for (int i = 0; i < n; ++i) {
cout << "Marks of student " << i + 1 << ": ";
cin >> marks[i]; // use like a normal array
}
int total = 0, passed = 0;
for (int i = 0; i < n; ++i) {
total += marks[i];
if (marks[i] >= 40) {
++passed;
}
}
cout << fixed << setprecision(2);
cout << "Average: " << static_cast<double>(total) / n << '\n';
cout << "Passed : " << passed << " of " << n << '\n';
delete[] marks; // release the whole array
marks = nullptr;
return 0;
}How many students? 4
Marks of student 1: 67
Marks of student 2: 35
Marks of student 3: 82
Marks of student 4: 90
Average: 68.50
Passed : 3 of 4The pointer marks is a local variable on the stack; the
four ints it points to are on the heap. When
main ends, the pointer disappears automatically, but the
heap memory would not be freed automatically if we
forgot delete[]. (Modern operating systems reclaim all
memory when a program exits, but in long-running programs — servers,
games, phone apps — leaks keep growing until the program slows down or
crashes.)
STACK HEAP
+-----------+ +----+----+----+----+
| marks o--+--------->| 67 | 35 | 82 | 90 |
+-----------+ +----+----+----+----+
| n [4] | (no name; reachable only
+-----------+ through the pointer)
A function can allocate memory and return the pointer; the caller then becomes responsible for deleting it. This “who owns the memory?” question is central to writing safe C++ code. Write it in a comment so that nobody forgets.
#include <iostream>
using namespace std;
// Creates an array with the multiplication table of 'number'.
// The CALLER owns the returned array and must delete[] it.
int* makeTable(int number, int rows) {
int* table = new int[rows];
for (int i = 0; i < rows; ++i) {
table[i] = number * (i + 1);
}
return table;
}
void printArray(const int* arr, int size) {
for (int i = 0; i < size; ++i) {
cout << arr[i] << ' ';
}
cout << '\n';
}
int main() {
int rows = 10;
int* table7 = makeTable(7, rows);
printArray(table7, rows);
delete[] table7; // the caller frees it
return 0;
}7 14 21 28 35 42 49 56 63 70 Unlike returning the address of a local variable (see Pointers with Functions and Structures; const and Pointers), this is correct: heap memory lives until it is deleted, not until the function returns.
A dynamic array cannot change size after it is created. To “grow” it we (1) allocate a bigger array, (2) copy the old elements, (3) delete the old array, and (4) make the pointer point to the new array. The next program reads bus fares until the user types 0, doubling the capacity whenever the array is full.
#include <iostream>
using namespace std;
int main() {
int capacity = 2;
int count = 0;
int* fares = new int[capacity];
int fare;
cout << "Enter fares (0 to stop): ";
while (cin >> fare && fare != 0) {
if (count == capacity) { // full: grow
int newCapacity = capacity * 2;
int* bigger = new int[newCapacity];
for (int i = 0; i < count; ++i) {
bigger[i] = fares[i]; // copy old values
}
delete[] fares; // free old block
fares = bigger; // use new block
capacity = newCapacity;
cout << "(grew to capacity " << capacity << ")\n";
}
fares[count] = fare;
++count;
}
int total = 0;
for (int i = 0; i < count; ++i) {
total += fares[i];
}
cout << count << " fares, total Rs. " << total << '\n';
delete[] fares;
return 0;
}Enter fares (0 to stop): 20 25 30 15 35 0
(grew to capacity 4)
(grew to capacity 8)
5 fares, total Rs. 125This is exactly the idea used inside std::vector (see Matrix Operations, 2D Arrays in Functions, and
std::vector), which does all of this — and the delete[]
— for us. In real programs prefer std::vector; we write it
by hand here to understand what happens underneath.
If the heap has no space, new does not return
nullptr; it throws an exception of type
std::bad_alloc, which stops the program unless it is
caught. (There is a special form new (std::nothrow) int[n]
that returns nullptr instead.) Exceptions are beyond the
scope of this tutorial; for our small programs it is enough to know that
new either succeeds or stops the program — so checking
if (p == nullptr) after a normal new is
unnecessary.
delete/delete[] — a memory
leak.delete for an array:
int* a = new int[10]; delete a; is undefined behaviour.
Right: delete[] a;.delete (a dangling pointer). Set
the pointer to nullptr after deleting.p = new int[10]; p = new int[20]; — the first block can
never be freed.new:
int x; int* p = &x; delete p; is undefined
behaviour.new initialises values:
new int[5] leaves values unspecified. Use
new int[5]{} for zeros.int a[n];) —
use new[] or std::vector.delete them.new returns the address of an unnamed heap object; keep
it in a pointer.new with one delete, and every
new[] with one delete[].new throws std::bad_alloc; it
does not return nullptr.List three differences between stack memory and heap memory.
Write statements to allocate an array of n doubles
on the heap, all set to 0.0, and later free it.
What is wrong with this code?
int* p = new int[5]; ... delete p;
What is the output?
int* a = new int(10);
int* b = a;
*b = *b + 5;
cout << *a << '\n';
delete a;In p = new int[10]; p = new int[20];, what problem
occurs and how would you fix it?
Why is it acceptable for a function to return a pointer from
new, but not the address of a local variable?
new/delete,
large, size can be chosen at run time, slower, objects have no names. 2.
double* d = new double[n]{}; ... delete[] d; d = nullptr;
3. Memory from new[] must be released with
delete[]; using delete is undefined behaviour.
4. 15 (a and b point to the same heap int; only one delete
is needed and only one is done). 5. The address of the first block is
lost, so it can never be deleted — a memory leak. Write
delete[] p; before the second new, or use a
second pointer. 6. Heap memory lives until it is deleted; a local
variable is destroyed at the end of its function, leaving a dangling
pointer.Most mistakes we have met so far are caught by the compiler (syntax errors) or produce clearly wrong output (logic errors). Memory errors are worse. They usually cause undefined behaviour (UB): the C++ standard places no requirement on what the program does. The program might crash, print garbage, print the correct answer today and a wrong answer tomorrow, or work on your laptop and fail on the lab PC. For that reason we will not show “the output” of the buggy programs below as if it were fixed. Instead we describe the typical result, and then we use a tool that detects the error reliably.
In each buggy program below the first line is
// error-demo. Do not copy these programs as examples of
good code!
A memory leak happens when heap memory is never deleted and the program loses the last pointer to it. The memory stays reserved but can never be used again.
#include <iostream>
using namespace std;
void recordReading(int units) {
int* reading = new int(units); // allocated every call...
cout << "Recorded " << *reading << " units\n";
} // ...but never deleted: LEAK
int main() {
for (int month = 1; month <= 3; ++month) {
recordReading(100 + month * 10);
}
return 0;
}Typical result: the program prints the three “Recorded” lines and seems perfectly fine. Nothing visible goes wrong, because a few leaked bytes do not matter in a short program. In a program that runs for days (a bank server, a mobile app), the leaked memory keeps growing until the system becomes slow or the program is killed. Leaks are silent — which is exactly why we need tools to find them.
Fix: add delete reading; at the end of
the function (or, better, do not use new at all here: a
plain int reading = units; is enough).
A dangling pointer is a pointer that still holds an address, but the object at that address has been destroyed. Using it is called use after free.
#include <iostream>
using namespace std;
int main() {
int* balance = new int(5000);
int* alias = balance; // two pointers, one object
delete balance; // object destroyed
balance = nullptr; // balance is safe now...
cout << "Balance: " << *alias << '\n'; // ...but alias dangles!
return 0;
}Typical result: without any tool, this program
usually does not crash. It prints Balance:
followed by some number — sometimes the old 5000, sometimes a
meaningless value (when we compiled it with g++ 13 on Linux it printed a
large garbage number). Either way, no error message appears. That is the
trap: the program runs “successfully” and the bug stays hidden. The
value can change from run to run, from compiler to compiler, and as soon
as the freed memory is reused by a later new. Setting
balance = nullptr did not help alias: nulling
one pointer does not fix its copies. Dangling pointers also come from
returning the address of a local variable (see Pointers
with Functions and Structures; const and Pointers).
Deleting the same memory twice is undefined behaviour. It commonly happens when two pointers share one object and both are deleted.
#include <iostream>
using namespace std;
int main() {
double* fees = new double[3]{1200, 1500, 900};
double* backup = fees; // NOT a copy of the data!
delete[] fees;
delete[] backup; // same memory deleted again
cout << "Done\n";
return 0;
}Typical result: with the GNU C library on Linux the
program is usually stopped at the second delete[] with a
message such as “free(): double free detected in tcache 2” followed by
“Aborted”, so Done is never printed. On other systems it
may crash later, or corrupt the heap silently and cause a strange
failure somewhere else. You cannot rely on any particular behaviour.
#include <iostream>
using namespace std;
int main() {
int* p; // garbage address
*p = 25; // writes 25 to an unknown place
cout << *p << '\n';
return 0;
}Typical result: g++ with -Wall usually
warns “‘p’ is used uninitialized” (the exact wording and whether it
appears depend on the compiler version and optimisation level). At run
time it may crash with a segmentation fault, or it may overwrite some
other variable and cause a strange bug far away — or it may even appear
to work and print 25, which is the worst outcome because the bug stays
hidden until the day it crashes. Fix: always
initialise: int* p = nullptr; and point it to valid memory
before dereferencing.
Writing past the end of a dynamic array (arr[n] when the
valid indexes are 0 to n−1) damages whatever lies next to it in memory.
This was discussed for ordinary arrays in Traversing and
Processing One-Dimensional Arrays, and it is equally dangerous on
the heap.
AddressSanitizer (ASan) is a tool built into g++ and
clang. When you compile with -fsanitize=address, the
compiler adds checks around every memory access. If the program uses
memory wrongly, ASan stops it and prints a report showing what
went wrong and where. Adding -g puts line numbers
into the report.
g++ -std=c++17 -Wall -g -fsanitize=address leak.cpp -o leak
./leak
ASan is available with g++ on Linux and macOS. On Windows it is not
available in most MinGW builds; you can use a Linux machine or an online
compiler that supports it (for example, Compiler Explorer with
-fsanitize=address).
Here is a real ASan report (g++ 13, Linux) for the
use-after-free program (Error 2) above. It is trimmed:
the process number is replaced by PID, some internal lines
are removed, and library paths are shortened. Your addresses will
differ. File line numbers count the // error-demo comment
as line 1.
AddressSanitizer output (trimmed):
==PID==ERROR: AddressSanitizer: heap-use-after-free on address 0x502000000010
READ of size 4 at 0x502000000010 thread T0
#0 ... in main main.cpp:12
0x502000000010 is located 0 bytes inside of 4-byte region
freed by thread T0 here:
#0 ... in operator delete(void*, unsigned long) asan_new_delete.cpp:164
#1 ... in main main.cpp:9
previously allocated by thread T0 here:
#0 ... in operator new(unsigned long) asan_new_delete.cpp:95
#1 ... in main main.cpp:6
SUMMARY: AddressSanitizer: heap-use-after-free main.cpp:12 in main
How to read it:
| Report line | Meaning |
|---|---|
ERROR: AddressSanitizer: heap-use-after-free |
the kind of error |
READ of size 4 |
the program tried to read 4 bytes (one int) |
first #0 ... main.cpp:12 |
the line where the bad access happened |
freed by thread T0 here … main.cpp:9 |
where the memory was deleted |
previously allocated by thread T0 here …
main.cpp:6 |
where it was created with new |
The double delete program (Error 3) produces this report:
AddressSanitizer output (trimmed):
==PID==ERROR: AddressSanitizer: attempting double-free on 0x503000000040 in thread T0:
#0 ... in operator delete[](void*) asan_new_delete.cpp:155
#1 ... in main main.cpp:10
0x503000000040 is located 0 bytes inside of 24-byte region
freed by thread T0 here:
#0 ... in operator delete[](void*) asan_new_delete.cpp:155
#1 ... in main main.cpp:9
previously allocated by thread T0 here:
#0 ... in operator new[](unsigned long) asan_new_delete.cpp:98
#1 ... in main main.cpp:6
SUMMARY: AddressSanitizer: double-free asan_new_delete.cpp:155 in operator delete[](void*)
And for the memory leak program (Error 1), ASan’s built-in LeakSanitizer reports at the end of the run (the three normal output lines appear first):
AddressSanitizer output (trimmed):
Recorded 110 units
Recorded 120 units
Recorded 130 units
==PID==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 12 byte(s) in 3 object(s) allocated from:
#0 ... in operator new(unsigned long) asan_new_delete.cpp:95
#1 ... in recordReading(int) main.cpp:6
#2 ... in main main.cpp:12
SUMMARY: AddressSanitizer: 12 byte(s) leaked in 3 allocation(s).
“12 byte(s) in 3 object(s)” means three ints of 4 bytes
each were never freed, all allocated at line 6. ASan only finds errors
on paths that actually run, so test your program with several
inputs.
The root cause of leaks and double deletes is that a raw pointer does not know whether it owns its memory. C++ solves this with an idea called RAII — Resource Acquisition Is Initialisation. The name is strange, but the idea is simple:
Put the resource (memory, a file, a lock) inside an object. The object gets the resource when it is created, and its destructor automatically releases it when the object goes out of scope.
We have already used RAII without knowing it:
std::string and std::vector allocate heap
memory internally and free it automatically. In the Classes and Objects chapter you will write your own RAII
classes with destructors (Destructors and Classes that
Manage Dynamic Memory).
For a single dynamic object, the standard library provides the
smart pointer std::unique_ptr in the
header <memory>. A unique_ptr is the
only owner of its object; when the unique_ptr is
destroyed, it deletes the object automatically. Create one with
std::make_unique (C++14 and later).
#include <iostream>
#include <memory>
#include <string>
using namespace std;
struct Account {
string holder;
double balance;
};
void showAccount(const Account& acc) {
cout << acc.holder << ": Rs. " << acc.balance << '\n';
}
int main() {
// One object on the heap, owned by a unique_ptr
unique_ptr<Account> acc = make_unique<Account>(Account{"Sita", 5000});
acc->balance += 1500; // use -> and * like a raw pointer
showAccount(*acc);
// An array on the heap, owned by a unique_ptr
int n = 4;
unique_ptr<int[]> fares = make_unique<int[]>(n); // all zero
for (int i = 0; i < n; ++i) {
fares[i] = 20 + 5 * i;
}
for (int i = 0; i < n; ++i) {
cout << fares[i] << ' ';
}
cout << '\n';
// Ownership can be moved, but not copied
unique_ptr<Account> other = move(acc);
if (!acc) {
cout << "acc is now empty; other owns the account\n";
}
showAccount(*other);
return 0;
} // no delete needed: both unique_ptrs free their memory hereSita: Rs. 6500
20 25 30 35
acc is now empty; other owns the account
Sita: Rs. 6500Important properties of unique_ptr:
| Property | Explanation |
|---|---|
| Automatic delete | memory is freed when the unique_ptr goes out of
scope |
| Cannot be copied | auto b = a; is a compile error, so double delete is
impossible |
| Can be moved | auto b = std::move(a); transfers ownership;
a becomes empty |
| Same size and speed as a raw pointer | no extra cost in normal use |
unique_ptr<T[]> |
owns an array and uses delete[] automatically |
There is also std::shared_ptr (several owners, memory
freed when the last owner goes); it is beyond the scope of this
tutorial.
Even with smart pointers, std::vector is still the best
choice for a dynamic array, because it can also grow. Compare the
leak-free versions:
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n = 5;
vector<int> marks(n); // n ints on the heap, all zero
for (int i = 0; i < n; ++i) {
marks[i] = 60 + i * 5;
}
marks.push_back(99); // grows automatically
int total = 0;
for (int m : marks) {
total += m;
}
cout << marks.size() << " marks, total " << total << '\n';
return 0; // vector frees its memory itself
}6 marks, total 449nullptr if nothing
else).std::vector, std::string and
std::unique_ptr over raw
new/delete.new, write the matching
delete at the same time, and use delete[] for
arrays.nullptr after delete,
and remember that copies of it still dangle.-Wall -g -fsanitize=address during
development.nullptr and thinking all copies
are safe.double* backup = fees; shares, it does not copy).unique_ptr (auto b = a;)
— use std::move if ownership should transfer, or pass by
reference.delete on memory owned by a
unique_ptr (for example delete acc.get();) —
this causes a double delete.-fsanitize=address in the final build
instructions for computers that do not support it — use it for
testing.-g -fsanitize=address to detect them.std::unique_ptr (with std::make_unique)
deletes its object automatically and cannot be copied;
std::vector is the preferred dynamic array.delete,
but it prints the right value, so it is fine.” Explain why this is
wrong.int* a = new int(7); int* b = a; delete a; delete b;-g also be used?int* score = new int(80); cout << *score; delete score;
using std::unique_ptr.a and b point to
the same object; delete it only once. 4.
-fsanitize=address; -g adds debug information
so the report shows file names and line numbers. 5.
auto score = std::make_unique<int>(80); cout << *score;
(no delete; needs <memory>). 6. Resource Acquisition
Is Initialisation; std::string and
std::vector.In procedural programming (also called structured programming) a program is a list of steps, organised into functions. Data is kept in variables and structures, and functions receive the data as parameters. C, and everything we have written in Units 1–8, follows this style.
Let us model a simple bank account for a cooperative in Lalitpur in procedural style:
#include <iostream>
#include <string>
using namespace std;
struct Account {
string holder;
double balance;
};
void deposit(Account& acc, double amount) {
if (amount > 0) {
acc.balance += amount;
}
}
bool withdraw(Account& acc, double amount) {
if (amount > 0 && amount <= acc.balance) {
acc.balance -= amount;
return true;
}
return false;
}
int main() {
Account sita = {"Sita Thapa", 2000};
deposit(sita, 500);
if (!withdraw(sita, 5000)) {
cout << "Withdrawal refused: not enough balance\n";
}
cout << sita.holder << " has Rs. " << sita.balance << '\n';
// Nothing stops this line from breaking the rules:
sita.balance = -99999;
cout << sita.holder << " has Rs. " << sita.balance << '\n';
return 0;
}Withdrawal refused: not enough balance
Sita Thapa has Rs. 2500
Sita Thapa has Rs. -99999The functions deposit and withdraw
carefully protect the balance, but any code in the
program can still write sita.balance = -99999;. The data
and the rules about the data are separate. In a program with 50,000
lines written by 10 people, finding who put a negative balance into an
account becomes very hard.
Problems of the procedural approach in large programs:
| Problem | Explanation |
|---|---|
| Data is exposed | any function can change any structure member |
| Data and functions are separate | the rules about the data are scattered across the program |
| Changes spread | changing the structure (e.g., renaming balance) breaks
code everywhere |
| Hard to model the real world | real things have both properties and behaviour |
In object-oriented programming (OOP) we build a program from objects. An object combines:
A class is the blueprint or design from which objects are made. An object is one instance (one real copy) of a class.
| Class (blueprint) | Attributes (data) | Behaviours (functions) | Example objects |
|---|---|---|---|
| Student | name, roll, marks | enrol, calculateGrade, printReport | Sita, Aarav |
| BankAccount | holder, number, balance | deposit, withdraw, getBalance | Ram’s account |
| Bus | route, number plate, seats | board, alight, availableSeats | Sajha bus 12 |
| ElectricityMeter | meter no., units | addReading, calculateBill | meter 00457 |
Here is the same bank account written with a class. You will study every part of the syntax in Defining Classes and Creating Objects; for now just compare the idea.
#include <iostream>
#include <string>
using namespace std;
class Account {
private: // hidden from outside code
string holder;
double balance;
public: // the allowed operations
Account(string name, double opening) {
holder = name;
balance = (opening >= 0) ? opening : 0;
}
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
bool withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return true;
}
return false;
}
void show() const {
cout << holder << " has Rs. " << balance << '\n';
}
};
int main() {
Account sita("Sita Thapa", 2000);
sita.deposit(500);
if (!sita.withdraw(5000)) {
cout << "Withdrawal refused: not enough balance\n";
}
sita.show();
// sita.balance = -99999; // would NOT compile: balance is private
return 0;
}Withdrawal refused: not enough balance
Sita Thapa has Rs. 2500Now the only way to change the balance is through
deposit and withdraw, which enforce the rules.
If someone writes sita.balance = -99999;, the
compiler refuses it.
1. Object. A run-time entity with state and behaviour, e.g., the account of Sita.
2. Class. A user-defined type that describes the data and functions common to all its objects. Defining a class creates no objects; it only describes them.
3. Encapsulation. Wrapping data and the functions that operate on it into a single unit (the class), and hiding the data from direct outside access (data hiding). Outside code uses the public functions only. Analogy: a medicine capsule keeps the ingredients inside; an ATM lets you withdraw money only through its buttons, not by opening the safe.
4. Abstraction. Showing only the essential
features and hiding the details of how they work. A driver uses
the steering, brake and accelerator without knowing how the engine burns
fuel. The user of our Account calls
withdraw(500) without knowing how the balance is
stored.
5. Inheritance. Creating a new class from an
existing class, re-using its data and functions and adding new ones. A
SavingsAccount is a kind of Account
that also earns interest. (See the Inheritance
chapter.)
6. Polymorphism. “Many forms”: the same name or
message behaving differently for different types. area()
means one calculation for a circle and another for a rectangle;
+ adds numbers but joins strings. (See the Polymorphism and Templates chapter.)
Other terms you will meet:
| Term | Meaning |
|---|---|
| Message passing | objects communicate by calling each other’s functions, e.g.,
sita.deposit(500) |
| Data member | a variable inside a class (attribute) |
| Member function / method | a function inside a class (behaviour) |
| Instance | another word for object |
| Interface | the public functions that outside code can use |
| Feature | Procedural (C style) | Object-oriented (C++ classes) |
|---|---|---|
| Main unit | function | class/object |
| Focus | steps (algorithms) | data and its behaviour |
| Data security | data usually exposed | data hidden (private) |
| Re-use | copy functions | inheritance, composition |
| Real-world modelling | weak | natural |
| Suitable for | small programs, scripts | medium to large systems |
| Examples | C, Pascal, early BASIC | C++, Java, C#, Python |
C++ is a multi-paradigm language: it supports both styles, and good C++ programs use both. OOP does not replace functions, loops and arrays — it organises them.
A simple technique: read the problem statement and underline the nouns (possible classes or attributes) and the verbs (possible member functions).
“A library keeps books. Each book has a title, an author and a status. A member can borrow a book and return it. The librarian can list all available books.”
Library, Book,
Member.Book: title, author, status
(available/borrowed).borrow(), giveBack(),
listAvailable().Classes decide what data a program keeps and who can change it, so design choices are also ethical choices. As a future IT professional you are responsible for the people who use your software.
| Principle | What it means in design | Example |
|---|---|---|
| Privacy | store only the data you need; hide it | a Student class should not keep a citizenship number if
it is not required; make such data private |
| Integrity and safety | never allow invalid states | withdraw must refuse to make a balance negative |
| Honesty | the program must do what it claims | do not hide extra fees inside a calculateBill
function |
| Fairness and accessibility | design for all users | clear messages, support for Nepali names and characters |
| Accountability | keep records of important changes | a bank account class might keep a list of transactions |
| Respect for others’ work | credit libraries and code you reuse | follow licences; do not present someone else’s class as your own |
Encapsulation is not only a technical idea: making data
private and allowing changes only through checked functions
is how we protect users from mistakes and misuse. Keep these
principles in mind whenever you design a class: before you finish a
project, write a short note for yourself on who will use it and how your
design protects them.
Account example, which line shows the
main weakness of the procedural approach, and how does the class version
prevent it?Patient class for a hospital system.Bus; object: an instance,
e.g., the Sajha bus with plate Ba 2 Kha 1234. 2. Data exposed to all
functions; data and rules separated; changes spread through the whole
program (also: hard to model real things). 3. Encapsulation: bundling
data and functions and hiding data, e.g., balance private and changed
only by deposit/withdraw. Abstraction: showing
only essentials, e.g., using an ATM without knowing its internals. 4.
Classes: Bus, Passenger; attributes: bus number, free seats; behaviours:
board(), leave() (and perhaps availableSeats()). 5.
sita.balance = -99999; — any code can break the rule; in
the class version balance is private so this line does not
compile. 6. Any two: keep medical data private; store only needed data;
validate inputs (no negative age); log who changes records; show honest,
clear information.class ClassName {
private:
// data members (usually private)
public:
// member functions (usually public)
}; // <-- do not forget this semicolonclass is a keyword; ClassName is an
identifier. By convention class names start with a capital letter
(Student, BankAccount).{ } contains members:
data members (variables) and member functions
(functions).int, it takes no memory until we create objects (variables)
of that type.Access specifiers decide which code may use a member.
| Specifier | Who can access the member |
|---|---|
private |
only member functions (and friends, Objects and Functions, Friends, and a Class Design Case Study) of the same class |
public |
any code that has an object of the class |
protected |
the class and its derived classes (the Inheritance chapter) |
A specifier applies to all members after it until the next specifier. The usual design rule is: data private, functions public. The public functions form the class’s interface.
The only difference between class and
struct in C++ is the default access:
members of a class are private unless stated otherwise;
members of a struct are public. By convention we use
struct for simple groups of data (see Structures and Enumerations) and class when
there are rules to protect.
#include <iostream>
using namespace std;
class Rectangle {
private:
double length;
double width;
public:
void setSize(double len, double wid) { // defined inside the class
if (len > 0 && wid > 0) {
length = len;
width = wid;
} else {
length = 0;
width = 0;
}
}
double area() const {
return length * width;
}
double perimeter() const {
return 2 * (length + width);
}
};
int main() {
Rectangle room; // create an object (an instance)
Rectangle plot; // another, independent object
room.setSize(4.5, 3.0); // call a member function with the dot
plot.setSize(20, 15);
cout << "Room area: " << room.area() << " sq. m\n";
cout << "Room perimeter: " << room.perimeter() << " m\n";
cout << "Plot area: " << plot.area() << " sq. m\n";
return 0;
}Room area: 13.5 sq. m
Room perimeter: 15 m
Plot area: 300 sq. mPoints to notice:
room and plot each have their
own length and width. The
member functions are shared by all objects (there is only one copy of
the code), but each call works on the data of the object used to call
it: room.area() uses room’s data.length and
width directly, without a dot — they refer to the data of
the calling object.const after area() means “this
function does not change the object”. The this Pointer,
Static Members, const Member Functions and Arrays of Objects
explains it fully; for now add it to functions that only read data.Memory picture:
room plot
+-------------+ +-------------+
| length 4.5 | | length 20 |
| width 3.0 | | width 15 |
+-------------+ +-------------+
functions setSize, area, perimeter: one shared copy
#include <iostream>
using namespace std;
class Rectangle {
private:
double length;
double width;
public:
double area() const { return length * width; }
};
int main() {
Rectangle room;
room.length = 4.5; // error: length is private
cout << room.area() << '\n';
return 0;
}Compiler messages (g++):
main.cpp:15:10: error: 'double Rectangle::length' is private within this context
This is encapsulation at work: the compiler, not the programmer’s memory, protects the data.
For long functions, we put only the prototype
(declaration) inside the class and write the body
outside, using the scope resolution
operator :: to say which class the function
belongs to:
ReturnType ClassName::functionName(parameters) { ... }
This keeps the class definition short and readable, like a table of contents.
#include <iostream>
#include <string>
using namespace std;
class Student {
private:
string name;
int roll;
double marks[3]; // marks in three subjects
public:
void setData(string studentName, int rollNo);
void setMarks(double m1, double m2, double m3);
double average() const;
char grade() const;
void printReport() const;
};
void Student::setData(string studentName, int rollNo) {
name = studentName;
roll = rollNo;
}
void Student::setMarks(double m1, double m2, double m3) {
marks[0] = m1;
marks[1] = m2;
marks[2] = m3;
}
double Student::average() const {
return (marks[0] + marks[1] + marks[2]) / 3.0;
}
char Student::grade() const {
double avg = average(); // a member function can call another
if (avg >= 80) return 'A';
if (avg >= 60) return 'B';
if (avg >= 45) return 'C';
return 'F';
}
void Student::printReport() const {
cout << "Roll " << roll << ": " << name
<< ", average " << average() << ", grade " << grade() << '\n';
}
int main() {
Student s1, s2;
s1.setData("Aarav Shrestha", 1);
s1.setMarks(85, 78, 92);
s2.setData("Priya Gurung", 2);
s2.setMarks(55, 62, 48);
s1.printReport();
s2.printReport();
return 0;
}Roll 1: Aarav Shrestha, average 85, grade A
Roll 2: Priya Gurung, average 55, grade CWithout Student:: in front,
double average() const { ... } outside the class would be
an ordinary non-member function, and the compiler would complain that
marks is not declared.
| Defined inside the class | Defined outside with :: |
|---|---|
| body written in the class | only prototype in the class |
automatically treated as inline (see Default Arguments, Inline Functions and Function
Overloading) |
not inline unless the keyword is used |
| good for very short functions (1–3 lines) | good for longer functions |
| class becomes long | class stays short and readable |
In real projects the class definition goes in a header
file (Student.h) and the outside definitions in a
source file (Student.cpp). In this tutorial we keep
everything in one file for simplicity.
Since data members are private, a class often provides small public functions to read them (getters or accessors) and to change them (setters or mutators). A setter is the right place to validate the new value.
#include <iostream>
#include <string>
using namespace std;
class Employee {
private:
string name;
double salary = 0; // default member initialiser (C++11)
public:
void setName(const string& newName) { name = newName; }
string getName() const { return name; }
bool setSalary(double amount) { // setter with validation
if (amount < 17300) { // below the example minimum
return false;
}
salary = amount;
return true;
}
double getSalary() const { return salary; }
};
int main() {
Employee e;
e.setName("Hari Bahadur");
if (!e.setSalary(12000)) {
cout << "Rejected: salary below minimum\n";
}
e.setSalary(25000);
cout << e.getName() << " earns Rs. " << e.getSalary() << '\n';
Employee* ptr = &e; // pointer to an object
cout << "Via pointer: " << ptr->getName() << '\n';
return 0;
}Rejected: salary below minimum
Hari Bahadur earns Rs. 25000
Via pointer: Hari Bahadur(The minimum of Rs. 17,300 is just an example value for this
program.) The last lines show that the arrow operator ->
from Pointers with Functions and Structures; const and
Pointers works for objects too.
Do not write a getter and setter for every member automatically. Ask: should outside code be allowed to change this? An account number, for example, should have a getter but probably no setter.
class A { ... } followed by int main() gives
confusing errors.main
(room.length = 5;). Use a public member function.ClassName:: when defining a member function
outside the class.area(); in
main is an error; write room.area().room.area instead of
room.area().const in one but not the other, or different parameter
types) — the compiler reports “no declaration matches”.};.private members are accessible only inside the class;
public members form the interface. class
defaults to private, struct to public.Rectangle room;. Access
public members with . (or -> through a
pointer).ReturnType ClassName::name(...).What is the difference between class and
struct in C++?
Find the two mistakes in this code:
class Box {
double side;
public:
void setSide(double s) { side = s; }
double volume() const;
}
double volume() const { return side * side * side; }What is the output?
Rectangle a, b; // Rectangle from this lesson
a.setSize(2, 3);
b.setSize(-1, 5);
cout << a.area() << " " << b.area() << " " << a.perimeter();Write a class Circle with a private
radius, a setter that rejects negative values, and a
function area().
Why are data members usually private and member functions usually public?
What does :: mean in
double Student::average() const?
class members are private by
default, struct members public. 2. (a) Missing
; after the class’s }. (b) The outside
definition lacks Box:: (should be
double Box::volume() const); without it the compiler sees a
non-member function, so const is not allowed and
side is not declared. (side being private by
default is not a mistake.) 3. 6 0 10. 4.
class Circle { double radius = 0; public: bool setRadius(double r) { if (r < 0) return false; radius = r; return true; } double area() const { return 3.14159 * radius * radius; } };
5. To hide data (encapsulation) so it can be changed only through
functions that keep it valid; the functions form the public interface.
6. Scope resolution: average is a member of class
Student.In Defining Classes and Creating Objects, after
Rectangle room; the members length and
width contained garbage until we remembered to call
setSize. Forgetting that call gives wrong results. A
constructor is a special member function that runs
automatically when an object is created, so that every
object starts in a valid state.
Rules of constructors:
void.public (otherwise objects cannot be
created from outside).A default constructor is a constructor that can be called with no arguments.
#include <iostream>
#include <string>
using namespace std;
class Student {
private:
string name;
int roll;
double marks;
public:
Student() { // default constructor
name = "Unknown";
roll = 0;
marks = 0.0;
cout << "Default constructor called\n";
}
void show() const {
cout << roll << " " << name << " " << marks << '\n';
}
};
int main() {
Student s1; // constructor runs here
s1.show();
Student group[2]; // runs once for EACH element
group[1].show();
return 0;
}Default constructor called
0 Unknown 0
Default constructor called
Default constructor called
0 Unknown 0Notice there is no function call in main — the
constructor ran automatically for s1 and for both elements
of the array.
Caution: write Student s1; —
not Student s1();. The second form
declares a function named s1 that returns a
Student (a famous C++ trap called the “most vexing parse”).
In modern C++ you can also write Student s1{};.
A parameterised constructor takes arguments, so each object can start with its own values.
Student(string studentName, int rollNo, double studentMarks) {
name = studentName;
roll = rollNo;
marks = studentMarks;
}
// creating objects:
Student a("Sita", 1, 88.5); // traditional
Student b{"Ram", 2, 76.0}; // brace initialisation (C++11)
Student c = Student("Hari", 3, 64); // also allowedThe constructor above first creates name,
roll and marks (with default or garbage
values) and then assigns to them in the body. A better way is
the member initialiser list, written after a colon
before the body. It initialises members directly when they are
created:
Student(string studentName, int rollNo, double studentMarks)
: name(studentName), roll(rollNo), marks(studentMarks) {
// body can be empty, or can check values
}Why prefer initialiser lists?
| Reason | Explanation |
|---|---|
| Efficiency | members are initialised once, not initialised and then assigned |
Required for const members |
a const member cannot be assigned in the body |
| Required for reference members | a reference must be bound when created |
| Required for members of class type without a default constructor | they must receive arguments when created |
Members are initialised in the order they are declared in the
class, not the order written in the list. Keep both in the same
order to avoid confusion; g++ with -Wall warns
(-Wreorder) if they differ.
A class can have several constructors, as long as their parameter lists differ (the overloading rules from Default Arguments, Inline Functions and Function Overloading). The compiler chooses the one that matches the arguments.
#include <iostream>
#include <string>
using namespace std;
class BankAccount {
private:
const int accountNo; // const: must use initialiser list
string holder;
double balance;
public:
BankAccount() // 1. default
: accountNo(0), holder("Not assigned"), balance(0) {}
BankAccount(int number, const string& name) // 2. no deposit
: accountNo(number), holder(name), balance(0) {}
BankAccount(int number, const string& name, double opening)
: accountNo(number), holder(name), balance(opening) { // 3.
if (balance < 0) {
balance = 0; // keep it valid
}
}
void show() const {
cout << accountNo << " | " << holder << " | Rs. " << balance
<< '\n';
}
};
int main() {
BankAccount empty; // calls 1
BankAccount ram(1001, "Ram Karki"); // calls 2
BankAccount sita(1002, "Sita Rai", 5000); // calls 3
BankAccount wrong(1003, "Test", -200); // calls 3, fixed to 0
empty.show();
ram.show();
sita.show();
wrong.show();
return 0;
}0 | Not assigned | Rs. 0
1001 | Ram Karki | Rs. 0
1002 | Sita Rai | Rs. 5000
1003 | Test | Rs. 0Using default arguments (see Default Arguments, Inline Functions and Function Overloading), one constructor can often replace several:
BankAccount(int number = 0, const string& name = "Not assigned",
double opening = 0)
: accountNo(number), holder(name), balance(opening < 0 ? 0 : opening) {}This single constructor can be called with 0, 1, 2 or 3 arguments, so
it also acts as the default constructor. Do not write both this and a
separate BankAccount() — the call
BankAccount a; would then be
ambiguous.
If a class has no constructors at all, the compiler
provides an implicit default constructor (it does nothing for
int/double members, which stay uninitialised
unless they have default member initialisers). But as soon as you write
any constructor, the compiler no longer provides the
default one:
#include <string>
using namespace std;
class Book {
string title;
double price;
public:
Book(const string& t, double p) : title(t), price(p) {}
};
int main() {
Book b1("Muna Madan", 250); // fine
Book b2; // error: no default constructor
return 0;
}Compiler messages (g++):
main.cpp:14:10: error: no matching function for call to 'Book::Book()'
Fix: add a default constructor, give all parameters default
arguments, or write Book() = default; together with default
member initialisers such as double price = 0;.
A copy constructor creates a new object as a copy of an existing object of the same class. Its parameter is a const reference to the class:
ClassName(const ClassName& other);The parameter must be a reference: if it were passed by value, making that copy would itself need the copy constructor — an endless loop, so the compiler rejects it.
The copy constructor is called when:
Student b = a;
or Student b(a);If you do not write one, the compiler generates a copy constructor
that copies each member one by one (a member-wise or
shallow copy). This is fine for int,
double and string members. In Destructors and Classes that Manage Dynamic Memory you
will see why it is dangerous for pointer members.
The next program prints a message from each constructor so that we can see exactly when each runs:
#include <iostream>
#include <string>
using namespace std;
class Point {
private:
int x, y;
public:
Point() : x(0), y(0) {
cout << " default ctor (0,0)\n";
}
Point(int px, int py) : x(px), y(py) {
cout << " param ctor (" << x << "," << y << ")\n";
}
Point(const Point& other) : x(other.x), y(other.y) {
cout << " copy ctor (" << x << "," << y << ")\n";
}
void show() const { cout << "(" << x << "," << y << ")"; }
};
void printByValue(Point p) { // parameter is a copy
cout << " inside printByValue: ";
p.show();
cout << '\n';
}
void printByRef(const Point& p) { // no copy made
cout << " inside printByRef: ";
p.show();
cout << '\n';
}
int main() {
cout << "Point a;\n";
Point a;
cout << "Point b(3, 4);\n";
Point b(3, 4);
cout << "Point c = b;\n";
Point c = b;
cout << " c is ";
c.show();
cout << '\n';
cout << "printByValue(b);\n";
printByValue(b);
cout << "printByRef(b);\n";
printByRef(b);
cout << "a = b; (assignment, not construction)\n";
a = b;
a.show();
cout << '\n';
return 0;
}Point a;
default ctor (0,0)
Point b(3, 4);
param ctor (3,4)
Point c = b;
copy ctor (3,4)
c is (3,4)
printByValue(b);
copy ctor (3,4)
inside printByValue: (3,4)
printByRef(b);
inside printByRef: (3,4)
a = b; (assignment, not construction)
(3,4)Two important lessons:
const reference avoids the copy — prefer it
for objects.a = b; is assignment to an object that
already exists, so no constructor runs; the (compiler-generated)
assignment operator is used instead. Construction happens only
when an object is born.Notice also that the copy constructor can read other.x
although x is private: access control is per
class, not per object, so a member function may access private
members of any object of the same class.
void Student() { ... } is not a constructor.Student s(); to create an object — this
declares a function.const member inside the constructor
body instead of the initialiser list.Point(Point other)) — not allowed.a = b; calls the copy constructor. It calls
the assignment operator.const and reference members. Initialisation follows
declaration order.ClassName(const ClassName&) runs
on copy-initialisation, pass by value and (sometimes) return by value;
the default one copies member by member.State four properties that make a constructor different from an ordinary member function.
What is the output?
class T {
public:
T() { cout << "A"; }
T(int) { cout << "B"; }
T(const T&) { cout << "C"; }
};
// in main:
T x; T y(5); T z = y; T arr[2]; x = y;Rewrite this constructor using a member initialiser list:
Circle(double r) { radius = r; }.
Why must the copy constructor take its parameter by reference?
A class has only the constructor
Book(string t, double p). Why does Book b;
fail, and give two ways to fix it.
Write a class Temperature with a
const string city and double celsius, and a
constructor with default arguments
("Kathmandu", 20.0).
ABCAA
(assignment prints nothing). 3.
Circle(double r) : radius(r) {} 4. Passing by value would
require a copy, which would call the copy constructor again, recursively
without end; the compiler rejects it. 5. Writing any constructor removes
the implicit default constructor; fix by adding
Book() : title(""), price(0) {} or giving default arguments
to all parameters (or Book() = default; with default member
initialisers). 6.
class Temperature { const string city; double celsius; public: Temperature(const string& c = "Kathmandu", double t = 20.0) : city(c), celsius(t) {} };A destructor is a special member function that runs automatically when an object is destroyed. Its job is to clean up: free dynamic memory, close files, release other resources. It is the other half of RAII (see Memory Errors, AddressSanitizer and Smart Pointers): the constructor acquires, the destructor releases.
Rules of destructors:
~ClassName().When is an object destroyed?
| Kind of object | Destroyed when |
|---|---|
| Local object | control leaves the block { } where it was defined |
| Function parameter (by value) | the function returns |
Global or static object |
the program ends |
Object created with new |
delete is used on its pointer |
| Element of an array | the array is destroyed |
Local objects are destroyed in the reverse order of their creation — like plates taken from a stack.
#include <iostream>
#include <string>
using namespace std;
class Guest {
private:
string name;
public:
Guest(const string& n) : name(n) {
cout << " " << name << " arrives\n";
}
~Guest() {
cout << " " << name << " leaves\n";
}
};
int main() {
cout << "Start of main\n";
Guest a("Sita");
Guest b("Ram");
{
cout << "Entering inner block\n";
Guest c("Hari");
cout << "Leaving inner block\n";
} // c destroyed here
Guest* d = new Guest("Priya"); // lives until delete
delete d; // destructor runs now
cout << "End of main\n";
return 0;
} // b, then a destroyedStart of main
Sita arrives
Ram arrives
Entering inner block
Hari arrives
Leaving inner block
Hari leaves
Priya arrives
Priya leaves
End of main
Ram leaves
Sita leavesObserve: Hari leaves at the end of the inner block; Priya leaves
exactly at delete; at the end of main, Ram
(created later) leaves before Sita.
In Dynamic Memory Allocation with new and delete
we allocated arrays with new[] and had to remember
delete[]. A class can do this remembering for us. The
following MarksList class owns a dynamic array whose size
is given at run time:
#include <iostream>
using namespace std;
class MarksList {
private:
int size;
int* marks; // points to an array on the heap
public:
MarksList(int n) : size(n), marks(new int[n]{}) {
cout << "[allocated " << size << " ints]\n";
}
~MarksList() {
delete[] marks; // release what the constructor acquired
cout << "[freed " << size << " ints]\n";
}
void set(int index, int value) {
if (index >= 0 && index < size) { // bounds check
marks[index] = value;
}
}
int get(int index) const {
return (index >= 0 && index < size) ? marks[index] : -1;
}
double average() const {
int total = 0;
for (int i = 0; i < size; ++i) {
total += marks[i];
}
return size > 0 ? static_cast<double>(total) / size : 0;
}
int count() const { return size; }
};
int main() {
int n = 4;
MarksList section(n);
int values[] = {72, 88, 65, 91};
for (int i = 0; i < n; ++i) {
section.set(i, values[i]);
}
section.set(10, 50); // ignored: out of range
cout << "Students: " << section.count() << '\n';
cout << "Average : " << section.average() << '\n';
cout << "get(10) : " << section.get(10) << '\n';
return 0;
} // destructor runs automatically: no leak possible[allocated 4 ints]
Students: 4
Average : 79
get(10) : -1
[freed 4 ints]The user of MarksList never writes new or
delete. The class also checks every index, so out-of-bounds
writes (see Memory Errors, AddressSanitizer and Smart
Pointers) cannot happen through its interface. This is encapsulation
keeping memory use safe.
What happens if we copy a MarksList? The
compiler-generated copy constructor copies each member: it copies
size, and it copies the pointer
marks — not the array it points to. Both objects now point
to the same heap array. This is called a
shallow copy.
original HEAP
+-------------+ +----+----+----+----+
| size 4 | | 72 | 88 | 65 | 91 |
| marks o----+--------->+----+----+----+----+
+-------------+ ^
copy (shallow) |
+-------------+ |
| size 4 | |
| marks o----+-------------+
+-------------+
When the two objects are destroyed, both destructors
call delete[] on the same address — a double delete. And if
one object changes a mark, the other “copy” changes too.
#include <iostream>
using namespace std;
class MarksList {
int size;
int* marks;
public:
MarksList(int n) : size(n), marks(new int[n]{}) {}
~MarksList() { delete[] marks; }
void set(int i, int v) { marks[i] = v; }
int get(int i) const { return marks[i]; }
};
int main() {
MarksList original(3);
original.set(0, 80);
MarksList copy = original; // shallow copy: same array!
copy.set(0, 10);
cout << "original.get(0) = " << original.get(0) << '\n';
return 0;
} // both destructors delete[] the same arrayAddressSanitizer output (trimmed):
original.get(0) = 10
==PID==ERROR: AddressSanitizer: attempting double-free on 0x502000000010 in thread T0:
#0 ... in operator delete[](void*) asan_new_delete.cpp:155
#1 ... in MarksList::~MarksList() main.cpp:10
#2 ... in main main.cpp:22
0x502000000010 is located 0 bytes inside of 12-byte region
freed by thread T0 here:
#0 ... in operator delete[](void*) asan_new_delete.cpp:155
#1 ... in MarksList::~MarksList() main.cpp:10
#2 ... in main main.cpp:22
previously allocated by thread T0 here:
#0 ... in operator new[](unsigned long) asan_new_delete.cpp:98
#1 ... in MarksList::MarksList(int) main.cpp:9
#2 ... in main main.cpp:16
SUMMARY: AddressSanitizer: double-free asan_new_delete.cpp:155 in operator delete[](void*)
The report shows exactly our problem: the program printed
original.get(0) = 10 (the “copy” changed the original), and
then the second destructor (~MarksList, line 10) freed
memory that the first destructor had already freed. Without ASan, the
typical result on Linux is an abort message such as “free(): double free
detected”; on other systems it may be silent heap corruption.
A deep copy allocates a new array for the copy and copies the elements. We must write this ourselves in two places:
operator=
— for assigning one existing object to another
(a = b;).The rule of three says:
If a class needs a user-written destructor, it almost certainly also needs a user-written copy constructor and copy assignment operator.
The reason: a destructor is needed because the class owns a resource, and a class that owns a resource must also decide what copying means. (Operator overloading is studied in the Polymorphism and Templates chapter; here we only need the one pattern shown below.)
#include <iostream>
using namespace std;
class MarksList {
private:
int size;
int* marks;
public:
MarksList(int n) : size(n), marks(new int[n]{}) {}
// 1. Destructor
~MarksList() { delete[] marks; }
// 2. Copy constructor: deep copy
MarksList(const MarksList& other)
: size(other.size), marks(new int[other.size]) {
for (int i = 0; i < size; ++i) {
marks[i] = other.marks[i];
}
cout << "(deep copy made)\n";
}
// 3. Copy assignment: free old array, then deep copy
MarksList& operator=(const MarksList& other) {
if (this != &other) { // guard: a = a;
int* fresh = new int[other.size];
for (int i = 0; i < other.size; ++i) {
fresh[i] = other.marks[i];
}
delete[] marks; // free the old array
marks = fresh;
size = other.size;
cout << "(deep assignment made)\n";
}
return *this;
}
void set(int i, int v) { if (i >= 0 && i < size) marks[i] = v; }
int get(int i) const { return (i >= 0 && i < size) ? marks[i] : -1; }
};
int main() {
MarksList original(3);
original.set(0, 80);
MarksList copy = original; // copy constructor
copy.set(0, 10);
cout << "original.get(0) = " << original.get(0) << '\n';
cout << "copy.get(0) = " << copy.get(0) << '\n';
MarksList other(5);
other = original; // copy assignment
other.set(0, 55);
cout << "original.get(0) = " << original.get(0) << '\n';
cout << "other.get(0) = " << other.get(0) << '\n';
return 0;
} // three objects, three separate arrays, three delete[]s(deep copy made)
original.get(0) = 80
copy.get(0) = 10
(deep assignment made)
original.get(0) = 80
other.get(0) = 55Now each object owns its own array, changes are independent, and each
destructor frees a different block. This program runs cleanly under
-fsanitize=address as well. (this in the
assignment operator is a pointer to the object being assigned to; The this Pointer, Static Members, const Member Functions and
Arrays of Objects explains it. The check
this != &other protects against self-assignment
a = a;, which would otherwise delete the array before
copying from it.)
Notice the safe order inside operator=: allocate and
copy into fresh first, then delete the old array.
If new were to fail, the object would still be
unchanged.
Writing the three functions correctly is tricky. The modern C++
advice is the rule of zero: if possible, do not own
raw resources directly. Use members that manage themselves —
std::vector, std::string,
std::unique_ptr. Then the compiler-generated copy,
assignment and destructor are automatically correct, and you write none
of the three.
class MarksList {
std::vector<int> marks; // vector copies deeply by itself
public:
MarksList(int n) : marks(n) {}
// no destructor, no copy constructor, no operator= needed
};We still learn the rule of three because (1) it shows what those library classes do internally, (2) you will meet older code that uses raw pointers, and (3) it is a common interview topic. (C++11 extended it to the “rule of five” by adding move operations; that is beyond the scope of this tutorial.)
void ~A() or ~A(int) are errors.obj.~A();) for a
normal object — it will run again automatically, causing double
cleanup.delete[]
in the destructor — a leak in every object.delete instead of delete[] for an
array member.other (which might be the same
object).return *this; from
operator=.~ClassName() runs automatically when an
object is destroyed; local objects are destroyed in reverse order of
creation.new), destructor releases
(delete) — RAII makes leaks impossible for users of the
class.vector, string and
unique_ptr members so that no special functions are
needed.Write the destructor prototype for a class Library.
Why can a class have only one destructor?
What is the output?
class X {
int id;
public:
X(int i) : id(i) { cout << "C" << id << " "; }
~X() { cout << "D" << id << " "; }
};
// in main:
X a(1);
{ X b(2); X c(3); }
X d(4);Explain the difference between a shallow copy and a deep copy, with a diagram.
A class has double* readings allocated with
new double[n]. Which three member functions must you write,
according to the rule of three?
Why does the copy assignment operator check
if (this != &other)?
How would using std::vector<double> instead of
double* change your answer to question 4?
~Library(); — destructors take no parameters, so they
cannot be overloaded. 2. C1 C2 C3 D3 D2 C4 D4 D1. 3.
Shallow copy copies the pointer so both objects share one heap block;
deep copy allocates a new block and copies the elements so each object
owns its own block (diagram as in the notes). 4. Destructor
(delete[] readings), copy constructor (deep copy), copy
assignment operator (free old block, deep copy, return
*this). 5. To handle a = a; safely — without
the check the object could free its own array and then copy from freed
memory. 6. None of the three is needed (rule of zero); the vector copies
deeply and frees itself.You may have wondered: when room.area() and
plot.area() run the same code, how does the
function know whose length and width to use?
The answer: every non-static member function receives a hidden extra
parameter called this, which is a
pointer to the object that called the function.
room.area(), this holds
&room.plot.area(), this holds
&plot.length really means
this->length.Three common uses of this:
this->name = name; (the left
side is the member, the right side is the parameter).return *this;), as in operator= (see Destructors and Classes that Manage Dynamic
Memory).*this, calls can be joined:
acc.deposit(500).withdraw(200);.#include <iostream>
#include <string>
using namespace std;
class Wallet {
private:
string owner;
double balance;
public:
Wallet(const string& owner, double balance) {
this->owner = owner; // member = parameter
this->balance = balance;
}
Wallet& add(double amount) {
balance += amount;
return *this; // return the object itself
}
Wallet& spend(double amount) {
if (amount <= balance) {
balance -= amount;
}
return *this;
}
void show() const {
cout << owner << ": Rs. " << balance << '\n';
}
const Wallet* address() const { return this; }
};
int main() {
Wallet aarav("Aarav", 1000);
Wallet sita("Sita", 800);
aarav.add(500).spend(300).add(50); // method chaining
aarav.show();
sita.spend(100).show();
cout << boolalpha;
cout << "this inside aarav == &aarav? "
<< (aarav.address() == &aarav) << '\n';
cout << "this inside sita == &sita? "
<< (sita.address() == &sita) << '\n';
return 0;
}Aarav: Rs. 1250
Sita: Rs. 700
this inside aarav == &aarav? true
this inside sita == &sita? true(A member initialiser list,
: owner(owner), balance(balance), would also work here,
because in owner(owner) the outer name must be a member and
the inner name is the parameter. Using this-> in the
body is shown to teach the idea.)
Inside a const member function, this is a
pointer to a const object, so the function cannot change data
members through it — which is exactly what const promises.
this does not exist in static member functions (below).
Normally each object has its own copy of every data
member. Sometimes we need one value shared by all objects of the
class — for example, the total number of students admitted, or
the interest rate for all savings accounts. Such a member is declared
static.
| Feature | Ordinary (non-static) member | Static member |
|---|---|---|
| Copies | one per object | one per class, shared |
| Exists | when an object exists | even when no object exists |
| Accessed as | obj.member |
ClassName::member (or obj.member) |
| Memory | inside each object | separate, like a global variable |
A static data member is declared inside the class and must
be defined once outside it (before C++17), or it can be
declared inline static inside the class (C++17):
class Student {
static int count; // declaration
inline static int nextRoll = 1; // C++17: declaration + definition
};
int Student::count = 0; // definition (outside, once)A static member function belongs to the class, not
to an object. It is called with ClassName::function(), has
no this pointer, and can therefore access
only static members (not ordinary data members).
#include <iostream>
#include <string>
using namespace std;
class Student {
private:
string name;
int roll;
static int count; // how many Student objects exist
inline static int nextRoll = 1; // next roll number to give
public:
Student(const string& studentName)
: name(studentName), roll(nextRoll) {
++nextRoll;
++count;
}
~Student() {
--count;
}
void show() const {
cout << "Roll " << roll << ": " << name << '\n';
}
static int getCount() { // static member function
return count; // may use static members only
}
};
int Student::count = 0; // define the static member
int main() {
cout << "Students now: " << Student::getCount() << '\n';
Student a("Sita");
Student b("Ram");
{
Student c("Hari");
c.show();
cout << "Students now: " << Student::getCount() << '\n';
} // c destroyed, count decreases
Student d("Priya");
a.show();
b.show();
d.show();
cout << "Students now: " << Student::getCount() << '\n';
return 0;
}Students now: 0
Roll 3: Hari
Students now: 3
Roll 1: Sita
Roll 2: Ram
Roll 4: Priya
Students now: 3Note that Priya received roll number 4, not 3: nextRoll
only increases, while count goes down when an object is
destroyed. Student::getCount() works even before any object
exists.
A member function marked const (after the parameter
list) promises not to modify the object. The compiler
checks the promise: assigning to a data member inside a const function
is an error.
double getBalance() const { return balance; } // reading: OK
void reset() const { balance = 0; } // ERROR: modifiesWhy does this matter? Because of const objects and
const references. When an object is const
(or we have it through a const& parameter, which is how
we usually pass objects), only const member functions may
be called on it:
#include <iostream>
using namespace std;
class Counter {
int value = 0;
public:
void increment() { ++value; }
int get() { return value; } // forgot const!
};
void report(const Counter& c) {
cout << c.get() << '\n'; // error: get() is not const
}
int main() {
Counter c;
c.increment();
report(c);
return 0;
}Compiler messages (g++):
main.cpp:13:18: error: passing 'const Counter' as 'this' argument discards qualifiers [-fpermissive]
The error message says that passing const Counter as
this “discards qualifiers”: get() did not
promise to leave the object unchanged, so the compiler will not call it
on a const object. The fix is to write
int get() const { return value; }.
Rule: mark every member function that does not
change the object as const. This is called
const-correctness. Getters,
print/show functions and calculation functions
(like area(), average()) should all be
const.
An array can hold objects just like it holds ints. Each
element is a complete object with its own data members.
Student list[30]; creates 30 objects using the
default constructor — so the class must have one.Item stock[3] = {Item("Pen", 20), Item("Copy", 60), Item("Bag", 1200)};
or, more briefly, {{"Pen", 20}, ...}.list[i].show().#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
class Item {
private:
string name;
double price;
int quantity;
public:
Item() : name("-"), price(0), quantity(0) {}
Item(const string& n, double p, int q) : name(n), price(p), quantity(q) {}
double value() const { return price * quantity; }
const string& getName() const { return name; }
void show() const {
cout << left << setw(12) << name << right << setw(8) << price
<< setw(6) << quantity << setw(10) << value() << '\n';
}
};
int main() {
const int N = 4;
Item stock[N] = {
{"Pen", 20, 150},
{"Copy", 60, 80},
{"School bag", 1200, 5},
{"Geometry", 250, 12}
};
cout << fixed << setprecision(0);
cout << left << setw(12) << "Item" << right << setw(8) << "Price"
<< setw(6) << "Qty" << setw(10) << "Value" << '\n';
double total = 0;
int best = 0;
for (int i = 0; i < N; ++i) {
stock[i].show();
total += stock[i].value();
if (stock[i].value() > stock[best].value()) {
best = i;
}
}
cout << "Total stock value: Rs. " << total << '\n';
cout << "Highest value item: " << stock[best].getName() << '\n';
Item emptyShelf[2]; // uses the default constructor
emptyShelf[0].show();
return 0;
}Item Price Qty Value
Pen 20 150 3000
Copy 60 80 4800
School bag 1200 5 6000
Geometry 250 12 3000
Total stock value: Rs. 16800
Highest value item: School bag
- 0 0 0Arrays of objects can also be created dynamically
(Item* list = new Item[n]; … delete[] list;) —
the default constructor runs for each element and the destructor runs
for each element at delete[]. In modern code prefer
std::vector<Item>.
this.name — this is a pointer, so
use this->name.Student::count”.this in a static
member function.const on getters, then being unable to call
them through const& parameters.Book shelf[10]; fails if only
Book(string, double) exists).const to the prototype but not to the outside
definition (or the other way round) — they must match.this is a pointer to the object that called a member
function; this->member resolves name clashes;
return *this; enables chaining.inline static in C++17).ClassName::,
have no this, and can use only static members.const member functions promise not to change the
object; only they can be called on const objects and
through const&.What is this? What is its value inside
plot.area()?
What is the output?
class A {
public:
static int n;
A() { ++n; }
};
int A::n = 0;
// in main:
A x, y;
{ A z; }
A arr[3];
cout << A::n;Why can a static member function not access ordinary data members?
Which of these should be const member functions:
getName(), setName(string),
printReport(), deposit(double),
average()?
Complete this constructor using this:
Book(string title, double price) { ... }.
Write a statement that creates an array of three
Item objects (from this lesson) for “Pencil” (Rs. 10, 100
pcs), “Eraser” (Rs. 5, 60 pcs) and “Ruler” (Rs. 25, 30 pcs).
plot.area() it
equals &plot. 2. 6 (the constructor runs
for x, y, z and 3 array elements; there is no destructor decreasing n).
3. It has no this pointer, so there is no particular object
whose members it could use. 4. getName(),
printReport(), average(). 5.
Book(string title, double price) { this->title = title; this->price = price; }
6.
Item tools[3] = {{"Pencil", 10, 100}, {"Eraser", 5, 60}, {"Ruler", 25, 30}};Objects can be passed to functions in the same three ways as other variables (see Passing Data by Value, Passing Data by Reference and Pointers with Functions and Structures; const and Pointers):
| Method | Syntax | Copy made? | Can change the caller’s object? | Use when |
|---|---|---|---|---|
| By value | void f(Money m) |
Yes (copy constructor) | No | small objects, or you need a local copy |
| By reference | void f(Money& m) |
No | Yes | the function must modify the object |
| By const reference | void f(const Money& m) |
No | No | reading only — the usual choice |
For objects containing strings, vectors or arrays, copying can be
expensive, so const& is the normal way to pass an
object that is only read.
A function (member or non-member) can return an object by value. The returned object is usually created directly in the caller’s variable (copy elision), so returning objects is efficient in modern C++.
The following Money class stores rupees and paisa (100
paisa = 1 rupee). Notice add, a member
function that takes another Money object and returns a
new Money object, and larger,
a non-member function that receives two objects and
returns one of them.
#include <iostream>
#include <iomanip>
using namespace std;
class Money {
private:
long paisa; // store everything in paisa
public:
Money(long rupees = 0, long extraPaisa = 0)
: paisa(rupees * 100 + extraPaisa) {}
Money add(const Money& other) const { // object as argument
Money result;
result.paisa = paisa + other.paisa; // private access OK
return result; // object returned
}
void addTo(Money& target) const { // modifies argument
target.paisa += paisa;
}
long inPaisa() const { return paisa; }
void show() const {
cout << "Rs. " << paisa / 100 << "." << setw(2) << setfill('0')
<< paisa % 100 << setfill(' ');
}
};
Money larger(const Money& a, const Money& b) { // non-member
return (a.inPaisa() >= b.inPaisa()) ? a : b;
}
int main() {
Money busFare(25, 50); // Rs. 25.50
Money tea(40); // Rs. 40.00
Money total = busFare.add(tea);
cout << "Bus fare + tea = ";
total.show();
cout << '\n';
Money wallet(100);
busFare.addTo(wallet); // wallet is changed
cout << "Wallet after addTo: ";
wallet.show();
cout << '\n';
cout << "Larger of bus fare and tea: ";
larger(busFare, tea).show();
cout << '\n';
return 0;
}Bus fare + tea = Rs. 65.50
Wallet after addTo: Rs. 125.50
Larger of bus fare and tea: Rs. 40.00Inside add, the code reads other.paisa and
writes result.paisa, although paisa is
private. As noted in Constructors, access control
works per class: a member function of
Money may use the private members of any
Money object. The non-member function larger,
however, must use the public inPaisa().
Sometimes a function that is not a member needs access to private members — for example, a function that works equally on two objects of the class, or on objects of two different classes. A class can grant this access by declaring the function a friend inside the class:
class Account {
double balance;
friend void audit(const Account& acc); // audit may read balance
};Facts about friend functions:
friend inside
the class; the definition outside is written like an ordinary function
(no friend, no ClassName::).this, and it is called like a normal function,
audit(acc), not acc.audit().In the next example, one function is a friend of two classes, so it can compare private data from both. This is a classic case where a friend is useful.
#include <iostream>
#include <string>
using namespace std;
class Savings; // forward declaration
class Current {
private:
string holder;
double balance;
public:
Current(const string& h, double b) : holder(h), balance(b) {}
friend void compareBalances(const Current& c, const Savings& s);
};
class Savings {
private:
string holder;
double balance;
public:
Savings(const string& h, double b) : holder(h), balance(b) {}
friend void compareBalances(const Current& c, const Savings& s);
};
// Not a member of either class, but a friend of both
void compareBalances(const Current& c, const Savings& s) {
cout << c.holder << " (current): Rs. " << c.balance << '\n';
cout << s.holder << " (savings): Rs. " << s.balance << '\n';
if (c.balance > s.balance) {
cout << "Current account has more money\n";
} else if (s.balance > c.balance) {
cout << "Savings account has more money\n";
} else {
cout << "Both balances are equal\n";
}
}
int main() {
Current shop("Gita Traders", 85000);
Savings home("Gita Shrestha", 120000);
compareBalances(shop, home);
return 0;
}Gita Traders (current): Rs. 85000
Gita Shrestha (savings): Rs. 120000
Savings account has more moneyThe forward declaration class Savings;
tells the compiler that Savings is a class name, so it can
be used in the friend declaration inside Current before
Savings is fully defined.
A whole class can be made a friend:
friend class Auditor; inside Account lets
every member function of Auditor access
the private members of Account.
class Account {
double balance = 0;
friend class Auditor; // Auditor's members may use balance
};
class Auditor {
public:
bool isNegative(const Account& a) const { return a.balance < 0; }
};Friendship is:
| Property | Meaning |
|---|---|
| Not mutual | if A is a friend of B, B is not automatically a friend of A |
| Not inherited | a class derived from a friend is not a friend (see the Inheritance chapter) |
| Not transitive | a friend of a friend is not a friend |
Use friends sparingly. Every friend weakens
encapsulation, because more code can touch the private data. Prefer
public member functions; use a friend only when a non-member really
needs private access (the most common real use is overloading
<< and >> for output and input,
see More Operator Overloading — Friends, Stream and
Comparison Operators).
Let us bring the Pointers and Dynamic Memory and Classes and Objects chapters together by designing a class properly. Requirements from a cooperative in Bhaktapur:
Design (nouns → data, verbs → functions):
| Member | Access | Reason |
|---|---|---|
accountNo, holder,
balance |
private | protect the data; balance changes only through checked functions |
history (vector<string>) |
private | transaction log; vector handles memory (rule of
zero) |
static int nextNumber,
static int opened |
private | shared by all accounts |
static const double MIN_BALANCE |
public | a rule everyone may read |
| constructor | public | enforces the opening-deposit rule |
deposit, withdraw |
public | return bool so the caller knows if it worked |
transferTo(BankAccount&, double) |
public | changes two objects: takes the other by reference |
getBalance, printStatement |
public, const |
read-only |
static totalOpened() |
public | class-level information |
Design decisions with an ethical side: the balance can never be set directly, every change is logged (accountability), and failed operations are reported honestly instead of being silently ignored.
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
using namespace std;
class BankAccount {
private:
int accountNo;
string holder;
double balance;
vector<string> history;
inline static int nextNumber = 1001;
inline static int opened = 0;
void record(const string& what, double amount) {
history.push_back(what + " " + to_string(static_cast<int>(amount)));
}
public:
static constexpr double MIN_BALANCE = 500;
BankAccount(const string& name, double opening)
: accountNo(nextNumber), holder(name), balance(0) {
++nextNumber;
++opened;
if (opening < MIN_BALANCE) {
cout << "Note: opening deposit raised to Rs. 500\n";
opening = MIN_BALANCE;
}
balance = opening;
record("OPEN", opening);
}
bool deposit(double amount) {
if (amount <= 0) {
return false;
}
balance += amount;
record("DEP ", amount);
return true;
}
bool withdraw(double amount) {
if (amount <= 0 || balance - amount < MIN_BALANCE) {
return false;
}
balance -= amount;
record("WDR ", amount);
return true;
}
bool transferTo(BankAccount& other, double amount) {
if (this == &other || !withdraw(amount)) {
return false;
}
other.deposit(amount);
return true;
}
double getBalance() const { return balance; }
void printStatement() const {
cout << "Account " << accountNo << " - " << holder << '\n';
for (const string& line : history) {
cout << " " << line << '\n';
}
cout << " Balance: Rs. " << fixed << setprecision(2) << balance
<< '\n';
}
static int totalOpened() { return opened; }
};
int main() {
BankAccount sita("Sita Thapa", 5000);
BankAccount ram("Ram Karki", 200); // below minimum
sita.deposit(2500);
if (!sita.withdraw(7200)) {
cout << "Withdrawal of 7200 refused (minimum balance rule)\n";
}
sita.withdraw(1000);
if (sita.transferTo(ram, 1500)) {
cout << "Transferred Rs. 1500 from Sita to Ram\n";
}
if (!ram.deposit(-50)) {
cout << "Deposit of -50 refused\n";
}
sita.printStatement();
ram.printStatement();
cout << "Accounts opened: " << BankAccount::totalOpened() << '\n';
return 0;
}Note: opening deposit raised to Rs. 500
Withdrawal of 7200 refused (minimum balance rule)
Transferred Rs. 1500 from Sita to Ram
Deposit of -50 refused
Account 1001 - Sita Thapa
OPEN 5000
DEP 2500
WDR 1000
WDR 1500
Balance: Rs. 5000.00
Account 1002 - Ram Karki
OPEN 500
DEP 1500
Balance: Rs. 2000.00
Accounts opened: 2Check the numbers: Sita starts with 5000, deposits 2500 (7500); withdrawing 7200 would leave 300 < 500, so it is refused; she withdraws 1000 (6500) and transfers 1500 (5000). Ram’s opening deposit of 200 was raised to 500, and he receives 1500 (2000).
What this case study uses from this chapter:
| Concept | Where |
|---|---|
| Encapsulation and validation | private balance, checked
deposit/withdraw |
| Constructor with rule enforcement | minimum opening deposit |
| Private helper function | record is private — an internal detail |
| Static members | nextNumber, opened,
totalOpened(), MIN_BALANCE |
this pointer |
this == &other blocks transfer to the same
account |
| Object passed by reference | transferTo(BankAccount& other, ...) |
const member functions |
getBalance, printStatement |
| Rule of zero | vector<string> manages the history memory |
const&.Money& add(...) { Money r; ...; return r; }) —
dangling reference. Return by value.ClassName:: or friend in the
definition of a friend function outside the class.acc.audit() instead of
audit(acc).const& for reading, by
& for modifying, by value only when a copy is
wanted.friend inside
the class and gets access to its private members; friendship is not
mutual, inherited or transitive.const getters, static members for
class-wide data, library types for memory.Give the three ways of passing an object to a function and say when each is appropriate.
What is the output?
// using the Money class from this lesson
Money a(10, 75), b(5, 50);
Money c = a.add(b);
c.show();How is a friend function different from a member function? Give three differences.
Why does the Current/Savings example
need the line class Savings;?
In the BankAccount case study, why is record private
while deposit is public?
Add a member function
bool changeHolder(const string& newName) to
BankAccount that refuses an empty name and records the
change in the history. Write its definition.
Rs. 16.25. 3.
A friend is not in the class’s scope/membership: it has no
this; it is called as f(obj) not
obj.f(); its definition has no ClassName::; it
is not inherited. 4. compareBalances mentions
Savings inside Current before
Savings is defined; the forward declaration tells the
compiler Savings is a class. 5. record is an
internal detail that outside code must not call (it could fake history
entries); deposit is part of the interface. 6.
bool changeHolder(const string& newName) { if (newName.empty()) return false; history.push_back("NAME " + holder + " -> " + newName); holder = newName; return true; }In everyday life, children inherit features from their parents: eye colour, surname, sometimes even a talent for music. The child also has its own new features. In C++, inheritance is the mechanism by which a new class is created from an existing class. The new class receives (inherits) the data members and member functions of the existing class, and it can add new members of its own.
| Term | Meaning | Other names |
|---|---|---|
| Base class | The existing class that is inherited from | Parent class, super class |
| Derived class | The new class created from the base class | Child class, sub class |
| Inheritance | The process of deriving a new class from a base class | Derivation |
Why do we want this?
PhDStudent) without touching the old,
working code.class Base {
// members of the base class
};
class Derived : access-mode Base {
// new members of the derived class
};The colon : means “is derived from”. The access
mode is one of public, protected or
private. For almost all normal programs you will use
public inheritance. If you write nothing, a
class uses private inheritance by default (a
struct uses public), so always write the
access mode clearly.
A Person has a name and an age. A Student
is a person who also has a roll number and a program. Instead of writing
name and age again, Student inherits them.
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
string name;
int age = 0;
void showPerson() const {
cout << "Name: " << name << ", Age: " << age << endl;
}
};
// Student inherits everything public from Person
class Student : public Person {
public:
int rollNo = 0;
string program;
void showStudent() const {
showPerson(); // inherited function, called directly
cout << "Roll: " << rollNo << ", Program: " << program << endl;
}
};
int main() {
Student s;
s.name = "Sita Sharma"; // inherited data member
s.age = 19; // inherited data member
s.rollNo = 12; // Student's own member
s.program = "BIT";
s.showStudent();
s.showPerson(); // inherited function called from main
return 0;
}Name: Sita Sharma, Age: 19
Roll: 12, Program: BIT
Name: Sita Sharma, Age: 19Notice that the Student class does not contain the words
name or age, but a Student object
still has them. A Student object is made of two parts: a
Person part (called the base sub-object) and the
new Student part.
Student object s
+---------------------------+
| Person part |
| name = "Sita Sharma" |
| age = 19 |
+---------------------------+
| Student part |
| rollNo = 12 |
| program = "BIT" |
+---------------------------+
In the Classes and Objects chapter we kept data
members private to protect them (encapsulation). But there
is a problem: private members of a base class are not accessible
inside the derived class. They still exist inside the derived
object, but the derived class’s functions cannot use them by name.
C++ provides a third access specifier, protected, for
exactly this situation.
| Specifier | Accessible in the same class | Accessible in derived class | Accessible outside (e.g., in main) |
|---|---|---|---|
private |
Yes | No | No |
protected |
Yes | Yes | No |
public |
Yes | Yes | Yes |
Think of a family house. Public members are the front garden: anyone walking by can see it. Protected members are the family room: family members (derived classes) can enter, strangers cannot. Private members are the parents’ locked cupboard: even the children cannot open it directly; they must ask the parents (use a public or protected function).
#include <iostream>
#include <string>
using namespace std;
class Account {
private:
string pin = "1234"; // only Account can use this
protected:
double balance = 0.0; // Account and its children can use this
public:
string holder;
bool checkPin(const string& p) const { return p == pin; }
};
class SavingsAccount : public Account {
public:
double rate = 0.05; // 5% interest per year
void addInterest() {
balance = balance + balance * rate; // OK: protected
// pin = "0000"; // ERROR: pin is private in Account
}
void deposit(double amount) { balance += amount; }
double getBalance() const { return balance; }
};
int main() {
SavingsAccount acc;
acc.holder = "Ram Thapa"; // OK: public
acc.deposit(10000);
acc.addInterest();
// acc.balance = 999999; // ERROR: protected, not visible in main
cout << acc.holder << " has Rs. " << acc.getBalance() << endl;
cout << "PIN correct? " << (acc.checkPin("1234") ? "Yes" : "No")
<< endl;
return 0;
}Ram Thapa has Rs. 10500
PIN correct? YesIf you remove the // from the line
acc.balance = 999999;, the compiler reports an error
similar to this:
error: 'double Account::balance' is protected within this context
The access mode written after the colon decides how the inherited members appear in the derived class. The rule is simple: the inherited member gets the more restrictive of (its own access) and (the access mode). Private members of the base are never directly accessible in the derived class, whatever the mode.
| Member in base class | public inheritance | protected inheritance | private inheritance |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | not accessible | not accessible | not accessible |
What does this mean in practice?
main cannot use them through the derived
object, but further derived classes can.#include <iostream>
using namespace std;
class Base {
public:
int pub = 1;
protected:
int prot = 2;
private:
int priv = 3;
public:
int getPriv() const { return priv; }
};
class PubChild : public Base {
public:
void show() const {
cout << "PubChild sees pub=" << pub << " prot=" << prot
<< " priv(via function)=" << getPriv() << endl;
}
};
class PrivChild : private Base {
public:
void show() const {
// pub and prot are now PRIVATE members of PrivChild
cout << "PrivChild sees pub=" << pub << " prot=" << prot << endl;
}
};
int main() {
PubChild a;
a.show();
cout << "main uses a.pub = " << a.pub << endl; // OK
PrivChild b;
b.show();
// cout << b.pub; // ERROR: pub is private in PrivChild
return 0;
}PubChild sees pub=1 prot=2 priv(via function)=3
main uses a.pub = 1
PrivChild sees pub=1 prot=2Before using inheritance, ask the is-a test: “Is every X a Y?”
Student can
inherit from Person.When one object contains another, we use composition (also called containment or aggregation): we put an object of one class as a data member of another class. This is the has-a relationship.
| Relationship | Question | C++ tool | Example |
|---|---|---|---|
| is-a | Is every X a Y? | Inheritance | Student is a Person |
| has-a | Does X contain a Y? | Composition (member object) | Car has an Engine |
#include <iostream>
#include <string>
using namespace std;
class Engine {
public:
int cc = 0;
void start() const { cout << cc << "cc engine started" << endl; }
};
class Vehicle {
public:
string number;
void showNumber() const { cout << "Number: " << number << endl; }
};
// Car IS-A Vehicle (inheritance) and HAS-A Engine (composition)
class Car : public Vehicle {
public:
Engine engine; // member object: has-a
void drive() const {
showNumber();
engine.start();
cout << "Driving on Ring Road..." << endl;
}
};
int main() {
Car myCar;
myCar.number = "Ba 2 Pa 4567";
myCar.engine.cc = 1200;
myCar.drive();
return 0;
}Number: Ba 2 Pa 4567
1200cc engine started
Driving on Ring Road...A good rule used by professional designers is: prefer composition
when in doubt; use inheritance only when the is-a test is clearly
true. Wrong inheritance (for example, making Car
inherit from Engine just to reuse start())
produces confusing code that other programmers cannot trust. Choosing a
clear, honest design is part of professional ethics: your classes should
say what they really are, so that teammates can maintain them
safely.
class Student : Person
gives private inheritance, so s.name fails
in main. Write
class Student : public Person.protected, or use a public
getter.class Student : public Person; with a semicolon
before the body. The semicolon goes only after the closing brace
};.class Car : public Engine). Use a member object
instead.class Derived : public Base { ... };protected members are visible in derived classes but
not outside.protected member of a base class, inherited
with private inheritance, becomes ______ in the derived
class.#include <iostream>
using namespace std;
class A {
public:
int x = 5;
};
class B : public A {
public:
int y = 10;
};
int main() {
B obj;
cout << obj.x + obj.y << endl;
return 0;
}class Teacher : Person { }; followed by
Teacher t; t.name = "Hari"; in main.Rectangle with protected
length and breadth, and a derived class
Box that adds height and a function
volume().15. 4. Default inheritance for class is
private, so name becomes private in Teacher;
write class Teacher : public Person. 5. has-a; composition
(e.g., a Library class with an array or vector of
Book objects). 6.
class Rectangle { protected: double length=0, breadth=0; };
and
class Box : public Rectangle { public: double height=0; double volume() const { return length*breadth*height; } };
(setters or a constructor needed to assign values from
main).The way classes are connected in an inheritance design is called its type (or form) of inheritance. C++ textbooks usually list five types. In this lesson you will study the first three; Types of Inheritance II — Multiple and Hybrid; the Diamond Problem covers the other two.
| Type | Structure | Example |
|---|---|---|
| Single | One base, one derived | Employee → Manager |
| Multilevel | A chain: derived class becomes a base for another | Person → Employee → Manager |
| Hierarchical | One base, many derived classes | Vehicle → Bus, Bike, Taxi |
| Multiple | One derived class, two or more bases | Student + Athlete → SportsStudent |
| Hybrid | A mix of two or more of the above | Diamond shape |
Single Multilevel Hierarchical Multiple
A A A A B
| | / | \ \ /
v v v v v v v
B B B C D C
|
v
C
In these diagrams an arrow goes from the base class down to the derived class. (Some books draw the arrow pointing up from derived to base, as in UML; both are common. Tell your reader which one you use.)
Single inheritance means one derived class has exactly one base class. This is the case you saw in Introduction to Inheritance. Here is another example from an office: every employee has a name and a basic salary; a manager also gets an allowance.
#include <iostream>
#include <string>
using namespace std;
class Employee {
protected:
string name;
double basic = 0.0;
public:
void setEmployee(const string& n, double b) {
name = n;
basic = b;
}
void showEmployee() const {
cout << "Name : " << name << endl;
cout << "Basic salary: Rs. " << basic << endl;
}
};
class Manager : public Employee { // single inheritance
private:
double allowance = 0.0;
public:
void setAllowance(double a) { allowance = a; }
double totalSalary() const { return basic + allowance; }
void showManager() const {
showEmployee();
cout << "Allowance : Rs. " << allowance << endl;
cout << "Total : Rs. " << totalSalary() << endl;
}
};
int main() {
Manager m;
m.setEmployee("Priya Shrestha", 60000);
m.setAllowance(15000);
m.showManager();
return 0;
}Name : Priya Shrestha
Basic salary: Rs. 60000
Allowance : Rs. 15000
Total : Rs. 75000Observe how totalSalary() uses basic
directly. This works because basic is
protected in Employee, and
Manager inherits publicly.
In multilevel inheritance, a derived class itself becomes the base class of another class. It forms a chain, just like grandparent → parent → child. The last class in the chain receives the members of all the classes above it.
Person (name, address)
|
v
Employee (+ employeeId, basic)
|
v
Manager (+ department, allowance)
A Manager object therefore contains three parts: a
Person part, an Employee part and its own
Manager part.
#include <iostream>
#include <string>
using namespace std;
class Person { // level 1
protected:
string name;
string address;
public:
void setPerson(const string& n, const string& a) {
name = n;
address = a;
}
};
class Employee : public Person { // level 2
protected:
int employeeId = 0;
double basic = 0.0;
public:
void setEmployee(int id, double b) {
employeeId = id;
basic = b;
}
};
class Manager : public Employee { // level 3
private:
string department;
double allowance = 0.0;
public:
void setManager(const string& d, double a) {
department = d;
allowance = a;
}
void show() const {
// name/address come from Person, id/basic from Employee
cout << "Name : " << name << endl;
cout << "Address : " << address << endl;
cout << "Emp ID : " << employeeId << endl;
cout << "Department: " << department << endl;
cout << "Salary : Rs. " << basic + allowance << endl;
}
};
int main() {
Manager m;
m.setPerson("Aarav Adhikari", "Baneshwor, Kathmandu");
m.setEmployee(1047, 55000);
m.setManager("Finance", 12000);
m.show();
return 0;
}Name : Aarav Adhikari
Address : Baneshwor, Kathmandu
Emp ID : 1047
Department: Finance
Salary : Rs. 67000Important points about multilevel inheritance:
Manager can use name even though it did
not inherit from Person directly. Protected members travel
down the chain as long as each step uses public (or protected)
inheritance.Employee had used private inheritance
from Person, then name would become private
inside Employee, and Manager could no longer
use it. The chain would be “broken”.In hierarchical inheritance, several derived classes inherit from one base class. It looks like a tree or an organisation chart. The base class holds everything common; each child adds what makes it special.
Example: in Kathmandu, a trip can be made by bus, taxi or motorbike. Every vehicle has a registration number and a distance travelled, but each calculates its fare differently.
Vehicle
(number, distanceKm, setTrip)
/ | \
v v v
Bus Taxi Bike
fare: slab Rs. 14 + Rs. Rs. 25 per km
45 per km (ride-sharing)
The fares in the example are sample numbers for practice, not official rates.
#include <iostream>
#include <string>
using namespace std;
class Vehicle {
protected:
string number;
double distanceKm = 0.0;
public:
void setTrip(const string& num, double km) {
number = num;
distanceKm = km;
}
void showTrip() const {
cout << number << " travelled " << distanceKm << " km";
}
};
class Bus : public Vehicle {
public:
double fare() const {
if (distanceKm <= 5) return 20;
else if (distanceKm <= 10) return 25;
else return 35;
}
};
class Taxi : public Vehicle {
public:
double fare() const { return 14 + 45 * distanceKm; }
};
class Bike : public Vehicle {
public:
double fare() const { return 25 * distanceKm; }
};
int main() {
Bus bus;
Taxi taxi;
Bike bike;
bus.setTrip("Ba 1 Kha 2233", 8);
taxi.setTrip("Ba 3 Ja 1010", 8);
bike.setTrip("Ba 88 Pa 777", 8);
bus.showTrip(); cout << ", fare Rs. " << bus.fare() << endl;
taxi.showTrip(); cout << ", fare Rs. " << taxi.fare() << endl;
bike.showTrip(); cout << ", fare Rs. " << bike.fare() << endl;
return 0;
}Ba 1 Kha 2233 travelled 8 km, fare Rs. 25
Ba 3 Ja 1010 travelled 8 km, fare Rs. 374
Ba 88 Pa 777 travelled 8 km, fare Rs. 200Note that the three classes are siblings.
Bus and Taxi share the same base, but they do
not know about each other: a Bus object has no
Taxi members. setTrip() and
showTrip() were written once and reused three times, which
is the main benefit of hierarchical inheritance.
Each derived class has its own function named fare().
Right now, to call the correct fare(), we must know the
exact type of each object. In Run-time Polymorphism and
Virtual Functions you will learn about virtual
functions, which let a single Vehicle pointer call
the right fare() automatically.
| Feature | Single | Multilevel | Hierarchical |
|---|---|---|---|
| Number of base classes of a derived class | 1 | 1 at each level | 1 |
| Number of derived classes of the base | 1 | 1 at each level | Many |
| Shape | A → B | A → B → C | A → B, A → C, A → D |
| Typical use | Add a few features to one class | Gradual specialisation | Many kinds of one thing |
| Everyday example | Account → SavingsAccount | Person → Student → BITStudent | Shape → Circle, Rectangle, Triangle |
Remember: in all three types, each derived class has only one direct base class. That is why they are simple and safe. When a class has two or more direct bases (multiple inheritance), new problems can appear; you will study them in the next lesson, Types of Inheritance II — Multiple and Hybrid; the Diamond Problem.
A practical guideline for designing any of these families:
protected (or private with protected/public
getter functions).private.bus and taxi) each have their own
copy of number and distanceKm.taxi.busPass()), which gives “has no
member named” errors.Animal → Dog,
Animal → Cat, Animal → Cow; (b)
Device → Phone → SmartPhone; (c)
Shape → Circle.A → B → C, how many “parts”
does a C object contain? Name them.#include <iostream>
using namespace std;
class A {
protected:
int a = 1;
};
class B : public A {
protected:
int b = 2;
};
class C : public B {
public:
int sum() const { return a + b + 3; }
};
int main() {
C obj;
cout << obj.sum() << endl;
return 0;
}class B : private A is
used instead? Explain.Staff
(name, phone) and derived classes Teacher (subject) and
Accountant (officeRoom). Give each derived class a
show() function.A part, a B part and a C part. 3.
6. 4. Compile error: a becomes private in
B, so C::sum() cannot access it (“‘int A::a’
is protected within this context” / inaccessible). 5.
class Staff { protected: string name, phone; public: void setStaff(...); };
class Teacher : public Staff { string subject; public: void show() const {...} };
and similarly Accountant; each show() prints
the inherited name and phone plus its own member.In multiple inheritance, one derived class has two or more direct base classes. The derived class receives the members of all its bases. C++ supports this directly (some other languages, such as Java, do not allow it for classes).
Syntax: list the bases after the colon, separated by commas, each with its own access mode.
class Derived : public Base1, public Base2 {
// ...
};Be careful: the access mode applies to each base
separately. In class C : public A, B, the base
B is inherited privately, because it has
no access mode of its own.
Example: a college keeps academic marks in one class and sports points in another. A student who plays for the college team is described by both.
Academic Sports
(gpa, setGpa) (game, points)
\ /
v v
SportsStudent
(name, scholarship())
#include <iostream>
#include <string>
using namespace std;
class Academic {
protected:
double gpa = 0.0;
public:
void setGpa(double g) { gpa = g; }
};
class Sports {
protected:
string game;
int points = 0;
public:
void setSports(const string& g, int p) {
game = g;
points = p;
}
};
// multiple inheritance: two direct base classes
class SportsStudent : public Academic, public Sports {
private:
string name;
public:
void setName(const string& n) { name = n; }
double scholarship() const {
double amount = 0;
if (gpa >= 3.6) amount += 20000;
if (points >= 50) amount += 15000;
return amount;
}
void show() const {
cout << name << " | GPA " << gpa << " | " << game
<< " points " << points << endl;
cout << "Scholarship: Rs. " << scholarship() << endl;
}
};
int main() {
SportsStudent s;
s.setName("Priya Gurung");
s.setGpa(3.7); // from Academic
s.setSports("Football", 62); // from Sports
s.show();
return 0;
}Priya Gurung | GPA 3.7 | Football points 62
Scholarship: Rs. 35000Multiple inheritance is powerful when the base classes describe independent features (academic record and sports record have nothing in common). Problems start when the bases have something with the same name.
Suppose both Academic and Sports had a
function named show(). If we call s.show() on
a derived object that does not define its own
show(), the compiler does not know which one we mean. This
is called ambiguity.
#include <iostream>
using namespace std;
class Printer {
public:
void start() const { cout << "Printer warming up" << endl; }
};
class Scanner {
public:
void start() const { cout << "Scanner lamp on" << endl; }
};
class OfficeMachine : public Printer, public Scanner { };
int main() {
OfficeMachine m;
m.start(); // which start()?
return 0;
}The compiler (g++) reports:
error: request for member 'start' is ambiguous
note: candidates are: 'void Scanner::start() const'
note: 'void Printer::start() const'
There are two standard ways to remove the ambiguity.
Solution 1: scope resolution operator. Tell the
compiler which class you mean with ClassName::.
Solution 2: redefine the function in the derived
class. The derived class writes its own start(),
which hides both inherited versions. Inside it, you may call each base
version explicitly.
#include <iostream>
using namespace std;
class Printer {
public:
void start() const { cout << "Printer warming up" << endl; }
};
class Scanner {
public:
void start() const { cout << "Scanner lamp on" << endl; }
};
class OfficeMachine : public Printer, public Scanner {
public:
// Solution 2: derived class defines its own start()
void start() const {
cout << "All-in-one machine starting..." << endl;
Printer::start();
Scanner::start();
}
};
int main() {
OfficeMachine m;
// Solution 1: choose a base version with ::
m.Printer::start();
m.Scanner::start();
cout << "---" << endl;
// Solution 2: call the derived class's own version
m.start();
return 0;
}Printer warming up
Scanner lamp on
---
All-in-one machine starting...
Printer warming up
Scanner lamp onHybrid inheritance is any combination of two or more types of inheritance in one design. The most famous hybrid shape is the diamond, which combines hierarchical inheritance (one base, two children) with multiple inheritance (one grandchild with two parents).
Person
(name, age)
/ \
v v
Student Employee
(rollNo) (salary)
\ /
v v
TeachingAssistant
A teaching assistant at a college is both a student (studying for a master’s degree) and an employee (paid to help in labs). This looks natural. But there is a hidden problem.
Student contains a Person part.
Employee also contains a Person part.
TeachingAssistant inherits from both, so it gets
two separate Person parts — two
names and two ages!
TeachingAssistant object (WITHOUT virtual)
+----------------------------------------+
| Student part |
| Person part #1 : name, age |
| rollNo |
+----------------------------------------+
| Employee part |
| Person part #2 : name, age |
| salary |
+----------------------------------------+
Writing ta.name is now ambiguous (which
name?), and even if we choose one with ::, the
two copies can hold different values, which makes no sense for one real
person. The next program shows the duplicate copies clearly.
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
string name;
};
class Student : public Person {
public:
int rollNo = 0;
};
class Employee : public Person {
public:
double salary = 0;
};
class TeachingAssistant : public Student, public Employee { };
int main() {
TeachingAssistant ta;
// ta.name = "Sita"; // ERROR: ambiguous, two copies of name
ta.Student::name = "Sita Rai";
ta.Employee::name = "S. Rai (staff)";
cout << "Student copy : " << ta.Student::name << endl;
cout << "Employee copy: " << ta.Employee::name << endl;
cout << "Two copies of Person -> the diamond problem!" << endl;
return 0;
}Student copy : Sita Rai
Employee copy: S. Rai (staff)
Two copies of Person -> the diamond problem!This is called the diamond problem: duplicate base sub-objects, wasted memory, and ambiguous member access.
C++ solves the diamond problem with virtual
inheritance. When the middle classes inherit from the
common base using the keyword virtual, the common base
becomes a virtual base class. Then only one
shared copy of it exists in the final object, no matter how
many paths lead to it.
class Student : virtual public Person { ... }; // "virtual" here
class Employee : virtual public Person { ... }; // and here
class TeachingAssistant : public Student, public Employee { ... };The words virtual public and public virtual
mean the same thing. Note that virtual is written on the
middle classes, not on the final class.
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
string name;
int age = 0;
};
class Student : virtual public Person {
public:
int rollNo = 0;
};
class Employee : virtual public Person {
public:
double salary = 0;
};
class TeachingAssistant : public Student, public Employee {
public:
void show() const {
cout << "Name : " << name << " (" << age << ")" << endl;
cout << "Roll : " << rollNo << endl;
cout << "Salary: Rs. " << salary << endl;
}
};
int main() {
TeachingAssistant ta;
ta.name = "Sita Rai"; // no ambiguity: only ONE name now
ta.age = 24;
ta.rollNo = 7;
ta.salary = 25000;
ta.show();
// Both paths now lead to the same shared Person part
ta.Student::name = "Sita K. Rai";
cout << "Via Employee path: " << ta.Employee::name << endl;
return 0;
}Name : Sita Rai (24)
Roll : 7
Salary: Rs. 25000
Via Employee path: Sita K. RaiThe last line proves that the Student path and the
Employee path now refer to the same single
name.
TeachingAssistant object (WITH virtual base)
+----------------------------------------+
| Student part : rollNo |
| Employee part : salary |
| ONE shared Person part : name, age |
+----------------------------------------+
A few extra facts about virtual base classes (for completeness):
TeachingAssistant) is responsible for constructing the
virtual base. You will see what this means for constructors in Constructors, Destructors and Function Overriding in
Inheritance.virtual inheritance only when a diamond is really
possible. It is not needed for ordinary single or multilevel
inheritance.Multiple inheritance is a sharp tool. Professional guidelines suggest:
| Use it when | Avoid it when |
|---|---|
Bases describe independent abilities (e.g., Printable,
Saveable) |
Bases share a common ancestor and you are not sure about virtual inheritance |
| Each base is small and clear | A simpler “has-a” design (composition) would work |
| Name clashes are unlikely or handled | Many functions have the same names in different bases |
class C : public A, B and expecting
B to be public. Each base needs its own access mode:
public A, public B.::
→ “request for member is ambiguous”.virtual on the final class
(class TA : virtual public Student, virtual public Employee)
instead of on the middle classes. This does not fix the
duplicate Person.virtual inheritance and virtual
functions (see the Polymorphism and
Templates chapter) are the same thing. They use the same keyword but
solve different problems.class C : public A, public B.Base::member or by redefining in the derived class.class Middle : virtual public Base → only one
shared copy (virtual base class).class Z : public X, Y { };, what is the access mode
of Y?#include <iostream>
using namespace std;
class A {
public:
void hello() const { cout << "A"; }
};
class B {
public:
void hello() const { cout << "B"; }
};
class C : public A, public B {
public:
void hello() const {
B::hello();
A::hello();
cout << "C" << endl;
}
};
int main() {
C c;
c.hello();
c.A::hello();
cout << endl;
return 0;
}virtual be written to
solve the diamond problem?Vehicle →
LandVehicle, WaterVehicle →
AmphibiousVehicle, so that AmphibiousVehicle
has only one Vehicle part.class). 3. First line
BAC, second line A. 4. Diagram as in notes;
without virtual: 2 copies, with virtual: 1 copy. 5. On the middle
classes’ base lists: class Student : virtual public Person.
6. class Vehicle {};
class LandVehicle : virtual public Vehicle {};
class WaterVehicle : virtual public Vehicle {};
class AmphibiousVehicle : public LandVehicle, public WaterVehicle {};A derived object contains a base part (see Introduction to Inheritance). Before the derived part can be set up, the base part must already exist, because the derived constructor may use base members. Think of building a house in Kathmandu: the foundation (base) must be finished before the first floor (derived) is built. When the house is demolished, the top floor is removed first and the foundation last.
Rule: Constructors run from base to derived (top of the hierarchy downwards). Destructors run in the exact reverse order, from derived to base.
#include <iostream>
using namespace std;
class Grandparent {
public:
Grandparent() { cout << "Grandparent constructor" << endl; }
~Grandparent() { cout << "Grandparent destructor" << endl; }
};
class Parent : public Grandparent {
public:
Parent() { cout << " Parent constructor" << endl; }
~Parent() { cout << " Parent destructor" << endl; }
};
class Child : public Parent {
public:
Child() { cout << " Child constructor" << endl; }
~Child() { cout << " Child destructor" << endl; }
};
int main() {
cout << "Creating object..." << endl;
{
Child c;
cout << "Object in use" << endl;
} // c goes out of scope here
cout << "Block finished" << endl;
return 0;
}Creating object...
Grandparent constructor
Parent constructor
Child constructor
Object in use
Child destructor
Parent destructor
Grandparent destructor
Block finishedNotice that we created only a Child, but three
constructors and three destructors ran. The derived constructor is
called first, but before its body runs it automatically calls
the base constructor.
If the base class has only a parameterised constructor (no default constructor), the derived class must tell C++ which values to pass to it. We do this in the derived constructor’s member initialiser list (from Constructors), by writing the base class name followed by arguments.
Derived(parameters) : Base(arguments), member(value) {
// body of derived constructor
}The base part is always constructed first, even if you write it later
in the list. Write it first anyway so that the code matches what really
happens (g++ with -Wall warns when the list order differs
from the real order).
#include <iostream>
#include <string>
using namespace std;
class Person {
protected:
string name;
int age;
public:
Person(const string& n, int a) : name(n), age(a) {
cout << "Person(" << name << ") created" << endl;
}
~Person() { cout << "Person(" << name << ") destroyed" << endl; }
};
class Student : public Person {
private:
int rollNo;
string program;
public:
// pass n and a up to Person's constructor
Student(const string& n, int a, int r, const string& p)
: Person(n, a), rollNo(r), program(p) {
cout << "Student(roll " << rollNo << ") created" << endl;
}
~Student() { cout << "Student(roll " << rollNo << ") destroyed" << endl; }
void show() const {
cout << name << ", " << age << " yrs, roll " << rollNo
<< ", " << program << endl;
}
};
int main() {
Student s("Ram Bahadur Thapa", 20, 15, "BIT");
s.show();
return 0;
}Person(Ram Bahadur Thapa) created
Student(roll 15) created
Ram Bahadur Thapa, 20 yrs, roll 15, BIT
Student(roll 15) destroyed
Person(Ram Bahadur Thapa) destroyedWhat happens if we forget : Person(n, a)? The compiler
tries to call Person() (the default constructor), which
does not exist:
error: no matching function for call to 'Person::Person()'
| Situation | Constructor order | Destructor order |
|---|---|---|
Single A → B |
A, B | B, A |
Multilevel A → B → C |
A, B, C | C, B, A |
Multiple class C : public A, public B |
A, B (order in the class header), then C | C, B, A |
| Member objects | Bases first, then member objects (in declaration order), then own body | Reverse |
| Virtual base in a diamond | Virtual base first (built once, by the most-derived class), then others | Reverse |
In multiple inheritance, the order follows the order written
in the class header (public A, public B),
not the order in the initialiser list.
In multilevel inheritance, each class passes arguments only to its
direct base. C calls B(...),
and B’s constructor calls A(...).
#include <iostream>
#include <string>
using namespace std;
class Academic {
protected:
double gpa;
public:
Academic(double g) : gpa(g) { cout << "Academic built" << endl; }
~Academic() { cout << "Academic destroyed" << endl; }
};
class Sports {
protected:
string game;
public:
Sports(const string& gm) : game(gm) { cout << "Sports built" << endl; }
~Sports() { cout << "Sports destroyed" << endl; }
};
// Header order: Academic first, then Sports
class SportsStudent : public Academic, public Sports {
public:
SportsStudent(double g, const string& gm)
: Academic(g), Sports(gm) {
cout << "SportsStudent built: GPA " << gpa
<< ", plays " << game << endl;
}
~SportsStudent() { cout << "SportsStudent destroyed" << endl; }
};
int main() {
SportsStudent s(3.4, "Volleyball");
cout << "--- end of main ---" << endl;
return 0;
}Academic built
Sports built
SportsStudent built: GPA 3.4, plays Volleyball
--- end of main ---
SportsStudent destroyed
Sports destroyed
Academic destroyedVirtual base classes and constructors. In the
diamond of Types of Inheritance II — Multiple and Hybrid;
the Diamond Problem, if Person has a parameterised
constructor, the most-derived class
(TeachingAssistant) must call Person(...)
directly in its initialiser list, for example
TeachingAssistant(...) : Person(n), Student(...), Employee(...).
Calls to Person(...) written inside Student
and Employee are ignored when a
TeachingAssistant is created. This guarantees that the one
shared Person part is built exactly once. Try this
yourself: give Person a parameterised constructor and build
a TeachingAssistant.
Sometimes a derived class needs a different version
of a function it inherits. For example, every Employee can
display() itself, but a Manager should also
show its allowance. If the derived class declares a function with the
same name and same parameter list as the base, the
derived version is used for derived objects. This is commonly called
function overriding in textbooks.
A note for accuracy: in standard C++ terminology, a function is truly
“overridden” only when the base function is declared
virtual (see Run-time Polymorphism and
Virtual Functions). When the base function is not virtual,
as below, the derived version hides (redefines) the
base version. For objects used directly by name, both behave the same.
The difference appears with pointers, which you will study in the Polymorphism and Templates chapter.
| Overloading (Default Arguments, Inline Functions and Function Overloading) | Overriding / redefining | |
|---|---|---|
| Where | Same scope (e.g., same class) | Base class and derived class |
| Name | Same | Same |
| Parameters | Must differ | Same |
| Chosen by | Argument types, at compile time | Object type (and virtual, see Run-time
Polymorphism and Virtual Functions) |
Inside the derived version, you can still call the base version with
Base::function(). This avoids repeating code.
#include <iostream>
#include <string>
using namespace std;
class Employee {
protected:
string name;
double basic;
public:
Employee(const string& n, double b) : name(n), basic(b) {}
double salary() const { return basic; }
void display() const {
cout << "Name : " << name << endl;
cout << "Salary: Rs. " << salary() << endl;
}
};
class Manager : public Employee {
private:
double allowance;
public:
Manager(const string& n, double b, double a)
: Employee(n, b), allowance(a) {}
// redefines Employee::salary()
double salary() const { return basic + allowance; }
// redefines Employee::display(), reusing the base version
void display() const {
cout << "Name : " << name << endl;
cout << "Salary: Rs. " << salary() << endl;
cout << "(includes allowance Rs. " << allowance << ")" << endl;
}
void displayAsEmployee() const {
Employee::display(); // call the base version explicitly
}
};
int main() {
Employee e("Gita Paudel", 40000);
Manager m("Aarav Joshi", 60000, 15000);
e.display();
cout << "---" << endl;
m.display();
cout << "---" << endl;
m.displayAsEmployee(); // base version: basic only
return 0;
}Name : Gita Paudel
Salary: Rs. 40000
---
Name : Aarav Joshi
Salary: Rs. 75000
(includes allowance Rs. 15000)
---
Name : Aarav Joshi
Salary: Rs. 60000Look at the last block of output: Employee::display()
called Employee::salary(), not
Manager::salary(), so it printed Rs. 60000. The base
function does not know about the derived redefinition. This is exactly
the problem that virtual functions solve in Run-time Polymorphism and Virtual Functions.
We combine everything: a base class with a parameterised constructor, a derived class passing arguments up, a redefined function, and destructors.
#include <iostream>
#include <string>
using namespace std;
class BankAccount {
protected:
string holder;
double balance;
public:
BankAccount(const string& h, double b) : holder(h), balance(b) {
cout << "[Account opened for " << holder << "]" << endl;
}
~BankAccount() { cout << "[Account of " << holder << " closed]" << endl; }
bool withdraw(double amount) {
if (amount > balance) return false;
balance -= amount;
return true;
}
void statement() const {
cout << holder << " balance: Rs. " << balance << endl;
}
};
class SavingsAccount : public BankAccount {
private:
double minimumBalance;
public:
SavingsAccount(const string& h, double b, double minBal)
: BankAccount(h, b), minimumBalance(minBal) {}
// redefined: must keep a minimum balance
bool withdraw(double amount) {
if (balance - amount < minimumBalance) return false;
return BankAccount::withdraw(amount);
}
};
int main() {
SavingsAccount acc("Sita Shrestha", 5000, 1000);
cout << "Withdraw 3000: "
<< (acc.withdraw(3000) ? "done" : "refused") << endl;
cout << "Withdraw 1500: "
<< (acc.withdraw(1500) ? "done" : "refused") << endl;
acc.statement();
return 0;
}[Account opened for Sita Shrestha]
Withdraw 3000: done
Withdraw 1500: refused
Sita Shrestha balance: Rs. 2000
[Account of Sita Shrestha closed]The second withdrawal was refused because it would leave only Rs. 500, below the minimum balance of Rs. 1000.
Student(...) : name(n) is an error; name must
be initialised by Person(n, ...).display(int)) — that hides the base function instead of
redefining it, and m.display() stops compiling.Base:: inside the
derived version (display() calls itself forever → infinite
recursion and crash).Derived(...) : Base(args) { }.Base::f() to reach the
base version.class C : public B where
class B : public A.#include <iostream>
using namespace std;
class X {
public:
X() { cout << "X "; }
~X() { cout << "~X "; }
};
class Y {
public:
Y() { cout << "Y "; }
~Y() { cout << "~Y "; }
};
class Z : public Y, public X {
public:
Z() { cout << "Z "; }
~Z() { cout << "~Z "; }
};
int main() {
{ Z z; }
cout << endl;
return 0;
}class Box : public Rectangle
where Rectangle(double l, double b) exists and
Box adds height.displayAsEmployee() print Rs. 60000 instead of Rs.
75000?Y X Z ~Z ~X ~Y. 4. Overloading: same scope, different
parameters, resolved by arguments at compile time. Overriding: base and
derived classes, same signature, chosen by object type (with
virtual, at run time). 5.
Box(double l, double b, double h) : Rectangle(l, b), height(h) {}.
6. Employee::display() calls salary(); since
salary() is not virtual, the base’s
Employee::salary() is used, which returns only
basic.In real life, one word often means different actions depending on the situation. In Nepali, “khelnu” can mean playing football, playing a flute or playing a game on a phone — the same word, but the action depends on the object. A person can also be a student at college, a customer in a shop and a son or daughter at home: one person, many roles.
In C++, polymorphism is the ability of the same name (a function name or an operator) to behave differently for different types or objects. The benefit is that programmers remember one name for one idea (for example, “add” or “draw”), and C++ picks the correct version.
| Type | Also called | Decided at | Achieved by | See |
|---|---|---|---|---|
| Compile-time | Static polymorphism, early binding | Compilation | Function overloading, operator overloading, templates | function overloading, operator overloading, more operators, templates |
| Run-time | Dynamic polymorphism, late binding | Program execution | Virtual functions with base-class pointers or references | virtual functions, abstract classes |
Polymorphism
/ \
v v
Compile-time Run-time
(static) (dynamic)
/ | \ |
v v v v
Function Operator Templates Virtual
overloading overloading functions
Binding means connecting a function call to the actual function code that will run. In compile-time polymorphism the compiler does this connection before the program runs (early binding). In run-time polymorphism the connection is made while the program runs (late binding). You will study late binding in Run-time Polymorphism and Virtual Functions.
In Default Arguments, Inline Functions and Function Overloading you wrote several functions with the same name but different parameter lists. The compiler chooses one by looking at the arguments. This is compile-time polymorphism.
#include <iostream>
using namespace std;
double area(double radius) { // circle
return 3.14159 * radius * radius;
}
double area(double length, double breadth) { // rectangle
return length * breadth;
}
int area(int side) { // square (integer)
return side * side;
}
int main() {
cout << "Circle : " << area(2.0) << endl;
cout << "Rectangle : " << area(4.0, 2.5) << endl;
cout << "Square : " << area(6) << endl;
return 0;
}Circle : 12.5664
Rectangle : 10
Square : 36For built-in types, operators already work: 5 + 3 adds
integers, and "Ram" + string(" Thapa") joins strings. The
+ operator already has “many forms”! But for our own
classes, C++ does not know what + should mean. If we have
two Money objects, a + b is an error unless we
tell C++ how to add them.
Operator overloading means giving an extra meaning
to a C++ operator when it is used with objects of a user-defined class.
We do this by writing a special function whose name is the keyword
operator followed by the symbol:
ReturnType operator+(const Money& other) const; // for a + b
ReturnType operator-() const; // for -a (unary)When we write a + b and a is an object, C++
translates it into a function call:
| We write | C++ calls (member function form) |
|---|---|
a + b |
a.operator+(b) |
a - b |
a.operator-(b) |
-a |
a.operator-() |
++a |
a.operator++() |
a++ |
a.operator++(0) (dummy int argument) |
So an operator function is just an ordinary member function with an
unusual name. The left operand is the object that calls
the function (this), and for binary operators the
right operand is passed as the argument.
Why bother? Compare these two lines:
total = price.add(tax).add(delivery); // without operator overloading
total = price + tax + delivery; // with operator overloadingThe second line reads like normal mathematics. Good operator overloading makes code shorter and clearer.
A unary operator works on one operand:
-x, ++x, x++, !x. As
a member function, it takes no arguments (the one
operand is the calling object). The postfix form of ++ and
-- is special: it takes a dummy int parameter
only so that C++ can tell prefix and postfix apart.
| Form | Declaration | Returns (usual convention) |
|---|---|---|
Prefix ++a |
Type& operator++() |
The object itself after increment (by reference) |
Postfix a++ |
Type operator++(int) |
A copy of the old value |
Unary minus -a |
Type operator-() const |
A new object; a is unchanged |
Example: a counter for tickets sold at a Dashain mela, and a temperature whose sign can be reversed.
#include <iostream>
using namespace std;
class Counter {
private:
int count;
public:
Counter(int c = 0) : count(c) {}
// prefix ++ : increment, then return the updated object
Counter& operator++() {
++count;
return *this;
}
// postfix ++ : save old value, increment, return old value
Counter operator++(int) {
Counter old = *this;
++count;
return old;
}
// unary minus : return a new object with opposite sign
Counter operator-() const {
return Counter(-count);
}
int get() const { return count; }
};
int main() {
Counter tickets(10);
Counter a = ++tickets; // tickets becomes 11, a is 11
cout << "After ++tickets: tickets=" << tickets.get()
<< ", a=" << a.get() << endl;
Counter b = tickets++; // b gets 11, then tickets becomes 12
cout << "After tickets++: tickets=" << tickets.get()
<< ", b=" << b.get() << endl;
Counter c = -tickets; // tickets unchanged
cout << "-tickets = " << c.get()
<< ", tickets still " << tickets.get() << endl;
return 0;
}After ++tickets: tickets=11, a=11
After tickets++: tickets=12, b=11
-tickets = -12, tickets still 12The output shows the same rule you learned for integers in Assignment, Increment/Decrement, Type Conversion and Overflow: prefix changes first and then gives the new value; postfix gives the old value and then changes.
A binary operator works on two operands:
a + b, a - b, a * b. As a member
function it takes one argument — the right operand. The
left operand is the calling object.
Example: Nepali money has rupees and paisa (100 paisa = 1 rupee). We
store money as a total number of paisa inside the class to avoid
rounding problems, and overload +, - and
* (multiply by a quantity).
#include <iostream>
#include <iomanip>
using namespace std;
class Money {
private:
long paisa; // store everything in paisa
public:
Money(long rupees = 0, int ps = 0) : paisa(rupees * 100 + ps) {}
Money operator+(const Money& other) const {
Money result;
result.paisa = paisa + other.paisa;
return result;
}
Money operator-(const Money& other) const {
Money result;
result.paisa = paisa - other.paisa;
return result;
}
Money operator*(int quantity) const { // Money * int
Money result;
result.paisa = paisa * quantity;
return result;
}
void show() const {
cout << "Rs. " << paisa / 100 << "."
<< setw(2) << setfill('0') << paisa % 100
<< setfill(' ') << endl;
}
};
int main() {
Money momo(180, 75); // Rs. 180.75 per plate
Money tea(35); // Rs. 35.00 per cup
Money bill = momo * 2 + tea * 3;
Money paid(500);
Money change = paid - bill;
cout << "Bill : "; bill.show();
cout << "Paid : "; paid.show();
cout << "Change : "; change.show();
return 0;
}Bill : Rs. 466.50
Paid : Rs. 500.00
Change : Rs. 33.50Read momo * 2 + tea * 3 carefully. Operator precedence
is the same as for built-in types (you cannot change
it), so * is done before +: first
momo.operator*(2), then tea.operator*(3), then
the two results are added with operator+.
Notes on good style:
const.const Money& (const
reference, see Passing Data by Reference) to avoid a
needless copy.momo * 2 works, but 2 * momo does
not, because the left operand 2 is an
int, not a Money object. A member function
always needs a class object on the left. In More Operator
Overloading — Friends, Stream and Comparison Operators you will
solve this with friend operator functions.| Can be overloaded (examples) | Cannot be overloaded |
|---|---|
+ - * / % |
. (member access) |
== != < > <= >= |
:: (scope resolution) |
++ -- (prefix and postfix) |
?: (conditional) |
<< >> |
sizeof |
= [] () and others |
.* (pointer-to-member) |
The full list of rules is in More Operator Overloading — Friends, Stream and Comparison Operators.
Money operator+(Money a, Money b) inside the class is an
error (“must have either zero or one argument”). The left operand is
this.int for postfix: writing two
operator++() functions gives a redefinition error.operator+ (e.g.,
paisa += other.paisa; return *this;). Then
a + b would secretly change a, which surprises
readers.2 * momo to work when only the member
operator*(int) exists.+ to
print). Overloaded operators should behave in the natural, expected
way.operator followed by the
symbol, e.g., operator+.++/-- use a dummy int
parameter; prefix returns a reference, postfix returns the old
copy.a + b if a and
b are objects and operator+ is a member
function?-- for a
class Stock.#include <iostream>
using namespace std;
class Num {
int v;
public:
Num(int x = 0) : v(x) {}
Num operator+(const Num& o) const { return Num(v + o.v); }
Num operator-() const { return Num(-v); }
int get() const { return v; }
};
int main() {
Num a(7), b(3);
Num c = a + -b;
cout << c.get() << " " << a.get() << endl;
return 0;
}2 * momo fail when Money has only
a member operator*(int)?Point (x, y) with a member
operator+ that adds two points.a.operator+(b). 3. Stock& operator--();
and Stock operator--(int);. 4. 4 7 (unary
minus gives -3, then 7 + (-3) = 4; a is unchanged). 5. For
a member operator, the left operand must be a Money object;
2 is an int, and int has no
member operator* for Money. 6.
class Point { int x, y; public: Point(int a=0, int b=0) : x(a), y(b) {} Point operator+(const Point& p) const { return Point(x + p.x, y + p.y); } };In Polymorphism and Operator Overloading,
momo * 2 worked but 2 * momo did not. The
reason: for a member operator function, the
left operand must be an object of the class, because
C++ calls leftOperand.operator*(rightOperand). The integer
2 is not an object and cannot have member functions.
The solution is to write the operator as a non-member function that takes both operands as parameters. Then the left operand can be any type:
| We write | Member form | Non-member (friend) form |
|---|---|---|
a + b |
a.operator+(b) |
operator+(a, b) |
2 * m |
not possible | operator*(2, m) |
cout << m |
not possible (left is cout) |
operator<<(cout, m) |
A non-member function cannot see the private data of the class. So we declare it a friend inside the class (friend functions were introduced in Objects and Functions, Friends, and a Class Design Case Study). A friend function is not a member, but it is allowed to access private members.
| Operator function type | Parameters for a unary operator | Parameters for a binary operator |
|---|---|---|
| Member function | 0 | 1 (right operand) |
| Friend (non-member) function | 1 | 2 (left, right) |
#include <iostream>
using namespace std;
class Money {
private:
long paisa;
public:
Money(long rupees = 0, int ps = 0) : paisa(rupees * 100 + ps) {}
// member: Money * int
Money operator*(int qty) const {
Money r;
r.paisa = paisa * qty;
return r;
}
// friend: int * Money (left operand is an int)
friend Money operator*(int qty, const Money& m);
// friend: Money + Money, written with two parameters
friend Money operator+(const Money& a, const Money& b);
void show() const {
cout << "Rs. " << paisa / 100 << "." << paisa % 100 / 10
<< paisa % 10 << endl;
}
};
// definitions: no "Money::" and no "friend" keyword here
Money operator*(int qty, const Money& m) {
return m * qty; // reuse the member version
}
Money operator+(const Money& a, const Money& b) {
Money r;
r.paisa = a.paisa + b.paisa; // allowed: friend
return r;
}
int main() {
Money ticket(250); // Rs. 250 bus ticket
Money snacks(85, 50);
Money t1 = ticket * 3; // member operator*
Money t2 = 3 * ticket; // friend operator*
Money total = t2 + snacks; // friend operator+
cout << "ticket * 3 = "; t1.show();
cout << "3 * ticket = "; t2.show();
cout << "with snacks = "; total.show();
return 0;
}ticket * 3 = Rs. 750.00
3 * ticket = Rs. 750.00
with snacks = Rs. 835.50Points to note:
friend appears only inside
the class, on the declaration.Money::, because the function is not a member.+,
-, ==, <) as friends, so that
both operands are treated the same way.We have been printing objects with helper functions such as
show(). It is much nicer to write
cout << m; just like for an int. The
expression cout << m has cout on the
left. cout is an object of type ostream
(output stream), which belongs to the standard library, so we cannot add
a member to it. Therefore operator<<
must be a non-member (usually a friend).
friend ostream& operator<<(ostream& out, const Money& m);Why return ostream&? So that we can
chain outputs:
cout << a << " and " << b << endl;
is evaluated as
((cout << a) << " and ") << b .... Each
<< returns the same stream, ready for the next
<<. The stream must be passed and returned by
reference, because streams cannot be copied.
Similarly, cin >> m calls
operator>>(cin, m). cin is an
istream (input stream). The object parameter must be a
non-const reference, because >>
changes the object by storing input in it.
friend istream& operator>>(istream& in, Money& m);The next program uses a Time class for travel time
(hours and minutes), with >>, <<,
+ and comparison operators. It reads two bus journeys and
compares them.
#include <iostream>
#include <iomanip>
using namespace std;
class Time {
private:
int hours;
int minutes;
void normalise() { // keep minutes in 0..59
hours += minutes / 60;
minutes = minutes % 60;
}
public:
Time(int h = 0, int m = 0) : hours(h), minutes(m) { normalise(); }
int totalMinutes() const { return hours * 60 + minutes; }
friend Time operator+(const Time& a, const Time& b) {
return Time(a.hours + b.hours, a.minutes + b.minutes);
}
friend bool operator==(const Time& a, const Time& b) {
return a.totalMinutes() == b.totalMinutes();
}
friend bool operator!=(const Time& a, const Time& b) {
return !(a == b); // reuse ==
}
friend bool operator<(const Time& a, const Time& b) {
return a.totalMinutes() < b.totalMinutes();
}
friend ostream& operator<<(ostream& out, const Time& t) {
out << t.hours << "h " << setw(2) << setfill('0')
<< t.minutes << "m" << setfill(' ');
return out;
}
friend istream& operator>>(istream& in, Time& t) {
in >> t.hours >> t.minutes;
t.normalise();
return in;
}
};
int main() {
Time ktmToPokhara, ktmToChitwan;
cout << "Kathmandu-Pokhara time (h m): ";
cin >> ktmToPokhara;
cout << "Kathmandu-Chitwan time (h m): ";
cin >> ktmToChitwan;
cout << "Pokhara trip : " << ktmToPokhara << endl;
cout << "Chitwan trip : " << ktmToChitwan << endl;
cout << "Both trips : " << ktmToPokhara + ktmToChitwan << endl;
if (ktmToPokhara == ktmToChitwan)
cout << "Both trips take the same time." << endl;
else if (ktmToChitwan < ktmToPokhara)
cout << "The Chitwan trip is shorter." << endl;
else
cout << "The Pokhara trip is shorter." << endl;
if (ktmToPokhara != Time(7, 0))
cout << "Pokhara trip is not exactly 7 hours." << endl;
return 0;
}Kathmandu-Pokhara time (h m): 6 45
Kathmandu-Chitwan time (h m): 4 90
Pokhara trip : 6h 45m
Chitwan trip : 5h 30m
Both trips : 12h 15m
The Chitwan trip is shorter.
Pokhara trip is not exactly 7 hours.In this example the friend functions are defined inside the
class body. That is allowed: they are still non-member friends,
just written in a convenient place. Notice also that
Time(7, 0) creates a temporary object for the comparison,
and that normalise() converted “4 h 90 m” into 5 h 30
m.
Once < is defined, objects can be compared inside
ordinary algorithms that you wrote in the Arrays
chapter (for example, finding the maximum or bubble sort), exactly like
numbers. Here we define > for students by percentage and
find the topper.
#include <iostream>
#include <string>
using namespace std;
class Student {
private:
string name;
double percent;
public:
Student(const string& n = "", double p = 0) : name(n), percent(p) {}
friend bool operator>(const Student& a, const Student& b) {
return a.percent > b.percent;
}
friend bool operator==(const Student& a, const Student& b) {
return a.name == b.name && a.percent == b.percent;
}
friend ostream& operator<<(ostream& out, const Student& s) {
out << s.name << " (" << s.percent << "%)";
return out;
}
};
int main() {
Student cls[4] = {
Student("Sita", 78.5), Student("Ram", 84.0),
Student("Priya", 91.25), Student("Aarav", 66.0)
};
Student topper = cls[0];
for (int i = 1; i < 4; i++) {
if (cls[i] > topper) { // uses our operator>
topper = cls[i];
}
}
cout << "Topper: " << topper << endl;
Student search("Ram", 84.0);
for (int i = 0; i < 4; i++) {
if (cls[i] == search) { // uses our operator==
cout << "Found " << search << " at index " << i << endl;
}
}
return 0;
}Topper: Priya (91.25%)
Found Ram (84%) at index 1Note: in C++17, each comparison operator must be written separately.
(C++20 adds the “spaceship” operator <=> that can
generate them, but it is outside the scope of this tutorial.) Always
make related operators consistent: if you define ==, also
define != as !(a == b); if you define
<, define > as
b < a.
** or
<>.+ means for two
ints.a + b * c always does *
first; ! is always unary.. .* :: ?:
sizeof (also typeid and the cast
keywords).=
(assignment), [] (subscript), () (function
call), -> (member access through pointer).<< and >> for streams must be
non-members, because the left operand is a stream.()).- to add, or
== to print, is legal but is bad, confusing practice.
Writing clear, honest code that behaves as other programmers expect is
part of being an ethical team member.| Operator | Recommended form | Reason |
|---|---|---|
+=, -=, ++, --,
unary - |
Member | They change or belong to one object |
+, -, *, ==,
< |
Friend (non-member) | Symmetric; allows 2 * m |
<<, >> |
Friend (must be non-member) | Left operand is a stream |
=, [], (),
-> |
Member (required) | Language rule |
ostream by value instead of
ostream& → compile error, because streams cannot be
copied.return out; in operator<<
→ chaining (cout << a << b) fails, and
-Wall warns “no return statement”.const Time& t in
operator>> → error, because input must modify
t.Money::operator+(...) when defining a friend
outside the class. A friend is not a member.== and forgetting !=, then
writing if (a != b) → “no match for operator!=”.ostream& operator<<(ostream&, const T&)
and istream& operator>>(istream&, T&) —
return the stream by reference for chaining.!=
from ==, > from <).., ::, ?:, sizeof,
.* cannot be overloaded.operator<< for cout be a
non-member function?operator== have? How
many does a member operator== have?#include <iostream>
using namespace std;
class Box {
int w;
public:
Box(int x) : w(x) {}
friend bool operator<(const Box& a, const Box& b) { return a.w < b.w; }
friend ostream& operator<<(ostream& o, const Box& b) {
o << "[" << b.w << "]";
return o;
}
};
int main() {
Box p(4), q(9);
cout << p << q << endl;
cout << (q < p ? "q" : "p") << " is smaller" << endl;
return 0;
}friend ostream operator<<(ostream out, Box b);operator>> for a class
Point with private x and y.cout (an ostream from
the library), and a member operator needs the class object on the left.
2. Friend: 2; member: 1. 3. Any four of ., .*,
::, ?:, sizeof,
typeid. 4. [4][9] then
p is smaller. 5. Stream must be passed and returned by
reference:
friend ostream& operator<<(ostream& out, const Box& b);
6.
friend istream& operator>>(istream& in, Point& p) { in >> p.x >> p.y; return in; }Because of the “is-a” relationship, a Bus is
a Vehicle. So C++ allows a pointer (or reference)
of type Vehicle* to hold the address of a Bus
object. This automatic conversion from derived to base is called
upcasting, and it is always safe with public
inheritance.
Bus bus;
Vehicle* vp = &bus; // OK: a Bus is a Vehicle
Vehicle& vr = bus; // OK: reference works the same way
// Bus* bp = &someVehicle; // NOT allowed automatically: not every
// // Vehicle is a BusWhy is this useful? Imagine a transport office that has buses, taxis
and bikes. We want one array of vehicles and
one loop that calculates every fare. We cannot put
different types in one array, but we can put pointers to the
base class in one array. The question is: when we call
vp->fare(), which fare() runs?
#include <iostream>
using namespace std;
class Vehicle {
public:
double fare(double km) const { return 0; } // generic
void describe() const { cout << "Some vehicle" << endl; }
};
class Bus : public Vehicle {
public:
double fare(double km) const { return km <= 5 ? 20 : 30; }
void describe() const { cout << "Sajha Bus" << endl; }
};
class Taxi : public Vehicle {
public:
double fare(double km) const { return 14 + 45 * km; }
void describe() const { cout << "Metered taxi" << endl; }
};
int main() {
Bus bus;
Taxi taxi;
Vehicle* vp;
vp = &bus;
vp->describe();
cout << "Fare for 8 km: Rs. " << vp->fare(8) << endl;
vp = &taxi;
vp->describe();
cout << "Fare for 8 km: Rs. " << vp->fare(8) << endl;
return 0;
}Some vehicle
Fare for 8 km: Rs. 0
Some vehicle
Fare for 8 km: Rs. 0This is not what we wanted. The pointer points to a Bus
and then a Taxi, but the Vehicle versions ran
both times. Why? The compiler decides which function to call by looking
at the type of the pointer (Vehicle*), not
the type of the object it points to. The decision is made at compile
time. This is called early binding or static
binding. It is fast, but it ignores the real object.
If we write the keyword virtual before a member function
in the base class, C++ decides which version to call at run
time, by looking at the actual object the
pointer or reference refers to. This is late binding
(dynamic binding), and it gives us run-time
polymorphism.
In the derived class we write the keyword override
(available since C++11) after the parameter list. It tells the compiler:
“I intend to override a virtual function from the base class.” If we
make a mistake (wrong name, wrong parameters, missing
const), the compiler reports an error instead of silently
creating a new function.
#include <iostream>
#include <string>
using namespace std;
class Vehicle {
protected:
string number;
public:
Vehicle(const string& n) : number(n) {}
virtual double fare(double km) const { return 0; }
virtual void describe() const { cout << "Vehicle " << number; }
void printBill(double km) const { // NOT virtual itself
describe(); // but calls virtuals
cout << " -> Rs. " << fare(km) << endl;
}
};
class Bus : public Vehicle {
public:
Bus(const string& n) : Vehicle(n) {}
double fare(double km) const override { return km <= 5 ? 20 : 30; }
void describe() const override { cout << "Bus " << number; }
};
class Taxi : public Vehicle {
public:
Taxi(const string& n) : Vehicle(n) {}
double fare(double km) const override { return 14 + 45 * km; }
void describe() const override { cout << "Taxi " << number; }
};
class Bike : public Vehicle {
public:
Bike(const string& n) : Vehicle(n) {}
double fare(double km) const override { return 25 * km; }
// describe() not overridden: Vehicle's version is used
};
int main() {
Bus bus("Ba 1 Kha 2233");
Taxi taxi("Ba 3 Ja 1010");
Bike bike("Ba 88 Pa 777");
Vehicle* fleet[3] = { &bus, &taxi, &bike }; // one array!
double km = 8;
for (int i = 0; i < 3; i++) {
fleet[i]->printBill(km); // correct version each time
}
Vehicle& ref = taxi; // references work too
cout << "Via reference: Rs. " << ref.fare(2) << endl;
return 0;
}Bus Ba 1 Kha 2233 -> Rs. 30
Taxi Ba 3 Ja 1010 -> Rs. 374
Vehicle Ba 88 Pa 777 -> Rs. 200
Via reference: Rs. 104Look carefully at this program:
fleet[i]->printBill(km) produced three different
results. Tomorrow, if the office adds a Tempo class, this
loop does not change at all. We only write the new class. This is the
real power of polymorphism: code that is open for
extension without editing old, tested code.printBill() is an ordinary function in the base class,
but because it calls the virtual functions
describe() and fare(), the derived versions
are used. (Compare with the displayAsEmployee() surprise in
Constructors, Destructors and Function Overriding in
Inheritance.)Bike did not override describe(), so the
base version was used. Overriding a virtual function is optional.Rules for virtual functions:
virtual is written in the base class. The function
stays virtual in all derived classes automatically (writing
virtual again in the derived class is allowed but not
needed; write override instead).const-ness (and a
compatible return type).bus.fare(8)) is already exact; there is nothing to decide
at run time.class Bus : public Vehicle {
public:
Bus(const string& n) : Vehicle(n) {}
// mistake: forgot "const", so this does NOT match the base function
double fare(double km) override { return km <= 5 ? 20 : 30; }
};error: 'double Bus::fare(double)' marked 'override', but does not
override
Without override, this code would compile, but
fleet[i]->fare(8) would silently call
Vehicle::fare() and return 0 — a hidden logic error that is
hard to find. Always write override on
overriding functions. It turns a logic error into a compile error.
The C++ standard does not say exactly how virtual functions must be implemented, but almost all compilers (including g++) use the same idea:
vp->fare(8) becomes: “follow the
object’s vptr to its vtable, take the fare entry, and call
that function.”| vtable of | describe entry | fare entry |
|---|---|---|
| Vehicle | Vehicle::describe | Vehicle::fare |
| Bus | Bus::describe | Bus::fare |
| Taxi | Taxi::describe | Taxi::fare |
| Bike | Vehicle::describe (inherited) | Bike::fare |
fleet[1] ---> +-----------------+ Taxi's vtable
| vptr ----------+---> +-----------------+
| number | | &Taxi::describe |
+-----------------+ | &Taxi::fare |
Taxi object +-----------------+
The cost is small: one extra pointer in each object (so
sizeof grows, by typically 8 bytes on a 64-bit system; this
is compiler-dependent) and one extra indirection per call. For the
flexibility gained, this is almost always worth it.
| Feature | Early (static) binding | Late (dynamic) binding |
|---|---|---|
| Decided at | Compile time | Run time |
| Based on | Type of pointer/reference/variable | Type of actual object |
| Needs | Nothing special | virtual function + pointer or reference |
| Speed | Slightly faster | Slightly slower (vtable lookup) |
| Flexibility | Low | High |
| Examples | Overloaded functions, non-virtual calls, templates | Virtual function called via base pointer |
If you assign or pass a derived object by value to a base-class variable, only the base part is copied. The derived part is “sliced off”, and the copy really is a plain base object. Virtual functions then have nothing to choose from.
#include <iostream>
using namespace std;
class Animal {
public:
virtual void sound() const { cout << "..." << endl; }
};
class Dog : public Animal {
public:
void sound() const override { cout << "Bhau bhau!" << endl; }
};
void byValue(Animal a) { a.sound(); } // copy: sliced
void byReference(const Animal& a) { a.sound(); } // no copy
void byPointer(const Animal* a) { a->sound(); } // no copy
int main() {
Dog tiger;
cout << "byValue : "; byValue(tiger);
cout << "byReference: "; byReference(tiger);
cout << "byPointer : "; byPointer(&tiger);
return 0;
}byValue : ...
byReference: Bhau bhau!
byPointer : Bhau bhau!Rule: polymorphic objects should be passed by reference or pointer, never by value.
Run-time polymorphism lets many programmers work on one system: one
person writes the base class “contract”, and others write derived
classes that plug in. This only works if every derived class
honours the meaning of the base function. If
fare() in a derived class secretly returns a negative
number, every loop that trusts Vehicle breaks. Keeping
promises made by an interface is a professional and ethical
responsibility in team software.
virtual in the base class → base version
always called through pointers (silent logic error).const, different
parameter type) → a new function is created instead of an override. Use
override to catch it.Bus* bp = &someVehicle; to compile.
Converting base to derived is not automatic (and usually unsafe).vp->ticketCounter() → “class Vehicle has no member named
…”. The base pointer only knows base members.virtual in the base, override in the
derived class.override keyword do? What happens if the
signature does not match?#include <iostream>
using namespace std;
class A {
public:
void f() const { cout << "A::f "; }
virtual void g() const { cout << "A::g "; }
};
class B : public A {
public:
void f() const { cout << "B::f "; }
void g() const override { cout << "B::g "; }
};
int main() {
B b;
A* p = &b;
p->f();
p->g();
b.f();
cout << endl;
return 0;
}Circle derived from Shape (virtual
area() and name()) would contain if
Circle overrides only area().Notification with a virtual
send() and two derived classes SMS and
Email that override it. Call send() through an
array of base pointers.A::f B::g B::f (f is non-virtual, so
base version through A*; g is virtual). 4. Copying a
derived object into a base object by value loses the derived part; use
references or pointers. 5. area entry →
Circle::area, name entry →
Shape::name. 6.
class Notification { public: virtual void send() const { cout << "Generic\n"; } };
class SMS : public Notification { public: void send() const override { cout << "SMS sent\n"; } };
similarly Email;
SMS s; Email e; Notification* list[2] = {&s, &e}; for (auto p : list) p->send();In Run-time Polymorphism and Virtual Functions,
Vehicle::fare() returned 0. That was a “dummy” answer: a
general vehicle has no real fare. Similarly, what is the area of a
general “shape”? There is no formula; only a circle, rectangle or
triangle has an area. Giving such a function a fake body is misleading,
because someone might call it by mistake and receive a wrong answer.
C++ lets us declare a virtual function without a
body in the base class. It is called a pure virtual
function, and we write = 0 at the end of its
declaration:
class Shape {
public:
virtual double area() const = 0; // pure virtual function
};The = 0 does not mean “returns zero”. It means “this
class gives no implementation; every concrete derived
class must provide one.”
| Term | Definition | Can we create objects? |
|---|---|---|
| Abstract class | A class with at least one pure virtual function (declared or inherited and not yet overridden) | No |
| Concrete class | A class with no pure virtual functions left | Yes |
| Interface (informal term) | An abstract class whose functions are all pure virtual and which has no data | No |
An abstract class is like the design rule for a family: “Every shape must be able to tell its area and perimeter.” It is a contract. The derived classes fulfil the contract. Think of the rule book for a Nepal Engineering Council licence exam: it says what every engineer must know, but the rule book itself is not an engineer.
Trying to create an object of an abstract class is a compile error:
#include <iostream>
using namespace std;
class Shape {
public:
virtual double area() const = 0;
};
int main() {
Shape s; // ERROR
return 0;
}error: cannot declare variable 's' to be of abstract type 'Shape'
note: because the following virtual functions are pure within 'Shape':
note: 'virtual double Shape::area() const'
But pointers and references to an abstract class are allowed and are exactly how we use it:
#include <iostream>
using namespace std;
class Shape { // abstract class
public:
virtual double area() const = 0;
virtual const char* name() const = 0;
void report() const { // normal function using the contract
cout << name() << " area = " << area() << endl;
}
};
class Square : public Shape { // concrete class
private:
double side;
public:
Square(double s) : side(s) {}
double area() const override { return side * side; }
const char* name() const override { return "Square"; }
};
class Circle : public Shape { // concrete class
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override { return 3.14159 * radius * radius; }
const char* name() const override { return "Circle"; }
};
void printTwice(const Shape& s) { // reference to abstract type: OK
s.report();
s.report();
}
int main() {
Square sq(4);
Circle c(1.5);
Shape* p = &sq; // pointer to abstract type: OK
p->report();
printTwice(c);
return 0;
}Square area = 16
Circle area = 7.06858
Circle area = 7.06858Important facts:
Polygon : public Shape that does not define
area() is still abstract;
Triangle : public Polygon can define it and become
concrete.report(). It is still a real class; only object creation is
forbidden.In real programs, polymorphic objects are often created with
new (see Dynamic Memory Allocation with new
and delete) because we decide at run time which kind of object to
make: for example, the user chooses “circle” or “rectangle” from a menu.
We store the result in a base-class pointer, and later we
delete it through that base pointer.
Suppose the derived class owns dynamic memory, and the base class destructor is not virtual:
#include <iostream>
using namespace std;
class Report {
public:
virtual void print() const = 0;
~Report() { cout << "~Report" << endl; } // NOT virtual
};
class MarksReport : public Report {
private:
int* marks;
public:
MarksReport(int n) : marks(new int[n]) {
cout << "MarksReport allocated " << n << " ints" << endl;
}
~MarksReport() {
delete[] marks;
cout << "~MarksReport freed the array" << endl;
}
void print() const override { cout << "Printing marks" << endl; }
};
int main() {
Report* r = new MarksReport(40);
r->print();
delete r; // deletes through a base pointer
return 0;
}g++ with -Wall already warns about this code:
warning: deleting object of abstract class type 'Report' which has
non-virtual destructor will cause undefined behavior
[-Wdelete-non-virtual-dtor]
When we ran it anyway, g++ 13 printed:
MarksReport allocated 40 ints
Printing marks
eport
~MarksReport never ran, so the array of
40 integers was never freed: a memory leak. Formally,
the C++ standard says that deleting a derived object through a base
pointer whose destructor is not virtual is undefined
behaviour — anything may happen, and a leak is only the most
common result.
The reason is the same as in Run-time Polymorphism and
Virtual Functions: delete r calls the destructor using
early binding, based on the pointer type
Report*.
Make the base-class destructor virtual. Then
delete r uses late binding: it calls the derived destructor
first, which then automatically calls the base destructor (the normal
reverse order from Constructors, Destructors and Function
Overriding in Inheritance).
Rule of thumb: If a class has any virtual
function, give it a virtual destructor. If there is nothing to
clean up in the base, write virtual ~Base() = default;
(C++11), which means “use the normal destructor, but make it
virtual”.
We now put everything together. The program asks nothing from the
keyboard; it builds a list of shapes at run time with new,
prints a table, finds the largest shape and the total area (for example,
to estimate the paint needed for a wall mural), and finally deletes
every object correctly.
Shape (abstract)
name(), area() = 0, perimeter() = 0,
virtual ~Shape()
/ | \
v v v
Circle Rectangle Triangle
(radius) (length, breadth) (a, b, c)
#include <iostream>
#include <iomanip>
#include <cmath>
#include <string>
using namespace std;
class Shape {
protected:
string label;
public:
Shape(const string& l) : label(l) {}
virtual ~Shape() { cout << " ~Shape(" << label << ")" << endl; }
virtual double area() const = 0;
virtual double perimeter() const = 0;
string getLabel() const { return label; }
};
class Circle : public Shape {
double radius;
public:
Circle(double r) : Shape("Circle"), radius(r) {}
~Circle() override { cout << " ~Circle" << endl; }
double area() const override { return 3.14159 * radius * radius; }
double perimeter() const override { return 2 * 3.14159 * radius; }
};
class Rectangle : public Shape {
double length, breadth;
public:
Rectangle(double l, double b)
: Shape("Rectangle"), length(l), breadth(b) {}
~Rectangle() override { cout << " ~Rectangle" << endl; }
double area() const override { return length * breadth; }
double perimeter() const override { return 2 * (length + breadth); }
};
class Triangle : public Shape {
double a, b, c;
public:
Triangle(double x, double y, double z)
: Shape("Triangle"), a(x), b(y), c(z) {}
~Triangle() override { cout << " ~Triangle" << endl; }
double area() const override { // Heron's formula
double s = (a + b + c) / 2;
return sqrt(s * (s - a) * (s - b) * (s - c));
}
double perimeter() const override { return a + b + c; }
};
int main() {
const int COUNT = 4;
Shape* shapes[COUNT];
shapes[0] = new Circle(2);
shapes[1] = new Rectangle(4, 3);
shapes[2] = new Triangle(3, 4, 5);
shapes[3] = new Circle(1);
cout << fixed << setprecision(2);
cout << left << setw(12) << "Shape" << right << setw(10) << "Area"
<< setw(12) << "Perimeter" << endl;
double total = 0;
int largest = 0;
for (int i = 0; i < COUNT; i++) {
cout << left << setw(12) << shapes[i]->getLabel() << right
<< setw(10) << shapes[i]->area()
<< setw(12) << shapes[i]->perimeter() << endl;
total += shapes[i]->area();
if (shapes[i]->area() > shapes[largest]->area()) largest = i;
}
cout << "Total area : " << total << endl;
cout << "Largest : " << shapes[largest]->getLabel() << endl;
cout << "Cleaning up:" << endl;
for (int i = 0; i < COUNT; i++) {
delete shapes[i]; // virtual destructor -> correct
shapes[i] = nullptr;
}
return 0;
}Shape Area Perimeter
Circle 12.57 12.57
Rectangle 12.00 14.00
Triangle 6.00 12.00
Circle 3.14 6.28
Total area : 33.71
Largest : Circle
Cleaning up:
~Circle
~Shape(Circle)
~Rectangle
~Shape(Rectangle)
~Triangle
~Shape(Triangle)
~Circle
~Shape(Circle)Every derived destructor ran, followed by the base destructor: no
leaks. (You can confirm with -fsanitize=address from Memory Errors, AddressSanitizer and Smart Pointers.)
Notice that ~Circle() override is allowed: once the base
destructor is virtual, derived destructors override it.
Modern alternative. In Memory Errors,
AddressSanitizer and Smart Pointers you met
std::unique_ptr, which deletes its object automatically.
unique_ptr<Shape> p = make_unique<Circle>(2);
calls the correct destructor only because
~Shape() is virtual. Smart pointers remove the need to
write delete, but they do not remove the need for a virtual
destructor.
| Declaration in base class | Meaning | Derived class must override? | Base objects allowed? |
|---|---|---|---|
void f(); |
Normal function, early binding | No (can hide) | Yes |
virtual void f(); |
Virtual, has default body | No (optional) | Yes |
virtual void f() = 0; |
Pure virtual, no body | Yes, to become concrete | No (abstract) |
virtual ~Base(); |
Virtual destructor | Automatic | Yes (unless abstract) |
Shape s;) → “cannot declare variable … of abstract
type”.new Circle(2).virtual double area() = 0; in the base but
double area() const override in the derived class:
const mismatch → the override error.delete[] shapes[i] when
each was created with plain new → use delete
for new and delete[] for
new[].= 0 and has no
implementation in the base class.virtual ~Base() = default;).#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base() { cout << "B "; }
virtual void hi() const = 0;
};
class Derived : public Base {
public:
~Derived() override { cout << "D "; }
void hi() const override { cout << "hi "; }
};
int main() {
Base* p = new Derived;
p->hi();
delete p;
cout << endl;
return 0;
}Polygon inherits from Shape (with
pure virtual area()) but does not define
area(). Can you write Polygon p;?
Explain.Square to the Shape case study. Write only
the class.virtual type name(params) = 0;. 2. Abstract: has at least
one pure virtual function, cannot be instantiated. Concrete: all
functions implemented, objects can be created. 3. delete
would use early binding and call only the base destructor; the derived
destructor would not run (resources leak) — formally undefined
behaviour. 4. hi D B. 5. No; Polygon is still
abstract because it inherits a pure virtual function without overriding
it. 6.
class Square : public Shape { double side; public: Square(double s) : Shape("Square"), side(s) {} double area() const override { return side * side; } double perimeter() const override { return 4 * side; } };Suppose we need a function that returns the larger of two values. With function overloading (see Default Arguments, Inline Functions and Function Overloading) we write:
int largest(int a, int b) { return (a > b) ? a : b; }
double largest(double a, double b) { return (a > b) ? a : b; }
char largest(char a, char b) { return (a > b) ? a : b; }The three bodies are identical; only the type
changes. If we find a bug, we must fix it three times. If we need
long or string later, we must copy it again.
This is exactly the kind of repetition that programmers want to
avoid.
A template is a pattern (a blueprint) from which the compiler generates functions or classes for any type we need. Writing code that works for many types is called generic programming. A good analogy is the shape cutter used for making sel roti or cookies: you design the cutter once, and then you can press out as many pieces as you want from different dough.
template <typename T>
T largest(T a, T b) {
return (a > b) ? a : b;
}template <typename T> announces a template with a
type parameter named T. (T is
only a name; it could be Type or Item.) The
older keyword class can be used instead of
typename: template <class T> means the
same thing.T is used like a normal type.largest(3, 7), the compiler sees two
int arguments, deduces
T = int, and generates (instantiates) an int
version. Calling largest(2.5, 1.5) generates a
double version. This happens at compile time, so templates
are a form of compile-time polymorphism.#include <iostream>
#include <string>
using namespace std;
template <typename T>
T largest(T a, T b) {
return (a > b) ? a : b;
}
template <typename T>
void swapValues(T& a, T& b) { // pass by reference (see Passing Data by Reference)
T temp = a;
a = b;
b = temp;
}
int main() {
cout << largest(45, 78) << endl; // T = int
cout << largest(3.75, 2.5) << endl; // T = double
cout << largest('K', 'P') << endl; // T = char
cout << largest(string("Sita"), string("Ram")) << endl;// T = string
// cout << largest(5, 2.5); // ERROR: T cannot be both int and double
cout << largest<double>(5, 2.5) << endl; // explicit: T = double
int x = 10, y = 20;
swapValues(x, y);
cout << "x = " << x << ", y = " << y << endl;
string first = "Kathmandu", second = "Pokhara";
swapValues(first, second);
cout << first << " " << second << endl;
return 0;
}78
3.75
P
Sita
5
x = 20, y = 10
Pokhara KathmanduNotes:
largest(string("Sita"), string("Ram")) compares strings
alphabetically, so "Sita" is larger. (If we wrote
largest("Sita", "Ram") without string(...),
T would be const char* and the function would
compare addresses, not text — a classic trap.)largest(5, 2.5) is an error because the compiler
deduces T = int from the first argument and
T = double from the second. We fix it by stating the type
explicitly: largest<double>(5, 2.5).largest needs >, so it also
works for our Student class from More
Operator Overloading — Friends, Stream and Comparison Operators,
which overloaded >. This is how operator overloading and
templates work together.A template may have several type parameters. It can also work on arrays, which is useful for the searching and sorting algorithms of the Arrays chapter.
#include <iostream>
#include <string>
using namespace std;
template <typename T>
T arraySum(const T arr[], int size) {
T total = T(); // T() gives 0 for numbers, "" for string
for (int i = 0; i < size; i++) {
total = total + arr[i];
}
return total;
}
template <typename T>
int linearSearch(const T arr[], int size, const T& key) {
for (int i = 0; i < size; i++) {
if (arr[i] == key) return i;
}
return -1;
}
template <typename K, typename V> // two type parameters
void showPair(const K& key, const V& value) {
cout << key << " -> " << value << endl;
}
int main() {
int marks[5] = {67, 82, 74, 90, 58};
double bills[3] = {1250.50, 980.25, 1500.00};
string parts[3] = {"Bhakta", "pur", " Durbar"};
cout << "Total marks : " << arraySum(marks, 5) << endl;
cout << "Total bills : Rs. " << arraySum(bills, 3) << endl;
cout << "Joined text : " << arraySum(parts, 3) << endl;
cout << "90 found at index " << linearSearch(marks, 5, 90) << endl;
cout << "pur found at index "
<< linearSearch(parts, 3, string("pur")) << endl;
showPair("Roll 12", 3.6);
showPair(101, string("Lalitpur"));
return 0;
}Total marks : 371
Total bills : Rs. 3730.75
Joined text : Bhaktapur Durbar
90 found at index 3
pur found at index 1
Roll 12 -> 3.6
101 -> LalitpurA class template is a blueprint for a class in which
one or more member types are left as parameters. Real examples: a “box
that holds one thing”, a “stack of things”, a “list of things”. The
logic of a stack is the same whether it holds ints or
strings.
template <typename T>
class Stack {
T items[50];
int top;
// ...
};
Stack<int> numbers; // T = int
Stack<string> names; // T = stringUnlike function templates, for class templates in C++17 we normally
write the type in angle brackets when creating objects:
Stack<int>. (C++17 can sometimes deduce it from
constructor arguments, but writing it clearly is better for
beginners.)
Member functions defined outside the class need the
template line again, and the class name is written with
<T>:
template <typename T>
bool Stack<T>::push(const T& value) { ... }Here is a complete stack class template. A stack is a “last in, first out” (LIFO) container, like a pile of plates in a hostel mess: you put a plate on top and take a plate from the top.
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class Stack {
private:
static const int CAPACITY = 5;
T items[CAPACITY];
int count;
public:
Stack() : count(0) {}
bool push(const T& value);
bool pop(T& out);
bool isEmpty() const { return count == 0; }
int size() const { return count; }
};
template <typename T>
bool Stack<T>::push(const T& value) {
if (count == CAPACITY) return false; // full
items[count] = value;
count++;
return true;
}
template <typename T>
bool Stack<T>::pop(T& out) {
if (count == 0) return false; // empty
count--;
out = items[count];
return true;
}
int main() {
Stack<int> pages;
for (int p = 1; p <= 6; p++) {
if (!pages.push(p * 10))
cout << "Stack full, could not push " << p * 10 << endl;
}
cout << "Int stack size: " << pages.size() << endl;
int value;
while (pages.pop(value)) cout << value << " ";
cout << endl;
Stack<string> visited;
visited.push("Thamel");
visited.push("Patan");
visited.push("Bhaktapur");
string place;
cout << "Going back: ";
while (visited.pop(place)) cout << place << " <- ";
cout << "start" << endl;
return 0;
}Stack full, could not push 60
Int stack size: 5
50 40 30 20 10
Going back: Bhaktapur <- Patan <- Thamel <- startThe compiler generated two separate classes,
Stack<int> and Stack<string>, from
one template. Note that template code (including member function
definitions) is normally placed completely in a header
file when a program has several files, because the compiler
needs the full template to generate each version.
The C++ Standard Template Library (STL) is a large collection of ready-made, well-tested templates that come with every C++ compiler. It has three main parts:
| Part | What it is | Examples |
|---|---|---|
| Containers | Class templates that store collections of data | vector, list, map,
set, stack, queue |
| Algorithms | Function templates that work on containers | sort, find, count,
reverse, max_element |
| Iterators | Objects that point to elements, connecting containers and algorithms | v.begin(), v.end() |
You met std::vector briefly in Matrix
Operations, 2D Arrays in Functions, and std::vector as a safer
alternative to arrays. Now you can see what it really is: a
class template. vector<int> and
vector<string> are generated from the same template,
just like our Stack<T>. A vector grows automatically
(using dynamic memory internally, and freeing it in its destructor —
RAII from Memory Errors, AddressSanitizer and Smart
Pointers), so you never write new or
delete for it.
std::sort (in <algorithm>) is a
function template. It sorts any range whose elements
can be compared with <. We give it two iterators: where
to start (begin()) and one position past the end
(end()).
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
struct Student {
string name;
double percent;
};
// comparison used by sort: should a come before b?
bool higherPercent(const Student& a, const Student& b) {
return a.percent > b.percent;
}
int main() {
vector<int> marks = {67, 82, 74, 90, 58};
marks.push_back(71); // grows automatically
sort(marks.begin(), marks.end()); // ascending
cout << "Sorted marks: ";
for (int m : marks) cout << m << " "; // range-based for loop
cout << endl;
vector<string> cities = {"Pokhara", "Biratnagar", "Kathmandu",
"Dharan"};
sort(cities.begin(), cities.end()); // alphabetical
cout << "Cities: ";
for (const string& c : cities) cout << c << " ";
cout << endl;
vector<Student> cls = {{"Sita", 78.5}, {"Ram", 84.0},
{"Priya", 91.25}, {"Aarav", 66.0}};
sort(cls.begin(), cls.end(), higherPercent); // custom order
cout << "Rank list:" << endl;
for (size_t i = 0; i < cls.size(); i++) {
cout << " " << i + 1 << ". " << cls[i].name << " ("
<< cls[i].percent << "%)" << endl;
}
auto best = max_element(marks.begin(), marks.end());
cout << "Highest mark: " << *best << endl;
return 0;
}Sorted marks: 58 67 71 74 82 90
Cities: Biratnagar Dharan Kathmandu Pokhara
Rank list:
1. Priya (91.25%)
2. Ram (84%)
3. Sita (78.5%)
4. Aarav (66%)
Highest mark: 90Points to note:
for (int m : marks) is a range-based
for loop (C++11): “for each element m in
marks”. Use const string& to avoid copying
strings.sort(v.begin(), v.end()) needs < for
the element type. For Student we passed our own comparison
function higherPercent as a third argument instead.max_element returns an iterator
(something like a pointer) to the largest element, so we print
*best.cls.size() returns an unsigned type,
size_t, so the loop counter is size_t to avoid
a signed/unsigned comparison warning.Both give “one interface, many types”, but in different ways:
| Feature | Templates | Virtual functions |
|---|---|---|
| Kind of polymorphism | Compile-time | Run-time |
| Types must be related by inheritance? | No | Yes (common base class) |
| Decided when | Compilation | Program execution |
| Can one container mix different types? | No (vector<int> holds only int) |
Yes (array of Shape*) |
| Typical use | Containers, algorithms | Families of related objects |
template <typename T> line before
an out-of-class member function definition, or writing
Stack::push instead of
Stack<T>::push.largest(5, 2.5) with two different types →
“deduced conflicting types for parameter ‘T’”. Use
largest<double>(5, 2.5).largest on a class with no >) → long error
messages mentioning “no match for ‘operator>’”. Read the first line
of the error.largest("Ram", "Sita")) — compares addresses. Use
std::string.Stack s; without <type>
for our class template → error; write
Stack<int> s;.int as a loop counter with v.size()
→ -Wall warning “comparison of integer expressions of
different signedness”. Use size_t or a range-based for
loop.template <typename T> introduces a type
parameter; the compiler deduces or is told the actual type and generates
code at compile time.ClassName<Type>;
outside definitions need template <typename T> and
ClassName<T>::.vector) and
algorithm templates (sort, max_element)
connected by iterators.smallest(a, b).#include <iostream>
#include <string>
using namespace std;
template <typename T>
T twice(T x) {
return x + x;
}
int main() {
cout << twice(21) << " " << twice(1.25) << " "
<< twice(string("ab")) << endl;
return 0;
}largest(10, 3.5) fail to compile? Give two
ways to fix it.Pair<T> that stores two
values of type T and has a member function
bigger() returning the larger one.vector<int>, sorts them with std::sort,
and prints them.template <typename T> T smallest(T a, T b) { return (a < b) ? a : b; }
3. 42 2.5 abab (T is int,
double and string in turn; for strings
+ joins). 4. The compiler deduces T = int and
T = double (conflict). Fix:
largest<double>(10, 3.5) or
largest(10.0, 3.5). 5.
template <typename T> class Pair { T first, second; public: Pair(T a, T b) : first(a), second(b) {} T bigger() const { return (first > second) ? first : second; } };
6.
vector<int> v; int x; for (int i = 0; i < 5; i++) { cin >> x; v.push_back(x); } sort(v.begin(), v.end()); for (int n : v) cout << n << " ";Main memory (RAM) is volatile: its contents disappear when
the program ends or the power goes off. Secondary storage (hard disk,
SSD, pen drive) is non-volatile: data stays there until someone
deletes it. A file is a named collection of data stored
on secondary storage, for example marks.txt or
C:\Users\Sita\bill.dat.
We use files when:
In C++ all input and output is done through streams. A stream is a flow of bytes (characters) between the program and some device. Think of a water pipe: the program is at one end, and the keyboard, screen or file is at the other end. Data flows through the pipe one byte after another.
| Stream object | Direction | Connected to | Header |
|---|---|---|---|
cin |
input (into program) | keyboard | <iostream> |
cout |
output (out of program) | screen | <iostream> |
an ifstream object |
input | a file | <fstream> |
an ofstream object |
output | a file | <fstream> |
an fstream object |
input and output | a file | <fstream> |
The great news is that file streams behave exactly
like cin and cout. If you know how to
use << with cout, you already know how
to write to a file. If you know >> and
getline with cin, you already know how to read
from a file. The only new work is connecting the stream to a
file and checking that the connection worked.
The file-stream classes are related to
cin/cout by inheritance (see the Inheritance chapter). ifstream is derived
from istream (the class of cin), and
ofstream is derived from ostream (the class of
cout). This is why the same operators work.
ios
/ \
istream ostream
(cin) \ / (cout)
| iostream |
| | |
ifstream fstream ofstream
| Feature | Text file | Binary file |
|---|---|---|
| What is stored | human-readable characters | raw bytes, same as in memory |
| Example | marks.txt containing Sita 85 |
marks.dat |
| Open in Notepad? | yes, readable | shows “garbage” symbols |
| The number 12345 | 5 characters '1' '2' '3' '4' '5' |
typically 4 bytes (one int) |
| Main functions | <<, >>,
getline |
write(), read() |
| Portability | good, any program can read it | depends on compiler, sizes, byte order |
| Jump to record n | hard (lines have different lengths) | easy (all records same size) |
On Windows, text mode also converts the newline character
'\n' into the two bytes carriage-return + line-feed when
writing, and converts it back when reading. Binary mode does no
conversion at all. On Linux there is no difference in the bytes, but you
should still use the correct mode so your program works everywhere.
There are two ways to connect a stream to a file.
ofstream out1("result.txt"); // 1. constructor opens the file
ofstream out2; // 2. create first ...
out2.open("result.txt"); // ... then open laterThe file name is a string. It can be a simple name such as
"result.txt" (the file is created in the program’s
working directory, which in Code::Blocks is usually the project
folder) or a full path. In a C++ string, a Windows backslash must be
written twice: "C:\\data\\marks.txt". Forward slashes
"C:/data/marks.txt" also work on Windows.
The second argument of the constructor or open() is the
mode. Modes are constants in the class ios
and can be combined with the bitwise OR operator |.
| Mode | Meaning |
|---|---|
ios::in |
open for reading (default for ifstream) |
ios::out |
open for writing (default for ofstream); an existing
file is emptied |
ios::app |
append: every write goes to the end of the file |
ios::ate |
“at end”: after opening, move to the end (you may move back later) |
ios::trunc |
truncate: delete old contents when opening |
ios::binary |
binary mode: no newline conversion |
Important default behaviours to remember:
ofstream f("a.txt"); creates a.txt if it
does not exist and erases it if it does exist.ofstream f("a.txt", ios::app); creates the file if
needed and keeps the old contents; new data is added at
the end.ifstream f("a.txt"); never creates a file. If the file
is missing, opening fails.fstream f("a.txt", ios::in | ios::out); opens an
existing file for both reading and writing. It does not
create a missing file; opening fails instead.Opening can fail for many reasons: the file does not exist, the name
is misspelled, the folder does not exist, or you do not have permission.
Always check. A stream object can be tested like a
bool: it is true when everything is fine and
false after an error.
ifstream fin("marks.txt");
if (!fin) { // same as: if (!fin.is_open())
cout << "Error: cannot open marks.txt\n";
return 1; // stop main with an error code
}Each stream also has state functions:
| Function | Returns true when |
|---|---|
is_open() |
the stream is connected to a file |
good() |
no error of any kind has happened |
eof() |
a read tried to go past the end of the file |
fail() |
the last operation failed (e.g. reading text into an
int, or end of file) |
bad() |
a serious error happened (e.g. disk failure) |
clear() |
(not a test) resets the error flags so the stream can be used again |
Call close() when you have finished with a file. Closing
flushes the stream, which means any data still waiting in the
stream’s memory buffer is really written to the disk, and it releases
the file so other programs can use it. If you forget, the destructor
(see Destructors and Classes that Manage Dynamic
Memory) of the stream object closes the file automatically when the
object goes out of scope. This is the RAII idea from Memory Errors, AddressSanitizer and Smart Pointers: the
object owns the resource and cleans it up. Still, calling
close() yourself is good practice, especially if you want
to reopen the same file in the same function.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
// Step 1: write to the file
ofstream fout("greeting.txt");
if (!fout) {
cout << "Cannot create greeting.txt\n";
return 1;
}
fout << "Namaste from Kathmandu!\n";
fout << "Year: " << 2026 << '\n';
fout.close();
cout << "File written.\n";
// Step 2: read the same file back
ifstream fin("greeting.txt");
if (!fin) {
cout << "Cannot open greeting.txt\n";
return 1;
}
string line;
while (getline(fin, line)) {
cout << "Read: " << line << '\n';
}
fin.close();
return 0;
}File written.
Read: Namaste from Kathmandu!
Read: Year: 2026The program creates greeting.txt in its working folder.
Open that file in Notepad after running: you will see exactly the two
lines that were written.
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ifstream fin("no_such_file.txt");
cout << boolalpha;
cout << "is_open(): " << fin.is_open() << '\n';
cout << "fail(): " << fin.fail() << '\n';
if (!fin) {
cout << "Error: no_such_file.txt could not be opened.\n";
cout << "Check the name and the folder.\n";
return 1;
}
cout << "This line is not reached.\n";
return 0;
}is_open(): false
fail(): true
Error: no_such_file.txt could not be opened.
Check the name and the folder.boolalpha (from the family of manipulators in Input and Output with cin, cout, getline and iomanip)
prints true/false instead of
1/0. The program returns 1 from
main, which tells the operating system that it ended with
an error.
ios::out and ios::app#include <iostream>
#include <fstream>
#include <string>
using namespace std;
void showFile(const string& name) {
ifstream fin(name);
string line;
cout << "--- " << name << " ---\n";
while (getline(fin, line)) {
cout << line << '\n';
}
}
int main() {
ofstream f1("log.txt"); // ios::out: start empty
f1 << "Line A\n";
f1.close();
ofstream f2("log.txt", ios::app); // append: keep Line A
f2 << "Line B\n";
f2.close();
showFile("log.txt");
ofstream f3("log.txt"); // ios::out again: erases!
f3 << "Line C\n";
f3.close();
showFile("log.txt");
return 0;
}--- log.txt ---
Line A
Line B
--- log.txt ---
Line CBeginners often say “the program ran but I cannot find the file”. A
relative name such as "marks.txt" is created in the
working directory. In Code::Blocks this is normally the
project folder (the folder containing the .cbp file), not
the bin\Debug folder. In VS Code it is the folder from
which the program is run. When in doubt, use a full path once to
check.
#include <fstream>. The compiler then
says 'ifstream' was not declared in this scope or “variable
has initializer but incomplete type”.ofstream when you meant to add data. Wrong:
ofstream f("log.txt"); (erases old data). Right:
ofstream f("log.txt", ios::app);."C:\data\marks.txt" (\d and \m
are treated as escape sequences). Right:
"C:\\data\\marks.txt" or
"C:/data/marks.txt".ifstream or fstream with
ios::in to create a missing file. They do not.cin
and cout.ifstream reads, ofstream writes,
fstream can do both. All are in
<fstream>.ios::out erases, ios::app appends,
ios::binary disables newline conversion. Combine modes with
|.if (!fin) { ... }.close() flushes and releases the file; the destructor
also closes it automatically.What is the difference between volatile and non-volatile storage? Why does this make files necessary?
Which class would you use to (a) read a file, (b) write a new file, (c) read and update the same file?
A file data.txt contains the word
Hello. What does it contain after this code runs?
ofstream f("data.txt", ios::app);
f << "World";
f.close();Why is the number 12345 stored in 5 bytes in a text
file but typically 4 bytes in a binary file?
Write a statement that opens bill.txt for appending
in binary mode.
Write a short program that tries to open input.txt
for reading and prints "OK" or
"Missing".
ifstream, (b) ofstream, (c)
fstream with ios::in | ios::out. 3.
HelloWorld (append adds at the end, with no space or
newline). 4. Text stores each digit as a character (1 byte each); binary
stores the int in its memory form, typically 4 bytes on
modern systems. 5.
ofstream f("bill.txt", ios::app | ios::binary); 6.
ifstream f("input.txt"); if (f) cout << "OK\n"; else cout << "Missing\n";
inside int main() with <fstream> and
<iostream> included.Writing to a text file is the same as writing to cout.
Everything you know from Input and Output with cin, cout,
getline and iomanip, including setw, fixed
and setprecision, works on an ofstream.
When you plan a data file, decide on a format first, because the program that reads the file must know exactly how it was written. Two simple formats are:
| Format | Example line | Read with |
|---|---|---|
| space-separated | Sita 85 90 |
fin >> name >> m1 >> m2 |
| comma-separated (CSV) | Sita Sharma,85,90 |
getline(fin, name, ',') then >> |
Space-separated is easiest, but a name containing a space
(Sita Sharma) would be split into two words. CSV
(“comma-separated values”) solves this and can also be opened directly
in Excel.
The program below writes four students’ marks to a file, then opens
the file again and reads the data back with >>.
#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>
using namespace std;
int main() {
// ---- Write ----
ofstream fout("marks.txt");
if (!fout) {
cout << "Cannot create marks.txt\n";
return 1;
}
fout << "Sita 85\n" << "Ram 72\n" << "Aarav 91\n" << "Priya 66\n";
fout.close();
// ---- Read ----
ifstream fin("marks.txt");
if (!fin) {
cout << "Cannot open marks.txt\n";
return 1;
}
string name;
int mark;
int count = 0, total = 0;
cout << left << setw(10) << "Name" << "Marks\n";
while (fin >> name >> mark) { // stops at end of file
cout << left << setw(10) << name << mark << '\n';
total += mark;
count++;
}
fin.close();
if (count > 0) {
cout << fixed << setprecision(2);
cout << "Students: " << count << ", Average: "
<< static_cast<double>(total) / count << '\n';
}
return 0;
}Name Marks
Sita 85
Ram 72
Aarav 91
Priya 66
Students: 4, Average: 78.50Look carefully at the loop condition
while (fin >> name >> mark). The expression
fin >> name >> mark does two things:
fin itself, which is then tested
as a bool. It is true only if the reads
succeeded.So the loop body runs only when fresh data was really read. When the
end of the file is reached, the read fails, the condition becomes
false, and the loop stops. The same pattern works for
lines: while (getline(fin, line)).
Many old textbooks show this loop instead:
while (!fin.eof()) { // WRONG pattern
fin >> mark;
... // uses mark even if the read failed
}The problem is that eof() becomes true only
after a read has already tried to go past the end. The loop
checks first and reads second, so it runs one extra
time with a failed read. Let us prove it.
while (!fin.eof()) gives wrong answers#include <iostream>
#include <fstream>
using namespace std;
int main() {
ofstream fout("numbers.txt");
fout << "10 20 30\n"; // three numbers, then a newline
fout.close();
// Wrong loop
ifstream fin1("numbers.txt");
int x, count = 0, sum = 0;
while (!fin1.eof()) {
fin1 >> x;
count++;
sum += x;
}
cout << "Wrong loop: count = " << count << ", sum = " << sum << '\n';
// Correct loop
ifstream fin2("numbers.txt");
count = 0;
sum = 0;
while (fin2 >> x) {
count++;
sum += x;
}
cout << "Right loop: count = " << count << ", sum = " << sum << '\n';
return 0;
}Wrong loop: count = 4, sum = 90
Right loop: count = 3, sum = 60After reading 30, the newline is still unread, so
eof() is still false. The loop runs a fourth
time. Now fin1 >> x reaches the end of the file while
skipping the newline, so the read fails and x is left
unchanged (it still holds 30). The body then adds
30 a second time and counts it, giving count 4 and sum 90.
The “average” would be 90 / 4 = 22.5 instead of 20. The rule is simple:
put the read inside the loop condition.
getlinegetline(fin, str) reads a whole line, including spaces,
and throws away the '\n'. A third argument changes the
stopping character: getline(fin, str, ',') reads up to the
next comma.
When a line mixes text and numbers, a good beginner method is:
getline(fin, name, ',');fin >> mark;'\n') with
fin.ignore(1000, '\n') before the next
getline.This is the same >>-then-getline
problem you met with cin in Input and Output
with cin, cout, getline and iomanip and The
std::string Class.
To add records without destroying old ones, open with
ios::app. This is how log files, attendance registers and
transaction histories grow day after day.
#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>
using namespace std;
struct Student {
string name;
int marks;
};
void addStudent(const string& file, const Student& s) {
ofstream fout(file, ios::app); // keep existing records
if (!fout) {
cout << "Cannot open " << file << '\n';
return;
}
fout << s.name << ',' << s.marks << '\n';
}
void showAll(const string& file) {
ifstream fin(file);
if (!fin) {
cout << "No records yet.\n";
return;
}
string name;
int marks;
int n = 0;
while (getline(fin, name, ',') && fin >> marks) {
fin.ignore(1000, '\n'); // skip end of line
n++;
cout << setw(2) << n << ". " << left << setw(15) << name
<< right << setw(4) << marks
<< (marks >= 40 ? " Pass" : " Fail") << '\n';
}
}
int main() {
const string file = "students.csv";
ofstream(file).close(); // start with an empty file
addStudent(file, {"Sita Sharma", 85});
addStudent(file, {"Ram Thapa", 38});
cout << "After first session:\n";
showAll(file);
addStudent(file, {"Priya Gurung", 77}); // a later session
cout << "\nAfter appending one more:\n";
showAll(file);
return 0;
}After first session:
1. Sita Sharma 85 Pass
2. Ram Thapa 38 Fail
After appending one more:
1. Sita Sharma 85 Pass
2. Ram Thapa 38 Fail
3. Priya Gurung 77 PassThe line ofstream(file).close(); creates a temporary
ofstream (which empties the file) and closes it at once. We
do it only so that the demonstration starts fresh every time; a real
program would not erase its records. The file students.csv
now contains:
Sita Sharma,85
Ram Thapa,38
Priya Gurung,77
This is the idea behind the “word count” feature in a word processor.
We read line by line; the number of lines is the number of successful
getline calls, the characters are the lengths of the lines,
and the words are counted with a stringstream (from
<sstream>), which lets us use >>
on a string exactly like on a file.
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
using namespace std;
int main() {
ofstream fout("poem.txt");
fout << "Kathmandu is a valley\n"
<< "surrounded by green hills\n"
<< "\n"
<< "Namaste to all\n";
fout.close();
ifstream fin("poem.txt");
if (!fin) {
cout << "Cannot open poem.txt\n";
return 1;
}
string line, word;
int lines = 0, words = 0, chars = 0;
while (getline(fin, line)) {
lines++;
chars += line.length(); // newline not counted
stringstream ss(line);
while (ss >> word) {
words++;
}
}
cout << "Lines: " << lines << '\n';
cout << "Words: " << words << '\n';
cout << "Characters (without newlines): " << chars << '\n';
return 0;
}Lines: 4
Words: 11
Characters (without newlines): 60Note that the empty third line is counted as a line but contributes
no words. You can also read one character at a time with
fin.get(ch), which does not skip spaces or newlines; that
is useful for tasks like counting vowels.
| Code | Reads | Skips leading spaces? | Stops at |
|---|---|---|---|
fin >> x |
one word or number | yes | space, tab or newline |
getline(fin, s) |
a whole line | no | '\n' (removed) |
getline(fin, s, ',') |
up to a comma | no | ',' (removed) |
fin.get(ch) |
one character | no | after one character |
while (!fin.eof()). It runs one extra time.
Right: while (fin >> x) or
while (getline(fin, line)).>>.
fin >> name on Sita Sharma,85 reads only
Sita. Use getline with a delimiter.fin.ignore(...) after >>
and before getline, so getline reads an empty
rest-of-line.fout << name << marks; produces
Sita85, which cannot be read back reliably. Always write a
space, comma or newline between fields.ofstream at the start of
every run and losing all old records. Use ios::app for
adding.Sita,85 but reading with
fin >> name >> marks).getline(fin, s, ',') is the easy way to read CSV fields
that may contain spaces.ios::app adds records; plain ofstream
erases them.setw, fixed,
setprecision exactly like cout.stringstream lets you split a line into words with
>>.Why does while (fin >> x) stop at the right
time, while while (!fin.eof()) does not?
The file a.txt contains 5 7 9. What
does this program print?
ifstream fin("a.txt");
int x, total = 0;
while (fin >> x) total += x * 2;
cout << total;A file line is Hari Bahadur,56. Which statements
read the name and the marks correctly?
What is the difference between fin >> ch and
fin.get(ch) for a char ch?
Write a program that appends today’s electricity reading (for
example Baisakh 245) to meter.txt and then
prints the whole file.
Modify Example 4 to also count how many lines are empty.
eof() becomes true only after a
failed read, so the second runs once too many. 2. 42
((5+7+9) × 2). 3.
getline(fin, name, ','); fin >> marks; 4.
>> skips spaces/newlines; get reads
every character including spaces and '\n'. 5. Open
ofstream f("meter.txt", ios::app), write
"Baisakh 245\n", close, then open ifstream and
loop
while (getline(fin, line)) cout << line << '\n';.
6. Add int empty = 0; and inside the loop
if (line.empty()) empty++; (for Example 4 the answer is
1).A text file is like an audio cassette: to hear the fifth song you
must pass through the first four. This is sequential
access. Lines in a text file have different lengths
(Ram 72 is shorter than Aarav Karki 91), so we
cannot calculate where the fifth line starts.
A binary file of fixed-size records is like a music playlist on your
phone: you can jump straight to song number five. If every record is
exactly 28 bytes, record number n (counting from 0) starts
at byte n * 28. This is random access
(also called direct access). Banks, library systems and
exam-result systems use this idea to find one record among thousands
quickly.
struct Student {
int roll;
char name[20]; // fixed-size char array, NOT std::string
float marks;
};Why not std::string? A string object does
not contain the characters themselves; it holds a pointer to
memory on the heap (see Dynamic Memory Allocation with
new and delete) where the characters live. If we copy the bytes of a
string object to a file, we save a memory address, not the
name. When another run of the program reads that address back, it points
to memory that no longer belongs to us, which is undefined behaviour and
usually a crash. So a record that is written with write()
must contain only plain data: numbers, char,
bool, fixed-size arrays of these, and nested structures of
these. (The technical C++ term is a trivially copyable type.)
The same rule applies to a class: an object of a class with only such
data members and ordinary member functions can be written this way, but
not one with pointers, string/vector members
or virtual functions.
write() and
read()fout.write(reinterpret_cast<const char*>(&s), sizeof(s));
fin.read(reinterpret_cast<char*>(&s), sizeof(s));| Part | Meaning |
|---|---|
&s |
address of the record in memory |
reinterpret_cast<const char*>(...) |
“treat this address as the address of a sequence of bytes”;
write and read work with bytes
(char) |
sizeof(s) |
how many bytes to copy |
reinterpret_cast is a powerful and dangerous cast; here
it is the standard, correct way to view an object as raw bytes.
write copies sizeof(s) bytes from memory to
the file; read copies them from the file back into memory.
No conversion to text happens, which is why binary I/O is fast and why
numbers keep their exact value.
Always open binary files with ios::binary. Without it,
on Windows, any byte that happens to equal the newline code (10) would
be converted, which corrupts the data.
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;
struct Student {
int roll;
char name[20];
float marks;
};
int main() {
Student list[4] = {
{1, "Sita Sharma", 85.5f},
{2, "Ram Thapa", 72.0f},
{3, "Aarav Karki", 91.0f},
{4, "Priya Gurung", 66.5f}
};
ofstream fout("students.dat", ios::binary);
if (!fout) {
cout << "Cannot create students.dat\n";
return 1;
}
for (const Student& s : list) {
fout.write(reinterpret_cast<const char*>(&s), sizeof(s));
}
fout.close();
cout << "sizeof(Student) = " << sizeof(Student) << " bytes\n";
ifstream fin("students.dat", ios::binary);
if (!fin) {
cout << "Cannot open students.dat\n";
return 1;
}
Student s;
cout << fixed << setprecision(1);
while (fin.read(reinterpret_cast<char*>(&s), sizeof(s))) {
cout << setw(3) << s.roll << " " << left << setw(15) << s.name
<< right << setw(6) << s.marks << '\n';
}
return 0;
}sizeof(Student) = 28 bytes
1 Sita Sharma 85.5
2 Ram Thapa 72.0
3 Aarav Karki 91.0
4 Priya Gurung 66.5sizeof(Student) is typically 28 bytes (4 + 20 + 4) with
g++ on common PCs, but the compiler may add hidden padding
bytes between members to keep them aligned, so never assume a size:
always write sizeof. Because sizes, padding and byte order
can differ between compilers and machines, a binary file should be read
by a program compiled with the same structure on the same kind of
system. Text files are the better choice for sharing data between
different programs.
Notice the loop while (fin.read(...)): just like
while (fin >> x), the read is in the condition, so
the loop stops at end of file without processing a record twice.
seekg, seekp, tellg,
tellpEvery file stream keeps a position: the byte number where the next read or write will happen. You can think of it as the cursor in a text editor. An input stream has a get pointer (g) and an output stream has a put pointer (p).
| Function | Stream | Purpose |
|---|---|---|
tellg() |
input | returns the current get position (bytes from the start) |
tellp() |
output | returns the current put position |
seekg(pos) |
input | move the get pointer to absolute byte pos |
seekp(pos) |
output | move the put pointer to absolute byte pos |
seekg(offset, dir) |
input | move relative to dir |
seekp(offset, dir) |
output | move relative to dir |
The direction dir is one of:
| Direction | Meaning | Example | Result |
|---|---|---|---|
ios::beg |
from the beginning | seekg(56, ios::beg) |
byte 56 |
ios::cur |
from the current position | seekg(-28, ios::cur) |
back one record |
ios::end |
from the end | seekg(0, ios::end) |
just past the last byte |
For an fstream that is connected to a file, the get and
put pointers are really one shared file position, so moving one also
moves the other. Always call seekg or seekp
when you switch from reading to writing (or back); Example 2 does this
in every function.
A useful trick: seekg(0, ios::end) followed by
tellg() gives the file size in bytes.
Dividing by sizeof(Student) gives the number of
records.
students.dat (each record = 28 bytes)
byte: 0 28 56 84 112
+-----------+-----------+-----------+-----------+
| record 0 | record 1 | record 2 | record 3 |
| Sita | Ram | Aarav | Priya |
+-----------+-----------+-----------+-----------+
^
seekg(2 * sizeof(Student)) puts the pointer here
To update, we open the file with fstream in mode
ios::in | ios::out | ios::binary: we can read and write
without erasing the file. To change record n we move the
put pointer to n * sizeof(Student) and write the new record
over the old one. Nothing else in the file moves.
#include <iostream>
#include <fstream>
#include <cstring>
using namespace std;
struct Student {
int roll;
char name[20];
float marks;
};
const char* FILE_NAME = "result.dat";
void createFile() {
Student list[3] = {
{1, "Sita Sharma", 85.5f},
{2, "Ram Thapa", 38.0f},
{3, "Aarav Karki", 91.0f}
};
ofstream fout(FILE_NAME, ios::binary);
fout.write(reinterpret_cast<const char*>(list), sizeof(list));
}
int countRecords(fstream& f) {
f.seekg(0, ios::end);
return static_cast<int>(f.tellg() / sizeof(Student));
}
bool readRecord(fstream& f, int n, Student& s) {
if (n < 0 || n >= countRecords(f)) {
return false; // out of range
}
f.seekg(n * sizeof(Student), ios::beg);
f.read(reinterpret_cast<char*>(&s), sizeof(s));
return static_cast<bool>(f);
}
bool writeRecord(fstream& f, int n, const Student& s) {
f.seekp(n * sizeof(Student), ios::beg);
f.write(reinterpret_cast<const char*>(&s), sizeof(s));
return static_cast<bool>(f);
}
int main() {
createFile();
fstream file(FILE_NAME, ios::in | ios::out | ios::binary);
if (!file) {
cout << "Cannot open " << FILE_NAME << '\n';
return 1;
}
cout << "Records in file: " << countRecords(file) << '\n';
Student s;
if (readRecord(file, 1, s)) { // record 1 = the 2nd one
cout << "Before: " << s.roll << ' ' << s.name << ' '
<< s.marks << '\n';
s.marks = 45.0f; // re-totalling changed marks
writeRecord(file, 1, s);
}
if (readRecord(file, 1, s)) {
cout << "After: " << s.roll << ' ' << s.name << ' '
<< s.marks << '\n';
}
cout << "Position after reading record 1: " << file.tellg() << '\n';
if (!readRecord(file, 7, s)) {
cout << "Record 7 does not exist.\n";
}
// Rename record 2 using strncpy (keep space for '\0')
readRecord(file, 2, s);
strncpy(s.name, "Aarav K. Karki", sizeof(s.name) - 1);
s.name[sizeof(s.name) - 1] = '\0';
writeRecord(file, 2, s);
cout << "\nAll records now:\n";
for (int i = 0; i < countRecords(file); i++) {
readRecord(file, i, s);
cout << s.roll << ' ' << s.name << ' ' << s.marks << '\n';
}
return 0;
}Records in file: 3
Before: 2 Ram Thapa 38
After: 2 Ram Thapa 45
Position after reading record 1: 56
Record 7 does not exist.
All records now:
1 Sita Sharma 85.5
2 Ram Thapa 45
3 Aarav K. Karki 91Points to notice:
createFile() writes the whole array in one call,
because the three records lie next to each other in memory:
sizeof(list) is 3 × sizeof(Student).readRecord checks the range before seeking, so
asking for record 7 gives a clear message instead of garbage.strncpy (see C-Style Strings) copies
at most 19 characters and we add the '\0' ourselves, so the
fixed-size array can never overflow.| Situation | How to detect | What to do |
|---|---|---|
| file missing | if (!file) after opening |
print a message; maybe create a new empty file |
| read past end | read returns a stream that tests false;
gcount() gives bytes actually read |
stop the loop; treat a short record as damaged |
| record number out of range | compare with fileSize / sizeof(Record) |
reject the request |
| stream in fail state after EOF | file.fail() is true |
call file.clear() before
seekg/seekp again |
wrong mode (no ios::binary) |
data looks corrupt on Windows | always add ios::binary |
The clear() point is important in loops like “read all
records, then update one”: after the reading loop hits the end, the
stream is in a fail state and every later operation is ignored until you
call clear().
while (file.read(reinterpret_cast<char*>(&s), sizeof(s))) {
// ... search ...
}
file.clear(); // reset eof/fail flags first
file.seekp(found * sizeof(Student));
file.write(reinterpret_cast<const char*>(&s), sizeof(s));| Choose text when… | Choose binary when… |
|---|---|
| people need to read or edit the file | speed and exact numeric values matter |
| another program (Excel, Python) must read it | you need random access to fixed-size records |
| the data is small and simple | records are updated in place often |
std::string inside a record written with
write(). Wrong: struct S { string name; };.
Right: char name[20];.ios::binary, which silently corrupts data on
Windows.fout.write(&s, sizeof(s)); without the
cast. g++ reports
cannot convert 'Student*' to 'const char*'.sizeof(&s) (the size of
a pointer, typically 8) instead of sizeof(s).ofstream or
fstream + ios::out only. That truncates the
file. Use ios::in | ios::out | ios::binary.clear() after reaching end of file, then
wondering why seekg and write do nothing.seekg. Record numbers in the
formula n * sizeof(Student) start from 0, just like array
indices.write() and read() with
reinterpret_cast<char*> and sizeof.string,
vector, pointers or virtual functions) may be written this
way.n starts at byte
n * sizeof(Record); this makes random access possible.seekg/tellg are for reading,
seekp/tellp for writing; directions are
ios::beg, ios::cur,
ios::end.ios::in | ios::out | ios::binary to update
records in place.std::string member not be saved directly with
write()?seekg and
seekp?f.seekg(0, ios::end);, what does
f.tellg() return?k of an
fstream file and overwrite it with the variable
rec of type Item.string object holds a pointer to heap memory; saving
it writes an address, not the characters, and that address is
meaningless in a later run. 2. 224 / 32 = 7 records; record 4 starts at
byte 4 × 32 = 128. 3. seekg moves the get (read) pointer;
seekp moves the put (write) pointer. 4. The size of the
file in bytes. 5.
file.seekp(k * sizeof(Item), ios::beg); file.write(reinterpret_cast<const char*>(&rec), sizeof(rec));
6. 84 (3 × 28), assuming the typical 28-byte record.These three words are often mixed up, so we define them carefully.
| Term | Definition | Everyday analogy |
|---|---|---|
| Program | a file of instructions stored on disk (e.g. bill.exe);
it is passive, it does nothing by itself |
a recipe written in a cookbook |
| Process | a program that has been loaded into memory and is running; it has its own memory space, open files and at least one thread | a kitchen where someone is actually cooking that recipe |
| Thread | one independent sequence of execution inside a process; the smallest unit the operating system schedules on a CPU core | one cook working in that kitchen |
If you open Chrome twice you have one program but (at least) two processes. Inside each process there can be many threads.
The important relationship is:
new)
and open files. Each thread has only its own stack (its
own local variables and function calls) and its own position in the
code.+------------------------- Process -------------------------+
| Shared by all threads: code, global variables, heap, |
| open files |
| |
| +-----------+ +-----------+ +-----------+ |
| | Thread 1 | | Thread 2 | | Thread 3 | |
| | own stack | | own stack | | own stack | |
| | (locals) | | (locals) | | (locals) | |
| +-----------+ +-----------+ +-----------+ |
+-----------------------------------------------------------+
Sharing memory is what makes threads powerful (they can cooperate on the same data easily and cheaply) and also what makes them dangerous (they can damage each other’s data). Keep this picture in mind for the whole chapter.
Every C++ program you have written so far was a
single-threaded process: one thread, which starts in
main(). We call it the main thread.
| Concurrency | Parallelism | |
|---|---|---|
| Meaning | several tasks are in progress during the same period of time | several tasks are executing at the same instant |
| Needs multiple cores? | no; one core can switch between tasks quickly | yes |
| Analogy | one waiter serving five tables by moving between them | five waiters each serving one table |
| Main benefit | responsiveness, not waiting idle | speed, more work per second |
On a single core the operating system gives each thread a short time slice (a few milliseconds) and then switches to another. This context switching happens so fast that the tasks seem to run together; that is concurrency. On a multicore CPU, two threads can run on two cores truly simultaneously; that is parallelism. A multithreaded program gets concurrency on any computer and parallelism when cores are available.
Concurrency on 1 core: Parallelism on 2 cores:
Core 1: A A B B A A B B Core 1: A A A A A A A A
Core 2: B B B B B B B B
----- time -----> ----- time ----->
| Software | Threads doing different jobs |
|---|---|
| Web browser | one tab loads a page, another plays video, another downloads a file |
| Word processor | you type while spell-check and auto-save run in the background |
| Mobile banking app | the screen stays responsive while a transaction is sent to the server |
| Online game | graphics, sound, network and input handled by separate threads |
| Web server (e.g. an exam-result site on result day) | many students served at the same time |
| Video editor | rendering split across all cores |
To imitate slow work we use std::this_thread::sleep_for,
which pauses the current thread for a given time.
<chrono> provides time units such as
seconds and milliseconds, and a
steady_clock to measure elapsed time. Do not worry about
every detail of the syntax; the next lesson, Creating and
Managing Threads in C++, explains it fully.
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
void boilWater() {
cout << "Boiling water...\n";
this_thread::sleep_for(chrono::seconds(2)); // pretend work
cout << "Water ready.\n";
}
void cookRice() {
cout << "Cooking rice...\n";
this_thread::sleep_for(chrono::seconds(2));
cout << "Rice ready.\n";
}
int main() {
auto start = chrono::steady_clock::now();
boilWater(); // cookRice() waits until this finishes
cookRice();
auto end = chrono::steady_clock::now();
chrono::duration<double> taken = end - start;
cout << "Total time: " << static_cast<int>(taken.count() + 0.5)
<< " seconds\n";
return 0;
}Boiling water...
Water ready.
Cooking rice...
Rice ready.
Total time: 4 secondsThe rice waits for the water even though the two jobs do not depend on each other. That is 4 seconds for 2 seconds of real work each.
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
void boilWater() {
cout << "Boiling water...\n";
this_thread::sleep_for(chrono::seconds(2));
cout << "Water ready.\n";
}
void cookRice() {
cout << "Cooking rice...\n";
this_thread::sleep_for(chrono::seconds(2));
cout << "Rice ready.\n";
}
int main() {
auto start = chrono::steady_clock::now();
thread t1(boilWater); // starts running boilWater now
thread t2(cookRice); // starts running cookRice now
t1.join(); // main waits for t1 to finish
t2.join(); // main waits for t2 to finish
auto end = chrono::steady_clock::now();
chrono::duration<double> taken = end - start;
cout << "Total time: " << static_cast<int>(taken.count() + 0.5)
<< " seconds\n";
return 0;
}One real run is shown below; your order of lines may differ.
Cooking rice...
Boiling water...
Rice ready.
Water ready.
Total time: 2 secondsBoth jobs ran at the same time, so the total fell from 4 to 2 seconds. Run it several times: the order of the first four lines can change from run to run (for example “Cooking rice…” may appear first), and on some runs two messages may even be mixed on one line. The threads are scheduled by the operating system, and we do not control which one prints first. This unpredictability is normal in multithreaded programs.
Threads are not free and not always safe. The main risks are:
| Risk | What it means |
|---|---|
| Race condition | two threads use the same data at the same time and at least one changes it; the result depends on timing and can be wrong (see Shared Data, Race Conditions and Mutexes; Course Wrap-up) |
| Deadlock | two threads each wait for something the other holds, so both wait forever (like two cars facing each other on a one-lane bridge) |
| Non-deterministic bugs | an error appears only sometimes, so it is very hard to reproduce and debug |
| Overhead | creating and switching threads costs time and memory; too many threads can make a program slower |
| Harder design and testing | the programmer must think about all possible orders of execution |
A useful rule for beginners: use threads only when there is real benefit, keep shared data to a minimum, and protect all shared data that is modified.
Multithreading bugs are often invisible in testing and appear only under heavy real use, exactly when many people depend on the system: a bank on salary day, a college result site on result day, a ticket site when booking opens. A professional programmer does not hide the risk by saying “it worked on my computer”. It is our responsibility to design shared data carefully, test under load, and clearly report known limitations to the people who will rely on the software.
<thread> librarySince C++11, threads are part of standard C++. Everything we need is
in the header <thread>:
| Name | Purpose |
|---|---|
std::thread |
a class; each object represents one thread of execution |
t.join() |
the calling thread waits until thread t finishes |
t.detach() |
let t run on its own; we will never wait for it |
t.joinable() |
true if t still needs a
join() or detach() |
std::this_thread::get_id() |
ID of the thread that calls it |
std::this_thread::sleep_for(d) |
pause the current thread for duration d |
std::thread::hardware_concurrency() |
a hint: how many threads the hardware can run at once (may return 0 if unknown) |
On Linux and with MinGW g++, add the option -pthread so
that the thread support library is linked:
g++ -std=c++17 -Wall -pthread threads.cpp -o threads
-pthread (also add it in Other compiler
options)."-pthread" to the
args list in tasks.json.std::thread at all;
you get errors such as 'thread' is not a member of 'std'.
The fix is to install a MinGW-w64 build with the posix thread
model, for example the MinGW-w64 bundled with recent Code::Blocks
releases (the ...mingw-setup.exe download). Check with
g++ -v: the output should contain
Thread model: posix.void task(); // any function returning void (or anything)
thread t(task); // the new thread starts running task() NOW
... // main continues at the same time
t.join(); // wait here until task() has finishedNote that we write thread t(task); and not
thread t(task());. We pass the function
itself; the new thread will call it. Writing
task() would call it immediately in the main thread.
#include <iostream>
#include <thread>
using namespace std;
void printTable(int n) {
for (int i = 1; i <= 3; i++) {
cout << n << " x " << i << " = " << n * i << '\n';
}
}
int main() {
cout << "Main thread starts\n";
thread t1(printTable, 5); // function + one argument
thread t2([]() { // a lambda: a function
cout << "Hello from the lambda thread\n"; // written in place
});
t1.join();
t2.join();
cout << "Both threads finished; main ends\n";
return 0;
}One real run is shown below. Run it several times: the lines from the
two threads can come in a different order, and occasionally pieces of
two lines can be mixed together, because both threads write to
cout at the same time.
Main thread starts
5 x 1 = 5
5 x 2 = 10
5 x 3 = 15
Hello from the lambda thread
Both threads finished; main endsThe first and last lines are always in the same place: “Main thread
starts” is printed before any thread is created, and the last line is
printed only after both join() calls have returned.
Lambdas in one minute. A lambda is a small function without a name, written directly where it is needed. The simplest form is:
[]() {
// body
}[] is the capture list (empty here: the lambda
uses none of the surrounding variables), () is the
parameter list, and { } is the body. [&]
means “the lambda may use the surrounding variables by reference”. For
now, lambdas are just a convenient way to give a thread a short piece of
code.
join() and
detach()Every std::thread object that started a thread must be
either joined or detached before the
object is destroyed.
join() |
detach() |
|
|---|---|---|
| Meaning | “I will wait for you to finish” | “Run independently; I will never wait for you” |
| Calling thread | blocks (waits) until the thread ends | continues immediately |
| Can we get results safely? | yes, after join() returns |
no, we do not know when it ends |
| Typical use | almost always | background jobs such as logging that may run until the program exits |
| Danger | none (only waiting time) | if main ends, the detached thread is killed; if it uses
variables that were destroyed, undefined behaviour |
Beginners should use join() unless
there is a strong reason to detach.
If you forget both, the std::thread destructor calls
std::terminate() and the program crashes:
#include <iostream>
#include <thread>
using namespace std;
void greet() {
cout << "Hello from a thread\n";
}
int main() {
thread t(greet);
cout << "main is ending\n";
return 0; // t is still joinable here!
}main is ending
terminate called without an active exception
This program compiles without errors, which is why it is dangerous.
At run time it aborts (on Linux the shell adds Aborted; on
Windows you may see a crash message or a non-zero return code in
Code::Blocks). The fix is one line: t.join(); before
return 0;.
A thread can be joined or detached only once. Calling
join() twice throws an exception; check
t.joinable() if you are not sure.
Extra arguments after the function name are passed to the function:
thread t(printTable, 5); // calls printTable(5) in the new thread
thread u(printBill, "Sita", 850); // calls printBill("Sita", 850)Important: std::thread
copies every argument into the new thread (this
protects the thread from variables that might disappear). So a function
with a reference parameter would receive a reference to the
copy, not to your variable. g++ therefore refuses to compile
it:
#include <iostream>
#include <thread>
using namespace std;
void addBonus(int& salary, int bonus) {
salary += bonus;
}
int main() {
int salary = 30000;
thread t(addBonus, salary, 5000); // salary passed without std::ref
t.join();
cout << salary << '\n';
return 0;
}error: static assertion failed: std::thread arguments must be
invocable after conversion to rvalues
To pass a real reference, wrap the variable in
std::ref(...) (for a const reference,
std::cref(...)). Both are in the header
<functional>.
std::ref#include <iostream>
#include <thread>
#include <functional> // std::ref
using namespace std;
void addBonus(int& salary, int bonus) {
salary += bonus;
}
void sumRange(int from, int to, long long& result) {
result = 0;
for (int i = from; i <= to; i++) {
result += i;
}
}
int main() {
int salary = 30000;
thread t1(addBonus, ref(salary), 5000); // real reference
long long firstHalf = 0, secondHalf = 0;
thread t2(sumRange, 1, 500000, ref(firstHalf));
thread t3(sumRange, 500001, 1000000, ref(secondHalf));
t1.join();
t2.join();
t3.join(); // read results only AFTER join
cout << "Salary with bonus: Rs. " << salary << '\n';
cout << "Sum 1..1000000 = " << firstHalf + secondHalf << '\n';
return 0;
}Salary with bonus: Rs. 35000
Sum 1..1000000 = 500000500000This output is always the same, because each thread writes to its
own variable and main reads the variables only
after the join() calls. This “each thread has its own
result, combine after joining” pattern is the safest way to share work,
and we will use it again in Shared Data, Race Conditions
and Mutexes; Course Wrap-up.
We can keep many threads in a std::vector<thread>
(see Matrix Operations, 2D Arrays in Functions, and
std::vector and Templates and an Introduction to the
STL) and join them in a loop.
#include <iostream>
#include <thread>
#include <vector>
#include <string>
using namespace std;
void worker(int number) {
// Build the whole line first, then print it with one <<,
// so lines are less likely to get mixed.
string msg = "Worker " + to_string(number) + " is running\n";
cout << msg;
}
int main() {
unsigned int cores = thread::hardware_concurrency();
cout << "This computer can run " << cores
<< " threads at the same time.\n";
vector<thread> workers;
for (int i = 1; i <= 4; i++) {
workers.push_back(thread(worker, i));
}
for (thread& t : workers) {
t.join();
}
cout << "Main thread ID: " << this_thread::get_id() << '\n';
cout << "All workers done.\n";
return 0;
}One real run on a 2-core machine is shown below. On your PC the first line will show your own number of hardware threads (for example 4, 8 or 12), the worker lines may come in any order, and the thread ID is just an implementation-specific number that changes every run.
This computer can run 2 threads at the same time.
Worker 1 is running
Worker 2 is running
Worker 3 is running
Worker 4 is running
Main thread ID: 140481936623424
All workers done.Two details:
hardware_concurrency() counts hardware
threads: a 4-core CPU with hyper-threading reports 8. It is only a
hint and may be 0 if the system cannot tell, so programs should use a
default (such as 2) in that case.for (thread& t : workers) loop must use a
reference. A std::thread cannot be copied (two objects must
not control the same thread), so for (thread t : workers)
does not compile.#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
void autoSave() {
for (int i = 1; i <= 3; i++) {
this_thread::sleep_for(chrono::milliseconds(100));
cout << "Auto-save " << i << " done\n";
}
}
int main() {
thread saver(autoSave);
saver.detach(); // run in the background
cout << "Editing document...\n";
this_thread::sleep_for(chrono::milliseconds(350));
cout << "User closed the editor.\n";
return 0; // any still-running detached thread is stopped here
}Editing document...
Auto-save 1 done
Auto-save 2 done
Auto-save 3 done
User closed the editor.The main thread slept long enough (350 ms) for all three saves to
happen. If you change it to 150 ms, you will usually see only one save,
because the detached thread is killed when main returns.
This is why detaching is risky: nobody waits to make sure the work was
completed.
thread t(task()); instead of
thread t(task);.join() or detach(), which ends
in terminate called without an active exception.join(); the thread may
not have written it yet.std::ref; the code
does not compile (or, with a const reference parameter, silently works
on a copy).-pthread or using an old win32-model MinGW;
errors such as undefined reference to pthread_create or
'thread' is not a member of 'std'.thread t2 = t1; does not
compile; threads can only be moved.#include <thread>, create with
thread t(function, args...); and compile with
-pthread.thread
object is created.join() (or, rarely, detach()) every
thread before its object is destroyed.std::ref(x) to pass a
reference.hardware_concurrency() tells roughly how many threads
can run truly in parallel.What is the difference between join() and
detach()?
What will happen when this program runs, and how do you fix it?
int main() {
thread t([]() { cout << "Hi\n"; });
return 0;
}Why does thread t(addBonus, salary, 5000); fail to
compile when addBonus takes int&? Write
the corrected line.
What is printed by this program? Is the output always the same?
void square(int x, int& out) { out = x * x; }
int main() {
int r = 0;
thread t(square, 7, ref(r));
t.join();
cout << r << '\n';
}Write a program that creates 3 threads; thread i
prints the multiplication table of i + 1 up to 5. Join all
threads in a loop.
Why must the loop for (thread& t : workers) use
&?
join() makes the caller wait for the thread to finish;
detach() lets it run independently and it can no longer be
joined. 2. The thread is still joinable when t is
destroyed, so std::terminate is called and the program
aborts (after possibly printing Hi); add
t.join(); before return 0;. 3.
std::thread copies arguments and cannot bind a copy to a
non-const int&; use
thread t(addBonus, ref(salary), 5000);. 4. 49,
always, because r is read only after join().
5.
vector<thread> v; for (int i = 0; i < 3; i++) v.push_back(thread(printTable, i + 1)); for (thread& t : v) t.join();
with a printTable(int n) function looping 1–5 (output order
of lines varies). 6. std::thread cannot be copied; a loop
variable without & would try to copy each thread.In The Concept of Multithreading you saw that all threads of a process share global variables and heap memory. Sharing read-only data is completely safe: ten people can read the same notice board at once. The trouble starts when at least one thread writes to data that another thread reads or writes at the same time.
counter++ is not
one stepTo us, counter++ looks like a single action. For the CPU
it is three separate steps:
counter from memory
into a register.Suppose counter is 5 and two threads, A and B, both
execute counter++. The operating system may switch between
them at any moment, so this order is possible:
| Step | Thread A | Thread B | counter in memory |
|---|---|---|---|
| 1 | reads 5 | 5 | |
| 2 | reads 5 | 5 | |
| 3 | adds 1 → 6 | 5 | |
| 4 | adds 1 → 6 | 5 | |
| 5 | writes 6 | 6 | |
| 6 | writes 6 | 6 |
Two increments happened but the counter went up by only one. One update was lost. This is a race condition: the result depends on the unpredictable timing (who “wins the race”) of the threads. In C++, two threads accessing the same variable at the same time, with at least one writing and no synchronisation, is called a data race, and the standard says the behaviour is undefined. The program is simply wrong, even if it sometimes prints the right answer.
A real-life analogy: Sita and Ram share a bank account with Rs. 1,000. At the same moment, Sita deposits Rs. 500 at one branch and Ram deposits Rs. 300 at another. Both clerks read “1,000”; one writes 1,500, the other writes 1,300. Whichever is written last wins, and Rs. 500 or Rs. 300 disappears. Banks must prevent exactly this.
#include <iostream>
#include <thread>
using namespace std;
int counter = 0; // shared by both threads
void addMany() {
for (int i = 0; i < 1000000; i++) {
counter++; // NOT safe: read, add, write
}
}
int main() {
thread t1(addMany);
thread t2(addMany);
t1.join();
t2.join();
cout << "Expected: 2000000\n";
cout << "Actual: " << counter << '\n';
return 0;
}The result is different on every run. Below are four real runs of the same executable on a 2-core machine:
--- Run 1 ---
Expected: 2000000
Actual: 1035413
--- Run 2 ---
Expected: 2000000
Actual: 1116460
--- Run 3 ---
Expected: 2000000
Actual: 1440889
--- Run 4 ---
Expected: 2000000
Actual: 1047121On your computer the numbers will be different again. Occasionally
you may even get exactly 2000000, which is the most dangerous case: the
program looks correct during testing and fails later. With
optimisation on (-O2), the compiler may change the loop and
the symptoms may change, but the program is still incorrect.
std::mutexA critical section is a piece of code that uses
shared data and must not be executed by two threads at the same time. We
protect it with a mutex (short for mutual
exclusion), from the header <mutex>.
A mutex is like the single key to a shared room (think of the key to an office store room):
m.lock() takes the key. If another thread already has
it, this thread waits until the key is returned.m.unlock() returns the key so that one waiting thread
can take it.Only the thread holding the key may be inside the critical section, so the read–add–write steps of one thread can no longer be interleaved with another’s.
mutex m;
m.lock(); // enter critical section
counter++;
m.unlock(); // leave critical sectionCalling lock() and unlock() by hand is
risky. If the code between them executes return,
break, or throws an exception, unlock() is
skipped, the mutex stays locked forever, and every other thread waits
forever (a deadlock). The safe way is std::lock_guard,
which uses the RAII idea from Memory Errors,
AddressSanitizer and Smart Pointers and Destructors
and Classes that Manage Dynamic Memory: its constructor
locks the mutex and its destructor unlocks it
automatically when the guard goes out of scope.
{
lock_guard<mutex> lock(m); // constructor calls m.lock()
counter++;
} // destructor calls m.unlock()lock_guard#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int counter = 0;
mutex counterMutex; // protects counter
void addMany() {
for (int i = 0; i < 1000000; i++) {
lock_guard<mutex> lock(counterMutex); // lock here...
counter++;
} // ...unlocked here
}
int main() {
thread t1(addMany);
thread t2(addMany);
t1.join();
t2.join();
cout << "Expected: 2000000\n";
cout << "Actual: " << counter << '\n';
return 0;
}--- Run 1 ---
Expected: 2000000
Actual: 2000000
--- Run 2 ---
Expected: 2000000
Actual: 2000000
--- Run 3 ---
Expected: 2000000
Actual: 2000000Now the answer is correct every time. The price is speed: locking and unlocking 2,000,000 times costs time, and while one thread holds the lock the other must wait. Good design keeps critical sections small and locks as rarely as possible.
For a single integer counter, C++ also offers
std::atomic<int> (header
<atomic>), whose ++ is one indivisible
operation. Mutexes are more general: they can protect any group of
statements, such as updating a balance and writing a log line
together.
Now we combine everything into a useful pattern. To add 40 million
numbers with n threads:
n equal chunks (the last
chunk takes any remainder).main joins all threads and then reads the total.#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <functional>
#include <chrono>
using namespace std;
mutex totalMutex;
// Each thread adds its own part into a LOCAL variable,
// then adds that local result to the shared total once.
void partialSum(const vector<int>& data, size_t from, size_t to,
long long& total) {
long long local = 0;
for (size_t i = from; i < to; i++) {
local += data[i];
}
lock_guard<mutex> lock(totalMutex); // protect the shared total
total += local;
}
int main() {
const size_t N = 40000000; // 40 million numbers
vector<int> data(N);
for (size_t i = 0; i < N; i++) {
data[i] = i % 10; // 0,1,...,9,0,1,...
}
// ---- one thread ----
auto t0 = chrono::steady_clock::now();
long long single = 0;
partialSum(data, 0, N, single);
auto t1 = chrono::steady_clock::now();
// ---- several threads ----
unsigned int n = thread::hardware_concurrency();
if (n == 0) {
n = 2; // safe default
}
long long total = 0;
vector<thread> workers;
size_t chunk = N / n;
for (unsigned int k = 0; k < n; k++) {
size_t from = k * chunk;
size_t to = (k == n - 1) ? N : from + chunk; // last takes rest
workers.push_back(thread(partialSum, cref(data), from, to,
ref(total)));
}
for (thread& w : workers) {
w.join();
}
auto t2 = chrono::steady_clock::now();
using ms = chrono::milliseconds;
cout << "Single thread: sum = " << single << ", time = "
<< chrono::duration_cast<ms>(t1 - t0).count() << " ms\n";
cout << n << " threads: sum = " << total << ", time = "
<< chrono::duration_cast<ms>(t2 - t1).count() << " ms\n";
cout << (single == total ? "Results match." : "MISMATCH!") << '\n';
return 0;
}One real run on a 2-core machine is shown. The sums are always 180000000 (each group of ten numbers 0–9 adds to 45, and there are 4 million groups), but the times depend on your computer and change slightly on every run.
Single thread: sum = 180000000, time = 102 ms
2 threads: sum = 180000000, time = 61 ms
Results match.With 2 cores the best we can hope for is about half the single-thread time; how close a run gets depends on what else the computer is doing at that moment, so run it a few times. With 8 hardware threads you might see 3–6 times faster, not 8, because creating threads, sharing the memory bus and combining results all cost something. Notice also:
cref(data) passes the big vector by const reference;
without it, std::thread would copy all 40 million numbers
for every thread (and memory use would jump).n times in total, not 40
million times. Compare this with Example 2.vector<long long> partial(n)) and add the
slots after joining; then no mutex is needed at all.| Rule | Why |
|---|---|
| Prefer data that is not shared (locals, one result slot per thread) | no sharing means no race |
| If data is shared and modified, protect every access with the same mutex | one unprotected access is enough to cause a race |
Use lock_guard, not manual
lock()/unlock() |
unlocking cannot be forgotten |
| Keep critical sections short; never sleep or do I/O while holding a lock if you can avoid it | other threads are waiting |
| Join all threads before using their results | results may not be ready otherwise |
| Test many times, and on more than one machine | race bugs appear only sometimes |
Congratulations — you have reached the end of this tutorial. The table below shows how the 13 chapters build on each other. Use it to review: for each row, check that you can explain the key ideas and write one short program.
| Chapter | Big idea | Key tools and terms | Used later in |
|---|---|---|---|
| Introduction to Computers and C++ | how a program is designed, built and run | algorithm, flowchart, compiler, linker, types of errors, ethics | every chapter |
| Variables, Data Types, Operators and I/O | storing and calculating values | int, double, char,
bool, cin, cout,
iomanip, casts |
every chapter |
| Control Structures: Selection | making decisions | if, else if, switch,
&&, \|\| |
loops, menus, validation |
| Control Structures: Loops | repeating work | while, do-while, for,
break, continue, nested loops |
arrays, files, threads |
| Functions | splitting a program into modules | prototypes, pass by value/reference, overloading, recursion, scope | classes, threads (thread functions, std::ref) |
| Arrays | many values of one type | 1D/2D arrays, searching, sorting, vector intro |
strings, pointers, parallel sum |
| Strings and Structures | text and grouped data | C-strings, std::string, struct,
enum |
records in files, classes |
| Pointers and Dynamic Memory | addresses and the heap | &, *,
new/delete, leaks, unique_ptr,
RAII |
classes, reinterpret_cast in binary files,
lock_guard |
| Classes and Objects | data + functions together (OOP) | encapsulation, constructors, destructors, this,
static, friend |
inheritance, streams, std::thread objects |
| Inheritance | reusing and extending classes | base/derived, access modes, multiple inheritance, constructor order | stream class hierarchy (ifstream is an
istream) |
| Polymorphism and Templates | one interface, many forms; generic code | operator overloading, virtual functions, abstract classes, templates, STL | << and >> on files,
vector<thread>,
lock_guard<mutex> |
| Files and Operations | data that survives the program | ifstream, ofstream, fstream,
modes, EOF loop, read/write,
seekg/seekp |
real applications, projects |
| Multithreading | doing several tasks at once | std::thread, join, detach,
std::ref, race condition, mutex,
lock_guard |
modern high-performance and responsive software |
The same ideas appear again and again. For example, the single line
lock_guard<mutex> lock(m); uses a class
template (Polymorphism and Templates) whose constructor
and destructor (Classes and Objects) implement
RAII (Pointers and Dynamic Memory). The line
while (fin >> name >> marks) uses a
loop (Loops), an overloaded operator
(Polymorphism and Templates), a stream object (Classes
and Objects) whose class is derived from
istream (Inheritance), and a bool
conversion (Variables and Selection).
Basics, I/O --> Selection, loops --> Functions
|
+---------------------+--------------+
| |
Arrays, strings, Pointers
structs and memory
| |
+-------> Classes and objects <------+
|
Inheritance
|
Polymorphism,
templates, STL
|
+---------------+----------------+
| |
Files Multithreading
You now know the core of C++. Here are some sensible next steps:
#includes and int main().
For every topic, know one common mistake and how to fix it.vector; next explore array, map,
unordered_map, set, deque and the
algorithms in <algorithm> (sort,
find, count_if, and others).unique_ptr and shared_ptr over raw
new/delete, and learn the “rule of zero”.auto,
range-based for, lambdas, move semantics,
std::optional, and more of <thread>,
<mutex> and <future>.Programming is learned by doing — keep writing programs.
unlock() after a manual
lock() (for example on an early return). Use
lock_guard.cref/ref, causing a full copy for every
thread.counter++ is read–add–write, three steps that can
interleave.std::mutex allows only one thread at a time into a
critical section; std::lock_guard locks in its constructor
and unlocks in its destructor.counter++ once can leave counter increased by
only 1.lock_guard safer than calling
lock() and unlock() yourself?local
instead of adding directly to total?chunk = N / 4. How many numbers does the last thread add,
and why?return, break or an exception, so the mutex
cannot stay locked by mistake. 4. Locals are private to each thread, so
no locking is needed in the loop; the shared total is updated (with the
lock) only once per thread, which is much faster. 5. chunk = 250,000;
threads 0–2 add 750,000 numbers; the last thread adds from index 750,000
to N, i.e. 250,002 numbers, because it takes the remainder. 6. Any
reasonable answer, e.g. the file streams of the Files chapter are
classes (Classes and Objects) derived from
istream/ostream (Inheritance); the thread
functions and std::ref of the Multithreading chapter rely
on functions and references (Functions).