1. Trang chủ >
  2. Công Nghệ Thông Tin >
  3. Kỹ thuật lập trình >

Chapter 5. Statements and Control Structures

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.65 MB, 448 trang )


Consider the following Ruby program. It adds two numbers passed to it on the

command line and prints the sum:

x = ARGV[0].to_f

y = ARGV[1].to_f

sum = x + y

puts sum



#

#

#

#



Convert first argument to a number

Convert second argument to a number

Add the arguments

Print the sum



This is a simple program that consists primarily of variable assignment and method

invocations. What makes it particularly simple is its purely sequential execution. The

four lines of code are executed one after the other without branching or repetition. It

is a rare program that can be this simple. This chapter introduces Ruby’s control structures, which alter the sequential execution, or flow-of-control, of a program. We cover:

















Conditionals

Loops

Iterators and blocks

Flow-altering statements like return and break

Exceptions

The special-case BEGIN and END statements

The esoteric control structures known as fibers and continuations



5.1 Conditionals

The most common control structure, in any programming language, is the

conditional. This is a way of telling the computer to conditionally execute some code:

to execute it only if some condition is satisfied. The condition is an expression—if it

evaluates to any value other than false or nil, then the condition is satisfied.

Ruby has a rich vocabulary for expressing conditionals. The syntax choices are

described in the subsections that follow. When writing Ruby code, you can choose the

one that seems most elegant for the task at hand.



5.1.1 if

The most straightforward of the conditionals is if. In its simplest form, it looks like this:

if expression

code

end



The code between if and end is executed if (and only if) the expression evaluates to

something other than false or nil. The code must be separated from the expression



118 | Chapter 5: Statements and Control Structures



with a newline or semicolon or the keyword then.* Here are two ways to write the same

simple conditional:

# If x

if x <

x +=

end

if x <



is less than 10, increment it

10

# newline separator

1

10 then x += 1 end



# then separator



You can also use then as the separator token, and follow it with a newline. Doing so

makes your code robust; it will work even if the newline is subsequently removed:

if x < 10 then

x += 1

end



Programmers who are used to C, or languages whose syntax is derived from C, should

note two important things about Ruby’s if statement:

• Parentheses are not required (and typically not used) around the conditional

expression. The newline, semicolon, or then keyword serves to delimit the expression instead.

• The end keyword is required, even when the code to be conditionally executed

consists of a single statement. The modifier form of if, described below, provides

a way to write simple conditionals without the end keyword.



5.1.1.1 else

An if statement may include an else clause to specify code to be executed if the

condition is not true:

if expression

code

else

code

end



The code between the if and else is executed if expression evaluates to anything other

than false or nil. Otherwise (if expression is false or nil), the code between the

else and end is executed. As in the simple form of if, the expression must be separated

from the code that follows it by a newline, a semicolon, or the keyword then. The

else and end keywords fully delimit the second chunk of code, and no newlines or

additional delimiters are required.

Here is an example of a conditional that includes an else clause:

if data

data << x

else



# If the array exists

#

then append a value to it.

# Otherwise...



* Ruby 1.8 also allows a colon, but this syntax is no longer legal in 1.9.



5.1 Conditionals | 119



data = [x]

end



#

create a new array that holds the value.

# This is the end of the conditional.



5.1.1.2 elsif

If you want to test more than one condition within a conditional, you can add one or

more elsif clauses between an if and an else. elsif is a shortened form of “else if.”

Note that there is only one e in elsif. A conditional using elsif looks like this:

if expression1

code1

elsif expression2

code2

.

.

.

elsif expressionN

codeN

else

code

end



If expression1 evaluates to anything other than false or nil, then code1 is executed.

Otherwise, expression2 is evaluated. If it is anything other than false or nil, then

code2 is executed. This process continues until an expression evaluates to something

other than false or nil, or until all elsif clauses have been tested. If the expression

associated with the last elsif clause is false or nil, and the elsif clause is followed by

an else clause, then the code between else and end is executed. If no else clause is

present, then no code is executed at all.

elsif is like if: the expression must be separated from the code by a newline, a semicolon, or a then keyword. Here is an example of a multiway conditional using elsif:

if x == 1

name = "one"

elsif x == 2

name = "two"

elsif x == 3 then name = "three"

elsif x == 4; name = "four"

else

name = "many"

end



5.1.1.3 Return value

In most languages, the if conditional is a statement. In Ruby, however, everything is

an expression, even the control structures that are commonly called statements. The

return value of an if “statement” (i.e., the value that results from evaluating an if

expression) is the value of the last expression in the code that was executed, or nil if

no block of code was executed.



120 | Chapter 5: Statements and Control Structures



The fact that if statements return a value means that, for example, the multiway

conditional shown previously can be elegantly rewritten as follows:

name = if

elsif

elsif

elsif

else

end



x

x

x

x



==

==

==

==



1

2

3

4



then

then

then

then



"one"

"two"

"three"

"four"

"many"



5.1.2 if As a Modifier

When if is used in its normal statement form, Ruby’s grammar requires that it be

terminated with the end keyword. For simple, single-line conditionals, this is somewhat

awkward. This is just a parsing problem, and the solution is to use the if keyword itself

as the delimiter that separates the code to be executed from the conditional

expression. Instead of writing:

if expression then code end



we can simply write:

code if expression



When used in this form, if is known as a statement (or expression) modifier. If you’re

a Perl programmer, you may be accustomed to this syntax. If not, please note that the

code to execute comes first, and the expression follows. For example:

puts message if message



# Output message, if it is defined



This syntax places more emphasis on the code to be executed, and less emphasis on

the condition under which it will be executed. Using this syntax can make your code

more readable when the condition is a trivial one or when the condition is almost always

true.

Even though the condition is written last, it is evaluated first. If it evaluates to anything

other than false or nil, then the code is evaluated, and its value is used as the return

value of the modified expression. Otherwise, the code is not executed, and the return

value of the modified expression is nil. Obviously, this syntax does not allow any kind

of else clause.

To use if as a modifier, it must follow the modified statement or expression immediately, with no intervening line break. Inserting a newline into the previous example

turns it into an unmodified method invocation followed by an incomplete if statement:

puts message

if message



# Unconditional

# Incomplete!



The if modifier has very low precedence and binds more loosely than the assignment

operator. Be sure you know just what expression you are modifying when you use it.

For example, the following two lines of code are different:



5.1 Conditionals | 121



y = x.invert if x.respond_to? :invert

y = (x.invert if x.respond_to? :invert)



In the first line, the modifier applies to the assignment expression. If x does not have a

method named invert, then nothing happens at all, and the value of y is not modified.

In the second line, the if modifier applies only to the method call. If x does not have

an invert method, then the modified expression evaluates to nil, and this is the value

that is assigned to y.

An if modifier binds to the single nearest expression. If you want to modify more than

one expression, you can use parentheses or a begin statement for grouping. But this

approach is problematic because readers don’t know that the code is part of a conditional until they reach the bottom. Also, using an if modifier in this way gives up the

conciseness that is the primary benefit of this syntax. When more than one line of code

is involved, you should typically use a traditional if statement rather than an if modifier. Compare the following three side-by-side alternatives:

if expression

line1

line2

end



begin

line1

line2

end if expression



(

line1

line2

) end if expression



Note that an expression modified with an if clause is itself an expression that can be

modified. It is therefore possible to attach multiple if modifiers to an expression:

# Output message if message exists and the output method is defined

puts message if message if defined? puts



Repeating an if modifier like this is hard to read, however, and it makes more sense to

combine the two conditions into a single expression:

puts message if message and defined? puts



5.1.3 unless

unless, as a statement or a modifier, is the opposite of if: it executes code only if an

associated expression evaluates to false or nil. Its syntax is just like if, except that

elsif clauses are not allowed:

# single-way unless statement

unless condition

code

end

# two-way unless statement

unless condition

code

else

code

end

# unless modifier

code unless condition



122 | Chapter 5: Statements and Control Structures



The unless statement, like the if statement, requires that the condition and the code

are separated by a newline, a semicolon, or the then keyword. Also like if, unless

statements are expressions and return the value of the code they execute, or nil if they

execute nothing:

# Call the to_s method on object o, unless o is nil

s = unless o.nil?

# newline separator

o.to_s

end

s = unless o.nil? then o.to_s end

# then separator



For single-line conditionals like this, the modifier form of unless is usually clearer:

s = o.to_s unless o.nil?



Ruby has no equivalent of the elsif clause for an unless conditional. You can still write

a multiway unless statement, however, if you’re willing to be a little more verbose:

unless x == 0

puts "x is not 0"

else

unless y == 0

puts "y is not 0"

else

unless z == 0

puts "z is not 0"

else

puts "all are 0"

end

end

end



5.1.4 case

The case statement is a multiway conditional. There are two forms of this statement.

The simple (and infrequently used) form is nothing more than an alternative syntax for

if/elsif/else. These two side-by-side expressions are equivalent:

name = case

when

when

when

when

else

end



x == 1

x == 2

x == 3

x == 4

"many"



then

then

then

then



"one"

"two"

"three"

"four"



name = if

x == 1

elsif x == 2

elsif x == 3

elsif x == 4

else "many"

end



then

then

then

then



"one"

"two"

"three"

"four"



As you can see from this code, the case statement returns a value, just as the if statement

does. As with the if statement, the then keyword following the when clauses can be

replaced with a newline or semicolon:*



* Ruby 1.8 also allows a colon in place of then, as it does for the if statement. But this syntax is no longer



allowed in Ruby 1.9.



5.1 Conditionals | 123



case

when x == 1

"one"

when x == 2

"two"

when x == 3

"three"

end



The case statement tests each of its when expressions in the order they are written until

it finds one that evaluates to true. If it finds one, it evaluates the statements that come

between that when and the following when, else, or end. The last expression evaluated

becomes the return value of the case statement. Once a when clause that evaluates to

true has been found, no other when clauses are considered.

The else clause of a case statement is optional, but if it appears, it must come at the

end of the statement, after all when clauses. If no when clause is true, and there is an

else clause, then the code between else and end is executed. The value of the last

expression evaluated in this code becomes the value of the case statement. If no when

clause is true and there is no else clause, then no code is executed and the value of the

case statement is nil.

A when clause within a case statement may have more than one (comma-separated)

expression associated with it. If any one of these expressions evaluates to true, then

the code associated with that when is executed. In this simple form of the case statement,

the commas aren’t particularly useful and act just like the || operator:

case

when x == 1, y == 0 then "x is one or y is zero" # Obscure syntax

when x == 2 || y == 1 then "x is two or y is one" # Easier to understand

end



All the case examples we’ve seen so far demonstrate the simpler, less common form of

the statement. case is really more powerful than this. Notice that in most of the examples, the left side of each when clause expression is the same. In the common form of

case, we factor this repeated lefthand expression of the when clause and associate it with

the case itself:

name = case x

when 1

"one"

when 2 then "two"

when 3; "three"

else "many"

end



# Just the value to compare to x

# Then keyword instead of newline

# Semicolon instead of newline

# Optional else clause at end



In this form of the case statement, the expression associated with the case is evaluated

once, and then it’s compared to the values obtained by evaluating the when expression.

The comparisons are performed in the order in which the when clauses are written, and

the code associated with the first matching when is executed. If no match is found, the

code associated with the else clause (if there is one) is executed. The return value of



124 | Chapter 5: Statements and Control Structures



this form of the case statement is the same as the return value of the simpler form: the

value of the last expression evaluated, or nil if no when or else matches.

The important thing to understand about the case statement is how the values of the

when clauses are compared to the expression that follows the case keyword. This comparison is done using the === operator. This operator is invoked on the value of the

when expression and is passed the value of the case expression. Therefore, the case

statement above is equivalent to the following (except that x is only evaluated once in

the code above):

name = case

when

when

when

else

end



1 === x then "one"

2 === x then "two"

3 === x then "three"

"many"



=== is the case equality operator. For many classes, such as the Fixnum class used earlier,

the === operator behaves just the same as ==. But certain classes define this operator in

interesting ways. The Class class defines === so that it tests whether the righthand

operand is an instance of the class named by the lefthand operand. Range defines this

operator to test whether the value on the right falls within the range on the left.

Regexp defines it so that it tests whether the text on the right matches the pattern on

the left. In Ruby 1.9,Symbol defines === so that it tests for symbol or string equality.

With these definitions of case equality, we are able to write interesting case statements

like the following:

# Take different actions depending on the class of x

puts case x

when String then "string"

when Numeric then "number"

when TrueClass, FalseClass then "boolean"

else "other"

end

# Compute 2006 U.S. income tax using case and Range objects

tax = case income

when 0..7550

income * 0.1

when 7550..30650

755 + (income-7550)*0.15

when 30650..74200

4220 + (income-30655)*0.25

when 74200..154800

15107.5 + (income-74201)*0.28

when 154800..336550

37675.5 + (income-154800)*0.33

else

97653 + (income-336550)*0.35

end

# Get user's input and process it, ignoring comments and exiting



5.1 Conditionals | 125



# when the user enters the word "quit"

while line=gets.chomp do # Loop, asking the user for input each time

case line

when /^\s*#/

# If input looks like a comment...

next

#

skip to the next line.

when /^quit$/i

# If input is "quit" (case insensitive)...

break

#

exit the loop.

else

# Otherwise...

puts line.reverse

#

reverse the user's input and print it.

end

end



A when clause can have more than one expression associated with it. Multiple expressions are separated by commas, and the === operator is invoked on each one. That is,

it is possible to trigger the same block of code with more than one value:

def hasValue?(x)

case x

when nil, [], "", 0

false

else

true

end

end



#

#

#

#

#

#



Define a method named hasValue?

Multiway conditional based on value of x

if nil===x || []===x || ""===x || 0===x then

method return value is false

Otherwise

method return value is true



case versus switch

Java programmers and others accustomed to C-derived language syntax are familiar

with a multiway conditional switch statement, which is similar to Ruby’s case statement. There are, however, a number of important differences:

• In Java and related languages, the name of the statement is switch and its clauses

are labeled with case and default. Ruby uses case as the name of the statement,

and when and else for the clauses.

• The switch statement of other languages simply transfers control to the start of the

appropriate case. From there, control continues and can “fall through” to other

cases, until it reaches the end of the switch statement or encounters a break or

return statement. This fall-through behavior allows multiple case clauses to refer

to the same block of code. In Ruby, this same purpose is served by allowing multiple comma-separated expressions to be associated with each when clause. Ruby’s

case statement never allows fall-through.

• In Java and most compiled languages with C-like syntax, the expressions associated with each case label must be compile-time constants rather than arbitrary

runtime expressions. This often allows the compiler to implement the switch

statement using a very fast lookup table. There is no such restriction on Ruby’s

case statement, and its performance is equivalent to using an if statement with

repeated elsif clauses.



126 | Chapter 5: Statements and Control Structures



5.1.5 The ?: Operator

The conditional operator ?:, described earlier in §4.6.10, behaves much like an if

statement, with ? replacing then and : replacing else. It provides a succinct way to

express conditionals:

def how_many_messages(n) # Handle singular/plural

"You have " + n.to_s + (n==1 ? " message." : " messages.")

end



5.2 Loops

This section documents Ruby’s simple looping statements: while, until, and for. Ruby

also includes the ability to define custom looping constructs known as iterators. Iterators (see §5.3) are probably more commonly used than Ruby’s built-in looping

statements; they are documented later in this chapter.



5.2.1 while and until

Ruby’s basic looping statements are while and until. They execute a chunk of code

while a certain condition is true, or until the condition becomes true. For example:

x = 10

while x >= 0 do

puts x

x = x - 1

end



# Initialize a loop counter variable

# Loop while x is greater than or equal to 0

#

Print out the value of x

#

Subtract 1 from x

# The loop ends here



# Count back up to 10 using an until loop

x = 0

# Start at 0 (instead of -1)

until x > 10 do

# Loop until x is greater than 10

puts x

x = x + 1

end

# Loop ends here



The loop condition is the Boolean expression that appears between the while or until

and do keywords. The loop body is the Ruby code that appears between the do and the

end keyword. The while loop evaluates its condition. If the value is anything other than

false or nil, it executes its body, and then loops to evaluate its condition again. In this

way, the body is executed repeatedly, zero or more times, while the condition remains

true (or, more strictly, non-false and non-nil).

The until loop is the reverse. The condition is tested and the body is executed if the

condition evaluates to false or nil. This means that the body is executed zero or more

times while the condition is false or nil. Note that any until loop can be converted to

a while simply by negating the condition. Most programmers are familiar with while

loops, but many have not used until loops before. For this reason, you may want to

use while loops except when until truly improves the clarity of your code.



5.2 Loops | 127



The do keyword in a while or until loop is like the then keyword in an if statement: it

may be omitted altogether as long as a newline (or semicolon) appears between the

loop condition and the loop body.*



5.2.2 while and until As Modifiers

If the body of a loop is a single Ruby expression, you can express that loop in a particularly compact form by using while or until as a modifier after the expression. For

example:

x = 0

puts x = x + 1 while x < 10



# Initialize loop variable

# Output and increment in a single expression



This modifier syntax uses the while keyword itself to separate the loop body from the

loop condition, and avoids the need for the do (or newline) and end keywords. Contrast

this code with the more traditional while loop written on a single line:

x = 0

while x < 10 do puts x = x + 1 end



until can be used as a modifier just as while can be:

a = [1,2,3]

puts a.pop until a.empty?



# Initialize an array

# Pop elements from array until empty



Note that when while and until are used as modifiers, they must appear on the same

line as the loop body that they modify. If there is a newline between the loop body and

the while or until keyword, the Ruby interpreter will treat the loop body as an

unmodified expression and the while or until as the beginning of a regular loop.

When while and until are used as modifiers for a single Ruby expression, the loop

condition is tested first, even though it is written after the loop body. The loop body is

executed zero or more times, just as if it were formatted as a regular while or until loop.

There is a special-case exception to this rule. When the expression being evaluated is

a compound expression delimited by begin and end keywords, then the body is executed

first before the condition is tested:

x = 10

begin

puts x

x = x - 1

end until x == 0



# Initialize loop variable

# Start a compound expression: executed at least once

#

output x

#

decrement x

# End compound expression and modify it with a loop



This results in a construct much like the do/while loop of C, C++, and Java. Despite

its similarity to the do/while loop of other languages, this special-case behavior of loop

modifiers with the begin statement is counterintuitive and its use is discouraged.

Future releases of Ruby may forbid the use of while and until modifiers with begin/end.



* In Ruby 1.8, a colon may be used in place of the do keyword. This is no longer allowed in Ruby 1.9.



128 | Chapter 5: Statements and Control Structures



Xem Thêm
Tải bản đầy đủ (.pdf) (448 trang)

×