2

I want to create a program that opens and writes to 3 different text files, the names a user inputs.

The condition would be that if last names end with certain characters, they would be saved to a specific text file.

For example, if the user inputs a last name that ends with ian it should be saved to the ARMENIA.TXT folder.

Here is my code and the issues I encounter with it:

struct names {
    char firstName[20];
    char lastName[30];
} person;

int main() {
    FILE *arm, *ita, *esp;
    char ian[] = "ian";
    char ini[] = "ini";
    char ez[] = "ez";
    char response;
    char *ret;

    arm = fopen("C:\\Programming\\ARMENIA.TXT", "wt");
    ita = fopen("C:\\Programming\\ITALIA.TXT", "wt");
    esp = fopen("C:\\Programming\\ESPANIA.TXT", "wt");

    if (arm, ita, esp == NULL) {
        printf("Error: The archives could not be created.");
        return 1;
    } else {
        printf("Operation Successful: Archives created.\n\n");
    }

    do {
        fflush(stdin);
        printf("\n\nPlease input your first name: ");
        scanf("%s", &person.firstName);
        printf("Please input your last name: ");
        scanf("%s", &person.lastName);

        if (ret = strstr(person.lastName, ian)) {  
            fwrite(person.lastName, 1, strlen(person.lastName), arm);
            fwrite(person.firstName, 1, strlen(person.firstName), arm);
        }

        if (ret = strstr(person.lastName, ini)) { 
            fwrite(person.lastName, 1, strlen(person.lastName), ini);
            fwrite(person.firstName, 1, strlen(person.firstName), ini);
        }

        if (ret = strstr(person.lastName, ez)) { 
            fwrite(person.lastName, 1, strlen(person.lastName), ez);
            fwrite(person.firstName, 1, strlen(person.firstName), ez);
        }

        printf("\n\nWould you like to enter another person into the archive?: (y) or (n): ");
        scanf(" %c", &response);

    } while (response == 'y');

    printf("\n\nThe operation has finished.\n\n");

    fclose(arm);
    fclose(ita);
    fclose(esp);

    return 0;
}

Issue: Will save to folder if last name contains ian (or ini / ez) in ANY part of the last name. How do I make the condition only if it ENDS with these strings?

Issue: Will crash if last name contains ini or ez -- basically, only first If statement works.

Issue: Needs to be saved as Lastname, Firstname -- For now, it saves as LastnameFirstname.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • Try `strrstr` which searches from the end of the string, and compare the index returned with what you expect the index of the substring to be if it is the last n chars. – Justin J. Jun 21 '17 at 21:56
  • `fflush(stdin);` -> undefined behaviour, the code can do anything it wants. Read the documentation of the functions you use. `arm, ita, esp == NULL` -> does not make sense. `&person.lastName` - wrong type. Etc. Fix the errors. Read a good C book, fixe the obvious issues. Enable and don't ignore compiler warnings! – too honest for this site Jun 21 '17 at 22:07
  • O.K -- I looked into `strrstr` and understood how to possibly apply it into my code. However, it seems this is not included in the `` library (I use Visual Studio if that helps) so I cannot implement it as it gives me an error of an "unresolved external". Is there an alternative way to include this function? Thank you for the nudge in the right direction though @Justin – J. Rousselot Jun 21 '17 at 22:10
  • 2
    @JustinJ. There is no standard function `strrstr`. – too honest for this site Jun 21 '17 at 22:12
  • @J.Rousselot What book/site *are* you learning this from? I'm worried that it might be giving bad advice. – Ray Jun 21 '17 at 22:17
  • @Ray -- Very scattered resources; spanning from simple google searches to random YouTube videos. I figure getting the information from a singular source would be ideal. With that said, do you recommend anything for C? – J. Rousselot Jun 21 '17 at 22:23
  • 1
    @J.Rousselot [Kernighan & Ritchie's "The C Programming Language", second edition (K&R2)](https://en.wikipedia.org/wiki/The_C_Programming_Language). It's an older book, covering the original C standard, so a few things have been changed since it was written, but most of those are additions. The coding style is a bit out of fashion in a few places, as well. But it's well written, and Ritchie is the guy who created C, so he actually knows what he's talking about. It's a better reference manual than tutorial, but it's not *bad* as a tutorial. – Ray Jun 21 '17 at 22:30

3 Answers3

1

There are a number of unrelated issues here.

arm = fopen("C:\\Programming\\ARMENIA.TXT", "wt");

That should be "w+", not "wt". Same for the others. Or just "w", unless you want to both read and write.

if (arm, ita, esp == NULL)

The comma operator evaluates its left operand and discards it, then evaluates its right operand and keeps that value. So this is equivalent to if (esp == NULL)

fflush(stdin);

This is meaningless. When printing, the system is allowed to buffer output and then print it all at once, which can speed things up. fflush forces it to actually print anything that's currently in the buffer. It's useful if, for example, you want to prompt the user for input and need to make sure the prompt shows up.

As such, it only works on an output stream. (This error is actually taught in some terrible references, so I suspect this one isn't your fault. You should stop using whatever reference told you to do this.)

Addendum: As Olaf points out in the comments, this invokes undefined behavior, which is a topic unto itself. The short and imprecise version is that any undefined behavior creates bugs that are really hard to track down.

if (ret = strstr(person.lastName, ian)) 

This tells you that ian is in person.lastName somewhere, but not that it's at the end. For example, suppose the last name is "ian in ez". It'll match all three of your conditions. As an aside, you don't need to store ian in a variable; you can just do strstr(person.lastName, "ian").

Ray
  • 1,706
  • 22
  • 30
  • "This is meaningless." - No, it is very relevant, as it invokes undefined behaviour! – too honest for this site Jun 21 '17 at 22:09
  • @Olaf I was glossing over that since the OP is a complete beginner, but you're right. Best to be as clear as possible about how bad UB is. I've added some text to that effect. – Ray Jun 21 '17 at 22:15
  • What is "UD"? You mean UB? – too honest for this site Jun 21 '17 at 22:16
  • See https://stackoverflow.com/questions/2979209/using-fflushstdin. While `fflush(stdin)` is undefined by the C and POSIX standards, it's apparently defined by Windows. So it should be OK in any code that has hard-coded Windows pathnames. – Barmar Jun 21 '17 at 22:18
  • @Barmar But is the OP using it because they're explicitly invoking implementation-dependent extensions, or because they don't realize it's not part of the standard? Best case, it's a bad habit that will need to be unlearned once they start writing portable code. Worst case, they don't realize that it's undefined in general and end up unknowingly writing buggy code. Best to keep the good habit of writing portable code whenever possible from the beginning. – Ray Jun 21 '17 at 22:24
  • @Ray Good point. It seems like one of those things students learn from coding templates, like `system("cls");` – Barmar Jun 21 '17 at 23:05
  • 1
    `strstr` finds the first match, checking if it is at the end using the offset will produce false negatives as in `panini`. A function such as Stephan Lechner's `strEndsWith` is a better solution. – chqrlie Jun 22 '17 at 00:26
  • @chqrlie Good catch on strstr. I've removed the sentence that suggests otherwise. No need to seek to the end though; the files are opened in "w+" mode, so they'll be truncated to 0 length before anything is written. And fwrite is an acceptable way to write strings; I'd use fprintf myself, but that's just a matter of personal preference unless you're using something more involved than "%s" as your format string. (Although they probably *do* want to add a newline or something between names.) – Ray Jun 22 '17 at 00:36
  • @Ray: You are correct about `"w+"`, I was thinking of `"r+"` for updating existing files. `"wb"` by the way is completely useless as the program never reads back from the files. `"wt"` is actually meaningful on legacy platforms where it forces text mode to produce the silly `\r\n` newline sequences. The unexperienced OP writes: *Issue: Needs to be saved as Lastname, Firstname -- For now, it saves as LastnameFirstname.* `fprintf()` is an obvious solution for his purpose. `fwrite` is not. – chqrlie Jun 22 '17 at 00:48
  • Thank you so much! Got the program running flawlessly with everyone's input. – J. Rousselot Jun 22 '17 at 01:12
1

In addition to the issues Ray pointed out, see the following simple code for a strEndsWith-function. Hope it helps.

int strEndsWith(const char* str, const char* ending) {
    size_t strlenStr = strlen(str);
    size_t strlenEnding = strlen(ending);
    if (strlenStr < strlenEnding)
        return 0;

    const char* strAtEnding = str + strlenStr - strlenEnding;

    return (strcmp(strAtEnding, ending) == 0);
}
Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
1

Your program has multiple issues.

Some of the have been addressed by Ray and Stephen Lechner, but here are some more:

  • The reason for the crashes is you pass string to fwrite instead of FILE* stream pointers: fwrite(person.lastName, 1, strlen(person.lastName), ini); should be written:

    fwrite(person.lastName, 1, strlen(person.lastName), ita);
    

    This is an indication that you compile without proper warnings enabled. Let the compiler help avoid such silly mistakes with -Wall for gcc or /W3 for msvc.

  • Note also that you should use fprintf to properly format the output to your text files. For example:

    if (strEndsWith(person.lastName, "ian")) {
        fprintf(arm, "%s, %s\n", person.lastName, person.firstName);
    }
    

Here is an improved version:

#include <stdio.h>
#include <string.h>

int strEndsWith(const char *str, const char *ending) {
    size_t len1 = strlen(str);
    size_t len2 = strlen(ending);
    return len1 >= len2 && !strcmp(str + len1 - len2, ending);
}

int main(void) {
    FILE *arm = fopen("C:\\Programming\\ARMENIA.TXT", "wt");
    FILE *ita = fopen("C:\\Programming\\ITALIA.TXT", "wt");
    FILE *esp = fopen("C:\\Programming\\ESPANIA.TXT", "wt");

    if (arm == NULL || ita == NULL || esp == NULL) {
        printf("Error: The archives could not be created.\n");
        return 1;
    } else {
        printf("Operation Successful: Archives created.\n\n");
    }

    for (;;) {
        char firstName[20];
        char lastName[30];
        char response;

        printf("\n\nPlease input the first name: ");
        if (scanf("%19s", firstName) != 1)
            break;
        printf("Please input the last name: ");
        if (scanf("%29s", lastName) != 1)
            break;

        if (strEndsWith(lastName, "ian")) {  
            fprintf(arm, "%s, %s\n", lastName, firstName);
        }
        if (strEndsWith(lastName, "ini")) {  
            fprintf(ita, "%s, %s\n", lastName, firstName);
        }
        if (strEndsWith(lastName, "ez")) {  
            fprintf(esp, "%s, %s\n", lastName, firstName);
        }

        printf("\n\nWould you like to enter another person into the archive?: (y) or (n): ");
        if (scanf(" %c", &response) != 1 || response != 'y')
            break;
    }

    printf("\n\nThe operation has finished.\n\n");

    fclose(arm);
    fclose(ita);
    fclose(esp);

    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189