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

Arrays

Há quatro tipos de arrays:

int* p; Ponteiros para dados
int[3] s; Arrays estáticos
int[] a; Arrays dinâmicos
int[char[]] x; Arrays associativos

Ponteiros

int* p;
Estes são simples ponteiros para dados, análogos aos ponteiros em C. Ponteiros estão disponíveis para interface com C e para trabalhos de sistema especializados. Não há comprimento associado com eles, então não há nenhum modo para o compilador ou tempo de execução fazer checagem de limites, etc., nele. Usos mais convencionais para ponteiros podem ser substituídos com arrays dinâmicos, parâmetros out e inout, e tipos de referência.

Arrays Estáticos

int[3] s;
Estes são análogos aos arrays em C. Arrays estáticos são distinto por terem um comprimento fixado em tempo de compilação.

O tamanho total de um array estático não pode exceder 16Mb. Um array dinâmico deveria ser usado para tais arrays grandes.

Um array estático com uma dimensão de 0 é permitido, mas nenhum espaço é alocado para ele. Isso é útil como o último membro de uma estrutura de comprimento variável, eu como o caso de uma expansão de gabarito.

Arrays Dinâmicos

int[] a;
Arrays dinâmicos consistem de um comprimento e um ponteiro para os dados do array. Múltiplos arrays dinâmicos podem compartilhar todo ou partes dos dados do array.

Declarações de Arrays

Há dois módos para declarar arrays, prefixa e posfixa. A forma prefixa é o método preferido, especialmente para tipos não-triviais.

Declarações de Arrays Prefixas

Declarações prefixas aparecem antes do identificador sendo declarado e são lidas da direita para esquerda, então:
int[] a; // array dinâmico de ints
int[4][3] b; // array de 3 arrays de 4 ints cada
int[][5] c; // array de 5 arrays dinâmicos de ints.
int*[]*[3] d; // array de 3 ponteiros para arrays dinâmicos
// de ponteiros para ints
int[]* e; // ponteiro para array dinâmico de ints

Declarações de Arrays Posfixas

Declarações posixas aparecem após o identificador sendo declarado e lidos da esquerda para direita. Cada grupo lista declarações equivalentes:
// array dinâmico de ints
int[] a;
int a[];

// array de 3 arrays de 4 ints cada
int[4][3] b;
int[4] b[3];
int b[3][4];

// array de 5 arrays dinâmicos de ints.
int[][5] c;
int[] c[5];
int c[5][];

// array de 3 ponteiros paraarrays dinâmicos de ponteiros para ints
int*[]*[3] d;
int*[]* d[3];
int* (*d[3])[];

// ponteiro para array dinâmico de ints
int[]* e;
int (*e[]);
Razão: A forma posfixa combina com o modo como arrays são declarados em C e C++, e suportarr isso provê uma fácil migração para programadores que usam isso.

Uso

Há dois tipos de largas operações para fazer em um array - afetando o controle do array, e afetando o conteúdo do array. C só tem operadores para afetar o controle. Em D, ambos são acessíveis.

O controle para um array é especificado nomeando o array, como em p, s ou a:

int* p;
int[3] s;
int[] a;

int* q;
int[3] t;
int[] b;

p = q; // p aponta para a mesma coisa que q.
p = s; // p aponta para o primeiro elemento do array s.
p = a; // p aponta para o primeiro elemento do array a.

s = ...; // erro, já que s é compilado em referência
// estática para um array.

a = p; // erro, já que o comprimento do array apontado
// por p é deesconhecido
a = s; // a é inicializado para apontar para o array s
a = b; // a aponta para o mesmo array que b

Fatiar

Fatiar um array significa especificar um subarray dele. Uma fatia de array não copia os dados, é só outra referência para ele. Por exemplo:
int[10] a; // declara array de 10 ints
int[] b;

b = a[1..3]; // a[1..3] é um array de dois elementos consistindo de
// a[1] e a[2]
foo(b[1]); // equivalente à foo(0)
a[2] = 3;
foo(b[1]); // equivalente à foo(3)
O [] é um atalho para uma fatia do array inteiro. Por exemplo, as atribuições para b:
int[10] a;
int[] b;

b = a;
b = a[];
b = a[0 .. a.length];
são todas semânticamente equivalentes.

Fatiar não é só acessível para referir à partes de outros arrays, mas para converter ponteiros emarrays com checagem de limites:

int* p;
int[] b = p[0..8];

Cópia de Array

Quando o operador de fatia aparece como o lvalue de uma expressão de atribuição, significa que o conteúdo do array são o alvo da atribuição ao invés de uma referência para o array. Cópia de array ocorre quando o lvalue é uma fatia, e o rvalue é um array do ou ponteiro para o mesmo tipo.
int[3] s;
int[3] t;

s[] = t; // os 3 elementos de t[3] são copiados em s[3]
s[] = t[]; // os 3 elementos de t[3] são copiados em s[3]
s[1..2] = t[0..1]; // o mesmo que s[1] = t[0]
s[0..2] = t[1..3]; // o mesmo que s[0] = t[1], s[1] = t[2]
s[0..4] = t[0..4]; // erro, só 3 elementos em s
s[0..2] = t; // erro, comprimento diferente para lvalue e rvalue
Sobrepor cópias é um erro:
s[0..2] = s[1..3]; // erro, sobrepôs cópia
s[1..3] = s[0..2]; // erro, sobrepôs cópia
Não permitir sobreposição torna possível otimização de código paralela mais agressiva que possível com a semântica serial de C.

Ajuste de Array

Se o operador de fatia aparece como o lvalue de uma expressão de atribuição, e o tipo do rvalue é o mesmo que o tipo do elemento do lvalue, então o conteúdo do array do lvalue é ajustado para o rvalue.
int[3] s;
int* p;

s[] = 3; // o mesmo que s[0] = 3, s[1] = 3, s[2] = 3
p[0..2] = 3; // o mesmo que p[0] = 3, p[1] = 3

Concatenação de Array

O operador binário ~ é o operador cat. Ele é usado para concatenar arrays:
int[] a;
int[] b;
int[] c;

a = b ~ c; // Cria um array da concatenação dos arrays b e c
Muitas linguagens sobrecarregam o operador + para significar concatenação. Isso conduz confusamente à fazer:
"10" + 3
produzir o número 13 ou a string "103" como resultado? Iso não é óbvio, e os projetistas da linguagem enrolam cuidadosamente escrevendo regras para diferenciar isso - regras que são impementadas incorretamente, negligenciadas, esquecidas, e ignoradas. É muito melhor ter + significando adição, e um operador separado para ser concatenação de array.

Similarmente, o operador ~= significa anexar, como em:

a ~= b; // a se torna a concatenação de a e b
Concatenação sempre cria uma cópia de seus operandos, mesmo se um dos operandos é um array de comprimento 0 , então:
a = b; // a se refere à b
a = b ~ c[0..0]; // a se refere à uma cópia de b

Operações de Arrays

Quando mais de um operador [] aparece em uma expressão, a escala representada por todos deve combinar.
a[1..3] = b[] + 3; // erro, 2 elementos não é o mesmo que 3 elementos

Exemplos:

int[3] abc; // array estático de 3 ints
int[] def = [ 1, 2, 3 ]; // array dinâmico de 3 ints

void dibb(int *array)
{
array[2]; // significa a mesma coisa que *(array + 2)
*(array + 2); // pega segundo elemento
}

void diss(int[] array)
{
array[2]; // ok
*(array + 2); // erro, array não é um ponteiro
}

void ditt(int[3] array)
{
array[2]; // ok
*(array + 2); // erro, array não é um ponteiro
}

Arrays Retangulares

Programadores numéricos FORTRAN experientes sabem que arrays multidimensionais "retangulares" para coisas como operações de matriz são muito mais rápidos que tentar axessá-los via ponteiros para ponteiros resultado de semânticas de "array de ponteiros para array". Por exemplo, a sintaxe D:
double[][] matrix;
declara matrix como um array de ponteiros para arrays. (Arrays dinâmicos são implementados como ponteiros para os dados do array.) Já que arrays podem ter tamanhos variando (sendo dimensionados dinâmicamente), isso é algumas vezes chamado de arrays "jagged". Even worse for optimizing the code, the array rows can sometimes point to each other! Fortunately, D static arrays, while using the same syntax, are implemented as a fixed rectangular layout:
double[3][3] matrix;
declara uma matriz retangular com 3 linhas e 3 colunas, todas continuamente na memória. Em outras linguagens, isso seria chamado de array multidimensional e é declarado como:
double matrix[3,3];

Comprimento do Array

Dentro do [ ] de um array estático ou dinâmico, a variável length é implicitamente declarada e ajustada para o comprimento do array.
int[4] foo;
int[] bar = foo;
int* p = &foo[0];

// Estas expressões são todas equivalentes:
bar[]
bar[0 .. 4]
bar[0 .. length]
bar[0 .. bar.length]

p[0 .. length] // 'length' não definido, já que p não é um array
bar[0]+length // 'length' não definido, fora do escopo de [ ]

bar[length-1] // pega último elemento do array

Propriedades de Arrays

Propriedades de arrays estáticos são:
.sizeof Retorna o comprimento do array multiplicado pelo número de bytes por elemento do array.
.length Retorna o número de elementos no array. Essa é uma quantidade fixa para arrays estáticos.
.ptr Retorna um ponteiro para o primeiro array.
.dup Cria um array dinâmico do mesmo tamanho e copia os conteúdos do array nele.
.reverse Reverte no lugar a ordem dos elementos no array. Retorna o array.
.sort Ordena no lugar a ordem dos elementos no array. Retorna o array.

Propriedades de arrays dinâmicos são:

.sizeof Retorna o tamanho da referência do array dinâmico, que é 8 em máquinas de 32 bits.
.length Pega/ajusta o número de elementos no array.
.ptr Retorna um ponteiro para o primeiro elemento do array.
.dup Cria um array dinâmico do mesmo tamanho e copia os conteúdos do array nele.
.reverse Reverte no lugar a ordem dos elementos no array. Retorna o array.
.sort Ordena no lugar a ordem dos elementos no array. Retorna o array.

Exemplos:

p.length // erro, comprimento não conhecido para ponteiros
s.length // contante de tempo de compilação 3
a.length // valor de tempo de execução

p.dup // erro, comprimento não conhecido
s.dup // cria um array de 3 elementos, copia
// elementos de s nele
a.dup // cria um array de a.length elementos, copia
// elementos de a nele

Ajustando o Comprimento de Array Dinâmico

A propriedade .length de um array dinâmico pode ser ajustada como o lvalue de um operador =:
array.length = 7;
Isso faz o array ser realocado no lugar, e o conteúdo existente copiado sobre o novo array. Se o comprimento do novo array é menor, só o bastante é copiado para preencher o novo array. Se o comprimento do novo array é maior, o restante é preenchido com o inicializador padrão.

Para maximizar eficiência, o tempo de execução sempre tenta redimensionar o array no lugar para evitar cópias extras. Isso sempre fará uma cópia se o novo tamanho é maior e o array não foi alocado via o operador new ou operação de redimensionamento prévia.

Isso significa que se há uma fatia de array imediatamente seguindo o array sendo redimensionado, o array redimensionado poderia sobrepôr a fatia, isto é:

char[] a = new char[20];
char[] b = a[0..10];
char[] c = a[10..20];

b.length = 15; // sempre redimensionado no,lugar pois é fatiado
// de a[] que tem bastante memória para 15 chars
b[11] = 'x'; // a[15] e c[5] também são afetados

a.length = 1;
a.length = 20; // nenhuma mudança líquida na disposição de memória

c.length = 12; // sempre faz uma cópia pois c[] não está no
// início de um bloco de alocação do gc
c[5] = 'y'; // não afeta conteúdo de a[] ou b[]

a.length = 25; // pode ou não fazer cópia
a[3] = 'z'; // pode ou não afetar b[3] que ainda sobrepões
// o velho a[3]
Para garantir comportamento de cópia, use a propriedade .dup para assegurar um array original que pode ser redimensionado.

Esses assuntos tambés se aplicam à concatenar arrays com os operadores ~ e ~=.

Redimensionar um array dinâmico é uma operação relativamente cara. Então, enquano o seguinte método de preencher um array:

int[] array;
while (1)
{ c = getinput();
if (!c)
break;
array.length = array.length + 1;
array[array.length - 1] = c;
}
trabalha, é ineficiênte. Uma aproximação mais prática seria minimizar o número de redimensionamentos:
int[] array;
array.length = 100; // suposição
for (i = 0; 1; i++)
{ c = getinput();
if (!c)
break;
if (i == array.length)
array.length = array.length * 2;
array[i] = c;
}
array.length = i;
Escolher uma boa suposição inicial é uma arte, mas você normalmente pode escolher um valor cobrindo 99% dos casos. Por exemplo, quando recolher entrada do usuário do console - é inprovável que seja maior que 80.

Checagem de Limites do Array

É um erro indexar um array com um índice que é menor que 0 ou maior que ou igual ao comprimento do array. Se um índice está fora dos limites, uma exceção ArrayBoundsError é lançada se detectado em tempo de execução, e um erro se detextado em tempo de compilação. Um programa não pode confiar no acontecimento de checagem de limites do array, por exemplo, o seguinte programa está incorreto:
try
{
for (i = 0; ; i++)
{
array[i] = 5;
}
}
catch (ArrayBoundsError)
{
// termina laço
}
O laço é corretamente escrito:
for (i = 0; i < array.length; i++)
{
array[i] = 5;
}
Nota de Implementação: Compiladores deveriam tentar detecar erros de limites de array em tempo de compilação, por exemplo:
int[3] foo;
int x = foo[3]; // erro, fora dos limites
Inserção de código de checagem de limites de array em tempo de compilação deveria ser ligada e desligada com um interruptor de tempo de compilação.

Inicialização de Array

Inicialização Padrão

Inicialização Vazia

Inicialização vazia ocorre quando o Inicializador para um array é void. O que isso significa é que nenhuma inicialização é feita, isto é, os conteúdos do array serão indefinidos. Isso é mais útil como uma otimização de eficiência. Inicializações vaizas são uma técnica avançada e deveriam somente ser usadas quando perfilar indica que isso importa.

Inicialização Estática de Arrays Estáticos

int[3] a = [ 1:2, 3 ]; // a[0] = 0, a[1] = 2, a[2] = 3
Isso é mais acessível quando os índices do array são dados por enums:
enum Color { red, blue, green };

int value[Color.max + 1] = [ Color.blue:6, Color.green:2, Color.red:5 ];
Se qualquer elemento do array é inicializado, todos devem ser. Isso é para capturar erros comuns onde outro elemento é aicionado à um enum, mas uma das instâncias estáticas dos arrays daquele  enum foi negigenciado em atualizar a lista do inicializador.

Tipos Especiais de Arrays

Strings

Linguagens deveriam ser boas em controlar strings. C e C++ não são bons nisso. As dificuldades primárias são gerenciamento de memória, controle de temporários, constantemente re-varrer a string procurando pelo 0, e os arrays fixos.

Arrays dinâmicos em D sugerem a solução óbvia - uma string é apenas um array dinâmico de caracteres. Literais de string tornam-se apenas um fácil modo para escrever arrays de caracteres.

char[] str;
char[] str1 = "abc";
Strings char[] estão no formato UTF-8. Strings wchar[] estão no formato UTF-16. Strings dchar[] estão no formato UTF-32.

Strings podem ser copiadas, comparadas, concatenadas e anexadas:

str1 = str2;
if (str1 < str3) ...
func(str3 ~ str4);
str4 ~= str1;
com as semânticas óbvias. Quaisquer temporarios gerados são limpos pelo coletor de lixo (ou usando alloca()). Não só isso, isso trabalha com qualquer array, não só com um array string especial.

Um ponteiro para caracter pode ser gerado:

char *p = &str[3]; // aponta para o 4º elemento
char *p = str; // aponta para o 1º elemento
Já que strings, porém, não são terminadas em 0 em D, quando transferir um ponteiro para uma string para C, adicione um terminador 0:
str ~= "\0";
O tipo de uma string é determinado pela fase semântica de compilação. O tipo é um de: char[], wchar[], dchar[], e é determinado  por regras de conversão implítita. Se há duas conversões implícitas igualmente aplicáveis, o resultado é um erro. Para diferenciar nesses casos, um molde é apropriado:
cast(wchar [])"abc"	// isso é um array de caracteres wchar
Literais de string são implicitamente convertidos entre chars, wchars, e dchars como necessário.

Strings de um único caracter de comprimento podem também ser convertidas para uma constante char, wchar ou dchar:

char c;
wchar w;
dchar d;

c = 'b'; // a c é atribuído o caracter 'b'
w = 'b'; // a w é atribuído o caracter wchar 'b'
w = 'bc'; // erro - somente um caracter wchar por vez
w = "b"[0]; // a w é atribuído o caracter wchar 'b'
w = \r; // a w é atribuído o caracter wchar de retorno de carro
d = 'd'; // a d é atribuído o caracter 'd'

printf() e Strings

printf() é uma função C e não é parte de D. printf() imprimirá strings C, que são terminadas em 0. Há dois modos de usar printf() com strings D. A primeira é adicionar um terminador 0, e moldar o resultado para um char*:
str ~= "\0";
printf("the string is '%s'\n", (char *)str);
O segundo modo é usar o especificador de precisão. Do modo como arrays D são dispostos, o comprimento vêm primeiro, então o seguinte trabalha:
printf("the string is '%.*s'\n", str);
No futuro, pode ser necessário adicionar um novo especificador de formato para printf() ao invés de confiar em um detalhe dependente de implementação.

Conversões Implícitas

Um ponteiro T* pode ser implicitamente convertido para um dos seguintes: Um array estático T[dim] pode ser implicitamente convertido para um dos seguintes: Um array dinâmico T[] pode ser implicitamente convertido para um dos seguintes:

Arrays Associativos

D vai uma etapa adiante com arrays - adicionando arrays associativos. Arrays associativos tem um índice que não é necessariamente um inteiro, e pode ser escassamente povoada. O índice para um array associativo é chamado chave, e seu tipo é chamado TipoChave.

Arrays associativos são declarados substituindo o TipoChave dentro do [] de uma declaração de array:

int[char[]] b; // array associativo b de ints que é
// indexado por um array de caracteres.
// O TipoChave é char[]
b["hello"] = 3; // ajusta o valor associado com a chave "hello" para 3
func(b["hello"]); // passa 3 como parâmetro para func()
Chaves particulares em um array associativo podem ser removidas com a função remove:
b.remove("hello");
InExpression rende um ponteiro para o valor se a chave está no array associativo, ou null se não:
int* p;
p = ("hello" in b);
if (p != null)
...
TiposChave não podem ser funções ou voids.

Se o TipoChave é um tipo de estrutura, um mecânismo padrão é usado para computar o hash e comparações dele baseadas nos dados binários dentro do valor da estrutura. Um mecânismo customizado pode ser usado provendo as seguintes funções como membros da estrutura:

uint toHash();
int opCmp(KeyType* s);
Por exemplo:
import std.string;

struct MyString
{
char[] str;

uint toHash()
{ uint hash;
foreach (char c; s)
hash = (hash * 9) + c;
return hash;
}

int opCmp(MyString* s)
{
return std.string.cmp(this.str, s.str);
}
}

Propriedades

Propriedades para arrays associativos são:
.sizeof Retorna o tamanho da referência para o array associativo; é tipicamente 8.
.length Retorna o número de valores no array associativo. Diferente de arrays dinâmicos, é somente leitura.
.keys Retorna um array dinâmico, os elementos dele são as chaves no array associativo.
.values Retorna um array dinâmico, os elementos dele são os valores no array associativo.
.rehash Reorganiza o array associativo no lugar de modo que buscas sejam mais eficiêntes. rehash é efetivo quando, por exemplo, o programa fica pronto carregando uma tabela de símbolos e agora precisa de buscas rápidas nela. Reotrna uma referência para o array reorganizado.

Exemplo de Array Associativo: word count

import std.file; // D file I/O

int main (char[][] args)
{
int word_total;
int line_total;
int char_total;
int[char[]] dictionary;

printf(" lines words bytes file\n");
for (int i = 1; i < args.length; ++i) // argumentos do programa
{
char[] input; // buffer de entrada
int w_cnt, l_cnt, c_cnt; // contadores de palavram linha e caracter
int inword;
int wstart;

input = std.file.read(args[i]); // lê arquivo em input[]

foreach (char c; input)
{
if (c == '\n')
++l_cnt;
if (c >= '0' && c <= '9')
{
}
else if (c >= 'a' && c <= 'z' ||
c >= 'A' && c <= 'Z')
{
if (!inword)
{
wstart = j;
inword = 1;
++w_cnt;
}
}
else if (inword)
{ char[] word = input[wstart .. j];

dictionary[word]++; // incrementa contador para a palavra
inword = 0;
}
++c_cnt;
}
if (inword)
{ char[] word = input[wstart .. input.length];
dictionary[word]++;
}
printf("%8ld%8ld%8ld %.*s\n", l_cnt, w_cnt, c_cnt, args[i]);
line_total += l_cnt;
word_total += w_cnt;
char_total += c_cnt;
}

if (args.length > 2)
{
printf("-------------------------------------\n%8ld%8ld%8ld total",
line_total, word_total, char_total);
}

printf("-------------------------------------\n");
char[][] keys = dictionary.keys; // procura todas palavras em dictionary[]
for (int i = 0; i < keys.length; i++)
{ char[] word;

word = keys[i];
printf("%3d %.*s\n", dictionary[word], word);
}
return 0;
}