Andrew Sellergren
Generally when we write numbers, we omit leading zeroes. Hence, we write 123 not 00123. However, when we write numbers in binary, we'll adopt the convention of including those leading zeroes to fill out 8 places to emphasize that 8 bits, or 1 byte, is a somewhat standard unit of measure. Thus, we write 0, 1, and 2 as follows:
00000000
00000001
00000010
Within the gedit window, the top portion is a normal text editor and the bottom portion is a terminal window for executing commands like ls
for list, cd
for change directory, rm
for remove. When we click File → Save in gedit, we can choose the jharvard
directory (our user's home directory) and name our file hello.c
. Then we can write:
int main(void)
{
printf("hello, world!");
}
In order to use the printf
function, though, we need to include the standard library that contains its definition:
#include <stdio.h>
int main(void)
{
printf("hello, world!");
}
Good practice is also to include an explicit return statement:
#include <stdio.h>
int main(void)
{
printf("hello, world!");
return 0;
}
To make our program more aesthetically pleasing, we should probably include a newline character at the end of our printed line:
#include <stdio.h>
int main(void)
{
printf("hello, world!\n");
return 0;
}
jharvard@appliance (~)
telling us that we are acting as user jharvard
and we're currently in our home directory denoted by the ~
. Here we can type make hello
which actually runs a compiler program named clang
under the hood. Now we've created a file name hello
in our home directory. We can run this by typing ./hello
and hitting Enter.We can step up this program's complexity a notch by getting input from the user:
#include <stdio.h>
int main(void)
{
string s = GetString();
printf("hello, world!\n");
return 0;
}
The GetString
function is one written by the staff to obtain input from the user in the form of a string. To use it, we need to tell the compiler where to find its definition:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
string s = GetString();
printf("hello, world!\n");
return 0;
}
GetString
takes 0 arguments, or inputs which alter a function's default behavior. The equals sign above doesn't stand for equality, but rather assignment: it says to take whatever's on the right (the output of GetString
) and store it in whatever's on the left (s
). Writing string s
is known as declaring it as a variable, or allocating space in RAM to store it. If we want to print out the user's input instead of a hardcoded string, we need to pass s
as an argument to printf
:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
string s = GetString();
printf("hello, %s!\n", s);
return 0;
}
printf
are separated by a comma. printf
knows to take the second argument and insert it into the first argument before printing out the whole string.When we compile and run this program, we get a blank line and a blinking cursor. The program waits for us to type something and hit Enter. To make it a little more obvious that this is what the program is waiting for, we can print out a line giving instructions to the user:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
printf("Enter a string: ");
string s = GetString();
printf("hello, %s!\n", s);
return 0;
}
GetString
is written to allocate more and more RAM as it's needed. What if we enter nothing? We can simulate this by hitting Enter without typing anything. Our program seems to work fine, but the output is a little silly, so maybe we should actually handle this corner case in our program explicitly. What if we enter a decimal number? Seems fine. As it turns out, even though we're typing numbers, it's ultimately being processed by this program as a string.char
, float
, double
, long
, long long
, and int
. A char
is a single character. A float
is a non-whole number, i.e. one with a decimal point. An int
is an integer. A double
is similar to a float
except it is stored with 64 bits instead of 32 bits. This means that we can use it to represent either larger numbers or more precise numbers (i.e. numbers with more numbers after the decimal point). A long
, like an int
, is stored with 32 bits. A long long
is stored with 64 bits.bool
and string
. As in the context of Scratch, a bool
or boolean variable is one that takes a value of either true or false.Let's write a program that asks the user for an integer instead of a string:
#include <stdio.h>
int main(void)
{
printf("Give me a number: ");
int n = GetInt();
printf("Thanks for the %d!\n", n);
return 0;
}
GetInt
is another function available to us in the CS50 Library. When we type make math
, we get an error:
math.c:6:13: implicit declaration of function 'GetInt' is invalid in C99
Oh right, we forgot to include (i.e. explicitly declare) the CS50 Library:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
printf("Give me a number: ");
int n = GetInt();
printf("Thanks for the %d!\n", n);
return 0;
}
#include
actually does is tell clang
to go and fetch the file named cs50.h
and paste its code at the top of our file before compiling it.math.c
. If we enter 0.1, we see a prompt that says "Retry." We can hit Enter all we want, but we actually wrote GetInt
to continue prompting the user until he or she provides an integer. How might we have implemented this continuous prompting? Perhaps an infinite loop.GetChar
GetDouble
GetFloat
GetInt
GetLongLong
GetString
#include <stdio.h>
, what happens when we compile? We get an "implicit declaration" error similar to the one we saw when we left out #include <cs50.h>
. If we take out all references to printf
, as well, then the compile will throw a new error indicating an unused variable. This is a non-fatal mistake, but we've configured the Appliance to be really annoying when it compiles your code.int
is only 32 bits, each of which can be either 0 or 1. That's 2 possibilities for 32 places, so we can store 232 possible numbers. But, wait, 232 is about 4 billion and the number that was printed was only around 2 billion. In fact, the number that was printed is 231 - 1. Turns out that although we have 32 bits with which to represent numbers in an int
, we need at least one of those bits to denote whether the number is positive or negative. Thus, with an int
you can represent the numbers between -231 and 231 - 1. When we provide a number larger than that to GetInt
, it defaults to the maximum.Before we move on, we'll examine one more example that will illustrate a few of the more common stumbling points in basic C syntax. Recall the formula for converting temperatures from Fahrenheit to Celsius: C = (5/9) x (F - 32). Let's write a C program named f2c.c
that will do this conversion for us:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for temperature
printf("Temperature in F: ");
float f = GetFloat();
// convert F to C
float c = 5 / 9 * F - 32;
// display result
printf("that number in C is %f\n", c);
return 0;
}
float
to store the temperature given by the user, because temperatures can have decimal places. We could use a double
, but this feels like overkill. We should also get into the habit of commenting our code from now on. Comments empower you and anyone else reading your code (e.g. your TF) to skim it and know exactly what it does without necessarily diving into each line.Of course, there's a bug in the code as written above. Recall "order of operations" from elementary school. This same concept applies in programming and is called operator precedence. Let's fix it by inserting parentheses:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for temperature
printf("Temperature in F: ");
float f = GetFloat();
// convert F to C
float c = (5 / 9) * (f - 32);
// display result
printf("that number in C is %f\n", c);
return 0;
}
F
to f
in our formula because variable names are case-sensitive. If we compile and run this program as is, we'll actually get 0 printed out for every input. Why? We have the same problem with integer division that we did when we divided 1 by 10 in math3.c
. 5 / 9
will always return 0 because it can't give us non-integers. If we write 5 / 9.0
instead, we get a float
result. The compiler knows that dividing an int
by a float
should return a float
to maintain precision. Alternatively, we could write 5 / (float) 9
, which will cast or convert 9 to a float
.When we compile and run the fixed version, we get correct outputs! To make it a little prettier, let's change the formatting character:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for temperature
printf("Temperature in F: ");
float f = GetFloat();
// convert F to C
float c = (5 / 9.0) * (f - 32);
// display result
printf("that number in C is %.1f\n", c);
return 0;
}
nonswitch.c
demonstrates the use of conditions and boolean expressions in C:
/******************************************************************
* nonswitch.c
*
* David J. Malan
* malan@harvard.edu
*
* Assesses the size of user's input.
*
* Demonstrates use of Boolean ANDing.
******************************************************************/
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for an integer
printf("Give me an integer between 1 and 10: ");
int n = GetInt();
// judge user's input
if (n >= 1 && n <= 3)
printf("You picked a small number.\n");
else if (n >= 4 && n <= 6)
printf("You picked a medium number.\n");
else if (n >= 7 && n <= 10)
printf("You picked a big number.\n");
else
printf("You picked an invalid number.\n");
return 0;
}
&&
is equivalent to the "and" operator in Scratch. Thus, the first condition implies that the number must be both greater than or equal to 1 and less than or equal to 3. Greater than or equal to is written as >=
and less than or equal to is written as <=
.Let's change our program to test for a single number:
/******************************************************************
* nonswitch.c
*
* David J. Malan
* malan@harvard.edu
*
* Assesses the size of user's input.
*
* Demonstrates use of Boolean ANDing.
******************************************************************/
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for an integer
printf("Give me an integer between 1 and 10: ");
int n = GetInt();
// judge user's input
if (n == 42)
{
printf("You picked the right answer.\n");
}
else
{
printf("You picked the wrong answer.\n");
}
return 0;
}
==
operator. We use this rather than =
to test for equality. In fact, if we use =
, we'll actually be assigning the value 42 to n
. By the way, when reading n = 42
in plain English, David often says "n gets 42." That just means that 42 is assigned to n
.n = 42
instead of n == 42
? Then we'll be checking whether the value 42 evaluates to true. As it turns out, any non-zero value is considered "true" in programming. Thus, if we write n = 42
, the first condition will always evaluate to true and the program will always print "You picked the right answer."One other thing to note, not all of the curly braces are necessary in the program above. We could rewrite it as follows:
/******************************************************************
* nonswitch.c
*
* David J. Malan
* malan@harvard.edu
*
* Assesses the size of user's input.
*
* Demonstrates use of Boolean ANDing.
******************************************************************/
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for an integer
printf("Give me an integer between 1 and 10: ");
int n = GetInt();
// judge user's input
if (n == 42)
printf("You picked the right answer.\n");
else
printf("You picked the wrong answer.\n");
return 0;
}
if
or else
. If you do use the curly braces (probably good practice for now), there are multiple different ways of writing them that are considered correct style. In general, just be consistent.We can rewrite nonswitch.c
using another piece of C syntax called the switch statement:
/******************************************************************
* switch1.c
*
* David J. Malan
* malan@harvard.edu
*
* Assesses the size of user's input.
*
* Demonstrates use of a switch.
******************************************************************/
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for an integer
printf("Give me an integer between 1 and 10: ");
int n = GetInt();
// judge user's input
switch (n)
{
case 1:
case 2:
case 3:
printf("You picked a small number.\n");
break;
case 4:
case 5:
case 6:
printf("You picked a medium number.\n");
break;
case 7:
case 8:
case 9:
case 10:
printf("You picked a big number.\n");
break;
default:
printf("You picked an invalid number.\n");
}
return 0;
}
switch
takes a variable on whose value the behavior of the statement depends. Within the switch statement, you can define multiple case
statements whose values are tested against the value of the variable. Each case
statement falls through to the next unless there is a break
statement. So, in the above, the cases where n
is equal to 1, 2, or 3 are all treated the same, the cases where n
is equal to 4, 5, or 6 are all treated the same, and the cases where n
is equal to 7, 8, 9, or 10 are all treated them same. Finally, the default
keyword defines behavior when no other case
statement is executed.switch2.c
for an example.printf
statements. Better yet, let's do it by writing a loop.for loops take the following general structure:
for (initializations; condition; updates)
{
// do this again and again
}
for
keyword, there are three parts. Before the first semicolon, we are initializing a variable which will be our iterator or counter, often named i
by convention. Between the two semicolons, we're providing a condition which, if true, will cause another iteration of the loop to be executed. Finally, we provide code to update our iterator.Our countdown program, then, looks like so:
#include <stdio.h>
int main(void)
{
for (int i = 10; i >= 0; i--)
{
printf("%d\n", i);
}
return 0;
}
printf
statements, we'd have to add 90 lines of code to count down from 100 instead of 10. Using a loop, we only need to add one character.--
operator means "subtract 1." If we change this to ++
, we can see that our program executes seemingly forever.Another type of loop is the do-while loop. One use case for the do-while loop is when we want to get input from the user:
/******************************************************************
* positive1.c
*
* David J. Malan
* malan@harvard.edu
*
* Demands that user provide a positive number.
*
* Demonstrates use of do-while.
******************************************************************/
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// loop until user provides a positive integer
int n;
do
{
printf("I demand that you give me a positive integer: ");
n = GetInt();
}
while (n < 1);
printf("Thanks for the %d!\n", n);
return 0;
}
do
block will always be executed once at the least. In this context, that means the user will always be prompted for a positive integer at least once. If the user fails to provide a positive integer, he or she will be reprompted. The loop continues to execute as long as the while
condition evaluates to true.