Functions

This section explains function declarations. It includes discussions on:

Function declaration (prototyping) syntax.

Declaration of functions that take a varying number of arguments.

Declaration of functions that require no arguments.

Overloading of functions (an introduction).

Restrictions on function declarations.

Specification of argument lists.

Default arguments to functions.

Function definition is covered in “Function Definitions”.

Syntax

decl-specifiers dname(argument-declaration-list)cv-mod-listopt

argument-declaration-list:
arg-declaration-list, ...

arg-declaration-list:
argument-declaration
arg-declaration-list
,argument-declaration

argument-declaration:
decl-specifiers declarator
decl-specifiers declarator
=expression
decl-specifiers abstract-declarator
opt
decl-specifiers abstract-declaratoropt=expression

The identifier given by dname has the type “cv-mod-list function, taking argument-declaration-list, and returning type decl-specifiers.”

Note that the keywords const, volatile, and many of the Microsoft-specific keywords can appear in cv-mod-list and in the declaration of the name. The following example shows several simple function declarations:

char *strchr( char *dest, char *src );

static int atoi( const char *ascnum ) const;

The following syntax explains the details of a function declaration:

Syntax

argument-declaration-list:
arg-declaration-listopt...opt
arg-declaration-list, ...

arg-declaration-list:
argument-declaration
arg-declaration-list
,argument-declaration

argument-declaration:
decl-specifiers declarator
decl-specifiers declarator
=expression
decl-specifiers abstract-declarator
opt
decl-specifiers abstract-declaratoropt=expression

Variable Argument Lists

Function declarations in which the last member of argument-declaration-list is the ellipsis (...) can take a variable number of arguments. In these cases, C++ provides type checking only for the explicitly declared arguments. Variable argument lists can be used when it is desirable to make a function so general that even the number of arguments can vary. The printf family of functions is an example of functions that use variable argument lists.

To access arguments after those declared, use the macros contained in the standard include file STDARG.H as described in “Functions with Variable Argument Lists”.

Microsoft Specific

Microsoft C++ does not allow the ellipsis to be specified as an argument other than the first if there is no comma preceding the ellipsis. Therefore, the declaration int Func( int i, ... );is legal, but the declaration int Func( int i ... );is not.¨Declaration of a function that takes a variable number of arguments requires that at least one “placeholder” argument be supplied, even if it is not used. If this placeholder argument is not supplied, there is no way to access the remaining arguments.

When arguments of type char are passed as variable arguments, they are converted to type int. Similarly, when arguments of type float are passed as variable arguments, they are converted to type double.

Declaring Functions that Take No Arguments

A function declared with the single keyword void in argument-declaration-list takes no arguments, as long as the keyword void is the first and only member of argument-declaration list. Arguments of type void elsewhere in argument-declaration-list produce errors. For example:

long GetTickCount( void ); // OK

long GetTickCount( int Reset, void ); // Error

long GetTickCount( void, int Reset ); // Error

In C++, explicitly specifying that a function requires no arguments is the same as declaring a function with no argument-declaration-list. Therefore, the following two statements are identical:

long GetTickCount();

long GetTickCount( void );

Note that, while it is illegal to specify a void argument except as outlined above, types derived from type void (such as pointers to void and arrays of void) can appear anywhere in argument-declaration-list.

Function Overloading

C++ allows specification of more than one function of the same name in the same scope. These are called “overloaded functions” and are described in detail in Chapter 12, “Overloading.” The purpose of overloaded functions is to allow programmers to supply different semantics for a function depending on the types and number of arguments.

For example, a print function that takes a string (or char *) argument performs very different tasks than one that takes an argument of type double. Overloading permits uniform naming and prevents programmers from having to invent names such as print_sz or print_d. Table 7.1 shows what parts of a function declaration C++ uses to differentiate between groups of functions with the same name in the same scope.

Table 7.1 Overloading Considerations

Function Declaration Element Used for Overloading?

Function return type No
Number of arguments Yes
Type of arguments Yes
Presence or absence of ellipsis Yes
Use of typedef names No
Unspecified array bounds No
const or volatile (in cv-mod-list) Yes
__near, __far, or __huge (in cv-mod-list), Yes  

Note:

Although functions can be distinguished on the basis of return type, they cannot be overloaded on this basis.

The following example illustrates how overloading can be used. Another way to solve the same problem is presented in “Default Arguments”.

#include <iostream.h>

#include <math.h>

#include <stdlib.h>

// Prototype three print functions.

int print( char *s ); // Print a string.

int print( double dvalue ); // Print a double.

int print( double dvalue, int prec ); // Print a double with a

// given precision.

main( int argc, char *argv[] )

{

const double d = 893094.2987;

if( argc < 2 )

{

// These calls to print invoke print( char *s ).

print( "This program requires one argument." );

print( "The argument specifies the number of" );

print( "digits precision for the second number" );

print( "printed." );

}

// Invoke print( double dvalue ).

print( d );

// Invoke print( double dvalue, int prec ).

print( d, atoi( argv[1] ) );

return 0;

}

// Print a string.

int print( char *s )

{

cout << s << endl;

return cout.good();

}

// Print a double in default precision.

int print( double dvalue )

{

cout << dvalue << endl;

return cout.good();

}

// Print a double in specified precision.

// Positive numbers for precision indicate how many digits

// precision after the decimal point to show. Negative

// numbers for precision indicate where to round the number

// to the left of the decimal point.

int print( double dvalue, int prec )

{

// Use table-lookup for rounding/truncation.

static const double rgPow10[] = {

10E-7, 10E-6, 10E-5, 10E-4, 10E-3, 10E-2, 10E-1, 10E0,

10E1, 10E2, 10E3, 10E4, 10E5, 10E6

};

const int iPowZero = 6;

// If precision out of range, just print the number.

if( prec < -6 || prec > 7 )

return print( dvalue );

// Scale, truncate, then rescale.

dvalue = floor( dvalue / rgPow10[iPowZero - prec] ) *

rgPow10[iPowZero - prec];

cout << dvalue << endl;

return cout.good();

}

The preceding code shows overloading of the print function in file scope.

For restrictions on overloading and information on how overloading affects other elements of C++, see Chapter 12, “Overloading.”

Restrictions on Functions

Functions cannot return arrays or functions. They can, however, return references or pointers to arrays or functions. Another way to return an array is to declare a structure with only that array as a member:

struct Address

{ char szAddress[31]; };

Address GetAddress();

It is illegal to define a type in either the return-type portion of a function declaration or in the declaration of any argument to a function. The following legal C code is illegal in C++:

enum Weather { Cloudy, Rainy, Sunny } GetWeather( Date Today )

The reason the preceding code is disallowed is because the type Weather has function scope local to GetWeather, and the return value cannot be properly used. Because arguments to functions have function scope, declarations made within the argument list would have the same problem if not allowed.

C++ does not support arrays of functions. However, arrays of pointers to functions can be useful. In parsing a Pascal-like language, the code is often separated into a lexical analyzer that parses tokens and a parser that attaches semantics to the tokens. If the analyzer returns a particular ordinal value for each token, code can be written to perform appropriate processing:

int ProcessFORToken( char *szText );

int ProcessWHILEToken( char *szText );

int ProcessBEGINToken( char *szText );

int ProcessENDToken( char *szText );

int ProcessIFToken( char *szText );

int ProcessTHENToken( char *szText );

int ProcessELSEToken( char *szText );

int (*ProcessToken[])( char * ) = {

ProcessFORToken, ProcessWHILEToken, ProcessBEGINToken,

ProcessENDToken, ProcessIFToken, ProcessTHENToken,

ProcessELSEToken };

const int MaxTokenID = sizeof ProcessToken / sizeof( int (*)() );

...

int DoProcessToken( int TokenID, char *szText )

{

if( TokenID < MaxTokenID )

return (*ProcessToken[TokenID])( szText );

else

return Error( szText );

}

The Argument Declaration List

The argument-declaration-list portion of a function declaration:

Allows the compiler to check type consistency between the arguments the function requires and the arguments supplied in the call.

Enables conversions, either implicit or user-defined, to be performed from the argument type supplied to the required argument type.

Checks initializations of or assignments to pointers to functions.

Checks initializations of or assignments to references to functions.

Argument Lists in Function Prototypes (Nondefining Declaration)

The form of argument-declaration-list is a list of the type names of the arguments. Consider an argument-declaration-list for a function, func, that takes these three arguments:

Pointer to type char

char

int

The code for such an argument-declaration-list can be written:

char *, char, int

The function declaration (the prototype), might therefore be written:

void func( char *, char, int );

While the preceding declaration contains enough information for the compiler to perform type checking and conversions, it does not provide much information about what the arguments are. A good way to document function declarations is to include identifiers as they would appear in the function definition, as in the following:

void func( char *szTarget, char chSearchChar, int nStartAt );

These identifiers in prototypes are useful only for default arguments, as they go out of scope immediately. They do, however, provide meaningful program documentation.

Argument Lists in Function Definitions

The argument list in a function definition differs from that of a prototype only in that the identifiers, if present, represent formal arguments to the function. The identifier names need not match those in the prototype (if there are any).

Note:

It is possible to define functions with unnamed arguments. However, these arguments are inaccessible to the function for which they are defined.