Caros alunos, a disciplina está terminada. Muito obrigado pelo semestre movimentado e participativo, parabéns pelos resultados obtidos e boa sorte nos próximos passos!
A turma virtual desta disciplina no SIGAA foi fechada assim que as notas foram consolidadas. Por essa razão, o restante da nossa comunicação se dá por meio desta página. Você também pode me mandar um e-mail, caso deseje.
Aqui estão as notas da disciplina. Se você ainda não pegou algumas das suas provas, por favor passe no meu gabinete para pegá-las, exceto no caso da AF, que a universidade requer que fique com o professor – mas você pode dar uma olhada nela mesmo assim.
Agora que a disciplina acabou, aqui vão algumas sugestões de tópicos interessantes que complementam o assunto que estudamos:
Nós estudamos 3 ferramentas poderosas para a organização de dados: ponteiros, vetores e estruturas (ou "registros"). Essas ferramentas permitem a organização de dados de múltiplas formas, as quais muito influenciam os programas que escrevemos. O estudo das maneiras clássicas de se organizar dados em algoritmos e programas de computador é feito na disciplina Estruturas de Dados (ou, no curso de Matemática, "Estruturas de Informação"), que é a continuação natural da nossa disciplina. Se você gostou de Fundamentos de Programação, imagino que Estruturas de Dados também lhe agradará. Este livro é uma ótima referência nesse assunto em português (ele usa pseudocódigo, para não poluir as ideias dos algoritmos com a sintaxe complicada das linguagens de programação reais).
Na nossa disciplina, o foco foi conseguir escrever programas que realizassem as tarefas desejadas. Uma vez, porém, que nós já sabemos fazer isso, novas preocupações aparecem, como por exemplo a velocidade e o uso de memória dos programas que escrevemos. O primeiro passo para escrever programas que façam uso eficiente dos recursos de computação disponíveis é utilizar bons algoritmos e estruturas de dados; esse assunto se vê na disciplina "Estruturas de Dados", mencionada acima. Na prática, porém, a habilidade de programação vai muito além dos algoritmos que se utiliza. O meu conselho básico, nesse aspecto, é o seguinte: saiba, tenha em mente e tente diminuir o custo da execução de cada instrução do seu código. Para exemplificar a questão, considere este código:
int i, n; ... // n recebe um valor positivo double v[n*n]; for (i=0; i < n*n; ++i) v[i] = 0;
Esse não é um código "otimizado", porque o produto "n*n" é calculado toda vez que a condição do laço é testada. Muito mais eficiente que isso é realizar o cálculo apenas uma vez guardar o resultado numa variável:
int i, n; ... // n recebe um valor positivo int tam = n*n; double v[tam]; for (i=0; i < tam; ++i) v[i] = 0; // mais eficiente ainda seria usar ponteiros aqui
A "otimização" de código é uma tarefa que pode ser muito ampla, e é importante conhecer bem a linguagem de programação utilizada. Aqui estão conselhos de um autor de renome no assunto.
Você deve ter percebido uma diferença grande entre os programas que escrevemos e aqueles que costumamos utilizar na prática: nós escrevemos programas que executam com interface de texto, enquanto os programas que costumamos utilizar têm interface gráfica, com botões, menus, etc. Acredite, a essência dos dois é a mesma, a diferença é "apenas" a interface, a comunicação entre o programa e o usuário. No caso da linguagem C, eu sugiro você usar GTK: há um exemplo na Wikipédia e aqui está um tutorial.
Esta página está em contínua construção. Por favor, entre em contato comigo caso você precise de alguma informação adicional, ou caso acredite ter encontrado um erro nesta página. Obrigado!
2014-04-08 (terça-feira): AP1.
2014-05-27 (terça-feira): AP2.
2014-06-13 (sexta-feira): AP3.
2014-06-18 (quarta-feira): AF.
Segunda chamada: conforme disposto no manual do aluno, o aluno tem direito a fazer uma segunda chamada caso falte a alguma prova. Entretanto, somente serão realizadas segunda-chamadas para alunos que atenderem a todas as seguintes exigências:
Solicitar a segunda chamada por escrito na secretaria do Departamento de Computação em até 3 dias úteis após a realização da 1a chamada, anexando à solicitação um atestado médico ou comprovação inequívoca da impossibilidade de comparecer à prova.
Solicitações que não atendam às exigências acima não serão aceitas. Em particular, não ter estudado o suficiente para a primeira chamada não é motivo aceitável para requerer uma segunda-chamada. Além disso, os solicitantes devem estar cientes de que as provas de segunda-chamada são intencionalmente mais difíceis, dado que o aluno tem, em princípio, mais tempo para estudar para elas.
The C Programming Language, Second Edition.
Brian W. Kernighan, Dennis M. Ritchie.
Editora Prentice Hall.
1988.
ISBN: 0-13-110362-8, 0-13-110370-9.
Livro excelente, mas descreve C89, e não C99 ou C11.
Há uma
tradução pela editora Campus
(atente para o "- PADRÃO ANSI" no título:
não consulte a edição anterior, que não tem isso no título!).
Programando em C - Volume I - Fundamentos.
Ulysses de Oliveira.
Editora Ciência Moderna.
2008.
ISBN: 9788573936599.
Eu não conhecia o livro, mas o folheei e me surpreendi:
esse é o primeiro livro em português que eu abri
e que me lembrou o palavreado técnico do padrão C99.
Não li, mas desconfio que seja um material de muito boa qualidade,
principalmente do ponto de vista da literatura disponível em português.
C: A Reference Manual, Fifth Edition.
Samuel P. Harbison, Guy L. Steele.
Editora Prentice Hall.
2002.
ISBN-13: 978-0-13-089592-9.
Nunca li, mas me parece ser um livro de qualidade, e descreve tanto C89 quanto C99
(não conheço um livro sobre C11).
Entretanto, parece ser uma boa referência, e não um texto para iniciantes.
No Linux, um compilador C já deve estar instalado. Se não estiver, experimente tentar instalar o compilador "gcc" pelo instalador de pacotes. Eu recomendo a distribuição Ubuntu do Linux, pela simplicidade de uso.
Eu infelizmente não possuo um computador com Windows
para fazer pequenos experimentos de instalação de compilador.
Acredito que o
MinGW
e o
Cygwin
sejam duas opções bastante completas;
não sei se é fácil e pouco trabalhoso instalá-los.
Talvez uma saída bastante simples seja usar o
TCC:
simplesmente baixe o arquivo tcc-0.9.26-win32-bin.zip
(ou a versão com 64
no lugar de 32
,
se o seu computador for de 64 bits),
descompacte o pacote, salve o seu código-fonte
(digamos, main.c
) na pasta tcc
e então simplesmente execute tcc main.c
na interface de linha de comando
(para chegar à pasta via linha de comando,
você deverá ter que executar o programa cmd.exe
e chegar à pasta tcc
: cd tcc
;
veja
isto).
Além de um compilador, você vai precisar utilizar um editor de texto para escrever o código-fonte dos seus programas, então:
No Windows, o Notepad++ me parece ser uma boa escolha, embora eu nunca o tenha utilizado.
Pessoalmente, eu uso o gVim, que está disponível tanto em Linux quanto Windows, tem muitas capacidades e é muito bem documentado, mas a maioria das pessoas o considera difícil de usar de início. Ele é rivalizado pelo Emacs, que dizem ser muito bom, mas eu nunca usei -- exceto pelo psicoterapeuta do Emacs, que é muito divertido.
Acesso por mês e dia:
Alguns exercícios passados para sala ou para casa podem não estar listados abaixo:
No livro: capítulo 1 de [LP].
Aula 2 (2014-02-14, 10h):
"Olá, mundo!" em pseudo-código e C;
a função main
;
execução de um programa em C como execução de um conjunto de funções;
leitura, escrita e atribuição de variáveis inteiras em pseudo-código e C.
if-else
;
argumentos e retorno de funções
(sem void
).
No livro: capítulo 2 e início do capítulo 3 de [LP].
Exercícios importantes (devem ser feitos mesmo que no papel; o computador não é o principal!):main
para a obtenção do módulo do número
digitado pelo usuário.
O programa deve imprimir mensagens de comunicação com o usuário
("digite um inteiro: ", "o módulo é ...").
Aula 4 (2014-02-18):
estrutura de seleção com múltiplas escolhas, em pseudocódigo e em C (switch
);
estruturas de repetição com teste no final e no início,
em pseudocódigo e em C (do-while
, while
);
atribuição a uma variável que está envolvida na expressão que define o valor a ser atribuído.
Exercícios importantes (devem ser feitos mesmo que no papel; o computador não é o principal!):
n
dos números que vão ser digitados.
Em seguida, o programa deve proceder à leitura dos n
números,
e, para cada um deles, deve ser impressa uma mensagem ("digite um número"),
de forma a guiar a interação do usuário com o programa.
Ao final, como antes, a soma dos números lidos deve ser impressa na tela.
Aula 5 (2014-02-21, 10h): prática de programação com base nos exercícios da aula anterior.
2014-02-21, 16h: sem aula: horário cedido para recepção da turma pelos alunos veteranos.
2014-02-25: sem aula: alunos ausentes.
Aula 6 (2014-02-28, 10h):
uso de repetição em programas para contar e somar;
repetição com variável de controle usando o laço while
;
estrutura de repetição com variável de controle em C (for
).
Aula 7 (2014-02-28, 16h):
declaração, leitura, escrita e aritmética de números racionais em C (double
);
conversões implícitas entre tipos numéricos em expressões aritméticas e atribuições;
conversão explícita de tipos em C;
semântica da conversão de double
para int
;
o tipo primitivo "caractere" (char
) e sua relação com números inteiros.
Observações:
Como dito em sala, em C,
quando um número racional é convertido para inteiro (como de double
para int
),
a parte fracionária é descartada [C99, 6.3.1.4, 1].
Em math.h
,
há também funções para piso
e teto.
Como dito em sala, o padrão C99 não exige a geração de um aviso (warning)
com relação à atribuição de um número racional a uma variável inteira [C99, anexo I, 1],
embora tal conversão implícita (de double
para int
)
esteja na lista dos avisos mais comumente emitidos [C99, anexo I, 2, item 3].
Exercícios importantes -- devem ser feitos mesmo que no papel, pois o maior aprendizado da disciplina é saber escrever instruções que, se executadas, corretamente realizam uma tarefa; o uso do computador complementa o aprendizado, mas é secundário.
Escreva um programa em C que lê um inteiro não negativo n
do usuário
e então imprime na tela o fatorial de n
.
Escreva duas versões do programa,
uma que utilize o laço while
(ou o do-while
)
e outra que utilize o laço for
.
Em ambos os casos, certifique-se de que o programa lê n
novamente caso o usuário digite um número negativo.
Escreva uma modificação do programa da questão anterior na qual o cálculo do fatorial é realizado por uma função. (Recapitule de aulas anteriores a sintaxe para a definição de funções que recebem argumentos e retornam valores.) Dessa vez, você não precisa escrever duas versões: escolha o laço (isto é, a estrutura de repetição) que lhe pareça mais adequada.
Escreva um programa em C que lê um inteiro positivo "n" do usuário e então imprime o valor de fib(n) na tela, sendo fib(n) o n-ésimo número da sequência de Fibonacci -- 1, 1, 2, 3, 5, 8, 13, 21, 34, 55... -- que é definida por:
Escreva um programa em C que lê uma temperatura em graus Fahrenheit
e a converte para graus Celsius,
usando a fórmula
.
O tipo numérico para o armazenamento de temperaturas deve ser double
.
Escreva uma versão mais geral do programa da questão anterior, da seguinte maneira. O cálculo da conversão de Fahrenheit para Celsius deve ser encapsulado numa função. Você deve também escrever uma função que faz a conversão oposta, de Celsius para Fahrenheit. Finalmente, o programa deve pedir ao usuário que informe não apenas a temperatura, mas também a conversão que deve ser feita (se de Fahrenheit para Celsius, ou se o inverso).
([LP], cap. 3, exercício proposto 2.) Utilizando as funções escritas no exercício anterior, escreva um programa que imprime na tela uma tabela como
0 C -- 32 F |
1 C -- 33.8 F |
2 C -- 35.6 F |
... |
100 C -- 212 F |
([LP], cap. 3, exercício proposto 4.) Escreva um programa que lê um conjunto de votos e ao final apresenta a contagem e a estatística dos votos. Os votos possíveis são números de 1 a 5: um número "i" de 1 a 3 significa um voto para o candidato "i"; um número 4 significa um voto em branco; um número 5 significa um voto nulo. O programa deve repetidamente ler votos ("Digite o próximo voto: "), até que um número menor que 1 ou maior que 5 seja digitado; tal número indicará o fim da votação. Ao fim da votação, deve ser impressa a quantidade de votos que cada candidato recebeu, bem como as quantidades de votos brancos e de votos nulos. Também devem ser impressas as porcentagens de cada tipo de voto ("33.5% para o candidato 1", etc).
(Mais complicado, pode não ser resolvido agora.) Escreva um programa que calcula, de forma exata ou aproximada, a raiz quadrada de um número racional positivo digitado pelo usuário. O seu programa não deve chamar qualquer função já pronta que calcule a raiz quadrada de um número, mas sim usar a estratégia a seguir. Primeiramente descubra um limite inferior LI e um limite superior LS para a raiz quadrada do número digitado X (por exemplo, LI=0 e LS=X). Se LI ou LS forem a raiz quadrada de X (como se testa isso?), então o valor desejado já foi encontrado. Depois disso, verifique se M = (LI+LS)/2 ao quadrado é menor ou maior que X; se for menor, a raiz quadrada de X está entre M e LS; se for maior, a raiz está entre LI e M; se for igual, a raiz é M. Caso a raiz não seja M, você pode repetir o processo, agora com um intervalo com a metade do tamanho do original. Caso a raiz quadrada exata não seja encontrada em IT iterações, o seu programa pode imprimir, como valor aproximado, o valor médio do último intervalo encontrado; o valor IT deve ser fornecido pelo usuário no início da execução, juntamente com X.
2014-03-04: sem aula (feriado nacional: carnaval).
Aula 8 (2014-03-07): introdução a bases numéricas (sistema unário, abreviações, sistema posicional); conversão de e para a base decimal; conceitos sobre armazenamento de dados no computador (bit, byte, palavra de memória, modelo de memória de C, alinhamento de dados na memória).
Aula 9 (2014-03-11):
o tipo unsigned int
(representação, UINT_MAX
(limits.h
), semântica de transbordo);
representação de inteiros com sinal
(sinal e magnitude; complemento de um; complemento de dois);
conversão de e para a representação por complemento de dois.
Observação:
em printf
e scanf
,
a conversão para o tipo unsigned int
é %u
.
Converta os seguintes números para a representação por complemento de dois utilizando 8 bits (ou informe se a quantidade de bits é insuficiente, quando for o caso): 10, 15, 16, 27, 53, 101, 127, 128, 130.
Análogo ao exercício anterior, mas utilizando o inverso aditivo de cada número listado (por exemplo, -10 no lugar de 10).
Os seguintes são números inteiros representados na notação de complemento de dois; escreva-os na notação decimal: 10010011, 00010001, 10101010, 10001000.
Aula 10 (2014-03-14 10h): prática de programação em laboratório (exercícios de 2014-02-28).
Aula 11 (2014-03-14 16h):
as constantes CHAR_BIT
, INT_MIN
e INT_MAX
(limits.h
);
transbordo de inteiros (int
, por exemplo) em C
("comportamento indefinido" e comportamentos comuns na prática);
a notação posicional para números não inteiros, em base qualquer;
conversão de números fracionários da base decimal para a binária.
Fontes a consultar: a Wikipédia é uma ótima fonte de informações em muitos assuntos, como por exemplo sobre o sistema numérico binário (inclusive sobre conversão de e para a base decimal), a representação de inteiros com sinal em bits e a representação de números racionais (assunto da próxima aula).
Exercícios:Se uma máquina utiliza "n" bits para armazenar números inteiros, quais são os maior e menor números representáveis? Responda a pergunta para cada uma das 3 representações vistas em sala: sinal e magnitude, complemento de um e complemento de dois.
Em um computador ao qual você tenha acesso,
escreva e execute um programa que imprima na tela
os limites do intervalo de inteiros representáveis pelo tipo int
na máquina em questão.
Escolha você mesmo alguns números não inteiros escritos em base decimal e os converta para a base binária.
O raciocínio apresentado em sala para a conversão de números fracionários decimais para a base binária funciona, na verdade, para qualquer base maior que um! Generalize então o algoritmo e a demonstração de que ele funciona. Em seguida, converta números fracionários decimais para outras bases, como a base 3. Verificar a resposta não é um processo elaborado, como vimos em sala, mas é mais rápido consultar na internet.
Aula 12 (2014-03-18): notação científica (em diferentes bases); notação científica normalizada (em diferentes bases); representação de números racionais em memória (sinal, expoente, significando); o problema da representação do zero; representação de inteiros com sinal por excesso-de-k; a representação do expoente na representação de racionais em memória.
Fontes a consultar: este texto da Wikipédia é uma boa explicação sobre o padrão IEEE 754. Este artigo é um trabalho famoso sobre a representação de números racionais em memória; uma versão mais recentemente editada dele é esta aqui; esse texto é, porém, longo e matematicamente denso; não é necessário lê-lo para entender o conteúdo das aulas.
Observação: como explicado em sala, no padrão IEEE 754, o expoente de um número racional é representado por excesso-de-k. Lembre, porém, que, quando os bits do significando e do expoente são zero, então o número representado é, por convenção, o zero (ao invés do número de expoente pequeno que seria interpretado normalmente).
Exercícios:(resposta aqui) Supondo 8 bits utilizados para o expoente e 23 bits para o significando, represente os seguintes números segundo o padrão IEEE 754: +0; -0; +1; -1; +2; -2; +2,25; -8,025; 5,125; -7,0625.
(resposta aqui) Ainda sob as suposições da questão anterior, os seguintes são números racionais representados segundo o padrão IEEE 754; diga que números são eles:
0100 0011 1100 1000 0000 0000 0000 0000 |
0100 0011 1010 0000 0000 1000 0000 0000 |
1100 0001 1110 0010 0000 0000 0000 0000 |
1011 1111 1000 0000 0000 0000 0000 0000 |
Quando as sequências de bits da questão anterior são entendidas como números inteiros representados por complemento de dois, que números inteiros são eles?
Aula 13 (2014-03-21 10h): prática de programação em laboratório (exercícios abaixo):
Exercícios:
Escreva uma função em C que recebe como único argumento um int
,
e que imprime na tela os dígitos da representação binária do número,
do menos significativo para o mais significativo
(isto é, "da direita para a esquerda").
Em seguida, escreva um programa em C no qual a função main
lê do usuário um número natural e em seguida chama a função em questão,
fornecendo como argumento o número que foi lido.
Atenção:
A ideia é que a função em questão lide apenas com números não-negativos.
Assim, a função deve fazer um teste, e,
se o argumento recebido for um número negativo,
a função pode simplesmente imprimir uma mensagem na tela
(algo como "O número recebido é negativo e não será impresso"
) e retornar.
A ideia é que o procedimento utilizado pela função para obter os dígitos do número seja aquele que foi ensinado em sala, via restos de divisões por 2. Por isso, nenhuma outra função deve ser chamada dentro da função em questão, e apenas os recursos de C que foram ensinados em sala devem ser utilizados.
A função deve ser declarada como
void imprimir_digitos (int x)
ou algo semelhante.
A palavra-chave void
à esquerda do nome da função
indica que a função não retorna nenhum valor.
Naturalmente, um comando como return 5;
não faz sentido nessa função,
mas o comando return;
pode ser livremente usado em qualquer parte do código da função,
e tem o significado óbvio: terminar a execução da função.
Escreva uma função semelhante à do exercício anterior, mas que imprima os dígitos do mais significativo ao menos significativo (isto é, "da esquerda para a direita"). As restrições do exercício anterior continuam valendo, exceto que a estratégia para a obtenção dos dígitos deve, naturalmente, ser diferente:
Para realizar a tarefa, a sua função deve, inicialmente, descobrir qual é a maior potência de 2 que não é maior que o número em questão. Essa informação dá o dígito mais significativo, e para descobrir a potência em questão, bastar testar as potências 2^0, 2^1, 2^2, etc, até que uma delas seja maior que o número dado.
Após descobrir o primeiro dígito (o mais significativo), descobrir o segundo é fácil, pois este está associado à potência de 2 imediatamente anterior à potência que foi utilizada para descobrir o primeiro dígito.
Após descobrir o primeiro dígito (o mais significativo), descobrir o segundo é fácil, pois este está associado à potência de 2 imediatamente anterior à potência que foi utilizada para descobrir o primeiro dígito.
O procedimento deve continuar até que todos os dígitos tenham sido impressos na tela.
Talvez você tenha percebido que, para números muito grandes,
a função explicada na questão anterior, se implementada sem cuidado,
pode não funcionar.
A razão é que, para números suficientemente grandes
-- digamos, um número cuja representação binária tenha todos os bits iguais a 1,
exceto o bit de sinal --
não existe potência de 2 armazenável no tipo int
que seja maior que o número em questão.
Nesse caso, simplesmente tentar uma potência maior
cada vez que a potência atual ainda for menor ou igual ao número dado
certamente leva a um transbordo
(e, como vimos em sala, quando ocorre um transbordo de int
,
o comportamento do programa não é previsto pelo padrão da linguagem).
Uma maneira de contornar esse fato é a seguinte. Em cada iteração do laço que descobre a primeira (maior) potência associada ao número em questão, seja 2^x a potência atual e seja y = INT_MAX - 2^x. Se y < 2^x, então 2^x é certamente a maior potência de 2 representável na máquina em questão (e portanto aquela associada ao dígito mais significativo do número em questão); em caso contrário, a potência 2^(x+1) também é representável.
Modifique a função que você escreveu na questão anterior, nela incluindo o procedimento acima. Para testar o novo código, chame a função com argumentos muito grandes (como INT_MAX e números um pouco menores) e verifique as respostas.
Escreva uma função void imprimir_digitos_em_base (int x, int b)
que imprime os dígitos de um número natural "x",
do menos significativo ao mais significativo,
quando "x" é representado em base "b".
A função deve supor que x ≥ 0 e b ≥ 1.
Observe que, se b = 1, a base é unária
(o símbolo utilizado para a escrita pode ser "|").
Escreva uma função que faz o mesmo que a da questão anterior, exceto que os dígitos devem ser impressos do mais significativo ao menos significativo. O raciocínio das questões anteriores deve ser generalizado de forma a contemplar uma base "b" qualquer.
Aula 14 (2014-03-21 16h):
valores especiais da representação de números racionais segundo o padrão IEEE 754
(zeros; infinitos; NaN's; números denormalizados);
representação de unsigned char
e signed char
;
operadores de bit.
Observações:
O padrão IEEE 754 determina 3 formatos binários básicos para a representação de números racionais. Cada formato especifica o número de bits utilizados para armazenar o expoente e o significando, de acordo com a tabela abaixo (a qual, para os propósitos da nossa disciplina, não precisa ser memorizada):
Comprimento | Sinal | Expoente | Significando |
32 | 1 | 8 | 23 |
64 | 1 | 11 | 52 |
128 | 1 | 15 | 112 |
Sobre a relação entre o tipo char
e os tipos signed char
e unsigned char
,
o padrão C99 diz [C99, 6.2.5, 15] (traduzindo-se):
A implementação definirá
char
de forma que tenha as mesmas abrangência [com relação aos valores representáveis], representação e comportamento que osigned char
ouunsigned char
.**: CHAR_MIN, definida em
<limits.h>
, terá um dos valores 0 ou SCHAR_MIN, e isso pode ser usado para distinguir as duas opções. Independentemente da escolha feita,char
é um tipo separado dos outros dois e não é compatível com qualquer um deles.
Há restrições sobre o uso controlado dos operadores
<<
e >>
, de deslocamento de bits
[C99, 6.5.7]:
O operando direito, por exemplo, não deve ser negativo e nem maior que o número de bits do tipo inteiro sobre o qual o deslocamento é realizado.
No caso de uma expressão a << b
, de deslocamento à esquerda,
o comportamento é indefinido no caso de a
ser negativa
ou no caso de o resultado não ser representável
(lembre que o resultado é uma multiplicação por 2^b).
No caso de uma expressão a >> b
, de deslocamento à direita,
o resultado é definido pela implementação caso a
seja negativa.
Exercícios:
(resposta aqui) As seguintes são representações de valores segundo o padrão IEEE 754, no formato de 32 bits explicado na tabela acima; descubra o valor correspondente a cada representação:
1 0000 0000 000 0000 0000 0000 0000 0000 |
1 0000 0000 000 0000 0000 0000 0000 0001 |
1 1111 1111 000 0000 0000 0000 0000 0001 |
1 1111 1111 000 0000 0000 0000 0000 0000 |
1 1111 1110 111 1111 1111 1111 1111 1111 |
Escreva um programa em C que determina se o tipo char
está representado com ou sem sinal,
utilizando para isso a dica da observação pertinente acima.
Execute e veja o resultado.
O código abaixo (escrito por mim) imprime na tela os bits de um int
,
desde que
o deslocamento de bits à esquerda seja "silencioso e tranquilo".
Explique:
Por que o código, na condição acima, funciona?
Por que a condição acima é importante?
Observações:
sizeof(int)
é simplesmente o número de bytes ocupados por um int
.
A função putchar
simplesmente imprime na tela um caractere.
void print_int_bits (int x) { int bits_remaining = CHAR_BIT * sizeof(int); const int msd_mask = ~INT_MAX; for (; bits_remaining--; x <<= 1) { if (x & msd_mask) putchar('1'); else putchar('0'); } }
2014-03-25: sem aula (Feriado Estadual: Data Magna do Ceará).
Aula 15 (2014-03-28 10h): prática de programação em laboratório (exercícios de 2014-03-21).
Aula 16 (2014-03-28 16h):
funções com e sem parâmetros ou valor de retorno (void
);
funções recursivas.
Exercícios:
Considere o código abaixo:
#include <stdio.h> int par (int n); // para a função "impar" saber o que é "par" antes de a definição desta última ser lida. int impar (int n) // Supõe n >= 0 { switch(n) { case 0: return 0; case 1: return 1; default: return par(n-1); } } int par (int n) // Supõe n >= 0 { switch(n) { case 0: return 1; case 1: return 0; default: return impar(n-1); } } int main (void) { int n; do { printf("Digite um número inteiro: "); scanf("%d", &n); if (n >= 0) { printf( " par(%d): %d\n", n, par(n) ); printf( "impar(%d): %d\n", n, impar(n) ); } else { printf("Saindo...\n"); } } while (n >= 0); return 0; }
Agora:
Simule a execução do código acima supondo que o usuário digita 2, 5 e -3.
O que faz o código acima? Por que ele funciona?
O que acontece numa chamada como par(-3)
?
Escreva uma função void digitos_binarios (int n)
que imprime na tela os dígitos de um número n
quando escrito em base binária.
A função deve supor que n ≥ 0
.
A estratégia para imprimir os dígitos é recursiva e é ilustrada pelo seguinte exemplo:
Como 29 = 14*2 + 1, então, para imprimir os dígitos binários de 29, nós podemos imprimir primeiramente os dígitos de 14, e, em seguida, imprimir o dígito menos significativo, que é 1.
Escreva uma função void digitos_na_base (int n, int b)
que generaliza a função da questão anterior das seguintes maneiras:
O argumento b
é a base na qual o número deve ser impresso.
A função deve supor que b ≥ 2
.
Se n
for negativo, a função imprime o número corretamente,
primeiramente imprimindo um "-" e depois imprimindo os dígitos de -n
.
Aula 17 (2014-04-01):
os comandos break
, continue
e goto
de desvio do fluxo de execução;
sintaxe de identificadores em C;
blocos;
falso (zero) e verdadeiro (diferente de zero) em C;
o operador ternário ?:
de C, para a avaliação condicional de expressões.
Observações:
Dois comentários adicionais deveriam ter sido feitos sobre a sintaxe dos identificadores de C:
Letras maiúsculas são diferenciadas de letras minúsculas em C;
logo, divisor
e Divisor
são nomes distintos de variável.
Identificadores não podem ser iguais às palavras-chave da linguagem C,
pois estas últimas são reservadas para o significado especial
que elas têm na linguagem.
Assim, por exemplo, não é possível declarar variáveis ou funções com os nomes
for
, if
, goto
, etc.
A lista de palavras-chave da linguagem C
está aqui.
Observe ainda que, como explicado na referida página,
existem restrições sobre identificadores que comecem por "_
";
assim sendo, é melhor sempre começar identificadores por uma letra.
Em C, o segundo operando dos operadores lógicos apenas é avaliado caso a avaliação do primeiro operando não seja suficiente para se deduzir o resultado da operação como um todo. Assim, por exemplo, este código
int a,b; ... if (b == 0 || a/b > 5) { ... } // teste 1 if (b != 0 && a/b > 5) { ... } // teste 2é correto do ponto de vista da avaliação da expressão
a/b
:
Teste 1: caso b
seja zero,
o teste resultará em verdadeiro e a expressão a/b
não chega a ser avaliada.
Se, por outro lado, b
não for zero,
então a expressão b == 0
será falsa e a expressão
a/b > 5
será avaliada,
sem risco de divisão por zero.
Teste 2: se b
for zero,
a expressão b != 0
será falsa
e portanto a condição completa do teste será falsa,
sem que a expressão a/b > 5
seja avaliada.
Se, por outro lado, b != 0
for verdadeira,
a expressão da direita será avaliada,
novamente sem risco de divisão por zero.
Exercícios:
Recapitule cada novidade apresentada na aula de hoje, e as tenha em mente ao escrever programas em C daqui para a frente; elas podem lhe ajudar a escrever programas mais claros e/ou sucintos, e podem ser essenciais na compreensão de programas escritos por outras pessoas.
Aula 18 (2014-04-04, 10h): prática de programação em laboratório (exercícios anteriores).
Aula 19 (2014-04-04, 16h):
declarações ("protótipos") de funções e uso no caso de
funções mutuamente recursivas;
o operador sizeof
e seu uso para tipos e expressões;
solução de exercícios anteriores.
2014-04-08: prova 1.
Aula 20 (2014-04-11, 10h): prática de programação em laboratório.
Exercícios:
Escreva um programa que lê do usuário 3 números inteiros, e que então os imprime na ordem contrária àquela em que foram digitados.
Escreva um programa que lê 3 números do usuário, e que então imprime o desvio médio desses números. O desvio médio de um conjunto de números é a média dos desvios absolutos dos números, com relação à média dos números. O desvio absoluto de cada número, com relação à média dos números, é o módulo da diferença entre o número e a média dos números.
Recapitule o problema das torres de Hanói, da primeira aula da disciplina: (1) há 3 pinos: A, B e C; (2) há "n" discos no pino A, todos de tamanhos diferentes e empilhados do menor (no topo) ao maior (na base); (3) o objetivo é mover todos os discos para o pino C; (4) pode-se mover, de um pino a outro, um disco por vez, sempre aquele disco que estiver no topo da pilha de um pino, e que passará então ao topo da pilha do outro pino, mas o movimento só é possível se não levar um disco a estar sobre outro menor que ele.
Na primeira aula da disciplina, você foi convidado a solucionar o problema das torres de Hanói com 3 e 4 discos, o que deve ter lhe dado a ideia da solução do problema para qualquer quantidade "n" de discos. Escreva então um programa em C que lê do usuário um número inteiro positivo "n" e então escreve na tela uma sequência de passos que levam à solução do problema das torres de Hanói com "n" discos.
Aula 21 (2014-04-11, 16h): correção das questões 1, 3 e 4 da prova 1; vetores: declaração e denotação dos elementos; exemplo de uso de vetores para a simplificação de repetições em programas.
Exercícios: (ATENÇÃO, os exercícios 1–3 abaixo devem ser feitos sem falta até a próxima aula -- como de costume, não para entrega, mas para melhor compreensão do conteúdo)
Usando vetor(es) e laço(s), escreva um programa que lê do usuário 5 números, e que então imprime o desvio médio desses números (como definido em exercício anterior).
Escreva um programa que:
Lê as notas de 5 alunos na AP1 de uma disciplina.
Em seguida (isto é, após todas as leituras acima), lê as notas dos mesmos 5 alunos na AP2.
Em seguida, imprime as médias de cada aluno; os alunos devem estar numerados de 1 a 5.
Escreva uma variação do programa anterior na qual, após a leitura das notas, o programa, ao invés de imprimir as médias, repetidamente pede ao usuário que digite o número do aluno, e então imprime a média do aluno digitado, até que o usuário digite um número que não seja de 1 a 5, quando então o programa deve terminar. Por exemplo:
Digite a AP1 do aluno 1: 8 Digite a AP1 do aluno 2: 9 Digite a AP1 do aluno 3: 8.7 Digite a AP1 do aluno 4: 8.2 Digite a AP1 do aluno 5: 7 Digite a AP2 do aluno 1: 9 Digite a AP2 do aluno 2: 9.9 Digite a AP2 do aluno 3: 10 Digite a AP2 do aluno 4: 7.8 Digite a AP2 do aluno 5: 8.1 Digite o número de um aluno: 4 A média do aluno 4 é 8 Digite o número de um aluno: 1 A média do aluno 1 é 8.5 Digite o número de um aluno: 5 A média do aluno 5 é 7.55 Digite o número de um aluno: 1 A média do aluno 1 é 8.5 Digite o número de um aluno: -8 Saindo...
Escreva uma função recursiva que lê do usuário 6 números e então os imprime na ordem contrária àquela em que eles foram lidos. Restrições: A sua função pode ter apenas 1 parâmetro, 1 variável local que não seja parâmetro e no máximo 10 linhas.
Escreva um programa que lê 6 números do usuário e os armazena num vetor "v". Em seguida, o programa deve imprimir o conteúdo do vetor, elemento-a-elemento. Em seguida, o programa deve repetidamente fazer o seguinte:
Ler dois números "i" e "j" de 0 a 5.
Trocar os valores de v[i] e v[j].
Imprimir o novo conteúdo do vetor.
Digite v[0]: 5 Digite v[1]: 7 Digite v[2]: 1 Digite v[3]: 2 Digite v[4]: 3 Digite v[5]: -7 v: [ 5 7 1 2 3 -7 ] Digite i: 0 Digite j: 4 v: [ 3 7 1 2 5 -7 ] Digite i: 5 Digite j: 5 v: [ 3 7 1 2 5 -7 ] Digite i: 5 Digite j: 2 v: [ 3 7 -7 2 5 1 ] Digite i: 5 Digite j: -2 Saindo...
Aula 22 (2014-04-15):
esclarecimento de dúvidas e solução de exercícios anteriores;
inicialização de vetores: completa e parcial, de trecho inicial e aleatória;
ponteiros: declaração,
dereferência (acesso ao elemento apontado: operador *
) e
referência (obtenção de ponteiro: operador &
).
Exercícios:
Considere o programa abaixo:
#include <stdio.h> void f (int a, int b) { int c = a; a = b; b = c; } int main (void) { int x = 5, y = 8; f(x,y); printf("X: %d\nY: %d\n", x, y); return 0; }
O que ele faz? O que era pretendido? Como é possível corrigir f
?
2014-04-18: sem aulas (recesso escolar: semana santa).
Aula 23 (2014-04-22): uso de ponteiros: função para trocar os valores de duas variáveis; incremento e decremento de ponteiros; uso de ponteiro para percorrer um vetor.
Exercícios:
Os operadores de incremento e decremento sobre ponteiros permitem que se percorra, numa certa função, um vetor declarado noutra função, como no programa abaixo:
#include <stdio.h> void imprimir_vetor (double *p, int tam) // p: ponteiro para o início de um vetor { // tam: tamanho do vetor int i = 0; for (; i < tam; ++i, ++p) // Sempre verdade: p == &vetor[i] printf("vetor[%d] = %g\n", i, *p); } int main (void) { double v[5]; int i; for (i=0; i < 5; ++i) { printf("Digite v[%d]: ", i); scanf("%lg", &v[i]); } imprimir_vetor(&v[0], 5); return 0; } // main
Tomando como base o exemplo acima, escreva uma função
double maior_elemento (double *p, int tam)
que descubra e retorne o maior elemento de um dado vetor.
Em seguida, escreva um programa completo no qual essa função seja utilizada.
Escreva uma função que preencha um dado vetor com números lidos a partir do usuário. Como no exercício anterior, a função deve receber como argumentos (1) um ponteiro para o primeiro elemento do vetor e (2) o tamanho do vetor.
Escreva uma função que inverta a ordem dos elementos de um dado vetor.
Assim, por exemplo, se o vetor fornecido tiver como elementos
[ 5 7 -8 0 9 9 ]
,
então a função deve alterar o vetor de forma que os elementos deste passem a ser
[ 9 9 0 -8 7 5 ]
.
Escreva uma função
void separar (double *p, int tam, double s)
que reorganiza os tam
elementos de um vetor
(iniciado em *p
) da seguinte maneira:
os elementos menores ou iguais a s
devem ficar
à esquerda dos elementos maiores que s
.
Assim, por exemplo,
se o vetor for [ 9 -1 8 -7 15 7 6 5 ]
e s == 5
,
então uma reorganização possível é
[ 5 -1 -7 8 15 7 6 9 ]
.
Escreva um programa para se jogar Torres de Hanói com 4 discos. Utilize vetores para armazenar a informação de quais discos estão em quais pinos, e em qual ordem. Os movimentos dos discos devem ser informados pelo usuário. A cada movimento fornecido pelo usuário, o programa deve executar o movimento e então imprimir na tela a configuração de discos resultante da jogada. O programa não deve permitir movimentos inválidos, como por exemplo um que leve um disco a estar sobre outro menor.
2014-04-25: sem aulas (luto oficial: falecimento do professor e coordenador João Rabelo).
Aula 24 (2014-04-29):
adição e subtração entre ponteiros e inteiros;
comparação e subtração entre ponteiros;
os tipos ptrdiff_t
e size_t
de stddef.h
;
percurso, numa função, de vetor declarado noutra função, via ponteiros.
Exercícios:
Escreva uma função void move_max (double *i, int t, int p)
que recebe como argumentos um ponteiro i
apontando para o primeiro elemento de um vetor de t
números,
e que então rearranja os elementos do vetor de forma
que o maior elemento do vetor passe a estar na “posição” (índice) p
,
caso não já esteja lá.
Por exemplo:
Se o vetor for [ 3 6 -1 9 0 ]
e p == 1
,
então um rearranjo válido é [ 3 9 -1 6 0 ]
.
Se o vetor for [ 7 3 9 6 8 -1 9 0 ]
e p == 4
,
então um rearranjo válido é [ 7 3 8 6 9 -1 9 0 ]
.
Fazendo uso da função escrita no exercício anterior,
escreva uma função void ordenar (double *i, int t)
que ordena os elementos de um dado vetor em ordem crescente.
A ideia a ser utilizada na função é a seguinte:
Colocar o maior elemento do vetor na última posição do vetor (usar a função do exercício anterior).
Repetir o passo anterior,
“fingindo que o vetor possui apenas os t-1
primeiros elementos”.
Repetir o passo anterior,
“fingindo que o vetor possui apenas os t-2
primeiros elementos”.
Etc, até restar apenas um elemento a ser ordenado, que então está trivialmente ordenado.
Exemplo:
[ 7 3 9 6 8 -1 9 0 ] --> [ 7 3 0 6 8 -1 9 9 ] --> [ 7 3 0 6 8 -1 9 9 ] --> [ 7 3 0 6 -1 8 9 9 ] --> [ -1 3 0 6 7 8 9 9 ] --> [ -1 3 0 6 7 8 9 9 ] --> [ -1 0 3 6 7 8 9 9 ] --> [ -1 0 3 6 7 8 9 9 ] .
Escreva uma função de ordenação como a do exercício anterior, exceto que a função de ordenação deve conter todo o código necessário nela mesma, não fazendo, portanto, chamada a nenhuma outra função.
Escreva uma função recursiva que ordene um vetor de números por meio da estratégia das questões anteriores.
Escreva uma função
void unir (double *i1, int t1, double *i2, int t2, double *i3)
que recebe como entrada dois vetores já ordenados (em ordem crescente),
e que então escreve os elementos desses dois vetores num terceiro vetor,
de forma que este último também fique ordenado.
A sua função não deve simplesmente copiar os elementos dos vetores de origem no vetor de destino e então chamar uma função de ordenação para este último; ao invés disso, a função deve fazer uso inteligente do fato de que os dois primeiros vetores já estão ordenados.
Exemplo: se os dois primeiros vetores forem
[ -8 -3 -1 2 7 ]
e
[ -1 0 3 6 7 8 9 9 ]
,
então o vetor resultante deve ser
[ -8 -3 -1 -1 0 2 3 6 7 7 8 9 9 ]
.
Observação: a função não precisa receber como argumento o tamanho do terceiro vetor; ela supõe que o vetor é grande o suficiente.
Aula 25 (2014-05-02 10h): prática de programação em laboratório (exercícios anteriores).
Aula 26 (2014-05-02 16h):
definição do operador [ ]
para ponteiros;
exemplos de uso;
conversão implícita de vetores para ponteiros em C;
ponteiros para void
: relação com outros ponteiros, operações proibidas;
Exercícios:
Tenha as informações da aula de hoje em mente, para simplificar os próximos códigos escritos.
Aula 27 (2014-05-06):
alocação dinâmica de memória via calloc
, malloc
e free
;
0
como valor válido de ponteiros;
a constante NULL
de stddef.h
(veja observação abaixo);
lista das operações válidas sobre ponteiros;
omissão do tamanho de um vetor em declaração com inicialização.
Observações:
Tecnicamente, a constante NULL
, definida em stddef.h
,
é uma expressão constante inteira de valor zero,
ou então uma tal expressão convertida para void *
(como "(void*) 0"
)
[padrão C99, 7.17 e 6.3.2.3].
Algumas pessoas acham melhor comparar ponteiros com NULL
,
ao invés de com zero, para indicar mais claramente que se trata
de uma comparação com um valor especial para ponteiros.
A comparação com zero é, entretanto,
perfeitamente válida do ponto de vista sintático.
Como dito em sala, um vetor declarado numa função certamente deixa de existir ao fim da chamada à função (assim como qualquer outra variável local), ao passo que um vetor alocado dinamicamente pertence ao programa até que seja explicitamente desalocado, e portanto pode continuar alocado mesmo depois do fim da chamada da função na qual ele foi alocado. Veja este exemplo.
Atenção: (1) uma expressão *p
, p
sendo um ponteiro,
somente deve ser avaliada num programa se p
não for um ponteiro nulo (isto é, se p != 0
).
Além disso, (2) se p
aponta para uma variável local,
então *p
só pode ser avaliada enquanto a variável local exista,
isto é, enquanto a função na qual ela foi declarada
não tiver terminado de executar.
Por fim, (3) se p
aponta para um elemento de um vetor alocado dinamicamente,
então *p
só pode ser avaliada enquanto o vetor ainda estiver alocado.
Exercícios:
Escreva um programa que lê do usuário "n" números, e que, após a leitura de todos eles, imprime na tela os quadrados dos números digitados. A quantidade "n" de números deve ser informada pelo usuário inicialmente. O programa deve armazenar os números num vetor, o qual deve ser alocado dinamicamente. Exemplo de execução:
Digite a quantidade de números: 3 Digite o número 1: 7 Digite o número 2: -8 Digite o número 3: 9.7 Quadrado do número 1: 49 Quadrado do número 2: 64 Quadrado do número 3: 94.09
Como pedido em sala, escreva um programa que lê do usuário os coeficientes de um sistema de equações lineares, um a um, e que então imprime os coeficientes de volta para o usuário, preferivelmente na forma retangular que costumamos utilizar para a impressão de tais sistemas. Utilize um único vetor para armazenar todos os coeficientes.
Escreva uma extensão do programa do primeiro exercício, na qual o usuário digita vários valores para "n", e na qual vários vetores são alocados, lidos, utilizados e desalocados, um depois do outro, até que o usuário digite um valor não-positivo para "n", quando então o programa deve parar.
Escreva uma função que compara dois vetores "v" e "w" de "n" números inteiros, e que retorna 1 se os vetores são iguais e 0 em caso contrário.
Que faz esta função? (Tente descobrir antes de executá-la num computador.)
int f (int *i, int t) // Supõe t > 0. { int *p = v; int m = *p; int i = 1; while (i++ < t) { if ( m < *(++p) ) m = *p; } return m; }
Escreva uma função que recebe uma matriz inteira "M" de "m" linhas e "n" colunas – representada na forma de um vetor de m*n números, linha-a-linha, da mesma maneira como no exercício sobre sistemas lineares – e que adiciona 0 aos elementos da linha 0, 1 aos elementos da linha 1, …, "m-1" aos elementos da linha "m-1". Depois, escreva um programa que lê os elementos de uma matriz, chama a função acima e depois imprime a matriz resultante.
Escreva uma função int checar_ordenacao (int *v, int n)
que receba um ponteiro para um vetor de "n" inteiros
e retorne 0 se o vetor está ordenado em ordem crescente.
Caso o vetor não esteja ordenado,
deve ser retornado o menor índice "i" tal que v[i-1] > v[i]
.
A função deve supor que n >= 1
(atente ao caso n == 1
).
Aula 28 (2014-05-09 10h): prática de programação em laboratório (exercícios anteriores).
Aula 29 (2014-05-09 16h): armazenamento de matrizes por meio de vetores em C, linha-a-linha, e correspondência entre os índices na matriz e no vetor; exemplo de programa que armazena e manipula uma matriz por meio de um vetor.
Exercícios:
Modifique o programa escrito em sala, de forma que, após todas as modificações na matriz, o programa entre em uma repetição na qual o usuário fornece um índice "k" do vetor utilizado para armazenar a matriz, ao que o programa responde imprimindo na tela os índices "i" e "j" da matriz correspondentes ao índice "k" fornecido, e o valor do elemento "M[i,j]" em questão. O novo programa termina de forma análoga ao anterior: quando o usuário fornecer um índice "k" fora do intervalo esperado.
Escreva uma função void somar_na_coluna (double *M, int m, int n, double x, int c)
que soma um número "x" a todos os elementos da coluna "c" de uma dada matriz
de "m" linhas e "n" colunas; o argumento "M" é um ponteiro para a matriz,
que está armazenada num vetor, linha-a-linha, conforme discutido em sala.
Para testar a sua função, escreva também um programa que lê do usuário
os valores de "m", "n", "x" e "c", assim como os elementos da matriz,
e que então chama a função em questão e depois imprime na tela a matriz modificada.
Faça o análogo do programa da questão anterior, exceto que não mais com relação a uma coluna da matriz, mas sim uma linha.
Escreva uma função void trocar_colunas (double *M, int m, int n, int a, int b)
que troca os elementos das colunas "a" e "b" de uma matriz "M" de "m" linhas e "n" colunas.
Além disso, assim como no caso dos exercícios anteriores,
escreva um programa simples que use a função escrita.
Faça o análogo do programa da questão anterior, exceto que não mais com relação a colunas da matriz, mas sim linhas.
Escreva uma função void soma_das_linhas (double *M, int m, int n, double *S)
que recebe uma matriz "M" de "m" linhas e "n" colunas
e calcula a linha que resulta da soma de todas das linhas de "M", coluna-a-coluna,
como no exemplo abaixo.
A linha resultante deve ser gravada no vetor (apontado pelo argumento) "S"
(a sua função deve supor que "S" já aponta para um vetor de tamanho pelo menos "n").
[ 3 -1 2 ] M: [ 6 0 5 ] --> S: [ 10 1 7 ] [ 1 2 0 ]
Aula 30 (2014-05-13): declaração de tipos derivados em C; escrita e simulação da execução de programa usando vetor de vetores em C; exemplos de declarações complexas em C.
Observações:
Esta página (em inglês) contém uma ótima e breve explicação sobre declarações de tipos derivados em C. Em sala, nós nos concentramos em, dado um tipo, escrever a declaração correspondente. Na página em questão, faz-se o exercício oposto.
Esta página (em português) contém conteúdo semelhante àquela citada acima.
A partir de agora, tenha em mente a possibilidade de armazenar uma matriz como um vetor de vetores em C, utilizando esse conhecimento na escrita de programas daqui para a frente.
Exercícios:
(Respostas aqui) Tente declarar os tipos abaixo:
Vetor de 5 vetores de 7 float
's.
Ponteiro para vetor de 5 int
's.
Ponteiro para ponteiro para ponteiro para vetor de 5 int
's.
Vetor de 5 vetores de 8 ponteiros para ponteiros para vetores de 3 char
's.
(Respostas aqui) Descubra os tipos declarados abaixo:
int *p[7];
int **p[4][6];
int *(*p)[4][6];
int *(*p[3])[4][6];
Aula 31 (2014-05-16 10h): prática de programação em laboratório (exercícios anteriores).
2014-05-16 16h: sem aula — professor secretariando concurso da UFC, conforme portaria nº 180/2014 do Centro de Ciências da UFC.
Aula 32 (2014-05-20): exemplo do uso de ponteiros para vetores, para percorrer uma matriz alocada na mesma função; exemplo no caso em que a matriz é declarada em outra função (aqui um programa semelhante).
Exercícios:
Escreva uma função void id (int n, int (*M)[n])
que transforma (a matriz apontada por) "M" em matriz identidade.
Escreva um programa em C que lê do usuário e depois imprime na tela uma matriz com linhas de diferentes tamanhos, conforme exemplo abaixo. Para tanto, o seu programa deverá usar um vetor de ponteiros (um ponteiro para cada linha), e cada ponteiro deverá apontar para um vetor do tamanho da linha em questão; cada vetor deve, naturalmente, ser alocado dinamicamente. O número de linhas e o tamanho de cada linha devem ser positivos. Não esqueça de desalocar todos os vetores ao final do programa!
Digite o numero de linhas: 3 Digite o tamanho da linha 0: 2 Digite M[0,0]: 7 Digite M[0,1]: 0 Digite o tamanho da linha 1: 4 Digite M[1,0]: 1 Digite M[1,1]: 2 Digite M[1,2]: 3 Digite M[1,3]: 4 Digite o tamanho da linha 2: 0 Digite o tamanho da linha 2: -2 Digite o tamanho da linha 2: 3 Digite M[2,0]: 8 Digite M[2,1]: 5 Digite M[2,2]: 6 A matriz digitada foi: 7 0 1 2 3 4 8 5 6
Escreva um programa que repetidamente lê números do usuário, e que, ao final, imprime para o usuário todos os números lidos. A quantidade de números a serem lidos não é conhecida de início, e também não é informada pelo usuário no início do programa. Ao invés disso, o programa deve repetidamente perguntar ao usuário se ele deseja informar outro número, como no exemplo abaixo. Como a quantidade de números é desconhecida, o seu programa deve armazená-los da seguinte maneira: de início, deve-se utilizar um vetor de 3 elementos; se "esse vetor ficar cheio" (isto é, o usuário digitar 3 números e ainda quiser digitar outro), um novo vetor, com o dobro do tamanho, deve ser alocado, e os números já digitados devem ser copiados para ele; esse procedimento deve ser repetido sempre que o vetor atual ficar cheio. Não esqueça de desalocar os vetores à medida em que eles vão deixando de ser utilizados, e nem de desalocar o último vetor alocado.
Digite um número: 7 Outro? (0: não, outro: sim) 1 Digite um número: -5.8 Outro? (0: não, outro: sim) 1 Digite um número: 0.1 Outro? (0: não, outro: sim) 1 Digite um número: 3.4 Outro? (0: não, outro: sim) 1 Digite um número: 4.9 Outro? (0: não, outro: sim) 1 Digite um número: -90 Outro? (0: não, outro: sim) 0 Os números digitados foram: 7 -5.8 0.1 3.4 4.9 -90
Aula 33 (2014-05-23 10h): prática de programação em laboratório (exercícios anteriores).
Aula 34 (2014-05-23 16h): solução de exercícios de aulas anteriores.
2014-05-27: AP2.
Aula 35 (2014-05-30 10h): aula de laboratório, na qual foram resolvidas as questões da AP2.
Aula 36 (2014-05-30 16h):
recapitulação sobre o tipo char
e
sequências de escape;
sequências de caracteres (strings) em C:
definição, inicialização, exemplo simples de uso e acesso aos elementos;
ponteiros e constantes de sequência de caracteres;
exemplo de programa ilustrando a diferença entre vetores de caracteres
inicializados por tais constantes e vetores estáticos representados
por tais constantes.
Observações:
Como dito em aulas anteriores, duas constantes de sequência de caracteres sucessivas são transformadas numa só, de forma que os dois vetores abaixo têm exatamente o mesmo conteúdo:
char v[] = "Esta é uma " "linha." "\nEsta é outra.\n" "Esta mais outra."; char w[] = "Esta é uma linha.\nEsta é outra.\nEsta mais outra.";
Aqui está um exemplo de programa em que sequências de caracteres são passadas como argumentos para uma função e então manipuladas.
Exercícios:
Escreva uma função que receba (um ponteiro apontando para o início de)
uma sequência de caracteres e que retorne o tamanho da sequência
(que é o número de caracteres da sequência, não incluindo o \0
).
ATENÇÃO: existe e é válida a sequência vazia ""
,
que contém apenas o caractere \0
.
Escreva uma função void concatenar (char *a, char *b, char *c)
que escreve no vetor apontado por c
a concatenação (justaposição) das sequências de caracteres
apontadas por a
e b
.
Escreva uma função que inverta os caracteres de uma sequência de caracteres — transformando, por exemplo, "Casa" em "asaC".
Escreva uma função int eh_prefixo (char *r, char *s)
que retorna 1 se (a sequência de caracteres apontada por) r
é um prefixo de (aquela apontada por) s
,
e que retorna 0 em caso contrário.
Exemplo: "papa" é prefixo de "paparicar'', mas "papai" não o é.
Escreva uma função int eh_substr (char *r, char *s)
que responde se "r" é uma subsequência contígua de "s".
Em caso positivo, a função deve retornar o índice
do início da primeira ocorrência de "r" em "s";
em caso negativo, a função deve retornar -1
.
EXEMPLO: se "r" for "en" e "s" for "aprendendo",
então a resposta é 3, já que "en" ocorre duas vezes em "aprendendo",
uma vez começando no índice 3 e outra no índice 6 de "s".
DICA: use a função do exercício anterior para descobrir
se há uma ocorrência de "r" começando na posição 0 de "s";
caso não haja, faça o teste para a posição 1 de "s", e assim sucessivamente.
Escreva uma função que receba uma sequência de caracteres
e que retorne o número de linhas do texto armazenado na sequência;
a sua função deve pressupor que toda linha da sequência está terminada
por um \n
.
Escreva uma função que receba uma sequência de caracteres
e retorne o número de caracteres da maior linha do texto
armazenado na sequência (sem contabilizar o \n
que termina cada linha).
Escreva uma função int eh_substr_2 (char *r, char *s)
semelhante àquela do exercício acima,
mas que retorna não o índice da primeira ocorrência de "r" em "s",
mas sim o número da linha dessa ocorrência (numerando-se as linhas
de zero em diante).
Aula 37 (2014-06-03):
as funções getchar
e putchar
para a leitura/escrita de caracteres na tela;
exemplo de leitura e escrita de sequência de caracteres
caractere-a-caractere, sem verificação dos limites do vetor utilizado;
exemplo de leitura e escrita por meio de funções
e com verificação dos limites do vetor.
Observações:
seguem abaixo indicações de funções úteis da biblioteca padrão
da linguagem C que lidam com sequências de caracteres.
Exceto onde explicitado o contrário,
todas estão declaradas em stdio.h
,
e estão documentadas
aqui, por exemplo.
Veja aqui um exemplo de uso dessas funções.
O uso das funções fgets
,
fputs
e printf
para a manipulação de sequências de caracteres
pode ser cobrado em prova.
Assim como a função ler_string
escrita em sala,
a função fgets
lê uma linha com atenção ao limite máximo
de caracteres a serem escritos no vetor.
Entretanto, diferentemente da função escrita em sala,
o \n
também é escrito no vetor
(e o \0
também, depois do \n
).
Para os propósitos da nossa disciplina,
a forma de chamar a função é fgets(p,t,stdin)
,
sendo p
um ponteiro para o início do vetor a armazenar
a sequência, e t
o número máximo de caracteres
que a função pode escrever no vetor.
Fornecer stdin
como 3o argumento é a maneira de fazer a função
ler caracteres da tela (pois a função é geral e pode ler também de arquivos).
Uma tal chamada consome no máximo t-1
caracteres da tela,
pois um caractere do vetor é sempre reservado para o \0
.
Se p
aponta para o início de uma sequência de caracteres,
então as duas chamadas abaixo fazem a mesma coisa: imprimir a sequência
toda na tela, independentemente de quantas linhas estejam nela armazenadas.
Atente para o fato de que nenhuma dessas chamadas imprime um \n
adicional após os caracteres da sequência: apenas os caracteres da sequência
são impressos (embora a sequência possa ter, ela mesma, quebras de linha).
fputs(p,stdout); // stdout é o que conecta a escrita à tela printf("%s", p);
Há também funções que não serão cobradas em prova
mas que são bastante úteis e frequentemente utilizadas na prática,
como isdigit
, strtod
e strcmp
;
veja as declarações delas e o que elas fazem
aqui.
Exercícios:
Escreva um programa que lê um texto do usuário, da seguinte maneira: (1) o usuário digita um limite para o número de caracteres do texto; (2) o programa lê uma linha de texto; (3) o programa pergunta ao usuário se mais uma linha de texto deve ser digitada, e, se sim, deve retornar ao passo 2. Ao final da leitura do texto, o programa deve imprimir na tela todo o texto que foi digitado. ATENÇÃO: se certifique de que o texto lido não contém mais caracteres do que informado pelo usuário inicialmente.
Faça uma variação do programa anterior, na qual, inicialmente, o usuário informa também o número de linhas a serem digitadas, de forma que não é necessário perguntar, após a leitura de cada linha, se há mais linhas a serem digitadas.
Escreva um programa no qual:
(1) o usuário digita o número de linhas do texto a ser digitado;
(2) antes de digitar a 1a linha, o usuário informa um limite
para o número de caracteres da linha;
(3) após a leitura da 1a linha, o usuário informa um limite para o
número de caracteres da 2a linha, e então digita o conteúdo
da linha.
(4) etc, até que todas as linhas tenham sido lidas,
quando então o texto completo é impresso na tela.
ATENÇÃO: como o tamanho do texto completo não é conhecido de início,
mas sim apenas o número de linhas, use alocação dinâmica para
armazenar o texto: aloque um
vetor de ponteiros para char
(cada ponteiro para uma linha); cada ponteiro deve servir para
apontar para um vetor, que será alocado dinamicamente,
de acordo com o tamanho da linha correspondente.
NÃO ESQUEÇA de desalocar tudo ao fim do programa.
Escreva um programa que lê um texto do usuário (por praticidade, um limite para o número de caracteres do texto pode ser fornecido pelo usuário no início do programa), e que então imprime as linhas do texto na ordem contrária àquela em que foram digitadas (os caracteres de uma mesma linha devem ser impressos na ordem em que foram digitados, apenas a ordem das linhas é que deve ser invertida).
Escreva um programa que lê um texto do usuário e que então imprime na tela a linha mais longa do texto. O número de caracteres do texto não é conhecido, e nem o número de linhas, mas o usuário deve fornecer, antes de digitar o texto, um limite para o tamanho da maior linha.
Escreva um programa que: (1) lê um texto do usuário; (2) lê uma sequência de caracteres; (3) informa quantas vezes a sequência ocorre no texto. Limites para o tamanho do texto e da sequência de caracteres podem ser fornecidos pelo usuário no início do programa.
Aula 38 (2014-06-06 10h): prática de programação em laboratório (exercícios anteriores).
Aula 39 (2014-06-06 16h): motivação, introdução e exemplo simples do uso de estruturas em C; escrita, explicação e simulação da execução de programa mais elaborado, envolvendo cópia de estruturas, estruturas aninhadas, funções e ponteiros para estruturas.
Observações importantes:
Estruturas podem ser inicializadas de duas formas diferentes: uma forma inicializa os membros de uma estrutura na ordem em que eles foram declarados, a outra forma inicializa os membros em qualquer ordem, mas fornecendo-se o nome de cada membro inicializado (na primeira forma, omite-se os nomes dos membros, pois a ordem determina que membro está sendo inicializado). Em ambos os casos, se a estrutura é inicializada mas não são fornecidos inicializadores para todos os membros dela, então os membros que não foram inicializados explicitamente recebem valor zero. (Perceba que essa situação é diferente daquela em que não há inicialização alguma da estrutura, caso em que cada membro da estrutura é deixado não-inicializado, isto é, começa assumindo valor desconhecido – “lixo de memória”.) A sintaxe de ambas as formas de inicialização é exemplificada na observação 1 deste anexo (preparado por mim para disciplina de semestre anterior). Vale a pena ler também a breve observação 2 desse anexo, onde se mostra que existe também para vetores uma sintaxe de inicialização de elementos em qualquer ordem. Não é necessário ler as demais observações do anexo.
Como dito em sala, o acesso a estruturas por meio de um ponteiro é tão comum
que há em C uma notação especial para isso:
se p
é um ponteiro para uma estrutura que
possui um membro m
, então p->m
é, por definição, o mesmo que (*p).m
.
Exercícios:
Escreva um programa que lê do usuário os dados de uma circunferência e de um ponto em ℜ², e que então informa se o ponto está dentro, fora ou sobre a circunferência. Os dados da circunferência (centro e raio) e do ponto (coordenadas x e y) devem ser armazenados em estruturas definidas pelo seu programa.
Escreva uma função que recebe como argumentos duas datas, e que então retorna a diferença entre elas, também na forma de uma data (isto é, em anos, meses e dias). Em seguida, escreva um programa que lê a data de nascimento de uma pessoa e uma data posterior qualquer, e que então imprime na tela a idade da pessoa na data em questão (em termos de anos, meses e dias).
Escreva um programa que lê do usuário os dados de "n" alunos
(nome, data de nascimento, notas nas AP1, AP2, AP3),
e que então repetidamente informa ao usuário os dados de
um aluno qualquer, identificado pelo nome, como no exemplo abaixo.
O programa deve terminar quando o usuário digitar um nome não cadastrado.
DICA: nesse programa você vai precisar de uma função que responda se duas
sequências de caracteres são ou não iguais;
você pode escrever uma tal função por conta própria, ou então usar a função
strcmp de string.h
.
Digite o número de alunos: 3 Digite o nome do aluno 0: Fulano Digite a data de nascimento do aluno 0: 1/2/1993 Digite as notas do aluno 0: 9 8.1 9 Digite o nome do aluno 1: Beltrano ... Digite o nome do aluno 2: Cicrano ... Digite o nome do aluno: Beltrano Nascimento: 9/8/1997 Notas: 5, 6, 8. Digite o nome do aluno: Fulano Nascimento: 1/2/1993 Notas: 9, 8.1, 9. Digite o nome do aluno: João. Aluno não cadastrado, terminando o programa.