Introduction to Pointers¶
Pointer Declaration and Initialization¶
A pointer is a variable that stores the memory address of another variable. Pointers are powerful features of C that provide direct memory access and efficient memory management.
Declaration¶
Pointers are declared using the asterisk (*) symbol:
int *ptr; // Declaration of an integer pointer
float *fptr; // Declaration of a float pointer
char *cptr; // Declaration of a character pointer
Initialization¶
Pointers must be initialized before use. They can be initialized to contain the address of a variable:
int num = 10;
int *ptr = # // ptr now contains the address of num
Key Points¶
- The data type of the pointer must match the data type of the variable it points to.
- The address-of operator (&) returns the memory address of a variable.
- The value-of operator (*) returns the value at the memory address stored in the pointer.
Example:
int num = 10;
int *ptr = #
printf("Value of num: %d\n", num); // Output: Value of num: 10
printf("Address of num: %p\n", &num); // Output: Address of num: (memory address)
printf("Value of ptr: %p\n", ptr); // Output: Value of ptr: (same memory address)
printf("Value at ptr: %d\n", *ptr); // Output: Value at ptr: 10
Pointers and Addresses¶
Memory Representation¶
Every variable in C is stored at a specific memory location, which has a unique address.
Example:
int x = 10;
int y = 20;
int *ptr = &x;
printf("Address of x: %p\n", &x);
printf("Address of y: %p\n", &y);
printf("Address of ptr: %p\n", &ptr);
printf("Value stored at ptr: %p\n", ptr);
printf("Value at the address stored in ptr: %d\n", *ptr);
Pointer Arithmetic¶
You can perform arithmetic operations on pointers:
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr;
printf("%d\n", *ptr); // Output: 10
printf("%d\n", *(ptr + 1)); // Output: 20
printf("%d\n", *(ptr + 2)); // Output: 30
[insert image on memory layout of variables and pointers here]
Pointers and Arrays¶
In C, array names are essentially pointers to the first element of the array.
Relationship Between Arrays and Pointers¶
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr;
// These are equivalent
printf("%d\n", arr[0]); // Output: 10
printf("%d\n", *(arr)); // Output: 10
printf("%d\n", *ptr); // Output: 10
// These are also equivalent
printf("%d\n", arr[2]); // Output: 30
printf("%d\n", *(arr + 2)); // Output: 30
printf("%d\n", *(ptr + 2)); // Output: 30
Array Traversal Using Pointers¶
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr;
int size = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < size; i++) {
printf("%d ", *ptr);
ptr++; // Move to the next element
}
// Output: 10 20 30 40 50
Important Notes¶
- When you increment a pointer, it moves to the next memory location appropriate for its data type.
- For integer pointers on most systems, ptr++ increases the address by 4 bytes (size of an int).
- For character pointers, ptr++ increases the address by 1 byte.
[insert image on array and pointer relationship in memory here]
Basic Concept of Dynamic Memory Allocation¶
Dynamic memory allocation allows you to allocate memory at runtime rather than compile time. This is useful when you don't know the exact memory requirements until the program is running.
Why Use Dynamic Memory Allocation?¶
- Allocate memory as needed during runtime
- Allocate exactly the amount of memory required
- Free memory when no longer needed
- Create data structures like linked lists, trees, etc.
Memory Allocation Functions¶
The standard library provides several functions for dynamic memory allocation:
malloc()- Allocates a block of memory of a specified sizecalloc()- Allocates and initializes a block of memory to zerorealloc()- Resizes a previously allocated block of memoryfree()- Deallocates memory previously allocated
malloc() Function¶
Syntax¶
void *malloc(size_t size);
Description¶
- Allocates a block of memory of
sizebytes - Returns a pointer to the beginning of the allocated memory block
- If allocation fails, returns NULL
- The memory contains garbage values (not initialized)
Example¶
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
int n;
printf("Enter number of elements: ");
scanf("%d", &n);
ptr = (int *)malloc(n * sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
for (int i = ;0; i < n; i++) {
ptr[i] = i + 1;
}
printf("You entered: ");
for (int i = 0; i < n; i++) {
printf("%d ", ptr[i]);
}
free(ptr); // Free allocated memory
return 0;
}
calloc() Function¶
Syntax¶
void *calloc(size_t num, size_t size);
Description¶
- Allocates memory for an array of
numelements, each ofsizebytes - Initializes all bits to zero
- Returns a pointer to the beginning of the allocated memory block
- If allocation fails, returns NULL
Example¶
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
int n;
printf("Enter number of elements: ");
scanf("%d", &n);
ptr = (int *)calloc(n, sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
printf("Values after calloc (initialized to zero):\n");
for (int i = 0; i < 10; i++) {
printf("%d ", ptr[i]);
}
free(ptr); // Free allocated memory
return 0;
}
Difference Between malloc() and calloc()¶
| Feature | malloc() | calloc() |
|---|---|---|
| Syntax | malloc(size) | calloc(num_elements, size_of_each) |
| Initialization | Contains garbage values | Initialized to zero |
| Parameters | One parameter (size) | Two parameters (number of elements, size of each) |
| Speed | Faster | Slightly slower (initializes memory) |
Introduction to Functions¶
What is a Function?¶
A function is a self-contained block of statements that performs a specific task. Functions help in modular programming, code reusability, and better organization.
Advantages of Functions¶
- Code reusability
- Modularity
- Easier debugging and maintenance
- Better readability and organization
- Reduced redundancy
Function Declaration and Definition¶
Function Declaration (Prototype)¶
A function declaration tells the compiler about the function name, return type, and parameters without providing the actual implementation.
Syntax:
return_type function_name(parameter_list);
Example:
int add(int a, int b); // Function declaration without definition
float calculateSum(float arr[], int size); // Another example
Function Definition¶
A function definition provides the actual implementation of the function.
Syntax:
return_type function_name(parameter_list) {
// Function body
return return_value;
}
Example:
int add(int a, int b) {
int sum = a + b;
return sum;
}
// Alternative implementation
int add(int a, int b) {
return a + b;
}
Return Types of Function¶
The return type specifies the type of value that the function will return to the calling function.
Basic Return Types¶
int- Returns an integer valuefloat- Returns a floating-point valuedouble- Returns a double-precision floating-point valuechar- Returns a charactervoid- Indicates that the function does not return any value
Examples¶
// Returning an integer
int getSquare(int num) {
return num * num;
}
// Returning a float
float getAverage(int arr[], int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return (float)sum / size;
}
// Void function (no return value)
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
Function Arguments¶
Types of Function Arguments¶
- Formal Arguments: Variables declared in the function header.
- Actual Arguments: Values passed to the function during the function call.
Pass by Value¶
In pass by value, the actual value of the argument is copied to the formal parameter. Changes made to the formal parameter inside the function do not affect the actual argument.
Example:
#include <stdio.h>
void swapByValue(int a, int b) {
int temp = a;
a = b;
b = temp;
printf("Inside function: a = %d, b = %d\n", a, b);
}
int main() {
int x = 5, y = 10;
printf("Before swap: x = %d, y = %d\n", x, y);
swapByValue(x, y);
printf("After swap: x = %d, y = %d\n", x, y);
return 0;
}
// Output:
// Before swap: x = 5, y = 10
// Inside function: a = 10, b = 5
// After swap: x = 5, y = 10
Pass by Reference Using Pointers¶
In pass by reference, the address of the actual argument is passed to the formal parameter, which is a pointer. Changes made to the value at that address inside the function affect the actual argument.
Example:
#include <stdio.h>
void swapByReference(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
printf("Inside function: a = %d, b = %d\n", *a, *b);
}
int main() {
int x = 5, y = 10;
printf("Before swap: x = %d, y = %d\n", x, y);
swapByReference(&x, &y);
printf("After swap: x = %d, y = %d\n", x, y);
return 0;
}
// Output:
// Before swap: x = 5, y = 10
// Inside function: a = 10, b = 5
// After swap: x = 10, y = 5
Function Calling – Call by Value vs Call by Reference¶
Call by Value¶
- The actual value is passed to the function
- Changes made to formal parameters do not affect actual arguments
- Original values remain safe
- Implemented in C by default
Example:
#include <stdio.h>
void modifyValue(int num) {
num = num * 2;
printf("Inside function: num = %d\n", num);
}
int main() {
int value = 10;
printf("Before function call: value = %d\n", value);
modifyValue(value);
printf("After function call: value = %d\n", value);
return 0;
}
// Output:
// Before function call: value = 10
// Inside function: num = 20
// After function call: value = 10
Call by Reference¶
- The address of the actual argument is passed
- Changes made to formal parameters affect actual arguments
- Used to modify original values or to avoid copying large data
- Implemented using pointers in C
Example:
#include <stdio.h>
void modifyValueByReference(int *num) {
*num = *num * 2;
printf("Inside function: num = %d\n", *num);
}
int main() {
int value = 10;
printf("Before function call: value = %d\n", value);
modifyValueByReference(&value);
printf("After function call: value = %d\n", value);
return 0;
}
// Output:
// Before function call: value = 10
// Inside function: num = 20
// After function call: value = 20
Passing an Array as Argument to a Function¶
Arrays are always passed to functions by reference in C. When we pass an array, we're actually passing a pointer to the first element of the array.
Methods to Pass Arrays to Functions¶
- Pointer notation
- Array notation
- Size of array as a separate parameter
Example: Pointer Notation¶
#include <stdio.h>
void printArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
printArray(arr, size);
return 0;
}
Example: Array Notation¶
#include <stdio.h>
void printArrayArr(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
printArrayArr(arr, size);
return 0;
}
Modifying Array Elements in a Function¶
#include <stdio.h>
void modifyArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] = arr[i] * 2;
}
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
printf("Before modification: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
modifyArray(arr, size);
printf("After modification: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
// Output:
// Before modification: 1 2 3 4 5
// After modification: 2 4 6 8 10
Basic Concept of Recursion¶
What is Recursion?¶
Recursion is a technique in which a function calls itself directly or indirectly in order to solve a problem. The function that calls itself is called a recursive function.
Two Main Components of Recursion¶
- Base Case: The condition that stops the recursion. Without a base case, recursion would continue indefinitely, leading to a stack overflow.
- Recursive Case: The part where the function calls itself with a modified argument, moving toward the base case.
Simple Example: Factorial Calculation¶
#include <stdio.h>
// Recursive function to calculate factorial
int factorial(int n) {
// Base case
if (n == 0 || n == 1) {
return 1;
}
// Recursive case
else {
return n * factorial(n - 1);
}
}
int main() {
int num = 5;
printf("Factorial of %d is %d\n", num, factorial(num));
return 0;
}
// Output: Factorial of 5 is 120
Recursion vs Iteration¶
| Factor | Recursion | Iteration |
|---|---|---|
| Implementation | Uses function calls | Uses loops (for, while) |
| Time Complexity | Can be less efficient due to function call overhead | Generally more efficient |
| Space Complexity | Higher (uses call stack) | Lower |
| Code Readability | Often simpler for complex problems | Can become complex for some problems |
| Risk of Errors | Stack overflow if not properly handled | Limited by loop bounds |
Another Example: Fibonacci Sequence¶
#include <stdio.h>
// Recursive function to calculate Fibonacci
int fibonacci(int n) {
// Base cases
if (n == 0) {
return 0;
}
else if (n == 1) {
return 1;
}
// Recursive case
else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
int main() {
int terms = 10;
printf("Fibonacci sequence up to %d terms:\n", terms);
for (int i = 0; i < terms; i++) {
printf("%d ", fibonacci(i));
}
printf("\n");
return 0;
}
// Output: Fibonacci sequence up to 10 terms:
// 0 1 1 2 3 5 8 13 21 34
Tail Recursion¶
A special form of recursion where the recursive call is the last operation in the function. Some compilers can optimize tail-recursive functions to avoid stack growth.
Example:
#include <stdio.h>
// Tail-recursive function for factorial
int factorialTail(int n, int accumulator) {
if (n == 0 || n == 1) {
return accumulator;
}
else {
return factorialTail(n - 1, n * accumulator);
}
}
// Wrapper function
int factorial(int n) {
return factorialTail(n, 1);
}
int main() {
int num = 5;
printf("Factorial of %d is %d\n", num, factorial(num));
return 0;
}
[insert image on recursion call stack visualization here]