
-----------------------------------
wtd
Wed Jul 20, 2005 7:29 pm

D: A Newbie-Oriented Tutorial
-----------------------------------
A.  Guidelines


Do not advance beyond any section unless you completely understand it.
Use meaningful names for things.  Code should be self-documenting as much as possible.


B.  It's been my observation...

Of late I've noticed a number of people interested in learning C++.  This is probably most commonly attributed to a desire to have a running head start when taking a programming class which uses C++.

This plan fails, in my estimation, on a few critical points.


Learning by yourself, without the benefit of considerable experience or a driving passion for the subject, is tedious, and comes with very little support.

In a classroom environment you'll have teachers and fellow students to assist you.  Further, these students will be doing exactly what you are, so there will be far fewer communication hurdles than there are in soliciting help from a forum on the web.
C++ is complex, and that complexity is harnessed for even the simplest tasks.  Sometimes this makes those tasks easierin the long-run, but it means that you must tackle complex concepts to understand even a simple "Hello world" program.

I've also noted that many students seem quite preoccupied with syntax, and are often therefore resistant to languages myself and others can suggest which do not bear a syntactic similarity to the C-like syntax family (to which C++ belongs). 

C.  So, what to do?

Certainly Java is an option.  Java, however, lacks a lot of the functionality present in C++, uses different syntax in many cases, and behaves rather differently in general.

Java also imposes a rather high mental overhead on programmers trying to learn it.  Object-oriented programming can't be left for later, for instance, since knowledge of it is essential for even the simplest programs.

C would fit, except that it's just too low-level.  

There is another good language that fits for the task, though it's less well known.  The D programming language designed by Digital Mars is a good alternative that may very well be easier to learn than C++, while giving a familiarity with the general "feel" of C-like syntax.

D.  Getting a Compiler

A free D compiler is available from:

prompt> dmd myprogram

Or:

prompt> dmd myprogram.d

This will generate an executable named "myprogram" on Linux or "myprogram.exe" on Windows, unless there is an error, which it will show you information about.

Compiling with the "-w" flag will show any warnings.  These are not errors which will prevent compilation, but they are potential problems.  Compiling with this flag is a matter of:

prompt> dmd myprogram.d -w

1.  Everything has a beginning

Let's dive right in by looking at the simplest possible D program.  

void main()
{

} 

Every program has a "main" function which serves as the entry point.  The program begins execution at the beginning of the function, and when execution reaches the end of the main function, the program exits.

2.  Functions

So, now that I've demonstrated a function, I should probably explain what a function is.

Functions are the means by which all executable code in a program is organized.  Data is organized in other ways, but for now we'll deal with executable code and simple data.

A function has four major components.


A name.  Every function has some name.  In the case of the main function, that name is "main".
Every function has a return type.  This indicates the type of data the function returns to the program.  In the main function we've seen, that return type is "void".  This means that the function return nothing to the program.

In the case of the main function, it should be noted that it can also return an integer, and in this case, that integer is returned to the operating system as an indicator of whether or not the program succeeded.  If we return nothing, the OS assumes the program finished successfully.
Every function has a body of code that is executed when the function is called.  This code comes between the curly braces.
Ever function has a list of parameters.  Parameters are values passed to the function which can then be used within that function to alter its behavior.

These parameters each have a type and a name, fall between the parentheses in a function declaration, and as in the main function we've seen, there may in fact be no parameters for a function.


Think of a function as the classic "black box".  It has inputs in the form of parameters, and its return value is the output.  What happens inside the function is impossible and unnecessary to know.

All that matters is that the function does what it says it will.  

Let's look at a simple function of our own devising.  This function will take an integer as a parameter and square it, returning the result.

First we write what we can think of as the interface to the function: its name, return type, and parameters.

int square(int number)
{

} 

Now we need to be able to square a number.  The simplest way to accomplish this is by multiplying it by itself.

number * number

At this point all we lack is the ability to return that result out of the function.  For this we naturally se the "return" statement.

int square(int number)
{
   return number * number;
}

A semi-colon ends the statement.

Calling the function is quite simple.  In this case we'll call it from within the main function, and do nothing with its return value, which is a valid course of action, though generally not advised.

void main()
{
   square(42);
}

int square(int number)
{
   return number * number;
}

3.  Statements and Expressions

In the previous section, I referred to "the return statement".  The last of those three words is tremendously important.

A statement is anything in a programming language that does something to the environment, but returns no new value to the program.

To complement this, we have expressions, which do compute and return to the program new values.  The program we created in the previous section contains two expressions.

number * number

Is an expression.  It multiples "number" by itself and returns the result to the program.  In the process it does not change the number parameter.

square(42)

Is also an expression.  It takes the value 42, computes a new value, and returns that value to the program, which promptly ignores it, in this case.

The difference between statements and expressions is a fundamental programming concept.

4.  Variables

As mentioned previously, the "square" function, when called forms an expression which returns a new value to the program.  However, we ignore that return value.   

There are two things we can do with that value, aside from ignoring it.  We can either immediately use the value by passing it to another function, or we can store it in a variable.

Variables are essentially pieces of memory in which we can store values.  Every variable has a name and a type of data which it can store.

All variables must be declared prior to use.   This is accomplished by preceding the name with the type of the variable.  To store the result in our previous program we might declare a variable as follows.

void main()
{
   int result;

   square(42);
}

int square(int number)
{
   return number * number;
}

We can then easily assign a value to the variable, as long as the type of the value matches the type of the variable.

void main()
{
   int result;

   result = square(42);
}

int square(int number)
{
   return number * number;
}

Fortunately we can both declare the variable and give it an initial value in one statement.

void main()
{
   int result = square(42);
}

int square(int number)
{
   return number * number;
}

We can, of course, pass variables to functions.

void main()
{
   int initialNumber = 42;
   int numberSquared = square(initialNumber);
}

int square(int number)
{
   return number * number;
}

5.  Simple Output

Thus far the examples program haven't actually done anything apparent to the user.  Let's change that by incorporating some simple output.

import std.stdio;

void main()
{
   puts("Hello world!");
}

Here we start by importing the std.stdio module.  This module contains, among other things, several useful functions for dealing with output.  By importing it, we make these functions available in our program.

One such function is "puts", which is an abbreviation of "put string".  A string is simply a series of individual characters.

The puts function simply prints a string to the standard output, and then moves to a new line.  An alternative and popular function to do the same is "writefln".

import std.stdio;

void main()
{
   writefln("Hello world!");
}

The "ln" in the function name indicates the fact that it skips to a new line.  The "f" is an abbreviation for "format".  Fomrat strings make it possible to output other data using formatting characters.

Let's revisit our previous program:

void main()
{
   int initialNumber = 42;
   int numberSquared = square(initialNumber);
}

int square(int number)
{
   return number * number;
}

And let's add an output statement to that so that we see:

42 squared is 1764

void main()
{
   int initialNumber = 42;
   int numberSquared = square(initialNumber);

   writefln("%d squared is %d",
            initialNumber,
            numberSquared);
}

int square(int number)
{
   return number * number;
}

The "%d" indicates an integer.  Other popular options are "%f" for a floating point number, "%c" for a character, and "%s" for a string.

Many other options are available, but will not be covered here yet.

6.  Arrays

In the previous section I talked about strings, and said that they were a "series of individual characters".  This is true, but more accurately a string is an array of character.

An array is an immensely useful way of organizing data.  It stores several values of a single type in a block of memory.  It also allows for random access to its contents.  In other words, it's as easy and quick to access the five-hundredth element as it is to access the first.

Creating an array is as simple as appending "int[] intArray;

An array is indexed by numbers starting with zero.  To put a value into the array in the first position, I would:

intArray[0] = 42;

Inserting a value at the five-hundredth space would then be:

intArray[499] = 73;

You should note that I never specified how long the array should be.  Instead I simply assigned values to various positions in the array and it just worked.

This is an example of a dynamic array.  It will grow to whatever size is required.  I can determine the size of an array at any time by accessing the array's length property.  The length property can also have a value assigned to it to resize the array.

import std.stdio;

void main()
{
   int[] intArray;

   intArray[0] = 42;
   intArray[1] = 54;
   intArray[2] = 91;

   writefln("The length of intArray is %d.",
            intArray.length);
}

If, however, I wish to create an array of a fixed size, I may do so.

int[3] fixedSizeIntArray;

Now, if I attempt to insert a value into the array outside of the bounds I set, an error will occur.  The following, for instance, will not work.

fixedSizeIntArray[3] = 56;

Again, the length of a fixed size array can be determined by the length property.  In this case, however, the length is read-only, and cannot be changed.

Fixed-size arrays can be conveniently initialized.  To create a fixed-size array with 4 elements:

static int[] fixedSizeIntArray = [56, 67, 42, 19];

Strings are simply an array of characters.  Typically we deal with strings as dynamic arrays, but we can deal with strings as fixed size arrays.

Creating and initializing a simple string is as easy as:

char[] myString = "Hello world";

We can then use that string in functions.

import std.stdio;

void main()
{
   char[] myString = "Hello world";
   
   writefln(myString);
}

It's through the use of strings that we can see many of the interesting operations that are available for arrays.

For example, concatenating two arrays together into one array:

char[] helloString = "Hello";
char[] worldString = "world";
char[] helloWorldString = 
   helloString ~ ' ' ~ worldString;

Accessing a portion of a string, or any other type of array, is done using an array "slice".  To grab the "llo" from "Hello":

char[] subString = helloString[2 .. 5];

7.  Associative Arrays

In the previous sections, I discussed dynamic arrays which resize as necessary.  Thus I can have something like:

int[] intArray;

intArray[0] = 42;
intArray[1001] = 98;

The potential problem with this is that the array created is not "sparse".  In order to create an array with an index of 1001, it creates everything upto 1001 as well.

While dynamic arrays are quite valuable, for situations where the indexes are relatively small and sequential.  But if the indexes are large and sparse, a more memory-efficient solution is to use an associative array.

An associative array, as the name indicates, associates one type of value with another.  Conveniently, it can use just about any type of data as the key.  Consider if we wish to have an array of strings, indexed by double-precision floating point numbers.

We would declare that array like so:

char[][double] myAssociativeArrayOfStrings;

Then we can easily insert values into the array.

myAssociativeArrayOfStrings[34.5] = "Hello";

And easily access the values.

writefln(myAssociativeArrayOfStrings[34.5]);

Perhaps a less contrived example of the usefulness of this is keeping track of scores for a set of players in a game.

int[char[]] scoresByPlayer;

scoresByPlayer["Chris"] = 43981;
scoresByPlayer["John"]  = 67215;

Of course, at this point you're probably wondering how we can even begin to know what keys are available at any given time.  As a solution to this problem we have the keys property which returns a dynamic array of keys for the associative array.

As with other arrays, associative arrays also have a length property which will indicate the number of entries.

8.  Iterating

Now that we've seen arrays which can contain many values, it should be fairly clear that it would be tedious to have to deal with each of those values separately, especially since most of the code is going to be repeated.

Fortunately there are a couple of ways to iterate over the contents of an array.

The classic "for" loop uses a counter variable.  This variable has an initial value, a test, and an update.  As long as the test holds true, the loop continues.  The loop also has a body.  This body code gets run on each loop.  After it runs, the update code is run.

Let's start with an array of integers representing a set of scores in a game.

static int[] scores = [5621, 9845, 1274];

We then write out the basic shell of a for loop.

for ( ; ; )
{

}

Let's start out with a counter with an initial value of zero, since arrays are indexed starting with zero.

for (int scoreIndex = 0; ; )
{

}

We then need a test.  We know that the indexes of an array will always be one less than the length of the array.

for (int scoreIndex = 0; scoreIndex < scores.length; )
{

}

And lastly we need an update.  In this case, we simple increment the counter.

for (int scoreIndex = 0; scoreIndex < scores.length; scoreIndex++)
{

}

Now we just need some code to run each time.

for (int scoreIndex = 0; scoreIndex < scores.length; scoreIndex++)
{
   writefln("Score #%d is %d",
            scoreIndex,
            scores[scoreIndex]);
}

This allows us some considerable freedom in how we iterate over arrays.  We could access every other score by changing the update code.

for (int scoreIndex = 0; scoreIndex < scores.length; scoreIndex += 2)
{
   writefln("Score #%d is %d",
            scoreIndex,
            scores[scoreIndex]);
}

However, probably the most common way of iterating over an array involves accessing each element in order with no regard for the index.  In this case, we're provided with a shortcut.

The "foreach" loop can be used to more easily access elements of an array.

foreach (int score; scores)
{
   writefln("Score is %d", score);
}

Of course, we could still maintain an index.

int scoreIndex = 0;

foreach (int score; scores)
{
   writefln("Score #%d is %d", 
            scoreIndex,
            score);
   scoreIndex++;
}

The "for" loop style is often used when we want to not only access, but also modify values in an array.  Consider if we wish to give ourselves a little ego boost and add 100 points to all of our scores.

for (int scoreIndex = 0; scoreIndex < scores.length; scoreIndex++)
{
   scores[scoreIndex] += 100;
}

We can achieve this more easily with the "foreach" loop, though.  We need only specify that the element in the array which we're accessing is "inout", or available both to access and to change.  It can go "in", and come "out" of the loop with a new value.

foreach (inout int score; scores)
{
   score += 100;
}

9.  If Statements

At some point you're certain to wonder how we can choose one course of action or another.  To accomplish this we use the "if" statement.

This involves a test, and if the test is true, a block of code is executed.  If it's false, however, then an optional "else" block is executed.

Let's tie this into the example in the previous section and offer commentary on a set of grades.

import std.stdio;

void main()
{
   static int[] scores = [5621, 9845, 1274];

   foreach (int score; scores)
   {
      if (score < 4000)
      {
         writefln("%d?  What a pathetic score.",
                  score);
      }
      else
      {
         writefln("%d...  Not bad.",
                  score);
      }
   }
}

Computers can be so cruel.

Of course, we can be a bit more specific than that.

if (score < 4000)
{
   writefln("%d?  What a pathetic score.",
            score);
}
else if (score < 7500)
{
   writefln("%d...  Not bad.",
            score);
}
else
{
   writefln("%d!  Whoa... I'm not worthy.",
            score);
}

10.  On Style and Brevity

In the previous section, I used curly braces extensively to indicate blocks.  However, if there is only a single statement, the curly braces are optional.  

The previous example could be rewritten as:

import std.stdio;

void main()
{
   static int[] scores = [5621, 9845, 1274];

   foreach (int score; scores)
      if (score < 4000)
         writefln("%d?  What a pathetic score.", score);
      else if (score < 7500)
         writefln("%d...  Not bad.", score);
      else
         writefln("%d!  Whoa... I'm not worthy.", score);
}

This style should be used very sparingly.

11.  Arrays and "main" and More on Looping

The main function we've seen thus far has no parameters.  This is acceptable, but there is another acceptable form.

void main(char[][] commandLineArguments)
{

}

This specifies an array of strings as the sole parameter to main.  This array will contain any arguments passed to the program at the command-line.  The first argument is always the name of the program itself, so the arguments really only begin with the second element of this array.

Let's look at a simple "Hello world" program.

import std.stdio;

void main()
{
   writefln("Hello world");
}

Now, let's introduce a function so that we can say hello to anyone.

void sayHelloTo(char[] nameToGreet)
{
   writefln("Hello, %s!", nameToGreet);
}

And we'll put that all together to greet someone named "Chris".

import std.stdio;

void main()
{
   sayHelloTo("Chris");
}

void sayHelloTo(char[] nameToGreet)
{
   writefln("Hello, %s!", nameToGreet);
}

But that's still pretty static.  Let's pass the name of the person to greet into the program via a command-line argument.

import std.stdio;

void main(char[][] commandLineArguments)
{
   sayHelloTo(commandLineArguments[1]);
}

void sayHelloTo(char[] nameToGreet)
{
   writefln("Hello, %s!", nameToGreet);
}

There's a problem, though. What if I didn't pass in an argument?  The commandLineArguments array will only contain one element, and my access of it will be out of bounds.  An error will occur.

I can detect the length of the array, though, and I can do one thing or another with the if statement.  Together I can use these to avoid problems.

import std.stdio;

void main(char[][] commandLineArguments)
{
   if (commandLineArguments.length > 1)
      sayHelloTo(commandLineArguments[1]);
   else
      sayHelloTo("world");
}

void sayHelloTo(char[] nameToGreet)
{
   writefln("Hello, %s!", nameToGreet);
}

And what if I wanted to greet several names?  Certainly I can pass several arguments to the program at the command-line. 

Well, in this case, I want to use a loop.  But I want to avoid the first element.  There are a few ways of accomplishing this.

I could loop over every element in the array, and maintain a counter.  When the counter is zero, I could use the "continue" statement to skip to the next iteration immediately, being certain to increment the counter first.

import std.stdio;

void main(char[][] commandLineArguments)
{
   int argumentCounter = 0;

   foreach (char[] argument; commandLineArguments)
   {
      if (argumentCounter == 0)
      {
         argumentCounter++;
         continue;
      }

      sayHelloTo(argument);
      argumentCounter++;
   }
}

void sayHelloTo(char[] nameToGreet)
{
   writefln("Hello, %s!", nameToGreet);
}

The other option available to me is to simply start iterating at the second element.  The most reasonable way to accomplish this would seem to be array slicing.  As seen with strings, we can access a portion of an array called a "slice".

Again, since we can't count on any arguments being passed in, we should test to see if arguments other than the name of the program are present.  Once we've ascertained that, we can create the slice and iterate over it.

import std.stdio;

void main(char[][] commandLineArguments)
{
   if (commandLineArguments.length > 1)
   {
      foreach (char[] argument; commandLineArguments[1 .. length])
      { 
         sayHelloTo(argument);
      }
   }
}

void sayHelloTo(char[] nameToGreet)
{
   writefln("Hello, %s!", nameToGreet);
}  

You should note the odd presence of the undeclared "length" variable.  Conveniently, the length variable is always created within a slice to refer to the length of the array being sliced.  Without this convenience, we'd have to write:

commandLineArguments[1 .. commandLineArguments.length]

Let's look at another useful statement within a loop.  The "break" statement allows us to exit out of the loop entirely at an arbitrary point.

For instance, we may wish to greet people until we run into "Mortimer".  Then we'll stop greeting anyone.

import std.stdio;

void main(char[][] commandLineArguments)
{
   if (commandLineArguments.length > 1)
   {
      foreach (char[] argument; commandLineArguments[1 .. length])
      { 
         if (argument == "Mortimer")
         {
            writefln("Mortimer?  Are you kidding?  I'm done with this.");
            break;
         }

         sayHelloTo(argument);
      }
   }
}

void sayHelloTo(char[] nameToGreet)
{
   writefln("Hello, %s!", nameToGreet);
}

12.  Function Pointers and Aliases

As we've seen already, functions are the means by which all executable code in a program is organized.  In other words, they're among the most important "things" in a program.  

Yet, it doesn't seem we can actually do much with them.  We can call them, and that's about it.

Well, as it happens, we can also pass them around like variables.  This ability comes about due to the fact that a function is really just a place in memory where a certain section of executable code starts.  We can get a "pointer" to this place in memory, store that, and then use it to call the function later on.

In the previous section we said hello to any name passed in as an argument at the command-line.  We defined the sayHelloTo function to accomplish this.  We needn't be limited to just that one function, though.  Let's create a less upbeat function.

void tellOff(char[] nameToTellOff)
{
   writefln("Damn you %s!",
            nameToTellOff);
}

And maybe a slang version:

void greetWithSlang(char[] nameToGreetWithSlang)
{
   writefln("Yo %s.  Sup?",
            nameToGreetWithSlang);
}

Now, we could simply write a program which greets all of the names with all three functions like so:

import std.stdio;

void main(char[][] commandLineArguments)
{
   if (commandLineArguments.length > 1)
   {
      foreach (char[] argument; commandLineArguments[1 .. length])
      { 
         sayHelloTo(argument);
      }

      foreach (char[] argument; commandLineArguments[1 .. length])
      { 
         tellOff(argument);
      }

      foreach (char[] argument; commandLineArguments[1 .. length])
      { 
         greetWithSlang(argument);
      }
   }
}

void sayHelloTo(char[] nameToGreet)
{
   writefln("Hello, %s!", nameToGreet);
}

void tellOff(char[] nameToTellOff)
{
   writefln("Damn you %s!",
            nameToTellOff);
}

void greetWithSlang(char[] nameToGreetWithSlang)
{
   writefln("Yo %s.  Sup?",
            nameToGreetWithSlang);
} 

However, we should always be looking for ways to streamline our code, and reduce redundancies.  Therefore, as we look at the above program, we notice that the three loops are exactly identical, except for the name of the function being called.

So, we need to identify a generic description of these functions.  We know that they all return void.  We know that they all take one argument: a string.  That's enough info.

void function(char[])

We can now declare a pointer to a function.

void function(char[]) greeterFunction;

Initializing this variable takes advantage of the & operator, which finds the memory address of a variable or function.

void function(char[]) greeterFunction = &sayHelloTo;

As with other data types, we can now create an array of these, and statically initialize it with the three functions we've created thus far.

static void function(char[])[] greeters = 
   [&sayHelloTo, &tellOff, &GreetWithSlang];

At this point, the type is beginning to look sloppy.  We can use a type alias to clear this up.

alias void function(char[]) GreeterFunction;

We can now write:

static GreeterFunction[] greeters = 
   [&sayHelloTo, &tellOff, &GreetWithSlang];

Now, we just need to incorporate this back into the program.  We'll need to iterate over each of the function pointers, and for each of those, iterate over the arguments passed in at the command-line.

import std.stdio;

void main(char[][] commandLineArguments)
{
   alias void function(char[]) GreeterFunction;
   
   static GreeterFunction[] greeters = 
      [&sayHelloTo, &tellOff, &GreetWithSlang];

   if (commandLineArguments.length > 1)
   {
      foreach (GreeterFunction greeter; greeters)
      {
         foreach (char[] argument; commandLineArguments[1 .. length])
         { 
            greeter(argument);
         }
      }
   }
}

void sayHelloTo(char[] nameToGreet)
{
   writefln("Hello, %s!", nameToGreet);
}

void tellOff(char[] nameToTellOff)
{
   writefln("Damn you %s!",
            nameToTellOff);
}

void greetWithSlang(char[] nameToGreetWithSlang)
{
   writefln("Yo %s.  Sup?",
            nameToGreetWithSlang);
}

13.  Labels and Breaking and Continuing in Nested Loops

In the previous section we ended up with a program with nested loops.  This has considerable ramifications, as we'll see.

First, though, we'll try to apply our filter for the name Mortimer.  Previously we had broken out of the loop when we encountered that name.  Let's do the same here.  For brevity's sake, only the main function is shown, since the other functions remain unchanged.

import std.stdio;

void main(char[][] commandLineArguments)
{
   alias void function(char[]) GreeterFunction;
   
   static GreeterFunction[] greeters = 
      [&sayHelloTo, &tellOff, &GreetWithSlang];

   if (commandLineArguments.length > 1)
   {
      foreach (GreeterFunction greeter; greeters)
      {
         foreach (char[] argument; commandLineArguments[1 .. length])
         { 
            if (argument == "Mortimer")
            {
               writefln("Mortimer?  Are you kidding?  I'm done with this.");
               break;
            }

            greeter(argument);
         }
      }
   }
}

When we run this we notice an odd behavior.  The program will run upto "Mortimer", then run the next function upto Mortimer, and then the next function.  

We wanted the program to stop looping entirely when it encountered that name.  The problem lies with the default behavior of break.  It will break out of the innermost loop.

In order to change this behavior we must first give labels to the loops, effectively naming them.

import std.stdio;

void main(char[][] commandLineArguments)
{
   alias void function(char[]) GreeterFunction;
   
   static GreeterFunction[] greeters = 
      [&sayHelloTo, &tellOff, &GreetWithSlang];

   if (commandLineArguments.length > 1)
   {
      functionLoop: 
      foreach (GreeterFunction greeter; greeters)
      {
         argumentLoop:
         foreach (char[] argument; commandLineArguments[1 .. length])
         { 
            if (argument == "Mortimer")
            {
               writefln("Mortimer?  Are you kidding?  I'm done with this.");
               break;
            }

            greeter(argument);
         }
      }
   }
}

The default behavior of the break statement remains the same.  Its behavior can be modified, though, by telling it which loop to break out of.

import std.stdio;

void main(char[][] commandLineArguments)
{
   alias void function(char[]) GreeterFunction;
   
   static GreeterFunction[] greeters = 
      [&sayHelloTo, &tellOff, &GreetWithSlang];

   if (commandLineArguments.length > 1)
   {
      functionLoop: 
      foreach (GreeterFunction greeter; greeters)
      {
         argumentLoop:
         foreach (char[] argument; commandLineArguments[1 .. length])
         { 
            if (argument == "Mortimer")
            {
               writefln("Mortimer?  Are you kidding?  I'm done with this.");
               break functionLoop;
            }

            greeter(argument);
         }
      }
   }
}

If we actually wanted the behavior shown by our first shot at this program, and which we labelled as erroneous, we could use the continue statement.  By default the continue statement would simply go to the next iteration of the argument loop, but we can also specify a loop for it to continue for.

import std.stdio;

void main(char[][] commandLineArguments)
{
   alias void function(char[]) GreeterFunction;
   
   static GreeterFunction[] greeters = 
      [&sayHelloTo, &tellOff, &GreetWithSlang];

   if (commandLineArguments.length > 1)
   {
      functionLoop: 
      foreach (GreeterFunction greeter; greeters)
      {
         argumentLoop:
         foreach (char[] argument; commandLineArguments[1 .. length])
         { 
            if (argument == "Mortimer")
            {
               writefln("Mortimer?  Are you kidding?  I'm done with this.");
               continue functionLoop;
            }

            greeter(argument);
         }
      }
   }
}

14.  Other Loops: Flexibility, Along With Some Basic Input and Scoping

As a follow up to the previous sections, for and foreach are not the only types of loops.  There are also simpler loops, which give us further flexibility, though are less syntactically convenient.

The more common of these is the "while" loop.  

With the for loop we outlined three things the loop needed:


An initial state for the counter.
A test to see if the counter is still valid.
An update for the counter.


The for loop gave us a convenient form for listing these three elements.  With the while loop they're still present, but individually located.

Typically the initial state is set up just before entering the loop.  The test is located in parentheses after the "while" keyword.  The update may occur anywhere within the body of the loop.

Let's convert a for loop to a while loop.  This loop is fairly simple.  It counts from one to ten, outputting each number.

for (int count = 0; count  dmd myprogram.d greeting.d -w
