0% found this document useful (0 votes)
19 views

Notes C programing (1)

The document provides an overview of various C programming concepts, including header files, switch case limitations, preprocessor usage, data types, variable scope and lifetime, and string manipulation. It also includes code examples for printing patterns, multiplying matrices, searching arrays, and managing student records with structures. Overall, it serves as a comprehensive guide for understanding fundamental C programming principles and practices.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
19 views

Notes C programing (1)

The document provides an overview of various C programming concepts, including header files, switch case limitations, preprocessor usage, data types, variable scope and lifetime, and string manipulation. It also includes code examples for printing patterns, multiplying matrices, searching arrays, and managing student records with structures. Overall, it serves as a comprehensive guide for understanding fundamental C programming principles and practices.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 42

NOTES

C PROGRAMMING

Q1. What are headers file. Why they are important?

Header files in C and C++ (.h or .hpp) act as blueprints, declaring functions, variables, data
structures, and macros without their implementation details. They separate the interface
(what something does) from the implementation (how it does it), promoting modularity and
code reusability across multiple source files.

By including header files, you inform the compiler about external elements, enabling the
linker to correctly connect different parts of your program. Include guards within header files
prevent errors from multiple inclusions. Ultimately, header files are essential for organized,
efficient, and maintainable C/C++ projects, streamlining compilation and preventing
redefinition issues.

Q2. Write the limitations of switch cases

Switch cases, while useful for multi-way branching, come with several limitations:

* Limited Data Types: They primarily work with integer types (char, int, enum) and constant
expressions. Floating-point numbers, strings, and complex objects cannot be directly used as
switch expressions or case labels.

* No Range Checking: You can't specify ranges within case labels. Each case must be a
single, distinct constant value.

* Fall-through Behavior (Often a Pitfall): Execution "falls through" to the next case if a break
statement is omitted. While sometimes intentional, it's a common source of bugs.

* Constant Case Labels: case labels must be constant expressions evaluated at compile time.
You cannot use variables or non-constant expressions.

* No Complex Conditions: Each case can only check for equality with a constant. You cannot
implement more complex logical conditions within a single case.

* Limited Scope for Case Labels: Case labels must be unique within a single switch block.
* Can Become Verbose: For a large number of discrete values, a switch statement can
become quite lengthy and less readable than other approaches.

Despite these limitations, switch statements remain a valuable tool when dealing with a
clear set of discrete integer-based choices.

Q3. Define preprocessor and its usage in programming.

The preprocessor is a program that runs before the actual compilation of your source code.
It manipulates the code based on directives you include in your source files, typically starting
with a # symbol. Think of it as a text substitution and code arrangement phase.

Usage in Programming:

The preprocessor serves several key purposes:

* File Inclusion (#include): This is perhaps its most common use. #include directives insert
the entire contents of another file (usually a header file) into your current source file. This
allows you to access declarations of functions, variables, and data structures defined
elsewhere, promoting code reusability. For example, #include <stdio.h> brings in standard
input/output function declarations.

* Macro Definition (#define): #define allows you to create symbolic constants or short
aliases for code snippets.

* Constant Macros: #define PI 3.14159 replaces all occurrences of PI with 3.14159 before
compilation. This enhances readability and makes it easier to change values globally.

* Function-like Macros: #define SQUARE(x) ((x) * (x)) creates a shortcut for squaring a
value. However, be cautious as they involve textual substitution and can have unexpected
side effects.

* Conditional Compilation (#ifdef, #ifndef, #if, #else, #elif, #endif): These directives allow you
to selectively compile parts of your code based on whether certain conditions are true or
false. This is useful for:

* Platform-specific code: Compiling different code sections for different operating systems
or hardware.

* Debugging: Including debugging code during development and excluding it in the final
release.

* Feature toggles: Enabling or disabling specific features during compilation.

* Other Directives: The preprocessor also handles directives like #undef (to undefine a
macro), #pragma (compiler-specific instructions), and #error (to generate a compilation
error with a custom message).
In essence, the preprocessor prepares your source code for the compiler by handling
inclusions, substitutions, and conditional compilation, making your code more flexible,
portable, and manageable.

Q4. Write various data type supported in C with example.

C offers fundamental data types like int for whole numbers, char for single characters, float
and double for decimal numbers with varying precision. Derived types build upon these,
including pointers for memory addresses, arrays for collections of similar elements, struct
and union for grouping data, and Enum for named integer constants.

Qualifiers like signed, unsigned, short, long, const, and volatile modify the size, range, and
behaviour of basic types. Choosing the right data type is crucial for efficient memory usage
and accurate program execution. These types form the building blocks for representing
various kinds of data in C programs.

Q5. Differentiate between scope and lifetime of a variable.

The scope of a variable refers to the region of the program where that variable can be
accessed (is "visible"). It determines where you can use the variable's name to refer to its
value. Common scopes include global (accessible throughout the program), local (accessible
only within a specific block like a function), and file scope (accessible only within the source
file).

The lifetime of a variable, on the other hand, defines how long a variable exists in memory
during the program's execution. Global and static local variables typically have a lifetime that
spans the entire program execution. Automatic local variables (the default inside functions)
exist only while the function or block of code where they are defined is being executed; they
are created when the block is entered and destroyed when it's exited. Dynamic variables,
allocated using functions like malloc, have a lifetime managed explicitly by the programmer
until they are deallocated using free.

In essence, scope is about visibility, while lifetime is about existence in memory. A variable
can be in scope but not yet have a valid value (e.g., before initialization), and its lifetime
dictates when the memory it occupies is allocated and deallocated.

Q6. Write a short notes on the requested topics


 String:

 Macro Expansion:

 File Inclusion:

 Enumerated data types:

String: In C, a string is essentially an array of characters terminated by a null character (\0).


Unlike some other languages, strings are not a built-in primitive type. You typically
manipulate them using character arrays or pointers to characters and utilize functions from
the <string.h> library for operations like copying, comparing, and searching.

Macro Expansion: This is a preprocessor activity where identifiers (macros) defined using
#define are replaced by their corresponding values or code snippets throughout the source
code before compilation. This allows for symbolic constants and simple code substitutions,
but can sometimes lead to unexpected side effects if not used carefully due to its textual
nature.

File Inclusion: The #include preprocessor directive is used to insert the entire contents of a
specified file (usually a header file .h) into the current source file. This is crucial for accessing
declarations of functions, variables, and data structures defined in separate files or libraries,
promoting code reusability and modularity.

Enumerated data types: They're a neat way to make your code more readable and less
prone to errors.

Think of them as custom data types where you explicitly list all the possible values a variable
of that type can hold. Instead of using plain integers or strings to represent a set of related
constants, you give these constants meaningful names.

For example, if you're working with days of the week, instead of having to remember that 1
means Monday, 2 means Tuesday, and so on, you can define an enumerated type like:

enum Day {

MONDAY,

TUESDAY,

WEDNESDAY,

THURSDAY,

FRIDAY,

SATURDAY,

SUNDAY
};

Then, you can declare a variable of type Day:

Day today = WEDNESDAY;

This makes your code much clearer because today = WEDNESDAY is far more intuitive than
today = 3. Plus, the compiler can often perform better type checking, preventing you from
accidentally assigning an invalid value to a variable of an enumerated type.

Many programming languages like C, C++, Java, and Swift support enumerated types,
although the syntax might vary slightly. They are a small but powerful feature that can
significantly improve the clarity and maintainability of your code when dealing with a fixed
set of related values.

Q7. Write a program in C to print following pattern:

2 3 4 5 6 7

3 4 5 6 7

4 5 6 7

5 6 7

6 7

#include <stdio.h>

int main() {

int rows = 6; // Number of rows in the pattern

for (int i = 1; i <= rows; i++) {

for (int j = i + 1; j <= rows + 1; j++) {

printf("%d ", j);


}

printf("\n"); // Move to the next line after each row

return 0;

Q8. WAP in c to multiply two matrices of N x N Dimension.

#include <stdio.h>

#include <stdlib.h>

int main() {

int n;

// 1. Ask for Size

printf("Enter the size (n) of the number grids (e.g., 2 for a 2x2 grid): ");

scanf("%d", &n);

// 2. Create Empty Grids (using dynamic memory)

int **matrix1 = (int **)malloc(n * sizeof(int *));

int **matrix2 = (int **)malloc(n * sizeof(int *));

int **resultMatrix = (int **)malloc(n * sizeof(int *));

// Error check for memory

if (!matrix1 || !matrix2 || !resultMatrix) {

printf("Oops! Couldn't create the grids in memory.\n");

if (matrix1) free(matrix1);
if (matrix2) free(matrix2);

if (resultMatrix) free(resultMatrix);

return 1;

for (int i = 0; i < n; i++) {

matrix1[i] = (int *)malloc(n * sizeof(int));

matrix2[i] = (int *)malloc(n * sizeof(int));

resultMatrix[i] = (int *)malloc(n * sizeof(int));

if (!matrix1[i] || !matrix2[i] || !resultMatrix[i]) {

printf("Oops! Couldn't create a row in the grid.\n");

// Clean up already allocated memory

for (int j = 0; j < i; j++) {

free(matrix1[j]);

free(matrix2[j]);

free(resultMatrix[j]);

free(matrix1);

free(matrix2);

free(resultMatrix);

return 1;

// 3. Fill the First Grid

printf("\nEnter numbers for the first grid (%d x %d):\n", n, n);

for (int i = 0; i < n; i++) {

for (int j = 0; j < n; j++) {


printf("Enter number at row %d, column %d: ", i + 1, j + 1);

scanf("%d", &matrix1[i][j]);

// 4. Fill the Second Grid

printf("\nEnter numbers for the second grid (%d x %d):\n", n, n);

for (int i = 0; i < n; i++) {

for (int j = 0; j < n; j++) {

printf("Enter number at row %d, column %d: ", i + 1, j + 1);

scanf("%d", &matrix2[i][j]);

// 5. Multiply the Grids

for (int i = 0; i < n; i++) {

for (int j = 0; j < n; j++) {

resultMatrix[i][j] = 0;

for (int k = 0; k < n; k++) {

resultMatrix[i][j] += matrix1[i][k] * matrix2[k][j];

// 6. Show the Result

printf("\nResulting grid after multiplication:\n");

for (int i = 0; i < n; i++) {

for (int j = 0; j < n; j++) {


printf("%d\t", resultMatrix[i][j]);

printf("\n");

// Clean up memory to be a good programmer!

for (int i = 0; i < n; i++) {

free(matrix1[i]);

free(matrix2[i]);

free(resultMatrix[i]);

free(matrix1);

free(matrix2);

free(resultMatrix);

return 0;

How this code follows the easy explanation:

* Asks for Size: The first printf and scanf do exactly that.

* Creates Empty Grids: The malloc parts are like setting up empty spaces in the computer's
memory to hold our number grids.

* Fills the First Grid: The nested for loops and scanf let you put numbers into the first grid.

* Fills the Second Grid: The next set of nested for loops and scanf does the same for the
second grid.

* Multiplies the Grids: The three nested for loops perform the special multiplication and
addition to get the numbers for the resultMatrix.

* Shows the Result: The final nested for loops and printf display the numbers in the
resultMatrix.
The code also includes some "good programmer" steps like checking if the computer had
enough memory to create the grids and cleaning up that memory at the end. This makes the
program more reliable.

Q9. Given an array of 20 integers. Write a program in C to search for a given integer in that
array.

#include <stdio.h>

int main() {

int arr[20];

int searchElement;

int found = 0;

int i;

// Prompt the user to enter 20 integer elements for the array

printf("Enter 20 integer elements for the array:\n");

for (i = 0; i < 20; i++) {

printf("Enter element %d: ", i + 1);

scanf("%d", &arr[i]);

// Prompt the user to enter the integer to search for

printf("\nEnter the integer you want to search for: ");

scanf("%d", &searchElement);

// Search for the element in the array using linear search

for (i = 0; i < 20; i++) {


if (arr[i] == searchElement) {

found = 1; // Set found flag to 1 if the element is found

printf("%d found at index %d.\n", searchElement, i);

break; // Exit the loop once the element is found

// If the element was not found after searching the entire array

if (!found) {

printf("%d not found in the array.\n", searchElement);

return 0;

Q10. Declare a structure which contains the following members and write a program in C
to list all students who scored more than 75 marks. Roll No., Name, Father's Name, Age,
City, Marks

#include <stdio.h>

#include <string.h>

// Structure to hold student information

struct Student {

int rollNumber;

char name[50];

char fathersName[50];

int age;
char city[50];

int marks; // Added marks to the structure

};

int main() {

// Assuming a maximum of 100 students

struct Student students[100];

int numStudents = 0;

// Let's add some sample student data (you would typically read this from a file or user
input)

students[0] = (struct Student){101, "Alice", "Robert", 20, "Delhi", 85};

students[1] = (struct Student){102, "Bob", "David", 22, "Mumbai", 68};

students[2] = (struct Student){103, "Charlie", "George", 21, "Bangalore", 92};

students[3] = (struct Student){104, "Diana", "Henry", 19, "Chennai", 78};

students[4] = (struct Student){105, "Eve", "Jack", 23, "Kolkata", 70};

numStudents = 5; // Set the actual number of students

printf("Students who scored more than 75 marks:\n");

printf("-----------------------------------------------------------------------------------\n");

printf("%-10s %-20s %-20s %-5s %-15s %-5s\n",

"Roll No", "Name", "Father's Name", "Age", "City", "Marks");

printf("-----------------------------------------------------------------------------------\n");

// Iterate through the students and check their marks

for (int i = 0; i < numStudents; i++) {

if (students[i].marks > 75) {

printf("%-10d %-20s %-20s %-5d %-15s %-5d\n",


students[i].rollNumber, students[i].name, students[i].fathersName,

students[i].age, students[i].city, students[i].marks);

printf("-----------------------------------------------------------------------------------\n");

return 0;

Q11. Explain dynamic memory concept with proper example.

Dynamic Memory Allocation in C: Growing and Shrinking Your Program's Workspace

Think of your computer's memory as a vast storage area. Static memory allocation is like
reserving a fixed-size cabinet in advance, whether you fill it completely or not. Dynamic
memory allocation, however, is like having access to a flexible storage room where you can
request shelves (memory blocks) of varying sizes as needed during your work (program
execution) and return them when you're done.

This on-demand memory management is crucial for several reasons. Firstly, it offers
flexibility. You don't need to predict the exact memory requirements when you write your
code. Instead, you can adapt to runtime conditions, such as the size of a file the user opens
or the number of inputs they provide.

Secondly, it promotes efficiency. By allocating only the necessary memory, you avoid wasting
precious resources. Imagine reading a file; with dynamic allocation, you can allocate
memory to store the file's contents only after knowing its actual size, rather than pre-
allocating a potentially huge buffer that might remain mostly empty.

Finally, dynamic memory is the backbone of dynamic data structures. Structures like linked
lists, trees, and graphs can grow or shrink during program execution. This is only possible
through dynamic memory allocation, where you can allocate and deallocate memory for
individual nodes as needed.

C provides four essential functions for dynamic memory management, all found in the
stdlib.h library:
* malloc(): Asks for a specific number of bytes and returns a pointer to the beginning of an
uninitialized memory block in the heap. If the request fails (e.g., insufficient memory), it
returns NULL.

* calloc(): Similar to malloc, but it takes the number of elements and their size as arguments
and initializes the allocated memory to zero.

* realloc(): Attempts to resize a previously allocated memory block. It might expand, shrink,
or even move the block to a new location if necessary, copying the existing data. It's vital to
check the return value of realloc, as it might return NULL, leaving the original block
untouched.

* free(): Returns a dynamically allocated memory block back to the heap, making it available
for future use. Failing to free() allocated memory leads to memory leaks, a common
programming error that can cause programs to consume excessive memory and eventually
crash.

Consider the example of reading a user-specified number of integers. Using dynamic


allocation, you can first ask the user how many integers they want to enter. Then, you can
allocate precisely the required amount of memory using malloc() or calloc(). If the user later
decides to enter more numbers, you can use realloc() to increase the size of the allocated
memory block. Finally, once you're done with the numbers, you free() the memory, ensuring
your program doesn't hold onto resources it no longer needs. This dynamic approach
contrasts sharply with static allocation, where you would have to declare an array with a
fixed maximum size, potentially wasting memory if the user enters fewer numbers or
encountering a buffer overflow if they exceed the declared limit.

Q12. Explain different type of modes and I/O function in file handling.

You're asking about the different modes used when opening files in C and the standard
input/output (I/O) functions used in file handling. Let's break this down.

File Opening Modes in C

When you use the fopen() function to open a file in C, you need to specify a mode that
dictates how you intend to interact with the file. Here are the common file opening modes:

Basic Modes:

* "r" (Read):

* Opens an existing file for reading.

* The file pointer is positioned at the beginning of the file.


* If the file does not exist, fopen() returns NULL.

* "w" (Write):

* Opens a file for writing.

* If the file exists, its contents are truncated (deleted).

* If the file does not exist, a new file is created.

* The file pointer is positioned at the beginning of the file.

* "a" (Append):

* Opens a file for writing at the end (appending).

* If the file exists, new data written to the file will be added at the end.

* If the file does not exist, a new file is created.

* The file pointer is positioned at the end of the file before each write operation.

Modes for Both Reading and Writing:

* "r+" (Read and Write):

* Opens an existing file for both reading and writing.

* The file pointer is positioned at the beginning of the file.

* If the file does not exist, fopen() returns NULL.

* "w+" (Write and Read):

* Opens a file for both writing and reading.

* If the file exists, its contents are truncated.

* If the file does not exist, a new file is created.

* The file pointer is positioned at the beginning of the file.

* "a+" (Append and Read):

* Opens a file for both reading and appending.

* If the file exists, reading can start from the beginning, but writing always happens at the
end.

* If the file does not exist, a new file is created.

* The initial file pointer position for reading is at the beginning, but writing always
appends.

Binary Modes:
You can append "b" to any of the above modes to open a file in binary mode. This is crucial
for handling non-text files like images, audio, or executables, as it prevents any
interpretation or modification of the data based on character encodings.

* "rb", "wb", "ab", "r+b", "w+b", "a+b", etc.

Key Considerations for Modes:

* Existence of File: Be mindful of whether the file needs to exist beforehand or if the mode
will create it.

* Data Loss: "w" and "w+" will overwrite existing file content.

* Pointer Position: The initial position of the file pointer affects where reading and writing
begin.

* Text vs. Binary: Use binary modes for non-textual data to avoid corruption.

Input/Output (I/O) Functions in File Handling

C provides a set of standard library functions (primarily in stdio.h) for performing I/O
operations on files. These functions allow you to read data from and write data to files.

Character I/O:

* fgetc(FILE *stream): Reads the next character from the specified input stream (file).
Returns the character read as an int (or EOF if the end of the file is reached or an error
occurs).

* fputc(int c, FILE *stream): Writes the character c to the specified output stream (file).
Returns the character written on success (as an int) or EOF on error.

Line I/O:

* fgets(char *s, int n, FILE *stream): Reads at most n-1 characters from the specified input
stream into the character array pointed to by s. It stops reading when a newline character is
encountered, the end of the file is reached, or n-1 characters have been read. The newline
character, if read, is included in the string. The string is null-terminated. Returns s on success
or NULL on error or end of file.

* fputs(const char *s, FILE *stream): Writes the null-terminated string s to the specified
output stream. It does not write the null terminator. Returns a non-negative value on
success or EOF on error.

Formatted I/O:

* fprintf(FILE *stream, const char *format, ...): Writes formatted output to the specified
output stream according to the format string. It works similarly to printf, but the output goes
to the file. Returns the number of characters written on success or a negative value on error.
* fscanf(FILE *stream, const char *format, ...): Reads formatted input from the specified
input stream according to the format string and stores the values in the variables pointed to
by the additional arguments. It works similarly to scanf, but the input comes from the file.
Returns the number of input items successfully matched and assigned, or EOF if the end of
the file is reached before any conversion.

Block (Binary) I/O:

* fread(void *ptr, size_t size, size_t count, FILE *stream): Reads up to count items of size
bytes each from the specified input stream into the memory location pointed to by ptr.
Returns the number of items successfully read (which might be less than count if an error or
end of file occurs).

* fwrite(const void *ptr, size_t size, size_t count, FILE *stream): Writes count items of size
bytes each from the memory location pointed to by ptr to the specified output stream.
Returns the number of items successfully written.

File Positioning:

* fseek(FILE *stream, long offset, int whence): Sets the file position indicator for the
specified stream.

* offset: The number of bytes to offset from the position specified by whence.

* whence: Specifies the reference point for the offset:

* SEEK_SET (0): Beginning of the file.

* SEEK_CUR (1): Current position of the file pointer.

* SEEK_END (2): End of the file.

* Returns 0 on success or a non-zero value on error.

* ftell(FILE *stream): Returns the current file position indicator for the specified stream.
Returns a long representing the byte offset from the beginning of the file, or -1L on error.

* rewind(FILE *stream): Sets the file position indicator to the beginning of the specified
stream. It's equivalent to fseek(stream, 0, SEEK_SET) and also clears the error indicator.

Error Handling and End-of-File:

* feof(FILE *stream): Checks if the end-of-file indicator for the specified stream is set.
Returns a non-zero value if it is set, and zero otherwise.

* ferror(FILE *stream): Checks if the error indicator for the specified stream is set. Returns a
non-zero value if an error has occurred, and zero otherwise.
* perror(const char *s): Prints an error message to the standard error stream (stderr),
preceded by the string s (if provided) and a colon and a space. The error message describes
the most recent error that occurred during a system call or library function call.

Understanding these file opening modes and I/O functions is fundamental to working with
files in C, allowing you to create programs that can read, write, and manipulate data stored
in external files. Remember to always close files using fclose(FILE *stream) when you are
finished with them to release system resources.

Q13. Define recursive function. Write a program to find factorial of a number with
recursive function.

Recursive Function Defined

A recursive function is a function that calls itself within its own definition. To avoid infinite
loops, a recursive function must have one or more base cases – conditions under which the
function stops calling itself and returns a value directly. Each recursive call should move
closer to one of these base cases. Recursive functions break down a problem into smaller,
self-similar subproblems, solving each subproblem by calling the same function again with a
modified input.

C Program to Find Factorial Using Recursion

#include <stdio.h>

// Recursive function to calculate factorial

long long factorial(int n) {

// Base case: factorial of 0 is 1

if (n == 0) {

return 1;

// Base case: factorial of 1 is 1

else if (n == 1) {

return 1;

// Recursive step: n! = n * (n-1)!


else if (n > 1) {

printf("Calculating factorial of %d...\n", n); // Trace the call

long long result = n * factorial(n - 1); // Recursive call

printf("Factorial of %d is %lld\n", n, result); // Trace the return

return result;

// Handling negative input (optional, but good practice)

else {

printf("Factorial is not defined for negative numbers.\n");

return -1; // Indicate an error

int main() {

int num;

printf("Enter a non-negative integer: ");

scanf("%d", &num);

long long fact = factorial(num);

if (fact != -1) {

printf("Factorial of %d is: %lld\n", num, fact);

return 0;

}
Explanation of the Code:

* factorial(int n) Function:

* Takes an integer n as input.

* Base Cases:

* If n is 0, it returns 1 (since 0! = 1).

* If n is 1, it returns 1 (since 1! = 1). These are the stopping conditions for the recursion.

* Recursive Step:

* If n is greater than 1, it calculates the factorial by multiplying n with the factorial of n-1.
This is where the function calls itself (factorial(n - 1)).

* The printf statements are added for tracing the function calls and returns, showing how
the calculation unfolds.

* Negative Input Handling:

* If n is negative, it prints an error message and returns -1 to indicate that factorial is not
defined for negative numbers.

* main() Function:

* Prompts the user to enter a non-negative integer.

* Reads the input using scanf.

* Calls the factorial() function to calculate the factorial of the entered number.

* Checks if the returned value is not -1 (indicating a valid factorial).

* Prints the calculated factorial.

How the Recursion Works (with tracing):

Let's say the user enters 5. Here's how the factorial(5) function will execute:

* factorial(5) is called.

* Prints: "Calculating factorial of 5..."

* Calculates: 5 * factorial(4)

* factorial(4) is called.

* Prints: "Calculating factorial of 4..."

* Calculates: 4 * factorial(3)

* factorial(3) is called.
* Prints: "Calculating factorial of 3..."

* Calculates: 3 * factorial(2)

* factorial(2) is called.

* Prints: "Calculating factorial of 2..."

* Calculates: 2 * factorial(1)

* factorial(1) is called.

* Enters the base case (n == 1).

* Returns 1.

* Prints: "Factorial of 1 is 1"

* Back in factorial(2):

* result becomes 2 * 1 = 2.

* Prints: "Factorial of 2 is 2"

* Returns 2.

* Back in factorial(3):

* result becomes 3 * 2 = 6.

* Prints: "Factorial of 3 is 6"

* Returns 6.

* Back in factorial(4):

* result becomes 4 * 6 = 24.

* Prints: "Factorial of 4 is 24"

* Returns 24.

* Back in factorial(5):

* result becomes 5 * 24 = 120.

* Prints: "Factorial of 5 is 120"

* Returns 120.

Finally, the main() function prints: "Factorial of 5 is: 120".

The tracing printf statements clearly show the "across the function" aspect, illustrating how
the function calls itself with a smaller input until it reaches the base case, and then the
results are returned back up the call stack to produce the final answer.
Q14. Write a program to check whether a given number is Armstrong number or not.

#include <stdio.h>

#include <math.h>

int main() {

int num, original, rem, digits = 0;

double sum = 0;

printf("Enter an integer: ");

scanf("%d", &num);

original = num;

while (original != 0) {

original /= 10;

digits++;

original = num;

while (original != 0) {

rem = original % 10;

sum += pow(rem, digits);

original /= 10;

}
if ((int)sum == num) {

printf("%d is an Armstrong number.\n", num);

} else {

printf("%d is not an Armstrong number.\n", num);

return 0;

Q15. Write a program to copy a string to another string using standard library function. Use
dynamic memory allocation to accept string.

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

int main() {

char *source = NULL, *dest = NULL, buffer[100];

size_t len = 0;

printf("Enter source string: ");

if (fgets(buffer, sizeof(buffer), stdin)) {

len = strlen(buffer);

if (len > 0 && buffer[len - 1] == '\n') buffer[--len] = '\0';

source = (char *)malloc((len + 1) * sizeof(char));

if (!source) { perror("Source memory fail"); return 1; }

strcpy(source, buffer);
dest = (char *)malloc((len + 1) * sizeof(char));

if (!dest) { perror("Dest memory fail"); free(source); return 1; }

strcpy(dest, source);

printf("Source: %s\n", source);

printf("Copied: %s\n", dest);

free(source);

free(dest);

return 0;

} else {

printf("Input error.\n");

return 1;

Q15. Differentiate between call by value and call by reference.

Call by Value

* When you use "call by value", a copy of the actual argument's value is passed to the
function's formal parameter.

* The function creates a new local variable to hold this copied value.

* Any modifications made to the parameter inside the function do not affect the original
argument in the calling function.

* Think of it like giving a photocopy of a document to someone; they can write on their
copy, but your original remains unchanged.

* This method ensures the original data remains safe from accidental modifications within
the function.
* C uses call by value as its default mechanism for passing arguments.

Example (C - Call by Value):

#include <stdio.h>

void change(int x) {

x = 100; // Modifies the local copy of 'a'

printf("Value inside function: %d\n", x); // Output: 100

int main() {

int a = 10;

printf("Value before function call: %d\n", a); // Output: 10

change(a);

printf("Value after function call: %d\n", a); // Output: 10 (original 'a' is unchanged)

return 0;

Call by Reference

* When you use "call by reference", the memory address (or a reference to the memory
location) of the actual argument is passed to the function's formal parameter.

* The formal parameter acts as an alias or a pointer to the original argument in the calling
function.

* Any modifications made to the parameter inside the function directly affect the original
argument in the calling function because they are both referring to the same memory
location.

* Think of it like giving someone the actual original document; if they write on it, the
original is changed.

* This method allows functions to modify the variables passed to them.

* C achieves call by reference by explicitly using pointers as arguments.

Example (C - Call by Reference using Pointers):


#include <stdio.h>

void change(int *x) {

*x = 100; // Modifies the value at the memory address pointed to by 'x'

printf("Value inside function: %d\n", *x); // Output: 100

int main() {

int a = 10;

printf("Value before function call: %d\n", a); // Output: 10

change(&a); // Pass the address of 'a'

printf("Value after function call: %d\n", a); // Output: 100 (original 'a' is changed)

return 0;

Key Differences Summarized:

| Feature | Call by Value | Call by Reference (in C using Pointers) |

|---|---|---|

| Passing | Copy of the variable's value | Memory address (pointer) of the variable |

| Formal Parameter | New local variable initialized with the copy | Pointer variable pointing
to the original |

| Changes Inside Function | Do not affect the original variable | Do affect the original
variable |

| Memory | Uses separate memory for argument and parameter | Both refer to the same
memory location |

| Default in C | Yes | Achieved explicitly using pointers |

| Use Case | When you don't want to modify the original | When you need to modify the
original |
Q16. What is a structure? How it is different from Union?

A structure in C groups different types of variables together, like a container holding various
items, each in its own space. You can access each item (member) independently using a dot.

A union, on the other hand, is like a single container that can hold different types of items,
but only one at a time. All members share the same memory space, so assigning a value to
one overwrites the others.

Structures are for holding distinct pieces of information, while unions are for saving memory
when you only need to use one of the members at any point.

Q17. What is static memory allocation and dynamic memory allocation?

Static Memory Allocation:

* Memory is allocated at compile time.

* The size and location of memory are fixed before the program runs.

* Memory is typically allocated on the stack.

* Used for local variables within functions and global variables.

* Memory is automatically deallocated when the function exits or the program terminates.

* Size cannot be changed during program execution.

Dynamic Memory Allocation:

* Memory is allocated at runtime.

* The size and location of memory can be determined during program execution.

* Memory is allocated from the heap.

* Used for data structures like linked lists, and when the memory requirement is not known
beforehand.

* Memory must be explicitly deallocated by the programmer using functions like free() to
prevent memory leaks.

* Size can be changed during program execution using functions like realloc().

Q18. What is the difference between getc(), getchar(), getch() and getche()?
between getchar(), gets(), getch(), and getche():

1. getchar() (Standard C - <stdio.h>)

* Purpose: Reads a single character from the standard input (stdin), which is usually the
keyboard.

* Buffering: It is buffered. The character you type is not immediately processed until you
press the Enter key. If you type multiple characters before pressing Enter, they are stored in
the input buffer and read one by one by subsequent calls to getchar().

* Echoing: The entered character is echoed (displayed) on the console.

* Return Value: Returns the ASCII value of the character read as an int. Returns EOF (End-Of-
File) if an error occurs or the end of the input stream is reached.

2. gets() (Standard C - <stdio.h>)

* Purpose: Reads a line of text (characters until a newline character or EOF is encountered)
from the standard input (stdin) and stores it in a character array (string).

* Buffering: It is buffered.

* Echoing: The entered characters are echoed on the console. The newline character is read
but not stored in the string; instead, it is replaced by a null terminator (\0).

* Return Value: Returns a pointer to the character array (str) on success. Returns NULL if an
error occurs or if EOF is encountered before any characters are read.

* Security Risk: gets() is inherently unsafe and has been deprecated (and even removed in
later C standards). It does not perform bounds checking, meaning if the user enters a string
longer than the allocated size of the character array, it can lead to buffer overflows, which
are serious security vulnerabilities. Avoid using gets(). Use fgets() instead.

3. getch() (Non-Standard - <conio.h>)

* Purpose: Reads a single character directly from the keyboard without echoing it to the
console and without requiring the Enter key to be pressed.

* Buffering: It is unbuffered or direct. As soon as you press a key, the character is read.

* Echoing: The entered character is not displayed on the screen. This makes it suitable for
reading passwords or other sensitive input.

* Return Value: Returns the ASCII value of the character entered as an int (though often cast
to char).
* Portability: conio.h is not part of the standard C library and is primarily associated with
older compilers like Turbo C/C++ and operating systems like MS-DOS. It might not be
available on other systems (like Linux or macOS) without specific emulation or libraries.

4. getche() (Non-Standard - <conio.h>)

* Purpose: Reads a single character directly from the keyboard and echoes it to the console
immediately, without requiring the Enter key.

* Buffering: It is unbuffered or direct.

* Echoing: The entered character is displayed on the screen.

* Return Value: Returns the ASCII value of the character entered as an int (though often cast
to char).

* Portability: Like getch(), getche() is also part of the non-standard conio.h header and has
limited portability.

In summary:

| Function | Header | Reads | Buffered? | Echoing? | Requires Enter? | Standard? |


Portability | Use Cases |

|---|---|---|---|---|---|---|---|---|

| getchar() | <stdio.h> | Single char | Yes | Yes | Yes | Yes | Portable | General single
character input. |

| gets() | <stdio.h> | Line | Yes | Yes | Yes | Yes (but deprecated) | Less portable, unsafe |
Reading a line of text (but avoid using). |

| getch() | <conio.h> | Single char | No | No | No | No | Limited | Reading passwords, non-


displayed single-key input. |

| getche() | <conio.h> | Single char | No | Yes | No | No | Limited | Interactive single-


character input where you want immediate feedback. |

For modern, portable C code, prefer getchar() for single characters (and handle buffering if
needed) and fgets() for reading lines of text as it provides buffer overflow protection. Avoid
gets(). Use getch() and getche() with caution, understanding their non-standard nature and
limited portability.

Q19. Write a C program that reads 20 integer numbers from keywords and sort them in
ascending order.

#include <stdio.h>
int main() {

int nums[20], i, j, temp;

printf("Enter 20 integers:\n");

for (i = 0; i < 20; i++) {

printf("Number %d: ", i + 1);

scanf("%d", &nums[i]);

// Bubble Sort for ascending order

for (i = 0; i < 19; i++) {

for (j = 0; j < 19 - i; j++) {

if (nums[j] > nums[j + 1]) {

temp = nums[j];

nums[j] = nums[j + 1];

nums[j + 1] = temp;

printf("\nSorted (ascending):\n");

for (i = 0; i < 20; i++) {

printf("%d ", nums[i]);

printf("\n");

return 0;
}

Q20. Write a C program to check input string is palindrome or not without using any string
library function.

#include <stdio.h>

#include <stdbool.h>

int strLength(const char *s) {

int len = 0;

while (s[len] != '\0') len++;

return len;

bool isPal(const char *s) {

int len = strLength(s);

for (int i = 0, j = len - 1; i < j; i++, j--) {

if (s[i] != s[j]) return false;

return true;

int main() {

char str[100];

printf("Enter a string: ");

scanf("%s", str);

if (isPal(str)) {
printf("\"%s\" is a palindrome.\n", str);

} else {

printf("\"%s\" is not a palindrome.\n", str);

return 0;

Q21. Write a C program to add two complex numbers by passing structure to a function.

#include <stdio.h>

// Structure to represent a complex number

struct Complex {

double real;

double imag;

};

// Function to add two complex numbers

struct Complex addComplex(struct Complex num1, struct Complex num2) {

struct Complex sum;

sum.real = num1.real + num2.real;

sum.imag = num1.imag + num2.imag;

return sum;

int main() {

// Declare two complex numbers


struct Complex c1, c2, result;

// Get input for the first complex number

printf("Enter the real and imaginary parts of the first complex number: ");

scanf("%lf %lf", &c1.real, &c1.imag);

// Get input for the second complex number

printf("Enter the real and imaginary parts of the second complex number: ");

scanf("%lf %lf", &c2.real, &c2.imag);

// Add the two complex numbers

result = addComplex(c1, c2);

// Display the result

printf("Sum = %.2f + %.2fi\n", result.real, result.imag);

return 0;

Q23. What do you mean by pointer? Write C program to access elements in a 2


dimensional array using pointer.

#include <stdio.h>

int main() {

int arr[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};

int (*ptr)[4]; // ptr is a pointer to an array of 4 integers

ptr = arr; // Assign the base address of the 2D array to ptr


printf("Elements of the 2D array are:\n");

// Iterate through rows

for (int i = 0; i < 3; i++) {

// Iterate through columns using pointer arithmetic

for (int j = 0; j < 4; j++) {

printf("%d ", ((ptr + i) + j));

printf("\n");

return 0;

Q24. How string is different an array? Write a C program convert a lower case string to
upper case string without using string manipulation function.

How Strings are Different in C

Unlike many other programming languages that treat strings as a built-in data type with
associated functions (like Python, Java, etc.), C handles strings quite differently:

* Character Arrays: In C, a string is essentially an array of characters. This means you need to
allocate a contiguous block of memory to store the sequence of characters that make up the
string.

* Null Termination: C strings are null-terminated. This means that the end of a string is
marked by a special character called the null character (\0). This null terminator is crucial
because it's how C functions that work with strings (like printf with %s) know where the
string ends. Without it, these functions would keep reading memory until they encounter a
null byte by chance, leading to unpredictable behavior and potential crashes.

* No Built-in String Type: C doesn't have a dedicated string keyword as a fundamental data
type. You work directly with char arrays.
* Manual Memory Management: Because strings are arrays, you often need to handle
memory allocation and deallocation yourself, especially if you're creating strings dynamically.

* String Manipulation Functions: C provides a set of functions in the string.h header file (like
strcpy, strcat, strlen, strcmp, etc.) for performing common string operations. However, as
you requested, we'll avoid using these for our conversion program to understand the
underlying mechanics.

C Program: Lowercase to Uppercase Conversion (Without String Functions)

Here's a C program that converts a lowercase string to uppercase without relying on the
functions from string.h:

#include <stdio.h>

#include <ctype.h> // For the toupper function (we'll use this for comparison, but avoid
string.h)

void toUpperCase(char str[]) {

int i = 0;

while (str[i] != '\0') {

if (str[i] >= 'a' && str[i] <= 'z') {

str[i] = str[i] - ('a' - 'A'); // Convert to uppercase

i++;

int main() {

char text[100]; // Declare a character array to hold the string

printf("Enter a lowercase string: ");

scanf("%99s", text); // Read input, limiting to 99 characters to prevent buffer overflow

toUpperCase(text);
printf("Uppercase string: %s\n", text);

return 0;

Q25. Write a program in C to count the number of words and characters in an input file
test 1.txt.

#include <stdio.h>

#include <stdlib.h>

#include <ctype.h>

int main() {

FILE *file;

char ch;

int wordCount = 0;

int charCount = 0;

int inWord = 0;

file = fopen("test1.txt", "r");

if (file == NULL) {

perror("Error opening file");

return EXIT_FAILURE;

while ((ch = fgetc(file)) != EOF) {


charCount++;

if (isspace(ch)) {

inWord = 0;

} else if (!inWord) {

inWord = 1;

wordCount++;

fclose(file);

printf("Number of characters: %d\n", charCount);

printf("Number of words: %d\n", wordCount);

return 0;

Q26. Write a program in C for the multiplication of two matrices of size 4x4.

#include <stdio.h>

#include <stdlib.h>

#define SIZE 4

void multiplyMatrices(int mat1[][SIZE], int mat2[][SIZE], int result[][SIZE]) {

for (int i = 0; i < SIZE; i++) {

for (int j = 0; j < SIZE; j++) {

result[i][j] = 0;
for (int k = 0; k < SIZE; k++) {

result[i][j] += mat1[i][k] * mat2[k][j];

void printMatrix(int matrix[][SIZE]) {

for (int i = 0; i < SIZE; i++) {

for (int j = 0; j < SIZE; j++) {

printf("%d\t", matrix[i][j]);

printf("\n");

int main() {

int matrix1[SIZE][SIZE] = {

{1, 2, 3, 4},

{5, 6, 7, 8},

{9, 10, 11, 12},

{13, 14, 15, 16}

};

int matrix2[SIZE][SIZE] = {

{16, 15, 14, 13},

{12, 11, 10, 9},

{8, 7, 6, 5},
{4, 3, 2, 1}

};

int product[SIZE][SIZE];

printf("Matrix 1:\n");

printMatrix(matrix1);

printf("\nMatrix 2:\n");

printMatrix(matrix2);

multiplyMatrices(matrix1, matrix2, product);

printf("\nProduct of the two matrices:\n");

printMatrix(product);

return 0;

Q27. Create a structure named 'Book' to store book details like title, author, and price.
Write a C program to input details for three books, find the most expensive and the lowest
priced books, and display their information.

#include <stdio.h>

#include <string.h>

#include <limits.h> // For INT_MAX and INT_MIN

#define MAX_TITLE_AUTHOR_LENGTH 100

#define NUM_BOOKS 3
// Structure to store book details

struct Book {

char title[MAX_TITLE_AUTHOR_LENGTH];

char author[MAX_TITLE_AUTHOR_LENGTH];

float price;

};

int main() {

struct Book books[NUM_BOOKS];

int i, mostExpensiveIndex = 0, lowestPriceIndex = 0;

float maxPrice = INT_MIN, minPrice = INT_MAX;

printf("Enter details for %d books:\n", NUM_BOOKS);

// Input details for each book

for (i = 0; i < NUM_BOOKS; i++) {

printf("\nBook %d:\n", i + 1);

printf("Title: ");

fgets(books[i].title, MAX_TITLE_AUTHOR_LENGTH, stdin);

// Remove trailing newline character if present

books[i].title[strcspn(books[i].title, "\n")] = 0;

printf("Author: ");

fgets(books[i].author, MAX_TITLE_AUTHOR_LENGTH, stdin);

books[i].author[strcspn(books[i].author, "\n")] = 0;

printf("Price: ");
scanf("%f", &books[i].price);

// Clear the input buffer

while (getchar() != '\n');

// Find the most expensive book

if (books[i].price > maxPrice) {

maxPrice = books[i].price;

mostExpensiveIndex = i;

// Find the lowest price book

if (books[i].price < minPrice) {

minPrice = books[i].price;

lowestPriceIndex = i;

// Display the information of the most expensive book

printf("\nMost Expensive Book:\n");

printf("Title: %s\n", books[mostExpensiveIndex].title);

printf("Author: %s\n", books[mostExpensiveIndex].author);

printf("Price: %.2f\n", books[mostExpensiveIndex].price);

// Display the information of the lowest price book

printf("\nLowest Price Book:\n");

printf("Title: %s\n", books[lowestPriceIndex].title);

printf("Author: %s\n", books[lowestPriceIndex].author);

printf("Price: %.2f\n", books[lowestPriceIndex].price);


return 0;

You might also like