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 ATipos 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:
{
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);
}
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 APorém, quando fazer resolução de sobrecarga, as funções na classe base não são consideradas:
{
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)
}
class APara considerar as funções da classe base no processo de resolução de sobrecarga, use uma DeclaraçãoAlias:
{
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)
}
class AO valor padrão de um parâmetro de função não é herdado:
{
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)
}
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:
- A declaração da função torna claro o que entra e sai da função.
- Elimina a necessidade de IDL como uma linguagem separada.
- Provê mais informação para o compilador, permitindo mais checagem de erros e possivelmente melhor geração de código.
- Isso (talvez?) elimina a necessidade de declarações de referência (&).
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:- Funções variádicas estilo C
- Funções variádicas com informação de tipo
- 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, ...);Deve haver no mínimo um argumento não-variádico declarado.
foo(3, 4); // ok
foo(3, 4, 6.8); // ok, um argumento variádico
foo(2); // erro, y é um argumento requerido
extern (C) int def(...); // erro, deve ter ao menos um parâmetroEsse 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 é 5Para proteger contra as vagaridades de layouts de pilha em arquiteturas de CPU diferentes, use std.c.stdarg para acessar os argumentos variádicos:
int foo(int x, int y, ...)
{
int z;
z = *cast(int*)_argptr; // z é ajustado para 5
}
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: cEssas 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:
int def(...); // ok
foo(3, 4, 5); // primeiro argumento variádico é 5Um 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.
int foo(int x, int y, ...)
{
int z;
z = *cast(int*)_argptr; // z é ajustado para 5
}
class FOO { }que imprime:
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);
}
00870FD0Para se protejer contra vagaridades de layouts de pilha em diferentes aarquiteturas de CPU, use std.stdarg para acessar argumentos variádicos:
4 arguments
int
2
long
3
double
4.5
FOO
00870FD0
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()Para arrays estáticos:
{
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;
}
int test()Para objetos de classe:
{
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;
}
class Foo { int x; char[] s; }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:
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
Foo test(Foo f ...)Para outros tipos, o argumento é construído com ele mesmo, como em:
{
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
}
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)Enquanto isso poderia parecer sem reações, na prática sempre que isso é feito isso ou é um bug ou parece com um bug.
{
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
}
}
É 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)Funções aninhadas podem ser acessadas somente se o nome está no escopo.
{
int foo(int b)
{
int abc() { return 1; }
return b + abc();
}
return foo(a);
}
void test()
{
int i = bar(3); // i é atribuído 4
}
void foo()e:
{
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
}
int bar(int a)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 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
}
int bar(int a)Esse aceso pode extender múltiplos níveis de aninhamento:
{
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
}
int bar(int a)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 c = 3;
int foo(int b)
{
int abc()
{
return c; // acessa bar.c
}
return b + c + abc();
}
return foo(3);
}
int bar(int a)Funções podem ser aninhadas dentro de funções membro:
{ 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);
}
struct FooFunçõ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:
{
int a;
int bar()
{
int c;
int foo()
{
return c + a;
}
}
}
void test()Funções aninhadas sempre tem a ligação tipo D.
{
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()
}
}
}
}
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()A solução é usar um delegado:
{
void foo() { bar(); } // erro, bar não definida
void bar() { foo(); } // ok
}
void test()Direções futuras: Essa restrição pode ser removida.
{
void delegate() fp;
void foo() { fp(); }
void bar() { foo(); }
fp = &bar;
}
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;Um delegado pode ser ajustado para uma função aninhada não-estática:
void test()
{
static int a = 7;
static int foo() { return a + 3; }
fp = &foo;
}
void bar()
{
test();
int i = fp(); // i é ajustado 10
}
int delegate() dg;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:
void test()
{
int a = 7;
int foo() { return a + 3; }
dg = &foo;
int i = dg(); // i é ajustado para 10
}
int* 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:
{
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()
}
struct FooEssa combinação de ambiente e função é chamada um fechamento dinâmico.
{
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
}
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) { ... }