The usage of variable parameters in C and C++ is explained in detail

  • 2020-04-02 01:41:16
  • OfStack

Variable parameters means that the number of parameters can change, can be more or less, also means that the type of parameters can also change, can be int, double can also be char*, class, structure, and so on. Mutable arguments are the key to implementing printf(), sprintf(), and so on. Mutable arguments can also be used to sum any amount of data, making it easier to average (otherwise, use arrays or overloads for each). There is a special keyword for parame in C#, but in C and C++ there is no similar syntax, but fortunately there are handlers for this, and this article will focus on how to use them.

The first step is variable parameter representation
With three points... See the declaration of the printf() and scanf() functions:
Int printf(const char *,...) ;
Int scanf(const char *,...) ;

These three points are used in Macros called Variadic Macros, with the default name of successive va_args__. Such as:
# define WriteLine (...). {printf (__VA_ARGS__); Putchar (" \ n "); }
WriteLine (" MoreWindows ");

Consider that the return value of printf() is the number of bytes representing the output. Change the above macro to:
# define WriteLine (...). The printf (__VA_ARGS__) + (putchar (' \ n ')! = EOF? 1:0);
This returns the value of the WriteLine macro, which returns the number of bytes output, including the last '\n'. Both I and j output 12 as shown in the following example.


       int i = WriteLine("MoreWindows");
       WriteLine("%d", i);
       int j = printf("%sn", "MoreWindows");
       WriteLine("%d", j);

Step 2: how do I handle the va_list type
The va_list and the three macros associated with it are all handled internally by the function, which is the key to implementing the argument.
in < Stdarg. H > The definition of va_list can be found in:
Typedef char *   Va_list;
Three more macros that are closely related to it: va_start(), va_end(), and va_arg().

In the same < Stdarg. H > The definition of these three macros can be found in:
# define va_start (ap, v)   (ap = (va_list)&v + _INTSIZEOF(v))
# define va_end (ap)           (ap = (va_list)0)
# define va_arg (ap, t)       (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
The _INTSIZEOF macro used is defined as follows:
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) -1) & ~(sizeof(int) -1))

To analyze the four macros:
Va_end (ap) is the simplest one, which is to set the pointer to NULL.
Va_start (ap,v), ap = (va_list)&v + _INTSIZEOF(v), first take the address of v, plus _INTSIZEOF(v). _INTSIZEOF(v) is a little bit more complicated. ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)) are all bitwise operations, which may seem a bit cumbersome, but it's not. So if sizeof(int) is 4,1,2,3,4, 5,6,7,8,8. The arithmetic expression for integer x to n in C language is ((x+n-1)/n)*n. When n is a power of 2, we can replace the last two operations with a bit operation -- the lowest n-1 binary bit is zero.

Va_arg (ap,t) is simply taking data of type t from ap and moving the pointer back accordingly. For example, va_arg(ap, int) takes an int and moves the pointer four bytes.

So you can use va_start() to get the starting address of the argument, then va_arg() for each value, and finally va_end() for the end of the argument.

The third step is the vfprintf() function and the vsprintf() function
Vfprintf () is an important function, and its name alone shows that it is closely related to the commonly used printf() function. It comes in multiple overloaded versions, and here's the most common one:
The function prototype


int vfprintf(
   FILE *stream,
   const char *format,
   va_list argptr 
);

The first parameter is a FILE pointer. The FILE structure is essential for reading and writing files in C. To pass stdout to the screen output.
The second parameter specifies the format of the output.
The third argument is of type va_list, which is rare, but is really just a char star for the starting address of the argument.
The return value: The number of bytes output is returned on success (not including the last '\0'), and -1 on failure.
Similar to the above function, vsprintf() only lists function prototypes:

int vsprintf(
   char *buffer,
   const char *format,
   va_list argptr 
); 

Int _vscprintf(const char *format, va_list argptr); Can be used to calculate how many bytes of space the buffer string in the vsprintf() function requires.

Code example
Here are my printf() and WriteLine() functions
 


 int Printf(char *pszFormat, ...)  
{
       va_list   pArgList;

       va_start(pArgList, pszFormat);
       int nByteWrite = vfprintf(stdout, pszFormat, pArgList);
       va_end(pArgList);

       return nByteWrite;
}

int WriteLine(char *pszFormat, ...)
{
       va_list   pArgList;

       va_start(pArgList, pszFormat);
       int nByteWrite = vfprintf(stdout, pszFormat, pArgList);
       if (nByteWrite != -1)
              putchar('n'); //Note 2
       va_end(pArgList);

       return (nByteWrite == -1 ? -1 : nByteWrite + 1);
}

The call is the same as the printf() function.
It is a pity that the number of variables passed in C and C++ cannot be determined (printf() scans the number of '%' to confirm the number of parameters), so you either need to specify the number or set the sentinel value at the end of the parameter:
Set sentry value:

const int GUARDNUMBER = 0; //The sentry logo
// The number of parameters of variable parameter cannot be determined printf() Medium is by scanning '%' The number, by setting here The sentry logo To determine the termination of the parameter 
int MySum(int i, ...)
{
       int sum = i;
       va_list argptr;

       va_start(argptr, i);
       while ((i = va_arg(argptr, int)) != GUARDNUMBER)
              sum += i;
       va_end(argptr);

       return sum;
}

Can be called like this:     Printf ("%d\n", MySum(1, 3, 5, 7, 9, 0));
But you cannot directly pass in a 0:     Printf (" % d \ n ", MySum (0)); / / the error
Specified number:

int MySum(int nCount, ...)
{
       if (nCount <= 0)
              return 0;

       int sum = 0;
       va_list argptr;

       va_start(argptr, nCount);
       for (int i = 0; i < nCount; i++)
              sum += va_arg(argptr, int);
       va_end(argptr);

       return sum;
}

When invoked, the first parameter represents the number of subsequent parameters, such as:
            Printf ("%d\n", MySum(5, 1, 3, 5, 7, 9));
            Printf (" % d \ n ", MySum (0));

The header file used by the code:
# include < Stdarg. H >
# include < stdio.h >

The use of variable parameters is far from the above, but when using variable parameters in C,C++ to be careful, in the use of printf() and other functions passed in the number of parameters must not be less than the number of the '%' symbol in the previous format string, otherwise access will be out of bounds, bad luck will lead to the crash of the program.

Mutable argument primitives involve pushing arguments into a function when it is called, which will be discussed next time.

Note 1. There is no vfprintf() their own parsing parameters to implement printf(), but very few will be able to function and printf() close (in fact, can be fully familiar with printf() has not many people, do not believe that you can read "C traps and defects" to understand printf() a lot of less commonly used parameters, and then go to Microsoft Visual Studio\VC98\CRT\SRC to see the OUTPUT of printf()).

Note 2. Output a single character putchar(ch) is much more efficient than printf(" %c ", ch). If the string is not long, multiple calls to putchar() will also be better than calls to printf(" %s\n ", szStr); High efficiency. This is obvious when there are a lot of function calls.


Related articles: