Suppose you used to code a lot and you want to get back in the swing of things.
Or you took a Scratch class in 3rd grade with the park district, and you want to write "real" code.
If so, the C programming language is probably the best way to get your foot in the door of programming.
This document is intended for those who have a basic understanding of datatypes, for
, while
, if
, and else
statements, and functions.
Prerequisites
-
We’ll be writing code, so you’ll need a text editor. Visual Studio Code is great. If you’re looking for something more minimalist, Notepad++ might be good for you.
-
We’ll also be running the code we write, so you’ll need a C compiler; I’ll use GCC.
-
If you have a Linux machine, just run the command
sudo apt install gcc
, enter your password, and wait for the installation.
-
Linux users: After installing gcc , make sure it works! Open a terminal and run the gcc command. If you get some sort of Fatal Error , you installed it correctly; it’s just getting mad that you called the compiler but didn’t give it anything to compile.
|
-
If you have Windows, see the next sections.
WINDOWS USERS ONLY: Installing WSL and configuring file name extensions
WSL, or the Windows Subsystem for Linux, lets you run a Linux terminal in the Windows environment. There are two ways to install it:
-
You can install from the command line (easier):
-
Press Windows key + R
-
Type
cmd
-
Press Ctrl+Shift+Enter
-
Type
wsl --install
in the command window
-
-
You can also find more information on it, including a download link, here.
After installing, you can run the wsl
command from the command line and you get a Linux terminal! Then you can run sudo apt install gcc
, enter your password, and you’re all set to compile C code.
After installing gcc , make sure it works! Open a terminal and run the gcc command. If you get some sort of Fatal Error , you installed it correctly; it’s just getting mad that you called the compiler but didn’t give it anything to compile.
|
We’ll be messing around with file name extensions as well. In Windows, you’ll need to open your file manager, go to the "View" tab, and make sure "File name extensions" is checked.
Our First C Program
Make a text document, call it HelloWorld.c
, then paste the following code into it:
#include <stdio.h>
int main() {
printf("Hello world!\n");
return 0;
}
Open your terminal (and run the wsl
command if you’re on Windows), then enter the following commands:
gcc HelloWorld.c
./a.out
You should see Hello World!
print to the screen.
With this context, we’ll talk more about how things work in C.
1. Variables and Arrays
The nuts and bolts of any programming language is the information it stores, so it’s natural that the first thing we talk about is variables.
1.1. Data Types
C has many different datatypes; unlike Python, JavaScript, and various other high-level languages, each variable has a type, and that type never changes.
For example, suppose we want to make an integer.
Then we’d write int myInteger = 10;
.
Make sure you end every line with a semicolon. Otherwise C complains, and it’s never fun when it does that. |
Or perhaps we need to store the value of \(\pi\),
then we could write float pi = 3.14159
;
float
is used to store any real number.
We could also initialize a variable using char firstLetter = 'a';
, for example.
When writing a character, make sure to use single quotes. Otherwise C complains, and it’s never fun when it does that. |
Common data types are as follows:
Datatype | Used in… |
---|---|
|
Integers |
|
Integers (Really big or really small ones) |
|
Integers (Super big or super small ones) |
|
Real numbers (lower precision, usually preferable when you need to conserve RAM) |
|
Real numbers (higher precision, usually preferable in PC programs) |
|
Character (A single character) |
1.2. Arrays
Now we can generate one variable, but what if we want to generate multiple variables at a time? Some standard ways to make an array in C is:
int myIntArr[40];
float myPreFilledFloatArr[3] = {3.14, 2.71, 1.41};
float anotherPreFilledArr[] = {'a', 'b', 'c', 'd', 'e'};
Some quirks about C arrays:
-
When making an array, all elements must have the same data type.
-
After making an array, you can’t go back and change the length.
2. stdio
functions
2.1. printf
and scanf
2.1.1. printf
The printf
function is the C function that prints things to the console.
We saw it print a string, but we can print variables as well.
#include <stdio.h>
int main() {
for(int i = 0; i < 10; i++) {
printf("We will now print the integer %d.\n", i);
}
return 0;
}
Compile this, and run it; note that we’re using a for
loop.
As the code is written, it will execute everything in the loop with
different values of i
, starting at 0, then 1, 2, and so on, up to and including 9.
You may be thinking, "Why doesn’t the loop execute for i=10
?"
The three statements determine what happens in the loop.
Click for a more detailed description of for loops.
The loop has three statements, and they function like so:
-
The first statement,
int i = 0
, executes once, when the loop starts. -
The second statement,
i < 10
, is the condition that, when true, executes the loop. When false, it exits the loop. -
The third statement,
i++
, executes at the end of every loop. This statement is equivalent toi = i+1
.
2.1.2. "Percent codes"
Note how we still printed a string, but we included the phrase %d
.
When you use this phrase, you need to tell printf
what integer to replace %d
with. That’s why we include i
in our printf
statement.
Different datatypes get different percent codes; the int
datatype gets %d
.
For more on percent codes, see this
table,
but the following table lists the common ones:
Percent code | Datatype |
---|---|
|
|
|
|
|
|
|
|
|
String (more on these later) |
2.1.3. scanf
If printf
is the quintessential C output function, then
scanf
is the quintessential C input function.
Compile and run this program:
#include <stdio.h>
int main() {
int num;
printf("Enter a number: ");
scanf("%d", &num);
num++;
printf("Your number plus one is %d.\n", num);
return 0;
}
Note that the program will stop and get a number from the user, and that percent codes are back! (They aren’t going anywhere; get used to them.)
Make sure you include the ampersand in your scanf .
&num is the address in RAM of num .
scanf doesn’t care about the value of num ,
it cares about where num is stored,
which is why we pass the address as the argument.
|
Also note that you can get multiple variables in one scanf
statement;
for example, the following code will grab multiple int
s from the user:
#include <stdio.h>
int main() {
int num1, num2, num3;
printf("Enter three numbers separated by spaces.");
printf("The third number should be 69: ");
while(1) {
scanf("%d %d %d", &num1, &num2, &num3);
if(num3 == 69) {
break;
} else {
printf("The third number wasn't 69. Please try again: ");
}
}
printf("The three numbers: %d, %d, and %d\n", num1, num2, num3);
return 0;
}
Note that this program contains a while
loop.
For more on while
loops, click here.
The while
loop runs "while" the condition in parentheses is true.
Normally, there’d be some expression with variables that has a chance of being true.
However, since 1
is always true, it’ll execute forever.
You might think this is a problem, but since there’s a break
statement
inside the loop, once the break
statement executes, we’ll be forced to
exit the loop.
3. Random Number Generation
To generate a random number, we need to include some libraries:
#include <stdlib.h>
#include <time.h>
…and use some functions:
-
rand()
: Takes no arguments; returns a random 32-bit integer, i.e. an integer between \(-2^{31}\) and \(2^{31} - 1\). -
srand(unsigned int seed)
: Takes an unsigned 32-bit integer, i.e. an integer between \(0\) and \(2^{32} - 1\). -
time(NULL)
: Time, in seconds, since Jan 1, 1970, UTC.
3.1. Ok, but what does this mean in practice?
Step 1: set the seed to how many seconds have passed since Jan 1, 1970.
Only do this once! Call it as one of the first things in main , or something.
|
srand(time(NULL));
Step 2: Whenever you need a random number, use rand()
to get a random number between
\(-2^{31}\) and \(2^{31} - 1\).
Using modular arithmetic and a little ingenuity,
we can make a random-integer function:
/*
function randint(int a, int b)
takes two integers, a and b,
where a < b,
and returns a random number between a (inclusive)
and b (exclusive).
*/
int randint(int a, int b) {
return (rand()%(b-a)) + a;
}
4. Project: The High-Low Game
At this point you have all of the skills you need to make the High-Low Game. The rules of this game are as follows:
-
The computer will pick a number from 0 to 100.
-
The player will guess a number.
-
The computer will tell the player if the number is "Too High", "Too Low", or "Correct".
-
This will continue until the player guesses the correct number.
If you get stuck, click to reveal the solution.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
// set the random seed
srand(time(0));
// correct answer
int correctAnswer = rand() % 101;
// set the guess to an illegal value
// that will never be correct
int userGuess = -1;
while(correctAnswer != userGuess) {
printf("Enter a number from 0 to 100: ");
scanf("%d", &userGuess);
if(userGuess > correctAnswer) {
printf("Too high!\n\n");
} else if(userGuess < correctAnswer) {
printf("Too low!\n\n");
} else {
printf("Correct!\n\n");
}
}
printf("The correct answer was %d.\n", correctAnswer);
return 0;
}
If you want to start this program from scratch, great! But if you’re not sure where to begin, check out this spooky scary skeleton code.
5. Strings
We’ve talked about almost every datatype so far, except for strings. But I have a good excuse: strings do not exist in C. Instead we have to deal with character arrays.
Let’s make a program where we initialize a string and examine it.
#include <stdio.h>
int main() {
char msg[] = "Hello World!";
printf("The message is: %s", msg);
return 0;
}
Notice that when you compile and run this program, you will see that strings print exactly like you expect.
5.1. How to deal with strings
First, we need to #include <string.h>
. This will give us plenty of functions to easily deal with strings.
5.1.1. sprintf
sprintf
lets us printf
to strings instead of the console.
See the following code snippet:
char msg[40]; //Initialize a buffer
int myInt1 = 5;
double myDouble1 = 3.1415;
sprintf(msg, "myInt1 = %d; myDouble1 = %lf\n", myInt1, myDouble1);
Then the string stored in msg
is "myInt1 = 5; myDouble1 = 3.141500"
.
5.1.2. sscanf
sscanf
lets us parse a string as some other data type,
similar to how scanf
lets us parse user input to the console.
See the following code snippet:
char msg[] = "15 3.1415";
int myInt;
float myFloat;
sscanf(msg, "%d %f", &myInt, &myFloat);
After running this code snippet, the variables are:
-
myInt = 15
-
myFloat = 3.1415
5.1.3. Other miscellaneous string.h
functions
…can be found here. These functions are a lot simpler, and each function pretty much does what it says on the tin.
5.2. Aside: safe alternative to scanf
One disadvantage to scanf
is that it’s not considered safe.
A user could input an obscenely long string, and C would automatically parse all of it,
leading to some weird stuff.
The generally accepted alternative has two lines of code:
-
fgets(buffer, length, stdin);
wherebuffer
is a character array,length
is the maximum amount of characters we will grab, andstdin
is just something you write [1]. -
sscanf
is used to parsebuffer
into the desired variables.
6. Project: Mastermind
At this point you should have all the info you need to code the game Mastermind. We’ll play with the following rules:
-
The computer generates a (ordered) list of 4 integers from 0 to 9.
-
The user inputs four numbers, with a space separating them. Use
fgets
to store the user input as a string, then usesscanf
to parse the input into fourint
variables (feel free to use an array here.) -
Then determine how many hits and blows the player’s guess has:
-
If a digit is correct and in the right space, the player gets a "hit".
-
If a digit is correct, but in the wrong space, the player gets a "blow".
-
-
You can play the game here with colors instead of digits.
Note that I won’t include a "solution" here; if you coded the game of Mastermind, and it works, then it’s a "correct" solution.
7. Pointers
We touched on pointers when we discussed scanf
, sscanf
, etc.
Recall that the syntax looked something like this:
int myInt;
double myDouble;
scanf("%d %lf", &myInt, &myDouble);
The ampersand gives us the addresses in RAM of myInt
and myDouble
.
In other words, they point to the location of their respective variables
and are initialized with the data type int*
, double*
, char*
, etc.
7.1. Pointer operations
Let intPtr
be an integer pointer and let myInt
be an integer.
In other words, intPtr
has type int*
and myInt
has type int
.
Expression | Meaning |
---|---|
|
A pointer to where |
|
The value stored wherever |
|
A pointer to where |
|
Impossible; the compiler literally won’t let you do it. |
7.2. You’ve used pointers before
…you just didn’t know it.
Suppose you have a line of code:
int myInts[] = {1, 2, 3, 4};
What is the actual value stored in myInts
? You can’t store four values in one value… so you’re actually storing a pointer to the first element! Run this code to demonstrate what’s going on behind the hood:
#include <stdio.h>
int main() {
int myIntArray[] = {6, 2, 1, 5, 8, 69, 420};
// Let's printf index 0 the old-fashioned way:
printf("The element with index 0 is: %d\n", myIntArray[0]);
// Now let's generate a pointer to the zeroth element
printf("But there's another way to find the element with index 0: %d\n\n", *myIntArray);
// Let's printf index 5 the old-fashioned way:
printf("The element with index 5 is: %d\n", myIntArray[5]);
// Now let's generate a pointer to the fifth element
// It's going to be 5 spaces ahead of the zeroth element
int* ptrToFifthElement = myIntArray + 5;
printf("But there's another way to find the element with index 5: %d\n", *ptrToFifthElement);
}
7.3. Aside: Memory leaks, calloc
and free
, and casting
Some programming languages automatically forget data that you never use again. Unfortunately, C does not do that, so you have to manually free up memory, and it can become annoying very quickly.
One way to mitigate this is by dynamically allocating your memory; in other words, manually allocate memory and free it. We’ll use two functions for this:
-
calloc
is a function that takes two arguments: the number of elements of the array, and the size of each element of the array. It returns typevoid*
. -
free
is a function that takes one argument: the pointer to the memory you want to free.
We will also need to cast the pointer to allocated memory, as another type. We can do that for almost any data type; for example, it’s an easy way to convert from one of the integer types to one of the floating-point types:
#include <stdio.h>
int main() {
double e = 2.718281828;
int three = 3;
double threeAsFloat = (double) three;
int eAsInt = (int) e;
printf("e is %lf as a double and %d as an int.\n", e, eAsInt);
printf("Three is %lf as a double and %d as an int.", threeAsFloat, three);
return 0;
}
Now that we know about casting, we can look at dynamic memory allocation in all its glory:
#include <stdio.h>
#include <stdlib.h>
void print_arr(int* arr, int size);
int main() {
int* myInts = (int*) calloc(5, sizeof(int));
print_arr(myInts, 5);
myInts[0] = 5;
myInts[1] = 6;
myInts[2] = 2;
myInts[3] = 8;
myInts[4] = 11;
print_arr(myInts, 5);
printf("Freeing the array.\n");
free(myInts);
printf("Trying to print the array again");
printf(" (gives weird values since we freed up the memory):\n");
print_arr(myInts, 5);
}
void print_arr(int* arr, int size) {
printf("Contents of the array: \n\n");
for(int i = 0; i < size; i++) {
printf("%d\n", arr[i]);
}
}
8. File I/O
Now that we know about pointers, we can deal with files.
Luckily, the stdio
library gives us all the tools we need to
read and write files.
8.1. So how do we open and close a file?
The functions we use for this are fopen
and fclose
.
8.1.1. fopen
Suppose we want to open some file file.txt
.
We need to give fopen
the file path and the permissions we need.
The permissions are given in this table:
Code | Permission level |
---|---|
|
Read-only |
|
Write-only ( |
|
Append ( |
|
Read and Write |
|
Read and Write |
|
Read and Append |
|
Read-only (binary) |
|
Write-only (binary) |
|
Append (binary) |
|
Read and Write (binary) |
|
Read and Write (binary) |
|
Read and Append (binary) |
8.1.2. fclose
Now that we’re done messing with our file we need to write
fclose("./file.txt")
.
8.2. fprintf and `fscanf
We used printf
and scanf
for console I/O,
and we used sprintf
and sscanf
for string I/O…
so what would we use for file I/O?
That’s right! We use fprintf
and fscanf
for these things.
Download the following files and put them in the same directory.
Change the numbers in savedata.txt
and the fscanf
parameters,
and change what the output is.
439 88
#include <stdio.h>
void main() {
FILE* f = fopen("./savedata.txt", "r");
int int1, int2;
fscanf(f, "%d %d", &int1, &int2);
fclose(f);
int1++;
int2++;
printf("%d %d", int1, int2);
f = fopen("./savedata.txt", "w");
fprintf(f, "%d %d", int1, int2);
fclose(f);
}
9. Project: Save Files
You should have a couple of games coded up by now. Add a "save file" feature, i.e. print the all-time high score to the screen.
10. Bonus Content: Bitwise Operations
Will add later. (I mean, who even uses bitwise operations anymore!!!)
11. Bonus2 Content: Compilers
C is considered a compiled language; if you followed the above instructions,
you would have installed gcc
. But there are other C compilers such as clang

More to be added later TODO
12. Bonus3 Content: Runtime exceptions

TODO
12.1. Debuggers?
TODO