CS201 — Midterm Summary (Lectures 1–22)
📘 Lecture 1 — Why Programming is Important
📖 Overview: This introductory lecture establishes why learning to program is valuable for everyone, not just computer scientists. It outlines the essential skills needed for effective programming and introduces the concept of a program design recipe as a structured approach to problem-solving.
🗂️ Topics Covered
The lecture begins by explaining why programming is important, citing that it develops analytical and problem-solving abilities. It then discusses the specific skills a programmer needs, including paying attention to detail, thinking about reusability, designing good user interfaces, understanding computers are stupid, and commenting code liberally. Finally, it introduces the program design recipe as a structured methodology for developing programs effectively.
📝 Lecture Summary
Why Programming is important
Learning to program is valuable because it develops analytical and problem-solving abilities. It is a creative activity and provides a means to express abstract ideas. By designing programs, we learn skills that are important for all professions, including critical reading, analytical thinking, and creative synthesis. Programming is much more than a vocational skill; it is fun and useful for everyone, from administrative secretaries to high-tech programmers.
What skills are needed
A good programmer should cultivate several key skills. These include paying attention to detail, thinking about reusability, designing a good user interface, understanding that computers are stupid, and commenting the code liberally.
Paying attention to detail
In programming, details matter. A good programmer carefully analyzes the problem statement and pays attention to all aspects of the problem, including calculations, flow, and logic. It is possible for a program to be grammatically correct (compile and run) but still produce incorrect or absurd results if the logic is flawed. For example, the sentence "Mr. ABC sleeps thirty hours every day" is grammatically correct but illogical.
Think about the reusability
When writing a program, always keep in mind that it could be reused at some other time or to solve a related problem. A classic example is a program that calculates the area of a circle (Pi * r²). This program can later be reused to calculate the area of a ring by subtracting the area of the inner circle from the area of the outer circle.
📐 Formula: Area of a circle = π * r² → The area is calculated by multiplying pi by the square of the radius.
Think about Good user interface
Never assume that the user of your program is computer literate. Always provide an easy to understand and easy to use interface that is self-explanatory.
Understand the fact that computers are stupid
Computers are incredibly stupid; they do exactly what you tell them to do, no more and no less. Unlike humans, they cannot think for themselves. Instructions to the computer must be explicitly stated. For example, a human understands "What is the time?", "Time please?", or "Time?" as the same request, but a computer will only respond if asked in the exact way it was programmed. When programming, you must be able to "think" as stupidly as the computer to specify everything in minute detail.
💡 Why this matters: This concept is fundamental to debugging. Most programming errors occur because the programmer assumed the computer would do something that was not explicitly instructed.
Comment the code liberally
Always comment your code liberally. Comment statements are ignored by the compiler and do not take any memory. They explain the functioning of the program and help other programmers, as well as the original creator, understand the code.
🔑 Definition — Comments: Non-executable text in a program used to explain the code's logic and functionality.
Program design recipe
To design a program effectively, you must follow a design recipe. This involves:
- Analyze a problem statement, typically expressed as a word problem.
- Express its essence, abstractly and with examples.
- Formulate statements and comments in a precise language.
- Evaluate and revise the activities in light of checks and tests.
- Pay attention to detail.
📌 Example: Consider a payroll system for a company with permanent, contractual, hourly, and per-unit employees. First, analyze the problem to identify these four categories and their rules (e.g., benefits for permanent staff, bonuses for per-unit employees making more than 10 pieces, overtime for contractual employees). Next, divide the problem into small segments with examples, such as calculating the salary of Mr. Ahmad, a permanent Finance Manager earning Rs.20,000 with Rs.4,000 in benefits and Rs.1,200 in deductions. Then, formulate these statements using pseudo code and flowcharts. Finally, test and check the program, revising the process as needed to achieve a refined solution.
Points to remember
The major points to keep in mind are: don't assume anything on the part of the users, make the user interface friendly, don't forget to comment the code, pay attention to detail, and program—not just writing code, but the whole process of design and development.
⭐ Key Takeaways
Programming is a creative and analytical skill that is valuable for everyone. The most critical skills for a programmer are paying extreme attention to detail, understanding that computers execute instructions literally and need explicit commands, and writing reusable code. The program design recipe provides a structured process of analysis, formulation, and testing that is essential for developing correct and robust programs. Finally, programs must be well-commented for future understanding, and user interfaces must be designed for non-technical users.
🧠 Quick Revision Questions
- Why is learning to program important for people who are not planning to become professional programmers?
- What does it mean to say a program is "grammatically correct" but still incorrect?
- Provide an example of how code reusability can save development time.
- What is the key assumption a programmer should never make about computer users?
- List the five steps in the program design recipe.
📘 Lecture 2 — Software Categories, History of C, and Development Environment
📖 Overview: This lecture introduces the fundamental categories of software—system and application software—and explains their roles in computing. It then traces the historical development of the C programming language from its predecessors BCPL and B, and describes the essential tools (editor, compiler, linker, loader, debugger) used in the C programming development environment.
🗂️ Topics Covered
The lecture covers the two main categories of software: system software (including operating systems, device drivers, and utility programs) and application software designed for end users. It then discusses the history of the C language, tracing its development from BCPL and B languages at Bell Laboratories to its standardization by ANSI and ISO. Finally, the lecture describes the complete development environment for C programs, including editors, compilers vs. interpreters, debuggers, linkers, and loaders, illustrated with a step-by-step execution diagram.
📝 Lecture Summary
Software Categories
Software is categorized into two main categories: system software and application software. System software controls the computer and communicates with its hardware, while application software is designed for end users to perform specific tasks.
System Software
The system software controls the computer by communicating with computer’s hardware (keyboard, mouse, modem, sound card etc) and controlling different aspects of operations. Sub categories of system software are: operating system, device drivers, and utilities.
🔑 Definition — Operating System: An operating system (sometimes abbreviated as "OS") is the program that manages all the other programs in a computer. It is an integrated collection of routines that service the sequencing and processing of programs by a computer. An operating system may provide many services, such as resource allocation, scheduling, input/output control, and data management. According to Microsoft, "Operating system is the software responsible for controlling the allocation and usage of hardware resources such as memory, central processing unit (CPU) time, disk space, and peripheral devices. The operating system is the foundation on which applications, such as word processing and spreadsheet programs, are built."
Device drivers are special software used to communicate between devices and the computer. For example, when we attach a new device like a scanner, we install its device driver software. For scanners, TWAIN drivers are commonly used. TWAIN stands for Technology Without An Interesting Name. Normally the manufacturer of the device provides the device driver software with the device.
Utility software is a program that performs a very specific task, usually related to managing system resources. Examples include Disk Compression utility, which compresses files to reduce their size when saving to disk and uncompresses them when reading, and Disk Defragmentation utility, which removes fragmentation by reorganizing file chunks stored in different locations on the disk so they are stored close to each other, making data reading faster. The compilers and interpreters also belong to the System Software category.
Application Software
Application software is a program or group of programs designed for end users. Examples include programs for Accounting, Payroll, Inventory Control System, guided systems for planes, and GPS (global positioning system) which is used in vehicles to determine geographical position through satellites.
History of C Language
The C language was developed in late 60’s and early 70’s at Bell Laboratories. In those days, BCPL and B languages were developed there. The BCPL language was developed in 1967 by Martin Richards as a language for writing operating systems software and compilers. In 1970, Ken Thompson used B language to create early versions of the UNIX operating system at Bell Laboratories. Both BCPL and B were 'type less' languages — every data item occupied one 'word' in memory and the burden of treating a data item as a whole number or real number was the responsibility of the programmer.
Dennis Ritchie developed a general purpose language called C by using different features of BCPL and B languages. C uses many important concepts of BCPL and B while adding data typing and other features. C became widely known as the development language of the UNIX operating system, and the UNIX operating system was written using C. The C language is so powerful that the compiler of C and other various operating systems are written in C. C has almost unlimited powers — you can program to turn on or off any device of computer, you can do a lot to hard disk and other peripherals, and it is very easy to write a program in C that stops the running of computer.
The C language and UNIX operating system widely spread in educational and research institutions. Due to its wide spread, different researchers started to add their features, creating different variations of C. These variations led to the need of a standard version. In 1983, a technical committee was created under the American National Standards Committee on Computer and Information Processing to provide an unambiguous and machine-independent definition of the language. In 1989, the standard was approved. ANSI cooperated with the International Standard Organization (ISO) to standardize C worldwide.
💡 Why this matters: Understanding C's history explains why C is so powerful at the hardware level and why it became the foundation for modern operating systems and programming languages.
Tools of the Trade
As programmers, we need different tools for the life cycle of programs: Editors, Compiler and Interpreter, Debugger, Linker, and Loader.
Editors are tools for writing the code of a program. While we could use word processors, they save additional formatting information (bold, italic, coloring) along with text. For programming purposes, we only need simple text. Text editors save only the text we type, so we will be using a text editor for programming.
Compiler and Interpreter are translators that convert our program written in C-Language into Machine language (0s and 1s that computers understand). Interpreters translate the program line by line — they read one line, translate it, then read the next line. The benefit is that we get errors as we go along, making it easy to correct them. The drawback is that programs execute slowly because the interpreter translates line by line, and interpreters cannot get the overall picture to optimize the program.
Compilers also translate C code into machine language, but they read the whole program and translate it completely. The difference is that a compiler will stop translating if it finds an error and no executable code will be generated, whereas an Interpreter will execute all lines before the error and stop at the line containing the error. So a Compiler needs a syntactically correct program to produce an executable code. This course will use a compiler.
Debugger is used to debug the program — to correct logical errors. Using a debugger, we can control our program while it is running. We can stop execution at some point and check values in different variables, change these values, etc. This way we can trace logical errors and see whether our program is producing correct results.
Linker is a tool that checks our program and includes all those routines or functions which we are using in our program to make a standalone executable code. Most of the time our program uses different routines and functions located in different files, so the Linker includes the executable code of those routines/functions. This process is called Linking.
Loader is the process that loads the program into memory and then instructs the processor to start execution from the first instruction (the starting point of every C program is from the main function). Linkers and loaders are part of the development environment and are categorized as system software.
The complete program execution flow is: Editor (write code) → Preprocessor (processes the code) → Compiler (creates object code stored on disk) → Linker (links object code with libraries) → Loader (puts program in memory) → CPU (takes each instruction and executes it, possibly storing new data values as the program executes).
⭐ Key Takeaways
You must understand the distinction between system software (operating systems, device drivers, utilities) and application software, and recognize that compilers and interpreters are system software. The historical development of C from BCPL and B, with Dennis Ritchie adding data typing and other features, explains why C is both powerful and machine-oriented. The key difference between interpreters (line-by-line translation, slower, easier debugging) and compilers (full program translation, faster execution, stops on errors) is critical for understanding program development. The complete toolchain — editor → preprocessor → compiler → linker → loader → CPU execution — must be memorized as the standard program lifecycle, with the linker creating standalone executables by resolving external function references.
🧠 Quick Revision Questions
- What are the two main categories of software, and what is the fundamental difference between them?
- Explain the difference between an interpreter and a compiler, including their respective advantages and disadvantages.
- Who developed the C language, from which two predecessor languages was it derived, and what key feature did C add that its predecessors lacked?
- List in order the five main tools involved in the program development process, from writing code to execution.
- What is the specific role of the linker in program development, and what does it do with functions located in different files?
📘 Lecture 3 — First C program & Variables, Data Types, Arithmetic Operators, Precedence of Operators, Tips
📖 Overview: This lecture introduces the structure of a first C program, explains how data is stored using variables and different data types, and covers how to perform calculations using arithmetic operators. It also clarifies the precedence of operators and provides essential programming tips. Mastering these fundamentals is critical for writing any C program.
🗂️ Topics Covered
The lecture begins by dissecting a simple "Hello World" program, explaining each part like #include, main(), and cout. It then defines variables as named memory locations and the assignment operator (=). The core data types—int, short, long for whole numbers, float and double for real numbers, and char for characters—are each explained with sample code. Finally, the arithmetic operators (+, -, *, /, %) and their precedence rules are detailed with examples.
📝 Lecture Summary
First C program
The best way to learn C is to start coding. The lecture presents the very first C program:
#include <iostream.h>
main()
{
cout << "Welcome to Virtual University of Pakistan";
}
The line # include <iostream.h> is a pre-processor directive. It is not part of the program but an instruction to the compiler to include the contents of the system file iostream.h, which contains code for input/output streams (sending stuff to the screen and reading from the keyboard). The sign # is known as HASH or SHARP.
The function name main() is special. A C program is made up of many functions, but C regards the name main as a special case and will run this function first. Forgetting or mistyping main will cause a compiler error.
The curly braces { } are used to group together pieces of a program; the body of main is enclosed in them. For every open brace, there must be a matching close brace.
cout is the output stream in C and C++. Think of a stream as a door through which data is transferred. cout takes data from the computer and sends it to the output device (usually the screen).
The << sign indicates the direction of data. Here, it points towards cout.
" Welcome to Virtual University of Pakistan" is a character string. In C, character strings are written in double quotes.
The semicolon (;) is very important. All C statements end with a semicolon. Missing a semicolon is a syntax error that the compiler will report.
Variables
During programming, we need to store data. Variables are locations in memory for storing data. Memory is divided into blocks, each with a numerical address. Since it's difficult to handle these numerical addresses, we give names to these locations, which are called variables. They are called "variables" because they can contain different values at different times.
Variable names in C may be started with a character or an underscore (_). However, one should avoid starting a name with an underscore because C libraries contain variable and function names starting with underscore, which could cause a conflict.
In a program, every variable has a Name, Type, Size, and Value.
🔑 Definition — Assignment Operator (=): In C, the equal-to sign (=) is used as the assignment operator. Do not confuse it with the algebraic equal sign. In C, X = 2 means "take the value 2 and put it in the memory location labeled X." Afterwards, you can assign some other value to X, like X = 10, which means the memory location now contains 10 and the previous value 2 is no longer there.
The assignment operator is a binary operator (it has two operands). It must have a variable on its left-hand side and an expression (that evaluates to a single value) on its right-hand side. For example, X = 5, X = 10 + 5, and X = X + 1 are all valid. The statement X = X + 1 adds 1 to the current value of X and then stores the result back in X. This is a common practice for incrementing a variable.
💡 Why this matters: This concept is fundamentally different from algebra. In algebra, X = X + 1 is invalid (except for infinity). In C, it's a core operation for counting and looping.
Data Types
A variable must have a data type associated with it, for example, int (integer), float (decimal number), or char (character). The primary difference between various data types is their size in memory. These data types are reserved words of C and cannot be used as variable names.
Whole Numbers
The C language provides three data types to handle whole numbers: int, short, and long.
-
intData Type: Used to store whole numbers (integers). It typically occupies 4 bytes (32 bits) in memory on a Windows operating system. To use an integer variable namedi, you declare it asint i;. This reserves 4 bytes of memory and labels iti. -
shortData Type: Used for storing small whole numbers. Its size is 2 bytes, and it can store numbers in the range of -32768 to 32767. It's suitable for data like a person's age. -
longData Type: Used for storing very large whole numbers that cannot fit in anint. For example,long x = 300500200;.
Sample Program 1 (using int):
#include <iostream.h>
main()
{
int x;
int y;
int z;
x = 5;
y = 10;
z = x + y;
cout << "x = ";
cout << x;
cout << " y=";
cout << y;
cout << " z = x + y = ";
cout << z;
}
In this program, three variables x, y, and z are declared as integers. They can also be declared on a single line as int x, y, z;. After assigning 5 to x and 10 to y, the statement z = x + y; evaluates the expression, adds the values of x and y, and stores the result (15) in z. Crucially, the values of x and y remain unchanged after this operation. The cout statement with a variable name (e.g., cout << x;) displays the value of the variable, not its name.
Real Numbers
The C language provides float and double to deal with real numbers (numbers with decimal points, also known as floating point numbers).
-
floatData Type: Used to store real numbers and uses 4 bytes of memory. Example:float x = 12.35; -
doubleData Type: Used to store large real numbers that cannot fit in afloat. Its size is normally twice the size offloat. Example:double x = 345624.769123;
char Data Type
Used to store single characters like 'a', 'b', or 'c'. When assigning a character value to a char variable, single quotes are used around the character, e.g., char x = 'a';.
Arithmetic Operators
C has the usual arithmetic operators for addition, subtraction, multiplication, and division. It also provides a special operator called modulus. All these are binary operators (they operate on two operands).
| OPERATION | OPERATOR | C EXPRESSION |
|---|---|---|
| Addition | + | x + y |
| Subtraction | - | x - y |
| Multiplication | * | x * y |
| Division | / | x / y |
| Modulus | % | x % y |
🔑 Definition — Integer Division: When using the division operator / with integer operands, the result is an integer. The fractional part (after the decimal point) is truncated (ignored). For example, 5 / 2 yields 2, not 2.5. To get the correct fractional result, use float data types.
🔑 Definition — Modulus Operator (%): This operator returns the remainder after division. It can only be used with integer operands. The expression x % y returns the remainder after x is divided by y.
📌 Example: 5 % 2 = 1, 23 % 5 = 3, 107 % 10 = 7.
Precedence of Operators
The arithmetic operators in an expression are evaluated according to their precedence, which determines the order of evaluation.
- Parentheses
( )are used to force the evaluation order. Operators within parentheses are evaluated first. For nested parentheses, the innermost is evaluated first. - Expressions are always evaluated from left to right.
- The operators
*,/, and%have the highest precedence after parentheses and are evaluated before+and-. - The operators
+and-have the lowest precedence. - If operators have the same precedence (e.g.,
*and/), the operator that appears first from the left is evaluated first.
📌 Example 1: 10 + 10 * 5 yields 60, not 100. Because * has higher precedence, 10 * 5 is evaluated first (yielding 50), and then 10 + 50 gives 60. If we force addition first using (10 + 10) * 5, the result is 100.
📌 Example 2: 5 * 3 + 6 / 3 yields 17, not 7. The evaluation is (5 * 3) + (6 / 3) = 15 + 2 = 17.
⭐ Key Takeaways
A student must remember that in C, the = sign is an assignment operator, not an algebraic equals, and it requires a single variable on its left-hand side. The main() function is the mandatory starting point of every C program. Different data types (int, short, long, float, double, char) exist to store different kinds of data, and choosing the right one is important for memory efficiency and precision. When performing integer division, the fractional part is truncated, and the modulus operator (%) is used to get a remainder. Finally, the precedence of operators dictates that *, /, and % are evaluated before + and -, and parentheses can be used to override this order.
🧠 Quick Revision Questions
- What is the difference between the statement
cout << “x =“;andcout << x;in the sample program? - What are the three data types in C used for storing whole numbers, and how do they differ in their memory size?
- If you have two integer variables,
a = 7andb = 3, what are the results of the expressionsa / banda % b? - Explain, step by step, why the expression
5 + 3 * 4evaluates to 17 and not 32. - What is a pre-processor directive, and what is the purpose of writing
#include <iostream.h>at the top of a program?
📘 Lecture 4 — Sample Program, Examples of Expressions, Use of Operators, Tips
📖 Overview: This lecture demonstrates how to write complete C++ programs to solve real-world problems, including calculating average age, extracting digits from a number, and computing circle measurements. It reinforces the use of arithmetic operators, the
cinandcoutstatements, and the importance of data types and operator precedence in programming.
🗂️ Topics Covered
The lecture covers a complete sample program for calculating the average age of ten students, detailing variable declaration, input/output operations, and integer division truncation. It then provides examples of writing algebraic expressions as C++ statements, emphasizing operator precedence and the use of parentheses. The use of operators is further explored through programs that separate digits of an integer using the modulus operator and calculate circle properties. Finally, it offers programming tips for clarity, memory reuse, and problem analysis.
📝 Lecture Summary
Sample Program
The lecture presents a sample program to calculate the average age of ten students. The problem requires declaring ten integer variables (age1 through age10) to store each student's age, using a comma separator for concise declaration. The program prompts the user for each age using cout and reads input using cin. The total age is computed by adding all ten variables and storing the result in TotalAge. The average is calculated as AverageAge = TotalAge / 10. However, because both TotalAge and 10 are integers, the division performs integer division, which truncates the decimal portion. For example, if the total age is 123, the average is 12, not 12.3. The lecture notes that to preserve decimal values, variables of type float or double should be used instead of int. The complete program code is provided, demonstrating the sequence of declarations, input prompts, cin statements, calculations, and final output.
🔑 Definition — cin: The input stream that gets data from the user and assigns it to the variable on its right side. The >> operator indicates the direction of data flow from the user to the variable.
📐 Formula: TotalAge = age1 + age2 + age3 + age4 + age5 + age6 + age7 + age8 + age9 + age10 ; → Adds all ten ages.
📌 Example: If user enters ages 12, 13, 11, 14, 13, 15, 12, 13, 14, 11, then TotalAge = 123, AverageAge = 123 / 10 = 12 (decimal truncated). Output: "Average age of class is: 12".
Examples of Expressions
This section shows how to translate algebraic expressions into C++ assignment statements. Since C++ has no power operator, multiplication is used directly. The precedence of arithmetic operators (*, /, % higher than +, -) determines evaluation order. Parentheses () can be used to force a specific order of evaluation and to improve readability. Examples given include: the quadratic equation y = ax² + bx + c becomes y = a * x * x + b * x + c, and x = a(x + b(y + cz²)) becomes x = a * (x + b * (y + c * z * z)). The lecture stresses that only parentheses () are available in C++ (not curly or square brackets), and that incorrect placement of parentheses can produce wrong results, as demonstrated by x = 2 + 4 * 3 = 14 versus x = (2 + 4) * 3 = 18.
🔑 Definition — Operator Precedence: The order in which operators are evaluated in an expression. In C++, multiplication (*), division (/), and modulus (%) have higher precedence than addition (+) and subtraction (-). Expressions are evaluated left to right for operators of the same precedence.
📐 Formula: Algebraic y = ax² + bx + c → C++ y = a * x * x + b * x + c
📌 Example: x = ( b * b – 4 * a * c) / ( 2 * a ) correctly computes (b² – 4ac) / 2a; without parentheses, b * b – 4 * a * c / 2 * a incorrectly computes b² – (4ac/2) * a.
Use of Operators
This section presents two sample programs. The first program takes a four-digit integer from the user and displays its digits separately in reverse order. The logic uses the modulus operator % to extract the last digit (remainder when divided by 10). Then, the number is divided by 10 using integer division to remove the last digit. This process is repeated four times. The variable number is reused by reassigning its value (e.g., number = number / 10), which saves memory by avoiding additional variables. The second program calculates the diameter, circumference, and area of a circle given its radius. Variables are declared as float to handle decimal results. The formulas used are: diameter = radius * 2, circumference = 2 * 3.14 * radius, and area = 3.14 * radius * radius. The value 3.14 is used as an approximation of Pi.
🔑 Definition — Modulus Operator (%): Returns the remainder of an integer division. For example, 1234 % 10 returns 4.
📐 Formula: digit = number % 10; number = number / 10; → Extracts the last digit of number and then removes it.
📌 Example: For number = 5678:
digit = 5678 % 10 = 8, display "8, ",number = 5678 / 10 = 567digit = 567 % 10 = 7, display "7, ",number = 567 / 10 = 56digit = 56 % 10 = 6, display "6, ",number = 56 / 10 = 5digit = 5 % 10 = 5, display "5". Output: "8, 7, 6, 5"
📌 Example (Circle): Radius = 5 → diameter = 10, circumference = 31.4, area = 78.5.
Tips
The lecture concludes with several programming tips for writing better code:
- Use descriptive names for variables (e.g.,
TotalAgeinstead ofx). - Indent the code for better readability and understanding.
- Use parentheses for clarity and to force the order of evaluation in an expression.
- Reuse variables for better usage of memory (e.g., reassigning
number = number / 10). - Take care of division by zero to avoid runtime errors.
- Analyze the problem properly, and then start coding (i.e., first think and then write).
⭐ Key Takeaways
You must remember that integer division truncates the decimal part, so use float or double for precise averages. The modulus operator % is essential for extracting digits from an integer, while integer division / removes the last digit. Operator precedence determines evaluation order, and parentheses can override it, but incorrect placement leads to wrong results. Reusing variables by reassigning values saves memory and is a common practice. Always analyze the problem thoroughly before coding, and use clear prompts and descriptive variable names for readability.
🧠 Quick Revision Questions
- What is the output of
cout << 17 / 5;and why? - How does the
cinstatement work, and what happens when the program reaches acinline? - Write the C++ expression for the algebraic formula:
x = (a + b) / (c - d). - Explain the process to extract all digits from a 4-digit number in reverse order.
- Why is it important to use
floatfor variables when calculating the area of a circle, and what would happen if you usedint?
📘 Lecture 5 — Conditional Statements and Logical Operators
📖 Overview: This lecture introduces the fundamental concept of decision-making in C++ programming. It explains how to use conditional statements like
ifandif/elseto control program flow based on conditions, introduces relational and logical operators, and demonstrates their application through practical examples and flow charts.
🗂️ Topics Covered
The lecture covers conditional statements (decision making), relational operators used in conditions, flow charting as a program design technique, the if/else structure for handling true/false conditions, logical operators (&&, ||, !) for combining multiple conditions, and two complete sample programs demonstrating these concepts. It concludes with programming tips for writing clear conditional code.
📝 Lecture Summary
Conditional Statements (Decision Making)
Every programming language provides a structure for decision making. In C, the primary decision-making structure is the if statement. It has a simple structure: if (condition) statement;. When the condition is true, the following statement (or block of statements) is executed. The condition is an expression that explains the condition on which a decision will be made.
To execute multiple statements when a condition is true, we group them using braces { } to form a block of statements. The structure becomes:
if (condition) {
statement1;
statement2;
// ...
}
Note that semi-colons are required after every C statement, while indentation is only a matter of style to improve readability.
🔑 Definition — Relational operators: Operators used to compare two values, such as greater than (>), less than (<), equal to (==), greater than or equal to (>=), less than or equal to (<=), and not equal to (!=).
📐 Table of Relational Operators:
- Greater than:
>(e.g.,x > y) - Equal to:
==(e.g.,x == y) - Less than:
<(e.g.,x < y) - Greater than or equal to:
>=(e.g.,x >= y) - Less than or equal to:
<=(e.g.,x <= y) - Not equal to:
!=(e.g.,x != y) 📌 Example:if (age1 > age2) cout << "Student 1 is older than student 2";— Ifage1(12) is greater thanage2(10), the message is displayed.
⚠️ Important Warning: Do not confuse the assignment operator (=) with the equal to operator (==). Writing if (x = 2) is not a syntax error but creates a logical error because the assignment returns a value, not a boolean true/false. Be very careful when using equality in if statements.
Flow Charting
A flow chart is a pictorial representation of a program using labeled geometrical symbols connected by arrows. It helps in correctly designing a program by visually showing the sequence of instructions. A programmer can trace and rectify logical errors by first drawing a flow chart and then simulating it.
The main flow chart symbols used:
- Start or Stop: Oval shape
- Process: Rectangle
- Decision: Diamond shape
- Flow Line: Arrow connecting symbols
- Continuation Mark: Small circle
The flow chart for the if structure shows the condition in a diamond shape, with one path (true) leading to the action and another path (false) bypassing it.
Sample Program 1
A program comparing ages of two students (Amer and Amara) demonstrates the usage of relational operators and conditional statements.
Key programming practices introduced:
- Initialization of variables: Assign an initial value (preferably 0 for integers) when declaring variables. This is a good programming practice. Example:
int x = 0; - Prompt user for input using
coutand read usingcin.
The program declares AmerAge and AmaraAge as int variables, prompts the user to enter both ages, and then uses if statements to compare them.
if (AmerAge > AmaraAge) {
cout << "Amer is older than Amara";
}
if (AmerAge < AmaraAge) {
cout << "Amer is younger than Amara";
}
🔑 Important concept: When a single if condition is not sufficient for all cases (e.g., when ages are equal), additional if statements are needed to handle all possible outcomes.
If/else Structure
The if/else structure extends the if statement by allowing the programmer to specify a different block of statements to execute when the condition is false.
Structure:
if (condition) {
statement(s); // executed if condition is true
}
else {
statement(s); // executed if condition is false
}
Using this structure, the age comparison program becomes:
if (AmerAge > AmaraAge) {
cout << "Amer is older than Amara";
}
else {
cout << "Amer is younger than Amara";
}
🔑 Critical insight: The else part executes for all cases other than the one stated in the if condition. In the age comparison, the else covers both "less than" and "equal to" conditions. So the message "Amer is younger than Amara" would display even if ages are equal, which is logically incorrect. The correct message should be "Amer is younger than or is of the same age as Amara" to accurately describe both alternative cases.
💡 Why this matters: It is very important to check all the conditions while making decisions to ensure complete and logical results. Make sure that all cases are covered and there is no situation where the program does not respond or gives incorrect output.
Logical Operators
Complex decisions often depend on more than one condition. Logical operators allow combining multiple conditions.
The three logical operators:
&&(AND): Both conditions must be true for the combined expression to be true.||(OR): At least one condition must be true for the combined expression to be true.!(NOT/negation): Reverses the meaning of a condition (unary operator).
Truth Table:
| Expression 1 | Expression 2 | Exp1 && Exp2 | Exp1 || Exp2 |
|---|---|---|---|
| True | False | false | true |
| True | True | true | true |
| False | False | false | false |
| False | True | false | true |
🔑 Definition — Short-circuit evaluation: An expression containing && or || is evaluated only until truth or falsehood is known. For example, in (age > 18) && (height > 6), evaluation stops immediately if age > 18 is false (since the entire expression becomes false).
📌 Example with ! (negation):
if (!(age > 18))
cout << "The age is less than 18";
Here, the cout statement executes when the original condition (age > 18) is false, because ! reverses false to true.
Operator precedence: && has higher precedence than ||. Both operators associate from left to right.
Sample Program 2
Problem: A shopkeeper gives 10% discount on all bills, but if the bill amount is greater than 5000, the discount is 15%. Calculate the payable amount.
Solution analysis:
- Declare three
doublevariables:amount,discount, andnetPayable - Initialize all to 0
- Prompt user for bill amount
- Use
if/elsestructure to check the amount
if (amount > 5000) {
discount = amount * (15.0 / 100);
netPayable = amount - discount;
}
else {
discount = amount * (10.0 / 100);
netPayable = amount - discount;
}
🔑 Critical implementation detail: In the statement discount = amount * (15.0 / 100);, we write 15.0 instead of 15. If we write 15, the division 15 / 100 would be evaluated as integer division, truncating the result (0.15 becomes 0), making the entire calculation zero. Writing at least one operand in decimal form (like 15.0) ensures floating-point division. Variables must also be declared as float or double for correct decimal results.
📌 Example execution:
- Input: 6500
- Condition: 6500 > 5000 (true)
- Discount = 6500 * (15.0/100) = 6500 * 0.15 = 975
- Net Payable = 6500 - 975 = 5525
- Output: "The discount at the rate 15% is Rupees 975" and "The payable amount is Rupees 5525"
Tips
Practical programming tips for writing clean conditional code:
- Always put the braces in an
if/elsestructure - Type the beginning and ending braces before typing inside them
- Indent both body statements of an
ifandelsestructure - Be careful while combining conditions with logical operators
- Use
if/elsestructure instead of a number of single selectionifstatements
⭐ Key Takeaways
Conditional statements are fundamental for creating programs that make decisions. The if statement executes code only when a condition is true, while if/else handles both true and false cases, reducing the need for multiple if statements. Relational operators (>, <, ==, >=, <=, !=) are used to form conditions, and one must never confuse = (assignment) with == (equality). For complex decisions, logical operators (&&, ||, !) combine multiple conditions, with && requiring both conditions true and || requiring at least one. Always initialize variables before use, use proper data types (float/double) for decimal calculations to avoid integer division issues, and ensure all possible cases are covered in your conditions to produce logically complete programs.
🧠 Quick Revision Questions
- What is the difference between the
ifstatement and theif/elsestructure, and how does theelsepart handle conditions not explicitly stated in theifcondition? - Why does writing
15 / 100in C++ produce a different result than15.0 / 100, and how can this affect your calculations? - In the expression
(age > 18) && (height > 6), ifageis 15, willheight > 6ever be evaluated? Explain why. - What is the difference between the
=operator and the==operator, and why is using the wrong one a logical error rather than a syntax error? - Given two variables
x = 5andy = 10, determine the value of(x > y) || (x == 5)and explain using the truth table.
📘 Lecture 6 — Repetition Structure (Loop)
📖 Overview: This lecture introduces the concept of repetition structures in C++ programming, specifically the
whileloop. It explains how loops allow efficient execution of repeated tasks, demonstrates practical examples like summing integers and calculating factorials, and covers important concepts including overflow conditions, infinite loops, and flow chart representation.
🗂️ Topics Covered
The lecture covers repetition structures (loops) with detailed explanation of the while loop construct, overflow conditions in integer arithmetic, sample programs for summing integers and even numbers, infinite loops and their causes, properties of while loops including zero-or-more-time execution, flow chart representation of loop structures, and factorial calculation using loops.
📝 Lecture Summary
Repetition Structure (Loop)
Repetition is a fundamental concept in programming, mirroring real-world patterns like days repeating or payroll procedures applied to multiple employees. Without loops, summing numbers from 1 to 1000 would require an extremely long and impractical statement. Instead, we observe that each integer can be obtained by adding 1 to the previous integer, and we can repeat this process.
The while loop is a repetition structure in C/C++. The keyword while cannot be used as a variable name. Its syntax is:
while (logical expression) {
statement1;
statement2;
...
}
The logical expression must contain a relational or logical operator. While this expression is true, the statements inside the braces execute repeatedly. When it becomes false, control moves to the next statement after the loop. It is good practice to always use braces with while, even if only one statement follows, and to indent the code inside the loop for readability.
Overflow Condition
When summing integers, we can change the loop condition to calculate sums for any upper limit. However, integers have a fixed memory allocation (typically 32 bits on most PCs). If the sum exceeds what 32 bits can store, an overflow condition occurs. In overflow, either:
- A run-time error occurs (the compiler cannot detect this)
- Only the lower 32 bits are stored, discarding the extra bits, resulting in an incorrect value
Sample Program 1 — Generic Sum Calculator
To make the program reusable without modifying source code, we introduce a variable upperLimit and prompt the user for input:
int upperLimit;
cout << "Please enter the upper limit for which you want the sum ";
cin >> upperLimit;
while (number <= upperLimit) {
sum = sum + number;
number = number + 1;
}
cout << "The sum of first " << upperLimit << " integers is " << sum;
This approach eliminates the need to recompile the program for different limits.
💡 Why this matters: Making programs interactive with user input is a crucial step toward creating flexible, reusable software.
Sample Program 2 — Sum of Even Numbers
To calculate the sum of even numbers only, we need to identify even numbers. A number is even if it is divisible by 2, meaning its remainder when divided by 2 is zero. C provides the modulus operator % to get the remainder.
if ((number % 2) == 0) {
sum = sum + number;
}
Alternatively, without the modulus operator, we can check: if ((2 * (number / 2)) == number) because integer division truncates decimals.
🔑 Definition — Modulus Operator (%): Returns the remainder of integer division. For example, 5 % 2 equals 1, while 4 % 2 equals 0.
Infinite Loop
An infinite loop occurs when the condition in the while statement is always true. This happens if the variable used in the condition never changes value. For example, omitting the statement number = number + 1; inside the loop will cause the loop to execute forever because number never increments.
Properties of While Loop
The while loop may execute zero or more times. If the condition is false when first evaluated, the statements inside the loop body will not execute even once. For example, if upperLimit is 0, then number <= upperLimit is false initially, and the loop body is skipped entirely.
The loop terminates only when the condition is tested and found false. Ensure the loop has an adequate exit condition by modifying the control variable within the loop body.
Flow Chart
The structured flow chart for a while loop is:
- Draw a rectangle with "while" written in it
- Draw a line to the right to a diamond (decision symbol) containing the loop condition
- From the diamond, draw a downward line (true path) to rectangles representing the repeated processes
- From the last process, draw a line going back to the while/decision connection
- From the diamond, draw a line exiting to the right (false path) representing loop exit
Flow charts should be drawn before coding to plan the program logic.
Sample Program 3 — Factorial Calculation
The factorial of a number N is defined as: N × (N-1) × (N-2) × ... × 3 × 2 × 1
The program uses a loop that multiplies decreasing values of number:
int factorial = 1;
int number = 1;
cout << "Please enter the number for factorial ";
cin >> number;
while (number > 1) {
factorial = factorial * number;
number = number - 1;
}
cout << "The factorial is " << factorial;
The loop continues while number > 1, multiplying factorial by number and then decrementing number by 1 in each iteration.
⭐ Key Takeaways
The while loop is a fundamental repetition structure that executes zero or more times based on a condition evaluated before each iteration. To avoid infinite loops, the control variable in the condition must change within the loop body. Overflow is a critical issue when dealing with large sums in fixed-size data types. Flow charts serve as essential planning tools before writing code. The modulus operator % is useful for conditional logic within loops, such as identifying even numbers.
🧠 Quick Revision Questions
- What is the syntax of a
whileloop in C++ and what does each part mean? - What causes an overflow condition when summing integers, and what are the two possible outcomes?
- How can you determine if a number is even using the modulus operator in C++?
- What is an infinite loop and what programming mistake typically causes it?
- How many times will a
whileloop execute if its condition is false when first evaluated?
📘 Lecture 7 — Do-While Statement and for Statement
📖 Overview: This lecture introduces two important looping structures in C++: the do-while loop and the for loop. It explains when to use each type of loop, provides detailed examples, and covers increment/decrement operators essential for loop control. Understanding these concepts is fundamental for writing efficient repetitive code.
🗂️ Topics Covered
The lecture covers the do-while statement with a complete character guessing game example, the for statement with table printing and sum of squares programs, increment and decrement operators including pre/post variations, and compound assignment operators for concise code writing. It also provides important programming tips for avoiding common loop errors.
📝 Lecture Summary
Do-While Statement
We have seen that there may be certain situations when the body of while loop does not execute even a single time. This occurs when the condition in while is false. In while loop, the condition is tested first and the statements in the body are executed only when this condition is true. If the condition is false, then the control goes directly to the statement after the closed brace of the while loop. So we can say that in while structure, the loop can execute zero or more times. There may be situations where we may need that some task must be performed at least once.
For example, a computer program has a character stored from a-z. It gives to user five chances or tries to guess the character. In this case, the task of guessing the character must be performed at least once. To ensure that a block of statements is executed at least once, C provides a do-while structure. The syntax of do-while structure is as under:
do
{
statement(s);
}
while ( condition ) ;
Here we see that the condition is tested after executing the statements of the loop body. Thus, the loop body is executed at least once and then the condition in do while statement is tested. If it is true, the execution of the loop body is repeated. In case, it proves otherwise (i.e. false), then the control goes to the statement next to the do while statement.
Broadly speaking, in while loop, the condition is tested at the beginning of the loop before the body of the loop is performed. Whereas in do-while loop, the condition is tested after the loop body is performed. Therefore, in do-while loop, the body of the loop is executed at least once.
💡 Why this matters: The do-while loop guarantees that user input or critical operations happen at least once before checking conditions, which is essential for interactive programs like games or data validation.
Example
Let's consider the example of guessing a character. We have a character in the program to be guessed by the user. Let's call it 'z'. The program allows five tries (chances) to the user to guess the character. We declare a variable tryNum to store the number of tries. The program prompts the user to enter a character for guessing. We store this character in a variable c.
We declare the variable c of type char. The data type char is used to store a single character. We assign a character to a variable of char type by putting the character in single quotes. Thus the assignment statement to assign a value to a char variable will be as c = 'a'. Note that there should be a single character in single quotes. The statement like c = 'gh' will be a syntax error.
Here we use the do-while construct. In the do clause we prompt the user to enter a character. After getting character in variable c from user, we compare it with our character i.e 'z'. We use if/else structure for this comparison. If the character is the same as ours then we display a message to congratulate the user else we add 1 to tryNum variable. And then in while clause, we test the condition whether tryNum is less than or equal to 5 (tryNum <= 5). If this condition is true, then the body of the do clause is repeated again. We do this only when the condition (tryNum <= 5) remains true. If it is otherwise, the control goes to the first statement after the do-while loop.
If guess is matched in first or second try, then we should exit the loop. We know that the loop is terminated when the condition tryNum <= 5 becomes false, so we assign a value which is greater than 5 to tryNum after displaying the message. Now the condition in the while statement is checked. It proves false (as tryNum is greater than 5). So the control goes out of the loop.
There is an elegant way to exit the loop when the correct number is guessed. We change the condition in while statement to a compound condition. This condition will check whether the number of tries is less than or equal to 5 AND the variable c is not equal to 'z'. So we will write the while clause as while (tryNum <= 5 && c != 'z' ); Thus when a single condition in this compound condition becomes false, then the control will exit the loop. Thus we need not to assign a value greater than 5 to variable tryNum.
The code of the improved program is:
#include <iostream.h>
main()
{
int tryNum = 0;
char c;
do
{
cout << "Please enter a character between a-z for guessing : ";
cin >> c;
if (c == 'z')
{
cout << "Congratulations, Your guess is correct";
}
else
{
tryNum = tryNum + 1;
}
}
while (tryNum <= 5 && c != 'z');
}
Output:
Please enter a character between a-z for guessing : g
Please enter a character between a-z for guessing : z
Congratulations, Your guess is correct
for Statement
Let's see what we do in a loop. In a loop, we initialize variable(s) at first. Then we set a condition for the continuation/termination of the loop. To meet the condition to terminate the loop, we affect the condition in the body of the loop. If there is a variable in the condition, the value of that variable is changed within the body of the loop. If the value of the variable is not changed, then the condition of termination of the loop will not meet and loop will become an infinite loop. So there are three things in a loop structure: (i) initialization, (ii) a continuation/termination condition and (iii) changing the value of the condition variable, usually the increment of the variable value.
To implement these things, C provides a loop structure known as for loop. This is the most often used structure to perform repetition tasks for a known number of repetitions. The syntax of for loop is:
for ( initialization condition ; continuation condition ; incrementing condition )
{
statement(s);
}
We see that a for statement consists of three parts. In initialization condition, we initialize some variable while in continuation condition, we set a condition for the continuation of the loop. In third part, we increment the value of the variable for which the termination condition is set.
Let's suppose, we have a variable counter of type int. We write for loop in our program as:
for (counter = 0; counter < 10; counter = counter + 1)
{
cout << counter << endl;
}
This for loop will print on the screen 0, 1, 2 .... 9 on separate lines. In for loop, at first, we initialize the variable counter to 0. And in the termination condition, we write counter < 10. This means that the loop will continue till value of counter is less than 10. In other words, the loop will terminate when the value of counter is equal to or greater than 10. In the third part of for statement, we write counter = counter + 1 this means that we add 1 to the existing value of counter. We call it incrementing the variable.
When the control goes to for statement first time, it sets the value of variable counter to 0, tests the condition (i.e. counter < 10). If it is true, then executes the body of the loop. In this case, it displays the value of counter which is 0 for the first execution. Then it runs the incrementing statement (i.e. counter = counter + 1). Thus the value of counter becomes 1. Now, the control goes to for statement and tests the condition of continuation. If it is true, then the body of the loop is again executed which displays 1 on the screen. The increment statement is again executed and control goes to for statement. The same tasks are repeated. When the value of counter becomes 10, the condition counter < 10 becomes false. Then the loop is terminated and control goes out of for loop.
The point to be noted is that, the increment statement (third part of for statement) is executed after executing the body of the loop. Thus for structure is equivalent to a while structure, in which, we write explicit statement to change (increment/decrement) the value of the condition variable after the last statement of the body. The for loop does this itself according to the increment statement in the for structure. There may be a situation where the body of for loop, like while loop, may not be executed even a single time. This may happen if the initialization value of the variable makes the condition false. The statement in the following for loop will not be executed even a single time as during first checking, the condition becomes false:
for (counter = 5; counter < 5; counter++)
{
cout << "The value of counter is " << counter;
}
Sample Program 1
Let's take an example to explain for loop. We want to write a program that prints the table of 2 on the screen. In this program, we declare a variable counter of type int. We use this variable to multiply it by 2 with values 1 to 10. For writing the table of 2, we multiply 2 by 1, 2, 3 .. up to 10 respectively and each time display the result on screen. So we use for loop to perform the repeated multiplication.
The code of the program:
#include <iostream.h>
main()
{
int counter;
for (counter = 1; counter <= 10; counter = counter + 1)
{
cout << "2 x " << counter << " = " << 2 * counter << "\n";
}
}
In the for statement, we initialize the variable counter to 1 as we want the multiplication of 2 starting from 1. In the condition clause, we set the condition counter <= 10 as we want to repeat the loop for 10 times. And in the incrementing clause, we increment the variable counter by 1. In the body of the for loop, we write a single statement with cout. In the first iteration where the value of counter is 1, the cout statement will display 2 x 1 = 2. After the execution of cout statement, the for statement will increment the counter variable by 1. When the value of counter is 11, the condition (counter <= 10) will become false and the loop will terminate.
Output:
2 x 1 = 2
2 x 2 = 4
2 x 3 = 6
2 x 4 = 8
2 x 5 = 10
2 x 6 = 12
2 x 7 = 14
2 x 8 = 16
2 x 9 = 18
2 x 10 = 20
To make the program generic and reusable, we use a variable instead of a hard-coded number. We prompt the user to enter the number for which they want a table. We store this number in the variable and then use it to write a table. We also allow the user to enter the number of multipliers up to which they want a table using a variable maxMultiplier.
#include <iostream.h>
main()
{
int counter, number, maxMultiplier;
cout << "Please enter the number for which you want a table : ";
cin >> number;
cout << "Please enter the multiplier up to which you want a table : ";
cin >> maxMultiplier;
for (counter = 1; counter <= maxMultiplier; counter = counter + 1)
{
cout << number << " x " << counter << " = " << number * counter << "\n";
}
}
Output:
Please enter the number for which you want a table : 7
Please enter the multiplier up to which you want a table : 8
7 x 1 = 7
7 x 2 = 14
7 x 3 = 21
7 x 4 = 28
7 x 5 = 35
7 x 6 = 42
7 x 7 = 49
7 x 8 = 56
Increment/decrement Operators
We have seen that in while, do-while and for loop we write a statement to increase the value of a variable. For example, we used the statements like counter = counter + 1; which adds 1 to the variable counter. This increment statement is so common that it is used almost in every repetition structure. The C language provides a unary operator that increases the value of its operand by 1. This operator is called increment operator and sign ++ is used for this. The statement counter = counter + 1; can be replaced with the statement counter++;
The statement counter++ adds 1 to the variable counter. There is also an operator -- called decrement operator. This operator decrements the value of its operand by 1. So the statements counter = counter - 1; and j = j - 1; are equivalent to counter--; and j--; respectively.
The increment operator is further categorized as pre-increment and post-increment. Similarly, the decrement operator as pre-decrement and post-decrement.
In pre-increment, we write the sign before the operand like ++j while in post-increment, the sign ++ is used after the operand like j++. If we are using only variable increment, pre or post increment does not matter. The difference of pre and post increment matters when the variable is used in an expression where it is evaluated to assign a value to another variable. If we use pre-increment (++j), the value of j is first increased by 1. This new value is used in the expression. If we use post-increment (j++), the value of j is used in the expression first. After that it is increased by 1. Same is the case in pre and post decrement.
🔑 Definition — Pre-increment: The operator ++ placed before the variable. Example: x = ++j; If j = 5, after this expression, x = 6 and j = 6.
🔑 Definition — Post-increment: The operator ++ placed after the variable. Example: x = j++; If j = 5, after this expression, x = 5 and j = 6.
The operators ++ and -- are used to increment or decrement the variable by 1. There may be cases when we are incrementing or decrementing the value of a variable by a number other than 1. For example, we write counter = counter + 5; or j = j - 4;. Such assignments are very common in loops, so C provides operators to perform this task in short. These operators are compound assignment operators: +=, -=, *=, /= and %=.
The use of these operators:
| Long Form | Short Form |
|---|---|
x = x + 4; | x += 4; |
x = x - 3; | x -= 3; |
x = x * 2; | x *= 2; |
x = x / 2; | x /= 2; |
x = x % 3; | x %= 3; |
Note that there is no space between these operators. These are treated as single signs. Be careful about the operator %=. This operator assigns the remainder to the variable. These operators are alternate in shorthand for an assignment statement.
Sample Program 2
Let's write a program using for loop to find the sum of the squares of the integers from 1 to n. Where n is a positive value entered by the user (i.e. Sum = 1² + 2² + 3² + ......+ n²)
The code of the program:
#include <iostream.h>
main()
{
int i, n, sum;
sum = 0;
cout << "Please enter a positive number for sum of squares: ";
cin >> n;
for (i = 1; i <= n; i++)
{
sum += i * i;
}
cout << "The sum of the first " << n << " squares is " << sum << endl;
}
In the program we declared three variables i, n and sum. We prompted the user to enter a positive number. We stored this number in the variable n. Then we wrote a for loop. In the initialization part, we initialized variable i with value 1 to start the counting from 1. In the condition statement we set the condition i <= n (number entered by the user) as we want to execute the loop n times. In the increment statement, we incremented the counter variable by 1. In the body of the for loop we wrote a single statement sum += i * i;. This statement takes the square of the counter variable (i) and adds it to the variable sum. This statement is equivalent to sum = sum + (i * i);. Thus in each iteration the square of the counter variable (which is increased by 1 in each iteration) is added to the sum. After completing the for loop the cout statement is executed which displays the sum of the squares of number from 1 to n.
Output when the number 5 is entered:
Please enter a positive number for sum of squares: 5
The sum of the first 5 squares is 55
Tips
Comments should be meaningful, explaining the task. Don't forget to affect the value of loop variable in while and do-while loops. Make sure that the loop is not an infinite loop. Don't affect the value of loop variable in the body of for loop, the for loop does this by itself in the for statement. Use pre and post increment/decrement operators cautiously in expressions.
⭐ Key Takeaways
The do-while loop guarantees at least one execution of the loop body because the condition is tested after the body executes, making it ideal for programs that require user input before checking. The for loop consolidates initialization, condition, and increment into a single line, making it the most commonly used loop for known repetition counts. Pre-increment (++j) increments the variable before using its value in an expression, while post-increment (j++) uses the current value first then increments. Compound assignment operators like += and *= provide concise shorthand for updating variables in loops. The lecture emphasizes writing generic code using variables instead of hard-coded constants to improve reusability and maintainability.
🧠 Quick Revision Questions
- What is the key difference between a while loop and a do-while loop in terms of when the condition is tested?
- What are the three parts of a for loop statement, and in what order are they executed during each iteration?
- If
j = 5, what will be the values ofxandjafter executingx = ++j;versusx = j++;? - How does using a compound condition (
tryNum <= 5 && c != 'z') provide a more elegant way to exit the guessing game loop compared to manually setting tryNum to 6? - Write the equivalent short form using compound assignment operators for each:
counter = counter + 5;,total = total * 2;, andremainder = remainder % 3;
📘 Lecture 8 — Switch Statement
📖 Overview: This lecture introduces the switch statement as an efficient alternative to multiple if/else statements for multi-way decision making. It covers the break and continue statements, structured programming guidelines, and rules for flow charting, emphasizing the importance of single-entry-single-exit constructs for creating maintainable code.
🗂️ Topics Covered
The lecture covers the switch statement syntax and behavior, the break statement for controlling flow in switches and loops, the continue statement for skipping iterations, the goto statement and why it should be avoided, guidelines for structured programming, rules for structured programming/flow charting, and a sample program demonstrating salary deduction calculations using switch.
📝 Lecture Summary
Switch Statement
When a program must handle multiple conditions and execute different actions for each, using many separate if statements is computationally expensive because the processor must evaluate each condition. For example, in a payroll system or when converting student grades to descriptions (A=Excellent, B=Very Good, etc.), a more efficient approach is needed. The C language provides the switch structure, a multiple-selection construct designed for multi-way decisions.
🔑 Definition — switch statement: A control structure that transfers control to one of several statement lists based on the value of an integer expression. 📐 Syntax:
switch ( variable/expression )
{
case constant1 : statementList1 ;
case constant2 : statementList2 ;
...
case constantN : statementListN ;
default : statementList ;
}
→ The switch variable must be an integer type (including char), and each case constant must be an integer constant. Compound conditions using && or || are not allowed.
The switch statement evaluates the expression, then looks for a matching case constant. When found, execution begins at that case and falls through all subsequent cases to the end of the switch block. This fall-through behavior means that if a user enters grade 'B', not only will "Very Good" print, but also "Good", "Poor", and "Fail" — unless controlled. To handle both uppercase and lowercase letters in grade input, cases can be stacked:
case 'A':
case 'a':
statements;
💡 Why this matters: The fall-through behavior is a unique feature of switch; without proper control, it can cause serious logical errors.
Break Statement
The break statement interrupts the flow of control, forcing an immediate exit from the enclosing switch or loop. In a switch, break is placed after the statements of a case to prevent fall-through. When a matching case executes and its break is encountered, control jumps out of the entire switch statement.
🔑 Definition — break statement: A statement that terminates the nearest enclosing loop or switch statement.
📌 Example: In a guessing game, if the user guesses the character 'z', we can use break to exit the loop:
if ( c == 'z' )
{
cout << "Great, Your guess is correct";
break;
}
continue Statement
The continue statement forces the immediate next iteration of a loop, skipping any remaining statements in the current iteration. It's useful when early parts of a loop body must execute every time, but later parts execute only conditionally.
🔑 Definition — continue statement: A statement that skips the rest of the current loop iteration and proceeds to the next iteration.
⚠️ Important distinction: In while and do-while loops, the loop variable must be incremented/decremented before the continue statement to avoid infinite loops. In for loops, the increment step is built into the loop structure, so continue automatically triggers it before checking the condition.
goto Statement
The goto statement is an unconditional branch that can jump control to any labeled point in a program. While historically used in languages like COBOL and FORTRAN, goto leads to "spaghetti code" — programs with tangled, unpredictable execution paths that are nearly impossible to debug or modify.
🔑 Definition — goto statement: An unconditional branch that transfers control to a labeled statement elsewhere in the program.
💡 Why this matters: The lecture explicitly states, "Though goto is there in C language but we will not use it in our programs." Structured programming rejects goto in favor of sequences, decisions, and loops.
Guide Lines
Programs should minimize the use of break and continue in loops (though break is necessary in switch). These statements create multiple exit points, violating the single entry, single exit principle. Each module or construct should have exactly one entry point and one exit point, making execution flow predictable and code easier to understand, debug, and modify.
🔑 Definition — single entry, single exit: A design principle where each program construct has exactly one way to enter and one way to leave, ensuring predictable control flow.
Rules for Structured Programming/Flow Charting
- Start simple: Draw a start symbol, a rectangle (process), and a stop symbol.
- Replace rectangles: Any rectangle can be replaced by two rectangles (splitting the problem).
- Replace with constructs: Any rectangle can be replaced by a structured construct (decision, loop, or multi-way decision).
- Repeat: Rules 2 and 3 can be applied repeatedly to decompose complex problems.
These rules ensure a top-down, modular approach where each part is manageable and the flow chart corresponds one-to-one with the code.
Sample Program
Problem: A company deducts fund from employee salaries as follows:
- Salary < 10,000: No deduction
- 10,000 ≤ Salary < 20,000: Deduct Rs. 1,000
- Salary ≥ 20,000: Deduct 7% of salary
Solution: Use switch with salary / 10000 as the control expression. Integer division yields 0 (salary < 10,000), 1 (10,000–19,999), or 2+ (≥20,000).
int salary;
float deduction, netPayable;
cout << "Please enter the salary : ";
cin >> salary;
switch ( salary / 10000 )
{
case 0 :
deduction = 0;
netPayable = salary;
break;
case 1 :
deduction = 1000;
netPayable = salary - deduction;
break;
default :
deduction = salary * 7 / 100;
netPayable = salary - deduction;
}
📌 Sample Output:
Please enter the salary : 15000
Net Payable (salary – deduction) = 15000 – 1000 = 14000
⭐ Key Takeaways
The switch statement is a powerful, efficient alternative to multiple if/else chains for multi-way decisions involving integer values, but its fall-through behavior requires careful use of break statements to prevent logical errors. The break and continue statements can disrupt control flow in loops and should be minimized to maintain single-entry-single-exit design. The goto statement should never be used due to its tendency to create unmanageable spaghetti code. Structured programming relies on decomposing problems using sequences, decisions, and loops, with flow charts that follow four simple replacement rules to ensure modular, traceable code. Missing a break in a switch or forgetting to increment a loop variable before continue in while/do-while loops are common pitfalls that cause hard-to-find bugs.
🧠 Quick Revision Questions
- What is the main advantage of using a switch statement instead of multiple if statements for multi-way decisions?
- What happens if you forget to include a break statement after a case in a switch?
- How does the continue statement behave differently in a while loop versus a for loop?
- Why should the goto statement be avoided in structured programming?
- In the sample salary deduction program, why is the switch expression
salary / 10000used instead ofsalaryitself?
📘 Lecture 9 — Functions
📖 Overview: This lecture introduces functions, a fundamental programming construct that enables code reuse and modular design. It explains how to break complex problems into smaller, manageable subtasks using top-down design methodology, and covers function declaration, definition, calling mechanisms, and practical examples.
🗂️ Topics Covered
The lecture begins with an introduction to functions and the concept of top-down design, then explains the structure of a function including return-value-type, function-name, argument-list, and body. It covers declaration versus definition of functions, calling mechanisms, and provides three sample programs: calculating integer power, calculating area of a ring using a circleArea function, and testing whether a number is even.
📝 Lecture Summary
Introduction
Functions are a major programming construct in C, which is a function-oriented language. Every program is written in different functions. In daily life, we divide tasks into subtasks. For example, making a laboratory stool involves making a seat and three legs, where legs can be identical and reused. This is the concept of functional design or top-down designing. Top-down design follows the principle of 'divide and conquer' — we divide a big task into smaller tasks and accomplish them. For example, to find how many students are logged in LMS, the request is delegated to a network administrator who performs the task while the caller can do other work. This demonstrates information hiding, where some information is hidden from the caller.
💡 Why this matters: Functions enable parallel processing and modular programming, allowing complex problems to be broken into manageable pieces.
Functions
Functions are like subtasks that receive some information, do some process, and provide a result. Functions are invoked through a calling program, which does not need to know what the function is doing internally. There is a specific function-calling methodology. The main() function is also a function. Functions are very important in code reusing.
There are two categories of functions:
- Functions that return a value
- Functions that do not return a value
For example, a function that calculates the square of an integer returns the square, while a function that displays information on screen does not return any value.
Structure of a Function
The declaration syntax of a function is:
return-value-type function-name( argument-list )
{
declarations and statements
}
return-value-type: Function may or may not return a value. If a function returns a value, it must be of a valid data type (int, float, char, etc.). The keyword return is used to return a value — it does two things: returns a value to the calling program and exits from the function. For functions that do not return any value, the return-value-type is void. The default return-value-type is int.
Function-name: Same rules as variable naming; should be self-explanatory like square, squareRoot, circleArea.
argument-list: Contains information passed to the function. Some functions need no information, so argument list is empty. Arguments must be of valid data types.
declarations and statements: The body of the function where the task is performed.
📐 Example:
int square(int number)
{
int result = 0;
result = number * number;
return result;
}
Calling Mechanism
The calling program writes the function name and provides its arguments (without data types). While calling a function, we don't write the return value data type or the data types of arguments.
📐 Example:
result = square(number);
Functions can be used as stand-alone statements or on the right-hand side of assignment statements:
result = 10 + square(5);result = square(number + 10);result = square(number) + square(number + 1) + square(3 * number);cout << "The square is " << square(number);
Functions that do not return any value cannot be used in assignment statements; they are written as stand-alone statements.
Declaration and Definition of a Function
Declaration is the prototype of the function, including return type, name, and argument list. It is also known as signature of a function. Definition is the actual function code with complete statements.
If functions are written after the calling function or in a different file, they must be declared before use. Function declaration is a one-line statement with return type, function name, and data types of arguments (argument names are optional). The definition starts with the declaration statement plus argument names, followed by braces and statements.
🔑 Definition — Function Declaration (Prototype): A one-line statement that tells the compiler about a function's return type, name, and parameter types before the function is defined or used. Example: int square(int);
🔑 Definition — Function Definition: The complete code of a function including the header with argument names and the body with declarations and statements. Example: int square(int number) { return (number * number); }
Functions can be called using call by value, where a copy of the argument values is passed to the function, and the original values remain unchanged.
Sample Program 1
Problem: Calculate the integer power of some number (x^n).
Solution: There is no operator for power in C, so we write a function raiseToPow. Input: a number (double) and power (int). Output: double. The function multiplies x by itself power times using a loop.
📐 Formula:
double raiseToPow(double x, int power)
{
double result = 1.0;
for (int i = 1; i <= power; i++)
{
result *= x;
}
return result;
}
📌 Example:
// Calling program
double x;
int i;
cout << "Please enter the number: ";
cin >> x;
cout << "Please enter the integer power: ";
cin >> i;
cout << x << " raise to power " << i << " is equal to " << raiseToPow(x, i);
The function call uses call by value — copies of x and i are passed, so original values remain unchanged.
Sample Program 2
Problem: Calculate the area of a ring.
Solution: A ring consists of a small circle and a big circle. Area of ring = area of big circle - area of small circle. Area of any circle = π * r². We write a circleArea function and reuse it.
📐 Formula:
double circleArea(double radius)
{
return (3.1415926 * radius * radius);
}
📌 Example:
double rad1, rad2, ringArea;
cout << "Please enter the outer radius value: ";
cin >> rad1;
cout << "Please enter the radius of the inner circle: ";
cin >> rad2;
ringArea = circleArea(rad1) - circleArea(rad2);
cout << "Area of ring: " << ringArea;
💡 Why this matters: The circleArea function is reused twice without rewriting code, demonstrating code reuse and modular design.
Sample Program 3
Problem: Write a function that tests whether a given number is even or not. Return true (non-zero) if even, false (zero) if odd.
Solution: In C, zero is considered false, and any non-zero value is considered true. Return type is int.
🔑 Definition — isEven function: A function that returns 1 (true) if a number is even and 0 (false) if odd. Declaration: int isEven(int);
📌 Example:
int isEven(int number)
{
if (2 * (number / 2) == number)
{
return 1;
}
else
{
return 0;
}
}
// Usage in main:
if (isEven(number))
{
cout << "The number entered is even" << endl;
}
else
{
cout << "The number entered is odd" << endl;
}
Functions can be used directly in conditional statements like if (isEven(number)).
⭐ Key Takeaways
Functions are essential for code reuse and modular design using top-down methodology. A function has a header (return type, name, parameter list) and a body. Declaration (prototype) tells the compiler about the function before it is defined or used, while definition contains the actual code. Functions communicate via arguments and return values, with call by value protecting original variables. Functions can be used in assignment statements, expressions, and conditional statements. The three sample programs demonstrate practical function usage: mathematical computation (power), code reuse (circle area), and boolean testing (even check).
🧠 Quick Revision Questions
- What is the difference between a function declaration and a function definition?
- What does the
returnkeyword do in a function? - In Sample Program 1, why do the original values of x and i remain unchanged after calling
raiseToPow? - How is the
circleAreafunction reused in Sample Program 2? - In Sample Program 3, what value does
isEvenreturn if the number is even, and how is this used in the if statement?
📘 Lecture 10 — Header Files, Scope of Identifiers, Functions
📖 Overview: This lecture explains how to organize C++ programs using header files and
#defineconstants. It then covers the scope (visibility) of variables at global, function, and block levels, followed by the two fundamental function calling mechanisms: call by value and call by reference. Finally, it introduces recursive functions. This is critical for writing modular, safe, and efficient code.
🗂️ Topics Covered
This lecture covers header files as a mechanism to organize function prototypes and constant definitions using #define. It details the three scopes of identifiers: global, function-level, and block-level scope, with examples of variable hiding. It then explains the default call-by-value mechanism, contrasts it with call-by-reference using pointers (& and *), and demonstrates recursion with the factorial example.
📝 Lecture Summary
Header Files
The lecture explains that instead of listing many function prototypes before every function, you can place all prototypes in a separate text file with a .h extension (a header file). Using the #include directive, this file is included in your program, which is equivalent to writing those prototypes manually. Header files are also used for defining constants. Instead of declaring a double pi variable, you can use the preprocessor directive #define:
#define pi 3.1415926
This does not create a variable. During compilation, the preprocessor replaces every occurrence of pi with 3.1415926 before the code is compiled. This makes code more readable (e.g., 2 * pi * radius is clearly the circumference formula) and avoids repeating the literal number.
🔑 Definition — Header File: A text file (usually with .h extension) containing function prototypes and #define constants that can be included in a program using the #include directive.
🔑 Definition — #define Directive: A preprocessor command that associates a name with a value; during compilation, the name is replaced by its value. The name cannot be used on the left-hand side of an assignment (it is not a variable).
Scope of Identifiers
An identifier (name of a variable, function, or label) has a scope that defines where it is visible. This discussion focuses on variable scope.
- Function-Level Scope: A variable declared inside a function body is local to that function. It is not visible in other functions. For example,
int i;insidefunc1()cannot be accessed fromfunc2(). - Block-Level Scope: A variable declared inside a code block (between
{and}), such as inside aforloop, is local to that block.
Code Example with Variable Hiding:
void increment() {
int num; // Function-level scope
{
int num; // Bad practice! Block-level scope with same name
num++; // This increments the inner 'num', not the outer one.
}
}
If a variable with the same name is declared in an inner block, the outer variable is hidden inside that block, and the inner variable is used.
Global Scope (File Scope): A variable declared outside of any function has global scope (or file scope). It is visible to all functions in that file.
#include <iostream.h>
int i; // Global variable
main() {
i = 10;
f();
cout << i; // Output will be 20
}
void f() {
cout << i; // Output will be 10
i = 20; // Modifies the global variable
}
💡 Why this matters: Minimizing the use of global variables is a key principle of Encapsulation and Data Hiding. They can be accidentally changed by any function, leading to hard-to-find bugs. Prefer to use local variables as much as possible.
Functions — Call by Value
The default calling mechanism in C is call by value. When a function is called, a copy of the argument's value is passed to the function. The original variable in the calling function remains unchanged.
Example:
#include <iostream.h>
void f(int); // Prototype
main() {
int i = 10;
f(i); // A copy of i (value 10) is passed to f
cout << i; // Output: 10 (original i unchanged)
}
void f(int i) {
i *= 2;
cout << i; // Output: 20 (the copy is doubled)
}
📐 Formula: f(argument) → The function f receives a copy of argument. Any changes made to the copy inside f do not affect the original variable.
Functions — Call by Reference
In call by reference, the address (memory location) of the variable is passed to the function. The function can then directly modify the original variable at that address. This is achieved using the address-of operator & and dereference operator *.
Example (Square function that modifies the original):
#include <iostream.h>
void square(double *); // Prototype: accepts a pointer to double
main() {
double x = 123.456;
square(&x); // &x passes the address of x
cout << x; // Output: 15241.4 (original x changed)
}
void square(double* x) { // x is a pointer (stores an address)
*x = *x * *x; // *x accesses the value at the address (dereference)
}
🔑 Definition — Pointer: A variable that stores the memory address of another variable. double* x means "x is a pointer to a double".
🔑 Definition — Address-of Operator (&): Returns the memory address of a variable (e.g., &x).
🔑 Definition — Dereference Operator (*): Accesses the value stored at the address held by a pointer (e.g., *x).
💡 Why this matters: Call by reference is powerful but risky due to side-effects (unexpectedly changing a variable). It should only be used when essential (e.g., to modify the original argument) or for efficiency when passing a large data structure.
Recursive Functions
A recursive function is a function that calls itself. It is useful for problems that can be broken down into smaller, self-similar sub-problems (e.g., calculating power x^n, factorials).
Example (Factorial):
long fact(long n) {
if (n <= 1) // Base case: stop recursion
return 1;
else
return n * fact(n-1); // Recursive call
}
A recursive function must have:
- Base Case: A condition that stops the recursion (e.g.,
n <= 1). - Recursive Step: The function calls itself with a smaller or simpler argument.
📌 Example (Calculation of fact(3)):
fact(3)→3 * fact(2)fact(2)→2 * fact(1)fact(1)→ returns1(base case)- Back-substitution:
fact(2)=2 * 1=2,fact(3)=3 * 2=6
💡 Why this matters: Recursion can produce elegant code, but it adds memory and stacking overhead (many function calls are placed on the call stack). Iterative solutions are often more efficient. Use recursion when elegance is a priority and resources are not constrained; otherwise, prefer iteration.
⭐ Key Takeaways
- Header files are essential for organizing code. Use
#includeto include a.hfile containing function prototypes and constants defined with#define. - Remember the three scope levels: global (file-wide), function-level (inside
{ }of a function), and block-level (inside nested{ }). A variable is only visible within its declared scope. - Call by value is the safe default: a copy is passed, and the original variable is protected. Call by reference (using
&and*) allows a function to alter the original variable. Use it only when necessary. - Recursive functions call themselves and must have a base case to terminate. They offer elegant code for repetitive patterns but can be less efficient than iterative solutions.
- Minimize the use of global variables. Prefer local variables to promote encapsulation and prevent accidental side-effects.
🧠 Quick Revision Questions
- What is the purpose of a header file? Give two examples of what you might put inside it.
- A variable declared inside a
forloop's code block has which scope? Is it visible outside that block? - Explain the difference between call by value and call by reference. Which one protects the original variable from being modified?
- What is the output of a program that uses a global variable
int i = 5;and a functionvoid f() { i = 10; }called frommain()? What is the value ofiinmain()afterf()is called? - Identify the base case in the recursive factorial function
long fact(long n) { if (n <= 1) return 1; else return n * fact(n-1); }. What happens if the base case is missing?
📘 Lecture 11 — Arrays
📖 Overview: This lecture introduces the concept of arrays in C programming as a data structure for storing collections of identical data types. It covers array declaration, initialization, manipulation through loops, and practical applications including linear search and copying arrays, which are essential for handling large amounts of related data efficiently.
🗂️ Topics Covered
This lecture begins with an introduction to arrays as a solution for storing multiple values of the same type without declaring individual variables. It covers array declaration syntax, memory layout with contiguous storage, and index-based access starting from zero. The lecture explains multiple array initialization methods, demonstrates sample programs for reading input and calculating sums, discusses array copying techniques, introduces linear search algorithms, shows how to use the rand() function, and emphasizes the const keyword for declaring array sizes.
📝 Lecture Summary
Introduction
Arrays are introduced as a solution to the problem of storing many variables of the same type. For example, calculating the average age of 100 students would require 100 separate variables without arrays. Arrays provide a data structure where identical data types are stored in contiguous memory locations, making it possible to handle large collections efficiently.
Arrays
In C, every array has a data type, name, and size. The data type can be any valid C data type, and variable naming conventions apply to array names. The size must be a precise number. Arrays occupy memory depending on their size and have a contiguous area of memory. Array elements are accessed using an index (also called subscript), which starts from zero and goes up to one less than the array's size.
Declaration:
data_type array_name [size] ;
For example:
int ages[10];
An array int C[10] of integer type with name 'C' and size ten can contain ten elements. In memory, this array occupies forty bytes (one int = 4 bytes × 10). The index of the last element is always size minus one.
🔑 Definition — Array Index: The numeric position used to access individual elements of an array, starting from 0 and ending at size-1.
🔑 Definition — Contiguous Memory: Array elements are stored in consecutive memory locations, meaning element C[1] is located immediately after C[0] in memory.
Memory Image of Array C:
C[0] → value 24
C[1] → value 59
C[2] → value 35
C[3] → ...
...
C[7]
C[8]
C[9]
Index 6 ([6]) means the seventh element, and index 7 means the eighth element. The index of the last element is always one less than the array size.
Usage of Arrays: Arrays can be declared with simple variables in a single line:
int i, age [10];
int height [10], length [10] ;
Individual elements are accessed using the index mechanism, not the whole array at once. age[5] refers to a single element, not the entire array.
Example — Reading ages into an array using a loop:
for (i = 0 ; i < 10 ; i++ )
{
cout << "Please enter the age of the student ";
cin >> age [i];
}
Example — Calculating total of array elements:
int totalAge = 0;
for (i = 0 ; i < 10 ; i++ )
{
totalAge += age [i];
}
The loop index must be an integer, either a literal like 4, 5, or an integer variable like i.
💡 Why this matters: Arrays allow processing of multiple related values using loops, eliminating the need to write separate code for each individual variable. This becomes crucial when dealing with hundreds or thousands of data items.
Initialization of Arrays
Arrays should always be explicitly initialized rather than relying on default compiler initialization. There are several methods:
Method 1 — Using a loop:
int i, age [10];
for ( i = 0; i < 10 ; i++ )
{
age[i] = 0;
}
Method 2 — At declaration with initialization list:
int age [10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
Method 3 — Shortcut with single value:
int age [10] = { 0 };
This initializes all elements to zero.
Method 4 — Without specifying size:
int age [ ] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
The compiler detects the initialization list has ten zeros and creates an array of 10 integers.
🔑 Definition — Initialization List: A comma-separated list of values enclosed in curly braces used to set initial values of array elements at declaration time.
💡 Why this matters: Loop-based initialization is preferred for large arrays because writing explicit initialization lists becomes impractical for arrays with hundreds or thousands of elements.
Sample Program 1
Problem Statement: Write a program which reads positive integers from the user and stores them in an array. User can enter a maximum of 100 numbers. Stop taking input when user enters -1.
Solution:
An integer array of size 100 is declared. A do-while loop is used because the loop executes at least once. Two termination conditions exist: either 100 numbers entered or user enters -1. The logical AND operator && enforces both conditions must be true for loop continuation.
#include <iostream.h>
main( )
{
int c [ 100 ] ;
int z , i = 0 ;
do
{
cout << "Please enter the number (-1 to end input) " << endl;
cin >> z ;
if ( z != -1 )
{
c[ i ] = z ;
}
i ++ ;
} while ( z != -1 && i < 100 ) ;
cout << " The total number of positive integers entered by user is " << i - 1;
}
The assignment statement c[i] = z is inside the if block to prevent storing -1. The counter i is incremented in each repetition. After loop termination, the actual count of positive integers is i - 1.
Sample Output:
Please enter the number (-1 to end input) 1
2
3
4
5
6
-1
The total number of positive integers entered by user is 6
📌 Example: When user enters 1, 2, 3, 4, 5, 6, and then -1, the loop terminates. The variable i has been incremented to 7 (one for each input including -1), so the count is 7 - 1 = 6 positive integers.
Copying Arrays
To copy an array, both arrays must be of the same data type and same size. Arrays cannot be assigned directly with a single statement like b = a.
Using element-by-element assignment:
b[0] = a[0] ;
b[1] = a[1] ;
...
b[9] = a[9] ;
Using a loop (preferred method):
for (i = 0; i < 10 ; i ++)
{
b[i] = a[i];
}
🔑 Definition — Array Copy: The process of assigning each element of one array to the corresponding element of another array of the same type and size.
Example — Sum of squares program:
#include <iostream.h>
main()
{
int a[10];
int sumOfSquares = 0 ;
int i =0;
cout << "Please enter the ten numbers one by one " << endl;
for (i = 0 ; i < 10 ; i++ )
{
cin >> a [i];
}
for ( i = 0 ; i < 10 ; i ++ )
{
sumOfSquares = sumOfSquares + a[ i ] * a[ i ] ;
}
cout << "The sum of squares is " << sumOfSquares << endl;
}
Sample Output:
Please enter the ten numbers one by one
1
2
3
4
5
6
7
8
9
10
The sum of squares is 385
📌 Example: For numbers 1 through 10, the calculation is 1² + 2² + 3² + ... + 10² = 1 + 4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 + 100 = 385.
Linear Search
Linear search is a method to find a specific value in an array by comparing each element one by one. A flag variable (typically named found) indicates whether the target was found.
#include <iostream.h>
main()
{
int z, i ;
int a [ 100 ] ;
for ( i =0 ; i < 100 ; i ++ )
{
a [ i ] = i ;
}
cout << " Please enter a positive integer " ;
cin >> z ;
int found = 0 ;
for ( i = 0 ; i < 100 ; i ++ )
{
if ( z == a [ i ] )
{
found = 1 ;
break ;
}
}
if ( found == 1 )
cout << " We found the integer at index " << i ;
else
cout << " The number was not found " ;
}
Sample Output:
Please enter a positive integer 34
We found the integer at index 34
🔑 Definition — Linear Search: A sequential search algorithm that checks each element of an array until the target value is found or all elements have been examined.
Using the rand() function:
The rand() function generates random numbers between 0 and 32767. It requires including <stdlib.h>. The random number can be limited to a range using the modulus operator.
x = rand ( );
Generating a die roll (1 to 6):
1 + rand ( ) % 6;
Guessing game using rand():
#include <iostream.h>
#include <stdlib.h>
main()
{
int z, i ;
int a [ 100 ] ;
for ( i =0 ; i < 100 ; i ++ )
{
a [i] = rand() ;
}
cout << " Please enter a positive integer " ;
cin >> z ;
int found = 0 ;
for ( i = 0 ; i < 100 ; i ++ )
{
if ( z == a [ i ] )
{
found = 1 ;
break ;
}
}
if ( found == 1 )
cout << " We found the integer at position " << i ;
else
cout << " The number was not found " ;
}
Sample Output:
Please enter a positive integer 34
The number was not found
💡 Why this matters: Linear search is the simplest searching algorithm and works on unsorted data. The maximum number of comparisons equals the array size, making it suitable for small to medium-sized datasets but inefficient for very large arrays.
The Keyword 'const'
The const keyword creates an identifier whose value cannot be changed during program execution. It is used to declare array sizes, making code maintenance easier.
const int arraySize = 100;
Using const in array declaration:
int age [arraySize];
Using const in loop condition:
for ( i = 0; i < arraySize ; i ++)
🔑 Definition — const: A keyword that declares a variable as read-only, preventing its value from being modified after initialization.
📌 Example: If const int arraySize = 100; is declared, changing only this single line to const int arraySize = 200; automatically updates all array declarations and loop conditions throughout the program.
💡 Why this matters: Using const for array size is a good programming practice because it centralizes size management. Changing the array size requires modification in only one location, reducing the risk of errors from inconsistent updates across the program.
Tips
- Initialize the array explicitly
- Array index (subscript) starts from 0 and ends one less than the array size
- To copy an array, the size and data type of both arrays should be same
- Array subscript may be an integer or an integer expression
- Assigning another value to a
constis a syntax error
⭐ Key Takeaways
Students must remember that arrays are data structures for storing multiple elements of the same type in contiguous memory, with indices starting at 0 and ending at size-1. Array elements are accessed individually using subscript notation and can be processed using loops. Arrays can be initialized at declaration using initializer lists or through loops at runtime. Copying arrays requires element-by-element assignment since direct assignment is not allowed. Linear search sequentially compares each element until a match is found or all elements are exhausted. The const keyword should be used for array sizes to improve code maintainability and prevent accidental modification, and assigning a new value to a const variable is a syntax error.
🧠 Quick Revision Questions
- Why does array indexing in C start from 0 instead of 1, and what is the index of the last element in an array of size
n? - What are the four different methods to initialize an array, and which one is most suitable for large arrays?
- In the sample program that reads numbers until -1, why is the count of positive integers
i - 1instead of justi? - What are the two requirements for copying one array to another, and why can't you use a simple assignment statement like
b = a? - How does the modulus operator
%help in generating random numbers within a specific range, such as simulating a die roll (1-6) or a coin toss (0-1)?
📘 Lecture 12 — Character Arrays, Array Comparison, Sorting, Searching, Functions and Arrays, Multidimensional Arrays
📖 Overview: This lecture introduces character arrays for storing strings and explains their special properties, including null termination. It covers essential array operations like comparison, sorting (selection sort), and searching (linear and binary search). The lecture also explains how arrays are passed to functions (by reference), and introduces multidimensional arrays for handling matrices and higher-dimensional data.
🗂️ Topics Covered
Character arrays and their declaration, initialization, and null-terminated storage. Reading and displaying strings using cin and cout with loops. Comparing two arrays element-by-element. Sorting arrays using the selection sort (brute force) method. Searching arrays using linear search and the more efficient binary search algorithm. Passing arrays to functions — call by reference behavior. Multidimensional arrays (2D, 3D) and their population using nested loops.
📝 Lecture Summary
Character Arrays
Character arrays are used to store strings (sequences of characters). A simple char variable can hold only a single character, so a character array is needed for names or sentences. To declare one: char name[100];. A key property of character arrays is that C/C++ marks the end of a string with a null character (\0), which has an ASCII code of zero. When reading a string with cin >> name;, the computer reads characters until the Enter key is pressed and automatically appends a null character after the last character. Therefore, the array must be at least one element larger than the maximum number of characters intended to be stored.
🔑 Definition — Null Character (\0): A special character with ASCII code 0 that marks the termination point of a string in a character array. The compiler uses it to know where the string ends, preventing the display of garbage data beyond it.
📐 Formula for Array Size: Array Size = (Number of characters in string) + 1 (for the null terminator).
📌 Example: To store the string "imran" (5 characters), declare char name[100] = "imran"; or char name[] = "imran"; which automatically allocates 6 elements (5 letters + 1 null). When using a loop to display characters, it must stop at the null character: for(i=0; i<100; i++) { if(name[i]=='\0') break; cout << name[i]; }.
💡 Why this matters: Without proper null termination, functions that process strings will read past the intended end into garbage memory, causing unpredictable behavior or crashes.
Initialization Of Character Arrays
Character arrays can be initialized in two main ways: either as a list of individual characters enclosed in single quotes and curly braces (char name[100] = {'i','m','r','a','n'};), or more conveniently, as a string literal in double quotes (char name[100] = "imran";). If the array size is omitted in the declaration (e.g., char name[] = "Hello World";), the compiler automatically allocates the exact number of characters needed, plus one for the null character. This is the preferred method for safety and accuracy.
💡 Why this matters: Omitting the size prevents off-by-one errors. The compiler ensures the array is exactly sized for the string and its null terminator.
Arrays Comparison
Two arrays are considered equal only if they have the same size and every corresponding element matches exactly. This is done element-by-element, often within a loop. A flag variable (e.g., int equals = 0;) tracks the result. Using the not-equal operator (!=) is efficient: if at any position the elements differ, the loop breaks immediately, setting the flag to indicate inequality. If all elements match, the flag is set to indicate equality. This process applies to both integer and character arrays. For character arrays (strings), note that C++ is case-sensitive: 'A' is not equal to 'a'.
🔑 Definition — Flag: A variable (often an integer, 0 or 1) used to signal a condition or status, such as whether two arrays are found to be equal.
📌 Example: In the provided code, two arrays of 5 integers are compared. The loop for (i = 0; i < 5; i++) checks if (num1[i] != num2[i]). If a mismatch is found at index 2 (e.g., 5 vs 4), it prints "The arrays are not equal", sets equals=0, and breaks. If no mismatches occur, equals is set to 1 and "Both arrays are equal" is printed.
Sorting
The lecture covers a brute-force sorting technique (a form of selection sort) to arrange an array in ascending order. The process finds the smallest element in the entire array and swaps it with the element at the first position. It repeats this process: finding the smallest element in the remaining portion (starting from the second position) and swapping it with the element at the second position, and so on, until the entire array is sorted. Swapping is a critical technique that requires a temporary variable to hold one value during the exchange, preventing data loss.
🔑 Definition — Swapping: The process of exchanging two values using a temporary storage variable. For example, to swap num[0] and num[15]: int x = num[0]; num[0] = num[15]; num[15] = x;.
Searching
Linear search is a simple but potentially slow method: it compares the target value against every element of the array sequentially until found. It is applicable to unsorted arrays but may require as many comparisons as there are elements.
The binary search algorithm is much more efficient but requires the array to be sorted. It uses a "divide and conquer" strategy. The algorithm repeatedly divides the search interval in half. It compares the target value to the middle element. If the target is greater, the search continues only in the right half; if smaller, only in the left half. This halving process continues until the value is found or the interval is empty.
🔑 Definition — Binary Search: An efficient search algorithm that works on sorted arrays by repeatedly dividing the search space in half, significantly reducing the number of comparisons.
📐 Formula for Maximum Iterations: For an array of N elements, the maximum number of comparisons is log₂(N). For 1000 elements (≈ 2¹⁰), binary search requires no more than 10 comparisons, compared to up to 1000 for linear search.
Functions And Arrays
C++ uses call by value by default for simple variables: a copy of the variable's value is passed to the function. However, when an array is passed to a function, the default mechanism is call by reference. This is because the name of an array (e.g., name) represents the address (memory location) of its first element. Passing this address gives the function direct access to the original array's memory. Consequently, any modifications a function makes to the array's elements directly affect the original array in the calling program.
🔑 Definition — Call by Reference (Arrays): When an array name is passed to a function, the function receives the memory address of the array's first element, allowing it to modify the original array directly.
📌 Example: The provided code demonstrates this. The getvalues function receives an array parameter. Within it, the statement num[i] = i; modifies the original array declared in main. When main displays the array after the function call, it shows the values assigned within getvalues (0, 1, 2... 9).
💡 Why this matters: Passing a single element of an array (e.g., fn(x[3])) uses call by value for that single integer, leaving the original array unchanged. Understanding this distinction is crucial for designing robust functions.
Multidimensional Arrays
Multidimensional arrays extend the concept of arrays to multiple dimensions, like rows and columns. A two-dimensional array is declared as int matrix[2][3]; which defines 2 rows and 3 columns (a 2x3 matrix). Elements are accessed using two indices (row, column), starting from 0. To populate or display a 2D array, nested loops are used: the outer loop iterates over rows, and the inner loop iterates over columns. C++ supports arrays of any number of dimensions (e.g., a 3D array: int num[3][5][7];).
🔑 Definition — Multidimensional Array: An array that uses multiple indices to access its elements. A 2D array is visualized as a table of rows and columns.
📌 Example: To fill a 2x3 matrix from the user, the code uses:
for (row = 0; row < 2; row++) {
for (col = 0; col < 3; col++) {
cin >> matrix[row][col];
}
}
The inner loop runs 3 times for each single iteration of the outer loop, filling positions [0,0],[0,1],[0,2] first, then [1,0],[1,1],[1,2].
⭐ Key Takeaways
- Character arrays must be null-terminated with
\0, and their declared size should be at least one more than the string's length. - Comparing arrays requires element-by-element comparison. Using the
!=operator allows for an efficient early exit upon mismatch. - Binary search is a powerful, efficient algorithm that works only on sorted arrays, reducing search time from linear (N steps) to logarithmic (log N steps).
- Arrays are passed to functions by reference by default, meaning modifications within the function affect the original array. Single array elements are passed by value.
- Multidimensional arrays (e.g., 2D for matrices) are processed using nested loops, and C++ supports arrays of any dimension.
🧠 Quick Revision Questions
- What is the purpose of the null character (
\0) in a character array, and why is it automatically added? - Explain the brute-force sorting method described in the lecture. How does the swapping technique prevent data loss?
- What is the key precondition for using binary search on an array, and why does it make the algorithm significantly faster than linear search for large datasets?
- Why is passing an array to a function considered a call by reference? What is the underlying reason related to the array's name?
- How do you access the element in the second row and third column of a 2D integer array named
matrix? What is its index?
📘 Lecture 13 — Array Manipulation, Real World Problem and Design Recipe
📖 Overview: This lecture explores advanced array manipulation techniques, focusing on two-dimensional arrays and matrix operations like flipping and transposing. It then applies the design recipe methodology to solve a real-world tax anomaly problem, demonstrating how to break down complex problems into functional units.
🗂️ Topics Covered
The lecture covers the processing of two-dimensional arrays using nested loops, matrix flipping (reversing row order), matrix transposition with careful handling of triangular swapping, and the application of the design recipe to a real-world problem involving tax brackets and unlucky employees who take home less pay despite higher gross salaries.
📝 Lecture Summary
Array Manipulation
Two-dimensional arrays store data in rows and columns, similar to mathematical matrices. Identical or similar values are stored in arrays based on context — for example, height and age of individuals cannot be mixed in one array. Processing arrays naturally involves loops, where single-dimensional arrays use one loop and two-dimensional arrays use nested loops.
The lecture demonstrates reading a 3×3 matrix using nested loops. The outer loop iterates over rows while the inner loop iterates over columns for each row.
🔑 Definition — Two-dimensional array: A data structure with rows and columns, accessed as arrayName[rowIndex][columnIndex].
📐 Code structure for inputting a 2D array:
for (row = 0; row < maxRows; row++) {
for (col = 0; col < maxCols; col++) {
cin >> a[row][col];
}
}
📌 Example: For a 3×3 array, the outer loop runs with row=0,1,2. For row=0, the inner loop reads columns 0,1,2. Then row becomes 1, and columns 0,1,2 are read again, continuing until all 9 elements are entered.
Flipping a Matrix (Reversing Row Order)
To display a matrix with rows in reverse order, the lecture shows that we can start the row loop from maxRows - 1 and decrement down to 0. The column loop remains unchanged (starting from 0 to maxCols-1). This avoids creating a new matrix and saves memory.
🔑 Key technique: Loops can start from a higher value and decrement instead of always incrementing from zero.
📐 Code for displaying flipped matrix:
for (row = maxRows - 1; row >= 0; row--) {
for (col = 0; col < maxCols; col++) {
cout << a[row][col] << '\t';
}
cout << '\n';
}
The '\t' character displays a tab (spaces) on the screen, while '\n' moves the cursor to a new line.
💡 Why this matters: This approach is memory-efficient because it does not require declaring a separate array to store the flipped version — it simply changes the order of printing.
Transpose of a Square Matrix
Transpose means interchanging rows and columns: element A(i,j) is replaced with A(j,i). For a square matrix (equal number of rows and columns), the diagonal elements remain unchanged because row and column indexes are the same.
🔑 Definition — Transpose: An operation where rows become columns and columns become rows.
📐 Formula: A(i,j) → A(j,i)
⚠️ Critical issue: When transposing, simple nested loops running through all rows and columns will swap elements twice (once in the upper triangle, once in the lower triangle), leaving the matrix unchanged.
Solution: Process only one triangle (the upper triangle) and swap it with the lower triangle. Start the inner loop from the current row number (col = row) instead of from zero.
📐 Correct code for transpose using upper triangle:
for (row = 0; row < arraySize; row++) {
for (col = row; col < arraySize; col++) {
temp = a[row][col];
a[row][col] = a[col][row];
a[col][row] = temp;
}
}
💡 Why this matters: Starting col = row ensures each pair of elements is swapped only once. For row 0, columns 0,1,2 are processed. For row 1, processing starts from column 1 (not column 0), avoiding re-swapping the element already swapped with row 0.
Real World Problem and Design Recipe
The problem involves a company with up to 100 employees where tax brackets create an anomaly: some employees with higher gross salaries take home less pay than those with lower gross salaries due to tax rate jumps.
Tax brackets:
- Rs. 0 – Rs. 5,000: 0% tax
- Rs. 5,001 – Rs. 10,000: 5% tax
- Rs. 10,001 – Rs. 20,000: 10% tax
- Rs. 20,001 and above: 15% tax
📌 Example: A person earning Rs. 10,000 per month pays 5% tax (Rs. 500), taking home Rs. 9,500. A person earning Rs. 10,001 per month falls into the 10% bracket, paying Rs. 1,000.10 in tax, taking home only Rs. 9,000.90 — which is less than the person earning Rs. 10,000.
Design Recipe Steps:
- Analysis: Precise problem statement — "Given tax brackets and given employees' gross salaries, determine those employees who actually get less take-home salary than others with lower initial income."
- Input determination: Number of employees (up to 100) and their gross salaries.
- Storage design: Use a 2D array
sal[arraySize][2]— column 0 stores gross salary, column 1 stores net salary. Use a 1D arraylucky[arraySize]initialized to 0, where 0 means lucky and 1 means unlucky. - Functional decomposition into four parts:
getInput()— reads gross salariescalcNetSal()— calculates net salaries based on tax bracketsfindUnluckies()— identifies unlucky employeesprintUnluckies()— displays unlucky employee numbers
🔑 Key concept — Array passing to functions: By default, arrays are passed by reference in C++. For 2D arrays, the number of columns must be specified in the function parameter (e.g., double sal[][2]). Variables like numEmps can be passed by value or by reference using &.
📐 Code structure for main program:
const int arraySize = 100;
double sal[arraySize][2];
int lucky[arraySize] = {0};
int numEmps;
cin >> numEmps;
getInput(sal, numEmps);
calcNetSal(sal, numEmps);
findUnluckies(sal, numEmps, lucky);
printUnluckies(lucky, numEmps);
💡 Why this matters: The design recipe provides a systematic approach to problem-solving — from analysis through implementation — ensuring all requirements are addressed before coding begins.
⭐ Key Takeaways
The most critical points to remember from this lecture are: two-dimensional arrays require nested loops for processing, with the outer loop controlling rows and the inner loop controlling columns. For matrix flipping, reverse the row loop direction without changing column processing. For matrix transposition, only swap the upper triangle with the lower triangle by starting the inner loop at the current row index to avoid double-swapping. The real-world problem demonstrates the complete design recipe: precise problem statement, input/output analysis, storage planning, functional decomposition, and implementation. Arrays are always passed by reference to functions, but integer variables like employee count must be explicitly passed by reference using & if changes need to persist outside the function.
🧠 Quick Revision Questions
- Why must the inner loop start at
col = rowwhen transposing a square matrix, rather thancol = 0? - What is the difference between flipping a matrix and transposing a matrix?
- How does the
markIfUnlucky()function determine whether an employee is unlucky? - Why must the number of columns be specified when passing a 2D array to a function?
- In the real-world problem, what happens if
numEmpsis declared inside thegetInput()function instead of inmain()?
📘 Lecture 14 — Pointers
📖 Overview: This lecture introduces pointers, a special type of variable that stores memory addresses rather than data values. It explains how pointers enable indirect referencing and call-by-reference in functions, which is essential for efficient memory management and modifying variables inside functions.
🗂️ Topics Covered
The lecture covers the concept of pointers as variables that store memory addresses, their declaration syntax using the asterisk operator, and the address-of (&) and dereferencing (*) operators. It includes a detailed bubble sort example demonstrating pointer-based swapping, explains how pointers enable call-by-reference in function calls, and discusses constant pointers and pointers to constant data. A second example shows pointer arithmetic for converting a string to uppercase.
📝 Lecture Summary
Pointers
Pointers are special variables that store a memory address rather than a data value. They contain the address of another variable, not the variable's value itself. This is analogous to locating a house either by the owner's name (direct reference) or by the house number and street address (indirect reference). In programming, normal variable names provide direct reference to memory locations, while pointers provide indirect reference through memory addresses.
🔑 Definition — Direct Reference: Using a variable's name to access its value (e.g., x = 10).
🔑 Definition — Indirect Reference: Using a pointer variable that contains the memory address of another variable to access that variable's value.
Declaration of Pointers
Pointers are declared with a specific syntax: data_type *pointer_name;. The asterisk (*) indicates that the variable is a pointer, and it is associated with the variable name, not the data type. For example, int *myptr; declares myptr as a pointer to an integer. Multiple pointers on one line require * with each name: int *ptr1, *ptr2, *ptr3;. Pointers can also be declared alongside simple variables: int *ptr, x, a[10];.
🔑 Definition — Address Operator (&): Used to get the memory address of a variable. Example: ptr = &x; assigns the address of x to pointer ptr.
🔑 Definition — Dereferencing Operator (*): Used to access the value stored at the memory address held by a pointer. Example: *ptr gives the value stored at the address ptr points to.
📐 Formula: ptr = &x; → assigns the memory address of variable x to pointer ptr
📐 Formula: z = *ptr; → assigns the value stored at the address ptr points to, to variable z
📌 Example: If x = 10 is stored at memory address 400000, and ptr is a pointer variable stored at address 500000, then after ptr = &x;, ptr holds the value 400000. The expression *ptr evaluates to 10.
Pointers can be initialized by assigning an address with &, or by setting them to 0 or NULL (a null pointer pointing to nothing). A null pointer indicates it currently points to no valid memory location.
Example 1 (Bubble Sort)
Swapping values of two variables requires a temporary variable: temp = x; x = y; y = temp;. However, a function swap(x, y) using call-by-value cannot actually swap the original variables because it only receives copies.
To swap values using a function, call-by-reference with pointers is used. The addresses of variables are passed, and the function receives pointers. The code demonstrates bubble sorting an array x[] = {1,3,5,7,9,2,4,6,8,10}. The swap function uses pointers:
void swap(int *x, int *y) {
int tmp;
if(*x > *y) {
tmp = *x;
*x = *y;
*y = tmp;
}
}
Inside the main loop, swap(&x[j], &x[j+1]); passes addresses to swap array elements.
📌 Example: The bubble sort program's output shows the array being sorted step by step: 1 3 5 7 2 4 6 8 9 10 → 1 3 5 2 4 6 7 8 9 10 → 1 3 2 4 5 6 7 8 9 10 → 1 2 3 4 5 6 7 8 9 10 → final sorted array.
💡 Why this matters: Passing large arrays to functions by value copies all elements, which is inefficient. Using pointers (call-by-reference) passes only the starting address, saving memory and time.
Pointers and Call By Reference
When passing an array to a function, only the array's starting address is passed by default (call-by-reference). This is efficient for large data sets. However, the called function can modify the original values, which may be undesirable.
🔑 Definition — Constant Pointer: Declared as int *const myptr = &x;. The pointer itself is constant and cannot point to another variable, but the value it points to can be changed.
🔑 Definition — Pointer to Constant Data: Declared as const int *myptr = &x;. The pointer can point to different variables, but the value at the pointed location cannot be changed through this pointer.
This construct is useful when passing addresses to functions for efficiency but preventing modification of the data. Function declaration: fn (const int *myptr) { ... } ensures the function cannot change the pointed value.
Example 2
This example converts lowercase letters in a string to uppercase using pointers and pointer arithmetic.
The function convertToUppercase(char *sptr) processes a string character by character:
void convertToUppercase(char *sptr) {
while (*sptr != '\0') {
if (islower(*sptr))
*sptr = toupper(*sptr);
++sptr;
}
}
The string "Welcome To Virtual University" is passed to this function. The pointer sptr starts at the first character. *sptr dereferences it to get the character. islower() checks if it's lowercase, and toupper() converts it. ++sptr increments the pointer to move to the next character (pointer arithmetic).
📌 Example: Input: "Welcome To Virtual University" → Output: "WELCOME TO VIRTUAL UNIVERSITY"
The functions islower and toupper are from the <ctype.h> header file.
⭐ Key Takeaways
A pointer is a variable that stores a memory address, declared with data_type *name;. The address-of operator & gets a variable's address, and the dereferencing operator * accesses the value at a pointer's stored address. Pointers enable call-by-reference, allowing functions to modify original variables instead of copies, which is efficient for large data like arrays. Constant pointers (int *const) cannot be reassigned, while pointers to constant data (const int *) prevent modification of the pointed value. Pointer arithmetic (e.g., ++sptr) allows sequential access to array elements.
🧠 Quick Revision Questions
- What is the difference between a normal variable and a pointer variable?
- How do you declare a pointer to a double-precision floating-point number?
- What does the statement
ptr = &x;accomplish? - Why does a simple
swap(x, y)function fail to swap values in the calling function, and how do pointers fix this? - What is the difference between
int *const myptr;andconst int *myptr;?
📘 Lecture 15 — Pointers and Arrays
📖 Overview: This lecture explores the powerful relationship between pointers and arrays in C/C++. It covers how array names are constant pointers, pointer arithmetic, pointer comparison, and how to manipulate strings and character arrays using pointers, enabling efficient data access and manipulation.
🗂️ Topics Covered
The lecture introduces the relationship between pointers and arrays, explaining that array names are constant pointers. It then covers pointer expressions and arithmetic, including incrementing pointers based on data type sizes. Pointer comparison using relational operators is discussed, followed by a detailed examination of strings as character arrays, including copying strings using pointers and the use of the const keyword.
📝 Lecture Summary
Introduction
This lecture builds on the previous discussion of pointers. While topics like pointers are often excluded from newer languages like Java, in C/C++ they offer powerful capabilities. The discussion covers the relationship between pointers and arrays, pointer expressions, arithmetic operations with pointers, and strings.
Relationship between Pointers and Arrays
When you declare int x, a symbolic name is attached to a memory location. With an array like int y[10], memory is reserved for ten integers collectively named y. The identifier y itself represents the memory address of the beginning of this reserved memory space. The first element is accessed as y[0].
🔑 Definition — Array Name as Constant Pointer: "The name of the array is a constant pointer which contains the memory address of the first element of the array."
The key difference between an array name and an ordinary pointer is that the array name is a constant pointer — it always points to the start of the array and cannot be reassigned a different address.
📌 Example:
int y[10];
int *yptr;
yptr = y; // yptr now holds the same address as y (the first element)
Here, y and yptr both point to the first element, but y is constant while yptr is a variable pointer that can be changed.
💡 Why this matters: Understanding that an array name is a constant pointer means you cannot do y++ to move through the array; you must use a pointer variable like yptr for that.
Pointer Expressions and Arithmetic
To access array elements, you can use both array notation and pointer arithmetic. For example, to access the fourth element: y[3] or *(yptr + 3).
When a pointer is incremented, it doesn't just increase by 1. The increment amount depends on the data type the pointer points to. For an integer pointer, yptr++ increments the address by the size of an integer (typically 4 bytes).
🔑 Definition — Pointer Increment: "When a pointer is incremented, it actually jumps the number of memory spaces according to the data type that it points to."
📌 Example Program:
#include<iostream.h>
main()
{
int y[10];
int *yptr;
yptr = y;
cout << "Address before increment: " << yptr << endl;
yptr++;
cout << "Address after increment: " << yptr << endl;
}
Sample output showing a difference of 4 bytes (in hexadecimal):
The memory address of yptr = 0x22ff50
The memory address after incrementing yptr = 0x22ff54
You can also assign the address of a specific element to a pointer: yptr = &y[0]; or yptr = &y[3];.
📌 Example — Different Ways to Access Array Elements:
int y[10] = {0,5,10,15,20,25,30,35,40,45};
int *yptr;
yptr = y;
cout << y[5] << endl; // 25
cout << *(yptr + 5) << endl; // 25
cout << yptr[5] << endl; // 25
This demonstrates three equivalent ways to access the 6th element (y[5]).
📌 Example — Stepping Through an Array Using a Pointer:
int y[10] = {10,20,30,40,50,60,70,80,90,100};
int *yptr = y;
for (int i = 0; i < 10; i++)
{
cout << "Value at position " << i << " is " << *yptr << endl;
yptr++;
}
💡 Why this matters: Pointer arithmetic allows efficient traversal of arrays without using index variables, making code faster and more flexible.
Dereferencing and Increment Distinctions
There is an important distinction between incrementing the pointer and incrementing the value where the pointer points to.
📌 Example — Program Using Pointer Arithmetic:
int x = 10;
int *yptr;
yptr = &x;
(*yptr)++; // Increments the value at the address (x becomes 11)
cout << "x is now: " << x << endl; // Output: 11
Key distinctions:
(*yptr)++— increments the value pointed to (x becomes 11)*yptr + 3— evaluates the value plus 3 but does NOT change the stored value*yptr += 3— increments the value by 3 (x becomes 13)yptr++— increments the address (pointer moves to next element)
🔑 Important Warning: "When a pointer is used to hold the memory address of a simple variable, do not increment or decrement the pointer. When a pointer is used to hold the address of an array, it makes sense to increment or decrement the pointer."
💡 Why this matters: Incrementing a pointer that points to a single variable can cause it to point to an invalid memory location, potentially crashing the program. Only increment/decrement pointers when they point to array elements.
Pointer Subtraction
You cannot add two pointers (e.g., yptr1 + yptr2 is invalid), but you can subtract them. Pointer subtraction gives the distance between two pointers in units of the data type they point to.
📌 Example — Pointer Subtraction:
int y[10], *yptr1, *yptr2;
yptr1 = &y[0];
yptr2 = &y[3];
cout << "Difference = " << yptr2 - yptr1; // Output: 3
The result is 3 because there are 3 array elements between y[0] and y[3].
🔑 Definition — Pointer Subtraction Result: Pointer subtraction tells how many units of the data type (not bytes) are between the two pointers.
A memory diagram shows:
Addresses: 3000 3004 3008 3012 3016
y[0] y[1] y[2] y[3] y[4]
| |
yptr yptr+4
Each integer element occupies 4 bytes in this example.
💡 Why this matters: Pointer subtraction is useful for determining the number of elements between two positions in an array, which is helpful in sorting algorithms and range checks.
Pointer Comparison
Pointers can be used in conditional statements with all comparison operators (less than, greater than, equal to, etc.).
When comparing dereferenced pointers (*yptr1 > *yptr2), you are comparing the values they point to — this is normal integer comparison.
When comparing the pointers themselves (yptr1 > yptr2), you are comparing their memory addresses.
📌 Example — Dereference Pointer Comparison:
int x, y, *xptr, *yptr;
cin >> x >> y;
xptr = &x;
yptr = &y;
if (*xptr > *yptr)
cout << "x is greater than y";
else
cout << "y is greater than x";
Sample Run:
Please enter the value of x = 6
Please enter the value of y = 9
y is greater than x
💡 Why this matters: Pointer comparison allows you to determine which pointer points to a higher memory address, which is useful in sorting and searching algorithms.
Pointer, String and Arrays
Character strings are arrays of characters terminated by a null character ('\0').
🔑 Definition — Null Character: The null character '\0' is a special escape character that marks the end of a string. It is considered a single character by the compiler.
📌 Example — Manual String Initialization:
char name[20];
name[0] = 'A';
name[1] = 'm';
name[2] = 'i';
name[3] = 'r';
name[4] = '\0'; // Must explicitly add null terminator
A shorter way using double quotes — the compiler automatically adds the null character:
char name[20] = "Amir"; // null character added automatically
🔑 Important Rule: "Arrays must be at least one character space larger than the number of printable characters which are to be stored."
Copying Strings Using Pointers
The lecture presents a complete program that copies one character array into another using pointers.
📌 Example — String Copy Program:
#include <iostream.h>
main()
{
char strA[80] = "A test string";
char strB[80];
char *ptrA = strA;
char *ptrB = strB;
while(*ptrA != '\0')
{
*ptrB++ = *ptrA++; // Copy character, then increment both pointers
}
*ptrB = '\0'; // Add null terminator to destination
cout << "String in strA = " << strA << endl;
cout << "String in strB = " << strB << endl;
}
Output:
String in strA = A test string
String in strB = A test string
Explanation: The statement *ptrB++ = *ptrA++ works as follows:
- The character
ptrApoints to is assigned to the locationptrBpoints to - Both pointers are then incremented (postfix increment)
- This repeats until
ptrApoints to the null character ('\0') - A null character is explicitly added to the end of
strB
💡 Why this matters: This technique demonstrates efficient string manipulation without using library functions, and it introduces the concept of passing references through pointers.
Function for String Copy Using Pointers
A reusable function can be created using const to protect the source string from accidental modification:
void myStringCopy(char *destination, const char *source)
{
while(*source != '\0')
{
*destination++ = *source++;
}
*destination = '\0';
}
Alternatively, using array notation:
void myStringCopy(char dest[], char source[])
{
int i = 0;
while (source[i] != '\0')
{
dest[i] = source[i];
i++;
}
dest[i] = '\0';
}
🔑 Key Point: When arrays are passed to functions, a reference to the original array is passed (call by reference). Therefore, the function does not need to return anything — changes are made directly to the original array.
⭐ Key Takeaways
- An array name is a constant pointer that always points to the first element of the array and cannot be reassigned. This is fundamentally different from a pointer variable which can be incremented or changed.
- Pointer arithmetic is type-sensitive — incrementing an integer pointer advances it by the size of an integer (usually 4 bytes), while incrementing a char pointer advances by 1 byte. This allows efficient array traversal.
- Never increment a pointer to a single variable — this can cause the pointer to point to invalid memory. Only increment/decrement pointers that point to array elements.
- Strings are null-terminated character arrays — always allocate at least one extra character for the null terminator (
'\0'). The null character allows functions likecoutand thewhileloop to know where the string ends. - Use
constwith pointers to protect source data — when passing a pointer to a function and you don't want the original data to be modified, useconstto enforce read-only access.
🧠 Quick Revision Questions
- What is the difference between an array name (
y) and a pointer variable (yptr) in terms of reassignment? - If
yptris an integer pointer pointing to address 3000 and integers occupy 4 bytes, what will be the value ofyptrafteryptr++? - What is the result of subtracting two pointers that point to
y[0]andy[5]in an integer array? - Why must a null character (
'\0') be added at the end of a character array when manually constructing a string? - What does the expression
*ptrB++ = *ptrA++do in a single statement?
📘 Lecture 16 — Pointers (continued), Multi-dimensional Arrays, Pointers to Pointers, Command-line Arguments
📖 Overview: This lecture deepens the understanding of pointers by contrasting them with array names, exploring multi-dimensional array manipulation using pointer arithmetic, introducing pointers to pointers, and explaining how command-line arguments can be passed to a program. A comprehensive case study on card shuffling and dealing demonstrates these concepts in a real-world application.
🗂️ Topics Covered
The lecture begins by comparing array names (constant pointers) with pointer variables, then moves into multi-dimensional array memory layout and pointer-based element access. It introduces the concept of pointers to pointers, demonstrates command-line argument handling via argc and argv, and concludes with a detailed card shuffling and dealing simulation that applies pointer to pointer, array of pointers, and random number generation techniques.
📝 Lecture Summary
Pointers (continued)
When a character array is declared as char myName[] = "Full Name";, the array name myName becomes a constant pointer — its address cannot be changed. In contrast, char *myNamePtr = "Full Name"; creates a pointer variable that can be reassigned to point to another string. Both store the starting address of the string "Full Name\0", but the array name is immutable, while the pointer variable is mutable.
🔑 Definition — Constant Pointer: A pointer whose memory address cannot be reassigned after initialization. Array names behave as constant pointers.
Multi-dimensional Arrays
Consider a two-dimensional array char multi[5][10];. In memory, elements are stored contiguously in a single linear sequence (row-major order). Unlike a one-dimensional array where adding 1 to the pointer moves to the next element, adding 1 to the name of a two-dimensional array (multi + 1) jumps over an entire row — in this case, 10 bytes (since char is 1 byte). This is because multi is treated as an array of arrays; multi + 1 points to the first element of the second row.
To access multi[1][2] (second row, third column) using pointer notation: *(*(multi + 1) + 2). The first dereference *(multi + 1) gives the address of the first element of row 1, and the second dereference fetches the value at column 2 within that row. This is double dereferencing.
💡 Why this matters: A function receiving a multi-dimensional array must know all dimensions except the leftmost one to correctly compute element addresses.
🔑 Definition — Double Dereferencing: Using the * operator twice to access a value through a pointer to a pointer (e.g., *(*(multi+row)+col)).
📐 Formula: array[row][col] ↔ *(*(array + row) + col)
📌 Example: For int multi[5][10], to access the element at row 3, column 3 using pointer notation: *(*(multi + 3) + 3) is equivalent to multi[3][3].
Pointers to Pointers
A pointer to pointer is a variable that stores the address of another pointer. In the case of double dereferencing, the first pointer contains the address of a second pointer, which contains the address of the variable holding the desired value. This construct is useful when dealing with arrays of strings of variable lengths.
Using a conventional two-dimensional array to store character strings of different lengths wastes memory because all rows must have the same fixed number of columns (based on the longest string). Instead, an array of pointers can be declared: char *myarray[] = {"Amir", "Jehangir"};. Here, myarray is an array of pointers, where each pointer points to a character string. The compiler allocates exactly the required space for each string: 5 bytes for "Amir\0" and 9 bytes for "Jehangir\0", eliminating memory waste.
🔑 Definition — Array of Pointers: An array where each element is a pointer, often used to store strings of varying lengths efficiently.
📌 Example: char *myarray[10]; declares an array of 10 pointers to characters.
Command Line Arguments
Programs can accept input from the command line using parameters inside the main() function: void main(int argc, char **argv).
- argc (argument count): an integer representing the number of command-line arguments, including the program name.
- argv (argument vector): a pointer to an array of character strings (pointer to pointer to char) containing the arguments.
The first argument (*(argv + 0) or argv[0]) is always the program name. Arguments are typically used to specify file names or options. If a program requires command-line arguments but none are provided, it should print an error message and instructions.
📐 Formula: main(int argc, char **argv) — argc is the count, argv is the array of argument strings.
📌 Example: Running C:\>Program-name 10 display sets argc = 3, argv[0] = "Program-name", argv[1] = "10", argv[2] = "display". The integer value can be extracted using the atoi() function: atoi(argv[1]) returns 10.
Case Study: A Card Shuffling and Dealing Simulation
This real-world example demonstrates pointer to pointer and array of pointers concepts.
Problem: Randomly shuffle a 52-card deck and deal all cards.
Design:
- Declare constant arrays of pointers for suits and faces:
const char *suite[4] = {"Hearts", "Diamonds", "Clubs", "Spades"};const char *face[13] = {"Ace", "Deuce", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King"};
- Represent the deck as a 2D integer array:
int deck[4][13] = {0};(initialized to all zeros, meaning no card has been placed yet).
Shuffle function: Uses a loop (card number 1 to 52) to randomly select a row (suit) and column (face) using rand() % 4 and rand() % 13. The do-while loop ensures that a slot is only filled if it hasn't been occupied yet (wDeck[row][column] != 0). Each card is assigned its sequential number in the deck array.
Deal function: Uses nested loops to search for each card number (1 to 52) in the deck array. When found, it prints the card using the face and suit arrays: wFace[column] << " of " << wSuit[row].
Random number seeding: The srand(time(0)) function seeds the random number generator with the current time (seconds elapsed since 1970), ensuring a different sequence of random numbers each time the program runs.
🔑 Definition — srand(): A function that seeds the random number generator to produce different sequences of random numbers on each program execution.
📌 Example: srand(time(0)); uses the current time as a seed for rand(), making the shuffle truly random.
⭐ Key Takeaways
Array names are constant pointers whose address cannot be changed, unlike pointer variables which can be reassigned. For multi-dimensional arrays, pointer arithmetic jumps entire rows — adding 1 to the array name skips one complete row, and elements are accessed via double dereferencing. Pointers to pointers enable efficient storage of variable-length strings through arrays of pointers, avoiding memory waste. Command-line arguments allow programs to accept input at execution time, with argc storing the count and argv providing the argument strings. The card shuffling case study integrates all these concepts — arrays of pointers for suits/faces, a 2D array for the deck, random number generation with seeding, and nested loops for dealing.
🧠 Quick Revision Questions
- What is the difference between
char myName[] = "Hello";andchar *myNamePtr = "Hello";regarding pointer mutability? - If
int arr[4][6];is declared, how many bytes doesarr + 1jump? Explain why. - Write the pointer notation equivalent of
multi[2][5]for a two-dimensional arraymulti. - Why does using an array of pointers to store strings of different lengths save memory compared to a 2D character array?
- In the command line
C:\> myprog 25, what are the values ofargcandargv?
📘 Lecture 17 — Lecture No. 17
📖 Overview: This lecture explores how to handle and manipulate strings in C/C++ programming. It covers the fundamental character representation (ASCII), built-in functions for character testing and conversion, string copy/concatenation/comparison operations, string conversion functions, and search functions, enabling efficient text processing.
🗂️ Topics Covered
This lecture covers String Handling concepts and evolution, detailed explanation of String Manipulation Functions, Character Handling Functions from the ctype.h library with a sample program demonstrating character analysis, String Conversion Functions from stdlib.h for converting strings to numbers, String Functions from string.h including strcpy, strcat, strcmp, and strlen, Search Functions for locating characters and substrings, and concludes with examples and exercises.
📝 Lecture Summary
String Handling
The lecture begins with the historical context of string handling, tracing back to the development of text processing tools at BELL Laboratories alongside C language and UNIX. Scientists needed a way to format and publish articles using text editors and in-line commands before modern word processors existed. The lecture then introduces the fundamental building block of strings: the character. Characters are stored inside computers as numbers, primarily using the ASCII (American Standard Code for Information Interchange) code. The char data type stores a single character, while int stores whole numbers. A character occupies one byte (8 bits), allowing 256 different values.
🔑 Definition — ASCII: A standard code used by computers to represent characters as numbers in memory.
📐 The relationship between integers and characters is direct: when an integer value (0-255) is assigned to a char variable, it displays the corresponding ASCII character.
📌 Example: The lecture provides a program that displays the ASCII code table. It uses a loop from i=0 to i<255, and inside the loop, the statement c = i assigns the integer value to the character variable, then displays both the integer and its character representation. The output shows that values for 'a'-'z' and 'A'-'Z' are continuous ranges.
Character Handling Functions
C provides many functions for testing and manipulating character data, found in the header file ctype.h. Any program using these functions must include this header. Each function in ctype.h receives a character (as an int) or EOF (End of File) as an argument. These functions have self-explanatory names and return true (non-zero) or false (zero).
🔑 Definition — EOF (End of File): A special character that indicates the end of a file or input stream.
Key functions from this library:
- int isdigit( int c ): Returns true if c is a digit (0-9), false otherwise.
- int isalpha( int c ): Returns true if c is a letter (a-z, A-Z), false otherwise.
- int isalnum( int c ): Returns true if c is a digit or a letter, false otherwise.
- int islower( int c ): Returns true if c is a lowercase letter, false otherwise.
- int isupper( int c ): Returns true if c is an uppercase letter, false otherwise.
- int tolower( int c ): If c is an uppercase letter, returns its lowercase equivalent; otherwise returns c unchanged.
- int toupper( int c ): If c is a lowercase letter, returns its uppercase equivalent; otherwise returns c unchanged.
- int isspace( int c ): Returns true if c is a white-space character (newline '\n', space ' ', form feed '\f', carriage return '\r', horizontal tab '\t', or vertical tab '\v'), false otherwise.
- int iscntrl( int c ): Returns true if c is a control character, false otherwise.
- int ispunct( int c ): Returns true if c is a printing character other than a space, digit, or letter, false otherwise.
- int isprint( int c ): Returns true if c is a printing character (including space ' '), false otherwise.
- int isgraph( int c ): Returns true if c is a printing character other than space ' ', false otherwise.
- int isxdigit( int c ): Returns true if c is a hexadecimal digit character (0-9, a-f, A-F), false otherwise.
The tolower and toupper functions are conversion functions that change the case of alphabetic characters while leaving non-alphabetic characters unchanged.
💡 Why this matters: Character handling functions are essential for validating user input, parsing text, and processing strings character-by-character in programs.
Sample Program
A complete sample program demonstrates the use of ctype.h functions by prompting the user to enter a string, then counting different types of characters (lowercase letters, uppercase letters, digits, white space, punctuation, and others). This program uses the getchar() function (from stdio.h) instead of cin to read a single character at a time from the input buffer. The loop continues until the user presses ENTER, which produces the newline character '\n'.
// Key structure of the program
while ((c = getchar()) != '\n')
{
if (islower(c)) lc++;
else if (isupper(c)) uc++;
else if (isdigit(c)) dig++;
else if (isspace(c)) ws++;
else if (ispunct(c)) pun++;
else oth++;
}
📌 Example: For input "Sixty Five = 65.00", the output shows:
- Lower case letters = 7 (i,x,t,y,i,v,e)
- Upper case letters = 2 (S, F)
- Digits = 4 (6,5,6,5)
- White space = 3 (spaces between words)
- Punctuation = 2 (= and .)
- Others = 0
String Conversion Functions
The header file stdlib.h includes functions for converting strings to different numeric types. These are essential when dealing with command-line arguments (which are always stored as character strings) or when input is received as a string but needs to be used as a number.
🔑 Definition — Command-line arguments: Parameters passed to a program when it is executed from the command line, stored as character strings in argv[].
Key conversion functions:
- double atof( const char *nPtr ): Converts the string nPtr to double.
- int atoi( const char *nPtr ): Converts the string nPtr to int.
- long atol( const char *nPtr ): Converts the string nPtr to long int.
- **double strtod( const char *nPtr, char endPtr ): Converts the string nPtr to double.
- **long strtol( const char *nPtr, char endPtr, int base ): Converts the string nPtr to long.
- **unsigned long strtoul( const char *nPtr, char endPtr, int base ): Converts the string nPtr to unsigned long.
📌 Example: The lecture provides a program that uses atoi to validate user input. It prompts for an integer between 10-100, reads it as a string (myInt), then checks if (atoi(myInt) == 0) to detect non-numeric input. If valid, it converts and checks if the integer is within range. Output for input "45.5" shows "OK, you have entered 45" because atoi converts until it encounters a non-digit character.
String Functions
The header file string.h provides functions for manipulating strings (character arrays terminated by null character '\0'). These functions handle copying, concatenation, comparison, and length determination.
Key string functions:
- char *strcpy( char *s1, const char *s2 ): Copies string s2 into character array s1. Returns s1. s2 (source) remains unchanged, while s1 (destination) receives the copy. The const keyword prevents modification of the source string.
- char *strncpy( char *s1, const char *s2, size_t n ): Copies at most n characters of string s2 into array s1. Returns s1. s1 must be large enough to hold the copied characters.
- char *strcat( char *s1, const char *s2 ): Appends string s2 to array s1. The first character of s2 overwrites the terminating null character of s1. Returns s1.
- char *strncat( char *s1, const char *s2, size_t n ): Appends at most n characters of string s2 to array s1. Returns s1.
- int strcmp( const char *s1, const char *s2 ): Compares string s1 to s2 lexicographically. Returns a negative number if s1 < s2, zero if s1 == s2, or a positive number if s1 > s2.
- int strncmp( const char *s1, const char *s2, size_t n ): Compares up to n characters of string s1 to s2. Returns negative, zero, or positive as with strcmp.
- int strlen( const char *s ): Determines the length of string s. Returns the number of characters preceding the terminating null character.
💡 Why this matters: String comparison considers spaces and case sensitivity. For example, "Hello", "hello", and "He llo" are all different strings because of case differences and spaces.
Examples
Example 1 (strcpy and strncpy): This program demonstrates string copy operations. Two strings are initialized as "String1" and "String2". After strcpy(string2, string1), both strings become "String1". Then strncpy(string3, string1, 3) copies only the first 3 characters ("Str") into string3.
📌 Example output:
Before the copy:
String 1: String1
String 2: String2
After the copy:
String 1: String1
String 2: String1
strncpy (string3, string1, 3) = Str
Example 2 (strcat and strncat): This program demonstrates string concatenation. s1 = "Welcome to", s2 = "Virtual University", s3 = "" (empty). After strcat(s1, s2), s1 becomes "Welcome to Virtual University". Then strncat(s3, s1, 6) copies the first 6 characters ("Welcom") into s3. Note that the output shows 7 characters ("Welcome") as mentioned in the output.
📌 Example output:
s1 = Welcome to
s2 = Virtual University
s3 =
strcat( s1, s2 ) = Welcome to Virtual University
strncat( s3, s1, 7 ) = Welcome
Example 3 (Comprehensive string manipulation): This program demonstrates multiple string functions together. It initializes four strings: s1="Welcome to", s2="Virtual University", s3="Welcome to Karachi", city="Karachi", province="Sind", and an empty string s[80].
strlen(s1)returns 11 (including the space, but not the null terminator)strlen(s2)returns 18strlen(s3)returns 18strcpy(s, "Hyderabad")copies "Hyderabad" into s- Multiple
strcatcalls build the sentence: "Hyderabad and Karachi are in Sind." strcmp(s1, s2)returns non-zero (not equal), so it outputs "s1 and s2 are not identical"strncmp(s1, s3, 7)compares first 7 characters ("Welcome") and returns zero (equal), so it outputs "First 7 characters of s1 and s3 are identical"
📌 Example output:
s1 = Welcome to
s2 = Virtual University
s3 = Welcome to Karachi
The length of s1 = 11
The length of s2 = 18
The length of s3 = 18
The nearest city to Karachi is Hyderabad
Hyderabad and Karachi are in Sind.
s1 and s2 are not identical
First 7 characters of s1 and s3 are identical
Search Functions
C provides a set of search functions for locating characters and substrings within strings. These functions are also defined in string.h.
Key search functions:
- char *strchr( const char *s, int c ): Locates the first occurrence of character c in string s. Returns a pointer to c in s if found, otherwise returns NULL.
- size_t strcspn( const char *s1, const char *s2 ): Returns the length of the initial segment of s1 consisting of characters NOT in s2.
- size_t strspn( const char *s1, const char *s2 ): Returns the length of the initial segment of s1 consisting only of characters IN s2.
- char *strpbrk( const char *s1, const char *s2 ): Locates the first occurrence in s1 of any character from s2. Returns a pointer to that character in s1, or NULL.
- char *strrchr( const char *s, int c ): Locates the last occurrence of c in string s. Returns a pointer to that character in s, or NULL.
- char *strstr( const char *s1, const char *s2 ): Locates the first occurrence of string s2 in string s1. Returns a pointer to the beginning of the substring in s1, or NULL.
- char *strtok( char *s1, const char *s2 ): Breaks string s1 into tokens (logical pieces like words) separated by delimiter characters in s2. The first call uses s1 as the first argument; subsequent calls use NULL to continue tokenizing the same string. Returns a pointer to the current token, or NULL if no more tokens exist.
💡 Why this matters: Search functions enable powerful text processing operations like finding words in documents, parsing command-line arguments, and breaking strings into components.
⭐ Key Takeaways
The most critical concepts from this lecture are understanding that characters are stored as numeric ASCII codes and can be tested or converted using ctype.h functions like isdigit(), isalpha(), and toupper(). String manipulation functions in string.h (strcpy, strcat, strcmp, strlen) provide essential operations for handling character arrays, while conversion functions in stdlib.h (atoi, atof, atol) convert string input to numeric types. Search functions like strstr and strtok enable locating substrings and parsing text into components. The getchar() function is useful for character-by-character input processing, and always remember that string comparison is case-sensitive and space-sensitive.
🧠 Quick Revision Questions
- What does the
isalnum()function return when given the character '5'? What about the character '#'? - Write the C++ code using strcmp to determine if two character arrays "Hello" and "hello" are identical. What value would strcmp return?
- Explain the difference between
strcpy(s1, s2)andstrncpy(s1, s2, n). When would you use one over the other? - What does the
atof()function do, and from which header file does it come? What wouldatof("3.14")return? - Describe how the
strtok()function works for breaking a sentence into words. What special argument is used in subsequent calls after the first call?
📘 Lecture 18 — File Handling
📖 Overview: This lecture introduces file handling in C++, explaining how to permanently store data on disk rather than in volatile memory. It covers the fundamental operations of opening, reading, writing, and closing text files, along with different file access modes and practical examples for input/output file processing.
🗂️ Topics Covered
The lecture begins by explaining the importance of files for permanent data storage versus volatile memory. It then covers text file handling fundamentals including the fstream header, ifstream and ofstream streams, and the open/read/write/close cycle. The discussion extends to file opening modes, error checking mechanisms, and various reading techniques including the get() function and the more efficient getLine() function. The lecture concludes with practical examples demonstrating file processing including salary calculations using strtok() and atoi() functions.
📝 Lecture Summary
Files
Computer memory is volatile, meaning data is lost when the computer is turned off. To store data permanently, we need files on disk. Files come in two primary types: text files containing readable English characters (plain text like source code or formatted text like Word documents), and executable program files containing non-printable binary information. For programming purposes, we need to learn how to create files on disk, read from them, write into them, and manipulate the data they contain — this is known as file handling.
💡 Why this matters: Without file handling, every program would lose its data when closed, making applications like payroll systems impractical.
Text File Handling
The basic steps of file handling are: Open the file, Read and write, then Close the file. C++ uses streams for file handling, similar to how cin and cout work for console input/output. The required header file is <fstream.h> (file stream). Three stream types are available: ifstream (input file stream) for reading files, ofstream (output file stream) for writing files, and fstream for reading and writing the same file.
🔑 Definition — File Stream: An object that acts as a handle or internal variable to refer to files on disk, enabling data transfer between the program and the file.
Declaring file streams:
ifstream inFile; // object for reading from a file
ofstream outFile; // object for writing to a file
Opening a file:
myFile.open(filename);
The filename argument is a character string in double quotation marks. It can be a simple name like "payroll.txt" if the file is in the current directory, or a fully qualified path like "C:\\myprogs\\payroll.txt".
Reading from a file:
myFile >> c; // reads one word into variable c (stops at spaces)
Multiple words can be read at once: myFile >> c1 >> c2 >> c3;
Closing a file:
myFile.close();
Error checking is essential before file operations:
if (!myFile) {
cout << "File cannot be opened" << endl;
exit(1);
}
The function eof() returns true when the end of file is reached, commonly used in while loops.
Example 1
A program reads from "myfile.txt" containing employee data (name, salary, department) and prints it to the screen.
📌 Example: Input file "myfile.txt" contains:
Name Salary Department
Aamir 12000 Sales
Amara 15000 HR
Adnan 13000 IT
Afzal 11500 Marketing
The program code:
#include <iostream.h>
#include <fstream.h>
main() {
char name[50], sal[10], dept[30];
ifstream inFile;
char inputFileName[] = "myfile.txt";
inFile.open(inputFileName);
if (!inFile) {
cout << "Can't open input file named " << inputFileName << endl;
exit(1);
}
while (!inFile.eof()) {
inFile >> name >> sal >> dept;
cout << name << "\t" << sal << " \t" << dept << endl;
}
inFile.close();
}
Output: Displays the same data as the input file with tabs between columns.
Output File Handling
The actual syntax of the open() function with mode is: open(filename, mode)
The file-opening modes determine how a file is accessed:
| Mode | Meaning |
|---|---|
ios::in | Open for extraction (input) |
ios::out | Open for insertion (output); creates new file or deletes existing contents |
ios::app | Append; each write goes to end of existing file |
ios::trunc | Discards file contents if exists (similar to default) |
ios::ate | Open without truncating; allows writing anywhere in file |
ios::binary | Treat file as binary rather than text |
🔑 Definition — ios::out mode: When opening a file for output with this mode, a new file is created if it doesn't exist, but if the file already exists, its contents are deleted.
📌 Example: Program to create a file "myFileOut.txt" and write "Welcome to VU":
#include <iostream.h>
#include <fstream.h>
main() {
ofstream outFile;
char outputFileName[] = "myFileOut.txt";
char ouputText[100] = "Welcome to VU";
outFile.open(outputFileName, ios::out);
if (!outFile) {
cout << "Can't open input file named " << outputFileName << endl;
exit(1);
}
outFile << ouputText;
outFile.close();
}
Alternative Reading Functions
The >> operator does not read newline characters, so we need to add them explicitly in output. The get() function reads individual characters from a file:
char c;
while ((c = inFile.get()) != EOF) {
// process each character
outFile.put(c); // write character to output file
}
For efficiency, reading lines rather than characters is better because disk access is much slower than processor/memory (hard disk average access time is ~7 milliseconds vs processor GHz speed). The getLine() function reads complete lines:
char name[100];
int maxChar = 100;
int stopChar = 'o';
inFile.getLine(name, maxChar, stopChar);
🔑 Definition — getLine(): Reads a line from a file into a character array. First argument is the array, second is maximum characters to read, third (optional) is the stop character (default is newline). The newline character is NOT read.
📌 Example: For input "Hello World", using inFile.getLine(str, 20, 'W') reads "Hello " into str.
Example 2
Problem: Read an input file containing employee names and salaries (one space between them). Calculate total salaries and write to an output file.
Solution: Use getLine() to read each line, then strtok() to split the line into tokens using space as delimiter. Use atoi() to convert salary string to integer.
🔑 Definition — strtok(): String token function that takes a string and a delimiter character, returning the first token. Subsequent calls with strtok(NULL, " ") return the next token from the same string.
🔑 Definition — atoi(): Function that converts a character string to an integer value.
📌 Example: Input file "salin.txt":
Aamir 12000
Amara 15000
Adnan 13000
Afzal 11500
Program code:
#include <iostream.h>
#include <fstream.h>
#include <cstring>
#include <cstdlib>
main() {
ifstream inFile;
char inputFileName[] = "salin.txt";
ofstream outFile;
char outputFileName[] = "salout.txt";
const int MAX_CHAR_TO_READ = 100;
char completeLineText[MAX_CHAR_TO_READ];
char *tokenPtr;
int salary, totalSalary;
salary = 0;
totalSalary = 0;
inFile.open(inputFileName);
outFile.open(outputFileName);
if (!inFile || !outFile) {
cout << "Can't open file" << endl;
exit(1);
}
while (!inFile.eof()) {
inFile.getline(completeLineText, MAX_CHAR_TO_READ);
tokenPtr = strtok(completeLineText, " "); // First token is name
tokenPtr = strtok(NULL, " "); // 2nd token is salary
salary = atoi(tokenPtr);
totalSalary += salary;
}
outFile << "The total salary = " << totalSalary;
inFile.close();
outFile.close();
}
Output file "salout.txt": The total salary = 51500
⭐ Key Takeaways
Always close files with the close() function and open them explicitly using the open() function. Error checking is critical when handling files — always verify that a file opened successfully before attempting to read or write. The >> operator reads word by word and does not capture newlines, while get() reads individual characters including newlines, and getLine() reads complete lines for better efficiency. File-opening modes like ios::out, ios::app, and ios::ate control whether files are created, truncated, or appended to. For processing structured data from files, use strtok() to parse tokens and atoi() to convert string numbers to integers.
🧠 Quick Revision Questions
- What are the three basic steps of file handling in C++ and what functions are used for each step?
- How do you check if a file was opened successfully before performing read/write operations?
- What is the difference between the
>>operator, theget()function, and thegetLine()function for reading from files? - What is the purpose of the
strtok()function and how is it used to parse space-delimited data from a file? - Explain the difference between
ios::out,ios::app, andios::atefile opening modes — when would you use each?
📘 Lecture 19 — Sequential Access Files (Continued) and Random Access Files
📖 Overview: This lecture completes the discussion of sequential access files and introduces random access files in C++. It covers how to navigate within files using seekg(), tellg(), seekp(), and tellp() functions, explains the concept of file positioning, and demonstrates efficient reading/writing with read() and write() functions. Understanding random access is crucial for building efficient file-based applications where data needs to be updated without rewriting entire files.
🗂️ Topics Covered
The lecture reviews sequential file handling functions (open, close, get, put, getline, stream operators) and the default behaviors of ifstream and ofstream. It then introduces random access files, explaining file position pointers, the tellg()/tellp() functions for determining current position, and seekg()/seekp() functions for setting position. The lecture covers determining file length, the problem of inserting data in the middle of a sequential file, the merge method solution, and the concept of fixed-length records. It also discusses efficient file I/O using read() and write() functions, copying files in reverse order, and includes two sample programs demonstrating these concepts.
📝 Lecture Summary
Sequential Access Files (Continued)
Sequential access files are simple character files where data is written and read in sequence, not randomly. The lecture reviews that open() is used to open files and close() to close them. open() takes parameters (filename, mode) while close() has no parameters.
For ifstream (Input File Stream), the default mode is for reading/input, so simply providing the filename is sufficient. We can also provide an additional argument like open("filename", ios::in), but this is not mandatory due to the default behavior.
For ofstream (Output File Stream), opening a file for writing in default mode destroys previous contents. To preserve contents, use append mode (ios::app). The ios::trunc value causes the contents of a preexisting file to be destroyed and the file is truncated to 0 length. To open a file for writing at random positions forward and backward, use ios:ate — the file opens and positions at the end, and anything written is appended.
🔑 Definition — ios::app: A file opening mode that appends data to the end of an existing file without destroying its contents.
📌 Example: ofstream out("test"); opens file for output in default mode (destroys previous contents). ofstream out("test", ios::app); opens file in append mode.
File reading and writing can be done character by character using get() (read one character) and put() (write one character). The stream extraction operator (>>) and stream insertion operator (<<) also work with files. The getline() function reads one line at a time — you provide how many characters to read and the delimiter. If reading 10 characters, it reads 9 and adds a null character (\0).
🔑 Definition — getline(): A function used to read one line at a time from a file, treating lines as character strings, automatically adding a null character.
📐 Syntax: getline(buffer, size, delimiter) → reads up to size-1 characters or until delimiter
📌 Example: Reading an integer, float, and character from a file created by a previous program:
ifstream in("test");
if(!in) { cout << "Cannot open file"; return 1; }
in >> i; // reads integer
in >> f; // reads float
in >> ch; // reads character (white spaces are ignored by default)
💡 Why this matters: Understanding the default behaviors of file stream modes is critical. Accidentally using output mode on a file with important data can destroy it. The append mode provides safe data preservation.
Random Access Files
To access files randomly (forward and backward), the file position (a pointer into the file) must be understood. While reading from or writing to a file, we must know from which location the process will start. Two functions determine the file pointer position: tellg() and tellp().
🔑 Definition — tellg(): Returns the current get position of the file pointer as a long integer — the position of the next character to be read from the file. 🔑 Definition — tellp(): Returns the next position to write a character while writing to a file, also as a long integer.
📌 Example: myfile.tellg() gives the current get position of the file pointer for an input file stream.
Setting the Position — To move within a file, use seekg() for reading (getting) and seekp() for writing (putting). These functions require a long argument indicating how many bytes to move and an optional direction specifier.
🔑 Definition — seekg(): Sets the get position in a file for reading, taking a long argument for bytes to move and an optional direction flag. 🔑 Definition — seekp(): Sets the put position in a file for writing, taking a long argument for bytes to move and an optional direction flag.
The movement is always relative to some position:
- From the beginning: can only move forward with positive values
- From current position: can move forward (positive) or backward (negative)
- From the end: can only move backward with negative values
📐 Formulas:
aFile.seekg(10L, ios::beg)→ move 10 bytes forward from beginningaFile.seekg(20L, ios::cur)→ move 20 bytes forward from current positionaFile.seekg(-10L, ios::cur)→ move 10 bytes backward from current positionaFile.seekg(-100L, ios::end)→ move 100 bytes backward from end
📌 Example — Saving and restoring position in a file:
streampos original = aFile.tellp(); // save current position
aFile.seekp(0, ios::end); // reposition to end of file
aFile << x; // write value to file
aFile.seekp(original); // return to original position
Determining File Length: By combining seekg() and tellg(), we can find the actual data length of a file:
inFile.seekg(0, ios::end); // go to the end of the file
long inSize = inFile.tellg(); // get the file pointer position (total bytes)
🔑 Definition — File length determination: Using seekg(0, ios::end) followed by tellg() returns the actual number of data bytes in the file, regardless of the physical disk block size.
💡 Why this matters: The physical file size on disk can be larger than the actual data length because disks store data in logical blocks (clusters). Knowing the actual data length is essential for accurate file processing.
Data Insertion in the Middle of a File
Inserting data in the middle of a sequential file is problematic. If a word like "Sukkur" needs to be replaced with "Rawalpindi", the new data is longer and would overwrite subsequent data, corrupting the file structure.
The Merge Method (from COBOL era) solves this:
- Open the data file and a new empty file
- Copy data from beginning to the insertion point into the new file
- Append new data to the new file
- Skip the data to be replaced in the original file
- Copy remaining data to the new file
- Delete old file, rename new file
This method is wasteful for large files (hundreds of megabytes or gigabytes) since the entire file must be copied just to change one word.
🔑 Definition — Constant record length: The key to resolving insertion issues — each record in the file occupies the same amount of space, large enough to accommodate any future updates without disturbing file structure.
Fixed-Length Records Solution: Make each record the same size. For example, if student records are 100 bytes each:
- First 10 bytes: student ID number
- 40 bytes: student name
- 40 bytes: city
- 10 bytes: date of birth
If IDs are sorted with no gaps (1 to 50), we can calculate exact positions. For student ID 23:
- 22 students × 100 bytes = 2200 bytes
- Record 23 starts at byte 2201 and goes to 2300
- Use
seekg(2200L, ios::beg)to jump directly — no loops, no comparisons
🔑 Definition — Key field: A unique identifier (like student ID or roll number) used to locate records quickly, making numeric comparisons faster than string comparisons.
Dual-Mode File Opening: A file opened with fstream can be used for both input and output. Use ios::in || ios::out as the mode flag — the OR operation combines the bits of both flags.
📌 Example — Replacing part of a string in a file (same length):
ofstream outfile;
outfile.open("test.txt");
outfile.write("This is an apple", 16); // Write the string
pos = outfile.tellp(); // Get position (should be 16)
outfile.seekp(pos - 7); // Move 7 positions backward
outfile.write(" sam", 4); // Write 4 chars — changes "apple" to " sam"
outfile.close();
Efficient Way of Reading and Writing Files
Reading a file character by character with getc() and writing with putc() is inefficient. Processing in chunks is much faster. The read() and write() functions are binary functions that handle multiple bytes at once.
🔑 Definition — read(): A binary stream function that reads a specified number of bytes from a file into a memory location.
🔑 Definition — write(): A binary stream function that writes a specified number of bytes from memory to a file.
📐 Syntax: read(buffer, numberOfBytes) — reads into buffer; write(buffer, numberOfBytes) — writes from buffer
📌 Example — Efficient file copying using read() and write():
char str[10000];
fi.open("inFilename", ios::in);
fo.open("outFilename", ios::out);
fi.seekg(0, ios::end);
j = fi.tellg(); // Get total file size
fi.seekg(0, ios::beg); // Go to beginning
for(i = 0; i < j/10000; i++) {
fi.read(str, 10000); // Read 10000 bytes
fo.write(str, 10000); // Write 10000 bytes
}
fi.read(str, j - (i * 10000)); // Read remaining bytes
fo.write(str, j - (i * 10000)); // Write remaining bytes
Using sizeof() with write(): Instead of specifying byte count directly, use sizeof() for type independence and portability:
aFile.write(&i, sizeof(i)); // Writes integer i to file
🔑 Definition — sizeof() operator: Returns the number of bytes occupied by a data type or variable, making code compiler-independent and portable across systems with different internal representations.
Copying a File in Reverse Order
To copy a file in reverse (last byte becomes first byte), position the file pointer one byte before the end using seekg(-1, ios::end). After reading a byte, the pointer automatically moves forward. To read the previous byte, move two positions back from the current position: seekg(-2, ios::cur).
📌 Example — Loop structure for reverse file copying:
aFile.seekg(-1L, ios::end); // Position one byte before end
while(aFile) {
aFile.get(c); // Read current byte
aFile.put(c); // Write to output
aFile.seekg(-2L, ios::cur); // Move two back to read previous byte
}
💡 Why this matters: seekg() is much faster than sequential reading. To reach the 100th location sequentially takes 100 reads, but seekg() jumps directly. Similarly, read() and write() are the fastest file handling methods as they reduce the number of function calls and physical disk accesses.
⭐ Key Takeaways
- File positioning functions —
tellg()/tellp()get the current file pointer position, whileseekg()/seekp()set it. Movement can be relative to the beginning (ios::beg), current position (ios::cur), or end (ios::end), with positive values moving forward and negative values moving backward. - Random access requires fixed-length records — To insert or update data in the middle of a file without corruption, each record must have a constant size large enough to accommodate the largest possible data value. This allows direct calculation of record positions using arithmetic.
- The merge method is the traditional approach for inserting into sequential files — copying everything up to the insertion point, inserting new data, then copying remaining data — but is inefficient for large files.
- read() and write() are more efficient than get()/put() for file I/O because they process data in chunks rather than character by character, reducing function calls and physical disk access operations.
- fstream with ios::in || ios::out allows a single file stream to be used for both reading and writing simultaneously, enabling direct updates to file contents without creating separate input and output streams.
🧠 Quick Revision Questions
- What is the difference between
seekg()andseekp(), and when would you use each function? - How can you determine the actual data length of a file using
seekg()andtellg()? - Why does inserting data in the middle of a sequential file cause problems, and what is the constant record length solution?
- In the reverse file copy algorithm, why must you move 2 positions backward (
seekg(-2, ios::cur)) instead of 1 position backward after reading each character? - What is the advantage of using
read()andwrite()functions over character-by-character I/O withget()andput()?
📘 Lecture 20 — Structures and Unions
📖 Overview: This lecture introduces the concept of structures in C/C++ as a way to group related variables of different types under a single name, creating user-defined data types. It also covers unions, which allow different data types to share the same memory location, and demonstrates practical applications through sample programs.
🗂️ Topics Covered
The lecture covers the declaration, initialization, and manipulation of structures, including how to access data members using dot and arrow operators, pass structures to functions, create arrays of structures, and use the sizeof operator. It also explains unions as a memory-sharing construct with syntax similar to structures, and presents two complete sample programs demonstrating structure usage for student data processing and file I/O operations.
📝 Lecture Summary
Structures
Structures are a fundamental concept in C/C++ that allow grouping related data about a single entity. For example, a student has properties like name, address, GPA, and courses — all of which belong to the same student. Similarly, a car has a model, manufacturer, and number of seats. Without structures, each piece of data must be handled separately. With structures, we create a new user-defined data type that bundles related variables together.
A structure is defined as “a collection of variables under a single name. These variables can be of different types, and each has a name that is used to select it from the structure.”
💡 Why this matters: Structures enable us to model real-world entities in code, making programs more organized and intuitive. They are the foundation for classes in object-oriented programming.
Declaration of a Structure
Structures are syntactically defined with the keyword struct, followed by the structure name. The data members are defined inside curly braces. For example:
struct student {
char name[60];
char address[100];
float GPA;
};
The variables inside the structure are called data members. Once declared, student becomes a new data type that can be used like built-in types:
student std1, std2;
This is equivalent to int x, y; — we have extended the language by creating a custom type.
Structures can be defined and variables declared simultaneously:
struct student {
char name[60];
char address[100];
float GPA;
} std1, std2;
🔑 Definition — Structure Declaration Syntax: The struct keyword followed by a name, then data members in curly braces, optionally followed by variable names in a comma-separated list.
Structures can contain pointers and other structures as data members. However, a structure cannot contain an instance of itself. For example:
struct address {
char streetAddress[100];
char city[50];
char country[50];
};
struct student {
char name[60];
address stdAdd; // Another structure as member
float GPA;
};
A pointer to a structure holds the memory address where the structure's data begins. We can define:
- Simple variables of the new structure type
- Pointers to structures
- Arrays of structures
card fullSet[52]; // Array of 52 card structures
student s[100]; // Array of 100 student structures
student *sptr; // Pointer to student structure
Limitations: We cannot use operators like + with structures (e.g., card1 + card2 is invalid). However, assignment works — if s1 and s2 are of the same structure type, s1 = s2 copies all data members.
Initializing Structures
Initialization can occur at the time of declaration using curly braces with comma-separated values:
student s1 = {"Ali", "CS201", 19, 2002};
The values are assigned in order: "Ali" to name, "CS201" to course, 19 to age, and 2002 to year.
To access data members, the dot operator (.) is used:
s1.age = 20;
s1.year = 2002;
cout << "The name of s1 = " << s1.name;
🔑 Definition — Dot Operator (.): Used to access data members of a structure variable. Syntax: structureVariable.memberName.
Functions and Structures
Structures can be passed to functions by value (a copy is created on the stack) or by reference (by passing the address). When passed by value, even if the structure contains an array, the entire array is copied — this protects the original data but can use significant memory.
Functions can also return structures. The structure is copied onto the stack and then assigned to the receiving variable.
Accessing structures through pointers: When using a pointer to a structure, the arrow operator (->) is used instead of the dot operator.
student s1 = {"Ali", "CS201", 22, 2002};
student *sptr;
sptr = &s1;
// Using arrow operator
cout << sptr->name; // Displays "Ali"
// Equivalent using dereference and dot operator
cout << (*sptr).name; // Parentheses required due to operator precedence
🔑 Definition — Arrow Operator (->): Used to access data members through a pointer to a structure. Syntax: pointer->member.
📐 Important Rule: When accessing through a simple variable, use dot operator (s1.name). When accessing through a pointer to structure, use arrow operator (sptr->name).
Arrays of Structures
Arrays of structures are declared like arrays of built-in types:
student s[100];
This creates an array of 100 student structures, indexed from 0 to 99. Access elements using array index with dot operator:
s[0].name; // Name of first student
s[1].age; // Age of second student
🔑 Important: The array index belongs to the array name (s), not the data member.
sizeof Operator
The sizeof operator can be used with structures to determine the total memory they occupy. It automatically accounts for all data members:
cout << "The structure s1 occupies " << sizeof(s1) << " bytes in the memory";
For the student structure with char name[64], char course[128], int age, and int year, the output would be 200 bytes (64 + 128 + 4 + 4).
💡 Why this matters: sizeof is particularly useful when writing structures to files using functions like
write().
Summary of what we can do with structures:
- Define the structure
- Declare variables of that structure type
- Declare pointers to structures
- Declare arrays of structures
- Take the size of a structure
- Perform simple assignment between two variables of the same structure type
Sample Program 1
Problem: For ten students (with name, course, age, and GPA), get input from the user to populate the array, calculate average age and average GPA, determine the class grade, and find the student with maximum GPA.
Solution: The program declares a student structure, populates an array of 10 students using a loop, calculates totals for age and GPA while tracking the maximum GPA and its index, then computes averages and determines the class grade based on average GPA.
📌 Example Output: For 3 students (Ali with GPA 3.5, Faisal with GPA 3.6, Jamil with GPA 3.3):
The average age is : 24
The average GPA is : 3.46667
Student with max GPA is : Faisal
The average Grade of the class is : B
The program uses a condition chain:
averageGPA == 4→ Grade AaverageGPA >= 3→ Grade BaverageGPA >= 2→ Grade C- Otherwise → Grade F
Sample Program 2
Problem: Read student data from a file, populate the structure, and write the structure to another file.
Solution: The program uses two functions — getData() which reads from a file and returns a student structure, and writeData() which writes a student structure to a file. The file handles (ifstream and ofstream) are declared globally for accessibility.
🔑 Function Prototypes:
student getData(); // Returns a student structure read from file
void writeData(student); // Writes a student structure to file
The getData() function creates a local tempStudent structure, reads data line by line using getline(), converts strings to integers and floats using atoi() and atof(), and returns the structure. The writeData() function uses the extraction operator to write each data member to the output file.
📌 Example: For input file containing:
nasir
CS201
23
3
The output file will contain the same data formatted with each field on a new line.
Unions
A union is a construct that allows different data types to share the same memory location. The syntax is similar to structures:
union intOrChar {
int i;
char c;
};
The size of a union is the size of its largest data member. For intOrChar, if int is 4 bytes and char is 1 byte, the union occupies 4 bytes.
🔑 Definition — Union: A memory location that can be accessed using different data type names. All data members share the same starting memory address.
Data members of unions are accessed using the dot operator:
intOrDouble uval;
uval.ival = 10;
cout << uval.ival; // Displays 10
⚠️ Important: When one member of a union is written, other members become invalid. If you write
uval.ival = 10and then readuval.dval, you get undefined behavior — the bytes are interpreted as a double, not as the integer 10.
Practical Example — Byte Shifting with Union: The lecture demonstrates using a union containing 4 characters and an integer to show how memory is shared:
union intOrChar {
char c[4];
int x;
} u1;
u1.x = 'a'; // ASCII value 97 stored as integer
u1.x *= 256; // Shift left by one byte (multiply by 256)
u1.x += 'b'; // Add 'b' (ASCII 98)
📌 Output progression:
Initial: c = a, , , x = 97
After 'b': c = b,a, , x = 24930
After 'c': c = c,b,a, x = 6382179
After 'd': c = d,c,b,a x = 1633837924
Characteristics of Unions:
- Share the same memory location among all data members
- Size equals that of the largest member
- Only one member can be active at a time
- Rarely used but important for memory-efficient programming
⭐ Key Takeaways
Structures are user-defined data types that group related variables under one name, declared using the struct keyword with data members in curly braces. Data members are accessed using the dot operator (.) for structure variables and the arrow operator (->) for pointers to structures. Structures can be passed to functions by value or by reference, and functions can return structures. Arrays of structures and the sizeof operator for structures provide powerful data management capabilities. Unions, which share memory among different data types, have syntax similar to structures but occupy only enough memory for their largest member, making them useful for memory-efficient programming though rarely needed in typical applications.
🧠 Quick Revision Questions
- What is the difference between a structure and a union in terms of memory allocation?
- How do you access a data member of a structure using a pointer? What operator is used and why is it necessary?
- What happens when you pass a structure containing an array to a function by value — is the array passed by value or by reference?
- In Sample Program 1, what is the purpose of storing the index variable when tracking the maximum GPA?
- For the union
intOrCharwithint(4 bytes) andchar(1 byte), how many bytes of memory does it occupy, and why?
📘 Lecture 21 — Bit Manipulation
📖 Overview: This lecture introduces the concept of bit manipulation, which involves working directly with individual bits (the smallest unit of memory) rather than bytes. It covers the essential bitwise operators in C++, their truth tables, and practical applications like checking or setting bits, encryption, and data recovery, which are crucial for efficient memory use and low-level programming.
🗂️ Topics Covered
The lecture begins by defining a bit and the concept of bit manipulation. It then details six bit manipulation operators: AND (&), OR (|), Exclusive OR (^), NOT (~), Left Shift (<<), and Right Shift (>>). Each operator is explained with its truth table and examples. Practical applications discussed include using AND to check if a bit is set, OR to set a bit, and Exclusive OR for encryption/decryption and data recovery via RAID. The lecture also covers the use of unsigned integers for bit operations, provides a sample program for password encryption, and explains shift operators as efficient alternatives to multiplication and division.
📝 Lecture Summary
Bit Manipulation
The lecture begins by defining a bit as the basic unit of memory, with eight bits forming a byte. Bit manipulation is the process of working directly with individual bits, turning them on (1) or off (0). This is contrasted with the usual manipulation of bytes using data types like integers. Bit manipulation is memory-efficient and particularly useful in operating systems for managing file attributes and other low-level tasks.
🔑 Definition — Bit: The smallest unit of memory that can hold a value of 0 or 1.
Bit Manipulation Operators
This section introduces the operators used for bit manipulation.
| Operator | Operator Sign |
|---|---|
| Bitwise AND Operator | & |
| Bitwise OR Operator | | |
| Bitwise Exclusive OR Operator | ^ |
| NOT Operator | ~ |
| Left Shift Operator | << |
| Right Shift Operator | >> |
The lecture emphasizes that these are not the same as the logical AND (&&) and OR (||) operators.
AND Operator ( & )
The AND operator (&) compares two bits and returns 1 only if both bits are 1. It is used to determine whether a specific bit is set (1) or not.
🔑 Truth Table for AND (&):
| Bit1 | Bit2 | Bit1 & Bit2 |
|---|---|---|
| 1 | 1 | 1 |
| 1 | 0 | 0 |
| 0 | 1 | 0 |
| 0 | 0 | 0 |
📐 Concept: Matching bit patterns. Each bit of the first number is compared with the corresponding bit of the second number.
📌 Example: Determine if the fourth bit (value 8) of a number is set.
int number = 12; // binary 1100
if (number & 0x8) // 0x8 is hexadecimal for 8 (binary 1000)
cout << "The fourth bit of the number is set" << endl;
else
cout << "The fourth bit of the number is not set" << endl;
Since the result of 1100 & 1000 is 1000 (non-zero), the condition is true.
🔑 Definition — Set: A bit is said to be 'set' if its value is 1 and 'not set' if it is 0.
OR Operator ( | )
The OR operator (|) compares two bits and returns 1 if either one of the bits is 1. It is used to set a specific bit to 1.
🔑 Truth Table for OR (|):
| Bit1 | Bit2 | Bit1 | Bit2 |
|---|---|---|
| 1 | 1 | 1 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 0 | 0 | 0 |
📌 Example: Set the first bit of a number.
If number = 8 (binary 1000), then number = number | 1 results in 1000 | 0001 = 1001, which is 9. This ensures the first bit is now 1.
💡 Why this matters: The & operator is used to check a bit, while the | operator is used to set a bit.
Exclusive OR Operator ( ^ )
The Exclusive OR operator (^), also called XOR, returns 1 only when the two input bits are different (one is 1, the other is 0).
🔑 Truth Table for Exclusive OR (^):
| Bit1 | Bit2 | Bit1 ^ Bit2 |
|---|---|---|
| 1 | 1 | 0 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 0 | 0 | 0 |
📐 Key Property: A key strength of the XOR operator is that applying it twice with the same value returns the original value. If c = a ^ b, then c ^ b = a.
📌 Example: 8 ^ 1 = 9 (since 1000 ^ 0001 = 1001). Then 9 ^ 1 = 8 (since 1001 ^ 0001 = 1000).
This property makes XOR very useful for encryption and decryption of passwords and for data recovery in RAID systems.
NOT Operator ( ~ )
The NOT operator (~) is a unary operator that inverts all the bits of a number. 1 becomes 0, and 0 becomes 1.
🔑 Truth Table for NOT (~):
| Bit1 | ~Bit1 |
|---|---|
| 1 | 0 |
| 0 | 1 |
📌 Example: ~8 will invert 1000 to 0111, which is 7.
Bit Flags
Bit flags are a practical use of bit manipulation, particularly in operating systems. They represent the state of a feature or an attribute (e.g., read-only, archive for a file). Each attribute is assigned a specific bit; if the bit is set (1), the attribute is active; if the bit is cleared (0), it is not. This is more memory-efficient than using multiple boolean variables.
Masking
Masking is a technique used with bitwise operators. In the context of XOR, it is used for data security.
🔑 Definition — Masking: The process of using a set of bits (a mask) to selectively enable, disable, or invert other bits. The lecture uses the example of password encryption where a password is masked (XORed) with a secret number to create an encrypted form.
- Example (Encryption):
encrypted_char = original_char ^ secret_key; - Example (Decryption):
original_char = encrypted_char ^ secret_key;
The lecture also explains how XOR works in RAID (Redundant Array of Inexpensive Devices). Data bits are written across multiple disks, and a parity bit (the XOR of all data bits) is stored on an extra disk. If one disk fails, the missing bit can be recovered by XORing all remaining bits, allowing for "hot plug" replacement.
📌 Example (Swapping Numbers without a temp variable):
To swap two unsigned integers a and b:
a = a ^ b;
b = b ^ a;
a = a ^ b;
Unsigned Integers
Bit manipulations are best performed on unsigned integers. The most significant bit (MSB) of a signed integer is used as a sign bit (0 for positive, 1 for negative), which can cause unexpected behavior during bit shifts or other operations. Using the unsigned keyword ensures the number is treated as positive only.
🔑 Definition — Unsigned Integer: An integer declared with the unsigned keyword (e.g., unsigned int i;) that can only represent non-negative values.
Sample Program
The lecture includes a sample program that demonstrates password encryption and decryption using the XOR operator.
📌 Example Code:
#include <iostream.h>
main() {
char password[10];
char *passptr;
cout << "Please enter a password(less than 10 character): ";
cin >> password;
passptr = password;
// Encryption
while (*passptr != '\0') {
*passptr = (*passptr ^ 3);
++passptr;
}
cout << "The encrypted password is: " << password << endl;
// Decryption
passptr = password;
while (*passptr != '\0') {
*passptr = (*passptr ^ 3);
++passptr;
}
cout << "The decrypted password is: " << password << endl;
}
📌 Output:
Please enter a password(less than 10 character): zafar123
The encrypted password is: ybebq210
The decrypted password is: zafar123
Shift Operators
Shift operators allow shifting the bits of a number to the left or right.
- Left Shift Operator (<<): Shifts bits to the left, filling the vacated rightmost bits with zeros. This is equivalent to multiplying the number by 2 for each shift.
- Right Shift Operator (>>): Shifts bits to the right, filling the vacated leftmost bits with zeros (for unsigned numbers). This is equivalent to dividing the number by 2 for each shift.
📌 Example:
- Left shift of 2 (binary
0010) by one position gives 4 (binary0100). - Right shift of 12 (binary
1100) by one position gives 6 (binary0110).
The lecture notes that shift operators are more efficient than arithmetic multiplication and division.
📐 Formula for Left Shift: number << n is equivalent to number * 2^n (for non-negative numbers without overflow).
📐 Formula for Right Shift: number >> n is equivalent to number / 2^n (for positive integers).
📌 Example:
int number = 12;
cout << (number << 1); // Output: 24 (12 * 2)
cout << (number >> 1); // Output: 6 (12 / 2)
⭐ Key Takeaways
The most critical concepts from this lecture are the six bit manipulation operators and their specific uses: AND (&) for checking bits, OR (|) for setting bits, XOR (^) for toggling bits and encryption, NOT (~) for inverting bits, and left/right shift (<</>>) for efficient multiplication/division by powers of two. The XOR operator's ability to revert an operation (a ^ b ^ b = a) is a powerful concept for data security and recovery, as seen in the password encryption and RAID examples. Finally, it is essential to use unsigned integers for bit manipulation to avoid issues with the sign bit.
🧠 Quick Revision Questions
- What is the difference between the logical AND operator (
&&) and the bitwise AND operator (&)? - Which bitwise operator would you use to set the third bit of an integer variable
xto 1? - Explain the key property of the Exclusive OR (XOR) operator that makes it useful for encryption and data recovery.
- What is a left shift operation equivalent to in arithmetic terms, and why is it often preferred?
- Why is it recommended to use
unsignedintegers when performing bit manipulation?
📘 Lecture 22 — Recap of Course Topics
📖 Overview: This lecture serves as a comprehensive review of all major topics covered in the first half of the CS201 course, serving as preparation for the mid-term examination. It begins with bitwise manipulation and assignment operators, then systematically recaps design recipes, variables, data types, operators, programming constructs, decisions, loops, functions, arrays, pointers, and file I/O.
🗂️ Topics Covered
The lecture covers bitwise manipulation and assignment operators, design recipes, variables, data types, operators (arithmetic, logical, bitwise), programming constructs (sequential, decisions, loops), decisions (if statement, nested if statement), loops (while, do-while, for), switch/break/continue statements, functions (calling, top-down methodology), arrays, pointers, and file I/O.
📝 Lecture Summary
Bitwise Manipulation and Assignment Operator
C/C++ provide compound assignment operators for bitwise operations, similar to arithmetic compound operators. The statement a = a & b; can be written as a &= b;. Similarly, a = a | b; becomes a |= b; and a = a ^ b; becomes a ^= b;. However, the ~ (NOT) operator is unary (requires only one operand), so there is no compound assignment operator for it. For example, ~a is written directly.
🔑 Definition — Compound Assignment Operator: An operator that combines an arithmetic or bitwise operation with assignment, e.g., +=, &=, |=.
📐 Formula: a = a op b; → a op= b; (where op is an arithmetic or bitwise operator)
📌 Example: a = a & b; can be rewritten as a &= b;
Design Recipe
Problems are expressed in words (e.g., company payroll). As programmers, we analyze and express the problem in a reduced, brief manner. We then create examples to formulate it. For instance, to calculate annual net salary, we take an example employee X. Later, we refine the problem, write the program, test it, and review if objectives are met. A key heading is "Pay attention to the detail" — computers are very dumb machines that perform exactly what we tell them.
💡 Why this matters: This systematic problem-solving approach ensures we understand the problem before coding, reducing errors.
Variables
Computer memory can be thought of as pigeon holes with addresses. Instead of using hard-coded memory addresses, symbolic names called variables are used, which can contain different values at different times. For example, int i; and double interest; declare variables i (int type) and interest (double type).
🔑 Definition — Variable: A symbolic name that represents a memory location and can hold different values during program execution.
Data Types
The int type stores whole numbers, with varieties short and long. The unsigned qualifier is for non-negative numbers. For real numbers, float is used, and double for larger real numbers. char stores one character, enclosed in single quotation marks. Generally, int is 4 bytes and char is 1 byte. The ASCII table contains numeric values for chars. Arrays aggregate variables of the same data type.
🔑 Definition — Data Type: A classification that specifies which type of value a variable can hold and what operations can be performed on it.
📌 Example: char grade = 'A'; stores the character A (ASCII value 65) in memory.
Operators
Three types of operators are discussed: arithmetic, logical, and bitwise.
Arithmetic Operators
+ adds numbers, - subtracts, * multiplies, / divides, and % (modulus) returns the remainder. For example, c = 7 % 2; stores 1 (remainder of 7 divided by 2) in variable c. Compound arithmetic operators include +=, -=, *=, /=, and %= — with no space between these operators.
🔑 Definition — Modulus Operator (%): Returns the remainder when one integer is divided by another.
📐 Formula: a % b → remainder of a divided by b
📌 Example: 7 % 2 = 1 (7 divided by 2 gives quotient 3, remainder 1)
Logical Operators
The result is always true or false. && is AND, || is OR. Comparison operators include <, <=, ==, >, >=. Important: Do not confuse == (equality) with = (assignment). In C/C++, the assignment statement itself has a value (the value assigned). For example, if (a = b) will execute the if block if b is non-zero, which may be a logical error. The correct comparison is if (a == b).
🔑 Definition — Assignment vs Equality: = assigns a value to a variable; == compares two values for equality.
📌 Example: if (a = b) will always execute if b ≠ 0; the intended code is if (a == b)
Bitwise Operators
& is bitwise AND, | is bitwise OR, ^ is bitwise Exclusive OR, and ~ is bitwise NOT. The ~ operator is unary (one operand), while &, |, and ^ are binary (two operands).
Programming Constructs
Three fundamental constructs are required for programming:
- Sequential execution — statements execute from first to last
- Decisions — conditional execution using
ifstatements - Loops — repetitive structures for executing code multiple times
Decisions
If Statement
Syntax: if (condition) { // if code block } else { // else code block }. The condition evaluates to true or false. Braces are mandatory for multiple statements but optional for single statements. The else part is optional. As a programming practice, using braces all the time is recommended for readability.
Nested If Statement
For complex conditions, logical connectives like && and || are used. For example: if (a > b && a < c). Nested if-statements are used for multiple decision levels. Proper indentation improves readability. The lecture introduced structured flowcharting, where we never go to the left of the straight line joining Start and Stop — equivalent to code where we can't move left of the left margin.
💡 Why this matters: Structured flowcharting ensures a one-to-one correspondence between flowcharts and code, making complex problems easier to translate into programs.
Loops
While Loop
Syntax: while (condition) { // while code block }. The condition is a logical expression returning true or false. Statements inside execute 0 to n times — if the condition is false initially, the loop body never executes.
Do-While Loop
Syntax: do { // do-while code block } while (condition);. The key difference is that the loop body executes at least once before the condition is evaluated.
For Loop
Syntax: for (initialization statements; condition; incremental statements) { //for code block }. Example: for (int i = 0; i < 10; i++) { }. The loop executes while the condition is true. Braces are optional for single statements but recommended.
switch, break and continue Statements
For multi-way decisions, we can use nested if-statements, separate if-statements, or switch statement. The break statement is necessary in each case — without it, if a case matches, all subsequent cases execute. break jumps out of the switch or loop. continue skips remaining statements in the current loop iteration and goes to the next iteration.
🔑 Definition — break statement: Causes immediate termination of the nearest enclosing switch or loop. 🔑 Definition — continue statement: Skips the rest of the current loop iteration and proceeds to the next iteration.
Functions
In C/C++, functions modularize code by breaking larger problems into manageable parts. Normally, one function's length should not exceed one screen. Function calling can be call by value or call by reference. The default in C is call by value, meaning the function gets a copy of the value, and the original remains unchanged. Call by reference (using pointers) allows the function to modify the original variable.
🔑 Definition — Call by Value: When a function receives a copy of the argument's value; changes inside the function do not affect the original. 🔑 Definition — Call by Reference: When a function receives the address of the argument; changes inside the function affect the original.
Top-Down Methodology
Top-down design identifies major portions of a problem at a high level, then breaks each portion into smaller parts to implement as functions.
Arrays
Arrays aggregate variables of the same data type. For example, storing students' ages uses an array instead of separate variables. Array indexing starts at 0 — for an array a of 10 ints, a[0] is the first element and a[9] is the last. In C, arrays are stored in row major order (a row is stored after the previous row). When passing arrays to functions, it is always call by reference by default (unlike ordinary variables). Multi-dimensional arrays require specifying all dimensions except the leftmost in function parameters. Arrays are almost always used with loops.
🔑 Definition — Array: A collection of variables of the same data type accessed by an index.
📌 Example: int ages[10]; declares an array of 10 integers; ages[0] accesses the first element.
Pointers
Pointers are variables that contain memory addresses instead of values. They are essential for implementing call by reference. For swapping two numbers, passing variables normally (call by value) won't work because only copies are swapped. Passing their addresses to pointer parameters allows swapping the original variables. Pointers and arrays are inter-linked — the array name itself is a constant pointer to the first element (cannot be incremented like normal pointers).
🔑 Definition — Pointer: A variable that stores the memory address of another variable.
📌 Example: int x = 5; int *p = &x; — pointer p stores the address of variable x
File I/O
Files are used for sequential and random access. Important functions include seek functions (seekg and seekp) to move within a file, and tell functions (tellg and tellp) to get the current file position. Files can be opened in several modes.
⭐ Key Takeaways
The most critical concepts are: bitwise compound assignment operators exist for &, |, and ^ but not for ~; the difference between assignment (=) and equality (==) operators is crucial to avoid logical errors; arrays use 0-based indexing and are always passed by reference to functions; pointers store memory addresses and enable call by reference; and the three programming constructs (sequential, decision, loop) form the foundation of all programs. Remember that structured flowcharting maintains a left-margin boundary, and proper indentation improves code readability. For the exam, ensure you understand how switches require break statements, the difference between while (0-to-n times) and do-while (at least once), and that array names are constant pointers.
🧠 Quick Revision Questions
- What are the compound assignment operators for bitwise AND, OR, and XOR? Give examples.
- How does the assignment operator
=differ from the equality operator==, and what logical error can arise from confusing them? - Explain the difference between a while loop and a do-while loop in terms of when the condition is evaluated.
- What is the default method of passing arguments to functions in C? How does this change when arrays are passed?
- What is the purpose of
breakandcontinuestatements inside loops?