Update tuto

master
Miloslav Ciz 2 years ago
parent decd2bd9d5
commit 818ab679b9

@ -502,7 +502,7 @@ Another local variable is `number` -- it is a local variable both in `main` and
And a last thing: keep in mind that not every command you write in C program is a function call. E.g. control structures (`if`, `while`, ...) and special commands (`return`, `break`, ...) are not function calls.
## Additional Details (Global, Switch, Float, Forward Decls, ...)
## More Details (Globals, Switch, Float, Forward Decls, ...)
We've skipped a lot of details and small tricks for simplicity. Let's go over some of them. Many of the following things are so called [syntactic sugar](sugar.md): convenient syntax shorthands for common operations.
@ -567,7 +567,7 @@ void playLottery(void)
money -= 10; // price of lottery ticket
if (rand() % 5)
if (rand() % 5) // 1 in 5 chance
{
money += 100;
puts("I've won!");
@ -761,13 +761,146 @@ As a bonus, let's see a few useful compiler flags:
- `-c`: Compile only (generate object files, do not link).
- `-g`: Include debug symbols, this will be important for [debugging](debugging.md).
## Advanced Data Types and Variables (Structs, Arrays)
## Macros/Preprocessor
The C language comes with a feature called *preprocessor* which is necessary for some advanced things. It allows automatized modification of the source code before it is compiled.
Remember how we said that compiler compiles C programs in several steps such as generating object files and linking? There is one more step we didn't mention: **[preprocessing](preprocessing.md)**. It is the very first step -- the source code you give to the compiler first goes to the preprocessor which modifies it according to special commands in the source code called **preprocessor directives**. The result of preprocessing is a pure C code without any more preprocessing directives, and this is handed over to the actual compilation.
The preprocessor is like a **mini language on top of the C language**, it has its own commands and rules, but it's much more simple than C itself, for example it has no data types or loops.
Each directive begins with `#`, is followed by the directive name and continues until the end of the line (`\` can be used to extend the directive to the next line).
We have already encountered one preprocessor directive: the `#include` directive when we included library header files. This directive pastes a text of the file whose name it is handed to the place of the directive.
Another directive is `#define` which creates so called [macro](macro.md) -- in its basic form a macro is nothing else than an alias, a nickname for some text. This is used to create constants. Consider the following code:
```
#include <stdio.h>
#define ARRAY_SIZE 10
int array[ARRAY_SIZE];
void fillArray(void)
{
for (int i = 0; i < ARRAY_SIZE; ++i)
array[i] = i;
}
void printArray(void)
{
for (int i = 0; i < ARRAY_SIZE; ++i)
printf("%d ",array[i]);
}
int main()
{
fillArray();
printArray();
return 0;
}
```
`#define ARRAY_SIZE 10` creates a macro that can be seen as a constant named `ARRAY_SIZE` which stands for `10`. From this line on any occurence of `ARRAY_SIZE` that the preprocessor encounters in the code will be replaced with `10`. The reason for doing this is obvious -- we respect the [DRY](dry.md) (don't repeat yourself) principle, if we didn't use a constant for the array size and used the direct numeric value `10` in different parts of the code, it would be difficult to change them all later, especially in a very long code, there's a danger we'd miss some. With a constant it is enough to change one line in the code (e.g. `#define ARRAY_SIZE 10` to `#define ARRAY_SIZE 20`).
The macro substitution is literally a copy-paste text replacement, there is nothing very complex going on. This means you can create a nickname for almost anything (for example you could do `#define when if` and then also use `when` in place of `if` -- but it's probably not a very good idea). By convention macro names are to be `ALL_UPPER_CASE` (so that whenever you see an all upper case word in the source code, you know it's a macro).
Macros can optionally take parameters similarly to functions. There are no data types, just parameter names. The usage is demonstrated by the following code:
```
#include <stdio.h>
#define MEAN3(a,b,c) (((a) + (b) + (c)) / 3)
int main()
{
int n = MEAN3(10,20,25);
printf("%d\n",n);
return 0;
}
```
`MEAN3` computes the mean of 3 values. Again, it's just text replacement, so the line `int n = MEAN3(10,20,25);` becomes `int n = (((10) + (20) + (25)) / 3);` before code compilation. Why are there so many brackets in the macro? It's always good to put brackets over a macro and all its parameters because the parameters are again a simple text replacement; consider e.g. a macro `#define HALF(x) x / 2` -- if it was invoked as `HALF(5 + 1)`, the substitution would result in the final text `5 + 1 / 2`, which gives 5 (instead of the intended value 3).
You may be asking why would we use a macro when we can use a function for computing the mean? Firstly macros don't just have to work with numbers, they can be used to generate parts of the source code in ways that functions can't. Secondly using a macro may sometimes be simpler, it's shorter and will be faster to execute because the is no function call (which has a slight overhead) and because the macro expansion may lead to the compiler precomputing expressions at compile time. But beware: macros are usually worse than functions and should only be used in very justified cases. For example macros don't know about data types and cannot check them, and they also result in a bigger compiled executable (function code is in the executable only once whereas the macro is expanded in each place where it is used and so the code it generates multiplies).
Another very useful directive is `#if` for conditional inclusion or exclusion of parts of the source code. It is similar to the C `if` command. The following example shows its use:
```
#include <stdio.h>
#define RUDE 0
void printNumber(int x)
{
puts(
#if RUDE
"You idiot, the number is:"
#else
"The number is:"
#endif
);
printf("%d\n",x);
}
int main()
{
printNumber(3);
printNumber(100);
#if RUDE
puts("Bye bitch.");
#endif
return 0;
}
```
When run, we get the output:
```
The number is:
3
The number is:
100
```
And if we change `#define RUDE 0` to `#define RUDE 1`, we get:
```
You idiot, the number is:
3
You idiot, the number is:
100
Bye bitch.
```
We see the `#if` directive has to have a corresponding `#endif` directive that terminates it, and there can be an optional `#else` directive for an *else* branch. The condition after `#if` can use similar operators as those in C itself (`+`, `==`, `&&`, `||` etc.). There also exists an `#ifdef` directive which is used the same and checks if a macro of given name has been defined.
`#if` directives are very useful for conditional compilation, they allow for creation of various "settings" and parameters that can fine-tune a program -- you may turn specific features on and off with this directive. It is also helpful for [portability](portability.md); compilers may automatically define specific macros depending on the platform (e.g. `_WIN64`, `__APPLE__`, ...) based on which you can trigger different code. E.g.:
```
#ifdef _WIN64
puts("Your OS sucks.");
#endif
```
## Pointers
Pointers are an advanced topic that many people fear -- many complain they're hard to learn, others complain about memory unsafety and potential dangers of using pointers. These people are stupid, pointers are great.
Pointers allow us to do certain advanced things such as allocate dynamic memory, return multiple values from functions, inspect content of memory or use functions in similar ways in which we use variables.
A **[pointer](pointer.md)** is nothing complicated: it is a **data type that can hold a memory address** (plus the information of what data type should be stored at that address). An address is simply a number. Why can't we simply use an `int` for an address? Because the size of `int` and a pointer may differ, the size of pointer depends on each platform's address width. It is also good when the compiler knows a certain variable is supposed to point to a memory (and to which type) -- this can prevent bugs.
TODO
## More on Functions (Recursion, Function Pointers)
## Dynamic Allocation (Malloc)

Loading…
Cancel
Save