Functions
- In C++ and most modern programming languages, we may put statements into functions to be invoked in the future.
- Also known as procedures in some languages.
- Why functions
- We need modules instead of a huge main function.
- Easier to divide the works: modularization.
- Easier to debug: mantenance.
- Easier to maintain consistency.
- We need something that can be used repeatedly.
- Enhance resuability
Structure of functions
-
In C++, a function is composed of a header and body.
-
A header for declaration:
- A function name (identifier).
- A list of input parameters.
- A return value.
-
A body for definition:
- Statements that define the task.
-
There are two types of functions.
- System-defined functions.
- User-defined functions.
-
System-defined functions are defined in the C++ standard library.
- To include the definition, use
#include
. <iostream>
,<iomanip>
,<cmath>
,<climits>
, etc.- Those from C are named by adding “c” as the initial.
- To include the definition, use
#include <iostream>
#include <cmath>
using namespace std;
int main():
{
int c = 0;
cin >> c;
cout << abs(c) << endl;
return 0;
}
- To study user-defined functions, lets start from an example.
#include <iostream>
using namespace std;
int add(int, int) // user-defined function declaration
int main()
{
int c = add(10, 20);
cout << c << endl;
return 0;
}
int add(int num1, int num2) // user-defined function definition
{
return num1 + num2;
}
- There is an
add()
function. - In the main function we invoke (call) the
add()
function. - Before the main function, there is a function
header/prototype
declaring the function. - After the main function, there is a function body defining the function.
Function declaration
- To implement a function, we first declare its prototype.
return type function name(parameter types);
- In a function prototype, we declare its appearance and input/output format.
int add(int, int);
- The name of the function follows the same rule for naming variable.
- A list(zero, one, or multiple) parameters.
- The parameters passed into the function with their types,
- We must declare their types. Declaring their names are optional.
- A return type indicates the type of the function return value.
- For a function declaration, the semicolon is required.
- Every type can be the return type:
- It may be “void” if the function returns nothing.
Creating a function
- Declare the function before using it.
- Typically after the preprocessors and before the main function.
- Then we need to define the function by writing the function body.
- Typically after the main function, though not required.
- In a function defintion, we need to sepecify parameters names.
- These parameters can be veiwed as variables declared inside the function
- They can be assessed only in the function.
Function defintion
- You have written one function: the main function
- Defining other functions can be done in the same way.
return type function name(parameters)
{
statements
}
- The first line, the function header, is almost identical to the prototype.
- The parameter names must be specified.
- Statements are then written for a special task.
- The keyword
return
terminates the function execution and returns a value.
Function invocation
- When a function is invoked in the main function, the program execution jumps to the function.
- After the function execution is complete, the program execution jumps back to the main function, exactly where the function is called.
- What if another function is called is called in a function?
Function declaration and definition
-
You may choose to define a function before the main function.
- In this case, the function prototype can be omited.
-
In any case, you must declare a function before you use it.
-
In some cases, function prototypes must be used.
void a() { // error! b(); } void b() { a(); } int main() { a(); b(); return 0; }
void a(); void b(); int main() { a(); b(); return 0; } void a() { // error! b(); } void b() { a(); }
-
Direct or indirect self-invocations are called recursion.
-
Using function prototypes also enhances communications and maintenance.
Function parameters vs. arguments
- When we invoke a function, we need to provide arguments.
- Parameters (also called formal parameters) are used inside the function.
- Arguments (also called actual parameters) are passed into the function.
- If a pair of parameter and argument are both variables, their names can be different.
int add(int num1, int num2) // num1 and num2 are formal parameters
{
return num1 + num2;
}
int main()
{
double q1 = 10.5;
double q2 = 20.7;
double c = add(q1, q2 - 3); // q1 and q2-3 are two arguments.
cout << c << "\n";
return 0;
}
Function arguments
- Function arguments can be:
- Literals,
- Variables,
- Constant variables.
- Expressions.
- If an argument’s type is different from the corresponding parameter’s type, compiler will try to cast it
Function return value
- We can return one or no value back to where we invoke the function.
- Using the
return
statement to return a value. - If you do not want to return anything, declare the function return type as
void
.- In this case, the
return
statement can be omited. - Or we may write
return;
. - Otherwise, having no
return
statement results in a compilation error.
- In this case, the
- There can be multiple
return
statement. - A function runs until the first
return
statement is met.- Or the end of the function for a function returning
void
.
- Or the end of the function for a function returning
- We need to ensure that at least one return will be executed.
int test(int);
int main()
{
cout << test(-1); // res: ?
return 0;
}
int test(int a)
{
if(a > 0)
return 5;
}
Coupling and decoupling
- what do these two functions do?
#include <iostream>
using namespace std;
int max(int a, int b);
int main()
{
int n = 0, m = 0;
cin >> n >> m;
cout << max(n, m);
return 0;
}
int max(int a, int b)
{
return (a > b) ? a : b;
}
#include <iostream>
using namespace std;
int max(int a, int b);
int main()
{
int n = 0, m = 0;
cin >> n >> m;
max(n, m);
return 0;
}
void max(int a, int b)
{
cout << (a > b) ? a : b;
}
// 耦合度比较高
- Which one to choose?
#include <iostream>
using namespace std;
int max(int a, int b);
int main()
{
int n = 0, m = 0, p = 0;
cin >> n >> m;
cout << max(max(n, m), p);
return 0;
}
int max(int a, int b)
{
return (a > b) ? a : b;
}
- General rule: Minimize the degree of coupling in your program.
- Decouple your program with appropriate functions.
Good programing style
- Name a function so that its purpose is clear.
- In a function, name a parameter so that its purposee is clear.
- Declare all functions with comments.
- Ideally, other programmer can understand what a function does without reading the definition.
- Declare all functions at the beginning of the program.
More about return value
Return statements in a void function
- In a void function, there is no need for a return statement.
- One may still add some if they help.
- Consider the following example:
- Given an input integer, write a program that prints out its digits, from the least significant to the most significant.
- Print out nothing if the input number is negative.
#include <iostream>
using namespace std;
void reversePrint(int i);
int main()
{
int i = 0;
cin >> i;
reversePrint(i);
return 0;
}
void reversePrint(int i)
{
if (i >= 0)
{
while (i > 0)
{
cout << i % 10;
i /= 10;
}
}
}
#include <iostream>
using namespace std;
void reversePrint(int i);
int main()
{
int i = 0;
cin >> i;
reversePrint(i);
return 0;
}
void reversePrint(int i)
{
if (i < 0)
return;
while (i > 0)
{
cout << i % 10;
i /= 10;
}
return;
}
- Adding return may improve readability and/or highlight some parts.
Operators also return values
- An operator can also be viewed as function.
- They take operand(s) as inputs, process them, and return a value.
- E.g.,
+
returns the sum of the two operands. - The return value of an operator can be very useful.
cin >>
andcout <<
also have return values. What are the return values?
Utilizing the return value of cin >>
- Consider this simple program:
- The user may keep entering values until she enters a negative number.
- The sum of all numbers except the negative one will be printed.
- The number of input values is unlimited.
#include <iostream>
using namespace std;
int main()
{
int sum = 0;
int i = 0;
cin >> i;
while (i >= 0)
{
sum += i;
cin >> i;
}
count << sum;
return 0;
}
- We may let our program read input from a file.
- The modified program is:
#include <iostream>
using namespace std;
int main()
{
int sum = 0;
int i = 0;
while (cin >> i)
{
sum += i;
}
count << sum;
return 0;
}
- The input stream returned by
cin >>
can be evaluated.- If nothing is read in this operation, evaluating the input stream will result in a false value.
- The loop can then be terminated.
- This works when we read input from a file.
- If the user enters numbers through a keyboard, the input stream will not end. This will of course fail.
#include <iostream>
using namespace std;
int main()
{
int sum = 0, i = 0;
istream& is = (cin >> i);
bool b = static_cast<bool>(is);
while (b)
{
sum += i;
b = (cin >> i);
}
return 0;
}
Scope of variables
- Four levels of variable lifetime (life scope) in C++ can be discuss now.
- Local, global, external, and static variables.
- more
Local variables
- A local variable is declared in a block.
- It lives from the declaration to the end of block.
- In the block, it will hide other variables with same name.
int main()
{
int i = 50; // it will be hidden.
for (int i = 0; i < 20; i++>)
{
cout << i << " "; // print 0 1 2 ... 19
}
cout << i << endl; // 50
return 0;
}
Global variables
- A global variable is declared outside any block (thus outside the main function).
- From declaration to the end of the program.
- It will be hidden by any local variable with the same name.
- To access a global variable, use the scope resolution operator
::
.
#include <iostream>
using namespace std;
int i = 5;
int main()
{
for (; i < 20; i++)
{
cout << i << " ";
}
cout << endl;
int i = 2;
cout << i << endl;
cout << ::i << endl;
return 0;
}
- There’s no difference in the way you declare a local or global variable. The Location matter.
- We may add
auto
to declare a local or global variable, but since it is default setting, almost no one adds this.
External variables
- In a large-scale system, many programs run together.
- If a program wants to access a variable defined in another program, it can be declare the variable with the keyword
extern
.extern int a;
a
must has been defined in another program.- There program must run together.
- You will not need this now…actually you should try to avoid it.
- It hurts modularization and makes the system hard to maintain.
- Though it still exists in some old systems.
- Note that global variables should also be avoided for same reason.
static variables
- Consider local and global variables again.
- A local variable will be recycled (its memory space will be released) immediately when its is “dead”.
- A global variable will not be recycled until the end of a program.
- A static variable, declared inside a block, also will not be recycled until the program terminates.
- Once a static variable is declared, the declaration statement will not be executed anymore even if it is encountered again.
- A static global variable cannot be declared as external in other programs.
int test();
int main()
{
for (int a = 0; a < 10; a++)
cout << test() << " ";
return 0;
}
int test()
{
int a = 0;
a++;
return a;
}
int test();
int main()
{
for (int a = 0; a < 10; a++)
cout << test() << " ";
return 0;
}
int test()
{
static int a = 0;
a++;
return a;
}
- One typical reason: To count the number of times that a function is invoked.
- Why not using a global variable for that?
Good programming style
- You hanve to distinguish between local and global variables
- The location of declarations matters.
- Always try to use local variables to replace global variables.
- Let functions communicate by passing values with each other. Do not let them communicate by reading from and writing into the same variables.
- One particular reason to use global variables is to define constants that are used by many functions
- You may not need static and external variables now or even in the future.
- But you need to know these things exist.
Variable initialization
- local variables are not initialized automatically.
- It is troublesome for a programmer to initialize (local) variables.
- In fact, the system initalizes global and static variables to 0. Why?
- Initalization takes time.
- There are too many local variables
- There are typically few global ans static variables. Efficiency matters.
Advances of functions
Call-by-value mechanism
void swap(int x, int y);
int main()
{
int a = 20, b = 10;
cout << a << " " << b << endl;
swap(a, b);
cout << a << " " << b << endl;
}
void swap(int x, int y)
{
int temp = x;
x = y;
y = temp;
}
void swap(int &x, int &y);
int main()
{
int a = 20, b = 10;
cout << a << " " << b << endl;
swap(a, b);
cout << a << " " << b << endl;
}
void swap(int &x, int &y)
{
int temp = x;
x = y;
y = temp;
}
-
The default way of invoking a function is the “call-by-value” (pass-by-value) mechanism.
-
When the function
swap()
(left one) is invoked:- First two new variables
x
andy
are created. - The values of
a
andb
are copied intox
andy
. - The values of
x
andy
are swaped. - The function ends,
x
andy
are destroyed, and memory spaces are released.
- First two new variables
-
The call-by-value mechanism is adopted so that:
- Functions can be written as independent entities.
- Modifying parameter values do not affect any other functions.
-
Work division becomes easier and program modularity can also be enhanced.
- Otherwise one cannot predict how her program will run without knowing how her termmates implement some functions.
-
In some situations, however, we do need a callee to modify the values of some variables defined in the caller.
- We may “call by reference”
- Or we may pass an array to a function.
Passing an array as an argument
- An array can be passed into a function
- Declaration: need a
[]
. - Invocation: use the array name.
- Definition: need a
[]
and a name fro that array in the function.
- Declaration: need a
#include <iostream>
using namespace std;
void printArray(const int arr[], int usedSize);
int main()
{
int num[5] = {1, 2, 3, 4, 5};
printArray(num, 5);
return 0;
}
void printArray(const int arr[], int usedSize)
{
for (int i = 0; i < usedSize; i++)
cout << arr[i] << endl;
}
- When an array is modified in a callee, the caller also sees it modified!
#include <iostream>
using namespace std;
void shiftArray(int arr[], int usedSize);
int main()
{
int num[5] = {1, 2, 3, 4, 5};
shiftArray(num, 5);
for (int i = 0; i < 5; i++)
cout << num[i] << " ";
cout << endl;
return 0;
}
void shiftArray(int arr[], int usedSize)
{
int temp = arr[0];
for (int i = 0; i < usedSize - 1; i++)
arr[i] = arr[i + 1];
arr[usedSize - 1] = temp;
}
-
Why?
- Passing an array is passing an address.
- The callee modifies whatever contained in those addresses.
-
We may also pass multi-dimensional arrays.
- The $k$th-dimensional array size must be specified for all $k\geq 2$!
- Just like when we declare a multi-dimensional array.
#include <iostream>
using namespace std;
void printArray(int arr[][2], int usedSize);
int main()
{
int num[5][2] = {1, 2, 3, 4, 5};
printArray(num, 5);
return 0;
}
void printArray(int arr[][2], int usedSize)
{
for (int i = 0; i < usedSize; i++)
{
for (int j = 0; j < 2; j++)
{
cout << arr[i][j] << " ";
}
cout << endl;
}
}
Constant parameters
- In many cases, we do not want a parameter to be modified inside a function.
int factorial(int n)
{
int ans = 1;
for (int a = 1; a <= n; a++)
ans *= a;
return ans;
}
- There is no reason for the paramenter
n
to be modified.- You know this, but how to prevent other programmer from doing so?
- We may declare a parameter as a constant parameter:
int factorial(const int n);
int main()
{
int n = 0;
cin >> n;
cout << factorial(n);
return 0;
}
int factorial(const int n)
{
int ans = 1;
for (int a = 1; a <= n; a++)
ans *= a;
return ans;
}
-
Once we do so, if we assign any value to
n
, there will be a compilation error. -
The argument passed into a constant parameter can be a non-constant variable.
- Only the value matters.
-
Sometimes an argument’s value in a caller may be modified in a callee.
- e.g., arrays.
-
If these arguments should not be modified in a callee, it is good to protect them
#include <iostream>
using namespace std;
void printArray(const int arr[][2], int usedSize);
int main()
{
int num[5][2] = {1, 2, 3, 4, 5};
printArray(num, 5);
return 0;
}
void printArray(const int arr[][2], int usedSize)
{
for (int i = 0; i < usedSize; i++)
{
for (int j = 0; j < 2; j++)
{
cout << arr[i][j] << " ";
}
cout << endl;
}
}
Function overloading
-
There is a function calculating $x^y$
int pow(int base, int exp)
-
Suppose we want to calculate $x^y$ where $y$ may be fractional:
double powExpDouble(int base, double exp)
-
What if we want more?
double powBaseDouble(double base, int exp)
double powBothDouble(double base, double exp)
-
We may need a lot of
powXXX()
functions, each for a different parameter set. -
To making programming easier, C++ provides function overloading.
-
We can define many functions having the same name if their parameters are not the same.
-
So, we do not need to memorize a lot of function names.
int pow(int, int)
double pow(int, double)
double pow(double, int)
- …
-
Almost all functions in the C++ standard library are overloaded, so we can use them conveniently.
-
Different functions must have different function signatures.
- This allows the computer to know which function to call.
-
A function signature includes
- Function name.
- Function parameters (number of parameters and their types)
-
A function signature does not include return type! Why?
-
When we define two functions with the same name, we say that they are overloaded functions. They must have different paramters:
- Numbers of parameters are different.
- Or at least one pair of corresponding parameters have different types.
Default arguments
- In the previous example, it is identical to give
num
default value 1. - In general, we may assign default values for some parameters in a function.
- As an example, consider the following function that calculates a circle area;
double circleArea(double redius, double pi = 3.14)
;
- Default arguments must be assigned before the function is called.
- In a function declaration or a function definition.
- Default arguments must be assigned just once.
- You can have as many parameters using default values as you want.
- However, parameters with default values must be put behind (to th right of) those without a default value.
- Once we use the default value of one argument, we need to use the default values for all the following arguments.
- How to choose between function overloading and default arguments.
Inline functions
-
When we call a function, the system needs to do a lot of works
- Allocating memory spaces for parameters.
- Copying and passing values as arguments.
- Record where we are in the caller.
- Pass the program execution to the callee.
- After the function ends, destroy all the local variables and get back to the calling function.
-
When there are a lot of function invocations, the program will take a lot of time doing the above switching tasks. It then becomes slow.
-
How to save some time?
-
In C++ (and some other modern languages), we may define inline functions.
-
To do so, simply put the keyword
inline
in front of the function name in a function prototype or header. -
When the compiler finds an inline function, it will replace the invocation by the function statements.
- The function thus does not exist!
- Statements will be put in the caller and executed directly.
-
In most cases, programmers do not use inline functions.