Interfacing with C plus plus-programing communication with microcontrolers (K. Bentley, 2006)
.pdf164 7 DRIVING LEDS
Then the following assignment is valid:
DACPtr = &Dac;
Membership Access Operators
If we use the object Dac, we can call the SendData() function as follows using the dot operator (.), also known as the membership access operator:
Dac.SendData(255);
We can also use a pointer variable such as DACPtr to call the SendData() function, although the syntax is different. In this case the membership pointer operator is used (->), formed by combining the minus sign (-) and the right angle bracket (>):
DACPtr->SendData(255);
Pointers to Base Class Objects can point to Objects of Derived Classes
This is one of the most useful and important concepts in object-oriented programming. In earlier sections it was explained that a pointer pointing to a float type variable cannot point to a location containing an int. This rule does not apply to base classes and derived classes. Although the two objects are different, a pointer to a base class object can point to an object of a derived class:
ParallelPort *PortPtr;
DAC Dac;
PortPtr = &Dac;// is allowed!
We are yet to discuss the advantages of using this type of pointer assignment. Its major use is associated with virtual functions and will be explained in Sections 8.5 and 8.6.
7.5.4 Pointers to Arrays
Pointers to One-Dimensional Arrays
When an array is declared to be equivalent to that shown in Figure 7-15, the address of the array will be a (no subscripts) which points to the first element of the array. Therefore, a and &a[0] are equivalent and both point to the first element. The important thing to note is that a is a pointer constant. It cannot be incremented, decremented or assigned any other values. Since the array has been stored in a specific memory space, the address value is fixed.
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
Figure 7-15 Schematic of a one-dimensional array.
7 DRIVING LEDS 165
The following statements are all valid:
int a[10]; |
|
int *ElementPtr; |
|
int b = 0; |
// same as ElementPtr = &a[0]; |
ElementPtr = a; |
|
*a = b; |
// the value of b is deposited |
|
// in a[0] |
Some of the statements shown below are illegal:
int a[10]; float *FltPtr;
int b = 0; |
// illegal – type mismatch |
FltPtr = a; |
|
a = &b; |
// illegal – a is constant |
Pointers to Two-Dimensional Arrays
As mentioned earlier, a two-dimensional array can be viewed as an array of onedimensional arrays. An example two-dimensional array can be declared as:
number of rows
int a[5][5];
number of elements per row
Recall that elements are stored in consecutive memory locations. For example, the element a[1][0] is stored next to a[0][4].
Unlike the case for one-dimensional arrays, the array name a is a pointer to the entire row starting at a[0][0] and ending at a[0][4]. The pointer a is still a constant.
a[0][0] a[0][1] a[0][2] a[0][3] a[0][4]
a[1][0] a[1][1] a[1][2] a[1][3] a[1][4]
.
.
.
a[4][0] a[4][1] a[4][2] a[4][3] a[4][4]
Figure 7-16 Schematic of a two-dimensional array.
166 7 DRIVING LEDS
A pointer to a row of five elements can be declared as follows:
int (*RowPtr)[5];
Note the subtle difference between the presence and absence of the parentheses in the above declaration. Compare this declaration to the declaration of an array of pointers discussed earlier.
If a is de-referenced, the result will be a pointer to the first element of the first row, i.e. &a[0][0]. This resulting pointer is still a constant. To access the value of a[0][0], the pointer a must be de-referenced twice. The following statements illustrate this:
int |
*ElementPtr; |
|
|
|
|
int |
b; |
|
|
|
|
int |
a[5][5]; |
|
|
|
|
int |
(*RowPtr)[5]; |
// pointer |
to the |
first row |
|
RowPtr = a; |
*a; |
||||
ElementPtr = |
// pointer |
to the |
first element |
||
b = |
**a; |
|
// of the first row |
||
a; |
// same as |
b = *ElementPtr; |
|||
ElementPtr = |
// Illegal |
– type |
mismatch |
||
RowPtr = *a; |
|
// Illegal |
– type |
mismatch |
|
*a = &b; |
|
// Illegal |
- *a is constant |
An important observation is that when an array name is de-referenced, it points to the next lower level entity. For example, if the name of a two-dimensional array is de-referenced, it will point to a one-dimensional array. If the name of a onedimensional array is de-referenced it will evaluate to be the contents of the first element of the array. Any further de-referencing is illegal.
7.5.5 Arrays of Pointers
It is also possible to declare arrays of pointers. In such an array, each element itself is also a pointer. An example of a pointer array declaration is given as follows:
int *IntPointers[20];
In this declaration, IntPointers is a constant pointer. It points to the first element of the array of pointers. If we use the de-referencing operator as shown below we will obtain the contents of the first element, which itself is a pointer to an int. Therefore it must be assigned to a compatible pointer variable. Consider the following declaration:
int a;
int *IntPtr;
int *IntPointers[20];
IntPointers is the start address of the array of pointers to int. In other words, it holds a memory address. This location contains a pointer to an int. Thus, the
7 DRIVING LEDS 167
contents of any element of the array can be assigned to a pointer to an int, such as IntPtr. To obtain the contents of the first element of the IntPointers array, we can de-reference IntPointers. Once de-referenced, it can be assigned to IntPtr as shown in the line below:
IntPtr = *IntPointers; // contents of 1st element
A pointer to an int is stored at the address obtained by de-referencing IntPointers (i.e. *IntPointers). If we need the contents of the location pointed to by this pointer to int, we must de-reference *IntPointers once more to obtain the integer value. Such an integer value can be assigned to an int type variable such as a. Thus:
a = **IntPointers; |
// Same as a = *IntPtr; |
7.5.6 Pointer Arithmetic
As already seen, a pointer variable can be incremented or decremented. Likewise an integer value can be added or subtracted. However, the results produced by pointer arithmetic are different to the results produced by normal arithmetic. Before explaining this further, we should understand where pointer arithmetic is useful.
One-dimensional Arrays
Pointer arithmetic is especially useful for accessing array elements. Consider the example:
int a[5];
Here we have declared an array of 5 integers. The array name a is a constant pointer and it points to the first element of the array. Figure 7-17 shows an example of such an array in memory.
Memory |
Array Element |
||
Address |
and its value |
||
600000 |
|
|
|
a[0] is 4 |
|||
600002 |
|
a[1] is 9 |
|
600004 |
|
a[2] is 8 |
|
600006 |
|
a[3] is 3 |
|
600008 |
|
a[4] is 5 |
|
Figure 7-17 An example of 5 integers in memory.
168 7 DRIVING LEDS
In the example shown in Figure 7-17, the memory address values change by 2 when we move from one address to the next address. This is because we have assumed each integer occupies two bytes. Thus, the element a[0] occupies the addresses 600000 and 600001. The next element a[1] begins at 600002 and so on.
The value of a is 600000. The pointer a is not a variable, it is a constant. It points to the first element of the array and therefore cannot be assigned another value. It can be de-referenced like other pointers as follows:
*a |
|
a[0] which is 4 |
|
Although a cannot be changed, we can add an integer value to a to obtain a new value. Thus:
a+1 is a valid expression.
If we interpret this result using normal arithmetic, it evaluates to 600001. However, in pointer arithmetic it evaluates to 600002. Since a is a pointer to an integer, ‘+1’ really means plus one int type data, which is two bytes in our example. The compiler takes into account the size of the data type pointed to by the pointer when evaluating pointer arithmetic expressions. The result a+1 still remains a pointer and points to the next element of the array:
a+1 600002
a+2 600004
a+3 600006
Just like the pointer a, the following three pointers can also be de-referenced:
*(a+1) |
|
a[1] which is 9 |
|
||
*(a+2) |
|
a[2] which is 8 |
|
||
*(a+3) |
|
a[3] which is 3 |
|
Use of parentheses is very important in the cases shown above. If the parentheses had not been used, the results would be as follows:
*a+1 |
|
a[0]+1 which is 5 |
|
||
*a+2 |
|
a[0]+2 which is 6 |
|
||
*a+3 |
|
a[0]+3 which is 7 |
|
As can be seen from this discussion, pointer arithmetic can be used to access an array element of a one-dimensional array. For the array a, the ith element can be accessed by:
*(a+i)
7 DRIVING LEDS 169
Two-dimensional arrays
Pointer arithmetic is applied slightly differently to two-dimensional arrays. Consider the example:
int a[3][2];
This declares an array of 6 elements stored in sequential memory as shown in Figure 7-18.
|
|
|
|
Memory |
Array Element |
|
|
|
|
|
Address |
and its value |
|
|
|
|
|
600000 |
|
|
|
0 |
1 |
|
a[0][0] is |
6 |
|
|
|
|
||||
|
|
|
|
600002 |
a[0][1] is |
7 |
0 |
6 |
7 |
|
|||
|
|
|
|
600004 |
a[1][0] is 55 |
|
1 |
55 |
9 |
|
|||
|
600006 |
a[1][1] is |
9 |
|||
|
|
|
|
|||
2 |
3 |
33 |
|
|||
|
600008 |
a[2][0] is |
3 |
|||
|
|
|
|
|||
Array a[3][2] |
600010 |
|
|
|||
a[2][1] is 33 |
Figure 7-18 Two-dimensional array in memory.
As for the case of one-dimensional arrays, the array name a is still a pointer. Its value is 600000. It is a constant and cannot be changed. The difference for twodimensional arrays is that a points to the first row of elements, not the first element of the array. Therefore, it represents a data size of 2 integers as specified by the second subscript of the array declaration. As a result the pointer a represents 2 integers, i.e. points to 4 bytes of memory. With this in mind, pointer arithmetic works as follows.
a+1 |
|
600004 |
points to the second row |
|
|||
a+2 |
|
600008 |
points to the third row |
|
Using this notation, a pointer to the ith row can be obtained by adding i to a:
a+i |
|
points to the ith row |
|
Like any other pointer, these pointers can also be de-referenced. When these pointers are de-referenced, the result is still a pointer:
170 7 DRIVING LEDS
*(a+1) |
|
600004 |
points to the first element of the second row |
|
|||
*(a+2) |
|
600008 |
points to the first element of the third row |
|
|||
*(a+i) |
|
points to the first element of the (i+1)th row |
|
|
The size of data pointed to by these de-referenced pointers is no longer an entire row. They now point to elements, which are single integers. Now pointer arithmetic will be based on single integers or two bytes. Thus:
*(a+1) |
+ |
1 |
|
600006 |
points to a[1][1] |
|
|||||
*(a+2) |
+ |
1 |
|
600010 |
points to a[2][1] |
|
Therefore, the pointer that points to the jth element of the ith row is:
*(a+i) + j |
|
points to a[i][j] |
|
By de-referencing the above pointers, the values of the elements can be accessed:
*(*(a+1) + 1) |
|
a[1][1] which is 9 |
|
||
*(*(a+i) + j) |
|
a[i][j] |
|
The same arguments we presented for two-dimensional arrays can be extended in the same logical manner to higher-dimensional arrays.
7.5.7 Pointers to Functions
Pointers can be declared to point to functions. Just like all the pointers discussed previously, function pointers will also have an address in memory. These addresses point to the first instruction to be executed.
An example of a function pointer declaration is:
int (*CalcFunctionPtr)(int,int);
In the above example, the name of the function pointer is CalcFunctionPtr. This pointer can only point to a particular category of function as specified by the pointer declaration. The declaration specifies that the function to be pointed to by CalcFunctionPtr must receive two integer parameters and must return an integer value. An example of a function that can be pointed to by
CalcFunctionPtr is:
int Add(int a, int b)
{
return (a + b);
}
Another function that can be pointed to by the CalcFunctionPtr is:
int Sub(int a, int b)
{
7 DRIVING LEDS 171
return (a – b);
}
In a similar manner to arrays, where the array name is a constant pointer, function names are also constant pointers for the simple reason that when a program is running the location of a function in memory is fixed.
In the above two functions, Add and Sub are two constant function pointers. Each of them point to the first instruction to be executed in their respective functions. These constant pointer values can be assigned to a declared pointer variable. An example program is given in Listing 7-1.
Listing 7-1 Use of pointers to functions.
#include <iostream.h> #include <conio.h>
int Add(int a, int b)
{
return (a + b);
}
int Sub(int a, int b)
{
return (a - b);
}
void main()
{
int a, b, Result; char key;
int (*CalcFunctionPtr)(int,int);
cout << "Enter two integer values " << endl; cin >> a >> b;
cout << "Press '+' or '-' key" << endl;
key = getch(); // getch() reads the key pressed
switch(key)
{
case '+' : CalcFunctionPtr = Add; break;
case '-' : CalcFunctionPtr = Sub;
}
172 7 DRIVING LEDS
Result = CalcFunctionPtr(a,b);
cout << "The result is " << Result << endl;
}
If the ‘+’ key is pressed, the switch statement will set the CalcFunctionPtr to point to the Add() function. If the ‘-‘ key is pressed, CalcFunctionPtr will be set to point to the Sub() function. The second-last line of the code fragment will execute either the Add() function or the Sub() function depending on the key pressed. This is an example where a function pointer is used to carry out different tasks using the same statement for different cases (Result = CalcFunctionPtr(a,b)).
In a more complex program you may have a large portion of the program written using the function pointer variable. If we need to change the cases, we do not need to change the part of the program that calculates the result. The program in Listing 7-2 shows how we can add another case for multiplication and yet the result will be calculated using the same statement as for Listing 7-1; ‘Result =
CalcFunctionPtr(a,b);’.
Listing 7-2 Adding more functionality to the program in Listing 7-1.
#include <iostream.h> #include <conio.h>
int Add(int a, int b)
{
return (a + b);
}
int Sub(int a, int b)
{
return (a - b);
}
int Mult(int a, int b)
{
return (a * b);
}
void main()
{
int a, b, Result; char key;
int (*CalcFunctionPtr)(int,int);
7 DRIVING LEDS 173
cout << "Enter two integer values " << endl; cin >> a >> b;
cout << "Press '+', '-' or '*' key" << endl;
key = getch(); // getch() reads the key pressed
switch(key)
{
case '+' : CalcFunctionPtr = Add; break;
case '-' : CalcFunctionPtr = Sub; break;
case '*' : CalcFunctionPtr = Mult;
}
Result = CalcFunctionPtr(a,b);
cout << "The result is " << Result << endl;
}
Functions Returning Pointers
Functions returning pointers are discussed here since their declarations are similar. A function with the name AnyFunction that receives two int type parameters and returns a pointer to an int is declared as follows:
int *AnyFunction(int,int);
Compare this declaration with the declaration of FunctionPtr, which is a pointer to a function taking two int type parameters and returning an int type value:
int (*FunctionPtr)(int,int);
In one case a pair of parentheses is present and in the other case the parentheses are omitted. Although only slightly different, these two declarations are completely different. AnyFunction is a function name and therefore is a constant pointer. FunctionPtr is a pointer variable.
7.5.8 Pointers to void
Pointers can also be declared to be of type ‘pointer to void’. These pointers do not have any restrictions as to the type of data or functions they can point to. The following example outlines their use.
int |
a; |
// |
declaration |
of |
an int |
|||
float b; |
// |
declaration |
of |
a float |
||||
void *VoidPtr; |
// |
declaration |
of a void pointer |
|||||
int |
Add(int,int); |
// |
declaration |
of a function |