O número inteiro é usado demais como um tipo de dados?


9

A maioria dos desenvolvedores de aplicativos usa números inteiros assinados em locais onde eles realmente pretendem usar números inteiros não assinados? Eu faço isso o tempo todo, assim como meus colegas de trabalho. Não vi muitas outras bases de código extensas (além da Delphi VCL) e exemplos na internet geralmente usam números inteiros. Enquanto os desenvolvedores da VCL usam seus próprios tipos de dados (que seria a maneira mais preguiçosa de declarar variáveis).

Algo parece um pouco terrível sobre código como este

TStuffRec = record
   recordID : Integer;
   thingID : Integer;
   otherThingID : Integer;
end;

quando poderia ser escrito como

TStuffRec = record
   recordID : Cardinal;
   thingID : Cardinal;
   otherThingID : Cardinal;
end;

Funcionalmente, esses registros quase sempre funcionam da mesma maneira (e esperamos que continuem funcionando da mesma forma, mesmo no Delphi de 64 bits). Mas números muito grandes terão problemas de conversão.

Mas também há desvantagens no uso de entradas não assinadas. Principalmente devido ao quão irritante é misturar os dois.

A verdadeira questão é: isso é algo que realmente é pensado ou incluído nas melhores práticas? Geralmente depende apenas do desenvolvedor?


5
Peter, você está procurando apenas respostas específicas para o Delphi?
Adam Lear

3
@ Anna Entender como os tipos de dados Delphi funcionam seria uma resposta excelente. Estou razoavelmente certo de que os programadores em C podem compreender e responder a essa pergunta.
22611 Peter Turner

Respostas:


9

Uma razão pela qual eu não uso tanto tipos inteiros não assinados no Delphi é que eles podem criar problemas quando misturados com números inteiros assinados. Aqui está um que me mordeu uma vez:

for i := 0 to List.Count - 1 do
  //do something here

Eu tinha ideclarado como um número inteiro sem sinal (afinal, é um índice em uma lista que começa em 0, nunca precisa ser negativa, certo?), Mas quando List.Countera 0, não iria causar um curto-circuito no loop conforme o esperado, porque 0 - 1avalia para um número positivo realmente alto. Opa!

Entre os possíveis problemas de segurança inerentes à mistura de números inteiros assinados e não assinados e os problemas de intervalo (se você precisar de números positivos maiores que high(signed whatever), é bem provável que você também acabe precisando de números positivos maiores high(unsigned whatever)também) até o próximo tamanho maior, em vez de mudar de assinado para não assinado do mesmo tamanho, geralmente é a ação correta.) Realmente não encontrei muitos usos para números inteiros não assinados ao representar a maioria dos dados.


2
De certa forma, um dos principais riscos do uso de um tipo de dados potencialmente menor que o necessário (em oposição a apenas não assinado vs. assinado) é que, se a condição de saída for maior do que o planejado, você poderá acabar com um loop infinito enquanto o contador transborda de novo e de novo. Parece estúpido em retrospectiva, mas uma vez eu escrevi um programa que deveria percorrer todos os valores de bytes possíveis e levou cerca de 15 minutos para finalmente me convencer de que não era possível fazer isso com um contador de bytes.
Aaronaught

@Aaronaught: Não em Delphi. (Pelo menos não, a menos que você faça algo estúpido como desativar a verificação de estouro interno.) Você terminará com uma exceção quando o contador estourar, em vez de um loop infinito. Ainda é um bug, mas é muito mais fácil rastrear.
Mason Wheeler

Se você diz. Eu sempre desabilitei a verificação de estouro no Delphi; depois de ser infinitamente bombardeado com falsos positivos de coisas como códigos de hash e somas de verificação, desisti completamente desse "recurso". Mas suponho que você esteja certo, isso teria detectado esse erro específico.
Aaronaught

@Aaronaught: Bem, sim, você gostaria de desativá-lo para coisas como códigos de hash e somas de verificação que são projetados especificamente para transbordar e envolver. Mas para cálculos de uso geral que não são projetados para transbordar e envolver, é um recurso de segurança importante e desativá-lo é como dirigir sem cinto de segurança.
Mason Wheeler

Talvez você tenha esquecido, mas as diretrizes de verificação de excesso e compilador foram incrivelmente problemáticas nas versões mais antigas do Delphi. Lembro-me vividamente de arrancar meus cabelos em várias ocasiões depois de ver o depurador parar diretamente no meio de um bloco {$ O -} / {$ O +} para relatar alegremente um estouro. Depois de um tempo, eu não aguentava mais e apenas o desativei globalmente. Novamente, sim, ele teria pego esse problema, mas ainda não acho que valha o número de falsos positivos. Cada um na sua, é claro!
Aaronaught 29/07

3

Para ser sincero, costumo usar números inteiros por hábito. Eu me acostumei ao fato de que eles oferecem intervalos grandes o suficiente para a maioria das situações e permitem valores negativos (como -1). De fato, muitas vezes o uso de bytes / palavra / abreviação seria mais apropriado. Agora, pensando nisso, posso me concentrar nesses pontos:

  • Perspectiva. O tamanho do mapa de bloco é limitado a blocos de 192x192, portanto, eu poderia usar o byte para endereçar blocos e loops. Mas se o tamanho do mapa aumentar, terei que passar por todos os usos e substituí-lo por, por exemplo, word. Quando eu preciso permitir objetos fora do mapa, tenho que ir novamente para mudar para smallint.

  • Rotações. Geralmente, escrevo um loop "de i: = 0 a Count-1", o que acontece se "i" for byte e Count = 0 é que o loop é executado de 0 a 255. Não que eu o desejasse.

  • Uniformização. É mais fácil lembrar e aplicar "var i: integer;" do que parar em cada caso e pensar "Hm .. aqui estamos lidando com o intervalo 0..120 .. byte .. não, espere, talvez precisemos de -1 para não inicializado .. abreviado .. espere .. e se 128 for não é suficiente .. Arrgh! " ou "Por que é pequeno neste local, não é pequeno?"

  • Combinando. Quando preciso combinar duas ou mais classes, elas podem estar usando tipos de dados diferentes para seus propósitos, o uso de tipos mais amplos permite ignorar conversões desnecessárias.

  • -1. Mesmo quando os valores estão no intervalo 0..n-1, muitas vezes preciso definir o valor "sem valor / desconhecido / não inicializado / vazio", que é, na prática comum -1.

O uso de números inteiros permite ignorar todos esses problemas, esquecer a otimização de baixo nível onde não é necessário, aumentar o nível e se concentrar em problemas mais reais.

PS Quando uso outros tipos?

  • Contadores, eles nunca são negativos e são somente leitura fora da classe.
  • Por motivos de desempenho / memória, force o uso de tipos de dados mais curtos em determinados locais.

1

A melhor prática é usar um tipo de dados que atenda às necessidades dos dados que estão sendo usados ​​(dados esperados).

Exemplos de C #: se eu precisasse suportar apenas 0 a 255, usaria um byte.

Se eu precisasse apoiar 1.000.000 negativos e positivos, então int.

Maior que 4,2 bilhões, use um longo.

Ao escolher o tipo correto, o programa usará a quantidade ideal de memória, assim como diferentes tipos usarão quantidades diferentes de memória.

Aqui está uma referência C # int do MSDN.

int 
 -2,147,483,648 to 2,147,483,647
 Signed 32-bit integer

uint 
 0 to 4,294,967,295
 Unsigned 32-bit integer

long 
 -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
 Signed 64-bit integer

ulong 
 0 to 18,446,744,073,709,551,615
 Unsigned 64-bit integer

Em C # (ou .net em geral), long e ulong se tornariam 128 bits em uma máquina de 128 bits? Como no Delphi, o Integertipo de dados é de 32 bits em uma máquina de 32 bits e, aparentemente, será de 64 bits em uma máquina de 64 bits.
22611 Peter Turner

11
@ Peter Turner: Não, em C # inté apenas uma abreviação de System.Int32, não importa em que máquina o código seja executado.
Nikie

@nikie, é type int System.Int32algo assim ou algo nesse sentido? Poderia ser alterado tão facilmente em uma versão futura do framework?
Peter Turner

@ Peter Turner / nikie (sizeof (int) .ToString ()); ==> Retorna 4 (sizeof (Int64) .ToString ()); ==> Retorna 8 No meu sistema operacional Windows de 64 bits. Como nikie, stats, um int é realmente justo e Int32.
precisa

11
Uma coisa a observar é que nem todos os tipos são compatíveis com a Common Language Specification . uinté um desses tipos não compatíveis, o que significa que não deve ser usado em API exposta publicamente para evitar a interrupção da capacidade de usar essa API em linguagens .NET diferentes daquela em que a biblioteca está escrita. É também por isso que a estrutura .NET A própria API está usando o intque uintfaria.
Adam Lear

1

Os tipos inteiros não assinados devem ser usados ​​apenas para representar números cardinais nos idiomas em que representam números cardinais. Devido à maneira como os computadores que executaram C funcionaram, os tipos inteiros não assinados se comportaram como membros dos anéis algébricos mod-2 ^ n (o que significa que os cálculos que estouraram estariam "encapsulados" previsivelmente) e o idioma especifica que, em muitos casos, esses tipos são deve se comportar como anéis algébricos abstratos, mesmo quando esse comportamento seria inconsistente com o comportamento de números cardinais ou números matemáticos.

Se uma plataforma oferecer suporte completo a tipos separados para números cardinais e anéis algébricos, sugiro que os números cardinais sejam processados ​​usando tipos de números cardinais (e coisas que precisam ser agrupadas usando os tipos de toque). Esses tipos não apenas podiam armazenar números com o dobro do tamanho dos tipos assinados, mas um método que recebia um parâmetro desse tipo não precisaria verificar se era negativo.

Dada a relativa falta de tipos de números cardinais, no entanto, geralmente é melhor simplesmente usar números inteiros para representar números inteiros matemáticos e números cardinais.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.