C Ripped Apart :: Section 4
Author: Mike Ware
Website: [warebiz] :: "The Programmer's Domain" - http://warebiz.tripod.com
Email: warebiz@yahoo.com
Copyright © 2003 Michael Shawn Ware, All Rights Reserved.


You are here --> [Section 4 :: Functions and Program Flow Topics]

"Jump To Articles"
    --> "Built-in" / User-Defined Functions
    --> Using Sentinel Values
    --> Using Prime Reads
    --> Using Counters


"Built-in" / User-Defined Functions

The design of all C programs is structured through the use of functions, which can be "built-in" or user-defined. I refer to "built-in" functions as C functions which have previously been defined by compiler developers. Examples of "built-in" functions are printf(), scanf(), and sizeof(). These functions have been pre-defined and programmers only have to link to their source definitions (using preprocessor directives) in order to use them in their programs. User-defined functions are C functions which have been explicitly defined by the programmer.

Whether "built-in" or user-defined, the definition of a function is as follows:

>>>>>>>>>
DEFINITION: function - a subprogram or program module designed to achieve a particular task and perhaps return a value to the calling function.
<<<<<<<<<

Functions serve many purposes and are one of the reasons why C is such a powerful language. Besides from achieving goals, functions serve to break a program into logical steps or paths and greatly increase the understanding of the program flow and execution. By breaking a program into logical steps, functions also make updating and maintaining programs easier because the programmer or analyst doesn't have to search through an entire listing of code to find a particular code segment (fewer dependencies). When creating user-defined functions, try to make the code general enough so it can be capable of solving problems in other programs. Portability is very important to remember when creating functions. This concept refers to the capability of a function to serve a purpose in multiple programs. This allows the programmer to place the user-defined function definition in a source file of its own so it can be linked (using preprocessor directives) to other programs which need to use it. This process of creating portable functions is called tool building and is a very critical aspect of programming.

Just like any other object or variable in C, a function must be declared before you use it. Declaration generally only specifies the return-type and name of the function (traditional only). A function definition will also be specified for user-defined functions. A definition specifies all aspects of a function, including return-type, name, argument names and types, and number and order of arguments. There are two typical techniques for declaring and defining user-defined functions: the traditional specification and the newer ANSI C standard technique.

Traditional Specification
The traditional technique says for the declaration of a function to be placed anywhere in the program as long as it is specified before it is used. The proper placement is in the global area of the program before the main() definition. For example, consider the following code segment which specifies the declaraction of a function named divider but does not yet define it:


#include <stdlib.h>
#include <stdio.h>

void divider();

int main()
{

      system("PAUSE");
      return EXIT_SUCCESS;
}


We now need to define the function. The traditional technique of defining a function has the following form:


return-type func-name ( argumentList )
     argumentDeclaration
  {
      stmt;
  }


Here, return-type is a data type specifying the type of the variable the function will return. return-type can be any valid data type, and functions which return no values have a return type of void. func-name is a name for the function. All rules for naming identifiers must be followed when naming functions. argumentList is a listing of arguments ( variables, constants, or expressions ) that will be sent to the function. argumentDeclaration is the declaration section for all function arguments. stmt is simple or compound and represents the executable body of the function. For example, here is the definition for our previous divider function:


#include <stdlib.h>
#include <stdio.h>

void divider();

int main()
{
      divider();

      system("PAUSE");
      return EXIT_SUCCESS;
}

void divider()
{
      printf("---------------------\n\n");
}


A function which is defined to return a value must return the variable, constant, or expression through the use of the return statement. The variable, constant, or expression being returned must match the return-type of the function. For a more practical example, consider the following program which calculates a students grade based on user specified test scores:


/////////////////////////////// CODE WRITTEN USING DEV C/C++ V.4 ////////////////////////////////////////

#include <stdlib.h>
#include <stdio.h>

#define POSSIBLEPOINTS 300
#define NUMOFSCORES 3

char calcGrade();

int main()
{
      int totalPoints = 0, count, temp;
      char letterGrade;

      for (count = 1; count <= NUMOFSCORES; count++)
      {
            printf("Enter the student's score for test %d: ", count);
            scanf("%d", &temp);
            totalPoints += temp;
      }

      letterGrade = calcGrade(totalPoints);

      printf("\n\nSTUDENT GRADE = %c\n\n", letterGrade);

      system("PAUSE");
      return EXIT_SUCCESS;
}

char calcGrade(stuPoints)
   int stuPoints;
{
      float average;
      char grade;

      average = (float)stuPoints / POSSIBLEPOINTS * 100;

      if (average >= 90.0)
            grade = 'A';
      else if (average >= 80.0)
            grade = 'B';
      else if (average >= 70.0)
            grade = 'C';
      else if (average >= 60.0)
            grade = 'D';
      else  // average < 60.0
            grade = 'F';

      return grade;
}

/////////////////////////////// CODE WRITTEN USING DEV C/C++ V.4 ////////////////////////////////////////


Examine a few details in the above code segment. First, notice that the variable names in the function call (called parameters) and the variable names in the function definition (called arguments) are not identical. This allows for arguments to represent a "copy" of its corresponding parameter but have a name local to the function. This means that an indefinite number of variables may have the same name as long as they are local to a function. Secondly, notice the use of a return statement to return the value of grade back to the calling function in main(). Thirdly, notice the variable letterGrade which is assigned the return value of the calcGrade() function call.

ANSI C Standard Technique
The ANSI C standard proposes prototyping, which provides a more compact and efficient method for declaring and defining a function. Prototyping involves declaring the function and its arguments by stating the return-type, name, number of arguments, and order and types of arguments. All of this data refers to a function's signature. The function declaration (more commonly called prototype) will specify the return-type, name, and types, number, and order of its arguments just like its definition. This was not proposed for the traditional method. Also, the argumentDeclaration section of the traditional method is now removed, and the arguments will be described and specified in the function parentheses. The form of the ANSI C (prototyping) method for defining a function is as follows:


return-type func-name ( argumentList )
  {
      stmt;
  }


The only difference between the traditional and ANSI C method is the removal of the argumentDeclaration section. Also, argumentList will now specify the types, order, and number of arguments. For example, consider the following updated form of our previous program used to calculate a student's grade based upon user specified test scores:


/////////////////////////////// CODE WRITTEN USING DEV C/C++ V.4 ////////////////////////////////////////

#include <stdlib.h>
#include <stdio.h>

#define POSSIBLEPOINTS 300
#define NUMOFSCORES 3

char calcGrade(int);

int main()
{
      int totalPoints = 0, count, temp;
      char letterGrade;

      for (count = 1; count <= NUMOFSCORES; count++)
      {
            printf("Enter the student's score for test %d: ", count);
            scanf("%d", &temp);
            totalPoints += temp;
      }

      letterGrade = calcGrade(totalPoints);

      printf("\n\nSTUDENT GRADE = %c\n\n", letterGrade);
      printf("\n\nTERM POINTS = %d\n\n", POSSIBLEPOINTS);

      system("PAUSE");
      return EXIT_SUCCESS;
}

char calcGrade(int stuPoints)
{
      float average = 0;
      char grade;

      average = (float)stuPoints / POSSIBLEPOINTS * 100;
      printf("%f", average);

      if (average >= 90.0)
            grade = 'A';
      else if (average >= 80.0)
            grade = 'B';
      else if (average >= 70.0)
            grade = 'C';
      else if (average >= 60.0)
            grade = 'D';
      else  // average < 60.0
            grade = 'F';

      return grade;
}

/////////////////////////////// CODE WRITTEN USING DEV C/C++ V.4 ////////////////////////////////////////


It is highly recommended to use the ANSI C standard technique for creating user-defined functions. Be aware, however, because some older compilers will not support the ANSI C standard implementations. In such a case, the traditional method must be used.

Passing Variables by Value VS Passing Variables by Reference
In the our previous program for calculating a student's grade, the two arguments are passed to the function by value. Passing a variable by value means that the function will receive a copy of the argument; thus, any changes done to the value of the variable will not be returned to the calling function. Since a return statement can only return one value, there will be times when you need to return multiple values to the calling function. For such a case, you will have to pass your arguments by reference. Passing a variable by reference means that instead of getting a copy of the argument, the function parameter will share the same memory address as the argument. In other words, when passing by reference, any changes that are made to the values of the variables will be sent back to the calling function. This is why passing by reference is useful for returning multiple values during a function call. One way to accomplish this feature in C is through the use of pointers. For now, remember that a pointer ultimately "points" to the memory location of a variable so the variable's value can be directly accessed and manipulated.

The following program illustrates how pointers can be used to pass variables by reference. Study the following code and perhaps compile it using your compiler for futher understanding:

/////////////////////////////// CODE WRITTEN USING DEV C/C++ V.4 ////////////////////////////////////////

#include <stdlib.h>
#include <stdio.h>

void swap(int* x, int* y);

int main()
{
      int number1, number2;

      printf("INTEGER SWAP FUNCTION ROUTINE\n");
      printf("--------------------------------------->\n\n");
      printf("Enter first integer: ");
      scanf("%d", &number1);
      printf("Enter second integer: ");
      scanf("%d", &number2);

      // call function to perform "swap" operation
      swap(&number1, &number2);

      printf("\n\nValue of first integer is now: %d\n", number1);
      printf("Value of second integer is now: %d\n\n", number2);

      system("PAUSE");
      return EXIT_SUCCESS;
}

void swap(int* x, int* y)
{
      int temp;
      temp = *x;
      *x = *y;
      *y = temp;
}

/////////////////////////////// CODE WRITTEN USING DEV C/C++ V.4 ////////////////////////////////////////


In the above program, number1 and number2 are passed by address using the ambersand ( & ) prefix method. The actual swap() arguments x and y are defined as pointers so they can directly access the values of their corresponding parameters in the function call statement. In the function body, the swap operation is performed by de-referencing the pointer variables so the values can be manipulated and ultimately sent back to the calling function.

With functions covered in a brief fashion, we can now explore more details concerning control structures. Read on for more about sentinel values...

Using Sentinel Values

There are three ways to control repetition during execution of a program: sentinel values, prime reads, and counters. Let's talk about sentinel values now and talk about prime reads and counters later. A sentinel value is a value that is not a legitimate data value for a particular problem (but is of proper type) used to check for a "stopping" value. There may be times when you must let users of your program enter as much information about something as they want to. When the user is done entering information, the user can enter a sentinel value, which will let the program know when the user is done inputting information.

>>>>>>>>>
DEFINITION: sentinel value - a value that is not a legitimate data value (but is of proper type) used to check for a "stopping" value for repetitive statements.
<<<<<<<<<

Example 1: [ -1 ] is used as a sentinel value


#include <stdio.h>

int main()
{
      int age, ageSum = 0;

      printf("Enter an age ( -1 to stop ): ");
      scanf("%d", &age);
      while ( age != -1 )
      {
            ageSum += age;
            printf("Enter an age ( -1 to stop ): ");
            scanf("%d", &age);
      }

      printf("The sum of all ages entered: %d\n\n", ageSum);

      system("PAUSE");
      return 0;
}

Example 2: [ -99 ] is used as a sentinel value

Read a list of text scores and calculate their average. An input of -99 for a score denotes end-of-data for the user.


#include <stdio.h>

int main()
{
      int numScores = 0, sum = 0, score;
      float average;

      printf("Enter a test score ( -99 to quit ): ");
      scanf("%d", &score);
      while ( score != -99 )
      {
            sum += score;
            numScores++;
            printf("Enter a test score ( -99 to quit ): ");
            scanf("%d", &score);
      }

      average = (float)sum / numScores;
      printf("The average of the %d test scores is %f \n\n", numScores, average);

      system("PAUSE");
      return 0;
}

The next article will introduce you to prime reads. Prime reads and sentinel values normally go hand and hand, but not for every situation. Read on to learn more about prime reads...

Using Prime Reads

Another method of controlling repetition is to use a prime read. A prime read and sentinel value often go hand and hand but not always. A prime read is a data input, before the loop statement, that allows the first actual data value to be entered so it can be checked in the loop statement. The variable that is inputted by the user and being tested by the expression in the loop is the prime read; the value of the prime read is what we call a sentinel value [see Using Sentinel Values].

Example 1: [ age ] is used as a prime read


#include <stdio.h>

int main()
{
      int age, ageSum = 0;

      printf("Enter an age ( -1 to stop ): ");
      scanf("%d", &age);
      while ( age != -1 )
      {
            ageSum += age;
            printf("Enter an age ( -1 to stop ): ");
            scanf("%d", &age);
      }

      printf("The sum of all ages entered: %d\n\n", ageSum);

      system("PAUSE");
      return 0;
}

Example 2: [ score ] is used as a prime read

Read a list of text scores and calculate their average. An input of -99 for a score denotes end-of-data for the user.


#include <stdio.h>

int main()
{
      int numScores = 0, sum = 0, score;
      float average;

      printf("Enter a test score ( -99 to quit ): ");
      scanf("%d", &score);
      while ( score != -99 )
      {
            sum += score;
            numScores++;
            printf("Enter a test score ( -99 to quit ): ");
            scanf("%d", &score);
      }

      average = (float)sum / numScores;
      printf("The average of the %d test scores is %f \n\n", numScores, average);

      system("PAUSE");
      return 0;
}

We've covered sentinel values and prime reads, but there is one last method used for controlling repetition during execution of a program. Read on to learn more about using counters...

Using Counters

Yet another method for controlling repetition during execution of a program is by using a counter. Using a counter requires knowledge of the exact number of times you need to repeat something. For example, if you were to instruct the user of your program to input ten numbers, you could set a counter variable to 0, and then set up a loop to continue cycles while the value of counter is less than ten (this loop would equal ten cycles: 0, 1, 2, .., 9).

Example 1:

Write a section of code that would output the numbers from 1 to 10:


    int count = 0, numTimesNeeded = 10;

    while ( count < numTimesNeeded )
    {
            printf("%d\n", count + 1);
            count++;
    }

Example 2:

Write a section of code that will allow the user to input ten test scores in order to find the average of the scores:


#include <stdio.h>

int main()
{
      int count = 0, numTimesNeeded = 10, total = 0, score;
      float average;

      while ( count < numTimesNeeded )
      {
            printf("Enter the score for test %d: ", count + 1);
            scanf("%d", &score);
            total += score;
            count++;
      }

      average = (float)total / numTimesNeeded;
      printf("The average of the %d test scores is: %f\n\n", numTimesNeeded, average);

      system("PAUSE");
      return 0;
}

We have now covered functions and three ways of controlling repetition: using a sentinel value, prime read, or counter. We explore arrays in the next section. Arrays allow for the storage of related data values all of the same type. Read on for more...

Move on to next set of topics: Section 5 - Arrays and Array Manipulation

Back to Top