3 Functions

3.1 Introduction to Functions

A function performs a specific task. It encapsulates the knowledge of how that task is performed, freeing the rest of the program from the need to know such details. Sometimes, a function encapsulates knowledge of an algorithm that is complicated. Sometimes, the function encapsulates a very simple algorithm, but it encapsulates information that might change later, when the program is modified. When that information changes, you only need to change one function.

Functions are important program design tools because they allow you to break a large program up into manageable pieces, and to deal with each piece separately.

3.2 Kinds of functions

Functions in C/C++ are typically of three kinds.

Pure functions. A pure function does not change anything. It only computes a value. For example, strlen is a pure function. You pass it a string, and it gives you the length of the string. You think of a pure function the way you do a mathematical function such as sqrt.

Procedures. A procedure is a function that makes changes to things, and that returns a void value. You think of it as performing a command. Sometimes, a procedure only changes the values of reference parameters. Sometimes, it changes other things, such as things pointed to by parameters or pointers inside data structures.

Mixed functions. A mixed function returns a value, and also has side effects. For example, scanf is a mixed function. It puts values in its parameters, but it also returns a value. (The value returned by scanf is the number of items that it successfully read.) Mixed functions can be useful, but should be used with care. If you do more than one in a single expression, the different mixed functions might interfere with one another.

The functions of a particular application can usually be broken into two groups. First are functions that are particular to the application, and would make little sense if found in another application. Such functions might, for example, print messages that would almost certainly not be appropriate to other applications. I will call these functions application specific. The second kind of functions are those that might easily be picked up and put into another application, without change. I will call these functions application independent.

3.3 Interfaces

The interface of a function tells the function user everything that he or she needs to know, and no more. There are two parts of the interface.

Prototype. The prototype tells the types of the parameters and the return type. It tells the compiler how the function can be used in a program.

Meaning. The meaning of a function tells what the function does. It is usually a comment, and is called a contract for the function. The contract should tell about all of the parameters, about the return value, and about any changes that the function makes. It should not tell details that are supposed to be encapsulated inside the function, such as how the function does its job.

You can tell whether a function is application specific or application independent by examining its contract. If the description of the function makes reference to where this function fits into the current application (for example, by saying that this function is called by function jam, which handles part three of the application) then the function is application specific. If the description only tells what the function does, without reference to who calls it or exactly how it is used in the application, then the function is often application independent.

Novice programmers tend to make all of their functions application specific. Expert programmers make as many functions as possible application independent.

3.4 Implementations

The implementation of a function tells how the function works. It is written in C/C++, and gives all of the detail. See the examples below.

3.5 Examples

Determine whether an integer is prime

This is a pure function. We provide the interface, consisting of the contract above the function and the heading of the function, and the implementation, consisting of the body of the function. Notice that the comment describing the algorithm is in the body, since it describes the implementation, not the interface.

This is an application independent function. Its description tells what it does, not for what purpose some other part of a program uses it.

///////////////////////////////////////////////////////
// prime(n) returns TRUE if n is prime, and FALSE if n
// is not prime.
///////////////////////////////////////////////////////

bool prime(long int n)
{
  //--------------------------------------------------
  // The algorithm is to divide n by 2,3,...,k until a 
  // factor is found or n < k*k.
  //--------------------------------------------------

  long int k = 2;
  while(n >= k*k) {
    if(0 == n % k) return FALSE;
    k++;
  }

  //--------------------------------------------------
  // If we get out of the loop, then no factor was found,
  // and n must be prime.
  //--------------------------------------------------

  return TRUE;
}

Character replacement in a string: destructive

Again, we provide an interface (two parts: contract and prototype) and an implementation. This function is application independent.
//////////////////////////////////////////////////
// replace_slash_by_colon(s) replaces each '/' in 
// null-terminated string s by ':'.  The change 
// is made in-place.
//////////////////////////////////////////////////

void replace_slash_by_colon(char* s)
{
  char* p = s;
  while(*p != 0) {
    if('/' == *p) *p = ':';
    p++;	
  }
}

Character replacement in string: nondestructive

This example is similar to the previous example, but the function is pure. The method of allocation is relevant to the interface since the user might need to free the allocated memory, and needs to know how it was allocated in order to free it.
/////////////////////////////////////////////////
// Parameter s must be a null-terminated string.
//
// Return a pointer to a newly allocated string 
// (allocated using new), where that string is a
// copy of s, but with each '/' replaced by ':'.
/////////////////////////////////////////////////

char* slash_replaced_by_colon(const char* s)
{
  const char* p = s;
  char* n = new char[strlen(s) + 1];
  char* q = n;
  while(*p != 0) {
   if('/' == *p) *q = ':';
   else *q = *p;
   p++;
   q++;
  }
  *q = 0;                // copy the null. 
  return n;
}

Character replacement in string: space given

This function is similar to the preceding two, but instead of allocating memory or making changes in an existing string, this function asks the caller to provide the memory into which the new string will be placed.
///////////////////////////////////////////////////
// Parameter s must be a null-terminated string.
//
// Put, into buffer dest, a copy of string src, but 
// with each '/' replaced by a ':'.
///////////////////////////////////////////////////

void replace_slash_by_colon(char* dest, const char* src)
{
  const char* p = src;
  char* q = dest;
  while(*p != 0) {
    if('/' == *p) *q = ':';
    else *q = *p;
    p++;
    q++;
  }
  *q = 0;  // copy the null. 
}

3.6. Pragmatics

A large program must be broken into small functions to make it manageable. How do you decide what those functions should do? Here are some guidelines.
  1. Choose functions so that their interfaces are simple and natural. A function that has ten parameters and does so many different things that its description is pages long is almost useless. A function with a few parameters and a short, simple description is generally much more useful. Do not use this as an excuse, however, to write inadequate contracts. Say everything that needs to be said.

  2. When writing a function F, ask yourself what natural subtasks the function breaks into. Ask yourself what library functions you wish that you had, functions that would make writing F easy. Then write interfaces for those functions. Now write function F, using the functions that you wish you had. Now write the functions that you wish you had. This is called top-down design, and is a very useful design method.

  3. Wherever possible, choose application independent functions. You will find that they are more useful to you, even in the current application. If you choose your functions wisely, then you will find that a function that you thought would only be useful in one place turns out to be useful elsewhere as well.