1. Trang chủ
  2. » Công Nghệ Thông Tin

Putting on Some High-Class Functions

36 261 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Putting on some high-class functions
Trường học University of Technology
Chuyên ngành Computer Science
Thể loại Chương
Thành phố Ho Chi Minh City
Định dạng
Số trang 36
Dung lượng 1,02 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Defining and Using a Function Consider the following example: class Example { public int nInt; // non-static public static int nStaticInt // static public void MemberFunction // non-stat

Trang 1

Passing arguments to a function

Getting results back — that would be nice

Reviewing the WriteLine()example

Passing arguments to the program

Programmers need the ability to break large programs into smaller chunksthat are easier to handle For example, the programs contained in previ-ous chapters are reaching the limit of what a person can digest at one time

C# lets you divide your code into chunks known as functions Properly

designed and implemented functions can greatly simplify the job of writingcomplex programs

Defining and Using a Function

Consider the following example:

class Example {

public int nInt; // non-static public static int nStaticInt // static public void MemberFunction() // non-static {

Console.WriteLine(“this is a member function”);

} public static void ClassFunction() // static {

Console.WriteLine(“this is a class function”);

}

Trang 2

The element nIntis a data member, just like those shown in Chapter 6.However, the element MemberFunction()is new MemberFunction()is

known as a member function, which is a set of C# code that you can execute

by referencing the function’s name This is best explained by example — evenI’m confused right now (Actually, you’ve been seeing functions all along,starting with Main().)

Note: The distinction between static and non-static class members is

impor-tant I cover part of that story in this chapter and continue in more detail in

Chapter 8, where I also introduce the term method, which is commonly used

in object-oriented languages like C# for non-static class functions

The following code snippet assigns a value to the object data member nInt

and the class, or static, member nStaticInt:

Example example = new Example(); // create an object example.nInt = 1; // initialize the data member through object Example.nStaticInt = 2; // initialize class member through class

The following snippet defines and accesses MemberFunction()and

ClassFunction()in almost the same way:

Example example = new Example(); // create an object example.MemberFunction(); // invoke the member function

// with that object Example.ClassFunction(); // invoke the class function with the class // the following lines won’t compile

example.ClassFunction(); // can’t access class functions via an object Example.MemberFunction(); // can’t access member functions via class

The distinction between a class (static) function and a member (nonstatic)function, or method, mirrors the distinction between a class (static) variableand a member (nonstatic) variable that I describe in Chapter 6

The expression example.MemberFunction()passes control to the codecontained within the function C# follows an almost identical process for

Example.ClassFunction() Executing this simple code snippet generatesthe output from the WriteLine()contained within each function, as follows:

this is a member function this is a class function

After a function completes execution, it returns control to the point where itwas called

I include the parentheses when describing functions in text — as in Main()—

to make them a little easier to recognize Otherwise, I get confused trying tounderstand what I’m saying

Trang 3

The bit of C# code within the two example functions does nothing more thanwrite a silly stringto the console, but functions generally perform usefuland sometimes complex operations like calculating the sine of something,concatenating two strings, sorting an array of students, or surreptitiouslye-mailing your URL to Microsoft A function can be as large and complex asyou want it to be, but it’s best to strive for shorter functions, using theapproach described in the next section.

An Example Function for Your Files

In this section, I take the monolithic CalculateInterestTableprogramsfrom Chapter 5 and divide them into several reasonable functions as ademonstration of how the proper definition of functions can help make theprogram easier to write and understand The process of dividing up working

code this way is known as refactoring, and Visual Studio 2005 provides a

handy Refactor menu that automates the most common refactorings

I explain the exact details of the function definitions and function calls inlater sections of this chapter This example simply gives an overview

By reading the comments with the actual C# code removed, you should beable to get a good idea of a program’s intention If you cannot, you aren’tcommenting properly (Conversely, if you can’t strip out most comments andstill understand the intention from the function names, you aren’t namingyour functions clearly enough and/or making them small enough.)

In outline form, the CalculateInterestTableprogram appears as follows:

public static void Main(string[] args) {

// prompt user to enter source principal // if the principal is negative, generate an error message // prompt user to enter the interest rate

// if the interest is negative, generate an error message // finally, prompt user to input the number of years // display the input back to the user

// now loop through the specified number of years while(nYear <= nDuration)

{ // calculate the value of the principal plus interest // output the result

} }

Trang 4

This illustrates a good technique for planning a function If you stand backand study the program from a distance, you can see that it is divided into thefollowing three sections:

 An initial input section in which the user inputs the principal, interest,and duration information

 A section that mirrors the input data so that the user can verify that thecorrect data was entered

 A final section that creates and outputs the tableThese are good places to start looking for ways to refactor the program Infact, if you further examine the input section of that program, you can seethat the same basic code is used to input the following:

 The principal

 The interest

 The durationThat observation gives you another good place to look

I have used this information to create the following

CalculateInterestTableWithFunctionsprogram:

// CalculateInterestTableWithFunctions - generate an interest table // much like the other interest table // programs, but this time using a // reasonable division of labor among // several functions.

using System;

namespace CalculateInterestTableWithFunctions {

public class Program {

public static void Main(string[] args) {

// Section 1 - input the data you will need to create the table

// Section 2 - verify the data by mirroring it back to the user

Console.WriteLine(); // skip a line Console.WriteLine(“Principal = “ + mPrincipal);

Console.WriteLine(“Interest = “ + mInterest + “%”);

Console.WriteLine(“Duration = “ + mDuration + “ years”);

Console.WriteLine();

Trang 5

OutputInterestTable(mPrincipal, mInterest, mDuration);

// wait for user to acknowledge the results Console.WriteLine(“Press Enter to terminate ”);

Console.Read();

} // InputInterestData - retrieve from the keyboard the // principal, interest, and duration // information needed to create the // future value table

// (This function implements Section 1 by breaking it down into // its three components)

public static void InputInterestData(ref decimal mPrincipal,

ref decimal mInterest, ref decimal mDuration) {

// 1a - retrieve the principal

// (Inputting any one of principal, interest rate, or duration // is just a matter of inputting a decimal number and making // sure that it’s positive)

public static decimal InputPositiveDecimal(string sPrompt) {

// keep trying until the user gets it right while(true)

{ // prompt the user for input Console.Write(“Enter “ + sPrompt + “:”);

// retrieve a decimal value from the keyboard string sInput = Console.ReadLine();

decimal mValue = Convert.ToDecimal(sInput);

// exit the loop if the value entered is correct

if (mValue >= 0) {

// return the valid decimal value entered by the user return mValue;

} // otherwise, generate an error on incorrect input Console.WriteLine(sPrompt + “ cannot be negative”);

Console.WriteLine(“Try again”);

Console.WriteLine();

} } // OutputInterestTable - given the principal and interest // generate a future value table for // the number of periods indicated in

Trang 6

// (this implements section 3 of the program)

public static void OutputInterestTable(decimal mPrincipal,

decimal mInterest, decimal mDuration) {

for (int nYear = 1; nYear <= mDuration; nYear++) {

// calculate the value of the principal // plus interest

decimal mInterestPaid;

mInterestPaid = mPrincipal * (mInterest / 100);

// now calculate the new principal by adding // the interest to the previous principal mPrincipal = mPrincipal + mInterestPaid;

// round off the principal to the nearest cent mPrincipal = decimal.Round(mPrincipal, 2);

// output the result Console.WriteLine(nYear + “-” + mPrincipal);

} } } }

I have divided the Main()section into three clearly distinguishable parts,each marked with bolded comments I further divide the first section into 1a,1b, and 1c

Normally, you wouldn’t include the bolded comments The listings would getrather complicated with all the numbers and letters if you did In practice,those types of comments aren’t necessary if the functions are well thoughtout and their names clearly express the intent of each

Part 1 calls the function InputInterestData()to input the three variablesthe program needs to create the table: mPrincipal, mInterest, and

mDuration Part 2 displays these three values just as the earlier versions ofthe program do The final part outputs the table via the function

OutputInterestTable()

Starting at the bottom and working up, the OutputInterestTable()tion contains an output loop with the interest rate calculations This is thesame loop used in the in-line, nonfunction CalculateInterestTablepro-gram in Chapter 5 The advantage of this version, however, is that when writ-ing this section of code, you don’t need to concern yourself with any of thedetails of inputting or verifying the data In writing this function, you need tothink, “Given the three numbers — principal, interest, and duration — output

func-an interest table,” func-and that’s it After you’re done, you cfunc-an return to the linethat called the OutputInterestTable()function and continue from there

Trang 7

OutputInterestTable()offers a good try-out of Visual Studio 2005’s newRefactor menu Take these steps to give it a whirl:

1 Using the CalculateInterestTableMoreForgiving example from Chapter 5 as a starting point, select the code from the declaration of the nYear variable through the end of the while loop:

int nYear = 0; // you grab the loop variable while(nYear <= nDuration) // and the entire while loop {

//

}

2 Choose Refactor➪Extract Method.

3 In the Extract Method dialog box, type OutputInterestTable Examine the Preview Method Signature box and then click OK.

Notice that the proposed “signature” for the new “method” begins withthe private statickeywords and includes mPrincipal, mInterest,and nDurationin the parentheses I introduce private, an alternative

to public, in Chapter 11 For now, you can make the function publicifyou like The rest is coming up

The result of this refactoring consists of the following two pieces:

 A new private staticfunction below Main(), called

OutputInterestTable()

 The following line of code within Main()where the extracted code was:

mPrincipal = OutputInterestTable(mPrincipal, mInterest, nDuration);

Pretty cool! The same divide-and-conquer logic holds for InputInterestData() However, the refactoring is more complex, so I do it by hand anddon’t show the steps The full art of refactoring is beyond the scope of thisbook

For InputInterestData(), you can focus solely on inputting the three mal values However, in this case, you realize that inputting each decimalinvolves identical operations on three different input variables The

deci-InputPositiveDecimal()function bundles these operations into a set ofgeneral code that you can apply to principal, interest, and duration alike

Notice that the three whileloops that take input in the original program getcollapsed into one whileloop inside InputPositiveDecimal() Thisreduces code duplication, always a bad thing

Making these kinds of changes to a program — making it clearer without

changing its observable behavior — is called refactoring Check out

www.refactoring.comfor tons of information about this technique

Trang 8

This InputPositiveDecimal()function displays the prompt it was givenand awaits input from the user The function returns the value to the caller if

it is not negative If the value is negative, the function outputs an error sage and loops back to try again

mes-From the user’s standpoint, the modified program acts exactly the same asthe in-line version in Chapter 5, which is just the point:

Enter principal:100 Enter interest:-10 interest cannot be negative Try again

Enter interest:10 Enter duration:10 Principal = 100 Interest = 10%

Duration = 10 years 1-110.0

2-121.00 3-133.10 4-146.41 5-161.05 6-177.16 7-194.88 8-214.37 9-235.81 10-259.39 Press Enter to terminate

I have taken a lengthy, somewhat difficult program and refactored it intosmaller, more understandable pieces while reducing some duplication As wesay in Texas, “You can’t beat that with a stick.”

Why bother with functions?

When Fortran introduced the function conceptduring the 1950s, the sole purpose was to avoidduplication of code by combining similar sec-tions into a common element Suppose youwere to write a program that needed to calcu-late and display ratios in multiple places Yourprogram could call the DisplayRatio()

function when needed, more or less for the solepurpose of avoiding duplicating code The sav-ings may not seem so important for a function

as small as DisplayRatio(), but functionscan grow to be much larger Besides, a commonfunction like WriteLine()may be invoked inhundreds of different places

Quickly, a second advantage became obvious:

It is easier to code a single function correctly —and doubly easier if the function is small The

DisplayRatio()function includes a check

to make sure that the denominator is not zero If

Trang 9

Having Arguments with Functions

A method such as the following example is about as useful as my hairbrushbecause no data passes into or out of the function:

public static void Output() {

Not so obvious is a third advantage: A carefullycrafted function reduces the complexity of theprogram A well-defined function should standfor some concept You should be able todescribe the purpose of the function without

using the words and or or The function should

do one thing

A function like calculateSin()is an idealexample The programmer who has beentasked with this assignment can implement thiscomplex operation without worrying about how

it may be used The applications programmercan use calculateSin()without worryingabout how this operation is performed inter-nally This greatly reduces the number of thingsthat the applications programmer has to worryabout By reducing the number of “variables,” alarge job gets accomplished by implementingtwo smaller, easier jobs

Large programs such as a word processor arebuilt up from many layers of functions at ever-increasing levels of abstraction For example, a

RedisplayDocument() function wouldundoubtedly call a Reparagraph()function

to redisplay the paragraphs within the ment Reparagraph()would need to invoke

docu-a CalculateWordWrap()function to decidewhere to wrap the lines that make up the para-graph CalculateWordWrap()would have

to call a LookUpWordBreak()function todecide where to break a word at the end of theline, to make the sentences wrap more natu-rally Each of these functions was described in

a single, simple sentence (Notice, also, howwell-named these functions are.)

Without the ability to abstract complex

con-cepts, writing programs of even moderate plexity would become almost impossible, muchless creating an operating system such asWindows XP, a utility such as WinZip, a wordprocessor like WordPerfect, or a game such asHalo, to name a few examples

Trang 10

com-Passing an argument to a function

The values input to a function are called the function arguments (Another name for argument is parameter.) Most functions require some type of argu-

ments if they’re going to do something In this way, functions remind me of

my son: We need to have an argument before he’ll do anything You passarguments to a function by listing them in the parentheses that follow thefunction name Consider the following small addition to the earlier Example

I could invoke this function from within the same class as follows:

Output(“Hello”);

I would get the following not-too-exciting output:

Output() was passed the argument: Hello

The program passes a reference to the string “Hello”to the function

Output() The function receives the reference and assigns it the name

funcString The Output()function can use funcStringwithin the functionjust as it would any other stringvariable

I can change the example in one minor way:

string myString = “Hello”;

Output(myString);

This code snippet assigns the variable myStringto reference the string

“Hello” The call Output(myString)passes the object referenced by

myString, which is your good old friend “Hello” Figure 7-1 depicts thisprocess From there, the effect is the same as before

Passing multiple arguments to functions

When I ask my daughter to wash the car, she usually gives me more than just

a single argument Because she has lots of time on the couch to think about

it, she can keep several at the ready

Trang 11

You can define a function with multiple arguments of varying types Considerthe following example function AverageAndDisplay():

// AverageAndDisplay using System;

namespace Example {

public class Program {

public static void Main(string[] args) {

// access the member function AverageAndDisplay(“grade 1”, 3.5, “grade 2”, 4.0);

// wait for user to acknowledge Console.WriteLine(“Press Enter to terminate ”);

Console.Read();

} // AverageAndDisplay - average two numbers with their // labels and display the results public static void AverageAndDisplay(string s1, double d1,

string s2, double d2) {

double dAverage = (d1 + d2) / 2;

Console.WriteLine(“The average of “ + s1

+ “ whose value is “ + d1 + “ and “ + s2 + “ whose value is “ + d2 + “ is “ + dAverage);

} } }

Executing this simple program generates the following output:

The average of grade 1 whose value is 3.5 and grade 2 whose value is 4 is 3.75 Press Enter to terminate

to func String

Trang 12

The function AverageAndDisplay()is declared with several arguments inthe order in which they are to be passed.

As usual, execution of the example program begins with the first statementafter Main() The first noncomment line in Main()invokes the function

AverageAndDisplay(), passing the two strings “grade 1”and “grade 2”

and the two doublevalues 3.5 and 4.0

The function AverageAndDisplay()calculates the average of the two

doublevalues, d1and d2, passed to it along with their names contained in

s1and s2, and the calculated average is stored in dAverage

Changing the value of an argument inside the function can lead to errors It’swiser to assign the argument to a temporary variable and modify that

Matching argument definitions with usage

Each argument in a function call must match the function definition in both

type and order The following is illegal and generates a build-time error:

// AverageWithCompilerError - this version does not compile!

using System;

namespace AverageWithCompilerError {

public class Program {

public static void Main(string[] args) {

Console.WriteLine(“This program will not compile as-is.”);

// access the member function AverageAndDisplay(“grade 1”, “grade 2”, 3.5, 4.0);

// wait for user to acknowledge Console.WriteLine(“Press Enter to terminate ”);

Console.Read();

} // AverageAndDisplay - average two numbers with their // labels and display the results public static void AverageAndDisplay(string s1, double d1,

string s2, double d2) {

double dAverage = (d1 + d2) / 2;

Console.WriteLine(“The average of “ + s1

+ “ whose value is “ + d1 + “ and “ + s2 + “ whose value is “ + d2 + “ is “ + dAverage);

} }

Trang 13

C# can’t match the type of each argument in the call to AverageAndDisplay()

with the corresponding argument in the function definition The string

“grade 1”matches the first stringin the function definition; however, thefunction definition calls for a doubleas its second argument rather than the

stringthat’s passed

You can easily see that I simply transposed the second and third arguments

That’s what I hate about computers — they take me too literally I know what

I said, but it’s obvious what I meant!

To fix the problem, swap the second and third arguments

Overloading a function does not mean giving it too much to do

You can give two functions within a given class the same name as long as their arguments differ This is called overloading the function name.

The following example demonstrates overloading:

// AverageAndDisplayOverloaded - this version demonstrates that // the average and display function // can be overloaded

using System;

namespace AverageAndDisplayOverloaded {

public class Program {

public static void Main(string[] args) {

// access the first member function AverageAndDisplay(“my GPA”, 3.5, “your GPA”, 4.0);

string s2, double d2) {

double dAverage = (d1 + d2) / 2;

Console.WriteLine(“The average of “ + s1

+ “ whose value is “ + d1);

Console.WriteLine(“and “ + s2

Trang 14

+ “ is “ + dAverage);

} public static void AverageAndDisplay(double d1, double d2) {

double dAverage = (d1 + d2) / 2;

Console.WriteLine(“The average of “ + d1

+ “ and “ + d2 + “ is “ + dAverage);

} } }

This program defines two versions of AverageAndDisplay() The programinvokes one and then the other by passing the proper arguments C# can tellwhich function the program wants by comparing the call with the definition.The program compiles properly and generates the following output whenexecuted:

The average of my GPA whose value is 3.5 and your GPA whose value is 4 is 3.75 The average of 3.5 and 4 is 3.75 Press Enter to terminate

In general, C# does not allow two functions in the same program to have thesame name After all, how could C# tell which function you intended to call?However, C# includes the number and type of the function’s arguments as part

of its name Normally, you may call a function AverageAndDisplay() However,C# differentiates between the two functions AverageAndDisplay(string,double, string, double)and AverageAndDisplay(double, double).When you say it that way, it’s clear that the two functions are different

Implementing default arguments

Often, you want to supply two (or more) versions of a function, as follows:

 One version would be the complicated version that provides completeflexibility but requires numerous arguments from the calling routine,several of which the user may not even understand

In practice, references to the “user” of a function often mean the

pro-grammer who is making use of the function User does not always refer

to the ultimate user of the program Another term for this kind of user

is client (Often the client is you.)

 A second version of the function would provide acceptable, ifsomewhat bland, performance by assuming default values for some

of the arguments

Trang 15

You can easily implement default arguments using function overloading.

Consider the following pair of DisplayRoundedDecimal()functions:

// FunctionsWithDefaultArguments - provide variations of the same // function, some with default arguments, by // overloading the function name

using System;

namespace FunctionsWithDefaultArguments {

public class Program {

public static void Main(string[] args) {

// access the member function Console.WriteLine(“{0}”, DisplayRoundedDecimal(12.345678M, 3));

// wait for user to acknowledge Console.WriteLine(“Press Enter to terminate ”);

Console.Read();

} // DisplayRoundedDecimal - convert a decimal value into a string // with the specified number of significant // digits

public static string DisplayRoundedDecimal(decimal mValue,

int nNumberOfSignificantDigits) {

// first round the number off to the specified number // of significant digits

// invoke DisplayRoundedDecimal(decimal, int) specifying // the default number of digits

string s = DisplayRoundedDecimal(mValue, 2);

return s;

} } }

The DisplayRoundedDecimal(decimal, int) function converts the

decimalvalue provided into a stringwith the specified number of digitsafter the decimal point Because decimals are often used to display monetaryvalues, the most common choice is two digits after the decimal point Therefore,the DisplayRoundedDecimal(decimal)function provides the same conver-sion service but defaults the number of significant digits to two, thereby savingthe user from even worrying about the meaning of the second argument

Trang 16

Notice that the generic (decimal)version of the function actually calls themore specific (decimal, int)version to perform its magic This is morecommon than not, because it reduces duplication The generic functionssimply provide arguments that the programmer doesn’t have the inclination

to look up in the documentation

Providing default arguments is more than just saving a lazy programmer atiny bit of effort Programming requires lots of concentration Unnecessarytrips to the reference documentation to look up the meaning of normallydefaulted arguments distract the programmer from the main job at hand,thereby making the job more difficult, wasting time, and increasing the likeli-hood of mistakes The author of the function understands the relationshipbetween the arguments and therefore bears the onus of providing friendlier,overloaded versions of functions

Visual Basic and C/C++ programmers take note: In C#, overloaded functionsare the only way to implement default arguments C# also doesn’t allowoptional arguments

Passing value-type arguments

The basic variable types such as int, double, and decimalare known as

value-type variables You can pass value-type variables to a function in one of

two ways The default form is to pass by value An alternate form is the pass

by reference.

Programmers can get sloppy in their speech In referring to value-types, when

a programmer says “passing a variable to a function,” that usually means

“pass the value of a variable to a function.”

Passing value-type arguments by value

Unlike object references, value-type variables like an intor a doubleare

nor-mally passed by value, which means that the value contained within the

vari-able is passed to the function and not the varivari-able itself

Pass by value has the effect that changing the value of a value-type variablewithin a function does not change the value of that variable in the calling pro-gram This is demonstrated in the following code:

// PassByValue - demonstrate pass by value semantics using System;

namespace PassByValue {

public class Program {

// Update - try to modify the values of the arguments

Trang 17

// functions in any order in a class public static void Update(int i, double d) {

i = 10;

d = 20.0;

} public static void Main(string[] args) {

// declare two variables and initialize them int i = 1;

Executing this program generates the following output:

Before the call to Update(int, double):

i = 1, d = 2 After the call to Update(int, double):

i = 1, d = 2 Press Enter to terminate

The call to Update()passes the values 1 and 2.0 and not a reference to the

variables iand d Thus, changing their value within the function has no moreeffect on the value of the variables back in the calling routine than asking forwater with ice at an English pub

Passing value-type arguments by reference

Passing a value-type argument to a function by reference is advantageous —

in particular, when the caller wants to give the function the ability to changethe value of the variable The following PassByReferenceprogram demon-strates this capability

C# gives the programmer the pass by reference capability via the refand

outkeywords The following slight modification to the example PassByValue

program snippet from the previous section demonstrates the point:

Trang 18

// PassByReference - demonstrate pass by reference semantics using System;

namespace PassByReference {

public class Program {

// Update - try to modify the values of the arguments // passed to it; note ref and out arguments public static void Update(ref int i, out double d) {

i = 10;

d = 20.0;

} public static void Main(string[] args) {

// declare two variables and initialize them int i = 1;

// wait for user to acknowledge Console.WriteLine(“Press Enter to terminate ”);

Console.Read();

} } }

The refkeyword indicates that C# should pass a reference to iand not justthe value contained within this variable Consequently, changes made withinthe function are exported back out of the calling routine

In a similar vein, the outkeyword says, “Pass back by reference, but I don’tcare what the initial value is because I’m going to overwrite it anyway.”(That’s a lot to pack into three words!) The outkeyword is applicable whenthe function is only returning a value to the caller

Executing the program generates the following output:

Before the call to Update(ref int, out double):

i = 1, d is not initialized After the call to Update(ref int, out double):

i = 10, d = 20 Press Enter to terminate

Ngày đăng: 04/10/2013, 21:20

TỪ KHÓA LIÊN QUAN