Expressions with Unary Operators

Unary operators are those operators that act on only one operand in an expression. The unary operators are:

Indirection operator (*)

Address-of operator (&)

Unary plus operator (+)

Unary negation operator ()

Logical NOT operator (!)

One's complement operator (~)

Preincrement operator (++)

Predecrement operator (––)

sizeof operator

new operator

delete operator

These operators have right-to-left associativity.

Syntax

unary-expression:
postfix-expression
++unary-expression
––unary-expression
unary-operator cast-expression
sizeofunary-expression
sizeof (type-name)
allocation-expression
deallocation-expression

unary-operator: one of
* & + – ! ~

Indirection Operator (*)

The unary indirection operator (*) “dereferences” a pointer; that is, it converts a pointer value to an l-value. The operand of the indirection operator must be a pointer to a type. The result of the indirection expression is the type from which the pointer type is derived. The use of the * operator in this context is different from its meaning as a binary operator, which is multiplication.

Address-Of Operator (&)

The unary address-of operator (&) takes the address of its operand. The address-of operator can be applied only to the following:

Functions (although its use for taking the address of a function is unnecessary)

L-values

qualified-names

In the first two cases listed above, the result of the expression is a pointer type (an r-value) derived from the type of the operand. For example, if the operand is of type char, the result of the expression is of type pointer to char. The address-of operator, applied to const or volatile objects, evaluates to const type * or volatile type *, where type is the type of the original object.

The result produced by the third case, applying the address-of operator to a qualified-name, depends on whether the qualified-name specifies a static member. If so, the result is a pointer to the type specified in the declaration of the member. If the member is not static, the result is a pointer to the member name of the class indicated by qualified-class-name. (See “Primary Expressions” for more about qualified-class-name.) The following code fragment shows how the result differs, depending on whether the member is static:

class PTM

{

public:

int iValue;

static float fValue;

};

int PTM::*piValue = &PTM::iValue; // OK: non-static

float PTM::*pfValue = &PTM::fValue; // Error: static

float *spfValue = &PTM::fValue; // OK

In the above example, the expression &PTM::fValue yields type float * instead of type float PTM::* because fValue is a static member.

The address of an overloaded function can be taken only when it is clear which version of the function is being referred to. See “Address Of Overloaded Functions” in Chapter 12, on topic for information about how to obtain the address of a particular overloaded function.

Applying the address-of operator to a reference type gives the same result as applying the operator to the object to which the reference is bound. The following program demonstrates this concept:

#include <iostream.h>

int main()

{

double d; // Define an object of type double.

double& rd = d; // Define a reference to the object.

// Compare the address of the object to the address

// of the reference to the object.

if( &d == &rd )

cout << “&d equals &rd” << “\n”;

else

cout << “&d is not equal to &rd” << “\n”;

return 0;

}

The output from the program is always &d equals &rd.

Unary Plus Operator (+)

The result of the unary plus operator (+) is the value of its operand. The operand to the unary plus operator must be of an arithmetic type.

Integral promotion is performed on integral operands. The resultant type is the type to which the operand is promoted. Thus, the expression +ch, where ch is of type char, results in type int; the value is unmodified.

See “Integral Promotions” in Chapter 3, on topic for more information about how the promotion is done.

Unary Negation Operator (-)

The unary negation operator () produces the negative of its operand. The operand to the unary negation operator must be of an arithmetic type.

Integral promotion is performed on integral operands, and the resultant type is the type to which the operand is promoted. See “Integral Promotions” in Chapter 3, on topic for more information on how the promotion is done.

Microsoft Specific

Unary negation of unsigned quantities is performed by subtracting the value of the operand from 2n, where n is the number of bits in an object of the given unsigned type. (Microsoft C++ runs on processors that utilize two's-complement arithmetic. On other processors, the algorithm for negation can differ.) ¨

Logical NOT Operator (!)

The result of the logical NOT operator (!) is 0 if its operand evaluates to a nonzero value; the result is 1 only if the operand is equal to 0. The operand must be of arithmetic or pointer type. The result is of type int.

For an expression e, the unary expression !e is equivalent to the expression (e == 0), except where overloaded operators are involved.

One's Complement Operator (~)

The one's complement operator (~), sometimes called the “bitwise complement” operator, yields a bitwise one's complement of its operand. That is, every bit that is set in the operand is 0 in the result. Conversely, every bit that is 0 in the operand is set in the result. The operand to the one's complement operator must be an integral type.

Integral promotion is performed on integral operands, and the resultant type is the type to which the operand is promoted. See “Integral Promotions” in Chapter 3, on topic for more information on how the promotion is done.

Increment and Decrement Operators (++, --)

The prefix increment operator (++), also called the “preincrement” operator, adds one to its operand; this incremented value is the result of the expression. The operand must be an l-value not of type const. The result is an l-value of the same type as the operand.

The prefix decrement operator (––), also called the “predecrement” operator, is analogous to the preincrement operator, except that the operand is decremented by one and the result is this decremented value.

Both the prefix and postfix increment and decrement operators affect their operands. The key difference between them is when the increment or decrement takes place in the evaluation of an expression. (For more information, see “Postfix Increment and Decrement Operators”). In the prefix form, the increment or decrement takes place before the value is used in expression evaluation, so the value of the expression is different from the value of the operand. In the postfix form, the increment or decrement takes place after the value is used in expression evaluation, so the value of the expression is the same as the value of the operand.

Because increment and decrement operators have side effects, using expressions with increment or decrement operators in a macro can have undesirable results (see “The Role of Preprocessing in C++” in Chapter 13, on topic for more information about macros). Consider this example:

#define max(a,b) ((a)<(b))?(b):(a)

...

int i, j, k;

k = max( ++i, j );

In the code fragment above, the macro expands to:

k = ((++i)<(j))?(j):(++i);

If i is greater than or equal to j, it will be incremented twice.

Note:

C++ inline functions are preferable to macros in many cases because they eliminate side effects such as those described above, and they allow the language to perform more complete type checking.

sizeof Operator

The sizeof operator yields the size of its operand with respect to the size of type char (the size in chars). The result of the sizeof operator is of type size_t, an integral type defined in the include file STDDEF.H. The operand to sizeof can be one of the following:

A type name. To use sizeof with a type name, the name must be enclosed in parentheses.

An expression. When used with an expression, sizeof can be specified with or without the parentheses. The expression is not evaluated.

When the sizeof operator is applied to an object of type char, it yields 1. When the sizeof operator is applied to an array, it yields the total number of bytes in that array. For example:

#include <iostream.h>

int main()

{

char szHello[] = “Hello, world!”;

cout << “The size of the type of ” << szHello << “ is: ”

<< sizeof( char ) << “\n”;

cout << “The length of ” << szHello << “ is: ”

<< sizeof szHello << “\n”;

return 0;

}

The program output is:

The size of the type of Hello, world! is: 1

The length of Hello, world! is: 14

When the sizeof operator is applied to a class, struct, or union type, the result is the number of bytes in an object of that class, struct, or union type, plus any padding added to align members on word boundaries. (The /Zp compiler option and the pack pragma affect alignment boundaries for members.) The sizeof operator never yields 0, even for an empty class.

The sizeof operator cannot be used with the following operands:

Functions. (However, sizeof can be applied to pointers to functions.)

Bit fields.

Undefined classes.

The type void.

Incomplete types.

Parenthesized names of incomplete types.

When the sizeof operator is applied to a reference, the result is the same as if sizeof had been applied to the object itself.

The sizeof operator is often used to calculate the number of elements in an array using an expression of the form:

sizeof array / sizeof array[0]

new Operator

The new operator attempts to dynamically allocate (at run time) one or more objects of type-name. The new operator cannot be used to allocate a function; however, it can be used to allocate a pointer to a function.

Syntax

allocation-expression:
::optnew modeloptplacementoptnew-type-name new-initializeropt
::optnewmodeloptplacementopt( type-name)new-initializeropt

placement:
( expression-list )

new-type-name:
type-specifier-list new-declaratoropt

The new operator is used to allocate objects and arrays of objects. The new operator allocates from an area of program memory called the “free store.” In C, the free store is often referred to as the “heap.”

When new is used to allocate a single object, it yields a pointer to that object; the resultant type is new-type-name * or type-name *. When new is used to allocate a singly dimensioned array of objects, it yields a pointer to the first element of the array, and the resultant type is new-type-name * or type-name *. When new is used to allocate a multiply dimensioned array of objects, it yields a pointer to the first element of the array, and the resultant type preserves the size of all but the left-most array dimension. For example:

new float[10][25][10]

yields type float (*)[25][10]. Therefore, the following code will not work because it attempts to assign a pointer to an array of float with the dimensions [25][10] to a pointer to type float:

float *fp;

fp = new float[10][25][10];

The correct expression is:

float (*cp)[25][10];

cp = new float[10][25][10];

The definition of cp allocates a pointer to an array of type float with dimensions [25][10]—it does not allocate an array of pointers.

All array dimensions but the leftmost must be constant expressions that evaluate to positive values; the leftmost array dimension may be any expression that evaluates to a positive value. When allocating an array using the new operator, the first dimension can be zero—the new operator returns a unique pointer.

The type-specifier-list may not contain const, volatile, class declarations, or enumeration declarations. Therefore, the following expression is illegal:

volatile char *vch = new volatile char[20];

The new operator does not allocate reference types because they are not objects.

Lifetime of Objects Allocated with new

Objects allocated with the new operator are not destroyed when the scope in which they are defined is exited. Because the new operator returns a pointer to the objects it allocates, the program must define a pointer with suitable scope to access those objects. For example:

int main()

{

// Use new operator to allocate an array of 20 characters.

char *AnArray = new char[20];

for( int i = 0; i < 20; ++i )

{

// On the first iteration of the loop, allocate

// another array of 20 characters.

if( i == 0 )

{

char *AnotherArray = new char[20];

}

...

}

delete AnotherArray; // Error: pointer out of scope.

delete AnArray; // OK: pointer still in scope.

}

By letting the pointer AnotherArray go out of scope in the above example, the programmer has allocated an object that can no longer be deleted.

Initializing Objects Allocated with new

An optional new-initializer field is included in the syntax for the new operator. This allows new objects to be initialized with user-defined constructors. For more information about how initialization is done, see “Initializers” in Chapter 7, on topic .

The following example illustrates how to use an initialization expression with the new operator:

#include <iostream.h>

class Acct

{

public:

// Define default constructor and a constructor that accepts

// an initial balance.

Acct() { balance = 0.0; }

Acct( double init_balance ) { balance = init_balance; }

private:

double balance;

};

int main()

{

Acct *CheckingAcct = new Acct;

Acct *SavingsAcct = new Acct ( 34.98 );

double *HowMuch = new double ( 43.0 );

...

return 0;

}

In the example above, the object CheckingAcct is allocated using the new operator, but no default initialization is specified. Therefore, the default constructor for the class, Acct(), is called. Then, the object SavingsAcct is allocated the same way, except that it is explicitly initialized to 34.98. Because 34.98 is of type double, the constructor that takes an argument of that type is called to handle the initialization. Finally, the nonclass type HowMuch is initialized to 43.0.

If an object is of a class type, and that class has constructors (as in the above example), the object can be initialized by the new operator only if one of these conditions is met:

The arguments provided in the initializer agree with those of a constructor

The class has a default constructor (a constructor that can be called with no arguments)

Access control and ambiguity control are performed on operator new and on the constructors according to the rules set forth in “Ambiguity” in Chapter 9, on topic and “Initialization Using Special Member Functions” in Chapter 11, on topic .

No explicit per-element initialization can be done when allocating arrays using the new operator; only the default constructor, if present, is called. (Note that a default constructor is a constructor that takes no arguments. Constructors declared with all default arguments are default constructors.)

If the memory allocation fails (operator new returns a value of 0), no initialization is performed. This protects against attempts to initialize data that does not exist.

As with function calls, the order of evaluation of initialization expressions is not defined. Furthermore, it is unsafe to rely on these expressions being completely evaluated before the memory allocation is performed. If the memory allocation fails and the new operator returns zero, it is possible that not all expressions in the initializer are completely evaluated.

How new Works

The allocation-expression—the expression containing the new operator—does three things:

Locates and reserves storage for the object or objects to be allocated. When this stage is complete, the correct amount of storage is allocated, but it is not yet an object.

Initializes the object(s). Once initialization is complete, enough information is present for the allocated storage to be an object.

Returns a pointer to the object(s) of a pointer type derived from new-type-name or type-name. This pointer is used by the program to access the newly allocated object.

The new operator actually invokes the function operator new. For arrays of any type, and for objects that are not of class, struct, or union types, a global function, ::operator new, is called to allocate storage. Class-type objects can define their own operator new on a per-class basis.

When the compiler encounters the new operator to allocate an object of type type, it issues a call to type::operator new( sizeof( type ) ), or if no user-defined operator new is defined, ::operator new( sizeof( type ) ). Therefore, the new operator can allocate the correct amount of memory for the object.

Note:

The argument to operator new is of type size_t. This type is defined in DIRECT.H, MALLOC.H, MEMORY.H, SEARCH.H, STDDEF.H, STDIO.H, STDLIB.H, STRING.H, and TIME.H.

An option in the syntax allows specification of placement (see the new operator syntax on topic ). The placement field can be used only for user-defined implementations of operator new; it allows extra information to be passed to operator new. An expression with a placement field such as:

T *TObject = new ( 0x0040 ) T;

is translated to

T *TObject = T::operator new( sizeof( T ), 0x0040 );

The original intention of the placement field was to allow hardware-dependent objects to be allocated at user-specified addresses.

Note:

Although the example above shows only one argument in the placement field, there is no restriction on how many extra arguments can be passed to operator new this way.

Even when operator new has been defined for a class type, the global operator can be used by using the form of this example:

T *TObject =::new TObject;

The scope-resolution operator (::) forces use of the global new operator.

delete Operator

The delete operator deallocates an object created with the new operator. The
delete
operator has a result of type void and therefore does not return a value.
The operand to delete must be a pointer returned by the new operator.

Using delete on a pointer to an object not allocated with new gives unpredictable results. You can, however, use delete on a pointer with the value 0. This provision means that, because new always returns 0 on failure, deleting the result of a failed new operation is harmless.

Syntax

deallocation-expression:
::optdeletecast-expression
::optdelete [ ]cast-expression

Using the delete operator on an object deallocates its memory. A program that dereferences a pointer after the object is deleted can have unpredictable results or crash.

If the operand to the delete operator is a modifiable l-value, its value is undefined after the object is deleted.

Pointers to const objects cannot be deallocated with the delete operator.

How delete Works

The delete operator actually invokes the function operator delete. For objects of class types (class, struct, and union), the delete operator invokes the destructor for an object prior to deallocating memory (if the pointer is not null). For objects not of class type, the global delete operator is invoked. For objects of class type, the delete operator can be defined on a per-class basis; if there is no such definition for a given class, then the global operator is invoked.

Microsoft Specific

Microsoft C++ allows multiple delete operators to be present for a given class type—one for each addressing option. This guarantees that the correct object is deleted when the delete operator is invoked in the program. See “Declaring Destructors” in Chapter 11, on topic , “Overloaded Operators” in Chapter 12, on topic , and “Memory-Model Specifiers and Overloading” in Appendix B, on topic

Using delete

There are two syntactic variants for the delete operator: one for single objects and the other for arrays of objects. The following code fragment shows how these differ:

int main()

{

// Allocate a user-defined object, UDObject, and an object

// of type double on the free store using the

// new operator.

UDType *UDObject = new UDType;

double *dObject = new double;

...

// Delete the two objects.

delete UDObject;

delete dObject;

...

// Allocate an array of user-defined objects on the

// free store using the new operator.

UDType (*UDArr)[7] = new UDType[5][7];

...

// Use the array syntax to delete the array of objects.

delete [] UDArr;

return 0;

}

These two cases produce undefined results: using the array form of delete (delete [ ]) on an object, and using the nonarray form of delete on an array.