More about functions

More about functions.

Type void. Functions that don’t return anything.

If a function does not return anything, we say that it’s result type is void. Usually such functions are called for their “side-effects” (they are performing some interaction with the outside world: printing on the screen, writing to a file, changing global varaibles, etc.)

Example: a void function report that prints the answer on the screen and does not return anything.

#include <iostream>
using namespace std;

// reports the answer
void report (int v);

int main() {
  // compute the answer
  int answer = 5 + 6;
  report (answer);
  return 0;
}

void report (int v) {
  cout << "The answer is " << v << endl;
  // a function returning void, does not need a return statement,
  // but you can add it (with no values to be returned)
  return;
}
The answer is 11

Scope

Local variables and blocks. It’s easy, actually, a variable lives only within the block ({ ... }) it was declared in, and is destroyed as soon as the block ends.

In C++, a variable can be overshadowed by another variable inside a nested scope

{ 
  int x = 0;
  {
    int x = 1;
    cout << x;  // prints 1, not 0
  }
}

A function must be declared before it can be used. (This is why we forward declare all our functions at the very beginning of the file, and define them later).

C++ does not allow nested functions definition.

Constant definitions

A variable can be defined with the const modifier. Such varaible is called a constant, and its value cannot be changed.

Example: In the following example, the assignment to the constant fails (we are getting an error at the compile time):

int main() {
  const int SOME_VALUE = 0;
  SOME_VALUE = 1;
}
g++ p4const.cpp 
p4const.cpp: In function 'int main()':
p4const.cpp:5:5: error: assignment of read-only variable 'SOME_VALUE'
   SOME_VALUE = 1;
              ^

Global variables and constants

Example: A function uid() returns unique user IDs. It’s using a global variable last_uid to maintain its state.

#include <iostream>
using namespace std;

// returns a unique user id
// keeping track of its state using a global variable
int uid();
int last_uid = 0;

int main() {
  // make 3 users:
  int alice_uid = uid();
  int bob_uid = uid();
  int carol_uid = uid();

  cout << "Alice. UID = " << alice_uid << endl;;
  cout << "Bob.   UID = " << bob_uid << endl;;
  cout << "Carol. UID = " << carol_uid << endl;;
}

int uid() {
  last_uid ++;
  return last_uid;
}
Alice. UID = 1
Bob.   UID = 2
Carol. UID = 3

A more complex example: Print 10 first prime numbers. We use a globlal variable to maintain the state of the function next_prime() between the function calls.

#include <iostream>
#include <cmath>
using namespace std;

// returns true if x is a prime, and false otherwise
bool is_a_prime(int x);

/* returns a next prime number
     every time the function 'next_prime' is called
     it returns a new prime number and saves it in the
     global state variable 'prime'
*/
int next_prime();
// a global variable, keeps track of the most recently 
// returned prime number
int prime = 0;


int main () {

  // print first 10 prime numbers
  for (int i = 0; i < 10; i++) {
    cout << next_prime() << endl;
  }

  return 0;
}


bool is_a_prime(int x){
  if (x <= 1) 
    return false;

  for(int i = 2; i <= sqrt(x); i++) {
    if (x % i == 0)
      return false;
  }

  return true;
}

int next_prime(){
  do {
    prime++;
  } while ( !is_a_prime(prime) );
  return prime;
}
2
3
5
7
11
13
17
19
23
29

Avoid global state if possible. Usually it’s possible.

Recursive programs

Compute the sum of 1 + 2 + 3 + … + 100.

An iterative implementation (using a loop)

#include <iostream>
using namespace std;

// iteratively compute the sum: x + ... + y
int compute(int x, int y);

int main() {
  cout << compute(1, 100) << endl;
  return 0;
}

int compute(int x, int y) {
  int sum = 0;
  for (int i = x; i <= y; i++) {
    sum += i; 
  }
  return sum;
}
5050

A recursive implementation

Based on the observation that:
Sum(1,100) =
1 + Sum(2,100) =
1 + 2 + Sum(3,100) = … =
1 + 2 + … + 100

So we can define the function recursively when x <= y:
Sum(x,y) = x + Sum(x+1,y)

#include <iostream>
using namespace std;

// recursively compute the sum: x + ... + y
int compute(int x, int y);

int main() {
  cout << compute(1, 100) << endl;
  return 0;
}

int compute(int x, int y) {
  if (x <= y) 
    return x + compute(x+1, y); 
  else 
    return 0;
}
5050

A better recursive implementation

#include <iostream>
using namespace std;

// A better recursive implementation that allows 
// tail-call optimization of the recursive calls.
//
// Compile with the -O2 option (optimization level 2):
// g++ -O2 program.cpp

// recursively compute the sum: x + ... + y
int compute(int sum, int x, int y);

int main() {
  cout << compute(0, 1, 100) << endl;
  return 0;
}

int compute(int sum, int x, int y) {
  if (x <= y) 
    return compute(sum + x, x+1, y); 
  else 
    return sum;
}
5050

Although I should say that g++ actually optimizes the first recursive version as well, however it may not be able to do so for a more complex recursive function.

Factorial function. Fibonacci numbers.

Similarly, we can define a function that’s recursively computing the factorial function (n!), or a function computing the nth Fibonacci number.