Programming Concepts
Meaning
Programming language is a medium for communication by human with machine.
We write codes for e.g in Java(High Level) but machine can't understand it so we need to convert it into low level language (Binary language($0, 1$)) so that machine can understand it.
Compilers and interpreters translates high level language into the low level language.
- Interpretor: translates(converts) high language line by line into binary language.
- Compiler: translate the full high level language into binary language at once.
Styles of programming:
- Imperative:
In this programming we need to define how to do something or how to achieve the goal. - Declarative:
In Declarative programming we need to define what to do to achieve something not how to do that thing.
Dynamic and Static Typing
- Dynamic Typing
In this, variables names automatically guess or derive the type of variable from the current value.x = 15 # it's integer y = False # it's boolean - Static Typing
In this, we are explicitly required to mention the type of variable.int z = 15 // it's integer
Scope and Lifetime of variables
- Scope of variables:
- when the variables are available for use.
- In below code we can't access variable
xoutside the function
def fun(x): y = x+1 return y - Lifetime of variables:
- How long the storage remains allocated.
- in above code,
xis only available until the function runs. Hole in scope:
Variable is alive but not in scope.
Memory Management
An activation record is what gets pushed onto the stack when a function is called.
it stores everything the function needs to run.
it:
- Popped popped when function exists
- control link point to start of previous record
- Return value link where to store result
Scope of variables: - Variables in activation record is located at top of stack
- Access global variables by following control links
![[Screenshot_20260206_015200_Visha Player.png]]
Heap Memory Management
Heap memory doesn't auto-delete like stack memory does. When you create data on the heap, it stays there forever until explicitly freed, creating dead storage (unreachable memory).
Stack vs Heap
Stack: Variables automatically deallocated when function exits.
Heap: Memory persists until manually freed, even if unreachable.
Manual Memory Management (C/C++)
Programmer controls memory with malloc() to allocate and free() to deallocate. Risk: Memory leaks and invalid assignments if forgotten.
Automatic Garbage Collection (Java, Python)
Runtime environment automatically cleans dead storage using mark-and-sweep algorithm:
- Mark phase: Identify all reachable memory from program variables
- Sweep phase: Return all unmarked (unreachable) memory to free space
Trade-off
Manual = Full control + better performance, but error-prone.
Automatic = Convenient for programmer, but performance penalty during GC cycles.
Terms
- Memory leak: Allocated memory that's no longer used but not freed
- Dead storage: Memory that exists but is unreachable from program
- Garbage collection: Automatic process to reclaim unused heap memory
Call by Value and Call by reference
Call by value and call by reference are NOT methods you choose - they describe HOW the programming language handles memory when passing arguments!
-
Call by value
In this a copy of data is created in memory, the function works with the copy and original data is in different memory location. -
Call by reference
In this memory reference(Address) is passed and function works with thar memory location by preserving the original data.
Stepwise Refinements
Refinement means breaking down bigger problem into smaller steps then breaking those into even more smaller steps.
-
Program Refinement
Improving the logic and structure of your code to make it better, cleaner, reusable.
Before (Rough version):
sum = 0
for i in range(1, 11):
if i % 2 == 0:
sum = sum + i
print(sum)
After (Refined version):
def sum_even_numbers(start, end):
"""Calculate sum of even numbers in range"""
return sum(num for num in range(start, end + 1) if num % 2 == 0)
result = sum_even_numbers(1, 10)
print(f"Sum of even numbers: {result}")
- Data Refinement
Changing how you store and organize the data to make it more efficient and appropriate.
Before refinement
# Storing student info as separate lists
student_names = ["Alice", "Bob", "Charlie"]
student_ages = [20, 21, 19]
student_grades = [85, 90, 88]
# Hard to manage! What if order gets messed up?
After refinement
# Using a class (most professional!)
class Student:
def __init__(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade
students = [
Student("Alice", 20, 85),
Student("Bob", 21, 90),
Student("Charlie", 19, 88)
]
Modular Software Development
Refinement is Starting with a high-level problem then decomposing it into smaller, manageable components (e.g., functions, modules, classes).
Prototype
Creating a simple version to test if the design works before full implementation.
Example: Prototype a "sorting algorithm" component to verify it handles edge cases (e.g., empty lists).
Components are divided into two things
- Components
What other components "see": Function names, input arguments, return types (e.g.,
calculateTotal(items: List[Price]) -> float).
- Specifications
What the component does: High-level behavior (e.g., "Sorts a list of integers in ascending order").
Hides implementation details (e.g., whether it uses QuickSort or MergeSort).
Benefits
Independent Improvement
Enhance or fix one component at a time without affecting others, as long as its interface (external contract) and specification (behavior) remain unchanged.
Programming with objects
Variables = Storage boxes for data (like age = 5, name = "John").
Methods = Functions that belong to a class (actions an object can do).
Objects = Complete packages created from a class that contain both variables AND methods.
class Car:
# These are VARIABLES (also called attributes/properties)
color = "red"
speed = 0
# This is a METHOD (function inside the class)
def drive(self):
print("Car is driving")
# Creating an OBJECT
my_car = Car() # <-- my_car is the OBJECT
Features of OOP
Abstraction
Abstraction means hiding complex details and showing only what we need.
Like a car: you don't need to know engine details, just press gas pedal.
Subtyping
Creating variations(different types) of objects.
Like different types of cars: sedan, SUV, truck - all are cars
# Stack: Add/remove from ONE end
# Queue: Add to one end, remove from other
# Dequeue: Add/remove from BOTH ends (more powerful!)~~
Subtype means CAN-BE-USED-AS relationship
Subtyping means:
Type A can do everything Type B can do.
Type A might have extra features too.
Wherever you need B, you can use A instead.
Example:
class Employee:
def work(self):
print("Working")
class Manager(Employee): # Manager is SUBTYPE of Employee
def work(self):
print("Managing")
def hold_meeting(self): # Extra feature
print("Meeting")
Why Manager is subtype of Employee:
- Manager IS-A Employee
- Manager can do everything Employee can (has work() method)
- Manager has extra stuff too (hold_meeting())
- Anywhere you need an Employee, you can use a Manager
Dynamic lookup
Decide which method to call at runtime
Like GPS recalculating route based on current traffic
Inheritance
Child classes get properties from parent classes
Like you inherit traits from your parents
Abstract Data type
An Abstract Data Type (ADT) defines a data type by its behavior (what it does) rather than its implementation (how it does it). It specifies:
- What operations can be performed on the data.
- What those operations do (their semantics).
- What values the data type can hold.
Key Characteristics of ADTs:
- Encapsulation: The internal details (implementation) are hidden from the user.
- Abstraction: The user interacts with the ADT through a well-defined interface, without needing to know how it works internally.
- Reusability: ADTs can be implemented in different ways (e.g., using arrays, linked lists, etc.) without changing how they are used.
Examples of Common ADTs:
| ADT | Description | Common Operations |
|---|---|---|
| List | A collection of elements in a specific order. | Insert, delete, search, traverse. |
| Stack | A collection where elements are added and removed in a "last-in, first-out" (LIFO) order. | Push, pop, peek. |
| Queue | A collection where elements are added at the rear and removed from the front ("first-in, first-out" or FIFO). | Enqueue, dequeue, peek. |
| Dictionary | A collection of key-value pairs. | Insert, delete, search (by key). |
| Tree | A hierarchical structure with nodes connected by edges. | Insert, delete, traverse (in-order, pre-order, post-order). |
Why Use ADTs?
- Modularity: You can change the implementation without affecting the rest of the program.
- Clarity: Focuses on what the data type does, not how it does it.
- Flexibility: Allows for multiple implementations (e.g., a stack can be implemented using an array or a linked list).
Example in Python:
Python’s built-in list is an implementation of the List ADT, but you can also implement your own stack using a list:
class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def peek(self):
return self.items[-1]
# Usage
s = Stack()
s.push(10)
s.push(20)
print(s.pop()) # Output: 20