The usage of standard C functions
Normal C functions, e.g., which are compiled and collected in a run-time library, can also be used in C++ programs. Such functions however must be declared as C functions. As an example, the following code fragment declares a function xmalloc() which is a C function:
extern "C" void *xmalloc(unsigned size);
This declaration is analogous to a declaration in C, except that the prototype is prefixed with extern “C”. A slightly different way to declare C functions is the following:
extern "C"
{
.
. (declarations)
.
}
It is also possible to place preprocessor directives at the location of the declarations. E.g., a C header file myheader.h which declares C functions can be included in a C++ source file as follows:
extern "C"
{
# include <myheader.h>
}
The above presented methods can be used without problem, but are not very current. A more frequently used method to declare external C functions is presented below.
Header files for both C and C++
The combination of the predefined symbol __cplusplus and of the possibility to define extern “C” functions offers the ability to create header files for both C and C++. Such a header file might, e.g., declare a group of functions which are to be used in both C and C++ programs. The setup of such a header file is as follows:
#ifdef __cplusplus
extern "C"
{
#endif
.
. (the declaration of C-functions occurs
. here, e.g.:)
extern void *xmalloc(unsigned size); . #ifdef __cplusplus } #endif
Using this setup, a normal C header file is enclosed by extern “C” { which occurs at the start of the file and by }, which occurs at the end of the file. The #ifdef directives test for the type of the compilation: C or C++. The `standard’ header files, such as stdio.h, are built in this manner and therefore usable for both C and C++.
An extra addition which is often seen is the following. Usually it is desirable to avoid multiple inclusions of the same header file. This can easily be achieved by including an #ifndef directive in the header file. An example of a file myheader.h would then be:
#ifndef _MYHEADER_H_ #define _MYHEADER_H_ . . (the declarations of the header file follow here, . with #ifdef _cplusplus etc. directives) . #endif
When this file is scanned for the first time by the preprocessor, the symbol _MYHEADER_H_ is not yet defined. The #ifndef condition succeeds and all declarations are scanned. In addition, the symbol _MYHEADER_H_ is defined. When this file is scanned for a second time during the same compilation, the symbol _MYHEADER_H_ is defined. All information between the #ifndef and #endif directives is skipped. The symbol name _MYHEADER_H_ serves in this context only for recognition purposes. E.g., the name of the header file can be used for this purpose, in capitals, with an underscore character instead of a dot. Apart from all this, the custom has evolved to give C header files the extension .h, and to give C++
header files no extension. For example, the standard iostreams cin, cout and cerr are available after inclusing the preprocessor directive #include , rather than #include in a source. In the Annotations this convention is used with the standard C++ header files, but not everywhere else (yet).
The definition of local variables
In C local variables can only be defined at the top of a function or at the beginning of a nested block. In C++ local variables can be created at any position in the code, even between statements. Furthermore local variables can be defined in some statements, just prior to their usage. A typical example is the for statement:
#include <stdio.h>
int main()
{
for (register int i = 0; i < 20; i++)
printf("%d\n", i);
return (0);
}
In this code fragment the variable i is created inside the for statement. According to the ANSI-standard, the variable does not exist prior to the for-statement and not beyond the for-statement. With some compilers, the variable continues to exist after the execution of the for-statement, but a warning like warning: name lookup of `i’ changed for new ANSI `for’ scoping using obsolete binding at `i’ will be issued when the variable is used outside of the for-loop. The implication seems clear: define a variable just before the for-statement if it’s to be used beyond that statement, otherwise the variable can be defined at the for-statement itself. Defining local variables when they’re needed requires a little getting used to. However, eventually it tends to produce more readable code than defining variables at the beginning of compound statements.
We suggest the following rules of thumb for defining local variables:
- Local variables should be defined at the beginning of a function, following the first {,
- or they should be created at `intuitively right’ places, such as in the example above. This does not
only entail the for-statement, but also all situations where a variable is only needed, say, half-way
through the function.
Function Overloading
In C++ it is possible to define several functions with the same name, performing different actions. The functions must only differ in their argument lists. An example is given below:
#include <stdio.h>
void show(int val)
{
printf("Integer: %d\n", val);
}
void show(double val)
{
printf("Double: %lf\n", val);
}
void show(char *val)
{
printf("String: %s\n", val);
}
int main()
{
show(12);
show(3.1415);
show("Hello World\n!");
return (0);
}
In the above fragment three functions show() are defined, which only differ in their argument lists: int, double and char *. The functions have the same name. The definition of several functions with the same name is called `function overloading’. It is interesting that the way in which the C++ compiler implements function overloading is quite simple. Although the functions share the same name in the source text (in this example show()), the compiler — and hence the linker– use quite different names. The conversion of a name in the source file to an internally used name is called `name mangling’. E.g., the C++ compiler might convert the name void show (int) to the internal name VshowI, while an analogous function with a char* argument might be called VshowCP. The actual names which are internally used depend on the compiler and are not relevant for the programmer, except where these names show up in e.g., a listing of the contents of a library.
A few remarks concerning function overloading are:
The usage of more than one function with the same name but quite different actions should be avoided. In the example above, the functions show() are still somewhat related (they print information to the screen). However, it is also quite possible to define two functions lookup(), one of which would find a name in a list while the other would determine the video mode. In this case the two functions have nothing in common except for their name. It would therefore be more practical to use names which suggest the action; say, findname() and getvidmode().
- C++ does not allow that several functions only differ in their return value. This has the reason that
it is always the programmer’s choice to inspect or ignore the return value of a function. E.g., the fragment printf(”Hello World!\n”);
holds no information concerning the return value of the function printf() (The return value is, by the way, an integer which states the number of printed characters. This return value is practically never inspected.). Two functions printf() which would only differ in their return type could
therefore not be distinguished by the compiler.
- Function overloading can lead to surprises. E.g., imagine a statement like
show(0);
given the three functions show() above. The zero could be interpreted here as a NULL pointer to a char, i.e., a (char *)0, or as an integer with the value zero. C++ will choose to call the function expecting an integer argument, which might not be what one expects.
Default function arguments
In C++ it is possible to provide `default arguments’ when defining a function. These arguments are supplied by the compiler when not specified by the programmer. An example is shown below:
#include <stdio.h>
void showstring(char *str = "Hello World!\n")
{
printf(str);
}
int main()
{
showstring("Here's an explicit argument.\n");
showstring(); // in fact this says:
// showstring("Hello World!\n");
return (0);
}
The possibility to omit arguments in situations where default arguments are defined is just a nice touch: the compiler will supply the missing argument when not specified. The code of the program becomes by no means shorter or more efficient. Functions may be defined with more than one default argument:
void two_ints(int a = 1, int b = 4)
{
.
.
.
}
int main()
{
two_ints(); // arguments: 1, 4
two_ints(20); // arguments: 20, 4
two_ints(20, 5); // arguments: 20, 5
return (0);
}
When the function two_ints() is called, the compiler supplies one or two arguments when necessary. A statement as two_ints(,6) is however not allowed: when arguments are omitted they must be on the righthand side. Default arguments must be known to the compiler when the code is generated where the arguments may have to be supplied. Often this means that the default arguments are present in a header file:
// sample header file
extern void two_ints(int a = 1, int b = 4);
// code of function in, say, two.cc
void two_ints(int a, int b)
{
.
.
}
Note that supplying the default arguments in the function definition instead of in the header file would not be the correct approach.
Related posts: