Andrew Sellergren
sudo yum -y update appliance50
to fix any issues you might have with the Appliance. This is analogous to getting automatic updates on your operating system.To get us warmed up for the day, let's take a look at a program that has an interesting bug:
/****************************************************************************
* buggy1.c
*
* David J. Malan
* malan@harvard.edu
*
* Should print 10 asterisks but doesn't!
* Can you find the bug?
***************************************************************************/
#include <stdio.h>
int main(void)
{
for (int i = 0; i <= 10; i++)
printf("*");
return 0;
}
/*
and */
at the top is a comment and won't be interpreted as source code by the compiler. Comments are notes that the programmer writes to clarify what the program does and how the code works.#include <stdio.h>
asks the compiler to include a file stdio.h
which contains the declarations of useful functions like printf
. To include the file, the compiler more or less copies and pastes its contents at the top of our source code.i <= 10
should really be i < 10
.i++
to i--
in the above, we'll iterate seemingly forever. At some point, we actually might iterate below negative 2 billion and wrap around to positive 2 billion, in which case the loop would actually stop. i++
and i--
are syntactic sugar, or shorthand. They are really short for i = i + 1
and i = i - 1
respectively.clang buggy1.c
in our terminal window. When we try to run ./buggy1
, we get a "No such file or directory" error. Remember that unless we provide the -o
flag, the default name for our program will be a.out
.We face another bug in buggy2.c
:
/****************************************************************************
* buggy2.c
*
* David J. Malan
* malan@harvard.edu
*
* Should print 10 asterisks, one per line, but doesn't!
* Can you find the bug?
***************************************************************************/
#include <stdio.h>
int main(void)
{
for (int i = 0; i <= 10; i++)
printf("*");
printf("\n");
return 0;
}
"ls"
instead of just ls
? As a workaround for a bug in the Appliance in which terminal output was printed as black on a black background. This bug has been fixed in the latest version of the Appliance.make
come from? It's a program built in to Linux.make
know what source code file to use? When you type make buggy1
, it assumes that the name of the source code file is buggy1.c
. make
has the convenience of passing some command line flags to clang
for us, including -o buggy1
.&&
("and") and ||
("or"). We also have switch statements at our disposal.To refresh ourselves with the syntax for loops, we'll start a new program from scratch:
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 10; i++)
printf("*");
printf("\n");
return 0;
}
As before, this program prints 10 asterisks plus a single newline character. If we were to convert this for loop to a while loop, it would begin like so:
#include <stdio.h>
int main(void)
{
while (i < 10)
printf("*");
printf("\n");
return 0;
}
Already there's a problem though. We haven't declared a variable named i
. Indeed, the compiler yells at us for "unddeclared identifier." In contrast to languages like Python and Ruby, C requires us to explicitly declare variables and their types. We can fix this compiler error by declaring i
:
#include <stdio.h>
int main(void)
{
int i;
while (i < 10)
printf("*");
printf("\n");
return 0;
}
Now, however, we get a compiler error for "variable uninitialized when used here." This makes sense since we haven't specified what the starting value for i
is. The fix is simple:
#include <stdio.h>
int main(void)
{
int i = 0;
while (i < 10)
printf("*");
printf("\n");
return 0;
}
When we compile and run this program, we get asterisks printed out to the screen indefinitely because we don't ever update the value of i
. We fix that like so:
#include <stdio.h>
int main(void)
{
int i = 0;
while (i < 10)
{
printf("*");
i++;
}
printf("\n");
return 0;
}
for loops have the advantage of being explicit, but soon we'll see that while loops are more useful in other contexts like lists and hash tables. A third type of loop, the do-while loop, is useful when we want to ensure that some piece of code is executed at least once. One use case for this is checking user input. Let's try this out:
#include <stdio.h>
int main(void)
{
do
{
printf("Give me an int: ");
int i = GetInt();
}
while (i < 0 || i > 10)
printf("Thanks, i is %d\n", i);
return 0;
}
i
while we're in the loop. When we get to the line to print out the value of i
, the operating system has already forgotten about it. We say that i
only exists within the scope of the loop.The fix is to declare i
outside the curly braces:
#include <stdio.h>
int main(void)
{
int i;
do
{
printf("Give me an int: ");
i = GetInt();
}
while (i < 0 || i > 10)
printf("Thanks, i is %d\n", i);
return 0;
}
GetInt
.GetInt
.We'll run into the same problem with scope if we try to reuse the counter variable from a for loop:
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 10; i++)
printf("*");
printf("i is now %d", i);
return 0;
}
i
is still only valid inside of the for loop.beer1.c
even takes user input to know how many bottles of beer there are:
/****************************************************************************
* beer1.c
*
* David J. Malan
* malan@harvard.edu
*
* Sings "99 Bottles of Beer on the Wall."
*
* Demonstrates a for loop (and an opportunity for hierarchical
* decomposition).
***************************************************************************/
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for number
printf("How many bottles will there be? ");
int n = GetInt();
// exit upon invalid input
if (n < 1)
{
printf("Sorry, that makes no sense.\n");
return 1;
}
// sing the annoying song
printf("\n");
for (int i = n; i > 0; i--)
{
printf("%d bottle(s) of beer on the wall,\n", i);
printf("%d bottle(s) of beer,\n", i);
printf("Take one down, pass it around,\n");
printf("%d bottle(s) of beer on the wall.\n\n", i - 1);
}
// exit when song is over
printf("Wow, that's annoying.\n");
return 0;
}
return 1
. Recall that non-zero return values for main
indicate errors. By returning one here, we shortcircuit the program so that nothing below this line is executed.i >= 0
as our termination condition, we would encounter an off-by-one error. The last verse would include a negative number of bottles of beer, which doesn't make sense.beer2.c
makes some improvement on our current design:
/****************************************************************************
* beer2.c
*
* David J. Malan
* malan@harvard.edu
*
* Sings "99 Bottles of Beer on the Wall."
*
* Demonstrates a while loop (and an opportunity for hierarchical
* decomposition).
***************************************************************************/
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for number
printf("How many bottles will there be? ");
int n = GetInt();
// exit upon invalid input
if (n < 1)
{
printf("Sorry, that makes no sense.\n");
return 1;
}
// sing the annoying song
printf("\n");
while (n > 0)
{
printf("%d bottle(s) of beer on the wall,\n", n);
printf("%d bottle(s) of beer,\n", n);
printf("Take one down, pass it around,\n");
printf("%d bottle(s) of beer on the wall.\n\n", n - 1);
n--;
}
// exit when song is over
printf("Wow, that's annoying.\n");
return 0;
}
i
and simply use the user's input as our counter variable. Functionally, this program is identical to beer1.c
. Which is better? beer2.c
uses less space (one less int
needs to be stored). beer1.c
has the advantage, however, of not altering the user's input. If we wanted to use that later in the program, perhaps to print "Thanks for playing 99 Bottles of Beer," we can't in beer2.c
because we decremented that number all the way to 0. To be honest, there's no right answer as to which program is better. They have different designs, both of which get the job done.Let's handle the annoying grammar issue that we avoided previously by writing "bottle(s)":
/****************************************************************************
* beer1.c
*
* David J. Malan
* malan@harvard.edu
*
* Sings "99 Bottles of Beer on the Wall."
*
* Demonstrates a for loop (and an opportunity for hierarchical
* decomposition).
***************************************************************************/
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for number
printf("How many bottles will there be? ");
int n = GetInt();
// exit upon invalid input
if (n < 1)
{
printf("Sorry, that makes no sense.\n");
return 1;
}
// sing the annoying song
printf("\n");
for (int i = n; i > 0; i--)
{
if (i == 1)
{
string s1 = "bottle"; // for 1 bottle
string s2 = "bottles"; // for 0 bottles
}
else if (i != 1)
{
string s1 = "bottles"; // for plural bottles
string s2 = "bottles"; // for plural bottles
}
// sing verses
printf("%d bottle(s) of beer on the wall,\n", i, s1);
printf("%d bottle(s) of beer,\n", i, s1);
printf("Take one down, pass it around,\n");
printf("%d bottle(s) of beer on the wall.\n\n", i - 1, s2);
}
// exit when song is over
printf("Wow, that's annoying.\n");
return 0;
}
Note that we can add single-line comments at the end of lines of code using //
. What's wrong with the above, though? We're trying to use s1
and s2
outside their scope, which is inside the if conditions. We need to declare them outside the curly braces:
/****************************************************************************
* beer1.c
*
* David J. Malan
* malan@harvard.edu
*
* Sings "99 Bottles of Beer on the Wall."
*
* Demonstrates a for loop (and an opportunity for hierarchical
* decomposition).
***************************************************************************/
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for number
printf("How many bottles will there be? ");
int n = GetInt();
// exit upon invalid input
if (n < 1)
{
printf("Sorry, that makes no sense.\n");
return 1;
}
// sing the annoying song
printf("\n");
for (int i = n; i > 0; i--)
{
string s1, s2;
if (i == 1)
{
s1 = "bottle"; // for 1 bottle
s2 = "bottles"; // for 0 bottles
}
else if (i != 1)
{
s1 = "bottles"; // for plural bottles
s2 = "bottles"; // for plural bottles
}
// sing verses
printf("%d bottle(s) of beer on the wall,\n", i, s1);
printf("%d bottle(s) of beer,\n", i, s1);
printf("Take one down, pass it around,\n");
printf("%d bottle(s) of beer on the wall.\n\n", i - 1, s2);
}
// exit when song is over
printf("Wow, that's annoying.\n");
return 0;
}
We can declare multiple variables of the same type on a single line simply by separating them with commas. There's still a bug, though. In the second-to-last verse, we write "1 bottles of beer." Looks like there's more than one corner case we need to handle: where i
is 1 and where i
is 2. Let's try this:
/****************************************************************************
* beer1.c
*
* David J. Malan
* malan@harvard.edu
*
* Sings "99 Bottles of Beer on the Wall."
*
* Demonstrates a for loop (and an opportunity for hierarchical
* decomposition).
***************************************************************************/
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for number
printf("How many bottles will there be? ");
int n = GetInt();
// exit upon invalid input
if (n < 1)
{
printf("Sorry, that makes no sense.\n");
return 1;
}
// sing the annoying song
printf("\n");
for (int i = n; i > 0; i--)
{
string s1, s2;
if (i == 1)
{
s1 = "bottle"; // for 1 bottle
s2 = "bottles"; // for 0 bottles
}
else if (i == 2)
{
s1 = "bottles"; // for 2 bottles
s2 = "bottle"; // for 1 bottle
}
// sing verses
printf("%d bottle(s) of beer on the wall,\n", i, s1);
printf("%d bottle(s) of beer,\n", i, s1);
printf("Take one down, pass it around,\n");
printf("%d bottle(s) of beer on the wall.\n\n", i - 1, s2);
}
// exit when song is over
printf("Wow, that's annoying.\n");
return 0;
}
Compiling and running this program gives us a segmentation fault. What this means is that your program has tried to access memory that it doesn't own. We've given s1
and s2
values in the cases where i
is equal to 1 or 2, but not in all other cases. We need to add an else condition:
/****************************************************************************
* beer1.c
*
* David J. Malan
* malan@harvard.edu
*
* Sings "99 Bottles of Beer on the Wall."
*
* Demonstrates a for loop (and an opportunity for hierarchical
* decomposition).
***************************************************************************/
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for number
printf("How many bottles will there be? ");
int n = GetInt();
// exit upon invalid input
if (n < 1)
{
printf("Sorry, that makes no sense.\n");
return 1;
}
// sing the annoying song
printf("\n");
for (int i = n; i > 0; i--)
{
string s1, s2;
if (i == 1)
{
s1 = "bottle"; // for 1 bottle
s2 = "bottles"; // for 0 bottles
}
else if (i == 2)
{
s1 = "bottles"; // for 2 bottles
s2 = "bottle"; // for 1 bottle
}
else
{
s1 = "bottles"; // for plural bottles
s2 = "bottles"; // for plural bottles
}
// sing verses
printf("%d bottle(s) of beer on the wall,\n", i, s1);
printf("%d bottle(s) of beer,\n", i, s1);
printf("Take one down, pass it around,\n");
printf("%d bottle(s) of beer on the wall.\n\n", i - 1, s2);
}
// exit when song is over
printf("Wow, that's annoying.\n");
return 0;
}
A quick compile and run shows that we've fixed the segmentation fault and the grammar problem. However, we've done it in a rather inelegant way. We can actually represent this logic in a much more succinct way:
/****************************************************************************
* beer1.c
*
* David J. Malan
* malan@harvard.edu
*
* Sings "99 Bottles of Beer on the Wall."
*
* Demonstrates a for loop (and an opportunity for hierarchical
* decomposition).
***************************************************************************/
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for number
printf("How many bottles will there be? ");
int n = GetInt();
// exit upon invalid input
if (n < 1)
{
printf("Sorry, that makes no sense.\n");
return 1;
}
// sing the annoying song
printf("\n");
for (int i = n; i > 0; i--)
{
string s1 = (i == 1) ? "bottle" : "bottles";
string s2 = (i == 2) ? "bottles" : "bottle";
// sing verses
printf("%d bottle(s) of beer on the wall,\n", i, s1);
printf("%d bottle(s) of beer,\n", i, s1);
printf("Take one down, pass it around,\n");
printf("%d bottle(s) of beer on the wall.\n\n", i - 1, s2);
}
// exit when song is over
printf("Wow, that's annoying.\n");
return 0;
}
(a) ? x : y
is an operator which says "if a is true, then x, else y".Let's write a program that simply increments the value of a variable:
#include <stdio.h>
int main(void)
{
int x = 2;
printf("x is %d\n", x);
x++;
printf("x is %d\n", x);
return 0;
}
Quite easily, we can change this into a program that cubes the value of a variable:
#include <stdio.h>
int main(void)
{
int x = 2;
printf("x is %d\n", x);
x = x * x * x;
printf("x is %d\n", x);
return 0;
}
There's an opportunity here for refinement. We can write a function which cubes its input:
#include <stdio.h>
int main(void)
{
int x = 2;
printf("x is %d\n", x);
x = cube(x);
printf("x is %d\n", x);
return 0;
}
int cube(int input)
{
int output = input * input * input;
return output;
}
cube
resembles our usual definition of main
: we specify the return type as int
(just like main
) and we specify the argument as int input
(unlike void
for main
).Compiling this program gives us an "implicit declaration" error. The problem is that the C compiler reads your code from top to bottom. When it encounters the keyword cube
, the function itself hasn't been defined yet. We can fix this in two ways: we can move the definition of cube
above the definition of main
or we can declare a function prototype above main
. The function prototype is simply the following:
// prototype
int cube(int input);
The prototype simply specifies the return type, name, and arguments of the function. With this above main
and the actual definition of cube
at the bottom, the program compiles and runs.
With this ability to write functions in hand, let's return to our "99 Bottles of Beer" problem and put the chorus into its own function:
/****************************************************************************
* beer4.c
*
* David J. Malan
* malan@harvard.edu
*
* Sings "99 Bottles of Beer on the Wall."
*
* Demonstrates hierarchical decomposition and parameter passing.
***************************************************************************/
#include <cs50.h>
#include <stdio.h>
// function prototype
void chorus(int b);
int main(void)
{
// ask user for number
printf("How many bottles will there be? ");
int n = GetInt();
// exit upon invalid input
if (n < 1)
{
printf("Sorry, that makes no sense.\n");
return 1;
}
// sing the annoying song
printf("\n");
for (int i = n; i > 0; i--)
{
chorus(i);
}
// exit when song is over
printf("Wow, that's annoying.\n");
return 0;
}
/**
* Sings about specified number of bottles.
*/
void chorus(int b)
{
// use proper grammar
string s1 = (b == 1) ? "bottle" : "bottles";
string s2 = (b == 2) ? "bottle" : "bottles";
// sing verses
printf("%d %s of beer on the wall,\n", b, s1);
printf("%d %s of beer,\n", b, s1);
printf("Take one down, pass it around,\n");
printf("%d %s of beer on the wall.\n\n", b - 1, s2);
}
chorus
has a return type of void
, which means it doesn't return anything. Rather, it only has a side effect of printing something. Functionally, this program is equivalent to our earlier one, but it's perhaps a little more readable.