user9593492
user9593492

Reputation: 17

working with pure pointer notation and loops

I have this finished assignment that I had to convert from array notation to pure pointer notation, it is working, Im done with it but I want to ask some questions that I am not clear of. I really want to fully understand pointers as they are my downfall.

-First, do I not need assign my pointer to the address of my variable? For example...

char *ps1 = &s1;
char *ps2 = &s2;

I thought I did, but the code works fine without it. Why? How does my pointer know to go the first element of that variable? Is it because my pointers are only local, only in my functions?

-Also, right now theres always a new random string generated, if I want it to always be the same string, how would I do that? I tried doing..

getRandomStr(s1);
originals1[41] = getRandomStr;

outside of the do while loop and then in the do while replace

puts(s1);

with

 puts(originals1);

and that worked, was always same string but I had a bunch of weird characters in the front.

Here is my code.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void getRandomStr(char *ps1);
void strfilter(char *ps1, char *ps2, char c);
void check(char *ps2);
char cont(void);

int main()
{
    char s1[41];
    char s2[21], c;
    char entry;

    /* I dont need these declarations below? */
    //char *ps1 = &s1;
    //char *ps2 = &s2;



    do {
        /* get random string (s1) through function getRandomStr */
        getRandomStr(s1);
        printf("Random string generated (s1): ");
        /* print rand string by printf("Random string generated: %s\n", s1);*/
        /* OR.. */
        puts(s1);
        printf("\nPlease enter up to 20 letters to be replaced (s2): ");
        /* operator enters string (s2) up to 20 chars */
        gets(s2);
        printf("\nPlease enter a replacement character (Ex. *, $, etc.) (c): ");

        /* operator enters a replacement char (c) */
        c = getchar();

        /* Error check to verify entered string (s2) is a letter A-Z */
        check(s2);

        printf("\nFiltered string (s1): ");
        strfilter(s1, s2, c);
        /* print filtered string s1 */
        puts(s1);


        entry = cont();

    } while (entry == 'y' || entry == 'Y');

}


void getRandomStr(char *ps1) 
{

    int i;

    srand(time(NULL));
    for (i = 0; i < 41; i++) {

        char letter = rand() % 26 + 'A';
        *(ps1 + i)  = letter;

    }

    *(ps1 + 41) = '\0';
}

void strfilter(char *ps1, char *ps2, char c)
{
    int i = 0;

    while (*(ps2 + i) != '\0') {

        for (int j = 0; *(ps1 + j) != '\0'; j++) {

            if (*(ps1 + j) == *(ps2 + i))
            {

                *(ps1 + j) = c;

            }
        }

        i++;
    }
    /* if want to print filtered s1 from function */
    /* puts(s1); */
}


char cont()
{

    char entry;
    printf("\nDo you want to run the program again (Y/N)? ");
    scanf_s(" %c%*c", &entry);
    return entry;
}

void check(char *ps2)
{
    int i = 0;

    /* while s2 is not end of string */
    while (*(ps2 + i) != '\0')
    {
        /* if s2 not a letter A-Z, give error message */
        if (!(*(ps2 + i) >= 'A' && *(ps2 + i) <= 'Z'))
        {

            printf("An invalid character was entered.\n");
            break;
        }

        i++;
    }
}

Upvotes: 1

Views: 788

Answers (1)

David C. Rankin
David C. Rankin

Reputation: 84652

The good news is your thinking is very close to correct. The bad news is the very close part is a qualified amount of correctness. First, let's address your confusion over pointers.

A pointer is simply a variable that holds the address of something else as its value. In other words, a pointer points to the address where something else can be found. For example, while int a = 5; stores the immediate value 5 as its value, int *b; creates a pointer to int, and b = &a; stores the address of a (the memory address where 5 is currectly stored) as its value. If you need the value stored at the memory address b points to, you dereference b using the unary '*' operator, e.g. int c = *b; will initialize c = 5). Since b points to the address where 5 is stored, if you change that value (e.g. *b = 6;) 6 is now stored at the address where 5 was before. Since b points to the address of a and you have changed the value at that address, a now equals 6.

The type for the pointer sets the number of bytes advanced when using pointer arithmetic. If you have a pointer (here to a string literal), e.g.

char *p = "abc";

Then each time you increment the pointer p, the address held by p is incremented by 1-byte. For example, after p++;, p points to b, so putchar (*p); will output 'b'; Since each string is C is nul-terminated with the nul-character '\0' (with decimal value 0), you can simply iterate over the string using pointer p with

while (*p)
    putchar (*p++);
putchar ('\n');

will output abc and then the newline. With an integer array each element requires 4-bytes. The beauty of the type setting the size is for int array[] = {1, 2, 3}; int *p = array, you can likewise iterate over array advancing the pointer p++ (but note, and integer array is not nul-terminated, so you have to manually limit the number of iterations, e.g.

while (p < array + 3)
    printf (" %d", *p++);
putchar ('\n');

Which would output " 1 2 3" and then the newline.

With that in mind, there are a number of issues you can address in your code. First, don't use magic numbers in your code (e.g. 21, 41), if you need a constant, define it. You can also use a global enum to define constants. Here, a single constant of 48 is adequate for both, e.g.:

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

#define MAXC 48     /* if you need a constant, define one (or more) */
...

Next, move srand() to main() so it is only called once:

int main (void)
{
    char s1[MAXC] = "", 
        s2[MAXC] = ""; 
    int c, entry = 0;       /* c must be type int to detect EOF */
    size_t len = 0;         /* len to trim trailing '\n' from fgets */

    srand(time(NULL));
    ...

By declaring a constant MAXC (for max characters), your getRandomStr will change accordingly:

void getRandomStr (char *ps1) 
{
    int i;

    /* must leave room for nul-terminating character */
    for (i = 0; i < MAXC - 1; i++) {

        int letter = rand() % 26 + 'A';
        *(ps1 + i)  = letter;
    }

    *(ps1 + MAXC - 1) = '\0';
}

You should also update your prompts in main() to use the maximum number of characters to advise the user of the limits, e.g.

    do {
        /* get random string (s1) through function getRandomStr */
        getRandomStr (s1);
        printf ("Random string generated (s1): ");
        /* print rand string by printf("Random string generated: %s\n", s1);*/
        /* OR.. */
        puts (s1);
        printf ("\nPlease enter up to %d letters to be replaced (s2): ",
                MAXC - 1);
    ...

Do NOT ever, ever, over fear of being shot and riduculed, use gets(). As explained, it is so insecure and so prone to exploit by buffer overrun, it has been completely removed from the C11 library. Instead use fgets,

        /* operator enters string (s2) up to 20 chars */
        if (!fgets (s2, MAXC, stdin)) { /* validate all user-input */
            fprintf (stderr, "(user canceled input)\n");
            break;
        }

All valid line-oriented input functions (e.g. fgets and POSIX getline) read and include the trailing '\n' in the buffer they fill. (with fgets -- provided there is enough space to read the entire line). So you should either remove the trailing '\n' by overwriting it with a nul-terminating character, or otherwise account for its existence in your code. A simple way to handle this is get the length of the string in the buffer, and overwrite the '\n', e.g.

        len = strlen (s2);                  /* get length */
        if (len && s2[len - 1] == '\n')     /* check last char == '\n' */
            s2[--len] = 0;                  /* overwrite with nul-char */

You can also use any of the string.h functions you like to locate the trailing '\n' and overwrite it.

Next, as indicated above, you must validate all user input in order to avoid problems in your code. So when you use getchar() you need to check that the user didn't just simply hit Enter. You also need to handle the '\n' that remains unread in the input buffer (stdin here) to insure it doesn't torpedo your next input. Since you will do this repeatedly, it makes sense to write a simple helper function to assist, e.g.

void empty_stdin (void)
{
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

You must also know when there are additional characters in stdin that you need to empty. Calling empty_stdin on an empty stdin will simply block until there is a character to be read...

With your new function, you can now get your "replacement character" in a more robust manner:

        printf("\nPlease enter a replacement character (Ex. *, $, etc.) (c): ");

        /* operator enters a replacement char (c) */
        while ((c = getchar()) == '\n') {
            if (c == EOF) {
                fprintf (stderr, "(user canceled input)\n");
                goto done;
            }
            fprintf (stderr, "error: invalid (empty) input.\n");
        }
        empty_stdin();
        ...
    } while (entry == 'y' || entry == 'Y');
    done:;
}

Next, check() must return a value to indicate success/failure. Otherwise, you have no way of knowing the result of your check. If you simply need to know whether something succeeded or failed, you can simply return an int for that indication, e.g.

int check (char *ps2)
{
    /* while s2 is not end of string */
    while (*ps2)
    {
        /* if s2 not a letter A-Z, give error message */
        if (*ps2 < 'A' || 'Z' < *ps2)
        {
            fprintf (stderr, "An invalid character was entered.\n");
            return 1;
        }
        ps2++;
    }
    return 0;
}

Then using check() can actually make sense in main(), e.g.

        /* Error check to verify entered string (s2) is a letter A-Z */
        if (check (s2) == 1) {
            fprintf (stderr, "error: s2 contains char not A-Z.\n");
            empty_stdin();
            continue;
        }

The same is true with cont(), since you take input in that function, you must have a way to indicate in main() whether you got valid input, and what that input was. Again, returning an int can accomplish both. Failed input or invalid input, return 0;, otherwise, return c;, e.g.

int cont (int c)
{
    printf ("\nDo you want to run the program again (Y/N)? ");
    if ((c = getchar()) == '\n' || c == EOF) {
        fprintf (stderr, "(empty or user canceled input)\n");
        return 0;
    }
    empty_stdin();

    return c;
}

(and yes the type for c, entry, check & cont must be int, because you can test against EOF with char due to EOF being an int (4-bytes))

You validate in main() with:

        if ((entry = cont(c)) == 0)
            break;

Lastly, you can use simple iteration over the pointer passed to strfilter to simplify the function a bit, e.g.

void strfilter(char *ps1, char *ps2, char c)
{
    while (*ps2) {
        char *p = ps1;
        while (*p) {
            if (*p == *ps2)
                *p = c;
            p++;
        }
        ps2++;
    }
    /* if want to print filtered s1 from function */
    /* puts(ps1); */
}

If you put it altogether, you end up with a program that does not give output weird characters. To have a better chance at not seeing funny characters, always compile with warnings enable, that means adding -Wall -Wextra -pedantic to your compiler options on gcc/clang, or for VS (cl.exe) use at least /W3) -- and do not accept code until it compiles without warning. (you can learn a lot of C, just by listening to what your compiler tells you -- and fixing the problems it tells you about on the line numbers it gives you). All said (and replacing scanf_s with getchar -- I'm not on windows, and gcc doesn't implement the proposed extension containing scanf_s -- there is nothing wrong with that function otherwise), you could do something like, e.g.

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

#define MAXC 48     /* if you need a constant, define one (or more) */

void getRandomStr(char *ps1);
void strfilter(char *ps1, char *ps2, char c);
int check(char *ps2);
int cont(int c);
void empty_stdin(void);

int main (void)
{
    char s1[MAXC] = "", 
        s2[MAXC] = ""; 
    int c, entry = 0;       /* c must be type int to detect EOF */
    size_t len = 0;         /* len to trim trailing '\n' from fgets */

    srand(time(NULL));

    do {
        /* get random string (s1) through function getRandomStr */
        getRandomStr (s1);
        printf ("Random string generated (s1): ");
        /* print rand string by printf("Random string generated: %s\n", s1);*/
        /* OR.. */
        puts (s1);
        printf ("\nPlease enter up to %d letters to be replaced (s2): ",
                MAXC - 1);
        /* operator enters string (s2) up to 20 chars */
        if (!fgets (s2, MAXC, stdin)) { /* validate all user-input */
            fprintf (stderr, "(user canceled input)\n");
            break;
        }
        len = strlen (s2);                  /* get length */
        if (len && s2[len - 1] == '\n')     /* check last char == '\n' */
            s2[--len] = 0;                  /* overwrite with nul-char */

        printf("\nPlease enter a replacement character (Ex. *, $, etc.) (c): ");

        /* operator enters a replacement char (c) */
        while ((c = getchar()) == '\n') {
            if (c == EOF) {
                fprintf (stderr, "(user canceled input)\n");
                goto done;
            }
            fprintf (stderr, "error: invalid (empty) input.\n");
        }
        empty_stdin();

        /* Error check to verify entered string (s2) is a letter A-Z */
        if (check (s2) == 1) {
            fprintf (stderr, "error: s2 contains char not A-Z.\n");
            empty_stdin();
            continue;
        }

        printf ("\nFiltered string (s1): ");
        strfilter (s1, s2, c);
        /* print filtered string s1 */
        puts (s1);

        if ((entry = cont(c)) == 0)
            break;

    } while (entry == 'y' || entry == 'Y');
    done:;
}

void getRandomStr (char *ps1) 
{
    int i;

    /* must leave room for nul-terminating character */
    for (i = 0; i < MAXC - 1; i++) {

        int letter = rand() % 26 + 'A';
        *(ps1 + i)  = letter;
    }

    *(ps1 + MAXC - 1) = '\0';
}

void strfilter(char *ps1, char *ps2, char c)
{
    while (*ps2) {
        char *p = ps1;
        while (*p) {
            if (*p == *ps2)
                *p = c;
            p++;
        }
        ps2++;
    }
    /* if want to print filtered s1 from function */
    /* puts(ps1); */
}

int cont (int c)
{
    printf ("\nDo you want to run the program again (Y/N)? ");
    if ((c = getchar()) == '\n' || c == EOF) {
        fprintf (stderr, "(empty or user canceled input)\n");
        return 0;
    }
    empty_stdin();

    return c;
}

int check (char *ps2)
{
    /* while s2 is not end of string */
    while (*ps2)
    {
        /* if s2 not a letter A-Z, give error message */
        if (*ps2 < 'A' || 'Z' < *ps2)
        {
            fprintf (stderr, "An invalid character was entered.\n");
            return 1;
        }
        ps2++;
    }
    return 0;
}

void empty_stdin (void)
{
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

Example Use/Output

$ ./bin/rndstring
Random string generated (s1): QFYFRUKOJVTZIXQXNQWATHJULIYYEMOWTMBKINYUKTVSQLP

Please enter up to 47 letters to be replaced (s2): QFYRU

Please enter a replacement character (Ex. *, $, etc.) (c): J

Filtered string (s1): JJJJJJKOJVTZIXJXNJWATHJJLIJJEMOWTMBKINJJKTVSJLP

Do you want to run the program again (Y/N)? Y
Random string generated (s1): DBOFXXORIYQHEEVAXKDJUQHQAALTTXKYAYGXVURGVJNZNKC

Please enter up to 47 letters to be replaced (s2): DBOFX

Please enter a replacement character (Ex. *, $, etc.) (c): Z

Filtered string (s1): ZZZZZZZRIYQHEEVAZKZJUQHQAALTTZKYAYGZVURGVJNZNKC

Do you want to run the program again (Y/N)? N

Look things over and let me know if you have further questions.

Upvotes: 2

Related Questions