2011/1/30 - 2011/7/28
Pen Jr.D言語なんてやってみそWelcome BeginnerHow old
配列( Arrays
最初から理解する必要はありませんが、基本的なプログラムにも出てきますから、ある程度のことは知っておいた方が良いでしょう。
配列
配列 というのは数値や文字などの並びで、[] を使って ランダム にアクセス出来ます。

文字列 は文字の 配列 です。

import std.stdio;

  /*----------------------------
  *     エントリーポイント
  */
void main(){
  // 文字列 s を宣言&初期化
  // 
  string s = "String";
  
  // 文字列を文字に分解
  writeln(
    s, " = [ ",
    s[ 0 ], ", ", s[ 1 ], ", ", s[ 2 ], ", ",
    s[ 3 ], ", ", s[ 4 ], ", ", s[ 5 ],
    " ]"
  );
  // 自由にアクセス
  writeln(
    s[ 0 ], s[ 0 ], s[ 0 ], s[ 1 ], s[ 1 ], s[ 2 ],
    s[ 3 ], s[ 4 ], s[ 4 ], s[ 5 ], s[ 5 ], s[ 5 ],
    '\n',
    s[ 5 ], s[ 4 ], s[ 3 ], s[ 2 ], s[ 1 ], s[ 0 ]
  );
}
D:\Pen-Jr\My Programs\Welcome>dmd test

D:\Pen-Jr\My Programs\Welcome>test
String = [ S, t, r, i, n, g ]
SSSttrinnggg
gnirtS
要素数
D言語 の 配列 は 要素数 を保持していて、.length を使って所得出来ます。

要素数 は、

配列.length

また、[] 内では length の代わりに $ を使います。

import std.stdio;

void main(){
  // 文字列 s
  string s = "String";
  
  writeln(
    "s = ", s,
    "\ns.length = ", s.length,
    // [] 内の $ == length
    "\ns[ $ - 6 ] = ", s[ $ - 6 ],
    "\ns[ $ - 3 ] = ", s[ $ - 3 ],
    // 最後の文字は $ - 1
    "\ns[ $ - 1 ] = ", s[ $ - 1 ]
  );
}
D:\Pen-Jr\My Programs\Welcome>test
s = String
s.length = 6
s[ $ - 6 ] = S
s[ $ - 3 ] = i
s[ $ - 1 ] = g
ポインタ( Pointers
C言語 の 文字列 は char* という 文字列(文字)ポインター で扱います。

ポインタ を 配列 と同じ様に扱うことも出来ます。

import std.stdio;

void main(){
  // C言語の文字列
  char* s = cast( char* )"String";
  
  // 配列同様の扱いも出来るだ
  writeln(
    s, " = [ ",
    s[ 0 ], ", ", s[ 1 ], ", ", s[ 2 ], ", ",
    s[ 3 ], ", ", s[ 4 ], ", ", s[ 5 ],
    " ]"
  );
}
cast
D:\Pen-Jr\My Programs\Welcome>test
4660B0 = [ S, t, r, i, n, g ]
ポインタは基本ですが、C言語の様に「理解できないと先に進めない」というようなことはありません。
スライシング
配列[ LwrExpression .. UprExpression ]
配列 の一部を簡単に切り出すことが出来ます。
import std.stdio;

  /*----------------------------
  *     エントリーポイント
  */
void main(){
  string s = "String";
  
  // 文字列の一部を取り出す
  writeln(
    s[ 0 .. 3 ],
    '\n',
    s[ 2 .. 5 ],
    '\n',
    // $ は配列の要素数
    s[ 3 .. $ ]
  );
}
D:\Pen-Jr\My Programs\Welcome>test
Str
rin
ing

配列操作だけでなく、ポインタ から 配列 への 変換 もできます。

配列の結合( Array Concatenation
Cat 式( Cat Expressions )
文字列リテラルの結合
文字列リテラル の場合は 空白 で区切られた 文字列 も同様に 結合 されます。
import std.stdio;

void main(){
  writeln( "Welcome" ~ "D" ~ "Language" );
  writeln( "Welcome" "D" "Language" );
  // 改行しても結合するだ
  writeln(
    "Welcome"
    "D"
    "Language"
  );
  
  string s =
    "Welcome"
    "D"
    "Language";
  writeln( s );
}

実行すれば

D:\Pen-Jr\My Programs\Welcome>test
WelcomeDLanguage
WelcomeDLanguage
WelcomeDLanguage
WelcomeDLanguage

全部同じです。

長い 文字列 を ソースコード で見やすく書くことが出来ます。
ゼロから数えよう
こんぴゅーたー は ゼロ から数えるのが基本です。

こんぴゅーたー世界の野球なら、イチローは 0番 バッターですし、ピッチャーの守備番号も 0番 ですから「5-3-2 の ダブルプレー」とかいうことになります。

初めは馴染まないかも知れませんが、常に ゼロ から数える基本に従って、とにかく慣れることが大切です。

コンソールアプリ
エントリーポイント(main 関数)
D言語 コンソールアプリは main 関数 が エントリーポイント と決まっていますから、コンソールアプリ の場合、main は予約語みたいなものです。

C や C++ も エントリーポイント は main 関数 ですが、D言語 は同じ様に書くことも 引数 や 返値 を省略して書くこともことも出来ます。

引数
main 関数 の 引数 は「文字列 の 配列」で、起動時に コマンドライン から任意の
引数( 文字列 ) を受け取ることが出来ます。
  // C言語などは
( char[][] args )

  // D言語は以下もOK
( string[] args )
( in string[] args )
import std.stdio;

  /*----------------------------
  *     エントリーポイント
  *
  *   引数が2つじゃないとエラー
  */
void main( string[] args ){
  //          ^ 引数は文字列配列
  writeln( args[ 0 ] );    // 常に実行ファイル名
  writeln( args[ 1 ] );    // 第1引数
  writeln( args[ 2 ] );    // 第2引数
}

引数 は空白で区切ります。

D:\Pen-Jr\My Programs\Tests>dmd test

D:\Pen-Jr\My Programs\Tests>test abc 123
test
abc
123
返値
D言語 では省略できますが、コンソールアプリ には 返値 があります。
import std.stdio;

  /*----------------------------
  *     エントリーポイント
  *
  *   C言語などでは返値や引数が不要でもこう書く
  */
int main( char[][] args ){
  writeln( "Hallo World" );
  
  return 0;   // 0 を返して終了する。
}
連続実行するアプリケーションの場合などは正常終了したか知る必要があったり、決まった 返値 が必要な場合もありますが、単独実行されるアプリケーションの場合 返値 は何を返しても問題ありませんから、省略して良いでしょう。
標準入出力( Standard I/O )
コンソールアプリ での 入出力(ユーザーインターフェイス)は 標準入出力 を使います。

デフォルト の 標準出力 は モニター(コマンドプロンプト画面)、標準入力 は キーボード になっていますが、共に ファイル などに切り替えが可能なので、「画面出力」や「キーボード入力」とは言いません。

もちろん 画面出力 や キーボード入力 専用にすることも出来ますが、説明が簡単になること以外の メリット はありませんから、標準出力 及び 標準入力関数 を使います。

ファイルの読み書きなどには、別の 関数 を使うか 指定 が必要ですから、基本的には 画面出力、キーボード入力 と解釈して問題ありません。
Phobos( 標準ライブラリ
関数テンプレート( Function Templates
返値型 関数名( テンプレート引数 )( 引数 ... )
というように、関数名の後に 引数リスト の () が 2つ あるのが 関数テンプレート です。
本来、オブジェクト指向 などより後( 要するに最後 )に学習すべきと思いますが、

D言語 の テンプレート は、妄想だけで実現不可能だったコードが書けてしまう 魔法の薬 だもんで、Phobos の 基本的 な 関数 にも使われていますし、今後も増えるのは確実ですから、基本的な使い方だけは 大雑把 にでも理解しておいた方が良いでしょう。

テンプレート( Templates
テンプレート というのは
template Hoge( T ){ ...
というよな記述がされ、インスタンス化 して使います。

インスタンス化 の 明示 には ! を使って、

Hoge!( int ).Foo ...;
とか・・・
テンプレート は、そのままでは 実体 のないコードで、実体化 が必要ということで、
実体化 の 明示 には 否定 で使う記号 ! が使われます。
ジェネリックプログラミング
テンプレートは、D でジェネリックプログラミングを行う方法です。

ということで、ジェネリックプログラミング とは

データ形式に依存しないコンピュータプログラミング方式。

ということです。

「じゃあ、面倒な型だとか憶えなくていいの?」

さあ・・・

多分、そういうことじゃないし、無理です。

メタプログラミング
テンプレート は メタプログラミング という、ソースコードを生成するためのものですから、生成するソースコードをよく知っていることが大前提です。

基本的な 言語仕様 を良く理解していることと、また、組み込み 異種言語 という認識も必要です。

C言語 の プリプロセッサ を使ったコードも メタプログラム になります。

単に原始的な マクロ言語 なのですが、プリプロセッサ を使う記述が 基本 なため、C言語 を複雑で難解なものに仕上げています。

インスタンス化( 呼び出し )
関数テンプレート というのは、「データ形式( 型 )に依存しない関数」ということで、基本的には 型 などを指定して インスタンス化 する必要がありますが、
void hoge( T )( T value )
T hoge( T )( T value )
T hoge( T, C )( T value, C val )
T[] hoge( T )( T[] value )
などの 関数テンプレート の 引数の 型 は コンパイラ が 判定( 推論 )するため、呼び出し時の テンプレート引数 を 全て省略 出来ます。

その場合、インスタンス化 の 明示 も不要で、

hoge( "Foo" );
hoge( 100 );
s = hoge( val );
などのように、普通の 関数同様 の呼び出しが出来ます。

型 の制限など処理は 関数内 でするため、その制約は 関数 によります。

また、

void hoge( T ... )( T values )
int foo( T ... )( T values )
のような場合は、引数 の 数 も関数の呼び出し時に決まりますから、
hoge( "Foo", 100, "Hoge" );
i = foo( "Hoge", s, 100, "Foo" );
引数 の 数 が 可変個 になります。
次のような場合は、
T hoge( T, C )( C value )

T foo( T, C ... )( C values )
引数の 型 は呼び出し時に コンパイラが判定しますが、

返値型 の 指定 が必要ですから、

s = hoge!string( 100 );
s = hoge!( string )( i );
i = foo!int( "Foo", "Hoge", 100 );
というように、! を使った インスタンス化 の 明示 が必要になります。
std.windows.charset
日本語環境専用?(文字化け
実際、日本からの要求で追加されたような記憶があります。
writeln などの Phobos での 文字出力 は Unicode で、日本語環境 の コマンドプロンプト の表示は Shift-JIS だけですから、

そのまま 日本語 を出力させると、

import std.stdio;

void main( in string[] args ){
  writeln( "abcABC日本語あいう0123" );
}
D:\Pen-Jr\My Programs\Tests>test
abcABC譌・譛ャ隱槭≠縺・≧0123

と、日本語が 文字化け します。

文字化け させないためには、Shift-JIS に変換する必要があります。
toMBSz
UTF-8Shift-JIS に変換。
const( char )* toMBSz( string s, uint codePage = 0 );
import std.windows.charset, std.stdio;

void main(){
  // C の標準出力(std.c.stdio)
  puts( toMBSz( "abcABC日本語あいう0123" ) );
  
  // D言語の標準出力は char* だとうまくない
  writeln( toMBSz( "abcABC日本語あいう0123" ) );
}
puts
D:\Pen-Jr\My Programs\Tests>test
abcABC日本語あいう0123
484C40
※ ポインター の値は実行毎に違う。

日本語表示は出来ましたが、

返値 が C言語 の文字列(0終端のポインター)なので、C言語 の 関数(puts)に直接アクセスする場合は都合がよいのですが、writeln などでは ポインター は アドレス( 数値 )が表示されてしまいます。

スライス を使えば、C言語 の文字列を D言語 の文字列に変換出来ますが、そのためには文字列の長さを得る必要があります。

std.c
C言語 の 標準ライブラリー の一部など、いくつかの C言語 の 関数 が使えます。
std.c.string
Cstring.h
size_t strlen( in char* s );

0 終端文字列 s の長さを得ます。

UTF8 から Shift-JIS への変換関数は、

const( char )[] toMBS( in string s ){
  const( char )* ret = toMBSz( s );
  // char* を文字列に
  return ret[ 0 .. strlen( ret ) ];
}
in を付けるのは良い慣習として後のため。
std.c.stdio
Cstdio.h
int puts( in char* s );
C言語 の 標準出力。
std.stdio
標準入出力。

stc.c.stdiopublic import しています。

write
標準出力。
void write( T... )( T args );
関数テンプレート
引数 の数や 型 の指定がなく、受け取った 引数 が表示可能な 型 ならば順次出力します。
import std.windows.charset, std.stdio, std.c.string;

  /*----------------------------
  *   UTF8 から Shift-JIS へ変換
  *
  */
const( char )[] toMBS( in string s ){
  const( char )* ret = toMBSz( s );
  return ret[ 0 .. strlen( ret ) ];
}

  /*----------------------------
  *     エントリーポイント
  *
  *   引数なしの実行はエラーになる
  *
  *   引数に in を付けるメリットはここではないけど、
  *   習慣としてはグ!
  */
void main( in string[] args ){
  write( toMBS( "うぃ!" ) );
  
  /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ^     標準出力
  ^   引数は続けても OK
  */
  write(
    toMBS( "おめぇ" ), args[ 1 ], toMBS( "才かよ" )
    );
  write( toMBS( "バイ!" ) );
}
D:\Pen-Jr\My Programs\Tests>howold 28
うぃ!おめぇ28才かよバイ!
D:\Pen-Jr\My Programs\Tests>
writeln
void writeln( T... )( T args );
write( args, '\n' ) と同等。

writewriteln に書き換えると、

D:\Pen-Jr\My Programs\Tests>howold 38
うぃ!
おめぇ38才かよ
バイ!

D:\Pen-Jr\My Programs\Tests>
readln
string readln( dchar terminator = '\x0a' );

コマンドライン入力 を 標準入力 に変更。

  /*----------------------------
  *     エントリーポイント
  *
  */
void main(){
  writeln( toMBS( "おっ!" ) );
  
  /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ^     標準出力
  ^   readln は無言
  */
  write( toMBS( "歳はなんぼじゃ? " ) );
  //
  string s = readln();
  
  writeln(
    toMBS( "おめぇ" ), s, toMBS( "才かよ" )
  );
  writeln( toMBS( "バイ!" ) );
}
D:\Pen-Jr\My Programs\Tests>howold
おっ!
歳はなんぼじゃ?

実行すると入力待ちになります。

おっ!
歳はなんぼじゃ? 18
おめぇ18
才かよ
バイ!

改行(terminator = 0x0a)込みで読み込まれて、うまくなかったりします。

出力時にスライスを使って最後の改行を除きます。

writeln(
  toMBS( "おめぇ" ),
  s[ 0 .. $ - 1 ],       // 最後の1文字を除いた
  toMBS( "才かよ" )
);
おっ!
歳はなんぼじゃ? 19
おめぇ19才かよ
バイ!
std.string
組み込みにない 文字列 処理。
chomp
C[] chomp( C )( C[] s );
関数テンプレート
文字列 s から末尾の CR,LF,CRLF を全て取り除きます。
import  std.windows.charset, std.stdio,
        std.c.string, std.string;

  /*----------------------------
  *   UTF8 から Shift-JIS へ変換
  */
const( char )[] toMBS( in string s ){
  const( char )* ret = toMBSz( s );
  return ret[ 0 .. strlen( ret ) ];
}

  /*----------------------------
  *     エントリーポイント
  *
  */
void main(){
  writeln( toMBS( "おっ!" ) );
  
  // 標準入力
  write( toMBS( "歳はなんぼじゃ? " ) );
  string s = readln();
  
  writeln(
    toMBS( "おめぇ" ),
    /*^^^^^^^^^^^^^^^^^^^^^^^^^^
    ^   末尾の改行を取り除く
    */
    chomp( s ),
    toMBS( "才かよ" )
  );
  
  
  writeln( toMBS( "ンじゃ!" ) );
}

スライスを使って最後の文字を取り除いても結果は同じですが、スライスでは最後の文字が改行ではなくても取り除いてしまいますから、readln の仕様に依存します。

isNumeric
final bool isNumeric(
    string s, in bool bAllowSep = false
  );
文字列 s が整数または浮動小数点数か判定。
std.ascii
文字 の簡単な分類。
isDigit
bool isDigit( dchar c );
文字 c が数字( 0..9 )なら 0 以外を返します。
std.conv
to
万能型変換。
template to( T )
テンプレート
to!( TargetType )( value );
to!TargetType( value );
テンプレートな記述 ! は省略出来ませんが、かなり便利です。

C言語 の 0 終端文字列 を D言語 の文字列に、

import std.stdio, std.windows.charset, std.conv;

void main(){
  writeln(
    to!string( toMBSz( "日本語" ) )
  );
}
D:\Pen-Jr\My Programs\Tests>test
日本語

文字列 を 数値に

import std.stdio, std.conv;

void main(){
  auto i = to!int( "123" );
  // 文字列じゃ計算できないよ
  writeln( i * 100 );
  writeln( i * i );
  
  // これは実行時エラー
  to!int( "ABC" );
}
D:\Pen-Jr\My Programs\Tests>dmd test

D:\Pen-Jr\My Programs\Tests>test
12300
15129

std.conv.ConvException: std.conv(727):
 Can't convert value `ABC' of type const(char)[] to type int
std.conv.ConvException: std.conv(1129):
 Can't convert value `ABC' of type const(char)[] to type int
to!string( 整数値, radix )
整数値 を radix進数 表記の文字列に変換します。
import std.stdio, std.conv;

void main(){
  auto i = 9876543210;
  writeln(
    i, '\n',
    // 16進数表記
    to!string( i, 16 ), '\n',
    // 2進数表記
    to!string( i, 2 )
  );
}
D:\Pen-Jr\My Programs\Tests>test
9876543210
24CB016EA
1001001100101100000001011011101010
サンプルコード
ほとんど Phobos を使うだけですが、

結構それっぽくなります。

import  std.windows.charset, std.stdio,
        std.conv, std.string;

  /*----------------------------
  *   UTF8 から Shift-JIS へ変換
  *
  *     toMBSz で文字コード変換し
  *       string を返す
  */
string toMBS( in string s ){
  return to!string( toMBSz( s ) );
}

  /*----------------------------
  *     標準入力
  *
  *   邪魔な改行も取り除いてから返す
  */
string inPut( in string s ){
  write( toMBS( s ) );
  return chomp( readln() );
}

  /*----------------------------
  *     エントリーポイント
  *
  */
void main(){
  writeln( toMBS( "おっ!" ) );
  
  string s = inPut( "歳はなんぼじゃ? " );
  
  write( toMBS( "おめぇ" ), s, toMBS( "才かよ" ) );
  
  /*++++++++++++++++++++++++++++
  +
  +     入力文字列を数値に変換
  +
  +   数値に変換出来ないとエラーだよ
  */
  int i = to!int( s );
  
  writeln(
    toMBS( "来年は" ), i + 1, toMBS( "才だぁな\n" ),
    toMBS( "30年たったら" ), i + 30, toMBS( "才ちゅうわけだ\n" ),
    toMBS( "めでてぇや\nほじゃ!" )
  );
}
D:\Pen-Jr\My Programs\Tests>howold
おっ!
歳はなんぼじゃ? 23
おめぇ23才かよ
来年は24才だぁな
30年たったら53才ちゅうわけだ
めでてぇや
ほじゃ!
制御文(
条件分岐
If 文( If Statement
基本中の基本。
if( 条件式 ) スコープ文
条件式が 真 なら次の スコープ文 が実行されます。

コマンドライン引数の処理は

import std.windows.charset, std.stdio, std.conv;

  /*----------------------------
  *   UTF8 から Shift-JIS へ変換
  */
string toMBS( in string s ){
  return to!string( toMBSz( s ) );
}

  /*----------------------------
  *     エントリーポイント
  *
  */
void main( in string[] args ){
  writeln( toMBS( "おっ!" ) );
  
    /*^^^^^^^^^^^^^^^^^^^^^^^^^^
    ^       If 文
    ^
    ^   引数があるか確認する
    ^
    ^   .length は配列の要素数
    ^   最初の引数は常にファイル名
    */
  if( args.length > 1 )
  {
    // 引数がなければ実行されない
    writeln(
      toMBS( "おめぇ" ), args[ 1 ], toMBS( "才かよ" )
    );
  }
  writeln( toMBS( "バイ!" ) );
}

これで、引数なしで実行してもエラーにはなりません。

if( 条件式 ) スコープ文 else スコープ文
条件式が 真 なら次の スコープ文 が実行され、

そうでなければ else の次の スコープ文 が実行されます。

  /*----------------------------
  *     エントリーポイント
  *
  */
void main( in string[] args ){
  writeln( toMBS( "おっ!" ) );
  
  // 引数があるか?
  if( args.length > 1 )
  {
    writeln(
      toMBS( "おめぇ" ), args[ 1 ], toMBS( "才かよ" )
    );
  }
  // 引数がない時の処理
  else
  {
    writeln( toMBS( "無言かよ" ) );
  }
  writeln( toMBS( "バイ!" ) );
}
D:\Pen-Jr\My Programs\Tests>howold
おっ!
無言かよ
バイ!

入力が数値か確認して、その数値によって処理を変える。

import  std.windows.charset, std.stdio,
        std.conv, std.string;

  /*----------------------------
  *   UTF8 から Shift-JIS へ変換
  */
string toMBS( in string s ){
  return to!string( toMBSz( s ) );
}

  /*----------------------------
  *     標準入力
  */
string inPut( in string s ){
  write( toMBS( s ) );
  return chomp( readln() );
}

  /*----------------------------
  *     エントリーポイント
  *
  */
void main(){
  writeln( toMBS( "おっ!" ) );
  
  string s = inPut( "歳はなんぼじゃ? " );
  
  // 数値が入力されたか確認する
  if( isNumeric( s ) )
  {
    writeln( toMBS( "おめぇ" ), s, toMBS( "才かよ" ) );
    
    // 文字列を数値に変換( 小数点があるとエラーだ )
    int i = to!int( s );
    
    if( i <= 10 )
    {
      writeln( toMBS( "なめてんか!\nうらぁ!!" ) );
    }
    else if( i >= 80 )
    {
      // if 文の中の if 文
      if( i >= 120 )
      {
        writeln( toMBS( "って、役人かよ!" ) );
      }
      // 対応する if 文に注意する
      else
      {
        writeln( toMBS( "マジすか?!\nしゃぁーす!" ) );
      }
    }
    else
    {
      writeln(
        toMBS( "来年は" ), i + 1, toMBS( "才だぁな\n" ),
        toMBS( "30年たったら" ), i + 30, toMBS( "才ちゅうわけだ\n" ),
        toMBS( "めでてぇや\nほじゃ!" )
      );
    }
  }
}

{} は省略して書くことも出来ます。

if( i <= 10 ) writeln( toMBS( "なめてんか!\nうらぁ!!" ) );
else if( i >= 80 )
  // {} がなくても、対応する else までひとつのスコープ文だよ
  if( i >= 120 ) writeln( toMBS( "って、役人かよ!" ) );
  // 対応する if 文によーーく注意する
  else writeln( toMBS( "マジすか?!\nしゃぁーす!" ) );
else
  writeln(
    toMBS( "来年は" ), i + 1, toMBS( "才だぁな\n" ),
    toMBS( "30年たったら" ), i + 30, toMBS( "才ちゅうわけだ\n" ),
    toMBS( "めでてぇや\nほじゃ!" )
  );
この場合全く同じ動作をしますが、読みづらくなり間違いの元でしかありません。

全部1行に収まるような簡単な処理以外 {} は省略しない方が良いでしょう。

Switch 文( Switch Statement
定番。
switch(  ) スコープ文
  case 定数リスト: 
  case 定数: .. case 定数: 
  default: 
switch 文の 式 を評価して、一致する case から次の break まで、
break がなければ 最後 まで実行します。

一致する case 文がないと default 文から実行され、

その場合 default がないと実行時に エラー になります。

定数リスト は、1つまたは , で区切られた 定数 で、値 が重複すると コンパイルエラー。
  /*----------------------------
  *     エントリーポイント
  *
  */
void main(){
  writeln( toMBS( "おっ!" ) );
  
  string s = inPut( "歳はなんぼじゃ? " );
  
  // 数値が入力されたか確認する
  if( isNumeric( s ) ){
    writeln( toMBS( "おめぇ" ), s, toMBS( "才かよ" ) );
    
    // 文字列を数値に変換
    // 小数点があるとエラーだし
    int i = to!int( s );
    
    /*^^^^^^^^^^^^^^^^^^^^^^^^^^
    ^     Switch 文
    ^
    ^   下1桁は無視する
    */
    switch( i / 10 )
    {
      case 0:
        writeln( toMBS( "なめてんか!\nうらぁ!!" ) );
        break;
      case 1, 2:
        writeln( toMBS( "よっ!わかもの!" ) );
        // break がないと次の処理もする
      case 3: .. case 7:
        writeln(
          toMBS( "来年は" ), i + 1, toMBS( "才だぁな\n" ),
          toMBS( "30年たったら" ), i + 30, toMBS( "才ちゅうわけだ\n" ),
          toMBS( "めでてぇや\nほじゃ!" )
        );
        break;
      default:
        if( i >= 120 ){
          writeln( toMBS( "って、役人かよ!" ) );
        }
        else{
          writeln( toMBS( "マジすか?!\nしゃぁーす!" ) );
        }
    }
  }
}
D:\Pen-Jr\My Programs\Tests>howold
おっ!
歳はなんぼじゃ? 20
おめぇ20才かよ
よっ!わかもの!
来年は21才だぁな
30年たったら50才ちゅうわけだ
めでてぇや
ほじゃ!

case 文が1つだけなら {} は省略できますが、それでは意味がありませんし「省略できない」と憶えておいて良いでしょう。

ループ
For 文( For Statement
ループの元祖で一般的だけど、レガシー、かな?
for( 初期化(宣言); 評価式;  ) スコープ文
初期化 -> 評価式が 真 なら スコープ文 を実行 -> 式(インクリメント)-> 繰り返し
import  std.windows.charset, std.stdio,
        std.conv, std.string;

  /*----------------------------
  *   UTF8 から Shift-JIS へ変換
  */
string toMBS( in string s ){
  return to!string( toMBSz( s ) );
}

  /*----------------------------
  *     標準入力
  */
string inPut( in string s ){
  write( toMBS( s ) );
  return chomp( readln() );
}

  /*----------------------------
  *     エントリーポイント
  *
  */
void main(){
  writeln( toMBS( "んちゃっ!" ) );
  
  string s = inPut( "歳はなんぼじゃ? " );
  
  // 数値が入力されたか確認する
  if( isNumeric( s ) ){
    writeln( toMBS( "おめぇ" ), s, toMBS( "才かよ" ) );
    
    /*
    *   文字列を数値に変換(数値以外はNG)
    *     i とか使うのやめる
    */
    int age = to!int( s );
    
    writeln(
      toMBS(
        "ほんじゃ歳の数だけ星のプレゼントじゃ"
        // 文字列リテラルは空白で分けて間にコメントもOK
        "\nうけとれや!"
      )
    );
    
    /*^^^^^^^^^^^^^^^^^^^^^^^^^^
    ^     For 文
    ^
    ^   age の数 * を表示する
    ^   ループ文の変数を i から使うのは古い慣習?
    ^
    ^   ++ はインクリメント
    */
    for( int i = 0; i < age; i++ )
    {  // この {} は省略可
      write( '*' );
    }
    
    writeln( toMBS( "\nバイバイキーン!" ) );
  }
}
ゼロ から数えましょう
D:\Pen-Jr\My Programs\Tests>howold
んちゃっ!
歳はなんぼじゃ? 20
おめぇ20才かよ
ほんじゃ歳の数だけ星のプレゼントじゃ
うけとれや!
********************
バイバイキーン!

また、評価式 が 真 ではないと、

D:\Pen-Jr\My Programs\Tests>howold
んちゃっ!
歳はなんぼじゃ? 0
おめぇ0才かよ
ほんじゃ歳の数だけ星のプレゼントじゃ
うけとれや!

バイバイキーン!

次の スコープ文 は 1回 も実行されません。

for 文を if 文に置き換えても、

writeln(
  toMBS( "ほんじゃ歳の数だけ星のプレゼントじゃ\nうけとれや!" )
  );
//
if( age ){
  // age が 0 の時は実行しない
  int i;
  FOR:  // ラベルだよ
  write( '*' );
  if( ++i < age ) goto FOR;
  writeln();  // ここで改行
}
writeln( toMBS( "バイバイキーン!" ) );
goto

※ ラベル

実行結果は同じですし、わかりやすかったりもしますが、ちょい冗長。

また、goto は万能ですが、スコープなどを良く理解しないで安易に使うと、未定義動作 というとてもやっかいなことの原因になりますから「使用禁止」にしましょう。
Foreach 文( Foreach Statement
C言語など古い言語にはないループ文。
foreach( ForeachTypeList; Aggergate ) スコープ文
Aggergate
  • 配列
  • 構造体
  • クラス
  • タプル

で、その各要素ごとに スコープ文 が実行されます。

配列要素の取り出しを for 文で書くと、

import std.stdio;

void main( in string[] args ){
  // 引数を全部表示する
  for( int i = 0; i < args.length; i++ )
  {
    writeln( args[ i ] );
  }
}
D:\Pen-Jr\My Programs\Welcome>test abc 123
test
abc
123

foreach 文で書けば、

import std.stdio;

void main( in string[] args ){
  // 引数を全部表示する
  foreach( string s; args )
  {  // {} は if、for文同様省略可
    writeln( s );
  }
}

クールだべ!

配列 の場合、ForeachTypeList の 型 は省略出来、また2つ 宣言 出来き、

宣言 が2つの場合、1つ目が インデックス になります。

import std.stdio;

void main( in string[] args ){
  // 型を省略
  foreach( s; args ) writeln( s );
  
  // 要素のインデックスも
  foreach( i, s; args )
  {
    write( i, " : ", s, " = [ " );
    
    // string s の内容も
    foreach( j, c; s )
    {
      if( j ) write( ", " );
      write( j, ":", c );
    }
    writeln( " ]" );
  }
}
D:\Pen-Jr\My Programs\Tests>test 123 Welcome
test
123
Welcome
0 : test = [ 0:t, 1:e, 2:s, 3:t ]
1 : 123 = [ 0:1, 1:2, 2:3 ]
2 : Welcome = [ 0:W, 1:e, 2:l, 3:c, 4:o, 5:m, 6:e ]

スライスも OK で、

import std.stdio;

void main( in string[] args ){
  // 引数だけを表示する
  foreach( s; args[ 1 .. $ ] )
  {
    writeln( s );
  }
}
D:\Pen-Jr\My Programs\Tests>test 123 abc
123
abc

D:\Pen-Jr\My Programs\Tests>test

D:\Pen-Jr\My Programs\Tests>

for 同様、要素がなければ、スコープ文は実行されません。

isNumeric だと難がある整数確認は、

import  std.stdio, std.windows.charset,
        std.conv, std.ascii;

  /*----------------------------
  *   UTF8 から Shift-JIS へ変換
  */
string toMBS( in string s ){
  return to!string( toMBSz( s ) );
}

  /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ^     文字列の内容が整数か
  ^
  ^     文字列の要素が全て
  ^       '0' ~ '9' かをチェック
  ^
  ^   返値が 0 以外なら整数値な文字列だよ
  */
int isInteger( in string s ){
  // 文字列 s の各要素(文字) c をチェック
  foreach( c; s )
  {
    // 要素 c が '0' から '9' か?
    if( !isDigit( c ) ) return 0;
  }
  return s.length;
}

  /*----------------------------
  *     エントリーポイント
  *
  */
void main( in string[] args ){
  // 引数だけを取り出す
  foreach( s; args[ 1 .. $ ] )
  {
    write( s, toMBS( "は数値" ) );
    if( isInteger( s ) ){
      writeln( toMBS( "だね。" ) );
    }
    else{
      writeln( toMBS( "じゃねぇよ。" ) );
    }
  }
}
isDigit
D:\Pen-Jr\My Programs\Welcome>test abc 123
abcは数値じゃねぇよ。
123は数値だね。
Foreach Range 文( Foreach Range Statement
foreach( ForeachType; LwrExpression .. UprExpression ) スコープ文

前出の for 文は

foreach( i; 0 .. age )
{  // {} は 省略可
  write( '*' );
}
writeln();

for 文の出番はなくなりそうです。

While 文( While Statement
C言語なんかで古くからあり、その昔 For 文より高速処理だったこともある。
while(  ) スコープ文
式を評価 -> 真 なら スコープ文 を実行 -> 繰り返し

他の ループ文 同様、式が 真 でなければ スコープ文 は 1回も 実行されません。

前出の For 文は、

int i;
while( i < age )
{
  write( '*' );
  i++;
}
writeln();

他と張り合うため短く書く

int i;
while( i++ < age ) write( '*' );
// ++ を後にすると、評価後にインクリメントされる
writeln();

もうちょい工夫して

int i = age;
while( i-- ) write( '*' );
// -- を後にすると、評価後にデクリメントされる
writeln();

最高速かも・・・

どのみち、このような ループ処理 での出番は少ないでしょうが、

ループの条件が違ってくると

import  std.windows.charset, std.stdio,
        std.conv, std.ascii, std.string;

  /*----------------------------
  *   UTF8 から Shift-JIS へ変換
  */
string toMBS( in string s ){
  return to!string( toMBSz( s ) );
}

  /*----------------------------
  *     標準入力
  */
string inPut( in string s ){
  write( toMBS( s ) );
  return chomp( readln() );
}

  /*----------------------------
  *     文字列の内容が整数か
  *
  *   返値が 0 以外なら整数値な文字列だよ
  */
int isInteger( in string s ){
  foreach( c; s ){
    if( !isDigit( c ) ) return 0;
  }
  return s.length;
}

  /*----------------------------
  *     エントリーポイント
  *
  */
void main(){
  writeln( toMBS( "んちゃっ!" ) );
  
  string s = inPut( "歳はなんぼじゃ? " );
  
  /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ^     While 文
  ^
  ^   数値入力があるまで繰り返し入力要求する
  */
  while( !isInteger( s ) )
  {
    s = inPut( "だから、おいくつでっか? " );
  }
  
  writeln(
    toMBS( "おめぇ" ), s, toMBS( "才かよ\n" ),
    toMBS( "ほんじゃ歳の数だけ星のプレゼントじゃ\nうけとれや!" )
  );
  
  /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ^     Foreach 文
  ^
  ^   繰り返し評価されないから、
  ^     式や関数を埋め込みやすいよ
  */
  foreach( i; 0 .. to!int( s ) ){
    write( '*' );
  }
  writeln( toMBS( "\nバイバイキーン!" ) );
}
D:\Pen-Jr\My Programs\Tests>howold
んちゃっ!
歳はなんぼじゃ?
だから、おいくつでっか? xx
だから、おいくつでっか? 20
おめぇ20才かよ
ほんじゃ歳の数だけ星のプレゼントじゃ
うけとれや!
********************
バイバイキーン!

while もクールだわ。

Do-While 文( Do Statement
do スコープ文 while(  );
スコープ文を実行 -> 式を評価 -> 繰り返し

他の ループ文 は 式 が 偽 なら、その スコープ文 を 1回 も実行しないのに対し、

Do-While 文 だけは、必ず 1回 は実行されます。

While 文を単純に置き換えようとするとややこしかったりして、意外と 性格 が違います。

ちょっとバージョンアップして

  /*----------------------------
  *     エントリーポイント
  *
  */
void main(){
  writeln( toMBS( "んちゃっ!" ) );
  
  int cnt;      // 入力回数カウンタ
  string age;   // 
  /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ^     Do-While 文
  ^
  ^   数値入力があるまで繰り返し入力要求する
  */
  do
  {
    switch( cnt ){
      case 0:
        age = inPut( "歳はなんぼじゃ? " );
        break;
      case 1:
        age = inPut( "だから、おいくつでっか? " );
        break;
      default:
        age = inPut( "てめえ、いくつだこら! " );
    }
    ++cnt;    // 忘れずに
  }
  while( !isInteger( age ) );
  
  // 安心して式の中で数値変換
  foreach( i; 0 .. to!int( age ) ){
    write( '*' );
  }
  writeln( toMBS( "\nバイバイキーン!" ) );
}
※ エントリーポイント(main関数)以外は
while のサンプルコードと同じ。

※ 変数宣言

D:\Pen-Jr\My Programs\Tests>howold
んちゃっ!
歳はなんぼじゃ? xx
だから、おいくつでっか?
てめえ、いくつだこら!
てめえ、いくつだこら! 20
おめぇ20才かよ
ほんじゃ歳の数だけ星のプレゼントじゃ
うけとれや!
********************
バイバイキーン!
Break 文( Break Statement
break;
最内周の forforeachwhiledo のスコープ文を終了し、そのループのを抜け、次の処理に移行します。

isInteger は、

  /*----------------------------
  *     文字列の内容が整数か
  *
  *     文字列の要素が全て
  *       '0' ~ '9' かをチェック
  *
  *   返値が 0 以外なら整数値な文字列だよ
  */
int isInteger( in string s ){
  int ret = s.length;
  foreach( c; s )
  {
    // 要素 c が '0' から '9' か?
    ret = isDigit( c );
    // 0 から 9 以外ならループを抜ける
    if( !ret ) break;
  }
  //
  //  ここに処理を追加しやすくなったよ
  //
  return ret;
}

break を使わない方がシンプルですが、

1つの 関数 に複数の Return 文があると、その 関数 に何か手を加えた際「特定の条件でその効果を得られない」というやっかいな問題の原因になることがあります。

「それ以降の処理をキャンセルする」という意味に一致する Return 文でないのなら、Break 文などを使う方法を考えることは「良い習慣」になるでしょう。

How old 1.0 beta
まだ「入り口から中を覗いた」程度ですが、少しは遊べると思います。
エラーに慣れも必要
最初はほとんど呪文で問題ありませんから、まずは自分なりに改造した呪文がやたら コンパイラ に叱られないようになることが第一歩です。

また、コンパイルエラー はほとんど 最も単純なミス が原因ですから、まずは 最も単純なミス を速く発見で出来るよう エラー慣れ することです。

それっぽくまとめてみる
  /*
  *       How old version 1.0 beta
  *
  *         2011-02-13 by Pen Jr.
  */
import  std.windows.charset, std.stdio,
        std.conv, std.string, std.ascii;

  /*----------------------------
  *   UTF8 から Shift-JIS へ変換
  */
string toMBS( in string s ){
  return to!string( toMBSz( s ) );
}

  /*----------------------------
  *     標準入力
  */
string inPut( in string s ){
  write( toMBS( s ) );
  return chomp( readln() );
}

  /*----------------------------
  *     文字列の内容が整数か
  *
  *   返値が 0 以外なら整数値な文字列だよ
  */
int isInteger( in string s ){
  int ret = s.length;
  foreach( c; s ){
    ret = isDigit( c );
    if( !ret ) break;
  }
  return ret;
}

  /*----------------------------
  *
  *   年齢を標準入力から得る
  */
int getAge(){
  int cnt;
  string age;
  do{
    switch( cnt ){
      case 0:
        age = inPut( "歳はなんぼじゃ? " );
        break;
      case 1:
        age = inPut( "だから、おいくつでっか? " );
        break;
      default:
        age = inPut( "てめえ、いくつだこら! " );
    }
    ++cnt;
  }
  while( !isInteger( age ) );
  
  writeln(
    toMBS( "おめぇ" ), age, toMBS( "才かよ" )
    );
  return to!int( age );
}

  /*----------------------------
  *
  *   年齢を受けて反応する
  */
void sayMsg( in int age ){
  switch( age / 10 ){
    case 0:
      writeln( toMBS( "なめてんか!\nうらぁ!!" ) );
      break;
    case 1, 2:
      writeln( toMBS( "よっ!わかもの!" ) );
    case 3: .. case 7:
      writeln(
        toMBS( "来年は" ), age + 1, toMBS( "才だぁな\n" ),
        toMBS( "30年たったら" ), age + 30, toMBS( "才ちゅうわけだ\n" ),
        toMBS( "めでてぇや\n" ),
        toMBS( "ほんじゃ歳の数だけ星のプレゼントじゃ\nうけとれや!" )
      );
      foreach( i; 0 .. age ){
        write( '*' );
      }
      writeln( toMBS( "\nほじゃ!" ) );
      break;
    default:
      if( age >= 120 ){
        writeln( toMBS( "って、役人かよ!" ) );
      }
      else{
        writeln( toMBS( "マジすか?!\nしゃぁーす!" ) );
      }
  }
}

  /*----------------------------
  *
  *     エントリーポイント
  */
void main(){
  writeln( toMBS( "おっ!" ) );
  sayMsg( getAge() );
}
Howold.zip のダウンロード

2011年7月28日

E-Mail : open@pen-jr.org

ご意見やご指摘は大歓迎ですが、質問など返信は期待しないでください

pen jr.
D言語研究
わかったつもりになるD言語
D言語友の会
News&Days
はじめてのブログ選び