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()returnsNULL. 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 returnsNULL, 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
NULLat 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,fpwill beNULLandfscanf()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);
}
}