We learned about Scratch, a visual programming language containing functions, conditionals, Boolean expressions, loops, etc.
The essential programming concept presented in Scratch will merge with our savvy along with the learning path as we learn how to program in a programming language.
When we write the code, humans write source code, the code that all the human programmers will ultimately write. It doesn't matter if it's a Scratch, C, or Python, but it is human-readable code.
While the computer or a machine only understands what we can call machine code, which is a binary number. This machine code is a pattern of zeros and ones that produce a desired effect.
We must convert the source code
into a machine code
to communicate with the computer. Hence, we need a very specific piece of software called a compiler
.
If we fit into the same paradigm as last time, the input will be a source code, and we need to output the machine code somehow.
The middle box is a special program that converts source code to machine code. We call this type of program a compiler.
In this lesson, we will learn the compiler that will allow us to convert the source code written in the programming language C
to the machine code.
In addition to that learning block, we will learn about how to write good code. The good code can be evaluated upon three axes.
Obviously, code correctness is the first and foremost goal. We can't always write the correct code; the bug causes an error or blue screen, but that is not what we intend to do.
More subjective goals are design and style. We briefly saw from the Scratch what is a well-designed code.
The better-designed and styled code is often faster and more maintainable by the writer and even colleagues.
The compiler that is utilized for this course will be Visual Studio Code or VS Code
We can write the code anywhere we want, even with a pen and paper. Notepad, text editor, or Microsoft Word will support writing the code, but none of those are designed to format the code nor to compile
and run the code.
VS Code has all the pre-loaded software required for this course. Hence, the course and the instructions herein were designed with VS Code.
Notice that there is a file explorer on the left, and on the right side, two areas are separated.
The one on the top is called text editor, where we write the code or edit the program. Finally, the one on the bottom is called a Command Line Interface, known as CLI, command line or terminal window where we can send commands to the computer.
Run the command code hello.c
Notice that we deliberately use the lowercase of the entire filename and include the .c
extension. You could use capital H or maybe capital C for the extension but follow the best practices and keep consistency with others.
After creating the hello.c
file, go to the text editor and type as above. We will go over the details in order, but let's compile and run this program.
Type make hello
in the terminal window; think of this step as double-clicking a file on your Mac or PC.
This is the process of compling
, in which the make
program automates the compilation for us. By using this command, the make
compiler automatically detects a file on the hard drive called hello.c
and converts its source code in C to machine code or the program
in zeros and ones.
If we execute this command without an error message, we can proceed. Check the typos of your code if any error occurred.
Notice that the terminal window has a dollar sign; it represents the current line and is ready for the command.
To run this program, type ./hello
in the terminal window. This might be weird for people who have never coded before, but it is how we run the program called hello
in the current folder or directory
in this environment.
Take attention to the left; our file explorer has two files we created, hello.c
with the code
command and another file called hello
.
The compiler can read the file hello.c
, where we store our code. The file hello
is an executable file that users cannot read nor open by the standard compiler.
If we look closer, without doing certain things, the text is already color coded.
Most programming environments, including VS Code, do it for you; we call it syntax highlighting.
It is a feature of typical text editors nowadays that analyzes the code that the user types, and when it notices different keystroke types, such as functions, loops or conditionals, it highlights those things in different colors.
In Scratch, we utilize the say
block to display text on the screen.
In a similar manner, in C, we have a function called printf
to do exactly this behavior.
So, if we convert the left say
block to the C code at the right printf
, we need a white oval or an argument
.
The argument passed to printf
is "hello, world\n"
, and then the semicolon at the end.
We will eventually get used to these nuisances like the parentheses, quotes, semicolons and the like.
The common mistake we, or even experienced programmers, occasionally make is omitting a semicolon.
We can meet numerous errors when we recompile the code. We accidentally forgot the semicolon, but the errors disappear if we put it back.
The red circle indicates where the error occurred, so reference those as an important clue when finding or analyzing errors.
The recompiling is important because the machine code in
hello
is not changed even though we changed the source code.
Another experiment we can do is removing \n
since we don't have a clue what it is and why it is there.
Try removing those and recompiling or making the program; it will end up giving us a weird terminal text shape.
Notice that the dollar sign is printed right after the hello world
. We can still type something with the dollar sign, but it looks stupid and not user-friendly.
Moving the cursor to the following line for the user when we are done running the program is convention. The backslash n (\n)
is the special symbol known as the escape sequence that C knows.
After jiggling the codes, we might be curious about the first line of code. If we delete the first line, what error message do we get?
From the error message, we can figure that we declare the printf
function without a library.
The statement in the first line, #include <stdio.h>
, is a special command that tells the compiler that we want to use the capabilities of library called standard input and output
.
Think of it as importing a library, or in Scratch, we import an extension like text-to-speech. That was the same as adding a library.
Library is the third-party written code, or someone else wrote it in this case. Same in C, we don't automatically get the printf
function. We have to include a header file that declares the printf
function to exist.
If you are interested in what functions in the library, check out here. Also, CS50 has its own library called cs50.h
; you can check it on the same page above.
Recall that in Scratch, we had the ability to ask the user name as an input and say hello with that name appended to it like this: "hello, (your name).
We can use a get_string
function from the cs50 library that collects the user input and the quotes. At the end of the string, leave the space for the user so that the cursor moves one space after the sentence.
Notice in C that we have to be a little more specific than Scratch. In C, if we want to get a function's return value, we have to use an equal sign and the variable's name on the left.
The equal sign doesn't mean equality, but the assignment, which means copying the value on the right over to the value on the left.
Yet, there is one more thing, which is why we call C pedantic language: we need to tell C what type of value we are storing.
When we move to the VS Code, we add the cs50.h
library on the top, and then the answer is passed to the printf
function.
But what is the %s
sign?
One step back to the Scratch, we had the best solution for the same function above. The join
block was the elegant solution for using the say
block and the input.
The picture above is how we translate the say
and the join
blocks. It is not perfectly matched, but this is how we can implement the same function.
The percent sign or a placeholder is known as a format code
in C, especially for printf
. This means the %s
is a placeholder for a string, the s stands for string, and we ask the computer to plug in some value here.
The function printf
is smart enough to take the answer and go back to the hello %s\n
if we have %s
and one additional argument after the comma inside of the function.
To sum up, the get_string
function is used to get a string from the user. Then, the variable answer
is passed to the printf
function. The %s
sign tells the printf
function to prepare itself to receive a string.
Same as the answer
, we can define the name of the variable and the type of the variable. There are many data types, such as int, bool, char, and many others.
Another building block we utilized within Scratch was that of conditionals, or the if-else
block.
Suppose we want to compare two values, and in Scratch, we have a block for that ability.
Notice that the right side keyword is almost identical to the left; that's because MIT borrowed those keywords.
What if there is a three-way fork in the road? In Scratch, it is unwieldy graphically. The code might look like this:
Notice the block is kind of a mess. If we nitpick or critique the design of our code, we can figure out that the third question is unnecessary.
By reducing redundant conditional, we produce a minor tweak from the original.
Let's now go to the VS Code and write the code in C.
Type code compare.c
in your terminal window, and it will create a new file named compare.c
. Once we close and run the command again, it will open the existing file with the same name.
And if we write the code without a typo, our program should run like this:
Notice that we created variables x and y
and set variables to an int or integer. These values are populated using the get_int()
from the CS50 library.
Our program can only check if x is less than y, so we need to improve our program by coding as follows:
Now, all potential outcomes are accounted for.
If we are building a program that asks if you agree or not, we can build the program like this:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
char c = get_char("Do you agree? ");
if (c == 'y')
{
printf("Agreed.\n");
}
else if (c == 'n')
{
printf("Not agreed.\n");
}
}
Compile this program and run it. The program works but in a little obnoxious way. We must manually type the lowercase and can't use the capital Y or N
.
Another point is that we used double quotes for the string
and single quotes for the character
.
This is a necessity in C; when we are dealing with strings, such as text, a name, a sentence or anything more than one character, we typically use a ""
. When dealing with deliberately single characters, we must use ''
. Because of this, we let the computer know the content inside of the single quote is indeed a char
, not a string.
Back to the code, how can we improve our program to take both lower and upper characters?
#include <cs50.h>
#include <stdio.h>
int main(void)
{
char c = get_char("Do you agree? ");
if (c == 'y')
{
printf("Agreed.\n");
}
else if (c == 'Y')
{
printf("Agreed.\n");
}
else if (c == 'n')
{
printf("Not agreed.\n");
}
else if (c == 'N')
{
printf("Not agreed.\n");
}
}
This could be a correct solution, but perhaps not the best design. Another term of art here, we can say the code smell is a little off. That's because we repeat the same thing but want the code to be different.
The code above has a third smaller size than the original and less redundancy, which has a better design. Also, we have to mention that we have a new operator, ||
, which effectively means or.
Recall that we made a meow block in Scratch that sounds like a cat meowing several times.
There is no sound block in C so that we will rely on the print. The program above in C will be as follows:
#include <stdio.h>
int main(void)
{
printf("meow\n");
printf("meow\n");
printf("meow\n");
}
As we learned through the course, this is not the best design. To implement the loop in C, we need to know the syntax of the loop.
If we set the counter to count a number, it might look like this:
In the same manner, if we want to increment the counter by one, we can use the following code:
counter = counter + 1;
// In a more simple way, we usually do this instead.
counter += 1;
// More simple way:
counter++;
The first code almost seems like a paradox, but it's the common syntax in programming. A single equal sign is an assignment from right to left. Also, we don't need to mention the keyword int
when we update an existing variable.
Converting the repeat block from Scratch will look like this:
Notice that we create an int
called i
and assign it the value 0
. Then, we create a while
loop that will run until the i
becomes zero. Whenever the loop runs, we subtract i
by one.
But that is not what human counts, so we can modify the code like this:
int i = 1;
while (i <= 3)
{
printf("meow\n");
i--;
}
And this will count i
from one to three. However, programmers usually start counting from 0, so the go-to syntax, once we get comfortable counting from zero, will be like this:
int i = 0;
while (i < 3)
{
printf("meow\n");
i++;
}
The most canonical way to solve the loop might be a for-loop, although it takes a little more time to get used to. But this for-loop consolidates into one line all of the same logic.
The for
is just a preposition that implies, here comes a loop.
Inside the parentheses, there are three parts.
The first yellow-colored part is initialized to 0
, just like before.
After the initializing part, the loop checks the Boolean expression part. And if i
is less than three, it prints out meow
.
Then i
is going to get incremented. The i
starts at 0
, and goes to 1
. At that point, the Boolean expression is rechecked. The first step happens only once, and we don't keep changing i
back to 0
.
When the i
reaches 3
, the Boolean expression asks, Is 3 < 3?
and indeed, three is not less than three, so the loop stops.
One additional building block, when we recall, Scratch had a forever
block when we played background music or checked a collision to a wall as such.
The infinite loop is not a good thing in general, but we can implement it by the following code:
int main(void)
{
while (true)
{
printf("meow\n");
}
}
Notice that true
will always be the case, and if we remind of the transistor, the 0
is false and 1
is true. Therefore, we can replace true
with 1
, and by doing so, the code will always run.
We will lose control of the terminal window by running this code. To break from the infinite loop, hit cmd + c or ctrl + c
on the keyboard.
We can encounter the following error if we don't include the <cs50.h>
library.
True and false are themselves unique words that we have to include. If we want to use particular Boolean values like this, we must include another library called standard bool, #include <stdbool.h>
, or the CS50 library alternatively.
Linux is an operating system accessible via the command line in the terminal window in VS Code. Essentially, it is just an alternative to macOS or Windows that provides both a GUI and a command-line environment.
Windows and macOS have terminal windows or the equivalent thereof. But Linux is known for, along with other operating systems, its command line environment.
The Command Line Interface, or CLI, literally refers to the terminal window. If we go back to the VS Code and close the tab to focus on the terminal window, the terminal window is really just the command line interface to our own server in the cloud.
There are many commands or command-line arguments that are worth knowing, and here are some examples.
cd
stands for change directory, which means changing our current directory(folder)cp
for copying files and directoriesls
stands for list and for listing files in a directorymkdir
, for making a directorymv
stands for move and can be used to move and rename files and directoriesrm
, for removing or deleting filesrmdir
, used for removing(deleting) directoriesIndeed, these commands are all succinct and abbreviated because we or developers earlier didn't want to type out long commands.
The most commonly used is ls
, which will list all the files or directories in the current directory.
As we can see in the picture, we have green files with an asterisk, which means the file is executable. Besides the green files, we have .c
files where we write the code and the blue pset1
directory.
Another useful command is mv
, where we can move a file from one file to another. Suppose I accidentally created a file called Greeting
, typed the code, and realized I didn't put the .c
extension.
Instead of copying all the code and making another file called greeting.c
and pasting all the code there, we can simply run this mv Greeting greeting.c
to move all the written code.
The above picture suffices to explain the process that we've discussed above. Create a file without the extension, write some code, and move all the code that was written to a new file called greeting.c
.
We can use the mkdir pset1
to create a directory, the same as the picture above.
Notice that we ran ls
but can't see the mario.c
file. We can see visually on the top left pset1
folder contains the mario.c
, but we can't run or compile the file because we are in a different directory.
For instance, if we browse Finder on Mac or File Explorer on Windows, we can double-click to get into that folder. The exact process here is that we have to change into the folder or directory, and cd
is the command there.
Notice that we have a pset1/
before the dollar sign, indicating that we are in the directory called pset1
. We can start running and compile mario.c
because we are in the right directory.
Ultimately, this command line interface will be a more powerful, capable and efficient mechanism for writing code, running commands, solving problems, and analyzing data more, even though a somewhat stiff learning curve exists.
make file.c
will generate or compile
the machine code from the source code.make file.c
command, the compiler generates the executable file with the same name. Which are not easily accessible and readable by humans.format codes
.''(single quote)
and ""(double quotes)
for strings.mv
moves all the code from a new file to an existing file. Or think it could change the file's name and delete the old one.Continue to part two
printf
function.0 and 1
0
.