Control flow statements

D includes the traditional loop and conditional statements found in other C-family languages. It also supports the infamous goto statement. It has a couple of other useful statements, such as a built-in foreach statement and a rather unique scope statement. In this section, we're going to look at examples of each of the first two. Because of their relation with exceptions, scope statements are included in detail in the next chapter.

Traditional loops

In terms of looping constructs, we have for, do, and do-while. The syntax and behavior should be familiar. Here is an example of each iterating over an array:

auto items = [10,20,30,40,50];
for(int i=0; i<items.length; ++i)
  writeln(items[i]);

int i = 0;
while(i < items.length)
  writeln(items[i++]);

i = 0;
do {
  writeln(items[i++]);
} while(i < items.length);

No surprises there. The braces are optional with for and while when they only contain one statement. When a loop with an empty body is desired, the following syntax is not allowed in D:

int sum;
for(int i=0; i<10; sum += i++)
    ;

Replace the semicolon with {} and it will compile.

You can also find the traditional break and continue statements in D. The latter stops the current loop iteration and goes back to the top and the former exits the current loop completely. To break out of nested loops, D supports labeled breaks:

auto array = [10,20,30,40,50];
EXIT_LOOPS: for(int i=0; i<array.length; ++i) {
    for(int j=array.length - 1; j>=0; --j) {
        auto val = array[i] + array[j];
        if(val == 100) break EXIT_LOOPS;
        writeln(val);
    }
}

A labeled break will break out of the loop associated with the given label. In this snippet, the outer loop is the closest one to the label, so that's the loop we're breaking out of.

The foreach loop

The foreach loop is a special loop construct that does some things behind the scenes so you don't have to. First, let's see what it looks like, and then I'll tell you how it works. Reusing the same items array from above, we can do this:

foreach(elem; items)
  writeln(elem);

Essentially, foreach maintains the index for us and infers the type of elem, streamlining the syntax. You can specify a type for the element, as in foreach(uint elem; items). You can also do this:

foreach(elem; 0..10)
  writeln(elem);

The 0..10 is a sequence of numbers that is only valid in specific contexts, such as in the slice operator and the foreach loop. This loop will print from 0 to 9 because, just as with the slice operator, the end number is exclusive. Since we're iterating an array, the loop can also tell us the current index. Simply add another variable and a comma.

foreach(index,elem; items)
  items[index] = elem+10;

By default, the index is of type size_t, but you can declare it to be int or uint. If the only reason you want the index is to modify the array elements, there's another way to do it. Use ref.

foreach(ref elem; items)
  elem += 10;

While it's fine to modify the individual elements of the iteration target in the foreach loop via a reference or the index operator, you should never modify its structure. For example, you cannot append or remove elements to or from an array, or otherwise modify its length in any way and expect no problems to arise. This holds true for any sort of range or container you are iterating. The compiler may let you get away with it, but you're entering undefined territory at runtime and can be certain something's going to break.

As hinted earlier, foreach doesn't operate only on arrays. It can iterate over any type that implements either the opApply function or, if that isn't present, the input range interface. We'll see the former in the next chapter and the latter in Chapter 6, Understanding Ranges. There is another statement called foreach_reverse that works like foreach, except that it iterates backwards, starting with the last element and ending with the first. It works with arrays and any type that implements either the opApplyReverse function or the bidirectional range interface.

There is some special behavior when iterating over arrays of characters and associative arrays. Character arrays, be they of the string family or of a mutable variety, can be decoded on the fly. Consider the following example:

foreach(c; "soufflé") {
  writeln(c);
}

Every character up through the letter 'l' will print as expected, but the letter 'é' is never printed. What actually does get printed depends on the platform. Never forget that characters in D are Unicode code units. By default, c is of type immutable(char) because soufflé is a string literal, or immutable(char)[]. In this case, since we know that the é requires two code units, we can fix this either by appending a w to soufflé to make it a wstring literal, or by declaring c as a wchar. By taking the latter approach, every character of the string will automatically be decoded from UTF-8 to UTF-16. When working with variables instead of literals and a suffix can't be appended, it's better to declare c as dchar. This way, no matter how many bytes are required to represent a letter, c will be big enough to hold them all. Just keep in mind that there is a cost associated with decoding each character.

It's also possible to use foreach on associative arrays. In this case, if you declare only one identifier, you are iterating the values. Declare two identifiers and the first one becomes the key. Types can be inferred as follows:

auto aa = ["One":1, "Two":2, "Three":3];
foreach(key,val; aa)
  writefln("%s = %s", key, val);

Alternatively, you can iterate on one of the following: byValue(), byKey(), .keys, or.values. You'll recall from the discussion of associative arrays that the first two return ranges and the remaining two each allocate a new array and copy over the keys or values. The first two are more efficient, but when using them you're iterating the associative array directly. In this case, attempting to modify the associative array during iteration by adding or removing elements will almost certainly cause something to break. When iterating on .keys or .values, it's a separate, copied array that is being iterated, so the associative array can be freely modified during iteration.

Traditional conditionals

First up, we have the traditional if statement. It looks and behaves like if statements in other C-family languages.

int x = 100;
if(x >= 200)
  writeln("200 or higher!");
else if( x >= 100)
  writeln("100 or higher!");
else
  writeln("Less than 100!");

Next up is the conditional expression, also referred to as the ternary operator. If its condition is true, it returns the second operand, otherwise it returns the third.

string isFour = (2 + 2 == 4) ? "It's a 4!" : "It's not a 4!";

The D documentation specifies that the expression in the condition of if statements and conditional expressions must produce a type that can be converted to bool. This isn't the same as a normal implicit conversion. For example, the expression someBool = somePointer will fail, because pointers are not implicitly convertible to bool. However, if(somePointer) is valid, as the conversion in this case is achieved via a cast inserted by the compiler. The same holds for the conditions in loops.

Finally, we have the traditional switch statement. In D, you must have a default case. Leaving it out will fail to compile.

int x = 10;
switch(x) {
  case 2:
    writeln("It's 2!");
    break;
  case 4:
    writeln("It's 4!");
    break;
  case 10:
    writeln("It's 10!");
    break;
  default:
    writeln("It's something else!");
    break;
}

Sometimes you want to cover case statements with one block. The C way is to list each case statement explicitly.

int i = 2;
switch(i) {
  case 1:
  case 2:
  case 3:
    writeln("OK");
    break;
  default: break;
}

You can do this in D too, and it works as expected. However, adding the highlighted line in the following code is a problem.

switch(i) {
  case 1:
  case 2:
    writeln("Warning!");
  case 3:
    writeln("OK");
    break;
  default: break;
}

This will give you an implicit fall-through warning if warning messages are enabled (with -wi) and an error if warnings are treated as errors (-w). It is intended to be a deprecated feature at some point. If you really want to fall through to the third case, then you can do so explicitly with goto case.

case 2:
  writeln("No Warning!");
  goto case;

It's also possible to jump to any specific case. For example:

switch(i) {
  case 1:
    goto case 3;
  case 2:
    writeln("No Warning!");
    goto case;
  case 3:
    writeln("OK");
    break;
  default: break;
}

D also supports the case range statement when the case values are sequential.

switch(i) {
  case 1: .. case 3:
    writeln("OK");
    break;
  default: break;
}

Leave out the .. and you're only covering two cases. Additionally, switch statements in D accept strings.

string s = "Yadda Yadda";
switch(s) {
  case "Yadda": writeln("One Yadda"); break;
  case "Yadda Yadda": writeln("Two Yaddas"); break;
  default: break;
}

You can also use return in place of break to exit the current function, and use continue when the switch is inside a loop. There is also a special form of switch, called a final switch, which can be useful in specific circumstances. We'll take a look at that in the next chapter, since it's closely related to enumerations.

The goto statement

Whether or not you think goto is evil, it is supported in D. It's the familiar C-style goto you're used to, not the old style that lets you jump around anywhere in your code base.

int x;
INC: writeln(x++);
if( x < 10) goto INC;

A do-it-yourself loop is one of the worst examples of goto there is, despite its ubiquity. However, it's short and easy to understand. One of the use cases of goto that most people agree is legitimate, that of cleaning up resources in a function, is quite useful in C, but is made mostly obsolete in D by the scope statement. It's still useful for explicit fall-through in switch statements.

The D goto has one important difference from the one in C: it is an error to skip variable initialization.

goto SKIP_I;    // Skipping initialization of i -- error!
int i = 2;
SKIP_I:
writeln(i);
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.131.38.14