Book Image

Microsoft Visual C++ Windows Applications by Example

By : Stefan Bjornander, Stefan Björnander
Book Image

Microsoft Visual C++ Windows Applications by Example

By: Stefan Bjornander, Stefan Björnander

Overview of this book

Table of Contents (15 chapters)
Microsoft Visual C++ Windows Applications by Example
Credits
About the Author
About the Reviewer
Preface
Index

Functions


A function can be compared to a black box. We send in information (input) and we receive information (output). In C++, the input values are called parameters and the output value is called a return value. The parameters can hold every type, and the return value can hold every type except the array.

To start with, let us try the function Square. This function takes an integer and returns its square.

int Square(int n)
{
  return n * n;
}

void main()
{
  int i = Square(3); // Square returns 9.
}

In the example above, the parameter n in Square is called a formal parameter, and the value 3 in Square called in main is called an actual parameter.

Let us try a more complicated function, SquareRoot takes a value of double type and returns its square root. The idea is that the function iterates and calculates increasingly better root values by taking the mean value of the original value divided with the current root value and the previous root value. The process continues until the difference between two consecutive root values has reached an acceptable tolerance. Just like main, a function can have local variables. dRoot and dPrevRoot hold the current and previous value of the root, respectively.

#include <iostream>
using namespace std;

double SquareRoot(double dValue)
{
  const double EPSILON = 1e-12;
  double dRoot = dValue, dOldRoot = dValue;

  while (true)
  {
    dRoot = ((dValue / dRoot) + dRoot) / 2;
    cout << dRoot << endl;

    if ((dOldRoot - dRoot) <= EPSILON)
    {
      return dRoot;
    }

    dOldRoot = dRoot;
  }
}
void main()
{
  double dInput = 16;
  cout << "SquareRoot of " << dInput << ": "
       << SquareRoot(dInput) << endl;
}

Void Functions

A function does not have to return a value. If it does not, we set void as the return type. As mentioned above, void is used to state the absence of a type rather than a type. We can return from a void function by just stating return without a value.

void PrintSign(int iValue)
{
  if (iValue < 0)
  {
    cout << "Negative.";
    return;
  }

  if (iValue > 0)
  {
    cout << "Positive.";
    return;
  }

  cout << "Zero";
}

There is no problem if the execution of a void function reaches the end of the code, it just jumps back to the calling function. However, a non-void function shall always return a value before reaching the end of the code. The compiler will give a warning if it is possible to reach the end of a non-void function.

Local and Global Variables

There are four kinds of variables. Two of them are local and global variables, which we consider in this section. The other two kinds of variables are class fields and exceptions, which will be dealt with in the class and exception sections of the next chapter.

A global variable is defined outside a function and a local variable is defined inside a function.

int iGlobal = 1;

void main()
{
  int iLocal = 2;
  cout << "Global variable: " << iGlobal // 1
       << ", Local variable: " << iLocal // 2
       << endl;
}

A global and a local variable can have the same name. In that case, the name in the function refers to the local variable. We can access the global variable by using two colons (::).

int iNumber = 1;

void main()
{
  int iNumber = 2;
  cout << "Global variable: " << ::iNumber // 1
       << ", Local variable: " << iNumber; // 2
}

A variable can also be defined in an inner block. As a block may contain another block, there may be many variables with the same name in the same scope. Unfortunately, we can only access the global and the most local variable. In the inner block of the following code, there is no way to access iNumber with value 2.

int iNumber = 1;

void main()
{
  int iNumber = 2;
  {
    int iNumber = 3;
    cout << "Global variable: " << ::iNumber // 1
         << ", Local variable: " << iNumber; // 3
  }
}

Global variables are often preceded by g_ in order to distinguish them from local variables.

int g_iNumber = 1;

void main()
{
  int iNumber = 2;
  cout << "Global variable: " << g_iNumber // 1
       << ", Local variable: " << iNumber; // 3
}

Call-by-Value and Call-by-Reference

Say that we want to write a function for switching the values of two variables.

#include <iostream>
using namespace std;

void Swap(int iNumber1, int iNumber2)
{
  int iTemp = iNumber1; // (a)
  iNumber1 = iNumber2;  // (b)
  iNumber2 = iTemp;     // (c)
}

void main()
{
  int iNum1 = 1, iNum2 = 2;
  cout << "Before: " << iNum1 << ", " << iNum2 << endl;
  Swap(iNum1, iNum2);
  cout << "After: " << iNum1 << ", " << iNum2 << endl;
}

Unfortunately, this will not work; the variables will keep their values. The explanation is that the values of iFirstNum and iSecondNum in main are copied into iNum1 and iNum2 in Swap. Then iNum1 and iNum2 exchange values with the help if iTemp. However, their values are not copied back into iFirstNum and iSecondNum in main.

The problem can be solved with reference calls. Instead of sending the values of the actual parameters, we send their addresses by adding an ampersand (&) to the type. As you can see in the code, the Swap call in main is identical to the previous one without references. However, the call will be different.

#include <iostream>
using namespace std;

void Swap(int& iNum1, int& iNum2)
{
  int iTemp = iNum1; // (a)
  iNum1 = iNum2;     // (b)
  iNum2 = iTemp;     // (c)
}

void main()
{
  int iFirstNum = 1, iSecondNum = 2;
  cout << "Before: " << iFirstNum << ", " << iSecondNum
       << endl;

  Swap(iFirstNum, iSecondNum);
  cout << "After: " << iFirstNum << ", " << iSecondNum
       << endl;
}

In this case, we do not send the values of iFirstNum and iSecondNum, but rather their addresses. Therefore, iNum1 and iNum2 in Swap does in fact contain the addresses of iFirstNum and iSecondNum of main. As in the reference section above, we illustrate this with dashed arrows. Therefore, when iNum1 and iNum2 exchange values, in fact the values of iFirstNum and iSecondNum are exchanged.

A similar effect can be obtained with pointers instead of references. In that case, however, both the definition of the function as well as the call from main are different.

#include <iostream>
using namespace std;

void Swap(int* pNum1, int* pNum2)
{
  int iTemp = *pNum1; // (a)
  *pNum1 = *pNum2;    // (b)
  *pNum2 = iTemp;     // (c)
}

void main()
{
  int iFirstNum = 1, iSecondNum = 2;

  cout << "Before: " << iFirstNum << ", " << iSecondNum
       << endl;

  Swap(&iFirstNum, &iSecondNum);
  cout << "After: " << iFirstNum << ", " << iSecondNum
       << endl;
}

In this case, pNum1 and pNum2 are pointers, and therefore drawn with continuous lines. Apart from that, the effect is the same.

Default Parameters

A default parameter is a parameter that will be given a specific value if the call does not include its value. In the example below, all three calls are legitimate. In the first call, iNum2 and iNum3 will be given the values 9 and 99, respectively; in the second call, iNum3 will be given the value 99. Default values can only occur from the right in the parameter list; when a parameter is given a default value, all the following parameters must also be given default values.

#include <iostream>
using namespace std;

int Add(int iNum1, int iNum2 = 9, int iNum3 = 99)
{
  return iNum1 + iNum2 + iNum3;
}

void main()
{
  cout << Add(1) << endl;       // 1 + 9 + 99 = 109
  cout << Add(1, 2) << endl;    // 1 + 2 + 99 = 102
  cout << Add(1, 2 ,3) << endl; // 1 + 2 + 3 = 6
}

Overloading

Several different functions may be overloaded, which means that they may have the same name as long as they do not share exactly the same parameter list. C++ supports context-free overloading, the parameter lists must differ, it is not enough to let the return types differ. The languages Ada and Lisp support context-dependent overloading, two functions may have the same name and parameter list as long as they have different return types.

#include <iostream>
using namespace std;

int Add(int iNum1)
{
  return iNum1;
}

int Add(int iNum1, int iNum2)
{
  return iNum1 + iNum2;
}

int Add(int iNum1, int iNum2, int iNum3)
{
  return iNum1 + iNum2 + iNum3;
}

void main()
{
  cout << Add(1) << endl;       // 1
  cout << Add(1, 2) << endl;    // 1 + 2 = 3
  cout << Add(1, 2 ,3) << endl; // 1 + 2 + 3 = 6
}

Static Variables

In the function below, iCount is a static local variable, which means that it is initialized when the execution of the program starts. It is not initialized when the function is called.

void KeepCount()
{
  static int iCount = 0;
  ++iCount;

  cout << "This function has been called " << iCount
       << "times." << endl;
}

If iCount was a regular local variable (without the keyword static), the function would at every call write that the function has been called once as iCount would be initialized to zero at every call.

The keyword static can, however, also be used to define functions and global variables invisible to the linker and other object files.

Recursion

A function may call itself; it is called recursion. In the following example, the mathematical function factorial (n!) is implemented. It can be defined in two ways. The first definition is rather straightforward. The result of the function applied to a positive integer n is the product of all positive integers up to and including n.

int Factorial(int iNumber)
{
  int iProduct = 1;

  for (int iCount = 1; iCount <= iNumber; ++iCount)
  {
    iProduct *= iCount;
  }

  return iProduct;
}

An equivalent definition involves a recursive call that is easier to implement.

int Factorial(int iNumber)
{
  if (iNumber == 1)
  {
    return 1;
  }

  else
  {
    return iNumber * Factorial(iNumber - 1);
  }
}

Definition and Declaration

It' s important to distinguish between the terms definition and declaration. For a function, its definition generates code while the declaration is merely an item of information to the compiler. A function declaration is also called a prototype.

When it comes to mutual recursion (two functions calling each other), at least the second of them must have a prototype to avoid compiler warnings. I recommend that you put prototypes for all functions at the beginning of the file. In the following example, we use two functions to decide whether a given non-negative integer is even or odd according to the following definitions.

bool Even(int iNum);
bool Odd(int iNum);

bool Even(int iNum)
{
  if (iNum == 0)
  {
    return true;
  }

  else
  {
    return Odd(iNum - 1);
  }
}

bool Odd(int iNum)
{
  if (iNum == 0)
  {
    return false;
  }

  else
  {
    return Even(iNum - 1);
  }
}

If we use prototypes together with default parameters, we can only indicate the default value in the prototype, not in the definition.

#include <iostream>
using namespace std;

int Add(int iNum1, int iNum2 = 9, int iNum3 = 99);

void main()
{
  cout << Add(1) << endl; // 1 + 9 + 99 = 109
}

int Add(int iNum1, int iNum2 /* = 9 */, int iNum3 /* = 99 */)
{
  return iNum1 + iNum2 + iNum3;
}

Higher Order Functions

A function that takes another function as a parameter is called a higher order function. Technically, C++ does not take the function itself as a parameter, but rather a pointer to the function. However, the pointer mark (*) may be omitted. The following example takes an array of the given size and applies the given function to each integer in the array.

#include <iostream>
using namespace std;

void ApplyArray(int intArray[], int iSize, int Apply(int))
{
  for (int iIndex = 0; iIndex < iSize; ++iIndex)
  {
    intArray[iIndex] = Apply(intArray[iIndex]);
  }
}

int Double(int iNumber)
{
  return 2 * iNumber;
}

int Square(int iNumber)
{
  return iNumber * iNumber;
}

void PrintArray(int intArray[], int iSize)
{
  for (int iIndex = 0; iIndex < iSize; ++iIndex)
  {
    cout << intArray[iIndex] << " ";
  }
  cout << endl;
}

void main()
{
  int numberArray[] = {1, 2, 3, 4, 5};
  int iArraySize = sizeof numberArray / sizeof numberArray[0];
  PrintArray(numberArray, iArraySize);


  // Doubles every value in the array.
  ApplyArray(numberArray, iArraySize, Double);//2,4,6,8,10
  PrintArray(numberArray, iArraySize);

  // Squares every value in the array.
  ApplyArray(numberArray, iArraySize, Square);//4,16,36,64,100
  PrintArray(numberArray, iArraySize);
}

One extra point in the example above is the method of finding the size of an array; we divide the size of the array with the size of its first value. This method only works on static arrays, not on dynamically allocated arrays or arrays given as parameters to functions. A parameter array is in fact converted to a pointer to the type of the array. The following two function definitions are by definition equivalent.

void PrintArray(int intArray[], int iSize)
{
  // ...
}

void PrintArray(int* intArray, int iSize)
{
  // ...
}

The main() Function

The main program is in fact a function; the only special thing about it is that it is the start point of the program execution. Just like a regular function it can have formal parameters and return a value. However, the parameter list must have a special format. The first parameter iArgCount is an integer indicating the number of arguments given by the system. The second parameter vpValues (vp stands for vector of pointers) holds the arguments. It is an array of pointers to characters, which can be interpreted as an array of strings, holding the system arguments. However, the first value of the array always holds the path name of the program. In some tutorials, the traditional parameter names argc and argv are used instead iArgCount and vpValues. The program below writes its path name and its arguments.

#include <iostream>
using namespace std;

int main(int iArgCount, char* vpValues[])
{
  cout << "Path name: " << vpValues[0] << endl;
  cout << "Parameters: ";

  for (int iIndex = 1; iIndex < iArgCount; ++iIndex)
  {
    cout << vpValues[iIndex] << " ";
  }
}

The arguments can be input from the command prompt.

The return value of the main function can (besides void) only be signed or unsigned int. The return value is often used to return an error code to the operating system; usually, zero indicates ok and a negative value indicates an error. The program below tries to allocate a large chunk of memory. It returns zero if it turns out well, minus one otherwise.

#include <cstdlib>

int main()
{
  const int BLOCK_SIZE = 7FFFFFFF;
  void* pBlock = new (nothrow) char[BLOCK_SIZE];

  if (pBlock != NULL)
  {
    // ...

    delete [] pBlock;
    return 0;
  }

  return -1;
}