Better Everything
18 min read · Mar 14, 2023
--
Harvard University offers a Computer Science course that is also freely available online, called CS50: Introduction to Computer Science. Anyone can watch the lectures and I decided to not only watch the lectures but also write and share lecture notes.
These are my lecture notes for Lecture 1, which is about the programming language called C and using the Linux Command Line Interface.
The first part of this series are the lecture notes of Lecture 0:
Similiarities and differences with Scratch
C offers the same ideas like functions, conditionals and loops that were implemented using the graphical computer language Scratch in Lecture 0. But it looks more cryptic due to C’s syntax.
Source code, is code that humans write, regardless whether it is written in Scratch, C, Python or other programming languages.
But computers only understand code in binary (0s and 1s), this code is called machine code. Machine code can contain data but also instructions on what it is that the code should do.
Source code is the input for programs called compilers that transform the input to machine code.
For Scratch we did not have to use a compiler as the code was compiled automatically. For C we do have to use a compiler.
Correctness, Design and Style
These are 3 goals when writing code:
- Correctness, does it do what it is supposed to do.
- Design, this is a more subjective goal. It involves making the code run faster and more maintainable.
- Style, by giving the code a clear structure and using variables with descriptive names it gets easier to understand what the code does.
Visual Studio Code
Visual Studio Code also known as VS Code is a free text editor for programming languages. Not only can you write code with it but you can also format it, compile it and run it.
VS code can be downloaded, but this course starts by using the web-based version for which you need a GitHub account. GitHub is a code hosting platform that is used to host and manage code online.
VS Code’s interface is divided in several parts. One of the main parts is the part where you can have different tabs to open, read and edit files.
Another part is the terminal window, this window provides a Command Line Interface (CLI). Instead of pointing and clicking icons like in a Graphical User Interface (GUI), in a CLI you control a computer by writing and running commands.
The web-based VS Code version of CS50 uses the Bash command language which is the default for Linux and Mac systems. In Windows you can make commands that do the same but the commands themselves can be different.
On the left there is another part called Explorer in which you can see all files in a folder or account.
Writing, compiling and running a C program in Visual Studio Code
A new file can be created with the code command in the terminal window: code filename.c. Using lowercase for the filename is considered as a best practice.
When you type the command code hello.c and hit enter, apart from the file, a tab is created in which the (empty) file is opened.
In the file you can write the following C program:
#include <stdio.h>int main(void)
{
printf("hello, world\n");
}
The web-based version of VS Code saves files automatically. After creating and saving the above file it can be compiled with the make command.
When using the downloaded version of VS Code on Windows, the make command is probably not available and should first be installed.
The make command looks like this: make filename. The filename should be the previously created C file without the .c file extension. The command creates a file containing machine code by using a compiler.
To compile the hello.c file the command would be: make hello.
To run the hello file with its machine code you can run the command: ./hello.
It prints: hello, world, that means that the text hello, world appears in the terminal window.
Syntax highlighting
VS Code has a feature called syntax highlighting. Based on the .c file extension, VS Code knows that the C programming language will be used. It recognizes different things in the code like functions, conditonals, loops and variables and highlights them using different colors.
Calling the printf function in C
The printf function takes a string as an input argument in between parentheses and the function’s side effect is that text appears in the terminal window.
A string is a string of characters that forms a text. In C this needs to be noted in between double quotes.
When you put \n, which is a newline character, in a string it creates a new line and the cursor moves to the beginning of that new line.
Statements like calling a function should end with a semi-colon.
Calling a function means that the code inside the function will be executed.
To call the printf function and make it put the text hello, world in the terminal window you would use this line:
printf("hello, world\n");Errors and solving them in C
One mistake, or bug, you could make is forgetting to end a statement with a semicolon, in that case an error would occur.
When omitting the semicolon at the end of the printf function call in the hello.c program an error occurs. The program won’t run properly and an error message appears in the terminal window.
The message contains some info, in this case: error: expected ';' after expression printf("hello, world\n"). But it also states an educated guess on where the error occurs: hello.c:5:29 this means in line 5 of the hello.c file at the 29th character.
After adding the semicolon and saving hello.c you have to recompile the program to also change the hello file. This can be done by running make hello again.
Importing libraries
The line #include <stdio.h> is to import a library called stdio with the header file stdio.h. A library is like an extension pack that adds more functions, one of them is the printf function.
The header file lists what the stdio library contains. stdio means standard IO and IO is a term that means input and output.
Documentation
In many programming languages, among which C, there exist documentation pages in which you can find information about what functionalities exist and how to use them.
CS50 also has its own documentation pages, partly about its own library and functions and partly about standard C functions.
CS50 Library and Placeholders
CS50’s own library is accessible via header file cs50.h. It has a number of functions to ask a user for input. Like get_string to get text input, and get_int to get an integer value.
Note: The cs50 library is available in the web-based version of VS Code because it is ‘pre-installed’ in the GitHub environment of CS50. If you work with a downloaded version of VS Code and want to use extra packages like the cs50 library, these have to be downloaded as well.
When a function like get_string is called, a prompt will appear in the terminal window where the user can type and hit enter. The function will then return that value. That means the user’s input will be available within the code.
The value that is returned can be assigned to a variable with the assignment operator =. This has nothing to do with the equals sign from mathematics.
Here is an example of using the get_string function:
#include <cs50.h>
#include <stdio.h>int main(void)
{
string name = get_string("What's your name? ");
printf("hello, %s\n", name)
}
The first line is to import the cs50 library to make the get_string function available and the second line imports the stdio library to make the printf function available.
The get_string function takes the string "What's your name? " as an argument and returns what is entered in the prompt.
That return value is assigned to variable name because we put name = before the function call. We must also specify what datatype the variable name is, which is string.
The printf function takes 2 arguments in this case. The first argument is a formatted string with one placeholder: %s. The percentage character is to define a placeholder and the s is to specify that the value that will be placed there should be of datatype: string.
When having x placeholders in the string, you also need to add x variables to replace them with. Here is an example of having 2 placeholders:
printf("hello, %s %s\n", first_name, last_name)In the above example there are 3 arguments, the string with 2 placeholders and 2 variables that will replace the placeholders.
To put a % in a string you have to use an escape character otherwise the computer would think the % should be interpreted as a placeholder, to escape a percentage you add another % before it like this: "100%%".
Datatypes
In addition to strings there are many other datatypes like:
- bool — can be either true or false
- char — can hold 1 character and should be noted in between single quotes like
'a'
cs50 function: get_char
placeholder: %c - float — to hold decimal values
cs50 function: get_float
placeholder: %f - double — to hold decimal values but with more space than floats
cs50 function: get_double
placeholder: %lf - int — to hold an integer value
cs50 function: get_int
placeholder: %i
Conditionals in C
A conditional can be implemented in C like this:
if (x < y)
{
printf("x is less than y\n");
}You begin with the if keyword and add the condition within parentheses. Then you add the code block that should be executed when the condition is met within curly brackets.
You can also add a code block for when the expression evaluates to false under the else keyword:
if (x < y)
{
printf("x is less than y\n");
}
else
{
printf("x is not less than y\n");
}Or test multiple conditions using the else if keywords:
if (x < y)
{
printf("x is less than y\n");
}
else if (x > y)
{
printf("x is greater than y\n");
}
else
{
printf("x is equal to y\n");
}To test whether 2 values are equal to each other you can use: ==. And you can also test whether something is smaller than or equal to with: <= or the opposite: >=.
Here is a full code example with conditionals in C:
#include <cs50.h>
#include <stdio.h>int main(void)
{
int x = get_int("What's x? ");
int y = get_int("What's y? ");
if (x < y)
{
printf("x is less than y\n");
}
else if (x > y)
{
printf("x is greater than y\n");
}
else
{
printf("x is equal to y\n");
}
}
To ask a user a yes-or-no question like ‘Do you agree?’, you can use the get_char function which returns a char (1 character) and test if it is y(es) or n(o).
To also allow capital Y and N as answers, you can combine conditions with a logical OR like: c=='y' or c=='Y'.
The logical OR operator can be implemented in C with ||:if (c == 'y' || c == 'Y')
Here is a full code example:
#include <cs50.h>
#include <stdio.h>int main(void)
{
char c = get_char("Do you agree? ");
if (c == 'y' || c == 'Y')
{
printf("Agreed.n");
}
else if (c == 'n' || c == 'N')
{
printf("Not agreed.\n");
}
}
In the example above, the condtions evaluate to true when 1 of the expressions evaluates to true. To require them both to be true you can use a logical AND operator which is && in C.
When the user input does not match the datatype, for instance when you pass more than 1 character to a get_char prompt, the function rejects the input and re-asks the question.
Variables in C
The get_string function returns a value that can be assigned to a variable. But variables can also be initialized without using functions and their return values.
The ‘recipe’ is as follows:
- datatype
- variable_name
- assignment operator: =
- value
Here are 3 examples:
string name = "John";
char letter = 'H';
int number = 1;Note that the string dataype comes from the cs50 library and the way it is used here is not possible without it.
After initializing a variable, it can be changed. To change it, you don’t have to repeat the datatype and can just use the assignment operator again:
Here is an example of changing a name:
#include <cs50.h>int main(void)
{
string name = "John";
name = "Joe";
}
You can also use the old value to determine the new value to be assigned like this:
#include <stdio.h>int main(void)
{
int counter = 10;
counter = counter + 5;
counter = counter - 2 - 1;
printf("%i\n",counter);
}
The above can also be achieved by using the += and -= operators:
#include <stdio.h>int main(void)
{
int counter = 10;
counter += 5;
counter -= 2 + 1;
printf("%i\n",counter);
}
Both programs print: 12.
Using counter++ or counter-- are ways to either add 1 to a variable or subtract 1 from a variable.
By adding const in front of a variable’s datatype you can make it a constant, which means that its value can not be changed after initializing.
For example: const int n = 5;
Loops in C
A while loop is a loop (repetition of code) that keeps going while (as long as) a condition is met.
Here is how to implement a while loop in C to print a string 3 times:
#include <stdio.h>int main(void)
{
int counter = 0;
while (counter < 3)
{
printf("meow\n");
counter += 1;
}
}
The variable counter is initialized at 0, then the execution of the code reaches the while loop. Since 0 < 3, the while loop’s code block is executed: meow is printed and counter’s value is increased to 1. Then the condition counter < 3 is checked again.
The code block is repeated for the situations in which counter is 1 and 2. So after printing meow for 3 times, counter gets the value 3, in that case the condition of the while loop is no longer met, and the execution breaks out of the loop.
A for loop is also a loop with which we can achieve the same as with the code above. This is an example of a for loop:
#include <stdio.h>int main(void)
{
for (int i = 0; i < 3; i++)
{
printf("meow\n");
}
}
Variable i is initialized with value 0. If i is smaller than 3 the code block with the print statement is carried out. Then with i++ the value is increased by 1 and the i<3 condition is checked again. This repeats until the condition is not met anymore.
A forever loop or while true loop is a loop that keeps repeating ‘forever’. The words true and false are actually special in C as these are boolean values. The bool datatype can hold either value true or false.
To make the boolean values available you can import the standard bool library by including the stdbool header file: #include <stdbool.h>. But you can also use the special cs50 library: #include <cs50.h>.
If you put true as the condition for a while loop, the condition will always be met and thus the code block will be repeated forever: while (true).
To stop the forever repeating loop you can use a keyboard interrupt by pressing CTRL + C (multiple times).
You can also use while (1), as 0 is evaluated as a ‘false’ value and other numbers like 1 as ‘true’ values.
A nested loop is a loop inside of another loop. Here is an example of a nested loop to print a number of hashtags (columns) for a number of lines (rows):
#include <stdio.h>int main(void)
{
for (int row = 0; row < 3; row++)
{
for (int column = 0; column < 5; column++)
{
printf("#");
}
printf("\n");
}
}
As you can see there are 2 for loops. The outer loop creates 3 rows. Each row is created by running the inner for loop and printing a newline character. The inner loop just prints a hashtag 5 times, once in every iteration of the loop.
Another form of loops is the do while loop. First you specify what code should be executed and only then do you specify under which condition the code should be repeated.
In the lecture it replaces this code:
#include <stdio.h>
#include <cs50.h>int main(void)
{
int size = get_int("Size: ");
while (size < 1)
{
size = get_int("Size: ");
}
printf("%i\n", size);
}
The get_int function rejects non-integer values, but accepts 0 or negative values. To make the execution of the code only continue when a positive value is entered the get_int function is repeatedly called as long as size is smaller than 1.
That code has repetition, size = get_int("Size: "); is used in multiple places, which makes the code harder to maintain. Here is a do while loop to prevent repetition:
#include <stdio.h>
#include <cs50.h>int main(void)
{
int size;
do
{
size = get_int("Size: ");
}
while (size < 1);
printf("%i\n", size);
}
First the size variable is created without a value: int size. Then the code to be repeated is declared with the do keyword and a codeblock within curly brackets. And finally the condition for repetition is given with while (size < 1);.
Both programs can be interacted with, like this:
Size: 0
Size: -5
Size: 10
10Comments in C
Comments are notes for the people reading the code to explain what the code is doing or why and how.
In C, comments can be implemented by adding two slashes in front of a line:
// this is a comment in CHere is an example of using comments in code to explain the program:
#include <stdio.h>
#include <cs50.h>int main(void)
{
// Get size of grid
int size;
do
{
size = get_int("Size: ");
}
while (size < 1);
// Print a square of #'s based on variable size
for (int row = 0; row < size; row++)
{
for (int column = 0; column < size; column++)
{
printf("#");
}
printf("\n");
}
}
Comments can also be used to map out the steps of a program like pseudocode:
#include <stdio.h>
#include <cs50.h>int main(void)
{
// Get size of grid
// Print a square of #'s based on variable size
}
Making functions in C
If we use the last piece of code of the section above and pretend like we have functions to do what the comments are saying we could create this:
#include <stdio.h>
#include <cs50.h>int main(void)
{
int size = get_size();
print_square(size);
}
Then by creating the functions we will have a working program with an easy readable main function.
The first function should be called get_size, it takes no input and should return an integer.
The syntax to create this function would be: int get_size(void). Where int is the datatype of the return value, get_size is the function name and void in between parentheses is to specify it takes no input arguments.
Then within curly braces we can put the code for the function. And to make the function return a value, in this case the value of variable size, we can use the return keyword.
int get_size(void)
{
int size;
do
{
size = get_int("Size: ");
}
while (size < 1);
return size;
}The second function should be called print_square, it takes an integer argument containing a value for size as input and does not have to return anything.
The syntax to create this function would be: void print_square(int size). Where void is to say it does not return anything, print_square is the function name and int size within parentheses is to declare it takes an integer value that will be called size as input argument.
Again we put the code that the function should be executed within curly braces and this time we don’t have to use the return keyword.
void print_square(int size)
{
for (int row = 0; row < size; row++)
{
for (int column = 0; column < size; column++)
{
printf("#");
}
printf("\n");
}
}To keep our main function at the top of the program we can put the function defintions below it. But since C processes the file top to bottom, the function calls will be reached before the functions are defined.
A solution for this is to copy only the first lines of the function definitions at the top of the script.
Here is a full code example:
#include <stdio.h>
#include <cs50.h>int get_size(void);
void print_square(int size);
int main(void)
{
int size = get_size();
print_square(size);
}
int get_size(void)
{
int size;
do
{
size = get_int("Size: ");
}
while (size < 1);
return size;
}
void print_square(int size)
{
for (int row = 0; row < size; row++)
{
for (int column = 0; column < size; column++)
{
printf("#");
}
printf("\n");
}
}
Difficulties of working with C
When working with C you might encounter one or more of these difficulties:
Integer overflow
An integer with 32 bits can store 4,294,967,296 different values. Half of these values are negative and the other half are non-negative. The highest number an integer variable can hold is 2,147,483,647 and the lowest number is -2,147,483,648.
This is a finite range of possible values. There is only a certain amount of values you can represent with a number of bits.
So when you try to represent a value outside of the range of possible values integer overflow occurs. That means an error occurs or the code works in unintended ways.
In this example:
#include <stdio.h>int main(void)
{
int x = 2000000000;
int y = 2000000000;
printf("%i\n", x + y);
}
Instead of printing 4000000000 it prints: -294967296.
The solution is to use the long datatype, instead of 32 bits this uses 64 bits and enables numbers up to 9,223,372,036,854,775,807.
Truncation
1 / 3 should of course be 0.33333, but since 1 and 3 are integer values in C. The result will also be an integer instead of a float that supports decimal points.
That is why this program prints 0, this phenomenon is called truncation:
#include <stdio.h>int main(void)
{
int x = 1;
int y = 3;
printf("%i\n", x / y);
}
The solution is to use type casting to convert the datatypes of 1 and 3 by explicitly specifying them:
#include <stdio.h>int main(void)
{
int x = 1;
int y = 3;
float z = (float) x / (float) y;
printf("%f\n", z);
}
The above program prints 0.333333 instead of 0.
Floating-point imprecision
By using "%.20f" as a formatted string instead of "%f" you specify that you want to see 20 digits behind the decimal point. When we do that like this:
#include <stdio.h>int main(void)
{
int x = 1;
int y = 3;
float z = (float) x / (float) y;
printf("%.20f\n", z);
}
the output is: 0.33333334326744079590.
1/3 as a decimal has infinite 3s behind the decimal point. But a float has only a finite number of bits to represent that decimal number. That is why it only is an approximation instead of an exact value.
When using a variable of datatype double the result will be more precise but still not perfect. This is because double the amount of bits are used than in a regular float.
Here is an example:
#include <stdio.h>int main(void)
{
int x = 1;
int y = 3;
double z = (double) x / (double) y;
printf("%.20lf\n", z);
}
That program prints: 0.33333333333333331483.
The web based VS Code uses the operating system Linux, which is popular for working with servers. It is an alternative to Windows or MacOs and especially its command line interface is popular.
So in the terminal window it is Linux that we are using. We use the terminal window to send commands to a server in the cloud. A server in the cloud is a computer that can host files and run programs that can be controlled from other computers via the Internet.
We have already used some commands in the CLI:
- code, this is a VS Code specific command to open a file. If the file does not exist it is made, if it does exist it is just reopened.
- make, which is used to transform the source code into machine code by using a compiler.
Some other important linux commands:
- mdkir is to make a directory.
For example:mkdir pset1creates a directory (folder) calledpset1. - cd is to change the (current working) directory.
For example:cd pset1navigates into thepset1directory, which we can check by the prompt changing from$topset1/ $. You can go back out of a directory by usingcd ... - cp is to copy one or multiple files or directories to another directory.
For example:cp hello pset1/hellocopies thehellofile from the directory you are in to thepset1directory. - mv is to move a file to a different folder by specifying a files current filepath and a new filepath. You can also use the mv command to rename a file.
For example:mv hello.c hello2.crenames thehello.cfile tohello2.c.
And:mv hello2.c pset1/hello.cmoves thehello2.cfile to thepset1directory and renames it tohello.c. - ls is to list the contents of a directory.
For example:lsoutputs:hello* hello.c pset1/. The asterisk means that thehellofile is an executable file and the slash means thatpset1is a directory. - rm is to remove a file.
For example:rm hellooutputs the prompt:rm: remove regular file ‘hello’?, if you then typeyand hit enter the file will be removed. - rmdir is to remove a directory.
For example:rmdir pset1if there still are files in the directory outputs:rmdir: failed to remove ‘pset1’: Directory not emptyotherwise it just removes it. - clear is to clean up the terminal window by removing all the previous commands and outputs.
When working with the Windows command line interface you can use these commands instead of the Linux versions of the commands:
- copy instead of cp, but this uses different syntax
- move instead of mv
- dir instead of ls
- del insted of rm
- cls instead of clear
2 other tips for working with command line interfaces:
- You can ‘scroll’ through the previous made commands with the UP and DOWN arrow.
- You can copy text in the CLI by selecting it with the mouse and then right-clicking it.
I hope my post was useful.
To learn more about programming, follow this page and check out my E-books:
The next lecture is about Arrays: