It's important to mention some pre-preparations and tools needed to write basic C programs, such as:
After creating a C file (main.c
) in our text editor (IDE), the very basic structure of a C code would look like this:
#include <stdio.h>
int main ( void ) {
printf("Hello world!\n");
return 0;
}
Now let's break down each part of the code (non beginners are to skip this part):
#include <stdio.h> // Header file - standard input/output
/**
* Main function
* This function is the first one to be called after executing our C program
* @parameter is either void (can be left blank) or contains command line arguments
* in that case it would look like:
* int main(int argc, char *argv[])
**/
int main ( void ) {
printf("Hello world!\n"); // Function (stored in stdio.h) to print a string into standard output, \n prints newline
return 0; // int main() is a function, so it has to return an integer value
}
Cheat sheet, in other words, everything you need to know.
Data types
data type | description | range
-------------------------------------------
char | ASCII character | (signed) -128 to 127 or (unsigned) 0 to 255
short | 2 bytes integer | (signed) -32 768 to 32 767 or (unsigned) 0 to 65 5635
int | 2 or 4 bytes integer| short or long (depends on compiler)
long | 4 bytes integer | (signed) -2 174 483 648 to 2 147 483 647 or (unsigned) 0 to 4 294 967 295
long long | 8 bytes integer | (signed) -2^63+1 to +2^63-1 (unsigned) 0 to +2^64-1
float | floating-point (4B) | 1.2*10^-38 to 3.4*10^+38 (6 decimal places)
double | floating-point (8B) | 2.3*10^-308 to 1.7*10^+308 (15 decimal places)
long double | floating-point (10B)| 3.4*10^-4932 to 1.1*10^+4932 (19 decimal places)
Note: yes, C doesn't have string
type. To create a string in C, you have to create an array of characters char*
.
Also, short
is the same as
short int
and is the same as signed short int
. The same goes for long
. If not defined, the type will be automatically signed
.
Arithmetic operators
operator | description | example
------------------------------------------------------------
+ | addition | i = 5 + 3; (i is 8)
- | subtraction | i = 5 - 3; (i is 2)
* | multiplication | i = 5 * 3; (i is 15)
/ | division | i = 9 / 3; (i is 3)
% | modulation division | i = 5 % 2; (i is 1)
++ | increment (i = i + 1) | i = 0; i++; (i is 1)
-- | decrement (i = i - 1) | i = 5; i--; (i is 4)
Operators can be also used in a short way:
i += 5; // the same as i = i + 5;
i /= 5; // the same as i = i / 5;
...
Also, be careful when dividing. The result can be either integer or real number depending on data types you divide:
data types | result | example
-----------------------------------------------------
int/int | int | int i = 10; int j = 3; i/j = 3
int/double | real | int i = 10; double j = 3; i/j = 3.333...
double/double | real | double i = 10; double j = 3; i/j = 3.333...
double/int | real | double i = 10; int j = 3; i/j = 3.333...
increment/decrement:
operator | description | example
----------------------------------------------------------------------------
++i | increment before usage | i = 0; j = ++i; (i = 1 and j = 1)
i++ | increment after usage | i = 0; j = i++; (i = 1 and j = 0)
--i | decrement before usage | i = 5; j = --i; (i = 4 and j = 4)
i-- | decrement after usage | i = 5; j = i--; (i = 4 and j = 5)
Bitwise operators
operator | description | example
----------------------------------------------------------------------
<< | bit shift to the left | i = 5; i <<= 1; (i is 10)
>> | bit shift to the right | i = 10; i >>= 1; (i is 5)
& | bitwise AND | i = 1 & 2; (i is 0) NOTE: & is different from && which is logical AND
| | bitwise OR | i = 1 | 2; (i is 3) NOTE: | is different from || which is logical OR
~ | bitwise complement | i = ~5; (i is -6)
To work with bitwise operators, you need to understand binary first.
The best way to start is to write a simple calculator, don't you agree?
It will help us to learn working with the arithmetic operators and data types mentioned in the Cheat Sheets.
int x = 50;
int y = 30;
printf("The sum of %d and %d is %d\n", x, y, (x + y));
printf("The difference of %d and %d is %d\n", x, y, (x - y));
printf("The product of %d and %d is %d\n", x, y, (x * y));
if (y == 0) { // if second number is 0, we print error
printf("Division by zero!\n");
}
else { // else, we print result as floating-point
printf("The quotient of %d and %d is %lf\n", x, y, ((double)x / y)); // casted x to double for real number result
}
This is a simple "calculator" that will be improved later and the output would look like this:
The sum of 50 and 30 is 80
The difference of 50 and 30 is 20
The product of 50 and 30 is 1500
The quotient of 50 and 30 is 1.666667
Looks confusing? Let me explain..
The first argument of our printf()
function is the string to be printed, however we can also print the values of our variables by inserting format specifier (%d
) in the string and putting our wanted variable as the next argument - in this case an integer. In the case of division, I used %lf
instead of %d
, because I wanted to print the result as a floating-point (double in this case) whereas x and y are both integers. For the complete list of the format specifiers, please have a look at the following table:
specifier | description | data type | example
-------------------------------------------------------------------------------
%c | ASCII character | char | 'a'
%s | string | char* | "Hello, world!"
%hi | short signed int | short | -5000
%hu | short unsigned int | unsigned short | 64000
%i or %d | signed integer | int | 5000000
%u | unsigned int | unsigned int | 5000000
%li | long signed int | long | -2000000000
%lu | long unsigned int | unsigned long | 4000000000
%lli | long long signed int | long long | -5000000000
%llu | long long unsigned int | unsigned long long | 5000000000
%f or %F | digital notation | float | 1.200000
%g or %G | scientifit notation | float | 1.2
%e or %E | scientifit notation | float | 1.200000e+00
%a or %A | scientifit notation | float | 0x1.333334p+0
%lf or %lF | digital notation | double | 1.200000
%lg or %lG | scientifit notation | double | 1.2
%le or %lE | scientifit notation | double | 1.200000e+00
%la or %lA | scientifit notation | double | 0x1.3333333333333p+0
%x or %X | hexadecimal | | 1A2C
%o | octal | | 065
%p | pointer address | | 0x145484
To print a special character such as % in our output, we just need to backslash it:
printf("\n"); // prints newline
printf("\\n"); // prints "\n"
printf("%"); // throws warning, prints nothing
printf("\%"); // prints "%"
printf("\\"); // prints "\"
These control statements are very similar or the same in any other programming language so if you are not a beginner, you are to skip this part as well. The statements are:
if | else if | else
switch
for
while
do while
With these statements we can control the flow of the program and execute certain part of it if a certain condition is met etc..
Let's demonstrate it on a quick example:
#include <stdio.h>
int main ( void ) {
/* Some variable declarations */
int temperature = 30; // outside temperature in °C
double sunny = 0.3; // 0 - overcast, 1 - very sunny
typedef enum { YES, MAYBE, NO } myBool; // C doesn't have boolean data type, so let's create our own
myBool raining = NO; // is it raining?
myBool willGoOutside = MAYBE; // will I go outside?
myBool didIGetCandy = NO; // did I get a candy?
/* IF-ELSE statements */
if (temperature < 10) {
willGoOutside = NO;
}
else if (temperature >= 20 && temperature <= 30) {
if (sunny < 0.5 || raining == NO) {
willGoOutside = NO;
}
else {
willGoOutside = YES;
}
}
else {
willGoOutside = MAYBE;
}
/* SWITCH statement */
switch (willGoOutside) {
case YES: printf("I will go outside.\n"); break;
case MAYBE: printf("I don't know if I will go outside or not yet\n"); break;
case NO: printf("I am not going outside.\n"); break;
default: printf("Well... this won't happen."); // No need to put break; in last case
}
/* WHILE loop */
while (didIGetCandy == NO) { // Endless loop
printf("GIVE ME CANDY!\n");
}
/* DO-WHILE loop */
do {
printf("GIVE ME CANDY!\n");
} while (didIGetCandy == NO); // This will print GIVE ME CANDY once even if didIGetCandy was YES
/* FOR loop */
for (int i = 0; i < 50; i++) { // Executes 50 times
printf("Thank you!\n");
}
return 0;
}
There is no real sense in this code so don't try to think about it much. It's just to demonstrate the usage of these statements.
If you don't know what is enum as I didn't mention it in the data types cheat sheet, enum is a custom data type that can contains only given constant values. In this case, I created a data type called myBool consisting only of values YES, MAYBE and NO. The keyword typedef is so I don't have to write they keyword enum before every myBool declaration. In other words, enum is our own defined data type.
Reading and working with user's input.
The easiest way of reading user's input is with getchar()
function that reads the first character from standard input:
char c;
printf("Enter character: ");
c = getchar();
printf("Your character is %c\n", c);
results:
Enter character: a
Your character is a
Enter character: hello
Your character is h
As you can see, this function only reads the first and only one character of the input. To read the whole line, we will need to run this function in a loop until it meets the <Enter> key:
char c;
printf("Enter text: ");
while (c != '\n') { // \n represents newline
c = getchar();
printf("%c", c);
}
output of the above code:
Enter text: hello world!
hello world!
Now, let's improve our calculator with this function!
#include <stdio.h>
int main ( void ) {
char c, op;
int x = 0;
int y = 0;
printf("Enter first number: ");
while (c != '\n') { // while reading the whole line
c = getchar();
if (c != '\n') { // if the character isn't a newline
x = x * 10 + (c - '0'); // (c - '0') converts ASCII character to integer
// this equation creates a single number from many inputted numbers
// for example [1], [5] and [0]:
// 0 * 10 + 1 = 1
// 1 * 10 + 5 = 15
// 15 * 10 + 0 = 150
}
}
printf("Enter operand (+, -, *, /): ");
op = getchar(); // reads operand
if (op != '+' && op != '-' && op != '*' && op != '/') { // If operand is not valid
printf("Error: wrong operand\n");
return 1; // exit program
}
getchar(); // get (and 'delete') the newline after the operand
printf("Enter second number: ");
do { // Using do-while here because c is still a newline
c = getchar();
if (c != '\n') {
y = y * 10 + (c - '0');
}
} while (c != '\n');
// Printing results
switch (op) {
case '+': printf("The sum of %d and %d is %d\n", x, y, (x + y)); break;
case '-': printf("The difference between %d and %d is %d\n", x, y, (x - y)); break;
case '*': printf("The product of %d and %d is %d\n", x, y, (x * y)); break;
case '/':
if (y == 0) {
printf("Division by zero!\n");
break;
} else {
printf("The quotient of %d and %d is %lf\n", x, y, ((double)x / y));
break;
}
}
return 0;
}
Even though this code is not perfect (number validation etc..), it still looks promising and should work fine. Let's test it out:
Enter first number: 5
Enter operand (+, -, *, /): +
Enter second number: 20
The sum of 5 and 20 is 25
Enter first number: 1243
Enter operand (+, -, *, /): -
Enter second number: 200
The difference of 1243 and 200 is 1043
Enter first number: 3
Enter operand (+, -, *, /): *
Enter second number: 100
The product of 3 and 100 is 300
Enter first number: 150
Enter operand (+, -, *, /): /
Enter second number: 6
The quotient of 150 and 6 is 25.000000
Enter first number: 13
Enter operand (+, -, *, /): /
Enter second number: 0
Division by zero!
Enter first number: 10
Enter operand (+, -, *, /): /
Enter second number: 3
The quotient of 10 and 3 is 3.333333
Enter first number: 10
Enter operand (+, -, *, /): what
Error: wrong operand
Yes, it works just fine.
Another way of reading user's input is with the scanf()
function, which also allows us to read the input in a formatted way using the format specifiers.
int i;
scanf("%d", &i);
First argument of the scanf()
function defines the format of the input we ant to read and following arguments are the addresses where to store the input (that's why it's important to use &i instead of just i). We can easily rewrite our simple calculator using this function:
#include <stdio.h>
int main ( void ) {
char c;
int x, y;
scanf("%d %c %d", &x, &c, &y); // first number - operand - second number
switch (c) {
case '+': printf("The sum of %d and %d is %d\n", x, y, (x + y)); break;
case '-': printf("The difference of %d and %d is %d\n", x, y, (x - y)); break;
case '*': printf("The product of %d and %d is %d\n", x, y, (x * y)); break;
case '/':
if (y == 0) printf("Division by zero!\n");
else printf("The quotient of %d and %d is %lf\n", x, y, ((double)x / y));
break;
default: printf("Wrong operand.\n"); break;
}
return 0;
}
Looks better than the first one right? Let's check out the results:
5+20
The sum of 5 and 20 is 25
1243 - 200
The difference of 1243 and 200 is 1043
3*100
The product of 3 and 100 is 300
150/6
The quotient of 150 and 6 is 25.000000
13/0
Division by zero!
10/3
The quotient of 10 and 3 is 3.333333
10 what
Wrong operand!
In order to make sure the inputted text format is valid to the one we wanted, it's important to know that this function actually returns the number of successfully filled items in the argument list. In this case, we should check for 3 as we wanted to fill 3 variables in our scanf()
function:
if (scanf("%d %c %d", &x, &c, &y) != 3) {
printf("Invalid format.\n");
return -1;
}
result:
hello+59
Invalid format.
You may ask why to use getchar() if we can use scanf() which seems to be more elegant and better in many ways. Why did we even bother with the getchar() example?
Well, both of these functions are different and can have a better use in different cases than the other. It's always up to the programmer to think of the best solution and choose the best methods to approach it. Remember that no information is useless. It's always about how you will use it and work with it.
Before we get into reading and writing into a file in C, let me mention something about working with files for C programming first. To know how to work with files in C can be very handy not only for certain tasks that requires it, like generating files, writing contents into a file or create temporary files... it can also be very useful for testing the actual program. For example, our calculator from my previous article, when we run the program to see how it works, we test all possible input cases we can think of - addition, subtraction, multiplication and division, giving it some random numbers to check if it works correctly. Also testing how it handles certain cases like dividing by zero and invalid inputs... Now imagine doing this kind of "testing" every time we do minor changes to the code and run the program. That would be time consuming as hell. One of the programmer's job is to think of the most effective solution to any problem. And so, we can just write these test cases into some test files and pass those files to our program as the input:
// input-test.txt (testing data)
5+20
1243 - 200
3*100
150/6
13/0
10/3
10 what
// output-test.txt (expected output of the program)
The sum of 5 and 20 is 25
The difference of 1243 and 200 is 1043
The product of 3 and 100 is 300
The quotient of 150 and 6 is 25.000000
Division by zero!
The quotient of 10 and 3 is 3.333333
Wrong operand!
(In Bash (UNIX shell)), to pass the input-test.txt file to our program as input:
./a.out < input-test.txt
And to test, whether the output of our program is same of different from the expected one in output-test.txt, we can use diff program:
diff output.txt <(./a.out < test.txt)
If we want to write all the program's output into a file, we just do:
./a.out > output.txt
Okay, I digressed a bit. Back to the main topic.
The basic way to work with files in C is to use the data type FILE *
(the * stands for pointer, for more information about pointers scroll down).
Opening a file:
FILE * fw = fopen("file1.txt", "w"); // opens file1.txt for writing
FILE * fr = fopen("file2.txt", "r"); // opens file2.txt for reading
First argument of the function fopen()
defines the file to be opened. We can open the file for either reading or writing, which is defined in the second argument ("w"
for writing, "r"
for reading).
Basic functions to work with the file:
char c = getc(fr); // reading a character from the file, similar to getchar()
putc(c, fw); // writing a character to the file, similar to putchar()
fscanf(fr, "%d", &i); // formated reading from the file, similar to scanf()
fprintf(fw, "%d", i); // formated writing to the file, similar to printf()
It's important to always close the file after we finish our work with it.
fclose(fr);
Simple example of a program, that reads the first character from file DATA.txt and writes that character into RESULT.txt as equivalent ASCII number.
#include <stdio.h>
int main() {
FILE *fr, *fw;
char c;
fr = fopen("DATA.txt", "r");
fw = fopen("RESULT.txt", "w");
c = getc(fr);
fprintf(fw, "%d", c);
fclose(fr);
fclose(fw);
return 0;
}
It's good to mention the end-of-file indicator (EOF
or feof()
).
// reads the whole content of file in fr
// and writes this content into a file in fw
// feof() stands for end-of-file indicator
// could be also replaced with EOF constant:
// while ((c = getc(fr)) != EOF) {...}
while (c = getc(fr), !feof(fr)) {
putc(c, fw);
}
Also keep in mind that when you open or close a file you were working with, it's not always guaranteed that this action will succeed. The file you are trying to open might not exist or you don't have the permission to etc... there are many various reasons why it wouldn't have succeeded. So it's a good habit to always check if the file was successfully opened and closed and proceed depending on it's results.
// if fopen() fails, it returns NULL
if ((fr = fopen("DATA.txt", "r")) == NULL) {
printf("File DATA.txt failed to open.\n");
return 1;
}
...
// if fclose() fails, it returns EOF
if (fclose(fr) == EOF) {
printf("File couldn't be closed.");
return 1;
}
Pointers are the core, the heart and soul of C. Unless you understand pointers and know how to work with them, you can never claim yourself as being a C programmer or not even being someone with the actual knowledge of C (anyone can write hello world).
So, what is a pointer? To put it simply, the difference between a normal variable and pointer is that a variable stores some kind of a value whereas a pointer stores the memory address of a certain variable (every variable has allocated space in memory, this space has an address). To be more concrete, a pointer points to the first address of the allocated memory of a certain variable. It's easier to understand from examples.
#include <stdio.h>
int main() {
int *p_i; // declaration of pointer p_i that will point to an integer
int i = 10; // declaration of integer i
p_i = &i; // assigning the address of i (not the value) to pointer p_i (for example address 0x1)
i = 15; // change the value of i
printf("%d\n", *p_i); // prints 15 (the value stored in address 0x1 is 15)
printf("%p\n", p_i); // prints the address of i (0x1 for example)
printf("%p\n", &i); // equivalent to the printf() above, prints the address of i
*p_i = 30; // changing the value in 0x1 to 30
printf("%d\n", *p_i); // prints 30 (the value stored in address 0x1)
printf("%d\n", i); // prints 30 (i is stored in 0x1 of which value we changed)
return 0;
}
Note: When printing an address using the %p
specifier, we should correctly cast the argument to (void*)
printf("%p\n", (void*)&i);
The reason why pointers are the heart of C is because we use them everywhere. From working with arrays, functions, strings to making the program faster and more efficient with the right use of pointers.
When working with arrays, we have to keep in mind that array indexes are counted from 0 (there are many cringy jokes on the internet about this).
int x[10]; // defines an array x of 10 integers
x[0] = 5; // first element is at index 0
x[9] = 150; // last element is at index 9
When we create an array, we actually create a pointer that points to the address of the first element of the allocated array. In other words, pointers are actually arrays with undefined sizes (the size can be allocated dynamically, see Dynamic Arrays bellow).
Note: Not really, but it's fine to think of it that way when working with arrays.
#include <stdio.h>
int main() {
int x[2];
int *p_x;
p_x = x; // p_x and x points exactly to the same address
p_x[0] = 150;
x[1] = 13;
/* These callings are equivalent (prints 150) */
printf("%d\n", x[0]);
printf("%d\n", *x);
printf("%d\n", p_x[0]);
printf("%d\n", *p_x);
/* These callings are equivalent (prints 13) */
printf("%d\n", x[1]);
printf("%d\n", *(x+1));
printf("%d\n", p_x[1]);
printf("%d\n", *(p_x+1));
return 0;
}
Static arrays, or statically allocated arrays are the classic arrays where we define their sizes in the code and work with them as they are.
#include <stdio.h>
#define MAX 150
int main() {
int x[10]; // defines array x of 10 elements
int y[MAX]; // defines array y of 150 elements
int z[5] = { 5, 3, 8, 4, 6 }; // explicit declaration
// filling all elements of array x with value 0
for (int i = 0; i < 10; i++) {
x[i] = 0;
}
// filling all elements of array y with user input
for (int i = 0; i < MAX; i++) {
scanf("%d", &y[i]);
}
// printing all elements of array y
for (int i = 0; i < MAX; i++) {
printf("%d ", y[i]);
}
// prints 5 3 8 4 6
for (int i = 0; i < 5; i++) {
printf("%d ", z[i]);
}
return 0;
}
Or dynamically allocated arrays are arrays, that we allocate during the program run and can change their allocated sizes during the process as well. These arrays, unlike statically allocated ones, allocates only what's needed. If we create an array and we don't know how many elements we should allocate beforehand, we should allocate them dynamically as if we allocate too little, it won't work correctly (overflow) and if we allocate too much, the program will eat too much memory for nothing.
To define a dynamically allocated array, we create an empty pointer first:
int *p_arr;
After that, we can allocate certain block of memory with malloc()
or calloc()
function from stdlib.h
:
#include <stdlib.h> // contains malloc/calloc/realloc functions
...
p_arr = (int *) malloc(10 * sizeof(int));
// or equivalently
p_arr = (int *) calloc(10, sizeof(int));
We have just allocated enough memory for 10 integers (10 times size of integer), in other words, we have just created an integer array p_arr with the size of 10 elements. Accessing these elements can be done the same way as with static arrays:
p_arr[0] = 10;
p_arr[9] = 150;
And after we finish working with our dynamic array or we don't need it anymore, it's good to always free it's allocated memory:
free(p_arr); // basically deletes the array
Let's try it on a simple example - a program, that reads all input as integers and returns their sum. Since we don't know how many numbers will be inputted, we will let the user tell us first:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p_arr;
int count = 0;
int sum = 0;
printf("How many numbers to be sum up: ");
if (scanf("%d", &count) != 1) {
printf("Invalid input.\n");
return 1;
}
p_arr = (int*) malloc(count * sizeof(int));
printf("Enter %d numbers:\n", count);
for (int i = 0; i < count; i++) {
if (scanf("%d", &p_arr[i]) != 1) {
printf("Invalid input.\n");
return 1;
}
}
for (int i = 0; i < count; i++) {
sum += p_arr[i];
}
printf("Sum of all entered numbers is: %d\n", sum);
free(p_arr);
return 0;
}
See the above output:
How many numbers to be sum up: 5
Enter 5 numbers:
1
2
3
4
5
Sum of all entered numbers is: 15
Of course, if we wanted to just sum up all the numbers, we could have came up with a better method and didn't need to use any array at all and it would have been more efficient (no need to allocate any memory). But this example is about arrays so of course I will demonstrate it on them.
Let's take a look on an example where not even the user himself knows how many numbers he will give us to sum up. In this case, we have no other choice but to allocate the memory on the go. Let's allocate 10 numbers first and every time the user input exceeds it, we allocate additional 10 numbers again:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p_arr; // our dynamic array
int x; // user inputted number
int maxSize = 0; // maximum allocated size
int currentSize = 0; // current amount of filled elements in our array
int sum = 0; // resulted sum
printf("Enter numbers:\n");
while (scanf("%d", &x) == 1) { // while we read the input
if (currentSize >= maxSize) { // if the current amount of elements is already at the maximum
int *tmp; // we create a temporary array
maxSize += 10; // extends the maximum allocated size
tmp = (int *) malloc(maxSize * sizeof(int)); // allocate tmp with this size
for (int i = 0; i < currentSize; i++) { // fill our tmp with our current p_arr data
tmp[i] = p_arr[i];
}
free(p_arr); // free the memory of p_arr array
p_arr = tmp; // and assign our tmp to it (p_arr becomes tmp)
}
p_arr[currentSize++] = x; // if everything is fine, add an input into our array
}
for (int i = 0; i < maxSize; i++) {
sum += p_arr[i];
}
printf("Sum of all entered numbers is: %d\n", sum);
free(p_arr); // don't forget to free even if it's at the end of the program
return 0;
}
Looks complicated? Don't worry. This example was just to help you understand how resizing (reallocating) a dynamically allocated array during the program's run works. C's stdlib.h
has a function called realloc()
, that actually does the exact same thing as we did above.
Let's rewrite the program above to our new version using realloc()
:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p_arr;
int x;
int maxSize = 0;
int currentSize = 0;
int sum = 0;
printf("Enter numbers:\n");
while (scanf("%d", &x) == 1) {
if (currentSize >= maxSize) {
maxSize += 10;
p_arr = (int*) realloc(p_arr, maxSize * sizeof(int));
}
p_arr[currentSize++] = x;
}
for (int i = 0; i < maxSize; i++) {
sum += p_arr[i];
}
printf("Sum of all entered numbers is: %d\n", sum);
free(p_arr);
return 0;
}
Much better right?
And finally, the same as with the file opening and closing, it's a good habit to check if memory allocation was successful as well. In case the memory is full or for some other reasons the allocation fails and you proceed to work with it as if it didn't, your program might eventually crash.
int *p_arr = (int*) malloc(10 * sizeof(int)); // allocate memory
if (!p_arr) { // if it fails
free(p_arr); // free it's space
printf("Allocation failed.\n"); // return error
return 1;
}
An array is basically 1D - one dimensional. It's elements are stored linearly. We can, however, declare and work with 2D (table/grid/matrix) and more dimensional arrays as well.
When declaring a 2D array, we define an array of which each of it's element is actually another array.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int x[10][10]; // 2D array of 10*10 elements
int y[10][10][5]; // 3D array of 10*10*5 elements
int z[3][2] = {
{1, 2},
{3, 4},
{5, 6}
}; // explicit declaration of 2D array of 3*2 elements
int **dynamic; // this is gonna be dynamically allocated 2D array using pointer to pointer
dynamic = (int**) malloc(3 * sizeof(int*)); // allocated 3 pointers
for (int i = 0; i < 3; i++) {
dynamic[i] = (int*) malloc(2 * sizeof(int)); // allocate 2 integers to each pointer
} // finally, we have allocated 3*2 elements
// print values of z
printf("Values of z:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
printf("%d ", z[i][j]);
dynamic[i][j] = z[i][j] * 2; // filling 'dynamic' array
}
printf("\n");
}
// print values of our dynamic array
printf("Values of dynamic:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
printf("%d ", dynamic[i][j]);
}
printf("\n");
}
for (int i = 0; i < 3; i++) { // free memory of each element
free(dynamic[i]);
}
free(dynamic); // finally free the memory of the whole array
return 0;
}
output:
Values of z:
1 2
3 4
5 6
Values of dynamic:
2 4
6 8
10 12
If you read the first part of this serial, you already know that C doesn't have explicit data type for strings. Instead, strings are represented as an array of characters in C. Or in other words, strings are represented as:
// string "Hello" is equal to
char str[6] = { 'H', 'e', 'l', 'l', 'o', '\0' }; // '\0' indicates the-end-of-string (similar to EOF in files)
String declaration can be done in an easier way than the one above:
char str1[6] = "Hello"; // compiler knows it's a string and will add '\0' automatically
char str2[] = "Hello, world!"; // compiler will allocate the size automatically during this initialization
const char *str3 = "Hello, my beautiful world!"; // constant string
C also has some handy functions to work with strings stored in the header file string.h
, the functions are:
#include <string.h>
...
strlen(str); // returns the length of given string
strcpy(str, "Hello"); // copies the content of the second argument string to the string from first argument (copies "Hello" to str)
strcat(str, " world"); // appends the content of the second argument string to the string from first argument (if str was "Hello", it will be "Hello world" now)
strchr(str, 'e'); // searches for the character (second argument) in a string (first argument) and returns a pointer to it's location if found or NULL if not
strcmp(str1, str2); // compares 2 strings and returns 0 - identical, >0 - str1 is larger, <0 str2 is larger (lexicographically)
strstr(str1, str2); // searches for a substring (second argument) in a string (first argument) and returns a pointer to it's location if found or NULL if not
What happens when we create a string of 5 characters (char str[] = "Hello";
) and fill it with a string of 12 characters? (strcpy(str, "Hello, world");
)
Well, the same thing as if we wanted to copy an array of 14 elements into an array of 6 elements. It overflows and the program might eventually crash. The good way to avoid this is to use the secure versions of these functions:
strncpy(str, "Hello, world!", 6); // same as strcpy but copies up to 6 characters of the second argument string
// we can eventually use it as
strncpy(str1, str2, strlen(str1));
Why should we use functions? Because we are programmers and we write legible, well-structured and non-redundant code. For this purpose, we use functions in C. We have been working with functions all this time (printf(), getc(), fopen(), strcpy(), malloc(), strlen(), ...
) but it would be good to be able to write our own functions as well.
Function declaration has this format:
datatype function_name(parameters) {
body of the function
}
Example of a function that returns the larger of 2 given numbers:
int larger(int x, int y) {
if (x > y) return x;
else return y;
}
Calling our function in main() program:
#include <stdio.h>
int larger(int x, int y) {
if (x > y) return x;
else return y;
}
int main() {
int x = 150;
int y = 85;
int z = larger(x, y);
printf("%d\n", x); // prints 150
printf("%d\n", larger(64, 88)); // prints 88
return 0;
}
When we pass variables as function arguments this way, the function creates it's own local copy of the variable and works with the copy. So even if we were to change the value of a variable with the same name inside the function, it won't affect the original one:
void playWithNumbers(int x) { // a procedure (function without return value)
x = 989456; // local variable x doesnt affect the variable x in main() function
}
int main() {
int x = 5;
playWithNumbers(x);
printf("%d\n", x); // prints 5
return 0;
}
It's safe as it doesn't affect out variables, we don't have to worry about variable names being the same in both functions etc. However, it can also be slow as it has to copy the content of the variable into the local one (which is perfectly fine for small data types like numbers). Imagine copying a large string or large data structure (see Structures bellow) every time we call the function. In some cases, it's better to pass not the value of the variable, but it's address instead and work with pointers to these addresses inside the function instead of their copies. The function then can change the value of our outside variable as well. Check it out on an example of a function, that will swap the values of 2 given variables:
#include <stdio.h>
void swap(int *p_x, int *p_y) { // create local pointers
int tmp;
tmp = *p_x;
*p_x = *p_y;
*p_y = tmp;
}
int main() {
int x = 5;
int y = 40;
swap(&x, &y); // pass addresses of our variables
printf("x: %d\n", x); // prints x: 40
printf("y: %d\n", y); // prints y: 5
return 0;
}
And finally the last topic of this serial. Structure is a special data type composed of various elements of any datatype (unlike arrays where every element has to be of the same datatype). If you are familiar with OOP (object oriented programming), you can view structures as something similar to objects.
#include <stdio.h>
#include <string.h>
struct person { // first way to define a structure
char name[10];
int age;
double height;
double weight;
};
typedef struct { // second way
char name[10];
char race[20];
int age;
} dog;
int main() {
struct person Peter;
strncpy(Peter.name, "Peter", 10);
Peter.age = 18;
Peter.height = 168.5;
Peter.weight = 64.2;
dog Rex; // no need to put struct at the start thanks to typedef
strncpy(Rex.name, "Rex", 10);
strncpy(Rex.race, "German Shepherd", 20);
Rex.age = 5;
printf("===== Person 1 =====\n");
printf("Name: %s\n", Peter.name);
printf("Age: %d\n", Peter.age);
printf("===== Dog 1 =====\n");
printf("Name: %s\n", Rex.name);
printf("Race: %s\n", Rex.race);
return 0;
}
Output:
===== Person 1 =====
Name: Peter
Age: 18
===== Dog 1 =====
Name: Rex
Race: German Shepherd
Because structures can be really large, we usually work with pointers to them (to their addresses) than with their actual contents (for example when passing structures to functions - see Functions above).
struct Person *p_peter = &peter;
(*p_peter).age = 18; // accessing the attribute of the structure through pointer p_peter
p_peter->age = 18; // can be actually written this way as well
Thank you for reading up to this point. Again, if you have any questions, comments or found any errors regarding this article, contact us directly via our Facebook.