Functions

Table of Contents


c2_u09_Functions_image.png

1. Program Structure

z_errorinmain.png

As programs become more sophisticated and offer more features, the size of the program increases. At some point, it becomes too complicated to keep your entire program just within main().

(You could certainly do it, but maintaining it would be a nightmare!)

One of the tools we use to build modular, easier-to-read code is functions. By using functions, we can delegate tasks out to other portions of the program, passing inputs as data to the function, and receiving some kind of output as a result.

As a basic example, let's say we need to calculate square footage of a room in multiple places in a program. By making a float GetArea( float width, float length ) function.

Then, we only have to implement the formula once and can use the function in every part of the program. Then, we wouldn't be copy-and-pasting the same formula over and over again in the code - which also means less likelihood of errors, and easier to update later as needed.

Program flow and functions

Whenever a function is called, the program flow is redirected into that function, running from top-to-bottom as normal (with branching and loops changing that flow). Once the function has completed, it can return some data, which is received by the call location.

From a design standpoint, this means we can break out different parts of programs into their own sections. Each function has a name, which should be used to label what the purpose of its code is.

c2_u09_Functions_functioncall.png

c2_u09_Functions_programbreakout.png


2. Function Basics

2.1. Uses of functions

Functions for formulas

In algebra, you've seen functions like this:

\[ f(x) = 2x + 3 \]

If you were like me in algebra, you might have thought to yourself, "That's the same as \(y = 2x + 3\), why did we replace \(y\) with \(f(x)\)?"

The reason is that we want to write a function in terms of its input, \(x\), and its output \(f(x)\). The equation \(2x + 3\) is the function body, which specifies how we get some output given some input.

We can use functions in programming like with math as well - defining formulas that we might need a lot - but there's more to functions than just making computations.

Let's say we want to make our "GetArea" function in math. We would need two inputs: Width and Length, and the output would end up being the area, so maybe we would name the function \(A\). It might look like this:

\[ A( w, l ) = w \cdot l \]

In C++, it would look like this:

float Area( float width, float length )
{
return width * length;
}

(Luckily we don't have to restrict ourselves to single-letter variable and function names in C++ :)


Functions to validate

Functions can also be handy in validating data and user input, putting the validation in one place instead of having to re-implement the same checks over and over.

For example, let's say we want to keep some variable percentage in our program betwee 0\% and 100\% - no negative values, nothing over 100\%. We could implement a function that takes the percentage as an input, and either returns the same percentage, or 0\%, or 100\% as output

int BoundPercent( int originalPercent )
{
if ( originalPercent < 0 )
{
    return 0;
}
else if ( originalPercent > 100 )
{
    return 100;
}
else
{
    return originalPercent;
}
}

Then, anywhere in the program, we could use this function to make sure our percentages are in the right range…

// in main()
hungerPercent       = BoundPercent( hungerPercent );
healthPercent       = BoundPercent( healthPercent );
happinessPercent    = BoundPercent( happinessPercent );

z_nocomp.png


Functions to get input

Getting user input is a common part of writing software, and we will usually need to validate what the user is entering prior to doing any operations on it. Let's say your program is full of numbered menus and you want to validate the user's menu choices easily. You can use a function with a while loop in it:

int GetUserInput( int min, int max )
{
int choice;
cout << "Choice: ";
cin >> choice;

while ( choice < min || choice > max )
{
    cout << "Invalid choice, try again: ";
    cin >> choice;
}

return choice;
}

You write this function once and then you can reuse it in your entire program for all menus…

cout << "1. Deposit money" << endl
 << "2. Withdraw money" << endl
 << "3. View balance" << endl;

choice = GetUserInput( 1, 3 ); // 1, 2, or 3

if ( choice == 1 )
{
cout << "1. Checking account" << endl
     << "2. Savings account" << endl;

choice = GetUserInput( 1, 2 ); // 1 or 2
}

Functions to output

Functions aren't required to return data. Sometimes, you just want a function that is responsible for formatting output or displaying a menu. In this case, a function's return type can be void.

void DisplayStudentInfo( string name, float gpa )
{
cout << "Name:   " << name
     << "GPA:    " << gpa << endl;
}

You also aren't required to pass input to functions. In this case, the parameter list between ( ) remains empty, but the () is always required for functions:

void DisplayMainMenu()
{
cout << "1. Deposit" << endl;
cout << "2. Withdraw" << endl;
cout << "3. View Balance" << endl;
cout << "4. Log Out" << endl;
}

Other uses

These are just some examples of why you might use functions in your programs. Anything you write in main() can go inside of a different function as well - it's just another tool for designing clean, maintanable, and readable code.

z_badcode.png


2.2. Anatomy of a function

Function Declaration

float GetArea( float width, float height );

Before you start using a function, you have to declare and define it… A function declaration is similar to a variable declaration - we tell the program "hey, we want to use this function, here's its name and some info about it." Declaration statements end with a semi-colon and don't contain a code block (the function body) because it's just a declaration.


Function Definition

float GetArea( float width, float height )
{
  return width * height;
}

The function definition is where we actually write what the function does. It includes the same information as the function declaration, but we include the function body in a code block.


Function Call

float room1Sqft = GetArea( 5, 10 );
float room2Sqft = GetArea( room2width, room2length );

After a function is defined, we can then call that function to execute its internal code. If the function has input parameters, we can pass in literal values (hard-coded data) or variables to provide that input data.


Function Header: The function header is the first line of a function, which includes the following information: return type, function name, and parameter list.

RETURNTYPE FUNCTIONNAME( PARAM1TYPE PARAM1NAME, ... )
  • Function Return Type: The return type of a function specifies what kind of data is returned from this function. The return type can be any data type, or it can be void if nothing is going to be returned.
  • Function Name: The function name should describe what the responsibility of the function is - what it does.
  • Function Parameters: The parameters of a function are a list of input variables that are expected to be passed into the function from elseware. The parameter list is located between the ( and ) in the function header.

Function Body

The function body is the code block, written between { and }, defining what logic the function performs.


Calling a function

Once the function has been defined, you can call it from anywhere in your program…

int main()
{
  float width, height;
  cout << "Enter width and height: ";
  cin >> width >> height;

  // Call GetArea
  float area = GetArea( width, height );

  cout << "Area: " << area << endl;
}

Function Call: Calling a function requires the function name, and passing in a series of inputs that become the function's parameters. In the example above, the GetArea function is called, with the values from the width and height variables being passed in. Once GetArea returns its output, that output is then stored in the area variable.

Arguments: An argument is the name of the value or variables being passed into the function during the function call. These arguments become the values that the function parameters within the function definition uses.

Here in the function call, 10 and 20 are the arguments:

// Call GetArea
float area = GetArea( 10, 20 );

So the values of 10 and 20 get used as the width and length parameters' values:

float GetArea( float width, float height )
{
  return width * height;
}

The arguments of a function call can be hard-coded values, like 10 and 20 above, or you can pass in other variables as arguments. Then, whatever is stored in those variables is copied over to the parameters.

// Call GetArea twice
float area1 = GetArea( room1Width, room1Length );
float area2 = GetArea( room2Width, room2Length );

The arguments passed in do not need to share a name with the parameters; these are not the same variables. They're only sharing the data stored within them.

The first time GetArea is called, whatever is stored within room1Width is copied from that variable and stored in the parameter variable width within the function's definition.

Common function errors:

  1. When you're declaring/defining a function you specify data types - the return type and the parameters' data types. When you're calling a function, you do not include data types!
    • YES: sqft = GetArea( room_width, room_length );
    • NO: sqft = GetArea( float room_width, float room_length );
  2. Functions must have parentheses ()! If you're missing the parentheses, your program isn't going to call the function!
    • YES: DisplayMenu();
    • NO: DisplayMenu;
  3. If a function contains a return statement, then you must store the returned data in a variable! If you don't assign the return anywhere, it will be lost!
    • YES: sqft = GetArea( room_width, room_length );
    • NO: GetArea( room_width, room_length );

3. Variable scope

Scope refers to the location in the code where a variable exists.

main():
If you declare a variable at the top of the main() function, not inside any if statements or loops, then that variable is in scope for the entire duration of main(), starting with the line it was declared on.
int main()
{
  int a;
  // ...etc...
}

Variables declared within if statements and loops:
Variables declared within the code block of an if statement or a loop only exists within that code block.
if ( a == 3 )
  {
    int b;  // only exists within this block
  }

for ( int i = 0; i < 10; i++ )
  {
    cout << i; // only exists within this block
  }

If you try to use these variables somewhere below the code block, your compiler will give an error, stating that the variable does not exist in that scope. Once the program leaves the code block, the variable is out of scope.


Functions:
Remember that main() is a function, just like any other functions we write in our program. Any variables declared within a function are local to that function, and accessible anywhere from the variable's declaration until the end of the function.
int GetChoice()
{
  int choice; // local variable
  cout << "Choice: ";
  cin >> choice;
  return choice;
}

Parameters:
Variables declared in the parameter list of a function are also local to that function and can be used anywhere within the function.
int Sum( int a, int b, int c )
{
// a, b, and c are local to this function.
return a + b + c;
}

Same names?
The same name can be reused for different variables in different scopes. Even if they share the same name, they are not related in any way.
int GetChoice()
{
  int choice;     // Variable A
  cout << "Choice: ";
  cin >> choice;
  return choice;
}

int main()
{
  int choice;     // Variable B
  choice = GetChoice();
}

4. Parameters and arguments

4.1. Pass-by-value

When we have a function declared with a parameter…

void Example( int someNumber )
{
  cout << someNumber;
}

…and we call that function elseware, passing in another variable as an argument…

int main()
{
  int myNumber = 2;
  Example( myNumber );
  return 0;
}

… What happens is that the value of the variable myNumber is copied and passed to the function parameter someNumber. This works the same as if you simply pass in Example( 10 ), passing in a hard-coded value instead of using a variable.

If you wrote the function to change the value of its parameter, that change would only be reflected within the function and would not affect the original argument passed as part of the function call.

void Example( int someNumber )
{
  cout << "Example begin: " << someNumber << endl;
  someNumber = 100;
  cout << "Example end: " << someNumber << endl;
}

int main()
{
  int myNumber = 2;

  Example( myNumber );

  cout << "main end: " << myNumber << endl;

  return 0;
}

The output of this program would be:

Example begin: 2
Example end: 100
main end: 2

Even though the value of someNumber from the Example function changes (which is valid), that change doesn't affect myNumber within main(), because only the value was copied over.

This is known as pass-by-value.


4.2. Pass-by-reference

If we wanted to change the value of an argument variable within a function, we'd have to change the parameter to pass-by-reference. To do this, we use the symbol = &= in the parameter's declaration, after the data type and before the variable name.

void Example( int& someNumber )
{
  cout << "Example begin: " << someNumber << endl;
  someNumber = 100;
  cout << "Example end: " << someNumber << endl;
}

The ampersand symbol can go next to the data type (int & blah), next to the variable name (int &blah), or separate from both (int & blah).

Once we've made the parameter a reference, then when the function is called, the argument is not copied - a reference to that variable is passed to the function. Any changes to the reference parameter in the function also affects the original argument variable.

int main()
{
  int myNumber = 2;

  // Calling it looks the same as before
  Example( myNumber );

  cout << "main end: " << myNumber << endl;

  return 0;
}

The output of this program would be:

Example begin: 2
Example end: 100
main end: 100

Pass-by-reference instead of return
In some cases, you may need to return multiple pieces of data from a function - however, you can only return one item from a function with the return statement (in C++). One option is to set the information you want "returned" as pass-by-reference parameters of the function.
void DoubleTheseNumbers( int & a, int & b, int & c )
{
  a *= 2;
  b *= 2;
  c *= 2;
}

Pass-by-reference of large things

We haven't gone over arrays or classes/objects yet, but another reason we might want to pass something by-reference instead of by-value is when the parameter is big (which can be the case with arrays and objects).

If we have a large object we need to pass to a function, doing a copy of the entire thing is inefficient - it is much simpler and faster to pass the large object by-reference.

z_copyparameter.png


4.3. Default parameters

When declaring a function, you can also set default parameters. These are the default values assigned to the parameters if the user doesn't pass anything in. The default parameters are only specified in a function declaration - NOT the definition!

In this example, it could be a function that displays ingredients for a recipe, and by default the batch is set to 1.0 (one batch).

void OutputIngredients( float eggs, float sugar, float flour, float batch = 1.0 );

The function could be called without passing in a batch:

cout << "Ingredients:" << endl;
OutputIngredients( 1, 2.0, 3.5 );

Or they could pass a batch amount explicitly:

cout << "Ingredients:" << endl;
OutputIngredients( 1, 2.0, 3.5, 0.5 );  // half batch

You can have multiple default parameters specified in your function declaration - but all variables with default values must go after any variables without default values.


4.4. Summary: Ways to pass data to/from functions

The following table illustrates different ways we can define our parameters, and what the goal is. "RT" means "Return Type", "T" is the "Type" of the parameter.

# Function Read Return Info
1. RT func( T X ) yes no Pass-by-value X
2. RT func( const T& X ) yes no Const pass-by-reference X
3. RT func( T& X ) yes yes Pass-by-reference X
  1. X is pass-by-value, which is fine for primitive data types like ints, floats, chars, and bools.
  2. X is passed by const-reference because X is a mose sophisticated data type like a string or other class-based object. (Longer time to copy if passed by value.)
  3. The function can read from X but also overwrite its value and the change will be reflected back to the argument being passed to the function call.

5. Function Overloading

In C++, you can also write multiple functions that have the same name, but a different parameter list. This is known as function overloading.

Let's say you want to be able to sum numbers, and you make a version for floats and a version for integers:

int Sum( int a, int b )
{
  return a + b;
}

float Sum( float a, float b )
{
  return a + b;
}

You can write as many versions of the function as you like, so long as the function headers are uniquely identifiable to the compiler, which means:

  • The functions have a different amount of parameters, or
  • The data types of the parameters are different, or
  • The parameters are in a different order (when mixing data types).

These will become much more useful once we cover classes and objects.


Author: Rachel Wil Sha Singh

Created: 2023-10-23 Mon 15:27

Validate