IF A METHOD IS A BLACK BOX, then a parameter provides a mechanism for passing information from the outside world into the box. Parameters are part of the interface of a method. They allow you to customize the behavior of a method to adapt it to a particular situation.
As an analogy, consider a thermostat -- a black box whose task it is to keep your house at a certain temperature. The thermostat has a parameter, namely the dial that is used to set the desired temperature. The thermostat always performs the same task: maintaining a constant temperature. However, the exact task that it performs -- that is, which temperature it maintains -- is customized by the setting on its dial.
As an example, let's look at a method that prints the larger of two numbers:
// prints the larger of <num1> and <num2> static void PrintMax(int num1, int num2) { if (num1 > num2) { Console.WriteLine(num1); } else { Console.WriteLine(num2); } }
This method is defined to receive two int-type parameters, num1 and num2. Code that calls this method must provide two integer values. For example, it might be called with two literal values, like this:
PrintMax(3, 5); // prints 5
In this example, PrintMax is called with two parameters: 3 and 5. The 3 is copied to the first parameter (num1), and the 5 is copied to the second parameter (num2). The body of the method executes, determines that num2 holds the larger number, and prints out the result.
Here is a complete program listing that shows how PrintMax might be used.
using System;
class FindLarger
{
static void Main()
{
Console.Write("Enter a number: ");
int firstNum = Convert.ToInt32(Console.ReadLine());
Console.Write("Enter another number: ");
int secondNum = Convert.ToInt32(Console.ReadLine());
Console.Write("The larger number is: ");
PrintMax(firstNum, secondNum);
} // Main
static void PrintMax(int num1, int num2)
{
if (num1 > num2)
{
Console.WriteLine(num1);
}
else
{
Console.WriteLine(num2);
}
} // PrintMax
}
In this program, the user is asked to enter two numbers, and then the larger of the two is displayed. Notice the line in Main() that calls PrintMax:
PrintMax(firstNum, secondNum);
The values for PrintMax's parameters are provided by variables in the Main program, instead of specific hard-coded literal values. When the computer executes this call to PrintMax, it copies the value of firstNum into num1, and the value of secondNum into num2, executes the body of PrintMax, and displays the larger value.
Note that the term "parameter" is used to refer to two different, but related, concepts. There are parameters that are used in the definitions of methods, such as num1 and num2 in the above example. And there are parameters that are used in method call statements, such as firstNum and secondNum in the program above. Parameters in a method definition are called formal parameters. The parameters that are passed to a method when it is called are called actual parameters. When a method is called, the actual parameters in the method call statement are evaluated and the values are assigned to the formal parameters in the method's definition. Then the body of the method is executed.
A formal parameter is very much like a variable, and -- like a variable -- it has a specified type such as int, boolean, or string. An actual parameter is a value, and so it can be specified by any expression, provided that the expression computes a value of the correct type. The type of the actual parameter must be one that could legally be assigned to the formal parameter with an assignment statement. For example, if the formal parameter is of type double, then it would be legal to pass an int as the actual parameter since ints can legally be assigned to doubles. When you call a method, you must provide one actual parameter for each formal parameter in the method's definition. Consider, for example, a method
static void doTask(int N, double x) { // statements to perform the task go here }
This method might be called with the statement
doTask(17, 2.0 * y);
When the computer executes this statement, it has essentially the same effect as the block of statements:
{ int N = 17; // Assign 17 to the first formal parameter, N. double x = 2.0 * y; // Compute 2.0 * y, and assign it to // the second formal parameter, x. // statements to perform the task go here }
(There are a few technical differences between this and "doTask(17, 2.0 * y);" -- besides the amount of typing -- because of questions about scope of variables and what happens when several variables or parameters have the same name.)
Beginning programming students often find it difficult to get used to defining methods with formal parameters. A common mistake is to assign values to the formal parameters at the beginning of the method, or to ask the user to input their values. This represents a fundamental misunderstanding. When the statements in the method are executed, the formal parameters will already have values. The values come from the method call statement. Remember that a method is not independent. It is called by some other routine, and it is the calling routine's responsibility to provide appropriate values for the parameters.
Let's do a few examples of writing small methods to perform assigned tasks. Of course, this is only one side of programming with methods. The task performed by a method is always a subtask in a larger program. The art of designing those programs -- of deciding how to break them up into subtasks -- is the other side of programming with methods. We'll return to the question of program design in Section 6.
As a first example, let's write a method to compute and print out all the divisors of a given positive integer. The integer will be a parameter to the method.
Remember that the format of any method is
modifiers return-type method-name ( parameter-list ) { statements }
Writing a method always means filling out this format. The assignment tells us that there is one parameter, of type int, and it tells us what the statements in the body of the method should do. Since we are only working with static methods for now, we'll need to use static as a modifier. We could add an access modifier (public or private), but in the absence of any instructions, I'll leave it out. Since we are not told to return a value, the return type is void. Since no names are specified, we'll have to make up names for the formal parameter and for the method itself. I'll use N for the parameter and PrintDivisors for the method name. The method will look like
static void PrintDivisors( int N ) { statements }
and all we have left to do is to write the statements that make up the body of the routine. This is not difficult. Just remember that you have to write the body assuming that N already has a value! The algorithm is: "For each possible divisor D in the range from 1 to N, if D evenly divides N, then print D." Written in C#, this becomes:
// Print all the divisors of <N>. // We assume that <N> is a positive integer. static void PrintDivisors( int N ) { int D; // One of the possible divisors of N. Console.WriteLine("The divisors of " + N + " are:"); for ( D = 1; D <= N; D++ ) { if ( N % D == 0 ) Console.WriteLine(D); } }
I've added a method header comment at the top describing what it does and what assumptions it makes. The header comment includes the assumption that N is a positive integer. It is up to the caller of the method to make sure that this assumption is satisfied.
As a second short example, consider the assignment: Write a method named PrintRow. It should have a parameter ch of type char and a parameter N of type int. The method should print out a line of text containing N copies of the character ch.
Here, we are told the name of the method and the names of the two parameters, so we don't have much choice about the first line of the method definition. The task in this case is pretty simple, so the body of the method is easy to write. The complete method is as follows:
// Write one line of output containing <N> copies of the // character <ch>. If <N> <= 0, an empty line is output. static void PrintRow( char ch, int N ) { int i; // Loop-control variable for counting off the copies. for ( i = 1; i <= N; i++ ) { Console.Write( ch ); } Console.WriteLine(); }
Note that in this case, the header comment makes no assumption about N, but it makes it clear what will happen in all cases, including the unexpected case that N < 0.
Finally, let's do an example that shows how one method can build on another. Let's write a method that takes a string as a parameter. For each character in the string, it will print a line of output containing 25 copies of that character. It should use the printRow() method to produce the output.
Again, we get to choose a name for the method and a name for the parameter. I'll call the method PrintRowsFromString and the parameter str. The algorithm is pretty clear: For each position i in the string str, call PrintRow(str[i],25) to print one line of the output. So, we get:
// For each character in <str>, write a line of output // containing 25 copies of that character. static void PrintRowsFromString( string str ) { for ( int i = 0; i < str.Length; i++ ) { PrintRow( str[i], 25 ); } }
We could use PrintRowsFromString in a Main() routine such as
static void Main() { Console.Write("Enter a line of text: "); string inputLine = Console.ReadLine(); Console.WriteLine(); PrintRowsFromString( inputLine ); }
Of course, the three routines, Main(), PrintRowsFromString(), and PrintRow(), would have to be collected together inside the same class.The program is rather useless, but it does demonstrate the use of methods.
I'll finish this section on parameters by noting that we now have three different sorts of variables that can be used inside a method: local variables defined in the method, formal parameter names, and static member variables that are defined outside the method but inside the same class as the method.
Local variables have no connection to the outside world; they are purely part of the internal working of the method. Parameters are used to "drop" values into the method when it is called, but once the method starts executing, parameters act much like local variables. Changes made inside a method to a formal parameter have no effect on the rest of the program (at least if the type of the parameter is one of the primitive types -- things are more complicated in the case of objects, as we'll see later).
Things are different when a method uses a variable that is defined outside the method. That variable exists independently of the method, and it is accessible to other parts of the program, as well as to the method. Such a variable is said to be global to the method, as opposed to the "local" variables defined inside the method. The scope of a global variable includes the entire class in which it is defined. Changes made to a global variable can have effects that extend outside the method where the changes are made. You've seen how this works in the last example in the previous section, where the value of the global variable, gamesWon, is computed inside a method and is used in the Main() routine.
It's not always bad to use global variables in methods, but you should realize that the global variable then has to be considered part of the method's interface. The method uses the global variable to communicate with the rest of the program. This is a kind of sneaky, back-door communication that is less visible than communication done through parameters, and it risks violating the rule that the interface of a black box should be straightforward and easy to understand. So before you use a global variable in a method, you should consider whether it's really necessary.
I don't advise you to take an absolute stand against using global variables inside methods. There is at least one good reason to do it: if all the methods in a class need access to a particular variable, you might as well make it a member variable.
Here are some points to remember:
The more member variables you use, the less you benefit from the complexity management that organizing your program into methods can bring.
Prefer using local variables. Create a member variable only when several methods must access it.