Baixe o app para aproveitar ainda mais
Prévia do material em texto
Capítulo 1 - Introdução 1. Identifique problemas de legibilidade e redigibilidade nas LPs que conhece. Verifique se existem casos nos quais essas propriedades são conflitantes. Problemas de Legibilidade C: Possui características que são fáceis de utilizar, mas que deixam o código ilegível. O desvio incondicional "goto" e o operador "*" que tanto pode significar a operação de multiplicação quanto operações de manipulação de ponteiros. Problemas de Redigibilidade Visual Basic: Não é possível declarar diversas variáveis de mesmo tipo especificando o tipo somente uma vez como em C++, por exemplo. Exemplos Conflitantes n1 < n2 ? printf("sim\n") : printf("nao\n"; if (n1 < n2) printf("sim") else printf("nao") O uso do operador ternário prioriza a redigibilidade em detrimento da legibilidade. Já no caso do if then else, a legibilidade é priorizada em detrimento da redigibilidade. 2. Identifique problemas de confiabilidade e eficiência nas LPs que conhece. Verifique se existem casos nos quais essas propriedades são conflitantes. Problemas de de Confiabilidade C: O uso de ponteiros permite acessar áreas de memórias não alocadas, ou seja, um programador inexperiente poderá ocasionar erros na sua máquina. Problemas de Eficiência JAVA: O Mecanismo de tratamento de exceções faz com que haja verificações de acesso a posições válidas em vetores em tempo de execução, assim reduzindo a eficiência. Exemplos Conflitantes Em JAVA existe a necessidade de se fazer um teste antes de qualquer acesso aos vetores. Em contrapartida, a linguagem C não faz esse tipo de exigência, logo, haverá um teste a menos nessa linguagem, portanto o código gerado será mais veloz. Nesse aspecto, JAVA prioriza a Confiabilidade em detrimento da Eficiência, no caso de C, a prioridade é na Eficiência em relação à Confiabilidade. 3. Identifique problemas de falta de ortogonalidade nas LPs que conhece. Esses problemas comprometem a facilidade de aprendizado na LP? JAVA: Os tipos de dados int e byte são do tipo inteiros, entretanto, a soma de um tipo byte e de um tipo int são feitas de formas diferentes. Isso não é intuitivo, logo, acaba por prejudicar o aprendizado do programador, pois ele só saberá que a combinação desses comandos não funcionará após o teste do mesmo. 4. Reusabilidade e modificabilidade muitas vezes contribuem para a melhoria uma da outra. Dê exemplos de situações nas quais isso ocorre. O número PI (π) é um número irracional que por muitas vezes são utilizadas as aproximações de 3,14 ou 3,1415. Em C, por exemplo, caso você queira que a variável pi fique com um valor inalterado durante todo o algoritmo, basta que você utilize o comando const, por exemplo: const pi = 3,14; Isso é um exemplo de reusabilidade e modificabilidade ao mesmo tempo. Reusabilidade porque você poderá utilizar essa variável em diversos trechos do programa e Modificabilidade, pois caso você deseje que irá utilizar o valor de pi com quatro casas decimais, você fará a alteração apenas naquela linha de código, não prejudicando outras partes do algoritmo na qual aquela variável foi utilizada. Outro exemplo de Reusabilidade e Modificabilidade é em relação às funções. Você poderá importá-las em outros programas ou reutilizá-las em diversos trechos do mesmo programa. E caso seja necessário outras implementações, você fará apenas naquele trecho de código. 5. Identifique situações nas quais a busca por eficiência computacional compromete a portabilidade de LPs e vice-versa. Quando o programador opta por uma linguagem compilada, o ganho de eficiência é alcançado, já que os erros já foram verificados. Porém, o código gerado após uma compilação não pode ser executado em diferentes arquiteturas. Já o oposto acontece quando se escolhe uma linguagem interpretada. A eficiência é sacrificada, visto que a verificação e tradução do código é feita em tempo de execução, mas a portabilidade é mantida. O interpretador traduz para a arquitetura em que o programa está sendo executado. 6. Uma LP sempre pode ser implementada usando tanto o método de compilação quanto o de interpretação? Em caso positivo, discuta se existem LPs que se ajustam melhor a um método de implementação do que a outro. Em caso negativo, apresente um exemplo de uma LP na qual só se pode utilizar um método de implementação e justifique. Sim, Compilação e Interpretação possuem a mesma finalidade que é a tradução do código fonte (linguagem de programação) para linguagem de máquina. Existe algumas diferenças entre elas, tais como eficiência e flexibilidade. Uma delas é que enquanto na Compilação o código só será executado após que toda a tradução seja feita. Na Interpretação na medida em que cada instrução (linha de código) for traduzida ela irá sendo executada, ou seja, pode ser que ocorra um erro no seu código apenas no final do mesmo, mas o resto será executado normalmente. Existem linguagens de programação que se ajustam melhor a um método do que outro. 7. Faça uma análise léxica, sintática e semântica das seguintes linhas de código C e descreva quais as conclusões obtidas: int a, i; int b = 2, c = 3; a = (b + c) * 2; i = 1 && 2 + 3 | 4; Análise léxica: int :: tipo de dado; a, i, b, c :: identificadores; 1, 2, 3, 4 :: números; “=”, “,”, “;”, “&&”, “|“, “(“, “)”, “+”, “*” :: símbolos. 8. Enumere e explique quais os principais fatores que influenciaram a evolução das LPs imperativas. - Arquitetura de Computadores: A forma de implementação dos computadores influencia o uso de variáveis como representação de células de memória e no uso de comandos como instruções. - Produtividade de Programação: Na medida que os softwares cresciam, a produtividade dos programadores tornou-se também um ponto a ser evoluído. Com isso, houve a incorporação de conceitos de programação estruturada, abstração de dados e orientação a objetos nas linguagens imperativas. - Eficiência de uso: Para que os recursos fossem melhores utilizados pelos usuários, foram incorporados conceitos de programação concorrente nas linguagens. 9. Induzir a legibilidade, confiabilidade e reuso de programas são algumas da propriedades desejáveis em Linguagens de Programação. Mostre, através de exemplos (um para cada propriedade) retirados de linguagens de programação conhecidas, como elas podem cumprir esses papéis e justifique os seus exemplos. Legibilidade: Em Python, diferentemente de C, caracteres especiais como & e | não possuem significado especial. São usados os comandos and e or (uma forma muito mais intuitiva). Confiabilidade: A declaração de variáveis em C permite ao programador a facilitação em encontrar erros, pois caso seja utilizada uma variável que não foi declarada o compilador irá identificar o erro. Reuso: Subprogramas facilitam a reusabilidade. A função print de Python é um subprograma extremamente reutilizável. Capítulo 2 - Amarrações 1. Liste pelo menos cinco diferentes tipos de amarrações que ocorrem no seguinte trecho de código C. float j = 3.2; j = j - 1.7; - A escolha do sinal "=" para denotar atribuição (tempo de projeto de linguagem) - A escolha do sinal "-" para denotar subtração (tempo de projeto de linguagem) - A associação de j ao tipo float (tempo de compilação) - A atribuição de 3.2 a variável j (tempo de execução) - A atribuição do resultado da expressão j - 1.7 a j (tempo de execução) 2. Especifique as regras de formação de identificadores de C, C++ e JAVA. Responda ainda se existem limites no número máximo de caracteres que podem ser usados e quais tipos de identificadores especiais são considerados. C, C++ e JAVA: São Case Sensitive C, C++ e JAVA: O primeiro caractere deve ser letra ou sublinhado (no caso de JAVA;acrescenta-se o "ou cifrão") C, C++ e JAVA: Os demais caracteres podem ser letras ou dígitos ou sublinhado (no caso de JAVA; acrescenta-se o "ou cifrão") C, C++ e JAVA: Podem ter qualquer número de caracteres (C: mas apenas os 31 primeiros são significativos, C++: todos significativos, JAVA: sem acréscimo) C, C++ e JAVA: C e C++ possuem vocábulos reservados (int, char, while) e vocábulos pré- definidos (C: printf, fopen, fclose; C++: new, delete). Java possui vocábulos reservados (true, boolean, try) e não possui vocábulos pré-definidos. 3. Considere o seguinte trecho de código em ADA (QUESTÃO IMPOSSÍVEL) procedure A is u : integer; procedure B is v : integer; procedure C is x : integer; procedure D is u : integer; begin null; end D; procedure E is v : integer; begin u := 7; end E; begin null; end C; procedure F is y : integer; procedure G is x : integer; begin null; end G; begin u := 10; end F; begin null; end B; begin null; end A; Identifique quais variáveis e subprogramas são visíveis em cada um dos subprogramas deste trecho de código. Suponha que novos requisitos do problema demandam que a variável u de D possa ser acessada por G. Quais modificações necessitariam ser feitas no programa? Cite erros que poderiam ocorrer em situações como essa. Variáveis: A: u(A) B: u(A) e v(B) C: u(A), v(B) e x(C) D: u(A), v(B), x(C) e u(D) (u(A) acessado através de referência seletiva A.u) E: u(A), v(B), x(C) e v(E) (v(B) acessado através de referência seletiva B.v) F: u(A), v(B) e y(F) G: u(A), v(B), y(F) e x(G) Subprogramas: A: B B: A, B, C e F C: A, B, C, D, E e F D: A, B, C, D, E e F E: A, B, C, D, E e F F: A, B, C, F e G G: A, B, C, F e G Para referenciar a variável u do subprograma D em G seria necessário definí-la em B. A variável u de D também passaria a ser visível pelos subprogramas B, C, E e F, o que pode ser indesejado. Isso poderia provocar erros de alteração indevida dessa variável nesses subprogramas. 4. Indique qual valor será escrito pelo trecho de código seguinte no caso da linguagem de programação utilizada adotar escopo estático e no caso dela adotar escopo dinâmico. procedimento sub() { inteiro x = 1; inteiro y = 1; procedimento sub1() { se (x = 1 & y = 1) então sub2(); senão sub3(); } procedimento sub2() { inteiro x = 2; y = 0; sub1(); } procedimento sub3() { escreva( x); } sub1(); } Cite e explique os problemas de legibilidade do trecho de código acima quando se adota o escopo estático e o escopo dinâmico. No escopo dinâmico, ao chamarmos a função sub3 depois de sub2 ser executada, a função fará referência ao x criado em sub2. Por isso imprimirá o valor 2. Já no escopo estático sub3 irá referenciar o valor de x em sub e imprimirá o valor 1. No escopo dinâmico temos que analisar a sequência em que os subprogramas são chamados para determinar o valor das variáveis que não são locais. É preciso mais cuidado e atenção por parte do programador ao manipular essas variáveis. O escopo estático é bem mais legível, pois não é necessário seguir a cadeia de chamadas das funções para conhecer o valor de uma variável não local, tornando o entendimento do código menos confuso. 5. Compare, em termos de legibilidade, as opções de C e C++ relativas à localização das definições e declarações nos programas. Em C, definições e declarações devem ser feitas globalmente ou no início de um bloco de código. Em C++, elas podem ser feitas globalmente ou em qualquer ponto de um bloco de código. Como variáveis globais possuem a mesma regra de localização, a comparação realizada aqui se refere às declarações e definições não globais. Enquanto a opção de C++ possibilidade a colocação das definições e declarações bem próximas do local de seu uso, ela eventualmente pode ser mais difícil de ser encontrada, uma vez que pode estar localizada em qualquer ponto do bloco de código antes de seu primeiro uso. Por sua vez, a opção de C fixa o local onde as declarações e definições podem ser encontradas, facilitando a busca. Contudo, esse local pode ser bem distante do ponto de uso, o que pode dificultar a leitura. 6. Identifique o problema que ocorre no seguinte trecho de código C. Exemplifique porque ele ocorre e indique como poderia ser resolvido. void circulo () { #define pi 3.14159 float raio = 3; float area = pi * raio * raio; float perimento = 2 * pi * raio; } void pressao () { float pi = 3.2, pf = 5.3; float variacao; variacao = pf - pi; } Quando o programa é compilado, o pré-processador encontra cada ocorrência da palavra pi, após o define, e a substitui por 3.14159. Isto causará erro na pressão. As palavras pi serão substituídas por uma constante, o que não deveria ter sido feito, pois a intenção era a criação de uma variável de nome pi (representando pressão interna). Para resolver este problema pode-se utilizar a definição "const" em vez de define. Uma definição através de const obedece as regras de escopo (a constante reconhecida do ponto de definição até o final do subprograma na qual foi definida). 7. Indique quais valores serão escritos pelo seguinte programa em C. Explique sua resposta e discuta a postura da linguagem em termos de ortogonalidade e de potencialidade para indução de erros de programação. int i; main () { printf("%d\n", i); f(); } void f() { int i; printf("%d\n", i); } A variável i foi definida como global. Automaticamente ela será inicializada com o valor 0 (zero). Portanto, na execução da função main, o primeiro valor a ser impresso é 0 (zero). Contudo, quando a função f é chamada, cria-se uma variável local i na memória. Variáveis locais em C não são inicializadas automaticamente. Assim, a impressão desta variável será o conteúdo corrente da área de memória aonde ela foi alocada. Isso caracteriza uma falta de ortogonalidade na linguagem, o que pode levar a erros de programação, visto que o programador pode achar que uma variável, seja global ou local, será sempre inicializada automaticamente com zero. 8. Uma declaração de função é um segmento de código contendo apenas a sua assinatura (isto é, um segmento de código com o cabeçalho da função, mas sem seu corpo). Apresente uma situação na qual a declaração de funções é útil (ou necessária) em C. Justifique sua resposta explicando para que o compilador utiliza a declaração. Uma situação na qual a declaração de funções é necessária ocorre quando se deseja compilar um arquivo que utiliza funções definidas em outro arquivo já compilado ou que será compilado posteriormente. Nesse caso, as declarações são necessárias para que o compilador possa verificar se as chamadas das funções são consistentes com os tipos requeridos em sua assinatura. Capítulo 3 - Valores e Tipos de Dados 1. Ponteiros são causadores potenciais de erros em programação. Dê exemplos, com trechos de código em C, de erros causados por ponteiros que provocam violação dos sistemas de tipos da linguagem, ocorrência de objetos pendentes e ocorrência de referências pendentes. 2. Uma diferença significativa entre a definição de tipos primitivos em C++ e JAVA se refere ao intervalo de valores de cada tipo. Enquanto em JAVA os intervalos foram fixados na definição da LP, em C++ é a implementação do compilador que define esses intervalos. Compare estas duas abordagens, justificando a opção de cada uma dessas linguagens. A linguagem C deixa para os implementadoresdos compiladores a definição dos intervalos dos tipos. Essa postura diminui a portabilidade dos programas, porém aumenta a sua eficiência. A eficiência é maior porque permite aos implementadores dos compiladores selecionar os intervalos de tipos de modo a utilizar melhor os recursos de hardware. Já na linguagem JAVA os intervalos foram fixados na definição da LP pois ela prioriza a portabilidade de seus programas em detrimento da eficiência de execução. Isso ocorre porque a definição dos intervalos de valores dos tipos no projeto da linguagem implica na necessidade de emulação em software das operações sobre esses tipos em alguns tipos de computadores. 3. Em geral, a verificação de uso de índice fora dos limites do vetor só pode ser verificado em tempo de execução. Algumas LPs, como JAVA, PASCAL e MODULA-2 fazem a verificação dinâmica dos índices. Outras, como C, C++ e FORTRAN não fazem essa verificação. Justifique porque algumas LPs adotaram uma postura e outras adotaram uma postura oposta. Uma terceira postura, intermediária, seria gerar código com verificação dinâmica na fase de desenvolvimento e sem verificação dinâmica para a fase de uso. Discuta essa opção em termos dos conceitos usados para justificar as opções das LPs mencionadas acima. A verificação dinâmica dos índices garante que a execução do programa não continuará normalmente caso haja um acesso indevido, conferindo-lhe maior confiabilidade. Por outro lado, a necessidade de testar em tempo de execução se todo acesso ao vetor é apropriado implica em uma perda de desempenho na execução do programa. Logo, as LPs que optam pela verificação dinâmica dos índices de vetores em tempo de execução, priorizam a confiabilidade do programa em detrimento do desempenho de execução. As LPs que não adotam essa postura, ou seja, que não verificam os índices de vetores dinamicamente, priorizam o desempenho dos programas em detrimento da confiabilidade. A postura intermediária garante uma eficiência equivalente a das LPs que não fazem verificação dinâmica dos índices e apresentam uma maior confiabilidade do que essas LPs, uma vez que erros de acesso poderão ser mais facilmente identificados na fase de desenvolvimento. Por outro lado, a postura intermediária garante um desempenho de execução dos programas superior ao das LPs que verificam os índices dinamicamente, mas os programas se tornam menos confiáveis do que nessas LPs, uma vez que alguns erros de acesso podem não ser identificados na fase de desenvolvimento. 4. Arrays podem ser estáticos, semi-estáticos, semi-dinâmicos e dinâmicos. Enquanto a criação de arrays estáticos e semi-estáticos pode ser feita facilmente em C, a construção de arrays semi-dinâmicos e dinâmicos envolve um maior esforço de programação. Responda como os mecanismos de C permitem a criação desses tipos de arrays. Ilustre com exemplos. 5. Produtos cartesianos, uniões, mapeamentos e tipos recursivos são categorias de tipos compostos de dados. Ilustre, com exemplos em C, cada um desses conceitos. Crie ainda um novo tipo de dados que combine três desses conceitos e diga qual a sua cardinalidade. Produto cartesiano struct produto{ int codigo; char nome[20]; float valor; }; União union temperatura{ int kelvin; float celsius; }; Mapeamento int v[10]; Tipo recursivo struct lista{ int info; struct lista* prox; } Combinação struct lista_produto{ int codigo; char nome[20]; union temperatura t ; }; Cardinalidade #int * (#char )20 * (#float + #int) 6. Determine a cardinalidade de cada um dos tipos abaixo, usando os conceitos de produto cartesiano, uniões e mapeamentos para explicar a cardinalidade dos tipos compostos: enum sexo {masculino, feminino}; enum estado_civil {solteiro, casado, divorciado}; enum classe {baixa, media, alta}; enum instrucao {primario, secundario, superior}; union cidadania { enum classe c; enum instrucao i; } struct pessoa { enum sexo s; enum estado_civil e; union cidadania c; }; struct amostra { int n; struct pessoa p[10]; } 7. Considere o seguinte programa escrito em C++: #include <iostream> int & xpto (int sinal) { int p = 4; if (!sinal) { p* = sinal; } else { p++; } return p; } void ypto () { int c[1000]; int aux; for (aux = 0; aux < 1000; aux++) { c[aux] = aux; } } main () { int a =; int& b = xpto (a); ypto(); cout << b; } Determine quais serão as saídas possíveis do programa acima. Explique sua resposta. 8. Considere o seguinte programa escrito em C: #include <stdio.h> int* calcula(int a) { int p; p = a; if (a) { p* = 3; } else { p++; }; return &p; } main() { int x = 1; int* b = calcula(x); int* c = calcula(0); printf("%d\n", *b); } Descreva o que ocorre nesse programa. Justifique sua resposta. 9. Listas heterogêneas são estruturas de dados capazes de armazenar no seu campo de informação valores de tipos distintos. Uma forma de implementar listas heterogêneas em C é através do uso de uniões. Outra forma é através do uso de ponteiros para void. Mostre, através de exemplos de código em C, como se pode fazer para definir listas heterogêneas usando essas duas abordagens (não é preciso implementar as operações de lista, apenas a definição da estrutura de dados). Compare e discuta essas soluções em termos de redigibilidade (das operações da lista) e flexibilidade (em termos de necessidade de recompilação do módulo lista quando for necessário alterar ou incluir um novo tipo de dado no campo informação). 10. Em C é possível criar estruturas de dados heterogêneas, isto é, com elementos de tipos diferentes. Contudo, ao se retirar um elemento da estrutura é necessário identificar quais operações são válidas sobre o elemento sendo removido de modo a evitar erros no sistema de tipos da LP. De quais maneiras isso pode ser feito? Compare as soluções propostas em termos de redigibilidade e em termos da necessidade de alteração do código usuário quando da alteração ou inclusão de um novo tipo de elemento na lista. 11. Caracterize a diferença entre uniões livres e uniões disjuntas em termos de cardinalidade e segurança quanto ao sistema de tipos da linguagem. Discuta e exemplifique como as uniões de C podem ser utilizadas para criar estrutura de dados heterogêneas (isto é, que abrigam tipos de informação distintos), destacando como o programador (ou a linguagem, se for o caso) deve proceder para garantir o uso desse tipo de dado sem nem que haja violações do sistema de tipos. Discuta ainda quão genérica pode ser uma estrutura de dados heterogênea que se baseia no mecanismo de uniões. 12. Muito embora JAVA seja fortemente influenciada por C, os projetistas dessa LP resolveram incluir o tipo boolean, o qual não existe em C. Explique porque essa decisão foi tomada. Dê exemplo de situação na qual a postura de C traz alguma vantagem. Faça o mesmo em relação a postura de JAVA. Justifique suas respostas. Capítulo 4 - Variáveis e Constantes 1. Sinonímia ocorre quando uma mesma variável ou constante pode ser referenciada por mais de um nome em um mesmo ambiente de amarração. Mostre exemplos de situações nas quais isso pode ocorrer em C e JAVA. Em C: int r = 0; int &s = r; s = 10; 2. Mostre situações nas quais a permissão de acesso ao endereço de variáveis pode ser benéfica ao programador. Mostre também quando isso pode ser prejudicial a confiabilidade dos programas. 3. Edite o programa seguinte, compile-o e o execute. Relate o que ocorreu na compilação e durante a execução. main () { char * z = "bola"; *z = 'c'; printf("%s\n", z); printf("bola"); } 4. Faça um programa com as linhas de código do exemplo 4.8, retirando oscomentários das linhas de código comentadas. Compile o programa usando seu compilador C e seu compilador C++. Relate o que aconteceu. 5. Explique as vantagens de se utilizar um modelo de gerenciamento de memória principal com regiões de pilha e monte em relação aos modelos que só utilizam a pilha ou só utilizam o monte ou que alocam variáveis apenas em tempo de carga do programa. As vantagens de se utilizar gerenciamento de memória principal com regiões de pilha e monte decorrem do fato de se poder beneficiar das melhores características de cada um desses modelos. A pilha tem por característica principal a alocação de espaço suficiente para armazenar as variáveis locais de um bloco ou subprograma quando estes são executados. No momento que o bloco ou subprograma é encerrado, essas variáveis são retiradas da pilha, liberando este espaço de memória. Já o monte resolve o problema do aumento dinâmico das variáveis, ou seja, caso haja necessidade de aumentar o espaço para uma variável já existente, esse espaço adicional será reservado no monte, mais especificamente em local onde exista espaço contíguo suficiente para alocar a nova variável. O problema de só utilizar um dos tipos (pilha ou monte) é que sozinhos eles não são muito eficientes. Ao usar apenas a pilha, teríamos problemas com variáveis nas quais o tamanho se modifica em tempo de execução. Em relação ao uso exclusivo do monte, a alocação não seria muito eficiente visto que a alocação dinâmica de memória no monte demanda o uso de estruturas de dados para gerenciamento da memória do programa, e por conseguinte a sua atualização a cada momento de alocação e desalocação de variáveis. Outra possibilidade seria alocar todas as variáveis em tempo de carga. Isso se torna ineficaz uma vez que frequentemente haverá desperdício ou insuficiência de memória. Além disso, essa abordagem impede a implementação de programas recursivos, pois só se sabe quantas vezes o mesmo será chamado durante cada execução do programa. 6. Enquanto implementações de linguagens de programação que incluem o conceito de ponteiros (por exemplo, C e C++) tipicamente parte da alocação e desalocação de memória sob a responsabilidade do programador, implementações de linguagens que não possuem ponteiros (por exemplo, JAVA) devem necessariamente incluir um sistema de gerenciamento de memória que controle e libere o espaço de memória utilizado. Compare estas diferentes abordagens em termos de eficiência, redigibilidade e confiabilidade. Eficiência As LPs que incluem ponteiros (e, consequentemente parte da alocação e desalocação de memória para o programa) são melhores que as que não possuem ponteiros. A explicação é que, como o programador assume a tarefa de alocação e desalocação, este pode fazê-lo nos momentos mais oportunos, nos exatos pontos em que as áreas de memória alocadas deixam de ser necessárias, e também possibilita ao programador desalocar a memória em pontos do programa que não interfiram no seu desempenho, problemas estes que podem ocorrer com o sistema de coletores de lixo. Redigibilidade A opção de não se utilizar ponteiros (como JAVA) deixa a programação mais fácil pois o programador não precisa se preocupar com os detalhes da programação com ponteiros. Mais especificamente, não é necessário qualquer esforço para desalocar memória alocada dinamicamente. Isso é executado automaticamente sem a interferência do programador, poupando-o de se preocupar com esses detalhes. Confiabilidade A programação com ponteiros é necessariamente menos confiável pois, como foi dito antes, a redigibilidade se reduz, o que aumenta a possibilidade de provocar erros por parte do programador, o qual tem de cuidar de detalhes que a programação com ponteiros exige. Um importante aspecto é que, na programação com ponteiros, o programador detém a tarefa de efetuar a desalocação dos espaços de memória dinâmicos, o que faz com que diversos erros possam ocorrer, tais como: esquecer de desalocar memória desnecessária, manutenção de referências para áreas já desalocadas, desalocação de áreas de memória ainda úteis. Todos esses erros não ocorrem com o coletor de lixo que atua nas LPs sem ponteiros pois estes processos são realizados sem a interferência do programador. 7. Identifique inicialmente a quais subprogramas pertencem as variáveis referenciadas nas linhas de código do programa do exemplo 4.9. Verifique se sua avaliação está correta seguindo a cadeia de links estáticas na figura 4.6. 8. Desenhe o estado da pilha de registros de ativação após cada chamada e encerramento de subprograma ao longo da execução do programa do exemplo 4.9. 9. Desenhe a pilha de registros de ativação, incluindo o endereço de retorno e as cadeias estática e dinâmica, quando a execução atinge o ponto marcado com # no seguinte esqueleto de programa ADA procedure Main is x: integer; procedure A; y: integer; procedure B (k: boolean); w: integer; begin -- B if k then B (false); else -- #; end B; procedure C; z: integer; begin -- C ... B (true); end C; begin -- A ... C; ... end A; begin -- Main ... A; end Main; 10. Em JAVA todos os objetos (variáveis compostas) são alocados no monte. Explique por que isso não é tão ineficiente em relação a LPs que alocam essas variáveis na pilha. 11. Explique como a persistência de dados é implementada em C. Compare essa abordagem com a de JAVA em termos de redigibilidade e legibilidade. Justifique sua resposta enfocando especialmente a implementação de persistência em estrutura de dados que utilizam ponteiros. Na sua opinião haveria algum efeito adverso em deixar sob o controle da linguagem de programação todo o processo de persistência de dados? 12. Considere um programa que armazena em um grafo (usando encadeamento dinâmico) as distâncias terrestres entre grandes cidades brasileiras e grava essa estrutura em memória secundária para recuperá-la posteriormente em uma outra variável. Compare como seriam os códigos das implementações em C e JAVA desse programa em termos de redigibilidade, legibilidade e confiabilidade. Capítulo 5 - Expressões e Comandos 1. Um programa deve ler uma sequência de números inteiros e imprimí-los. O programa deve ser interrompido quando o número lido for zero. Implemente três versões desse programa em C usando respectivamente os comandos iterativos com pré-teste, com pós-teste e um comando de escape. Discuta as soluções apresentadas em termos de redigibilidade e eficiência, indicando a melhor solução apresentada. 2. Descreva o que ocorre em cada trecho que culmina com impressões no seguinte programa em C, justificando suas afirmações. #include <stdio.h> main () { int a, b, c; b = c = 10; a = b++ + b++; printf("%d\n", a); printf("%dn", b); a = ++c + ++c; printf("%d\n", a); printf("%d\n", c); b = 10; a = b++ +b; printf("%d\n", a); printf("%d\n", b); a = 10; b = 5; if (a > b || ++b > 5) printf("%d\n", b); a = 1; b = 5; if (a > b || ++b > 5) printf("%d\n", b); } Esse programa em C é portável? O que ocorreria se um programa equivalente (isto é, usando classe e com o comando apropriado de saída) fosse implementado em JAVA? Justifique todas as suas afirmações. 3. Faça um programa em C (sem usar os escapes return ou exit) para ler um número inteiro positivo e calcular a tabuada de multiplicação de 0 a 9 de todos os números positivos inferiores ao número lido. Sempre que o resultado de uma multiplicação for múltiplo de dez, o programa deve perguntar ao usuário se ele deseja continuar com o cálculo da tabuada. Portanto, o programa deve se encerrar ao final do cálculo databuada ou quando o usuário responder que não deseja continuar o cálculo. A sua solução é a mais eficiente para esse problema? Porque? Como a solução mais eficiente seria implementada em JAVA? 4. Apresente um exemplo de expressão em C onde ocorre curto-circuito associado a efeito colateral. Analise o efeito que tal expressão pode produzir sobre a legibilidade de um programa. 5. O comando goto é empregado para realizar desvios incondicionais no fluxo de controle de programas. Com o advento das técnicas de programação estruturada, este comando foi muito criticado e, por muitas vezes, se sugeriu que linguagens de programação não deveriam incluí-lo entre seus comandos. C é uma linguagem que adota os princípios da programação estruturada. No entanto, C manteve o comando goto como um comando da linguagem. Qual a razão dessa decisão? Exemplifique, com um trecho de programa em C, uma situação na qual pode ser útil empregar o comando goto. Discuta como programadores da linguagem MODULA-2 e JAVA, que não incluem o goto entre seus comandos, lidam com esta situação. 6. Diga qual o valor das variáveis 'a' e 'n' após cada linha do seguinte trecho de código C. Justifique suas respostas. n = 3; a = -n ++; a = -n + 1; a = -n += 1; 7. Modifique o seguinte trecho de código para que ele realize a semântica sugerida pela sua disposição textual. if (x == 7) if (y == 1) z = 13; else z = 17; O trecho deve ser modificado para: if ( x == 7 ) { if ( y == 11) z = 13; } else z = 17; 8. Veja como funciona o exemplo 5.37. Execute-o passo a passo considerando que n tem valor 8 e os vetores 'a' e 'b' foram definidos da seguinte maneira: int[] a = {1, 3, 6, 9, 10, 12, 16, 18}; int[] b = {2, 4, 5, 7, 8, 10, 11, 15}; Reimplemente esse exemplo em C garantindo que ele funcione exatamente como em JAVA. 9. Implemente e teste o seguinte programa em C e descreva o que acontece. Justifique porque isso ocorre dessa maneira (isto é, apresente o racional da decisão tomada pelos projetistas ou implementadores dessa LP). void f() { int i = 10; entra; i++; } main () { f(); goto entra; } 10. Analise o seguinte programa em C, identificando o que ele faz. Faça uma crítica ao estilo de programação utilizado. main () { int i, j, k; k = 1; for (i = 0; i < 10; i++) { entra; j = 2 * i + 1; printf("i: %d, j: %d\n", i, j); } if (k) { k = 0; i = 7; goto entra; } } 11. Algumas LPs (tal como, C) consideram a operação de atribuição como sendo uma espécie de expressão (isto é, a atribuição é uma operação que retorna um valor). Dê exemplos de situações nas quais isso pode ser vantajoso. Diga também quando essa característica pode ser danosa para a qualidade da programação. Justifique sua resposta.
Compartilhar