Skip to content

Latest commit

 

History

History
190 lines (125 loc) · 9.52 KB

File metadata and controls

190 lines (125 loc) · 9.52 KB

More With Functions

In the last article, we started using functions that were provided by a library - stdio.h. In this article, we're going to create our own functions, and see what it takes to make a program that does something practical.

Previously ...

In my first article, I showed you some assembly code. It looked like this:

  push rbp
  mov rbp, rsp
  movss xmm0, DWORD PTR .LC0[rip]
  pop rbp
  ret
.LC0:
  .long 1078523331

That example is incredibly hard to read, let alone write. Especially considering all that program does is give us a rough approximation of Pi: 3.14.

Here's what that code actually looks like, in C, as a function:

// Return a constant representing the value of Pi
float calcPi()
{
    return 3.14f;
}

We've talked about functions before, when talking about main and printf. But this is our code now. This actually does something we have control over. So let's break it down; this should be a bit of a review as well.

The first line in the example is a 'comment'. Remember, comments never actually get put into our program. It's only there to help us remember what we were doing, or to communicate our thoughts to others.

Next, we declare a function called calcPi. The function calcPi then has a body that actually does the work of this function. It's a good design strategy when writing functions that they only do one thing, and one thing well. Let's break the 'function signature' down:

Function Return Value Function Name Function Arguments
float calcPi ()

That function signature states that for the function calcPi it returns a floating point value and takes no parameters.

float and return are C keywords. Keywords are predefined words in the C++ language that have special meanings. They help define the language syntax; those rules that define a set of rules that define the combination of symbols and keywords that are considered to be a correctly formatted and gramatically correct program. What we're doing is defining the syntax of our function using these keywords.

To continue, a float is what's called a 'data type' (also referred to as just a 'type'). It defines the rules that the data must follow. For example, a float is a real number that can have a negative or a positive sign, a whole part and a decimal part.

     sign whole decimal
        +     3.1415926

However, a float cannot be a string; you can't use "My dog is beautiful" as a floating point number.

There are a number of data types defined by the C/C++ language and I'll introduce them as we go. Once we have a few under our belt, I'll list out all the inherent data types.

The return part of the body of the function is directly related to that float in the 'function signature'. Thus the return keyword will return back to whatever called this function a value, in this case 3.14 ... f.

Using an f at the end of that number tells C/C++ that the constant value of 3.14 is a floating point number; It's the right data for a return value that should be a float.

Another example

The calcPi function has a return value. But what if I wanted to have the function work on something. For example,let's say I wanted to double a given floating point number? Then I would need to pass in some arguements into the function:

float multBy2(float value)
{
    return value * 2.0f;
}

The syntax of this function should look similar to the previous example, but with one exception - in between the round brackets (parenthesis) we have float value. This is the 'argument' of the function.

Function Return Value Function Name Function Arguments
float multBy2 (float value)

We've seen the float keyword already - it defines a data type, but now we have this additional word, value. This is what's called a 'variable name' (or just 'variable'). Much like in math, thinking to your linear algebra classes, a variable does not have a fixed, or constant value.

In the case of the multBy2 function, it returns a float but also takes in a single float as it's arguement. Functions can have many arguments; you separate each argument with a comma (,) and describe the data type as well as a unique variable name.

It then takes that variable and multiplies it by 2 (2.0f in this case). And then returns it to whatever called this function.

A lot to unpack in just that one line of code. Let's start:

  1. C++ defines core mathematical operations as such: +, -, *, /, %, (, ). These are called 'operators'. In order, that's Addition, Subtraction, Multiplication, Division and Parenthesis.
  2. Mathematical order of operations is grouped first by parenthesis, then multiplication and division, and finally addition/subtraction. There is no inherent order of operations defined in C++; it's not like prefix or postfix math. Any order of operations is inherently defined by the operator iteself. For example, the + operator processes from left to right. Source: cppreference
  3. The return keyword will evaluate everything to it's right before returning. Thus we can assume that everything to the right will get computed. However, if you want to resolve any ambiguity, you can place parenthesis around the operations: return (value * 2.0f);. This is purely for asthetics as it does nothing to the compiled code. (There is one exception, but I'm not going to cover it here as it's a fairly advanced issue).

How do we invoke a function? Glad you asked. Invoking multBy2 would look like this:

float result = multBy2(7.0f);

After that line is executed, we would have a new variable, result that would contain a floating point value of 14.0f.

I also want to point out that you can also do this:

multBy2(7.0f);

That's perfectly legal in C/C++. However it's completely ineffective; it doesn't result in us actually doing any meaningful work. But the program will happily do it anyway.

How would we use that for real? I mean, the entire point of this article is to see a program in action! We're going to use a web app called C++ shell to muck around with coding.

I've placed all of the above code into a program here (As well as a backup here).

// Example program
#include <stdio.h>

float calcPi()
{
    return 3.14f;
}

float multBy2(float value)
{
    return value * 2.0f;
}

int main()
{
    float doubled7 = multBy2(7.0f);
    float pi = calcPi();

    printf("variable: doubled7: %f\n", doubled7);
    printf("variable: pi: %f\n", pi);
}

Let's break down the main function line by line:

    float doubled7 = multBy2(7.0f);

On this line we create a new variable doubled7 and initialize it with the output of the function multBy2. From this point onward, in the main function, we will be able to access the variable doubled7. We could not have used doubled7 before that line!

    float pi = calcPi();

This is the fundamentally the same as the previous line. We create a new variable pi that is generated by the funtion calcPi.

    printf("variable: doubled7: %f\n", doubled7);

Remember, printf is a function that we haven't defined ourself. It's part of a library. We reference that library with an #include directive, which is done on the first line:

#include <stdio.h>

As you can see, the printf function takes more than one arguement. In the earlier articles, we didn't use any additional arguements! Is that legal? It is, for the printf function, because of the way it was declared. We won't go into that now, but it would be worth covering in a future article, if there's interest.

We kind of glossed over what printf does. But we should be crystal clear on how it actually is intended to work The printf function prints a formatted string to the console (printf actually stands for 'print formatted'. There are a set of formatting specifiers that you can encode into the string that will allow you to do in-place substitution of the value of a variable into the string. We've seen this previously with the \n escape character, but now we're seeing some additional formatting specifiers. In this case, using a % symbol in the formatting string indicates that we are going to replace what follows with the next arguement in the call to printf. The f character that follows the % tells printf to expect a float variable.

When the example program runs, we have the output string that looks like this: variable: doubled7: 14.000000

What else can we do with formatting specifiers? Well, we can shorten the length of that float (to 2 decimal places), enforce numbers to have a +/- sign, numerical padding (adding 0's to the left of the whole number part of a float) as well as a bunch of other things.

    printf("variable: pi: %f\n", pi);

I think you can see what this is doing.

Output

When you actually execute the code, you end up with the following output:

variable: doubled7: 14.000000
variable: pi: 3.140000

Summary

We've now seen more details on creating your own functions. We've really only scratched the surface there and we'll return to it later. In our next article, we're going to cover some very important concepts - program flow control.

You can head back to the main article page here and continue on with the next article.

Stay tuned!

-Ash