Por que temos que mencionar o tipo de dados da variável em C

19

Geralmente em C, temos que informar ao computador o tipo de dados na declaração da variável. Por exemplo, no programa a seguir, quero imprimir a soma de dois números de ponto flutuante X e Y.

#include<stdio.h>
main()
{
  float X=5.2;
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}

Eu tive que dizer ao compilador o tipo de variável X.

  • O compilador não pode determinar o tipo Xsozinho?

Sim, é possível se eu fizer isso:

#define X 5.2

Agora posso escrever meu programa sem informar ao compilador o tipo de X:

#include<stdio.h>
#define X 5.2
main()
{
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}  

Portanto, vemos que a linguagem C possui algum tipo de recurso, usando o qual ele pode determinar o tipo de dados por conta própria. No meu caso, determinou que Xé do tipo float.

  • Por que precisamos mencionar o tipo de dados, quando declaramos algo em main ()? Por que o compilador não pode determinar o tipo de dados de uma variável por conta própria, main()como faz em #define.
user106313
fonte
14
Na verdade, esses dois programas não são equivalentes, pois podem gerar resultados ligeiramente diferentes! 5.2é a double, portanto, o primeiro programa arredonda os literais duplos para floatprecisão, depois os adiciona como flutuadores, enquanto o segundo arredonda a representação dupla de 5,1 para doublee o adiciona ao doublevalor 5.2 usando doubleadição, depois arredonda o resultado desse cálculo para floatprecisão . Como o arredondamento ocorre em locais diferentes, o resultado pode ser diferente. Este é apenas um exemplo para os tipos de variáveis ​​que afetam o comportamento de um programa idêntico.
12
Quando você faz #define X 5.2, Xnão é uma variável, mas uma constante, portanto é literalmente substituído por pré-processador por 5.2qualquer lugar que você mencionou X. Você não pode reatribuir X.
Script #
16
Apenas como uma nota: isso é uma bênção e uma maldição. Por um lado, você deve digitar alguns caracteres quando o compilador realmente poderia ter feito isso por você (o C ++ autorealmente faz o que você deseja). Por outro lado, se você acha que sabe o que seu código está fazendo e, na verdade, digitou outra coisa, a digitação estática assim detectará um erro mais cedo, antes de se tornar um grande problema. Cada idioma atinge um equilíbrio: digitação estática, dedução de tipo, digitação dinâmica. Para algumas tarefas, a digitação extra vale a pena. Para outros, é um desperdício.
Cort Ammon - Reinstala Monica
Aprenda Ocaml e / ou Haskell .... você ficará feliz com suas habilidades de inferência de tipo.
Basile Starynkevitch

Respostas:

46

Você está comparando declarações de variáveis ​​com #defines, o que está incorreto. Com a #define, você cria um mapeamento entre um identificador e um trecho de código-fonte. O pré-processador C substituirá literalmente quaisquer ocorrências desse identificador pelo snippet fornecido. Escrevendo

#define FOO 40 + 2
int foos = FOO + FOO * FOO;

acaba sendo a mesma coisa para o compilador que a escrita

int foos = 40 + 2 + 40 + 2 * 40 + 2;

Pense nisso como copiar e colar automaticamente.

Além disso, variáveis ​​normais podem ser reatribuídas, enquanto uma macro criada com #definenão pode (embora você possa recuperá- #definelo). A expressão FOO = 7seria um erro do compilador, pois não podemos atribuir a "rvalues": 40 + 2 = 7é ilegal.

Então, por que precisamos de tipos? Aparentemente, algumas linguagens se livram dos tipos, isso é especialmente comum nas linguagens de script. No entanto, eles geralmente têm algo chamado "digitação dinâmica", onde variáveis ​​não têm tipos fixos, mas valores têm. Embora isso seja muito mais flexível, também é menos eficiente. C gosta de desempenho, por isso tem um conceito muito simples e eficiente de variáveis:

Há uma extensão de memória chamada de "pilha". Cada variável local corresponde a uma área na pilha. Agora, a pergunta é quantos bytes de comprimento essa área precisa ter? Em C, cada tipo tem um tamanho bem definido que você pode consultar sizeof(type). O compilador precisa conhecer o tipo de cada variável para que possa reservar a quantidade correta de espaço na pilha.

Por que as constantes criadas com não #defineprecisam de uma anotação de tipo? Eles não são armazenados na pilha. Em vez disso, #definecria trechos reutilizáveis ​​de código-fonte de uma maneira um pouco mais sustentável do que copiar e colar. Literais no código-fonte como "foo"ou 42.87são armazenados pelo compilador em linha como instruções especiais ou em uma seção de dados separada do binário resultante.

No entanto, literais têm tipos. Um literal de cadeia é a char *. 42é um intmas também pode ser usado para tipos mais curtos (restringindo a conversão). 42.8seria um double. Se você tem um literal e deseja que ele tenha um tipo diferente (por exemplo, para fazer 42.8um float, ou 42um unsigned long int), pode usar sufixos - uma letra após o literal que altera a maneira como o compilador trata esse literal. No nosso caso, podemos dizer 42.8fou 42ul.

Alguns idiomas têm digitação estática como em C, mas as anotações de tipo são opcionais. Exemplos são ML, Haskell, Scala, C #, C ++ 11 e Go. Como isso funciona? Magia? Não, isso é chamado de "inferência de tipo". Em C # e Go, o compilador examina o lado direito de uma atribuição e deduz o tipo disso. Isso é bastante simples se o lado direito for um literal como 42ul. Então é óbvio qual deve ser o tipo da variável. Outras linguagens também possuem algoritmos mais complexos que levam em consideração como uma variável é usada. Por exemplo, se você faz x/2, xnão pode ser uma string, mas deve ter algum tipo numérico.

amon
fonte
Obrigado por explicar. O que eu entendo é que, quando declaramos o tipo de uma variável (local ou global), na verdade estamos dizendo ao compilador, quanto espaço deve reservar para essa variável na pilha. Por outro lado #define, estamos tendo uma constante que é diretamente convertida em código binário - por mais longo que seja - e é armazenada na memória como está.
user106313
2
@ user31782 - não é bem assim. Quando você declara uma variável, o tipo informa ao compilador quais propriedades a variável possui. Uma dessas propriedades é tamanho; outras propriedades incluem como ele representa valores e quais operações podem ser executadas nesses valores.
Pete Becker
@PeteBecker Então, como o compilador conhece essas outras propriedades #define X 5.2?
user106313
1
Isso porque, ao passar o tipo errado, printfvocê invocou um comportamento indefinido. Na minha máquina, esse trecho imprime um valor diferente a cada vez; no Ideone, ele falha após a impressão zero.
Matteo Italia
4
@ user31782 - "Parece que posso executar qualquer operação em qualquer tipo de dados" Não. Não X*Yé válido se Xe Yfor ponteiro, mas tudo bem se for ints; *Xnão é válido se Xfor um int, mas tudo bem se for um ponteiro.
Pete Becker
4

X no segundo exemplo nunca é um flutuador. É chamado de macro, substitui o valor da macro definido 'X' na fonte pelo valor. Um artigo legível sobre #define está aqui .

No caso do código fornecido, antes da compilação, o pré-processador altera o código

Z=Y+X;

para

Z=Y+5.2;

e é isso que é compilado.

Isso significa que você também pode substituir esses 'valores' por códigos como

#define X sqrt(Y)

ou mesmo

#define X Y
James Snell
fonte
3
É apenas chamado de macro, não uma macro variável. Uma macro variável é uma macro que recebe um número variável de argumentos, por exemplo #define FOO(...) { __VA_ARGS__ }.
Hd
2
My bad, irá corrigir :)
James Snell
1

A resposta curta é C precisa de tipos devido ao histórico / que representa o hardware.

História: O C foi desenvolvido no início da década de 1970 e destinado como uma linguagem para programação de sistemas. O código é idealmente rápido e faz o melhor uso dos recursos do hardware.

Tipos de inferência no tempo de compilação seriam possíveis, mas os tempos de compilação já lentos teriam aumentado (consulte o desenho animado de 'compilação' do XKCD. Isso costumava se aplicar ao 'olá mundo' por pelo menos 10 anos após a publicação de C ). Inferir tipos em tempo de execução não seria adequado aos objetivos da programação de sistemas. A dedução do tempo de execução requer uma biblioteca de tempo de execução adicional. C veio muito antes do primeiro PC. Que tinha 256 RAM. Não Gigabytes ou Megabytes, mas Kilobytes.

No seu exemplo, se você omitir os tipos

   X=5.2;
   Y=5.1;

   Z=Y+X;

Então o compilador poderia ter felizmente descoberto que X e Y são flutuadores e transformaram Z no mesmo. De fato, um compilador moderno também descobriria que X & Y não são necessários e apenas configuraria Z como 10.3.

Suponha que o cálculo esteja incorporado dentro de uma função. O gravador de funções pode querer usar seu conhecimento do hardware ou o problema que está sendo resolvido.

Um duplo seria mais apropriado do que um carro alegórico? Leva mais memória e é mais lento, mas a precisão do resultado seria maior.

Talvez o valor de retorno da função possa ser int (ou longo) porque os decimais não são importantes, embora a conversão de float para int não seja sem custo.

O valor de retorno também pode ser dobrado, garantindo que float + float não transborde.

Todas essas perguntas parecem inúteis para a grande maioria do código escrito hoje, mas eram vitais quando o C foi produzido.

itj
fonte
1
isso não explica por exemplo, por declarações de tipo não foram feitas opcional, permitindo programador para escolher tanto para declará-los explicitamente ou confiar em compilador para inferir
mosquito
1
Na verdade, não @gnat. Alterei o texto, mas não havia sentido em fazê-lo naquele momento. O domínio C foi projetado para realmente desejar armazenar 17 em 1 byte ou 2 bytes ou 4 bytes ou como uma string ou como 5 bits em uma palavra.
itj
0

C não possui inferência de tipo (é assim que é chamado quando um compilador adivinha o tipo de uma variável para você) porque é antigo. Foi desenvolvido no início dos anos 70

Muitas linguagens mais recentes possuem sistemas que permitem o uso de variáveis ​​sem especificar seu tipo (ruby, javascript, python etc.)

Tristan Burnside
fonte
12
Nenhum dos idiomas mencionados (Ruby, JS, Python) possui inferência de tipo como um recurso de linguagem, embora as implementações possam usá-lo para aumentar a eficiência. Em vez disso, eles usam digitação dinâmica onde valores têm tipos, mas variáveis ​​ou outras expressões não.
amon
2
O JS não permite que você omita o tipo - apenas não permite que você o declare. Ele usa digitação dinâmica, onde os valores têm tipos (por exemplo, trueé boolean), não variáveis ​​(por exemplo, var xpodem conter valor de qualquer tipo). Além disso, a inferência de tipo para casos simples como os da questão provavelmente já foi conhecida uma década antes do lançamento do C.
Script #
2
Isso não torna a declaração falsa (para forçar algo, você também deve permitir). Inferência de tipos existente não muda o fato de que tipo de sistema de C é um resultado do seu contexto histórico (em oposição a um raciocínio declarou especificamente filosófica ou limitação técnica)
Tristan Burnside
2
Considerando que o ML - que é quase tão antigo quanto C - tem inferência de tipo, "é velho" não é uma boa explicação. O contexto em que C foi usado e desenvolvido (pequenas máquinas que demandavam uma área muito pequena para o compilador) parece mais provável. Não faço ideia por que você mencionaria linguagens de digitação dinâmicas, em vez de apenas alguns exemplos de linguagens com inferência de tipo - Haskell, ML, ceck C # - não é mais um recurso obscuro.
Voo
2
@BradS. Fortran não é um bom exemplo, porque a primeira letra do nome da variável é uma declaração de tipo, a menos que você use implicit nonenesse caso, você deve declarar um tipo.
dmckee