Functions
A
function is a reusable portion of a program, sometimes called
a
procedure or
subroutine.
- Like a mini-program (or subprogram) in its own right
- Can take in special inputs (arguments)
- Can produce an answer value (return value)
- Similar to the idea of a function in mathematics
Why write and use functions?
- Divide-and-conquer
- Can breaking up programs and algorithms into smaller,
more manageable pieces
- This makes for easier writing, testing, and debugging
- Also easier to break up the work for team development
- Reusability
- Functions can be called to do their tasks anywhere in a program, as
many times as needed
- Avoids repetition of code in a program
- Functions can be placed into libraries to be used by more than one
"program"
With functions, there are 2 major points of view
- Builder of the function -- responsible for creating the
declaration and the definition of the function
(i.e. how it works)
- Caller -- somebody (i.e. some portion of code) that
uses the function to perform a task
Using Functions
- The user of a function is the caller.
- Use a function by making calls to the function with real data, and
getting back real answers.
- Consider a typical function from mathematics:
f(x) = 2x + 5
In mathematics, the symbol 'x' is a placeholder, and when you run the
function for a value, you "plug in" the value in place of x. Consider the
following equation, which we then simplify:
y = f(10) // must evaluate f(10)
y = 2 * 10 + 5 // plug in 10 for x
y = 20 + 5
y = 25 // so f(10) gives the answer 25
In programming, we would say that the call f(10) returns
the value 25.
- C++ functions work in largely the same way. Format of a C++ function
call:
functionName(argumentList)
where the argumentList is a comma-separated list of arguments (data being
sent into the function). Use the call anywhere that the returned answer
would make sense.
- Example. There is a pre-defined math function called sqrt,
which takes one input value (of type double) and returns its
square root. Sample calls:
double x = 9.0, y = 16.0, z;
z = sqrt(36.0); // sqrt returns 6.0 (gets stored in z)
z = sqrt(x); // sqrt returns 3.0 (gets stored in z)
z = sqrt(x + y); // sqrt returns 5.0 (gets stored in z)
cout << sqrt(100.0); // sqrt returns 10.0, which gets printed
cout << sqrt(49); // because of automatic type conversion rules
// we can send an int where a double is expected
// this call returns 7.0
// in this last one, sqrt(625.0) returns 25.0, which gets sent as the
// argument to the outer sqrt call. This one returns 5.0, which gets
// printed
cout << sqrt(sqrt(625.0));
this code here:
// Online Taleem
//
// Simple set of function call examples using the cmath library function
// sqrt()
#include
#include
using namespace std;
int main()
{
double x = 9.0, y = 16.0, z;
cout.setf(ios::fixed);
cout.precision(1);
z = sqrt(36.0); // sqrt returns 6.0 (gets stored in z)
cout << "z = " << z << '\n';
z = sqrt(x); // sqrt returns 3.0 (gets stored in z)
cout << "z = " << z << '\n';
z = sqrt(x + y); // sqrt returns 5.0 (gets stored in z)
cout << "z = " << z << '\n';
// sqrt returns 10.0, which gets printed
cout << "Square root of 100: " << sqrt(100.0) << '\n';
// because of automatic type conversion rules we can send an int where a
// double is expected. This call returns 7.0
cout << "sqrt(49) returns " << sqrt(49) << '\n';
// in this last one, sqrt(625.0) returns 25.0, which gets sent as the
// argument to the outer sqrt call. This one returns 5.0, which gets
// printed
cout << "sqrt(sqrt(625.0)) = " << sqrt(sqrt(625.0)) << '\n';
return 0;
}
- In keeping with the "declare before use" policy, a function call can
be made ONLY if a declaration (or definition) of the function has been
seen by the compiler first.
- This can be done by placing a declaration above the call
- This is handled in libraries by including the header file for
the library with a #include directive
Predefined Functions
Building Functions
The
builder of a function (a programmer) is responsible for the
declaration (also known as
prototype) and the
definition.
Declaring a Function
- A function declaration, or prototype, specifies three
things:
- the function name -- usual naming rules for user-created
identifiers
- the return type -- the type of the value that the function
will return (i.e. the answer sent back)
- the parameter list -- a comma separated list of parameters that the
function expects to receive (as arguments)
- every parameter slot must list a type (this is the type
of data to be sent in when the function is called)
- parameter names can be listed (but optional on a declaration)
- parameters are listed in the order they are expected
- Declaration Format:
return-type function-name( parameter-list );
- Examples:
// GOOD function prototypes
int Sum(int x, int y, int z);
double Average (double a, double b, double c);
bool InOrder(int x, int y, int z);
int DoTask(double a, char letter, int num);
double Average (double, double, double); // Note: no parameter names here
// okay on a declaration
// BAD prototypes (i.e. illegal)
double Average(double x, y, z); // Each parameter must list a type
PrintData(int x); // missing return type
int Calculate(int) // missing semicolon
int double Task(int x); // only one return type allowed!
Defining a Function
- a function definition repeats the declaration as a header (without the
semi-colon), and then adds to it a function body enclosed in a block
- The function body is actual code that is implemented when the
function is called.
- In a definition, the parameter list must include the parameter
names, since they will be used in the function body. These are
the formal parameters
- Definition Format:
return-type function-name( parameter-list )
{
function-body (declarations and statements)
}
- To send the return value out, use the keyword return, followed
by an expression that matches the expected return type
return expression;
- Definition Examples:
int Sum(int x, int y, int z)
// add the three parameters together and return the result
{
int answer;
answer = x + y + z;
return answer;
}
double Average (double a, double b, double c)
// add the parameters, divide by 3, and return the result
{
return (a + b + c) / 3.0;
}
- More than one return statement may appear in a function definition,
but the first one to execute will force immediate exit from the function.
bool InOrder(int x, int y, int z)
// answers yes/no to the question "are these parameters in order,
// smallest to largest?" Returns true for yes, false for no.
{
if (x <= y && y <= z)
return true;
else
return false;
}
- Sample code using the functions above:
- Example 1 -- this file uses a layout that works, but is not as desirable
#include
using namespace std;
double Average(double, double, double);
int i1, i2, i3; // global variables
double x; // global variable (bad idea)
int main()
{
double a, b, c; // local variables to main()
double x, y, z; // local variables to main()
// NOT the same x, y, z as in the Average function
// and NOT the same x as the global variable x
a = 1.3; b = 2.5; c = 6.8;
// arguments in a function call do NOT have to have the same
// names as formal function parameters
x = Average(a, b, c); // local x (to main()) being used here
cout << "average = " << x << '\n';
return 0;
}
double Average(double x, double y, double z)
// the variables x, y, and z are LOCAL variables to this function
// NOT the same as the global x.
{
double total; // another LOCAL variable
total = x + y + z; // local x being used here
return (total / 3.0);
}
- Example 2 -- this file
illustrates a better layout for satisfying declare-before-use
// Online Taleem -- functions2.cpp
//
// Example illustrating some function declarations, definitions, calls
//
// Better layout than functions1.cpp. This one has prototypes at the top
#include
using namespace std;
// function PROTOTYPES listed here.
int Sum(int x, int y, int z);
double Average (double a, double b, double c);
bool InOrder(int x, int y, int z);
// The prototypes above satisfy declare-before-use, so these functions can
// be CALLED anywhere below this point.
int main()
{
int i1, i2, i3;
double d1, d2, d3;
cout << "Input three integers: ";
cin >> i1 >> i2 >> i3;
cout << "Input three doubles: ";
cin >> d1 >> d2 >> d3;
// samples of function calls
double avg;
int total;
total = Sum(i1, i2, i3);
avg = Average(d1, d2, d3);
cout << "The sum of the three integers = " << total << '\n';
cout << "The average of the three doubles = " << avg << '\n';
// note that we can place the call in the cout statements, which
// prints the return values
cout << "The sum of 10, 14, and 18 = " << Sum(10, 14, 18) << '\n';
cout << "The average of 1.3, 2.7, and 6.9 = "
<< Average(1.3, 2.7, 6.9) << '\n';
// Notice that we can pass in integers where doubles are expected
// legal via automatic-type-conversion rules
cout << "The average of the three integers = "
<< Average(i1, i2, i3) << '\n';
// Testing out the boolean function (InOrder)
if (InOrder(i1, i2, i3))
cout << "The three integers were typed in ascending order\n";
else
cout << "The three integers were NOT typed in ascending order\n";
return 0;
}
// function definitions can appear in any order, since the prototypes
// were listed at the top to handle declare-before-use
int Sum(int x, int y, int z)
// add the three parameters together and return the result
{
int answer;
answer = x + y + z;
return answer;
}
double Average (double a, double b, double c)
// add the parameters, divide by 3, and return the result
{
return (a + b + c) / 3.0;
}
bool InOrder(int x, int y, int z)
// answers yes/no to the question "are these parameters in order,
// smallest to largest?" Returns true for yes, false for no.
// (This kind of function often known as a "predicate function")
{
if (x <= y && y <= z)
return true;
else
return false;
}
Scope of Identifiers
- The scope of an identifier (i.e. variable) is the portion of the code
where it is valid and usable
- A global variable is declared outside of any blocks, usually at the
top of a file, and is usable anywhere in the file from its point of
declaration.
- "When in doubt, make it global" == BAD PROGRAMMING
PRACTICE
- Best to avoid global variables (except for constants, enumerations.
Sometimes)
- Function names usually global. (prototypes placed at the top of a
file, outside any blocks)
- A variable declared within a block (i.e. a compound statement) of
normal executable code has scope only within that block.
- Includes function bodies
- Includes other blocks nested inside functions (like loops,
if-statements, etc)
- Does not include some special uses of block notation to be seen
later (like the declaration of a class -- which will have a separate
scope issue)
- Variables declared in the formal parameter list of a function
definition have scope only within that function.
- These are considered local variables to the function.
- Variables declared completely inside the function body (i.e. the
block) are also local variables
void functions and empty parameter lists
Functions and the compiler
- The reason for the declare-before-use rule is that the compiler has to
check all function CALLS to make sure they match the expectations.
- the "expectations" are all listed in a function declaration
- function name must match
- arguments passed in a call must match expected types and order
- returned value must not be used illegally
- Decisions about parameters and returns are based on type-checking.
- legal automatic type conversions apply when passing arguments into
a funcion, and when checking what is returned against the expected
return type
No comments:
Post a Comment