5.7.2. Defining Functions


Defining functions

One of the most important features of C++, or of almost any programming language, is your ability to define new functions. You write a large piece of software by breaking it into a collection of small functions, each with a specific purpose.

The general form of a function definition is

  R name(arguments)
  {
    body
  }
where

In the body, statement

  return E;
indicates that the function is finished and that its result is the value of expression E.

Here are some example functions.

  // Successor(n) returns the integer that is one
  // larger than n.  For example, successor(3) = 4.

  int successor(int n)
  {
    return n+1;
  }


  // Larger(x,y) returns the larger of x and y.
  // For example, larger(5,3) = 5.
  // (Larger is equivalent to the max function from the
  // algorithm library, except that larger requires
  // its arguments to be of type double.)

  double larger(double x, double y)
  {
    if(x > y)
    {
      return x;
    }
    else
    {
      return y;
    }
  }


  // Largest(x,y,z) returns the largest of 
  // x, y and z.

  double largest(double x, double y, double z)
  {
    return larger(x, larger(y,z));
  }


  // IsPrime(n) returns true of n is prime and 
  // false if n is not prime.
  //
  // Requirement: n > 1.

  bool isPrime(long n)
  {
    //-------------------------------------------
    // We try potential factors up to the square
    // root of n, rounded to the nearest integer.
    //-------------------------------------------

    long s = (long)(sqrt(n) + 0.5);

    for(int k = 2; k <= s; k++)
    {
      if(n % k == 0)
      {
        return false;
      }
    }
    return true;
  }

Using your functions

As the definition of largest suggests, you should feel free to use the functions that you have defined as tools for defining other functions.

Use your defined function the same way you would a function from the library. For example,

  if(isPrime(2*y+1))
  {
    printf("%i is prime\n", 2*y+1);
  }
uses predicate isPrime in a test.

Notice that the form of the argument 2*y+1 is not required to match the name, n, used to refer to it in the definition of isPrime. Only the types must match. For any expression E of type long, isPrime(E) yields true if the value of expression E is prime. The argument name, n, in the definition of isPrime is just a convenient way to refer to the number that is passed within the body of isPrime.

When you use a function, you are said to call that function, and expression larger(7,4) is a function call expression.


Do not write types in function calls

After seeing a type in front of each argument in a function definition, some beginners try to write types in function calls too. Don't do that. For example,

  double z = largest(a, b, c);
makes sense. But
  double z = largest(double a, double b, double c);
does not make sense. The compiler will not accept it. Remember that you are not defining function largest here, so get rid of the types.


Each call runs the function

Do not try to do anything special to get a function to run. A function call expression automatically runs the function. Sometimes inexperienced programmers are not sure how to get the result of a function, and write something like

  isPrime(x);
  if(isPrime(x)) 
  {
    …
  }
believing that expression isPrime(x) in the if-statement refers to the answer produced by the statement just before it. But, since this program contains expression isPrime(x) twice, isPrime(x) is called twice. The first statement calls isPrime(x) and just throws away its result. Get rid of it and write
  if(isPrime(x)) 
  {
    …
  }


The mechanics of a function call

When a function is called, a new frame is created for it in the run-time stack. The frame holds all of the variables and parameters that the function uses. It is a scratch pad for the function to use. When the function returns, its frame is destroyed.

It is important to realize that a function's variables are associated with a frame, not with the function itself. When we look at recursion the distinction will become very important.


Standards for functions

Limit each function to no more than 15 noncomment lines, excluding the heading.

Say that a noncomment line is a line that contains at least two nonblank characters that are not part of a comment. (Notice that a line that only contains a brace is not a noncomment line.)

Keep your functions short. I do not want to see any function definitions with more than 15 noncomment lines, excluding the function heading.


Don't change call-by-value parameters

A function's body should not change the value of any of its call-by-value parameters.

If you write const in front of the type of each call-by-value parameter, then the compiler will not allow you to change it. For example, definition

  bool isPrime(const int n)
  {
    int m = (int)(sqrt(n) + 1);
    for(int i = 2; i ≤ m; i++) 
    {
      if(n % m == 0) 
      {
        return false;
      }
    }
    return true;
  }
does not allow you to change n in the body.


Do not write two or more loops in one function.

See the standards.

Avoid easily avoidable duplicate calls

Consider the following.
  int f(const double x)
  {
     return x*sqrt(x) + sqrt(x)/x;
  }
Notice that it computes sqrt(x) twice for the same value x. But that is easy to avoid.
  int f(const double x)
  {
     double s = sqrt(x);
     return x*s + s/x;
  }

Make a function do its whole job

A crippled function only does part of its job. It requires its caller to do the rest for it. Look at the following example.
  // CountChars(a,f) makes a[i] = the number of times character
  // i occurs in file f.
  //
  // Requirements:
  //   f must be a file that is open for reading.
  //   a must be an array with at least 256 indices.

  void countChars(int a[], FILE* f)
  {
    int c = getc(f);
    while(c != EOF)
    {
      a[c]++;
      c = getc(f);
    }
  }
What is wrong with that? It assumes that all variables in array a have already been set to 0. There is no mention of that in the contract! Somehow, the reader is just supposed to know that.

But really, you should not add a requirement to the contract. Zeroing out array a is part of the job of getting the counts. CountChars should not require its caller to help it out.

How can you make countChars zero out array a without adding a second loop to countChars?


No extraneous parameters

Do not add parameters to a function that it does not need. If there is no sensible place for a parameter in a function's contract, that parameter might not be needed.

Do not return a void value

If expression E has type void, do not write return E;. There is nothing to return. Just say return; or, at the end of the body, say nothing.

Do not return a void value

If expression E has type void, do not write return E;. There is nothing to return. Just say return; or, at the end of the body, say nothing.


Exercises

  1. Is the following function definition allowed?

      int dbl(int x)
        return x+x;
    
    Answer

  2. Is the following function definition allowed?

      int dbl(x)
      {
        return x+x;
      }
    
    Answer

  3. Rewrite the following two statements as one statement. Assume that function f has no side-effects and that variable q is not needed for anything else.

      int q = f(w);
      int r = f(q);
    
    Answer

  4. Suppose function g is defined as follows.

      int g(int n)
      {
        if(n > 3) 
        {
          return 0;
        }
        else 
        {
          return 2*n+3;
        }
      }
    
    What is the value of expression g(3)? Answer

  5. Using function g from the preceding problem, what is g(g(3))? Answer

  6. Define a function sqr(x) that takes a real number x (type double) and returns the square of x (also of type double). For example, sqr(10.0) should return 100.0. Also write a contract for this function. Answer

  7. Without using the abs function, define function absoluteValue(n) that takes an int n and returns its absolute value. Also write a contract for this function. Answer

  8. A perfect number is a positive integer that is equal to the sum of its proper divisors. For example, the proper divisors of 6 are 1, 2 and 3. Since 1 + 2 + 3 = 6, 6 is a perfect number.

    Define a function isPerfect(n) that takes a positive integer n and yields true of n is perfect and false if not. Also write a contract for this function.

    Answer

  9. Using your isPerfect function from the preceding exercise, define a function nextPerfect(n) that returns the smallest perfect number that is greater than n. Assume that n is a positive integer. For example, nextPerfect(1) = 6. Answer

  10. Suppose that function dbl is defined by

      int dbl(int x)
      {
        return 2*x;
      }
    
    Is the following allowed in C++?
      int z = 10;
      int y = dbl(int z);
    
    Answer

  11. Write a C++ definition of function seconds(ms), which takes an integer value ms representing some number of milliseconds (thousandths of a second) and returns a real number that is ms in seconds. For example, seconds(2001) = 2.001. Answer