Last active
July 28, 2024 23:36
-
-
Save stifskere/819812688a9793ade1c0ba1ed2269551 to your computer and use it in GitHub Desktop.
A c english tutorial
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
C Tutorial !!11!1!!1 | |
This is a simple C tutorial to understand the basics of the language, | |
not including most of the linkable libraries or advanced compiler behavior. | |
*/ | |
// Let's start with includes, includes let you concatenate | |
// a file to another file in your program, `.h` or header | |
// files are directly included on import, but there are | |
// `.c` files that should contain the definition of these header | |
// files, to include a header file you use the #include | |
// pre-processor directive and <> after if the header file | |
// is external or "" if the header file is local. | |
// | |
// TDLR; these files allow you using code writen in other files. | |
#include <stdio.h> | |
#include <malloc.h> | |
#include <math.h> | |
// we include these both headers as they will be important for this tutorial | |
// Let's talk about functions, a function is a piece of code | |
// you can reuse and call with some specified arguments | |
// and a return value, the sintax for a function is the | |
// following | |
// <return type> <name> (<arguments separated by ,>) {<function definition>} | |
void some_function(int argc, void *argv) { | |
} | |
// We will discuss later what those arguments mean, | |
// but for now we can say that we are able to call this function | |
// from inside another function, in this case we can call it | |
// from the main function. | |
// This function is unique, as it's the | |
// entry point of your program, the program will start | |
// running from here. | |
// | |
// This function returns an integer which is the exit code | |
// for the program | |
// | |
// https://en.wikipedia.org/wiki/Exit_status | |
int main() { | |
// The program starts here, you can write instructions | |
// call other functions and shape your program from here. | |
// Lets start talking about variables and types. | |
// The syntax for a variable is the following | |
// | |
// <type> <name> = <value expression>; | |
// | |
// Everything you write will end in a ;, except braces, | |
// braces don't need to end in a ;. | |
int variable = 10; | |
// Now we have a variable we can reference, its type is int (integer) | |
// and its value is 10, now when we reference this we will get the value 10. | |
// In this language only actual primitives exist, these are char, short, int, long, float | |
// and double, some of this types can be modified, for example if we add the "unsigned" prefix | |
// the number will not have negative numbers, and the memory that would be dedicated on having | |
// negative numbers is allocated for having more positive numbers, for example | |
// | |
// "char" range: -128 to 128 | |
// "unsiged char" range: 0 to 255 | |
// | |
// The size of char is equivalent to a byte, but we don't have the byte type in c. | |
// You can check what modifiers you can use and why in this table | |
// | |
// https://en.wikipedia.org/wiki/C_data_types | |
// | |
// char is more than a number, as it's name might tell you it's also a character. | |
// whenever you print a char to stdout you will see a letter, letters in computers | |
// are tied to a table that converts them to numbers and viceversa. | |
// | |
// https://es.wikipedia.org/wiki/ASCII | |
// | |
// We can find literals for every type mentioned above, a literal is the definition | |
// of the number, for example we could say that 10 is a literal for int, | |
// 10L is a literal for 10 as a long integer, the postfixes for defining literal | |
// as other types are the following (The operators can be merged the same way as merging | |
// types for variables) | |
// | |
// U unsigned | |
// L long int | |
// UL unsigned long int | |
// LL long long int | |
// ULL unsigned long long int | |
// | |
// And then there are prefixes which are other ways of writting numeric literals, | |
// these don't really modify the type, is mostly only ways to read the code. | |
// | |
// 0x hexadecimal, | |
// 0 octal | |
// 0b binary | |
// | |
// And lastly there is the literal for char which is '' with the character in between, | |
// this is not like python or other languages that might let you use whatever quotes you want | |
// | |
// 'A' means A char or 17 | |
// In C we can apply forms of conversion, by using a cast, a cast is defined using () | |
// and the type in between, for example | |
int variable1 = 17; | |
char variable2 = (char)variable1; // A | |
// There is 2 types of conversion we can find, which is implicit and explicit, | |
// implicit conversion is automatic, meaning we don't have to do anything | |
// to convert, it's part of the language behavior, while explicit | |
// means we need to do what's written above to convert a type. | |
// C has basic operators appliable to numbers, you can use those to interact with | |
// the numbers you store in variables. | |
// | |
// With operators you can do complex expressions like basic maths, the only difference | |
// this has is the fact you can grab variable values and run functions in the middle | |
// of the expression to get numbers you can use to for example get a random number | |
// or get a number from somewhere else. | |
// | |
// The operators you can find in C are the following | |
// | |
// + Adds 2 operands | |
// - Subtracts 2 operands | |
// * Multiplies 2 operands | |
// / Divides 2 operands | |
// % Get the modulus of the division of 2 operands | |
// ++ Increment a variable by 1 | |
// -- Decrement a variable by 1 | |
// = Assign a variable by the right operand | |
// | |
// The 3 last operators are to be used only in variable references, these act as | |
// expressions, and return the following values: | |
// | |
// <variable>++ returns the value and then increments the variable | |
// ++<variable> increments the variable and returns the incremented variable value | |
// | |
// The same happens with --, and for the = operator simply returns the right operand | |
// while asigning the variable reference to this right operand too. | |
// | |
// These last operators act in numbers, but then there are also boolean operators, | |
// even tho booleans in c do not exist, we can translate those as 0 or 1, 0 being | |
// false and 1 being true, if, while and for statements will expect numbers | |
// instead of booleans. | |
// | |
// == Compares the equality of 2 numbers, returning 1 if both of the operands are equal | |
// != Compares the inequality of 2 numbers, returning 1 if both of the operands are not equal | |
// > Compares if the left operand is bigger than the right one, returns 1 if so | |
// < Compares if the left operand is smaller than ther right one, returns 1 if so | |
// >= Compares if the left operand is bigger or equal to the right one, returns 1 if so | |
// <= Compares if the left operand is smaller or equal to the right one, returns 1 if so | |
// | |
// You can merge these operators and make complex expressions the following way | |
// | |
// && boolean AND operator, returns 1 if both operands are true | |
// || boolean OR operator, returns 1 if at least 1 operand is true | |
// ! boolean negation operator, returns the contrary value for example 0 would become 1 and viceversa | |
// | |
// For example we could say that "10 == 10 && 5 != 6" returns true because | |
// | |
// 10 = 10 && 5 != 6 | |
// 1 && 5 != 6 | |
// 1 && 1 | |
// 1 | |
// | |
// Then we have bitwise operators, which compare and modify bits in numbers, | |
// for everything we can do with bitwise operators we need to view numbers as binary | |
// | |
// for example 10 = 00001010 | |
// | |
// Lets play with the 10 and 11 for the examples | |
// | |
// 00001011 | |
// 00001010 | |
// 00001010 & (AND OPERATOR) | |
// | |
// The result is 10, as it applies the oprerator for every bit and | |
// checks every bit instead of the literal number. | |
// | |
// 00001011 | |
// 00001010 | |
// 00001011 | (OR OPERATOR) | |
// | |
// 00001011 | |
// 00001010 | |
// 00000001 ^ (XOR OPERATOR) | |
// | |
// Here are the conversion tables for the bitwise operators | |
// | |
// 0 & 0 = 0 | |
// 1 & 0 = 0 | |
// 0 & 1 = 0 | |
// 1 & 1 = 1 | |
// | |
// 0 | 0 = 0 | |
// 1 | 0 = 1 | |
// 0 | 1 = 1 | |
// 1 | 1 = 1 | |
// | |
// 0 ^ 0 = 0 | |
// 1 ^ 0 = 1 | |
// 0 ^ 1 = 1 | |
// 1 ^ 1 = 0 | |
// | |
// and then there is a few more operators like the shift and invert operator, | |
// the invert operator acts the same as !, but for binary | |
// | |
// 00001010 | |
// 11110101 ~ | |
// | |
// ~ 1 = 0 | |
// ~ 0 = 1 | |
// | |
// And about the shift operator simply moves the bits x positions. | |
// | |
// 00000001 | |
// 00000001 | |
// 00000010 << | |
// | |
// 00000010 | |
// 00000001 | |
// 00000001 >> | |
// | |
// If the width is smaller than the shifted ammount it results in 0, which means overflow, | |
// for example for unsigned char which has a size of 8 bits, if we shift 9 we will get 0 | |
// as result | |
// | |
// 00000001 | |
// 00001001 | |
// 00000000 >> or << | |
// | |
// The bitwise operators can be used for optimizing the usage of memory if you have payloads | |
// and OP codes and some other cases, or to store properties in enum constants. | |
// | |
// All the binary (not bitwise but bitwise too) operators have their X= version, which uses | |
// a variable as the left operand and directly assigns the result to that variable. | |
// | |
// The types of operators are binary (2 operands are required) and unary (only one | |
// operand is required), for example, we can increment a variable by 10 by doing | |
variable += 10; // since variable value was 10, now the value will be 20, | |
// this also returns 20 as expression. | |
// C operators run in pedmas/bodmas order, we can use () to override that. | |
// | |
// 2 + 5 * 3 == 17 | |
// (2 + 5) * 3 == 21 | |
// C also has arrays which is considered a complex structure, these are fixed size | |
// lists of values of a single type, and are declared the following way | |
int variable4[] = {1, 2, 3, 4}; | |
// Even tho before in this tutorial we specified that after {} a ; is not required | |
// this is not a control statement nor a body, so it's required in this case. | |
// | |
// the syntax for an array is the following | |
// | |
// <type> <name>[] = {<, separated values>}; | |
// | |
// Any type can be stored in an array, even sub arrays, or other complex structures you might declare, | |
// there is also another way of declaring arrays which is by size, we can do the following | |
int variable5[10]; | |
// What that does in this case is start the variable with 10 positions and a 0 in each position, | |
// as 0 is the default value for any non complex data type. | |
// | |
// To access array values or assign them we can do it the following way | |
// | |
// variable4[0] | |
// | |
// And the last expression will return 1, as the 0th position of the variable4 array is 1, | |
// position starts with 0, there is no length concept in C, we need to keep track of that | |
// or get it by measuring with the sizeof() operator, which returns the size of a data type | |
// or literal data itself | |
int var4Size = sizeof(variable4) / sizeof(int); | |
// And that would return 4, because variable4 returns the size in bytes of a structure | |
// or the bytes a type takes when allocated, since int is 4 bytes, sizeof(variable4) | |
// would return 16, because the array takes 16 bytes in memory, and if we divide that | |
// by the size of the type which is 4, we get 4, as 16 / 4 is 4. | |
// | |
// We can also asign a value to each place, for example to asign a value to | |
// variable5[0] we could do the following | |
variable5[0] = 10; | |
// Note that we can also nest arrays, and when we access a value of the array | |
// it returns whatever it stores and lets us manage it how the type is specified | |
// to do so, if we have an array of arrays and access the index 0 of that, | |
// we get the reference to another array, which you could access to a custom | |
// index of that array so <ref>[0][0] == 1 if {{1, 2}, {3, 4}}, the type for | |
// a nested array is <type>[][] and we can nest it as many times as we need. | |
// Arrays are directly tied to pointers, now that we understand how arrays work, | |
// we must understand one of the most important ways of data manipulation in C | |
// which are pointers, pointers lets us move trough memory as if it was an array, | |
// we can take the address of a variable and store it in a pointer variable, | |
// if we modify that pointer variable since it's pointing to the same memory | |
// address as the variable, both variables will be altered, we can also move | |
// positions within pointers. | |
// Lets take RAM as if it was a big array of bytes, if we point to some variable | |
// we might get a big memory address such as 0x7ffee3b9e0c0, lets for example | |
// take a pointer to the first item of the variable4 array. | |
int *var4Ptr = variable4; | |
// This stores the address of the first item of the array, we don't need | |
// to take the reference as arrays are just another way of manipulating | |
// memory like pointers, and both are implicitly converted to one another. | |
// If we wanted to get the reference to a variable, we would need to tell C | |
// we want the reference of that variable with the & operator. | |
int *varPtr = &variable; | |
// now this would actually give us the address of the element in that variable, | |
// the point of having pointers is being able to modify the same value in different places | |
// referencing the same memory, to modify the value of variable trough varPtr, we can do | |
// the following | |
(*varPtr) = 50; | |
// When we use * in a pointer we get the reference to that position, we can then | |
// assign a value to that reference and the value will be changed for both | |
// variables, as both point to the same place. | |
// | |
// If we declare a pointer to an array we can safely move trough it incrementing the | |
// memory address as if it was a number, it will then point to other parts in the memory, | |
// we specify types in pointers for that reason, so when we move it moves sizeof(<type>) times | |
// instead of only 1 byte. | |
var4ptr++; // now assuming that var4Ptr value was 0x7ffee3b9e0c0 it is incremented by 4 | |
// because it's an int pointer and int is 4 bytes, now we have access to | |
// the next value address. | |
// To simply retrieve a value we can use the (*ptr) operator and it will | |
// return a reference to that memory address, which depending on the | |
// type it will be some size or the other, but the point here is that we | |
// get a manipulable value. | |
// If we want to grab different types of pointers and increase or decrease the | |
// memory address manually we can use a void* type. | |
void *genericPtr = var4Ptr; // This assigns this ptr to the same address as var4Ptr; | |
// but in a generic way since this is an int array, we will | |
// need to manually increase or decrease by 4 to move | |
// around the array. | |
genericPtr += 4; // advances to the next position and assigning an int to it will replace | |
// the next 4 bytes, (an explicit cast is recomended for safety) | |
(*genericPtr) = (int)5; | |
// if we had arrays of arrays that would be equivalent to (<type> *<name>)[<size of each array>] | |
// and incrementing would increment by sizeof(<type>) * <array size> * times | |
// The most used case of that is string arrays, since in here we don't have strings we use | |
// char*, and char* has a literal that's "", and all the text inside, that will return | |
// us an array of size sizeof(char) * sizeof(value), but since sizeof(char) returns 1, we can | |
// simplify to sizeof(value). | |
char *text = "Hello World!"; // size = 12 | |
// and interact with that like a pointer, we could also have it as a int <name>[], as again, both types | |
// can be implcitly casted. | |
// | |
// Like arrays, we can also nest pointers, to do so, we simply declare a int** and that | |
// would be the same as int <name>[][], accessing a value of the index assuming we used | |
// the pointer in a safe maneer gives us a reference to another pointer, we can manage | |
// like the first one (*<ref>)++ would increment the internal pointer, and accessing | |
// the value would be like (**<ref>). | |
// In C we have some useful apis that can be imported, such as malloc, stdio and cmath. | |
// Let's talk abut malloc first, malloc is a header file that allows us to allocate and free | |
// memory in a custom way, we can allocate as many bits as we want and then fill those programatically | |
// malloc declares 2 main functions along some others, these are malloc and free, lets talk about malloc first | |
// | |
// malloc(int size) allows us to allocate as many memory as we want if it's free in the system, it returns a | |
// void* that we can cast to any type we want. | |
// | |
// for example a jagged array, which is an array that it's sub arrays have different sizes each. | |
// | |
// https://en.wikipedia.org/wiki/Jagged_array | |
// | |
// A basic use of malloc is | |
int *allocated = (int *)malloc(5 * sizeof(int)); // 4 * 5 == 20 | |
// Now we can manipulate that pointer as if it was a normal pointer, but we should keep the first address | |
// to free it after, because this isn't cleared by C after finishing the scope, as it's not statically | |
// allocated in any way. | |
// | |
// The safe use for a malloced pointer is to first check if the malloc function didn't return 0, | |
// if it did we can say the allocation failed for many reasons, such as corrupted memory or | |
// insufficient memory, 0 is a reserved memory address which we can consideer null or None in | |
// python. | |
// | |
// To free it we use free, that takes the first address and clears the allocated size. | |
// | |
// If we need to create a copy of the first address to clear them so we don't need to keep | |
// track of how many we moved, we can do the following | |
int *fAllocated = allocated; | |
// That before doing anything with the allocated memory. | |
// | |
// To free it we use the free function that returns void. | |
free(fAllocated); | |
// There is also another library that we can use for printing values, writing to files | |
// and more, you can check the stdio.h reference in wikipedia | |
// | |
// https://es.wikipedia.org/wiki/Stdio.h | |
// | |
// Printing to the terminal can be done with printf which means print format, | |
// lets us format whatever we want to print and it takes 2 arguments | |
// | |
// printf(char *, ...); the last argument means varargs (as many arguments as we want of any type) | |
// | |
// https://en.wikipedia.org/wiki/Variadic_function | |
// | |
// There is a format specification for including the arguments inside the string, the basic ones | |
// being %s which is include next string and %d which is include next integer or %c which | |
// is include next char. | |
// | |
// as an example | |
int apples = 50; | |
printf("there are %d apples", apples); | |
// if we pass more arguments than we use or less we might get undefined behavior. | |
// | |
// This library also includes scanf, which lets us take input from the user, | |
// we should take care with that because we can get literally anything from | |
// the user, so we should always be sure that the data we receive | |
// is clean by using control statements. | |
char *input; | |
scanf("%s", *input); | |
// The value of input should now be whatever the user introduced, we can get | |
// the length with sizeof. | |
// lastly, there is cmath, which contains a lot of mathematical functions | |
// that are mostly shortcuts to complex expressions we might write such as pow | |
// or round... | |
// | |
// https://cplusplus.com/reference/cmath/ | |
// | |
// The python math library is just bindings to cmath. | |
// | |
// We will not be doing any example of cmath, because the reference has many. | |
// | |
// Before going into control statements we will talk about error handling, in | |
// c there are no errors, there is nothing that tells you "you fucked up in that line", | |
// you most likely get a segmentation fault for pointers or any other error | |
// that might be tied to another exit code, note that when you get a segmentation fault | |
// the used memory doesn't free so you will keep it till you restart the computer. | |
// | |
// https://www.gnu.org/software/libc/manual/html_node/Error-Codes.html | |
// | |
// In C there are a few types of errors which is compiler error, linker error | |
// and runtime error. | |
// | |
// A compiler error will give us the description of what went wrong, for example | |
// a syntax error such as an unclosed parenthesis. | |
// | |
// A linker error is an error that will occur when the linker had a problem | |
// while finding the necessary files to feed the compiler with. | |
// | |
// and the runtime error that doesn't really give any information | |
// are errors that happen while the program is running due to a logic error | |
// or something we did in our program that doesn't work. | |
// | |
// For example dividing by 0 would be considered a logic error. | |
// | |
// Now lets get started with control statements, lets talk about the if statement | |
// the if statement gives us the possibility to run code conditionally, for example | |
// if we have the age of a person and want to check whether they are older than 18 to | |
// let them buy some controlled substance or to be issued a driving license (in europe) | |
// we can do the following | |
int personAge = 15; | |
if (personAge >= 18) { | |
printf("You are permited to drive"); | |
} // control statements don't end with ; | |
// if statments have else clauses which run whenever the first condition is not met. | |
if (personAge >= 18 && personAge <= 70) { | |
printf("You may drive."); | |
} else { | |
printf("Get better."); | |
} | |
// or we could divide that to give different results with else if | |
if (personAge < 18) { | |
printf("You might not drive, to little."); | |
} else if (personAge > 70) { | |
printf("Too old, not acceptable."); | |
} else { | |
printf("here is your license."); | |
} | |
// And that's really it with the if statements, we could remove the {} | |
// if we are only executing one expression, if we remove the {} | |
// no assignments are allowed within the if scope, | |
// if we declare a variable inside the if scope we can't access to it | |
// outside of the scope, but otherwise if we declare a variable outside | |
// we can access inside. | |
// Then there are for loops, which are loop control statements, they take | |
// a variable declaration (since C11), a condition to check whether | |
// it should keep looping and a control statement to modify a condition | |
// variable, for example | |
// i is 0, while i < 10 then we run the inner code and increment the variable, recheck till false. | |
for (int i = 0; i < 10; i++) | |
printf("%d", i); // we can use {} or not, however we want. | |
// This prints 0 trough 9. | |
// Any kind of condition can be passed to loops, this is just an example, we can chose to | |
// not place one or all of the parts of the loop, the loop would work with only | |
for (;;) {} // wich would be an infinite loop. | |
// While loops work a different way, we can run the first iteration | |
// before or after the loop | |
int iterator = 0; | |
while (iterator < 10) | |
printf("%d", iterator++); | |
// The last code would print 0 trough 9. | |
// or do while loops | |
iterator = 0; | |
do { | |
printf("%d", iterator); | |
} while (iterator++ < 10); // a ; is required. | |
// This code would also print 0 trough 9. | |
// This could be used to iterate arrays or whatever you want to do, use your imagination. | |
// We can use the break or continue keyword in all of the loop statements | |
// | |
// The contnue keyword can be used to advance to the next iteration ignoring code below, | |
// the break keyword can be used to exit the loop and continue with the code below. | |
// | |
// And there is also the switch statement, which is like a big if statement but | |
// that only accepts checking numbers, not strings or any other complex structure. | |
char grade; | |
printf("Enter your letter grade (A, B, C, D, or F): "); | |
scanf("%c", &grade); // get a grade. | |
switch (grade) { // test case | |
case 'A': // grade == A | |
printf("Excellent!\n"); // print excellent | |
break; // don't check other cases, if absent the switch will keep going. | |
case 'B': | |
printf("Very good!\n"); | |
break; | |
case 'C': | |
printf("Good job!\n"); | |
break; | |
case 'D': | |
printf("You passed, but you can do better.\n"); | |
break; | |
case 'F': | |
printf("Uh oh, you failed. Time to study!\n"); | |
break; | |
default: // if grade is none of the above. | |
printf("Invalid grade entered.\n"); | |
} | |
// There is another expression we might use to assign a variable conditionally. | |
// That's called ternary operator, and it's syntax is | |
// | |
// (<condition> ? <if true> : <otherwise>) | |
// | |
// Here is an example assigning it to a variable | |
char *result = personAge >= 18 ? "might pass" : "will definitively not pass"; | |
// assuming person age is 15 the result variable will contain "will definitively not pass" | |
} // This is all we can do within a function, but in reality all we need are numbers, other abstractions | |
// are unnecesary, leave that to javascript developers. | |
// In here we can declare other functions, structs and enums, we can also talk about preprocessor statements | |
// which are scope agonostic. | |
// structs are a complex data structure we can use to organize data, it's a collection of memory addresses, and it's size | |
// is a sum of all the data inside the struct. | |
struct Person { | |
char *name; | |
int age; | |
}; | |
// Whatever you declare inside a struct will be allocated as normal variables, but since you have a collection | |
// of memory addresses you can access to the values in the same place from a C point of view. | |
// to declare a variable of this type we can do it the following way, this is a global variable that can be | |
// used after declaration. | |
struct Person person = {"john", 23}; // to construct a Person we pass the data in order of field declaration | |
// Alternatively if we don't want to write the struct keyword in every instance we can use typedef, which | |
// is a keyword that can only be used in top level to declare type expressions, | |
// | |
// The syntax is the following | |
// | |
// typedef <expression> <name>; | |
// | |
// for example we could declare a struct and a room. | |
typedef struct { | |
char *name; | |
int age; | |
} Person; | |
// and to instantiate the struct we might now use | |
Person person = {"maria", 46}; // values go by order of declaration. | |
// To declare a classroom we could do | |
typedef Person[] ClassRoom; | |
// and now declaring a variable of type ClassRoom would be like declaring | |
// an array of Person structs. | |
ClassRoom person = {{"juan", 20}, {"helena", 18}}; // we initialize the structs inside | |
// of the array as if it was another | |
// value. | |
// Structs also support named declarations which is specifying the name before | |
// the value, so we can declare it in the order we want. | |
Person person = { | |
.name = "pepe", // We assign the name that we declared in the struct earlier. | |
.age = 15 // Same with the age. | |
}; | |
// There is another way of storing data which is more linear, it is called union, | |
// basically unions store the data as subsequent pointers | |
typedef union { | |
char *name; | |
int age; | |
} UnionPerson; | |
// to instantiate it | |
UnionPerson unionPerson = {"john", 30}; | |
// how the data would look in this case, instead of having "random" allocated pointers, | |
// the data would look like this. | |
// { j o h n } { int left pad | 30 } | |
// 01001010 01101111 01101000 01101110 00000000 00000000 00000000 00011110 | |
// Basically we could manage this type safely with a void pointer advancing and | |
// retrieving the memory we want manually, for the first char* we would advance | |
// sizeof("john") which is 4 bytes and for the and the int which is 4 bytes more. | |
// There are also enums which are simply numerical constants, we might | |
// also use as flags. | |
// | |
// Flags can be used to store properties such as permissions, for example | |
// let's declare a permissions enum | |
typedef enum { // use typedef because we would need to use enum every instantiation. | |
Administrator = 1; // 00000001 | |
Read = 1 << 2; // 00000010 | |
Write = 1 << 3; // 00000100 | |
Send = 1 << 4; // 00001000 | |
} Permissions; | |
// Now we can merge these properties using bitwise operators for example | |
Permissions perms = Read | Write; // 00000110 // in here we check the bits that are 1 not the actual number | |
int hasAdmin = (Administrator & perms) != 0; | |
// 00000001 administrator | |
// 00000110 perms | |
// 00000000 & is 0, doesn't have the flag. | |
// In definition, enums are just numerical constants, a way to give names to numbers so they signify something. | |
// We can talk more deeply about function definitions now, a function is something you can call, as we saw | |
// with printf or scanf, those are functions too, to declare your own function you simply do | |
// <return type> <name>(<, separated arguments>) <body> | |
int sum(int left, int right) { // the function asks for 2 parameters, left and right. | |
return left + right; // and returns as result the sum of left and right, | |
// anything after a return statement is not run. | |
} | |
// to call it we can use | |
int result = sum(10, 20); // and that would result in 30, becasue | |
// what's stored in the variable is the expression | |
// after the return statement. | |
// normally pointers are passed to functions in order for functions to modify the pointer value, | |
// that can be useful for multiple values or when the function shouldn't return a value or it's not | |
// semantically well seen that it does. | |
// We can also chose to separate the function definition from the declaration, this is often done | |
// with header and c files, header files contain the declaration and c files contain the defintion | |
// this way what the user includes is more clean and structured. | |
// | |
// basicmath.h | |
int sum(int, int); | |
int multiply(int, int); | |
int subtract(int, int); | |
float divide(int, int); | |
// basicmath.c | |
#include <basicmath.h> | |
int sum(int left, int right) { | |
return left + right; | |
} | |
int multiply(int left, int right) { | |
return left * right; | |
} | |
int subtract(int left, int right) { | |
return left - right; | |
} | |
float divide(int left, int right) { | |
return left / right; | |
} | |
// Now we would be able to use that functions by linking basicmath.c and including basicmath.h in our source file, | |
// another use case is if we want to define the functions after our main method and want to use them within our main | |
// method, when we have a declaration we can use the function below that declaration, it will automatically | |
// try to find the definition and call it. | |
// Then there are pre processor statements, these allow us to modify our file conditionally based on system | |
// constants or include other files before compiling, these statement's can't interact | |
// with runtime code. | |
// | |
// Lets start with a #define | |
// | |
// A define statement lets us give a name to a piece of code we might repeat, basically a macro. | |
#define NULL (void *)0 | |
// In here we can conditionally use ;, because the result file will contain the rightmost value. | |
// in this case (void *)0 | |
// | |
// if we make an if statement | |
// | |
// if (ptr != NULL) | |
// | |
// that would be translated to | |
// | |
// if (ptr != (void *)0) | |
// | |
// and like that we can also undefine variables | |
#undef NULL | |
// NULL is already declared in stdlib.h, but that was an example. | |
// | |
// About including files, the included file will literally | |
// be copied and pasted wherever you use the statement, | |
// if the file contains a variable you can use #include | |
// to even include it inside a function the same | |
// way that was mentioned earlier. | |
// | |
// Normally including files is made above because | |
// these normally these declare functions | |
// and other top level statements. | |
// | |
// But in reality you are responsible for your | |
// design, so you do you. | |
// | |
// then there is #if, #else and #elif that let us | |
// tell the compiler to include a part of the code depending on the | |
// platform, for example with curses, in windows, linux... is ncurses | |
// in older macos is curses | |
// | |
// #ifdef to check variable availability | |
// #ifndef to check variable unavailability | |
#ifdef __APPLE__ | |
#include <AvailabilityMacros.h> // https://opensource.apple.com/source/xnu/xnu-2050.7.9/EXTERNAL_HEADERS/AvailabilityMacros.h.auto.html | |
#ifdef MAC_OS_X_VERSION_MIN_REQUIRED | |
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 | |
#include <curses.h> | |
#else | |
#include <ncurses.h> | |
#endif | |
#else | |
#include <curses.h> | |
#endif | |
#else | |
#include <ncurses.h> | |
#endif | |
// These are most variables that are predefined in the system. | |
// you can also define code in there, which will be included | |
// or not depending on whether the condition is true or false. | |
// | |
// there is another preprocesor statement you can use that's #error | |
// you could throw a compiler error based on a condition. | |
#ifdef __APPLE__ | |
#error "header not available in apple" | |
#endif | |
// And lastly there is the #pragma pre processor statement, this one lets us | |
// isue special commands to the compiler using a standarized method. | |
// | |
// https://gcc.gnu.org/onlinedocs/cpp/Pragmas.html | |
// | |
// | |
// | |
// | |
// Anyways, here is the tutorial, good luck! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment