D言語研究室
int main(char[][] args)
{
printf("hello world\n");
printf("args.length = %d\n", args.length);
for (int i = 0; i < args.length; i++) {
printf("args[%d] = '%s'\n", i, (char *)args[i]);
}
// 備考:
// 1.castするには明示的に cast(char*)としたほうがいいという話あり
// 2.char*へcastしなくとも、printf("%.*s",args[i]);のように
// %.*s を用いれば、文字列の長さ or \0に到達するまで表示してくれる
return 0;
}
|
char[] は、C++で言うところのSTLのstringクラスです。そこで、char []はchar*にキャストしている部分は、C++で言うところの s.c_str() のようにC言語的な文字列のポインタを取得していると考えることが出来ます。また、D言語のchar []が、C++のstringクラスだと考えれば、lengthプロパティによって長さを取得できることも頷けるでしょう。
同様に配列も、単純な配列ではなくC++で言うところのvectorです。すなわち、サイズを把握しています。上のサンプルのようにlengthプロパティによって配列サイズを取得できます。
本当は printf は c.stdio(C言語のスタンダードI/Oの意味)のライブラリなので
import std.c.stdio;
がプログラムの先頭にいるはずなのですが、書かなくても無事コンパイルできるようです。(たぶんディフォルトの設定の問題なんだと思います。)
int main(char[][] args)
{
printf("hello world\n");
printf("args.length = %d\n", args.length);
// for eachでまわしてみるべし
foreach (int i,char s[];args){
printf("args[%d] = '%s'\n", i,(char *)s);
// char [] は 概念的にはC++のstringクラスみたいなもの
// よって char* に変換しておかないとここでアクセス違反になる
}
}
|
最小限のサンプルを
foreach構文を用いて書き直したところです。
foreachのひとつ目は、ループ変数で、これは自動的に0で初期化され、ループごとにインクリメントされます。ふたつ目は集合を取り出すための変数で、ここではchar[][]であるargsの要素なのでchar[]を指定しています。3つ目は、取り出す集合を指定します。
たいていのプログラムには集合要素すべてに対して、どうのこうのするという処理が頻繁に出てきますので、foreachは、なかなか重宝します。
また、foreachで指定しているループ変数のほうは省略できます。
foreach (char s[];args){
printf("'%s'\n", (char *)s);
}
|
char[]を対象としてforeachでまわして一文字ずつ切り出すことも出来ます。次のは上記のプログラムと等価なプログラムです。
// for eachでまわしてみるべし
foreach (char s[] ; args){
foreach (char c;s)
printf("%c",c);
printf("\n");
}
|
ループ中に要素の値を書き換えてみます。
static int a[] = [ 123 , 456 ];
// for eachでまわしてみるべし
foreach (inout int n ; a) n += 10;
foreach (int n ; a) printf("%d\n",n);
|
※ 話によると「inout」キーワードは、将来的には「ref」に変更されるかも知れないそうです。
inoutを付けておくと、参照として受け取るようです。つけていない場合は変更しても元のデータは改変されないことから、参照ではなくコピーを受け取っていることがわかります。
ということは、コピー処理が重い場合は、inoutキーワードを指定しておけばよさそうです。あるいは、データの書き換えはしないときに、参照で受け取れる構文があれば理想なのだと思います。
※ 変数宣言の前にstaticがつけてありますが、これはstaticを外すとエラーになるからです。「staticな初期化子を持つのは静的な配列でなければならない」ようですが、この仕様にはどうも疑問が残ります。
foreachに関しては、C++で言うiteratorをimplicit(非明示的)に用いる用法なのですが、実用面で言えば、iteratorに対するreverse_iteratorのように、逆順での繰り返しを行なう構文が欲しいという気が少しします。
ただ、ユーザー定義のクラスについてはdelegateと無名関数を用いて次のように逆順の繰り返しを行なうことは出来ます。
class Foo
{
uint array[2];
void reverse_apply(void delegate(inout uint) dg)
{
for (int i = array.length-1 ; i>=0 ;--i)
dg(array[i]);
}
}
int main(){
Foo f = new Foo;
f.reverse_apply(
// 無名関数のdelegate。これを渡してやる。
// C++でfunctorを渡すのに似ている
delegate int(inout uint n) {
printf("%d\n",n);
}
);
return 0;
}
|
無名関数のdelegateがどんなコードが生成されるのか興味のあるところですが、C++でtemplateを駆使したdelegateよりは(コンパイラの最適化に)期待できそうです。コンパイラ側がその気になれば、C++のfunctorのようにinline展開できるでしょうし。
あとユーザー型に対してforeachが正しく動作するようにするためには次のようなメンバを用意すれば良いです。
int opApply(int delegate(inout uint) dg)
{
int result = 0;
for (int i = 0; i < array.length; i++)
{
result = dg(array[i]);
if (result)
break;
}
return result;
}
|
上記の関数をメンバとして持つならば
Foo f = new Foo;
foreach(uint n;f) printf("%d\n",n);
このようなforeachの呼び出しが出来るようになります。
foreach(int i,uint n;f) printf("[%d] = %d\n",i,n);
のように呼び出すforeachならば、
int opApply(int delegate(inout int i,inout uint) dg)
を用意しないといけません。
D言語のtemplateは、C++のと少し用法が違います。いったん、明示的に実体化しないと使えません。
template foo(T) {
T add (T x,T y) { return x+y; }
}
// ならば、
alias foo!(int) foo_int;
// と実体化したのちに、
int n = foo_int.add(3,5);
// のように呼び出すか、あるいは
int n = foo!(int).add(3,5);
// のように呼び出す必要があります。
|
同じ型で実体化された同じ名前のテンプレートは、実体をひとつしか持ちません。
例)
template foo(T) { T t; }
// に対して、
foo!(int).t = 10;
int n = foo!(int).t;
// これは2度目の実体化に相当するが、
// 実体化されたものは上のものと同一であるため、
// 変数tの実体は唯一であり、nには10が代入される
|
このinstance化の仕様はおかしいという人がいますが、私はこれでいいと思います。こうしておかなければ、何度も実体化した場合、関数用コードが多重に生成されるのを防ぐためにtemplate内のすべての関数にstaticを指定する必要が出てくるからです。
switch文で該当するcase文がないのは不正です。必ずdefaultが必要です。
switch ( x ) {
case 1 : ...
case 2 : ...
// x == 3の場合は、実行時エラーに!(default:break;を書く必要がある)
}
|
namespaceは現在D言語には存在しません。これがないとライブラリ開発の足かせとなる気がしなくもないです。そこで考えていたのですが..ついに、namespaceの代替案を思いつきました!
templateを使うのです!
template namespace_timer(T) { タイマー用のクラスいろいろ; }
使いたくなったら、alias namespace_timer!(void) timer;として
timer.getTime();
みたいな感じで使う。
こうすれば識別子が衝突しない。(しにくい)
...
...
だめでつか?(つД`) ゴメンナチイ
早く本物のnamespaceをくだしい..(つД`)
※ namespaceに相当する機能は、Walter氏自身、必要だとは感じているようですが、news groupでは「他にやることがたくさんあるので..」みたいな発言をされていました。
moduleというのが、ひとつの名前空間を形成します。
D言語にはnamespaceに相当するものがないので積極的にmoduleを使っていくことになります。
module名:ファイル名からパス名と拡張子を取り除いたものが、そのmodule名になります。ファイルの行頭で、module ほにゃらら; として、自分のmodule名を指定することも出来ます。
moduleはimport文を使えば、読み込めます。このとき、読み込んだmodule_Aが他のmodule_Bの関数などを呼び出している場合、module Aもコンパイルするときに指定してやらないと、module_Aのなかで使用しているmodule_Bの関数シンボルをリンク時に解決できなくてエラーになります。ということで、コンパイルオプションは以下のようになります。
例)
dmd test.d module_A.d
moduleがたくさんあると、たくさんコンパイル時に指定してやらないといけないのでちょっと使い勝手が悪いです。
importについては、module_Aのなかで
private import module_B;
のようにすれば、module_Bのシンボルは、このmodule_Aからのみアクセスできます。module_Aをimportするモジュールからは、module_Bのシンボルはアクセス出来ません。モジュールのimportは原則、private importにするべきだと思います。
逆に、
public import module_B;
とすれば、module_Aをimportするモジュールからも、module_Bのシンボルはアクセス出来ます。
ディフォルトではimportはpublic importのようです。
import文のアルゴリズムについて書いておきましょう。
import yaneSDK4D;
と書いた場合、
1.現在の(コンパイラの起動した)フォルダに存在するyaneSDK4D.dを探す。あればそれを読み込む
2.なければ、include path(コンパイラオプション or 定義ファイルによる設定)のなかのyaneSDK4D.dを探す。あればそれを読み込む
ということになっているようです。
import yaneSDK4D.timer;
と書いた場合、
1.現在の(コンパイラの起動した)フォルダから相対でyaneSDK4D\timer.dを探す。あればそれを読み込む
2.なければ、include path(コンパイラオプション or 定義ファイルによる設定)から相対でyaneSDK4D\timer.dを探す。あればそれを読み込む
ということになっています。すなわち、C/C++のincludeとは仕様が異なるのです。
※ また、yaneSDK4D.dというファイルを用意する場合は、同じ名前(yaneSDK4D)のフォルダが存在すると、コンパイル時に重複エラーになるようです。
import yaneSDK4D.*; のような書き方が出来ればいいと思うのですが、現状できません。あまり階層化しても、そのファイルをひとつひとつコンパイル時に指定しないといけないため、あまり得策ではありません。
ライブラリを作るならば一つフォルダ用意して、そのなかに(フォルダを作らずに)モジュールをべた置きしていくほうが良いでしょう。
ライブラリは、DMDコンパイラの場合、-Iオプションでimportするときのpathを指定できます。また ; で区切って複数指定することも出来ます。
例) dmd test.d -IyaneSDK4D;SDL
moduleはひとつの名前空間を作ること説明しましたが、importによって、他のモジュールのシンボルだけその名前空間に読み込むことが出来ます。
--- y4d.dというファイル。すなわちy4dというmodule
import y4d_timer;
import y4d_draw;
|
このy4d.dをmain.dからimportしたとします。そうすれば、y4d_timer.dとy4d_draw.dのなかにあるシンボルは、y4dという名前空間からアクセス出来ます。
--- main.d
import y4d;
int main(){
// y4d_timerで定義されているclass timerにアクセスする
y4d.timer t1 = new y4d.timer;
// y4dをimportしているので、y4dというprefixなしでも修飾できる
timer t2 = new timer;
return 0;
}
/*
このmodule内でclass timerを定義した場合、
y4dのほうのtimerは隠蔽されます。
すなわち、y4d.timerと書かなければy4dのtimerクラスには
アクセス出来なくなります。
単にtimerと書いた場合は、このmodule内のtimerクラスを
指すことになります。
*/
|
名前空間名の変更は、aliasを用いて書けます。
alias y4d yaneSDK4D;
// このあとy4dの代わりにyaneSDK4Dと修飾できるようになる
// 例)
yaneSDK4D.timer t = new yaneSDK4D.timer;
|
名前の衝突が怖ければ、自分の使うクラスだけimportしたモジュール(例えば、mysdk.d)を用意して、そのモジュールをimportして、そのモジュール名付きでアクセスする(例えば、mysdk.timer)というのもひとつの方法だと思います。
package内のmoduleは、ファイル先頭でmodule宣言をしないと、そのモジュールを2箇所からimportすると、以下のようなエラーになるようです。
yaneSDK4D\y4D_timer.d(2): module singleton is in multiply defined
↑この2行目とは、import ytl.singletonと書いてある行。
|
module名はディフォルトで、package名がつかないから、こういう仕様になっているんでしょうか。だとすれば、module宣言は必須ということに..?
さらに、このモジュール宣言、
y4D_inputというフォルダ名にしていたんですけど、
だと重複エラーに。
どうもフォルダ名とまったく同じように
module y4D_input.input;
↑大文字
|
にしないといけない模様。
これ、小文字しかファイル名に使えないシステムに持って行ったときに、ハマりそうですね。ということはpackage名(フォルダ名)は、小文字で統一しておかないとまずいってことになりそうです。
各classには
暗黙のtoStringという関数があるようです。
※ それぞれのclassは、暗黙でObjectクラスからの派生クラスと扱われていて、そのObjectクラスにtoStringという関数が用意されているからです。
import std.string;
class someclass {
char [] getString() { return toString("abc"); }
}
|
このコードは正しくstd.stringのtoStringが呼び出されません。
import std.string;
class someclass {
char [] getString() { return .toString("abc"); }
// あるいは std.string.toString↑
}
|
と書く必要があるようです。
D言語の関数呼び出しに関してMike氏のD言語のパフォーマンステストの記事があります。[英語]
http://www.geocities.com/one_mad_alien/perftest.html
フレームポインタをごちゃごちゃしない分だけCより速いようですね。なかなか期待が持てます。
C#などと同じく、基本型(intとかboolとか)とstructは、実体を直接扱います。
classは参照で扱います。また、intやboolはそのコンストラクタ(?)で、0とfalseに初期化されます。double,floatはNaNに初期化されるので注意が必要です。
int[] a1 = new int[10];
// a1 は、0で初期化されたint配列。すぐに使える。
// 例)
// a1[5] = 100;
bool[] a2 = new bool[10];
// a2は、falseで初期化されたbool配列。すぐに使える。
// 例)
// a2[3] = true;
class foo { }
foo[] a3 = new foo[10];
// a3は、fooクラスの配列。まだfooのインスタンスまでは代入されていない
// また、fooのコンストラクタも呼び出されていない
// 実際に使うためには、このあとfooをnewしなければならない
// 例)
// foreach(inout f;a2) f = new foo;
// すなわち、クラスの配列のnewは、クラスのインスタンスを生成するのではなく
// 配列(C++風に言うとポインタのvector)を生成する。
struct foo { int a = 5; }
foo[] a4 = new foo[10];
printf("%d\n",a4[3].a); // 5が表示される
// a4は、foo構造体の配列。
// メンバの初期化も終わっている。
// ※ structにはコンストラクタもデストラクタも書けません。
interface foo { }
foo[] a5 = new foo[10];
// interfaceは(扱いとしては)抽象クラスの一種なので、
// new自体は出来ないが、interfaceの配列自体はnewできる。
|
C++でテンプレートをごにょごにょしてコンパイル時に何らかの処理をしてしまおうという試みがあります。
http://bm98.yaneu.com/intensive/spt4.html
実際、かなりトリッキーなことも出来て、テンプレートを用いたプログラミング(いわゆるgeneric programming)に応用できるのですが、これをDでやってみようという試みです。
// **************************************************
template total(T){}
template total(int T)
{
instance total(T-1) m_total;
enum{ val = T + m_total.val }
}
template total(int T : 1)
{
enum{ val = 1 }
}
void main()
{
printf("%d\n", instance total(100).val);
}
// **************************************************
// 出力
5050
|
めでたしめでたし。
floatは32bit,doubleは64bit,realは実装系依存(Intel系では80bit)です。
どうせハードで処理するので、realを使っても遅くはならないでしょうから、浮動小数には全部realを使っていくことになるのですが、ここで一つ問題が出てきます。
※ realのほうがバイト数が多いので、メモリにアクセスする分の時間は余分にかかります。realのほうがdoubleより10%程度は演算が遅いと考えたほうが良いでしょう。80bitもの精度が不要ならばdoubleで十分です。doubleとfloatの演算速度に関してはほとんど変わらないので、doubleでいいと思いますが。
void test(int x) { printf("int\n"); }
void test(double x) { printf("double\n"); }
int main(){
test(3.0);
return 0;
}
|
このコードの場合、どちらを呼び出されるでしょうか?
3.0はdouble値です。intのほうを呼び出すためには、(暗黙の)型キャストが必要となります。そこで、型キャストをしないで済むほうのdoubleを引数にとる関数が選ばれます。
同じように、次のコードを考えてみましょう。
void test(int x) { printf("int\n"); }
void test(real x) { printf("real\n"); }
int main(){
test(3.0);
return 0;
}
|
これはどちらが呼び出されるでしょうか?一見するとrealのほうのようですが、そうではありません。このコードは、呼び出しが曖昧だとしてコンパイルエラーになります。
さきほど説明したように、3.0はdouble値です。これは、realにもintにもなれますが、どちらも1回の暗黙キャストを必要とします。よって、どちらにするか決定できないのです。
void test(int x) { printf("int\n"); }
void test(double x) { printf("double\n"); }
void test(real x) { printf("real\n"); }
int main(){
test(3); // int
test(3.0); // double
return 0;
}
|
この場合は、ひとつ目のtestではintを引数としてとるものが呼び出され、ふたつ目のtestではdoubleを引数としてとるものが呼び出されます。どちらも暗黙の変換を必要としないからです。曖昧エラーにもなりません。
intとrealの関数を用意することは少なくないと思いますが、この点、注意が必要です。
D言語でのchar[]は、\0終端ではありません。ただし、最初に確保されるバッファ(16バイト)は\0で初期化されているようで、15文字以内ならば計らずとも\0終端です。そこで、文字列の代入直後は、\0終端になっています。ところが連結etcを行なった場合、\0終端ではなくなってしまいます。
char[]をchar*へキャストした場合、これは単純なキャストなので、\0終端補整をするような処理は含まれません。よって、以下のようになります。
char [] a = "12345678";
a = a ~ a; // 連結。この結果、\0終端ではなくなる
printf("%.*s\n",a); // 正しい
printf("%s\n",cast(char*)a); // これだとゴミが表示される
printf("%s\n",std.string.toStringz(a)); // 正しい
|
ファイルに書き出すときなどはtoStringZを用いて変換しなくてはならない場面があります。本当は、char*ではなく、char[]を引数としてとるバージョンが欲しいんですけどね..。
class対応のtemplateを書いていると、nullを代入するコードを書いてしまいがちです。
このtemplateをint型のような基本型でinstance化したとき、int型変数へnullは代入できないので、この部分でコンパイルエラーになります。
以下のは、書いてはいけない例です。
T get() { if (!a) return null; else return a[0]; }
|
nullをT型に変換して返しているのですが、Tがint型であるときは、このコードは不正です。
次のテクニックを用いて、回避することが出来ます。
T get() { if (!a) return cast(T)(null); else return a[0]; }
|
T型にcastするのです。nullをint型にcastすれば0になるので、このコードならばコンパイルは通ります。あるいは、T.init と書いてもいけます。こちらのほうがD言語ではよさげです。ただ、浮動小数型だと初期値がNanなのが気になるだけで..。
D の文字列は、空文字列かどうかを、if(str)などとしてチェックできるのですが、一度何かしらの文字列をセットしてしまうと、そうはいかないようです。
以下サンプルです。
void main()
{
char[] s;
func(s); // false
s = "aaa";
func(s); // true
s = "";
func(s); // true!!
s.length=0;
func(s); // false
}
void func(char[] s)
{
if( s ) printf("true\n");
else printf("false\n");
}
|
ですので、何もセットされていないかどうかを判定するには、
if( s.length==0 )
で判定した方がよさそうです。ちょっとnews groupで議論してみました。
☆ 発端
文字列が空かどうかをチェックするのにs.length == 0はあんまりなので配列にemptyプロパティを持たせたほうがいいんじゃないかという提案をしてみました。
私の投稿
http://www.digitalmars.com/drn-bin/wwwnews?D/21782
Walter氏からのお返事。
http://www.digitalmars.com/drn-bin/wwwnews?D/21793
Walter氏自身に問題意識がなさげ。そんなわけで、こない返した。
http://www.digitalmars.com/drn-bin/wwwnews?D/21817
冷やかし野郎出現。
http://www.digitalmars.com/drn-bin/wwwnews?D/21831
そしたら、s.length = 0;したときにsはnullになるような実装はどうかとか言ってやがる奴がいる。
http://www.digitalmars.com/drn-bin/wwwnews?D/21825
で、GCの実装をみれば、sizeが0になれば自動的にnullになると言っている。
http://www.digitalmars.com/drn-bin/wwwnews?D/21826
確かに、lengthを書き換えたときはnullになるように実装されています。だから、Walter氏、最初に !s も s.length == 0 も等価だよ、と言ったんだと思いますけど、s = "";ならば、これは非nullでありながらlength == 0になります。
Walter氏自身、sが非nullでlength==0になることを自覚してなさげなので、ガツーンと思いの丈をぶちかましてきますた。
http://www.digitalmars.com/drn-bin/wwwnews?D/21834
http://www.digitalmars.com/drn-bin/wwwnews?D/21839
Ben氏には伝わったようです。
http://www.digitalmars.com/drn-bin/wwwnews?D/21843
Ilya氏にはキチガイ扱いされたり。
http://www.digitalmars.com/drn-bin/wwwnews?D/21847
http://www.digitalmars.com/drn-bin/wwwnews?D/21848
それをなだめるMatthew氏。
http://www.digitalmars.com/drn-bin/wwwnews?D/21859
結局のところ、GCはlength==0になったときにポインタにnullをセットするように設計されているけど、これはある配列がlength==0ならばnullであることを保証するためのものなのか、というのが争点になってきます。
emptyプロパティが実装されるかどうかはわからないですけど、Dの場合、nullであっても配列のサイズは取得できるので実装してあったほうが使い勝手がいいと思います。
結局のところ、s.length == 0が、現状Dで一番正しい書き方だっていうのがWalter氏自身も理解してないぐらいなので誰も理解してない模様。やじられまくり。
しかし、s = ""とか 部分配列の代入でlengthが0にもなっているにもかかわらずポインタがnullにならないのがバグくさいです。少なくとも、現時点でのWalter氏の設計意図からすればそのようなので、最終的には、単に !s と書いて空かどうかを判定できるようになると思います。
配列の初期化子についてお困りの人が多いようなので書いておきます。
1.{ }は、構造体の初期化子で、配列の初期化に使うのは [ ]です。
2.staticな配列以外は、初期化子を書けません。
3.どうしても非staticな配列の初期化が必要ならば、staticの配列から非staticな配列へ代入すればコピーされるので、これを利用します。
int[] an1 = { 1,2,3 }; // 1.の理由で×
int[] an2 = [ 1,2,3 ]; // 2.の理由で×
static int[] an3 = { 1,2,3 }; // 1.の理由で×
static int[] an4 = [ 1,2,3 ]; // ○
int[] an5 = an4; // ○ 3.のタイプ
char [] as1 = "abc"; // これは配列ではないので○
char [][] as2 = ["abc" , "def" , "ghi"]; // 2.の理由で×
static char [] as3 = "abc"; // これは配列ではないけど○
static char [][] as4 = ["abc" , "def" , "ghi"]; // ○
char [][] as2 = as4; // ○ 3.のタイプ
printf("%.*s\n",as2[1]); // ○ 表示。"def"が表示される
char [][] as5 = new char[][10]; // char[] を10個。要するに10個の文字列が入る配列を用意している
|
D言語のinlineアセンブラを少し使ってみたのですが、ずいぶんと従来の(Visual C++etcの)インラインアセンブラとは違った印象を受けます。使ってみて、気づいたことは、
・レジスタは大文字
・push offset ラベル が出来ない。(アセンブラのバグくさい。ロベールさんがnews groupに報告済み)
・各ステートメントごとに ; が必要
・構造体メンバを参照する構文が、前置式
例) mov EDX , fooclass.somevar[EAX]
・thisを参照できなさげ。[EBP - 4] が this のようです。VC++ みたいに内部で this 変数を作ってる模様。ただ、渡すときに使うレジスタは ECX じゃなく EAX みたいです。つまり、naked にした時は EAX から this がとれるということです。仮想関数を呼ぶのは、VC++ でも無理しないといけなかったように、D でも同じだけ無理しないといけないみたいです(vtable 中の位置を自分で指定するとか!) ただ、ポリモーフィズム無視で呼ぶこと自体はできるようです。call foo; とか。
・nakedな関数はasmの先頭にnakedは書けば関数自体がnakedになる模様
・ラベルで終わるときは、ラベルのあとにも、いちいち ; が必要
例) asm { L1 ; }
・実行ファイル中のコードの開始位置は 0A10 のようです。
パディングに nop を使ってないので nop; nop; nop; nop; ... (90 90 90 90 ...) が目印に使えます。ご参考までに。
そんなことを言っていたら、ロベールさんが変換スクリプトを作ってくださいました!!
=>
C/C++ → D インラインアセンブラ変換スクリプト (Ruby)
interfaceクラスからその派生クラスに正しくダウンキャストされません。
interface I { abstract void f(); }
class X : I { void f() { } }
void test() {
I i = new X;
X x = cast(X)(i);
x.f(); // アクセス保護違反に
}
|
これは、interfaceではなくてclassにすれば問題ないです..がinterfaceの意味が..と思っていたのですが、Delphiのinterfaceも同様だそうで。 →
Y.Tomino氏の入門記事[日本語]のD Way参照のこと。
とりあえず、interfaceから派生クラスにダウンキャストできないのは仕様だろうという結論になったので、ダウンキャストを前提とするならば、interfaceではなく
abstract classを用いるべきのようです。
これは、いわゆる、抽象クラスです。共通の基底クラスを持たせたいときなどに使います。
abstract class ab_class {
void test1();
void test2();
}
class imp : ab_class {
void test1() { printf("test1\n"); }
void test2() { printf("test2\n"); }
}
int main(){
ab_class a = new imp;
a.test1(); // imp.test1が呼び出される
}
|
しかしクラスなので、多重継承は出来ません。多重継承を行ないたいときは、interfaceを用います。
enum { A,B,C }
// 無名enumはconst intの代わりに使えます。
enum a { A,B,C }
// 名前つきenum。
// int x = a.B のように名前つきで使います。
|
また、ベース型を指定できます。整数型以外も指定できます。
enum keycode : char { LEFT='a', DOWN='z', UP='w', RIGHT='s' }
enum mathConst : float { PI=3.14, E=2.72, GAMMA=0.58 }
|
次のようなプログラムは、エラーになりません。D言語のポリシーとして、コンパイラの警告という考えかたは無いため、これ、意外とハマる気がしなくもないですが。
void[] test1(){
try {
} catch {
return null;
// このreturn がなければエラーになる
}
}
void[] test2(int n) {
if (n == 5){
} else {
return null;
// このreturn がなければエラーになる
}
}
|
※ 備考:-debugオプションを指定してビルドした場合、returnのないpathを実行するときにassertされます。
文字列のコピーは必要に応じて行なわれます。これは知っていないと思わぬバグを引き起こすことがあります。
次のプログラムは、文字列を連結してsに代入しています。
char[] s;
char[] t = "123";
while (1) {
s = t ~ s;
// 何か処理
}
|
一見すると、文字列同士を連結するので、連結後、コピーが生成されて、それをsに代入するかのような錯覚を起こします。ところが、初期状態ではsは空っぽなので、1度目は、copyの必要なしと判断されて、copyは行なわれません。
copyが行なわれないということは、sはtとバッファを共有することを意味します。これが意図しない動作であることも多いはずです。これを防ぐためには、次のように明示的にコピーすることです。
structを使ったほうが軽いので使えるところでは積極的に struct を使っていこうかと思ったのですが、これまた使いにくいのです。
コンストラクタが使えないのは、まあいいとして、
struct foo { int a,b; }
int main(){
foo f1; // スタック上に確保される
f1 = new foo; // ng (コンパイルエラー)
// structのnewは、ポインタ型を返す。
// よってfoo*をfooにキャスト出来ないのでエラー。
foo* f2 = new foo; // ヒープ上に確保するならこれ
foo[] f3 = new foo[5]; // ok。foo[5]はヒープ上に確保される
f3 ~= new foo; // コンパイルエラー。
// structの配列は、D言語的な動的配列(C++のstd::vectorぽいの)
// ではなくC言語的な単純な配列なのでくっつけることは出来ない
foo*[] f4 = new foo; // ok
f4 ~= new foo; // ok
// foo* の(D言語的な)動的配列なのでつっつけることが出来る
foo* f5 = new foo[5]; // ok。foo[5]は、foo[]型を返すが、
// foo[]型はfoo*型へ暗黙でキャストできるのでok
foo[] f6 = new foo; // ng
// foo*型をfoo[]型へ暗黙ではキャストできない
// (配列にはサイズ情報が必要なため)
foo[][] f7 = new foo[][]; // ng
foo[][] f8 = new foo[][10]; // ok
foo[][] f9 = new foo[10][]; // ng
|
あれ、ポインタいらないんじゃなかったの?とか思ったり。
わかったことは、
・structはディフォルトでスタック上に配置される
・structをヒープ上に配置したければnewして、ポインタを使う
・class用に書かれたテンプレートにstructを食わせるには、
alias instance.vector(foo*).t foo_vector;
というようにポインタ型を指定する。
まあ、structをclass的に使おうっていうのが少し間違っているのかも知れないですけど、ポインタを持ち出さないといけないあたりが少し悲しいです。
関数呼び出しのとき、構造体は参照渡しではありません。
では、構造体の配列はどうなのでしょうか?
struct Point { int x,y; }
void foo(Point[4]* points){
(*points)[0].x = 10; // points[0].x = 10; は不正
}
void test_function() {
Point[4] points;
foo(&points);
}
|
こうやってポインタを渡すことは出来ますが、こんなことをしなくとも、構造体の配列は参照渡しです。よって、単に次のように書くことが出来ます。
struct Point { int x,y; }
void foo(Point[4] points){
points[0].x = 10;
}
void test_function() {
Point[4] points;
foo(points);
}
|
Last Updated : 2004-4-19
written by yaneurao
http://bm98.yaneu.com/dlang/