[MUSIC PLAYING]
DAVID J. MALAN: All right.
So this is CS50.
And this is week 1, the one in which you learn a new language, which
is something we technically said last week, at least if you had never
played with this graphical language known as Scratch before, which itself
was a programming language.
But today, as promised, we transition to something
a little more traditional, a little more text-based,
not puzzle piece- or block-based, known as C.
This is an older language.
It's been around for decades.
But it's a language that underlies so many of today's more modern languages,
among them something called Python that we'll also
come to in a few weeks' time.
Indeed, at the end of the semester, the goal
is for you to feel that you've not learned Scratch,
you've not learned C, or even Python, for that matter,
but fundamentally that you've learned how to program.
Unfortunately, when you learn how to program
with a more traditional language like this, there's just so much distraction.
Last week I described all of the syntax, all of the weird punctuation
that you see in this, like the hash symbol, these angled brackets,
parentheses, curly braces, backslash n, and more.
Well, today we're not going to reveal what all of those little particulars
mean.
But by next week, will this no longer look like the proverbial Greek
to you, a language that, presumably, you've never actually seen or typed
before.
But to do that, we'll explore some of the very same topics as last week.
So recall that, via Scratch-- and presumably via problem set 1--
we took a look at things called functions that are actions or verbs.
And related to functions were arguments like inputs.
And related to some functions were returned values like outputs.
Then we talked a bit about conditionals, forks in the road, so to speak,
Boolean expressions, which are just yes/no questions or true/false
questions, loops, which let you do things again and again, variables,
like in math, that let you store values temporarily,
and then even other topics still.
So if you were comfortable on the heels of problem set 0 and last week,
realize that all of these topics are going to remain with us.
So really, today is just about acquiring all the more of a mental model for how
you translate those ideas into, presumably, a very cryptic new syntax--
a new syntax, frankly, that's actually more
simple in some ways than your own human language,
be it English or something else, because there's far fewer vocabulary words.
There's actually far less syntax that you might
have in, say, a typical human language.
But you need to be with these computer languages all the more precise
so that you're most, ultimately, correct,
and ultimately will see to your code is successful along a few other lines
as well.
So if you think about the last time you kind of wandered around not really
knowing what you were doing or encountered something new--
might not have been that long ago, entering Harvard Yard for the very
first time, or Old Campus or the like, be it in Cambridge or New Haven--
you didn't really need to know how to do everything as a first year.
You didn't need to know who everyone was,
where everything was, how Harvard or Yale, or anything else for that matter,
worked.
You sort of got by day to day by just focusing on those things that matter.
And anything you didn't really understand,
you sort of turned a blind eye to until it's important.
And that's, indeed, what we're going to do today.
And really, for the next several weeks, we'll
focus on details that are initially important
and try to wave our hands, so to speak, at details that, yeah, eventually
we'll get to, might be interesting.
But for now, they might be distractions.
And by distractions, I really mean some of that syntax
to which I alluded earlier.
So by the end of today-- and really, by the end of problem set 1,
your first foray, presumably, into this language called C--
you'll have written some code.
And you'll be asking yourself-- we'll be asking yourselves--
just how good is that code?
Well, first and foremost, per last week, be it in Scratch
or phone book form, code ultimately needs to be correct, to be well done.
You want the problem to be solved correctly.
So that one sort of goes without saying.
And along the way this term, we'll provide you with tools and techniques
so you don't have to just sit there sort of endlessly trying an input,
checking the output, trying another input, checking the output.
There's a lot of automation tools in the real world--
and in this class and others like it-- that
will help facilitate you answering that question for yourself, is my code
correct, according to our specifications or the like.
But then something that's going to take more time
and you're probably not going to feel 100% comfortable with the first week,
the first weeks, is just how well designed your code is.
It's one thing to speak English or write English,
but it's another thing-- or any language, for that matter.
But it's another thing to speak it or write it well.
And we spend all these years in middle school, high school, presumably,
writing papers and other documents, getting grades and feedback on them
as to how well formulated your arguments were, how well structured
your paper was, and the like.
And there's that same idea in programming.
It doesn't matter necessarily that you've just solved a problem correctly.
If your code is a complete visual mess, or if it's crazy long,
it's going to be really hard for someone else
to wrap their mind around what your code is doing and, indeed, to be confident
if it is correct.
And honestly, you-- the next morning, the next year,
the next time you look at that code-- might have no idea
what you yourself were even thinking.
But you will if you focus, too, on designing good code,
getting your algorithms efficient, getting your code nice and clean,
and even making sure your code looks pretty, which
we'd describe as a matter of style.
So in the written human world, having punctuation in the right place,
capitalization and the like-- the sort of way you write an essay
but not necessarily send a text message--
relates to style, for instance.
And so good style in code is going to have
a few of these characteristics that are pretty easily taught and remembered.
But you just have to start to get in the habit of writing code in a certain way.
So these three axes, so to speak, correctness, design, and style,
are really the overarching goals when writing code that
ultimately is going to look like this.
So this program we conjectured last week does
what if you run it on a Mac or PC or somewhere else, presumably?
What does it do?
Yeah?
AUDIENCE: [INAUDIBLE].
DAVID J. MALAN: It just prints, Hello, world.
And honestly, that's kind of atrocious that you
need to hit your keyboard keys this many times with this cryptic syntax just
to get a program to say, Hello, world.
So a spoiler-- in a few weeks' time when we
introduce other, more modern languages, like Python,
you can distill this same logic into literally one line of code.
And so we're getting there, ultimately.
But it's helpful to understand what it is that's going on here,
because even though this is a pretty cryptic syntax,
there's nothing after this week and, really, next week that you shouldn't
be able to understand even about something that right now looks
a little something like this.
So how do you write code?
Well, I've given us sort of the answer to a problem.
How do you print, Hello, world, on the screen?
So what do I do with this code?
Well, we're in the habit of typically writing things with, like, Microsoft
Word or Google documents.
And yeah, I could open up Word or Google Docs or Pages or the like
and just literally transcribe that character for character,
save it, and boom, I've got a program.
But the problem, per last week, is that computers only understand or speak
what other language, so to speak?
AUDIENCE: [INAUDIBLE].
DAVID J. MALAN: Yeah, so binary, zeros and ones.
And so this, obviously, is not zeros and ones.
So it doesn't matter if I put it in a Word doc, Google Doc, Pages file,
or the like.
The computer is not going to understand it until I somehow
translate it to zeros and ones.
And honestly, none of those tools that I rattled off
are really appropriate for programming.
Why?
Well, they come with features like bold facing and italics
and sort of fluffy, aesthetic stuff that has no functional impact on what
you're trying to do with your code.
And they don't have the ability, it would
seem, to convert that code ultimately to zeros and ones.
But tools that do have this capability might
be called Integrated Development Environments, or IDEs,
or, more simply, text editors.
A text editor is a tool that a programmer uses perhaps every day
to write their code.
And it's a simple program-- here, for instance, a very popular one
called Visual Studio Code, or VS Code.
And at the top here, you see that I've actually
created in advance before class a very simple empty file called "hello.c."
Why?
Well, .c indicates by convention that this is going to be a file in which
there is C code.
It's not .docx, which would mean in this file is a Microsoft Word document,
or .pages is a Pages file.
This is .c, which means in this file is going to be text in the language called
C. This number 1 here is just an automatic line number that's going help
me keep track of how long or short this program is.
And the cursor is just blinking there, waiting
for me to start typing some code.
Well, let me go ahead and type out exactly the same code.
For me, it comes pretty comfortably from memory.
So I'm going to go ahead and include something called standardio.h--
more on that later.
I'm going to magically type int main(void), whatever that means--
we'll come back to that later--
one of these curly braces and then a sibling there that closes the same.
Then I'm going to hit Tab to indent a few spaces.
And then I'm going to type not print, but printf, then "Hello, world," /n,
close quote, close parenthesis, semicolon.
And I dare say this was essentially the very first program I wrote some
25 years ago.
I wrote it to say, "Hi, CS50."
Now it just says the more canonical, conventional, "Hello, world."
But that's it.
That's my very first program.
And all I need to now do is maybe hit Command-S or Control-S
to save the file.
And voila, I am a programmer.
The catch though, is, OK, how do I run this?
Like, on your Mac or PC, how do you run a program?
Well, usually double-click an icon.
On your phone, you tap an icon.
In this environment that we're using and that many programmers-- dare
say most programmers-- use, you don't have immediately a nice, pretty icon
to double-click on.
That's very user friendly, but it's not very necessary.
Especially when you get more comfortable with programming,
you're going to want to type commands because it's just faster than pointing
and clicking a mouse.
And you're going to want to automate things,
which is a lot easier if it's all command or text-based, as opposed
to mouse and muscular movements.
And so here I have my program.
It lives in this file called "hello.c."
I need to now convert it, though, to zeros and ones.
Well, how do I go about doing this, and how am I going to get from this
so-called code--
or source code, as it's conventionally called--
to this, these zeros and ones that we'll now start calling machine code.
The zeros and ones from last week can be used not only
to represent numbers and letters, colors, audio, video, and more.
It can also represent instructions to a computer, like print, or play a sound,
or delete a file, or save a file.
All the sort of basics of a computer somehow
can be represented by other patterns of zeros and ones.
And just like last week, it depends on the context
in which these numbers are stored.
Sometimes they're interpreted as numbers, like in a spreadsheet.
Sometimes they're interpreted as colors.
Sometimes they're interpreted as instructions, commands to your computer
to do very low-level operations, like print something on the screen.
So fortunately, last week's definition of computer science of problem solving
is a nice mental model for exactly the goal at hand.
I have some input, AKA source code.
I want to output ultimately machine code, those zeros and ones.
I certainly don't want to do this kind of process by hand.
So hopefully there's an algorithm implemented by some special program
that does exactly that.
And those of you who do have some prior experience,
this program might be called a?
A compiler.
So a few of you have, indeed, programmed before.
Not all languages use compilers.
C, in fact, is a language that does use a compiler.
And so I just need to find myself--
on my computer somewhere, presumably-- a so-called compiler,
a program whose purpose in life is to convert one language to another.
And source code written textually in C, like we saw a moment ago,
is source code.
The machine code is the corresponding zeros and ones.
So let me go back to the same programming environment called
Visual Studio Code or VS Code.
This is typically a program you or any programmer on the internet
can download onto their own Mac or PC and be on their way with whatever
computer you own writing some code.
A downside, though, of that approach is that all of us
have slightly different versions of Macs or PCs.
We have slightly different versions of operating systems.
They may or may not be up to date.
It's just a technical support nightmare to create a uniform environment,
especially for an introductory class, where everyone should ideally
be on the same page so we can get you up and running quickly.
And so I'm actually using a cloud-based version of VS Code, something
that you only need a browser to access.
And then you can be on any computer, today or tomorrow.
By the end of the semester, we're going to get you out of the cloud,
so to speak, as best we can and get you onto your own Mac or PC,
so that after this class, especially if it's the only CS class you ever take,
you feel like you can continue programming in any number of languages,
even with CS50 behind you.
But for now, wonderfully, the browser version of VS Code
should pretty much be identical to what the eventual downloadable
version of the same would be.
And you'll see in problem set 1 how to access this
and how to get going yourself with your first programs.
But I haven't mentioned this bottom part of the screen,
this bottom part of the screen.
And this is an area where we have what's called a terminal window.
So this is sort of old-school technology that allows you, with a keyboard,
to interact with a computer, wherever it may be-- on your lap, in your pocket,
or even, in this case, in the cloud.
So on the top-hand portion of this screen
is my text editor, like tabbed windows, like in many programs, where
I can just create files and write code.
The bottom of the screen here, my so-called terminal window,
gives me the ability to run commands on a server
that currently I have exclusive access to.
So because I logged into VS Code with my account online,
I have my own sort of virtual server, if you will, in the cloud--
otherwise known as, in this context, a container.
This has its own operating system for me, its own hard drive,
if you will, where I can save and create files of my own,
separate from yours and vice versa.
And it's at this very simple prompt, which
is conventionally-- but not always-- abbreviated by a dollar sign,
has nothing to do with currency.
It just means, type your commands here.
This is where I'm going to be able to type commands,
like compile my source code into machine code.
So it's a Command Line Interface, or CLI, on top of an operating system
that you might not have ever used or seen, but it's very popular,
called Linux.
Odds are almost all of us in this room are using Mac OS or Windows right now,
but we're all going to start using an operating system called Linux, which
is in a family of operating systems that offer not only this command
line interface, but are used not just for programming, but for serving
websites and developing applications and the like.
And it's, indeed, a familiar and very powerful interface, as we'll see.
So how do I go about making this file, hello.c, into a program?
There's no icon to double-click, but there is a command.
I can type, make hello, at this dollar sign prompt, go ahead and hit Enter,
and nothing appears to happen.
But that's a good thing.
And as we'll see in programming, almost always,
if you don't see anything go wrong, that means everything went right.
So this is going to be a rarity at first,
but this is a good thing that it just seems to do nothing.
But now there is in the folder in my accounts
in this on the cloud a file called "hello."
And it's a bit of a weird command, but you'll get familiar with it
before long.
. just means go into my current folder.
/hello means run the program called "hello" in this current folder.
So ./hello, and then Enter, and voila, now I'm actually not just programming,
but running my actual code.
So what have I just done?
Let me go ahead and do this.
I'm going to go ahead and open up the sidebar of this program,
and you'll see in problem set 1 how to do this.
And this might look a little different based on your own configuration.
Even the color scheme I'm using might ultimately look different from yours,
because it supports a nice colorful theme.
So you can have different colors and brightnesses depending
on your mood or the time of day.
What I've opened here, though, is what is called in VS Code Explorer,
and this is just all of the files in my cloud account.
And there's not many right now.
There's only two.
One is the file called hello.c, and it's highlighted
because I've got it open right there.
And the other is a file called "hello," which is brand new
and was created when I ran that command.
And what's now worth noting is that now things are getting a little more
like Mac OS and Windows.
Like on the left-hand side, you have a GUI, a Graphical User Interface.
But on the bottom here, again, you have a CLI, Command Line Interface.
These are just different ways to interact with computers,
and you'll get comfortable with both.
And honestly, you're certainly familiar and comfortable with GUIs already,
so it's the command line one with which we'll spend some time.
Now suppose that I just wanted to do something
more than compile this program.
Suppose I wanted to go ahead and remove it.
Like, uh-uh, no, I made a mistake.
I want to say, "Hello, CS50," not "Hello, world."
I could just hover up here, like in any software, and I could right-click,
and I could poke around, and there, delete permanently.
So most of us might have that instinct on a Mac or PC.
You right-click or Control-click, and you poke around.
But in a command line interface, let me do this instead.
The command for removing or deleting a file
in the world of Linux, this other operating system,
is just a type rm for remove, and then "hello," Enter.
It's a somewhat cryptic confirmation message, but this just means,
are you sure?
I'm going to go ahead and type Y for Yes.
And now when I hit Enter, watch what happens
at top left in the Explorer, the GUI, the graphical interface.
Voila, it disappears.
Not terribly exciting, but this just means
this is a graphical version of what we're seeing here.
And in fact, if you want to never use the GUI again--
I'll go ahead and close it with a keyboard shortcut here--
you can forever just type ls for list and hit Enter.
And you will see in the command line interface
all of the files in your current folder.
So anything you can do with a mouse, you can
do with this command line interface.
And indeed, we'll see many more things that you can do as well.
But the inventors of this, this operating system and its predecessors,
were very succinct.
Like, the command is rm for remove.
The command is ls for list.
It's very terse.
Why?
Because it's just faster to type.
So before we forge ahead with making something more interesting than
just "Hello, world," let me pause here to see
if there's questions on source code or machine
code or compiler or this command line interface.
Yeah?
AUDIENCE: [INAUDIBLE].
DAVID J. MALAN: Really good question, and let me recap.
If I were to make changes to the program,
run it, and then maybe make other changes and try to rerun it,
would those changes be reflected, even though I've reworded slightly.
Well, let's do this.
I already removed the old version.
So let me go ahead and point out that if I do ./hello now,
I'm going to see some kind of error because I just deleted the file.
No such file or directory, so it's not terribly user friendly,
but it's saying what the problem is.
Let me go ahead and remake it by typing make hello.
Now if I type ls, I'll see not one but two files again, and one of them
is even green with a little asterisk to indicate that it's executable.
It's sort of the textual version of something
you could double-click in our human world.
So now, of course, if I run hello, we're back where I started, "Hello, world."
But now suppose I change it to "Hello, CS50," like I did years ago.
Let me go ahead and save the file with Command-S or Control-S. Down here now,
let me run ./hello again, and voila.
Huh.
So let me ask someone else to answer that question.
What's the missing step?
Why did it not say, "Hello, CS50."
Yeah?
AUDIENCE: [INAUDIBLE].
DAVID J. MALAN: Yeah, so I didn't compile it again.
So sort of newbie mistake, you're going to make this mistake and many others
before long.
But now let me go ahead and remake hello, enter.
It's going to seemingly make the same program.
But this time when I run it, it's, "Hello, CS50."
Any other questions on some of these building blocks?
And we'll come back to all the crazy syntax I typed before long.
But for now, we're focusing on just the output.
Yeah?
AUDIENCE: [INAUDIBLE].
DAVID J. MALAN: When I keep running make,
it creates a new version of the machine code.
So it keeps changing the hello program and the hello file, and that's it.
There's no make file, per se.
AUDIENCE: [INAUDIBLE].
DAVID J. MALAN: Good question, no.
If I open up that directory, you'll see that there's just the one.
And it doesn't matter how many times I run make hello--
three, four, five-- it just keeps overwriting the original.
So it's kind of like just saving in the world of Google Docs or Microsoft
Word or the like.
But there's an additional step today.
We have to then convert my words to the computer's, the zeros and ones.
Yeah, in front.
AUDIENCE: [INAUDIBLE].
DAVID J. MALAN: Oh, what happens if I run hello.c?
So let me go ahead and do ./hello.c, which is a mistake you'll invariably
make early on.
Permission denied.
So what does that mean?
This is where the error messages mean something
to the people who designed the operating system, but it's a little cryptic.
It's not that you don't have access to the file.
It means that it's not executable.
This is not something you have permission to run,
but you do have permission to read or write it-- that is, change it.
AUDIENCE: [INAUDIBLE].
DAVID J. MALAN: Oh, really good question.
So if I have named my file, hello dot C, or more generally something
dot C, of the things that Make does is it automatically
picks the file name for me.
And we'll discuss a bit--
we'll discuss this a bit more next week.
Make itself-- is kind of the first of white lies today--
itself is not a compiler.
It's a program that knows how to find and use the compiler on the system
and automatically create the program.
If I use, as we'll discuss next week, the actual compiler myself,
I have to type a much longer sequence of commands to specify explicitly
what do I want the name of my program to be.
Make is a nice program, especially in week 1,
because it just automates all of that for us.
And so here, we have now a program that very simply prints
something on the screen.
So let's not put this into the context of where
we left off last time in the context of Scratch and inputs and outputs.
So we discuss the last time, of course, functions and arguments.
Functions, again, are those actions and verbs like say, or ask, or the like.
And the arguments were the inputs to those functions,
generally in those little white ovals that, in Scratch, you
could type words or numbers into.
We'll see, in all of the languages we're going to see this term,
have that same capability.
And let's just start to translate one of these things to another.
So for instance, let's put this same program in C,
in the context of Scratch.
This is what Hello, World looked like last week in the form of one function.
This week, of course, it looks like print.
And then the parentheses, notice, are kind of
deliberately designed in the world of Scratch to resemble that same shape.
Even though this is a white oval, you kind of get
that it's kind of evoking that same idea with the parentheses.
Technically the function in C, it's not called say.
It's not even called print.
It's called printf.
The F stands for formatted, but we'll see what that means in a moment.
But printf is the closest analogous function
for say in the world of C. Notice if, though, you
want to print something like Hello, World or Hello CS50 in C,
you don't just write the words as we did last week.
You also had an add what, if you notice already
what's missing from this version.
Yeah, so the double quotes on the left and the right.
So, that's necessary in C whenever you have a string of words.
And I'm using that word deliberately.
Whenever you have multiple words like this, this is known as a string
as we'll see.
And you have to put it in double quotes, not single quotes.
You have to put it in double quotes.
There's one other stupid thing that we need to have in my C code
in order to get this function to do something ultimately, which is what?
Semicolon.
So just like in our human world, you eventually
got into the habit of using, at least in formal writing, periods.
Semicolon is generally what you use to finish your thought
in the world of programming with C.
All right, so we have that function in place.
Now, what does this really fit into in terms of the mental model?
Well, functions take arguments.
And it turns out functions can have different types of outputs.
And we've actually seen both already last week.
One type of output from a function can be something called a side effect.
And it generally refers to something visual,
like something appearing on the screen or a sound playing from your computer.
It's sort of a side effect of the function doing its thing.
And indeed, last week we saw this in the context of passing in something
like Hello, World as input to the say function.
And we saw on the screen Hello, World, but it was kind of a one off.
It's one and done.
You can't actually do anything with that visual output
other than consume it, visually, with your human eyes.
But sometimes, recall last week, we had functions like the ask block that
actually returned me some value.
Remember the ask, what's your name.
It handed me back whatever answer the human typed in.
It didn't just arbitrarily display it on the screen.
The cat didn't necessarily say it on the screen.
It was stored, instead, in that special variable that was called answer.
Because some functions have not side effects but return values.
They hand you back an output that you can use and reuse,
unlike the side effect, which, again displays and that's it.
You can't sort of catch it and hold on to it.
So, in the context of last week, we had the ask block.
And that had this special answer return value.
In C, we're going to see in just a moment,
we could translate this as follows.
The closest match I can propose for the ask block
is a function that we're going to start calling get string.
String is, again, a word, a set of words, like a phrase
or a sentence in programming.
It, too, is a function insofar as it takes input and pretty much--
this isn't always true-- but very often when
you have a word in C followed by an open parenthesis and a closed parenthesis,
it's most likely the name of a function.
And we're going to see that there's some exceptions to that.
But for now this indeed looks like a function
because it matches that pattern.
If I want to ask the question, what's your name, question mark--
and I'm even going to deliberately put a space there just to kind of move
the cursor a little bit over so that the human isn't typing literally
after the question mark.
So that's just the nitpicky aesthetic.
This is perhaps the closest analog to just asking that question.
But because the ask block returns a value,
the analog here forget string is that it, too, returns a value.
It doesn't just print the human's input.
It hands it back to you in the form of a variable, a.k.a. return value,
that I can then use and reuse.
Now ideally it would be as simple as this literally
saying answer on the left equals.
And this is where things start to diverge
from math and sort of our human world.
This equal sign, henceforth, is not the equal sign.
It is the assignment operator.
To assign a value means to store a value in some variable.
And you read these things, weirdly, right to left.
So here is a function called get string.
I claim that it's going to return to you whatever
the human types in as their name.
It's going to get stored over here on the left because
of this so-called assignment operator, that yes is an equal sign.
But it doesn't mean equality in this context.
It makes things equal.
But it does so by copying the value on the right into the thing on the left.
Unfortunately, we're not quite done yet with C.
And this is where, again, it gets a little annoying at first
where Scratch just let us express our ideas without so much syntax.
In C when you have a variable you don't just
give it a name like you did in Scratch.
You also have to tell the computer in advance what type of value
it is storing.
String is one such type of value.
Int, for integer, is going to be another.
And there's even more than that we'll see today and beyond.
And this is partly an answer to the question that came up one or more times
last week, which was how does a computer distinguish this pattern of zeros
and ones from this.
Like is this a letter, a number, a color, a piece of video.
And I just claimed last week that it totally depends on the program.
It depends on the context.
And that's true.
But within those programs, it often depends
on what the human programmer said the type of the value is.
If this specifies that the string, which means
interpret the following zeros and ones that
are stored in my program as words or letters, more generally.
If it's an int for integer, it would be implying, by the programmer,
treat the following zeros and ones in my program as a number,
an integer, not a string.
So here's where this week, unlike with Scratch,
which is kind of figures out what you mean, with C in a lot of languages
you have to be this pedantic and tell it what you mean.
There's still one stupid thing missing from my code here.
What's still missing here?
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: And we still need the stupid semicolon.
And I'm sort of impugning it here.
Because honestly, these are the kinds of stupid mistakes
you're going to make today, tomorrow, this weekend, next week,
a few weeks from now, until you start to notice this and recognize it
as well as you do English or whatever your spoken language is.
Yeah, question.
Good question.
Suppose I mix apples and oranges, so to speak,
and I try to put a string in an int or an int in a string,
the compiler is going to complain.
So when I run that make command as I did earlier,
it's not going to be nice and blissfully quiet and just give me another prompt.
It's going to yell at me with honestly a very cryptic
looking error message until we get the muscle memory for reading it.
Other questions.
Ah, what happened to the backslash n.
So, we'll come back to that in just a moment, if we may.
Because I have deliberately omitted it here but we did have it earlier.
And we'll see the different behavior in a sec.
Other questions.
Yeah, not at all nitpicky.
These are the kinds of things that just matter.
And it's going to take time to recognize and develop this muscle memory.
Everything I've typed here except, for the W at the moment, is lowercase.
And the W is capitalized just because it's English.
Everything else is lowercase.
And this kind of varies by language and also context.
So, in many languages the convention is to use all lowercase letters
for your variable names.
Other languages might use some capitals, as well.
But we'll talk about that before long.
But this is the kind of thing that matters
and is hard to see at first, especially when a little S doesn't look that
different when it's on your tiny laptop screen from a capital S.
But you'll start to develop these instincts.
All right, so besides this particular block,
let's go ahead and consider how we can go about implementing this now in code.
So let me switch back to VS Code here.
This was the program I had earlier.
And let me go ahead and undo my CS50 change.
And this time just rerun it.
Rerun Make on Hello with the original version with the backslash n.
Enter, nothing bad seems to have happened.
So dot slash Hello, enter Hello, World.
Now, if you're curious, this is a good instinct
to start to acquire what happens if I get rid of this.
Well, I'm probably not going to break things too badly.
So let's try.
Let me go ahead now and do Make Hello.
Still compile.
So it's not a really bad mistake.
So let me go ahead and run dot slash Hello.
What's the difference here?
Yeah, what do you see that's different?
Yeah, the dollar sign, my so-called prompt, stayed on the same line.
Why?
Well, we can presumably infer now that the backslash
n is some fancy notation for saying create a new line,
move the cursor, so to speak, to the next line.
Notice that the cursor will move to the next line in my terminal window.
If I keep hitting it, it just automatically,
by nature of hitting enter, does it.
But it'd be kind of stupid if when you run a program in this world,
simple as it is, if the next command is now
weirdly spaced in the middle of the terminal with the dollar sign,
it just looks sloppy.
It's really just an aesthetic argument.
And notice that it's not acceptable or correct to do this, to hit enter there.
Let me go ahead and save that, though, and see what happens.
Let me go ahead now and run Make Hello enter.
Oh my god, like four errors.
This is like, what, 10 lines of errors for a one line program.
And this is where, again, you'll start to develop the instincts for just
reading this stuff.
These kinds of tools, like the compiler tool we're using,
were not designed necessarily with user friendliness in mind.
That's changed over the decades, but certainly early
on it's really just meant to be correct and precise with its errors.
So what did I do here?
Missing terminating close quote character,
long story short, when you have a string in C,
your double quotes just have to be on the same line just because.
Now, there's the slight white lie.
There's ways around this.
But the best way around it is to use this so-called escape sequence.
To escape something means generally to put a backslash, and then
a special symbol like n for new line.
And this is just the agreed upon way that humans, decades ago, decided,
OK you don't just hit your enter key.
You instead put backslash n and that tells the computer
to move the cursor to the new line.
So again, kind of cryptic.
But once you know it, that's it.
It's just another word in our vocabulary.
So now let me transition to making my program a little more interactive.
Instead of just saying Hello, world, let me
change it like last week to say Hello, David,
or whoever is interacting with the program.
So I'm going to do string answer gets, get string,
quote unquote, what's your name.
I'm not going to bother with a new line here.
I could.
This is now just a judgment call.
I deliberately want the human to type their name on the same line
just because.
And how do I now print this?
Well last week recall we used say.
And then we use the other block called join.
So the idea here is the same.
But the syntax this week is going to be a little different.
It's going to be printf, which prints something on the screen.
I'm going to go ahead and say Hello comma.
And let me just go with this initially with the backslash n, semicolon.
Let me go ahead and recompile my code.
Whoops, damn doesn't work still.
And look at all these errors.
There's more errors than code I wrote.
But what's going on here?
Well, this is actually something, a mistake you'll see,
somewhat often, at least initially.
And let's start to glean what's going on here.
So here, if I look at the very first line of output after the dollar sign--
so even though it jumped down the screen pretty fast,
I wrote Make Hello at the dollar sign, prompt.
And then here's the first error.
On Hello dot C, line 5--
technically character 5, but generally line is enough to get you going--
there's an error, use of undeclared identifier string.
Did you mean standard in?
So, I didn't.
And this is not an obvious solution at first.
But you'll start to recognize these patterns in error messages.
It turns out that if I want to use string, I actually have to do this.
I have to include another library up here, another line of code,
rather, called CS50 dot H. We'll come back
to what this means in just a moment.
But if I now retroactively say, all right, what does standard I/O
do for us up here.
Before I added that new line, what is standard I/O doing?
Well, if you think back to Scratch, there
were a few examples with the camera and with the speech to-- the text to voice.
Remember I had to poke around in the extensions button.
And then I had to load it into Scratch.
It didn't come natively with Scratch.
C is quite like that.
Some functions come with the language.
But for the most part, if you want to use a function, an action or a verb
like printf, you have to load that extension, so to speak,
that more traditionally is called a library.
So there is a standard I/O library, STD I/O, standard I/O,
where I/O just means input and output.
Which means, just like in MIT's World, there
was an extension for doing text to voice or for using your camera.
In C, there's an extension, a.k.a.
a library, for doing standard input and output.
And so if you want to use any functions related to standard input and output,
like text from a keyboard, you have to include standard I/O dot
H. And then can you use printf.
Same goes here.
Get string, it turns out, is a function that CS50 wrote some time ago.
And as we'll see over the coming weeks, it just
makes it way easier to get input from a user.
C is very good with printf at printing output on the screen.
C makes it really annoying and hard, as we'll see in a few weeks,
to just get input from the user.
So we wrote a function called get_string,
but the only way you can use that is to load the extension,
a.k.a. load the library called CS50.
And we'll come back in time, like, why is it .h, why is it a hash symbol.
But for now, standard I/O is a library that
gives you access to printf and input- and output-related stuff.
CS50 is a second library that provides you
with access to functions that don't come with C
that include something like get_string.
So with that said, we've now kind of teased apart
at a high level what lines 2 and now 1 are doing.
Let me go ahead and rerun make hello.
Now it worked.
So all those crazy error messages were resolved by just one fix,
so key takeaway is not to get overwhelmed by the sheer number
of errors.
Let me now do ./hello and if I type in my name, what am I going to say?
What do you think?
Yeah, hello answer, because the computer is going to take me literally.
And it turns out that if you just write "hello,
answer" all in the double quotes, you're really just passing
English as the input to the printf function,
you're not actually passing in the variable.
And unfortunately in C, it's not quite as
easy to plug things in to other things that you've typed.
Remember in Scratch, there was not just the Save block
but the Join block, which was kind of pretty,
you can combine apples and oranges--
or was it apple and banana?
Then we changed it to hello and then the answer that the human typed in.
In C, the syntax is going to be a little different.
You tell the computer inside of your double quotes that you want to have
a placeholder there, a so-called format code. %s means, hey, computer,
put a string here eventually.
Then outside of your quotes, you just add a comma and then you type
in whatever variable you want the computer to plug in at that %s location
for you.
So %s is a format code which serves as a placeholder.
And now the printf function was designed by humans years
ago to figure out how to do the apple and banana
thing of joining two words together.
It's not nearly as user-friendly as it is in Scratch,
but it's a very common paradigm.
So let me try and rerun this now. make hello.
No errors, that's good.
./hello.
What's my name, David?
If I type Enter now, now it's hello.
David.
And the printf, here's the F in printf.
It formats its input for you by using these placeholders for things like
strings, represented again by %s.
So a quick question then, if I focus here on line 7 for just a moment
and even zoom in here, how many inputs is printf taking as a function?
A moment ago, I'll admit that it was taking one input, "hello, world,"
quote unquote.
How many inputs might you infer printf is taking now?
2.
And it's implied by this comma here, which is separating the first one,
quote, unquote, "hello, %s" from the second one, answer.
And then just as a quick safety check here, why is it not 3?
Because there's obviously two commas here.
Why is it not actually 3 arguments or inputs?
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Exactly.
The comma to the left is actually part of my English grammar,
that's all, so same syntax.
And, again, here's where programming can just be confusing
early on because we're using the same special punctuation to mean
different things, it just depends on the context.
And so now is actually a good time to point out
all of the somewhat pretty colors that have been popping up on the screen
here--
even though I wasn't going to a format menu, I wasn't boldfacing things,
I certainly wasn't changing things to red or blue or whatnot--
that's because a text editor like VS Code syntax highlights for you.
This is a feature of so many different programming environments nowadays,
VS Code does it as well.
If your text editor understands the language that you're programming in--
C, in this case--
it highlights in different colors the different types of ideas in your code.
So, for instance, string and answer here are in black,
but get_string a function is in this sort of nasty brown-yellow
here right now, but that's just how it displays on the screen.
The string, though, here in red is kind of jumping out at me,
and that's marginally useful.
The %s is in blue.
That's kind of nice, because it's jumping out at me.
And so it's just using different colors to make different things on the screen
pop so you can focus on how these ideas interrelate
and, honestly, when you might make a mistake.
For instance, let me accidentally leave off this quote here.
And now all of a sudden, notice if I delete the quote,
the colors start to get a little awry.
But if I go back there and put it back, now everything's back in place.
What's another feature of this text editor?
Notice when my cursor is next to this parenthesis, which
demarcates the end of the inputs to the function,
notice that highlighted in green here is the opening parenthesis.
Why?
It's just a visually useful thing, especially when
you start writing more and more code, just to make
sure your parentheses are lining up.
And that's true for these curly braces over here on the left and the right.
We'll come back to those in a moment.
If I put my cursor there, you can see that these things correspond
to one another.
So it's nothing in your code fundamentally, it's just the editor
trying to help you, the human, program.
And you can even see it, though it's a little subtle--
see these four dots here and these four dots here?
That's my indentation.
I configured VS Code to indent by four spaces, which
is a very common convention.
Any time I hit the Tab key, this too can help you make sure--
once we have more interesting and longer programs--
that everything lines up nice and neatly.
Phew.
All right, any questions then on printf or more?
Yeah.
AUDIENCE: [? Would ?] the printf [INAUDIBLE]??
DAVID J. MALAN: Short answer, yes. printf
can handle more than one type of variable or value.
%s is one.
We're going to see %i is another for plugging in an integer.
You can have multiple i's, multiple s's, and even other symbols too.
We'll come back to that in just a little bit.
printf can take many more arguments than just these two.
This is just meant to be representative.
Yeah, over here.
Can you declare variables within the printf?
No.
The only variable I'm using right now is answer,
and it's got to be done outside the context of printf in this case.
Good question, we'll see more of that before long.
Yeah, in back.
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: How do we download the CS50 library?
So we will show you in problems set 1 exactly how to do that.
It's automatically done for you in our version of VS Code in the cloud.
If, ultimately, you program on your own Mac or PC, either initially or later
on, it's also installable online.
But if you want to ask that via online or afterward,
we can point you in the right direction.
But PSet 1 will itself.
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: String is the type of the variable or, more properly,
the data type of the variable.
int is another keyword I alluded to earlier, I haven't used it yet.
int, for integer, is going to be another type, or data type, of variable.
AUDIENCE: OK. [? Thank you. ?]
DAVID J. MALAN: Yeah.
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Oh, good question.
Could I go ahead and just plug in this function,
kind of like we did in Scratch, getting rid of the variable altogether
and just do this, which recall, is reminiscent of what
I did in Scratch by plopping block on top of block on block?
Am I answering that right?
Can I put string in front of get_string?
No.
You only put the word string in front of a variable
that you want to make string.
And even though I'm apparently answering the wrong question,
let me go ahead and zoom out, save this, do make hello again.
Seems to compile OK.
If I run ./hello, type in David, voila.
That, too, works.
And so, actually, let's go down this rabbit hole for just a moment.
Clearly, it's still correct--
at least, based on my limited testing.
Is this better designed or worse designed?
Let's open that question like we did last week.
Yeah?
Yeah, I kind of agree with that.
Reasonable people could disagree, but I do
agree that this seems harder to read because I start reading here,
but wait a minute. get_string is going to get used first,
and then it's going to give me back a value.
So, yeah, it just feels like it was nicer to read top to bottom,
I would say.
Your thoughts?
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Yeah.
And so this is useful if I only want to print out the person's name once.
If I want to use it later in a longer program, I'm out of luck,
and so I haven't saved it in a variable.
So I think, long story short, we could debate this all day long.
But in this case, eh, if you can make a reasonable argument one
way or the other, that's a pretty solid ground to stand on.
But, invariably, reasonable people are going
to disagree, whether first-time programmers or many years after that.
So let's frame this one last example in the context of the same process
of taking inputs and outputs.
The functions we've been talking about all
take inputs, otherwise now known as arguments, or parameters, pretty much
synonymous.
That's just the fancy word for an input to a function.
And some functions have either side effects, like we saw--
printing something, saying something on the screen,
sort of visually or audibly--
or they return a value, which is a reusable value, like name or answer,
in this case.
If we look then at what we did last time in the world of Scratch last week,
the input was what's your name, the function was ask,
and the return value was answer.
And now let's take a look at this block, which is honestly a more user-friendly
version of what we just did with the %s.
Last week we said save, then join, then hello and answer.
But the interesting takeaway there was not how to say hello anything.
It was the fact that in Scratch 2, the output of one function,
like the green join, could become the input to another function,
the purple say.
The syntax in C is admittedly pretty different,
but the idea is essentially the same.
Here, though, we have hello, a placeholder,
but we have to, in this world of C, tell printf
what we want to plug in for that placeholder.
It's just different.
But that's the way to do it.
When we get to Python and other languages later in the term,
there's actually easier ways to do this.
But this is a very common paradigm, particularly when
you want to format your data in some way.
All right, let's then take a step back to where we began,
which was with that whole program, which had the include
and it had int main(void) and all of this other cryptic syntax.
This Scratch piece last week was kind of like the go-to
whenever you want to have a main part of your program.
It's not the only way to start a Scratch program.
You could listen for clicks or other things, not just the green flag.
But this was probably the most popular place to start a program in Scratch.
In C, the closest analog is to literally write this out.
So just like last week, if you were in the habit of dragging and dropping
when green flag clicked, as a C programmer,
the first thing you would do is after creating an empty file,
like I did with hello.c, you'd probably type int
main(void) open curly brace, closed curly brace,
and then you can put all of your code inside of those curly braces.
So just like Scratch had this sort of magnetic nature
to it where the puzzle pieces would snap together, C, as a text-based language,
tends to use these curly braces, one of them opened, the other one closed.
And anything inside of those braces, so to speak,
is part of this puzzle piece, a.k.a.
main.
So what was atop them?
We went down this rabbit hole moment ago with these things called header files,
even though I didn't call them by this name.
But, indeed, when we have a whole program in Scratch, super easy.
Just have the one green flag clicked and then say hello, world.
There's no special syntax.
After all, it's meant to be very user-friendly and graphical.
In C, though, you technically can't just put int main(void) printf hello, world.
You also need this.
Because, again, you need to tell the compiler to load the library--
code that someone else wrote-- so that the compiler knows what printf even is.
You have to load the CS50 library whenever
you want to use get_string or other functions, like get_int,
as we'll soon see.
Otherwise, the compiler won't know what get_string is.
You just have to do it this way.
The specific file name I'm mentioning here,
stdio.h, cs50.h, is what C programmers called a call a header file.
We'll see eventually what's inside of those files.
But long story short, it's like a menu of all of the available functions.
So in cs50.h, there's a menu mentioning get_string, get_int,
and a bunch of other stuff.
And in stdio.h, there's a menu of functions, among which are printf.
And that menu is what prepares the compiler
to know how to implement those same functions.
All right, let me pause here.
Question.
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Not quite.
A library provides all of the functionality we're talking about.
A header file is the very specific mechanism via which you include it.
And we'll discuss this more next week.
For now, they're essentially the same, but we'll discuss
nuances between the two next week.
Yeah, the library would be standard I/O. The library would CS50.
The corresponding header file is stdio.h, cs50.h.
Indeed.
Other questions.
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Indeed.
That, too, is on the menu.
We'll come back to that.
But the word string--
incredibly common in the world of programming, it's not a CS50 idea--
but in C, there's technically no such data type as string by default.
We have sort of conjured it up to simplify the first few weeks.
That's a training wheel that we'll very deliberately, in a few weeks,
take away, and we'll see why we've been using get_string and string.
Because C otherwise makes things quite more challenging early on,
which then gets besides the point for us.
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Yes.
Early on, you will have to use whatever is prescribed by the specification.
That will include CS50's functions.
Long story short, you referred, I think, a moment ago to another function
called scanf, we won't talk about for a few weeks.
Long story short, in C, it's pretty easy and possible to get input from a user.
The catch is that it's really easy to do it dangerously.
And C, because it's an older, lower-level language, so to speak,
that gives you pretty much ultimate control over your computer's hardware.
It's very easy to make mistakes.
And, indeed, that's too why we use the library,
so your code won't crash unintendedly.
All right, so with this in mind, we have this now mapping
between the Scratch version and the other.
Let me just give you a quick tour of some of the other placeholders and data
types that students will start seeing as we assemble more interesting programs.
In the world of Linux, here is a non-exhaustive list
of commands with which you'll get familiar over the next few weeks
by playing with problem sets.
We've only seen two of these so far, ls for list, rm for others.
But I mention them now just so that it doesn't
feel too foreign when you see them on screen or online in a problem set.
cp is going to stand for copy.
mkdir is going to stand for make directory. mv is
going to stand for move or rename.
rmdir is going to be remove directory, and cd is going to be for change /
and let me show you this last one here first,
only because it's something you'll use so commonly.
If I go back to my code here on the screen, I'm going to go ahead
and re-open the little GUI on the left-hand side, the so-called Explorer,
revealing that I've got two files, hello and hello.c
so nothing has changed since there.
Suppose now that it's a few weeks into class
and I want to start organizing the code I'm
writing so that I have a folder for this week or next week,
or maybe a folder for problem set 1, problem set 2.
I can do this in a few ways.
In the GUI, I can go up here and do what most of you
would do instinctively on a Mac or PC.
You look for like a folder icon, you click it,
and then you name a folder like PSet1, Enter.
Voila, you've got a folder called PSet1.
I can confirm as much with my command line interface by typing what command?
How can I list what's in my folder?
Yeah, so ls for list.
And now I see hello--
and it's green with an asterisk because that's
my executable, my runnable program--
hello.c, which is my source code, and now PSet1 with a slash
at the end, which just implies that it's indeed a folder.
All right, I didn't really want to do it that way.
I'd like to do it more advanced.
So let me go ahead and right-click on PSet1, delete permanently.
I get a scary irreversible error message.
But there's nothing in it, so that's fine.
Now I've deleted it using the GUI.
But now let me go ahead and start doing the same thing from the command line.
And if you're wondering how things keep disappearing,
if you hit Control-L in your terminal window or explicitly type clear,
it will delete everything you previously typed just to clean things up.
In practice, you don't need to be doing this often.
I'm doing it just to keep our focus on my latest commands.
If I do-- what was the command to make a new directory?
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Yeah, so mkdir, make directory.
Let me create PSet1, Enter.
And notice at left, there's my PSet1.
If I want to get a little overzealous, plan for next week,
here's my PSet2 directory.
Suppose now I want to open those folders on a Mac or PC or in this GUI,
I could double-click on it like this, and you'd
see this little arrow is moving.
It's not doing anything because there's nothing in there, but that's fine.
But suppose again I want to get more comfortable with my command line.
Notice if I type ls now, I see all four same things.
Let me change directories with cd space PSet1 Enter.
And now notice two things will have happened.
One, my prompt has changed slightly to remind me where I am,
just to keep me sane so that I don't forget what folder I'm actually in.
So here is just a visual reminder of what folder I'm currently in.
If I type ls now, what should I see after hitting Enter?
Nothing, because I've only created empty folders so far.
And, indeed, I see nothing.
If I wanted to create a folder called Mario for a program that might be
called Mario this week, I can do that.
Now if I type ls, there is Mario.
Now if I do cd Mario, notice my prompt's going
to change to be a little more precise.
Now I'm in PSet1/Mario.
And notice what's happening at top left.
Nothing now, because these folders are collapsed.
But if I click the little triangle, there I see Mario.
Nothing's going on in there because there's no files yet.
But suppose now I want to create a file called mario.c.
I could go up here, I could click the little plus icon, and use the GUI.
Or I can just type code mario.c.
Voila.
That creates a new tab for me.
I'm not going to write any code in here yet, but I am going to save the file.
And now at top left, you'll see that mario.c appears.
So at some point, you can eventually just close the Explorer.
Because, again, it's not providing you with any new information.
It's maybe more user-friendly, but there's
nothing you can't do at the command line that you could do with the GUI.
All right, but now I'm kind of stuck.
How do I get out of this folder?
In my Mac or PC world, I'd probably click
the Back button or something like that or just close it and start all over.
In the terminal window, I can do cd dot dot.
Dot dot is a nickname, if you will, for the parent directory.
That is, the previous directory.
So if I hit Enter now, notice I'm going to close the Mario folder,
a.k.a. directory, and now I'm back in PSet1.
Or, if I want to be fancy, let me go back into Mario temporarily.
If I type ls, there's mario.c, just to orient us.
If I want to do multiple things at a time, I could do cd../..
which goes to my parent to my grandparent all in one breath.
And voila, now I'm back in my default folder, if you will.
And one last little trick of the trade, if I'm in PSet1/Mario like I
was a moment ago, and you're just tired of all the navigation,
if you just type cd and hit Enter, it'll whisk you
away back to your default folder, and you don't have
to worry about getting there manually.
Recall a bit ago, though, that I was running hello as this, ./hello.
If dot refers to my parent, perhaps infer here syntactically,
what does a single dot mean instead?
It means this directory, your current directory.
Why is that necessary?
It just makes super explicit to the computer
that I want the program called hello that's installed here,
not in some random other folder on my hard drive, so to speak.
I want the one that's right here instead.
All right, so besides these commands, there's
going to be others that we encounter over time.
Those are kind of the basics.
That allows you to wean yourself off of a GUI, Graphical User Interface,
and start using more comfortably, with practice and time,
a command line interface instead.
Well, what about those other types, now back in the world of C?
Those commands were not C. Those are just command-specific to a command line
interface, like in Linux, which, again, we're using in the cloud.
It's an alternative to Mac OS and Windows.
Back in the world of C now, we've seen strings, which are words.
I mentioned int or integer, but there's others as well.
In the world of C, we've seen string, we will see int.
If you want a bigger integer, there's something literally called a long.
If you want a single character, there's something called a char.
If you want a Boolean value, true or false, there is a bool.
And if you want a floating-point value--
a fancy way of saying a real number, something with a decimal point in it--
that is what C and other languages call a float.
And if you want even more numbers after the decimal point that
is more precision, you can use something called a double.
That is to say, here is, again, an example in programming
where it's up to you now to provide the computer with hints, essentially,
that it will rely on to know what is this pattern of zeros and ones.
Is it a number, a letter?
Is it a sound, an image, a color, or the like?
These are the types of data types that provide exactly those hints.
What are the functions that come in the menu that is the CS50 library?
We talked about standard I/O, and that's just one function so far, printf.
In the CS50 library, you can see that it follows a pattern.
The C50 library exists largely for the first few weeks
of the class to make our lives easier when you just want to get user input.
So if you want to get a string, like a word or words from the human,
you use get_string.
If you want to get an integer from the user, you're going to use get_int.
When you want to get any of those other data types, for the most part,
you use get_ something else.
And they're indeed all lowercase by convention.
What about printf?
If we have the ability now to store different types of data
and we have functions with which to get different types of data,
how might you go about printing different types of data?
Well, we've seen %s for string, %i for integer, %c for char,
%f for a float or a double, those real numbers I described earlier,
and then %li for a long integer.
So here's the first example of inconsistencies.
In an ideal world, that would just be %l and we'd move on.
It's %li instead in this case.
That's printf and some of its format codes.
What more might we do?
Well, in C, as we'll see--
no pun intended-- there is a whole bunch of operators.
And, indeed, computers, one of the first things they did
was a lot of math and calculations, so there's a lot of operators like these.
Computers, and in turn, C, really good at addition, subtraction,
multiplication, division, and even the percent sign,
which is the remainder operator.
There's a special symbol in C and other languages
just for getting the remainder, when you divide one number by another.
There are other features in the world of C, like variables, as we've seen.
And there's also what is of playfully called syntactic sugar that
makes it easier over time to write fewer characters
but express your thoughts the same.
So just as a single example of this, as a single example,
consider this use of a variable last week.
Here in Scratch is how you might set a variable called counter to 0.
In C, it's going to be similar.
If you want the variable to be called counter,
you literally write the word counter, or whatever you want it to be called.
You then use the assignment operator, a.k.a. the equals sign,
and you assign it whatever its initial value should be here on the right.
So, again, the 0 is going to get copied from right to left into the variable
because of that single equal sign.
But this isn't sufficient in C. What else
is missing on the right-hand side, instinctively now?
Even if you've never programmed in this before.
Yeah, in front.
AUDIENCE: Semicolon.
DAVID J. MALAN: A semicolon at the end.
And one other thing, I think, is probably missing.
Again.
AUDIENCE: A data type.
DAVID J. MALAN: A data type.
So if we can keep going back and forth here,
what data type seems appropriate intuitively for counter?
int for integer.
So, indeed, we need to tell the computer when
creating a variable what type of data we want,
and we need to finish our thought with the semicolon.
So there might be a counterpart there.
What about in Scratch if we wanted to increment that counter variable?
We had this very user-friendly puzzle piece last time
that was change counter by 1, or add 1 to counter.
In C, here's where things get a little more interesting.
And pretty commonly done, you might do this. counter = counter + 1;
with a semicolon.
And this is where, again, it's important to note, the equal sign,
it's not equality.
Otherwise, this makes no sense.
counter cannot equal counter plus 1, right?
That just doesn't work if we're talking about integers here.
That's because the equal sign is assignment.
So it can certainly be the case that you calculate
counter plus 1, whatever that is, then you update the value of counter
from right to left to be that new value.
This, as we'll see, is a very common thing
to do in programming just to kind of count upward, for whatever reason.
You can write this more succinctly.
This code here is what we'll call syntactic sugar, sort
of a fancy way of saying the same thing with fewer words or fewer characters
on the screen.
This also adds 1, or whatever number you type over here,
to the variable on the left.
And there's one other form of syntactic sugar we're going to start seeing too,
and it's even more terse than this.
That too will increment counter by 1 by literally changing its value by 1.
Or if you change it to minus minus, subtracting 1 from it.
You can't do that with 2 and 3 and 4, but you
can do it by default with just plus plus or minus minus adding or subtracting 1.
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Ah, so when you are changing a variable that already
has been created, as we did with the code that looked like this,
you no longer need to remind the computer what the data type is.
Thankfully, the computer is at least as smart as that.
It will remember the type of the data that you intended.
Other questions or comments on this?
All right, that's quite a lot.
Why don't we go ahead and here take a 10-minute break?
And we'll be back, we'll start writing some code.
All right, so we are back.
We've just looked at some of the basics of compiling,
even if it doesn't quite feel that basic.
But now, let's actually start focusing really
on writing more and more code, more and more interesting
code, kind of like we dove into Scratch last week.
So here I have these code open.
I've closed the GUI.
I'm going to focus more on my terminal window and my code editors.
Many different ways I can create new files, but I want to create something
called a calculator.
So, again, within this environment of VS Code,
I can literally write the code command which is VS Code specific,
and it just creates a new file for me automatically.
Or I could do that in the GUI.
I'm going to go ahead and create this file called calculator.c
and I'm going to go ahead and include some familiar things.
So I'm just going to go ahead and proactively include cs50.h, stdio.h.
I'm going to go ahead from memory and do the int void main--
more on that next week, why it's int, why it's void, and so forth.
And now let me just implement a very simple calculator.
We saw some mathematical operators, like plus and the like.
So let's actually use this.
So let me go ahead and first give myself a variable
called x, sort of like grade school math or algebra.
Let me go ahead then and get an int, which is new,
but I mentioned this exists.
And then let me just ask the user for whatever their x value is.
The thing in the quotes is just the English, or the string
that I'm printing on the screen. so I could say anything I want.
I'm just going to say x colon to prompt the user accordingly.
Now I'm going to go ahead and get another variable called y.
I'm going to get int again.
And now, I'm going to prompt the user for y.
And I'm just very nitpickly using a space just
to move the cursor so it doesn't look too messy on the screen.
And then lastly, let me go ahead and just print out the sum of x and y.
In an ideal world, I would just say something like printf x + y.
But that is not valid in C. The first argument, recall, in printf
has to be a string in double quotes.
So if I want to print out the value of an integer,
I need to put something in quotes here, maybe followed by a newline,
if I want to move the cursor as well.
So, again, we only glimpsed it briefly, but what
do I replace these question marks with if I want a placeholder for an integer?
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Yeah, so %i.
Just like %s was string, %i is integer.
So I change this %i.
And now if I want to add x and y, for instance, super-- simple calculator,
doesn't do much of anything other than addition of two integers--
I think this works.
And, again, it looks definitely cryptic at first glance.
It would be if programming weren't this cryptic.
Other languages will clean this up for us.
But, again, if you focus on the basics, printf takes one input first--
which is a format string with English or whatever language,
some placeholders, maybe--
then it takes potentially more arguments after the comma,
like the value of x plus y.
All right, let me go ahead now and make calculator,
which, again, compiles my source code in C,
pictured above, and converts it into corresponding machine
code, or zeros and ones.
No error messages.
so that's already good.
Now I do ./calculator.
Let's do 1 plus 1 and Enter.
Voila.
Now I have the makings of a calculator.
Now let's start to tinker with this a little bit.
What if I instead had done this?
int z = x + y and then plug-in z here.
If I rerun make calculator, Enter, rerun ./calculator, type in 1 plus 1,
still equals 2, and let me claim that it will work for other values as well--
which of these versions is better designed?
If both seem to be correct at very cursory glance, is this version better
or is the previous one without the z?
OK, so this one is arguably better because I've now got a reusable
variable called z that I cannot only print but, heck,
if my program is longer, I can use it elsewhere.
Counterthoughts?
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Yeah.
Debatable, like before, because it depends on my intent.
And, honestly, I think a pretty good argument
can be made for the first version.
Because if I have no intention of-- as you note--
using that variable again, you know what?
Maybe I might as well do this, just because it's
one less thing to think about.
It's one less distraction.
It's one less line of code to have to understand.
It's just a little tighter.
So here, again, it does depend on your intention.
But this field is pretty reasonable.
And I think, as someone noted earlier, when
I did the same thing with get_string, that, yeah, maybe kind of crossed
s line because get_string and the what's your name inside of it,
it was just so much longer.
But x + y, eh, it's not that hard to wrap our mind around what's
going on inside of the printf argument.
So, again, these are the kinds of thoughts that hopefully you'll
acquire the instinct for on not necessarily reaching
the same answer as someone else, but, again, the thought
process is what matters here.
All right, so how might I enhance this program a little bit?
Let's just talk about style for just a moment.
So x and y, at least in this case, are pretty reasonable variable names.
Why?
Because that's the go-to variable names in math
when you're adding two things together.
So x and y seem pretty reasonable.
I could have done something like, well, maybe my first variable
should be called first number and my next variable
should be called second number.
And then down here, I would have to change this
to first number plus second number.
Like, eh, this isn't really adding anything semantically
to help my comprehension.
But that would be one other direction we could have taken things.
So if you have very simple ideas that are conventionally
expressed with common variable names like x and y, totally fine here.
What if I want to annotate this program and remind myself what it is it does?
Well, I can add in C what are called comments.
With a slash slash, two forward slashes, you can write a note to yourself,
like prompt user for x.
And then down here, I could do something like prompt user
for y, just to remind myself what I'm doing there.
And down here, perform addition.
Now, in this case, I'm not sure these commands are really
adding all that much.
Because in the time it took me to write and eventually read these comments,
I could have just read the three lines of code.
But as our programs get more sophisticated
and you start to learn more syntax--
that, honestly, you might forget the next day, the next week,
the next month-- might be useful to have these notes to self that
reminds you of what your code is doing or maybe even
how it is doing that thing.
With these early programs, not really necessary,
doesn't really add all that much to our comprehension,
but it is a mechanism you have in place that
can help you actually remind yourself or remind someone
else what it is that's going on.
Well, let me go ahead and rerun this again in this current version,
make calculator.
And here, too, you might think I'm typing crazy fast--
not really.
I'm hitting Tab a lot.
So it turns out that Linux, the operating system
we're using here in the cloud--
but, actually, Windows and Mac OS nowadays support this too--
supports autocomplete.
So if you only have one program that starts with C-A-L,
you don't have to finish writing calculator, you can just hit Tab,
and the computer will finish your thought for you.
The other thing you can do is if you hit Up and keep going up,
you'll scroll through your entire history of commands.
So there too, I've been saving some keystrokes
by hitting Up quickly rather than retyping the same darn thing again
and again.
So, again, just another little convenience
to make programming and interacting with the command line interface even faster.
All right, let me go ahead and just make sure it's compiled in the current form.
The comments have no functional impact.
These green things are just notes to self.
Let me run calculator with maybe-- how about this?
Instead of 1 plus 1, how about 1 billion--
whoops, let's do that again.
Wa, da, da.
1 million, 1 billion, and another 1 billion, and that answer is 2 billion.
All right, so that seems correct.
Let's run this program one more time.
How about 2 billion plus another 2 billion?
Did you know that?
So, apparently, it's not so correct.
And, clearly, running 1 plus 1 was not the most robust testing of my code
here.
What might have gone wrong?
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Yeah.
The computer probably ran out of space with bits.
So it turns out with these data types-- we've been talking about string and int
and also float and char and those other things-- they all use a specific,
and, most importantly, finite number of bits to represent them.
It can vary by computer.
Newer computers use more bits, older computers tended to use fewer bits.
It's not necessarily standardized for all of these data types.
But in this case, in this environment, it is using 32 bits for an integer.
That's a lot.
So with 32 bits, you can count pretty high.
This is 64 light bulbs on the stage and could count even higher.
An int is only using half of these, or we have two integers here on the stage.
Now, if you think back to last week, we talked about 8 bits at one point.
And if you have 8 bits, 8 zeros and ones, you can count as high as 256--
just a good number to generally remember as trivia.
8 bits gives you 256 permutations of zeros and ones.
32 gives you roughly how many, if anyone knows?
It's 2 to the 32 power.
So it's roughly 4 billion, 2 to the 32.
If you don't know that, it's fine.
Most programmers, though, eventually remember these kinds of heuristics.
So it's roughly 4 billion.
So that feels like enough.
2 billion plus 2 billion is exactly 4 billion.
And that actually should fit in a 32-bit integer.
The catch is that my Mac, your PC, and the like
also like to support negative numbers.
And if you want to support both positive and negative numbers, that technically
means with 32-bit integers, you can count as high
as roughly 2 billion positive or 2 billion negative
in the other direction.
That's still 4 billion, give or take, but it's only half as many
in one direction or the other.
So how could I go about implementing a correct calculator here?
What might the solution be?
Yeah, so not just li, which was for long integer.
I have to make one more change, which is to the data type itself.
So let me go back up here and change x from an int to a long, a.k.a.
long integer.
And then let me change y as well.
And then let me change the format code per the little cheat sheet we had up
a few minutes ago to li.
Let me recompile the calculator--
seems to work OK.
Let's rerun it.
Now let's do 1 plus 1.
That's should obviously be the same.
Now let's do 2 billion and another 2 billion
and cross our fingers this time.
Now we're counting as high as 4 billion.
And we can go way higher than 4 billion, but we're only
kicking the can down the street a bit.
Even though we're now using--
with a long--
64 bits, which is as long as this stage now, that's still a finite value.
It might be a really big value, but it's still finite.
And we'll come back at the end of today to these kinds
of fundamental limitations.
Because arguably now, my calculator is correct for like millions,
billions of possible inputs but not all.
And that's problematic if you actually want
to use my calculator for any possible inputs, not just
ones that are roughly less than, say, 2 billion, as in this case.
All right, any questions then on that?
But it's really just a precursor for all the problems
that we're going to have to eventually deal with later on.
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: A good question.
Yes.
If we were still using z, we would also have to change it to a long.
Otherwise, we'd be ignoring 32 of the bits that
had been added together via the longs.
Good question.
All right, so how about we spice things up with maybe not just addition here,
how about something with some conditions?
Let's start to ask some actual questions.
So a moment ago, recall that we had just the declaration of variables.
Now let's look back at something in Scratch that
looked a little something like this, a bunch of puzzle pieces
asking questions by way of these conditionals
and then these Boolean expressions here in green, maybe saying something
like x is less than y.
In C, this actually maps pretty cleanly.
It's much cleaner from left to right than it was with printf and join.
Here, we have just code that looks like this.
If, a space, two parentheses and then x less than y,
and then we have something like printf there in the middle.
So here, it's actually kind of a nice mapping.
Notice that, just as the yellow puzzle piece in Scratch
is kind of hugging the purple puzzle piece,
that's effectively the role that these curly braces are playing.
They're sort of encapsulating all of the code on the inside.
The parentheses represent the Boolean expression
that needs to be asked and answered to decide whether or not to do this thing.
And here's an exception to what I alluded to earlier.
Usually, when you see a word and then a parenthesis, something,
and then closed parenthesis, I claimed that's usually a function.
And I'm still feeling pretty good about that claim.
But there are exceptions.
And the word if is not a function.
It's just a programming construct.
It's a feature of the C language that similarly uses parentheses, just
for different purposes for a Boolean expression.
How about something like this?
Last week, if you wanted to have a two-way fork in the road,
go this way or that way, you can have if and else.
In C, that would look a little something like this.
And if we add in the printf's, it now looks quite like the same,
but it adds, of course, the word else and then a couple of more curly braces.
As an aside, in C, It's not strictly necessary to have curly braces
if you have only one line of code indented underneath.
For best practice, though, do so anyway, because it makes super clear to you
and ultimately anyone else reading your code
that you intend for just that one or more line of code to execute.
How about this from last week?
Here was a three-way fork in the road.
If x is less than y, else if x is greater than y, else if x equals y.
Now, here's where you have some disparities between Scratch
and C. Scratch uses an equals sign for equality, to compare two values.
C uses a single equals sign for assignment from right
to left, minor difference between the two worlds.
In C, we could implement the same code like this, the addition being
just this additional else if.
And if we add in the printf's, it looks a little something now like this.
This is correct both in the Scratch world and in the C world.
But could someone make a claim that this is not, again, well-designed?
Exactly.
We don't need the last if.
We need the else, at least, but we don't need the last if.
Because, at least in the world of comparing integers,
it's either going to be less than, greater than, or equal to.
There is no other case.
So you can save a few seconds, if you will,
of your program running-- a blink of the eye-- by only asking two questions
and then inferring what the answer to the third
must be just by nature of your own human logic here.
Now, why is that a good thing?
If, for instance, x and y happen to equal each other--
I type in 1 and 1 for both values, either in Scratch or in the C world--
in the case of this version, you're sort of stupidly
asking three questions, all of which are going to get asked
even though the answer is no, no, yes.
That is false, false, true.
That seems to be unnecessary because if we instead optimize this code,
get rid of the unnecessary if and just do as you proposed logically--
else print that x is equal to y--
now if x indeed equals y because they're both 1 or some other value,
now you're only going to ask two questions, so 2/3 as many questions,
and then you're going to get your same correct result.
So, again, a minor detail, but, again, the kinds of things
you should be thinking about, not only as
you write your code to be correct but also write
it to be well-designed as well.
All right, so why don't we go ahead and translate this
into the context of an actual program here?
I'll create a blank window here.
And let's do something with points, like points on my own very first CS50
problem set.
Let me go ahead and run code of points.c.
That's just going to give me a new text file.
And then up here, I'm going to do my usual, include cs50.h.
include stdio.h.
int main void.
So a lot of boilerplate, so to speak, in these early programs.
And now, let's see.
Let's ask the user, how many points did they
lose on their most recent CS50 PSet?
So sort of evoke my photograph of my own very first PSet last week
where I lost a couple of points myself.
So int points = get_int.
Then I'll ask a question in English like, how many points did you lose,
question mark, space?
And then once I have this answer, let's now ask some questions of it.
So if points is less than 2--
borrowing the syntax that we saw on the screen a moment ago--
let's go ahead and print out something explanatory
like you lost fewer points than me, backslash n.
else if points greater than 2--
which is, again how many I lost--
I'm going to go ahead and print out you lost more points than me, backslash n.
else if-- wait a minute, else seems to be sufficient logically here.
I'm just going to go ahead and print out something
like you lost the same number of points as me, backslash n.
So, really, just a straightforward application of that simple idea
but to a concrete scenario here.
So let me go ahead and save this.
Let me go ahead and run make points, Enter.
No errors, that's good.
Run points.
And then, how many points did you lose?
How about, it's 1 point?
All right, you lost fewer points than me.
How about 0 points?
Even better.
How about 3 points?
And so forth.
So, again, we have the ability to express in C now pretty basic idea
from last week in reality, which is this notion of conditionals and asking
questions.
There's something subtle here, though, that's maybe not super well-designed
that someone might call a magic number.
This is programming speak for something I've done here.
There's a bit of redundancy unrelated to the if and the else and the else.
But is there something I typed twice just to ask, perhaps, for the obvious?
Exactly, I've hard-coded, so to speak, manually typed out the number 2--
in two locations, in this case--
that did not come from the user.
So, apparently, once I compile this, this is it.
You're always comparing yourself to me in like, 1996,
which for better or for worse, is all the program can do.
But this is an example too of a magic number in the sense
like, wait, where did that 2 come from, and why is it in two places?
It feels like we are setting the stage for just a higher probability
of screwing up down the road.
Because the longer this code gets, suppose I'm comparing against 2 points
elsewhere--
2, 3, 4, 5 places--
am I going to keep typing the number 2?
Like, yeah, that's fine.
It's correct.
It's going to work.
But, honestly, eventually, you're going to screw up,
and you're going to miss one of the 2's, you're going to change it to a 3,
because maybe I did worse the next year, or 1, I did better.
And you don't want these numbers to get out of sync.
So what would be a logical improvement to this design,
rather than hard-coding the same number sort of magically
in two or more places?
Yeah, why don't I make a variable that I can use in there?
So, for instance, I could create a variable
like this, another integer called mine.
And I'm just going to initialize it to 2.
And then I'm going to change mentions of 2 to this.
And mine is a pretty reasonable name for a variable insofar
as it refers to exactly whose points are in question.
There's a risk here, though, minor though it is.
I could accidentally change mine at some point.
Maybe I forget what mine represents, and I do some addition or subtraction.
So there's a way to tell the computer "don't trust me,
because I'm going to screw up eventually"
by making a variable constant too.
So a constant in a programming language--
this did not exist in Scratch--
is just an additional hint to the computer that essentially enables
you to program more defensively.
If you don't trust yourself necessarily to not
screw up later, or honestly, in practice,
if you know that number should never change, make it constant
and never think about it again.
This tells the compiler to make sure that even you later in your code cannot
change the number 2.
And another convention in C and other languages, when you have a constant,
it's often common to just capitalize the variable.
Kind of like you're yelling, but it really just
visually makes it stand out.
So it's kind of like a nice rule of thumb
that helps you realize, oh, that must be a constant.
Capitalization alone does not make it constant.
The word const does.
But the capitalization is just a visual reminder
that this is somewhere, somehow a constant.
So just a minor refinement, but, again, we're
sort of getting better at programming just by instilling
these kinds of heuristics.
Questions, then, on conditionals in C or these constants?
Yeah.
AUDIENCE: Why do you not use semicolons after line 9 and line 13?
DAVID J. MALAN: Yeah, why do you not use a semicolon in lines 9, 13?
Just because.
This is the way the language was designed.
And it's confusing early on.
Generally speaking, when you're using conditionals-- and eventually,
we'll see loops--
there's no semicolons involved.
For now, assume that semicolons usually finish your thought after a function.
That's not 100% reliable of a heuristic, but it'll get you most of the way
there.
And just because.
Left hand was not talking to the right hand when some of these languages
were designed.
All right, so let's do something else.
How about this?
If I have the ability to ask something conditionally--
is this thing true or is this other thing--
could I write a very simple program that does something basic like,
tells me if a number the human types is even or odd?
Well, let me just get the framework for that in place.
Let me go ahead and write code of a parity--
is a fancy way of saying even or odd.
And let me go ahead and include cs50.h, include stdio.h, int main void--
again, more on those down the road.
But, for now, I'm going to go ahead and get a number n from the user
by calling get_int and asking them for whatever n is.
And then now I'm going to introduce some pseudocode.
So here's the first example of a program,
honestly, that I'm not really sure how to proceed.
So let me just resort to some pseudocode using comments.
Eventually, I'll get rid of this and write actual code.
But if n is even, then print--
actually, let me just print that.
Let me just go ahead and say printf, quote unquote, "even",
because I know how to use printf.
else-- all right, I know how to printf odd,
so let me just say printf, quote unquote, "odd".
So here, I've sort of taken a bite out of the problem, if you will.
And let me go ahead and put in my little placeholders.
I want to do some kind of conditions.
So if, question marks now, let me go ahead and fill in the blanks here.
else I'll put this here.
So I think I'm OK now.
I'm getting closer to solving this.
But I still have this question mark here.
How, using syntax we've seen, might I determine if n is even or odd?
What do you think?
Nice.
There's this little operator I mentioned by name earlier, the remainder
operator, that will let you do exactly that.
If you divide any number by 2, that mathematical heuristic
is going to tell you if it's even or odd based on whether there's
a remainder of 0 or 1.
And that's nice because the alternative would seem to be doing something stupid
like if n == 0 or if n equals 2 or n equals 4--
your code would be infinitely long if you had to ask all possible questions.
But if I do n divided by 2 and look at the remainder--
it's a little cryptic, but this will indeed do the trick.
So the percent sign is the remainder operator.
It does numerator divided by denominator and returns not the result of that
but, rather, the remainder of that.
So if you divide anything by 2, it's going to be a 0 or 1 remainder.
And if, indeed, 2 divides into n evenly, giving you 0,
then you're going to print even.
Else, it's got to be odd.
But there is something odd-- pun intended-- in this highlighted line.
What is another new piece of syntax, apparently, besides the percent sign?
What's a little off there?
Yeah.
Yeah, so that's not a typo.
And I even caught myself verbally saying it a moment ago,
just because it's so ingrained.
What must this mean here?
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Yeah, if something's equivalent to the other.
So now this is the equality operator.
It's not assignment from right to left.
And this one too is an example of, literally,
humans not really planning ahead, perhaps, left hand
not talking to right hand in that someone decided,
let's use the equals sign for assignment.
And then some number of minutes or days later, people are like, damn,
how do we now compare for equality?
Well, let's just use two.
And if you think this is a little weird, in some languages, like JavaScript,
there's a third version where you use three equal signs.
So, again, it's humans that design these languages.
So if you're ever frustrated by them, confused by them, eh, admittedly,
it might just not have been the best design.
But we just kind of have to live with it ever since.
So let me go ahead and zoom out here.
Let me go ahead and make parity here.
So make parity-- and, again, parity is just the name of my file, parity.c.
./parity, type in a number like 2.
That's indeed even.
4, that's indeed even.
3, that's indeed odd, and so forth.
If we continue testing, presumably, we'll get the same kinds of answers.
How about something else?
Let me go ahead now and let me start copying and pasting some of this code
because, admittedly, it's getting a little tedious to keep typing out
all of that boilerplate at the top.
Let me create a program called agree.c that's
reminiscent of any of those forms you have
to agree to online with a checkbox or typing in yes or no or the like.
So let me throw away all the guts of this main program
and now ask something like this.
Let me go ahead and prompt user to agree to something.
I'm going to go ahead and say, how about get_string do you agree--
whatever the question might be-- and I want the human to type y or n
for yes or no, respectively.
So if it's only a single character, actually, I
can actually get by with just get_char.
Not used it before, but it was on our menu
of functions from the CS50 library.
And if I want to get the user's response,
the return value should be a char also on the left.
So now we've seen strings, ints, and now chars,
if we only care about a single letter.
And now let's go ahead, check whether user agreed.
So how about if c == "y", then let me go ahead and, inside of my curly braces,
print out agreed or some such sentence like that.
else if they did not type c-- or you know what?
Let's be explicit here, just so they can't
type z or b or some random letter.
else if c=="n" n for no, then let me go ahead and print out not agreed,
or something like that.
And I'm just going to ignore the user if they don't cooperate
and they type z or b or something that's not y or n.
All right, let me go ahead now and compile this code, make agree, ./agree.
All right, do I agree?
Yes.
Let's go with the default. OK, so that seems to work.
No, I don't agree this time.
That seems to work.
How about my caps lock key is on or I'm just really yelling, capital Y?
It ignores me.
Capital N, it ignores me.
So, obviously, a bug, at least if I want to tolerate uppercase and lowercase,
which is kind of reasonable.
So what would be the possible solutions here, do you think?
How do I solve this and tolerate both capital and lowercase?
Maybe what's the simplest, most naive implementation?
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Yeah, so why don't I just ask two questions?
Or you know what, even more simplistic based only on what we've seen before--
if you will, let me just copy and paste some of this code.
Change this to an else-- whoops, not in caps-- else if "Y".
And then I bet I could do the same thing with n.
But here too, just like with Scratch, as soon
as you start to find yourself copying and pasting,
you're probably doing something wrong.
And what you said verbally, if I may, was actually better.
Because you're implying that I could just say something like OR c == "Y"
or, down here, c == "N".
The catch is, you can't use the word OR in C. It's actually two vertical bars.
So you can express one question or another.
You only need one of the answers to be yes or true,
and you use two vertical bars.
By contrast, just so you've seen it, if you
wanted to check if something is equal to something AND something else,
you could use two ampersands.
This logically would make no sense here, though.
Certainly, what the human typed can't both be lowercase and uppercase.
That just makes no sense.
So in this case, we do want OR.
But that allows me to tighten my code up.
I don't have to start copying and pasting whole branches.
I can now ask two questions at once.
Questions, then, on this variation?
Really good question.
Can you convert the input to all lowercase?
Absolutely, you could.
We don't have the capability yet.
It turns out that's going to require--
to be easy, another library, though we could do it
ourselves knowing a little bit about ASCII or Unicode from last week.
But, yes, that would be an alternative, but more on that a different time.
Other questions?
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Good question.
Unfortunately, you have to be explicit in C. You can't just say this,
even though that's kind of how you might think about it.
You have to ask a complete question using the equality sign twice
in this case.
Let me ask a question now too.
It's not a typo.
I deliberately used single quotes around all of my single letters here.
Why might that be?
Previously, we used double quotes for anything that looked like text.
Yeah.
Correct, string is double quotes for multiple characters-- or even one,
technically, but yes.
And single quotes for single characters.
Because my data type is different.
I chose the simple route of just using a single char.
In fact, this program won't work with Y-E-S
or N-O. That's not supported at the moment-- more on that another time.
I had to use single quotes because that's how C does it.
If you're dealing with single characters,
a.k.a. chars, use single quotes.
If it's a string--
even if it's one single character in a string
as though you're starting to write out a longer word or sentence--
that would be double quotes.
And we'll see why this is before long too.
But, again, just things to keep in mind whenever writing code
in this particular language.
Yeah, down here.
So, short answer, if I'm understanding correctly, this would be incorrect.
And this would be even more incorrect.
But if you don't mind, let me kick the can a couple of weeks
on this as to why this doesn't work.
The most pleasant way to do this would indeed be to do something like this.
But even this is a slippery slope, because what
if the user does something weird, like they capitalize just the Y?
You can imagine this getting messy quickly.
I like your idea earlier about just forcing everything
to lowercase just to standardize things.
Unfortunately, you cannot compare strings for equality like this
for, again, reasons will come to before long.
So for today, we're keeping it simple, even though, arguably, it's
not nearly as user-friendly to only tolerate individual letters.
And there's a question over here.
On the US English keyboard it's shift and then
the backslash key above Return, but depending on your keyboard,
it will vary.
All right, so let's actually now look back
at something we did a little bit of last week.
Let me go ahead and open a file called meow.c,
because, recall, that's what we had Scratch do initially.
Let me include not the C50 library this time,
but just stdio.h because I only want printf for this demo.
Let me go ahead now and just print out meow.
And then if I want the cat to meow three times, like it did last week,
meow, meow, meow.
Save it.
make meow, ./meow.
Voila.
The program is written-- correct, I claim.
It ran.
It compiled OK.
But, again, this was the beginning of our conversation
last week of not being particularly well-designed.
And if someone wants to maybe point out the now obvious,
why is this not well-designed, necessarily?
Yeah, it's just repetition, right?
Again, I literally resorted to copy-paste.
That should be the signal that you're probably
doing something wrong or, at best, just lazy of you, in this case.
So the solution, as you might glean from last week,
is probably going to be one of those things called loops.
So let's just take a look at some of the syntax for loops in C.
But, again, no new ideas, it's just some new syntax
that'll take some getting used to.
In Scratch, if you wanted to meow forever with something like this,
there's not a forever keyword in C, so this one's a little weird to look at.
But this is the best we can do.
It turns out there is a keyword called while in C.
And that kind of has the right semantics,
because it's like while I do something again and again,
that's the best I can do.
But just like an if condition or an else if condition,
those took a Boolean expression in parentheses,
a while loop also takes a Boolean expression in parentheses.
So I have to ask a question.
Now, if I want to do something forever, I could kind of stupidly just
say while 2 is greater than 1, while 3 is greater than 2,
or just something completely arbitrary.
But that should rub you the wrong way, because like, why 2 versus 1?
Why 3-- if you want true, just say true.
So it turns out in C, there are special keywords, true and false,
that are literally true and false, respectively.
I could also put the number 1 for true and the number 0 for false,
but most people would just say true to be explicit.
So it's a little hackish, if you will, but very conventional.
There's no forever keyword in C. If I want to then print meow forever,
I'm going to just use something like printf here.
So, again, not perfect translation from one
to the other, but absolutely possible in C. What about this?
This is a little more common if you want to do something
a finite number of times, like repeat 3.
There's a few different ways we can do this in C. Here's one approach.
And here's where C-- like a lot of text-based languages,
you kind of have to whip out that toolkit of all of the basic building
blocks and think about, all right, how can I
build a little machine in software that does something some number of times?
Well, let me give myself a variable called counter, set it equal to 0.
Let me create a loop whose Boolean expression is counter less than 3,
the idea being here, why don't I just kind of count 1, 2, 3?
So how do I implement this physicality in code?
I give myself a variable, set it to 0, 0 fingers up.
Now, I ask the question, is counter less than 3?
If so, go ahead and print out meow.
And just intuitively, even if you've never seen C code or any code
before Scratch, what more do I need to do?
I've left room here for one more line of logic.
Yeah.
We have to increase counter.
So I need code like I showed earlier, like counter equals counter plus 1.
And so here's where programming sometimes
becomes a bit more like plumbing.
You can't just say what you mean, like you couldn't Scratch.
You have to build a little sort of software machine
that initializes a value, does something, increments it, checks it.
And so it's kind of like this software-based machine,
but together, that's just using some familiar building blocks.
But this is pretty common.
Just like in Scratch, you might have used loops a bunch
of times, pretty common in C.
So can we tighten this code up?
This is correct, but here are some conventions that are popular.
If you're going to count, just say i.
A convention in programming-- with, at least,
languages like C-- is just use i as an integer if all its purpose is
is to count from like, 0 on up.
Counter is not wrong.
It's not bad.
It's just more verbose than you need to be.
Just call it i.
You don't need more semantics than that.
All right, what else can I do here?
There's another opportunity to tighten up this code.
Do you recall?
Yeah.
Yeah, that syntactic sugar that does nothing new,
but it does it more succinctly.
I can change this to either the intermediate format or even tighter
format of just i++.
Now, this is pretty canonical.
This is how most people would implement something
three times using a loop in C--
using a while loop, that is.
Turns out that it's so common in C and other languages
to do something finitely many times, there's a couple of ways to do it.
In this model, to be clear, the logic, though,
is that we start by initializing the variable, like I've highlighted here.
We then ask the question, is i less than 0?
If so, everything that's indented inside the curly braces
gets executed-- namely, meow then the update.
Then the computer is going to have to recheck the condition
to make sure that i hasn't gotten so big that it's greater than 3.
But if not, it then does this again and it does this again.
And then it repeats, constantly checking the condition
and executing what's in the block, checking the condition
and executing what's in the block.
After three times of that, the condition is going to be false, or a no answer,
and that's it for the code.
It just proceeds to whatever's down here, just like with Scratch.
It jumps to the next blocks down below.
All right, what's another way, though, to do this?
Well, I've deliberately been counting from 0--
and that's a programming convention, right?
We started last week with all the light bulbs off, which was 0.
So it's pretty reasonable to start counting at 0's, just
like you would here.
Like, no fingers are up, this is 0--
fingers on your hand.
But if you prefer, you could start counting at i equals 1.
But then you don't want to do it while i is less than 3,
you want to do i is less than or equal to 3.
On most keyboards, there's no symbol for less than or equal to or greater than
or equal to, so in C, you use two characters, less than
and then an equals sign with no spaces in between.
That just means less than or equal to.
We could change it to set i to 2 and make this condition be less than
or equal to 4.
We could make this be a 10 and less than or equal to 12.
But, again, just stick with the basics.
Start at 0 and count on up would be the convention.
Or if you prefer to count down, that's fine too.
Set i to 3 and then do this so long as i is greater than 0,
but you have to decrement instead of increment.
So, again, we could do this all day long.
There's literally an infinite number of ways to implement this idea.
And that's why I keep emphasizing convention.
Call the variable i for something like this,
initialize it to 0 for something like this, and just generally count up,
unless you really prefer to count down.
Again, just certain human conventions.
All right, how about another way to do this?
This is what's called a for loop in C, also very common.
It's not quite as straightforward in that it doesn't really read top
to bottom in exactly the same way.
This kind of has a lot more logic tucked into its first line.
But it does exactly the same thing.
What happens here is--
notice that inside the parentheses, next to the word for,
there's two semicolons-- which is another weird use of syntax.
They're not at the end of the line, now they're
in the middle of the parentheses.
But that's what the humans chose years ago.
The first thing before the semicolons initializes your variable, int i = 0.
The next thing is the condition that's going to constantly get
checked every cycle through this loop.
And the last thing is going to be what you do after each loop, which
in this case is going to be count up.
So, again, if I rewind we initialize i to 0.
We then ask the question, is i less than 3?
If so, execute what's inside of the loop.
Then the computer does this, it does the update, incrementing i by 1.
And then it's not going to blindly meow again.
It's going to check again the condition, is i less than 3?
Then it's going to meow if so.
Then it might go ahead and increment i and check the condition again.
So, again, this does not read quite the same simple fashion top to bottom.
You kind of read it left to right and then jump around.
But, again, the initialization, the constant Boolean expression
being checked, and the update after each time
does the exact same thing as what we saw a moment ago in this while loop format.
Which one is better?
Eh, they're the same.
I think most people would probably eventually use
a for loop once comfortable, but just because is really the answer there.
All right, any questions, then, on loops as we've translated them to C?
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: A for loop and while loop
can both be used to do exactly the same thing.
There are subtle differences with issues of scope,
which we'll discuss before long, where when you
create a variable in a for loop--
notice that it was, again, inside of those parentheses, which
technically means it's only going to exist in these four lines of code.
By contrast, with the while loop, I declared my variable outside
of the loop.
That variable is going to continue to exist elsewhere in my program.
So that's one of the minor differences there.
Good question.
But you'll see some others over time.
All right, so we claim then that it's better in some form
to do this with loops.
So let's actually jump back to the code.
Let me go ahead and now re-implement meowing with a for loop, for instance.
So how about for int i = 0, i less than 3, i++.
Then inside my curly braces, let me go ahead and print out with printf, meow,
with a newline and a semicolon.
So I did it pretty quickly just because I've long acquired the muscle memory.
But if I now make meow, no errors there.
Run ./meow.
And I see meow, meow, meow.
Well, let's do now what we did last week, which
was to begin to make our own custom functions, if you will,
by using our own in C. So here's where the syntax gets a little funky,
but we'll explain over time what each of these keywords is doing.
If I want to create a function called meow--
because the authors of C did not create a function called meow decades ago--
I need to give it a name, like meow.
I need to specify if it takes any inputs.
For now, I'm going to say no.
And I'm going to explicitly say no by writing the special word void.
It's also necessary when implementing a function in C--
which was not necessary in Scratch--
to specify what its return type is.
But for now, I'm just going to say that meow is the name of the function,
it takes no inputs--
and that's what the void in parentheses means--
and it does not return anything like ask did,
or like get_string or get_int does.
meow's purpose in life is just to have side effects, visual side effects
by printing something on the screen.
So what is meow going to do?
I'm going to have it quite simply say printf,
quote unquote, "meow", backslash n.
And now, just like in Scratch, I can now just call
a brand new function called meow.
And here's where too, if you really don't like the curly braces,
technically speaking, you can get rid of them when there's
only one line of code inside your loop.
But, again, stylistically, I would encourage
you to preserve them to make super clear to yourself and others
what it is that's going on.
Let me go ahead and save this and do make meow.
Whoops.
Darn.
All right, what did I do?
Something stupid.
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Yeah, so 0 does not belong there.
I meant to hit parenthesis.
So let me rerun make meow.
OK, fixed.
My mistake.
All right, it's still working OK.
But recall what I did in Scratch, kind of out of sight, out of mind.
And just to make a point, let me just highlight this and move it
way down in the file.
Because, again, now that meow exists, it's an abstraction.
I just know a meow function exists.
I want to be able to use it.
So let me scroll back up.
My main function is the same.
Let me go ahead and make meow again.
And now, just by moving that function, I've created all these lines of errors.
And let's look at the first.
Again, the rule of thumb here-- it's a little small,
but it says meow.c in bold-- which is the name of the file where the bug is--
5 is the line number, and 20 is the character.
So line number is enough alone.
Let's see.
Oh, this is what happens when I scrolled up too far.
Sorry.
This is the error we're now looking at, line 7.
I was looking at the old error message from earlier before I fixed the 0.
meow.c line 7.
All right, apparently, C does not know what the meow function is.
Implicit declaration of function meow is invalid in C99.
Well, what does that mean?
Declaration of function means your creation of a function.
Like, I'm declaring that meow exists, but I haven't apparently
defined it yet.
And then C99 is the version of C from the year 1999,
which we generally use here, it's one of the more recent versions.
So why is that the case?
Can you infer from the mere fact that I just moved meow to the bottom
of the file-- which was fine in Scratch but now is bad--
why is that?
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Yeah, C is just kind of old school.
It reads your code top to bottom.
And if it does not know what meow is when you first try to use it,
it just freaks out and prints out these error messages.
So the solution is, quite simply, don't do that, just leave it where it was.
But you can imagine this getting a little annoying over time, if only
because main is, by name, the main part of your program.
And, honestly, it would just be nice if main were always
at the top of your code.
Because if you want to understand what a file is doing,
it makes sense to just read it top to bottom.
Well, there is a solution to this.
You can put functions in different orders with main at the top so long
as you-- and this is perhaps the only time copy paste is appropriate--
so long as you leave a little breadcrumb for the compiler
at the very top of your file that literally
repeats the return value, the name, and the arguments
to that function, semicolon.
This is, so to speak, declaring your function--
and the real fancy way is this is a prototype.
It's like, what is this thing going to look like?
But the semicolon means I'm not going to deal with this yet.
I'm going to actually define the function
or implement it down below here.
This is kind of a stupid detail.
More recent languages get rid of this need,
you can put your functions in any order.
But, again, if you just think about the basics
of programming languages like this one here--
and as you noted--
it must just be reading your code top to bottom.
So annoying, yes, but explained, yes too.
So let me go ahead and make meow one more time, ./meow, still working OK.
And let me make one final enhancement to this meow program here.
Let me go ahead now and say something like this.
Let me go ahead and say, all right, wouldn't it
be nice if my meow function could do something for me some number of times?
So suppose I want to do this.
This meow function at the moment is going to meow three times.
But suppose I want to meow n times, where n is just
some number provided by the user.
Well, just like in Scratch, custom functions can take inputs,
I just presently am saying void.
But if I change this to int n, thereby telling the compiler,
hey, meow still doesn't return something,
but it does take something as input.
It takes an integer, and I want to call it n.
So this is another way of declaring a variable
but a way of declaring a variable that gets handed into,
as input, the function.
So now if I tighten up main here, now I can actually do something really cool
just like in Scratch, which is this.
If I now look at this code-- let me Zoom in here--
now my main program is really well-written in the sense
that it just says what it does, meow three times.
This works, though, because I defined meow as now taking an input,
an integer called n, and then using n in my now familiar for loop.
There's one change.
You might have caught my one mistake.
I also have to remind myself up here to make that change too.
Again, this is one of the only redundancies or copy-paste
that's sort of reasonable.
But there, I have now a better version.
So let me go ahead and rerun this, make meow, ./meow.
Voila.
So, again, no change in correctness but now,
again, we're sort of modularizing our code.
And, heck, what you could do now-- and this is just a tease about a feature
down the road--
those header files we talked about early, those libraries,
this is the kind of modularization we're talking about.
We, the staff, wrote a function called get_string, get_int, and so forth,
we put it in a file called CS50, and we put little breadcrumbs-- specifically,
these things called prototypes--
in cs50.h.
So that when you all, as aspiring programmers, include cs50.h,
you are sort of secretly telling the compiler at the very top of your code
what the menu of available functions is.
Why?
Because in CS50 is lines like these-- obviously, not for meow,
but for get_string, get_int, and so forth.
And stdio.h is the same lines of code for things like printf.
So that's all that's going on there.
It's just a way of telling the computer in advance what functions to expect.
All right, any questions, then, on these here?
Correct.
So if you don't mind, I want to continue to wave
my hand at that detail for today.
Indeed, int main void is a little weird, because what would the input domain be?
We have no mechanism for providing input yet.
And what does it mean for main to return anything?
Like, who is it returning to?
For another day, if we may.
They're going to come into play but that, for now,
today is just something you should take at face value,
as necessary copy-paste to begin programs.
So meow is a function that takes an input, the number of times to meow,
but it didn't actually have a return value, hence the void.
But what if we actually want to create our own function that
not only takes 0 or more inputs as arguments
but also returns some value, maybe an int, maybe a float, maybe something
else altogether?
Well, it turns out, in C, we can do that as well.
Let me go ahead and create a new file here called discount.
And let's implement a quick program via which
we can discount some regular price by some percentage,
as though there's a sale going on in a store.
Let me go ahead and include our usual cs50.h followed by stdio.h at the top.
Let me give myself int main void as before.
And inside of main, let's go ahead and do something simple.
Let's give ourselves a float called regular,
representing the regular price of something in a store.
Let's go ahead and get a float from the user asking them
what that regular price is.
Then, next, let's go ahead and declare a second variable-- also a float--
called sale, ultimately representing the sale price
after some percentage discount off.
And let's go ahead and simply calculate whatever regular is.
And, say, 15% off is a pretty good discount.
So let's go ahead and discount regular, whatever it is, by 15%,
which is equivalent, of course, to multiplying it with the asterisk
by 0.85.
Of course, if we're taking off 15%, we multiply the regular price by 0.85.
Now, let's go ahead and print out the results here.
Let me go ahead and say printf sale price, colon--
let me go ahead and %f, but, more specifically,
%.2f because, at least in US currency we typically show cents to two decimal
places--
followed by a newline.
And then let me go ahead and plug in the value of sale.
All right, let's go down here and do make discount, Enter.
So far, so good-- ./discount.
And the regular price is maybe $100.
So the sale price should be $85.
So our arithmetic seems to be correct here.
But let's fast-forward now in time.
Suppose that we find ourselves discounting
a lot of prices in an application, maybe a website
like Amazon where they're offering some kind of percentage discount.
And it'd be nice to have a reusable function that
just does this arithmetic for us, simple though it may be.
So let's go ahead and modify discount this time
to give ourselves our own function called discount,
for instance, that takes an input--
like the regular price that you want to discount--
and then it also returns a value.
It doesn't just print it out.
It returns a value, namely, a float that represents what the sale price is.
So let me go down below main and go ahead
and define a function that's going to return a float,
because we're dealing with dollar amount still.
The function is going to be called discount.
And it's going to take one input, like the price that we want to discount.
In here, I'm going to do something very simple.
I'm going to say float sale equals whatever that price is times 0.85.
And then I'm going to go ahead and return sale.
Now, for that matter, I can actually tighten this up a bit.
If I'm only declaring a variable to store a value that I'm then
returning with this keyword return, I actually don't even need that variable.
So I can delete the second line.
And I can actually just go ahead and get rid of that variable
altogether and immediately return whatever the arithmetic
result is of taking the price input, the argument that's being passed in,
times 0.85.
So very simple function that simply does the discounting for me.
As always, let me go ahead and copy-paste--
the only time it's OK to copy-paste-- the prototype of that function, so
the top of the file, so that when compiling this code,
main has already seen the word discount before.
And now let me go into the code here.
And instead of doing the math myself in main,
let me presume that we have some function already
in our toolkit called discount that lets me discount the regular price
and return that value.
And then down here, my code doesn't need to change.
I'm still going to print out sale the variable in which I'm
storing that result. But notice what I've done here.
I've sort of abstracted the way the notion
of taking a discount by creating my own function that takes a float called
price, or anything else as input.
It does a little bit of math, simple though it is here,
and then it returns a value.
But notice that discount is not printing that value.
It's literally using this other keyword called
return so that I can hand back that value, just like get_string hands
back a value, just like get_int back an integer without printing it
for you-- so that I up here on line 9 can go ahead and store
that value in a variable if I want and then actually print it out.
Let me go ahead now and recompile this code with make discount.
Let me go ahead and do ./discount.
And let's, again, do $100.
Sale price is going to be $85 as well.
Now, it turns out that functions don't have to take just 0 or 1 argument
as input.
They can actually take 2 or 3 or more.
So, in fact, suppose we wanted to now enhance this version of my program
and take in as input to the discount function, not just the price
that I want to discount but also the percentage off,
thereby allowing us to support not just 15% off but any number of percentage
points off.
Well, let me go up here and declare an int, say, and call it percent_off.
And let me ask the user for how many percentage
points they want to take off.
So I'm going to say percent_off inside of the prompt here,
get that int called percent_off.
And now in addition to passing in regular as an input
to the discount function, I'm also going to pass in percent_off.
But I need to tell the computer that it is taking now two arguments,
and the way I do this is just with a comma
down here in the function's own definition.
Here is going to be a percentage argument, a second argument,
per the comma.
And I'm now going to use that percentage in a slightly familiar way.
I don't want to just do percentage like this, because, of course,
that's going to increase the size of the total price.
I actually need to do a little bit of real-world math where if this is
a percentage off, like the number 15 for 15 percentage points,
I need to do 100 minus that many percentage points,
thereby giving me 100 minus 15--
85.
And then I need to divide that by 100 in order now
to give myself 0.85 times the price that was passed in.
But if I go ahead now and save this, run, make discount one last time,
I notice that I've actually got an error here.
What have I done wrong?
Well, I need to change that prototype too.
And, again, this is admittedly an annoying aspect of C
that you have to maintain consistency here.
But that's fine.
I'm just going to go up here, change this to int
percentage-- spelling incorrectly.
And now let me retry compilation, make discount,
crossing my fingers this time.
Worked OK. ./discount, and voila, $100.
And percent off, say, 15 points.
And, voila, $85.
Now, it's worth noting that I've deliberately
returned the results of my math from this function.
I haven't just done the math on the original variable that's being passed.
In fact, if we take a look at this second version
where discount is now taking a price argument and a percentage argument,
notice that I'm not doing something like this.
I'm not just saying price equals price times 100
minus percentage divided by 100 and leaving at that.
The problem there is that this variable price is going
to be scoped to that discount function.
And we'll encounter this again before long, but this notion of scope
just refers to where in which a variable actually lives or exists
or is accessible.
So it turns out if I change price in the context of this discount
function, that's not going to have a lasting effect.
If I actually want to get the result back
to the function that used the discount function, namely, main,
I actually do need to take this approach of actually returning
the value explicitly so that ultimately I'm handing back the discounted price.
All right.
Well, let's go ahead and maybe how about let's just
use these primitives in just a few different ways.
How about a little game of yesteryear, Super Mario Brothers?
And in the original Super Mario Brothers and in bunches of variants,
so you have these side-scrolling worlds that
look like this where there's some coins in the sky hidden behind these question
marks.
So let's just use this as a visual to consider how in C could
I start to make something semi-graphical.
Like, not actual colors or fanciness, that feels like too much too soon--
just something like printing out some question marks.
Well, if I go back over here, let me create that actual file
that I alluded to earlier.
So let me code up mario.c.
Let me go ahead and include stdio.h, int main void, again,
which we'll continue to copy-paste for today.
And then let me just go ahead and do something simple like 1, 2, 3, 4,
and a newline.
All right, this is what we might call ASCII art, which
just means graphics but really just implemented with your keyboard.
And if I make mario and do ./mario, it's not nearly as engaging visually
as this, but it's the beginning of this kind of map for a game.
Well, if I wanted to now print out of those things dynamically,
let me go back to my code here.
And instead of printing out for all at once,
I could do something like four int i gets 0, i less than 4, i plus plus.
And then inside here, I could just print out one of them at a time.
Let me save that, make mario.
And, at the risk of disappointing, so close
but I made a mistake, just a stupid aesthetic.
The prompt is not on the new line.
How could I move it?
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Yeah, I need an escape character, the backslash n.
But should I put it here?
OK, no, because that's going to put it after everyone,
and it's going to make this thing vertical instead of horizontal.
So, logically, just like in Scratch, put it at the end of the loop, so something
out here.
And just print out, for instance, only, quote unquote, new line.
And now if I do make mario again, ./mario, OK.
We're back in business.
But a little better designed in that now I'm
not repeating myself multiple times, I'm doing this again and again.
But let's do one other thing here with mario.
Let me go ahead and ask the user how many question marks or coins to print.
The catch here is that there's another type of loop that's helpful for this,
and it's called a do while loop, generally.
A do while loop is similar to a while loop,
but it checks the condition last instead of first.
Recall earlier on the slide, we had while,
open parenthesis, closed parenthesis.
And I kept claiming that we check whether i is less than-- whatever
it was, 3 in advance again and again.
A do while loop just inverts the logic so that you can actually
do something like this.
At the top of this program, I'm going to go ahead now
and give myself a variable n like this of type integer.
And then I'm going to do, literally, the following with the keyword do.
n equals get_int-- and I'm going to ask the user for the width,
like the number of dollar signs to print.
And I'm going to do this while n is less than, say, 1.
So this is a little cryptic, but the salient differences
are the Boolean expression is now at the bottom of my block of code,
not at the top.
Now, why is this?
Well, the difference here if I make mario is--
whoops.
I need to add cs50.h, because I'm now using get_int.
If I now compile this version of Mario and do ./mario,
a do while loop is helpful when you want to do something no matter what first
and then check some condition or some Boolean expression to see if maybe,
in this case, the user cooperated.
It would make no sense if the user typed in, say, 0,
because there's no work to be done.
It'd be really weird if they said negative 100,
because that makes no sense logically.
So with this simple construct here, I am doing the following
while n is less than 1.
The implication is that as soon as n equals 1 or is bigger than 1,
I'm going to break out of this loop, and I've got myself
a variable called n containing, essentially, a positive value, 1
through 2 billion or so.
And I can now use this, for instance, here, change the 4 to an n
so now my program is completely dynamic.
Let me go ahead and do make mario, ./mario again.
And I'll do 4, still works.
I'll do 40, still works.
And the difference here with the do while is if something like this
involves getting user input, well, there's no question to ask.
The user hasn't given you anything yet.
So you have to do something first, then check, and break out of the loop
if the human has, for instance, cooperated, in this case.
All right, well why don't we escalate to something
more like this in the same game, where you're underground as Mario,
and this is like a two-dimensional wall that's popping up here?
It looks like a 3 by 3, for instance, for the sake of discussion.
And it's like, made of bricks, so I'll use maybe hash symbols this time.
Well, it turns out that we can nest--
that is, combine-- some of these same ideas as follows.
Let me go ahead now and change back to this code.
And I'm going to keep the do while loop from before.
And I'm going to ask, though, this question,
what's the size of this square?
I'm going to assume it's n by n, so 3 by 3, 4 by 4, whatever.
So I'm just going to ask for the size of this square of bricks.
And now, how do I do this?
Well, I'm going to go ahead, for instance, and print out--
how about for int i = 0, i less than n, i++.
Let me just keep it simple and print out something
like this, just a single hash symbol that is a brick,
and a newline after it.
All right, let's make mario.
Run mario of 3.
OK, that's close to being it.
I've got a column.
All right, but I need it to be wider.
So the solution last time was to get rid of the newline
and then maybe put the newline here, after the loop.
All right, so let's do make mario, ./mario, and type in 3 and huh.
All right, so I kind of need to combine these two ideas somehow.
So how might we solve this problem?
I want to print rows and columns, not row or column.
How do I do this?
Yeah.
AUDIENCE: Add another loop in the for loop.
DAVID J. MALAN: Yeah.
Add another loop in the for loop, right?
If you use one loop conceptually to kind of count the rows from top
to bottom, and then within each row, you then
sort of typewriter style-- old school typewriter--
do like, character, character, character, character horizontally,
I think we could do exactly what we want to achieve here.
So how about this?
Let me get rid of this line and get rid of this line for now.
And let me just give myself another loop on the inside.
And since I'm already using i, another reasonable convention
here would be to say something like j.
So j also gets 0, j is less than n, j++.
And now, what's going to happen?
Let me go ahead and print out just one of these things at a time.
And let me save and let me run this.
Let me see how close we are.
Make mario 3.
OK, three, that's clearly wrong, but I see nine things there on the screen.
So we're close.
What's the one fix I need now to move the old school typewriter head
down to the next row when appropriate?
What do you think?
Yeah, I need one of these backslash n's.
And let me add some comments now to help everyone visualize what I've done.
For each row, for each column, how about print a brick--
just to kind of explain the logic?
And so I add that because now move to next row,
I could do something like this with a backslash n.
So here is where the comments, really, my pseudocode
actually kind of illuminates the situation a bit.
Let me go ahead and recompile mario, ./mario 3, now we're talking.
It's not a perfect square, just because these hash symbols
are a little taller than they are wide, but that's just a font detail here.
Now I've done something that's quite more akin to something like this.
All right, so let me pause here and see if there are any questions.
Again, the code's getting a little more complicated,
but we're just building more complicated programs like in Scratch,
with familiar puzzle pieces-- some variables,
some loops, some conditionals.
It's all the same as before.
Yeah.
Can you multiply strings in C?
No.
But ask that same question again in a few weeks when we get to Python,
and the answer will be yes.
Other questions.
Yeah.
In C, you must specify the return type, the name
of the function, and the inputs, or arguments,
to the function in that order.
And if none of them are applicable, you write the word void.
So same question as earlier, let me kick that can a week or so,
and we'll come back to that and we'll see why.
But for now, just take on faith that you need to do that with main.
Because main is a little special, similar to the
when green flag is clicked.
It too was a little special as well.
Yeah
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Yes.
If you want to get out of a loop early, you could do this.
So let me answer this question this way.
An alternative to a do while loop would be to do something like this.
How about while true--
so do the following forever--
let me go ahead and get an inch from the user for the size of this thing.
If n is greater than 0--
that is, a positive integer--
then go ahead and use a new keyword called break.
This is identical to what we just did.
It's just a little longer.
It's like a couple extra lines, a lot of them are blank.
And so it's just an alternative.
But a do while does the same thing but a little tighter--
if that's in answer to your question.
All right, so let's now introduce, finally, a sequence of problems
that I've kind of been brushing under the rug,
though we did see a little bit of evidence of this earlier
when we tried to add 2 billion and 2 billion,
and it overflowed the number of bits in an int, so to speak.
Let me go ahead and code up a program called calculator again.
But I'm going to go ahead now and change this to floats.
So I'm going to change x to a float, and I'm going to use get_float.
And a float, again, is just a floating point value,
which is a fancy way of saying a real number with a decimal point in it.
And down here, I'm going to go ahead and use %f for float.
And I'm going to go ahead now and do one more thing.
Instead of addition, I want to do something fancier, like division,
so divide x by y.
And I'm going to give myself another third float called z,
as we did at the beginning of today.
And I'm going to print out z instead of x and y explicitly.
So I'm going to go ahead now and do make calculator, ./calculator.
And let's do something like, oh, 2/3.
2 divided by 3 is 0.66667.
So that's what you would rather expect.
Let me run it again, 1/10.
All right, so 0.1, and a bunch of zeros.
That too is what you would rather expect.
But now let me get a little curious.
It turns out that in C, you can modify the behavior of these format
codes a little bit.
By default, you get 6 or so digits.
Suppose that you want to get exactly 2 digits.
You can more succinctly say 0.2 before the f and after the percent.
This is the kind of thing that's hard to remember, but you Google it,
and you find that, OK, format code for floats
uses 0.2 to do two decimal points.
So let me do make calculator again, ./calculator.
How about 2/3?
0.67.
So it handles the display of significant digits for us here.
And now let me go ahead and do 1/10 and 0.10.
So it's adhering to that.
Well, maybe I really want a lot of precision, right?
I've got a really powerful computer.
Let me see 50 numbers after the decimal point.
That's a lot of significant digits.
Let me remake the calculator-- whoops, typo.
Let me remake the calculator, ./mario calculator.
And how about 2/3 again?
Well, that's interesting.
Pretty sure it's supposed to be a 0.6 with a line over it, right?
In grade school math.
All right, well, maybe that's just a bug.
How about 1/10?
OK, that's really getting funky.
So what's going on?
It seems that my program cannot only not do addition very well--
we eventually hit problems in the billions--
we can't even do very precise numbers here.
What's going on?
Exactly.
In a nutshell, the computer's approximating the answer
using that many numbers after the decimal point.
But the problem fundamentally is actually
very similar to that integer overflow from before.
And I'm using that now as a term of art.
Integers can overflow if you're trying to use more bits than you actually
have available to you.
You sort of change them all to ones, and then you're out of bits, so to speak.
Same thing here, but in the different context of floats--
if you only have 32 bits-- or, heck, if we
change to double and only have 64 bits, that's a lot of precision,
but it's not infinite.
And, yet, pretty sure there's an infinite number of real numbers.
In the world, which is to say a computer with finite memory cannot possibly
represent all possible numbers in the world.
Because, again, there's not an infinite number
of permutations of 32 or 64 bits.
It might be a lot, in the billions or more, but it's still finite.
And so, indeed, this is the computer's closest approximation
to what's actually going on there.
And so this is an example of what we would actually generally call
floating-point imprecision.
Floating-point imprecision refers to the inability for computers fundamentally
to represent all possible real numbers 100%
precisely, at least by default in languages like C. Thankfully,
in the world of scientific computing and so forth,
there are solutions to this problem that just give you more digits.
But the problem fundamentally is still going to be there.
So there's a reason I changed x and y to floats.
Let's see what would happen if we rewound a bit.
And instead of using floats for x and y, again, you say integer, so int x and y.
And let's go far back and use get_int as well,
thereby giving us integers x and y.
Let's still leave z as a float, because at the end of the day,
we want to be able to handle fractions or floating-point values.
But let's go ahead now and print out this value of z
having changed x and y now to ints.
make calculator, ./calculator, and let's do, say, 2 for the numerator,
3 for the denominator.
And it's not 0.666, and it's not even rounding oddly.
It's just all zeros this time.
So why is that?
Well, it turns out that C, when dividing an integer by an integer,
is always going to give you back an integer, an int.
The problem is that floating-point values don't fit in ints.
Only the integral part to the left of the decimal point does.
Everything at and beyond the decimal point itself get thrown away,
known as a feature in C called truncation.
When dividing an integer by an integer, you get back an integer.
But if you're trying to then store what's actually a floating point
result in that integer, C is just going to throw away everything
at and beyond the decimal point, leaving us with this case,
in just the 0 from what should have been 0.666666 and so forth.
So let's see one more example, in fact.
Let me go back to my terminal here.
Let me do ./calculator again.
And let's do 4/3.
This time, It should be 1.33333 and so forth.
But let's see, 4 divided by 3, both as integers, this time gives us 1.0000,
but there too the answer should be 1.333.
But the floating-point part is getting truncated or thrown away,
leaving us with just 1.
So how do we solve this?
Well, certainly, we could just use floats from the get-go, as I did.
But if, by nature of your program, you only have access to integers--
or maybe even longs, for which the same problem would occur--
what we can actually do is called type conversion.
And we can explicitly tell the computer that we actually
want to treat this int as though it's a floating-point value.
And we can do that for both x and y.
So let me go back to my code here, and I have a couple of options, in fact.
I can convert y to a float by doing this, I can cast y to a float
by literally writing the type float inside of parentheses
right before the y.
And if I really want to be explicit, I can also do the same to x.
But, strictly speaking, it suffices to just change one or the other,
not necessarily both.
Let me go ahead now and do make calculator again, ./calculator,
and let's try 2 divided by 3.
And now, we're back to an answer that's closer to correct.
But, indeed, we're still having some rounding issues there.
Let's run it one more time for 4 divided by 3.
There too we're closer to the right answer, at least.
But we still have that floating-point imprecision,
but that's going to be another problem altogether to solve.
And here in a little more detail is that issue
of integer overflow, which is in the context of ints.
Suppose that we think back to last week when we had three bits,
and we counted from 0 to 7, 0, 1, 2, 3, 4, 5, 6, 7.
I think I asked the question, how would we count to 8?
Someone proposed, well, we need a fourth bit.
That's fine if you have a fourth bit, if you have access
to another light bulb or transistor.
If you don't, though, the next number after this is technically 1000.
But if you don't have space for or hardware for that fourth bit,
you might as well just be representing the number 0.
So in the world of integers, if you're only using three bits,
those three bits eventually overflow when you count past 7.
Because what should be 8 can't fit, so to speak, so it rolls back over to 0.
And as arcane as this problem might seem,
we humans have done this a couple of times.
You might recall knowing about or reading
about the Y2K problem, where a lot of people
thought the world was going to end.
Why?
Because on January 1st of 2000, a lot of computers,
presumably, were going to update their clocks from 1999 to the year 2000.
The problem is, though, for decades, for efficiency, we humans
were honestly in the habit of not storing years as four digits.
Why?
Because that's just a lot of space to waste, especially since centuries
don't happen that often.
So a lot of computer systems, especially early on when
hardware was very expensive and memory was very tight,
just stored the last two digits of any year.
The problem, of course, on January 1st of 2000 is that 99 rolls over to 100.
But if you don't have room for another digit it's just 00.
And if your code assumes a prefix of 19, well, we just went from the year
1999 back to the year 1900.
Thankfully, long story short, a lot of people wrote a lot of code
in a lot of old languages and mostly warded off this problem,
so the world did not end.
The next time the world might end though, is on January 19, 2038.
Now, that might feel like a long time away,
but so did the year 2000, at one point.
Why might clocks again break in today's modern computers in 2038,
might you think?
AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Indeed.
So this refers to some number of seconds.
So it turns out that the way computers generally keep track of time
is they count the total number of seconds since the epoch, which
is defined as January 1, 1970.
Why?
It was just a good year to start counting at,
when computers really came onto the scene.
Unfortunately, most computers used 32 bits to count the number of seconds
since January 1, 1970, the implication of which is we
can only count up to roughly 2 billion seconds.
2 billion seconds is going to happen in 2038, at which 30 11's
are going to roll over as follows.
That number 2 billion, which is the max--
because if you're representing positive and negative numbers,
recall that you can only count as high as positive 2 billion
or negative 2 billion--
looks like this.
This is roughly the number 2 billion in binary.
It's all ones with one zero way over here.
If I count one second past that 2 billion number, give or take--
that means, all right, I add 1, I carry the 1--
it's just like 9's becoming 0's in decimal.
If I keep this sort of simple animation and I keep carrying the 1,
carrying the 1, carrying the 1, 1 second after 2 billion seconds, give or take,
I have this number in the computer's memory.
So there's still 1 bit that's a 1 all the way to the left.
Unfortunately, that bit often represents negativity,
whereby if that first bit is negative, that represents that the rest of it
somehow represents a negative number.
It's not negative 0.
There's a fancier representation.
But a very big, positive number very suddenly
becomes a very big, negative number.
And that number is roughly negative 2 billion.
That means computers in 2038 on that date
are going to accidentally think that it's
been negative 2 billion seconds since January 1, 1970, which is going to make
computers potentially think it's 1901.
So what is the solution to the 2038 problem, perhaps?
Y2K was because we were using two digits for years.
What about 2038?
More bits.
And, thankfully, we're getting a little better at lessons learned here,
and computers now are increasingly using 64 bits.
And all of us will be long gone by the time we run out
of that number of seconds, so it's someone else's problem
many, many years from now.
But that's really the fundamental solution.
If you're running up against something finite, well,
just kick the can further and just give yourself more bits.
And, frankly, because hardware is so much cheaper these days,
computers are so much faster, it's not as big of a deal
as it might have been decades ago.
But that's indeed the solution.
But this arises in very common contexts.
In fact, let me go ahead and write a real quick program here called pennies.
You might think that just converting dollars to pennies in US currency
might be simple, but let me go ahead and do this.
In pennies.c, I'm going to go ahead and include cs50.h.
And I'm going to include stdio.h, int main void as my starting point.
And now down here, I'm going to do this.
I'm going to get a float called amount, and I'm
going to ask the user for some amount of dollars, so a dollar amount,
and I'm going to store that in a variable called amount.
Then I'm going to simply convert that amount to pennies by doing, say, how
about amount times 100?
And then I'm going to go ahead and print out that the number of pennies is %i--
because that's just an integer in pennies--
backslash n, quote unquote, comma, pennies.
All right, so if I didn't make any mistakes here, let me make pennies,
./pennies.
And suppose I have, say, $0.99, so 0.99.
That's 99 pennies.
Suppose I have $1.23.
That's pretty good.
Suppose I have $4.20.
Huh.
There's that imprecision issue.
And this isn't even that big of an amount.
Now, not a big deal if the cashier gives you one penny less than you're owed,
but you can imagine this adding up.
You can imagine this being worrisome for financial implications,
for financial transactions, for scientific measurements and the like.
My program can't even handle this.
Well, there are some solutions here.
And it looks like what's really happening--
if I print it out using the %f with a 0.50 or whatever to see more decimal
points--
presumably, the computer is struggling to represent $4.20 precisely.
It's probably storing 4 dollars and 19.9999-something cents.
So it's close, but it's not quite there.
So I could at least solve this by rounding up, for instance.
And it turns out there is a round function out there.
And it turns out that it's in a library called the math library.
And you would know this by looking at online documentation and the like,
as we'll point you to.
And if I now make pennies again and do ./pennies, I can now do $4.20.
And, voila.
Now it's correct.
So at least in this context, it seems like a solvable problem.
But it's certainly something I need to be mindful of, nonetheless.
Unfortunately, even professional, full-time programmers over the years
have not been particularly attentive to these kinds of details.
And in a class like this, the goal is not just to teach you programming
but to really teach you what's going on underneath the hood, so to speak,
so that you have a bottom-up understanding of how data
is represented, how computers are manipulating it,
so that you are not on the failing end of some program having some bug.
And so that we as a society are not beholden to those kinds of mistakes
too.
And this happens, unfortunately, all of the time.
This is a Boeing airplane that a few years ago needed
to be rebooted after every 248 days.
Why?
Because this Boeing airplane software was using a 32-bit integer counting up
tenths of a second to keep track of something or other
related to its electrical power.
And, unfortunately, after 248 days of the airplane being continuously on--
which in the airline industry is apparently not
uncommon to make every dollar count, keeping the planes up and running
all the time--
the 32-bit number would roll over and the power
would shut off on the airplane as a side effect because of sort
of undefined behavior in that case.
The temporary solution by Boeing at the time was apparently, essentially,
sort of operating system style, well, have you rebooted your plane?
And that was indeed the fix until they rolled out an actual software patch.
This stuff really matters.
And the more hardware we carry around and the more we as a society
use these kinds of devices, the more of these problems
we're going to run into down the road.
That's it for CS50.
We'll see you next time.
[MUSIC PLAYING]