Search code examples
c

variable number of arguments in C


I got a problem with variable number of arguments. I'm pretty bad in C. In my task i need to specify the types in undeclared parameters. It seems i don't have a reason to do it, because i'm using just a string, but i need to use it with <stdarg.h>. The function suppose to print words which have the specific letter repeating 3+ times. So, when I'm trying this code:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

void repeatingLetter(char* sentence, char letter) 
{
    char* word = strtok(sentence, " ");

    while (word != NULL) {
        int count = 0;
        int len = strlen(word);

        for (int i = 0; i < len; i++) {
            if (word[i] == letter) {
                count++;
            }
        }

        if (count >= 3) {
            printf("%s\n", word);
        }

        word = strtok(NULL, " ");
    }
}

void findWords(int numSentences, ...) 
{
    va_list args;
    va_start(args, numSentences);

    char letter;
    printf("Enter a letter to check: ");
    scanf("%c", &letter);

    for (int i = 0; i < numSentences; i++)
    {
        char* sentence = va_arg(args, char*);

        printf("Words in '%s' with '%c' repeated 3 or more times:\n", sentence, letter);
        repeatingLetter(sentence, letter);
        printf("\n");
    }

    va_end(args);
}

int main() 
{
    int numSentences;
    printf("Enter a number of sentences: ");
    scanf("%d", &numSentences);
    getchar(); 

    char** sentences = (char**)malloc(numSentences * sizeof(char*));

    for (int i = 0; i < numSentences; i++)
    {
        sentences[i] = (char*)malloc(100 * sizeof(char));
        printf("Enter a sentences %d: ", i + 1);
        fgets(sentences[i], 100, stdin);
        sentences[i][strcspn(sentences[i], "\n")] = '\0';
    }

    findWords(numSentences, sentences);

    for (int i = 0; i < numSentences; i++) 
    {
        free(sentences[i]);
    }
    free(sentences);

    return 0;
}

I'm getting something like that:

    Enter a number of sentences: 3
    Enter a sentences 1: Hello world
    Enter a sentences 2: oooooops
    Enter a sentences 3: gdsgdgdg
    Enter a letter to check: o
    Words in '╨Ъ.┌c' with 'o' repeated 3 or more times:
    
    Words in '╨Ъ.┌c' with 'o' repeated 3 or more times:
    
    Words in 'o
    
    gdgdg
    
    d
    
    ' with 'o' repeated 3 or more times:

I don't understand what's a problem. Can you please help me?

I have tried to do this with integer and float. There weren't any problems, but I can't understand what's wrong with strings


Solution

  • In my task i need to specify the types in undeclared parameters. It seems i don't have a reason to do it, because i'm using just a string, but i need to use it with <stdarg.h>. The function suppose to print words which have the specific letter repeating 3+ times

    These parameters are not "undeclared" . ... is the declaration, a placeholder for a variable number of arguments. This is what stdargs is all about.

    And there is a function that will get these parameters, all strings as you said, and print all that have some letter occurring at least n times.

    The probable target of your program is to call the function from a variadic function with a variable number of arguments. If it is not you objective please post the task requirements.

    it is easier to solve one problem each time

    Complete code is at the end for this example

    1 of 2: the function

    void repeatingLetter(char* sentence, char letter)
    {
        char* word = strtok(sentence, " ");
    
        while (word != NULL)
        {
            int count = 0;
            int len   = strlen(word);
    
            for (int i = 0; i < len; i++)
            {
                if (word[i] == letter) { count++; }
            }
    
            if (count >= 3) { printf("%s\n", word); }
    
            word = strtok(NULL, " ");
        }
    }
    

    About the original code

    • do not printf() things inside the target function.
    • do not return void
    • the function scans a word, not a sentence
    • do not write interactive code. It only slows down everything

    In C 0 is false. Only 0. Return false for a string not matching the criteria. Just that. If you have a sentence, break that into words and then call the function with the words...

    Example

    int repeatingLetter(
      const char* word, const char letter, size_t times)
    {
      if (word == NULL) return 0;
      if ((letter == 0) && (times == 0)) return 0;
      char*  pl = (char*)word;  // pointer to 1st letter
      size_t count = 0;
      while (*pl != 0)
      {
          if (*pl == letter)
              if (++count == times) return 1;
          ++pl;
      }
      return 0;
    }
    

    This one does just that, but no more than that. Returns 1 if letter appears at least times times in the word.

    To test it you can just write another function in advance, like this

    int t_repeat(const char* s, const char letter, size_t times)
    {
        printf("\n\
        sentence: \"%s\"\n\
        letter: '%c'\n\
        total number of times: %llu\n",
            s, letter, times);
        int res = repeatingLetter(s, letter, times);
        printf("\n\tfunction returned %d\n", res);
        return 0;
    }
    

    it is trivial, but can save some time if you do this before. See this code for main():

    int main(void)
    {
        t_repeat("aaa", 'a', 3);
        t_repeat("aa", 'a', 3);
        t_repeat("aaa", 'a', 4);
        t_repeat("aaa", 0, 4);
        t_repeat("aaa", 'a', 0);
        t_repeat(NULL, 'a', 0);
        return 0;
    }
    

    So it is easy to type some conditions and test it, before even considering the rest of the program.

    ouptut for this test

    
        sentence: "aaa"
        letter: 'a'
        total number of times: 3
    
            function returned 1
    
        sentence: "aa"
        letter: 'a'
        total number of times: 3
    
            function returned 0
    
        sentence: "aaa"
        letter: 'a'
        total number of times: 4
    
            function returned 0
    
        sentence: "aaa"
        letter: ''
        total number of times: 4
    
            function returned 0
    
        sentence: "aaa"
        letter: 'a'
        total number of times: 0
    
            function returned 0
    
        sentence: "(null)"
        letter: 'a'
        total number of times: 0
    
            function returned 0
    

    So it seems to be ok to go on

    2 of 2: calling the function with a variable number of strings

    If findWords is

    void findWords(
        const char letter,
        const size_t n_times,
        const size_t N, ...)
    

    then main() for a test could be as simple as

    int main(void)
    {
        findWords('x', 3, 8,
            "xvalue",
            "xvaluexx",
            "xxxAll",
            "Sxtxaxck",
            "Sxtxaxck",
            "Stack Overflow",
            "xOverflowx",
            "xOverflowxx"
        );
        return 0;
    };  // main()
    
    

    output of such test

    "xvaluexx"
    "xxxAll"
    "Sxtxaxck"
    "Sxtxaxck"
    "xOverflowxx"
    

    Here is a simple implementation for findWords:

    // find 'l' at least 't' times in each of the 
    // 'N' arguments in '...'
    void findWords(
        const char l, const size_t t, const size_t N, ...)
    {
        va_list args;
        va_start(args, N);
        for (size_t i = 0; i < N; i++)
        {
            char* string = va_arg(args, char*);
            if (repeatingLetter(string, l, t) != 0)
                printf("\"%s\"\n",string);
        }
        va_end(args);
    }
    

    And is almost the same function as t_repeat() in the first test.

    complete code for the example

    #include <stdarg.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int  repeatingLetter(const char*, const char, size_t);
    void findWords(const char, const size_t, const size_t, ...);
    
    int main(void)
    {
        findWords('x', 3, 8,
            "xvalue",
            "xvaluexx",
            "xxxAll",
            "Sxtxaxck",
            "Sxtxaxck",
            "Stack Overflow",
            "xOverflowx",
            "xOverflowxx"
        );
        return 0;
    };  // main()
    
    int repeatingLetter(
        const char* word, const char letter, size_t times)
    {
        if (word == NULL) return 0;
        if ((letter == 0) && (times == 0)) return 0;
        char*  pl    = (char*)word;  // pointer to 1st letter
        size_t count = 0;
        while (*pl != 0)
        {
            if (*pl == letter)
                if (++count == times) return 1;
            ++pl;
        }
        return 0;
    }
    
    // find 'l' at least 't' times in each of the 
    // 'N' arguments in '...'
    void findWords(
        const char l, const size_t t, const size_t N, ...)
    {
        va_list args;
        va_start(args, N);
        for (size_t i = 0; i < N; i++)
        {
            char* string = va_arg(args, char*);
            if (repeatingLetter(string, l, t) != 0)
                printf("\"%s\"\n",string);
        }
        va_end(args);
    }
    

    on the creation of a va_list

    When you declare a function like

    void findWords(
        const char l, const size_t t, const size_t N, ...)
    

    the compiler gets aware of the variadic nature of the function. Then, when a call is made like in

    int main(void)
    {
        findWords('x', 3, 8,
            "xvalue",
            "xvaluexx",
            "xxxAll",
            "Sxtxaxck",
            "Sxtxaxck",
            "Stack Overflow",
            "xOverflowx",
            "xOverflowxx"
        );
        return 0;
    };  // main()
    

    code is inserted to build the va_list for this call and pass it down to findWords. Here the number of arguments is a fixed number. main is not a variadic function. findWords() is variadic, and the va_list it gets can be even forwarded to inner functions.

    If you need to build such a thing at runtime you can use a custom struct, or a simple NULL-terminated void* array. But you will not use stdargs.h. If you do that you can even mix the argument types or pass down a list of type/value pairs.

    main(int argc,char* argv[] gets a variable number of arguments

    In fact once the program is loaded the number of arguments is fixed: the OS builds the argv array and sets up argc.