A printf() - like function

A printf() - like function

A note for how-to make a printf()-like function without using printf() function.


Developing a printf-like function is a very common programming exercise in C as it teaches you a lot about string formatting, memory allocation, and pointer manipulation.


The printf( ) function

  • in C is a standard library function used to output formatted data to standard output. It is primarily used to print messages and other information to the console

  • To simulate the behavior of the printf( ) function, we need to understand how it works. The printf( ) function uses a format string to specify how the output should be formatted. The format string can contain placeholders, which are replaced by specific values when the output is generated.

  • Here is an example of how printf( ) is called with a format string and some arguments:

      printf("The value of x is %d and the value of y is %f", x, y);
    

    In this example, the format string specifies two placeholders: %d and %f. The first placeholder is replaced by the value of x, which is an integer. The second placeholder is replaced by the value of y, which is a floating-point number.

    To simulate the behavior of printf( ), we can use the vprintf( ) function, which is similar to printf( ) but takes a va_list variable instead of variable arguments.

    Here is an example of how we can simulate the

    printf( ) function:

      #include <stdio.h>
      #include <stdarg.h>
    
      void myprintf(const char* format, ...)
      {
          va_list args;
          va_start(args, format);
          vprintf(format, args);
          va_end(args);
      }
    
      int main()
      {
          int x = 10;
          float y = 3.14;
    
          myprintf("The value of x is %d and the value of y is %f", x, y);
          return 0;
      }
    

    In this example, we define a new function called myprintf( ) that takes a format string and a variable number of arguments. The va_list variable args is used to hold the variable arguments, and is initialized using the va_start() function. The vprintf( ) function is then used to output the formatted string with the provided arguments. Finally, the va_end() function is called to clean up the va_list.

    When we call the myprintf() function with a format string and variables, it behaves just like the printf() function, outputting the formatted string to the console.


To simulate the behavior of the printf() function in C without using it, you need to understand how it works. Here are the key elements of its functionality:


Variadic functions

functions that accept a variable number of arguments. In C, the stdarg.h library is used to define and use variadic functions.


"What are variadic functions (va_list) in C?" @CodeVault

youtube.com/watch?v=oDC208zvsdg&ab_chan..

There are three main elements used in variadic functions:

  • The va_list type: This is a type defined in the stdarg.h library. It is used to declare a variable that will hold the variable arguments passed to the function.

  • The va_start macro: This macro initializes the va_list variable with the starting address of the first argument, and it takes two arguments - the va_list variable and the last named parameter of the function.

  • The va_arg macro: to retrieve arguments passed to a function that accepts a variable number of arguments.

Here is an example of a variadic function that uses these elements:

#include <stdio.h>
#include <stdarg.h>

/**
   * variadic_function - prints n numbers of integers
   * @count: the number of integers passed to the function
   * Return: void
 */
void myfunc(int count, ...) 
{

/* declaring a  va_list typed variable.*/ 
     va_list ap; 

/* declaring an integer variable to use in the for-loop.*/
      int i; 

/*
  * This macro initializes the va_list typed variable with 
  * the starting address of the first argument (int count)
  */

     va_start(ap, count);

/*
  * use count (number of integers passed to the function)
  * in the for-loop structure.
  */
     for (i = 0; i < count; i++) 
{

/*
  * declares a variable named "num" which is of type int.
  * The value assigned to this variable is obtained by
  * a macro called "va_arg" which retrieve arguments
  * passed to a function
*/ 
      int num = va_arg(ap, int);
      printf("%d\n", num);
}


/*to clean up the va_list typed ap.*/

    va_end(ap);
}



int main()
{

   myfunc(3, 5, 6, 7); 
   return 0;
}

In this example, the function myfunc() takes an integer argument count followed by a variable number of integer arguments. Inside the function, a va_list variable ap is declared to hold the variable arguments. The va_start() macro is then used to initialize the va_list variable with the starting address of the first argument. The for loop is used to iterate over the variable arguments, and the va_arg() macro is used to retrieve each argument as an integer. Finally, the va_end() macro cleans up the va_list variable.

When we call myfunc() with count and the list of integers, the function iterates over each integer and prints it to the console.


The <stdarg.h> header file

  • which provides the macros and types necessary to work with variable argument lists.


    putchar() function:

    The putchar function is a standard library function in C programming that is used to write a single character to the standard output stream (stdout). Its syntax is as follows:

      //int c, which represents the character to be written to the output stream.
      int putchar(int c);
    

    The putchar function takes a single argument of type int, which represents the character to be written to the output stream. The function returns the written character as an unsigned char cast to an int, or EOF (-1) on error.

    In practice, the putchar function is often used in loops to output a string character by character or to output digits one by one.


    Writing a single character using putchar()

      #include <stdio.h>
    
      int main() {
          char c = 'A';
          putchar(c);
          return 0;
      }
    

    In this example, the putchar function is used to write the single character 'A' to the standard output stream. The output of this program will be the character A.

    Writing a string using putchar()

        #include <stdio.h>
    
        int main() {
            char str[] = "Hello, world!";
    
      /*
      * (!= '\0') condition because at the end of any char array, the last 
      * character contained '\0' (a null-terminated string).
      */ 
              for(int i=0; str[i]!='\0'; i++){
                  putchar(str[i]);
              }
              return 0;
        }
    

    In this example, the putchar function is used to write the characters of the string "Hello, world!" to the standard output stream, one character at a time. This is achieved by looping through the characters of the string and calling putchar() on each character.

    Writing a number using putchar()

        #include <stdio.h>
    
        void print_digit(int n) {
    
      /*
       * print the character represented by the value ('0'+n)-> the value of 
       * (character '0'+n) in The ASCII.
      */ 
            putchar(n + '0');
        }
    
        int main() {
            int num = 12345;
            while (num > 0) {
                print_digit(num % 10);
                num /= 10;
            }
            return 0;
        }
    
    • In the code putchar(n + '0'), n is an integer variable representing a single digit of a number.

The ASCII code for the digit characters 0 to 9 are 48 to 57, respectively. Therefore, adding the ASCII code of 0 to an integer value n representing a digit converts it to the corresponding ASCII code for the character representing that digit.

In other words, n + '0' is an expression that evaluates to the ASCII code for the character representation of n.

The putchar() function is used to write a single character to the output stream, and in this case, the character being written is the one corresponding to the digit n.

  • The putchar function is used to write a number 12345 to the standard output stream, one digit at a time. The print_digit() function takes an integer argument and writes it to the output stream as a character by adding '0' to the integer. The main() function loops through the digits of the number by repeatedly taking the remainder of dividing by 10 and dividing by 10 and calls the print_digit() function on each digit. The output of this program will be the characters 12345.

Here's an Example implementation that should give you a good starting point:


The Flowchart:

+----------+
|  Start   |
+----------+
     |
     v
+------------------+
|  Initialization |
+------------------+
     |
     v
+--------------+
|  Loop through |
| the format    |
| string       |
+--------------+
     |
     v
+---------------------+
| Check for %         |
| character           |
+---------------------+
     |
     v
+---------------------+
| Get the specifier   |
| character           |
+---------------------+
     |
     v
+-------------------+
| If it's %c        |
| handle %c          |
|                   |
| Else if it's %s   |
| handle %s          |
|                   |
| Else if it's %d   |
| handle %d          |
|                   |
| Else if it's %f   |
| handle %f          |
|                   |
| Else              |
| handle other      |
+-------------------+
     |
     v
+---------------------------+
| Move to next character in  |
| format string              |
+---------------------------+
     |
     v
+------------------------+
| Continue looping until  |
| the end of the format   |
| string is reached       |
+------------------------+
     |
     v
+-----------------------+
| End of loop            |
+-----------------------+
     |
     v
+--------------------+
| Cleanup and return  |
+--------------------+
     |
     v
+---------+
|  Stop   |
+---------+

The Program:

#include <stdio.h>
#include <stdarg.h>

/* 
 * This function uses the putchar function to output characters and digits one by one instead of using printf.
 * When a %c specifier is encountered, the function outputs the character argument directly using putchar.
 * For the %s specifier, the function loops through the string argument character by character and outputs each character using putchar.
 * For the %d specifier, the function checks if the argument is negative and outputs a minus sign if it is. 
 * The function then counts the number of digits in the number and outputs each digit by extracting it from the number using integer division and modulo operations.
 * For the %f specifier, the function extracts the integer part of the number, counts the number of digits before the decimal point, and outputs each digit before the decimal point. 
 * Then the function outputs the decimal point and the six decimal places by multiplying the fractional part of the number by 10 and extracting each digit using integer division and modulo operations one by one.
 * If an invalid format specifier is encountered, the function outputs a question mark. 
 *
 * @param format A string containing the format specifiers and optional text to output.
 * @param ... The arguments corresponding to each format specifier in the format string.
 * 
 */


#include <stdio.h>
#include <stdarg.h>


void myprintf(const char *format, ...);


void myprintf(const char *format, ...) {
    va_list args;
    va_start(args, format);

    for (int i = 0; format[i] != '\0'; i++) {
        if (format[i] == '%') {
            i++;

/*start checking for the specifier type*/


/* if char */
            if (format[i] == 'c') {
                char c = (char) va_arg(args, int);
                putchar(c);

/* if string */
            } else if (format[i] == 's') {
                char* str = va_arg(args, char*);
                for (int j = 0; str[j] != '\0'; j++) {
                    putchar(str[j]);
                }

/*if integer*/
            } else if (format[i] == 'd') {
                int d = va_arg(args, int);
                if (d < 0) {
                    putchar('-');
                    d = -d;
                }
                int digit_count = 0; 
                int tmp = d; 
 /*How many digits counter.*/ 
                do {  
                    digit_count++;    
                    tmp /= 10;    
                } while (tmp > 0);    

                     /*loop for (how many digits) times.*/
                     /*---------------------------------*/

                for (int j = digit_count - 1; j >= 0; j--) {
                    int divisor = 1;
                    for (int k = 0; k < j; k++) {
                        divisor *= 10;
                    }

   /*so if it was ex: 3 digits number the divisor will = 100 =(10x10) */

/*-------------------------------------------------------------------*/

/*to get the most left digit*/
/*ex: if 3 digits(456) so digit = (456/100)%10 = 4*/

                    int digit = (d / divisor) % 10; 
                    putchar('0' + digit);
                }

/*if double/float*/
            } else if (format[i] == 'f') {
                double f = va_arg(args, double);
                if (f < 0) {
                    putchar('-');
                    f = -f;
                }
/*extracts the integer part of the number*/

                int int_part = (int) f;
                int digits_before_decimal = 0;
                int tmp = int_part;
 /*How many digits counter.*/ 
                do {
                    digits_before_decimal++;
                    tmp /= 10;
                } while (tmp > 0);

                     /*loop for (how many digits) times.*/
                     /*---------------------------------*/

                for (int j = digits_before_decimal - 1; j >= 0; j--) {
                    int divisor = 1;
                    for (int k = 0; k < j; k++) {
                        divisor *= 10;
                    }
                    int digit = (int_part / divisor) % 10;
                    putchar('0' + digit);
                }
/*
 * after printing the digits befor the floating point, print the floating point
*/
                putchar('.');

/*the fractional part*/
                double frac_part = f - int_part;
/*six decimal places (6)*/
                for (int j = 0; j < 6; j++) {
                    frac_part *= 10;
                    int digit = (int) frac_part;
                    frac_part -= digit;
                    putchar('0' + digit);
                }
            } else {
                putchar('?');
            }
        } else {
            putchar(format[i]);
        }
    }

    va_end(args);
}

int main() {
    char ch = 'X';
    char* str = "Hello, world!";
    int num = 42;
    double pi = 3.14159;

    myprintf("%c\n", ch);
    myprintf("%s\n", str);
    myprintf("%d\n", num);
    myprintf("%f\n", pi);

    return 0;
}

This implementation uses the va_list and va_arg macros to extract a variable number of arguments from the function call. The format string contains the various format specifiers (e.g., %d for integers, %s for strings, etc.) that are used to determine the type of each argument. The code then prints out the values of each argument according to their corresponding format specifiers.


In this version of the my_printf function, we use the putchar function to output characters and digits one by one instead of using printf. When we encounter a %c specifier, we use putchar to output the character argument directly. When we encounter a %s specifier, we loop through the string argument character by character and output each character using putchar. When we encounter a %d specifier, we first check if the argument is negative and output a minus sign if it is. Then, we count the number of digits in the number and output each digit by extracting it from the number using integer division and modulo operations. We do the same for the %f specifier, but we first extract the integer part of the number, count the number of digits before the decimal point, and output each digit before the decimal point. Then we output the decimal point and the six decimal places by multiplying the fractional part of the number by 10 and extracting each digit using integer division and modulo operations one by one. Finally, we output a question mark for any invalid format specifier.


Hope that helped!