D語言中的函數

 

函數

虛函數

所有的非靜態非私有函數都是虛函數。這聽起來也許低效,但是因爲D編譯器在生成代碼時知道所有的類層次結構,所有未被重載的函數可以被優化爲非虛函數。事實上,因爲 C++ 程序員傾向於“在不確定時,聲明它爲虛函數”,D 採用的方法“聲明爲虛函數除非我們能夠證明它可以是非虛函數”造成的結果是產生更多更直接的函數調用。由重載非虛函數造成的 bug 也減少了。

擁有非D鏈接的函數不會是虛函數,因此也不能被重載。

標記爲 final 的函數不可以在派生類中重載,除非它們也是 private 。例如:

	 A
	{
	     def() { ... }
	    final  foo() { ... }
	    final   bar() { ... }
	      abc() { ... }
	}

	 B : A
	{
	     def() { ... }	
	     foo() { ... }	
	     bar() { ... }	
	     abc() { ... }	
	}

	 test(A a)	
	{
	    a.def();	
	    a.foo();	
	    a.bar();	
	    a.abc();	
	}

	 func()
	{   B b =  B();
	    test(b);
	}
	
支持 協變返回類型 ,這意味着派生類的重載函數可以返回從被重載的函數的返回類型派生的類型:
	 A { }
	 B : A { }

	 Foo
	{
	    A test() {  ; }
	}

	 Bar : Foo
	{
	    B test() {  ; }	
	}
	

函數繼承和重載

派生類中的函數將重載基類中同函數名同參數類型的函數:
	 A
	{
	     foo( x) { ... }
	}

	 B : A
	{
	      foo( x) { ... }
	}

	 test()
	{
	    B b =  B();
	    bar(b);
	}

	void bar(A a)
	{
	    a.foo();	
	}
	
但是,在進行重載解析的時候,不會考慮基類中的函數:
	 A
	{
	     foo( x) { ... }
	     foo( y) { ... }
	}

	class B : A
	{
	      foo( x) { ... }
	}

	 test()
	{
	    B b =  B();
	    bar(b);
	}

	 bar(A a)
	{
	    a.foo(1);		
	    B b =  B();
	    b.foo(1);		
	}
	
考慮重載解析過程中的基類函數,使用 別名聲明
	 A
	{
	     foo( x) { ... }
	     foo( y) { ... }
	}

	 B : A
	{
	    alias A.foo foo;
	      foo( x) { ... }
	}

	 test()
	{
	    B b =  B();
	    bar(b);
	}

	 bar(A a)
	{
	    a.foo(1);		
	    B b =  B();
	    b.foo(1);		
	}
	
函數參數的默認值不會被繼承:
	 A
	{
	     foo( x = 5) { ... }
	}

	 B : A
	{
	     foo( x = 7) { ... }
	}

	 C : B
	{
	     foo( x) { ... }
	}


	 test()
	{
	    A a =  A();
	    a.foo();		

	    B b =  B();
	    b.foo();		

	    C c =  C();
	    c.foo();		
	}
	

內聯函數

D中沒有 inline 關鍵字。編譯器決定是否將一個函數內聯,就像 register 關鍵字不再同編譯器是否將變量存在寄存器中相關一樣。(也沒有 register 關鍵字。)

函數重載

在 C++ 中,函數重載有很多複雜的級別,一些被定義爲“更好的”匹配。如果代碼編寫者利用函數重載選擇時更爲細微的行爲,代碼就會變得難以維護。不僅 C++ 專家很難弄明白爲什麼選擇這個函數而不選擇哪個,不同的 C++ 編譯器也可能會採用不同的方式實現這個充滿技巧的特徵,這造成了微妙而災難性的結果。

在 D 中,函數重載很簡單。允許精確匹配,允許包含隱式轉換的匹配,除此之外就不匹配。如果有多於一個匹配,就是錯誤。

非 D 鏈接的函數不允許重載。

函數參數

參數是 inoutinout 的。in 是默認值;outinout 參數工作起來像存儲類。例如:
	 foo( x,  y,  z,  q);
	
x 爲 in、y 爲 out、z 爲 inout ,而 q 爲 in

out 已經很少見了,inout 更少見,因此如果使用這兩種特性就需要使用關鍵字,而將 in 作爲默認值。它們出現在 D 中是因爲:

  • 函數聲明清楚的表示了哪些是函數的輸入,哪些是函數的輸出。
  • 不再需要單獨使用一種叫 IDL 語言。
  • 可以給編譯器提供更多的信息,從而可以提供更好的錯誤檢查並生成更好的代碼。
  • (也許)不需要引用(C++中的‘&’)聲明。
out 參數被設爲對應類型的默認值。例如:
	 foo( bar)
	{
	}

	 bar = 3;
	foo(bar);
	
	

可變函數參數

函數可以是可變的,就是說它們可以有任意個參數。一個可變的函數用必需參數後面的‘...’表示:
	 foo( x,  y, ...);

	foo(3, 4);	
	foo(3, 4, 6.8);	
	foo(2);			
帶有非 D 鏈接的代碼必須帶有至少一個非可變參數。
	 abc(...);		
	 (C) def(...);		
可變函數聲明瞭一個特殊的局部變量,_argptr ,它是指向第一個可變參數的 void* 類型的指針。如果要訪問這些參數,_argptr 必須被轉換爲指向所希望的參數類型的指針:
	foo(3, 4, 5);	

	 foo( x,  y, ...)
	{    z;

	    z = *(*)_argptr;	
	}
	
對於採用 D 鏈接的可變函數,有一個隱含的名爲 _arguments 的參數,它的類型爲 TypeInfo[]_arguments 給函數提供了參數的個數,也提供了每個參數的類型,這樣就可以編寫類型安全的可變函數。
	 FOO { }

	 foo( x, ...)
	{
	    printf(, _arguments.);
	     ( i = 0; i < _arguments.; i++)
	    {
	    	_arguments[i].print();

		 (_arguments[i] == (int))
		{
		     j = *( *)_argptr;
		    _argptr += .;
		    printf(, j);
		}
		 (_arguments[i] == (long))
		{
		     j = *( *)_argptr;
		    _argptr += .;
		    printf(, j);
		}
		 (_arguments[i] == (double))
		{
		    double d = *( *)_argptr;
		    _argptr += .;
		    printf(, d);
		}
		(_arguments[i] == (FOO))
		{
		    FOO f = *(FOO*)_argptr;
		    _argptr += FOO.;
		    printf(, f);
		}
		
		    (0);
	    }
	}

	 main()
	{
	    FOO f =  FOO();

	    printf(, f);
	    foo(1, 2, 3L, 4.5, f);
	}
	
將打印出:
	00870FD0
	4 arguments
	int
		2
	long
		3
	double
		4.5
	FOO
		00870FD0
	
爲了避開不同 CPU 結構上的各種奇特的堆棧分佈帶來的麻煩,使用 std.stdarg 訪問可變參數:
	 std.stdarg;

	 foo( x, ...)
	{
	    printf(, _arguments.);
	     ( i = 0; i < _arguments.; i++)
	    {
		_arguments[i].print();

		 (_arguments[i] == ())
		{
		     j = va_arg!()(_argptr);
		    printf(, j);
		}
		 (_arguments[i] == ())
		{
		     j = va_arg!()(_argptr);
		    printf(, j);
		}
		 (_arguments[i] == ())
		{
		     d = va_arg!()(_argptr);
		    printf(, d);
		}
		 (_arguments[i] == (FOO))
		{
		    FOO f = va_arg!(FOO)(_argptr);
		    printf(, f);
		}
		
		    (0);
	    }
	}
	

局部變量

如果使用一個未被賦值過的局部變量,會被視爲錯誤。編譯器的實現未必總能夠檢測到這些情況。其他語言的編譯器有時會爲此發出警告,但是因爲這種情況幾乎總是意味着存在 bug ,所以應該把它處理爲錯誤。

如果聲明一個變量但卻從未使用,會被視爲錯誤。死變量,如同過時的死代碼一樣,都會使維護者迷惑。

如果聲明的一個變量掩蓋了統一函數中的另一個變量,會被視爲錯誤:

	 func( x)
	{    x;		
	      y;
	     ...
	     {    y;		  z;
	     }
	     {    z;			     }
	}
	
因爲這種情況看起來不合理,在實踐中出現這種情況時不是一個 bug 至少看起來也像一個 bug 。

如果返回一個局部變量的地址或引用,會被視爲錯誤。(譯註:有多少 C++ 書曾告誡你不要這麼做?如果沒有,你可以扔掉那本書了。在 D 中,你不再需要擔心了,你所損失的,只是一點極爲低級的能力。)

如果局部變量同標籤同名,會被視爲錯誤。

嵌套函數

函數可以嵌套在其他函數中:
	 bar( a)
	{
	     foo( b)
	    {
		 abc() {  1; }

		 b + abc();
	    }
	     foo(a);
	}

	 test()
	{
	     i = bar(3);	
	}
	
嵌套函數只能由它外圍的函數及同它有相同嵌套深度的嵌套函數訪問:
	 bar( a)
	{
	     foo( b) {  b + 1; }
	     abc( b) {  foo(b); }	
	     foo(a);
	}

	 test()
	{
	     i = bar(3);	
	     j = bar.foo(3);	
	}
	
嵌套函數可以訪問它外圍的函數的變量和其他的符號。在這裏,“訪問”意味着既可以讀,也可以寫。
	 bar( a)
	{    c = 3;

	     foo( b)
	    {
		b += c;		
		c++;		
		 b + c;	
	    }
	    c = 4;
	     i = foo(a);	
	     i + c;		
	}

	 test()
	{
	     i = bar(3);	
	}
	
這種訪問能力能夠跨越多重嵌套:
	 bar( a)
	{    c = 3;

	     foo( b)
	    {
		 abc()
		{
		     c;	
		}
		 b + c + abc();
	    }
	     foo(3);
	}
	
靜態嵌套函數不能訪問外圍函數的任何堆棧變量,但能訪問靜態變量。這種行爲同靜態成員函數類似。
	 bar( a)
	{    c;
	    d;

	     foo( b)
	    {
		b = d;		
		b = c;		
		 b + 1;
	    }
	     foo(a);
	}
	
函數可以嵌套在成員函數內:
	 Foo
	{    a;

	     bar()
	    {	 c;

		 foo()
		{
		     c + a;
		}
	    }
	}
	
嵌套結構和嵌套類的成員函數不能訪問外圍函數的堆棧變量,但是能訪問其他的符號:
	 test()
	{    j;
	     s;

	     Foo
	    {    a;

		 bar()
		{    c = s;		
		     d = j;		

		     foo()
		    {
			 e = s;	
			 f = j;	

			 c + a;	
					
					
		    }
		}
	    }
	}
	
嵌套函數總是使用 D 函數鏈接類型。

同模塊級的聲明不同,函數作用域的聲明會按照聲明的順序處理。這意味着連個嵌套函數不能互相調用:

	 test()
	{
	     foo() { bar(); }	
	     bar() { foo(); }	
	}
	
解決的方法是使用委託:
	 test()
	{
	     () fp;
	     foo() { fp(); }
	     bar() { foo(); }
	    fp = &bar;
	}
	
未來的方向:這個限制可能會被刪除。

委託、函數指針和動態閉包

函數指針可以指向一個靜態嵌套函數:
	 function() fp;

	 test()
	{     a = 7;
	      foo() {  a + 3; }

	    fp = &foo;
	}

	 bar()
	{
	    test();
	     i = fp();		
	}
	
委託可以用非靜態嵌套函數賦值:
	 () dg;

	 test()
	{    a = 7;
	     foo() {  a + 3; }

	    dg = &foo;
	     i = dg();		
	}
	
但是,一旦聲明堆棧變量的函數退出了,堆棧變量就不再有效;同樣的,指向堆棧變量的指針也不再有效:
* bar()
{   b;
    test();
    i = dg();
    &b;
}
非靜態嵌套函數的委託包括兩塊數據:指向外圍函數堆棧幀的指針(叫做 幀指針 )和函數的地址。與此類似,結構/類的非靜態成員函數委託由 this 指針和成員函數的地址組成。這兩種形式的委託可以互相轉換,實際上它們具有相同的類型:
	 Foo
	{          a = 7;
	   
	   
	   
	     bar() {  a; }
	}

	 foo( () dg)
	{
	   
	   
	   
	     dg() + 1;
	}

	 test()
	{
	   
	   
	   
	     x = 27;
	   
	   
	   
	     abc() {  x; }
	    Foo f;
	   
	   
	   
	     i;

	    i = foo(&abc);		
	    i = foo(&f.bar);	
	}
	
環境和函數的結合被稱作 動態閉包

未來的方向:函數指針和委託可能會合併到一個統一的語法中並且可以互相代替。

匿名函數和匿名委託

參見 函數文字量 。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章