Skip to main content

Exercise Solutions

Concept Questions

Question 1

A file pointer is a variable of type FILE* that serves as a handle to an open file. It allows a program to reference and interact with a specific file.

The fopen() function is used to open a file and associate it with a file pointer:

FILE* fp = fopen("filename.txt", "r");

When fopen() returns NULL, it means the file could not be opened. This can happen for several reasons:

  • The file does not exist (when opening in read mode)
  • The program lacks permission to access the file
  • The file path is invalid
  • The disk is full (when opening in write mode)

It is critical to check for NULL before using the file pointer, as attempting to use a NULL pointer will cause the program to crash.

Question 2

The three file access modes are:

  • "r" (Read): Opens an existing file for reading only. The file must exist and the program must have permissions to read the file. If it does not, fopen() returns NULL. Existing content is preserved and cannot be modified.

  • "w" (Write): Opens a file for writing. If the file exists, its content is truncated (completely erased). If the file does not exist, a new file is created. Any previous data is lost.

  • "a" (Append): Opens a file for writing at the end. If the file exists, new data is added to the end, preserving existing content. If the file does not exist, a new file is created.

When to use each:

  • Use "r" when you need to read existing data
  • Use "w" when you want to create a new file or overwrite an existing one
  • Use "a" when you want to add data to the end of an existing file without losing previous content

Question 3

Checking the return value of fopen() is critical because:

  • If fopen() fails and returns NULL, using the file pointer will cause undefined behavior and likely crash the program
  • Without error checking, the program may attempt to read from or write to a file that was never successfully opened
  • This can lead to data corruption, loss of data, or security vulnerabilities
  • Proper error checking allows the program to handle failures gracefully and inform the user of the problem

Example of proper error checking:

FILE *fp = fopen("data.txt", "r");
int fileError = 0;
if (fp == NULL) {
printf("Error: Could not open file\n");
fileError = 1; // flag error state for future
}
...

Question 4

fgets():

  • Reads an entire line from a file (up to a specified number of characters)
  • Stores the line in a character array (string)
  • Includes the newline character (\n) in the string if present
  • Returns NULL at end-of-file
  • Best for reading complete lines of text

fscanf():

  • Reads formatted data from a file using format specifiers
  • Similar to scanf() but reads from a file
  • Does not include newline characters in strings (unless specified in format)
  • Returns the number of items successfully read
  • Best for reading structured data with specific formatting

When to use each:

  • Use fgets() when you want to read entire lines as strings
  • Use fscanf() when you need to parse structured data with specific formats (e.g., reading integers, floats, and strings in a specific order)

Question 5

A regular expression is a pattern used to match text. In the context of fscanf(), regular expressions allow you to specify what characters to read or skip.

The format specifier %[^\n] means:

  • %[...] - Read a string matching the character set inside the brackets
  • ^ - Negation: match any character NOT in the set
  • \n - The newline character

So %[^\n] reads any string of characters up to (but not including) a newline.

Example:

char line[100];
fscanf(fp, "%[^\n]", line); // Reads entire line without newline
fscanf(fp, "\n", NULL); // Consumes the newline

This is useful because it allows you to read an entire line including spaces, whereas %s would stop at the first space.

Question 8

part a

struct Student
{
int id;
char name[51];
float gpa;
};

part b

int readStudents(const char filename[], struct Student students[],
int max)
{
FILE *fp = fopen(filename, "r");
int count = 0;

if (fp == NULL) {
printf("Error: Could not open file %s\n", filename);
}
else {
while (count < max &&
fscanf(fp, "%d:%[^:]:%f", &students[count].id,
students[count].name,
&students[count].gpa) == 3) {
count++;
}
fclose(fp);
}

return count;
}

part c

void writeStudents(const char filename[],
const struct Student students[], int count)
{
FILE *fp = fopen(filename, "w");
int i;

if (fp == NULL) {
printf("Error: Could not open file %s\n", filename);
}
else {
for (i = 0; i < count; i++) {
fprintf(fp, "%d:%s:%.2f\n", students[i].id,
students[i].name, students[i].gpa);
}
fclose(fp);
}
}

Walkthrough

Exact Output

ID: 3, Value: 78.6
ID: 2, Value: 92.0
ID: 1, Value: 85.5

Debugging

Errors Identified

Syntax Errors:

  • None (the code is syntactically valid)

Logical Errors:

  • name data member is not long enough to store up to 50 characters, we need one more for null char
  • Missing error check for fopen(). If the file cannot be opened, fp will be NULL and fscanf() will crash the program
  • The function is suppose to read from the file but it is open in write ("w") mode
  • The format string is wrong and does not match the formatting described
  • rc is not set as file opening is never checked

Corrected Program

#include <stdio.h>

struct Employee
{
int id;
char name[51];
float salary;
};

int main(void)
{
FILE *fp = fopen("employees.txt", "r");
struct Employee emp;
int rc = 0;

if (fp == NULL) {
printf("Error: Could not open employees.txt\n");
rc = 1;
}
else {
while (fscanf(fp, "%d;%[^:]:%f", &emp.id, emp.name,
&emp.salary) == 3) {
printf("ID: %d, Name: %s, Salary: $%.2f\n",
emp.id, emp.name, emp.salary);
}
fclose(fp);
}

return rc;
}

Programming

part a

struct Contact
{
char name[101];
char email[101];
char phone[21];
};

part b

void addContact(const char filename[], const struct Contact* contact)
{
FILE *fp = fopen(filename, "a");

if (fp == NULL) {
printf("Error: Could not open file %s\n", filename);
}
else {
fprintf(fp, "%s|%s|%s\n", contact->name,
contact->email, contact->phone);
fclose(fp);
}
}

part c

void displayContacts(const char filename[])
{
FILE *fp = fopen(filename, "r");
struct Contact contact;

if (fp == NULL) {
printf("Error: Could not open file %s\n", filename);
}
else {
printf("%-20s | %-20s | %-16s\n", "Name", "Email",
"Phone");
printf("---------------------|----------------------|------------------\n");

while (fscanf(fp, "%100[^|]|%100[^|]|%20[^\n]\n", contact.name, contact.email, contact.phone) == 3) {
printf("%-20s | %-20s | %-16s\n", contact.name, contact.email, contact.phone);
}
fclose(fp);
}
}

part d

void findContact(const char filename[], const char searchName[])
{
FILE *fp = fopen(filename, "r");
struct Contact contact;
int found = 0;

if (fp == NULL) {
printf("Error: Could not open file %s\n", filename);
}
else {
while (!found && fscanf(fp, "%100[^|]|%100[^|]|%20[^\n]\n",
contact.name, contact.email,
contact.phone) == 3) {
if (strcmp(contact.name, searchName) == 0) {
printf("Contact found:\n");
printf("Name: %s\n", contact.name);
printf("Email: %s\n", contact.email);
printf("Phone: %s\n", contact.phone);
found = 1;

}
}

if (!found) {
printf("Contact '%s' not found.\n", searchName);
}

fclose(fp);
}
}