You need to open the file first. I think you're not developing the program the right way, so I'll do it and post each version so you can see how to incrementally build a program.
1 - Start with a skeleton program:
Code:
#include <stdio.h>
int main( void ) {
return 0;
}
2 - The big thing is the source of the input, so I open and print a test file to make sure that everything works:
Code:
#include <stdio.h>
int main( void ) {
FILE *fp = fopen( "test.txt", "r" );
if ( fp ) {
int ch;
while ( ( ch = fgetc( fp ) ) != EOF ) {
fputc( ch, stdout );
}
fclose( fp );
} else {
perror( "error opening the file" );
}
return 0;
}
3 - Okay, the file opens and the program reads it like I think I want. Now I'll count all of the characters and compare that with the actual file to see if it's accurate:
Code:
#include <stdio.h>
int main( void ) {
FILE *fp = fopen( "test.txt", "r" );
if ( fp ) {
int ch;
int n = 0;
while ( ( ch = fgetc( fp ) ) != EOF ) {
++n;
}
printf( "total characters: %d\n", n );
fclose( fp );
} else {
perror( "error opening the file" );
}
return 0;
}
4 - Now I'm sure that the file is being input the way I want, so I can start counting character types one at a time, checking each time that the count is right on a test file:
Code:
#include <stdio.h>
#include <ctype.h>
int main( void ) {
FILE *fp = fopen( "test.txt", "r" );
if ( fp ) {
int ch;
int nalpha = 0;
int ndigit = 0;
int npunct = 0;
int nspace = 0;
while ( ( ch = fgetc( fp ) ) != EOF ) {
if ( isalpha( ch ) ) {
++nalpha;
} else if ( isdigit( ch ) ) {
++ndigit;
} else if ( ispunct( ch ) ) {
++npunct;
} else if ( isspace( ch ) ) {
++nspace;
}
}
printf( "alphabetic characters: %d\n", nalpha );
printf( "digit characters: %d\n", ndigit );
printf( "punctuation characters: %d\n", npunct );
printf( "whitespace characters: %d\n", nspace );
fclose( fp );
} else {
perror( "error opening the file" );
}
return 0;
}
5 - Testing is done on producing the output, but the file is still hard coded, and I want the user to pass a file to the program. main is getting kind of long, so I'll refactor the counting code out into a function and test it all again to make sure it still works. Any change, even something tiny, means retesting:
Code:
#include <stdio.h>
#include <ctype.h>
void process_file( FILE *fp );
int main( void ) {
FILE *fp = fopen( "test.txt", "r" );
if ( fp ) {
process_file( fp );
fclose( fp );
} else {
perror( "error opening the file" );
}
return 0;
}
void process_file( FILE *fp ) {
int ch;
int nalpha = 0;
int ndigit = 0;
int npunct = 0;
int nspace = 0;
while ( ( ch = fgetc( fp ) ) != EOF ) {
if ( isalpha( ch ) ) {
++nalpha;
} else if ( isdigit( ch ) ) {
++ndigit;
} else if ( ispunct( ch ) ) {
++npunct;
} else if ( isspace( ch ) ) {
++nspace;
}
}
printf( "alphabetic characters: %d\n", nalpha );
printf( "digit characters: %d\n", ndigit );
printf( "punctuation characters: %d\n", npunct );
printf( "whitespace characters: %d\n", nspace );
}
6 - Now I can add the argument stuff for taking a file as a command line parameter without cluttering main up too much. I make sure my tests touch every code path, so I'll fake error and stuff to make sure that the error cases work like I want:
Code:
#include <stdio.h>
#include <ctype.h>
void process_file( FILE *fp );
int main( int argc, char *argv[] ) {
if ( argc > 1 ) {
FILE *fp = fopen( argv[1], "r" );
if ( fp ) {
process_file( fp );
fclose( fp );
} else {
perror( "error opening the file" );
}
} else {
fprintf( stderr, "usage: prog <filename>\n" );
}
return 0;
}
void process_file( FILE *fp ) {
int ch;
int nalpha = 0;
int ndigit = 0;
int npunct = 0;
int nspace = 0;
while ( ( ch = fgetc( fp ) ) != EOF ) {
if ( isalpha( ch ) ) {
++nalpha;
} else if ( isdigit( ch ) ) {
++ndigit;
} else if ( ispunct( ch ) ) {
++npunct;
} else if ( isspace( ch ) ) {
++nspace;
}
}
printf( "alphabetic characters: %d\n", nalpha );
printf( "digit characters: %d\n", ndigit );
printf( "punctuation characters: %d\n", npunct );
printf( "whitespace characters: %d\n", nspace );
}
7 - Now I can really crank down on the stability of the code by adding defensive cases:
Code:
#include <stdio.h>
#include <ctype.h>
int process_file( FILE *fp );
int main( int argc, char *argv[] ) {
if ( argc > 1 ) {
FILE *fp = fopen( argv[1], "r" );
if ( fp ) {
if ( !process_file( fp ) ) {
perror( "error reading from the file" );
}
fclose( fp );
} else {
perror( "error opening the file" );
}
} else {
fprintf( stderr, "usage: prog <filename>\n" );
}
return 0;
}
int process_file( FILE *fp ) {
int ch;
int nalpha = 0;
int ndigit = 0;
int npunct = 0;
int nspace = 0;
int rc = 0;
if ( fp != NULL ) {
while ( ( ch = fgetc( fp ) ) != EOF ) {
if ( isalpha( ch ) ) {
++nalpha;
} else if ( isdigit( ch ) ) {
++ndigit;
} else if ( ispunct( ch ) ) {
++npunct;
} else if ( isspace( ch ) ) {
++nspace;
}
}
if ( !ferror( fp ) ) {
printf( "alphabetic characters: %d\n", nalpha );
printf( "digit characters: %d\n", ndigit );
printf( "punctuation characters: %d\n", npunct );
printf( "whitespace characters: %d\n", nspace );
rc = 1;
}
}
return rc;
}
8 - Now the code is solid, but there aren't any comments, so I'll go through it and add comments to places that might be confusing and then call it a day. There aren't many because this is a pretty simple program:
Code:
/*
File - prog.c
Author - D. Burke (Noir)
Count alphabetic, digit, punctuation, and
whitespace characters in a user supplied file
*/
#include <stdio.h>
#include <ctype.h>
int process_file( FILE *fp );
int main( int argc, char *argv[] ) {
if ( argc > 1 ) {
FILE *fp = fopen( argv[1], "r" );
if ( fp ) {
if ( !process_file( fp ) ) {
// failure means a stream error or bad file
perror( "error reading from the file" );
}
fclose( fp );
} else {
perror( "error opening the file" );
}
} else {
fprintf( stderr, "usage: prog <filename>\n" );
}
return 0;
}
int process_file( FILE *fp ) {
int ch;
int nalpha = 0;
int ndigit = 0;
int npunct = 0;
int nspace = 0;
// assume failure
int rc = 0;
if ( fp != NULL ) {
while ( ( ch = fgetc( fp ) ) != EOF ) {
if ( isalpha( ch ) ) {
++nalpha;
} else if ( isdigit( ch ) ) {
++ndigit;
} else if ( ispunct( ch ) ) {
++npunct;
} else if ( isspace( ch ) ) {
++nspace;
}
}
if ( !ferror( fp ) ) {
// only produce output if there are no errors
printf( "alphabetic characters: %d\n", nalpha );
printf( "digit characters: %d\n", ndigit );
printf( "punctuation characters: %d\n", npunct );
printf( "whitespace characters: %d\n", nspace );
rc = 1;
}
}
return rc;
}
That's how you should do it too. Start with a skeleton and build the program up bit by bit, making sure to test after every change. It's okay to change the requirements for testing like when I counted all the characters in the file or just printed the file out. It's okay to backtrack and change your mind on stuff too like when I decided to factor the counting code into a function. It's not as much building a program from a blueprint as it is evolving a program from an idea. You get to change your mind and make it better along the way even after you've finished doing it another way.