DOUG LLOYD: All right. So now let's tackle a really big topic, functions. So far in the course, all the programs that we've been writing have been written inside of main. They're pretty simple programs. You don't need to have all these branches and things going on. We can just fit it all inside of main and it doesn't get terribly overwhelming. But as the course goes on and as you begin to develop programs independently, they're probably going to start to get a lot more than 10 or 15 lines. You might get hundreds or thousands or tens of thousands of lines of code. And it's really not that crazy a thought. As such, it's probably not a good idea to keep everything inside of main. It can get a little difficult to find what you're looking for if you do that. Fortunately, though C, and pretty much every other programming language that might work with, allows us to write functions. And I'm just going to take a quick aside here to mention that functions is one area of computer science. And you'll see many more of them at various points throughout the course and if you continue on. Where there's a lot of synonyms for the same word. So we call the functions. But you might also hear them referred to as procedures, or methods, particularly, if you've ever done any object oriented programming before-- and don't worry if you haven't, not a big deal-- but in audit oriented languages are frequently called methods. Sometimes they're called subroutines. But they really all refer to the same basic idea.
Let's see what that idea is. What is a function? Well a function is really nothing more than a black box. A black box that has a set of zero or more inputs and a single output. So for example, this might be a function. This is a function called func. And it takes three inputs a, b, and c. And inside that black box, we don't know exactly what it does, but it processes the inputs in some way and then it gives a single output, in this case, z. Now to make it a little less abstract, we could say that maybe we have a function called add that takes three inputs a, b, and c and processes the output in some way inside the black box to produce a single output. So in this case, if add takes 3, 6, and 7. Somewhere inside the add function, we would expect them to be added together to produce the output, which is 3 plus 6 plus 7 or 16.
Similarly, you have a function called mult that takes two inputs, a and b, processes them in some way such that the output of the function is the product of the two inputs. The two inputs multiplied together. 4 and 5 being passed into mult, something happens, the output we expect is 20. Why do we call it a black box? Well if we aren't writing the functions ourselves, which we've done quite a bit so far cs50. We've seen print f, for example, which is a function that we didn't write ourselves, but we do use all the time. If we aren't writing the functions ourselves, we don't really need to know how it's actually implemented under the hood.
So for example the black box I just showed you for multiplication, mult a, b could be defined-- and this is just some pseudocode-- could be defined as output a times b. That make sense, right. If we have a function called mult that takes two inputs. We would expect that the output would be the two inputs multiplied together, a times b. But mult could also be implemented like this, we have a counter variable to get set inside of mult to 0. And then we repeat this process b times add a to counter. For example, if we multiply 3a by 5b, we could say set counter to 0, repeat five times, add 3 to counter. So we start at 0 and then we do this five times 3, 6, 9, 12, 15. It's the same result. We still get 3 times 5 just the implementation is different.
That's what we mean when we say a black box. It just means we don't really care how it's implemented under the hood as long as the output is what we expect. In fact, that's part of the contract of using functions, particularly functions that others write. The behavior is always going to be typical, unpredictable based on the name of the function. And that's why it's really important when you write functions or when other people write functions that you might use, that those functions have clear, relatively obvious names, and are well documented. Which is certainly the case for function like print f.
So why do we use functions? Well as I said earlier, if we write all of our code inside of main things can get really cumbersome and really complicated. Functions allow us the ability to organize things and break up a very complicated problem into a lot more manageable sub parts. Functions also allow us to simplify the coding process. It's a lot easier to debug a 10 line function versus a 100 line function or a 1,000 line function. If we only have to debug small pieces at a time, or write small pieces at the time, it makes that programming experience a lot better. Trust me on that one.
Lastly, if we write functions we can reuse those various parts. Functions can be recycled. They can be used in one program or another. You've already written the function, all you need to do is tell that program where to find that function. We've been recycling and using print f for over 40 years. But it was only written one time. Pretty useful, right. All right. So functions are great. We know that. Now let's start writing them. Let's start getting them into our programs. In order to do that, the first thing we do is declare the function. When you declare a function what you're basically doing is telling the compiler, hey, just so you know, I am going to be writing a function later on and here's what it's going to look like. The reason for this is because compilers can do some weird things if they see a set of symbols that they're not familiar with. So we just give the compiler a heads up, I'm creating a function and it's going to do this. Function declarations generally if you're organizing your code in a way that others will be able to understand and make use of, you generally want to put all of your function declarations at the very top of your code, right before you start writing main even. And conveniently, there's a very standard form that every function declaration follows. They all pretty much look like this. There are three parts to a function declaration, return type, name, and argument list.
Now the return type is what kind of variable the function will output. So for example, if we think back a minute ago to the multiplying two numbers function, what do we expect if we multiply an integer by an integer the output will be probably an integer, right. Multiplied two integers together, you get an integer. So the return type of that function would be int. Name is what you want to call your function. This is probably the least important part of the function declaration, in terms of functionality. But is actually probably one of the most important parts of the function declaration in terms of knowing what the function actually does. If you name your function f or g or h or mystery or something like that, you're probably going to get a little tripped up trying to remember what those functions do. So it's important to give your function's meaningful names.
Lastly, argument list is the comma separated list of all the inputs to your function, each of which has a type and a name. So not only do you have to specify what type of variable the function will output, you also want to specify what type and types of variables the function will be accepting as inputs. So let's do an example here. Let's just take a look at a more concrete one. So here's an example of a function declaration for a function that would add two integers together. The sum of two integers is going to be an integer as well, as we just discussed. And so the return type, here in green, would be int. That just tells us that add two ints is going to, at the end of the day, output, or spit it back out to us, an integer. Given what this function does we want to give it a meaningful name. Add two ints seems appropriate, considering we're taking two integers as inputs and hopefully adding them together. It might be a bit of a cumbersome name and frankly this function is probably not necessary since we have the addition operator, if you recall from our discussion of operators, previously. But let's just say for sake of argument that this function is useful and so we'll call it add two ints. Lastly, this function takes two inputs. Each of which is an integer. So we have this comma separated list of inputs. Now we generally want to give a name to each of them so that they can be used within the function. The names aren't terribly important.
In this case, we don't necessarily have any meaning attached to them. So we can just call them a and b. That's totally fine. If however, you find yourself in a situation where the names of the variables might actually be important, you might want to call them something other than a and b to give them something more symbolically meaningful. But in this case, we don't really know anything else about the function. We just want to add two integers. So we'll just call those integers a and b. That's one example.
Why don't you take a second to think about this one, how would you write a function declaration for a function that multiplies two floating point numbers? Do you remember what a floating point number is? What would this function declaration look like? I actually recommend you pause the video here and take how much time you need. Think about what this function declaration would be? What would the return type be? What would a meaningful name be? What would the inputs be? So why don't you pause the video here and write-up a function declaration for a function that would multiply two floating point numbers together. Hopefully you paused the video.
So let's take a look at an example of one possible declaration. Float mult two reals float x, float y. The product of two floating point numbers, which recall are how we represent real numbers or numbers with decimal values in c, is going to be a floating point number. When you multiply a decimal by a decimal, you're probably going to get a decimal. You want to give it a relevant name. Multiply two reals seems fine. But you could really call it mult two floats, or mult floats. Anything like that, as long as it gave some actual meaning to what this black box was going to do. And again, in this case, we don't seem to have any meaning attached to the names of the variables we're passing in, so we just call them x and y. Now if you call them something else, that's totally fine. In fact, if you did this declaration instead using doubles instead of floats, if you recall that doubles are a different way to more precisely specify real numbers or floating point variables. That's totally fine too. Either one of those would be fine. In fact, there are several different combinations of ways to declare this function. But these are two pretty good ones. We've declared a function, that's great. We've told the compiler what it is, what we're going to be doing. Now let's actually write that function. Let's give it a definition, so that inside the black box predictable behavior is happening. In fact, we are multiplying two real numbers together, or adding numbers together, or doing whatever it is that we asked our function to do.
So in fact, let's try and define multiply two reals which we just talked about a second ago. Now the beginning of a function definition looks almost exactly the same as a function declaration. I have both of them here. At the top is the function declaration, type, name, comma separated argument list, semicolon. The semicolon indicates that that is a function declaration. The beginning of the function definition looks almost exactly the same, type, name, comma separated argument list, no semicolon, open curly brace. The open curly brace, just as we've been doing with main, means that we are now beginning to define what happens inside the black box that we've decided to call mult two reals. Here is one way to implement it. We could say, we could declare a new variable of type float called product and assign that variable to the value x times y. And then return product. What does return mean here. Well return is the way we indicate that's how we're passing the output back out. So return something, is the same as, this is the output of the black box. So that's how you do it. Here's another way to implement it. We could just return x times y. x is a float. y is a float. So x times y is also a float. We don't even need to create another variable. So that's a different way to implement the exact same black box.
Now take a moment, pause the video again, and try and define add two ints, which is the other function that we talked about a moment ago. Again here, I've put the function declaration, and so the semicolon, and an open curly brace and a closed curly brace to indicate where we will fill in the contents of add two ints, so that we define the particular behavior inside the black box. So pause the video. And take as much time as you need to try and define an implementation of add two ints, such that when the function outputs a value, it does, in fact, return the sum of the two inputs. So just like the previous example, there are several different ways that you could implement add two ints. Here's one. In here in orange I've just had some comments-- I've just added some comments to indicate what's happening on each line of code. So I declare a variable called sum of type int. I say sum equals a plus b. That's where we're actually doing the work adding a and b together. And I return sum. And that makes sense because sum is a variable of type int. And what's the data type that this function tells me it's going to output? Int. So I'm returning sum, which is an integer variable. And that makes sense given what we've declared and defined our function to do.
Now you can also define the function this way, int sum equals a plus b-- skip that first step-- and then, return sum. Now you could have also implemented it this way, which I highly do not recommend. This is bad style for one thing and really bad design, but it does, in fact, work. If you take this code, which is int add bad adder dot c, and use it. It actually does add two integers together. It's a very poor implementation of this particular behavior. But it does work. It's just here to illustrate the point that we don't really care what happens inside the black box, as long as it has the output that we expect. This is a poorly designed black box. But at the end the day, it does still output the sum of a plus b. All right. So we've declared functions. And we've defined function. So that's really good. Now let's start to use the functions that we've declared and we've defined. To call a function-- it's actually pretty easy-- all you need to do is pass it appropriate arguments, arguments of the data type that it expects, and then assign the return value of that function and this-- excuse me-- assign the return value of that function to something of the correct type.
So let's have a look at this in practice in a file called adder 1 dot c, which I have in my cs50 IDE. So here is adder 1 dot c. At the beginning you see I have my includes, pound include, standard IO, and cs50 dot h. And then I have my function declaration. This is where I'm telling the compiler I'm going to be writing a function called add two ints. It's going to output an integer type variable. That's what this part is right here. And then I have two inputs to it a and b, each of which is an integer. Inside of main, I ask the user for input by saying, give me an integer. And they are prompted to forget int, which is a function that is included in the cs50 library. And that gets stored in x, an integer variable.
Then we prompt them for another integer. We get another integer and store that in y. And then, here on line 28, is where we make our function call. We are saying, int z equals add 2 ints x comma y. Do you see why this makes sense? x is an integer type variable and y is an integer type variable. So that's good. That make sense with what our function declaration on line 17 looks like. The comma separated input list expects two integers, a and b. In that case, we can call them whatever we want. It just expects two integers. And x is an integer and y is an integer. That works.
And we know that function is going to output an integers as well. And so we are storing the output of the function, add two ints, in an integer type variable, which we're calling z. And then we can say, the sum of percent i and percent i is percent i. x, y and z respectively filling in those percent i's. What is the definition of add two ints look like? It's pretty simple. It's one of the ones we just saw a second ago, int sum equals a plus b return sum. Does this work? Let's save the file. And then down here on my terminal I'm going to make adder 1, and I clear my screen. I'm going to zoom in because I know it's a little difficult to see.
So we compile this program as adder 1. So we can do dot slash adder 1. Give me an integer, 10. Give me another integer, 20. The sum of 10 and 20 is 30. So we made a successful function call. You can run the function again, negative 10, 17 sum of negative 10 and 17 is 7. This function works. It has the behavior that we expect it to. And so we've made a successful function, definition, declaration, and a successful function call. Couple miscellaneous points about functions before we conclude this section. Recall from our discussion of data types, previously, that functions can sometimes take no inputs. If that's the case, we declare the function as having a void argument list. Do you recall what the most common function we've seen so far that takes a void argument list is? It's main. Recall also that function sometimes don't actually have an output. In that case, we declare the function as having a void return type. Let's conclude this section by tackling a practice problem.
So here's the problem laid out. I want you to write a function called valid triangle. What this function should do is take three real numbers that represent the lengths of the three sides of a triangle as its parameters, or its arguments, or its inputs-- another set of synonyms that you might encounter. This function should either output true or false depending on whether those three lengths are capable of making a triangle. Do you remember the data type that we used to indicate true or false? Now how do you implement this? Well know there are a couple of rules regarding triangles that are actually useful to know. A triangle can only have sides with positive length. That makes sense. You're probably saying, duh. The other thing to note though, is that the sum of the lengths of any two sides of the triangle has to be greater than the length of the third side. That's actually true. You can't have a triangle of sides 1, 2 and 4, for example, because 1 plus 2 is not greater than 4. So those are the rules that determine whether or not the three inputs can conceivably form a triangle. So take a couple of minutes and declare and then define this function called valid triangle, such that it actually has the behavior specified here.
It will output true if those three sides are capable of comprising a triangle, and false otherwise Ready to see how you did? Here's one implementation of valid triangle. It's not the only one. Yours might vary slightly. But this one does, in fact, have the behavior that we expect. We declare our function at the very top, bool valid triangle float x float y float z. So again, this function takes three real numbers as its arguments, floating point value variables, and outputs a true or false value, which is a Boolean, recall. So that's why the return type is bool. Then we define the function. First thing we do is check to make sure that all of the sides are positive. If x is less than or equal to 0, or if y is equal to 0, or if z is less than or equal to 0, that can't possibly be a triangle. They don't have positive sides. And so we can return false in that situation. Next, we check to make sure that every pair of inputs is greater than the third one.
So if x plus y is less than or equal to z, or if x plus z is less than or equal to y, or if y plus z is less than or equal to x, that also can't be a valid triangle. So we return false again. Assuming we passed both of the checks though, then we can return true. Because those three sides are capable of returning-- of creating a valid triangle. And that's it. You've now declared and defined. And you may be able to now use and call this function. Great job. I'm Doug Lloyd. This is cs50.