I want to read cards from an input file and print out their values.
However when I try to print out characters it prints out '0'.
If I print out the character 'A', then normally the int value 65 is supposed to be printed out since I stored the character 'A' as an Int.
Could anyone help me out?
Thank you in advance.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define max 100
#define MAX_LENGTH 14
int main(){
char *lines = malloc(max*sizeof(char));
char **colour = malloc(max*sizeof(char));
int *value =malloc(max*sizeof(int));
FILE *fp;
fp = fopen("config2.txt", "r");
if(fp == NULL){
printf("Cannot open filelist.txt\n");
return 1;
}
int i= 0;
while (i < max && fgets(lines, MAX_LENGTH, fp) != NULL) {
colour[i] = malloc(MAX_LENGTH);
sscanf(lines, "%s %d", colour[i], &value[i]);
printf("%s\n", colour[i]);
printf("%d\n", value[i]);
i++;
}
return 0;
}
input:
RED A
RED 2
RED 3
RED 4
RED 5
RED 6
RED 7
RED 8
RED 9
RED 10
RED J
RED Q
RED K
Your primary problem handling either 'A' or an integer as the value, stems from the misunderstanding that A can be parsed with sscanf using the "%d" format specifier, it can't. Why? When you attempt to parse 'A' with "%d" a matching failure occurs, no further characters are extracted from the input buffer, and the return for sscanf will be the number of successful conversions that took place prior to the failure.
When you have data of differing types, e.g.
RED A
RED 2
In order to parse the values for A or 2, it will require two different sscanf expressions, which you can easily distinguish simply by checking the return for sscanf. You do this in a conditional and if parsing with "%s %d" fails, you attempt the parse with "%s %c" and validate whether that succeeded.
For instance, say instead of allocating with malloc (you are not reallocating anyway), you simply declare an array of struct to hold the color and value read from each line, e.g.
...
#define MAXCOLR 14
#define MAXLINE 100
#define MAXCHR 1024 /* don't skimp on read buffer size */
typedef struct { /* simple struct to associate each color/value */
char color[MAXCOLR];
int value;
} colorval_t;
int main (int argc, char **argv) {
size_t ndx = 0; /* index */
char buf[MAXCHR]; /* read buffer */
colorval_t arr[MAXLINE] = {{ .color = "" }}; /* array of struct */
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
...
while (ndx < MAXLINE && fgets (buf, MAXCHR, fp)) {
char c; /* temp char to use for parsing 2nd case */
if (sscanf (buf, "%13s %d", arr[ndx].color, &arr[ndx].value) == 2)
ndx++;
else if (sscanf (buf, "%13s %c", arr[ndx].color, &c) == 2) {
arr[ndx].value = c;
ndx++;
}
}
The while loop above being the operative code for handling the parse of information read from each line into buf. The first sscanf call attempts the parse into a string and integer value. If the return is not 2, then a second call to sscanf is made to attempt to parse the contents into a string and a character. If that succeeds, the character value (e.g. the ASCII value for the character) is assigned to value, which from your question appears to be what you intended.
Adding a few validations and then outputting the color and value for each struct contained in arr, you could do something like the following. (note: the program takes the filename to read as the first argument or reads from stdin by default if no argument is given. Don't hardcode filenames. Either pass the filename as an argument or prompt for its entry)
#include <stdio.h>
#define MAXCOLR 14
#define MAXLINE 100
#define MAXCHR 1024 /* don't skimp on read buffer size */
typedef struct {
char color[MAXCOLR];
int value;
} colorval_t;
int main (int argc, char **argv) {
size_t ndx = 0;
char buf[MAXCHR];
colorval_t arr[MAXLINE] = {{ .color = "" }};
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
while (ndx < MAXLINE && fgets (buf, MAXCHR, fp)) {
char c;
if (sscanf (buf, "%13s %d", arr[ndx].color, &arr[ndx].value) == 2)
ndx++;
else if (sscanf (buf, "%13s %c", arr[ndx].color, &c) == 2) {
arr[ndx].value = c;
ndx++;
}
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (size_t i = 0; i < ndx; i++)
printf ("arr[%2zu] : %-14s %d\n", i, arr[i].color, arr[i].value);
return 0;
}
(note: the use of the field-width modifier 13 to protect the character array bounds for color)
Example Use/Output
Using your data as input would result in the following:
$ ./bin/rdcolorval <dat/colorval.txt
arr[ 0] : RED 65
arr[ 1] : RED 2
arr[ 2] : RED 3
arr[ 3] : RED 4
arr[ 4] : RED 5
arr[ 5] : RED 6
arr[ 6] : RED 7
arr[ 7] : RED 8
arr[ 8] : RED 9
arr[ 9] : RED 10
arr[10] : RED 74
arr[11] : RED 81
arr[12] : RED 75
Look things over and let me know if you have further questions.
the following proposed code:
does not keep the contents of every line, rather only of the current line. You might want to change that
properly checks for errors
does not use dynamic memory allocation. You might want to change that
cleanly compiles
Does not include header files those contents are not used
Properly cleans up (closes files) when exiting either normally or when a call to sscanf() fails.
uses a valid signature for main()
uses the convention of defined values being in all capitals
properly uses MAX CHARACTERS modifier on the input format specifier %s so as to avoid any possibility of a buffer overflow and the resulting undefined behavior
documents why each header file is being included
does NOT correct the mismatch between the incoming data and the call to sscanf() Strongly suggest you read about atoi() and strtol() and modify the call to sscanf() to expect two char sequences and save them accordingly.
And now, the proposed code:
#include <stdio.h> // printf(), fprintf(), sscanf()
#include <stdlib.h> // exit(), EXIT_FAILURE
//#define MAX_LINES 100
#define MAX_LENGTH 14
int main( void )
{
char lines[ MAX_LENGTH +1];
char colour[ MAX_LENGTH ];
int value;
FILE *fp = fopen( "config2.txt", "r" );
if(fp == NULL)
{
perror( "fopen to read config2.txt failed" );
exit( EXIT_FAILURE );
}
while ( fgets( lines, MAX_LENGTH, fp ) )
{
if( sscanf(lines, "%100s %d", colour, &value) != 2 )
{
fprintf( stderr, "sscanf to extract two fields from input line failed\n" );
fclose( fp );
exit( EXIT_FAILURE );
}
printf( "colour: %s\n", colour );
printf( "value: %d\n", value );
}
fclose( fp );
return 0;
}
Your code is pretty close to something that works. The main problem is that you can not scan a line like "RED A" using the format specifier "%s %d" as A is not an integer. Instead you can scan it using a char.
Further you have a problem with the malloc of colour. You need to do sizeof(char*) as you want an array of char pointers.
So try something like:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define max 100
#define MAX_LENGTH 14
int main(){
char *filename = "config2.txt";
char *lines = malloc(max*sizeof(char));
char **colour = malloc(max*sizeof(char*)); // Use sizeof(char*)
char *value =malloc(max*sizeof(char)); // Use char instead of int
FILE *fp;
fp = fopen(filename, "r");
if(fp == NULL){
fprintf(stderr, "Cannot open %s : ", filename);
perror("");
exit(1);
}
int i= 0;
while (i < max && fgets(lines, MAX_LENGTH, fp) != NULL) {
colour[i] = malloc(MAX_LENGTH);
if (sscanf(lines, "%s %c", colour[i], &value[i]) != 2) // Use %c instead of %d and check return value
{
printf("Unexpected input file data\n");
exit(1);
}
printf("%s ", colour[i]);
printf("%c (%d)\n", value[i], value[i]); // Print value as both char and int
i++;
}
// Clean up
fclose (fp);
for (int j = 0; j<i; ++j) free(colour[j]);
free(colour);
free(value);
return 0;
}
Output:
RED A (65)
RED 2 (50)
RED 3 (51)
RED 4 (52)
RED 5 (53)
RED 6 (54)
RED 7 (55)
RED 8 (56)
RED 9 (57)
RED 1 (49)
RED J (74)
RED Q (81)
RED K (75)
Also notice that you should always check the return value of malloc. Example:
SomeType *someVar = malloc(sizeof(SomeType)); // or better malloc(sizeof *momeVar);
if (someVar == NULL)
{
// Out of memory - add error handling (e.g. terminate program)
}
Related
i am trying to read a text file and extract the cordinates of 'X' so that i can place then on a map
the text file is
10 20
9 8 X
2 3 P
4 5 G
5 6 X
7 8 X
12 13 X
14 15 X
I tried multiple times but I am unable to extract the relevant data and place it in separate variables to plot
I am quite new to c and am trying to learn things so any help is appreciated
thanks in advance
From my top comments, I suggested an array of point structs.
Here is your code refactored to do that.
I changed the scanf to use %s instead of %c for the point name. It generalizes the point name and [probably] works better with the input line because [I think] the %c would not match up correctly.
It compiles but is untested:
#include <stdio.h>
#include <stdlib.h>
struct point {
int x;
int y;
char name[8];
};
struct point *points;
int count;
int map_row;
int map_col;
void
read_data(const char *file_name)
{
FILE *fp = fopen(file_name, "r");
if (fp == NULL) {
/* if the file opened is empty or has any issues, then show the error */
perror("File Error");
return;
}
/* get the dimensions from the file */
fscanf(fp, "%d %d", &map_row, &map_col);
map_row = map_row + 2;
map_col = map_col + 2;
while (1) {
// enlarge dynamic array
++count;
points = realloc(points,sizeof(*points) * count);
// point to place to store data
struct point *cur = &points[count - 1];
if (fscanf(fp, "%d %d %s", &cur->x, &cur->y, cur->name) != 3)
break;
}
// trim to amount used
--count;
points = realloc(points,sizeof(*points) * count);
fclose(fp);
}
There are a number of way to approach this. Craig has some very good points on the convenience of using a struct to coordinate data of different types. This approach reads with fgets() and parses the data you need with sscanf(). The benefit eliminates the risk of a matching-failure leaving characters unread in your input stream that will corrupt the remainder of your read from the point of matching-failure forward. Reading with fgets() you consume a line of input at a time, and that read is independent of the parsing of values with sscanf().
Putting it altogether and allowing the filename to be provided by the first argument to the program (or reading from stdin by default if no argument is provided), you can do:
#include <stdio.h>
#define MAXC 1024 /* if you need a constand, #define one (or more) */
int main (int argc, char **argv) {
char buf[MAXC]; /* buffer to hold each line */
int map_row, map_col; /* map row/col variables */
/* use filename provided as 1st argument (stdin if none provided) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open");
return 1;
}
/* read/validate first line saving into map_row, map_col */
if (!fgets (buf, MAXC, fp) ||
sscanf (buf, "%d %d", &map_row, &map_col) != 2) {
fputs ("error: EOF or invalid map row/col data.\n", stderr);
return 1;
}
/* loop reading remaining lines, for used as line counter */
for (size_t i = 2; fgets (buf, MAXC, fp); i++) {
char suffix;
int x, y;
/* validate parsing x, y, suffix from buf */
if (sscanf (buf, "%d %d %c", &x, &y, &suffix) != 3) {
fprintf (stderr, "error: invalid format line %zu.\n", i);
continue;
}
if (suffix == 'X') { /* check if line suffix is 'X' */
printf ("%2d %2d %c\n", x, y, suffix);
}
}
if (fp != stdin) { /* close file if not stdin */
fclose (fp);
}
}
(note: this just illustrates the read and isolation of the values from lines with a suffix of 'X'. Data handling, and calculations are left to you)
Example Use/Output
With your data in dat/coordinates.txt you could do:
$ ./bin/readcoordinates dat/coordinates.txt
9 8 X
5 6 X
7 8 X
12 13 X
14 15 X
As Craig indicates, if you need to store your matching data, then an array of struct provides a great solution.
I have a file input.dat. In this file, there are 3 lines:
1 2 3
5 7 10 12
8 9 14 13 15 17
I am going to read one of the three lines using C, and return the number of the elements.
For example, I want to read the 2nd line 5 7 10 12 into memory, and also return the number of values in the 2nd line, which is 4. My code is below...
#include <stdio.h>
#include <stdlib.h>
#define STRING_SIZE 2000
int main() {
FILE *fp = fopen("in.dat", "r");
char line[STRING_SIZE];
int lcount = 0, nline = 1, sum = 0, number;
if (fp != NULL) {
while (fgets(line, STRING_SIZE, fp) != NULL) {
if (lcount == nline) {
while (sscanf(line, "%d ", &number)) {
sum++;
}
break;
} else {
lcount++;
}
}
fclose(fp);
}
exit(0);
}
When I run this code, it never stops like a dead loop. What is the problem here?
the loop while (sscanf(line, "%d ", &number)) keeps parsing the first number in the line.
You should use strtol instead:
#include <stdio.h>
#include <stdlib.h>
#define STRING_SIZE 2000
int main() {
FILE *fp = fopen("in.dat", "r");
char line[STRING_SIZE];
int lcount = 0, nline = 1;
if (fp != NULL) {
while (fgets(line, STRING_SIZE, fp) != NULL) {
if (lcount == nline) {
char *p = line, *q;
int count = 0;
for (;;) {
long val = strtol(p, &q, 0); // parse an integer
if (q == p) {
// end of string or not a number
break;
}
// value was read into val. You can use it for whatever purpose
count++;
p = q;
}
printf("%d\n", count);
break;
} else {
lcount++;
}
}
fclose(fp);
}
return 0;
}
You were thinking along the right path with your use of sscanf(), the only piece of the puzzle you were missing is how to apply an offset to line so that you read the next value in the line with your next call to sscanf(). You do that by keeping track of the number of characters consumed on each call to sscanf() using the "%n" conversion (it does not add to your conversion count returned by sscanf()) For example reading lines from the open file-stream fp, you could do:
#define MAXC 1024 /* if you need a constant, #define one (or more) */
...
char line[MAXC] = ""; /* buffer to hold each line */
...
while (fgets (line, MAXC, fp)) { /* reach each line in file */
int offset = 0, /* offset in line for next sscanf() read */
nchr = 0, /* number of char consumed by last read */
val, /* integer value read with sscanf() */
nval = 0; /* number of values read in line */
/* conververt each integer at line + offset, saving no. of chars consumed */
while (sscanf (line + offset, "%d%n", &val, &nchr) == 1) {
printf (" %d", val); /* output value read */
offset += nchr; /* update offset with no. chars consumend */
nval++; /* increment value count */
}
printf (" - %d values\n", nval); /* output no. values in line */
}
(Note: strtol() provides better error reporting than sscanf() on a failed conversion)
If you put it together with an example that reads from the filename provided as the first argument to the program (or reads from stdin by default if no argument is given), you could do:
#include <stdio.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
int main (int argc, char **argv) {
char line[MAXC] = ""; /* buffer to hold each line */
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
while (fgets (line, MAXC, fp)) { /* reach each line in file */
int offset = 0, /* offset in line for next sscanf() read */
nchr = 0, /* number of char consumed by last read */
val, /* integer value read with sscanf() */
nval = 0; /* number of values read in line */
/* conververt each integer at line + offset, saving no. of chars consumed */
while (sscanf (line + offset, "%d%n", &val, &nchr) == 1) {
printf (" %d", val); /* output value read */
offset += nchr; /* update offset with no. chars consumend */
nval++; /* increment value count */
}
printf (" - %d values\n", nval); /* output no. values in line */
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
}
Example Use/Output
With the data you show in the filename dat/nvals.txt you would get:
$ ./bin/fgetsnvals dat/nvals.txt
1 2 3 - 3 values
5 7 10 12 - 4 values
8 9 14 13 15 17 - 6 values
Look things over and let me know if you have further questions.
A bit cleaner version of chqrlie's answer. Started with a string as that is what the question is really about after fgets().
sscanf() won't step through the string, it always reads from the beginning.
strtol() looks for a long int in the beginning of the string, ignoring initial white-space. Gives back the address of where it stops scanning.
The manual of strtol() says that errno should be checked for any conversion error.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#define STRING_SIZE 2000
int main(void)
{
char line[STRING_SIZE] = "5 7 10 12";
char* start = line;
char* end;
int count = 0;
while(1)
{
/**
* strtol() look for long int in beginning of the string
* Ignores beginning whitespace
*
* start: where to strtol() start looking for long int
* end: where strtol() stops scanning for long int
*/
errno = 0; // As strol() manual says
strtol(start, &end, 0);
if (errno != 0)
{
printf("Error in strtol() conversion.\n");
exit(0);
}
if (start == end) break; // Quit loop
start = end;
count++;
}
printf("count: %d\n", count);
return 0;
}
This question already has answers here:
C read file line by line
(17 answers)
Closed 2 years ago.
120 test 123
101 test1 132
126 test2 140
150 test3 150
This is inside of a txt file. What i want to do is getting only the first number in the first line of the file and put it into an array and go to the next line then do the same thing again... for example array[0]={120} , array[1]={101}...
int j,i=1,array[20];
FILE *fp;
fp=fopen("input.txt","r");
for(i=0;i<10;i++)
fscanf(fp,"%d",&array[i]);
This is what i tried to do but i guess fscanf doesnt skip the line after getting the number... It might be a newbie question but i am stuck on that part :P
Continuing from the comment, you want to consume an entire line of input with each read, then parse the first integer from the line into your array. Using fgets() to read each line into a buffer and then parse with sscanf is a simple solution, e.g.
#include <stdio.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define MAXI 256
int main (int argc, char **argv) {
char buf[MAXC];
int array[MAXI], n = 0;
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
while (n < MAXI && fgets (buf, MAXC, fp)) /* read each line into buf */
if (sscanf (buf, "%d", &array[n]) == 1) /* parse 1st int from buf to array */
n++; /* increment counter */
if (fp != stdin) /* close file if not stdin */
fclose (fp);
for (int i = 0; i < n; i++)
printf ("array[%2d] : %d\n", i, array[i]);
}
Example Input File
$ cat dat/arr_1st_num.txt
120 test 123
101 test1 132
126 test2 140
150 test3 150
(note: blank lines or lines not beginning with an integer value are simply read and discarded and do not impact your read and storage of data)
Example Use/Output
$ ./bin/array_1st_num dat/arr_1st_num.txt
array[ 0] : 120
array[ 1] : 101
array[ 2] : 126
array[ 3] : 150
Look things over and let me know if you have further questions.
What you can do is you can scan the whole line instead as a string, then you get the first token (which is the first number) by getting the first "token" of the string using the strtok function. Lastly, we use the atoi function to convert the string number to an integer number (don't forget to include stdlib.h)
#include<stdlib.h>
int j, i=1, array[20];
FILE *fp;
fp = fopen("input.txt","r");
char currentLine[1000];
for(i=0; i<10; i++) {
fscanf(fp, "%[^\n]", currentLine);
// extract the first token
char * firstToken = strtok(currentLine, " ");
// store the firstToken (which is the number)
// to the array
array[i] = atoi(firstToken);
}
The Text File was writable with 1 blank line after the struct.
Is similar to:
1 111 1 Peter
22 22 2 John Lays
3 3 3 Anne Belgs
The struct is:
struct estruturaCarro {
int id, potencia, avariado;
char name[11];
} carro;
I have write the data to the text File:
fprintf(fp, "%-2d %-3d %-1d %-10s \n\n", carro.id, carro.potencia, carro.avariado, carro.name);
I read the file data this way, but I'm sure it's not the best way:
while(true){
int result = fscanf(fp, "%d %d %d %10[^\n]", &carro.id, &carro.potencia, &carro.avariado, &carro.name);
if(result==4){
printf("%-2d %-3d %-1d % -s\n", carro.id, carro.potencia, carro.avariado, carro.name);
}else break;
}
How can I read the text file, saving the data struct, without reading the blank lines?
If I want to validate the values read (ID = xx, potencia = xxx, avariado = x, name = 10 characters), is it better, before or after filling the array of struct?The format of each line of the file is:xx xxx x aaaaaaaaaa (x = digits, a = characters)For example, if one of the lines is
9 123 4 Error 1st value
stop reading the file (informing the user), because the line should be:
9 123 4 Error 1st value (one space was missing after 9) - or because NAME has more than 10 characters.
You don't show how true is set in while(true), but it must be based on the return of fscanf in the way you are approaching it. You must test three cases:
fscanf return is EOF, read done, true should be set to FALSE to break loop;
fscanf return less than 4, matching or input failure, no guarantee this occurred due to an empty line; and
fscanf return is equal to 4, good input.
Unless you are checking all three, you cannot cover all cases for fscanf and further, if there is any additional or extraneous character in your input file, your formatted read will fail.
That is why a better option is to read each line into a buffer with fgets and then parse the information you need from the buffer itself with sscanf. This provides the benefit of allowing separate validation of (1) the read; and (2) the parse of information. Further, since you are consuming a line-at-a-time, there is no uncertainty on what remains in the input stream and a failure in parsing any one line does not prevent a successful read of the remainder.
A short implementation with fgets() and sscanf() reading each struct into an array-of-struct, you can do something similar to the following:
#include <stdio.h>
#define MAXN 11 /* if you need a constant, #define one (or more) */
#define MAXC 1024 /* (don't skimp on buffer size) */
typedef struct { /* use a simple typedef */
int id, potencia, avariado;
char name[MAXN];
} carro_t;
int main (int argc, char **argv) {
char buf[MAXC]; /* buffer to read line */
carro_t carro[MAXN] = {{ .id = 0 }}; /* array of struct */
size_t ndx = 0; /* array index */
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
while (ndx < MAXN && fgets (buf, MAXC, fp)) { /* read each line */
carro_t tmp = { .id = 0 }; /* temp struct */
if (*buf == '\n') /* if line empty */
continue; /* get next line */
if (sscanf (buf, "%d %d %d %10[^\n]", /* separate/validate */
&tmp.id, &tmp.potencia, &tmp.avariado, tmp.name) == 4)
carro[ndx++] = tmp; /* add to array of struct */
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (size_t i = 0; i < ndx; i++)
printf ("%3d %3d %3d %s\n",
carro[i].id, carro[i].potencia, carro[i].avariado, carro[i].name);
return 0;
}
(note: the filename is provide as the first argument to the program, or if no filename is provided, read from stdin by default)
While with your particular data file, there is no reason you cannot use fscanf, it is fragile and one character too many (like "Anne Belgss") will cause it to break. A fscanf implementation removing the true and simply looping as you have could be:
for (;;) {
carro_t tmp = { .id = 0 };
if (fscanf (fp, "%d %d %d %10[^\n]", /* separate/validate */
&tmp.id, &tmp.potencia, &tmp.avariado, tmp.name) == 4)
carro[ndx++] = tmp; /* add to array of struct */
else
break;
}
Either way, you will produce the same output with "this" input file, e.g.
Example Use/Output
$ ./bin/readwblanks ~/tmpd/file
1 111 1 Peter
22 22 2 John Lays
3 3 3 Anne Belgs
just use simple reading way is ok,like this.
while(fscanf(fp,"%d %d %d %s",&carro.id,&carro.potencia,&carro.avariado,carro.name)!=EOF)
printf("%d %d %d %s\n",carro.id,carro.potencia,carro.avariado,carro.name);
result is like this
enter image description here
I'm new in C and try to do some practice
I want to change the txt file from
Chicken
10000
Cow
20000
Duck
20
Sheep
1000
to
Chicken 10000
Cow 20000
Duck 20
Sheep 1000
then destroy the animal <50
Chicken 10000
Cow 20000
Sheep 1000
My first step:
Read the file and compose the letter to the string.
Like the first word "Chicken", will be composed of "C" "h" "i" "c" "k" "e" "n".
As following my code, when I use strcpy(str0, ""); I have changed the string array strings[0] = str0; (str0 is "Chicken" now) before strcpy(str0, "");, but when strcpy(str0, "");operate, my memory in strings[0] was also be changed.
How can I fix the problem?
This is my code:
void append(char* s, char c)
{
int len = strlen(s);
s[len] = c;
s[len+1] = '\0';
}
int main() {
char str0[256] = "";
char tmp_char;
const char *string[2];
int i, c, line_counter=0;
FILE *file;
file = fopen("cry.txt", "r");
if (file) {
while ((c=getc(file)) !=EOF) {
if (c == 10) {
line_counter++;
string[0]=str0;
strcpy(str0, "");
continue;
}
tmp_char = c;
append(str0, tmp_char);
}
fclose(file);
}
return 0;
}
You should divide your problem into smaller pieces and implement them independently from each other --> "divide and conquer"
Before starting programming you should think about the steps.
I would analyze the problem the following way:
open infile
open outfile
while not eof
read data set --> 2 lines
read a line -> parse animal name
read a line -> parse number
filter data set
write data set
close files
I would derive the following structures/functions from this (or use library functions - depending on the class's task):
structure
DataSet{animalName, count};
function
readLine(filehandle, bufferpointer, maxbuffersize) -> success
readDataset(bufferpointer1, bufferpointer2) -> success
(parseAnimalName(linebuffer1, buffersize, namebuffer, maxlength) -> success)
(parseAnimalCount(linebuffer, numberpinter) -> success)
filterAnimal(DataSet) -> bool
writeAnimal(filehandle, DataSet) --> success
Depending on the possibility to use libraries parse functions I would omit the functions in parentheses.
With this isolated little functionalities it should be a lot easier to implement the whole problem and also analyze where bugs occur.
Once you solve the problem yourself you can compare it with my solution. I commented it pretty heavily.
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LIMIT 30
// Arbitrary max number of items
#define MAX_ITEMS 16
// Arbitrary maximum result size
#define MAX_RESULT_SIZE 256
// Output string must keep both animal name and stringified integer.
// This integer will take at most 11 characters.
// It means that in string of format "%s %d\n" the animal name
// must take at most (MAX_RESULT_SIZE - 11 - whitespace - \n - NUL) characters.
#define MAX_STR_SIZE ((MAX_RESULT_SIZE) - 14)
int main(void) {
int retcode;
const char *filename = "file.txt";
FILE *file = fopen("file.txt", "r");
if (file == NULL) {
fprintf(stderr, "Failed to open file %s: %s", filename, strerror(errno));
}
char text[MAX_STR_SIZE + 1];
int number;
int id = 0;
char results[MAX_ITEMS][MAX_RESULT_SIZE];
// Dynamically define fmt string to limit fscanf to MAX_STR_SIZE
// Format specifier "%256s" makes sure that fscanf won't read a string that is
// longer than 256 characters (remember about additional one byte for NUL character,
// output memory must have space for 257 characters).
char fmt[32];
snprintf(fmt, sizeof(fmt), "%%%zus\n", (size_t)MAX_STR_SIZE);
while(1) {
if (id >= MAX_ITEMS) break;
retcode = fscanf(file, fmt, text);
if (retcode == EOF) break;
// From fscanf manual page we know 'On success, these functions return the
// number of input items successfully matched and assigned'. If this is
// different than 1 then something went wrong with input string. Maybe
// It's different than we assumed.
if (retcode != 1) {
fprintf(stderr, "Input is not matching format specifiers");
exit(EXIT_FAILURE);
}
retcode = fscanf(file, "%d\n", &number);
if (retcode == EOF) break;
if (retcode != 1) {
fprintf(stderr, "Input is not matching format specifiers");
exit(EXIT_FAILURE);
}
// Filtering logic
if (number < LIMIT) continue;
sprintf(results[id++], "%.*s %d", MAX_STR_SIZE, text, number);
}
for(int i = 0; i < id; i++) printf("%s\n", results[i]);
fclose(file);
return 0;
}
your line just assigns pointer to the variable str0 into string[0]
string[0]=str0;
That is why your string[0] changes after str0 changes. They point to the same memory.
To solve this, you have to copy value from str0 into string[0]