Inside Delphi 2006 (Wordware Delphi Developers Library)

Functions in C++

The title of this part of the chapter is not "Functions and Procedures in C++" because C++ doesn't have procedures like Delphi does. In C++, you can only create functions, but these can work like Delphi procedures if you make them return nothing. To make the function return nothing, all you have to do is tell it to return void. The void data type is a special data type that is used to indicate that no value exists.

Here is how you declare a function in C++:

return_type function_name(parameter list) { function body }

As you can see, there are some differences between a function declaration in Delphi and C++:

The example in Listing 5-18 shows how to create and use a simple procedure in C++.

Listing 5-18: A simple procedure

#include <iostream.h> #include <conio.h> #pragma hdrstop void hello() { cout << "Hello from a C++ procedure." << endl; } #pragma argsused int main(int argc, char* argv[]) { hello(); // call the hello() procedure getch(); return 0; }

When declaring and calling a function in C++, you have to write the parentheses even when the function has no parameters. To show that a function accepts no parameters, you can also include the reserved word void inside the parentheses:

void hello(void) { cout << "Hello from a C++ procedure." << endl; }

Parameters

The parameter list in C++ is a comma-separated list:

return_type function_name(data_type first_param, data_type last_param) { function body }

Listing 5-19 features a simple function named add, which accepts two integer values and returns their sum.

Listing 5-19: A simple function

#include <iostream.h> #include <conio.h> #pragma hdrstop int add(int first, int second) { return first + second; } #pragma argsused int main(int argc, char* argv[]) { int i, j, sum; cout << "First number: "; cin >> i; cout << "Second number: "; cin >> j; sum = add(i, j); // call the add(int, int) function cout << "Sum = " << sum << endl; getch(); return 0; }

To return a value from a function in C++, you have to use the reserved word return. However, you have to be careful how you use it, since it greatly differs from the Result variable in Delphi. Since Result is a variable, it can be used as such without disrupting the normal flow of execution. But the reserved word return breaks execution and immediately exits the function, returning the expression that follows it to the caller function, so you can't write anything after it.

For instance, Listing 5-20 shows the Max3 function implemented in Delphi. As you can see in the listing, the Result variable is freely used throughout the function.

Listing 5-20: Delphi version of the Max3 function

program Project1; {$APPTYPE CONSOLE} uses SysUtils; function Max3(num1, num2, num3: Integer): Integer; begin Result := num1; // num1 is the largest? if num2 > Result then Result := num2; // num2 is the largest? if num3 > Result then Result := num3; // num3 is the largest end; begin WriteLn('Max = ', Max3(1, 2, 3)); ReadLn; end.

The C++ version of the function is different — not in logic, but in the way things are done. There are two differences: You have to declare a variable in the function to temporarily store the max value and use the reserved word return after all other statements to return the result to the calling function, as shown in Listing 5-21.

Listing 5-21: The C++ version of the Max3 function

#include <iostream.h> #include <conio.h> #pragma hdrstop int max3(int num1, int num2, int num3) { int max_val = num1; // num1 is the largest? if(num2 > max_val) max_val = num2; // num2 is the largest? if(num3 > max_val) max_val = num3; // num3 is the largest return max_val; } #pragma argsused int main(int argc, char* argv[]) { cout << "Max = " << max3(1, 2, 3) << endl; getch(); return 0; }

There's also another way of implementing the Max3 function — by using the conditional operator — but if you've got a weak stomach, feel free to skip the following listing. Listing 5-22 contains two versions of the Max3 function, called Max3 and Max3_2. The Max3 function is easier to read since it uses a temporary variable to store the max value. The Max3_2 function is something you'd want to do to your worst enemy, but it is nice from a geek's point of view since it returns the max value without using a temporary variable.

Listing 5-22: Two more versions of the Max3 function using the conditional

#include <iostream.h> #include <conio.h> #pragma hdrstop int max3(int num1, int num2, int num3) { int max_val = num1 > num2 ? num1 : num2; return num3 > max_val ? num3 : max_val; } int max3_2(int num1, int num2, int num3) { return num3 > (num1>num2 ? num1:num2) ? num3: (num1>num2 ? num1:num2); } #pragma argsused int main(int argc, char* argv[]) { cout << "Max = " << max3(10, 20, 30) << endl; cout << "Max(again) = " << max3_2(500, 400, 300) << endl; getch(); return 0; }

Before we move on to the topic of parameter passing, you should remember one last thing: You cannot define more parameters of the same type in a comma-separated list, as you can in Delphi:

procedure ParamSample(I, J, K: Integer; A, B: Double); begin end;

In C++, all parameters must be fully defined, with the data type first and then the parameter name:

void param_sample(int i, int j, int k, double a, double b) { }

Passing Parameters By Value and By Reference

When you declare a parameter list as we did earlier (data type first and then the name of the parameter), you declare parameters that receive a copy of the argument's value. (An argument is anything passed to a function parameter, like a constant value or a variable.) Value parameters in C++ work just like Delphi value parameters — the parameter receives only a copy of the value. The value of the parameter can be freely modified in the function's body, but no matter what you do with it, the changes aren't reflected to the world outside the function.

To change the value of a variable passed to the function, you need to declare the function's parameters a bit differently so that they accept the address of the variable instead of a copy of its value. By accepting the address of the original variable, the code in the function's body is able to modify the value of the variable. This is known as passing by reference.

To declare a reference parameter (known as a var parameter in Delphi), you have to use the reference operator (&) after the data type, between the data type and the parameter name, or before the parameter name — it doesn't matter. Here's how to declare a reference parameter (the following declarations differ only in the position of the reference operator):

return_type function_name(data_type& param_name); return_type function_name(data_type & param_name); return_type function_name(data_type &param_name);

Listing 5-23 shows two versions of the my_abs function, which is actually the my_abs procedure, since it doesn't return the absolute value of the argument as the result but changes the argument itself.

Listing 5-23: Passing by value and by reference

#include <iostream.h> #include <conio.h> #pragma hdrstop void my_abs(int i) // passing by value, doesn't work { if (i < 0) i = -i; } void my_abs_ref(int& i) // passing by reference, works OK { if (i < 0) i = -i; } // pass normally, by value, since we aren't changing the value of num void show_int(int num) { cout << num << endl; } #pragma argsused int main(int argc, char* argv[]) { int test = -5; my_abs(test); show_int(test); // -5, not good my_abs_ref(test); show_int(test); // 5, OK, since test was passed by reference getch(); return 0; }

Default Parameters

Default parameters in C++ are declared the same way as they are in Delphi:

void function_name(data_type param_name = default_value);

Default parameters in C++ have the same limitation as they do in Delphi — you cannot declare a non-default parameter after a default one:

int default_OK(int i = 5, int j = 20) { return i * j; } int default_ERROR(int i = 5, int j) { return i * j; }

Function Prototypes

In C++ and Delphi, all functions (and procedures) must be declared before they are used. So far, we've tackled this problem by declaring all functions above the main function, which made them automatically usable in the program. However, this is not the preferred way of function declaration in C++. The preferred way of declaring functions, which gives the compiler the ability to perform stronger type checking, is to declare them below the main function. But when you declare a function below the main function, it cannot be used in the main function without a function prototype.

A function prototype is similar to a forward declaration in Delphi. In C++, a function prototype is a function header followed by a semicolon:

return_type function_name(parameter_list);

To make the function visible in the entire unit, function prototypes are declared at the top of the unit. The following example shows how to declare functions below the main function and how to write a function prototype.

Listing 5-24: Function prototypes

#include <iostream.h> #include <conio.h> #pragma hdrstop void prototyped_hello(); // function prototype, no body #pragma argsused int main(int argc, char* argv[]) { prototyped_hello(); getch(); return 0; } void prototyped_hello() // function's body after the main function { cout << "function prototype" << endl; }

When you have a function with a parameter list, you can omit parameter names in the function prototype. The following example illustrates how to create a function prototype for a function with a parameter list:

// void prototyped_func(int i, char c, double d);

or

void prototyped_func(int, char, double); #pragma argsused int main(int argc, char* argv[]) { prototyped_func(0, 'a', 1.0); return 0; } #pragma argsused void prototyped_func(int i, char c, double d) { }

Local, Global, and Static Local Variables

As in Delphi, local variables are variables declared in a function, allocated on the stack, and can only be used inside the function in which they are declared. In C++, you can also declare a "really" local variable inside a block. A variable declared inside a block only exists in the block in which it is declared:

void local_variables() { int local_i = 10; if(local_i == 10) { int block_local_i = 20; cout << local_i << endl; cout << block_local_i << endl; } /* local_i can be used anywhere in the function block_local_i can only be used in the if statement's block */ }

Global Variables

Global variables are variables declared outside of any function, can be used in any function, and unlike local variables, they exist as long as the application is running. The following example shows how to declare and use a global variable in C++:

#include <iostream.h> #include <conio.h> #pragma hdrstop // function prototype void display_global(); // global variable int global_i = 101; #pragma argsused int main(int argc, char* argv[]) { // use the global variable in the main() function cout << "global_i in main() = " << global_i << endl; display_global(); getch(); return 0; } void display_global() { // use the global variable in the display_global() func cout << "global_i in display_global() = " << global_i << endl; }

Static Local Variables

Both global and local variables can be marked as static, but since there are differences between static local and static global variables, static local variables are described first, and static global variables are described later in this chapter.

To declare a static local variable, use the static storage specifier before the data type:

static data_type identifier;

Here are the characteristics of static local variables:

The code in Listing 5-25 and Figure 5-11 illustrate the difference between local and static local variables.

Figure 5-11: Static local variables preserve their value between multiple function calls.

Listing 5-25: Using static local variables

#include <iostream.h> #include <conio.h> #pragma hdrstop void local_var(); void static_local_var(); #pragma argsused int main(int argc, char* argv[]) { int i; for(i = 1; i <= 5; i++) local_var(); for(i = 1; i <= 5; i++) static_local_var(); getch(); return 0; } void local_var() { int cnt = 0; cout << "local_var() function has been called " << ++cnt << " times." << endl; } void static_local_var() { static cnt = 0; // initialized to 0 only the first time the func is called cout << "static_local_var() function has been called " << ++cnt << " times." << endl; }

Function Overloading and Inlining

To overload a function in C++ you don't have to mark it with a special directive like you do in Delphi. To create overloaded functions in C++, you only have to declare several functions that have the same name but differ in the number or type of parameters.

The example in Listing 5-26 shows how to create an overloaded function in C++ and how to use data type suffixes to make sure the appropriate overload is called. If you omit the "f" suffix for the float data type, the example will fail to compile because the compiler won't be able to decide if it should call max(int, int) or max(float, float).

Listing 5-26: Overloaded functions

int max(int, int); int max(int, int, int); int max(float, float); #pragma argsused int main(int argc, char* argv[]) { max(1, 2); max(1, 2, 3); /* to make sure that the max(float, float) overload is called, append the floating-point type suffix ("f") to the 1.0 and 2.0 constant values */ max(1.0f, 2.0f); return 0; } int max(int a, int b) { return 0; } int max(int a, int b, int c) { return 0; } int max(float a, float b) { return 0; }

To inline a function in C++, use the reserved word inline at the beginning of function declaration:

inline return_type function_name(parameter_list);

You can read more about inlined functions earlier in this chapter in the section titled "The Inline Directive."

Units

In Delphi, a unit is a single file with interface and implementation sections. Function and procedure declarations, constants, types, and variables that are to be used from outside the unit are written in the interface part of the unit. Function and procedure implementations, as well as constants, types, and variables that are used only in the unit, are written in the implementation part.

In C++, the interface section is stored in a header file (.h or .hpp) and the implementation section is stored in the standard source file (.cpp).

For instance, let's see how to move the my_abs() function from the following example to a my_math unit:

#include <iostream.h> #include <conio.h> #pragma hdrstop int my_abs(int num); #pragma argsused int main(int argc, char* argv[]) { int n; cout << "Enter a number: "; cin >> n; cout << "The absolute value of " << n << " is " << my_abs(n) << "." << endl; getch(); return 0; } int my_abs(int num) { return num < 0 ? -num : num; }

First, you need to create a new .cpp file and save it to the project directory under  my_math.cpp. When you've saved the file, move the implementation of the my_abs() function to the  my_math.cpp file (see Listing 5-27A).

Listing 5-27A: The my_math.cpp file

int my_abs(int num) { return num < 0 ? -num : num; }

The second thing that you have to do is to create a new header file (both the CPP File and Header File items can be found in the C++Builder Files category on the Tool Palette). When you've created a new header file, move the my_abs() function's prototype to it and save the header file to the project directory under  my_math.h (see Listing 5-27B).

Listing 5-27B: The my_math.h file

int my_abs(int num);

Now that you've moved the my_abs() function to the my_math unit, you won't be able to compile the application until you include the my_math header file using the #include directive. However, to include a custom header, you have to do two things differently:

The difference between #include "header_name" and #include <header_name> is in how the compiler searches for header files. The <header_name> syntax is meant to be used with standard headers that are stored in default directories. If you use the <header_name> syntax with your own units, like <my_math.h>, the application will fail to compile. But when you include the header using the "header_name" syntax, the compiler searches in the current directory, the directory of the file that contains the #include directive, or other user-supplied directories.

The following listing shows how to properly include the my_math header file.

Listing 5-27C: Including a custom header file

#include <iostream.h> #include <conio.h> #pragma hdrstop #include "my_math.h" #pragma argsused int main(int argc, char* argv[]) { int n; cout << "Enter a number: "; cin >> n; cout << "The absolute value of " << n << " is " << my_abs(n) << "." << endl; getch(); return 0; }

Static Global Variables and Static Functions

When you declare a variable in the interface section of a Delphi unit, the variable is automatically usable in all other units and the main project file. When you declare a variable in a C++ unit, it is also global and likewise usable in other units, but not automatically. To use a variable declared in another unit, you have to redeclare the variable using the extern modifier in the unit in which you want to use it. When you use the extern modifier on a variable, you are simply telling the compiler that the variable is stored in some other unit.

For instance, the following listing shows an updated version of the  my_math.cpp file that now contains an int variable.

Listing 5-28A: An updated version of the my_math.cpp file

int unit_integer = 2005; int my_abs(int num) { return num < 0 ? -num : num; }

To use the unit_integer variable in the main unit, you have to include the my_math header file and you have to redeclare the variable using the extern modifier. When you redeclare a variable using the extern modifier, you can omit the initialization part since you're only telling the compiler to look for the variable elsewhere (see Listing 5-28B).

Listing 5-28B: Using an external variable

#include <iostream.h> #include <conio.h> #pragma hdrstop #include "my_math.h" extern int unit_integer; #pragma argsused int main(int argc, char* argv[]) { cout << unit_integer << endl; // correctly displays 2005 getch(); return 0; }

If you need a global variable that is only visible in the unit in which it is declared, like variables declared in the implementation section of a Delphi unit, you need to mark the variable as static to create a static global variable:

static int unit_integer = 2005;

If you mark the unit_integer variable in the  my_math.cpp file as static, you won't be able to run the application anymore because the extern modifier in the main unit is no longer able to see the unit_integer variable in the  my_math.cpp file.

The static directive has the same effect on functions. By default, all functions are globally visible, but when marked as static, they can only be used inside the unit in which they are declared (see Figure 5-12).

Figure 5-12: Static global variables and static functions can't be used outside of the unit in which they're declared.

Категории