「D言語/関数」の版間の差分

削除された内容 追加された内容
Ef3 (トーク | 投稿記録)
→‎スコープ: シャドウイングは許されない(コンパイルエラー)
タグ: 2017年版ソースエディター
Ef3 (トーク | 投稿記録)
トップレベルのインスタンスの識別子は、シャドウイング禁止の対象外。内部関数のローカル変数と包含する関数のローカル変数との間もシャドウイングではない。コードにhttps://run.dlang.io/ のフォーッマッターを適用。また、C言語風の前方参照を回避するコーディングスタイルを廃止。
タグ: 2017年版ソースエディター
1 行
== 関数 ==
関数とは一連の動作ををまとめた手続きであり、0個以上の引数を受け取り、1つの戻値を返すこと'''も'''できます。(C言語の関数については[[C言語/関数]])
 
; [https://paiza.io/projects/hJbYomhq4UMlWl4EE3YgfQ?language=kotlin コード例]:<syntaxhighlight lang=d line>
; コード例
<syntaxhighlight lang="D">
import std.stdio;
 
void main()
{
writeln("main内");
auto n = 21;
n.foo().writeln; // writeln(foo(n)); と同じ
bar();
}
16 行
{
writeln("foo内");
return 2 * n;
}
 
24 行
}
</syntaxhighlight>
;実行結果:<syntaxhighlight lang=text>
 
main内
;実行結果
mainfoo
42
foo内
bar内
42
</syntaxhighlight>
bar内
 
=== 関数の定義・呼び出し方 ===
<!-- to be done -->
<!-- === 戻り値の自動化 ===
関数の戻り値を決めるとき、auto キーワードでD言語コンパイラが自動的に型を設定してくれます。
 
<syntaxhighlight lang="D">
auto foo()
{
return 5;
}
</syntaxhighlight>
 
たとえば上記コードなら、「5」は整数なので、整数は int 型なので、戻り値にも自動的に int 型が設定されます。
 
=== 関数の戻値型の型推論 ===
関数の戻値型に、キーワード <code>auto</code> を使うことで、戻値型を型推論することができます。
 
;[https://paiza.io/projects/rOX7ozOQz8CbnxhqPHy4XA?language=d コード例]:<syntaxhighlight lang=d line highlight=10>
;コード例
<syntaxhighlight lang="D">
import std.stdio;
 
autovoid foomain()
{
returnwriteln(foo(2) 5+ 4);
}
 
auto foo(int i)
void main(){
{
return writeln( foo()i + 4)3;
}
</syntaxhighlight>
;実行結果:<syntaxhighlight lang=text>
 
9
;実行結果
</syntaxhighlight>
9 -->
: int 型の変数と int 型のリテラル同士の加算なので int が型推論されます。
 
== スコープブロック文 ==
69 ⟶ 59行目:
スコープブロックは入れ子にすること可能です。
 
;[https://paiza.io/projects/Y-ukLeqDBuGKSchaJKdR7QrOX7ozOQz8CbnxhqPHy4XA?language=d コード例]:<syntaxhighlight lang="D"d line line>
import std.stdio;
 
void main()
{
func0();
writef("aはmainでいま%d\n", a);
}
 
int a = 99;
 
void func0()
{
int a = 2; // 9行の a は、トップレベルなので シャドウイング にあたらない。
int a = 2;
writef("aはfunc0でいま%d\n", a);
// 新たなスコープ
{
// int a = 42; ← 613行目の a を隠してしまう(シャドウイング)ので a を識別子に使えない。
writef("aはfunc0内の内側のスコープでいま%d\n", a);
}
writef("aはfunc0内の内側のスコープでいま%d\n", a);
}
 
void main() {
func0();
writef("aはmainでいま%d\n", a);
}
</syntaxhighlight>
;実行結果:<syntaxhighlight lang=text>:<syntaxhighlight lang=text>
aはfunc0でいま2
aはfunc0内の内側のスコープでいま2
aはfunc0内の内側のスコープでいま2
aはmainでいま99
</syntaxhighlight>
 
 
;コード例
<syntaxhighlight lang="D">
import std.stdio;
 
int a = 99;
 
void func0() {
a = 2; // 変数宣言しない(できない)
 
writef("aはfunc0でいま%d\n", a);
}
 
void main() {
func0();
writef("aはmainでいま%d\n", a);
}
</syntaxhighlight>
 
;実行結果
<pre>
aはfunc0でいま2
aはmainでいま2
</pre>
 
上記の例では、関数 func0 では新規宣言されておらず、外部の変数を上書きします。
 
その結果、aの値がmainでも「2」に変わります。
 
== 内部関数 ==
130 ⟶ 93行目:
D言語では、関数の内部で関数を定義でき、利用できます。D言語では、ほぼあらゆる言語の要素がいたるスコープで記述でき、例えば、<code>import</code> ですらも特定のスコープ内のみに適用する、といったことが可能です。内部関数は、それのほんの一例です。
 
;コード例:<syntaxhighlight lang=d line>
<syntaxhighlight lang="D">
import std.stdio;
 
void main()
{
int x = 0;
// ↓ これが内部関数
void naibuinner() {
{
writeln("inside!");
x++;
writeln("inside! X = ", x);
}
// 内部関数はここで終わり
 
writeln("1回目");
naibuinner();
 
writeln("2回目");
naibuinner();
}
</syntaxhighlight>
;実行結果:<syntaxhighlight lang=text>
 
;表示結果
<pre>
1回目
inside! X = 1
2回目
inside! X = 2
</syntaxhighlight>
</pre>
:標準C言語とC++には、2020年のいまのところ内部関数はありません。C#には2017年ごろ、C#7にて内部関数が追加されました(なお、C#の内部関数の記法はD言語のそれとは大きく違う)。
 
標準C言語とC++には、2020年のいまのところ内部関数は無いです。C#には2017年ごろ、C#7にて内部関数が追加されました(なお、C#の内部関数の記法はD言語のそれとは大きく違う)。
 
=== 内部関数と変数の有効範囲について ===
内部関数の中にある変数の扱いは、通常のスコープと同様では異なり、識別子が重複るとより内側の関数スコープの識別子が使われ、シャドウイングとしてエラーにはなりません
 
==== shadowing内部関数と内包する関数のローカル変数の識別子と衝突した場合 ====
;[https://paiza.io/projects/mD2lc7V3K_vYYS3sy-bUVA?language=d コード例]:<syntaxhighlight lang=d line>
 
;コード例
<syntaxhighlight lang="D">
import std.stdio;
 
void main()
int a=7;
{
 
outer();
void soto() {
writef("mainに帰還。aはいま%d\n", a);
int a=5;
void naibu() {
int a=3;
writef("内部関数の中で aはいま%d\n", a);
}
naibu(); // 利用の際は呼び出すのを忘れないように
writef("関数では aはいま%d\n", a);
}
 
int a = 7;
 
void mainouter()
{
soto() int a = 5;
void inner()
writef("mainに帰還。aはいま%d\n", a);
{
int a = 3;
writef("内部関数の中で aはいま%d\n", a);
}
 
inner(); // 利用の際は呼び出すのを忘れないように
writef("関数では aはいま%d\n", a);
}
</syntaxhighlight>
;実行結果:<syntaxhighlight lang=text>
 
;表示結果
<pre>
内部関数の中で aはいま3
関数では aはいま5
mainに帰還。aはいま7
</syntaxhighlight>
</pre>
: 関数スコープの識別子の解決は、スコープの内側から外側に向かってトップレベルまで行なわれます。
 
: トップレベルまで探査して見つからなければ、未定義変数の参照となります。
内部関数の置き場所になっているsoto ですら、もはやaの値は(内部関数で定義した3ではなく)7に戻っています。
: これは、コンパイル時に静的に行なわれます。
: 他の言語の経験がある人であれば「クロージャー」だと言えば判りやすいでしょうか。
 
==== 内部関数からトップレベルの変数を参照 ====
==== shadowingしない場合 ====
いっぽう、再宣言しない場合、外側のスコープにある変数を書き換えます。
 
;内部関数から内包する関数のローカル変数を参照:<syntaxhighlight lang="D"d line>
import std.stdio;
 
void main()
int a=7;
{
 
outer();
void soto() {
writef("mainに帰還。aはいま%d\n", a);
void naibu() {
a=3; // int が無く、再宣言なしの単なる代入命令
writef("内部関数の中で aはいま%d\n", a);
}
naibu(); // 利用の際は呼び出すのを忘れないように
writef("関数では aはいま%d\n", a);
}
 
int a = 7;
 
void mainouter()
{
soto void inner();
{
writef("mainに帰還。aはいま%d\n", a);
a = 3; // int が無く、再宣言なしの単なる代入命令
writef("内部関数の中で aはいま%d\n", a);
}
 
inner(); // 利用の際は呼び出すのを忘れないように
writef("関数では aはいま%d\n", a);
}
</syntaxhighlight>
;実行結果:<syntaxhighlight lang=text line>
 
 
;表示結果
<pre>
内部関数の中で aはいま3
関数の隣りでは aはいま3
mainに帰還。aはいま73
</syntaxhighlight>
</pre>
: 3行目のaの値に注目してください。上記コードの内部関数では再宣言しないで代入したので、mainに帰還した際にも、変数aが7でなく3に置き換わっています。
 
3行目のaの値に注目してください。上記コードの内部関数では再宣言しないで代入したので、mainに帰還した際にも、変数aが7でなく3に置き換わっています。
 
 
== セキュリティ・レベル ==
C言語に無い特徴として、D言語の「関数」には
* @system
* @trusted
* @safe
の3種類のセキュリティ・レベルの設定があります。
 
@system と @trusted と @safe の3種類のセキュリティ・レベルがあります。
 
何も指定しない場合、レベルは @system になっています。
 
:<syntaxhighlight lang="D"d line>
int foo() @system
{
252 ⟶ 209行目:
}
</syntaxhighlight>
 
のように指定します。
 
;@system で宣言された関数(ディフォルト)
:C言語の関数のように、全ての言語機能にアクセスできます。
;@safe で宣言された関数
:ポインタの利用が禁止されます。
:@safe または @trusted な関数だけしか呼び出しできません。
:(@system レベルの関数を呼び出しできません)
 
;コード例:<syntaxhighlight lang=d line>
@system はC言語の関数のように、気楽に使えます。
import std.stdio;
 
void main(){
@safe で宣言された関数では、ポインタの利用が禁止されます。また@safeで宣言された関数は、@safe または @trusted な関数だけしか呼び出しできません。
writeln( foo() );
 
}
 
つまり、@safe の関数は、@system レベルの関数を呼び出しできないのです。
 
;コード例
<syntaxhighlight lang="D">
import std.stdio;
 
int foo() @system
271 ⟶ 229行目:
return 28;
}
</syntaxhighlight>
;実行結果:<syntaxhighlight lang=text>
28
</syntaxhighlight>
;うごくコード例2:<syntaxhighlight lang=d line>
import std.stdio;
 
void main(){
276 ⟶ 240行目:
writeln( foo() );
}
</syntaxhighlight>
 
;実行結果
28
 
 
 
;うごくコード例2
<syntaxhighlight lang="D">
import std.stdio;
 
int foo2() @safe
296 ⟶ 250行目:
return foo2();
}
</syntaxhighlight>
;実行結果:<syntaxhighlight lang=text>
35
</syntaxhighlight>
;禁止されているコード例(コンパイルエラー):<syntaxhighlight lang=d line highlight=15>
import std.stdio;
 
void main()
{
writeln(foo());
writeln( foo() );
}
</syntaxhighlight>
 
int foo2() @system
;実行結果
{
35
 
 
;禁止されているコード例
::※ エラーになります
 
<syntaxhighlight lang="D">
import std.stdio;
 
int foo2() @system{
return 35;
}
321 ⟶ 271行目:
return foo2();
}
 
void main(){
writeln( foo() );
}
</syntaxhighlight>
;コンパイル結果:<syntaxhighlight lang=text>
 
onlineapp.d(15): Error: `@safe` function `onlineapp.foo` cannot call `@system` function `onlineapp.foo2`
;実行結果
onlineapp.d(8): `onlineapp.foo2` is declared here
:※ エラーになる。
</syntaxhighlight>
 
 
338 ⟶ 284行目:
D言語の契約プログラミングでは、要求事項を見たさない入力または出力がされた際、プログラムの実行を停止する。また、静的な契約 (static assert) も存在し、こちらはコンパイル時間数実行が可能な範囲においてコンパイル時にチェックされ、条件が満たされない場合はコンパイルがエラーとなる。
 
;コード例(自然数に対して階乗を計算するプログラム):<syntaxhighlight lang=d line>
;コード例
下記に、自然数に対して階乗を計算するプログラムを与えます。
 
<syntaxhighlight lang="D">
import std.stdio;
 
void main()
int factorial( int n )
in {
writeln(factorial(5));
assert(n >= 0); // 階乗への入力は非負整数でなければならない
}
 
out (result) {
int factorial(int n)
assert ( result >= n ); // n! ≧ n
in
assert ( n <= 3 || result > n^^2 ) // n ≧ 4 のとき、n! > (nの2乗)
{
assert(n >= 0); // 階乗への入力は非負整数でなければならない
}
out (result)
do {
{
if (n == 0) return 1;
assert(result >= n); // n! ≧ n
else return n * factorial(n-1);
assert(n <= 3 || result > n ^^ 2); // n ≧ 4 のとき、n! > (nの2乗)
}
do
 
{
void main(){
if writeln(n factorial(5)== 0);
return 1;
return n * factorial(n - 1);
}
</syntaxhighlight>
;実行結果:<syntaxhighlight lang=text>
 
120
;実行結果
</syntaxhighlight>
120
 
 
:解説や書式
 
;書式:<syntaxhighlight lang=d line>
書式は、関数の冒頭などで。
in
 
{
<syntaxhighlight lang="D">
in {
assert(入力の要求事項);
}
out (result)
{
assert(出力の要求事項);
}
do {
return 出力内容 ;
}
do
 
{
return 出力内容;
}
</syntaxhighlight>
: のように書く。
: 例として、もし writeln( factorial(4) ); で、引数を4でなく、たとえば「-7」など負の数にすると、入力の要求事項を満たさなくなるので、実行時エラーになる。
 
;エラーの例:<syntaxhighlight lang=text>
のように書く。
core.exception.AssertError@onlineapp.d(12): Assertion failure
 
----------------
例として、もし writeln( factorial(4) ); で、引数を4でなく、たとえば「-7」など負の数にすると、入力の要求事項を満たさなくなるので、実行時エラーになる。
??:? _d_assertp [0x56223707d810]
 
./onlineapp.d:12 int onlineapp.factorial(int) [0x56223707c3fe]
 
./onlineapp.d:6 _Dmain [0x56223707c3ca]
なお、「do」はかつてbodyが使われていたが、現在ではbodyは非推奨になっており、最新の仕様ではbodyは廃止である。(まだサポート中のOSにbodyが残っている。)
</syntaxhighlight>
 
:2022年現在、C言語には、直接契約プログラミングをサポートしてはいないが、assert()関数とマクロセットで契約プログラミングを実現する試みがある、
 
2020年現在、C言語には、契約プログラミングの機能は無い。
 
契約プログラミングを公式にサポートしている言語はいまのところ少ない。(非標準のライブラリなどでサポートされている言語はそこそこあるが、しかしプログラム言語の機能としてサポートされている言語は、かなり少ない。)
401 ⟶ 348行目:
readf() などの関数を使うと、ユーザーによるキーボードからの入力を受け付ける。それを使って数値をいろいろと入力して、契約プログラミングのコードをテストしてみるとする。
 
;コード例:<syntaxhighlight lang=d line>
<syntaxhighlight lang="D">
import std.stdio;
 
void main()
float bbb(float num)
in {
assert(num >= 0);
}
out (result) {
assert(result >= 0);
}
body {
return num + 3;
}
 
void main(){
writeln("Please input number");
 
int some;
readf("%d", &some);
 
writef("you input %d\n", some);
 
writefwriteln(bbb("you input %d\n",some ));
 
writeln( bbb(some) );
}
 
float bbb(float num)
in
{
assert(num >= 0);
}
out (result)
{
assert(result >= 0);
}
do
{
return num + 3;
}
</syntaxhighlight>
 
 
実際にテストしてみると、何も入力していない間は、いったんコンパイルできて実行できても、「-4」などマイナスの数を入力すると、そこで実行を停止する。
(コマンド rdmd でも コマンド dmd でも同様。)
437 ⟶ 382行目:
core.exception.AssertError@hello.d(5): Assertion failure
のようにエラーメッセージが表示される。
 
 
もちろん、プラスの「5」など契約に適合した数値を入れているかぎりは、動作する。
443 ⟶ 387行目:
これは、「'''契約をユーザーの入力のチェックに使ってはいけない'''」という決まりに反しており、契約の誤った使い方である。もちろん、決まりを破ってでも契約によるエラーメッセージを出したい理由があるならばそうすることもできるが、特段の理由がない限りは避けるべきである。
 
::註:この例がファジングを意図していると解すべきではない。
== 参考文献 ==
 
== 参考文献 ==
<references />
[[カテゴリ:D言語]]