New types derived directly from existing types are types that point to, refer to, or (in the case of functions) transform type data to return a new type.
Arrays of variables or objects can contain a specified number of a particular type. For example, an array derived from integers is an array of type int. The following code sample declares and defines an array of 10 int variables and an array of 5 objects of class SampleClass
:
int ArrayOfInt[10];
SampleClass aSampleClass[5];
Functions take zero or more arguments of given types and return objects of a specified type (or return nothing, if the function has a void return type).
Pointers to variables or objects select an object in memory. The object can be global, local (or stack-frame), or dynamically allocated. Pointers to functions of a given type allow a program to defer selection of the function used on a particular object or objects until run time. The following example shows a definition of a pointer to a variable of type char:
char *szPathStr;
References to objects provide a convenient way to access objects by reference but use the same syntax required to access objects by value. The following example demonstrates how to use references as arguments to functions and as return types of functions:
BigClassType &func( BigClassType &objname )
{
objname.DoSomething(); // Note that member-of operator(.)
// is used.
objname.SomeData = 7; // Data passed by non-const
// reference is modifiable.
return objname;
}
The important points about passing objects to a function by reference are:
Additionally, functions that return a reference need only accept the address of the object to which they refer, instead of a copy of the whole object.
Although the preceding example describes references only in the context of communication with functions, references are not constrained to this use. Consider, for example, a case where a function needs to be an l-value — a common requirement for overloaded operators:
class Vector
{
public:
Point &operator[]( int nSubscript ); // Function returns a
// reference type
...
};
The preceding declaration specifies a user-defined subscript operator for class Vector
. In an assignment statement, two possible conditions occur:
Vector v1;
int i;
Point p;
v1[7] = p; // Vector used as an l-value
p = v1[7]; // Vector used as an r-value
The latter case, where v1[7]
is used as an r-value, can be implemented without use of references. However, the former case, where v1[7]
is used as an l-value, cannot be implemented easily without functions that are of reference type. Conceptually, the last two statements in the preceding example translate to the following code:
v1.operator[]( 7 ) = 3; // Vector used as an l-value
i = v1.operator[]( 7 ); // Vector used as an r-value
When viewed in this way, it is easier to see that the first statement must be an l-value to be semantically correct on the left side of the assignment statement.
For more information about overloading, and about overloaded operators in particular, see Overloaded Operators in Chapter 12.
You can also use references to declare a const reference to a variable or object. A reference declared as const retains the efficiency of passing an argument by reference, while preventing the called function from modifying the original object. Consider the following code:
// IntValue is a const reference.
void PrintInt( const int &IntValue )
{
printf( "%d\n", IntValue );
}
Reference initialization is different from assignment to a variable of reference type. Consider the following code:
int i = 7;
int j = 5;
// Reference initialization
int &ri = i; // Initialize ri to refer to i.
int &rj = j; // Initialize rj to refer to j.
// Assignment
ri = 3; // i now equal to 3.
rj = 12; // j now equal to 12.
ri = rj; // i now equals j (12).
See Literals in Chapter 1 for more information about the various kinds of constants allowed in C++.
These pointers define a type that points to a class member of a particular type. Such a pointer can be used by any object of the class type or any object of a type derived from the class type.
Use of pointers to class members enhances the type safety of the C++ language. Three new operators and constructs are used with pointers to members, as shown in Table 2.5.
Table 2.5 Operators and Constructs Used with Pointers to Members
Operator or Construct |
Syntax |
Use |
::* | type::*ptr-name | Declaration of pointer to member. The type specifies the class name, and ptr-name specifies the name of the pointer to member. Pointers to members can be initialized. For example: |
|
||
.* | obj-name.*ptr-name | Dereference a pointer to a member using an object or object reference. For example: |
|
||
–>* | obj-ptr–>*ptr-name | Dereference a pointer to a member using a pointer to an object. For example: |
|
Consider this example that defines a class AClass
and the derived type pDAT
, which points to the member I1
:
#include <iostream.h>
// Define class AClass.
class AClass
{
public:
int I1;
Show() { cout << I1 << "\n"; }
};
// Define a derived type pDAT that points to I1 members of
// objects of type AClass.
int AClass::*pDAT = &AClass::I1;
void main()
{
AClass aClass; // Define an object of type AClass.
AClass *paClass = &aClass; // Define a pointer to that object.
int i;
aClass.*pDAT = 7777; // Assign to aClass::I1 using .* operator.
aClass.Show();
i = paClass->*pDAT; // Dereference a pointer
// using ->* operator.
cout << i << "\n";
}
The pointer to member pDAT
is a new type derived from class AClass
. It is more strongly typed than a “plain” pointer to int because it points only to int members of class AClass
(in this case, I1
). Pointers to static members are plain pointers rather than pointers to class members. Consider the following example:
class HasStaticMember
{
public:
static int SMember;
};
int HasStaticMember::SMember = 0;
int *pSMember = &HasStaticMember::SMember;
Note that the type of the pointer is “pointer to int,” not “pointer to HasStaticMember::int
.”
Pointers to members can refer to member functions as well as member data. Consider the following code:
#include <stdio.h>
// Declare a base class, A, with a virtual function, Identify.
// (Note that in this context, struct is the same as class.)
struct A
{
virtual void Identify() = 0; // No definition for class A.
};
// Declare a pointer to the Identify member function.
void (A::*pIdentify)() = &A::Identify;
// Declare class B derived from class A.
struct B : public A
{
void Identify();
};
// Define Identify functions for classe B
void B::Identify()
{
printf( "Identification is B::Identify\n" );
}
void main()
{
B BObject; // Declare objects of type B
A *pA; // Declare pointer to type A.
pA = &BObject; // Make pA point to an object of type B.
(pA->*pIdentify)(); // Call Identify function through pointer
// to member pIdentify.
}
The output from this program is:
Identification is B::Identify
The function is called through a pointer to type A
. However, because the function is a virtual function, the correct function for the object to which pA
refers is called.