You are on page 1of 22

Introduction00:00

The Harvard Mark I was an early computer, now on display in the Science
Center.
In addition to these notes, slides, source code, and video from each lecture
are available online; plus walkthroughs associated with each problem set, which
are included in the problem set specifications.
Starting this week, well look at a lower level programming language called C,
relying on some of the concepts we introduced with Scratch last week:

The block is an example of a statement or function.

and are types of loops.

Hexagon blocks are boolean expressions inside conditions like

. Nested conditions can allow for three,


four, or more branches.

Announcements04:30
Sectioning Wednesday through Friday; you can select a less comfortable,
more comfortable, or in-between section.
Supersections take place in Week 2, to cover the early material while we get
sections worked out (which takes a while in a course this large!).
Sections will start Week 3 (two weeks from now).
Problem Set 0 has been posted.
This semester, students have nine late days, and only one can be used per
problem set. This does essentially mean that all the problem sets are due
Fridays, but you should try to finish by Thursdays, if only because everything in
the software world takes longer than you expect it to!
In-person support available at office hours in Annenberg and Widener, as
well as at Yale.

C06:43
Last week we started working with pictoral, puzzle-piece based code.
Today well start to look at source code that looks like this:

#include <stdio.h>

int main(void)
{
printf("hello, world\n");

Lets open a blank file and save it as hello.c. Well type in that code example
above that we keep using.
We can then run our program like so (more on the purpose of this first "make"
step in a bit!):

jharvard@ide50:~/workspace $ make hello


clang -ggdb3 -O0 -std=c99 -Wall -Werror hello.c -lcs50 -lm -o hello
jharvard@ide50:~/workspace $ ./hello
hello, world

jharvard@ide50:~/workspace $

To turn the source code into a piece of software that we can run, we use a
program called a compiler. A compiler converts source code to object code,
or 0s and 1s that a computer can understand.
o C source code may look esoteric, but its much more friendly to humans
than if we had to program in 0s and 1s directly, as our predecessors once did!
To bridge between the puzzle pieces we used in Scratch and the C syntax well
be using moving forward, lets break down this example and compare it to the
way we would express the same idea in Scratch:
#include <stdio.h>

int main(void)
{
printf("hello, world\n");

}Just as the block represents a function in Scratch that prints


text to the screen, printf is a function in C that does the same. Just like
Scratch had a little white text box placeholder where you could type an
input, printf takes input in between the parentheses.

main is equivalent to - it indicates where the program


should start. More on what the int and (void) in that line signify later.

while (true)
{
printf("hello, world\n");

}In this case, while (true) works the same way as the
block in Scratch. A while loop continues executing as long as its
condition evaluates to true, so if we give it just the expression true
(which will always evaluate to true), the loop will run forever.

for (int i = 0; i < 10; i++)


{
printf("hello, world!\n");
}

When you want to repeat something a finite number of times, for (int i =

0; i < 10; i++) is equivalent to in Scratch.


int counter = 0;
while (true)
{
printf("%i\n", counter); (4)
counter++;
}

A variable stores a value in C just as in Scratch, and in C we can say int

counter = 0 instead of . We label our


variable declaration with int to signify that we want to store an integer,
or number.

The %i that we use in our printf statement in line 4 acts as a placeholder


to print a decimal number, and we tell printf to replace the placeholder
with the value of the variable counter.
(x < y)
((x < y) && (y < z))

Boolean expressions look pretty similar, involving more parentheses to


ensure that the compiler interprets our expression in the correct order.

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");
}

Conditions, as well, have a lot in common between C and Scratch.

CS50 IDE13:38
Weve previously used a virtual machine called the CS50 Appliance to make
sure that everyone had a consistent setup, but starting this year, youll instead
use a web-based coding environment called the CS50 IDE to achieve the same
effect. IDE stands forintegrated development environment.
The CS50 Appliance took up a lot of space and required a lot of resources on
students' computers; instead of living on your computer locally, the CS50 IDE
lives in the cloud.
o The cloud simply refers to a bunch of computers elsewhere that store
data and run software and the like that we can rent.
Just like Scratch had several panels, so does the CS50 IDE: we have a file
browser, a window into which you can actually type your code, and a terminal in
which you can run your code.
Thanks to the efforts of Dan Armendariz and Dan Bradley on the CS50 staff,
the CS50 IDE allows you to toggle Less Comfortable mode on or off (on by
default) to give less comfortable students sensible defaults and a clear interface
while also allowing more comfortable students to fine-tune their environment as
they see fit.

Writing Code17:15
We can open up the CS50 IDE in a browser and take a look at an actual
program - for now, the same hello, world example that weve been working with
so far:

1#include <stdio.h>
2
3int main(void)
4{
5 printf("hello, world"\n);

6}
Weve discussed the role of main and of printf, and the curly braces merely
serve to enclose the meat of our program ( printf("hello, world"\n);) and associate
it with main.
#include <stdio.h> gives us access to a library, a collection of functions that
someone else wrote, so we dont have to reinvent the wheel. stdio.h is the
header file for the standard I/O library in C, which includes functions like printf.
Before, we ran make hello before running our program, which compiled our
source code.
o Under the hood, make calls the actual compiler, clang - so we could just
type clang hello.c (although if we dont specify a name for the program the
compiler should output using the -o option, it will be named a.out by default).
o Everything we type on the command line after the program were
running (like clang) is a command-line argument. When we type clang -o
hello hello.c, -o hello tells clang to output a program called hello (rather than
the default a.out), whilehello.c tells clang the name of the file where it will find
our source code to compile.
make hello looks in the current directory for a file called hello.c and compiles it
to a program called hello, and weve set up the CS50 IDE to also tell make to
include various other useful arguments that you dont need to worry about for
now.
You must save your .c files and recompile after making a change to your code
before being able to see the effect of your change on running the program.
Lets make this a little more dynamic, by making our program say hello to
someone in particular.

1#include <stdio.h>
2
3int main(void)
4{
5 string s = "Hannah";
6 printf("hello, %s\n", s);

7}

We must specify the type of our variable when we declare ithere a string, a
sequence of characters.
The equals sign assigns the value on the right to the variable name on the
left.
The semicolon weve been placing at the end of each line tells the computer
where each command terminates.
%s is a placeholder for a string, which will be replaced by printf when it runs.
We then tell printf what to fill in by giving it an additional argument (functions
can be given more than one argument using a comma-separated list).
When we try to compile the above code, we get a ton of errors. The best way
to work through these errors is starting at the top, because later errors are often
caused by the computer getting confused after encountering the first problem
and may refer to lines of code that are actually perfectly fine.

jharvard@ide50:~/workspace $ make hello


clang -ggdb3 -O0 -std=c99 -Wall -Werror hello.c -lcs50 -lm -o hello-1
hello.c:5:5: error: use of undeclared identifier 'string'; did you mean 'stdin'?
string s = "David";
^~~~~
stdin

...

This first error, undeclared identifier 'string', is the one we want. string doesnt
actually exist as a type in C, but for the first few weeks of CS50, well provide you
some types (such as string and bool for boolean values, which also dont exist
natively in C) and functions to abstract over lower-level features of C well
examine later. These types and functions are defined in the CS50 Library, which
you can use in much the way youd use the standard I/O library:

#include <cs50.h>

When we add this line, our program compiles just fine!


We prepend the name of our program with ./ (as in ./hello) to tell the computer
to run the program named hello in the current directory (similarly, .. signifies the
directory one level above the current directory, also known as the parent
directory).
We can change the name manually by editing the line that assigns the value
of the string s and recompiling, but what if we want this to be truly dynamic and
print the name of the current user? The CS50 Library, in cs50.h, contains
functions like GetString, which let us get input from the user on demand.

1#include <cs50.h>
2#include <stdio.h>
3
4int main(void)
5{
6 string s = GetString();
7 printf("hello, %s\n", s);

8}

Note that we have parentheses after GetString, signifying that it is a function.


Now we compile and run our program, and the blinking cursor waits for
input.Once we enter a name, the program prints the name as we wanted.
We can do more complex things with code as well, as in adder.c:
1#include <cs50.h>
2#include <stdio.h>
3
4int main(void)
5{
6 // ask user for input
7 printf("Give me an integer: ");
8 int x = GetInt();
9 printf("Give me another integer: ");
10 int y = GetInt();
11
12 // do the math
13 printf("The sum of %i and %i is %i!\n", x, y, x + y);

14}

Just as GetString let us get a string from the user, GetInt lets us get an integer
from the user. If the user does not cooperate - say, by typing something that isnt
an integer - GetInt will repeatedly prompt the user to retry.
Note that printf allows us to have more than one placeholder, but we must
have correspondingly many values in the argument list to fill in.
Make sure youre in the directory with your .c files when you run make,
using cd to switch directories if necessary, otherwise makewont know what
youre trying to do!
Another example in conditions-0.c:

1#include <cs50.h>
2#include <stdio.h>
3
4int main(void)
5{
6 // ask user for an integer
7 printf("I'd like an integer please: ");
8 int n = GetInt();
9
10 // analyze user's input (somewhat inaccurately)
11 if (n > 0)
12 {
13 printf("You picked a positive number!\n");
14 }
15 else
16 {
17 printf("You picked a negative number!\n");
18 }

19}

This is how we represent an if/else construct in C.


Theres a bug in this program: what if the user types in 0? 0 is neither positive
nor negative, but this program will say its a negative number.
To fix this, we add the following lines above the else part of our condition, as
in conditions-1.c:

else if (n == 0)
{
printf("You picked zero!\n");

If we use = rather than ==, the compiler complains that were using the result
of assignment in a condition - a pretty opaque error message, but it means that
a single equals sign is for assigning variable values (as we discussed before),
while a double equals sign is for checking equality as in a conditional.
We should test our code to make sure it works as expected, and the most
effective way to test is to try to break down all the cases and test each of
them (so here, we should test a positive case, a negative case, and zero.
Conditionals can combine multiple boolean expressions (as the

block did in Scratch), as illustrated in


nonswitch.c:

1#include <cs50.h>
2#include <stdio.h>
3
4int main(void)
5{
6 // ask user for an integer
7 printf("Give me an integer between 1 and 10: ");
8 int n = GetInt();
9
10 // judge user's input
11 if (n >= 1 && n <= 3)
12 {
13 printf("You picked a small number.\n");
14 }
15 else if (n >= 4 && n <= 6)
16 {
17 printf("You picked a medium number.\n");
18 }
19 else if (n >= 7 && n <= 10)
20 {
21 printf("You picked a big number.\n");
22 }
23 else
24 {
25 printf("You picked an invalid number.\n");
26 }
27}
Rather than the word and, we use && to indicate boolean and (a single
ampersand is used for another purpose that we wont go into for the
moment).
In imprecision.c, we encounter a somewhat troubling phenomenon:

1#include <stdio.h>
2
3int main(void)
4{
5 printf("%.29f\n", 1.0 / 10.0);

6}
This program prints the result of dividing 1.0 by 10.0 to 29 decimal places
(%f is the placeholder for a floating-point number, and%.29f tells printf to
print the value to 29 decimal places).
This prints 0.100000000000000000555111512313, rather
than 0.100000000000000000000000000000 as expected. Well talk about why
later!
As a final note, see thadgavin.c for an example of some of the wild and crazy
things you can do with your code, both in terms of style (please never hand in
a problem set styled like this source code) and output.

Imprecision00:00
Last week, we saw in imprecision.c that 1.0/10.0 does not in fact equal 0.1 as
expected. Well, actually it does equal 0.1, of course - but the computer gets it a
little bit wrong.
Computers only have a finite amount of memory, so they have to pick and
choose what values theyre going to support.
If we only have 8 bits, we can only represent 256 values (and since we use
one of those values for zero, the greatest number we can represent is 255).
Floating point values are stored a little differently, but the computer still only
uses typically either 32 or 64 bits to store a floating point number - so it cant
possibly represent infinitely many values.
When we get these inaccuracies after many decimal places, were running up
against the hardware limitations of the computer.
Paying attention to how numbers are stored in your code can be critical -
this clip from Modern Marvels shows how disasters can result when numerical
imprecision isnt taken into account in high-precision systems.

Announcements11:57
Supersections this weekend; they will be filmed and streamed live for those
unable to attend.
Problem Set 1 is live on the course website, and due next Thursday.
Office hours will take place Monday through Thursday this week.

C12:19
Lets return to our canonical program from last time:

1#include <stdio.h>
2
3int main(void)
4{
5 printf("hello, world"\n);

6}

Recall that #include <stdio.h> allows us to use the functions of the standard
I/O library, written by other programmers in the past, and declared in a file
called stdio.h elsewhere in our system.
We introduced main last week as the analog of Scratchs [ when [green flag]
clicked ] block. In C and several other languages, your first function must be
called main. Well explain later what int and void are doing here.
printf() is a function that prints out a formatted string. It takes one or more
arguments (also known as parameters or simply inputs). The first is a string - a
word or phrase or even a whole essay - which you usually want to terminate with
a \n to ensure that the output ends with a newline. Subsequent arguments
tell printf what values to fill in for any format strings (such as %f for a floating-
point number) that you included in the first string argument.
o \n in the above is whats known as an escape character - rather than
being interpreted literally as a backslash and the letter n, it tells the compiler
to do something else. In this case, that "something else" is starting a new line.
Semicolons indicate the end of statements, and curly braces delineate blocks
of code (like the structures of control puzzle pieces in Scratch).
Volunteer Kopal acts as the printf function, accepting input from David (acting
as the hello program that calls printf) written on a piece of paper and writing the
input given on the touchscreen, simulating the effect of printf.
Another volunteer, Ife, represents the GetString function. Kopal, as printf,
writes "State your name" on the touchscreen. Ife then gets a name from the
audience and brings it back. David stores the returned name, "Nik", in a variable
called s (by writing it on a sheet of paper labeled s).
David now gives Kopal/printf a sheet of paper that says hello, %s\n and the
sheet of paper containing the value of s. He fills in the name stored in the
variable s, "Nik", in place of the placeholder %s.
This same model of message passing underlies all the code we write, as
outputs of functions are passed as inputs to other functions and so on.
Types20:56
Weve been talking mostly about strings thus far, but values in C can have a
few other types:
o char, a single character (like a or 7 or %), which takes up one byte, or 8
bits; uses a printf format code of %c
o float, a floating-point value (a number with a decimal point,
like 10.0 or 3.14159), which takes up 32 bits (four bytes); %f
o double, a floating-point number that takes up twice as much space as
a float (so 64 bits/8 bytes); also %f
o int, an integer, also 32 bits/4 bytes, meaning that the largest integer we
can represent is roughly 4 billion; %i or %d
o longlong, a 64 bit integer (which can represent much larger values!);
'%lld`
And from the CS50 Library, found in cs50.h:
o bool, true or false
o string, a sequence of characters
We can also use various escape sequences in printf format strings:
o \n for a newline
o \t for a tab character
o \" to include a double-quote in the middle of a printf format string (since
a bare double-quote would make the compiler think it had reached the end of
the string!)
In the CS50 Library, we provide functions
like GetString(), GetInt(), GetFloat(), GetLongLong() and so on, that let you get
input of a specific type from the user. These functions include error checking to
prevent the user from providing invalid input.

Conditions26:41
Conditions have the following structure:

if (condition)
{
// do this

The // in line 3 marks a comment, English words directed at yourself or other


readers of your code. Lines starting with // (or multi-line blocks beginning
with /* and ending with */) tell the compiler not to look for actual instructions
here.
There can also be two exclusive branches:
if (condition)
{
// do this
}
else
{
// do that

Or three:

if (condition)
{
// do this
}
else if (condition)
{
// do that
}
else
{
// do this other thing

Boolean expressions (the conditions inside the conditional) can be combined


with && as "and", and || as "or":

if (condition && condition)


{
// do this
}

if (condition || condition)
{
// do this

}
Switches express the same thing as certain if/else if//else constructs, but
can be more elegant and involve fewer curly braces. They provide no additional
functionality that cant be done with regular conditionals, but can sometimes be
stylistically preferable.
You can use a switch whenever all the conditions of your conditional would be
of the form expression == value for the same expression but different values.

switch (expression)
{
case i:
// do this
break;

case j:
// do that
break;

default:
// do this other thing
break;

Loops28:01
One type of loop in C is the for loop, which has the following basic structure:

for (initializations; condition; updates)


{
// do this again and again

A specific example:

for (int i = 0; i < 50; i++)


{
printf("%i\n", i);

}
o In this case, int i = 0 is the initialization of the loop, telling it to start
counting at zero by creating a variable called i and assigning it the value 0.
o i < 50 is the condition of the loop: immediately after the initialization,
and at the start of every step of the loop thereafter, the condition is checked,
and the code in the body of the loop will only be executed if the condition
evaluates to true.
o i++ is the update of the loop, which will be executed after the body of
the loop to move to the next step.
o Weve put printf("%i\n", i); in the body of the loop, so this code will print
the numbers from 0 to 49 (not 50, because when we update to i = 50, the
condition i < 50 evaluates to false and the body of the loop is not executed).
o The curly braces are not syntactically required if the body of the loop is
only one line, but we will always use them in class (and we request that you do
too!) for clarity and to prevent mistakes.

Integer Overflow28:01
Just as floating point values have limits on their precision, integers have limits
on the size of values they can represent.
For a 32-bit integer, the maximum value is roughly 4 billion.
When a binary number overflows, we go from a value like this (255 stored in 8
bits):

128 64 32 16 8 4 2 1

1 1 1 1 1 1 1 1

To a value like this:

?? 128 64 32 16 8 4 2 1
1 0 0 0 0 0 0 0 0

But this is still only an 8-bit value, so theres nowhere to put the leading 1, and
instead we get:

128 64 32 16 8 4 2 1
0 0 0 0 0 0 0 0

We can see the effects of these limits on integers in various software:


o In the video game Lego Star Wars, the number of coins you can collect
is capped at 4 billion exactly - from which we can infer that the original
developer for this game used a 32-bit integer to store the users number of
coins.
o In the original Civilization game, each world leader was assigned an
aggressiveness score, and Gandhi was given the lowest score of 1. If a nation
transitioned to democracy in the game, the leaders aggressiveness score was
decreased by 2. However, the aggressiveness scores were stored in unsigned
8-bit integers (meaning they couldnt be negative) - so decreasing Gandhis
aggressiveness score to -1 had the effect of looping around to 255 (so Gandhi
became the most aggressive leader in the game!)
o The Boeing 787 would lose all power after 248 days of continuous
operation due to an integer overflow in the control units of its power
generators (the workaround solution is to reboot the plane more often than
that!)

Loops, continued40:44
Slightly different from a for loop, we have a while loop, that merely depends
upon a single condition which is checked before every iteration of the loop:

while (condition)
{
// do this again and again

Similarly, in a do-while loop, the condition is checked after every iteration of


the loop (as indicated by the syntax):

do
{
// do this again and again
}

while (condition);

Variables41:25
As weve discussed, a variable in C has a particular type, which must be
declared when the variable is created. Here, the first line creates a new variable
of the type int, and the second assigns a value of 0 to it. The declaration of the
variable and assigning it a value can happen as far away from each other in code
as you like, but for clarity its best to keep them close together.

int counter;

counter = 0;

A more succinct way to write the above code:

int counter = 0;
Functions and Arguments41:57
Functions are followed by parentheses, which contain any arguments that are
being passed to the function:

string name = GetString();

printf("hello, %s\n", name);

In function-0.c, we show how to define your own function:

1#include <cs50.h>
2#include <stdio.h>
3
4// prototype
5void PrintName(string name);
6
7int main(void)
8{
9 printf("Your name: ");
10 string s = GetString();
11 PrintName(s);
12}
13
14/**
15 * Says hello to someone by name.
16 */
17void PrintName(string name)
18{
19 printf("hello, %s\n", name);

20}

Separating out this logic in PrintName() is a form of abstraction, hiding the


low-level implementation details of how we print the name.
Similarly, in function-1.c, we can use the return value of a function:

1#include <cs50.h>
2#include <stdio.h>
3
4// prototype
5int GetPositiveInt();
6
7int main(void)
8{
9 int n = GetPositiveInt();
10 printf("Thanks for the %i!\n", n);
11}
12
13/**
14 * Gets a positive integer from a user.
15 */
16int GetPositiveInt(void)
17{
18 int n;
19 do
20 {
21 printf("Please give me a positive int: ");
22 n = GetInt();
23 }
24 while (n < 1);
25 return n;

26}

The int in int GetPositiveInt(void), as well as the void in void PrintName(string


name), indicates the return type of the function.
PrintName() doesnt return anything (it just prints a name to the screen, which
is a side effect), so its return type is void.
GetPositiveInt() returns an int - the first positive integer value the user enters -
using the return command. Any non-void function must have a return value (if
you dont have a return command, your return value is assumed to be 0, for
reasons well discuss later in the course).
In return.c, we have another example of returning a value from a function:

1#include <stdio.h>
2
3// function prototype
4int cube(int a);
5
6int main(void)
7{
8 int x = 2;
9 printf("x is now %i\n", x);
10 printf("Cubing...\n");
11 x = cube(x);
12 printf("Cubed!\n");
13 printf("x is now %i\n", x);
14}
15
16/**
17 * Cubes argument.
18*/
19int cube(int n)
20{
21 return n * n * n;

22}

In this case, this function both accepts an input argument (an int, which were
calling n) and outputs, or returns, a value.

Problem Set 149:18


On this problem set, youll implement in C an ASCII version of Marios pyramid
(or a more challenging version in the hacker edition!)
Youll also implement a greedy algorithm for determining the coins
necessary when giving change, and investigate rates of water flow.
Copyright CS50

You might also like