2

I want to check to make sure that a given string contained in an array called secretWord has no symbols in it (e.g. $ % & #). If it does have a symbol in it, I make the user re-enter the string. It takes advantage of recursion to keep asking until they enter a string that does not contain a symbol.

The only symbol I do accept is the NULL symbol (the symbol represented by the ASCII value of zero). This is because I fill all the empty space in the array with NULL symbols.

My function is as follows:

void checkForSymbols(char *array, int arraysize){ //Checks for symbols in the array and if there are any it recursively calls this function until it gets input without them.
 for (int i = 0; i < arraysize; i++){
    if (!isdigit(array[i]) && !isalpha(array[i]) && array[i] != (char) 0){
      flushArray(array, arraysize);
      printf("No symbols are allowed in the word. Please try again: ");
      fgets(secretWord, sizeof(secretWord) - 1, stdin);
      checkForSymbols(secretWord, sizeof(secretWord));
    }//end if (!isdigit(array[i]) && !isalpha(array[i]) && array[i] != 0)
    else
      continue;
  }//end for(i = 0; i < sizeof(string[]); i++){
}//end checkForSymbols

The problem: When I enter any input (see example below), the if statement runs (it prints No symbols are allowed in the word. Please try again: and asks for new input). I assume the problem obviously stems from the statement if (!isdigit(array[i]) && !isalpha(array[i]) && array[i] != (char) 0). But I have tried changing the (char) 0 part to '\0' and 0 as well and neither change had any effect.

How do I compare if what is in the index is a symbol, then? Why are strings without symbols setting this if statement off?

And if any of you are wondering what the "flushArray" method I used was, here it is:

    void flushArray(char *array, int arraysize){ //Fills in the entire passed array with NULL characters
  for (int i = 0; i < arraysize; i++){
    array[i] = 0;
  }
}//end flushArray

This function is called on the third line of my main() method, right after a print statement on the first line that asks users to input a word, and an fgets() statement on the second line that gets the input that this checkForSymbols function is used on.


As per request, an example would be if I input "Hello" as the secretWord string. The program then runs the function on it, and the if statement is for some reason triggered, causing it to

  1. Replace all values stored in the secretWord array with the ASCII value of 0. (AKA NULL)
  2. Prints No symbols are allowed in the word. Please try again: to the console.
  3. Waits for new input that it will store in the secretWord array.
  4. Calls the checkForSymbols() method on these new values stored in secretWord.

And no matter what you input as new secretWord, the checkForSymbols() method's if statement fires and it repeats steps 1 - 4 all over again.


Thank you for being patient and understanding with your help!

Omninano
  • 29
  • 3
  • 2
    Please provide a [mcve] including how the input was read, what the exact input tested was and how this function was called. – kaylum Feb 13 '17 at 02:24
  • 1
    Is flushArray called before the user input is placed into the array? Also, why not just stop when you find the first null (i.e. treat it as a null-terminated string)? – samgak Feb 13 '17 at 02:26
  • 1
    Also, you should learn to use a debugger, then you can see what the value is in array[i] that is failing the test. Or you could just print it out (or it's ASCII value) – samgak Feb 13 '17 at 02:27
  • 1
    `fgets(secretWord, ...)` there is no `secretWord`. And though you say this is recursive, there isn't that either. Your `checkForSymbols` invokes `checkforNums` (which we conveniently can't see). Take your time, think about your goal, the problem, and thus your question, and include **all** required code to demonstrate the problem in a minimal, **complete** example. – WhozCraig Feb 13 '17 at 02:32
  • 1
    The way to approach this is to insure `array` is a *string* (e.g. *nul-terminated*), then determine which symbols you want to exclude (e.g. `char *exclude = "$%";`). Then you can simply ask `if (strpbrk (array, exclude)) { ...handle array contains symbol... }`. You can also reverse the logic and check that `array` is only made up of `a-zA-Z` just as easily. – David C. Rankin Feb 13 '17 at 02:35
  • I use memchr or strchr in such cases ,strpbrk does same thing with string – minigeek Feb 13 '17 at 02:49
  • @kaylum I edited my question so hopefully it meets all the guidelines now. Thank you for suggesting a way I can improve the question instead of just instantly down-voting :) @samgak `flushArray` is not called before the user input is placed into the array. It is a newly instantiated, blank array. I used your good idea and added a printf statement to check which character triggers the `if` statement. It shows up as blank, so the NULL characters are what is making the `if` statement fire. – Omninano Feb 13 '17 at 03:12
  • @WhozCraig Thank you for bringing this to my attention. Hopefully my edited question solves all of those issues of explaining what those unknown variables are removing the typo. :) – Omninano Feb 13 '17 at 03:17
  • @DavidC.Rankin I want to exclude ANYTHING that is not a letter or a number, not just an easy finite list of common symbols that I can type out. Although I do like your idea of checking to see if the value is within the `a - Z` or `0 - 9`... – Omninano Feb 13 '17 at 03:21
  • @Omninano your if condition seems to be always true.since your condition is !isdigit i.e if string does not contain digit it Will return false but becomes true because of `!` – minigeek Feb 13 '17 at 03:27
  • `fgets` reads (and null-terminates) a line from the file (here `stdin`) **including the terminating newline** unless the line is too big (or equivalently the buffer too small) or EOF or error occurs first. Newline is one of the characters your code rejects. Also if in a locale where characters use 8 bits (nearly all) and your compiler makes `char` signed then `isalpha(x)` and `isdigit(x)` with negative `x` are officially Undefined Behavior and in practice likely wrong, see http://stackoverflow.com/questions/6693654/isalpha-giving-an-assertion . – dave_thompson_085 Feb 13 '17 at 05:40

3 Answers3

1

You can do something like this to find symbols in your code, put the code at proper location

 #include <stdio.h> 
 #include <string.h> 
 int main () { 
      char invalids[] = "@.<#>"; 
      char * temp; 
      temp=strchr(invalids,'s');//is s an invalid character?
      if (temp!=NULL) {
             printf ("Invalid character"); 
      } else {
             printf("Valid character");
      }
  return 0; 
 }

This will check if s is valid entry or not similarly for you can create an array and do something like this if array is not null terminated.

 #include <string.h> 
 char false[] = { '@', '#', '&', '$', '<' }; // note last element isn't '\0' 
 if (memchr(false, 'a', sizeof(false)){ 
      // do stuff 
 }

memchr is used if your array is not null terminated.

As suggested by @David C. Rankin you can also use strpbrk like

 #include <stdio.h> 
 #include <string.h>
 int main () { 
      const char str1[] = ",*#@_$&+.!"; 
      const char str2[] = "@#"; //input string
      char *ret;
      ret = strpbrk(str1, str2);
      if(ret) {
            printf("First matching character: %c\n", *ret); 
      } else {
           printf("Continue"); 
     } 
 return(0); 
 }
minigeek
  • 2,766
  • 1
  • 25
  • 35
  • Yes, but this means I would require a finite list of common symbols that I would have to type out by hand. I just want to detect **ANYTHING** that is not a letter or a number. – Omninano Feb 13 '17 at 03:25
  • @Omninano yeah then reverse the logic look for atoz and 0to9 as your false condition. – minigeek Feb 13 '17 at 03:54
  • @Omninano try replacing `if (!isdigit(array[i]) && !isalpha(array[i]) && array[i] != (char) 0)` with `if (isdigit(array[i]) || isalpha(array[i]) || array[i] == (char) 0)` & put `checkForSymbols(secretWord, sizeof(secretWord));` in else instead of if – minigeek Feb 13 '17 at 04:35
1

The only symbol I do accept is the NULL symbol (the symbol represented by the ASCII value of zero). This is because I fill all the empty space in the array with NULL symbols.

NULL is a pointer; if you want a character value 0, you should use 0 or '\0'. I assume you're using memset or strncpy to ensure the trailing bytes are zero? Nope... What a shame, your MCVE could be so much shorter (and complete). :(


void checkForSymbols(char *array, int arraysize){
    /* ... */
        if (!isdigit(array[i]) && !isalpha(array[i]) /* ... */

As per section 7.4p1 of the C standard, ...

In all cases the argument is an int, the value of which shall be representable as an unsigned char or shall equal the value of the macro EOF. If the argument has any other value, the behavior is undefined.

Not all char values are representable as an unsigned char or equal to EOF, and so it's possible (and highly likely given the nature of this question) that the code above invokes undefined behaviour.

As you haven't completed your question (by providing an MCVE, and describing what errors are occuring) I'm assuming that the question you're trying to ask might be a duplicate of this question, this question, this question, this question and probably a whole lot of others... If so, did you try Googling the error message? That's probably the first thing you should've done. Should that fail in the future, ask a question about the error message!


As per request, an example would be if I input "Hello" as the secretWord string.

I assume secretWord is declared as char secretWord[] = "Hello"; in your example, and not char *secretWord = "Hello";. The two types are distinct, and your book should clarify that. If not, which book are you reading? I can probably recommend a better book, if you'd like.

Any attempt to modify a string literal (i.e. char *array = "Hello"; flushArray(array, ...)) is undefined behaviour, as explained by answers to this question (among many others, I'm sure).


It seems a solution to this problem might be available by using something like this...

Community
  • 1
  • 1
autistic
  • 1
  • 3
  • 35
  • 80
  • I apologize for my lack of knowledge with the `memset` and `strncpy` functions, as I am a learner. I am still figuring out how this language works and am open to teaching. I did not realize my MCVE was not too long and incomplete... I have all the code there to reproduce the problem sans the `#include` statements and the the three lines of the `main` function that I described in the third to last paragraph. – Omninano Feb 13 '17 at 04:06
  • Yes, I did Google for solutions using many different wordings, and even directly used the Stack Overflow search with multiple different wordings. Nothing I could find specifically addressed a problem like mine, hence why I eventually made this post. The four possible duplicate questions you linked did not cover the same problem I have having, but thank you for attempting to help me to find resources out there to help me. No error messages occur in my program. It just keeps prompting "No symbols are allowed..." and requests new input over and over again, no matter what input you give it. – Omninano Feb 13 '17 at 04:09
1

In response to your comment, you are probably making it a bit tougher on yourself than it needs to be. You have two issues to deal with (one you are not seeing). The first being to check the input to validate only a-zA-Z0-9 are entered. (you know that). The second being you need to identify and remove the trailing '\n' read and included in your input by fgets. (that one may be tripping you up)

You don't show how the initial array is filled, but given your use of fgets on secretWord[1], I suspect you are also using fgets for array. Which is exactly what you should be using. However, you need to remove the '\n' included at the end of the buffer filled by fgets before you call checkforsymbols. Otherwise you have character 0xa (the '\n') at the end, which, of course, is not a-zA-Z0-9 and will cause your check to fail.

To remove the trailing '\n', all you need to do is check the last character in your buffer. If it is a '\n', then simply overwrite it with the nul-terminating character (either 0 or the equivalent character representation '\0' -- your choice). You simply need the length of the string (which you get with strlen from string.h) and then check if (string[len - 1] == '\n'). For example:

    size_t len = strlen (str);          /* get length of str */
    if (str[len - 1] == '\n')           /* check for trailing '\n' */
        str[--len] = 0;                 /* overwrite with nul-byte */

A third issue, important, but not directly related to the comparison, is to always choose a type for your function that will return an indication of Success/Failure as needed. In your case the choice of void gives you nothing to check to determine whether there were any symbols found or not. You can choose any type you like int, char, char *, etc.. All will allow the return of a value to gauge success or failure. For testing strings, the normal choice is char *, returning a valid pointer on success or NULL on failure.

A fourth issue when taking input is you always need to handle the case where the user chooses to cancel input by generating a manual EOF with either ctrl+d on Linux or ctrl+z on windoze. The return of NULL by fgets gives you that ability. But with it (and every other input function), you have to check the return and make use of the return information in order to validate the user input. Simply check whether fgets returns NULL on your request for input, e.g.

    if (!fgets (str, MAXS, stdin)) {    /* read/validate input */
        fprintf (stderr, "EOF received -> user canceled input.\n");
        return 1;   /* change as needed */
    }

For your specific case where you only want a-zA-Z0-9, all you need to do is iterate down the string the user entered, checking each character to make sure it is a-zA-Z0-9 and return failure if anything else is encountered. This is made easy given that every string in C is nul-terminated. So you simply assign a pointer to the start of your string (e.g. char *p = str;) and then use either a for or while loop to check each character, e.g.

for (; *p != 0; p++) { do stuff }

that can be written in shorthand:

for (; *p; p++) { do stuff }

or use while:

while (*p) { do stuff; p++; }

Putting all of those pieces together, you could write your function to take a string as its only parameter and return NULL if a symbol is encountered, or return a pointer to your original string on success, e.g.

char *checkforsymbols (char *s)
{
    if (!s || !*s) return NULL;     /* validate string and not empty  */
    char *p = s;                    /* pointer to iterate over string */

    for (; *p; p++)     /* for each char in s */
        if ((*p < 'a' || *p > 'z') &&   /* char is not a-z */
            (*p < 'A' || *p > 'Z') &&   /* char is not A-Z */
            (*p < '0' || *p > '9')) {   /* char is not 0-9 */
            fprintf (stderr, "error: '%c' not allowed in input.\n", *p);
            return NULL;    /* indicate failure */
        }

    return s;   /* indicate success */
}

A short complete test routine could be:

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

#define MAXS 256

char *checkforsymbols (char *s);

int main (void) {

    char str[MAXS] = "";
    size_t len = 0;

    for (;;) {                              /* loop until str w/o symbols */
        printf (" enter string: ");         /* prompt for user input */
        if (!fgets (str, MAXS, stdin)) {    /* read/validate input */
            fprintf (stderr, "EOF received -> user canceled input.\n");
            return 1;
        }
        len = strlen (str);                 /* get length of str */
        if (str[len - 1] == '\n')           /* check for trailing '\n' */
            str[--len] = 0;                 /* overwrite with nul-byte */
        if (checkforsymbols (str))          /* check for symbols */
            break;
    }

    printf (" valid str: '%s'\n", str);

    return 0;
}

char *checkforsymbols (char *s)
{
    if (!s || !*s) return NULL;     /* validate string and not empty  */
    char *p = s;                    /* pointer to iterate over string */

    for (; *p; p++)     /* for each char in s */
        if ((*p < 'a' || *p > 'z') &&   /* char is not a-z */
            (*p < 'A' || *p > 'Z') &&   /* char is not A-Z */
            (*p < '0' || *p > '9')) {   /* char is not 0-9 */
            fprintf (stderr, "error: '%c' not allowed in input.\n", *p);
            return NULL;    /* indicate failure */
        }

    return s;   /* indicate success */
}

Example Use/Output

$ ./bin/str_chksym
 enter string: mydoghas$20worthoffleas
error: '$' not allowed in input.
 enter string: Baddog!
error: '!' not allowed in input.
 enter string: Okheisagood10yearolddog
 valid str: 'Okheisagood10yearolddog'

or if the user cancels user input:

$ ./bin/str_chksym
 enter string: EOF received -> user canceled input.

footnote 1.

C generally prefers the use of all lower-case variable names, while reserving all upper-case for macros and defines. Leave MixedCase or camelCase variable names for C++ and java. However, since this is a matter of style, this is completely up to you.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85