Primeiros programas em C: tipos, operadores e laços

Escreva seus primeiros programas em C e entenda exatamente o que eles fazem. Compile e execute código com gcc ou clang e corrija o que o compilador apontar, armazene dados em variáveis inteiras, de caractere e de ponto flutuante e imprima e leia valores com printf e scanf. Crie expressões com os operadores aritméticos, de comparação, lógicos e bit a bit de C, prevendo conversões e precedência, e controle um programa com if, switch, while, do-while e for. Para terminar, aprenda por que ocorrem estouros de inteiros, o que acontece quando ocorrem e como escrever e testar código que os evite.

BásicoAula 1 de 8 do curso Curso de Programação em C

O que você vai aprender

  1. Transforme código-fonte C em um programa em execução, saiba o que o compilador faz em cada etapa e use suas mensagens para encontrar e corrigir erros.
    • Escreva, compile e execute seu primeiro programa em C
      Leia as partes de um programa mínimo em C, compile-o com gcc ou clang e execute o arquivo executável em um terminal.
    • Leia as mensagens do compilador e corrija erros
      Diferencie erros de compilação, avisos, erros de ligação, erros de execução e erros de lógica e use mensagens do compilador e execuções de teste para corrigi-los.
  2. Declare e inicialize variáveis, escolha entre os tipos inteiros e de ponto flutuante de C e preveja a faixa e a precisão oferecidas por cada tipo.
    • Declare, inicialize e dê nomes a variáveis
      Crie variáveis com um tipo e um nome significativo, atribua valores iniciais a elas e use constantes para valores fixos.
    • Escolha um tipo inteiro e conheça sua faixa de valores
      Escolha entre os tipos inteiros com e sem sinal de C e valores de caractere e booleanos, usando seus tamanhos, faixas e formas literais.
    • Trabalhe com números de ponto flutuante e seus limites
      Armazene números reais em float e double, preveja sua precisão e seus erros de arredondamento e converta com segurança entre valores de ponto flutuante e inteiros.
  3. Formate números e texto com precisão usando printf e leia e verifique valores digitados pelo usuário com scanf.
    • Formate a saída com printf
      Use especificações de conversão, larguras de campo, precisão, opções de formatação e sequências de escape para produzir exatamente a saída desejada.
    • Leia e verifique a entrada com scanf
      Leia números e caracteres digitados pelo usuário, verifique se a leitura foi bem-sucedida e recupere-se de entradas inválidas.
  4. Use os operadores aritméticos, de atribuição, de comparação, lógicos e bit a bit de C e preveja o valor e o tipo de qualquer expressão a partir das conversões, da precedência e da associatividade.
    • Calcule com operadores aritméticos e de atribuição
      Aplique +, -, *, / e %, divisão inteira, atribuição composta, incremento e decremento e evite suas armadilhas típicas.
    • Preveja conversões de tipo em expressões com tipos diferentes
      Explique como C converte operandos de tipos diferentes, use conversões explícitas de tipo de forma deliberada e evite a armadilha dos tipos com e sem sinal.
    • Combine condições com operadores relacionais, lógicos e bit a bit
      Escreva comparações e condições compostas corretas, use a avaliação de curto-circuito e manipule bits individuais.
    • Aplique a precedência, a associatividade e a ordem de avaliação
      Preveja como C agrupa os operadores em uma expressão, avalie expressões passo a passo e use parênteses quando o agrupamento não estiver claro.
  5. Escolha entre alternativas com if, else e switch e repita tarefas com laços while, do-while e for que começam, executam e terminam exatamente como desejado.
    • Tome decisões com if e else
      Escreva instruções if, else if e else com condições e chaves corretas e evite as armadilhas comuns da sintaxe de C.
    • Selecione entre casos com switch
      Use switch com rótulos case, break e default para escolher entre valores inteiros fixos e use a continuação entre casos apenas intencionalmente.
    • Repita enquanto uma condição for verdadeira com while e do-while
      Escreva laços controlados por condição que comecem corretamente, avancem e parem, incluindo laços de entrada e laços que precisam ser executados pelo menos uma vez.
    • Conte com laços for, aninhe laços e encerre-os antecipadamente
      Escreva laços for que sejam executados exatamente o número desejado de vezes, combine laços dentro de laços e use break e continue deliberadamente.
  6. Explique o que acontece quando um resultado não cabe em seu tipo inteiro e escreva e teste código que evite ou detecte estouros.
    • Explique o que acontece quando um valor não cabe no tipo
      Descreva a redução modular sem sinal, o estouro indefinido com sinal e as conversões fora da faixa representável e relacione-os a falhas reais.
    • Escreva código que evite ou detecte estouros
      Escolha tipos com faixa suficiente, verifique operações antes que estourem e use ferramentas do compilador e testes de valores-limite para detectar estouros cedo.

Guia de estudo de Primeiros programas em C

6 capítulos

1

Compile e execute programas em C

O código C torna-se um programa em execução por meio de um processo de compilação. Conheça as partes de um primeiro programa completo, compile-o com um padrão de linguagem escolhido e execute o arquivo resultante. Quando uma compilação ou um teste falhar, use a etapa da falha e a primeira mensagem do compilador para localizar a causa. Seguir cuidadosamente o ciclo de edição, compilação e teste impede que um executável antigo oculte uma alteração malsucedida e facilita a verificação de cada correção.

1.1 Do código-fonte a um programa em execução

Um programa em C começa como texto simples e é executado como código de máquina. O compilador conecta essas duas formas. Salvar uma edição altera o código-fonte; isso não altera o executável gerado anteriormente.

Leia um programa completo

Salve isto como hello.c:

#include <stdio.h>

int main(void)
{
    printf("Hello, world!\n");
    return 0;
}

#include <stdio.h> disponibiliza declarações para entrada e saída padrão, incluindo printf. A execução começa em main. Em int main(void), int nomeia o tipo de retorno e void significa que não há parâmetros. As chaves delimitam o corpo. A chamada a printf escreve o texto entre aspas; \n passa para uma nova linha. return 0; encerra o programa e informa sucesso ao sistema operacional. Um código diferente de zero pode informar falha. Desde C99, chegar ao fim de main também retorna zero.

Instruções simples terminam com ponto e vírgula. A linha de inclusão não. C diferencia maiúsculas de minúsculas: Printf e printf são nomes diferentes. Espaços entre tokens geralmente não afetam o programa, mas uma indentação consistente torna visível a estrutura dos blocos.

Compile e depois execute

No terminal, abra a pasta que contém o código-fonte e digite:

gcc -std=c17 -Wall -Wextra hello.c -o hello
./hello

O primeiro comando gera um executável chamado hello; o segundo o executa a partir da pasta atual. Sua saída é Hello, world!, seguida de uma nova linha. Você pode usar clang no lugar de gcc. Sem -o hello, o nome usual da saída no Linux ou macOS é a.out. Executáveis do Windows usam .exe. O make hello do CS50 é um atalho que solicita à ferramenta de compilação que invoque o compilador.

Depois de alterar a saudação, salve, compile novamente e execute outra vez. Se a compilação falhar, um executável antigo ainda pode estar presente. Executá-lo mostra o comportamento antigo, portanto verifique se a compilação foi bem-sucedida antes de avaliar sua edição.

O que a compilação faz

O pré-processador trata diretivas como #include, inserindo o texto do cabeçalho. O compilador propriamente dito traduz C para linguagem de montagem. O montador produz código-objeto, que ainda pode conter nomes não resolvidos. O ligador combina o código-objeto e resolve referências como printf. O código de biblioteca pode ser copiado para um executável por ligação estática ou fornecido por uma biblioteca compartilhada carregada quando o programa é executado.

Você pode interromper a compilação em etapas intermediárias:

OpçãoResultado
-ETexto pré-processado
-SLinguagem de montagem
-cUm arquivo-objeto, como hello.o

Um arquivo-objeto ainda não é o executável final. Um executável pode funcionar sem o compilador, mas é gerado para um sistema operacional e um processador específicos. Para levar o programa a outra plataforma, compile o código-fonte nela.

Comentários e versões da linguagem

// inicia um comentário que termina na nova linha. /* ... */ pode abranger várias linhas, mas esses comentários não podem ser aninhados: o primeiro */ encerra o comentário. Use comentários para explicar uma decisão que o código, por si só, não esclarece.

Selecione explicitamente a versão da linguagem. C17 dá suporte aos exemplos acima, incluindo comentários //, long long e declarações em cabeçalhos de for. C23 acrescenta recursos como bool como palavra-chave; foi publicado como ISO/IEC 9899:2024. -Wall -Wextra ativa avisos úteis. Os padrões dos compiladores variam, portanto um -std=c17 explícito deixa claras as regras pretendidas para a linguagem.

Principais conclusões

  • O código-fonte C deve ser compilado novamente após cada alteração.
  • A execução começa em main; retornar zero informa sucesso.
  • As etapas de geração são pré-processamento, compilação, montagem e ligação.
  • Use -std=c17 -Wall -Wextra para selecionar o padrão e ativar avisos.

1.2 Encontrar a primeira causa de um erro

O momento em que um problema aparece ajuda a encontrar sua causa. Um programa pode falhar na compilação, parar durante a execução ou terminar com a resposta errada. Essas situações exigem verificações diferentes.

Diferencie os tipos de problema
TipoO que você vêOnde procurar
Erro de compilaçãoNenhum novo executávelSintaxe, declarações e tipos
AvisoUm provável problema; a compilação geralmente continuaA expressão indicada na mensagem
Erro de ligaçãoO código-objeto existe, mas o executável não pode ser concluídoNomes de funções não resolvidos ou bibliotecas necessárias
Erro de execuçãoUm problema aparece durante a execuçãoOperações e valores alcançados por aquela entrada
Erro de lógicaO programa executa, mas fornece uma resposta não pretendidaCondições, fórmulas e atualizações

Um aviso pode identificar código inválido ou comportamento indefinido, além de um erro comum. Uma compilação bem-sucedida não garante um programa correto.

Leia um diagnóstico

Uma mensagem do compilador normalmente fornece uma localização no código-fonte, uma gravidade e uma descrição. Uma mensagem como hello.c:5:16: error: expected ';' after expression aponta para a falta de um ponto e vírgula perto da linha 5. Às vezes, o compilador percebe o problema na linha seguinte. Examine a linha indicada e a imediatamente anterior.

Corrija primeiro o primeiro erro e compile novamente. Uma aspa não fechada pode fazer linhas posteriores parecerem parte de uma cadeia de caracteres; uma chave sem par pode fazer declarações posteriores parecerem estar no bloco errado. Várias mensagens podem desaparecer após uma correção. Não comece alterando todas as linhas mencionadas na saída.

Para este trecho:

int total = 6
printf("%d\n", total);

A declaração precisa de um ponto e vírgula após 6. A linha de printf pode ser onde o compilador detecta pela primeira vez que a declaração anterior não terminou. Verifique também a escrita exata, o pareamento de parênteses e chaves e o cabeçalho que declara uma função de biblioteca. Uma chamada a printf precisa de <stdio.h>; chamadas a funções não declaradas não são permitidas em C99 e versões posteriores.

Leve os avisos a sério

Compile com -Wall -Wextra. Essas opções ajudam a detectar uma atribuição em uma condição, variáveis não usadas, comparações entre valores com e sem sinal e formatos de saída incompatíveis. Por exemplo, printf("%d\n", 3.5); solicita um inteiro, mas passa um double; o formato não converte o argumento para você. Corrija o formato para %f ou converta o valor deliberadamente. -Werror faz os avisos interromperem a compilação.

Entenda o comportamento indefinido

Comportamento indefinido significa que C não impõe requisitos ao comportamento do programa. Estouro de inteiro com sinal, divisão inteira por zero e algumas leituras de variáveis não inicializadas podem causá-lo. Um programa pode parar, imprimir um valor inesperado ou parecer funcionar. As configurações do compilador podem mudar o que acontece.

Uma mensagem como “Floating point exception” pode até resultar de uma divisão inteira por zero em alguns sistemas. Diagnostique a operação e a entrada, não apenas as palavras de uma mensagem do sistema operacional. Verifique o divisor antes de executar a divisão.

Rastreie um resultado incorreto

Para um erro de lógica, preveja um caso pequeno à mão e compare-o com a saída real. Acrescente chamadas temporárias a printf para mostrar um total acumulado ou o valor testado por uma condição. Altere uma causa de cada vez e compile novamente após cada alteração. Teste valores comuns e limites como zero, um, uma entrada negativa e a maior entrada esperada. Um rastreamento mostra onde o programa diverge pela primeira vez da sua previsão.

Principais conclusões

  • Corrija o primeiro diagnóstico e compile novamente antes de investigar mensagens posteriores.
  • Um aviso pode identificar comportamento indefinido; a compilação, por si só, não comprova que o programa está correto.
  • Faça as conversões de formato corresponderem aos tipos dos argumentos.
  • Para um resultado incorreto, rastreie uma entrada pequena e compare os valores intermediários.
  • Teste valores-limite além de entradas comuns.
2

Armazene dados em variáveis dos tipos básicos

Um tipo determina como um valor armazenado é interpretado e quais valores ele pode conter. Comece com declarações, valores iniciais e nomes disponíveis dentro de um bloco. Depois, escolha tipos inteiros pela faixa e tipos de ponto flutuante pela precisão e também pela faixa. Códigos de caracteres e valores booleanos também fazem parte desse quadro. Ler literais e verificar limites prepara você para prever o que um cálculo pode representar antes de escrevê-lo.

2.1 Variáveis, nomes e valores fixos

Uma variável dá nome a um valor armazenado. Seu tipo informa a C como interpretar esse armazenamento. Você declara o tipo antes de usar a variável, e ela mantém esse tipo durante todo o seu tempo de vida.

Dê um valor inicial a cada variável
int count = 0;
double length_m = 1.8;
count = count + 1;

As duas primeiras linhas declaram e inicializam variáveis. A terceira atribui um novo valor a uma variável existente: ela lê o valor atual de count, soma um e armazena o resultado novamente. A atribuição é uma ação, portanto count = count + 1 faz sentido, embora não fosse uma equação matemática.

A inicialização ocorre em uma declaração; a atribuição pode ocorrer depois. Em int a, b = 5;, apenas b é inicializada. Uma variável local a tem valor indeterminado até que você armazene um valor nela. Sua leitura pode produzir comportamento imprevisível, incluindo comportamento indefinido. Inicialize contadores e totais ao declará-los, como em int total = 0;.

A atribuição copia o valor naquele momento:

int width = 4;
int area = width * 3;
width = 7;
printf("%d %d\n", width, area);

Isso imprime 7 12. A atribuição a area calculou 12 uma vez. Ela não criou uma fórmula que recalcula sempre que width muda. Para atualizar area, atribua a ela um novo cálculo.

Escolha nomes legíveis

Um identificador pode conter letras, algarismos e sublinhados, mas não pode começar com um algarismo nem ser uma palavra-chave. height_cm e second_place são válidos; 2nd_place não é, e my-score é interpretado como subtração. Nomes diferenciam maiúsculas de minúsculas, portanto total e Total são diferentes. Nomes que incluem um significado ou uma unidade facilitam a análise de fórmulas.

Respeite o escopo de bloco

Um nome local está disponível desde sua declaração até o fim de seu bloco. Desde C99, declarações podem aparecer entre outras instruções.

int count = 2;
{
    int count = 9;
    printf("%d\n", count);
}
printf("%d\n", count);

A declaração interna oculta o nome externo enquanto esse bloco é executado. A saída é 9 e depois 2, em linhas separadas. São duas variáveis, não duas atribuições a uma variável. Declarar o mesmo nome duas vezes em um bloco é um erro. Reutilizar nomes em blocos aninhados é permitido, mas dificulta o acompanhamento do código; portanto, prefira nomes que deixem clara cada função.

Dê nomes a valores fixos
const int SECONDS_PER_MINUTE = 60;
#define MAX_HEIGHT 8

const cria uma variável tipada à qual não se pode atribuir um novo valor por esse nome. Uma instrução posterior SECONDS_PER_MINUTE = 90; exige um diagnóstico. O inicializador fornece o valor fixo na declaração.

#define cria uma substituição do pré-processador: ocorrências posteriores de MAX_HEIGHT são substituídas pelo token 8 antes da compilação. Não há = nem ponto e vírgula nessa diretiva. Um ponto e vírgula passaria a fazer parte da substituição e poderia invalidar uma expressão como height > MAX_HEIGHT.

Um nome como SECONDS_PER_MINUTE informa ao leitor por que 60 aparece em um cálculo e oferece um único lugar para manter esse valor. Letras maiúsculas são uma convenção de nomenclatura, não uma regra que torna um valor constante. A declaração ou diretiva fornece esse comportamento.

Principais conclusões

  • Inicialize variáveis locais antes de lê-las.
  • Uma atribuição copia um valor uma vez; valores dependentes não se atualizam sozinhos.
  • Um bloco limita onde um nome local pode ser usado.
  • Nomes diferenciam maiúsculas de minúsculas e não podem começar com um algarismo.
  • const fornece uma variável tipada somente leitura; #define fornece uma substituição de tokens.

2.2 Escolha de tipos inteiros, de caractere e booleanos

Tipos inteiros armazenam números inteiros exatamente dentro de uma faixa fixa. Escolha um tipo pelos valores que o programa precisa tratar, incluindo resultados intermediários, e por fazerem ou não sentido valores negativos.

Tamanhos e faixas

O padrão garante larguras mínimas: pelo menos 8 bits para char, 16 para short e int, 32 para long e 64 para long long. Cada tipo nessa sequência tem pelo menos a largura do anterior. Os tamanhos reais dependem da plataforma.

TipoBytes típicos em Linux/macOS de 64 bitsDiferença no Windows de 64 bits
char1Nenhuma
short2Nenhuma
int4Nenhuma
long84 bytes
long long8Nenhuma

sizeof(int) informa o tamanho em bytes como um size_t; imprima-o com %zu. O cabeçalho <limits.h> fornece nomes como INT_MIN, INT_MAX, UINT_MAX, LLONG_MAX e CHAR_BIT, o número de bits por byte.

Com N bits de valor, um inteiro sem sinal tem valores de 0 a 2ᴺ − 1. Um inteiro com sinal em complemento de dois com N bits varia de −2ᴺ⁻¹ a 2ᴺ⁻¹ − 1. Assim, um int de 32 bits varia de −2.147.483.648 a 2.147.483.647, enquanto um unsigned int de 32 bits chega a 4.294.967.295. C23 exige complemento de dois; C17 também permitia outras representações.

Em um padrão de complemento de dois de 8 bits, o bit mais significativo tem peso −128. 11111111 representa −1, e 10000000 representa −128. Para formar −5, comece com 00000101, inverta os bits para 11111010 e some um para obter 11111011.

Caracteres também são inteiros

Um char armazena um código de caractere em um byte. Use aspas simples para uma constante de caractere, como em 'A'. Em ASCII, 'A' tem código 65 e '0' tem código 48. printf("%c %d\n", 'A', 'A'); imprime A 65 em ASCII. Uma constante de caractere em C tem tipo int, portanto sizeof('A') é sizeof(int).

char sem qualificador pode ter ou não sinal, dependendo da implementação. Use signed char ou unsigned char quando a faixa numérica for importante. <stdint.h> oferece tipos inteiros de largura exata, como int32_t e uint8_t, em plataformas que dão suporte a essas larguras exatas; as plataformas mais comuns dão esse suporte.

Leia literais inteiros

Uma constante decimal como 42 tem tipo int quando cabe nele. Sufixos solicitam outros tipos: 42u é sem sinal, 42L é long, 42LL é long long e 42ULL é unsigned long long. Hexadecimal usa 0x, como em 0x2A. Um zero inicial indica octal: 052 também significa 42, e 010 significa 8. 09 é um octal inválido. C23 acrescenta notação binária, como 0b101010.

Valores lógicos e escolha de tipo

Em C17, inclua <stdbool.h> para usar bool, true e false; C23 os torna palavras-chave. Uma conversão para bool resulta em falso para zero e verdadeiro para todo valor diferente de zero, incluindo 0.5. Imprima um valor booleano com %d para ver 0 ou 1.

Use int para contagens comuns e contadores de laço. Use long long ou um tipo de largura exata adequado quando a faixa puder exceder cerca de 2,1 bilhões. Tipos sem sinal são adequados para padrões de bits, mas uma subtração abaixo de zero sofre redução modular para um valor grande, portanto eles ainda precisam de verificações de faixa.

Principais conclusões

  • Use os limites do tipo, em vez de supor um tamanho para long.
  • Um int com sinal de 32 bits chega a 2.147.483.647.
  • Valores sem sinal não têm faixa negativa e sofrem redução modular abaixo de zero.
  • Um zero inicial começa um literal inteiro octal.
  • Se char sem qualificador tem sinal depende da implementação.
  • Em C17, os nomes booleanos vêm de <stdbool.h>.

2.3 Valores decimais e precisão limitada

Um tipo de ponto flutuante armazena uma aproximação de um número real. Ele tem uma faixa ampla, mas apenas um número fixo de algarismos significativos. Faixa e precisão respondem a perguntas diferentes: qual pode ser a magnitude de um valor e quão bem valores próximos podem ser distinguidos.

Escolha um tipo de ponto flutuante

Em sistemas IEEE 754 comuns, float ocupa 4 bytes e oferece cerca de 6–7 algarismos decimais significativos. double ocupa 8 bytes e oferece cerca de 15–16. Seus maiores valores finitos são aproximadamente 3,4 × 10³⁸ e 1,8 × 10³⁰⁸. long double é pelo menos tão preciso quanto double, com formato dependente da plataforma. double é uma escolha padrão útil para grandezas medidas.

Constantes como 2.0, 3.14 e 1e-3 são double; 1e-3 significa 0,001. Acrescente f para float, como em 2.5f, ou L para long double. A constante 2 é um inteiro, portanto pode resultar em divisão inteira onde 2.0 produziria divisão de ponto flutuante.

Observe a aproximação armazenada

Muitas frações decimais não têm uma forma binária finita exata. Em um sistema comum de precisão dupla:

printf("%.20f\n", 0.1);
printf("%.17f\n", 0.1 + 0.2);

As saídas são 0.10000000000000000555 e 0.30000000000000004. Os algarismos adicionais mostram a aproximação armazenada. Eles explicam por que 0.1 + 0.2 == 0.3 pode ser falso.

Use uma tolerância ao comparar medidas calculadas:

#include <math.h>
/* inside main */
if (fabs(a - b) < 1e-9) {
    printf("Close enough\n");
}

fabs retorna uma diferença absoluta. Escolha a tolerância de acordo com a escala e a precisão dos valores; 1e-9 é um exemplo, não uma configuração universal. Algumas compilações no Linux precisam de -lm ao final do comando do compilador para ligar as funções matemáticas.

A precisão afeta números inteiros grandes

Um double binário representa exatamente todos os inteiros até 2⁵³. Um float binário deixa de representar todos os inteiros acima de 2²⁴ = 16.777.216: atribuir 16.777.217 a esse float resulta em 16.777.216. Uma faixa maior não significa que todos os incrementos inteiros permaneçam disponíveis.

A impressão arredonda o valor binário armazenado. printf("%.2f\n", 2.675); geralmente imprime 2.67, pois o valor armazenado é um pouco menor que 2,675. Para quantias exatas em centavos inteiros, use um inteiro como long long cents = 1999; e formate a exibição ao final.

Converta e arredonde deliberadamente

A conversão para inteiro descarta a fração em direção a zero: (int)3.99 é 3, e (int)-3.99 é −3. round de <math.h> retorna um valor de ponto flutuante arredondado para o inteiro mais próximo, com valores intermediários afastados de zero: round(-2.5) resulta em −3,0. Converta esse resultado para inteiro somente quando ele couber no tipo. Uma conversão de ponto flutuante para inteiro cuja parte inteira esteja fora da faixa tem comportamento indefinido.

Na aritmética IEEE, resultados fora da faixa finita podem tornar-se infinito. Uma divisão de ponto flutuante de um valor diferente de zero por zero pode produzir infinito; zero dividido por zero produz NaN, que significa “não é um número”. Um NaN é considerado diferente até de si mesmo em uma comparação. Esses são comportamentos de ponto flutuante IEEE; a divisão inteira por zero tem comportamento indefinido.

Principais conclusões

  • Tipos de ponto flutuante têm precisão limitada, além de faixa limitada.
  • Compare medidas calculadas com uma tolerância adequada.
  • A impressão arredonda a aproximação armazenada.
  • A conversão de ponto flutuante para inteiro trunca em direção a zero e exige um resultado representável.
3

Exiba a saída e leia a entrada com printf e scanf

Um programa precisa trocar texto e valores com clareza. Formate a saída para que o leitor possa distinguir rótulos, campos e medidas arredondadas e faça cada conversão de entrada corresponder à variável que a recebe. A leitura de texto tem vários resultados possíveis: todos os valores solicitados, apenas alguns ou nenhuma conversão. Verificar esses resultados e o texto não lido faz com que cálculos posteriores dependam de valores que o programa realmente obteve.

3.1 Impressão de valores no formato desejado

printf transforma valores em texto usando uma cadeia de formatação. Caracteres comuns são impressos como escritos. Uma conversão que começa com % informa como exibir o próximo argumento.

Faça cada conversão corresponder ao argumento
printf("%d samples, %.2f kg\n", 4, 1.5);

Isso imprime 4 samples, 1.50 kg e uma nova linha. As conversões correspondem aos argumentos em ordem. Um formato não converte o tipo do argumento fornecido para o tipo que você pretendia.

Tipo de valor ou exibiçãoConversão
int%d ou %i
unsigned int%u
long, long long%ld, %lld
size_t%zu
double ou um float promovido%f, %e, %g
long double%Lf
Caractere, literal de cadeia de caracteres%c, %s
Hexadecimal sem sinal, octal%x ou %X, %o

Um argumento float é promovido a double em printf, portanto ambos usam %f; %lf é equivalente aqui. Passar 3 para %f é uma incompatibilidade de tipo, não uma solicitação para imprimir 3.0. Use 3.0 ou (double)3. Um tipo incompatível ou um argumento ausente pode causar comportamento indefinido. Compile com avisos ativados para que literais de cadeia de formatação sejam verificados.

Defina largura e precisão

Uma largura é um número mínimo de caracteres, incluindo sinal e ponto decimal. Uma precisão de ponto flutuante após o ponto controla as casas decimais de %f. Em %8.2f, oito é a largura mínima e dois é o número de casas após o ponto decimal. Um número mais largo é impresso por completo; a largura não o corta.

Com o inteiro 42, %5d produz 42, %-5d produz 42 , %05d produz 00042 e %+d produz +42. Sem uma precisão, %f imprime seis casas decimais.

printf("%-6s%8s\n", "Item", "Price");
printf("%-6s%8.2f\n", "Tea", 2.5);
printf("%-6s%8.2f\n", "Cake", 12.75);

Cada rótulo ocupa um campo de seis caracteres alinhado à esquerda, seguido de um campo de preço de oito caracteres alinhado à direita. Ambos os preços têm duas casas decimais, portanto seus pontos decimais ficam alinhados. Os espaços vêm das larguras de campo, não de uma separação automática entre argumentos.

Exibições científica e geral

%e usa notação científica: 12345,678 é impresso como 1.234568e+04 na precisão padrão. Para %g, a precisão conta algarismos significativos, com seis por padrão. Ele usa notação científica quando o expoente decimal é menor que −4 ou pelo menos igual à precisão e remove zeros à direita. Assim, %g imprime 0,0001 como 0.0001, 0,00001 como 1e-05 e 1000000,0 como 1e+06. Ele não escolhe simplesmente a forma mais curta possível.

Imprima caracteres especiais

Dentro de uma cadeia de caracteres em C, \n significa nova linha, \t significa tabulação, \\ significa uma barra invertida e \" significa uma aspa. Um sinal de porcentagem em um formato é %%: printf("100%%\n"); imprime 100% e uma nova linha.

printf não acrescenta espaços nem quebras de linha por conta própria. printf("Count:"); printf("%d", 4); imprime Count:4. Um terminal pode atrasar a exibição de texto sem uma nova linha; fflush(stdout); envia uma solicitação pendente antes de pedir a entrada.

Principais conclusões

  • Faça as conversões corresponderem aos tipos reais dos argumentos.
  • A largura de campo é um mínimo; a precisão de %f conta casas decimais.
  • A precisão de %g conta algarismos significativos.
  • Escreva %% para um sinal de porcentagem literal em um formato.
  • printf não acrescenta espaço nem nova linha, a menos que o formato os forneça.

3.2 Leitura da entrada e verificação do que foi lido

scanf lê texto, converte-o e armazena valores em variáveis. Verifique seu resultado antes de usar essas variáveis. Um texto que começa com algarismos pode ser aceito apenas em parte, portanto uma conversão bem-sucedida e uma linha de entrada totalmente válida são coisas diferentes.

Forneça um destino do tipo correto
int count;
double mass_kg;
if (scanf("%d %lf", &count, &mass_kg) != 2) {
    return 1;
}

O & passa o endereço de cada variável para que scanf possa armazenar nela. %d precisa de um int, %u de um inteiro sem sinal, %ld de um long e %lld de um long long. Para entrada de ponto flutuante, %f precisa de um float e %lf de um double. Ao contrário de printf, essas duas conversões não são intercambiáveis. Um & ausente ou um tipo de destino incorreto pode causar comportamento indefinido.

%d lê em decimal. %i também reconhece entrada hexadecimal e octal com zero inicial; com %i, o texto “010” significa 8. Use %d para contagens decimais comuns.

Verifique o valor de retorno

scanf retorna o número de atribuições concluídas. Para dois valores solicitados, sucesso significa 2. Uma conversão malsucedida deixa seu destino inalterado. Ele ainda pode estar não inicializado, portanto não o use a menos que a contagem de retorno confirme o sucesso. EOF significa que a entrada terminou ou que ocorreu um erro de entrada antes da conclusão da primeira conversão.

Para scanf("%d:%d", &h, &m), os dois-pontos devem aparecer literalmente. A entrada “14:30” preenche ambas as variáveis. A entrada “14 30” preenche h, depois falha ao encontrar os dois-pontos e deixa m inalterada. O valor de retorno é 1.

Entenda espaços em branco e entradas parciais

Conversões numéricas ignoram espaços, tabulações e novas linhas iniciais. %c lê o próximo caractere, incluindo uma nova linha que tenha restado. Use scanf(" %c", &choice) para ignorar caracteres de espaço em branco antes de ler um caractere visível. Evite uma nova linha no final do formato: ela pode fazer a chamada aguardar um caractere posterior que não seja espaço em branco.

Com %d, “12abc” armazena 12 e deixa “abc” sem ler. “3.7” armazena 3 e deixa “.7”. “abc” não armazena nada e retorna 0. Chamar a mesma conversão novamente sem consumir a entrada inválida pode falhar para sempre no mesmo caractere.

Recupere-se de uma conversão malsucedida
int n, result;
while ((result = scanf("%d", &n)) != 1) {
    if (result == EOF) {
        return 1;
    }
    int ch;
    while ((ch = getchar()) != '\n' && ch != EOF) {
    }
    printf("Try again: ");
    fflush(stdout);
}

getchar retorna um int, o que permite representar tanto um caractere quanto EOF. O laço interno descarta a linha em que houve falha. O laço externo tenta novamente, mas ainda aceita um prefixo numérico como 12 em “12abc”. Exigir que o próximo caractere seja '\n' verifica a regra mais restrita de um inteiro imediatamente seguido de uma nova linha; essa regra também rejeita espaços finais. Uma regra que permita espaços ou uma última linha terminada em EOF precisa tratá-los explicitamente.

Após uma conversão bem-sucedida, verifique a faixa permitida, como de 1 a 8 para uma configuração de altura. scanf não rejeita com segurança um valor numérico fora da faixa: um valor grande demais para seu destino tem comportamento indefinido. Use entradas de teste que caibam no tipo escolhido. Para calcular um resultado como massa dividida pela altura ao quadrado, valide medidas positivas e mantenha o quadrado agrupado como height_m * height_m.

Principais conclusões

  • Passe endereços de variáveis e use %lf para ler um double.
  • Exija que a contagem de retorno corresponda ao número de valores necessários.
  • Uma conversão bem-sucedida pode deixar entrada adicional sem ler.
  • Consuma a entrada inválida antes de repetir uma conversão numérica malsucedida.
  • Verifique os valores permitidos após a leitura; a conversão de entrada tem seus próprios limites de faixa do tipo.
  • Use " %c" quando a leitura de um caractere deve ignorar espaços em branco.
4

Faça cálculos com operadores e expressões

Uma expressão combina valores, tipos e operadores. Rastreie primeiro a divisão inteira e as atualizações e, em seguida, acompanhe as conversões que ocorrem em cada operação. Comparações e condições de proteção controlam se um cálculo é alcançado, enquanto operações bit a bit atuam sobre bits individuais. Por fim, diferencie o agrupamento definido pela precedência do sequenciamento necessário quando uma expressão altera uma variável. Tanto o valor quanto o tipo de um resultado intermediário são importantes.

4.1 Aritmética, atribuição e atualizações de uma unidade

Toda expressão em C tem um valor e um tipo. O tipo determina como uma operação funciona. A divisão inteira, por exemplo, não consegue preservar um resultado fracionário, mesmo que você o armazene depois em uma variável de ponto flutuante.

Quocientes e restos

+, - e * somam, subtraem e multiplicam. / divide, e % fornece um resto inteiro. Multiplicação, divisão e resto têm precedência maior que adição e subtração.

Quando ambos os operandos da divisão são inteiros, C trunca o quociente em direção a zero. 17 / 5 é 3, -7 / 2 é −3 e 7 / -3 é −2. O resto satisfaz a identidade da divisão: multiplicar o quociente pelo divisor e somar o resto resulta no dividendo. Assim, -7 % 2 é −1 e 7 % -3 é 1. Um resto diferente de zero tem o sinal do dividendo.

Para verificar se um número é par, use n % 2 == 0. Para verificar se um inteiro positivo ou negativo é ímpar, use n % 2 != 0; verificar se é exatamente 1 não detecta valores ímpares negativos. % precisa de operandos inteiros; <math.h> fornece fmod para restos de ponto flutuante. Um divisor inteiro igual a zero torna / e % indefinidos, portanto verifique-o antes da operação.

Para 135 minutos, 135 / 60 fornece duas horas inteiras e 135 % 60 fornece os 15 minutos restantes. Para um inteiro decimal positivo, % 10 extrai seu último algarismo e / 10 o remove: 4096 fornece 6 e 409.

Preserve frações quando precisar delas
int correct = 45, total = 60;
double percent = 100.0 * correct / total;

A constante de ponto flutuante faz a multiplicação ser de ponto flutuante, e a divisão resulta em 75,0. Em correct / total * 100, a primeira operação é uma divisão inteira e resulta em zero. Multiplicar esse zero não recupera a fração perdida. Fazer primeiro a multiplicação inteira, correct * 100 / total, resulta em 75 neste caso, desde que o produto caiba no tipo.

Armazene um novo valor

Uma atribuição avalia seu lado direito e armazena o valor convertido à esquerda. A expressão de atribuição tem esse valor armazenado, portanto a = b = 0; define ambas as variáveis como zero, agrupando da direita para a esquerda.

Atribuições compostas combinam cálculo e armazenamento: x += 5 soma cinco a x, e x /= 2 divide x por dois usando seu tipo atual. Todo o operando da direita pertence ao cálculo. Se x for 4, x *= 2 + 1 o torna 12, pois multiplica por três.

Atualizações prefixadas e pós-fixadas

++ soma um e -- subtrai um. Em uma linha isolada, i++; e ++i; têm o mesmo efeito final. Dentro de uma expressão maior, a forma pós-fixada produz o valor antigo; a prefixada produz o novo valor.

InícioInstruçãoValores finais
a = 5y = a++;y = 5, a = 6
a = 5y = ++a;y = 6, a = 6

Mantenha as modificações fáceis de ordenar. i = i++ + 1; e printf("%d %d\n", n, n++); têm usos conflitantes não sequenciados da mesma variável e comportamento indefinido. Separe a leitura, a atualização e a saída em instruções quando sua ordem for importante.

Principais conclusões

  • A divisão inteira trunca em direção a zero.
  • Um resto inteiro diferente de zero tem o sinal do dividendo.
  • Use um operando de ponto flutuante antes da divisão se precisar de uma fração.
  • A forma pós-fixada produz o valor antigo; a prefixada produz o valor atualizado.
  • Mantenha leituras e atualizações conflitantes em instruções separadas.

4.2 Os tipos são decididos antes de um resultado ser armazenado

C converte valores à medida que cada operador é aplicado. A variável de destino recebe o resultado final; ela não altera retroativamente operações anteriores da expressão.

Converta antes de dividir
int sum = 7, count = 2;
double a = sum / count;
double b = (double)sum / count;
double c = (double)(sum / count);

a torna-se 3,0: os dois operandos inteiros produzem o inteiro 3, que é então convertido. b torna-se 3,5: a conversão explícita converte sum antes da divisão, portanto count também é convertido para double. c torna-se 3,0: sua conversão explícita está fora da divisão e ocorre depois que a fração foi descartada.

A mesma regra se aplica dentro de uma expressão mais longa. 7 / 2 * 2.0 resulta em 6,0, pois o primeiro resultado é o inteiro 3. 7 / 2.0 * 2 resulta em 7,0. Percorra os operadores na ordem de seu agrupamento, anotando o tipo de cada valor intermediário.

Encontre um tipo aritmético comum

Se algum operando for de ponto flutuante, a aritmética geralmente converte para o tipo de ponto flutuante mais amplo envolvido: long double, depois double, depois float. Para inteiros, tipos pequenos primeiro passam pela promoção inteira. Em plataformas com char de 8 bits, short de 16 bits e int de 32 bits, todo valor dos tipos menores cabe em int, portanto esses tipos são promovidos a int.

Com unsigned char a = 200, b = 100;, a + b é o inteiro 300 em um sistema com byte de 8 bits e int de 32 bits. Não há redução modular em 255 durante a adição. Armazenar 300 em um caractere sem sinal de 8 bits o reduz para 44. A promoção também pode produzir estouro com sinal: dois valores unsigned short de 50.000 são multiplicados como int, mas 2.500.000.000 excede um INT_MAX de 32 bits.

Para tipos com e sem sinal, a hierarquia e a faixa representável determinam o tipo comum. int com unsigned int é convertido para sem sinal. Um tipo com sinal de hierarquia superior pode manter a operação com sinal quando representa todos os valores do tipo sem sinal; caso contrário, a aritmética ainda pode ser sem sinal.

A armadilha dos tipos com e sem sinal

Com um inteiro sem sinal de 32 bits, -1 < 0u é falso. O −1 com sinal é convertido para o valor sem sinal 4.294.967.295 antes da comparação. Da mesma forma, 2u - 10 resulta em 4.294.967.288. Um resultado de sizeof também é sem sinal, o que importa ao compará-lo com uma contagem possivelmente negativa. Ative avisos e mantenha quantidades relacionadas em tipos compatíveis.

Conversões no armazenamento podem perder informações

Uma conversão de ponto flutuante para inteiro trunca em direção a zero. Sua parte inteira deve caber no tipo, ou o comportamento será indefinido. Um inteiro convertido para um tipo sem sinal de N bits é reduzido módulo 2ᴺ; −1 torna-se o maior valor sem sinal. Em C17, uma conversão de inteiro fora da faixa para um tipo com sinal tem resultado definido pela implementação ou gera um sinal definido pela implementação. Essa conversão é distinta do estouro indefinido na aritmética com sinal.

Constantes de caractere também participam de operações aritméticas. '7' - '0' resulta em 7 porque C garante códigos consecutivos para algarismos decimais. Em ASCII, as letras também são consecutivas, mas não suponha essa propriedade para todo conjunto de caracteres. Em fórmulas inteiras, multiplicar antes de dividir só pode preservar informações úteis se esse produto couber em seu tipo de cálculo.

Principais conclusões

  • O tipo de destino não altera operações aritméticas anteriores.
  • Converta explicitamente um operando antes da operação que precisa de um tipo de maior faixa ou de ponto flutuante.
  • Inteiros pequenos são promovidos antes das operações aritméticas.
  • Operandos mistos com e sem sinal podem alterar comparações e subtrações.
  • Conversão fora da faixa e estouro na aritmética com sinal seguem regras diferentes.
  • Acompanhe os tipos intermediários, além dos valores finais.

4.3 Condições e padrões de bits

Uma condição verifica se algo é verdadeiro. Uma operação bit a bit atua sobre os bits individuais de um inteiro. C fornece operadores diferentes para essas tarefas, mesmo quando seus símbolos são parecidos.

Compare valores e combine testes

As comparações <, >, <=, >=, == e != produzem um int: 1 para verdadeiro e 0 para falso. Em uma condição, zero é falso e qualquer valor diferente de zero é verdadeiro. Assim, if (count) testa se count é diferente de zero. == compara dois valores; = atribui um novo valor.

&& significa que ambas as condições devem ser verdadeiras, || significa que pelo menos uma deve ser verdadeira, e ! inverte o valor lógico. Eles retornam 0 ou 1. Uma faixa de 1 a 8 precisa de duas comparações:

height >= 1 && height <= 8

1 <= height <= 8 não expressa essa faixa em C. Ela é agrupada como (1 <= height) <= 8, portanto a segunda comparação recebe 0 ou 1 e é sempre verdadeira. Para testar valores fora da faixa, escreva height < 1 || height > 8.

Negar uma condição composta altera ambas as partes: !(a && b) é equivalente a !a || !b, e !(a || b) a !a && !b. Essas são as leis de De Morgan. Elas explicam por que estar fora de um intervalo precisa de um “ou”, embora estar dentro precise de um “e”.

Coloque uma proteção antes da operação arriscada
if (d != 0 && n / d > 2) {
    printf("Above two\n");
}

&& avalia primeiro seu lado esquerdo. Se esse lado for falso, ignora o lado direito. || ignora seu lado direito quando o esquerdo é verdadeiro. Essa avaliação de curto-circuito permite que uma condição de proteção resguarde uma operação posterior. Inverter as duas partes acima faria a divisão antes de verificar o divisor. Para inteiros com sinal, há uma questão separada de faixa quando INT_MIN é dividido por −1 em um sistema de complemento de dois; proteja também esse par quando valores arbitrários forem possíveis.

Um teste de ano bissexto combina divisibilidade e exceções: (y % 4 == 0 && y % 100 != 0) || y % 400 == 0. O ano 2000 passa no teste de 400; 1900 falha porque a exceção dos séculos se aplica.

Trabalhe com um bit por vez

& bit a bit mantém os bits ativados em ambos os operandos; | mantém os bits ativados em pelo menos um; ^ mantém os bits diferentes; ~ inverte os bits. Com 12 (1100) e 10 (1010), os resultados são 8, 14 e 6 para &, | e ^.

Deslocamentos movem bits. Para valores não negativos cujo resultado caiba no tipo, x << k multiplica por 2ᵏ, e x >> k descarta k bits menos significativos, dividindo por 2ᵏ com o resto descartado. Use tipos sem sinal em operações com bits. Em C17, deslocar por uma quantidade negativa ou maior ou igual à largura do tipo promovido é indefinido; deslocar um valor negativo com sinal à esquerda é indefinido, e deslocá-lo à direita é definido pela implementação.

As posições dos bits começam em zero. flags | (1u << 3) ativa o bit 3; flags & ~(1u << 3) o desativa; (flags >> 3) & 1u o lê. 1 & 2 é 0 porque não há bit ativado em comum, enquanto 1 && 2 é 1 porque ambos os valores são diferentes de zero. Operadores bit a bit avaliam ambos os operandos e não podem substituir condições lógicas de proteção.

Principais conclusões

  • Comparações e operadores lógicos produzem 0 ou 1.
  • Um teste de faixa une duas comparações com &&.
  • Condições de proteção por curto-circuito devem vir antes da operação protegida.
  • Use operadores lógicos para valores lógicos e operadores bit a bit sem sinal para máscaras.
  • As posições dos bits começam em zero; as quantidades de deslocamento devem estar dentro da largura do tipo promovido.

4.4 Agrupamento de expressões e ordenação de efeitos

Duas perguntas ajudam você a ler uma expressão: quais operandos pertencem a cada operador e quando acontecem operações com efeitos colaterais? A precedência e a associatividade respondem à pergunta sobre agrupamento. Elas geralmente não determinam a ordem de avaliação dos operandos.

Siga a precedência

Operadores mais altos nesta tabela têm maior precedência. Cada linha está abaixo da anterior; os operadores separados por “depois” têm níveis distintos.

Da maior para a menorOperadores
Pós-fixadosx++, x--, chamadas
Unários++x, --x, +, -, !, ~, conversões explícitas de tipo, sizeof, endereço &
Multiplicativos*, /, %
Aditivos+, -
Deslocamento<<, >>
Relacionais<, <=, >, >=
Igualdade==, !=
Bit a bit&, depois ^, depois `
Lógicos&&, depois `
Condicional?:
Atribuição=, += e outras atribuições compostas
Vírgula,

Parênteses prevalecem sobre o agrupamento. 2 + 3 * 4 é 14; (2 + 3) * 4 é 20.

Use a associatividade em níveis iguais

A maioria dos operadores aritméticos binários é agrupada da esquerda para a direita. 10 - 4 - 3 significa (10 - 4) - 3, que é 3. 100 / 10 / 2 resulta em 5. A atribuição é agrupada da direita para a esquerda, portanto a = b = 0 armazena zero em ambas as variáveis. O agrupamento descreve a estrutura da expressão; ele não estabelece uma regra geral de execução da esquerda para a direita.

Para int a = 3, b = 4, c = 5;, rastreie a + b * c > 20 && c % 2 == 1. A multiplicação resulta em 20, a adição resulta em 23 e 23 > 20 resulta em 1. O resto é 1, e seu teste de igualdade resulta em 1. O 1 && 1 final é 1.

Proteja testes de bits e atribuições com parênteses

x & 1 == 0 significa x & (1 == 0), portanto sempre produz zero. Escreva (x & 1) == 0 para testar o bit menos significativo. A adição também tem precedência maior que o deslocamento: 1 << 2 + 1 é 1 << 3, ou 8.

A atribuição tem precedência menor que a comparação. a = b == c armazena o 0 ou 1 da comparação em a. Em while ((ch = getchar()) != '\n'), os parênteses internos fazem a atribuição receber primeiro o caractere. Sem eles, ch receberia um resultado de comparação.

Escolha um valor ou sequencie duas ações

condition ? first : second avalia a condição e depois apenas a expressão do valor selecionado. max = (a > b) ? a : b; escolhe um valor; um if pode escolher um bloco de ações. O operador vírgula avalia primeiro sua expressão à esquerda e produz o valor de sua expressão à direita. Ele pode coordenar duas atualizações em um cabeçalho de for. Vírgulas entre argumentos de função são separadores e não estabelecem essa ordem.

Mantenha efeitos colaterais separados

C deixa não especificadas muitas ordens de avaliação de operandos e argumentos. n++ * n++ tem alterações não sequenciadas na mesma variável, portanto tem comportamento indefinido. Parênteses adicionais não corrigem a falta de sequenciamento. Use instruções separadas quando uma ação precisar preceder outra. &&, ||, ?: e o operador vírgula fornecem regras específicas de sequenciamento; a precedência aritmética comum não oferece essa garantia.

Principais conclusões

  • A precedência determina o agrupamento; geralmente não determina a ordem de avaliação.
  • Em um mesmo nível aritmético, o agrupamento costuma ser da esquerda para a direita.
  • Coloque entre parênteses um resultado bit a bit antes de compará-lo.
  • O operador condicional avalia apenas a expressão do valor selecionado.
  • O operador vírgula sequencia operandos; vírgulas que separam argumentos não.
  • Separe atualizações conflitantes para evitar comportamento indefinido.
5

Controle o fluxo do programa com condições e laços

O controle de fluxo determina quais instruções são executadas e com que frequência. Use condições para selecionar um ramo, casos inteiros exatos para selecionar um ponto de entrada em um switch e laços para repetir uma tarefa até que sua regra de parada seja satisfeita. Rastreamentos tornam a ordem visível: valores iniciais, testes, instruções do corpo e atualizações. Preste atenção especial a entradas nos limites e ao destino de break ou continue em estruturas aninhadas, nas quais uma pequena alteração de sintaxe pode mudar todo o caminho.

5.1 Escolha do ramo correto

Uma instrução if escolhe se uma ação será executada. Sua condição deve estar entre parênteses. Zero significa falso; todo valor diferente de zero significa verdadeiro.

Use chaves para mostrar o ramo
if (temperature < 0) {
    printf("Freezing\n");
} else {
    printf("At or above freezing\n");
}

O primeiro bloco é executado para uma temperatura negativa. Em exatamente zero, a comparação é falsa e o bloco else é executado. Leia o limite diretamente de <, <=, > ou >=; a mensagem deve corresponder a esse limite.

Sem chaves, apenas a próxima instrução é controlada:

if (temperature > 30)
    printf("Hot\n");
printf("Drink water\n");

A segunda mensagem é executada em qualquer temperatura. A indentação ajuda as pessoas, mas não altera a gramática. As chaves permitem acrescentar uma segunda instrução a um ramo sem deixá-la acidentalmente fora dele.

Ordene uma sequência de else-if

Uma sequência if–else if–else executa o primeiro ramo cuja condição seja verdadeira e ignora os demais. Para faixas de pontuação, comece pelo maior limite inferior:

if (score >= 90) {
    grade = 'A';
} else if (score >= 80) {
    grade = 'B';
} else if (score >= 70) {
    grade = 'C';
} else {
    grade = 'F';
}

Uma pontuação de 85 falha no primeiro teste e passa no segundo. Uma pontuação de 90 passa no primeiro. Se você testasse score >= 70 primeiro, 85 entraria nesse ramo e nunca chegaria ao ramo B pretendido. Instruções if separadas comportam-se de maneira diferente: cada condição é testada independentemente, portanto vários blocos podem ser executados.

Observe dois pequenos erros de sintaxe

if (x = 5) atribui 5 e depois trata esse valor diferente de zero como verdadeiro. Use if (x == 5) para comparar. Com if (x = 0), a atribuição ainda acontece, mas a condição é falsa. Avisos ajudam a revelar atribuições acidentais em condições.

Em if (x > 0);, o ponto e vírgula é uma instrução controlada vazia. Um bloco colocado após ele é executado independentemente, mesmo quando x é negativo. Não acrescente um ponto e vírgula entre uma condição if e seu bloco pretendido.

Associe cada else explicitamente

Um else associa-se ao if anterior mais próximo que ainda não tenha um else. Em decisões aninhadas, use chaves para deixar clara a associação pretendida. O aninhamento é útil quando testes malsucedidos precisam de mensagens diferentes: primeiro teste se uma medida é válida e depois teste se seu valor está na faixa desejada. Combinar ambos com && funciona quando apenas uma ação compartilhada é necessária.

Coloque exceções antes de regras gerais

Anos bissextos oferecem uma decisão ordenada útil. Primeiro, teste a divisibilidade por 400, que indica ano bissexto. Em seguida, teste a divisibilidade por 100, que indica que não é ano bissexto. Depois, teste a divisibilidade por 4; caso contrário, o ano não é bissexto. Para 2000, o primeiro ramo se aplica. Para 1900, o segundo se aplica. Verificar apenas a divisibilidade por quatro deixaria passar a exceção dos séculos. A ordem importa porque regras gerais podem incluir casos com uma exceção mais específica.

Principais conclusões

  • Uma sequência de else-if executa apenas seu primeiro ramo verdadeiro.
  • As chaves controlam quais instruções pertencem a um ramo.
  • == compara; = atribui.
  • Um ponto e vírgula após uma condição if cria um ramo vazio.

5.2 Seleção de casos fixos com switch

Um switch escolhe um ponto de início usando um valor inteiro. Ele é útil para um caractere de comando ou número de menu com várias alternativas fixas. A execução pode então continuar nos casos seguintes, a menos que você a interrompa.

Encontre um caso correspondente e saia dele
switch (choice) {
    case 1:
        printf("Start\n");
        break;
    case 2:
        printf("Pause\n");
        break;
    default:
        printf("Unknown choice\n");
        break;
}

Com choice igual a 2, o programa começa em case 2, imprime Pause e sai do switch no break. Se nenhum caso corresponder, a execução começa em default. Sem um default, um valor sem correspondência faz o programa ignorar o switch.

A expressão de controle deve ter um tipo inteiro, incluindo char ou uma enumeração. Cada caso deve ser uma expressão constante inteira distinta, como 3, 'q' ou um nome adequado definido com #define. Em C17, uma variável const int não é uma expressão constante inteira para um rótulo case. Valores de ponto flutuante e cadeias de caracteres não podem controlar um switch.

Entenda a continuação entre casos

Rótulos case marcam pontos de entrada, não blocos separados que terminam automaticamente. Remova o primeiro break acima e uma escolha de 1 imprimirá tanto Start quanto Pause. O programa não testa se choice é 2 antes de continuar por esse caso. A execução para apenas em uma transferência de controle ou no fim do switch.

Vários rótulos podem compartilhar uma ação:

case 'y':
case 'Y':
    printf("Yes\n");
    break;

Ambos os caracteres levam à mesma ação. Quando um caso realiza uma tarefa e continua deliberadamente em outro, explique a intenção com um comentário. C23 também oferece [[fallthrough]];. Avisos podem ajudar a encontrar um break ausente, mas agrupar rótulos deliberadamente não exige repetir a ação.

Dê um bloco às variáveis locais de um caso

Em C17, uma declaração não pode vir diretamente após um rótulo case. Use chaves ao redor das instruções desse caso:

case 3: {
    int delay = 5;
    printf("%d\n", delay);
    break;
}

O bloco também mantém delay local ao caso. C23 flexibiliza a restrição de uma declaração após um rótulo, mas as chaves ainda deixam o escopo claro.

Escolha entre switch e if

Use switch para alternativas inteiras exatas. Use if–else if para desigualdades, condições que envolvam várias variáveis ou medidas de ponto flutuante. Faixas de pontuação de mesma amplitude às vezes podem ser convertidas em casos exatos por divisão inteira. Com pontuações de 0 a 100, score / 10 resulta em 8 para 85; os casos 9 e 10 podem compartilhar a faixa mais alta. Valide esse domínio para que pontuações negativas ou excessivas não sejam silenciosamente atribuídas a uma faixa.

Um switch dentro de um laço

Um break sai do switch ou laço mais próximo que o contém. Dentro de um switch que está em um laço, o break encerra apenas o switch. Para encerrar o laço do menu, defina uma variável de controle verificada por sua condição ou retorne de main quando o programa tiver que terminar. Um continue nesse switch se aplica ao laço que o contém, levando-o à próxima iteração. Rastreie a qual estrutura cada transferência se destina antes de decidir se o menu para.

Principais conclusões

  • Um switch seleciona um rótulo de entrada usando um valor inteiro.
  • A execução continua pelos casos até break, outra transferência de controle ou o fim.
  • Os valores dos casos devem ser expressões constantes inteiras distintas.
  • Use um bloco para uma declaração local a um caso em C17.
  • O break de um switch não encerra também o laço que o contém.

5.3 Repetição até que os dados indiquem a parada

Um laço while testa uma condição antes de cada passagem. Um laço do–while testa após o corpo, portanto é executado pelo menos uma vez. Escolha a forma que coloca o teste onde o programa precisa dele.

Rastreie um cálculo controlado por condição
int n = 4096, sum = 0;
while (n > 0) {
    sum += n % 10;
    n /= 10;
}

n % 10 extrai um algarismo, e n /= 10 o remove. Os valores após as passagens são:

Passagemsumn
16409
21540
3154
4190

A condição seguinte é falsa, portanto o laço termina com uma soma dos algarismos igual a 19. Se n começar em zero, o corpo é executado zero vezes e a soma permanece zero. Entradas negativas também ignoram esse laço, portanto um programa que as aceite precisa de uma regra definida.

Um laço de contagem define um contador, testa-o e o atualiza. Outros laços avançam lendo dados ou chegando a um break. Ao dividir 100 repetidamente pela metade com divisão inteira, os valores são 50, 25, 12, 6, 3 e 1: são necessárias seis passagens para chegar a um.

Deixe a entrada controlar a repetição
int x, sum = 0, count = 0;
while (scanf("%d", &x) == 1 && x != 0) {
    sum += x;
    count++;
}
if (count != 0) {
    printf("%.2f\n", (double)sum / count);
}

Para a entrada “5 8 3 0”, a sentinela zero encerra o laço e não é somada. A soma é 16, a contagem é 3 e a média exibida é 5.33. O teste da contagem de retorno protege o uso de x; o teste da contagem protege a divisão caso o primeiro valor seja zero. Esse cálculo também exige entradas e totais que caibam em int.

Sem a verificação da sentinela, while (scanf("%d", &x) == 1) para quando uma conversão numérica falha ou a entrada termina. Em um terminal, Ctrl+D normalmente sinaliza o fim da entrada no Linux e no macOS; no Windows, use Ctrl+Z seguido de Enter.

Execute uma vez antes de testar
int height;
do {
    printf("Height (1-8): ");
    fflush(stdout);
    if (scanf("%d", &height) != 1) {
        return 1;
    }
} while (height < 1 || height > 8);

A solicitação e a leitura ocorrem antes do teste de faixa. Os valores zero e nove fazem o laço se repetir; os valores um e oito são aceitos. O ponto e vírgula após while (...) é obrigatório nessa forma. A verificação da conversão encerra este exemplo em caso de entrada não numérica; o teste de faixa sozinho não consegue tratar uma conversão malsucedida.

Avance em todo caminho que continua

break sai imediatamente do laço mais interno. continue ignora o restante do corpo e retorna à condição em um laço while. Se uma atualização estiver abaixo de um continue, ela também será ignorada. Assim, um laço pode continuar testando o mesmo valor para sempre.

Antes de executar um laço, identifique o que muda e por que a condição de parada será alcançada. Um while (1) deliberado pode parar por meio de break; uma atualização de contador ausente é uma situação diferente. Ctrl+C interrompe um programa travado em um terminal. Teste a menor entrada e o valor no limite de parada para detectar erros de zero passagens e de uma passagem a mais.

Principais conclusões

  • while testa primeiro; do–while testa após pelo menos uma passagem.
  • Uma sentinela interrompe a entrada sem se tornar parte dos dados.
  • Inicialize acumuladores fora do laço.
  • Um caminho que continua deve avançar em direção à condição de parada.
  • Um continue em while pode ignorar uma atualização posterior.
  • Verifique se há dados antes de calcular uma média.

5.4 Contagem de passagens e combinação de laços

Um laço for mantém um padrão de contagem em um único cabeçalho. Laços aninhados repetem uma tarefa interna para cada passagem externa. Ambos ficam mais fáceis de verificar quando você lista os valores reais do contador e identifica todas as saídas.

Acompanhe o cabeçalho em ordem
for (int i = 0; i < 3; i++) {
    printf("%d ", i);
}

A inicialização é executada uma vez. Depois, a condição é testada, o corpo é executado se ela for verdadeira e a atualização é executada antes do próximo teste. A saída é 0 1 2 . A variável declarada no cabeçalho é local ao laço; declare-a antes dele se precisar de seu valor final depois.

Para n não negativo, i = 0; i < n; i++ é executado n vezes, visitando de zero a n − 1. Com i <= n, é executado uma vez a mais. Para um passo positivo s de a até um b incluído, a contagem é (b - a) / s + 1 usando divisão inteira, desde que a aritmética e as atualizações do contador caibam no tipo. De 2 a 20 em passos de 3, os valores são 2, 5, 8, 11, 14, 17, 20: sete passagens.

Mantenha o acumulador fora do laço
int total = 0;
for (int i = 1; i <= 100; i++) {
    total += i;
}

O total final é 5050. A inicialização deve ocorrer uma vez antes do laço. Mover int total = 0; para o corpo cria um novo total a cada passagem, perdendo o trabalho anterior.

Escolha contadores que possam chegar ao fim

Um ponto e vírgula diretamente após o cabeçalho do for torna o corpo vazio. Um bloco seguinte é executado uma vez após o laço. Use chaves ligadas diretamente ao cabeçalho para tornar o corpo visível.

Uma contagem regressiva sem sinal com i >= 0 não pode parar dessa maneira: valores sem sinal são sempre não negativos, e decrementar zero causa redução modular para o máximo. Um contador de ponto flutuante que soma 0.1 repetidamente pode passar pelo valor exato 1.0 sem atingi-lo, portanto x != 1.0 não é confiável como regra de parada. Conte com um inteiro e calcule uma medida como x = i / 10.0 dentro do corpo.

Construa linhas com laços aninhados

Para cada passagem externa, o laço interno começa e executa sua própria sequência. Três passagens externas e quatro passagens internas fixas executam o corpo interno doze vezes. Quando o limite interno depende da linha, conte cada linha separadamente.

int height = 3;
for (int row = 1; row <= height; row++) {
    for (int s = 0; s < height - row; s++) printf(" ");
    for (int h = 0; h < row; h++) printf("#");
    printf("\n");
}

As linhas contêm dois espaços e uma cerquilha, um espaço e duas cerquilhas e, em seguida, três cerquilhas. A nova linha vem após os laços internos, portanto cada passagem externa produz uma linha.

Ignore trabalho ou pare antecipadamente

continue em um laço for vai para a atualização e depois para o próximo teste. Um laço que soma apenas valores ímpares de 1 a 9 pode ignorar valores pares e terminar com 25. break sai imediatamente do laço mais interno. Em laços aninhados, um break interno não interrompe o laço externo.

Um laço for costuma ser a escolha mais clara para uma faixa conhecida de contagem; while é adequado para repetição controlada por dados, e do–while é adequado para uma tarefa que deve ocorrer antes do teste. Reescrever um laço for como while exige cuidado especial com continue, pois a atualização ainda deve ocorrer nesse caminho.

Principais conclusões

  • Um laço for executa a inicialização uma vez e a atualização após cada passagem que continua.
  • Verifique os valores visitados para detectar erros de uma unidade.
  • Use contadores inteiros com faixa suficiente.
  • Laços aninhados reiniciam a sequência interna a cada passagem externa.
  • Um break interno sai apenas do laço interno.
  • Um continue em for ainda executa a atualização do cabeçalho.
  • Mantenha a inicialização de um total acumulado fora do corpo repetido.
6

Reconheça e evite estouros de inteiros

Faixas inteiras finitas afetam cálculos mesmo quando a resposta final parece pequena o suficiente. Diferencie redução modular sem sinal, estouro com sinal e valores perdidos durante a conversão e examine os tipos das operações intermediárias. A prevenção começa antes de um cálculo arriscado: converta um operando para um tipo de maior faixa, comprove os limites ou use uma operação com verificação de estouro. Faixas de contadores e testes de valores-limite ajudam a encontrar falhas que entradas pequenas comuns e execuções curtas de teste podem não detectar.

6.1 O que acontece quando um inteiro não cabe no tipo

A faixa de um inteiro é finita. Uma resposta matemática pode ser válida e ainda assim grande demais para o tipo C usado para calculá-la. As consequências dependem de a operação ser sem sinal, com sinal ou uma conversão.

A aritmética sem sinal sofre redução modular

Um resultado sem sinal de N bits é reduzido módulo 2ᴺ. Com um unsigned int de 32 bits, UINT_MAX + 1u resulta em zero, 0u - 1u resulta em 4.294.967.295 e 3u - 5u resulta em 4.294.967.294. O resultado é definido, mas pode estar errado para a tarefa: subtrair cinco itens de um estoque de três não deveria produzir bilhões de itens.

Para uint8_t b = 255;, b = b + 1; armazena zero. Em um sistema típico com int de 32 bits, a adição ocorre primeiro como int e produz 256; converter esse valor de volta para um tipo sem sinal de 8 bits produz zero. Um armazenamento pequeno sem sinal não significa que toda operação intermediária tenha esse tipo pequeno.

O estouro na aritmética com sinal é indefinido

Com um int de 32 bits, somar um a INT_MAX exige 2.147.483.648. C não define o resultado aritmético com sinal. O hardware pode parecer fazer uma redução modular para INT_MIN, mas o compilador pode otimizar com base na suposição de que estouro com sinal nunca ocorre. Uma verificação como if (x + 1 < x) pode ser removida para inteiros com sinal, pois toda adição definida de um a um inteiro com sinal resulta em um valor maior.

Não calcule primeiro para depois examinar o dano. Uma operação indefinida já terá acontecido antes que um teste posterior seja executado.

Um destino de maior faixa pode chegar tarde demais
int a = 50000, b = 50000;
long long product = a * b;

A multiplicação é realizada como int, portanto estoura em um sistema com int de 32 bits. A largura do destino não pode corrigir isso. (long long)a * b muda o tipo da operação antes da multiplicação e produz 2.500.000.000.

Fatoriais crescem rapidamente: 12! é 479.001.600 e cabe em um inteiro com sinal de 32 bits; 13! é 6.227.020.800 e não cabe. Com aritmética sem sinal de 32 bits, 13! sofre redução modular para 1.932.053.504. Um long long com sinal de 64 bits comporta 20! = 2.432.902.008.176.640.000, mas não 21!.

A conversão tem regras próprias

Converter um inteiro para um tipo sem sinal usa redução modular. Em C17, converter um inteiro fora da faixa para um tipo com sinal produz um resultado ou sinal definido pela implementação. Converter um valor de ponto flutuante cuja parte inteira truncada não caiba no destino inteiro tem comportamento indefinido. Ler um número fora da faixa por meio de uma conversão numérica de scanf também é indefinido.

Falhas de faixa podem ter grandes efeitos

O primeiro voo do Ariane 5, em 4 de junho de 1996, foi perdido depois que um valor de ponto flutuante não coube em um inteiro com sinal de 16 bits no software de referência inercial. Esse programa foi escrito em Ada; a lição sobre verificar faixas de conversão também se aplica a C.

Um contador de segundos com sinal de 32 bits iniciado na época Unix chega a 2.147.483.647 às 03:14:07 UTC de 19 de janeiro de 2038. Sistemas que usam essa representação de tempo precisam de uma com maior faixa. Em 2015, a FAA também tratou do software de controle dos geradores do Boeing 787, que poderia estourar após 248 dias de alimentação contínua. Essas falhas mostram por que tanto o longo tempo de execução quanto os valores intermediários devem entrar em uma estimativa de faixa.

Os limites de ponto flutuante são diferentes: sistemas IEEE comuns podem produzir infinito, e um float pode perder precisão para números inteiros antes de atingir seu máximo. A redução modular de inteiros sem sinal produz um valor numérico comum; o estouro com sinal não oferece nenhum resultado confiável.

Principais conclusões

  • A aritmética sem sinal usa redução modular.
  • O estouro na aritmética com sinal tem comportamento indefinido.
  • Um destino de maior faixa não pode corrigir uma operação intermediária de faixa menor.
  • Conversão de inteiro, conversão de ponto flutuante e estouro aritmético têm regras diferentes.
  • Estime valores para toda a duração da operação.

6.2 Prevenção de estouro antes que ele aconteça

Comece pelo maior valor que cada cálculo pode produzir. Inclua produtos intermediários e atualizações de contadores, não apenas a resposta final. Depois, escolha um tipo ou uma verificação prévia que mantenha cada operação dentro da faixa.

Amplie a faixa do cálculo antecipadamente

Um inteiro com sinal de 32 bits comporta cerca de 2,1 bilhões, enquanto um long long com sinal de 64 bits comporta até 9.223.372.036.854.775.807. Um contador de milissegundos atinge o limite com sinal de 32 bits em cerca de 24,9 dias. Use um tipo de 64 bits quando uma duração puder ser maior.

Escreva (long long)a * b para ampliar a faixa de um produto inteiro antes de ele ser calculado. Constantes também importam: 86400LL * days inicia a multiplicação em long long. (long long)(a * b) converte apenas após o produto de menor faixa, portanto não pode evitar seu estouro. Tipos de maior faixa ainda têm limites; compare os maiores valores possíveis com eles.

Verifique a adição usando subtração

Para int a e int b, inclua <limits.h> e teste:

if ((b > 0 && a > INT_MAX - b) ||
    (b < 0 && a < INT_MIN - b)) {
    printf("Sum out of range\n");
} else {
    int sum = a + b;
    printf("%d\n", sum);
}

Cada subtração é protegida pelo sinal de b, mantendo a própria verificação representável. Para 2.000.000.000 mais 200.000.000, INT_MAX - b é 1.947.483.647, menor que a, portanto a adição é recusada. O caso de igualdade cabe: quando a é igual a INT_MAX - b, a soma é exatamente INT_MAX.

Verifique a multiplicação positiva por divisão

Para operandos int positivos, a > INT_MAX / b significa que o produto excederia o máximo. A condição evita calcular o produto e evita um divisor zero porque b é positivo. Em um cálculo de fatorial, o teste antes de multiplicar 12! por 13 detecta que 479.001.600 é maior que 165.191.049. Pare antes dessa multiplicação.

Para um estoque sem sinal, teste stock >= requested antes de subtrair. Mudar apenas para um tipo sem sinal não evita um resultado sem sentido causado por redução modular.

Reescreva expressões intermediárias arriscadas

O ponto médio (low + high) / 2 pode estourar mesmo quando sua resposta cabe no tipo. Quando 0 <= low <= high e ambos são int, use low + (high - low) / 2. A diferença é não negativa e está dentro da faixa, e o resultado nunca excede high. Essas condições são importantes; a fórmula não é uma demonstração geral para extremos com sinal arbitrários.

Contadores de laço também precisam de faixa. Um short de 16 bits não consegue chegar a 40000, portanto um laço que o use para contar até esse ponto não pode terminar como pretendido. Use um tipo de contador que possa representar o limite e qualquer atualização final.

Use aritmética com verificação de estouro e testes

C23 fornece <stdckdint.h> com ckd_add, ckd_sub e ckd_mul. Por exemplo, ckd_add(&r, a, b) armazena um resultado e retorna verdadeiro quando a resposta matemática não cabe em r. GCC e clang também fornecem __builtin_add_overflow, __builtin_sub_overflow e __builtin_mul_overflow como extensões do compilador.

Avisos detectam alguns estouros de constantes e erros envolvendo valores com e sem sinal. No GCC ou clang, -fsanitize=undefined -g pode relatar um estouro com sinal alcançado durante um teste; -ftrapv solicita interrupções para estouros em adição, subtração e multiplicação com sinal. Teste zero, um, menos um, a maior entrada esperada e valores nos limites e logo abaixo deles. Uma ferramenta de teste verifica os caminhos que você executa, portanto mantenha no código o raciocínio sobre as faixas.

Principais conclusões

  • Converta um operando para um tipo de maior faixa antes de uma operação arriscada.
  • Use verificações prévias que considerem o sinal na adição com sinal.
  • A verificação prévia por divisão para multiplicação exige operandos positivos.
  • A subtração sem sinal ainda precisa de uma verificação de faixa.
  • A fórmula mais segura do ponto médio exige extremos não negativos e ordenados.
  • A aritmética com verificação de estouro informa se o resultado cabe no destino.
  • Testes de valores-limite e sanitizadores complementam o raciocínio sobre faixas.

Exercícios resolvidos de Primeiros programas em C

6 Lições Interativas

1

Compile uma saudação e diagnostique uma edição malsucedida

9 passos
Acompanhe um primeiro programa do código-fonte à execução e use diagnósticos e um rastreamento para corrigir problemas.
  1. 1
    IntroduçãoAcompanhe a compilação

    Um programa em C salvo é texto. Para executá-lo, é necessário um executável produzido a partir desse texto. Mantenha claras as funções dos dois arquivos: uma edição altera o código-fonte, enquanto uma compilação bem-sucedida substitui o executável.

    Você vai compilar uma saudação curta, examinar o que cada etapa faz e diagnosticar uma edição malsucedida. O hábito central é verificar o resultado da compilação antes de usar uma execução como evidência sobre o novo código-fonte.

  2. 2
    ExplicaçãoLeia a estrutura do programa

    O programa começa em main:

    #include <stdio.h>
    int main(void)
    {
        printf("Ready\n");
        return 0;
    }
    

    O cabeçalho fornece a declaração de printf. O corpo imprime uma linha e depois retorna sucesso. Aqui, void significa que main não tem parâmetros; int nomeia o tipo de seu valor de retorno. As chaves delimitam o corpo, e instruções simples terminam com ponto e vírgula.

  3. 3
    ExemploCompile e execute

    Salve o código-fonte como ready.c. Na pasta dele, digite:

    gcc -std=c17 -Wall -Wextra ready.c -o ready
    ./ready
    

    A compilação seleciona C17 e avisos úteis e dá ao executável o nome ready. A execução imprime Ready seguido de uma nova linha. No Linux ou macOS, ./ seleciona a pasta atual. Você pode usar clang na mesma posição do comando; executáveis do Windows usam o sufixo .exe.

  4. 4
    ConexãoVeja onde entra o código-objeto

    O pré-processamento trata a diretiva de inclusão. Em seguida, a compilação produz linguagem de montagem, e a montagem produz código-objeto. A ligação resolve referências e conclui o executável; o código de biblioteca pode ser fornecido por uma biblioteca compartilhada em tempo de execução.

    As opções -E, -S e -c param em etapas progressivamente posteriores. Em particular, -c para em um arquivo-objeto: a etapa de ligação necessária para o executável ainda não foi concluída.

  5. 5
    DesafioPreveja a próxima execução

    Você compila a saudação e depois salva uma edição que muda “Ready” para “Begin”. Você executa o executável existente sem compilar. Qual texto aparece e como você o altera?

    ---reveal---

    Ele ainda imprime Ready. O executável reflete a compilação bem-sucedida anterior. Compile a edição salva e execute o novo executável para imprimir Begin. Salvar apenas o código-fonte não atualiza o código de máquina.

  6. 6
    ExemploCorrija o primeiro erro

    Uma edição deixa esta linha sem ponto e vírgula:

    printf("Begin\n")
    return 0;
    

    O compilador pode apontar para a primeira linha ou para return, onde percebe a falta do encerramento da instrução de expressão. Acrescente o ponto e vírgula após a chamada e compile novamente. Comece por essa primeira causa mesmo que mensagens posteriores mencionem outras linhas; um único erro de sintaxe pode atrapalhar a leitura do código seguinte pelo compilador.

  7. 7
    DicaTrate avisos como evidências úteis

    Um aviso geralmente permite que a compilação continue, mas pode identificar um erro grave. printf("%d\n", 2.5); passa um double onde o formato exige int, o que tem comportamento indefinido.

    Corrija a incompatibilidade de tipo em vez de aceitar qualquer resultado impresso em uma execução de teste. Os avisos de -Wall -Wextra ajudam a revelar esses erros; -Werror faz com que interrompam a compilação. Para um cálculo incorreto, imprima valores intermediários e compare-os com um pequeno rastreamento feito à mão.

  8. 8
    DesafioDiferencie uma compilação malsucedida de uma execução incorreta

    Após uma compilação bem-sucedida da saudação, uma nova edição não compila. Executar ./ready ainda imprime uma saudação. Isso mostra que a edição é válida? O que você deve examinar em seguida?

    ---reveal---

    Não. O executável antigo pode permanecer após uma compilação malsucedida. Examine o primeiro erro do compilador, incluindo a linha anterior do código-fonte, corrija-o e compile com sucesso. Depois, execute o novo executável e compare sua saída com sua previsão. A saudação existente não diz nada sobre a edição malsucedida.

  9. 9
    RecapitulaçãoUse um ciclo de correção claro

    Mantenha cada teste ligado ao código-fonte que realmente compilou. Salve a edição, compile, leia o primeiro diagnóstico, corrija sua causa e compile novamente antes de executar.

    Se o programa compilar, mas calcular o valor errado, use um pequeno rastreamento e entradas nos limites. Se uma operação tiver comportamento indefinido, altere o código para que ela não possa ocorrer; uma saída aparentemente normal não é um resultado confiável para essa operação.

2

Escolha o armazenamento para contagens e medidas

13 passos
Rastreie atribuição e escopo e relacione faixa de inteiros e precisão de ponto flutuante a escolhas práticas de tipos.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoPergunte do que um valor precisa
  2. 2
    ExemploUm valor copiado permanece como foi copiado
  3. 3
    ExplicaçãoMantenha nomes dentro de seus blocos
  4. 4
    DesafioAcompanhe um nome oculto
  5. 5
    ExplicaçãoCompare faixas antes de escolher
  6. 6
    ExemploLeia um padrão de bits e um literal
  7. 7
    DesafioEscolha uma faixa portátil
  8. 8
    ExplicaçãoVeja por que frações decimais sofrem desvios
  9. 9
    ExemploCompare com um erro permitido
  10. 10
    DesafioDiferencie conversão de arredondamento
  11. 11
    Ideia-chaveA precisão pode falhar antes da faixa
  12. 12
    DesafioEscolha o tipo adequado à tarefa
  13. 13
    RecapitulaçãoVerifique o armazenamento de duas maneiras
3

Leia medidas e imprima resultados claros

11 passos
Faça formatos corresponderem a tipos, examine entradas parciais e acompanhe um cálculo de medidas com verificações.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoConecte valores tipados ao texto
  2. 2
    ExplicaçãoLeia o formato de saída
  3. 3
    ExemploConstrua uma exibição alinhada
  4. 4
    DesafioPreveja um inteiro preenchido com zeros
  5. 5
    ExplicaçãoLeia para os destinos corretos
  6. 6
    ExemploObserve uma conversão parcial
  7. 7
    DicaNão leia uma nova linha restante por acidente
  8. 8
    DesafioRastreie o que uma leitura malsucedida preserva
  9. 9
    ExemploVerifique o domínio do cálculo
  10. 10
    DesafioCombine a leitura e a exibição
  11. 11
    RecapitulaçãoUse duas verificações na entrada
4

Leia uma expressão dos tipos aos efeitos

17 passos
Acompanhe divisão, conversões, condições lógicas de proteção, máscaras, agrupamento e atualizações em uma ordem definida.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoAnote valores e tipos
  2. 2
    ExemploDivida uma quantidade em partes inteiras
  3. 3
    ExplicaçãoDiferencie os dois incrementos
  4. 4
    DesafioRastreie uma atualização composta
  5. 5
    ExemploColoque o operando de ponto flutuante cedo o suficiente
  6. 6
    Ideia-chaveValores pequenos sem sinal podem ser calculados como int com sinal
  7. 7
    DesafioAcompanhe um valor com sinal na aritmética sem sinal
  8. 8
    ExplicaçãoUna comparações completas
  9. 9
    ExemploFaça a proteção ser executada primeiro
  10. 10
    ExemploAltere uma flag sem afetar as outras
  11. 11
    DesafioCompare valores lógicos com bits em comum
  12. 12
    ExplicaçãoAgrupe antes de rastrear
  13. 13
    DicaColoque a máscara dentro da comparação
  14. 14
    ConexãoEscolha um valor sem avaliar ambos
  15. 15
    DesafioParênteses podem corrigir duas atualizações?
  16. 16
    DesafioCombine agrupamento com um teste de faixa
  17. 17
    RecapitulaçãoExamine uma expressão em três etapas
5

Rastreie decisões e construa um total acumulado

18 passos
Acompanhe ramos, um menu com switch, entrada com sentinela e contagem aninhada com saídas deliberadas.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoAcompanhe o caminho realmente seguido
  2. 2
    ExplicaçãoUse o primeiro ramo verdadeiro
  3. 3
    DicaChaves e igualdade são importantes
  4. 4
    DesafioEncontre a primeira faixa correspondente
  5. 5
    ExplicaçãoSelecione uma entrada de comando
  6. 6
    ExemploCompartilhe uma ação deliberadamente
  7. 7
    DesafioEncontre o destino de break
  8. 8
    ExplicaçãoLeia antes de usar a entrada
  9. 9
    ExemploRastreie o cálculo com sentinela
  10. 10
    DesafioTrate uma parada imediata
  11. 11
    ConexãoSolicite uma vez com do-while
  12. 12
    ExplicaçãoConte exatamente as visitas pretendidas
  13. 13
    ExemploMantenha um total entre passagens
  14. 14
    DicaEvite contadores que não alcançam a saída
  15. 15
    ExemploConte as linhas separadamente
  16. 16
    DesafioRastreie uma saída antecipada aninhada
  17. 17
    DesafioCombine uma variável de controle e um laço de menu
  18. 18
    RecapitulaçãoVerifique cada caminho para a próxima passagem
6

Torne um produto seguro antes de calculá-lo

12 passos
Diferencie as regras de estouro, amplie a faixa no ponto correto e verifique casos-limite sem causar a falha testada.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoExamine a aritmética antes do armazenamento
  2. 2
    ExemploEncontre a operação de menor faixa
  3. 3
    ExplicaçãoO resultado sem sinal é definido, mas pode estar errado
  4. 4
    DesafioUma conversão explícita tardia corrige o produto?
  5. 5
    ExemploAmplie a faixa da própria operação
  6. 6
    ExplicaçãoTeste a adição sem somar
  7. 7
    ExemploTeste um produto positivo por divisão
  8. 8
    DesafioVerifique o limite corretamente
  9. 9
    Ideia-chaveUma resposta segura pode ocultar uma soma insegura
  10. 10
    DicaUse ferramentas e teste os limites
  11. 11
    DesafioVerifique um projeto completo de faixas
  12. 12
    RecapitulaçãoFaça a verificação antes da operação

Questões de Primeiros programas em C

50 Questões de Prática em 5 conjuntos

Conjunto 1: Compile programas e acompanhe valores armazenados10 Questões de Prática

Questão 1 de 10

Um programa foi compilado com sucesso; depois, sua saudação foi editada e salva. Executar o arquivo executável existente ainda imprime a saudação antiga. Qual ação faz a edição entrar em vigor?
  1. Executar novamente o mesmo executável.
  2. Alterar o nome do arquivo executável.
  3. Compilar novamente o código-fonte salvo.
  4. Acrescentar outra nova linha à saudação.
Mostrar respostaOcultar resposta
Resposta: C
O executável contém o resultado da compilação anterior. Salvar o texto do código-fonte não recompila esse arquivo, portanto compile o código-fonte editado e depois execute o novo executável. Executar novamente o antigo usa o mesmo código de máquina anterior e não incorpora a saudação alterada.

Questão 2 de 10

Um compilador relata a falta de um ponto e vírgula, seguida de vários erros em linhas posteriores. O que você deve corrigir primeiro?
  1. O primeiro problema de sintaxe relatado.
  2. O último problema de sintaxe relatado.
  3. Todas as linhas posteriores antes de recompilar.
  4. A entrada de execução do executável.
Mostrar respostaOcultar resposta
Resposta: A
Comece pelo primeiro erro e examine a linha indicada e a anterior. A falta de um ponto e vírgula pode confundir a interpretação do compilador sobre instruções posteriores. Corrigir todas as linhas posteriores primeiro pode alterar código correto que só foi apontado por causa do erro anterior.

Questão 3 de 10

Quais valores são impressos por este código?

int units = 6;
int total = units * 4;
units = 9;
printf("%d %d\n", units, total);
  1. 9 36
  2. 6 24
  3. 6 36
  4. 9 24
Mostrar respostaOcultar resposta
Resposta: D
A declaração de total calcula seis vezes quatro uma vez e armazena 24. A atribuição posterior altera units para nove, mas não recalcula total, portanto a saída é 9 e 24. Esperar 36 trata a atribuição anterior como uma fórmula que mantém as duas variáveis ligadas.

Questão 4 de 10

Um valor deve comportar resultados negativos e contagens positivas de até 6.000.000.000 tanto em sistemas típicos Linux quanto Windows de 64 bits. Qual tipo inteiro padrão fornece a faixa necessária?
  1. int
  2. long long
  3. unsigned int
  4. long
Mostrar respostaOcultar resposta
Resposta: B
Um long long padrão tem pelo menos 64 bits e pode representar esses valores positivos e negativos. Um long típico do Windows tem apenas 32 bits, mesmo quando o próprio Windows é de 64 bits. Unsigned int também tem faixa insuficiente nesses sistemas e não pode representar valores negativos.

Questão 5 de 10

Qual valor decimal o literal inteiro C17 014 representa?
  1. 14
  2. 10
  3. 12
  4. 16
Mostrar respostaOcultar resposta
Resposta: C
O zero inicial torna 014 um literal octal. Seus algarismos representam um grupo de oito mais quatro, resultando em doze. Lê-lo como o decimal quatorze ignora o prefixo de base; um literal decimal para quatorze é escrito como 14, sem zero inicial.

Questão 6 de 10

Um double calculado difere muito pouco de um double de referência após várias operações decimais. Qual comparação é adequada a uma medida que permite um pequeno erro absoluto?
  1. Comparar a diferença absoluta com o erro permitido.
  2. Comparar os valores impressos na precisão máxima.
  3. Converter ambos para int antes de testar a igualdade.
  4. Exigir igualdade exata após atribuir ambos a double.
Mostrar respostaOcultar resposta
Resposta: A
Um teste de diferença absoluta como fabs(actual - target) < tolerance verifica o erro permitido pela medida. A igualdade exata pode falhar porque cálculos decimais introduzem diferenças de arredondamento binário. Converter para int descarta frações e pode ocultar uma diferença muito maior que o erro permitido.

Questão 7 de 10

O que é impresso entre as barras verticais por printf("|%04d|", 23);?
  1. | 23|
  2. |2300|
  3. |23 |
  4. |0023|
Mostrar respostaOcultar resposta
Resposta: D
A largura solicita pelo menos quatro caracteres e a opção de zero fornece zeros iniciais, portanto 23 torna-se 0023. O alinhamento comum à direita usaria dois espaços iniciais, mas esse é o comportamento de %4d sem a opção de zero. A largura não acrescenta zeros ao valor.

Questão 8 de 10

Dado int a = 2, b = 9;, uma chamada a scanf("%d:%d", &a, &b) recebe “17 42”. Qual é o valor de retorno e quais valores são armazenados?
  1. Retorno 2; a = 17, b = 42.
  2. Retorno 1; a = 17, b = 9.
  3. Retorno 0; a = 2, b = 9.
  4. Retorno 1; a = 17, b = 0.
Mostrar respostaOcultar resposta
Resposta: B
O primeiro %d armazena 17. Em seguida, a entrada tem um espaço onde o formato exige dois-pontos literais, portanto a correspondência para e b mantém seu valor antigo, nove. A contagem de retorno é um; espaços em branco entre números não podem substituir a pontuação exigida pelo formato.

Questão 9 de 10

Em um sistema com unsigned char de 8 bits e int de 32 bits, o que isto imprime?

unsigned char a = 230, b = 40;
int sum = a + b;
unsigned char small = sum;
printf("%d %u\n", sum, (unsigned)small);
  1. 14 14
  2. 270 270
  3. 14 270
  4. 270 14
Mostrar respostaOcultar resposta
Resposta: D
Ambos os valores de caractere são promovidos a int antes da adição, portanto sum recebe 270 sem redução modular em 255. A conversão para small reduz então 270 módulo 256, resultando em 14. Prever 14 para ambos os valores aplica à adição promovida a pequena faixa de armazenamento, à qual ela não pertence.

Questão 10 de 10

O comando gcc -std=c17 -c sensor.c é bem-sucedido. O que ele produziu e qual etapa de geração ainda é necessária antes da execução normal?
  1. Um executável; ainda é necessário pré-processar.
  2. Texto em montagem; ainda é necessário montar.
  3. Um arquivo-objeto; ainda é necessário ligar.
  4. C pré-processado; ainda é necessário compilar.
Mostrar respostaOcultar resposta
Resposta: C
A opção -c compila e monta o código-fonte em código-objeto, normalmente sensor.o, e para antes da ligação. A ligação ainda é necessária para criar o executável e resolver referências externas. O texto em linguagem de montagem é a saída anterior selecionada por -S, não por -c.
Conjunto 2: Leia entradas e calcule com tipos de C10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Uma variável é declarada como double distance_m;. Qual conversão e argumento de scanf correspondem ao seu tipo?
  1. scanf("%f", &distance_m)
  2. scanf("%lf", &distance_m)
  3. scanf("%lf", distance_m)
  4. scanf("%d", &distance_m)
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Quanto valem -23 / 6 e -23 % 6 em C17?
  1. −4 e 1
  2. −3 e 5
  3. −4 e −5
  4. −3 e −5
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Para int distance = 19, time = 4;, qual expressão calcula uma velocidade double que mantém a parte fracionária?
  1. (double)distance / time
  2. (double)(distance / time)
  3. distance / time
  4. (int)(distance / (double)time)
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Uma leitura de int com %d aceita a entrada “46kg”. Qual valor é armazenado e qual entrada permanece sem leitura imediatamente após a conversão?
  1. 46; nada permanece.
  2. 0; “46kg” permanece.
  3. 46; “kg” permanece.
  4. Nenhum valor; “kg” permanece.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Para valores int não negativos total e count, qual condição verifica se a média inteira é maior que 5 sem dividir por zero?
  1. total / count > 5 && count != 0
  2. count != 0 && total / count > 5
  3. count != 0 & total / count > 5
  4. count == 0 || total / count > 5
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Qual valor 2 + 5 * 3 % 7 produz?
  1. 1
  2. 0
  3. 7
  4. 3
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Qual texto printf("|%6.1f|", 8.375); imprime com arredondamento comum para o décimo mais próximo?
  1. | 8.375|
  2. |8.4 |
  3. | 8.4|
  4. |0008.4|
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Quais valores são impressos?

int n = 8;
int a = n++;
int b = ++n;
a *= b % 4;
printf("%d %d %d\n", a, b, n);
  1. 16 10 10
  2. 18 10 10
  3. 16 9 10
  4. 24 11 11
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Com int e unsigned int de 32 bits, o que isto imprime?

int delta = -3;
unsigned int limit = 2;
printf("%d %u\n", delta < limit, limit + delta);
  1. 1 4294967295
  2. 1 -1
  3. 0 1
  4. 0 4294967295
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Um valor sem sinal é flags = 10u. Qual valor resulta da ativação do bit 2, contando o bit menos significativo como bit 0?
  1. 12
  2. 14
  3. 8
  4. 2
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 3: Rastreie ramos, switches e laços10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Uma pontuação é 76. Qual caractere é atribuído?

if (score >= 70) grade = 'C';
else if (score >= 85) grade = 'B';
else grade = 'F';
  1. C
  2. B
  3. F
  4. Nenhum caractere é atribuído.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

O que é impresso quando mode é 2?

switch (mode) {
case 1: printf("A"); break;
case 2: printf("B");
case 3: printf("C"); break;
default: printf("D");
}
  1. B
  2. BCD
  3. BC
  4. ABC
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Quais são os valores finais de n e passes?

int n = 45, passes = 0;
while (n > 2) {
    n /= 2;
    passes++;
}
  1. n = 1, passes = 5
  2. n = 2, passes = 5
  3. n = 3, passes = 4
  4. n = 2, passes = 4
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Quantas passagens são executadas em for (int i = 4; i < 19; i += 4)?
  1. 3
  2. 4
  3. 5
  4. 15
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

O que é impresso?

int level = 2;
if (level = 0) {
    printf("Low");
} else {
    printf("Other");
}
  1. Low
  2. LowOther
  3. Nada é impresso.
  4. Other
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Qual total é impresso?

int total = 0;
for (int r = 1; r <= 4; r++) {
    for (int c = 1; c <= r; c++) {
        if (c == 3) break;
        total += c;
    }
}
printf("%d\n", total);
  1. 10
  2. 7
  3. 13
  4. 20
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

O corpo de um laço deve fazer uma solicitação uma vez antes de poder testar a primeira resposta. Qual forma de laço expressa diretamente essa ordem?
  1. while com teste antes do corpo
  2. do–while
  3. for com teste antes do corpo
  4. switch com rótulo default
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Qual soma resulta?

int sum = 0;
for (int i = 2; i <= 8; i++) {
    if (i % 3 == 0) continue;
    sum += i;
}
  1. 35
  2. 9
  3. 26
  4. 8
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Quantas vezes “tick” é impresso?

int running = 1, passes = 0;
while (running && passes < 3) {
    switch (passes) {
    case 1: break;
    default: printf("tick\n");
    }
    passes++;
}
  1. 1
  2. 3
  3. 0
  4. 2
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

A entrada é “9 4 0 7”. Qual contagem e qual média são impressas?

int x, sum = 0, count = 0;
while (scanf("%d", &x) == 1 && x != 0) {
    sum += x;
    count++;
}
if (count) printf("%d %.1f\n", count, (double)sum / count);
  1. 2 6.5
  2. 3 4.3
  3. 3 6.7
  4. 4 5.0
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 4: Diagnostique conversões e falhas de faixa10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Qual valor 0u - 1u produz quando unsigned int tem 32 bits de valor?
  1. −1
  2. 2.147.483.647
  3. 4.294.967.295
  4. 0
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Com os valores int de 32 bits a = 60000 e b = 40000, qual expressão calcula com segurança seu produto exato em um long long típico de 64 bits?
  1. (long long)(a * b)
  2. (long long)a * b
  3. a * b
  4. (double)(a * b)
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Para flags = 6u sem sinal, qual resultado flags & 2u == 0u produz?
  1. 1
  2. 2
  3. 6
  4. 0
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Para valores int arbitrários a e b, qual teste detecta estouro na adição com sinal antes de avaliar a + b?
  1. (b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b)
  2. (b > 0 && a > INT_MAX - b) || (b < 0 && a > INT_MIN - b)
  3. (b > 0 && a >= INT_MAX - b) || (b < 0 && a <= INT_MIN - b)
  4. (b > 0 && a < INT_MAX - b) || (b < 0 && a < INT_MIN - b)
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Em uma plataforma com unsigned short de 16 bits e int de 32 bits, duas variáveis unsigned short contêm 48000 cada uma. O que acontece quando seu produto é avaliado sem conversões explícitas?
  1. São multiplicadas sem sinal e reduzidas módulo 65536.
  2. São promovidas a unsigned int e reduzidas módulo 2³².
  3. São promovidas a int, cujo produto estoura.
  4. São promovidas a long long e produzem o produto exato.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Um programa C17 soma um a um int que já é igual a INT_MAX. Em qual resultado um código C portátil pode confiar?
  1. O resultado deve ser INT_MIN em todo sistema.
  2. O resultado deve permanecer igual a INT_MAX.
  3. O resultado deve ser convertido para unsigned int.
  4. Nenhum resultado é garantido pela linguagem.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Dois extremos int não negativos satisfazem low <= high. Em um sistema com int de 32 bits, eles são 1.800.000.000 e 2.000.000.000. Qual expressão calcula seu ponto médio sem estourar?
  1. (low + high) / 2
  2. low + (high - low) / 2
  3. low + high / 2
  4. low / 2 + high
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Um programa C17 converte o valor double representável −6,875 para int. Qual valor é armazenado?
  1. −6
  2. −7
  3. 6
  4. −6,875
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Um acumulador de fatorial int positivo é f, e o próximo multiplicador é um int positivo i. Qual condição identifica, antes da multiplicação, que f * i excederia INT_MAX?
  1. f >= INT_MAX / i
  2. f / i > INT_MAX
  3. f > INT_MAX / i
  4. f * i > INT_MAX
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Um cálculo int fornece a saída esperada em uma compilação, mas gera um relatório de estouro com sinal com -fsanitize=undefined. Qual conclusão deve orientar a correção?
  1. A compilação bem-sucedida comprova que o resultado é portátil.
  2. O relatório pode ser corrigido com uma conversão printf de maior faixa.
  3. O relatório garante que o int foi reduzido a um valor específico.
  4. O estouro alcançado deve ser evitado no cálculo.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 5: Combine entrada, controle de fluxo e aritmética segura10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Um programa solicita duas medidas double em uma chamada a scanf. Qual valor de retorno confirma que ambas as atribuições foram concluídas?
  1. 0
  2. 2
  3. 1
  4. EOF
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Qual valor é impresso?

int total = 0;
for (int r = 2; r <= 5; r++) {
    for (int c = 1; c < r; c++) {
        if (c % 2 == 0) continue;
        total += r;
    }
}
printf("%d\n", total);
  1. 14
  2. 18
  3. 40
  4. 23
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Um int de 32 bits days vale 30000. Qual expressão calcula corretamente sua duração em milissegundos em um long long de 64 bits?
  1. 86400000LL * days
  2. (long long)(86400000 * days)
  3. 86400000 * days
  4. (long long)(86400 * days) * 1000
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

O que este trecho em C17 garante sobre sua saída?

int n = 4;
printf("%d %d\n", n++, n++);
  1. Imprime 4 e 5 porque os argumentos são avaliados da esquerda para a direita.
  2. Imprime 5 e 4 porque os argumentos são avaliados da direita para a esquerda.
  3. Tem comportamento indefinido devido a modificações não sequenciadas.
  4. Imprime 4 e 5 ou 5 e 4 como alternativas definidas.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

O que é impresso quando a = -2 e b = 7?

if (a > 0)
    if (b > 0) printf("Both");
    else printf("A only");
  1. A only
  2. Both
  3. BothA only
  4. Nada é impresso.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Por que este laço para de avançar após imprimir zero uma vez?

int i = 0;
while (i < 4) {
    if (i == 1) continue;
    printf("%d\n", i);
    i++;
}
  1. Em um, continue sai do laço antes do próximo teste.
  2. Em um, continue ignora a atualização, então i permanece em um.
  3. Em zero, a condição impede a primeira atualização.
  4. Em quatro, o corpo do laço redefine i como um.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Um valor de estoque sem sinal é 7u e uma quantidade solicitada é 11u. Qual regra evita uma redução modular no estoque e preserva uma subtração válida quando há estoque suficiente?
  1. Subtrair apenas quando stock >= requested.
  2. Subtrair e rejeitar se o novo estoque for negativo.
  3. Subtrair apenas quando stock <= requested.
  4. Subtrair e exigir que o novo estoque seja diferente de zero.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Com unsigned int de 32 bits, qual valor este código deixa em flags?

unsigned int flags = 13u;
flags &= ~(1u << 2);
flags |= 1u << 1;
  1. 15
  2. 9
  3. 11
  4. 7
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Um menu começa com running = 1 e actions = 0. A entrada é “s q s”. Quanto vale actions após este laço?

char cmd;
while (running && scanf(" %c", &cmd) == 1) {
    switch (cmd) {
    case 'q': running = 0; break;
    case 's': actions++; break;
    }
}
  1. 0
  2. 1
  3. 2
  4. 3
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Qual valor é atribuído a rate?

int completed = 14, planned = 20;
double rate = completed / planned * 100.0;
  1. 70,0
  2. 0,7
  3. 14,0
  4. 0,0
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Travou em uma questão sua? Ajuda com Tarefas

Termos-Chave

91 Termos-Chave, cada um com sua definição

A

Acumulador
Um acumulador mantém um resultado acumulado ao longo das passagens de um laço, começando com um valor inicial adequado fora da tarefa repetida.
O acumulador da soma de algarismos começa em zero e soma um algarismo extraído a cada passagem do laço.
Aritmética com verificação de estouro
A aritmética com verificação de estouro calcula e informa se o resultado matemático exato cabe no tipo de destino escolhido.
A operação de aritmética com verificação de estouro ckd_mul de C23 pode informar que um produto não cabe em seu destino.
Armazenamento da saída em buffer
O armazenamento da saída em buffer retém temporariamente o texto produzido antes de enviá-lo, o que pode atrasar uma solicitação sem nova linha.
Uma solicitação pode aguardar no buffer de saída até que fflush(stdout) a envie antes de uma chamada de entrada.
Associatividade
A associatividade determina como operadores no mesmo nível de precedência são agrupados, sem geralmente determinar a ordem de avaliação.
A associatividade da divisão da esquerda para a direita agrupa 24 / 4 / 2 como (24 / 4) / 2, resultando em três.
Atribuição
Uma atribuição calcula um valor e o armazena em um destino existente, convertendo-o para o tipo do destino.
A atribuição count = count + 1; lê o valor antigo e o substitui por um valor uma unidade maior.
Atribuição composta
Uma atribuição composta combina um operador com o armazenamento de seu resultado novamente no destino à esquerda.
A atribuição composta total += item; atualiza o total acumulado sem alterar item.
Avaliação de curto-circuito
A avaliação de curto-circuito ignora o operando direito de um operador lógico quando o operando esquerdo já determina o resultado.
A avaliação de curto-circuito em count != 0 && sum / count > 4 ignora a divisão quando count é zero.
Aviso
Um aviso é um diagnóstico que sinaliza um provável problema e geralmente permite que a compilação continue.
Um aviso de formato pode revelar que %d foi associado a um argumento double, uma combinação inválida.

B

Bloco de instruções
Um bloco de instruções agrupa declarações e instruções entre chaves para que possam servir como um único corpo controlado.
Um bloco de instruções mantém uma mensagem e uma atualização de contador juntas no mesmo ramo condicional.

C

Cadeia de formatação
Uma cadeia de formatação combina texto literal e especificações de conversão que controlam como uma chamada de entrada ou saída trata valores.
A cadeia de formatação em printf("Mass: %.1f kg\n", mass) fornece um rótulo, precisão decimal e uma unidade.
Caracteres de espaço em branco
Caracteres de espaço em branco incluem espaços, tabulações e novas linhas que os formatos podem ignorar ou ler, dependendo da conversão.
Conversões numéricas de scanf ignoram caracteres de espaço em branco iniciais, mas %c lê uma nova linha restante, a menos que seja instruído a ignorá-la.
Código de saída
Um código de saída é o valor que um programa informa ao ambiente que o chamou quando termina, indicando sucesso ou falha.
Um código de saída zero de main permite que um script chamador reconheça a conclusão bem-sucedida.
Código-fonte
Código-fonte é o texto em C legível por pessoas que um compilador traduz em instruções usadas para gerar um programa.
Após alterar o texto de saudação que um programa imprime em seu código-fonte, compile o programa novamente antes de executar a nova versão.
Compilador
Um compilador traduz um programa de uma linguagem de origem para outra forma, como linguagem de montagem ou código de máquina.
O compilador verifica uma declaração em C e relata a falta de um ponto e vírgula antes de criar um novo executável.
Complemento de dois
Complemento de dois é uma representação binária com sinal na qual o bit mais significativo tem peso negativo.
Em complemento de dois de oito bits, o padrão 10000000 representa −128.
Comportamento definido pela implementação
Comportamento definido pela implementação é uma escolha que a implementação de C deve documentar quando o padrão permite vários comportamentos.
A conversão de inteiro fora da faixa para tipo com sinal em C17 tem comportamento definido pela implementação ou gera um sinal.
Comportamento indefinido
Comportamento indefinido ocorre quando um programa em C realiza uma operação para a qual a linguagem não impõe requisitos.
Um produto com sinal que estoura tem comportamento indefinido, mesmo que uma execução sem otimização pareça sofrer redução modular.
Condição
Uma condição é uma expressão cujo valor zero ou diferente de zero decide qual caminho de controle de fluxo será seguido.
A condição em if (items) é falsa para zero, enquanto uma contagem negativa diferente de zero executa o ramo.
Constante de caractere
Uma constante de caractere é uma forma de caractere entre aspas simples cujo valor é um código inteiro; constantes de caractere comuns em C têm tipo int.
A constante de caractere ASCII 'A' pode ser impressa como a letra A com %c ou como o número 65 com %d.
Contagem de atribuições
A contagem de atribuições retornada por scanf é o número de destinos preenchidos com sucesso antes de a chamada parar.
Uma contagem de atribuições igual a dois em uma leitura de três medidas deixa o terceiro destino sem confirmação.
Continuação entre casos
Continuação entre casos é a execução que prossegue de um caso de switch para o código seguinte sem uma transferência de controle intermediária.
A continuação entre casos após um break ausente pode executar a impressão do caso seguinte sem testar seu rótulo novamente.
Conversão explícita de tipo
Uma conversão explícita de tipo solicita explicitamente a conversão para um tipo nomeado, usando esse tipo entre parênteses antes de uma expressão.
A conversão explícita em (double)sum / count ocorre antes da divisão, preservando um quociente fracionário.
Conversão fora da faixa representável
Uma conversão fora da faixa representável tenta representar um valor de origem fora dos valores permitidos pelo tipo de destino.
Uma conversão fora da faixa representável de um double enorme para int é indefinida quando a parte inteira não cabe no tipo.
Conversão implícita
Uma conversão implícita altera automaticamente o tipo de um valor quando uma operação ou atribuição exige um tipo diferente.
Somar um int a um double aplica uma conversão implícita ao operando inteiro antes da adição.
Conversão para um tipo de maior faixa
A conversão para um tipo de maior faixa converte um valor para um tipo com faixa representável suficiente antes que ele participe de um cálculo arriscado.
Converter a para long long antes de a * b altera o tipo do produto; converter explicitamente após o produto é tarde demais.
Conversões aritméticas usuais
As conversões aritméticas usuais escolhem tipos comuns para operandos em muitas operações aritméticas e de comparação.
As conversões aritméticas usuais transformam um int negativo em sem sinal quando ele é comparado com unsigned int.

D

Declaração
Uma declaração introduz um nome e seu tipo para que C possa verificar como o objeto ou a função nomeada é usada.
A declaração int count = 0; dá ao nome um tipo inteiro e também fornece seu valor inicial.
Deslocamento de bits
Um deslocamento de bits move o padrão de bits de um inteiro para a esquerda ou direita por uma quantidade sujeita às regras de largura e sinal do tipo promovido.
Deslocar um valor sem sinal duas posições à direita descarta os dois bits menos significativos de sua representação.
Divisão inteira
A divisão inteira divide operandos inteiros e descarta a parte fracionária do quociente em direção a zero.
A divisão inteira em 22 / 5 resulta em quatro mesmo quando o resultado é atribuído a um double.

E

Efeito colateral
Um efeito colateral é uma alteração, como modificar dados armazenados ou realizar entrada ou saída durante a avaliação de uma expressão.
Um incremento altera uma variável como efeito colateral, portanto dois incrementos conflitantes precisam de uma ordem definida.
else pendente
Um else pendente é um else em condicionais aninhadas cuja associação pode não estar clara para o leitor sem chaves.
A regra do else pendente associa um else ao if sem else mais próximo, independentemente da indentação.
Entrada padrão
A entrada padrão é o fluxo usual de texto recebido pelo programa, frequentemente conectado ao teclado ou a uma entrada redirecionada.
Uma chamada a scanf lê da entrada padrão e converte os próximos caracteres correspondentes em valores armazenados.
Erro de arredondamento
Um erro de arredondamento é a diferença introduzida quando um valor ou cálculo é substituído por um valor representável próximo.
Várias adições decimais podem produzir um pequeno erro de arredondamento que faz um teste de igualdade exata falhar.
Erro de compilação
Um erro de compilação é um problema detectado durante a tradução do código-fonte que impede a geração bem-sucedida de uma nova versão.
Um parêntese de fechamento sem par causa um erro de compilação antes que um novo executável possa ser gerado.
Erro de lógica
Um erro de lógica faz um programa calcular ou escolher algo diferente do resultado pretendido, mesmo que ele seja executado.
Um teste de faixas de pontuação na ordem errada cria um erro de lógica que atribui a nota incorreta.
Erro de uma unidade
Um erro de uma unidade faz um laço ou cálculo de limite incluir um valor a mais ou a menos.
Substituir i < n por i <= n em um laço de contagem iniciado em zero causa um erro de uma unidade.
Escopo de bloco
O escopo de bloco torna um nome local disponível desde sua declaração até o fim do bloco que o contém.
O escopo de bloco impede o uso de uma variável temporária declarada dentro de um bloco if após o término desse bloco.
Especificação de conversão
Uma especificação de conversão é uma parte de um formato que começa com porcentagem e identifica o tipo exigido e a apresentação de um valor.
A especificação de conversão %lld corresponde a um argumento long long; %d não pode convertê-lo automaticamente.
Estouro de inteiro
Estouro de inteiro ocorre quando o resultado matemático de uma operação aritmética inteira está fora da faixa do tipo do resultado.
Um produto int pode causar estouro de inteiro antes que seu resultado chegue a um destino long long.
Executável
Um executável é um programa gerado em uma forma que o sistema operacional de destino pode carregar e executar no processador pretendido.
Executar ./hello inicia o executável na pasta atual, sem invocar novamente o compilador C.

F

Falha de correspondência
Uma falha de correspondência ocorre quando a entrada disponível não corresponde à conversão ou ao caractere literal esperado por scanf.
Letras no início de uma conversão %d causam falha de correspondência e permanecem sem leitura para uma nova tentativa.
Fim da entrada
Fim da entrada significa que o fluxo de entrada não tem mais caracteres disponíveis para leitura, diferentemente de um texto comum sem correspondência.
Um laço de entrada numérica para no fim da entrada, mas também pode parar antes ao encontrar texto não numérico.

I

Identificador
Um identificador é um nome usado em código C, como o nome de uma variável, com regras de escrita e distinção entre maiúsculas e minúsculas.
O identificador distance_m é válido; 2distance não pode ser um nome porque começa com um algarismo.
Incremento pós-fixado
Um incremento pós-fixado soma um a uma variável enquanto produz seu valor antigo para uso na expressão em que aparece.
Com n = 3, o incremento pós-fixado em old = n++; atribui três a old e deixa n em quatro.
Inicialização
Inicialização dá a um objeto seu valor inicial como parte de sua declaração, antes que atribuições posteriores o alterem.
A inicialização de total com zero antes de um laço fornece à primeira adição um valor inicial definido.
Instrução break
Uma instrução break sai imediatamente do switch ou laço mais próximo que a contém e continua após essa estrutura.
Uma instrução break dentro de um switch de menu sai desse switch enquanto o laço while ao redor continua.
Instrução continue
Uma instrução continue ignora o restante do corpo do laço e segue para o próximo ponto de continuação do laço.
Uma instrução continue em um laço for alcança a atualização do cabeçalho; em um laço while, retorna ao teste.
Instrução switch
Uma instrução switch escolhe um rótulo de entrada comparando um valor inteiro de controle com valores inteiros fixos de casos.
Uma instrução switch pode atribuir a mesma ação aos caracteres de comando r minúsculo e R maiúsculo.
Inteiro com sinal
Um inteiro com sinal é um tipo de número inteiro que representa valores negativos e não negativos dentro de uma faixa fixa.
Um inteiro com sinal pode armazenar uma contagem negativa de temperatura, mas as adições posteriores ainda devem caber em sua faixa.
Inteiro sem sinal
Um inteiro sem sinal representa apenas números inteiros não negativos e usa redução modular quando a aritmética excede sua faixa.
Um inteiro sem sinal funciona bem como máscara de bits, mas subtrair uma quantidade excessiva do estoque pode causar redução modular para um valor enorme.
Iteração
Uma iteração é uma execução do corpo de um laço, após seu teste de controle permitir que essa passagem comece.
Um laço for que visita 3, 6, 9 e 12 tem quatro iterações, embora a atualização final do contador chegue a 15.

L

Laço aninhado
Um laço aninhado é um laço dentro de outro, cuja sequência interna é executada novamente a cada passagem externa alcançada.
Um laço aninhado que faz quatro passagens internas para cada uma de três linhas executa seu corpo interno doze vezes.
Laço do-while
Um laço do-while executa seu corpo antes de verificar sua condição de repetição, garantindo pelo menos uma execução do corpo.
Um laço do-while lê em seu corpo a primeira resposta a ser verificada quanto à faixa antes de decidir se deve solicitá-la novamente.
Laço for
Um laço for coloca inicialização, condição de repetição e atualização em um único cabeçalho associado a um corpo repetido.
Um laço for que começa em zero com i < n e i++ faz n passagens para n não negativo quando as atualizações cabem no tipo.
Laço infinito
Um laço infinito se repete sem alcançar uma saída, muitas vezes porque seu estado testado nunca muda como pretendido.
Um continue antes da única atualização pode criar um laço infinito que continua testando um valor inalterado.
Laço while
Um laço while repete seu corpo enquanto uma condição for verdadeira, verificando essa condição antes de cada execução do corpo.
Um laço while que processa algarismos para quando a divisão inteira reduz o valor restante a zero.
Largura de campo
Uma largura de campo é o número mínimo de caracteres reservado para um valor de saída formatado, incluindo seu sinal e ponto decimal.
Uma largura de campo de seis em %6d acrescenta quatro espaços antes de 42, enquanto um valor de sete algarismos ainda é impresso por completo.
Ligador
O ligador combina código-objeto e resolve referências a código e dados nomeados para produzir um programa completo.
O ligador resolve a referência a printf; seu código de biblioteca pode ser fornecido por uma biblioteca compartilhada.
Literal inteiro
Um literal inteiro é uma constante inteira escrita no código-fonte com uma notação de base e um sufixo de tipo opcional.
O literal inteiro 0x30 usa hexadecimal, enquanto 48u escreve esse valor em decimal sem sinal.

M

Macro
Uma macro define uma substituição do pré-processador que é aplicada ao código-fonte posterior antes da compilação normal.
Com #define LIMIT 8, o token LIMIT é substituído por 8 onde quer que essa macro se aplique.
Máscara de bits
Uma máscara de bits é um padrão inteiro usado para selecionar, ativar, desativar ou examinar determinados bits em outro valor.
A máscara de bits 1u << 5 seleciona o bit cinco, contando o bit menos significativo como posição zero.

N

Não é um número (NaN)
Um NaN é um valor de ponto flutuante IEEE que significa “não é um número”, usado para resultados como zero dividido por zero.
Um NaN é considerado diferente de si mesmo em uma comparação, portanto a igualdade comum não pode tratá-lo como uma medida comum.

O

Operador bit a bit
Um operador bit a bit atua sobre bits individuais de operandos inteiros para combinar, inverter ou deslocar seus padrões de bits.
O operador bit a bit & mantém apenas os bits ativados em comum entre doze e dez, resultando em oito.
Operador condicional
O operador condicional usa condition ? a : b para escolher uma expressão de valor após testar a condição.
Com uma condição igual a zero, o operador condicional em condition ? a : b avalia b e ignora a.
Operador lógico
Um operador lógico combina ou inverte valores lógicos, produzindo zero ou um por meio de &&, || ou !.
O operador lógico em ready && valid testa se ambos os valores armazenados são diferentes de zero.
Operador relacional
Um operador relacional compara valores numéricos usando <, <=, > ou >= e produz o int zero ou um.
O operador relacional em height >= 1 verifica um limite; outra comparação verifica o outro.
Operador vírgula
O operador vírgula avalia seu operando esquerdo antes do direito e produz o valor do operando direito.
O operador vírgula ordena as atualizações em i++, j--; vírgulas entre argumentos de printf não fazem isso.
Operando
Um operando é um valor ou expressão ao qual um operador é aplicado; seu tipo ajuda a determinar como a operação se comporta.
Em sum / count, os tipos dos operandos determinam se a divisão preserva um resultado fracionário.
Ordem de avaliação
A ordem de avaliação descreve quando operandos são avaliados e efeitos colaterais ocorrem, uma questão separada do agrupamento de expressões.
Argumentos de função em C17 não têm ordem de avaliação obrigatória da esquerda para a direita, apesar de suas posições na escrita.

P

Pré-processador
O pré-processador trata diretivas como #include e a substituição de macros antes das principais etapas de tradução de C.
O pré-processador insere declarações de um cabeçalho padrão antes que o compilador verifique seu uso.
Precedência
A precedência determina quais operadores se agrupam primeiro com seus operandos quando os parênteses não especificam o agrupamento.
A precedência maior da multiplicação agrupa 4 * 2 antes da adição em 3 + 4 * 2.
Precisão
Precisão é a quantidade de informação significativa que um formato numérico pode reter, distinta de sua maior magnitude.
A precisão limitada de float perde incrementos de uma unidade inteira em magnitudes ainda muito abaixo do máximo do tipo.
Promoção inteira
Uma promoção inteira converte um tipo inteiro pequeno para int ou para unsigned int se int não puder representar toda a sua faixa.
Em um sistema com short de 16 bits e int de 32 bits, a promoção inteira permite que operandos unsigned short sejam multiplicados como int.

R

Ramo
Um ramo é um caminho de instruções selecionado por uma decisão, como a parte if ou a parte else de uma condicional.
Um programa de pontuação com uma sequência de else-if alcança apenas um ramo, a partir da primeira condição verdadeira.
Rastreamento de execução
Um rastreamento de execução registra os valores em mudança e as etapas executadas de um programa para que seu comportamento possa ser verificado em ordem.
Em um laço de soma de algarismos, um rastreamento dos algarismos restantes e da soma acumulada após cada passagem ajuda a localizar uma atualização incorreta do laço.
Redução modular sem sinal
Redução modular sem sinal é a redução de um resultado aritmético sem sinal por módulo, trazendo-o de volta à faixa não negativa do tipo.
Subtrair um de zero sem sinal demonstra a redução modular sem sinal ao produzir o valor máximo.
Resto
Um resto é o valor resultante da operação inteira % de C; um resultado diferente de zero tem o sinal do dividendo.
O resto -22 % 5 é −2 porque o quociente truncado é −4 e a identidade da divisão deve ser satisfeita.
Resultado intermediário
Um resultado intermediário é um valor calculado dentro de uma expressão maior antes de uma operação ou atribuição posterior.
A soma em (low + high) / 2 é um resultado intermediário que pode estourar mesmo quando o ponto médio cabe no tipo.
Rótulo case
Um rótulo case nomeia uma expressão constante inteira distinta que pode servir como ponto de entrada em um switch.
O rótulo case case 4: é válido em C17; uma variável const int comum não pode fornecer seu valor.
Rótulo default
Um rótulo default fornece um ponto de entrada em um switch para um valor de controle que não corresponda a nenhum rótulo case.
Um rótulo default pode encaminhar um número de menu inesperado a uma mensagem de erro em vez de ignorar o switch.

S

Sanitizador
Um sanitizador é uma ferramenta de verificação em tempo de execução adicionada durante a compilação que relata certas operações inválidas em caminhos executados.
Um sanitizador de comportamento indefinido pode identificar a multiplicação em que um cálculo de contador com sinal estoura.
Sentinela
Uma sentinela é um valor designado que sinaliza o fim dos dados e é reconhecido antes do processamento normal.
Uma sentinela zero pode encerrar um cálculo de média sem aumentar sua soma nem sua contagem de dados.
Sequência de escape
Uma sequência de escape usa uma barra invertida em um literal de caractere ou de cadeia de caracteres em C para representar um caractere especial.
A sequência de escape \n acrescenta uma nova linha dentro de uma cadeia em C; printf usa %% para um sinal de porcentagem.

T

Teste de valores-limite
Um teste de valores-limite executa valores em um limite relevante ou perto dele para verificar o comportamento onde uma regra ou faixa muda.
Um teste de valores-limite para adição inclui uma soma igual a INT_MAX e um par que o excederia em uma unidade.
Tipo inteiro de largura exata
Um tipo inteiro de largura exata é um tipo inteiro definido por typedef, quando disponível, cujo nome determina sua largura exata em bits e se tem sinal.
O tipo inteiro de largura exata uint8_t, quando fornecido, comporta valores de zero a 255.
Tolerância
Uma tolerância é uma diferença numérica permitida, escolhida de acordo com a escala e a exatidão exigidas por uma comparação.
O programa compara fabs(measured - target) com a tolerância em vez de exigir igualdade exata.
Truncamento em direção a zero
Truncamento em direção a zero remove a parte fracionária de um número, diminuindo a magnitude de um valor negativo.
O truncamento em direção a zero faz a conversão do valor representável −7,9 para int resultar em −7 em vez de −8.

V

Valor booleano
Um valor booleano representa falso ou verdadeiro; a conversão para bool em C mapeia zero para falso e todo valor diferente de zero para verdadeiro.
Após incluir <stdbool.h> em C17, bool ready = 2; armazena o valor booleano verdadeiro.
Valor de ponto flutuante
Um valor de ponto flutuante representa uma grandeza numérica real usando uma precisão fixa e uma escala, frequentemente com arredondamento.
Um comprimento armazenado como valor de ponto flutuante pode aproximar o decimal digitado sem corresponder a ele exatamente.
Variável
Uma variável é um local de armazenamento nomeado com um tipo declarado que determina como seu valor armazenado é interpretado.
A variável mass_kg armazena uma medida enquanto count registra quantas amostras foram inseridas.
Verificação de faixa
Uma verificação de faixa testa se entradas ou um resultado proposto satisfazem limites antes de uma operação insegura ser realizada.
Uma verificação de faixa de estoque compara as quantidades disponível e solicitada antes que a subtração sem sinal possa sofrer redução modular.

Sobre esta aula

Por StudeliRelatar um erro

Crie seus próprios Materiais de Estudo

Transforme suas anotações, slides e documentos em Guias de Estudo, Questões de Prática, Flashcards e mais 6 ferramentas.

Começar