xhartae/chapter/chapter_3.h

97 lines
6.8 KiB
C

/*
Copyright (c) 2023 : Ognjen 'xolatile' Milan Robovic
Xhartae is free software! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation.
And when you do redistribute it or modify it, it will use either version 3 of the License, or (at yours truly opinion) any later version.
It is distributed in the hope that it will be useful or harmful, it really depends... But no warranty what so ever, seriously. See GNU/GPLv3.
*/
#ifndef CHAPTER_3_HEADER
#define CHAPTER_3_HEADER
#include <stdarg.h>
#include "chapter_0.h"
#include "chapter_1.h"
/*
Now, time has come to talk about (sadly) the most important standard library function in C programming language. Function 'printf' is like some semi-drunk old man, stumbling on
the streets, talking to himself, talking to other people who try to ignore him, but if you stand and talk to him, he'll sometimes start talking about random things, and sometimes
he'll stay on-topic. So, what does function 'printf' do? Essentially, it'll print formatted string to standard output, formatted in this case means that in the main string (first
argument to 'printf' function) there are special characters that'll accept variadic arguments, format them (convert them to string in special manner) and output that final string.
It's located in <stdio.h> header file, and it's commonly used through-out most C programs. You can learn more about it from manual pages with command 'man 3 printf', and below you
can see some variations of that function. I just realigned them and added 'extern', which C assumes as default (but some compilers would generate a warning for that). We'll learn
to implement variadic functions as well in this chapter, but I consider that a bad practice, even tho they can be extremely useful. Functions we learned to declare and define so
far have constant and limited amount of arguments, while variadic functions can have any amount of arguments greater than one, as you can see below.
@C
extern int printf ( const char * format, ...);
extern int sprintf (char * str, const char * format, ...);
extern int snprintf (char * str, size_t size, const char * format, ...);
extern int fprintf (FILE * stream, const char * format, ...);
extern int dprintf (int fd, const char * format, ...);
extern int vprintf ( const char * format, va_list ap);
extern int vsprintf (char * str, const char * format, va_list ap);
extern int vsnprintf (char * str, size_t size, const char * format, va_list ap);
extern int vfprintf (FILE * stream, const char * format, va_list ap);
extern int vdprintf (int fd, const char * format, va_list ap);
@
Why do I dislike them? Because they're implemented with the help of macros from <stdarg.h> header file, in not particularly good way, and I dislike using 'va_*' because I simply
don't like the naming style. Also, you can't properly verify that you won't cause tiny bugs in your program, so before writing 'printf' function, you need to think, which is
something I don't like to do mostly. For example, if you pass a 'float' type variable to 'printf' function, with "%f" in its' format, you need to convert it to 'double', because
that 'va_arg' macro accepts only fully promoted types, which we'll explain below. All that aside, they can be very useful in specific cases!
You'd use it something like these few examples below, and it's a good practise not to mix 'printf' with 'write' functions (if you're using both of them), because they're
synchronized differently. Because of how they're buffered, mixing them can (in some cases) end up with mixed output.
@C
// And ignore the alignment here, it's just an usage example. You can put all of this into 'main' function in some temporary file, compile it and run it.
int a = 1; printf ("Integer: %d\n", a);
char b = 'A'; printf ("Character: %c\n", b);
char * c = "Heyo"; printf ("%s world!\n", c);
float d = 1.0F; printf ("%f\n", (double) d);
double e = 1.0; printf ("%f\n", e);
uint32_t f = 0X0011AAU; printf ("0X%X\n", f);
size_t g = sizeof (a); printf ("%ul\n", g);
int * h = & a; printf ("\t%p", (void *) h);
printf ("\nCyaa world!\n" );
// You can have more than one arguments, like this:
printf ("%s world!\n Our integer is %d.\n Our character is '%c'.\nCyaa world!\n", c, a, b);
// Never do this:
char * message = "Heyo world!";
printf (message);
// Instead:
char * message = "Heyo world!";
printf ("%s", message);
// Or:
printf ("Heyo world!");
@
Now, don't get scared, C is old language, and I'm aware that this looks like someone made a joke 50 years ago and nobody understood it, they took it serious. In the end, variadic
argument list type 'va_list' is black magic, as well as other 'va_*' stuff. Since 'va_arg' takes only fully promoted types, we're left with types 'int', 'double', 'void *' and
'char *'. Also, keep in mind that you don't need more arguments in 'printf' function, only that "const char * fmt" needs to be there always. It may take some time to get used to
this function, but you can use it for very quick and easy debugging. If you're not sure what value your variables hold at some point, you can just print it there.
I'll show you how to implement variadic argument functions, and we'll use these in few places, but I'm still not a big fan of them. Of course, we won't implement everything that
'printf' function from standard library has, or any more of its' alternatives, just these three below. However, we'll add few small additions, like colouring text with ASCII
escape sequences and doing some basic formatting. Once you learn C better, you should take a look at manual pages for functions that you find interesting. Or even better, instead
of reading stuff, try to implement them yourself, it's the best approach to learning anything.
If you wonder why did we cover curses in chapter two, and printing in chapter three, which is simpler and shorter essentially, it's because curses use few "black magic" functions
from header file <termios.h>, while printing uses <stdarg.h>, which is more difficult to use. We'll use it more in later chapters, but only as a show-case.
*/
extern void print ( char * format, ...); // Notice the "...", which means it can accept any amount of arguments after that 'format'.
extern void file_print (int file, char * format, ...); // Same as above, but it won't print to terminal, it'll write it to a file descriptor instead.
extern void string_print (char * string, char * format, ...); // Same as above again, but it'll copy it to some string.
#endif