Programming in C++

In-Depth Look at Functions / Pointers

User-Defined Functions

I believe I mentioned something about functions in an article at the beginning of this tutorial, but maybe it is a good idea to brush up on the basics before I discuss some of the more tricky aspects. Earlier, I defined a function as a subprogram that performs some specific task and perhaps returns a value to the calling function. If you wish to see the syntax of a function, go here [see C++ Syntax (section 2)] before continuing. Using functions in a program is how programmers break down the solution to a program into logical steps. A function that is defined for use in one program may be of value in another program that needs to perform the same task. Let me begin by showing an example of a function used in a program that simply determines the average of a set of scores, given that the total of all the scores and the number of scores are passed to the function as parameters.

   float getAverage ( totalScores, number )
   {
           float average;
           average = float ( totalScores) / number;
           return average;
   }

In the above function, the two arguments (called function parameters) are passed to the function by value. Passing by value means that the function receives copies of the arguments and any changes to the values of the variables are not returned to the calling function. In a case when you need to return multiple values to the calling function, you will have to pass your arguments by reference. Passing 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 a function call. All reference arguments are preceded by the ambersand symbol (&). An example of a function prototype that passes arguments by reference is:

   void myFunction ( int& a, int& b );

Notice that type void is used to declare a function that will be returning multiple values to the calling function. We use void because the function will not be returning one sole value of a specific type, and it could be returning values that are of different types. Void lets us return as many values to the calling function as we want as long as they are passed by reference. Let me show you a complete program that uses a function that passes values by reference.

///////////// BEGIN PROGRAM /////////////

   #include <iostream.h>
   #include <stdlib.h>

   void myFunction ( int a, int& b );

   void main ( )
   {
           int x = 5;, y = 10;
           myFunction ( x, y );
           cout << "x = " << x << endl;
           cout << "y = " << y << endl;
           system("PAUSE");
           return 0;
   }// end main ( )

   void myFunction ( int a, int& b)
   {
           a = b * 3 + a * 2;
           b = 10 * a;
   }// end myFunction ( )

///////////// END PROGRAM /////////////

OUTPUT produced by program:

x = 5
y = 400


Here is another example: Write a function that will peform the integer division of M by N and will return the quotient and the remainder of the division.

   void Divide ( int M, int N, int& quotient, int& remainder )
   {
           quotient = M / N;
           remainder = M % N;
   }// end Divide ( )

Something that may come in handy one day when you are faced with a problem is a function that can swap the values of two variables, whether they are of type int, float, double, char, etc. This can be accomplished through the use of passing functions by reference.

NOTE: You can use the same name for multiple functions in a program as long as those functions have different signatures. A function's signature includes the function's class, name, number of parameters, and types and order of parameters, but does not include its return type. For more info, seek [Function Overloading (Section 11)].

   void swap ( int& x, int& y )
   {
           int temp;
           temp = x;
           x = y;
           y = temp;
   }// end swap ( ) for integers


   void swap ( double& x, double& y )
   {
           double temp;
           temp = x;
           x = y;
           y = temp;
   }// end swap ( ) for double


   void swap ( char& x[], char& y[] )
   {
           char temp[21];
           temp = x;
           x = y;
           y = temp;
   }// end swap ( ) for strings [max number of chars = 21]


Getting used to functions will take some time, and you will only become familiar with them by using them in your programs. Be able to distinguish between an argument being passed by value and an argument being passed by reference. In the next section, I dig into some C++ "built in" functions that are used for specific purposes. Read on for more about "built in" functions...

"Built In" Functions

All of the following "built in" functions are contained in the header file #include <string.h> (using Dev-C++ compiler) so be sure to include it when using these functions. These functions are standard functions that are built into the C++ langauge itself in order to perform some operation that would be much harder to do if a programmer was trying to write a user-defined function to acheive its purpose. There are many built in functions, and I will only cover 11 in this article. NOTE: the last example in this article uses pointers [see Pointers], which is covered in more detail further below.

strcpy ( destination, source );
Performs a char-by-char copy of source into destination; primarily used for string assignment.

strncpy ( destination, source, N );
Copies at most N characters from source into destination; the additional N argument must be present.

strcat ( string1, string2 );
Concatenates string2 onto the end of string1; sort of like saying string1 += string2 ( remember that string1 must be large enough to hold string1 + string2 chars.

strncat ( string1, string2, N );
Concatenates at most N characters from string2 onto the end of string1; the additional N argument must be present.

strcmp ( string1, string2 );
Compares string1 and string2 and returns one of the following (useful for alphabetical order):
    -1 if string1 < string2
    0 if string1 = string2
    1 if string1 > string2

strncmp ( string1, string2, N );
Compares at most N characters of string1 and string2 and returns a result similar to strcmp ( ).

strcmpi ( string1, string2 );
Performs exact operation of strcmp ( ) except it is not case sensitive.

strncmpi ( string1, string2, N );
Performs exact operation of strncmp ( ) except it is not case sensitive.

strlen ( string );
Returns the length of the string not including the null terminator.

strchr ( string, ch );
Returns a pointer to the first occurence of a given character (ch) in string; NULL is returned if ch is not found in the string.

strrchr ( string, ch );
Similar to strchr ( ) except it returns a pointer to the last (right most) occurence of a given char in string.


Suppose we have the following code segment in a program:

char string1[20] = "Programming";
char string2[20] = "Computer";
char string3[20] = "Programs";
char string4[20] = "proGRAMMING";
cout << strlen ( string1 );
cout << strcmp ( string1, string3 );
cout << strcmp ( string2, string3 );
cout << strncmp ( string1, string3, 6 );
cout << strcmpi ( string1, string4 );

OUTPUT produced from the above code segment would be using Dev's compiler:

11
-1
-1
0
0


Example: Suppose char name[30] contains a person's name in the following format:

First I. Last

Write a code segment that will find and print the person's middle initial.

   char midInitial;
   char* temp;

   temp = strchr( name, '.' );
   if ( temp = NULL )
           cout << "There is no middle initial.";
   else
   {
           temp--;
           midInitial = temp[0];
           cout << "The person's middle initial is: " << midInitial << endl << endl;
   }


A pointer is used in the above example. Pointers are useful for allowing direct access to the location of a variable in the memory of a computer. Read on for more about pointers...

Using Pointers

A pointer provides direct access to the memory addresses deep within the mind of a computer. They provide efficient methods and techniques for manipulating data in arrays, they are used in functions as reference parameters, and they are the basis for dynamic allocations of memory.

A pointer is a derived data type. Its value is any of the memory addresses available in a computer for storing and accessing data. A pointer is built on the basic concept of a pointer constant, which is drawn from the set of addresses for a computer and exists by itself. We as programmers cannot change them; we can only use them. Every variable that is declared in C++ has an associated memory location with it; the location value can be thought of as a pointer constant.

Even though addresses are constant, there is no way to know or predict what they will be during each new compile and run of a program. We must still refer to these addresses symbolically to be able to work with them. The address operator ( & ) makes it possible to associate a pointer constant with any named location in memory. For example,

   &name

points directly to the memory location designated for name.

NOTE: When the ampersand ( & ) symbol is used as a prefix to a variable name, it "means" address of variable. When it is used as a suffix to a type, it "means" it is a reference parameter.

For int, float, double, and other types, ( two or four byte ), the first byte in memory location for the specified variable is used as the pointer. This byte is also referred to as the least significant byte ( LSB ). For char types, there is only one byte in memory so therefore that byte serves as the location in memory for a pointer.

When you store the address of a variable into another variable, the variable that holds the address is called a pointer variable. Accessing a variable through a pointer requires use of the indirection operator ( * ) before the pointer variable name. You also use the indirection operator ( * ) to define and declare pointer variables. For example,

   char *p;
   int *a;
   float *s;

One of the most common errors by novices and professionals alike is uninitialized pointers. As with variables, it is possible to initialize pointers when they are declared and defined. For example,

   int x;
   int *p = &x;

However, it is not always recommended to initialize pointers. You could also initialize a pointer to nothing by issuing:

   int *p = NULL;

Pointers are useful with functions. When dealing with passing variables to functions, you can pass by value, reference, or by a pointer which is an alternative for passing by reference. Passing by reference and by pointer will accomplish the same task: changing the values in the calling function's scope, but passing by reference is highly recommended over passing by pointer.

Example 1: Using pointers as parameters in a swap function.

   int a = 5;
   int b = 10;

   swap ( &a, &b );
   .
   .
   .
   void swap ( *px, *py )
   {
           int temp;
           temp = *px;
           *px = *py;
           *py = temp;
   }


Example 2: Suppose char name[30] contains a person's name in the following format:

First I. Last

Write a code segment that will find and print the person's middle initial.

   char midInitial;
   char* temp;

   temp = strchr( name, '.');
   if ( temp = NULL )
           cout << "There is no middle initial.";
   else
   {
           temp--;
           midInitial = temp[0];
           cout << "The person's middle initial is: " << midInitial << endl << endl;
   }


Dynamically Allocating / Releasing Memory

Pointers "point" directly to internal memory locations in a computer's hardware. A problem can arise when you need to assign a value to a pointer that is larger than the pointer can handle. For example, consider the situation where you have a pointer named quote that is declared as follows:

   char* quote;

Now, for instance, let's say that quote will be given a value somewhere in the program as follows:

   quote = "C++ Rocks";

The program assigned "C++ Rocks", disregard the quotes, during execution. Now, consider the situation which arises when we try to assign "C++ Ripped Apart Rocks" to quote, which currently contains "C++ Rocks". With the current value of "C++ Rocks", quote has a size of 13 characters and is generally occupying 13 blocks of internal memory. The value we wish to give it, "C++ Ripped Apart Rocks", has a size of 22 characters, and would generally need to occupy 22 blocks of memory. Thus, if we tried to assign quote the value of "C++ Ripped Apart Rocks" an error would occur because quote currently does not have enough memory capacity to handle it. Therefore, there has to be some way we can handle this type of situation, and the solution ultimately lies in how we can allocate new memory for quote.

We can dynamically allocate and release memory for structured data and non-structured data. Structured data is complex data which may occupy many blocks of memory such as arrays, objects, strings. Non-structured data is simple data which generally occupy 1 block of memory such as integers, floating point values, double values, and character values. In our example, we are using structured data since quote is a pointer initially containing "C++ Rocks", which is a series of character values or a string.

The general form for dynamically allocating or releasing memory for structured data is as follows:


   type* ptrName;     --> declaration of pointer
   .
   .
   // ptrName gets a value
   .
   .
   delete [ ] ptrName;     --> release or "free" previously allocated memory for ptrName
   .
   .
   ptrName = new type[size];     --> allocate memory for "new"; size is the size needed to handle the pointer data ptrName


The general form for dynamically allocating or releasing memory for non-structured data is as follows:


   type* ptrName;     --> declaration of pointer
   .
   .
   // ptrName gets a value
   .
   .
   delete ptrName;     --> release or "free" previously allocated memory for ptrName
   .
   .
   ptrName = new type;     --> allocate memory for "new"


NOTE: When allocating memory, new is an operator that accomplishes two things: it allocates the needed memory for the pointer, and it returns or results the beginning address of the allocated memory.

Our previous quote example, could be handled as illustrated in the following complete program, which was written using Dev-C++'s V.4 compiler.



#include <stdlib.h>
#include <iostream.h>
#include <string.h>

void main( )
{
     char* quote = "C++ Rocks";
     char* secondQuote = "C++ Ripped Apart Rocks";

     cout << "Initially, your quote was " << '"' << quote << '"';
     cout << endl << endl;

     delete [] quote;
     quote = new char[strlen(secondQuote)+1];
     strcpy(quote, secondQuote);

     cout << "Now, you prefer to say " << '"' << quote << '"';
     cout << endl << endl;

     system("PAUSE");
}



In the next article, you will be introduced to an array. An array is necessary for programs that require storage of many values. Read on for more about using arrays in your programs...

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“God could create the world in six days because he didn't have to make it compatible with the previous version.”