函 数 2.014

[size=large]函数体:
块语句
语句体
In语句 语句体
Out语句 语句体
In语句 Out语句 语句体
Out语句 In语句 语句体

In语句:
in 语句块

Out语句:
out 语句块
out ( 标识符 ) 语句块

语句块:
body 语句块

  2.014

Pure Functions

Pure functions are functions that produce the same result for the same arguments. To that end, a pure function:

has parameters that are all invariant or are implicitly convertible to invariant
does not read or write any global mutable state

A pure function can throw exceptions and allocate memory via a NewExpression.

int x;
invariant int y;
const int* pz;

pure int foo(int i, // ok, implicitly convertible to invariant
char* p, // error, not invariant
const char* q, // error, not invariant
invariant int* s // ok, invariant
)
{
x = i; // error, modifying global state
i = x; // error, reading mutable global state
i = y; // ok, reading invariant global state
i = *pz; // error, reading const global state
return i;
}


Nothrow Functions

Nothrow functions do not throw any exceptions derived from class Exception.



1 虚函数(Virtual Functions)

虚函数指的是这样一类函数:它们通过叫作 vtbl[] 的 函数指针表 被间接调用,而非直接进行调用。所有的非静态、非私有、非模板 成员函数都是虚函数。这听起来也许有些低效,但是,因为 D 编译器在生成代码时知道所有的类层次结构,所以,所有未被重写的函数可以被优化成非虚函数。

事实上,C++ 程序员倾向于“在不确定时,声明它为虚函数”;

而 D 采用的方式是“都声明成虚函数,除非我们可以证明它可以是非虚函数”,这样综合后的结果就是产生更多更直接的函数调用。这也可以大大减少由于没有将会被重写(voerride)的函数声明为虚函数,而引起的错漏。

因为带有“非 D 连接特性(non-D linkage)”的函数不能是虚函数,因此不能被重写。

因为成员模板函数不能是虚的,因此不能被重写。

标记为 final 的函数不可以在派生类中被重写,除非它们同时也是 private 类型。

例如:
class A
{
int def() { ... }
final int foo() { ... }
final private int bar() { ... }
private int abc() { ... }
}

class B : A
{
int def() { ...} // 正确,重写 A.def
int foo() { ...} // 错误,A.foo 是 final 的
int bar() { ...} // 正确,A.bar 是 final private 的,但不是 virtual 的
int abc() { ...} // 正确,A.abc 不是 virtual 的,B.abc 是 virtual 的
}

void test(A a)
{
a.def(); // 调用 B.def
a.foo(); // 调用 A.foo
a.bar(); // 调用 A.bar
a.abc(); // 调用 A.abc
}

void func()
{ B b = new B();
test(b);
}

协变返回类型(covariant return types)”,即表示在 派生类 里的重写函数可以返回这样一种类型——此类型派生自 被重写函数 所返回的类型:

class A { }

class B : A { }

class Foo
{
A test() { return null; }
}

class Bar : Foo
{
B test() { return null; } // 重写,并且同 Foo.test() 是协变的
}

[color=red]2.014
Virtual functions all have a hidden parameter called the this reference, which refers to the class object for which the function is called. [/color]


2 函数继承(inheritance)和重写(overidding)

派生类中的函数将重载 基类中 同函数名、同参数类型 的函数:

class A
{
int foo(int x) { ... }
}

class B : A
{
override int foo(int x) { ... }
}
void test()
{
B b = new B();
bar(b);
}
void bar(A a)
{
a.foo(1); // 调用 B.foo(int)
[color=red]//2.014 issues runtime error (instead of calling A.foo(int))[/color]
}

而且,在进行重载解析的时候,基类中对应的那些函数都不会被考虑的:

class A
{
int foo(int x) { ... }
int foo(long y) { ... }
}

class B : A
{
override int foo(long x) { ... }
}

void test()
{
B b = new B();
b.foo(1); // 调用 B.foo(long),因为不会考虑 A.foo(int)
A a = b;
a.foo(1); // calls A.foo(int)
}

要在重载解析过程中让基类的那些函数有效,请使用“别名声明”:

class A
{
int foo(int x) { ... }
int foo(long y) { ... }
}

class B : A
{
alias A.foo foo;
override int foo(long x) { ... }
}

void test()
{
B b = new B();
bar(b);
}
void bar(A a)
{
a.foo(1); // 调用 A.foo(int)
B b = new B();
b.foo(1); // 调用 A.foo(int)
}


 2.014
If such an AliasDeclaration is not used, the derived class's functions completely override all the functions of the same name in the base class, even if the types of the parameters in the base class functions are different. If, through implicit conversions to the base class, those other functions do get called, an std.HiddenFuncError exception is raised:

import std.hiddenfunc;

class A
{
void set(long i) { }
void set(int i) { }
}
class B : A
{
void set(long i) { }
}

void foo(A a)
{ int i;
try
{
a.set(3); // error, throws runtime exception since
// A.set(int) should not be available from B
}
catch (HiddenFuncError o)
{
i = 1;
}
assert(i == 1);
}

void main()
{
foo(new B);
}

If an HiddenFuncError exception is thrown in your program, the use of overloads and overrides needs to be reexamined in the relevant classes.



函数参数的默认值不会被继承的:
class A
{
void foo(int x = 5) { ... }
}

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

class C : B
{
void foo(int x) { ... }
}

void test()
{
A a = new A();
a.foo(); // 调用 A.foo(5)
B b = new B();
b.foo(); // 调用 B.foo(7)
C c = new C();
c.foo(); // 错误,C.foo 需要一个参数
}


3 内联函数(Inline Functions)

D 中没有 inline 关键字。编译器决定是否将一个函数内联,就像 register 关键字不再同编译器是否将变量存在 寄存器中 相关一样。(也没有 register 关键字。)


4 函数重载(Function Overloading)

在 C++ 中,函数重载有很多复杂的级别,一些被定义为“更好的”匹配。如果代码编写者
利用函数重载选择时更为细微的行为,代码就会变得难以维护。不仅 C++ 专家很难弄明白
为什么选择这个函数而不选择哪个,不同的 C++ 编译器也可能会采用不同的方式实现这个
充满技巧的特征,这造成了微妙而灾难性的结果。

在 D 中,函数重载很简单。允许精确匹配,允许包含隐式转换的匹配,除此之外就不匹
配。

如果有多于一个匹配,就是错误。

非 D 链接的函数不允许重载。

上一节别改成如下的:2.014

Function Overloading

Functions are overloaded based on how well the arguments to a function can match up with the parameters. The function with the best match is selected. The levels of matching are:

no match
match with implicit conversions
match with conversion to const
exact match

Each argument (including any this pointer) is compared against the function's corresponding parameter, to determine the match level for that argument. The match level for a function is the worst match level of each of its arguments.

If two or more functions have the same match level, then partial ordering is used to try to find the best match. Partial ordering finds the most specialized function. If neither function is more specialized than the other, then it is an ambiguity error. Partial ordering is determined for functions f() and g() by taking the parameter types of f(), constructing a list of arguments by taking the default values of those types, and attempting to match them against g(). If it succeeds, then g() is at least as specialized as f(). For example:

class A { }
class B : A { }
class C : B { }
void foo(A);
void foo(B);

void test()
{
C c;
/* Both foo(A) and foo(B) match with implicit conversion rules.
* Applying partial ordering rules,
* foo(B) cannot be called with an A, and foo(A) can be called
* with a B. Therefore, foo(B) is more specialized, and is selected.
*/
foo(c); // calls foo(B)
}

A function with a variadic argument is considered less specialized than a function without.

Functions defined with non-D linkage cannot be overloaded. because the name mangling does not take the parameter types into account.

Overload Sets
Functions declared at the same scope overload against each other, and are called an Overload Set. A typical example of an overload set are functions defined at module level:

module A;
void foo() { }
void foo(long i) { }
A.foo() and A.foo(long) form an overload set. A different module can also define functions with the same name:

module B;
class C { }
void foo(C) { }
void foo(int i) { }
and A and B can be imported by a third module, C. Both overload sets, the A.foo overload set and the B.foo overload set, are found. An instance of foo is selected based on it matching in exactly one overload set:

import A;
import B;

void bar(C c)
{
foo(); // calls A.foo()
foo(1L); // calls A.foo(long)
foo(c); // calls B.foo(C)
foo(1,2); // error, does not match any foo
foo(1); // error, matches A.foo(long) and B.foo(int)
A.foo(1); // calls A.foo(long)
}

Even though B.foo(int) is a better match than A.foo(long) for foo(1), it is an error because the two matches are in different overload sets.

Overload sets can be merged with an alias declaration:

import A;
import B;

alias A.foo foo;
alias B.foo foo;

void bar(C c)
{
foo(); // calls A.foo()
foo(1L); // calls A.foo(long)
foo(c); // calls B.foo(C)
foo(1,2); // error, does not match any foo
foo(1); // calls B.foo(int)
A.foo(1); // calls A.foo(long)
}



4.1 函数形式参数(Function Parameters)

这些参数可以是 in、out、ref 或者 lazy。默认是 in;其它的参数工作起来跟存储类别
(storage class)一样。

例如:
int foo(int x, out int y, ref int z, int q);

x 为 in,y 为 out,z 为 ref,而 q 为 in。

out 已经很少见了,而 ref 则更少见,因此如果使用这些特性就需要使用关键字,而将 in
作为默认值。

上面这段被改成:2.014

Parameter storage classes are in, out, ref, lazy, final, const, invariant, or scope.

For example:
int foo(in int x, out int y, ref int z, int q);

x is in, y is out, z is ref, and q is none.

The in storage class is equivalent to const scope.

If no storage class is specified, the parameter becomes a mutable copy of its argument.


• 函数声明清楚的表示了哪些是函数的输入,哪些是函数的输出。
• 不再需要单独使用一种叫 IDL 语言。
• 可以给编译器提供更多的信息,从而可以提供更好的错误检查并生成更好的代码。

out 参数被设为对应类型的默认值。

例如:
void foo(out int x)
{
// 在 foo() 的开始,x 被设置为 0
}
int a = 3;
foo(a);
// a 现在为 0

void abc(out int x)
{
x = 2;
}

int y = 3;
abc(y);
// y 现在为 2

void def(ref int x)
{
x += 1;
}

int z = 3;
def(z);
// z 现在为 4

对于通过引用传递的动态数组和对象参数,in/out/ref 只会作用到 该引用上, 而不会是那些内容里。

懒式参数被求值的时间不是以函数被调用时,而是参数在函数内被求值时进行的。因此,懒
式参数会被计算 0 次或多次。

懒式参数不能是一个左值。

void dotimes(int n, lazy void exp)
{
while (n--)
exp();
}

void test()
{ int x;
dotimes(3, writefln(x++));
}

控制台输出:
01
2

void 类型的懒式参数不能接受一个任何类型的参数。


5 参数可变型函数

那些带有可变数目参数的函数就叫做参数可变型(variadic)函数。

一个参数可变型函数可以有下面三种形式:

1. C-风格的参数可变型函数
2. 带有类型信息的参数可变型函数
3. 类型安全的参数可变型函数


5.1 C-风格的参数可变型函数

C-风格的参数可变型函数被声明时在所必需的函数参数里带有“...”参数。它有一个非D连
接属性,如 extern (C):

extern (C) int foo(int x, int y, ...);
foo(3, 4); // 正确
foo(3, 4, 6.8); // 正确,参数可变型函数
foo(2); // 错误,y 是一个必需的参数

必须至少声明一个固定型参数。

extern (C) int def(...); // 错误,必须至少有一个参数

C-风格的参数可变型函数符合 C 对于参数可变型函数的调用协定,而且对于调用像
printf 那样的 C 库函数最有用。这些参数可变型函数的实现会声明一个特殊的局部变
量:_argptr,此变量是一个指向可变参数里的第一个的 void* 型指针。

要访问这些参数,_argptr 就必须被转换成一所期望参数类型的指针:

foo(3, 4, 5); // 第一个可变型参数是 5
int foo(int x, int y, ...)
{ int z;
z = *cast(int*)_argptr; // z 被设置为 5
}

为了避开不同 CPU 结构上的各种奇特的堆栈分布带来的麻烦,[color=red]请使用 std.c.stdarg 访问那些可变(variadic)参数:[/color]

import std.c.stdarg;


5.2 D-风格的参数可变型函数

带有参数和类型信息的参数可变型函数声明方式:在必需的函数参数之后带上参数“...”。

它有 D 连接属性,因此不需要声明任何非可变型参数:

int abc(char c, ...); // 一个必需的参数: c
int def(...); // 正确

这些参数可变型函数会声明一个特殊的局部变量:_argptr,此变量是一个指向第一个可变
参数的 void* 型指针。要访问这些参数,_argptr 就必须被转换成一所期望参数类型的指
针:

foo(3, 4, 5); // 第一个可变型参数是 5
int foo(int x, int y, ...)
{ int z;
z = *cast(int*)_argptr; // z 被设置为 5
}

一个名叫 _arguments 而类型为 TypeInfo[] 的额外的隐藏参数会被传递给该函
数。_arguments 会给出参数的数目以及每一个的类型,这样可以创建类型安全的 variadic 函数。

import std.stdio;

class Foo { int x = 3; }

class Bar { long y = 4; }

void printargs(int x, ...)
{
writefln("%d arguments", _arguments.length);
for (int i = 0; i < _arguments.length; i++)
{ _arguments[i].print();

if (_arguments[i] == typeid(int))
{
int j = *cast(int *)_argptr;
_argptr += int.sizeof;
writefln("\t%d", j);
}

else if (_arguments[i] == typeid(long))
{
long j = *cast(long *)_argptr;
_argptr += long.sizeof;
writefln("\t%d", j);
}

else if (_arguments[i] == typeid(double))
{
double d = *cast(double *)_argptr;
_argptr += double.sizeof;
writefln("\t%g", d);
}

else if (_arguments[i] == typeid(Foo))
{
Foo f = *cast(Foo*)_argptr;
_argptr += Foo.sizeof;
writefln("\t%X", f);
}

else if (_arguments[i] == typeid(Bar))
{
Bar b = *cast(Bar*)_argptr;
_argptr += Bar.sizeof;
writefln("\t%X", b);
}

else
assert(0);
}
}

void main()
{
Foo f = new Foo();
Bar b = new Bar();
writefln("%X", f);
printargs(1, 2, 3L, 4.5, f, b);
}

其输出为:

00870FE0
5 arguments
int
2
long
3
double
4.5
Foo
00870FE0
Bar
00870FD0

为了避开不同 CPU 结构上的各种奇特的堆栈分布带来的麻烦,[color=red]请使用 std.stdarg 访问那些可变(variadic)参数:[/color]

import std.stdio;
import std.stdarg;

void foo(int x, ...)
{
writefln("%d arguments", _arguments.length);
for (int i = 0; i < _arguments.length; i++)
{ _arguments[i].print();

if (_arguments[i] == typeid(int))
{
int j = va_arg!(int)(_argptr);
writefln("\t%d", j);
}

else if (_arguments[i] == typeid(long))
{
long j = va_arg!(long)(_argptr);
writefln("\t%d", j);
}

else if (_arguments[i] == typeid(double))
{
double d = va_arg!(double)(_argptr);
writefln("\t%g", d);
}

else if (_arguments[i] == typeid(FOO))
{
FOO f = va_arg!(FOO)(_argptr);
writefln("\t%X", f);
}

else
assert(0);
}
}


5.3 类型安全的参数可变型函数

使用类型安全的参数可变型函数的情形是:参数的可变部分被用于创建一个数组或一个类对
象。

对于数组:
int test()
{
return sum(1, 2, 3) + sum(); // 返回 6+0
}

int func()
{
int[3] ii = [4, 5, 6];
return sum(ii); // 返回 15
}

int sum(int[] ar ...)
{
int s;
foreach (int x; ar)
s += x;
return s;
}

对于静态数组:
int test()
{
return sum(2, 3); // 错误,对于数组需要 3 个值
return sum(1, 2, 3); // 返回 6
}

int func()
{
int[3] ii = [4, 5, 6];
int[] jj = ii;
return sum(ii); // 返回 15
return sum(jj); // 错误,类型不匹配
}

int sum(int[3] ar ...)
{
int s;
foreach (int x; ar)
s += x;
return s;
}

对于类对象:
class Foo
{
int x;
char[] s;
this(int x, char[] s)
{
this.x = x;
this.s = s;
}
}

void test(int x, Foo f ...);
...
Foo g = new Foo(3, "abc");
test(1, g); // 正确,因为 g 是 Foo 的实例
test(1, 4, "def"); // 正确
test(1, 5); // 错误,不能匹配 Foo 的构造函数

实现可能在堆栈上构造对象或数组实例。因此,在参数可变型返回之后 引用该实例 就会出现
一个错误:
Foo test(Foo f ...)
{
return f; // 错误,在返回之后 f 实例内容无效
}

int[] test(int[] a ...)
{
return a; // 错误,在返回之后数组内容无效
return a[0..1]; // 错误,在返回之后数组内容无效
return a.dup; // 正确,因为做了复本
}

对于其它类型,参数会自己建立,就像下面的:
int test(int i ...)
{
return i;
}
...
test(3); // 返回 3
test(3, 4); // 错误,太多参数
int[] x;
test(x); // 错误,类型不匹配


5.4 懒式参数可变型函数

如果可变型参数是一个不带参数的委托数组:

void foo(int delegate()[] dgs ...);

那么跟该委托的类型不匹配的每一个参数会被转换成一个委托。

int delegate() dg;
foo(1, 3+x, dg, cast(int delegate())null);

同等于:
foo( { return 1; }, { return 3+x; }, dg, null );


6 局部变量

如果使用一个未被赋值过的局部变量,会被视为错误。编译器的实现未必总能够检测到这些
情况。其他语言的编译器有时会为此发出警告,但是因为这种情况几乎总是意味着存在错
漏,所以应该把它处理为错误。

如果声明一个变量但却从未使用,会被视为错误。死变量,如同过时的死代码一样,都会使
维护者迷惑。

如果声明的一个变量掩盖了统一函数中的另一个变量,会被视为错误:

void func(int x)
{ int x; 错误,掩盖了前面定义的 x
double y;
...
{ char y; 错误,掩盖了前面定义的 y
int z;
}
{ wchar z; 合法,上一个 z 超出了作用域
}
}

因为这种情况看起来不合理,在实践中出现这种情况时不是一个错漏至少看起来也像一个错
漏。

如果返回一个局部变量的地址或引用,会被视为错误。

如果局部变量同标签同名,会被视为错误。


[/size]

。。。。。。。。。。。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值