This commit is contained in:
Miloslav Ciz 2025-04-17 19:16:48 +02:00
parent d624e17688
commit c0fb21debe
21 changed files with 2001 additions and 1985 deletions

View file

@ -122,11 +122,11 @@ int main(void)
## Variables, Arithmetic, Data Types
Programming is a lot like mathematics, we compute equations and transform numerical values into other values. You probably know in mathematics we use *variables* such as *x* or *y* to denote numerical values that can change (hence variables). In programming we also use variables -- here **[variable](variable.md) is a place in memory which has a name** (and in this place there will be stored a value that can change over time).
Programming is a lot like [mathematics](math.md), we compute equations and transform [numeric](number.md) values into other values -- in the end everything is just a number. You probably remember that in mathematics we use *variables* such as *x* or *y* to denote numeric values that can change (hence variables). In programming we also use variables that are likewise names that stand for values that can change -- more specifically **[variable](variable.md) is a place in memory which has a name** (and in this place there will be stored a value that can change over time).
We can create variables named `x`, `y`, `myVariable` or `score` and then store specific values (for now let's only consider numbers) into them. We can read from and write to these variables at any time. These variables physically reside in [RAM](ram.md), but we don't really care where exactly (at which address) they are located -- this is e.g. similar to houses, in common talk we normally say things like *John's house* or *the pet store* instead of *house with address 3225*.
We can create variables named `x`, `y`, `myVariable` or `score` and then store specific values (for now let's only consider numbers) into them. We can read from and write to these variables at any time. These variables physically reside in [RAM](ram.md), but we don't really care where exactly (at which address) they are located -- this is similar to houses for example, in common talk we normally say something like *John's house* or *the pet store* instead of *house with address 3225*.
Variable names can't start with a digit (and they can't be any of the [keywords](keyword.md) reserved by C). By convention they also shouldn't be all uppercase or start with uppercase (these are normally used for other things). Normally we name variables like this: `myVariable` or `my_variable` (pick one style, don't mix them).
Variable names can't start with a digit (and they can't be any of the [keywords](keyword.md) reserved by C). By convention they also shouldn't be all uppercase or start with uppercase (these are normally used for other things). Frequently used conventions for naming variable are these: `myVariable` or `my_variable` (pick one style, don't mix them).
In C as in other languages each variable has a certain **[data type](data_type.md)**; that is each variable has associated an information of what kind of data is stored in it. This can be e.g. a *whole number*, *fraction*, a *text character*, *text string* etc. Data types are a more complex topic that will be discussed later, for now we'll start with the most basic one, the **integer type**, in C called `int`. An `int` variable can store whole numbers in the range of at least -32768 to 32767 (but usually much more).
@ -409,7 +409,7 @@ The output is
Now imagine we decide we also want our temperatures in Fahrenheit. We can simply edit the code in `writeTemperature` function and the program will automatically be writing temperatures in the new way.
Let's see how to create and invoke functions. Creating a function in code is done between inclusion of libraries and the `main function`, and we formally call this **defining a function**. The function definition format is following:
Let's see how to create and invoke functions. Creating a function in code is typically done between inclusion of libraries and the `main function`, and we formally call this **defining a function**. The function definition format is following:
```
RETURN_TYPE FUNCTION_NAME(FUNCTION_PARAMETERS)
@ -829,7 +829,7 @@ The `-c` flag tells the compiler to only compile the file, i.e. only generate th
gcc -c -o program.o program.c
```
This will generate the file *program.o*. Note that during this process the compiler is working only with the *program.c* file, it doesn't know the code of the function `square`, but it knows this function exists, what it returns and what parameter it has thanks to us including the library header *library.h* with `#include "library.h"` (quotes are used instead of `<` and `>` to tell the compiler to look for the files in the current directory).
This will generate the file *program.o*. Note that during this process the compiler is working only with the *program.c* file, it doesn't know the code of the function `square`, but it knows the function exists, what it returns and what parameter it takes thanks to us including the library header *library.h* with `#include "library.h"` (quotes are used instead of `<` and `>` to tell the compiler to look for the files in the current directory).
Now we have the file *program.o* in which the compiled `main` function resides and file *library.o* in which the compiled function `square` resides. We need to link them together. This is done like this:
@ -899,7 +899,7 @@ What is this good for? Why don't we just create global variables such as `carl_i
**Structs can be nested**. So you may see things such as `myHouse.groundFloor.livingRoom.ceilingHeight` in C code.
Another extremely important compound type is **[array](array.md)** -- a sequence of items, all of which are of the same data type. Each array is specified with its length (number of items) and the data type of the items. We can have, for instance, an array of 10 `int`s, or an array of 235 `Human`s. The important thing is that we can **index** the array, i.e. we access the individual items of the array by their position, and this position can be specified with a variable. This allows for **looping over array items** and performing certain operations on each item. Demonstration code follows:
Another immensely important compound type is **[array](array.md)** -- a sequence of items, all of which are of the same data type. Each array is specified with its length (number of items) and the data type of the items. We can have, for instance, an array of 10 `int`s, or an array of 235 `Human`s. The important thing is that we can **index** the array, i.e. we access the individual items of the array by their position, and this position can be specified with a variable. This allows for **looping over array items** and performing certain operations on each item. Demonstration code follows:
```
#include <stdio.h>
@ -1230,13 +1230,13 @@ This creates a new data type `Fruit`. Variables of this type may have values `AP
## 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 belong under advanced topics that many people fear -- many complain they're hard to learn, others complain about memory unsafety and potential "dangers" connected to pointers. These people are stupid, pointers are great. Let's dissect the subject bit by bit.
But beware, there may be too much new information in the first read. Don't get scared, give it some time.
Beware still, there may be too much new information in the first read. Don't get scared, give it some time.
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.
Pointers allow us to handle certain advanced tasks such as allocating dynamic memory, returning multiple values from functions, inspecting content of memory or using functions in similar ways in which we use variables.
A **[pointer](pointer.md)** is essentially nothing complicated: it is a **data type that can hold a memory address** (plus an information about what data type should be stored at that address). An address is simply a number. Why can't we just use an `int` to store a memory address? Because the size of `int` and a pointer may differ, the size of pointer depends on each platform's address width. Besides this, as said, a pointer actually holds not only an address but also the information about the type it points to, which is a safety mechanism that will become clear later. It is also good when the compiler knows a certain variable is supposed to point to a memory rather than to hold a generic number -- this can all prevent bugs. I.e. pointers and generic integers are distinguished for the same reason other data types are distinguished -- in theory they don't have to be distinguished, but it's safer.
The definition of a **[pointer](pointer.md)** is essentially nothing complicated: it is a **data type that can hold a memory address** (plus an information about what data type should be stored at that address). Address is nothing but a non-negative integer [number](number.md). Why can't we just use an `int` to store a memory address? Because the size of `int` and a pointer may differ, the size of pointer depends on the size of the computer's address bus. Besides this, as said, a pointer actually holds not only an address but also the information about the type it points to, which is a safety mechanism that will become clear later. It is also good when the compiler knows a certain variable is supposed to point to a memory rather than to hold a generic number -- this can all prevent bugs. I.e. pointers and generic integers are distinguished for the same reason other data types are distinguished -- in theory they don't have to be distinguished, but it's safer.
It is important to stress again that a pointer is not a pure address but it also knows about the data type it is pointing to, so there are many kinds of pointers: a pointer to `int`, a pointer to `char`, a pointer to a specific struct type etc.
@ -1882,7 +1882,7 @@ int main(int argc, char **argv)
}
```
To add a few comments: the program opens a file whose name it gets passed as a command line argument, so it is used as: `./hexview myfile`. We try to correctly perform all safety checks, e.g. if we actually get passed the file name, if we manage to open it and so on. Our program (a bit inefficiently) loads the whole file into memory (advanced programs only load parts of the file) -- for this it first checks the file size, allocates sufficient memory for it with `malloc` (also checking for errors) and loads it there. Then we have a function to draw the current file view and inside the main program body we have an interactive loop that loads and handles user commands and issues the view drawing. That is basically it!
To append a few comments: the program opens a file whose name it gets passed as a command line argument, so it is used as: `./hexview myfile`. We try to correctly perform all safety checks, e.g. if we actually get passed the file name, if we manage to open it and so on. Our program (a bit inefficiently) loads the whole file into memory (advanced programs only load parts of the file) -- for this it first checks the file size, allocates sufficient memory for it with `malloc` (also checking for errors) and loads it there. Then we have a function to draw the current file view and inside the main program body we have an interactive loop that loads and handles user commands and issues the view drawing. That is basically it!
## Bonus: Introduction To Graphics (ASCII, PPM, SDL2, ...)
@ -1909,7 +1909,7 @@ But first let's quickly mention what graphics programming at this level is essen
- At the end, once drawing is complete, we have to **show (*present*) the picture**. This is to say that when we're drawing, the picture isn't actually seen, it is only changing in memory, it is shown to the user only when it's completed, i.e. when we issue a special command such as `drawingDone()`. Why can't the picture just be shown at all times? In theory it can, but you encounter problems, imagine e.g. a game that quickly redraws the picture on the screen -- here the user would see flickering, he might even see enemies show briefly behind a wall before the wall is actually drawn and so on. So a way to solve this is to do the drawing off screen and only at the end say "now we're done drawing, show the image" (for more details see [double buffering](double_buffering.md)).
- Also note that usually there is some kind of management around graphic code, i.e. some initialization of the program's window, setting its resolution, allocation of memory for the screen pixels, setting the pixel formats, [callbacks](callback.md) and so on. Similarly at the end you often have to clean things up and as many graphic systems are based on events, you have to periodically check events like key presses, window resizes etc. Interactive programs will furthermore have an infinite loop (so called *game loop*) in which they check events, redraw the screen, wait for a while (to keep the right [FPS](fps.md)) and so on. Libraries try to do many thing for you but you have to at least tell them some very basic things. So be prepared for a lot extra code.
Now let's finally do this. We'll set up some basic code for drawing a rectangle and try to draw it with different approaches.
Now let's finally get to writing the code. We'll set up some basic code for drawing a rectangle and try to draw it with different approaches.
The ASCII approach:
@ -1986,7 +1986,7 @@ int main(void)
}
```
With this we have a simple interactive program that draws a dotted screen with rectangle that represents the player, you can compile it like any other program, it uses no external libraries. User can move the rectangle around by typing commands. There is a main infinite loop (this is the above mentioned *game loop*, a typical thing in interactive applications) in which we read the user commands and redraw the picture on the screen. Notice we have our basic `drawPixel` function as well as the `drawScreen` function for presenting the finished picture, we also have a helper `drawRectangle` function. The `screen` array represents our virtual picture (it is declared as one dimensional array but in reality it is treated as two dimensional by the `setPixel` function). As an exercise you can try to draw other simple shapes, for example horizontal and vertical lines, non-filled rectangles -- if you're brave enough you can also try a filled circle (hint: points inside a circle mustn't be further away from the center than the circle radius).
With this we have a simple interactive program that draws a dotted screen with rectangle representing the player, you can compile it like any other program, it uses no external libraries. User can move the rectangle around by typing commands. There is a main infinite loop (this is the above mentioned *game loop*, a typical thing in interactive applications) in which we read the user commands and redraw the picture on the screen. Notice we have our basic `drawPixel` function as well as the `drawScreen` function for presenting the finished picture, we also have a helper `drawRectangle` function. The `screen` array represents our virtual picture (it is declared as one dimensional array but in reality it is treated as two dimensional by the `setPixel` function). As an exercise you can try to draw other simple shapes, for example horizontal and vertical lines, non-filled rectangles -- if you're brave enough you can also try a filled circle (hint: points inside a circle mustn't be further away from the center than the circle radius).
Now let's try to do something similar, but this time creating a "real picture" made of true pixels, exported to a file:
@ -2150,7 +2150,7 @@ This code is almost a bare minimum template for SDL that doesn't even perform an
*See also [exercises](exercises.md).*
We haven't nearly covered the whole of C, but you should have pretty solid basics now. Now you just have to go and write a lot of C programs, that's the only way to truly master C. WARNING: Do not start with an ambitious [project](project.md) such as a 3D game. You won't make it and you'll get demotivated. Start very simple (a Tetris clone perhaps?). Try to develop some consistent programming style/formatting -- see our [LRS programming style](programming_style.md) you may adopt (it's better than trying to make your own really).
We haven't nearly covered the whole of C, but you should be left with pretty solid basics now. Now you just have to go and write a lot of C programs, that's the only way to truly master C. WARNING: Do not start with an ambitious [project](project.md) such as a 3D game. You won't make it and you'll get demotivated. Start very simple (a Tetris clone perhaps?). Try to develop some consistent programming style/formatting -- see our [LRS programming style](programming_style.md) you may adopt (it's better than trying to make your own really).
See also supplemental articles at the beginning of this tutorial.