Global Variables and Constants

Several functions may need to use a constant. If so, it makes sense to declare the constant as a global constant. You do this by declaring it outside of the scope of any function, including the main function (i.e. usually global declarations are placed between #include directives and function prototypes).

Although this is allowed in C++ we will not use globals.

Note: We will not use global variables in this class (see the rules link at the Programming Assignments page).

 

Local Variables

Examine the following program:

#include <iostream.h>
#include <math.h>
double cube_plus_one (double number1);
int main ()
{
   double number1, number2, number3, result;
   cout << "Enter three real numbers:";
   cin >> number1 >> number2 >> number3;
   result = cube_plus_one(number1) * number2 + sqrt(number3);
   cout << "The result is: " << result << endl;
return 0;
}
double cube_plus_one (double number1)
{
   double number2 = 1.0;
   return (number2 + (number1 * number1 * number1));
}

Note the following:

  • We use the term scope to refer to the section of code where a variable has meaning.
  • The variables number1 and number2 , defined in main, are local to main.
  • The variables number1 and number2, defined in cube_plus_one, are local to cube_plus_one.
  • Changes to number1 or number2, in main will not affect the value of number1 or number2, in cube_plus_one, and vice-versa.
  • Arguments may be thought of as a local variable with an initialization. In general, variables that are declared in a function are local to that function and have that function as their scope.
  • Variables declared in the main function are local to the main function and have main as their scope.
  • Call-by-value arguments are like local variables that are initialized to some value.

e.g:

Consider the factorial function: n! = n*n-1*n-2*...*2*1. The implementation below illustrates local variables.

Note that the changes to n in the function don't change anything in the function calling factorial

#include <iostream.h>
int factorial (int n);
// Returns n!, n should be nonnegative
int main()
{
int n;
cout << "Enter an integer number: ";
cin >> n;
cout << "\n\tYou entered: " << n;
cout << "\n\tThe factorial is: "
<< factorial(n) << endl;
cout << "\tThe value you entered is: " << n
<< endl << endl;
return 0;
}
int factorial (int n)
// computing the n!, the local value of n is changed in the process
{
   long product = 1;
   while (n > 0)
   {
     product = n * product;
     n--;
   }
   return product;
}

Overloading Function Names

C++ allows you to give two or more different function definitions to the same function name. In fact, we've already seen examples of this.

e.g:

Arithmetic operators are defined on all of the number types, that is, long, int, float, double and long double. As it turns out, arithmetic operators are actually functions and so, to accomplish this, for each type, there is a separate function definition for each operator.

Let us say, we want to write a function to average two numbers:

double average(double n1, double n2)
{
   return ((n1 + n2) / 2.0);
}

and we might also need a function to average three numbers:

double average3(double n1, double n2, double n3)
{
   return ((n1 + n2 + n3) / 3.0);
}

We can give this second function the same name, average, and at that point we have overloaded the function name average. We still need a function prototype for each function definition even though they have the same name.

#include <iostream.h>
double ave( double n1, double n2);
// returns the average of n1 and n2
double ave( double n1, double n2, double n3);
// returns the average of n1, n2 and n3
int main()
{
cout << "The average of 2.0, 2.5 and 3.0 is "
     << ave(2.0, 2.5, 3.0) << endl;
cout << "The average of 4.5 and 5.5 is "
     << ave(4.5, 5.5) << endl;
return 0;
}
double ave( double n1, double n2)
// returns the average of n1 and n2
{
  return (n1 + n2)/2;
}
double ave( double n1, double n2, double n3)
// returns the average of n1, n2 and n3
{
  return (n1 + n2 + n3)/3;
}
 

The compiler checks the number and type of arguments in the invocation and uses the definition that matches the invocation. When overloading a function ensure that one of the following holds:

  • the number of arguments is different for each function definition
  • the type of at least one of the formal parameters must be different

Note. You cannot have two function definitions with the same number of arguments of the same type. This is true even if the functions differ in the type of the returned value.

e.g:

The following are illegal:

int dbl_func (double n1, double n2);

double dbl_func (double num1, double num2);

We really must be careful about the types that we give functions. If two definitions exist for a function and we accidentally give the wrong type for the arguments, the compiler may pick the wrong definition.

Note: For an example, see page 154.

All functions seen so far take at least one argument and return exactly one value. We sometimes need functions that:

  • return no value at all.
  • return more than one value

C++ allows us to easily accomplish the first need. The mechanism provided is the void function.

We cannot directly accomplish the second need. However, C++ provides a mechanism to indirectly do this. The mechanism is known as call-by-reference.

Return no value

A function that returns no values is called a void-function. Definitions of void-functions in C++ void-functions are defined in a way that is very similar to functions that return a value.

e.g: Consider the function that outputs a temperature in Celsius and its equivalent value in Fahrenheit.

void output_temps(double c_degrees, double f_degrees)
// Outputs two temperatures to the screen
{
   cout.setf(ios::fixed);
   cout.setf(ios::showpoint);
   cout.precision(1);
   cout << f_degrees << " degrees Farenheit is "
         << c_degrees << " C.\n";
   return;
}

void functions vs. normal functions:

  • Use void instead of int, double, char, etc. as the function type. This is a way of saying that we don't want to return any value for this function, which is what we intend.
  • The return should not contain an expression. This is because we clearly don't want to return any value for the function but we still want to let the compiler know that we are done with the function.

Note: The prototype for such a function is like that for any other function. A call to a void function is an executable statement like any other. Such functions do not evaluate to a value and so to invoke them we write them as a statement by themselves.

Functions with no arguments

It's perfectly legal and sometimes very useful to have functions that do not take any arguments. For example, we might want to have a function that simply writes a few lines of text to the screen before we produce new output

void initialize_screen ()
{
   cout << "---------------------------"
         << "CSC 215-402
         << "Void functions\n"
         << "--------------------------";
   return;
}

return statements in void-functions

Note: Although the above program used a return statement for the void function, void functions don't actually need a return statement. Since a value is not returned, the compiler will treat that the final brace as an implicit return statement. However, it's a good idea to include them (as a matter of good programming style – or since it is a good way of indicating the end of the function).

However, note that as with functions that return values, we may want to return before the end of a function, and in those cases the return is necessary (see page 170-174 of your text).

main as a function

main is actually just a function itself (think of it as a function to the operating system). The convention used for the main function requires that an integer value be returned since it is of type int there. Note that although the value returned is not used, C++ tradition preserves its use.

 

Call-by-Reference Parameters

The call-by-value functions that we've been using up until now are not sufficient for some tasks.

e.g.:

Let us say we wanted to write a function to handle screen input and say we the requirement was to get two (two or more) numbers from the user. We would not be able to use a call-by-value function since we need to return the value of two (or more ) things and call-by-value functions can return at most one value.

Call-by-Reference

We need a way to return more than one value to the main function. One way to accomplish this is to allow the function to change the values of its arguments so that the values we want to return are updated directly in the space of the calling routine.

A call-by-reference argument provides the the address of the variable that is its argument. This means that it gives it access to the memory location labeled by the argument in the main function.

We indicate that we have a call-by-reference argument by simply putting an ampersand (&) directly behind (and adjacent to) the type of the argument in the function prototype and definition.

The ampersand in C++ (from C) actually means "give me an address of" specified type. The addresses that we've been talking about are simply the places in memory that the compiler has assigned to each of the variables declared in the main function.

Consider the following function:

void get_two_numbers (double& x, double& y)
{
   cout << "Please input two numbers: ";
   cin >> x >> y;
   return;
}

Consider the invocation:

int main ()
{
   double product, first_float, second_float;
   get_two_numbers (first_float, second_float);
   product = first_float * second_float;
   cout << "\nThe product is: " << product << endl;
   return 0;
}

Note the following:

  • The function get_two_numbers gets the memory addresses of first_float and second_float.
  • The values entered are placed into the memory space reserved for first_float and second_float.
  • When get_two_numbers returns control to main, main sees the new contents of first_float and second_float.

How would this have been different if we had left the arguments to get_two_numbers as call-by-value?

void get_two_numbers(double x, double y)
{
   cout << "Enter two numbers: ";
   cin >> x >> y;
   return;
}

Note:

  • Variables x and y are local variables that are initialized to some random value.
  • A local address is created for them and the input values placed in those addresses.
  • When the function returns all the information is lost because the main function doesn't have access to those addresses, precisely because they are local variables.