- 配列( 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*という 文字列(文字)ポインター で扱います。ポインタは基本ですが、C言語の様に「理解できないと先に進めない」というようなことはありません。 - スライシング
-
配列 の一部を簡単に切り出すことが出来ます。
配列[ LwrExpression .. UprExpression ]
- 配列の結合( 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-8をShift-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" ) ); }
D:\Pen-Jr\My Programs\Tests>test abcABC日本語あいう0123 484C40
※ ポインター の値は実行毎に違う。日本語表示は出来ましたが、
返値 が C言語 の文字列(0終端のポインター)なので、C言語 の 関数(
puts)に直接アクセスする場合は都合がよいのですが、writelnなどでは ポインター は アドレス( 数値 )が表示されてしまいます。スライス を使えば、C言語 の文字列を D言語 の文字列に変換出来ますが、そのためには文字列の長さを得る必要があります。
- std.c
-
C言語 の 標準ライブラリー の一部など、いくつかの C言語 の 関数 が使えます。
- std.c.string
-
C の
string.hsize_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
-
C の
stdio.hC言語 の 標準出力。int puts( in char* s );
- std.stdio
-
標準入出力。
stc.c.stdioをpublic importしています。-
write -
標準出力。
引数 の数や 型 の指定がなく、受け取った 引数 が表示可能な 型 ならば順次出力します。
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' )と同等。writeをwritelnに書き換えると、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
-
文字列
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
-
文字 c が数字( 0..9 )なら 0 以外を返します。
bool isDigit( dchar c );
- std.conv
-
- to
-
万能型変換。
テンプレートな記述
!は省略出来ませんが、かなり便利です。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文から実行され、定数リスト は、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( "じゃねぇよ。" ) ); } } }
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;最内周のfor、foreach、while、doのスコープ文を終了し、そのループのを抜け、次の処理に移行します。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() ); }
2011年7月28日

