- 文法( Lexical )
-
プログラム言語 の場合、文法 というのはあまり適切ではないかも知れません。
相手は CPU などで、機械語 が ネイティブ言語 なわけですから「数式の文法」ということで、
ネイティブ な言語ほど厳格になり、自然言語の 文法 ということから イメージ する寛容なところは、ほぼ 皆無 になります。もちろん D言語 は厳格ですが、変に拡張された言語にはよくある 理不尽な特別ルール がありませんから、いくつかの基本ルールにさえ従えば、思惑と違う 未知の解釈 をされることもなく、必要なことをその都度学習することにが出来ます。
- トークン( Tokens )
-
ソースコードの中で、意味を持つ
最低単位 を トークン( 字句 ) と言います。
トークン の 分類は
字句解析( トークン解析 )が基本になるのは「自然言語」でも同じですが全角文字は NG ですし、曖昧なところはなく厳格です。 - 構文( Statements )
-
基本的な ソースファイル には、様々な 定義・宣言
と ブロック があり、プログラムは 任意 の
ブロック を呼び出しながら処理を実行します。
- ブロック( Block Statement )
-
{から}までが1つの ブロック です。ブロック内の 各文 は ソースコード の 記述順( 上から下 )に実行されます。
- 行終端( End of Line )
-
;が行終端です。トークン の区切りには自由に 改行 を使えますから、ソースコード の見た目とは別に
コンパイラ から見た 行終端 を明示する必要があります。 - 宣言文( Declarations )
- 式文( Expression Statement )
- 制御文( Statement )
- フリーフォーマット
-
"半角空白、タブ、改行(CR&LF)"
は全て 空白 で、連続する場合は1つ 空白 として扱われまから、区切りには自由に
改行 や タブ を挿入出来ます。
例えば
import std.stdio; void main ( char [ ] [ ] args ) { writeln ( "Welcome Beginner" ); }
これでも、
D:\Pen-Jr\My Programs\Welcome>dmd test D:\Pen-Jr\My Programs\Welcome>test Welcome Beginner
結果は同じです。
インデント や 空行 をうまく使って、見やすい ソースコード にすることが出来ます。
また、ブロック と 行終端 の記述さえちゃんとすれば、
import std.stdio;void main(char[][]args){writeln("Welcome Beginner");}
こんなコードでも、
D:\Pen-Jr\My Programs\Welcome>dmd test D:\Pen-Jr\My Programs\Welcome>test Welcome Beginner
コンパイラ にすれば問題なしです。
D言語 は、記述さえ正しければ、ほとんど 改行 または 空白 の区切りは必須ではなく、また 空白 の入れ方で意味が変わったりすることがないようよく考えられています。 - コメント( Comments )
-
行コメント //から行の終わり(改行)までブロックコメント(ネスト不可) /*から*/までブロックコメント(ネスト可) /+から+/まで通常 コメント 内は、その コメント の 終了文字(列)以外は 解釈(トークン分け)されませんから、自由な記述が出来ます。/***************************** % $ 2011-03-05 @ by ! Pen Jr. # */ // インポート Phobos import std.stdio; // writeln のため /*---------------------------- | | エントリーポイント | */ void main( /* 引数省略 */ ) { // // 標準出力 writeln( "Welcome Beginner" ); }
コメント は過剰なくらい書き込む癖を付けた方が後々のためになります。記憶力が抜群なころ、立派な コメント にそぐわないアホなコードをバカにして コメント をおろそかにしたせいで、記憶力が アホ になり、数日前に書いた自分の コード の解読に苦労する 懲りない アホ になってしまってからでは手遅れだったりしますから・・・
- 全角文字に要注意!!!
-
日本語変換を使いながらプログラムコードを書いていると、
一見問題なさそうなコードですが、
import std.stdio; void main() { writeln( "Welcome Beginner" ); }
コンパイルすると、
D:\Pen-Jr\My Programs\Welcome>dmd test test.d(4): unsupported char 0xff1b test.d(5): found '}' when expecting ';' following statement test.d(5): found 'EOF' when expecting '}' following compound statement
めちゃ叱られます。
原因は 全角記号 が混入しているからで、
混入してるやつは
;でwriteln( "Welcome Beginner" ); writeln( "Welcome Beginner" );
下が正しいコードです。
並べて見ればわかりますが、日本語変換では時折見た目では区別がつかない間違った変換をすることもありますから、文字列やコメント以外の記述時は日本語変換を忘れず OFF にしまひょ!
- 識別子( Identifier )
-
任意に定義できる トークン
でシンボルも 識別子 です。
識別子はアルファベットか _、または普遍基本文字(Universal Alpha)で始まり、 アルファベット,_,数字,普遍基本文字の列が後ろに続いたものです。 普遍基本文字は ISO/IEC 9899:1999(E) Appendix D. (C99標準です) に定義されています。識別子は大文字小文字を区別します。 長さに制限はありません。 __ (2個のアンダースコア)で始まる識別子は処理系側に予約されています。
普遍基本文字とかは一般的ではありませんから無視して、
要するに単語( Word )のことですが「厳格な仕様」があり、違反が容認されることはありません。- 予約語( Keywords )
-
組み込み 定義 された 型 や 命令 など言語で予約された 識別子
で、任意の意味付けは出来ません。
サンプルコードでは
import、void、charが 予約語 です。
- 宣言文( Declaration Statement )
-
予約語 以外の 識別子 には 宣言 で任意の意味を 定義 出来ます。
import std.stdio; // 外部宣言の読み込み void main() // 関数 main の宣言文 { int i; // 変数 i の宣言文 }
- リテラル
-
リテラル とは ソースコード に
文字列 や 整数値
などを 直接表記 したもので
などの 型 を 表記 できます。
- 文字列リテラル( String Literals )
-
二重引用符
""で囲まれた 文字列 で、エスケープシーケンス が解析されます。また、文字列 はコメント同様 トークン分け されません。
- エスケープシーケンス( Escape Sequence )
-
¥n改行 ¥r先頭に戻る ¥tタブ ¥¥¥¥"二重引用符 ¥‘一重引用符 ¥0ヌル ¥??など他にもありますが、
¥から始まる 文字 で、文字列リテラル ではそのまま使えない"や 制御コード などを記述するために使います。import std.stdio; void main() { // 文字列内の改行は改行、空白は空白 writeln( "Welcome Beginner" ); // エスケープシーケンス writeln( "Welcome\nBeginner" ); writeln( "\"Welcome Beginner\"" ); writeln( "\\Welcome\\Beginner\\" ); writeln( "\'Welcome\'\n\'Beginner\'" ); }
D:\Pen-Jr\My Programs\Welcome>dmd test D:\Pen-Jr\My Programs\Welcome>test Welcome Beginner Welcome Beginner "Welcome Beginner" \Welcome\Beginner\ 'Welcome' 'Beginner'
- 文字リテラル( Character Literals )
-
1文字か エスケープシーケンス を
''で囲みます。import std.stdio; void main() { writeln( "Welcome\nBeginner" ); writeln( // 文字を順次出力 'W', 'e', 'l', 'c', 'o', 'm', 'e', // エスケープシーケンスも '\n', 'B', 'e', 'g', 'i', 'n', 'n', 'e', 'r' ); }
D:\Pen-Jr\My Programs\Welcome>test Welcome Beginner Welcome Beginner
- 整数リテラル( Integer Literals )
-
整数値 はそのまま普通に記述出来ますが、一般的な
','などの記号は使えません。桁など見やすくしたい時には
'_'(アンダーバー)を任意に挿入できます。import std.stdio; void main() { // 基本 0-9 のみ writeln( 1234567890 ); // _ は使える writeln( 1_234_567_890 ); // 符号は普通に writeln( -1234567890 ); }
D:\Pen-Jr\My Programs\Welcome>test 1234567890 1234567890 -1234567890
- 浮動小数点数リテラル( Floating Literals )
-
浮動小数点数 は 整数 と別扱いですが、整数値 同様普通に記述出来ます。
import std.stdio; void main() { // . と 0-9 writeln( 0.0001 ); // _ は使える writeln( 0.000_1 ); // e や E も writeln( 0.0001e+4 ); writeln( 1E-4 ); }
D:\Pen-Jr\My Programs\Welcome>test 0.0001 0.0001 1 0.0001
- モジュール( Modules )
-
基本的には
「1つのソースファイル = モジュール」ということで、
std.stdioやサンプルコードなどの ファイル もモジュールです。- モジュール名
-
モジュール名 は ファイル名 ということになりますが、
Windows では ファイル名 の大文字と小文字の区別はしませんから、慣習に従って全て小文字で書きます。
また、予約語 をファイル名にしてもコンパイルは通り、実行も出来ますが、
import出来ない モジュール になってしまいますから、要注意です。- パッケージ名
-
std.stdioの場合stdが パッケージ名 で、stdioが モジュール名 です。パッケージ名 は OS の ディレクトリー に対応していますから
ということにもなります(同じ意味ではない)。¥std¥stdio
- import宣言( Import Declaration)
-
必要な モジュール を読み込むには、
import モジュールリスト;モジュールリスト には読み込む モジュール を','区切りで列挙します。C、C++ の#includeと違い、ソースコードをその場所に読み込むのではなく、必要な情報(宣言・定義)だけを読み込みますから、各モジュール に対してimportは一度、出来るだけ モジュール の先頭付近に記述します。
- 関数( Functions)
-
初めはちょっと難しいかも知れませんが、D言語
では初歩的なコード でも 関数 を使います。
- 宣言
-
基本的には 宣言 で 処理コード も記述します。
属性 返値型 関数名( 引数, ... ) { return 返値; }- 属性 は 任意
- 返値 がない時の 返値型 は
void - 引数 がない時の
voidなどは不要
import std.stdio; /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ 返値なし、引数なしの関数 ^ */ void welcome() { writeln( "Welcome Beginner" ); } // エントリーポイント void main() { welcome(); // 関数の呼び出し }
D:\Pen-Jr\My Programs\Welcome>dmd test D:\Pen-Jr\My Programs\Welcome>test Welcome Beginner
C、C++ などでは プロトタイプ宣言 といって、返値と引数だけの 関数宣言 が別に必要ですが、D言語 では 不要 です。 - 引数(パラメタ)
-
呼び出し時に受け取る 値の型、数、並び を宣言します。
import std.stdio; /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ 文字列を受け取る関数 ^ */ void welcome( string s ) { writeln( "Welcome ", s ); } // エントリーポイント void main() { welcome( "Beginner" ); welcome( "Everybody" ); }
D:\Pen-Jr\My Programs\Welcome>dmd test D:\Pen-Jr\My Programs\Welcome>test Welcome Beginner Welcome Everybody D:\Pen-Jr\My Programs\Welcome>
引数 の 型、数、並び の全てが宣言通りでないと、関数 の呼び出しは失敗します。import std.stdio; /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ 文字列と数値を受け取る関数 ^ */ void welcome( string, int ) { // 中身が空なのは問題ない // 使わない引数名は省略できる } // エントリーポイント void main() { // 以下は全部コンパイルエラー welcome(); welcome( "Pen Jr" ); welcome( 1 ); welcome( 0, "Everybody" ); }
- デフォルト引数
-
引数 に デフォルト値 を設定することも出来ます。
( 記憶クラス 型 引数名 = デフォルト値 )
デフォルト値 の設定された 引数 は省略して 関数 を呼び出すことが出来ます。
import std.stdio; /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ 文字列を受け取る関数 ^ */ void welcome( string s = "Beginner" ) { writeln( "Welcome ", s ); } // エントリーポイント void main() { welcome( "Pen Jr" ); // 引数を省略して呼び出す welcome(); }
D:\Pen-Jr\My Programs\Welcome>dmd test D:\Pen-Jr\My Programs\Welcome>test Welcome Pen Jr Welcome Beginner
省略された 引数 の値は デフォルト値 になります。
デフォルト値 の設定された 引数 の後に デフォルト値 のない 引数 は宣言できません。
import std.stdio; void welcome( string s = "Everybody", int n ){ // 処理がなくても叱られないよ } void main(){ }
コンパイルすると
D:\Pen-Jr\My Programs\Welcome>dmd test test.d(3): default argument expected for n D:\Pen-Jr\My Programs\Welcome>
デフォルト値のない
nが叱られます。デフォルト値 の設定を続けるのは無問題。
import std.stdio; /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ 文字列と数値を受け取る関数 ^ */ void welcome( string s = "Everybody", int n = 1 ) { writeln( "Welcome ", s ); writeln( "Get Number", n ); } // エントリーポイント void main() { welcome( "Pen-Jr", 100 ); welcome( "Pen-Jr" ); // OK // 後ろの引数から順に省略しないとだめよ /+ welcome( 100 ); // これはエラー +/ welcome(); // 引数なしでも呼べるだに }
D:\Pen-Jr\My Programs\Welcome>dmd test D:\Pen-Jr\My Programs\Welcome>test Welcome Pen-Jr Get Number100 Welcome Pen-Jr Get Number1 Welcome Everybody Get Number1
- 返値
-
返値は
return文で返します。- Return 文( Return Statement )
-
返値 を決めてその 関数 を終了します。
return 返値;返値 がない(
void)関数 の場合はreturnまたは 返値 を省略出来ますが、返値 の宣言がある 関数 を正常に終了するためにはreturnと 返値 が必要です。返値 のある 関数 は 式 の中で呼び出すことが出来ます。
import std.stdio; /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ 文字列を返す関数 */ string welcome() { return "Welcome Beginner"; } // エントリーポイント void main() { writeln( welcome() ); writeln( welcome ); // 引数がない場合 () も省略出来るぜ }
D:\Pen-Jr\My Programs\Welcome>dmd test D:\Pen-Jr\My Programs\Welcome>test Welcome Beginner Welcome Beginner
import std.stdio; /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ 引数 n1 * n2 を返す関数 */ int getMul( int n1 = 10, int n2 = 100 ) { return n1 * n2; } // エントリーポイント void main() { writeln( getMul( 123, 10 ) ); writeln( getMul( 456 ) ); writeln( getMul( 789, getMul ) ); }
D:\Pen-Jr\My Programs\Welcome>dmd test D:\Pen-Jr\My Programs\Welcome>test 1230 45600 789000
- main 関数(エントリーポイント)
-
エントリーポイント というのは、プログラムが実行されるとき最初に呼び出されるポイント(関数)のことを言います。
コンピューターの電源が入れられると CPU はある決まった番地(アドレス : 0番地とは限らない)から処理を始めます。その処理は普通そのシステム(基盤・マザーボード)に実装済みで、そこから呼び出される OS にも エントリーポイント があり、その OS から呼び出されるプログラムには全て エントリーポイント があります。
ただし、OS のアプリケーションなどはプログラム実行時に直接ユーザーが書いた エントリーポイント から処理が始まるわけではなく、必要な手続きを済ませてから呼び出され、必要な後処理をして終了しますが、その内容は知らなくて問題ありません。
- 式( Expressions )
-
- 比較式
-
値 の 比較 をして、結果は
trueかfalseのいずれかの 値(ブール値)になります。演算子 ==等しい !=等しくない <小さい >大きい <=以下 >=以上 等値式 の==と!=以外は一般的ですが、以上 と 以下 を=>や=<と書くのは エラー です。import std.stdio; void main() { writeln( // 等価 "0 == 0 -> ", 0 == 0, "\n0 == 1 -> ", 0 == 1, "\n0 != 0 -> ", 0 != 0, "\n0 != 1 -> ", 0 != 1, "\n\n0 < 1 -> ", 0 < 1, "\n0 > 1 -> ", 0 > 1 ); }
D:\Pen-Jr\My Programs\Welcome>test 0 == 0 -> true 0 == 1 -> false 0 != 0 -> false 0 != 1 -> true 0 < 1 -> true 0 > 1 -> false
- 算術演算式
-
ほぼ一般的な算術式と同じ意味で、同様の記述が出来ます。
演算子 +加算 -減算 *乗算 /除算 %余り %( 余り )以外はテンキー通り。import std.stdio; void main() { writeln( // 加算 "1 + 1 = ", 1 + 1, // 減算 "\n1 - 1 = ", 1 - 1, // 乗算 "\n2 * 3 = ", 2 * 3, // 除算 "\n10 / 3 = ", 10 / 3, // 余り "\n10 % 3 = ", 10 % 3, // () も一般的な演算同様に使える "\n\n2 + 3 * 4 = ", 2 + 3 * 4, "\n( 2 + 3 ) * 4 = ", ( 2 + 3 ) * 4 ); }
D:\Pen-Jr\My Programs\Welcome>test 1 + 1 = 2 1 - 1 = 0 2 * 3 = 6 10 / 3 = 3 10 % 3 = 1 2 + 3 * 4 = 14 ( 2 + 3 ) * 4 = 20
- 代入式
-
慣れてしまえば当たり前のことになりますが、
一般的な 式 は 結果 を右側に書きますが、ほとんどの プログラム言語 の 代入式 は
代入先 = 式; 代入先 = リテラル;
結果 を 左側 に 代入 します。代入式 には
=を使います。import std.stdio; void main() { // 初期化値を代入 int i = 10; string s = "ABC"; // 値を代入 i = 3; s = "Welcome Beginner\n"; // 計算結果を代入 i = i + 2 * 3; writeln( // 代入も式( 代入後の結果 ) s, i = i * 2 ); }
D:\Pen-Jr\My Programs\Welcome>test Welcome Beginner 18
=は 代入。等値(比較)式 は
==です。 - 単項演算式
-
ほとんどが プログラム言語独特 の 演算式 で、最初は意味不明でしょうが、わかりやすい
演算式 から憶えて行けば問題ないでしょう。
前置 と 後置 で全く意味が違ったりもします。
- インクリメント・デクリメント
-
よく出てきますし、慣れれば便利に使えるでしょう。
import std.stdio; void main() { int i; writeln( "i = ", i, // 前置 "\n\n++i = ", ++i, ", i = ", i, "\n--i = ", --i, ", i = ", i, // 後置 "\n\ni++ = ", i++, ", i = ", i, "\ni-- = ", i--, ", i = ", i ); }
D:\Pen-Jr\My Programs\Welcome>test i = 0 ++i = 1, i = 1 --i = 0, i = 0 i++ = 0, i = 1 i-- = 1, i = 0
前置 と 後置 で違いますから、
式の中で使う時には要注意です。
import std.stdio; void main() { int i; writeln( "i = ", i, // 前置 "\n123 * ++i = ", 123 * ++ i, "\n123 * --i = ", 123 * --i, "\n123 * i = ", 123 * i, // 後置 "\n\ni = ", i, "\n123 * i++ = ", 123 * i++, "\n123 * i-- = ", 123 * i--, "\n123 * i = ", 123 * i ); }
D:\Pen-Jr\My Programs\Welcome>test i = 0 123 * ++i = 123 123 * --i = 0 123 * i = 0 i = 0 123 * i++ = 0 123 * i-- = 123 123 * i = 0
式の中での 前置 は インクリメント または デクリメント 後 の 値 になり、後置は インクリメント または デクリメント 前 の 値 になります。また、こんぴゅーたー は
0から数えますから、なおさら混乱すると思います。慣れるまでは、出来るだけ分けて記述した方がよいでしょう。
- 否定
-
import std.stdio; void main() { writeln( "!true = ", !true, "\n!false = ", !false, "\n!( 0 == 0 ) = ", !( 0 == 0 ) "\n!( 0 == 0 ) = ", !( 0 == 0 ), "\n\n!0 = ", !0, "\n!123 = ", !123 ); }
D:\Pen-Jr\My Programs\Welcome>test !true = false !false = true !( 0 == 0 ) = false !0 = true !123 = false
- . ( Dot )
-
数値の後にある場合は小数点になりますが、それ以外の 後置式 はほとんどの場合、
「の」に置き換えた意味になります。
例えば、
std.stdioは「stdのstdio」。よく出てきますし、統一されていて C++ のように
->だったり::とかだったりしませんから、自然に憶えることが出来るでしょう。
- Effect( 効果・影響・結果 )
-
「Effect のない 式」 は コンパイルエラー です。
Effect のない 式 というのは
import std.stdio; void main() { int i, j; i = i + j; // OK ++i; // ++ は i = i + 1 に置き換えられ OK --j; // 同じく OK /+ // 以下 "no effect" エラー i * 3; i + j * 3; // 途中効果は関係なく i + j としてエラー ++i + --j; +/ /* * 結局 i、j は使わず、プログラム対して * 効果がないのはスルーだよ */ writeln( "Welcome Beginner" ); }
Effect のない 式 は必要なく、ほとんどの場合 記述ミス ですし、不慣れだと犯し勝ちな ミス ですから、コンパイルエラー は修正の助けになるはずです。
また、Effect のない プログラムコード も作為的でなければ 設計ミス か 記述ミス ですが、式 以外の no effect は、ほとんど スルー です。
int noEffect( in int n = 0 ){ return n; } void main() { int i; // 関数の呼び出しに効果がないのは OK noEffect( i ); noEffect( 10 ); noEffect; /+ // 式文になるとエラーだな i + noEffect( 100 ); +/ }
意味なしプログラムもスルー
D:\Pen-Jr\My Programs\Welcome>dmd test -w D:\Pen-Jr\My Programs\Welcome>
式 には必ず 結果 がありますから、その 結果 の行き場所がない 式( 代入を除く )があれば エラー にするだけで、特別な解析 をしているわけではありません。プログラムコード に Effect があることを判断するためには 特別な解析 が必要になりますし、それは コンパイラ の仕事にはなっていません。
- 式の中の式の中の・・・
-
import std.stdio; int getMul( in int n1, in int n2 ) { writeln( n1, " * ", n2 ); return n1 * n2; } // 途中結果を表示 int disp( in int n ) { writeln( "n = ", n ); return n; } // エントリーポイント void main() { int i = 3; writeln( "Final Answer = ", /*^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ 以下1つの式だす */ disp( i * 2 ) + disp( i = i-- + getMul( i = 3 * ++i, 10 ) ) / disp( ++i ) ); }
ちゃんと実行出来まっせ。
D:\Pen-Jr\My Programs\Welcome>dmd test D:\Pen-Jr\My Programs\Welcome>test n = 6 9 * 10 n = 93 n = 94 Final Answer = 6
こんな コード は複雑な内には入らないくらい複雑怪奇な 式文 が存在するのは、数学 の 式 が複雑怪奇なのと同じです。
ちなみに最後の
writelnは、writeln("Final Answer = ",/*以下1つの式だす*/disp(i*2)+disp(i=i--+getMul(i=3*++i,10))/disp(++i));
でも同じです。
特に 単項演算 など同じ記号が違う意味になってたりしますから混乱しそうですが、D言語 はよく考えられていて、空白のあるなしなどで 簡単 に違った意味になったりしません。常に全部頭に入っていないとコードが書けないようなことはありませんから、必要になった時、その都度見直して使えば良いでしょう。
プログラミング に 数学の知識が要求されることは 常識 だったりしますが、専門の職業プログラマーとかでないなら、小学生の算数レベルでも 算数センス だけでなんとかなることは、筆者がそのサンプルに該当します。もちろん、数学の知識が豊富にあれば、既存のアルゴリズムの応用でより正確な設計が「愚かな回り道」なしで出来ますから、その手の玄人達に小学生レベルで対抗するのは無謀以外の何者でもありませんが、職業でさえなければ「愚かな回り道」だって 面白い遊び になります。
2011年3月10日

