www.digitalmars.com
Last update Wed May 31 17:11:57 2006

Funções

CorpoFunção:
InstruçãoBloco
InstruçãoBody
InstruçãoIn InstruçãoBody
InstruçãoOut InstruçãoBody
InstruçãoIn InstruçãoOut InstruçãoBody
InstruçãoOut InstruçãoIn InstruçãoBody
InstruçãoIn:
in InstruçãoBloco
InstruçãoOut:
out InstruçãoBloco
out ( Identificador ) InstruçãoBloco
InstruçãoBody:
body InstruçãoBloco

Funções Virtuais

Todas as funções membro não-estáticas não-privadas são virtuais. Isso pode parecer ineficiênte, mas já que o compilador D conhece toda hierarquia da classe quando gera código, todas as funções que não são anuladas podem ser otimizadas para ser não-virtuais. De fato, já que programadores C++ tendem a "quando em dúvida, faça virtual", o jeito D de "faça virtual a menos que possamos provar que pode ser feito não-virtual" resulra em média de chamadas de função muito mais direta. Também resulta em menos bugs causadis por não declarar uma função virtual que é anulada.

Funções com ligação não-D não podem ser virtuais, e conseqüentemente não podem ser anuladas.

Funções marcadas como final não podem ser anuladas em uma classe derivada, a menos que sejam também private. Por exemplo:

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

class B : A
{
int def() { ... } // ok, anula A.def
int foo() { ... } // erro, A.foo é final
int bar() { ... } // ok, A.bar é final privada, mas não virtual
int abc() { ... } // ok, A.abc não é virtual, B.abc é virtual
}

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

void func()
{
B b = new B();
test(b);
}
Tipos de retorno covariantes são suportados, o que quer dizer que a função anulada em uma classe derivada pode retornar um tipo que é derivado do tipo retornado pela função anulada:
class A { }
class B : A { }

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

class Bar : Foo
{
B test() { return null; } // anula e é covariante com Foo.test()
}

Herança de Função e Anulação

Uma Função em uma classe derivada com o mesmo nome e tipos de parâmetros que uma função em uma classe base anula essa função:
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(); // chama B.foo(int)
}
Porém, quando fazer resolução de sobrecarga, as funções na classe base não são consideradas:
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); // chama B.foo(long), já que A.foo(int) não é considerado
A a = b;
a.foo(1); // chama A.foo(int)
}
Para considerar as funções da classe base no processo de resolução de sobrecarga, use uma DeclaraçãoAlias:
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); // chama A.foo(int)
B b = new B();
b.foo(1); // chama A.foo(int)
}
O valor padrão de um parâmetro de função não é herdado:
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(); // chama A.foo(5)

B b = new B();
b.foo(); // chama B.foo(7)

C c = new C();
c.foo(); // erro, precisa de um argumento para C.foo
}

Funções Inline

Não há uma palavra reservada inline. O compilador toma a decisão se torna uma função inline ou não, analogamente à palavra register não mais sendo relevante para uma decisão do compilador sobre por variáveis no registrador. (Não há uma palavra reservada register também.)

Sobrecarga de Funções

Em C++, há muitos níveis complexos de sobrecarga de funções, com alguns definidos como "melhores" escolhas que outros. Se o projetista de código toma vantagem do comportamento mais sutil de seleção de sobrecarga de funções, o código pode se tornar difícil de manter. Não somente será necessário um expert em C++ para entender porque uma função é selecionada sobre outra, mas diferentes compiladores C++ podem implementar essa astuta característica diferentemente, produzindo sutilmente resultados desastrosos.

Em D, sobrecarga de funções é simples. Combina exatamente, combina com conversões implícitas, ou não combina. Se há mais de uma ecolha, é um erro.

Funções definidas com ligação não-D não podem ser sobrecarregadas.

Parâmetros de Funções

Parâmetros podem ser in, out, ou inout. in é o padrão; out e inout trabalham como classes de armazenamento. Por exemplo:

int foo(int x, out int y, inout int z, int q);
x é in, y é out, z é inout, e q é in.

out é bastante raro, e inout mais raro ainda, para anexar as palavras reservadas à elas e deixar in como o padrão. As razões ára ter elas são:

Parâmetros out são ajustados para o inicializador padrão para o tipo dele. Por exemplo:
void foo(out int bar)
{
}

int bar = 3;
foo(bar);
// bar agora é 0

Funções Variádicas

Funções levando um número de parâmetros variável são chamadas funções variádicas. Uma função variádica pode ter uma de três formas:
  1. Funções variádicas estilo C
  2. Funções variádicas com informação de tipo
  3. Funções variádicas seguras quanto ao tipo

Funções Variádicas Estilo C

Uma função variádica estilo C é declarada tendo um parâmetro ... depois dos parâmetros requeridos. Tem ligação não-D, tal como extern (C):
extern (C) int foo(int x, int y, ...);

foo(3, 4); // ok
foo(3, 4, 6.8); // ok, um argumento variádico
foo(2); // erro, y é um argumento requerido
Deve haver no mínimo um argumento não-variádico declarado.
extern (C) int def(...); // erro, deve ter ao menos um parâmetro
Esse tipo de função seleciona convenção de chamada C para funções variádicas, e é mais útil  para chamar funções de biblioteca C como printf. As implementações dessas funções variádicastem uma variável local especial declarada para elas, _argptr, que é um ponteiro void* para o primeiro dos argumentos variádicos. Para acessar os argumentos, _argptr deve ser moldado para um ponteiro para o tipo de argumento esperado:
foo(3, 4, 5); // primeiro argumento variádico é 5

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

z = *cast(int*)_argptr; // z é ajustado para 5
}
Para proteger contra as vagaridades de layouts de pilha em arquiteturas de CPU diferentes, use std.c.stdarg para acessar os argumentos variádicos:
import std.c.stdarg;

Funções Variádicas com Informação de Tipo

Funções variádicas com informação de tipo e argumentos são declaradas com um parâmetro ... após os parâmetros requeridos pela função. Tem ligação D, e não precesa ter qualquer argumento não-variádico declarado:
int abc(char c, ...); // um parâmetro requerido: c
int def(...); // ok
Essas funções variádicas tem uma variável local especial declarada para elas, _argptr, que é um ponteiro void* para o primeiro dos argumentos variádicos. Para acessar os argumentos, _argptr deve ser moldado para um ponteiro para o tipo de argumento esperado:
foo(3, 4, 5); // primeiro argumento variádico é 5

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

z = *cast(int*)_argptr; // z é ajustado para 5
}
Um argumento oculto adicional com o nome _arguments e tipo TypeInfo[] é passado para a função. _arguments da o número de argumentos e o tipo de cada, possibilitando a criação de funções variádicas seguras quanto ao tipo.
class FOO { }

void foo(int x, ...)
{
printf("%d arguments\n", _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;
printf("\t%d\n", j);
}
else if (_arguments[i] == typeid(long))
{
long j = *cast(long *)_argptr;
_argptr += long.sizeof;
printf("\t%lld\n", j);
}
else if (_arguments[i] == typeid(double))
{
double d = *cast(double *)_argptr;
_argptr += double.sizeof;
printf("\t%g\n", d);
}
else if (_arguments[i] == typeid(FOO))
{
FOO f = *cast(FOO*)_argptr;
_argptr += FOO.sizeof;
printf("\t%p\n", f);
}
else
assert(0);
}
}

void main()
{
FOO f = new FOO();

printf("%p\n", f);
foo(1, 2, 3L, 4.5, f);
}
que imprime:
00870FD0
4 arguments
int
2
long
3
double
4.5
FOO
00870FD0
Para se protejer contra vagaridades de layouts de pilha  em diferentes aarquiteturas de CPU, use std.stdarg para acessar argumentos variádicos:
import std.stdarg;

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

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

Funções Variádicas Seguras quanto ao Tipo

Funções variádicas seguras quanto ao tipo são usadas quando a porção variável do argumento variável dos argumentos é usada para construir objetos de classe ou array.

Para arrays:

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

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

int sum(int[] ar ...)
{
int s;
foreach(int x; ar)
s += x;
return s;
}
Para arrays estáticos:
int test()
{
return sum(2, 3); // erro, precisa de 3 valores para array
return sum(1, 2, 3); // retorna 6
}

int func()
{
static int[3] ii = [4, 5, 6];
int[] jj = ii;
return sum(ii); // retorna 15
return sum(jj); // erro, tipo mal-combinado
}

int sum(int[3] ar ...)
{
int s;
foreach (int x; ar)
s += x;
return s;
}
Para objetos de classe:
class Foo { int x; char[] s; }

void test(int x, Foo f ...);

...

Foo g = new Foo(3, "abc");
test(1, g); // ok, já que g é uma instância de Foo
test(1, 4, "def"); // ok
test(1, 5); // error, não há construtor para Foo
Uma implementação pode construir a instância do objeto ou array na pilha. Conseqüentemente, é um erro se referir à essa instância após a função variádica ter retornado:
Foo test(Foo f ...)
{
return f; // erro, conteúdo da instância f inválido após return
}

int[] test(int[] a ...)
{
return a; // erro, conteúdo do array inválido após return
return a[0..1]; // erro, conteúdo do array inválido após return
return a.dup; // ok, já que cópia é feita
}
Para outros tipos, o argumento é construído com ele mesmo, como em:
int test(int i ...)
{
return i;
}

...
test(3); // retorna 3
test(3, 4); // erro, muitos argumentos
int[] x;
test(x); // erro, tipo mal-combinado

Variáveis Locais

É um erro usar variáveis locais sem primeiro atribuir um valor a elas. A implementação pode nem sempre ser capaz de detectar esses casos. Compiladores de outras linguagens algumas vezes mostram advertências para isso, mas já que é sempre um bug, deveria ser um erro.

É um erro declarar uma variável local que nunca é referida. Variáveis mortas, como código morto anacronistico, é apenas uma fonte de confusão para programadores de manutenção.

É um erro declarar uma variável lcal que oculta outra variável local na mesma função:

void func(int x)
{
int x; // error, hides previous definition of x
double y;
...
{
char y; // error, hides previous definition of y
int z;
}
{
wchar z; // legal, previous z is out of scope
}
}
Enquanto isso poderia parecer sem reações, na prática sempre que isso é feito isso ou é um bug ou parece com um bug.

É um erro retornar o endereço ou uma referÇencia para uma variável local.

É um erro ter uma variável local e um marcador com o mesmo nome.

Funções Aninhadas

Funções podem ser aninhadas dentro de outras funções:

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 é atribuído 4
}
Funções aninhadas podem ser acessadas somente se o nome está no escopo.
void foo()
{
void A()
{
B(); // ok
C(); // erro, C indefinido
}
void B()
{
void C()
{
void D()
{
A(); // ok
B(); // ok
C(); // ok
D(); // ok
}
}
}
A(); // ok
B(); // ok
C(); // erro, C indefinido
}
e:
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); // erro, bar.foo não visível
}
Funções aninhadas tem acesso às variáveis e outros símbolos definidos pela função envolvendo-a lexicamente. Esse acesso inclui a habilidade de ler e escrever elas.
int bar(int a)
{
int c = 3;

int foo(int b)
{
b += c; // 4 é somado à b
c++; // bar.c é agora 5
return b + c; // 12 é retornado
}
c = 4;
int i = foo(a); // i é ajustado para 12
return i + c; // retorna 17
}

void test()
{
int i = bar(3); // i is assigned 17
}
Esse aceso pode extender múltiplos níveis de aninhamento:
int bar(int a)
{
int c = 3;

int foo(int b)
{
int abc()
{
return c; // acessa bar.c
}
return b + c + abc();
}
return foo(3);
}
Funções aninhadas estáticas não podem acessar quaisquer variáveis de pilha de qualquer função envolvendo-as lecicamente, mas podem acessar variáveis estáticas. Isso é análogo a como funções membros estáticas se comportam.
int bar(int a)
{ int c;
static int d;

static int foo(int b)
{
b = d; // ok
b = c; // error, foo() cannot access frame of bar()
return b + 1;
}
return foo(a);
}
Funções podem ser aninhadas dentro de funções membro:
struct Foo
{
int
a;

int bar()
{
int c;

int foo()
{
return c + a;
}
}
}
Funções membro de classes e estruturas aninhadas não tem acesso às variáveis de pilha da função envolvendo-as, mas tem acesso aos outros símbolos:
void test()
{
int
j;
static int s;

struct Foo
{
int a;

int bar()
{
int c = s; // ok, s é estático
int d = j; // erro, nenhum acesso ao quadro de test()

int foo()
{
int e = s; // ok, s é estático
int f = j; // error, nenhum acesso ao quadro de test()
return c + a; // ok, quadro de bar() é acessível,
// então há membros de Foo acessíveis via
// o ponteiro 'this' para Foo.bar()
}
}
}
}
Funções aninhadas sempre tem a ligação tipo D.

Diferente dos níveis de declaração de módulo, declarações dentro do escopo de funções são processadas em ordem. Isso significa que dias funções aninhadas não podem mutuamente chamar cada outra:

void test()
{
void foo() { bar(); } // erro, bar não definida
void bar() { foo(); } // ok
}
A solução é usar um delegado:
void test()
{
void delegate() fp;
void foo() { fp(); }
void bar() { foo(); }
fp = &bar;
}
Direções futuras: Essa restrição pode ser removida.

Delegados, Ponteiros para Funções, e Fechamentos Dinâmicos

Um ponteiro para função pode apontar para uma função aninhada estática:
int function() fp;

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

fp = &foo;
}

void bar()
{
test();
int i = fp(); // i é ajustado 10
}
Um delegado pode ser ajustado para uma função aninhada não-estática:
int delegate() dg;

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

dg = &foo;
int i = dg(); // i é ajustado para 10
}
As variáveis de pilha, porém, não são válidas uma vez que a função declarando-as é deixada, da mesma maneira que ponteiros para variáveis de pilha não são válidos sobre a saída da função:
int* bar()
{
int b;
test();
int i = dg(); // erro, test.a não existe mais
return &b; // error, bar.b não é válido após a saída de bar()
}
Delegados para funções aninhadas não-estáticas contém dois pedaços de dados: o ponteiro para o quadro de pilha da função envolvendo-o lexicamente (chamado frame pointer) e o endereço da função. Isso é análogo aos delegados para membros não-estáticos de estruturas/classes que consistem de um ponteiro this e o endereço da função membro. Ambas as formas de delegado são  permutáveis, e são realmente do mesmo tipo:
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 é ajustado para 28
i = foo(&f.bar); // i pe ajustado para 8
}
Essa combinação de ambiente e função é chamada um fechamento dinâmico.

Direções Futuras: Ponteiros para funções e delegados podem se combinar em uma sintaxe comum e serem permutáveis com o outro.

Funções Anônimas e Delegados Anônimos

Veja Funções Literais.

Função main()

Para programas de console, main() serve como ponto de entrada. Ela é chamada após todos os inicializadores de módulo serem executados, e após quaisquer testes de unidade serem executados. Após ela retornar, todos os destrutores de módulos são executados. main() deve ser declarada usando uma das seguintes formas:
void main() { ... }
void main(char[][] args) { ... }
int main() { ... }
int main(char[][] args) { ... }