发新话题
打印

17 D语言 函数 Functions

17 D语言 函数 Functions

知识若不分享 实在没有意义 http://www.d-programming-language-china.org 20070423

点击下面网址查看原文:
http://www.d-programming-language-china.org

by: uFramer D语言论坛 http://www.d-programming-language-china.org
from: http://www.digitalmars.com/d/function.html
version: 基于D 1.013 (Apr 19, 2007)

QUOTE:
FunctionBody:
    BlockStatement
    BodyStatement
    InStatement BodyStatement
    OutStatement BodyStatement
    InStatement OutStatement BodyStatement
    OutStatement InStatement BodyStatement

InStatement:
    in BlockStatement

OutStatement:
    out BlockStatement
    out ( Identifier ) BlockStatement

BodyStatement:
    body BlockStatement

Virtual Functions虚函数

Virtual functions are functions that are called indirectly through a function pointer table, called a vtbl[], rather than directly.
所有的非静态非私有非模板成员函数都是虚函数。这听起来也许低效,但是因为D编译器在生成代码时知道所有的类层次结构,所有未被重载的函数可以被优化为非虚函数。事实上,因为 C++ 程序员倾向于“在不确定时,声明它为虚函数”,D 采用的方法“声明为虚函数除非我们能够证明它可以是非虚函数”造成的结果是产生更多更直接的函数调用。由重载非虚函数造成的 bug 也减少了。
拥有非D链接的函数不会是虚函数,因此也不能被重载。

标记为 final 的函数不可以在派生类中重载,除非它们也是 private 。例如:

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

class B : A
{
    int def() { ... }    // ok,重载 A.def
    int foo() { ... }    // 错误,A.foo 是 final 的
    int bar() { ... }    // ok,A.bar 是 final private 的,但不是 virtual 的
    int abc() { ... }    // ok,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);
}

支持 协变返回类型 ,这意味着派生类的重载函数可以返回从被重载的函数的返回类型派生的类型:

class A { }
class B : A { }

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

class Bar : Foo
{
    B test() { return null; }    // 重写了 test(),并且同 Foo.test() 具有协变返回类型
}

Function Inheritance and Overriding函数继承和重载

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

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();    // 调用 B.foo(int)
}

但是,在进行重载解析的时候,不会考虑基类中的函数:

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

class B : A
{
    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);        // 调用 B.foo(long),因为不会考虑 A.foo(int)
}

考虑重载解析过程中的基类函数,使用 别名声明 :( 本文出处: http://www.d-programming-language-china.org )

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)
}

函数参数的默认值不会被继承:

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 需要一个参数
}

Inline Functions内联函数

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

Function Overloading函数重载

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

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

Function Parameters函数参数

参数是 in、out,ref 或 lazy 的。in 是默认值;其他参数工作起来像存储类。例如:( 本文出处: http://www.d-programming-language-china.org )

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

x 为 in、y 为 out、z 为 ref ,而 q 为 in 。
out 已经很少见了,ref 更少见,因此如果使用这两种特性就需要使用关键字,而将 in 作为默认值。它们出现在 D 中是因为:

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

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

void foo(out int x)
{
    // x is set to 0 at start of foo()
}

int a = 3;
foo(a);
// a is now 0


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

int y = 3;
abc(y);
// y is now 2


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

int z = 3;
def(z);
// z is now 4

动态数组作为对象参数,按引用传递,in/out/ref作用到引用而不是内容。

Lazy arguments are evaluated not when the function is called, but when the parameter is evaluated within the function. Hence, a lazy argument can be executed 0 or more times. A lazy parameter cannot be an lvalue.( 本文出处: http://www.d-programming-language-china.org )

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

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

prints to the console:

QUOTE:
0
1
2

A lazy parameter of type void can accept an argument of any type.

Variadic Functions

Functions taking a variable number of arguments are called variadic functions. A variadic function can take one of three forms:

C-style variadic functions
Variadic functions with type info
Typesafe variadic functions

1 C-style Variadic Functions

C风格的variadic函数用必需参数后面加‘...’表示,它是不是D连接的,就象extern(C):( 本文出处: http://www.d-programming-language-china.org )

extern (C) int foo(int x, int y, ...);

foo(3, 4);    // ok
foo(3, 4, 6.8);    // ok, one variadic argument
foo(2);        // error, y is a required argument

声明时必须带有至少一个非可变参数。

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

C-style variadic functions match the C calling convention for variadic functions, and is most useful for calling C library functions like printf. variadic函数实现时具有了一个特殊的局部变量,_argptr ,它是指向第一个可变参数的 void* 类型的指针。如果要访问这些参数,_argptr 必须被转换为指向所希望的参数类型的指针:

foo(3, 4, 5);    // first variadic argument is 5

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

    z = *cast(int*)_argptr;    // z is set to 5
}

为了不受不同CPU结构栈布局的影响,使用std.c.stdarg访问variadic参数:

import std.c.stdarg;

2 D-style Variadic Functions

Variadic functions with argument and type info are declared as taking a parameter of ... after the required function parameters. It has D linkage, and need not have any non-variadic parameters declared:

int abc(char c, ...);    // one required parameter: c
int def(...);        // ok

These variadic functions have a special local variable declared for them, _argptr, which is a void* pointer to the first of the variadic arguments. To access the arguments, _argptr must be cast to a pointer to the expected argument type:( 本文出处: http://www.d-programming-language-china.org )

foo(3, 4, 5);    // first variadic argument is 5

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

    z = *cast(int*)_argptr;    // z is set to 5
}

对于采用 D 链接的可变函数,有一个隐含的名为 _arguments 的参数,它的类型为 TypeInfo[] 。_arguments 给函数提供了参数的个数,也提供了每个参数的类型,这样就可以编写类型安全的可变函数。

class Foo { int x = 3; }
class Bar { long y = 4; }

void printargs(int x, ...)
{
    printf("%d arguments\n", _arguments.length);
    for (int i = 0; i < _arguments.length; i++)
    {    _arguments.print();

    if (_arguments == typeid(int))
    {
        int j = *cast(int *)_argptr;
        _argptr += int.sizeof;
        printf("\t%d\n", j);
    }
    else if (_arguments == typeid(long))
    {
        long j = *cast(long *)_argptr;
        _argptr += long.sizeof;
        printf("\t%lld\n", j);
    }
    else if (_arguments == typeid(double))
    {
        double d = *cast(double *)_argptr;
        _argptr += double.sizeof;
        printf("\t%g\n", d);
    }
    else if (_arguments == typeid(Foo))
    {
        Foo f = *cast(Foo*)_argptr;
        _argptr += Foo.sizeof;
        printf("\t%p\n", f);
    }
    else if (_arguments == typeid(Bar))
    {
        Bar b = *cast(Bar*)_argptr;
        _argptr += Bar.sizeof;
        printf("\t%p\n", b);
    }
    else
        assert(0);
    }
}

void main()
{
    Foo f = new Foo();
    Bar b = new Bar();

    printf("%p\n", f);
    printargs(1, 2, 3L, 4.5, f, b);
}

将打印出:

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

为了避开不同 CPU 结构上的各种奇特的堆栈分布带来的麻烦,使用 std.stdarg 访问可变参数:

import std.stdarg;

void foo(int x, ...)
{
    printf("%d arguments\n", _arguments.length);
    for (int i = 0; i < _arguments.length; i++)
    {    _arguments.print();

    if (_arguments == typeid(int))
    {
        int j = va_arg!(int)(_argptr);
        printf("\t%d\n", j);
    }
    else if (_arguments == typeid(long))
    {
        long j = va_arg!(long)(_argptr);
        printf("\t%lld\n", j);
    }
    else if (_arguments == typeid(double))
    {
        double d = va_arg!(double)(_argptr);
        printf("\t%g\n", d);
    }
    else if (_arguments == typeid(FOO))
    {
        FOO f = va_arg!(FOO)(_argptr);
        printf("\t%p\n", f);
    }
    else
        assert(0);
    }
}

3 Typesafe Variadic Functions

Typesafe variadic functions are used when the variable argument portion of the arguments are used to construct an array or class object.
For arrays:

int test()
{
    return sum(1, 2, 3) + sum(); // returns 6+0
}

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

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

For static arrays:( 本文出处: http://www.d-programming-language-china.org )

int test()
{
    return sum(2, 3);    // error, need 3 values for array
    return sum(1, 2, 3); // returns 6
}

int func()
{
    int[3] ii = [4, 5, 6];
    int[] jj = ii;
    return sum(ii);        // returns 15
    return sum(jj);        // error, type mismatch
}

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

For class objects:

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);        // ok, since g is an instance of Foo
test(1, 4, "def");    // ok
test(1, 5);        // error, no matching constructor for Foo

An implementation may construct the object or array instance on the stack. Therefore, it is an error to refer to that instance after the variadic function has returned:

Foo test(Foo f ...)
{
    return f;    // error, f instance contents invalid after return
}

int[] test(int[] a ...)
{
    return a;        // error, array contents invalid after return
    return a[0..1];    // error, array contents invalid after return
    return a.dup;    // ok, since copy is made
}

For other types, the argument is built with itself, as in:

int test(int i ...)
{
    return i;
}

...
test(3);    // returns 3
test(3, 4);    // error, too many arguments
int[] x;
test(x);    // error, type mismatch

4 Lazy Variadic Functions

If the variadic parameter is an array of delegates with no parameters:

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

Then each of the arguments whose type does not match that of the delegate is converted to a delegate.( 本文出处: http://www.d-programming-language-china.org )

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

is the same as:

foo( { return 1; }, { return 3+x; }, dg, null );

Local Variables局部变量

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

如果声明的一个变量掩盖了同一函数中的另一个变量,会被视为错误:( 本文出处: http://www.d-programming-language-china.org )

void func(int x)
{    int x;        // 错误,掩盖了前面定义的 x
     double y;
     ...
     {    char y;        // 错误,掩盖了前面定义的 y
            int z;
     }
     {    wchar z;        // 合法,前面的 z 已经脱离作用域了        }
}

因为这种情况看起来不合理,在实践中出现这种情况时是一个 bug,或者至少看起来像一个 bug 。
如果返回一个局部变量的地址或引用,会被视为错误。(译注:有多少 C++ 书曾告诫你不要这么做?如果没有,你可以扔掉那本书了。在 D 中,你不再需要担心了,你所损失的,只是一点极为低级的能力。)

如果局部变量同标签同名,会被视为错误。( 本文出处: http://www.d-programming-language-china.org )

Nested Functions嵌套函数

函数可以嵌套在其他函数中:

int bar(int a)
{
    int foo(int b)
    {
        int abc() { return 1; }
        return b + abc();
    }
    return foo(a);
}

void test()
{
    int i = bar(3);    // i 被赋值为 4
}

嵌套函数只能访问到作用域内的名字:

void foo()
{
    void A()
    {
     B();    // error, B() is forward referenced
     C();    // error, C undefined
    }
    void B()
    {
        A();    // ok, in scope
        void C()
        {
            void D()
            {
                A();    // ok
                B();    // ok
                C();    // ok
                D();    // ok
            }
        }
    }
    A(); // ok
    B(); // ok
    C(); // error, C undefined
}

and:

int bar(int a)
{
    int foo(int b) { return b + 1; }
    int abc(int b) { return foo(b); }    // ok
    return foo(a);
}

void test()
{
    int i = bar(3);    // ok
    int j = bar.foo(3);    // error, bar.foo not visible
}

嵌套函数可以访问它外围的函数的变量和其他的符号。在这里,“访问”意味着既可以读,也可以写。

int bar(int a)
{    int c = 3;

    int foo(int b)
    {
        b += c;        // 4 被加到 b 上
        c++;        // bar.c 现在是 5
        return b + c;    // 返回 12
    }
    c = 4;
    int i = foo(a);    // i 被设为 12
    return i + c;        // 返回 17
}

void test()
{
    int i = bar(3);    // i 被赋值为 17
}

这种访问能力能够跨越多重嵌套:( 本文出处: http://www.d-programming-language-china.org )

int bar(int a)
{    int c = 3;

    int foo(int b)
    {
        int abc()
        {
            return c;    // 访问 bar.c
        }
    return b + c + abc();
    }
    return foo(3);
}

静态嵌套函数不能访问外围函数的任何堆栈变量,但能访问静态变量。这种行为同静态成员函数类似。

int bar(int a)
{    int c;
    static int d;

    static int foo(int b)
    {
        b = d;        // ok
        b = c;        // 错误,foo() 不能访问 bar() 的堆栈帧
        return b + 1;
    }
    return foo(a);
}

函数可以嵌套在成员函数内:

struct Foo
{    int a;

    int bar()
    {    int c;

        int foo()
        {
            return c + a;
        }
    return 0;
    }
}

嵌套结构和嵌套类的成员函数不能访问外围函数的堆栈变量,但是能访问其他的符号:

void test()
{    int j;
    static int s;

    struct Foo
    {    int a;

    int bar()
    {    int c = s;        // ok,s 是静态的
        int d = j;        // 错误,不能访问 test() 的堆栈帧

        int foo()
        {
            int e = s;    // ok,s 是静态的
            int f = j;    // 错误,不能访问 test() 的堆栈帧

            return c + a;    // ok,bar() 的堆栈帧是可访问的
                // 通过指向 Foo.bar() 的 this 指针
                // 可以访问 Foo 的成员
        }
    }
    }
}

嵌套函数总是使用 D 函数链接类型。
同模块级的声明不同,函数作用域的声明会按照声明的顺序处理。这意味着连个嵌套函数不能互相调用:

void test()
{
    void foo() { bar(); }    // 错误,bar 未定义
    void bar() { foo(); }    // ok
}

解决的方法是使用委托:( 本文出处: http://www.d-programming-language-china.org )

void test()
{
    void delegate() fp;
    void foo() { fp(); }
    void bar() { foo(); }
    fp = &bar;
}

未来的方向:这个限制可能会被删除。

1 Delegates, Function Pointers, and Dynamic Closures委托、函数指针和动态闭包

函数指针可以指向一个静态嵌套函数:

int function() fp;

void test()
{    static int a = 7;
    static int foo() { return a + 3; }

    fp = &foo;
}

void bar()
{
    test();
    int i = fp();        // i 被设为 10
}

委托可以用非静态嵌套函数赋值:

int delegate() dg;

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

    dg = &foo;
    int i = dg();        // i 被设为 10
}

但是,一旦声明堆栈变量的函数退出了,堆栈变量就不再有效;同样的,指向堆栈变量的指针也不再有效:

int* bar()
{    int b;
    test();
    int i = dg();         // 错误,test.a 不再存在
    return &b;         // 错误,bar.b 在 bar() 退出后不再有效
}

非静态的嵌套函数的委托包括两块数据:指向外围函数堆栈帧的指针(叫做 帧指针 )和函数的地址。与此类似,结构/类的非静态的成员函数委托由 this 指针和成员函数的地址组成。这两种形式的委托可以互相转换,实际上它们具有相同的类型:( 本文出处: http://www.d-programming-language-china.org )

struct Foo
{    int a = 7;
    int bar() { return a; }
}

int foo(int delegate() dg)
{
    return dg() + 1;
}

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

    i = foo(&abc);    // i is set to 28
    i = foo(&f.bar);    // i is set to 8
}

环境和函数的结合被称作 动态闭包 。

The .ptr property of a delegate will return the frame pointer value as a void*.

The .funcptr property of a delegate will return the function pointer value as a function type.

未来的方向:函数指针和委托可能会合并到一个统一的语法中并且可以互相代替。

2 Anonymous Functions and Anonymous Delegates匿名函数和匿名委托

参见 函数文字量 。
http://www.digitalmars.com/d/expression.html#FunctionLiteral( 本文出处: http://www.d-programming-language-china.org )

main() Function

For console programs, main() serves as the entry point. It gets called after all the module initializers are run, and after any unittests are run. After it returns, all the module destructors are run. main() must be declared using one of the following forms:

void main() { ... }
void main(char[][] args) { ... }
int main() { ... }
int main(char[][] args) { ... }

Compile Time Function Execution

A subset of functions can be executed at compile time. This is useful when constant folding algorithms need to include recursion and looping. In order to be executed at compile time, a function must meet the following criteria:

function arguments must all be:
integer literals
floating point literals
character literals
string literals
array literals where the members are all items in this list
const variables initialized with a member of this list

function parameters may not be variadic, or lazy

the function may not be nested or synchronized( 本文出处: http://www.d-programming-language-china.org )

the function may not be a non-static member, i.e. it may not have a this pointer

expressions in the function may not:
throw exceptions
use pointers, delegates, non-const arrays, structs, unions or classes
reference any global state or variables
reference any local static variables
new or delete
call any function that is not executable at compile time

the following statement types are not allowed:
synchronized statements
throw statements
with statements
scope statements
try-catch-finally statements
labelled break and continue statements
as a special case, the .dup property can be executed at compile time

In order to be executed at compile time, the function must appear in a context where it must be so executed, for example:

initialization of a static variable
dimension of a static array
argument for a template value parameter( 本文出处: http://www.d-programming-language-china.org )

template eval( A... )
{
    const typeof(A[0]) eval = A[0];
}

int square(int i) { return i * i; }

void foo()
{
    static j = square(3);    // compile time
    writefln(j);
    writefln(square(4));    // run time
    writefln(eval!(square(5))); // compile time
}

Executing functions at compile time can take considerably longer than executing it at run time. If the function goes into an infinite loop, it will hang at compile time (rather than hanging at run time).

Functions executed at compile time can give different results from run time in the following scenarios:

floating point computations may be done at a higher precision than run time
dependency on implementation defined order of evaluation
use of uninitialized variables

These are the same kinds of scenarios where different optimization settings affect the results.

1 String Mixins and Compile Time Function Execution

Any functions that execute at compile time must also be executable at run time. The compile time evaluation of a function does the equivalent of running the function at run time. This means that the semantics of a function cannot depend on compile time values of the function. For example:( 本文出处: http://www.d-programming-language-china.org )

int foo(char[] s)
{
    return mixin(s);
}

const int x = foo("1");

is illegal, because the runtime code for foo() cannot be generated. A function template would be the appropriate method to implement this sort of thing.

( lastupdate:20070426 最新文章请访问http://www.d-programming-language-china.org )

关于一大步成功社区:
yidabu提倡在交流中学习,在分享中提高
收集感兴趣的知识,写下心得,通过网络与别人一起分享
理解一点就实践一步,收获什么就分享什么,成功就是这样一点点一步步累积起来的
网络只是一个工具,只有自己身心提高才是实实在在的。d-programming-language-china.org为大家提供一个学习交流各种知识的平台

TOP

发新话题