Search

Sponsored Links

Meta

Categories

Archives

Recent Posts

RSS Feeds

06
Dec

C++ Basics Tutorial - Lesson 5

Related Blog Items

Please refer lesson 4

5. Pointers and Strings
5.1 Pointer Declaration and Initialization
5.2 Casting Between Numeric Address and Pointer
5.3 Constant Pointer and Pointer to Constant
5.4 Pass Pointer By Reference
5.5 Receive array with pointer
5.6 Pointer Expressions and Arithmetic
5.7 Pointer Offset and Subscription
5.8 “sizeof” Operator
5.9 Size of String and Character Array
5.10 Function Pointer and Call Back Technique
5.11 Array of Function Pointer and Menu-Driven System
5.12 Power and Flexibility of Pointers

5. Pointers and Strings

5.1 Pointer Declaration and Initialization

int x, y, qty, *xPtr1 = 0, *yPtr1 = NULL, *qtyPtr1 = &qty;
float z, *zPtr1
zPtr1 = &z;

Pointer variable xPtr1, yPtr1, zPtr1 and qtyPtr1 are variables containing the addresses of another normal variables. * indicates that the following variable is a pointer. int * indicates that the following variable is a pointer pointing to an integer. When used in type declaration, function prototype or function definition, * is not a dereferencing operator.

“NULL” is defined as 0 in < iostream > and several standard library header files. A pointer with a value of 0 points to nowhere.

5.2 Casting Between Numeric Address and Pointer

You can acquire the numeric address contained in a pointer and vice versa through casting between the integer type and the pointer type:


typedef unsigned long       DWORD; // each unsign char takes four byte
typedef unsigned char       BYTE;  // each unsign char takes one byte

struct Any {
    BYTE m_ba1[100];
    BYTE m_ba2[100];
    BYTE m_ba3[100];
};

void main()
{
    Any * pAny = new Any;
    DWORD dwBase = (DWORD)pAny;  // casting pointer to DWORD address
    cout <<  Offset of m_ba1 is   << (DWORD)pAny->m_ba1   dwBase << endl;
    cout <<  Offset of m_ba2 is   << (DWORD)pAny->m_ba2   dwBase << endl;
    cout <<  Offset of m_ba3 is   << (DWORD)pAny->m_ba3   dwBase << endl;

    ::memset(pAny1->m_ba3, 123, 100);
    Any * pAny2 = (Any *)(dwBase + 200);  // casting DWORD address to pointer
    cout << "pAny1 offset by 200: m_ba1[23] = "
         << (int)(pAny2->m_ba1[23]) << endl;
}

Output will be:

Offset of m_ba1 is 0
Offset of m_ba2 is 100
Offset of m_ba3 is 200
pAny1 offset by 200: m_ba1[23] = 123

5.3 Constant Pointer and Pointer to Constant

A pointer can be pointed to a constant, and the pointer itself can also be a constant. If a pointer is a constant pointer, it should be initialized when declared, and it can not be pointed to any other variable.

Suppose p1 is a pointer pointing to a Person object. There are three kinds of use of const :

print(Person * const p1) pointer is constant but object is not.
print(const Person * p1) object is constant but pointer is not
print(const Person * const p1) both the object and the pointer are constant

5.4 Pass Pointer By Reference

If we want to pass a pointer to a function and modify that pointer in the function, we can not say

Type * ptr = new Type;
Test(ptr);

void Test(Type * ptr0)
{…}

Because the pointer is passed by value, and the original pointer “ptr” will keep unchanged even if you change the “ptr0″ in the function. You have to pass the pointer by reference:

Test(&ptr);

void Test(Type ** ptr0)
{
(*ptr0) = &x;

}

In this way you can use “(*ptr0)” to access the original pointer “ptr”.

5.5 Receive array with pointer

When passing the name of an array to a called function, the name of array is an address of the first element. Because of this, a pointer can be used in the called function to receive the array. Then by moving the pointer (e. g. pointer ++), all the rest array element can be accessed.

5.6 Pointer Expressions and Arithmetic

There are three kinds of arithmetic operations that can be done to a pointer:

=>Increment
pointer ++ / - -;
pointer += / -= 3;

It means moving the pointer to the next or previous 3rd element, not just increase the value of the pointer by 3. If the size of the variable to which the pointer is pointing to is 4, then actually the value of the pointer will be increased by 3 x 4.

=>Difference

int x = pointer2 - pointer1;

If pointer1 is pointing to the 5th element and pointer2 the 8th, then x will be 3, not 3×4 (suppose the type size is 4).

=>Assignment

pointer1 = pointer2;

pointer1 and pointer2 must be of the same type, otherwise a cast operator must be used to convert the type of the pointer. The only exception is when pointer1 is declared to be type void (i.e., void *). Any type of pointer can be assigned to a pointer to void without casting. However, it can not be conversed.

=>Comparison

The two pointers for comparison must be pointing to the same array. The result may show which one is pointer to a higher-numbered element.
Pointer arithmetic (including increment and difference) is meaningless unless performed on one array, because we are only sure that array elements are located one after another. We can not assume two separate variables are put together in the memory.

The following four expressions is doing the same thing:

cout << array[4];
cout << *(array + 4);
cout << arrayptr1[4];
cout << *(arrayPtr1 + 4);

5.7 Pointer Offset and Subscription

The reason pointer concept is created initially is not to point to a single primitive, but to manipulate arrays and strings and custom types.

When a pointer is pointed to an array or a string, it is actually pointed to the first element of the array (subscription 0). To refer to the elements in the following array, a pointer offset or subscription can be used:

int b[5];
int * ptr = b;
*(ptr + 3) = 7;
// or ptr[3] = 7;

*(ptr+3) refers to the element with subscrip 3 (4th element). This element can also be represented by ptr[3].

If you point a pointer to the middle of an array, what will happen? The pointer can be used as the name of a new array, whose first element is the element to which the pointer is pointing to.

#include <iostream>
#include "conio.h"
 
int main()
{
   char * a1 = "0123456789";
   int a2[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
   char * ptr1 = &a1[3];
   int * ptr2 = &a2[3];
   cout << "ptr1[0] = " << ptr1[0] << ",  ptr1[6] = " << ptr1[6] << endl;
   cout << "ptr2[0] = " << ptr2[0] << ",  ptr2[6] = " << ptr2[6] << endl;
   cout << "\n Press any key to quit..." << endl;
   getch();
}

Output result:

ptr1[0] = 3, ptr1[6] = 9
ptr2[0] = 3, ptr2[6] = 9

5.8 “sizeof” Operator

C++ provides unary operator “sizeof” to determine the number of bytes occupied by an array, variable, constant or type name such as “int”, “char”, etc. When applied to type name, “( )” is needed:

int a, b, n;
float x, array [3];
a = sizeof x;
b = sizeof array;
n = sizeof array / sizeof (float);

The last statement is to find the number of elements of an array.
Notice that the number of bytes for a certain type is different for different systems. C++ is platform-dependent, not like Java.

5.9 Size of String and Character Array

Although a string char * and a character array char [ ] can be used in the same way in most cases, when it comes to size issue, they are different. The size of the char * is the size of the char pointer, while the size of the char array is the size of the array:

char * temp1 = "abcdefg";
char temp2[20] = "abcdefg";
char temp3[30] = "abcdefg";
 
cout << "Size of a char * is " << sizeof temp1
<< ", size of char[20] is " << sizeof temp2
<< ", size of char[30] is " << sizeof temp3 << endl;

Output will be:
Size of a char * is 4, size of char[20] is 20, size of char[30] is 30

5.10 Function Pointer and Call Back Technique

Function pointers are mainly used to achieve call back technique, which will be discussed right after.

Just like an array name is the address of the first array element, a function name is a actually the starting address of the function code. A function pointer holds the address of a function, just like a float pointer holds the address of a float variable. A function pointer can point to either a global function or a class function.

=>Global function pointer
#include "stdafx.h"
 
typedef void(*CALLBACK_FUNCTION)(int);  // define the function pointer
 
void Bark(int nRepeat)  // actual function to be passed to the pointer
{
  for(int i = 0; i < nRepeat; i++)
    printf("Bark!\n");
}
 
void Cry(int nRepeat)  // actual function to be passed to the pointer
{
  for(int i = 0; i < nRepeat; i++)
    printf("Woooo!\n");
}
 
void Server(CALLBACK_FUNCTION m, int nRepeat)
{
  (*m)(nRepeat);  // Invoking through function pointer
  printf("\n");
}
 
int main(int argc, char* argv[])
{
  Server(&Bark, 2);  // passing function pointer
  Server(&Cry, 3);
  return 0;
}

Bark!
Bark!

Woooo!
Woooo!
Woooo!

=>Class method pointer
#include "stdafx.h"
 
class Creature {};
 
typedef char * (Creature::*CALLBACK_METHOD)(int); // define the function pointer
 
class Cat : public Creature {
public:
    Cat(char * name) : m_name(name) {}
 
    char * Miao(int nRepeat)  // actual function to be passed to the pointer
    {
        for(int i = 0; i < nRepeat; i++)
            printf("Miao!\n");
        return m_name;
    }
 
private:
    char * m_name;
};
 
class Snake : public Creature {
public:
    Snake(char * name) : m_name(name) {}
 
    char * Ssss(int nRepeat)  // actual function to be passed to the pointer
    {
        for(int i = 0; i < nRepeat; i++)
            printf("Ssss!\n");
        return m_name;
    }
 
private:
    char * m_name;
};
 
void Server(Creature * pCreature, CALLBACK_METHOD m, int nRepeat)
{
    char * name = (pCreature->*m)(nRepeat); // invoking through function pointer
    char buf[80];
    sprintf(buf, "My name is %s!\n\n", name);
    printf(buf);
}
 
int main(int argc, char* argv[])
{
    Cat * pCat = new Cat("Jessy");
    Snake * pSnake = new Snake("John");
    Server(pCat, (CALLBACK_METHOD)&Cat::Miao, 2);  // passing function pointer
    Server(pSnake, (CALLBACK_METHOD)&Snake::Ssss, 3);
    return 0;
}

Miao!
Miao!
My name is Jessy!

Ssss!
Ssss!
Ssss!
My name is John!

=>Call Back Technique

Callback technique is an effort to seperate what from how . When you want your program to be applicable to different use cases, you may find that at a certian point in your program, you need to invoke a function which has the same signature but different implementation for different use cases. In other words, for every specific case, the type of information passed to and returned by that function (which represents what ) is the same, but the implementation of the function (which represents how ) is different. Different client (who uses your program) may have different implementations.

In this case, you have to provide a mechanism so that the client may register his own version of that function to your program before the invoking point, so that your program knows which one to call. This technique is called callback .

There are three ways to achieve call-back.

The OO approach of callback is to let the client class inherit from and implement an interface. In your code you simply hold an interface pointer and call the interface methods through that pointer. The client program will create his implementation object, assign its pointer to the interface pointer in your class before the calling pointer in your class is reached.

However, if the client class is already finished and did not implement the interface you want, you have to use a less OO approach. The client programmer (or your do it for him) should write a separate matching class for the client class, which inherit from the desired interface with the function you will call back, which provides the service you need by manipulating client-class objects. In your code you have a pointer to the interface and invoke the service provided by the separate class.

The least OO approach is to use function pointers like the above example.

5.11 Array of Function Pointer and Menu-Driven System

One use of function pointers is in menu-driven systems. The user is prompted to select an option from a menu (e.g., 1~5). Each option is severed by a different function. Pointers to different functions is stored in an array of function pointers. The user s choice is used as a subscript of the array.

 
int fun1();
int fun2();
int fun3();
int fun4();
int fun5();
 
int main()
{
   char (*funPtr[3])(int) = {fun1, fun2, fun3};
   int choice = 0;
 
   while(choice != -1)
   {
      (*funPtr[choice])();
      chin >> choice;
   }
}

The limitation this application is that all the functions should have the same signature and return type.

5.12 Power and Flexibility of Pointers

Pointers in C++ is a very powerful tool. It is extremely flexible and therefore can generate every kind of errors if misused.

For example, you can only cast an object of a sub-class to its super-class. No other casting between user-defined classes is allowed. However, pointers to different classes can be cast to each other without any restrict. What is passed in the casting is only the address. So you can cast a pointer to an integer to a Employee-class pointer. Then the Employee pointer will just assume that starting from the passed address it can find all attributes of Employee class.

 
class Person {
public:
      Person(char * = 0, int = 0, bool = true, char = ' ');
      void print() const;
      void doNothing();
private:
      char * name;
      int age;
      bool genda;
      char blood;
};
 
Person::Person(char * n, int a, bool g, char b)
    : name(n), age(a), genda(g), blood(b) {}
 
void Person::print() const
{
      cout << name << " " << age << " " << genda << " "
         << blood << endl;
}
 
void Person::doNothing() {}
 
class Employee {
public:
      Employee(char * = 0, int = 0, bool = true, char * = 0);
      void display();
private:
      char * empName;
      int empAge;
      bool empGenda;
      char * empTitle;
};
 
Employee::Employee(char * n, int a, bool g, char * t)
    : empName(n), empAge(a), empGenda(g), empTitle(t) {}
 
void Employee::display()
{
      cout << empName << " " << empAge << " " << empGenda
         << " " << empTitle << endl;
}
 
int main(int argc, char* argv[])
{
      Derived d1(1234, 5678);
      Derived d2(d1);
      d2.print();
      cout << endl;
 
      Person p1("Frank", 34, true, 'O');
      Employee e1("Frank", 34, true, "Engineer");
 
      Person * p2 = (Person *)&e1;
      Employee * e2 = (Employee *)&p1;
 
      cout << "p1 = ";
      p1.print();
      cout << "p2 = (Person *)&e1 = ";
      p2->print();
 
      cout << "\ne1 = ";
      e1.display();
      cout << "e2 = (Employee *)&p1 = ";
      e2->display();
 
      return 0;
}

Output will be:

p1 = Frank 34 1
p2 = (Person *)&e1 = Frank 34 1

e1 = Frank 34 1 Engineer
e2 = (Employee *)&p1 = Frank 34 1 -?@

Popularity: 14%

You need to log on to convert this article into PDF


Related Blog Items

No Comments

No comments yet.

Leave a comment

*
To prove you're a person (not a spam script), type the security word shown in the picture.
Anti-spam image